Skip to content

Centralize C# type attribute back-compat merging in TypeProvider#11237

Draft
jorgerangel-msft with Copilot wants to merge 32 commits into
mainfrom
copilot/centralize-type-attribute-merging
Draft

Centralize C# type attribute back-compat merging in TypeProvider#11237
jorgerangel-msft with Copilot wants to merge 32 commits into
mainfrom
copilot/centralize-type-attribute-merging

Conversation

Copilot AI commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Attribute back-compat merging (generated + last-contract + custom-code) was owned locally by CanonicalTypeProvider.BuildAttributes(). This moves that logic into a shared TypeProvider API so it follows the existing centralized back-compat pattern (Build*ForBackCompatibility).

Changes

  • TypeProvider: Added BuildAttributesForBackCompatibility() that:
    • restores compatible attributes from the last contract
    • merges generated (Attributes) and custom-code (CustomCodeView.Attributes) attributes,
    • filters CodeGen-specific attributes (CodeGenSuppress/CodeGenMember/CodeGenType/CodeGenSerialization) that should not be preserved,
    • deduplicates by rendered display string.
  • CanonicalTypeProvider: BuildAttributes() now delegates to the shared API instead of owning merge/dedup logic.
  • Tests: Added coverage for the merge/dedup behavior.

Because the method reads Attributes, CustomCodeView, LastContractView, and DeclarationModifiers from the provider, invoking it on the generated provider reproduces the previous merge exactly:

protected override IReadOnlyList<MethodBodyStatement> BuildAttributes()
    => _generatedTypeProvider.BuildAttributesForBackCompatibility();

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp label Jul 13, 2026
Copilot AI and others added 2 commits July 13, 2026 20:37
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Copilot AI changed the title [WIP] Centralize type attribute back-compat merging in C# generator Centralize C# type attribute back-compat merging in TypeProvider Jul 13, 2026
Copilot AI requested a review from jorgerangel-msft July 13, 2026 20:39
Copilot AI and others added 2 commits July 13, 2026 21:03
… attributes, perf

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
@pkg-pr-new

pkg-pr-new Bot commented Jul 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-csharp@11237

commit: 68a9a9b

…p internalize gating, add tests

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
…tions

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
…pe-attribute-merging

# Conflicts:
#	packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs
#	packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/TestHelpers/MockHelpers.cs

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Centralizes attribute back-compat handling in the C# generator’s TypeProvider so attribute restoration/merging from the last contract happens through the same back-compat pipeline as other members, and adds targeted ClientModel filtering for generator-owned attributes that must be recomputed.

Changes:

  • Added TypeProvider.BuildAttributesForBackCompatibility(...) and wired attribute restoration into ProcessTypeForBackCompatibility().
  • Added ClientModel-specific filtering to drop recomputed generator-owned attributes (e.g., proxy/buildable attributes) after restoration.
  • Added/expanded unit tests + golden test data for restore/skip/dedup scenarios.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/TestHelpers/TestTypeProvider.cs Extends test provider to supply attributes for back-compat tests.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TypeProviderTests.cs Adds unit tests covering attribute back-compat restore/skip/dedup behavior.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsExperimentalAttribute/BackCompatAttributeType.cs Last-contract input for Experimental attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsExperimentalAttribute.cs Expected generated output for Experimental attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsEditorBrowsableAttribute/BackCompatAttributeType.cs Last-contract input for EditorBrowsable attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsEditorBrowsableAttribute.cs Expected generated output for EditorBrowsable attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsDefaultMemberAttribute/BackCompatAttributeType.cs Last-contract input for DefaultMember attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsDefaultMemberAttribute.cs Expected generated output for DefaultMember attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsCodeGenAttributes/BackCompatAttributeType.cs Last-contract input for CodeGen attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsCodeGenAttributes.cs Expected generated output for CodeGen attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsAttributesAlreadyInCustomCode/BackCompatAttributeType.cs Last-contract input where an attribute exists in custom code already.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsAttributesAlreadyInCustomCode(Custom)/BackCompatAttributeType.cs Custom-code input for “already in custom code” scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsAttributesAlreadyInCustomCode.cs Expected generated output for “already in custom code” scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsAllCodeGenPrefixedAttributes/BackCompatAttributeType.cs Last-contract input for CodeGen-prefixed attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilitySkipsAllCodeGenPrefixedAttributes.cs Expected generated output for CodeGen-prefixed attribute skip scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilityReturnsGeneratedAttributesWhenNoLastContract.cs Expected output when no last contract is present.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilityDoesNotDuplicateExistingAttribute/BackCompatAttributeType.cs Last-contract input for duplicate-prevention scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilityDoesNotDuplicateExistingAttribute.cs Expected output for duplicate-prevention scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilityAddsAttributeFromLastContract/BackCompatAttributeType.cs Last-contract input for add-new-attribute scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/TestData/TypeProviderTests/BuildAttributesForBackCompatibilityAddsAttributeFromLastContract.cs Expected output for add-new-attribute scenario.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/SourceInput/CodeGenAttributes.cs Adds shared CodeGen prefix constant for filtering.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/TypeProvider.cs Implements attribute back-compat restore logic and integrates it into processing/update.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/TestHelpers/MockHelpers.cs Extends generator mock setup to allow injecting serialization providers in tests.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/TestData/ScmModelProviderTests/BuildAttributesForBackCompatibilityDoesNotRestoreProxyAttribute/Pet.cs Last-contract input for proxy-attribute filtering in ScmModelProvider.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/TestData/ScmModelProviderTests/BuildAttributesForBackCompatibilityDoesNotRestoreProxyAttribute.cs Expected output verifying proxy attribute is not restored.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/ScmModelProvider/ScmModelProviderTests.cs Adds test ensuring proxy attributes aren’t restored for models.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/BackCompatibilityTests/BuildAttributesForBackCompatibilityDoesNotRestoreProxyAttribute/Pet.cs Last-contract input for proxy-attribute filtering in MRW serialization provider.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/BackCompatibilityTests/BuildAttributesForBackCompatibilityDoesNotRestoreProxyAttribute.cs Expected output verifying proxy attribute is not restored in MRW serialization provider.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/BackCompatibilityTests.cs Adds MRW serialization-provider back-compat attribute filtering test.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDoesNotRestoreBuildableAttribute/SampleContext.cs Last-contract input for buildable-attribute filtering in MRW context definition.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/TestData/ModelReaderWriterContextDefinitionTests/BuildAttributesForBackCompatibilityDoesNotRestoreBuildableAttribute.cs Expected output verifying buildable attribute is not restored.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/Definitions/ModelReaderWriterContextDefinitionTests.cs Adds test ensuring buildable attributes aren’t restored for MRW context definition.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Utilities/ScmBackCompatibilityHelpers.cs Adds helper to remove newly-restored generator-owned attributes.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ScmModelProvider.cs Overrides attribute back-compat to drop restored proxy attribute for models.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs Overrides attribute back-compat to drop restored proxy attribute for serialization providers.
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/ModelReaderWriterContextDefinition.cs Overrides attribute back-compat to drop restored buildable attribute for contexts.

@jorgerangel-msft

Copy link
Copy Markdown
Contributor

@copilot there was a crash with our changes:

It looks like there's also a critical bug in our code when regenerating one of the azure libraries:

Sync and generate from a TypeSpec project

  

  Options:

    -d, --debug            Enable debug logging                          [boolean]

    -o, --output-dir       Specify an alternate output directory for the generated

                           files.                          [string] [default: "."]

    -y, --no-prompt        Skip any interactive prompts.                 [boolean]

        --help             Show help                                     [boolean]

        --repo             Repository where the project is defined        [string]

        --commit           Commit hash to be used                         [string]

        --tsp-config       Path to tspconfig.yaml                         [string]

        --local-spec-repo  Path to local spec repo                        [string]

        --emitter-options  The options to pass to the emitter             [string]

        --save-inputs      Don't clean up the temp directory after generation

                                                                         [boolean]

        --skip-install     Skip installing dependencies                  [boolean]

        --trace            Enable tracing during compile                   [array]

    -v, --version          Show version number                           [boolean]

  

  ExternalError: Emitter "@azure-typespec/http-client-csharp" crashed! This is a bug.

  Please file an issue at [https://github.com/azure-sdk/azure-sdk-for-net/issues](https://github.com/azure-sdk/azure-sdk-for-net/issues)

  

Error : error : Failed to generate the library. Exit code: 1. [/mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/src/Azure.Data.AppConfiguration.csproj]

  StackTrace: 

  The method or operation is not implemented.

     at Microsoft.TypeSpec.Generator.Expressions.LiteralExpression.Write(CodeWriter writer)

     at Microsoft.TypeSpec.Generator.Statements.AttributeStatement.Write(CodeWriter writer)

     at Microsoft.TypeSpec.Generator.Statements.MethodBodyStatement.GetDebuggerDisplay()

     at Microsoft.TypeSpec.Generator.Statements.MethodBodyStatement.ToDisplayString()

     at Microsoft.TypeSpec.Generator.Providers.TypeProvider.BuildAttributesForBackCompatibility(IEnumerable`1 originalAttributes)

     at Microsoft.TypeSpec.Generator.Providers.TypeProvider.ProcessTypeForBackCompatibility()

     at Microsoft.TypeSpec.Generator.CSharpGen.ExecuteAsync()

     at Microsoft.TypeSpec.Generator.GeneratorRunner.RunAsync(CommandLineOptions options)

     at Microsoft.TypeSpec.Generator.Program.Run(CommandLineOptions options, GeneratorRunner runner)

  

      at generate (file:///mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/TempTypeSpecFiles/node_modules/@typespec/http-client-csharp/dist/emitter/src/emit-generate.js:69:19)

      at process.processTicksAndRejections (node:internal/process/task_queues:103:5)

      at async emitCodeModel (file:///mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/TempTypeSpecFiles/node_modules/@typespec/http-client-csharp/dist/emitter/src/emitter.js:70:17)

      at async emitAzureCodeModel (file:///mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/TempTypeSpecFiles/node_modules/@azure-typespec/http-client-csharp/dist/emitter/src/emitter.js:48:12)

      at async Object.$onEmit [as emitFunction] (file:///mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/TempTypeSpecFiles/node_modules/@azure-typespec/http-client-csharp/dist/emitter/src/emitter.js:8:29)

      at async runEmitter (file:///mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/TempTypeSpecFiles/node_modules/@typespec/compiler/dist/src/core/program.js:684:9)

      at async file:///mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/TempTypeSpecFiles/node_modules/@typespec/compiler/dist/src/core/program.js:655:37

      at async emit (file:///mnt/vss/_work/_temp/azure-sdk-for-net-20260717-165539/sdk/appconfiguration/Azure.Data.AppConfiguration/TempTypeSpecFiles/node_modules/@typespec/compiler/dist/src/core/program.js:653:12)

Copilot AI and others added 2 commits July 17, 2026 19:29
…butes with integral literal args

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
…ic exception types

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>

Copilot AI commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@copilot there was a crash with our changes:

...

Fixed. Root cause: back-compat restoration renders each last-contract attribute via ToDisplayString() to build the dedup key, and LiteralExpression.Write threw NotImplementedException for integral literal kinds it didn't handle. Azure.Data.AppConfiguration has a last-contract attribute with an integral (e.g. byte/uint) argument, which hit that gap.

Two changes:

  • LiteralExpression.Write now handles byte/sbyte/short/ushort/uint/ulong, so such attributes render correctly and are restored.
  • BuildAttributesForBackCompatibility is now resilient: if an attribute still fails to render (e.g. an unresolved type or unsupported argument kind), it's skipped rather than crashing the whole emitter.

Added unit tests: AttributeStatementWithIntegralArguments and BuildAttributesForBackCompatibilityRestoresAttributeWithIntegralLiteralArguments.

…tected types

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
…ibute display string

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>

@jorgerangel-msft jorgerangel-msft left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot I'd like to see some unit tests where a type has attributes in back compat, custom code, and current generated source. We should validate there are no duplicates and things work as expected

…; drop comment

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>

Copilot AI commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@copilot I'd like to see some unit tests where a type has attributes in back compat, custom code, and current generated source. We should validate there are no duplicates and things work as expected

Added BuildAttributesForBackCompatibilityDeduplicatesAcrossGeneratedCustomAndLastContract. The type has [Obsolete("This is obsolete")] in generated source, [CLSCompliant(true)] in custom code, and the last contract declares all three of [Obsolete("This is obsolete")], [CLSCompliant(true)], and [Restorable]. The test asserts the merged result restores only [Restorable] (no duplicate Obsolete/CLSCompliant), validated via TestData.

Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 4 comments.

Comment on lines +995 to +999
if (!DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public)
&& !DeclarationModifiers.HasFlag(TypeSignatureModifiers.Protected))
{
return original;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot update the description to be accurate

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The accurate wording is: attribute restoration only applies to externally visible types (public / protected), while generated and custom-code attributes are still merged and generator-owned / CodeGen attributes are skipped. I can’t edit the PR body directly from this environment, but that is the text that should replace the old “non-public types only” wording.

Copilot AI commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@copilot analyze the regen pr Azure/azure-sdk-for-net#6108 for additions in attributes on types. Give me a summary of any attributes that were added that we didn't explicitly f...

I checked the link, but it currently redirects to Azure/azure-sdk-for-net issue #6108 (Review Authentication Support Against Guidelines) rather than a PR/files diff, so I can’t reliably enumerate attribute additions from that URL. Based on our current filters, a type-level attribute would still be restored unless it either contains CodeGen, is in TypeProvider’s non-restorable set (EditorBrowsable, Experimental, Obsolete, Serializable, DefaultMember, and the filtered System.Text.Json.Serialization attributes), or is one of the SCM-owned attributes filtered by the ClientModel providers. If you send the correct regen PR link or the relevant diff, I can cross-check it and list the exact unfiltered additions.

@jorgerangel-msft

Copy link
Copy Markdown
Contributor

@copilot address this comment #11237 (comment)

Copilot AI commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address this comment #11237 (comment)

The referenced thread is about the PR description, not the implementation. The correct wording is: attribute restoration only applies to externally visible types (public / protected), while generated and custom-code attributes are still merged and generator-owned / CodeGen attributes are skipped. I can’t edit the PR body from this environment, so that text still needs to be applied in the description itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:csharp Issue for the C# client emitter: @typespec/http-client-csharp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider adding attributes to the list of supported backcompat scenarios

3 participants