Describe the bug
Summary
When custom code references a generated type that is not also declared in the customization compilation (no [CodeGenType] stub / partial), the generator emits the type with an empty namespace, e.g.:
IDictionary<string, global::.ToolConfig> toolConfigs = default
This produces uncompilable output. Observed in Azure.AI.Extensions.OpenAI for ToolConfig and the enum ResponsesToolSearchExecutionType , surfaced in the generated ExtensionsOpenAIModelFactory .
Root cause
• The customization Roslyn compilation ( SourceInputModel.Customization ) is built from custom files only; generated sources are not part of it.
• Generated types referenced by custom code normally resolve because the library declares a stub (e.g. [CodeGenType("BingGroundingSearchToolParameters")] ). Types without a stub resolve to Roslyn error type symbols.
• TypeSymbolExtensions.GetCSharpType → ConstructCSharpTypeFromSymbol builds a CSharpType from the error symbol. The fully-qualified display string of an unresolved symbol carries no namespace, so:
// TypeSymbolExtensions.cs (~line 204)
string[] pieces = fullyQualifiedName.Split('`')[0].Split('.'); // ["ToolConfig"]
string ns = string.Join('.', pieces.Take(pieces.Length - 1)); // "" (empty)
• The empty namespace flows into a real type used by ModelFactoryProvider (the factory derives parameter types from the model's full constructor, which here lives in custom code), producing global::.ToolConfig .
Why it only shows in the model factory
The generated model itself is in the same namespace, so the writer emits the short name ToolConfig and the empty namespace is invisible. The model factory (with using OpenAI.Responses; ) qualifies the type, exposing the empty namespace.
Affected code
Microsoft.TypeSpec.Generator/src/Utilities/TypeSymbolExtensions.cs — ConstructCSharpTypeFromSymbol (namespace computation for error-type symbols).
Suggested fix
When the symbol is an unresolved error type ( TypeKind.Error ) and the computed namespace is empty, fall back to the output library by name via CodeModelGenerator.Instance.TypeFactory.TypeProvidersByName (already maintained, TypeFactory.cs:29 ) to recover the real CSharpType /namespace. This is a best-effort fallback that triggers only for otherwise-broken symbols, so resolved types are unaffected, and it fixes the class of bug for any library — not just those that add [CodeGenType] stubs.
Current workaround
Add a stub for each affected type in custom code, e.g. [CodeGenType("ToolConfig")] public partial class ToolConfig { } .
Reproduction
Repro
- Generated type Foo exists (no [CodeGenType] stub in custom code).
- A custom-code constructor parameter references Foo (e.g. IDictionary<string, Foo> ).
- That constructor is the full constructor used by the model factory.
- Generated factory contains global::.Foo .
Checklist
Describe the bug
Summary
When custom code references a generated type that is not also declared in the customization compilation (no [CodeGenType] stub / partial), the generator emits the type with an empty namespace, e.g.:
IDictionary<string, global::.ToolConfig> toolConfigs = default
This produces uncompilable output. Observed in Azure.AI.Extensions.OpenAI for ToolConfig and the enum ResponsesToolSearchExecutionType , surfaced in the generated ExtensionsOpenAIModelFactory .
Root cause
• The customization Roslyn compilation ( SourceInputModel.Customization ) is built from custom files only; generated sources are not part of it.
• Generated types referenced by custom code normally resolve because the library declares a stub (e.g. [CodeGenType("BingGroundingSearchToolParameters")] ). Types without a stub resolve to Roslyn error type symbols.
• TypeSymbolExtensions.GetCSharpType → ConstructCSharpTypeFromSymbol builds a CSharpType from the error symbol. The fully-qualified display string of an unresolved symbol carries no namespace, so:
// TypeSymbolExtensions.cs (~line 204)
string[] pieces = fullyQualifiedName.Split('`')[0].Split('.'); // ["ToolConfig"]
string ns = string.Join('.', pieces.Take(pieces.Length - 1)); // "" (empty)
• The empty namespace flows into a real type used by ModelFactoryProvider (the factory derives parameter types from the model's full constructor, which here lives in custom code), producing global::.ToolConfig .
Why it only shows in the model factory
The generated model itself is in the same namespace, so the writer emits the short name ToolConfig and the empty namespace is invisible. The model factory (with using OpenAI.Responses; ) qualifies the type, exposing the empty namespace.
Affected code
Microsoft.TypeSpec.Generator/src/Utilities/TypeSymbolExtensions.cs — ConstructCSharpTypeFromSymbol (namespace computation for error-type symbols).
Suggested fix
When the symbol is an unresolved error type ( TypeKind.Error ) and the computed namespace is empty, fall back to the output library by name via CodeModelGenerator.Instance.TypeFactory.TypeProvidersByName (already maintained, TypeFactory.cs:29 ) to recover the real CSharpType /namespace. This is a best-effort fallback that triggers only for otherwise-broken symbols, so resolved types are unaffected, and it fixes the class of bug for any library — not just those that add [CodeGenType] stubs.
Current workaround
Add a stub for each affected type in custom code, e.g. [CodeGenType("ToolConfig")] public partial class ToolConfig { } .
Reproduction
Repro
Checklist