microsoft / microsoft/typespec
[http-client-csharp] Model factory loses namespaces for custom constructor parameter types
- Dominant language
- Java
- Stars
- 5.9k
- Forks
- 394
- Avg merge
- 1d 23h
- Merged PRs (30d)
- 104
Description
## 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:
```csharp
using ;
public static EffectiveNetworkSecurityGroup EffectiveNetworkSecurityGroup(
global::.NetworkSubResource networkSecurityGroup = default,
global::.EffectiveNetworkSecurityGroupAssociation association = default,
IEnumerable 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:
```csharp
[CodeGenSuppress(
"EffectiveNetworkSecurityGroup",
typeof(NetworkSubResource),
typeof(EffectiveNetworkSecurityGroupAssociation),
typeof(IReadOnlyList),
typeof(string),
typeof(IDictionary))]
public partial class EffectiveNetworkSecurityGroup
{
internal EffectiveNetworkSecurityGroup(
NetworkSubResource networkSecurityGroup,
EffectiveNetworkSecurityGroupAssociation association,
IReadOnlyList effectiveSecurityRules,
string tagMap,
IDictionary 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:
```powershell
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:
```csharp
parameter.Type.InputType
```
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:
1. preserve or correctly resolve the custom constructor parameter's C# type and namespace;
2. use `InputType` only when a valid input association exists; or
3. 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 ;`.
Contributor guide
Assessment
This issue has not been assessed yet.