Description
When a model's generated full constructor is suppressed and replaced by a custom internal constructor, ModelFactoryProvider can generate a factory overload whose custom model parameter types have an empty namespace.
The resulting C# is invalid:
using ;
public static EffectiveNetworkSecurityGroup EffectiveNetworkSecurityGroup(
global::.NetworkSubResource networkSecurityGroup = default,
global::.EffectiveNetworkSecurityGroupAssociation association = default,
IEnumerable<global::.EffectiveNetworkSecurityRule> effectiveSecurityRules = default,
string tagMap = default)
This was reproduced in Azure.ResourceManager.Network during the full management-plane regeneration in Azure/azure-sdk-for-net#62505.
Versions
@azure-typespec/http-client-csharp-mgmt: 1.0.0-alpha.20260902.1
@azure-typespec/http-client-csharp: 1.0.0-alpha.20260828.4
@typespec/http-client-csharp: 1.0.0-alpha.20260828.11
Microsoft.TypeSpec.Generator*: 1.0.0-alpha.20260828.11
Reproduction shape
The model has a custom internal compatibility constructor because its generated full constructor is suppressed:
[CodeGenSuppress(
"EffectiveNetworkSecurityGroup",
typeof(NetworkSubResource),
typeof(EffectiveNetworkSecurityGroupAssociation),
typeof(IReadOnlyList<EffectiveNetworkSecurityRule>),
typeof(string),
typeof(IDictionary<string, BinaryData>))]
public partial class EffectiveNetworkSecurityGroup
{
internal EffectiveNetworkSecurityGroup(
NetworkSubResource networkSecurityGroup,
EffectiveNetworkSecurityGroupAssociation association,
IReadOnlyList<EffectiveNetworkSecurityRule> effectiveSecurityRules,
string tagMap,
IDictionary<string, BinaryData> additionalBinaryDataProperties)
{
// Compatibility implementation.
}
}
The project also contains a valid custom model-factory overload with a different compatibility signature, so it does not suppress the newly generated overload.
Run the local management regeneration:
pwsh eng/packages/http-client-csharp-mgmt/eng/scripts/RegenSdkLocal.ps1 `
-Services Azure.ResourceManager.Network `
-Parallel 1
Root cause
ModelFactoryProvider.GetBinaryDataParamAndFullCtorForFactoryMethod intentionally selects an internal constructor from modelProvider.CanonicalView.Constructors when the generated full constructor was suppressed.
GetParameters then calls GetModelFactoryParam, which uses:
For generated parameters, InputType maps back to the input model correctly. For Roslyn-derived custom-constructor model parameters, that association is unavailable and InputType has the model name but an empty namespace. The writer consequently emits global::.Type, and namespace collection emits using ;.
Expected behavior
Model-factory generation should either:
- preserve or correctly resolve the custom constructor parameter's C# type and namespace;
- use
InputType only when a valid input association exists; or
- skip the generated factory overload with an actionable diagnostic when the selected custom constructor cannot be represented safely.
It must never emit an empty namespace, global::.Type, or using ;.
Suggested regression coverage
Add a model-factory test with:
- a suppressed generated full constructor;
- a custom internal replacement constructor;
- custom constructor parameters that reference generated model types;
- model-factory generation enabled.
Assert that all generated parameter types retain their namespaces and that output contains neither global::. nor using ;.
Description
When a model's generated full constructor is suppressed and replaced by a custom internal constructor,
ModelFactoryProvidercan generate a factory overload whose custom model parameter types have an empty namespace.The resulting C# is invalid:
This was reproduced in
Azure.ResourceManager.Networkduring the full management-plane regeneration in Azure/azure-sdk-for-net#62505.Versions
@azure-typespec/http-client-csharp-mgmt:1.0.0-alpha.20260902.1@azure-typespec/http-client-csharp:1.0.0-alpha.20260828.4@typespec/http-client-csharp:1.0.0-alpha.20260828.11Microsoft.TypeSpec.Generator*:1.0.0-alpha.20260828.11Reproduction shape
The model has a custom internal compatibility constructor because its generated full constructor is suppressed:
The project also contains a valid custom model-factory overload with a different compatibility signature, so it does not suppress the newly generated overload.
Run the local management regeneration:
Root cause
ModelFactoryProvider.GetBinaryDataParamAndFullCtorForFactoryMethodintentionally selects an internal constructor frommodelProvider.CanonicalView.Constructorswhen the generated full constructor was suppressed.GetParametersthen callsGetModelFactoryParam, which uses:For generated parameters,
InputTypemaps back to the input model correctly. For Roslyn-derived custom-constructor model parameters, that association is unavailable andInputTypehas the model name but an empty namespace. The writer consequently emitsglobal::.Type, and namespace collection emitsusing ;.Expected behavior
Model-factory generation should either:
InputTypeonly when a valid input association exists; orIt must never emit an empty namespace,
global::.Type, orusing ;.Suggested regression coverage
Add a model-factory test with:
Assert that all generated parameter types retain their namespaces and that output contains neither
global::.norusing ;.