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..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 @@ -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 + // 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))) .Where(signature => !MethodSignatureHelper.HaveSameParametersInSameOrder( currentMethod.Signature, signature)) @@ -505,14 +511,42 @@ 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, + currentMethodSignatures: currentOverloadSignatures); + + // 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))) + { + 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); + 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 +557,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..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 @@ -76,11 +76,91 @@ 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 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)) + { + return false; + } + + int equivalentParameterCount = 0; + int overlappingParameterCount = Math.Min(signature.Parameters.Count, otherSignature.Parameters.Count); + while (equivalentParameterCount < overlappingParameterCount) + { + if (!AreEquivalentParameterTypes( + signature.Parameters[equivalentParameterCount].Type, + otherSignature.Parameters[equivalentParameterCount].Type)) + { + 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; + } + + /// + /// 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 @@ -191,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/Providers/ModelFactories/ModelFactoryProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator/test/Providers/ModelFactories/ModelFactoryProviderTests.cs index 0eba7a1a42f..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 @@ -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,49 @@ 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. 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("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[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 generatedMethod = modelFactory.Methods.Single(m => + m.Signature.Name == "CompatibilityModel" + && m.Signature.Parameters[1].Name == "count"); + Assert.IsEmpty(generatedMethod.Signature.Attributes); + Assert.IsTrue(generatedMethod.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 +444,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 +463,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 +640,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 +665,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 +734,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 +760,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 +787,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 +837,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 +866,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 +896,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 +1002,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 +1336,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..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 @@ -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,173 @@ 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)); + } + + // 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)