Skip to content

Commit 73de482

Browse files
authored
Address some known functionality gaps for extension operators (#79167)
Related to #78968. Related to #76130.
1 parent ad0a3e5 commit 73de482

File tree

4 files changed

+2299
-319
lines changed

4 files changed

+2299
-319
lines changed

src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs

Lines changed: 111 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,33 +1092,49 @@ private bool BindSimpleBinaryOperatorParts(BinaryExpressionSyntax node, BindingD
10921092
out LookupResultKind resultKind, out ImmutableArray<MethodSymbol> originalUserDefinedOperators,
10931093
out BinaryOperatorSignature resultSignature, out BinaryOperatorAnalysisResult best)
10941094
{
1095-
bool foundOperator;
1096-
10971095
if (!IsTypelessExpressionAllowedInBinaryOperator(kind, left, right))
10981096
{
10991097
resultKind = LookupResultKind.OverloadResolutionFailure;
11001098
originalUserDefinedOperators = default(ImmutableArray<MethodSymbol>);
11011099
best = default(BinaryOperatorAnalysisResult);
1100+
resultSignature = new BinaryOperatorSignature(kind, leftType: null, rightType: null, CreateErrorType());
1101+
return false;
11021102
}
1103-
else
1104-
{
1105-
best = this.BinaryOperatorOverloadResolution(kind, isChecked: CheckOverflowAtRuntime, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators);
1106-
}
11071103

1108-
// However, as an implementation detail, we never "fail to find an applicable
1109-
// operator" during overload resolution if we have x == null, x == default, etc. We always
1110-
// find at least the reference conversion object == object; the overload resolution
1111-
// code does not reject that. Therefore what we should do is only bind
1112-
// "x == null" as a nullable-to-null comparison if overload resolution chooses
1113-
// the reference conversion.
1104+
bool isChecked = CheckOverflowAtRuntime;
1105+
OverloadResolution.GetStaticUserDefinedBinaryOperatorMethodNames(kind, isChecked, out string name1, out string name2Opt);
1106+
best = this.BinaryOperatorOverloadResolution(kind, isChecked, name1, name2Opt, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators);
1107+
1108+
return bindSimpleBinaryOperatorPartsContinue(node, diagnostics, left, right, kind, ref resultKind, ref originalUserDefinedOperators, out resultSignature, ref best, isChecked, name1, name2Opt);
11141109

1115-
if (!best.HasValue)
1116-
{
1117-
resultSignature = new BinaryOperatorSignature(kind, leftType: null, rightType: null, CreateErrorType());
1118-
foundOperator = false;
1119-
}
1120-
else
1110+
bool bindSimpleBinaryOperatorPartsContinue(
1111+
BinaryExpressionSyntax node,
1112+
BindingDiagnosticBag diagnostics,
1113+
BoundExpression left,
1114+
BoundExpression right,
1115+
BinaryOperatorKind kind,
1116+
ref LookupResultKind resultKind,
1117+
ref ImmutableArray<MethodSymbol> originalUserDefinedOperators,
1118+
out BinaryOperatorSignature resultSignature,
1119+
ref BinaryOperatorAnalysisResult best,
1120+
bool isChecked,
1121+
string name1,
1122+
string name2Opt)
11211123
{
1124+
// However, as an implementation detail, we never "fail to find an applicable
1125+
// operator" during overload resolution if we have x == null, x == default, etc. We always
1126+
// find at least the reference conversion object == object; the overload resolution
1127+
// code does not reject that. Therefore what we should do is only bind
1128+
// "x == null" as a nullable-to-null comparison if overload resolution chooses
1129+
// the reference conversion.
1130+
1131+
if (!best.HasValue)
1132+
{
1133+
resultSignature = new BinaryOperatorSignature(kind, leftType: null, rightType: null, CreateErrorType());
1134+
return false;
1135+
}
1136+
1137+
bool foundOperator;
11221138
var signature = best.Signature;
11231139

11241140
if (signature.Method is { } bestMethod)
@@ -1155,9 +1171,27 @@ private bool BindSimpleBinaryOperatorParts(BinaryExpressionSyntax node, BindingD
11551171
bool rightDefault = right.IsLiteralDefault();
11561172
foundOperator = !isObjectEquality || BuiltInOperators.IsValidObjectEquality(Conversions, leftType, leftNull, leftDefault, rightType, rightNull, rightDefault, ref useSiteInfo);
11571173
diagnostics.Add(node, useSiteInfo);
1174+
1175+
if (!foundOperator)
1176+
{
1177+
Debug.Assert(isObjectEquality);
1178+
1179+
// Try extension operators since predefined object equality was not applicable
1180+
LookupResultKind extensionResultKind;
1181+
ImmutableArray<MethodSymbol> extensionOriginalUserDefinedOperators;
1182+
BinaryOperatorAnalysisResult? extensionBest = BinaryOperatorExtensionOverloadResolution(kind, isChecked, name1, name2Opt, left, right, node, diagnostics, out extensionResultKind, out extensionOriginalUserDefinedOperators);
1183+
1184+
if (extensionBest.HasValue)
1185+
{
1186+
best = extensionBest.GetValueOrDefault();
1187+
resultKind = extensionResultKind;
1188+
originalUserDefinedOperators = extensionOriginalUserDefinedOperators;
1189+
foundOperator = bindSimpleBinaryOperatorPartsContinue(node, diagnostics, left, right, kind, ref resultKind, ref originalUserDefinedOperators, out resultSignature, ref best, isChecked, name1, name2Opt);
1190+
}
1191+
}
11581192
}
1193+
return foundOperator;
11591194
}
1160-
return foundOperator;
11611195
}
11621196

11631197
#nullable enable
@@ -1914,13 +1948,28 @@ private BinaryOperatorAnalysisResult BinaryOperatorOverloadResolution(
19141948
{
19151949
OverloadResolution.GetStaticUserDefinedBinaryOperatorMethodNames(kind, isChecked, out string name1, out string name2Opt);
19161950

1951+
return BinaryOperatorOverloadResolution(kind, isChecked, name1, name2Opt, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators);
1952+
}
1953+
1954+
private BinaryOperatorAnalysisResult BinaryOperatorOverloadResolution(
1955+
BinaryOperatorKind kind,
1956+
bool isChecked,
1957+
string name1,
1958+
string name2Opt,
1959+
BoundExpression left,
1960+
BoundExpression right,
1961+
CSharpSyntaxNode node,
1962+
BindingDiagnosticBag diagnostics,
1963+
out LookupResultKind resultKind,
1964+
out ImmutableArray<MethodSymbol> originalUserDefinedOperators)
1965+
{
19171966
BinaryOperatorAnalysisResult possiblyBest = BinaryOperatorNonExtensionOverloadResolution(kind, isChecked, name1, name2Opt, left, right, node, diagnostics, out resultKind, out originalUserDefinedOperators);
19181967

19191968
if (!possiblyBest.HasValue && resultKind != LookupResultKind.Ambiguous)
19201969
{
19211970
LookupResultKind extensionResultKind;
19221971
ImmutableArray<MethodSymbol> extensionOriginalUserDefinedOperators;
1923-
BinaryOperatorAnalysisResult? extensionBest = extensionOverloadResolution(kind, isChecked, name1, name2Opt, left, right, node, diagnostics, out extensionResultKind, out extensionOriginalUserDefinedOperators);
1972+
BinaryOperatorAnalysisResult? extensionBest = BinaryOperatorExtensionOverloadResolution(kind, isChecked, name1, name2Opt, left, right, node, diagnostics, out extensionResultKind, out extensionOriginalUserDefinedOperators);
19241973

19251974
if (extensionBest.HasValue && (extensionBest.GetValueOrDefault().HasValue || (originalUserDefinedOperators.IsEmpty && !extensionOriginalUserDefinedOperators.IsEmpty)))
19261975
{
@@ -1931,55 +1980,55 @@ private BinaryOperatorAnalysisResult BinaryOperatorOverloadResolution(
19311980
}
19321981

19331982
return possiblyBest;
1983+
}
19341984

1935-
#nullable enable
1985+
#nullable enable
19361986

1937-
BinaryOperatorAnalysisResult? extensionOverloadResolution(
1938-
BinaryOperatorKind kind,
1939-
bool isChecked,
1940-
string name1,
1941-
string name2Opt,
1942-
BoundExpression left,
1943-
BoundExpression right,
1944-
CSharpSyntaxNode node,
1945-
BindingDiagnosticBag diagnostics,
1946-
out LookupResultKind resultKind,
1947-
out ImmutableArray<MethodSymbol> originalUserDefinedOperators)
1987+
private BinaryOperatorAnalysisResult? BinaryOperatorExtensionOverloadResolution(
1988+
BinaryOperatorKind kind,
1989+
bool isChecked,
1990+
string name1,
1991+
string name2Opt,
1992+
BoundExpression left,
1993+
BoundExpression right,
1994+
CSharpSyntaxNode node,
1995+
BindingDiagnosticBag diagnostics,
1996+
out LookupResultKind resultKind,
1997+
out ImmutableArray<MethodSymbol> originalUserDefinedOperators)
1998+
{
1999+
resultKind = LookupResultKind.Empty;
2000+
originalUserDefinedOperators = [];
2001+
2002+
if (left.Type is null && right.Type is null)
19482003
{
1949-
resultKind = LookupResultKind.Empty;
1950-
originalUserDefinedOperators = [];
2004+
return null;
2005+
}
19512006

1952-
if (left.Type is null && right.Type is null)
1953-
{
1954-
return null;
1955-
}
2007+
var result = BinaryOperatorOverloadResolutionResult.GetInstance();
2008+
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
2009+
var extensionDeclarationsInSingleScope = ArrayBuilder<NamedTypeSymbol>.GetInstance();
2010+
BinaryOperatorAnalysisResult? possiblyBest = null;
19562011

1957-
var result = BinaryOperatorOverloadResolutionResult.GetInstance();
1958-
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
1959-
var extensionDeclarationsInSingleScope = ArrayBuilder<NamedTypeSymbol>.GetInstance();
1960-
BinaryOperatorAnalysisResult? possiblyBest = null;
2012+
foreach (var scope in new ExtensionScopes(this))
2013+
{
2014+
extensionDeclarationsInSingleScope.Clear();
2015+
scope.Binder.GetExtensionDeclarations(extensionDeclarationsInSingleScope, this);
19612016

1962-
foreach (var scope in new ExtensionScopes(this))
2017+
if (this.OverloadResolution.BinaryOperatorExtensionOverloadResolutionInSingleScope(extensionDeclarationsInSingleScope, kind, isChecked, name1, name2Opt, left, right, result, ref useSiteInfo))
19632018
{
1964-
extensionDeclarationsInSingleScope.Clear();
1965-
scope.Binder.GetExtensionDeclarations(extensionDeclarationsInSingleScope, this);
1966-
1967-
if (this.OverloadResolution.BinaryOperatorExtensionOverloadResolutionInSingleScope(extensionDeclarationsInSingleScope, kind, isChecked, name1, name2Opt, left, right, result, ref useSiteInfo))
1968-
{
1969-
possiblyBest = BinaryOperatorAnalyzeOverloadResolutionResult(result, out resultKind, out originalUserDefinedOperators);
1970-
break;
1971-
}
2019+
possiblyBest = BinaryOperatorAnalyzeOverloadResolutionResult(result, out resultKind, out originalUserDefinedOperators);
2020+
break;
19722021
}
2022+
}
19732023

1974-
diagnostics.Add(node, useSiteInfo);
2024+
diagnostics.Add(node, useSiteInfo);
19752025

1976-
extensionDeclarationsInSingleScope.Free();
1977-
result.Free();
1978-
return possiblyBest;
1979-
}
2026+
extensionDeclarationsInSingleScope.Free();
2027+
result.Free();
2028+
return possiblyBest;
2029+
}
19802030

19812031
#nullable disable
1982-
}
19832032

19842033
private BinaryOperatorAnalysisResult BinaryOperatorNonExtensionOverloadResolution(
19852034
BinaryOperatorKind kind,
@@ -3758,18 +3807,18 @@ private bool CheckConstraintLanguageVersionAndRuntimeSupportForOperator(SyntaxNo
37583807

37593808
if (Compilation.SourceModule != methodOpt.ContainingModule)
37603809
{
3761-
if (SyntaxFacts.IsCheckedOperator(methodOpt.Name))
3810+
if (methodOpt.GetIsNewExtensionMember())
3811+
{
3812+
result &= CheckFeatureAvailability(node, MessageID.IDS_FeatureExtensions, diagnostics);
3813+
}
3814+
else if (SyntaxFacts.IsCheckedOperator(methodOpt.Name))
37623815
{
37633816
result &= CheckFeatureAvailability(node, MessageID.IDS_FeatureCheckedUserDefinedOperators, diagnostics);
37643817
}
37653818
else if (isUnsignedRightShift)
37663819
{
37673820
result &= CheckFeatureAvailability(node, MessageID.IDS_FeatureUnsignedRightShift, diagnostics);
37683821
}
3769-
else if (methodOpt.GetIsNewExtensionMember())
3770-
{
3771-
result &= CheckFeatureAvailability(node, MessageID.IDS_FeatureExtensions, diagnostics);
3772-
}
37733822
}
37743823
}
37753824

src/Compilers/CSharp/Portable/Binder/Binder_TupleOperators.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ private TupleBinaryOperatorInfo BindTupleBinaryOperatorInfo(BinaryExpressionSynt
9595
return BindTupleBinaryOperatorNestedInfo(node, kind, left, right, diagnostics);
9696
}
9797

98-
// https://github.com/dotnet/roslyn/issues/76130: Add test coverage for this code path
99-
10098
BoundExpression comparison = BindSimpleBinaryOperator(node, diagnostics, left, right, leaveUnconvertedIfInterpolatedString: false);
10199
switch (comparison)
102100
{

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

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,18 +179,13 @@ protected static DeclarationModifiers MakeDeclarationModifiers(bool isCompoundAs
179179
bool isExplicitInterfaceImplementation = methodKind == MethodKind.ExplicitInterfaceImplementation;
180180
var defaultAccess = inInterface && !isExplicitInterfaceImplementation ? DeclarationModifiers.Public : DeclarationModifiers.Private;
181181
var allowedModifiers =
182-
DeclarationModifiers.Unsafe;
182+
DeclarationModifiers.Unsafe | DeclarationModifiers.Extern;
183183

184184
if (!isCompoundAssignmentOrIncrementAssignment)
185185
{
186186
allowedModifiers |= DeclarationModifiers.Static;
187187
}
188188

189-
if (!inExtension)
190-
{
191-
allowedModifiers |= DeclarationModifiers.Extern;
192-
}
193-
194189
if (!isExplicitInterfaceImplementation)
195190
{
196191
allowedModifiers |= DeclarationModifiers.AccessibilityMask;

0 commit comments

Comments
 (0)