Skip to content

Commit 587b27c

Browse files
authored
Relax signature collision rules for static extension methods (#77603)
Specifically, two static extension methods are allowed to have the same signature (according to the current language rules) as long as all the following conditions are met: - methods have different return type and/or different return ref-ness - methods extending different types (ref-ness of the receiver parameter is not considered)
1 parent a10af89 commit 587b27c

File tree

3 files changed

+1014
-5
lines changed

3 files changed

+1014
-5
lines changed

src/Compilers/CSharp/Portable/Symbols/MemberSignatureComparer.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol>
101101
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
102102
typeComparison: TypeCompareKind.AllIgnoreOptions);
103103

104+
/// <summary>
105+
/// Same as <see cref="DuplicateSourceComparer"/>, but also considers return type_and_ref-ness differences
106+
/// </summary>
107+
public static readonly MemberSignatureComparer DuplicateSourceWithReturnComparer = new MemberSignatureComparer(
108+
considerName: true,
109+
considerExplicitlyImplementedInterfaces: true,
110+
considerReturnType: true,
111+
considerTypeConstraints: false,
112+
considerCallingConvention: false,
113+
refKindCompareMode: RefKindCompareMode.DoNotConsiderDifferences,
114+
typeComparison: TypeCompareKind.AllIgnoreOptions);
115+
104116
/// <summary>
105117
/// This instance is used to determine if some API specific to records is explicitly declared.
106118
/// It is the same as <see cref="DuplicateSourceComparer"/> except it considers ref kinds as well.

src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1972,6 +1972,9 @@ private void CheckMemberNameConflicts(BindingDiagnosticBag diagnostics)
19721972
var conversionsAsMethods = new Dictionary<MethodSymbol, SourceMemberMethodSymbol>(MemberSignatureComparer.DuplicateSourceComparer);
19731973
var conversionsAsConversions = new HashSet<SourceUserDefinedConversionSymbol>(ConversionSignatureComparer.Comparer);
19741974

1975+
Dictionary<MethodSymbol, OneOrMany<MethodSymbol>>? staticExtensionImplementationsWithoutReturn = null;
1976+
Dictionary<MethodSymbol, MethodSymbol>? staticExtensionImplementationsWithReturn = null;
1977+
19751978
// SPEC: The signature of an operator must differ from the signatures of all other
19761979
// SPEC: operators declared in the same class.
19771980

@@ -2113,11 +2116,89 @@ private void CheckMemberNameConflicts(BindingDiagnosticBag diagnostics)
21132116
{
21142117
ReportMethodSignatureCollision(diagnostics, conversion, previousMethod);
21152118
}
2119+
2120+
if (staticExtensionImplementationsWithoutReturn?.TryGetValue(conversion, out OneOrMany<MethodSymbol> previousStaticExtension) == true)
2121+
{
2122+
Debug.Assert(false); // At the moment this code path is not reachable because implementation methods always come after all user defined methods
2123+
ReportMethodSignatureCollision(diagnostics, conversion, previousStaticExtension.First());
2124+
}
2125+
21162126
// Do not add the conversion to the set of previously-seen methods; that set
21172127
// is only non-conversion methods.
21182128
}
2129+
else if (symbol is SourceExtensionImplementationMethodSymbol { UnderlyingMethod.IsStatic: true } staticExtension)
2130+
{
2131+
staticExtensionImplementationsWithReturn ??= new Dictionary<MethodSymbol, MethodSymbol>(MemberSignatureComparer.DuplicateSourceWithReturnComparer);
2132+
2133+
// Does this method collide with any previously-seen static extension implementation?
2134+
if (staticExtensionImplementationsWithReturn.TryGetValue(staticExtension, out var fullMatch))
2135+
{
2136+
ReportMethodSignatureCollision(diagnostics, staticExtension, fullMatch);
2137+
}
2138+
else
2139+
{
2140+
staticExtensionImplementationsWithReturn.Add(staticExtension, staticExtension);
2141+
2142+
staticExtensionImplementationsWithoutReturn ??= new Dictionary<MethodSymbol, OneOrMany<MethodSymbol>>(MemberSignatureComparer.DuplicateSourceComparer);
2143+
2144+
if (staticExtensionImplementationsWithoutReturn.TryGetValue(staticExtension, out OneOrMany<MethodSymbol> previousStaticExtension))
2145+
{
2146+
NamedTypeSymbol extension = staticExtension.UnderlyingMethod.ContainingType;
2147+
var extensionParameter = extension.ExtensionParameter;
2148+
bool foundExtendedTypeConflict = false;
2149+
2150+
if (extensionParameter is not null)
2151+
{
2152+
foreach (SourceExtensionImplementationMethodSymbol partialMatch in previousStaticExtension)
2153+
{
2154+
NamedTypeSymbol partialMatchExtension = partialMatch.UnderlyingMethod.ContainingType;
2155+
if (extension.Arity != partialMatchExtension.Arity ||
2156+
partialMatchExtension.ExtensionParameter is not { } partialMatchExtensionParameter)
2157+
{
2158+
continue;
2159+
}
2160+
2161+
if (extensionParameter.Type.Equals(
2162+
extension.Arity == 0 ?
2163+
partialMatchExtensionParameter.Type :
2164+
(new TypeMap(partialMatchExtension.TypeParameters, extension.TypeParameters)).SubstituteType(partialMatchExtensionParameter.Type).Type,
2165+
TypeCompareKind.AllIgnoreOptions))
2166+
{
2167+
foundExtendedTypeConflict = true;
2168+
ReportMethodSignatureCollision(diagnostics, staticExtension, partialMatch);
2169+
break;
2170+
}
2171+
}
2172+
}
2173+
2174+
if (!foundExtendedTypeConflict)
2175+
{
2176+
staticExtensionImplementationsWithoutReturn[staticExtension] = previousStaticExtension.Add(staticExtension);
2177+
}
2178+
}
2179+
else
2180+
{
2181+
staticExtensionImplementationsWithoutReturn.Add(staticExtension, OneOrMany.Create<MethodSymbol>(staticExtension));
2182+
}
2183+
}
2184+
2185+
// Does this static extension implementation collide *as a method* with any previously-seen
2186+
// non-static-extension-implementation method?
2187+
2188+
if (methodsBySignature.TryGetValue(staticExtension, out var previousMethod))
2189+
{
2190+
ReportMethodSignatureCollision(diagnostics, staticExtension, previousMethod);
2191+
}
2192+
2193+
if (conversionsAsMethods.TryGetValue(staticExtension, out var previousConversion))
2194+
{
2195+
ReportMethodSignatureCollision(diagnostics, staticExtension, previousConversion);
2196+
}
2197+
}
21192198
else if (symbol is MethodSymbol method && method is (SourceMemberMethodSymbol or SourceExtensionImplementationMethodSymbol))
21202199
{
2200+
Debug.Assert(method is not SourceExtensionImplementationMethodSymbol { UnderlyingMethod.IsStatic: true });
2201+
21212202
// Does this method collide *as a method* with any previously-seen
21222203
// conversion?
21232204

@@ -2128,7 +2209,15 @@ private void CheckMemberNameConflicts(BindingDiagnosticBag diagnostics)
21282209
// Do not add the method to the set of previously-seen conversions.
21292210

21302211
// Does this method collide *as a method* with any previously-seen
2131-
// non-conversion method?
2212+
// static extension implementation method?
2213+
2214+
if (staticExtensionImplementationsWithoutReturn?.TryGetValue(method, out OneOrMany<MethodSymbol> previousStaticExtension) == true)
2215+
{
2216+
ReportMethodSignatureCollision(diagnostics, method, previousStaticExtension.First());
2217+
}
2218+
2219+
// Does this method collide *as a method* with any previously-seen
2220+
// non-conversion or non-static-extension-implementation method?
21322221

21332222
if (methodsBySignature.TryGetValue(method, out var previousMethod))
21342223
{

0 commit comments

Comments
 (0)