Skip to content

[http-client-csharp] Custom-code attribute providers need supported opt-out from source input views #10993

@ArcturusZhang

Description

@ArcturusZhang

Problem

#10981 added CodeModelGenerator.AddCustomCodeAttributeProvider(TypeProvider provider), which lets derived C# generators register generator-specific custom-code attributes without mutating CustomCodeAttributeProviders via reflection. Azure management has started using this for a management-specific attribute provider (CodeGenResourceDataAttributeDefinition) in Azure/azure-sdk-for-net#59851.

However, there is still a root-cause gap when the contributed provider is a normal TypeProvider outside the Microsoft.TypeSpec.Generator assembly.

Real generation path

In CSharpGen.ExecuteAsync, the generator adds custom-code attribute providers to the custom-code workspace before SourceInputModel is initialized:

`csharp
GeneratedCodeWorkspace customCodeWorkspace = await GeneratedCodeWorkspace.Create(isCustomCodeProject: true);

foreach (var attributeProvider in CodeModelGenerator.Instance.CustomCodeAttributeProviders)
{
generateAttributeTasks.Add(customCodeWorkspace.AddInMemoryFile(attributeProvider));
}

await Task.WhenAll(generateAttributeTasks);

CodeModelGenerator.Instance.SourceInputModel = new SourceInputModel(
await customCodeWorkspace.GetCompilationAsync(),
await GeneratedCodeWorkspace.LoadBaselineContract());
`

AddInMemoryFile(attributeProvider) writes the provider. For a normal TypeProvider, this can touch the provider's lazy CustomCodeView / LastContractView. Since SourceInputModel has not been initialized yet, generation crashes with:

ext SourceInputModel has not been initialized yet at Microsoft.TypeSpec.Generator.CodeModelGenerator.get_SourceInputModel() at Microsoft.TypeSpec.Generator.Providers.TypeProvider.BuildCustomCodeView(...) at Microsoft.TypeSpec.Generator.Providers.TypeProvider.get_CustomCodeView() at Microsoft.TypeSpec.Generator.Providers.TypeProvider.get_Type() at Microsoft.TypeSpec.Generator.Providers.TypeProvider.get_Name() at Microsoft.TypeSpec.Generator.GeneratedCodeWorkspace.AddInMemoryFile(TypeProvider type)

Why built-in CodeGen attributes work

Built-in providers such as CodeGenTypeAttributeDefinition avoid this by overriding:

csharp private protected sealed override NamedTypeSymbolProvider? BuildCustomCodeView(...) => null; private protected sealed override NamedTypeSymbolProvider? BuildLastContractView(...) => null;

That is the right behavior for generated attribute definitions: the attribute definition itself should not participate in source-input customization lookup.

But these methods are private protected, so a derived generator in another assembly (for example Azure management) cannot override them.

Base test does not catch this path

The current base test for the new extension point manually adds the provider syntax tree into a mock compilation:

csharp return compilation.AddSyntaxTrees(GeneratedCodeWorkspace.GetTree(customAttributeProvider));

That happens in a test helper path where SourceInputModel is already mocked/available, so it does not reproduce the real CSharpGen.ExecuteAsync ordering above.

Current downstream workaround

Azure management can use the new AddCustomCodeAttributeProvider(...) API to avoid reflection for provider registration, but it still needs a workaround to suppress source-input views on its contributed attribute provider. The downstream provider currently has to mutate TypeProvider private lazy fields via reflection so CustomCodeView and LastContractView return null.

Request

Please provide a supported way for contributed custom-code attribute providers to opt out of source-input views, or adjust the custom-code attribute provider writing path so it does not evaluate CustomCodeView / LastContractView before SourceInputModel is initialized.

Possible options:

  1. Make BuildCustomCodeView / BuildLastContractView overridable by derived generator assemblies (protected internal or a dedicated protected opt-out property/method).
  2. Add a base CustomCodeAttributeDefinition provider type that derived generators can inherit from, with source-input views disabled.
  3. Update GeneratedCodeWorkspace.AddInMemoryFile or the custom-code attribute provider path to write attribute providers without evaluating customization views.
  4. Extend AddCustomCodeAttributeProvider to wrap/mark providers as attribute definitions that should not resolve source-input views.

Related downstream PR

Azure SDK for .NET PR: Azure/azure-sdk-for-net#59851

The first extension point from #10981 helps, but this remaining issue prevents downstream generators from fully removing reflection workarounds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    emitter:client:csharpIssue for the C# client emitter: @typespec/http-client-csharpfeatureNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions