Bug: ModelFactoryProvider.GetParameters() preserves constructor parameter order, causing backward-compat overloads to emit wrong argument order
Description
When @@hierarchyBuilding is applied to a model to change its base type (e.g., restoring TrackedResourceData during an AutoRest → TypeSpec migration), the generated backward-compat ModelFactory overload passes arguments in the internal constructor parameter order instead of the public method parameter order, causing compile errors.
Root Cause
ModelFactoryProvider.GetParameters() builds the primary ModelFactory method's parameters by iterating fullConstructor.Signature.Parameters, preserving constructor parameter order:
// In ModelFactoryProvider.BuildMethods():
MethodSignature methodSignature = new MethodSignature(
modelProvider.Name, ...,
GetParameters(modelProvider, item)); // item = fullConstructor
// GetParameters iterates constructor params in constructor order:
private static IReadOnlyList<ParameterProvider> GetParameters(
ModelProvider modelProvider, ConstructorProvider fullConstructor)
{
IReadOnlyList<ParameterProvider> parameters = fullConstructor.Signature.Parameters;
foreach (ParameterProvider item in parameters)
{
list.Add(GetModelFactoryParam(item)); // preserves constructor order
}
}
When @@hierarchyBuilding remaps a model to TrackedResource, InheritableSystemObjectModelVisitor reorders the internal constructor to: (resourceType, systemData, additionalBinaryDataProperties, location, id, name, tags, properties). This differs from the standard public API order (id, name, resourceType, systemData, tags, location, ...).
The primary ModelFactory method gets parameters in this constructor order. Then BuildMethodsForBackCompatibility → TryBuildMethodArgumentsForOverload iterates currentMethod.Parameters (constructor-ordered) and emits positional arguments. But the backward-compat overload declares parameters in the previous contract order, so the positional args don't match.
Reproduction
Apply @@hierarchyBuilding to a non-ARM Patch model that has id, name, type, location, tags properties, where a previous API contract exists with a ModelFactory method:
TypeSpec model:
model CapacityPoolPatch {
location?: string;
@visibility(Lifecycle.Read) id?: string;
@visibility(Lifecycle.Read) name?: string;
@visibility(Lifecycle.Read) type?: string;
tags?: Record<string>;
properties?: PatchProperties;
}
model PatchProperties {
size?: int64;
qosType?: string;
coolAccess?: boolean;
customThroughputMibpsInt?: int32;
}
client.tsp:
@@alternateType(CapacityPoolPatch.id, armResourceIdentifier, "csharp");
@@alternateType(CapacityPoolPatch.location, azureLocation, "csharp");
@@hierarchyBuilding(CapacityPoolPatch, Azure.ResourceManager.Foundations.TrackedResource, "csharp");
Expected
The backward-compat overload should use named arguments or match parameter names:
public static CapacityPoolPatch CapacityPoolPatch(
ResourceIdentifier id, string name, ResourceType resourceType,
SystemData systemData, IDictionary<string, string> tags,
AzureLocation location, long? size, CapacityPoolQosType? qosType,
bool? isCoolAccessEnabled)
{
return CapacityPoolPatch(
id: id, name: name, resourceType: resourceType,
systemData: systemData, tags: tags, location: location,
size: size, qosType: qosType,
isCoolAccessEnabled: isCoolAccessEnabled,
customThroughputMibpsInt: default);
}
Actual
Arguments emitted in constructor order (positionally):
return CapacityPoolPatch(
resourceType, systemData, location, id, name, tags,
size, qosType, isCoolAccessEnabled,
customThroughputMibpsInt: default);
This causes CS1503 because resourceType (ResourceType) is passed where id (ResourceIdentifier) is expected, etc.
Fix Suggestion
Either:
GetParameters() should sort output parameters to match a canonical order (id, name, resourceType, systemData, tags, location, ...remaining) rather than preserving constructor parameter order
TryBuildMethodArgumentsForOverload should emit named arguments when calling the current method, matching by parameter name instead of position
Impact
Blocks using @@hierarchyBuilding on Patch models during Azure SDK management-plane migration (AutoRest → TypeSpec) when the model has a backward-compat ModelFactory overload from a previous contract. Discovered during NetApp SDK migration (Azure/azure-sdk-for-net#58197).
Bug:
ModelFactoryProvider.GetParameters()preserves constructor parameter order, causing backward-compat overloads to emit wrong argument orderDescription
When
@@hierarchyBuildingis applied to a model to change its base type (e.g., restoringTrackedResourceDataduring an AutoRest → TypeSpec migration), the generated backward-compat ModelFactory overload passes arguments in the internal constructor parameter order instead of the public method parameter order, causing compile errors.Root Cause
ModelFactoryProvider.GetParameters()builds the primary ModelFactory method's parameters by iteratingfullConstructor.Signature.Parameters, preserving constructor parameter order:When
@@hierarchyBuildingremaps a model toTrackedResource,InheritableSystemObjectModelVisitorreorders the internal constructor to:(resourceType, systemData, additionalBinaryDataProperties, location, id, name, tags, properties). This differs from the standard public API order(id, name, resourceType, systemData, tags, location, ...).The primary ModelFactory method gets parameters in this constructor order. Then
BuildMethodsForBackCompatibility→TryBuildMethodArgumentsForOverloaditeratescurrentMethod.Parameters(constructor-ordered) and emits positional arguments. But the backward-compat overload declares parameters in the previous contract order, so the positional args don't match.Reproduction
Apply
@@hierarchyBuildingto a non-ARM Patch model that hasid,name,type,location,tagsproperties, where a previous API contract exists with a ModelFactory method:TypeSpec model:
client.tsp:
Expected
The backward-compat overload should use named arguments or match parameter names:
Actual
Arguments emitted in constructor order (positionally):
This causes
CS1503becauseresourceType(ResourceType) is passed whereid(ResourceIdentifier) is expected, etc.Fix Suggestion
Either:
GetParameters()should sort output parameters to match a canonical order (id, name, resourceType, systemData, tags, location, ...remaining) rather than preserving constructor parameter orderTryBuildMethodArgumentsForOverloadshould emit named arguments when calling the current method, matching by parameter name instead of positionImpact
Blocks using
@@hierarchyBuildingon Patch models during Azure SDK management-plane migration (AutoRest → TypeSpec) when the model has a backward-compat ModelFactory overload from a previous contract. Discovered during NetApp SDK migration (Azure/azure-sdk-for-net#58197).