From c4d209f493fd8839892af3fa1fe334d3e07fc08e Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 6 May 2026 09:17:11 -0400 Subject: [PATCH 1/3] Remove use of implicit optionality --- .../azure/versioning/previewVersion/main.tsp | 2 +- packages/typespec-azure-core/lib/operations.tsp | 11 +++-------- .../lib/extension/operations.tsp | 4 ++-- .../lib/interfaces.tsp | 3 +-- .../lib/legacy-types/extension-operations.tsp | 4 ++-- .../lib/legacy-types/extension.tsp | 4 ++-- .../lib/legacy-types/operations.tsp | 8 ++++---- .../lib/legacy-types/private-endpoints.tsp | 4 ++-- .../lib/operations.tsp | 16 ++++++---------- .../lib/private-endpoints.tsp | 4 ++-- 10 files changed, 25 insertions(+), 35 deletions(-) diff --git a/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp b/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp index 4af68514d2..d1c09cc8b9 100644 --- a/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp +++ b/packages/azure-http-specs/specs/azure/versioning/previewVersion/main.tsp @@ -105,7 +105,7 @@ model UpdateWidgetColorRequest { ``` """) @doc("Update widget color (preview only)") -@patch(#{ implicitOptionality: true }) +@patch @route("/widgets/{id}/color") @added(ApiVersions.v2024_12_01_preview) op updateWidgetColor( diff --git a/packages/typespec-azure-core/lib/operations.tsp b/packages/typespec-azure-core/lib/operations.tsp index 1244533695..09af9cf371 100644 --- a/packages/typespec-azure-core/lib/operations.tsp +++ b/packages/typespec-azure-core/lib/operations.tsp @@ -155,12 +155,10 @@ interface ResourceOperations< * @template Resource Resource type. * @template Traits Object describing the traits of the operation. */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" @Foundations.Private.ensureVerb("ResourceCreateOrUpdate", "PATCH") @createsOrUpdatesResource(Resource) @parameterVisibility(Lifecycle.Create, Lifecycle.Update) - @patch(#{ implicitOptionality: true }) // For legacy reasons + @patch ResourceCreateOrUpdate< Resource extends TypeSpec.Reflection.Model, Traits extends TypeSpec.Reflection.Model = {} @@ -193,12 +191,10 @@ interface ResourceOperations< * @template Resource Resource type. * @template Traits Object describing the traits of the operation. */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" @Foundations.Private.ensureVerb("LongRunningResourceCreateOrUpdate", "PATCH") @createsOrUpdatesResource(Resource) @parameterVisibility(Lifecycle.Create, Lifecycle.Update) - @patch(#{ implicitOptionality: true }) // For legacy reasons + @patch LongRunningResourceCreateOrUpdate< Resource extends TypeSpec.Reflection.Model, Traits extends TypeSpec.Reflection.Model = {} @@ -232,10 +228,9 @@ interface ResourceOperations< * @template Resource Resource type. * @template Traits Object describing the traits of the operation. */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" @Foundations.Private.ensureVerb("ResourceUpdate", "PATCH") @updatesResource(Resource) - @patch(#{ implicitOptionality: true }) // For legacy reasons + @patch ResourceUpdate< Resource extends TypeSpec.Reflection.Model, Traits extends TypeSpec.Reflection.Model = {} diff --git a/packages/typespec-azure-resource-manager/lib/extension/operations.tsp b/packages/typespec-azure-resource-manager/lib/extension/operations.tsp index 3501d79d8f..063a511eb0 100644 --- a/packages/typespec-azure-resource-manager/lib/extension/operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/extension/operations.tsp @@ -232,7 +232,7 @@ op CreateOrReplaceAsync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, @@ -272,7 +272,7 @@ op CustomPatchAsync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, diff --git a/packages/typespec-azure-resource-manager/lib/interfaces.tsp b/packages/typespec-azure-resource-manager/lib/interfaces.tsp index abb4be4578..1e7fb305b5 100644 --- a/packages/typespec-azure-resource-manager/lib/interfaces.tsp +++ b/packages/typespec-azure-resource-manager/lib/interfaces.tsp @@ -326,8 +326,7 @@ interface ResourceUpdateSync< * @template Properties RP-specific property bag for the resource * @template BaseParameters The http parameters that are part of the request */ - #suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" - @patch(#{ implicitOptionality: true }) + @patch update is ArmCustomPatchSync, BaseParameters>; } diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp index 6fb5ee3d3c..6c2ea98fb9 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/extension-operations.tsp @@ -150,7 +150,7 @@ op CreateOrReplaceSync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, @@ -194,7 +194,7 @@ op CustomPatchAsync< @extensionResourceOperation(TargetResource, ExtensionResource, "update", OverrideResourceName) @enforceConstraint(TargetResource, Foundations.Resource) @enforceConstraint(ExtensionResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< TargetResource extends Foundations.SimpleResource, ExtensionResource extends Foundations.SimpleResource, diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp index d458d236ed..7a060daa14 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/extension.tsp @@ -106,7 +106,7 @@ interface ExtensionOperations< @armOperationRoute(OperationOptions) @doc("Update a {name}", Resource) @legacyExtensionResourceOperation(Resource, "update", OverrideResourceName) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchAsync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, @@ -146,7 +146,7 @@ interface ExtensionOperations< @armOperationRoute(OperationOptions) @doc("Update a {name}", Resource) @legacyExtensionResourceOperation(Resource, "update", OverrideResourceName) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchSync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp index ae313bb716..d1291970ec 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/operations.tsp @@ -134,7 +134,7 @@ interface RoutedOperations< @armOperationRoute(OverrideRouteOptions) @legacyResourceOperation(Resource, "update", OverrideResourceName) @Private.armUpdateProviderNamespace - @patch(#{ implicitOptionality: false }) + @patch CustomPatchAsync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, @@ -175,7 +175,7 @@ interface RoutedOperations< @armOperationRoute(OverrideRouteOptions) @legacyResourceOperation(Resource, "update", OverrideResourceName) @Private.armUpdateProviderNamespace - @patch(#{ implicitOptionality: false }) + @patch CustomPatchSync< Resource extends Foundations.SimpleResource, PatchModel extends {} | void = Azure.ResourceManager.Foundations.TagsUpdateModel, @@ -465,7 +465,7 @@ model ProviderParameter { @doc("Update a {name}", Resource) @armResourceUpdate(Resource) @Private.enforceConstraint(Resource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< Resource extends Foundations.SimpleResource, PatchModel extends TypeSpec.Reflection.Model | void = TagsUpdateModel, @@ -505,7 +505,7 @@ op CustomPatchAsync< @doc("Update a {name}", Resource) @armResourceUpdate(Resource) @Private.enforceConstraint(Resource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< Resource extends Foundations.SimpleResource, PatchModel extends TypeSpec.Reflection.Model | void = TagsUpdateModel, diff --git a/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp b/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp index 5681219166..d592af14e6 100644 --- a/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp +++ b/packages/typespec-azure-resource-manager/lib/legacy-types/private-endpoints.tsp @@ -131,7 +131,7 @@ op CreateOrReplaceAsync< @doc("Update a {name} PrivateEndpointConnection", ParentResource) @armResourceUpdate(Resource) @Private.enforceConstraint(ParentResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchAsync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource, @@ -175,7 +175,7 @@ op CustomPatchAsync< @doc("Update a {name PrivateEndpointConnection}", ParentResource) @armResourceUpdate(Resource) @Private.enforceConstraint(ParentResource, Foundations.Resource) -@patch(#{ implicitOptionality: false }) +@patch op CustomPatchSync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource, diff --git a/packages/typespec-azure-resource-manager/lib/operations.tsp b/packages/typespec-azure-resource-manager/lib/operations.tsp index 7183b06d1c..2ca0790dc8 100644 --- a/packages/typespec-azure-resource-manager/lib/operations.tsp +++ b/packages/typespec-azure-resource-manager/lib/operations.tsp @@ -342,8 +342,7 @@ op ArmResourceCreateOrReplaceAsync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmTagsPatchAsync< Resource extends Foundations.SimpleResource, @@ -377,8 +376,7 @@ op ArmTagsPatchAsync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmResourcePatchAsync< Resource extends Foundations.SimpleResource, @@ -417,7 +415,7 @@ op ArmResourcePatchAsync< @autoRoute @doc("Update a {name}", Resource) @armResourceUpdate(Resource) -@patch(#{ implicitOptionality: false }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmCustomPatchAsync< Resource extends Foundations.SimpleResource, @@ -450,8 +448,7 @@ op ArmCustomPatchAsync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmTagsPatchSync< Resource extends Foundations.SimpleResource, @@ -476,8 +473,7 @@ op ArmTagsPatchSync< * @template Parameters Optional. Additional parameters after the path parameters * @template Provider Optional. The provider namespace model for the resource. */ -#suppress "@typespec/http/deprecated-implicit-optionality" "Legacy" -@patch(#{ implicitOptionality: true }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmResourcePatchSync< Resource extends Foundations.SimpleResource, @@ -508,7 +504,7 @@ op ArmResourcePatchSync< @autoRoute @doc("Update a {name}", Resource) @armResourceUpdate(Resource) -@patch(#{ implicitOptionality: false }) +@patch @Private.enforceConstraint(Resource, Foundations.Resource) op ArmCustomPatchSync< Resource extends Foundations.SimpleResource, diff --git a/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp b/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp index 932a7e9398..d7eafced3c 100644 --- a/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp +++ b/packages/typespec-azure-resource-manager/lib/private-endpoints.tsp @@ -244,7 +244,7 @@ interface PrivateEndpoints< @doc("Update a {name} PrivateEndpointConnection", ParentResource) @builtInResourceOperation(ParentResource, Resource, "update", OverrideResourceName) @Private.enforceConstraint(ParentResource, Foundations.Resource) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchAsync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource = PrivateEndpointResource, @@ -287,7 +287,7 @@ interface PrivateEndpoints< @doc("Update a {name PrivateEndpointConnection}", ParentResource) @builtInResourceOperation(ParentResource, Resource, "update", OverrideResourceName) @Private.enforceConstraint(ParentResource, Foundations.Resource) - @patch(#{ implicitOptionality: false }) + @patch CustomPatchSync< ParentResource extends Foundations.SimpleResource, Resource extends PrivateEndpointConnectionResource = PrivateEndpointResource, From 97d2f9b791d829ca596a36157863d428eb73da4f Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 6 May 2026 09:20:22 -0400 Subject: [PATCH 2/3] add chg --- ...mplicit-optionality-arm-2026-5-6-9-17-0.md | 41 +++++++++++++++++++ ...-optionality-azure-core-2026-5-6-9-17-0.md | 37 +++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 .chronus/changes/remove-implicit-optionality-arm-2026-5-6-9-17-0.md create mode 100644 .chronus/changes/remove-implicit-optionality-azure-core-2026-5-6-9-17-0.md diff --git a/.chronus/changes/remove-implicit-optionality-arm-2026-5-6-9-17-0.md b/.chronus/changes/remove-implicit-optionality-arm-2026-5-6-9-17-0.md new file mode 100644 index 0000000000..f3361a2e4c --- /dev/null +++ b/.chronus/changes/remove-implicit-optionality-arm-2026-5-6-9-17-0.md @@ -0,0 +1,41 @@ +--- +changeKind: breaking +packages: + - "@azure-tools/typespec-azure-resource-manager" +--- + +Removed `implicitOptionality: true` from the `@patch` decorator on the following operation templates: + +- `ArmTagsPatchAsync` +- `ArmResourcePatchAsync` +- `ArmTagsPatchSync` +- `ArmResourcePatchSync` +- `ResourceUpdateSync.update` (in `ResourceUpdateSync` interface) + +Previously, these templates used `@patch(#{ implicitOptionality: true })` which caused all properties in the PATCH request body to be implicitly treated as optional, regardless of how they were declared in the model. This behavior is now removed — properties will retain their declared optionality. + +#### Migration + +If you use these ARM operation templates, the PATCH body will no longer implicitly make all properties optional. In most cases, ARM resources already use `ResourceUpdateModel` which produces the correct optional property envelope — if so, **no changes are needed**. + +If you have a custom patch body that relied on implicit optionality, explicitly mark properties as optional: + +**Before** (implicit optionality made all properties optional automatically): + +```tsp +model MyResourceProperties { + displayName: string; + config: MyConfig; +} +``` + +**After** (explicitly declare optional properties for update): + +```tsp +model MyResourceUpdateProperties { + displayName?: string; + config?: MyConfig; +} +``` + +Or continue using `ResourceUpdateModel` which already handles this transformation for standard ARM resource patterns. diff --git a/.chronus/changes/remove-implicit-optionality-azure-core-2026-5-6-9-17-0.md b/.chronus/changes/remove-implicit-optionality-azure-core-2026-5-6-9-17-0.md new file mode 100644 index 0000000000..b50a0d974d --- /dev/null +++ b/.chronus/changes/remove-implicit-optionality-azure-core-2026-5-6-9-17-0.md @@ -0,0 +1,37 @@ +--- +changeKind: breaking +packages: + - "@azure-tools/typespec-azure-core" +--- + +Removed `implicitOptionality: true` from the `@patch` decorator on the following operation templates: + +- `ResourceCreateOrUpdate` +- `LongRunningResourceCreateOrUpdate` +- `ResourceUpdate` + +Previously, these templates used `@patch(#{ implicitOptionality: true })` which caused all properties in the PATCH request body to be implicitly treated as optional, regardless of how they were declared in the model. This behavior is now removed — properties will retain their declared optionality. + +#### Migration + +If your service relies on all properties being optional in the PATCH body, you need to explicitly mark them as optional in your model: + +**Before** (implicit optionality made all properties optional automatically): + +```tsp +model Widget { + name: string; + color: string; +} +``` + +**After** (explicitly declare optional properties): + +```tsp +model WidgetUpdate { + name?: string; + color?: string; +} +``` + +Alternatively, you can use the `ResourceUpdateModel` template or `OptionalProperties` utility to derive an all-optional version of your model for PATCH operations. If you were already using `ResourceUpdateModel` or manually defining optional properties in your update model, no changes are needed. From d560338099243d137d7e8c67057f7cd4e68f1ff0 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Wed, 6 May 2026 09:23:46 -0400 Subject: [PATCH 3/3] Create fix-remove-implicit-optionality-2026-4-6-13-22-52.md --- .../fix-remove-implicit-optionality-2026-4-6-13-22-52.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/fix-remove-implicit-optionality-2026-4-6-13-22-52.md diff --git a/.chronus/changes/fix-remove-implicit-optionality-2026-4-6-13-22-52.md b/.chronus/changes/fix-remove-implicit-optionality-2026-4-6-13-22-52.md new file mode 100644 index 0000000000..ea29528314 --- /dev/null +++ b/.chronus/changes/fix-remove-implicit-optionality-2026-4-6-13-22-52.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: internal +packages: + - "@azure-tools/azure-http-specs" +--- + +Remove use of implicit optionality which was a noop in one operation