From fbf6b33710d536fa51f3b6897b726cac20a811d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:34:00 +0000 Subject: [PATCH 1/7] Initial plan From 992b0ffd83eeee6b97995c3414f8a95ba14a75a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:14:02 +0000 Subject: [PATCH 2/7] Require all parameters on hidden model factory back-compat overloads Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelFactoryProvider.cs | 32 +++- .../src/Shared/MethodSignatureHelper.cs | 58 ++++++- .../ModelFactoryProviderTests.cs | 122 ++++++++++----- ...y_AbstractReturnTypeOverloadIsGenerated.cs | 4 +- .../SampleNamespaceModelFactory.cs | 20 +++ .../SampleNamespaceModelFactory.cs | 21 +++ ...rderedOverloadFallsBackToHiddenOverload.cs | 23 +++ .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...gPreviousOverloadsRequireAllParameters.cs} | 6 +- .../SampleNamespaceModelFactory.cs | 0 ...sOverloadsRequireAllParametersReversed.cs} | 6 +- ...verloadConstrainsNewlyGeneratedOverload.cs | 2 +- .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...existWithRequiredCompatibilityOverload.cs} | 2 +- .../SampleNamespaceModelFactory.cs | 0 ...refixOverloadKeepsGeneratedOptionality.cs} | 4 +- .../SampleNamespaceModelFactory.cs | 0 ...ePreviousOverloadsRequireAllParameters.cs} | 6 +- .../SampleNamespaceModelFactory.cs | 0 ...onalPrefixOverloadRequiresAllParameters.cs | 4 +- .../SampleNamespaceModelFactory.cs | 0 ...OptionalParametersRequireAllParameters.cs} | 4 +- ...eredOverloadKeptWhenStillInLastContract.cs | 2 +- .../SampleNamespaceModelFactory.cs | 0 ...OptionalParametersRequireAllParameters.cs} | 4 +- ...criminatorReturnTypeOverloadIsGenerated.cs | 2 +- .../test/Shared/MethodSignatureHelperTests.cs | 143 +++++++++++++++++- 30 files changed, 388 insertions(+), 77 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom) => BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last) => BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last) => BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs => BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs} (83%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last) => BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs => BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs} (83%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom) => BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last) => BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs => BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs} (84%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last) => BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs => BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs} (82%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last) => BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs => BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs} (83%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last) => BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last) => BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs => BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs} (78%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last) => BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs => BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs} (78%) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs index c25c27689e1..a186348bf8e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs @@ -214,7 +214,13 @@ protected internal sealed override IReadOnlyList BuildMethodsFor // from and stripping defaults would break the published omitted-argument calls. // The exclusion is name-based and therefore applies only to published signatures; // a custom overload is always emitted and must always constrain. + // A published signature that is restored as a compatibility overload is excluded as + // well: that overload either requires every parameter, which makes it preferred at + // its own argument count and unreachable by any shorter call, or it replaces this + // overload entirely. var competingSignatures = preservedPreviousSignatures + .Where(signature => !compatiblePreviousMethods.Any(method => + ReferenceEquals(method.Signature, signature))) .Where(signature => !MethodSignatureHelper.HaveSameParametersInSameOrder( currentMethod.Signature, signature)) @@ -505,14 +511,27 @@ private bool TryBuildCompatibleMethodForPreviousContract( return false; } + var signature = MethodSignatureHelper.BuildBackCompatMethodSignature( + previousMethod.Signature, + hideMethod, + currentMethodSignatures: currentOverloadSignatures); + + // Best effort: never emit a compatibility overload that a call could not resolve against a + // custom overload. When a visible overload is rejected here the caller falls back to a hidden + // one, which requires every parameter and is therefore preferred at its own argument count. + if (currentOverloadSignatures.Any(overload => MethodSignatureHelper.AreAmbiguous(signature, overload))) + { + CodeModelGenerator.Instance.Emitter.Debug( + $"Skipped model factory method '{Name}.{previousMethod.Signature.Name}' from last contract because it would be ambiguous with a custom overload.", + BackCompatibilityChangeCategory.ModelFactoryMethodSkipped); + return false; + } + if (currentMethodSignature != null && TryBuildMethodArgumentsForOverload(previousMethod.Signature, currentMethodSignature, out var arguments)) { var callToOverload = Return(new InvokeMethodExpression(null, currentMethodSignature, arguments)); builtMethod = new MethodProvider( - MethodSignatureHelper.BuildBackCompatMethodSignature( - previousMethod.Signature, - hideMethod, - currentMethodSignatures: currentOverloadSignatures), + signature, callToOverload, this, previousMethod.XmlDocs); @@ -523,10 +542,7 @@ private bool TryBuildCompatibleMethodForPreviousContract( MethodBodyStatements body = ConstructMethodBody(previousMethod.Signature, modelToInstantiate); builtMethod = new MethodProvider( - MethodSignatureHelper.BuildBackCompatMethodSignature( - previousMethod.Signature, - hideMethod, - currentMethodSignatures: currentOverloadSignatures), + signature, body, this, previousMethod.XmlDocs); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index 6fe30751308..d78d8c12b20 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -76,11 +76,67 @@ internal static MethodSignature BuildBackCompatMethodSignature( IReadOnlyList currentMethodSignatures, bool shouldNotBeAsync = false) { - RequireMinimumParameterPrefix(previousMethodSignature, currentMethodSignatures); + // A hidden overload only exists to keep previously compiled call sites working, so every + // parameter is required. Any call that omits an argument then binds to a visible overload, + // which keeps the optionality of the current shape. + RequireMinimumParameterPrefix( + previousMethodSignature, + hideMethod ? null : currentMethodSignatures); return CreateBackCompatSignature(previousMethodSignature, hideMethod, shouldNotBeAsync); } + /// + /// Determines whether a call could bind to both and + /// without overload resolution being able to prefer one of them. + /// Only argument counts that both signatures accept with the same parameter types are considered, + /// since any other call is resolved by the argument count or the argument types. + /// + internal static bool AreAmbiguous(MethodSignature signature, MethodSignature otherSignature) + { + if (signature.Name != otherSignature.Name + || HasUnsupportedParameterKind(signature) + || HasUnsupportedParameterKind(otherSignature)) + { + return false; + } + + int equivalentParameterCount = 0; + int overlappingParameterCount = Math.Min(signature.Parameters.Count, otherSignature.Parameters.Count); + while (equivalentParameterCount < overlappingParameterCount) + { + CSharpType type = signature.Parameters[equivalentParameterCount].Type; + CSharpType otherType = otherSignature.Parameters[equivalentParameterCount].Type; + if (type.IsNullable != otherType.IsNullable || !type.AreNamesEqual(otherType)) + { + break; + } + + equivalentParameterCount++; + } + + int minimumArgumentCount = Math.Max( + GetMinimumArgumentCount(signature), + GetMinimumArgumentCount(otherSignature)); + for (int argumentCount = minimumArgumentCount; argumentCount <= equivalentParameterCount; argumentCount++) + { + // An overload that receives an argument for every one of its parameters is preferred over + // an overload that has to substitute a default, so the pair can only be unresolvable when + // both of them are in the same state. + bool substitutesDefault = argumentCount < signature.Parameters.Count; + bool otherSubstitutesDefault = argumentCount < otherSignature.Parameters.Count; + if (substitutesDefault == otherSubstitutesDefault) + { + return true; + } + } + + return false; + } + + private static bool HasUnsupportedParameterKind(MethodSignature signature) + => signature.Parameters.Any(p => p.IsRef || p.IsOut || p.IsParams); + /// /// Removes the default values from the leading parameters of so it /// can no longer be called with fewer arguments than the prefix that distinguishes it from diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs index 0eba7a1a42f..5204539eec5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs @@ -194,6 +194,8 @@ public async Task BackCompatibility_NewModelPropertyAdded() "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]", printedAttribute); + // The hidden overload requires every parameter so that a call omitting an argument can + // only bind to the current overload, which keeps its optional parameters. var parameters = backwardCompatibilityMethod!.Signature.Parameters; Assert.AreEqual(3, parameters.Count); Assert.AreEqual("stringProp", parameters[0].Name); @@ -201,7 +203,7 @@ public async Task BackCompatibility_NewModelPropertyAdded() Assert.AreEqual("listProp", parameters[2].Name); foreach (var param in parameters) { - Assert.IsNotNull(param.DefaultValue); + Assert.IsNull(param.DefaultValue); } var currentParameters = currentOverloadMethod!.Signature.Parameters; @@ -212,7 +214,7 @@ public async Task BackCompatibility_NewModelPropertyAdded() Assert.AreEqual("dictProp", currentParameters[3].Name); foreach (var param in currentParameters) { - Assert.IsNull(param.DefaultValue); + Assert.IsNotNull(param.DefaultValue); } Assert.IsTrue(parameters[0].Type.AreNamesEqual(currentParameters[0].Type)); @@ -271,8 +273,11 @@ public async Task BackCompatibility_NewPropertyAddedWithDifferentParamOrder() result); } + // A hidden compatibility overload requires every parameter, so a reordered fully optional + // previous overload can coexist with the generated overload without making any call + // ambiguous. The generated overload keeps the optionality of the current model shape. [Test] - public async Task BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix() + public async Task BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters() { var compatibilityModel = GetCompatibilityModel(includeCount: true); @@ -288,8 +293,10 @@ public async Task BackCompatibility_ReorderedFullyOptionalParametersRequireMinim Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } + // The previous overload mixes required and optional parameters. The hidden compatibility + // overload still requires every one of them. [Test] - public async Task BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters() + public async Task BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters() { var compatibilityModel = GetCompatibilityModel(includeCount: true); @@ -323,8 +330,10 @@ public async Task BackCompatibility_ReorderedCustomOverloadRequiresMinimumPrefix Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } + // The generated overload is suppressed and replaced by a custom overload, so the hidden + // compatibility overload forwards to the custom one while requiring every parameter. [Test] - public async Task BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters() + public async Task BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload() { _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", @@ -339,6 +348,43 @@ public async Task BackCompatibility_CustomOverloadsPreserveTrailingOptionalParam Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } + // A reordered previous overload would normally replace the generated overload while keeping its + // published optionality. Here that visible overload would be ambiguous with a custom overload + // (both would substitute defaults for a single-argument call), so it is emitted as a hidden + // overload requiring every parameter instead. + [Test] + public async Task BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload() + { + InputModelType model = InputFactory.Model("CompatibilityModel", properties: + [ + InputFactory.Property("Id", InputPrimitiveType.String), + InputFactory.Property("Count", new InputNullableType(InputPrimitiveType.Int32)), + InputFactory.Property("Name", InputPrimitiveType.String), + ]); + + _instance = (await MockHelpers.LoadMockGeneratorAsync( + inputNamespaceName: "Sample.Namespace", + inputModelTypes: [model], + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + + var modelFactory = _instance.OutputLibrary.ModelFactory.Value; + modelFactory.ProcessTypeForBackCompatibility(); + + var compatibilityMethod = modelFactory.Methods.Single(m => + m.Signature.Name == "CompatibilityModel" + && m.Signature.Parameters.Count == 3 + && m.Signature.Parameters[1].Name == "name"); + Assert.AreEqual(1, compatibilityMethod.Signature.Attributes.Count); + Assert.AreEqual( + "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]", + compatibilityMethod.Signature.Attributes[0].ToDisplayString()); + Assert.IsTrue(compatibilityMethod.Signature.Parameters.All(p => p.DefaultValue is null)); + + var content = new TypeProviderWriter(modelFactory).Write().Content; + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + } + [Test] public async Task BackCompatibility_CustomPublishedSignatureDoesNotRequireLongerCurrentOverload() { @@ -392,7 +438,7 @@ public async Task BackCompatibility_OptionalCustomPublishedSignatureRequiresLong } [Test] - public async Task BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload() + public async Task BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ @@ -411,11 +457,12 @@ public async Task BackCompatibility_ChangedCustomOptionalityConstrainsCompatibil var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); + // The hidden compatibility overload requires every parameter regardless of the shapes it + // coexists with. var compatibilityMethod = modelFactory.Methods.Single(m => m.Signature.Name == "CompatibilityModel" && m.Signature.Parameters.Count == 2); - Assert.IsNull(compatibilityMethod.Signature.Parameters[0].DefaultValue); - Assert.IsNotNull(compatibilityMethod.Signature.Parameters[1].DefaultValue); + Assert.IsTrue(compatibilityMethod.Signature.Parameters.All(p => p.DefaultValue is null)); // The custom method keeps the fully optional (string, string, string) shape, so the // generated overload must require every parameter. Leaving any trailing default would @@ -587,8 +634,9 @@ public async Task BackCompatibility_ReorderedOverloadKeptWhenStillInLastContract Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } - // A previous signature that is a positional prefix of a new current overload cannot be - // disambiguated by argument count, so every parameter on the new overload must be required. + // A previous signature that is a positional prefix of a new current overload can only be + // reached by a call that supplies every one of its arguments, so the hidden overload requires + // all of them and the new overload keeps its optional parameters. [Test] public async Task BackCompatibility_PositionalPrefixOverloadRequiresAllParameters() { @@ -611,11 +659,11 @@ public async Task BackCompatibility_PositionalPrefixOverloadRequiresAllParameter Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } - // When the previous signature is a strict positional prefix of a longer new overload, - // requiring one parameter beyond the previous signature makes their applicable argument - // counts disjoint. Parameters after that boundary must retain their optionality. + // The previous signature is a strict positional prefix of a longer new overload. Requiring + // every parameter on the hidden overload makes their applicable argument counts disjoint, so + // the new overload keeps every default from the current model shape. [Test] - public async Task BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality() + public async Task BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ @@ -680,10 +728,10 @@ public async Task BackCompatibility_VisitorAddedOverloadRequiresMinimumPrefix() Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } - // Two previous overloads compete with the same new current method. Both retain their published - // defaults while the new overload acquires the longest prefix needed to avoid both. + // Two previous overloads compete with the same new current method. Each of them requires all + // of its parameters, so the new overload keeps the defaults of the current model shape. [Test] - public async Task BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes() + public async Task BackCompatibility_MultiplePreviousOverloadsRequireAllParameters() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ @@ -706,13 +754,11 @@ public async Task BackCompatibility_MultiplePreviousOverloadsRequireIndependentP Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } - // Overloads that shipped together in the published contract already coexisted there, so they - // are not new competitors for one another; only a surviving current or custom overload can - // introduce ambiguity that was not already present. Here 'id, count' is a positional prefix - // of the wider overload, so treating them as competitors would make the wider one fully - // required and break the previously valid call 'CompatibilityModel("i", 1, "e")'. + // Two overloads that shipped together in the published contract both become hidden + // compatibility overloads. Even though 'id, count' is a positional prefix of the wider + // overload, requiring every parameter on both keeps their applicable argument counts disjoint. [Test] - public async Task BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality() + public async Task BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ @@ -735,7 +781,7 @@ public async Task BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOpti // Same last-contract overloads as above but declared in the opposite order. Signatures are // mutated in place, so this pins that the result does not depend on declaration order. [Test] - public async Task BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed() + public async Task BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ @@ -785,9 +831,9 @@ public async Task BackCompatibility_AllRequiredPreviousOverloadKeepsPublishedOpt } // A current generated overload can have the same signature as a previously published overload - // while acquiring different defaults from the current model shape. Preserve the published - // required boundary on that overload and the published optionality on its reordered companion - // rather than swapping their callability. + // while acquiring different defaults from the current model shape. That overload keeps the + // published required boundary, while its reordered companion becomes a hidden compatibility + // overload that requires every parameter. [Test] public async Task BackCompatibility_PublishedOverloadBoundariesArePreserved() { @@ -814,14 +860,14 @@ public async Task BackCompatibility_PublishedOverloadBoundariesArePreserved() && m.Signature.Parameters[1].Name == "kind"); Assert.IsTrue(currentOverload.Signature.Parameters.All(p => p.DefaultValue is null)); - Assert.IsTrue(compatibilityOverload.Signature.Parameters.All(p => p.DefaultValue is not null)); + Assert.IsTrue(compatibilityOverload.Signature.Parameters.All(p => p.DefaultValue is null)); } - // The newly generated overload did not exist in the previous contract, so it can acquire the - // required prefix needed for disambiguation. The previous overload must remain fully optional - // so calls using its unique parameter names continue to compile. + // The hidden compatibility overload requires every parameter, so it is only applicable when a + // call supplies all of its arguments. The newly generated overload is therefore free to keep + // the optionality of the current model shape. [Test] - public async Task BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments() + public async Task BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ @@ -844,8 +890,8 @@ public async Task BackCompatibility_NewOverloadIsConstrainedToPreservePublishedN m.Signature.Name == "CompatibilityModel" && m.Signature.Parameters.Count == 3); - Assert.IsTrue(currentOverload.Signature.Parameters.All(p => p.DefaultValue is null)); - Assert.IsTrue(compatibilityOverload.Signature.Parameters.All(p => p.DefaultValue is not null)); + Assert.IsTrue(currentOverload.Signature.Parameters.All(p => p.DefaultValue is not null)); + Assert.IsTrue(compatibilityOverload.Signature.Parameters.All(p => p.DefaultValue is null)); } // This test validates that only the previous model factory methods are generated when only the parameter ordering is changed @@ -950,7 +996,8 @@ public async Task BackCompatibility_NoCurrentOverloadFound() var parameters = backwardCompatibilityMethod!.Signature.Parameters; Assert.AreEqual(1, parameters.Count); Assert.AreEqual("stringProp", parameters[0].Name); - Assert.IsNotNull(parameters[0].DefaultValue); + // The compatibility method is hidden, so it requires every parameter. + Assert.IsNull(parameters[0].DefaultValue); var attributes = backwardCompatibilityMethod!.Signature.Attributes; Assert.AreEqual(1, attributes.Count); var printedAttribute = attributes[0].ToDisplayString(); @@ -1283,12 +1330,13 @@ public async Task BackCompatibility_NewPropertyAddedWithRenamedParam() Assert.AreEqual("oldStringProp", parameters[0].Name); Assert.AreEqual("oldModelProp", parameters[1].Name); Assert.AreEqual("listProp", parameters[2].Name); + // The hidden overload requires every parameter, so the current overload keeps its defaults. foreach (var param in parameters) { - Assert.IsNotNull(param.DefaultValue); + Assert.IsNull(param.DefaultValue); } - Assert.IsTrue(currentParameters.All(p => p.DefaultValue is null)); + Assert.IsTrue(currentParameters.All(p => p.DefaultValue is not null)); // The backcompat overload's body instantiates the model directly because the previous // parameter names (oldStringProp, oldModelProp) do not match any current property name. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AbstractReturnTypeOverloadIsGenerated.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AbstractReturnTypeOverloadIsGenerated.cs index be14011abf7..8c46eba00bb 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AbstractReturnTypeOverloadIsGenerated.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AbstractReturnTypeOverloadIsGenerated.cs @@ -9,7 +9,7 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.AbstractModel AbstractModel(string kind, string prop1, string prop2) + public static global::Sample.Models.AbstractModel AbstractModel(string kind = default, string prop1 = default, string prop2 = default) { return new global::Sample.Models.UnknownAbstractModel(kind, prop1, prop2, additionalBinaryDataProperties: null); } @@ -20,7 +20,7 @@ public static partial class SampleNamespaceModelFactory } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.AbstractModel AbstractModel(string prop1 = default, string kind = default) + public static global::Sample.Models.AbstractModel AbstractModel(string prop1, string kind) { return AbstractModel(kind: kind, prop1: prop1, prop2: default); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs new file mode 100644 index 00000000000..922b7867a8a --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs @@ -0,0 +1,20 @@ +using Sample.Models; + +namespace Sample.Namespace +{ + public static partial class SampleNamespaceModelFactory + { + public static CompatibilityModel CompatibilityModel( + string id, + string name = default, + string extra = default, + string other = default) + { } + } +} + +namespace Sample.Models +{ + public partial class CompatibilityModel + { } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs new file mode 100644 index 00000000000..a3fc14f5394 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs @@ -0,0 +1,21 @@ +using Sample.Models; + +namespace Sample.Namespace +{ + public static partial class SampleNamespaceModelFactory + { + // A reordering of the current overload. Keeping it visible with its published optionality + // would make the single-argument call ambiguous with the custom overload below. + public static CompatibilityModel CompatibilityModel( + string id = default, + string name = default, + int? count = default) + { } + } +} + +namespace Sample.Models +{ + public partial class CompatibilityModel + { } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs new file mode 100644 index 00000000000..9dc1a9e39cb --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs @@ -0,0 +1,23 @@ +// + +#nullable disable + +using System.ComponentModel; +using Sample.Models; + +namespace Sample.Namespace +{ + public static partial class SampleNamespaceModelFactory + { + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count, string name) + { + return new global::Sample.Models.CompatibilityModel(id, count, name, additionalBinaryDataProperties: null); + } + + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, int? count) + { + return new global::Sample.Models.CompatibilityModel(id, count, name, additionalBinaryDataProperties: null); + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs similarity index 83% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs index dbac26c46c9..e70f18cab55 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs @@ -9,19 +9,19 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string description, string name) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string description = default, string name = default) { return new global::Sample.Models.CompatibilityModel(id, description, name, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count) { return new global::Sample.Models.CompatibilityModel(id, default, default, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count = default, string extra = default, string other = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count, string extra, string other) { return new global::Sample.Models.CompatibilityModel(id, default, default, additionalBinaryDataProperties: null); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs similarity index 83% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs index 3fe16a53ee8..03c5ce245b3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs @@ -9,19 +9,19 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string description, string name) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string description = default, string name = default) { return new global::Sample.Models.CompatibilityModel(id, description, name, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count = default, string extra = default, string other = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count, string extra, string other) { return new global::Sample.Models.CompatibilityModel(id, default, default, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count) { return new global::Sample.Models.CompatibilityModel(id, default, default, additionalBinaryDataProperties: null); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadConstrainsNewlyGeneratedOverload.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadConstrainsNewlyGeneratedOverload.cs index d9c8aeedee7..3ff07140161 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadConstrainsNewlyGeneratedOverload.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadConstrainsNewlyGeneratedOverload.cs @@ -15,7 +15,7 @@ public static partial class SampleNamespaceModelFactory } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(bool isRegex, string id = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(bool isRegex, string id) { return CompatibilityModel(id: id, description: default, text: default, isRegex: isRegex); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs similarity index 84% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs index 70b749e4a2e..b4eaf4540a1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs @@ -10,7 +10,7 @@ namespace Sample.Namespace public static partial class SampleNamespaceModelFactory { [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, bool? enabled = default, string description = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, bool? enabled, string description) { return CompatibilityModel(id: id, name: name, enabled: enabled, description: description, kind: default); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs similarity index 82% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs index 04b10419d19..bfc0d138a06 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs @@ -9,13 +9,13 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, int? count, string description = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, int? count = default, string description = default) { return new global::Sample.Models.CompatibilityModel(id, name, count, description, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name) { return CompatibilityModel(id: id, name: name, count: default, description: default); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs similarity index 83% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs index 7e248bb941c..c7fbf34a796 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs @@ -9,7 +9,7 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, int? count, bool? flag, string kind) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, int? count = default, bool? flag = default, string kind = default) { return new global::Sample.Models.CompatibilityModel( id, @@ -21,13 +21,13 @@ public static partial class SampleNamespaceModelFactory } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, int? count = default, string kind = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, int? count, string kind) { return CompatibilityModel(id: id, name: name, count: count, flag: default, kind: kind); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, string kind = default, int? count = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, string kind, int? count) { return CompatibilityModel(id: id, name: name, count: count, flag: default, kind: kind); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_PositionalPrefixOverloadRequiresAllParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_PositionalPrefixOverloadRequiresAllParameters.cs index 8ebf6150303..2bc28c06a72 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_PositionalPrefixOverloadRequiresAllParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_PositionalPrefixOverloadRequiresAllParameters.cs @@ -9,13 +9,13 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, int? count) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, int? count = default) { return new global::Sample.Models.CompatibilityModel(id, name, count, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name) { return CompatibilityModel(id: id, name: name, count: default); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs similarity index 78% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs index e177eb33854..05ad40d1411 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs @@ -9,7 +9,7 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, string kind, bool? enabled, string description, int? count = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, string kind = default, bool? enabled = default, string description = default, int? count = default) { return new global::Sample.Models.CompatibilityModel( id, @@ -22,7 +22,7 @@ public static partial class SampleNamespaceModelFactory } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, bool? enabled = default, string description = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, bool? enabled, string description) { return CompatibilityModel(id: id, name: name, kind: default, enabled: enabled, description: description, count: default); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedOverloadKeptWhenStillInLastContract.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedOverloadKeptWhenStillInLastContract.cs index c636faa9f68..471c4513d96 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedOverloadKeptWhenStillInLastContract.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedOverloadKeptWhenStillInLastContract.cs @@ -15,7 +15,7 @@ public static partial class SampleNamespaceModelFactory } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string image = default, bool? isMain = default, string kind = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string image, bool? isMain, string kind) { return new global::Sample.Models.CompatibilityModel(id, kind, image, isMain, additionalBinaryDataProperties: null); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs similarity index 78% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs index 4b2770a96c5..05ad40d1411 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs @@ -9,7 +9,7 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, string kind, bool? enabled, string description, int? count = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, string name = default, string kind = default, bool? enabled = default, string description = default, int? count = default) { return new global::Sample.Models.CompatibilityModel( id, @@ -22,7 +22,7 @@ public static partial class SampleNamespaceModelFactory } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, bool? enabled = default, string description = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, bool? enabled, string description) { return CompatibilityModel(id: id, name: name, kind: default, enabled: enabled, description: description, count: default); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_UnknownDiscriminatorReturnTypeOverloadIsGenerated.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_UnknownDiscriminatorReturnTypeOverloadIsGenerated.cs index 5220d6b97f2..c9aa1d5716c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_UnknownDiscriminatorReturnTypeOverloadIsGenerated.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_UnknownDiscriminatorReturnTypeOverloadIsGenerated.cs @@ -20,7 +20,7 @@ public static partial class SampleNamespaceModelFactory } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.UnknownAbstractModel UnknownAbstractModel(string prop1 = default, string kind = default) + public static global::Sample.Models.UnknownAbstractModel UnknownAbstractModel(string prop1, string kind) { return new global::Sample.Models.UnknownAbstractModel(kind, prop1, additionalBinaryDataProperties: null); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs index b0d703ba338..0d0fe8b0e1b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs @@ -369,13 +369,12 @@ public void BuildBackCompatMethodSignature_NullLiteralOverlapRequiresAllParamete var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousSignature, - hideMethod: true, + hideMethod: false, currentMethodSignatures: [currentSignature]); // Both differing positions accept null, so requiring only three parameters would leave // CompatibilityModel("id", "name", null) ambiguous. Assert.That(backCompatSignature.Parameters, Has.All.Property("DefaultValue").Null); - Assert.IsTrue(backCompatSignature.Attributes.Any(a => a.Type.Equals(typeof(System.ComponentModel.EditorBrowsableAttribute)))); } [Test] @@ -393,7 +392,7 @@ public void BuildBackCompatMethodSignature_NullableValueTypeDifferencePreservesT var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousSignature, - hideMethod: true, + hideMethod: false, currentMethodSignatures: [currentSignature]); Assert.IsNull(backCompatSignature.Parameters[0].DefaultValue); @@ -419,7 +418,7 @@ public void BuildBackCompatMethodSignature_RequiredFactoryParametersRespectNullL var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousSignature, - hideMethod: true, + hideMethod: false, currentMethodSignatures: [currentSignature]); Assert.IsNull(backCompatSignature.Parameters[0].DefaultValue); @@ -449,7 +448,7 @@ public void BuildBackCompatMethodSignature_AllRequiredCompetitorPreservesPublish var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousSignature, - hideMethod: true, + hideMethod: false, currentMethodSignatures: [currentSignature]); foreach (var parameter in backCompatSignature.Parameters) @@ -491,7 +490,7 @@ public void BuildBackCompatMethodSignature_PreservesDefaultsForInapplicableArgum var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousSignature, - hideMethod: true, + hideMethod: false, currentMethodSignatures: [currentSignature]); Assert.IsNotNull(backCompatSignature.Parameters[0].DefaultValue); @@ -507,7 +506,7 @@ public void BuildBackCompatMethodSignature_PreservesDefaultsForRefOutOverload() var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousSignature, - hideMethod: true, + hideMethod: false, currentMethodSignatures: [currentSignature]); Assert.IsNotNull(backCompatSignature.Parameters[0].DefaultValue); @@ -525,12 +524,140 @@ public void BuildBackCompatMethodSignature_PreservesDefaultsForInapplicableParam var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousSignature, - hideMethod: true, + hideMethod: false, currentMethodSignatures: [currentSignature]); Assert.IsNotNull(backCompatSignature.Parameters[0].DefaultValue); } + // A hidden overload is only there to keep previously compiled call sites working. Requiring + // every parameter makes it inapplicable to any call that omits an argument, so the visible + // overloads keep the optionality of the current shape. + [Test] + public void BuildBackCompatMethodSignature_HideMethodTrue_WithOverloads_RequiresAllParameters() + { + var previousSignature = CreateMethodSignature("CompatibilityModel", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default)); + var currentSignature = CreateMethodSignature("CompatibilityModel", + new ParameterProvider("first", $"", typeof(bool)), + new ParameterProvider("second", $"", typeof(int))); + + var backCompatSignature = MethodSignatureHelper.BuildBackCompatMethodSignature( + previousSignature, + hideMethod: true, + currentMethodSignatures: [currentSignature]); + + Assert.That(backCompatSignature.Parameters, Has.All.Property("DefaultValue").Null); + Assert.IsTrue(backCompatSignature.Attributes.Any(a => a.Type.Equals(typeof(System.ComponentModel.EditorBrowsableAttribute)))); + } + + [Test] + public void AreAmbiguous_DifferentNames_ReturnsFalse() + { + var signature = CreateMethodSignature("Method1", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default)); + var otherSignature = CreateMethodSignature("Method2", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default)); + + Assert.IsFalse(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + [Test] + public void AreAmbiguous_DifferentParameterTypes_ReturnsFalse() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string))); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("count", $"", typeof(int))); + + Assert.IsFalse(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + // Both overloads are applicable to the argument-less call and neither of them is preferred. + [Test] + public void AreAmbiguous_FullyOptionalOverloads_ReturnsTrue() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default)); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("count", $"", typeof(int), defaultValue: Default)); + + Assert.IsTrue(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + [Test] + public void AreAmbiguous_SameParameterTypes_ReturnsTrue() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("name", $"", typeof(string))); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default)); + + Assert.IsTrue(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + // Both overloads have to substitute a default for the single-argument call, so neither of + // them is preferred. + [Test] + public void AreAmbiguous_BothSubstituteDefaults_ReturnsTrue() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default), + new ParameterProvider("count", $"", typeof(int?), defaultValue: Default)); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default), + new ParameterProvider("extra", $"", typeof(string), defaultValue: Default), + new ParameterProvider("other", $"", typeof(string), defaultValue: Default)); + + Assert.IsTrue(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + // The shorter overload receives an argument for every parameter, so it is preferred over the + // longer one that has to substitute a default. + [Test] + public void AreAmbiguous_OnlyOneSubstitutesDefault_ReturnsFalse() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("name", $"", typeof(string))); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("name", $"", typeof(string)), + new ParameterProvider("count", $"", typeof(int?), defaultValue: Default)); + + Assert.IsFalse(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + [Test] + public void AreAmbiguous_NoSharedArgumentCount_ReturnsFalse() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default)); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("name", $"", typeof(string))); + + Assert.IsFalse(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + [Test] + public void AreAmbiguous_ParamsOverload_ReturnsFalse() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("name", $"", typeof(string))); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("rest", $"", typeof(string[]), isParams: true)); + + Assert.IsFalse(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + private static MethodSignature CreateMethodSignature( string name, params ParameterProvider[] parameters) From 587d4b26fc75f3a6d865af8b2b307e2e417d8573 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:36:25 +0000 Subject: [PATCH 3/7] Clarify ambiguity guard and show generated optionality in its test Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelFactoryProvider.cs | 6 ++++-- .../ModelFactories/ModelFactoryProviderTests.cs | 14 ++++++++++---- .../SampleNamespaceModelFactory.cs | 6 ++---- .../SampleNamespaceModelFactory.cs | 6 +++--- ...usReorderedOverloadFallsBackToHiddenOverload.cs | 8 ++++---- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs index a186348bf8e..6bb2f09a2c1 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs @@ -517,8 +517,10 @@ private bool TryBuildCompatibleMethodForPreviousContract( currentMethodSignatures: currentOverloadSignatures); // Best effort: never emit a compatibility overload that a call could not resolve against a - // custom overload. When a visible overload is rejected here the caller falls back to a hidden - // one, which requires every parameter and is therefore preferred at its own argument count. + // custom overload. A hidden overload requires every parameter, so it can only be rejected + // here by a custom overload with an identical parameter type list; every other shape is + // resolved by the argument count or the argument types. A rejected visible (reorder + // replacement) overload falls back to the hidden all-required overload instead. if (currentOverloadSignatures.Any(overload => MethodSignatureHelper.AreAmbiguous(signature, overload))) { CodeModelGenerator.Instance.Emitter.Debug( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs index 5204539eec5..a396ac72773 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs @@ -351,14 +351,15 @@ public async Task BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibil // A reordered previous overload would normally replace the generated overload while keeping its // published optionality. Here that visible overload would be ambiguous with a custom overload // (both would substitute defaults for a single-argument call), so it is emitted as a hidden - // overload requiring every parameter instead. + // overload requiring every parameter instead. The generated overload only has to require the + // parameters that distinguish it from the custom overload, so it keeps its trailing defaults. [Test] public async Task BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ InputFactory.Property("Id", InputPrimitiveType.String), - InputFactory.Property("Count", new InputNullableType(InputPrimitiveType.Int32)), + InputFactory.Property("Flag", InputPrimitiveType.Boolean), InputFactory.Property("Name", InputPrimitiveType.String), ]); @@ -373,14 +374,19 @@ public async Task BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenO var compatibilityMethod = modelFactory.Methods.Single(m => m.Signature.Name == "CompatibilityModel" - && m.Signature.Parameters.Count == 3 - && m.Signature.Parameters[1].Name == "name"); + && m.Signature.Parameters[0].Name == "flag"); Assert.AreEqual(1, compatibilityMethod.Signature.Attributes.Count); Assert.AreEqual( "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]", compatibilityMethod.Signature.Attributes[0].ToDisplayString()); Assert.IsTrue(compatibilityMethod.Signature.Parameters.All(p => p.DefaultValue is null)); + var generatedMethod = modelFactory.Methods.Single(m => + m.Signature.Name == "CompatibilityModel" + && m.Signature.Parameters[0].Name == "id"); + Assert.IsEmpty(generatedMethod.Signature.Attributes); + Assert.IsTrue(generatedMethod.Signature.Parameters.Skip(1).All(p => p.DefaultValue is not null)); + var content = new TypeProviderWriter(modelFactory).Write().Content; Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs index 922b7867a8a..b513ebe4222 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs @@ -5,10 +5,8 @@ namespace Sample.Namespace public static partial class SampleNamespaceModelFactory { public static CompatibilityModel CompatibilityModel( - string id, - string name = default, - string extra = default, - string other = default) + bool flag, + string id = default) { } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs index a3fc14f5394..afb42249b5b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs @@ -5,11 +5,11 @@ namespace Sample.Namespace public static partial class SampleNamespaceModelFactory { // A reordering of the current overload. Keeping it visible with its published optionality - // would make the single-argument call ambiguous with the custom overload below. + // would make the single-argument call ambiguous with the custom overload. public static CompatibilityModel CompatibilityModel( + bool flag = default, string id = default, - string name = default, - int? count = default) + string name = default) { } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs index 9dc1a9e39cb..c303650b672 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs @@ -9,15 +9,15 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count, string name) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, bool? flag = default, string name = default) { - return new global::Sample.Models.CompatibilityModel(id, count, name, additionalBinaryDataProperties: null); + return new global::Sample.Models.CompatibilityModel(id, flag, name, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, int? count) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(bool flag, string id, string name) { - return new global::Sample.Models.CompatibilityModel(id, count, name, additionalBinaryDataProperties: null); + return new global::Sample.Models.CompatibilityModel(id, flag, name, additionalBinaryDataProperties: null); } } } From e4ac243e41302439382c41c73530bb91ddd75255 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:45:52 +0000 Subject: [PATCH 4/7] Fix model factory overload optionality review feedback Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Providers/ModelFactoryProvider.cs | 17 ++++----- .../src/Shared/MethodSignatureHelper.cs | 11 ++++-- .../ModelFactoryProviderTests.cs | 38 +++++++++---------- ...rderedOverloadFallsBackToHiddenOverload.cs | 2 +- .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...viousOverloadsKeepPublishedOptionality.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...rloadsKeepPublishedOptionalityReversed.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...oadsPreserveTrailingOptionalParameters.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...ixOverloadPreservesTrailingOptionality.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...ousOverloadsRequireIndependentPrefixes.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...OptionalParametersRequireMinimumPrefix.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...tersPreserveTrailingOptionalParameters.cs} | 0 .../test/Shared/MethodSignatureHelperTests.cs | 19 ++++++++++ 23 files changed, 55 insertions(+), 32 deletions(-) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom) => BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last) => BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last) => BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs => BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last) => BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs => BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom) => BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last) => BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs => BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last) => BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs => BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last) => BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs => BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last) => BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last) => BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs => BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last) => BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs => BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs} (100%) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs index 6bb2f09a2c1..dd68342f310 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs @@ -214,10 +214,10 @@ protected internal sealed override IReadOnlyList BuildMethodsFor // from and stripping defaults would break the published omitted-argument calls. // The exclusion is name-based and therefore applies only to published signatures; // a custom overload is always emitted and must always constrain. - // A published signature that is restored as a compatibility overload is excluded as - // well: that overload either requires every parameter, which makes it preferred at - // its own argument count and unreachable by any shorter call, or it replaces this - // overload entirely. + // A published signature that is restored as a compatibility overload is excluded + // from this constraint set as well. It is still generated below: either as a hidden + // overload that requires every parameter or as a visible replacement for this + // overload. var competingSignatures = preservedPreviousSignatures .Where(signature => !compatiblePreviousMethods.Any(method => ReferenceEquals(method.Signature, signature))) @@ -516,11 +516,10 @@ private bool TryBuildCompatibleMethodForPreviousContract( hideMethod, currentMethodSignatures: currentOverloadSignatures); - // Best effort: never emit a compatibility overload that a call could not resolve against a - // custom overload. A hidden overload requires every parameter, so it can only be rejected - // here by a custom overload with an identical parameter type list; every other shape is - // resolved by the argument count or the argument types. A rejected visible (reorder - // replacement) overload falls back to the hidden all-required overload instead. + // Best effort: never emit a compatibility overload that a call could not resolve against + // a custom overload. Returning false rejects only this candidate; the caller can still + // try the hidden all-required fallback for a rejected visible replacement and will keep + // processing other previous signatures. if (currentOverloadSignatures.Any(overload => MethodSignatureHelper.AreAmbiguous(signature, overload))) { CodeModelGenerator.Instance.Emitter.Debug( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index d78d8c12b20..806bdb6807a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -238,11 +238,14 @@ private static int GetMinimumRequiredParameterCount( } // Require only the prefix up to and including the first position whose parameter types - // distinguish the overloads. A type-name difference alone is insufficient because a - // null literal is applicable to any reference or nullable value type. + // distinguish the overloads. If every overlapping argument count already includes that + // distinguishing position, the overloads are resolved by argument types without raising + // the target's minimum argument count. A type-name difference alone is insufficient + // because a null literal is applicable to any reference or nullable value type. int overlappingParameterCount = Math.Min( targetMethodSignature.Parameters.Count, competingMethodSignature.Parameters.Count); + int minimumOverlappingArgumentCount = Math.Max(targetMinimumArgumentCount, competingMinimumArgumentCount); for (int i = 0; i < overlappingParameterCount; i++) { CSharpType targetType = targetMethodSignature.Parameters[i].Type; @@ -253,7 +256,9 @@ private static int GetMinimumRequiredParameterCount( && ((targetType.IsValueType && !targetType.IsNullable) || (competingType.IsValueType && !competingType.IsNullable))) { - return Math.Max(i + 1, targetMinimumArgumentCount); + return minimumOverlappingArgumentCount > i + ? 0 + : Math.Max(i + 1, targetMinimumArgumentCount); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs index a396ac72773..50ce9881921 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs @@ -284,13 +284,13 @@ public async Task BackCompatibility_ReorderedFullyOptionalParametersRequireAllPa _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [compatibilityModel], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix"), content); } // The previous overload mixes required and optional parameters. The hidden compatibility @@ -303,13 +303,13 @@ public async Task BackCompatibility_ReorderedPartiallyOptionalParametersRequireA _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [compatibilityModel], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters"), content); } [Test] @@ -338,14 +338,14 @@ public async Task BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibil _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [GetCompatibilityModel(includeCount: false)], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom"), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom", method: "BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters"), content); } // A reordered previous overload would normally replace the generated overload while keeping its @@ -385,7 +385,7 @@ public async Task BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenO m.Signature.Name == "CompatibilityModel" && m.Signature.Parameters[0].Name == "id"); Assert.IsEmpty(generatedMethod.Signature.Attributes); - Assert.IsTrue(generatedMethod.Signature.Parameters.Skip(1).All(p => p.DefaultValue is not null)); + Assert.IsTrue(generatedMethod.Signature.Parameters.All(p => p.DefaultValue is not null)); var content = new TypeProviderWriter(modelFactory).Write().Content; Assert.AreEqual(Helpers.GetExpectedFromFile(), content); @@ -457,8 +457,8 @@ public async Task BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedO _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom"), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom", method: "BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); @@ -682,13 +682,13 @@ public async Task BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOp _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality"), content); } // The management generator's ModelFactoryVisitor restores last-contract methods verbatim during @@ -751,13 +751,13 @@ public async Task BackCompatibility_MultiplePreviousOverloadsRequireAllParameter _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes"), content); } // Two overloads that shipped together in the published contract both become hidden @@ -776,12 +776,12 @@ public async Task BackCompatibility_CoexistingPreviousOverloadsRequireAllParamet _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality"), content); } // Same last-contract overloads as above but declared in the opposite order. Signatures are @@ -799,13 +799,13 @@ public async Task BackCompatibility_CoexistingPreviousOverloadsRequireAllParamet _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed"), content); } // An all-required previous overload is only callable at exactly its own argument count, so it @@ -884,7 +884,7 @@ public async Task BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibil _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs index c303650b672..f38f4384c01 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs @@ -9,7 +9,7 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, bool? flag = default, string name = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, bool? flag = default, string name = default) { return new global::Sample.Models.CompatibilityModel(id, flag, name, additionalBinaryDataProperties: null); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs index 0d0fe8b0e1b..93fe82418b5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs @@ -496,6 +496,25 @@ public void BuildBackCompatMethodSignature_PreservesDefaultsForInapplicableArgum Assert.IsNotNull(backCompatSignature.Parameters[0].DefaultValue); } + [Test] + public void RequireMinimumParameterPrefix_PreservesDefaultsWhenRequiredCompetitorIsTypeDistinguished() + { + var generatedSignature = CreateMethodSignature("CompatibilityModel", + new ParameterProvider("id", $"", typeof(string), defaultValue: Default), + new ParameterProvider("flag", $"", typeof(bool?), defaultValue: Default), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default)); + var customSignature = CreateMethodSignature("CompatibilityModel", + new ParameterProvider("flag", $"", typeof(bool)), + new ParameterProvider("id", $"", typeof(string), defaultValue: Default)); + + MethodSignatureHelper.RequireMinimumParameterPrefix( + generatedSignature, + [customSignature], + preservePublishedMinimumArgumentCount: false); + + Assert.That(generatedSignature.Parameters, Has.All.Property("DefaultValue").Not.Null); + } + [Test] public void BuildBackCompatMethodSignature_PreservesDefaultsForRefOutOverload() { From 0afc3de2d6ea9c71ab14a7d3452ab5270cd69bd3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:47:01 +0000 Subject: [PATCH 5/7] Address helper prefix review feedback Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Shared/MethodSignatureHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index 806bdb6807a..0f413c486bf 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -257,7 +257,7 @@ private static int GetMinimumRequiredParameterCount( || (competingType.IsValueType && !competingType.IsNullable))) { return minimumOverlappingArgumentCount > i - ? 0 + ? targetMinimumArgumentCount : Math.Max(i + 1, targetMinimumArgumentCount); } } From 3062a9132167c9804a329a02cf218cde20ab4590 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:58:01 +0000 Subject: [PATCH 6/7] Address model factory review follow-ups Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../src/Shared/MethodSignatureHelper.cs | 18 ++----- .../ModelFactoryProviderTests.cs | 48 +++++++++---------- .../SampleNamespaceModelFactory.cs | 6 ++- .../SampleNamespaceModelFactory.cs | 6 +-- ...rderedOverloadFallsBackToHiddenOverload.cs | 8 ++-- .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...gPreviousOverloadsRequireAllParameters.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...sOverloadsRequireAllParametersReversed.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...existWithRequiredCompatibilityOverload.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...refixOverloadKeepsGeneratedOptionality.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...ePreviousOverloadsRequireAllParameters.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 .../SampleNamespaceModelFactory.cs | 0 ...OptionalParametersRequireAllParameters.cs} | 0 .../SampleNamespaceModelFactory.cs | 0 ...OptionalParametersRequireAllParameters.cs} | 0 .../test/Shared/MethodSignatureHelperTests.cs | 19 -------- 24 files changed, 40 insertions(+), 65 deletions(-) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom) => BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last) => BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last) => BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs => BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last) => BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs => BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom) => BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last) => BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs => BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last) => BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs => BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last) => BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs => BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last) => BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last) => BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs => BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs} (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last) => BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)}/SampleNamespaceModelFactory.cs (100%) rename packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/{BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs => BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs} (100%) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index 0f413c486bf..60723397de5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -95,8 +95,8 @@ internal static MethodSignature BuildBackCompatMethodSignature( internal static bool AreAmbiguous(MethodSignature signature, MethodSignature otherSignature) { if (signature.Name != otherSignature.Name - || HasUnsupportedParameterKind(signature) - || HasUnsupportedParameterKind(otherSignature)) + || signature.Parameters.Any(p => p.IsRef || p.IsOut || p.IsParams) + || otherSignature.Parameters.Any(p => p.IsRef || p.IsOut || p.IsParams)) { return false; } @@ -134,9 +134,6 @@ internal static bool AreAmbiguous(MethodSignature signature, MethodSignature oth return false; } - private static bool HasUnsupportedParameterKind(MethodSignature signature) - => signature.Parameters.Any(p => p.IsRef || p.IsOut || p.IsParams); - /// /// Removes the default values from the leading parameters of so it /// can no longer be called with fewer arguments than the prefix that distinguishes it from @@ -238,14 +235,11 @@ private static int GetMinimumRequiredParameterCount( } // Require only the prefix up to and including the first position whose parameter types - // distinguish the overloads. If every overlapping argument count already includes that - // distinguishing position, the overloads are resolved by argument types without raising - // the target's minimum argument count. A type-name difference alone is insufficient - // because a null literal is applicable to any reference or nullable value type. + // distinguish the overloads. A type-name difference alone is insufficient because a + // null literal is applicable to any reference or nullable value type. int overlappingParameterCount = Math.Min( targetMethodSignature.Parameters.Count, competingMethodSignature.Parameters.Count); - int minimumOverlappingArgumentCount = Math.Max(targetMinimumArgumentCount, competingMinimumArgumentCount); for (int i = 0; i < overlappingParameterCount; i++) { CSharpType targetType = targetMethodSignature.Parameters[i].Type; @@ -256,9 +250,7 @@ private static int GetMinimumRequiredParameterCount( && ((targetType.IsValueType && !targetType.IsNullable) || (competingType.IsValueType && !competingType.IsNullable))) { - return minimumOverlappingArgumentCount > i - ? targetMinimumArgumentCount - : Math.Max(i + 1, targetMinimumArgumentCount); + return Math.Max(i + 1, targetMinimumArgumentCount); } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs index 50ce9881921..5356a891f46 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs @@ -284,13 +284,13 @@ public async Task BackCompatibility_ReorderedFullyOptionalParametersRequireAllPa _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [compatibilityModel], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix"), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } // The previous overload mixes required and optional parameters. The hidden compatibility @@ -303,13 +303,13 @@ public async Task BackCompatibility_ReorderedPartiallyOptionalParametersRequireA _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [compatibilityModel], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters"), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } [Test] @@ -338,28 +338,28 @@ public async Task BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibil _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [GetCompatibilityModel(includeCount: false)], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom", method: "BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters"), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters"))).Object; + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters"), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } // A reordered previous overload would normally replace the generated overload while keeping its // published optionality. Here that visible overload would be ambiguous with a custom overload // (both would substitute defaults for a single-argument call), so it is emitted as a hidden - // overload requiring every parameter instead. The generated overload only has to require the - // parameters that distinguish it from the custom overload, so it keeps its trailing defaults. + // overload requiring every parameter instead. The current generated overload is also + // constrained against the custom overload to avoid ambiguous source calls. [Test] public async Task BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload() { InputModelType model = InputFactory.Model("CompatibilityModel", properties: [ InputFactory.Property("Id", InputPrimitiveType.String), - InputFactory.Property("Flag", InputPrimitiveType.Boolean), + InputFactory.Property("Count", new InputNullableType(InputPrimitiveType.Int32)), InputFactory.Property("Name", InputPrimitiveType.String), ]); @@ -374,7 +374,7 @@ public async Task BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenO var compatibilityMethod = modelFactory.Methods.Single(m => m.Signature.Name == "CompatibilityModel" - && m.Signature.Parameters[0].Name == "flag"); + && m.Signature.Parameters[1].Name == "name"); Assert.AreEqual(1, compatibilityMethod.Signature.Attributes.Count); Assert.AreEqual( "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]", @@ -383,9 +383,9 @@ public async Task BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenO var generatedMethod = modelFactory.Methods.Single(m => m.Signature.Name == "CompatibilityModel" - && m.Signature.Parameters[0].Name == "id"); + && m.Signature.Parameters[1].Name == "count"); Assert.IsEmpty(generatedMethod.Signature.Attributes); - Assert.IsTrue(generatedMethod.Signature.Parameters.All(p => p.DefaultValue is not null)); + Assert.IsTrue(generatedMethod.Signature.Parameters.All(p => p.DefaultValue is null)); var content = new TypeProviderWriter(modelFactory).Write().Content; Assert.AreEqual(Helpers.GetExpectedFromFile(), content); @@ -457,8 +457,8 @@ public async Task BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedO _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom", method: "BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload"), - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload"))).Object; + compilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Custom"), + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); @@ -682,13 +682,13 @@ public async Task BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOp _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality"), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } // The management generator's ModelFactoryVisitor restores last-contract methods verbatim during @@ -751,13 +751,13 @@ public async Task BackCompatibility_MultiplePreviousOverloadsRequireAllParameter _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes"), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } // Two overloads that shipped together in the published contract both become hidden @@ -776,12 +776,12 @@ public async Task BackCompatibility_CoexistingPreviousOverloadsRequireAllParamet _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality"), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } // Same last-contract overloads as above but declared in the opposite order. Signatures are @@ -799,13 +799,13 @@ public async Task BackCompatibility_CoexistingPreviousOverloadsRequireAllParamet _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); var content = new TypeProviderWriter(modelFactory).Write().Content; - Assert.AreEqual(Helpers.GetExpectedFromFile(method: "BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed"), content); + Assert.AreEqual(Helpers.GetExpectedFromFile(), content); } // An all-required previous overload is only callable at exactly its own argument count, so it @@ -884,7 +884,7 @@ public async Task BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibil _instance = (await MockHelpers.LoadMockGeneratorAsync( inputNamespaceName: "Sample.Namespace", inputModelTypes: [model], - lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last", method: "BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments"))).Object; + lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync(parameters: "Last"))).Object; var modelFactory = _instance.OutputLibrary.ModelFactory.Value; modelFactory.ProcessTypeForBackCompatibility(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs index b513ebe4222..922b7867a8a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Custom)/SampleNamespaceModelFactory.cs @@ -5,8 +5,10 @@ namespace Sample.Namespace public static partial class SampleNamespaceModelFactory { public static CompatibilityModel CompatibilityModel( - bool flag, - string id = default) + string id, + string name = default, + string extra = default, + string other = default) { } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs index afb42249b5b..a3fc14f5394 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload(Last)/SampleNamespaceModelFactory.cs @@ -5,11 +5,11 @@ namespace Sample.Namespace public static partial class SampleNamespaceModelFactory { // A reordering of the current overload. Keeping it visible with its published optionality - // would make the single-argument call ambiguous with the custom overload. + // would make the single-argument call ambiguous with the custom overload below. public static CompatibilityModel CompatibilityModel( - bool flag = default, string id = default, - string name = default) + string name = default, + int? count = default) { } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs index f38f4384c01..9dc1a9e39cb 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_AmbiguousReorderedOverloadFallsBackToHiddenOverload.cs @@ -9,15 +9,15 @@ namespace Sample.Namespace { public static partial class SampleNamespaceModelFactory { - public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id = default, bool? flag = default, string name = default) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, int? count, string name) { - return new global::Sample.Models.CompatibilityModel(id, flag, name, additionalBinaryDataProperties: null); + return new global::Sample.Models.CompatibilityModel(id, count, name, additionalBinaryDataProperties: null); } [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] - public static global::Sample.Models.CompatibilityModel CompatibilityModel(bool flag, string id, string name) + public static global::Sample.Models.CompatibilityModel CompatibilityModel(string id, string name, int? count) { - return new global::Sample.Models.CompatibilityModel(id, flag, name, additionalBinaryDataProperties: null); + return new global::Sample.Models.CompatibilityModel(id, count, name, additionalBinaryDataProperties: null); } } } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Custom)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ChangedCustomOptionalityConstrainsGeneratedOverload(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionality.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParameters.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsKeepPublishedOptionalityReversed.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CoexistingPreviousOverloadsRequireAllParametersReversed.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Custom)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Custom)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsPreserveTrailingOptionalParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_CustomOverloadsCoexistWithRequiredCompatibilityOverload.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadPreservesTrailingOptionality.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_LongPositionalPrefixOverloadKeepsGeneratedOptionality.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireIndependentPrefixes.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_MultiplePreviousOverloadsRequireAllParameters.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadIsConstrainedToPreservePublishedNamedArguments(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_NewOverloadKeepsOptionalityAgainstCompatibilityOverload(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireMinimumPrefix.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedFullyOptionalParametersRequireAllParameters.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters(Last)/SampleNamespaceModelFactory.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters(Last)/SampleNamespaceModelFactory.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs similarity index 100% rename from packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedRequiredParametersPreserveTrailingOptionalParameters.cs rename to packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/TestData/ModelFactoryProviderTests/BackCompatibility_ReorderedPartiallyOptionalParametersRequireAllParameters.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs index 93fe82418b5..0d0fe8b0e1b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs @@ -496,25 +496,6 @@ public void BuildBackCompatMethodSignature_PreservesDefaultsForInapplicableArgum Assert.IsNotNull(backCompatSignature.Parameters[0].DefaultValue); } - [Test] - public void RequireMinimumParameterPrefix_PreservesDefaultsWhenRequiredCompetitorIsTypeDistinguished() - { - var generatedSignature = CreateMethodSignature("CompatibilityModel", - new ParameterProvider("id", $"", typeof(string), defaultValue: Default), - new ParameterProvider("flag", $"", typeof(bool?), defaultValue: Default), - new ParameterProvider("name", $"", typeof(string), defaultValue: Default)); - var customSignature = CreateMethodSignature("CompatibilityModel", - new ParameterProvider("flag", $"", typeof(bool)), - new ParameterProvider("id", $"", typeof(string), defaultValue: Default)); - - MethodSignatureHelper.RequireMinimumParameterPrefix( - generatedSignature, - [customSignature], - preservePublishedMinimumArgumentCount: false); - - Assert.That(generatedSignature.Parameters, Has.All.Property("DefaultValue").Not.Null); - } - [Test] public void BuildBackCompatMethodSignature_PreservesDefaultsForRefOutOverload() { From e3e28417244cb2a493c57ad9fbfb1787421ac9d0 Mon Sep 17 00:00:00 2001 From: Jorge Rangel Date: Wed, 2 Sep 2026 14:49:27 -0500 Subject: [PATCH 7/7] fix: correct reference type nullability handling in overload ambiguity checks AreAmbiguous treated IsNullable as significant for every type, but C# erases reference type nullability from a signature. A pair such as M(string?, string = default) and M(string, string = default, string = default) was reported as unambiguous even though M("a") is CS0121. Nullability is now only significant for value types, so int and int? stay distinguishable. Also: - Extract AreEquivalentParameterTypes and reuse it in GetMinimumRequiredParameterCount, which already relied on the same rule. - Restore the published default values when a compatibility overload candidate is rejected for ambiguity. BuildBackCompatMethodSignature strips defaults in place, so a rejected candidate previously left the previous signature partially required for later attempts. - Document that AreAmbiguous is sound but deliberately incomplete, and why ref/out/params pairs are reported as unambiguous. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 098ae4f6-52c2-41dc-a7aa-88f561b153e5 --- .../src/Providers/ModelFactoryProvider.cs | 14 +++++++ .../src/Shared/MethodSignatureHelper.cs | 40 +++++++++++++++---- .../test/Shared/MethodSignatureHelperTests.cs | 33 +++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs index dd68342f310..02905ca83f3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Providers/ModelFactoryProvider.cs @@ -511,6 +511,15 @@ private bool TryBuildCompatibleMethodForPreviousContract( return false; } + // BuildBackCompatMethodSignature strips defaults from previousMethod.Signature in place and + // the signature it returns shares those parameter instances, so capture the published + // defaults first and restore them if this candidate is rejected below. Without that, a + // rejected candidate would leave the previous signature partially required for every later + // attempt, which would over-constrain the fallback shapes. + var publishedDefaultValues = previousMethod.Signature.Parameters + .Select(parameter => parameter.DefaultValue) + .ToArray(); + var signature = MethodSignatureHelper.BuildBackCompatMethodSignature( previousMethod.Signature, hideMethod, @@ -522,6 +531,11 @@ private bool TryBuildCompatibleMethodForPreviousContract( // processing other previous signatures. if (currentOverloadSignatures.Any(overload => MethodSignatureHelper.AreAmbiguous(signature, overload))) { + for (int i = 0; i < publishedDefaultValues.Length; i++) + { + previousMethod.Signature.Parameters[i].DefaultValue = publishedDefaultValues[i]; + } + CodeModelGenerator.Instance.Emitter.Debug( $"Skipped model factory method '{Name}.{previousMethod.Signature.Name}' from last contract because it would be ambiguous with a custom overload.", BackCompatibilityChangeCategory.ModelFactoryMethodSkipped); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs index 60723397de5..5ea3e2058a2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/src/Shared/MethodSignatureHelper.cs @@ -89,11 +89,28 @@ internal static MethodSignature BuildBackCompatMethodSignature( /// /// Determines whether a call could bind to both and /// without overload resolution being able to prefer one of them. - /// Only argument counts that both signatures accept with the same parameter types are considered, - /// since any other call is resolved by the argument count or the argument types. /// + /// + /// Only argument counts that both signatures accept with an equivalent leading parameter type + /// sequence are considered. At those counts the two candidates are equally applicable and + /// convert their arguments identically, so the only remaining tie-breaker is whether a default + /// has to be substituted. + /// + /// This never reports a false positive, but it is deliberately incomplete: it cannot see an + /// ambiguity introduced past the equivalent prefix, for example when the first differing + /// positions both accept the null literal, or are connected by implicit conversions in + /// both directions such as decimal and float/double. Deciding those cases + /// requires real conversion analysis, which is tracked by + /// https://github.com/microsoft/typespec/issues/11805. + /// + /// internal static bool AreAmbiguous(MethodSignature signature, MethodSignature otherSignature) { + // ref and out arguments have to be spelled at the call site and a params overload can also + // be invoked in its expanded form, so the argument-count reasoning below does not model + // them. Reporting such a pair as unambiguous is the optimistic answer: it keeps the + // compatibility overload instead of dropping it, matching how the rest of the back-compat + // pipeline treats shapes it cannot reason about. Model factory signatures never use them. if (signature.Name != otherSignature.Name || signature.Parameters.Any(p => p.IsRef || p.IsOut || p.IsParams) || otherSignature.Parameters.Any(p => p.IsRef || p.IsOut || p.IsParams)) @@ -105,9 +122,9 @@ internal static bool AreAmbiguous(MethodSignature signature, MethodSignature oth int overlappingParameterCount = Math.Min(signature.Parameters.Count, otherSignature.Parameters.Count); while (equivalentParameterCount < overlappingParameterCount) { - CSharpType type = signature.Parameters[equivalentParameterCount].Type; - CSharpType otherType = otherSignature.Parameters[equivalentParameterCount].Type; - if (type.IsNullable != otherType.IsNullable || !type.AreNamesEqual(otherType)) + if (!AreEquivalentParameterTypes( + signature.Parameters[equivalentParameterCount].Type, + otherSignature.Parameters[equivalentParameterCount].Type)) { break; } @@ -134,6 +151,16 @@ internal static bool AreAmbiguous(MethodSignature signature, MethodSignature oth return false; } + /// + /// Determines whether two parameter types are indistinguishable to overload resolution at the + /// same position. Nullability is only part of a signature for value types: int and + /// int? are different parameter types, while C# erases reference type nullability so + /// string and string? are the same one. + /// + private static bool AreEquivalentParameterTypes(CSharpType type, CSharpType otherType) + => (!type.IsValueType || type.IsNullable == otherType.IsNullable) + && type.AreNamesEqual(otherType); + /// /// Removes the default values from the leading parameters of so it /// can no longer be called with fewer arguments than the prefix that distinguishes it from @@ -244,8 +271,7 @@ private static int GetMinimumRequiredParameterCount( { CSharpType targetType = targetMethodSignature.Parameters[i].Type; CSharpType competingType = competingMethodSignature.Parameters[i].Type; - bool sameType = targetType.IsNullable == competingType.IsNullable - && targetType.AreNamesEqual(competingType); + bool sameType = AreEquivalentParameterTypes(targetType, competingType); if (!sameType && ((targetType.IsValueType && !targetType.IsNullable) || (competingType.IsValueType && !competingType.IsNullable))) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs index 0d0fe8b0e1b..fb999456636 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Shared/MethodSignatureHelperTests.cs @@ -658,6 +658,39 @@ public void AreAmbiguous_ParamsOverload_ReturnsFalse() Assert.IsFalse(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); } + // C# erases reference type nullability from a signature, so 'string?' and 'string' are the + // same parameter type. Treating them as different would hide the real ambiguity of + // Method("a") between these two overloads. + [Test] + public void AreAmbiguous_ReferenceTypeNullabilityDifference_ReturnsTrue() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", new CSharpType(typeof(string), isNullable: true)), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default)); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("id", $"", typeof(string)), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default), + new ParameterProvider("extra", $"", typeof(string), defaultValue: Default)); + + Assert.IsTrue(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + + // Nullability of a value type is part of the signature, so 'int' and 'int?' remain + // distinguishable and Method(1) prefers the 'int' overload. + [Test] + public void AreAmbiguous_ValueTypeNullabilityDifference_ReturnsFalse() + { + var signature = CreateMethodSignature("Method", + new ParameterProvider("count", $"", typeof(int?)), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default)); + var otherSignature = CreateMethodSignature("Method", + new ParameterProvider("count", $"", typeof(int)), + new ParameterProvider("name", $"", typeof(string), defaultValue: Default), + new ParameterProvider("extra", $"", typeof(string), defaultValue: Default)); + + Assert.IsFalse(MethodSignatureHelper.AreAmbiguous(signature, otherSignature)); + } + private static MethodSignature CreateMethodSignature( string name, params ParameterProvider[] parameters)