-
Notifications
You must be signed in to change notification settings - Fork 385
Preserve model factory back-compat parameter optionality #11703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1117731
87e3d31
b1d8764
52eb9bd
1320ecc
b08c9d8
9c58f47
94cc781
684de9a
d952b09
4ecb8fa
9d7df2b
c4d1cde
844339a
890dd3e
55b2467
4dd3d00
94550ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,6 +102,7 @@ protected internal sealed override IReadOnlyList<MethodProvider> BuildMethodsFor | |
| return [.. originalMethods]; | ||
| } | ||
|
|
||
| IReadOnlyList<MethodProvider> customFactoryMethods = CustomCodeView?.Methods ?? []; | ||
| List<MethodProvider> factoryMethods = [.. originalMethods]; | ||
|
|
||
| // Preserve the original parameter names on current factory methods when the only | ||
|
|
@@ -110,18 +111,48 @@ protected internal sealed override IReadOnlyList<MethodProvider> BuildMethodsFor | |
| // property is renamed via @@clientName, spec rename, or naming-rule change). | ||
| BackCompatHelper.RestorePreviousParameterNames(this, factoryMethods); | ||
|
|
||
| HashSet<MethodSignature> currentMethodSignatures = new List<MethodProvider>([.. factoryMethods, .. CustomCodeView?.Methods ?? []]) | ||
| .Select(m => m.Signature) | ||
| .ToHashSet(MethodSignature.MethodSignatureComparer); | ||
| var allFactoryMethods = factoryMethods | ||
| .Concat(customFactoryMethods) | ||
| .ToList(); | ||
| HashSet<MethodSignature> currentMethodSignatures = allFactoryMethods | ||
| .Select(m => m.Signature) | ||
| .ToHashSet(MethodSignature.MethodSignatureComparer); | ||
|
|
||
| var compatiblePreviousMethods = new List<MethodProvider>(); | ||
| HashSet<MethodSignature> previousPublicSignatures = new(MethodSignature.MethodSignatureComparer); | ||
| foreach (var previousMethod in LastContractView.Methods) | ||
| { | ||
| if (!MethodSignatureHelper.IsPublicApi(previousMethod.Signature.Modifiers) || | ||
| currentMethodSignatures.Contains(previousMethod.Signature)) | ||
| if (!MethodSignatureHelper.IsPublicApi(previousMethod.Signature.Modifiers)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| // Record every public previous signature, including the ones skipped below, because | ||
| // a current overload that still matches one of them must never be removed. | ||
| previousPublicSignatures.Add(previousMethod.Signature); | ||
|
|
||
| if (currentMethodSignatures.Contains(previousMethod.Signature)) | ||
| { | ||
| // A method with this signature is already present, but it may have been restored | ||
| // from the last contract by a library visitor before this pass ran. | ||
| var restoredOverload = factoryMethods.FirstOrDefault(m => | ||
| MethodSignature.MethodSignatureComparer.Equals(m.Signature, previousMethod.Signature) | ||
| && m.Signature.Attributes.Any(a => | ||
| a.Type is { IsFrameworkType: true } | ||
| && a.Type.FrameworkType == typeof(System.ComponentModel.EditorBrowsableAttribute))); | ||
| if (restoredOverload is not null) | ||
| { | ||
| MethodSignatureHelper.RequireMinimumParameterPrefix( | ||
| restoredOverload.Signature, | ||
| GetCurrentOverloadSignatures( | ||
| allFactoryMethods, | ||
| restoredOverload.Signature.Name, | ||
| restoredOverload)); | ||
| } | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
| // If the removal of this factory method has already been accepted in the ApiCompat | ||
| // baseline, honor that decision and do not resurrect a compatibility shim for it. | ||
| if (BackCompatHelper.IsMethodRemovalAcceptedInBaseline(this, previousMethod.Signature)) | ||
|
|
@@ -138,55 +169,85 @@ protected internal sealed override IReadOnlyList<MethodProvider> BuildMethodsFor | |
| continue; | ||
| } | ||
|
|
||
| compatiblePreviousMethods.Add(previousMethod); | ||
| } | ||
|
|
||
| foreach (var previousMethod in compatiblePreviousMethods) | ||
| { | ||
| List<MethodSignature> currentOverloads = []; | ||
| bool foundCompatibleOverload = false; | ||
| var currentOverloadSignatures = GetCurrentOverloadSignatures( | ||
| allFactoryMethods, | ||
| previousMethod.Signature.Name); | ||
|
|
||
| // Attempt to find an updated method in the current contract to call | ||
| foreach (var currentMethodSignature in currentMethodSignatures) | ||
| foreach (var currentMethodSignature in currentOverloadSignatures) | ||
| { | ||
| if (currentMethodSignature.Name.Equals(previousMethod.Signature.Name)) | ||
| if (MethodSignatureHelper.HaveSameParametersInSameOrder(currentMethodSignature, previousMethod.Signature)) | ||
| { | ||
| if (MethodSignatureHelper.HaveSameParametersInSameOrder(currentMethodSignature, previousMethod.Signature)) | ||
| { | ||
| foundCompatibleOverload = true; | ||
| break; | ||
| } | ||
|
|
||
| currentOverloads.Add(currentMethodSignature); | ||
| foundCompatibleOverload = true; | ||
| break; | ||
| } | ||
|
|
||
| currentOverloads.Add(currentMethodSignature); | ||
| } | ||
|
|
||
| if (foundCompatibleOverload) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| var compatibilityOverloadSignatures = GetCompatibilityOverloadSignatures( | ||
| currentOverloadSignatures, | ||
| compatiblePreviousMethods, | ||
| previousMethod); | ||
|
|
||
| foreach (var currentOverload in currentOverloads) | ||
| { | ||
| // If the parameter ordering is the only difference, just use the previous method | ||
| // If the parameter ordering is the only difference, just use the previous method. | ||
| if (MethodSignatureHelper.ContainsSameParameters(previousMethod.Signature, currentOverload) | ||
| && TryBuildCompatibleMethodForPreviousContract(previousMethod, currentOverload, false, out MethodProvider? replacedMethod)) | ||
| && !previousPublicSignatures.Contains(currentOverload)) | ||
| { | ||
| factoryMethods.Add(replacedMethod); | ||
|
|
||
| var factoryMethodToRemove = factoryMethods | ||
| .FirstOrDefault(m => MethodSignature.MethodSignatureComparer.Equals(m.Signature, currentOverload)); | ||
| if (factoryMethodToRemove != null) | ||
| var coexistingOverloads = GetCurrentOverloadSignatures( | ||
| allFactoryMethods, | ||
| previousMethod.Signature.Name, | ||
| factoryMethodToRemove); | ||
| var coexistingCompatibilityOverloads = GetCompatibilityOverloadSignatures( | ||
| coexistingOverloads, | ||
| compatiblePreviousMethods, | ||
| previousMethod); | ||
| if (TryBuildCompatibleMethodForPreviousContract( | ||
| previousMethod, | ||
| currentOverload, | ||
| false, | ||
| coexistingCompatibilityOverloads, | ||
| out MethodProvider? replacedMethod)) | ||
| { | ||
| factoryMethods.Remove(factoryMethodToRemove); | ||
| } | ||
| factoryMethods.Add(replacedMethod); | ||
|
|
||
| CodeModelGenerator.Instance.Emitter.Debug( | ||
| $"Replaced model factory method '{Name}.{currentOverload.Name}' with previous parameter order from last contract.", | ||
| BackCompatibilityChangeCategory.ModelFactoryMethodReplaced); | ||
| if (factoryMethodToRemove != null) | ||
| { | ||
| factoryMethods.Remove(factoryMethodToRemove); | ||
| } | ||
|
|
||
| foundCompatibleOverload = true; | ||
| break; | ||
| CodeModelGenerator.Instance.Emitter.Debug( | ||
| $"Replaced model factory method '{Name}.{currentOverload.Name}' with previous parameter order from last contract.", | ||
| BackCompatibilityChangeCategory.ModelFactoryMethodReplaced); | ||
| foundCompatibleOverload = true; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (TryBuildCompatibleMethodForPreviousContract(previousMethod, currentOverload, true, out replacedMethod)) | ||
| if (TryBuildCompatibleMethodForPreviousContract( | ||
| previousMethod, | ||
| currentOverload, | ||
| true, | ||
| compatibilityOverloadSignatures, | ||
| out var hiddenMethod)) | ||
| { | ||
| factoryMethods.Add(replacedMethod); | ||
| factoryMethods.Add(hiddenMethod); | ||
| CodeModelGenerator.Instance.Emitter.Debug( | ||
| $"Added back-compat overload for model factory method '{Name}.{previousMethod.Signature.Name}' delegating to '{currentOverload.Name}'.", | ||
| BackCompatibilityChangeCategory.ModelFactoryMethodAdded); | ||
|
|
@@ -201,7 +262,12 @@ protected internal sealed override IReadOnlyList<MethodProvider> BuildMethodsFor | |
| } | ||
|
|
||
| // If no compatible overload found, try to add the previous method by instantiating the model directly. | ||
| if (TryBuildCompatibleMethodForPreviousContract(previousMethod, null, true, out var builtMethod)) | ||
| if (TryBuildCompatibleMethodForPreviousContract( | ||
| previousMethod, | ||
| null, | ||
| true, | ||
| compatibilityOverloadSignatures, | ||
| out var builtMethod)) | ||
| { | ||
| factoryMethods.Add(builtMethod); | ||
| CodeModelGenerator.Instance.Emitter.Debug( | ||
|
|
@@ -219,6 +285,35 @@ protected internal sealed override IReadOnlyList<MethodProvider> BuildMethodsFor | |
| return [.. factoryMethods]; | ||
| } | ||
|
|
||
| private static IReadOnlyList<MethodSignature> GetCompatibilityOverloadSignatures( | ||
| IReadOnlyList<MethodSignature> currentOverloadSignatures, | ||
| IReadOnlyList<MethodProvider> compatiblePreviousMethods, | ||
| MethodProvider previousMethod) | ||
| { | ||
| HashSet<MethodSignature> overloadSignatures = new(MethodSignature.MethodSignatureComparer); | ||
| foreach (var compatiblePreviousMethod in compatiblePreviousMethods) | ||
| { | ||
| if (!ReferenceEquals(compatiblePreviousMethod, previousMethod)) | ||
| { | ||
| overloadSignatures.Add(compatiblePreviousMethod.Signature); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Do not treat last-contract overloads as new competitors Adding every other --generated by Copilot |
||
| } | ||
| } | ||
|
|
||
| overloadSignatures.UnionWith(currentOverloadSignatures); | ||
| return [.. overloadSignatures]; | ||
| } | ||
|
|
||
| private IReadOnlyList<MethodSignature> GetCurrentOverloadSignatures( | ||
| IEnumerable<MethodProvider> methods, | ||
| string methodName, | ||
| MethodProvider? methodToExclude = null) | ||
| { | ||
| return methods | ||
| .Where(m => (methodToExclude is null || !ReferenceEquals(m, methodToExclude)) && m.Signature.Name == methodName) | ||
| .Select(m => m.Signature) | ||
| .ToList(); | ||
| } | ||
|
|
||
| internal static IReadOnlyList<string> GetUnavailableSignatureTypes(MethodSignature signature) | ||
| { | ||
| var unavailableTypes = new HashSet<string>(StringComparer.Ordinal); | ||
|
|
@@ -310,6 +405,7 @@ private bool TryBuildCompatibleMethodForPreviousContract( | |
| MethodProvider previousMethod, | ||
| MethodSignature? currentMethodSignature, | ||
| bool hideMethod, | ||
| IReadOnlyList<MethodSignature> currentOverloadSignatures, | ||
| [NotNullWhen(true)] out MethodProvider? builtMethod) | ||
| { | ||
| builtMethod = null; | ||
|
|
@@ -345,7 +441,10 @@ private bool TryBuildCompatibleMethodForPreviousContract( | |
| { | ||
| var callToOverload = Return(new InvokeMethodExpression(null, currentMethodSignature, arguments)); | ||
| builtMethod = new MethodProvider( | ||
| MethodSignatureHelper.BuildBackCompatMethodSignature(previousMethod.Signature, hideMethod), | ||
| MethodSignatureHelper.BuildBackCompatMethodSignature( | ||
| previousMethod.Signature, | ||
| hideMethod, | ||
| currentMethodSignatures: currentOverloadSignatures), | ||
| callToOverload, | ||
| this, | ||
| previousMethod.XmlDocs); | ||
|
|
@@ -356,7 +455,10 @@ private bool TryBuildCompatibleMethodForPreviousContract( | |
| MethodBodyStatements body = ConstructMethodBody(previousMethod.Signature, modelToInstantiate); | ||
|
|
||
| builtMethod = new MethodProvider( | ||
| MethodSignatureHelper.BuildBackCompatMethodSignature(previousMethod.Signature, hideMethod), | ||
| MethodSignatureHelper.BuildBackCompatMethodSignature( | ||
| previousMethod.Signature, | ||
| hideMethod, | ||
| currentMethodSignatures: currentOverloadSignatures), | ||
| body, | ||
| this, | ||
| previousMethod.XmlDocs); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.