Skip to content

Commit b264ee2

Browse files
authored
Bind compound assignment operator declarations (#77999)
1 parent 6514158 commit b264ee2

File tree

9 files changed

+4051
-17
lines changed

9 files changed

+4051
-17
lines changed

src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorFacts.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,26 @@ internal static string BinaryOperatorNameFromSyntaxKindIfAny(SyntaxKind kind, bo
9292
}
9393
}
9494

95+
internal static string CompoundAssignmentOperatorNameFromSyntaxKind(SyntaxKind kind, bool isChecked)
96+
{
97+
switch (kind)
98+
{
99+
case SyntaxKind.PlusEqualsToken: return isChecked ? WellKnownMemberNames.CheckedAdditionAssignmentOperatorName : WellKnownMemberNames.AdditionAssignmentOperatorName;
100+
case SyntaxKind.MinusEqualsToken: return isChecked ? WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName : WellKnownMemberNames.SubtractionAssignmentOperatorName;
101+
case SyntaxKind.AsteriskEqualsToken: return isChecked ? WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName : WellKnownMemberNames.MultiplicationAssignmentOperatorName;
102+
case SyntaxKind.SlashEqualsToken: return isChecked ? WellKnownMemberNames.CheckedDivisionAssignmentOperatorName : WellKnownMemberNames.DivisionAssignmentOperatorName;
103+
case SyntaxKind.PercentEqualsToken: return WellKnownMemberNames.ModulusAssignmentOperatorName;
104+
case SyntaxKind.CaretEqualsToken: return WellKnownMemberNames.ExclusiveOrAssignmentOperatorName;
105+
case SyntaxKind.AmpersandEqualsToken: return WellKnownMemberNames.BitwiseAndAssignmentOperatorName;
106+
case SyntaxKind.BarEqualsToken: return WellKnownMemberNames.BitwiseOrAssignmentOperatorName;
107+
case SyntaxKind.LessThanLessThanEqualsToken: return WellKnownMemberNames.LeftShiftAssignmentOperatorName;
108+
case SyntaxKind.GreaterThanGreaterThanEqualsToken: return WellKnownMemberNames.RightShiftAssignmentOperatorName;
109+
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName;
110+
default:
111+
throw ExceptionUtilities.UnexpectedValue(kind);
112+
}
113+
}
114+
95115
public static string UnaryOperatorNameFromSyntaxKind(SyntaxKind kind, bool isChecked)
96116
{
97117
return UnaryOperatorNameFromSyntaxKindIfAny(kind, isChecked) ??
@@ -141,6 +161,10 @@ public static string OperatorNameFromDeclaration(Syntax.InternalSyntax.OperatorD
141161
{
142162
return OperatorFacts.UnaryOperatorNameFromSyntaxKind(opTokenKind, isChecked);
143163
}
164+
else if (SyntaxFacts.IsOverloadableCompoundAssignmentOperator(opTokenKind))
165+
{
166+
return OperatorFacts.CompoundAssignmentOperatorNameFromSyntaxKind(opTokenKind, isChecked);
167+
}
144168
else
145169
{
146170
// fallback for error recovery

src/Compilers/CSharp/Portable/Symbols/Metadata/PE/PEMethodSymbol.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,6 +1172,8 @@ private bool HasValidOperatorParameterRefKinds()
11721172

11731173
private bool IsValidInstanceUserDefinedOperatorSignature(int parameterCount)
11741174
{
1175+
// PROTOTYPE: Cover negative scenarios
1176+
11751177
if (!this.ReturnsVoid || this.IsGenericMethod || this.IsVararg || this.ParameterCount != parameterCount || this.IsParams())
11761178
{
11771179
return false;
@@ -1278,6 +1280,23 @@ private MethodKind ComputeMethodKind()
12781280
case WellKnownMemberNames.CheckedIncrementOperatorName:
12791281
case WellKnownMemberNames.IncrementOperatorName:
12801282
return IsValidInstanceUserDefinedOperatorSignature(0) ? MethodKind.UserDefinedOperator : MethodKind.Ordinary;
1283+
1284+
case WellKnownMemberNames.AdditionAssignmentOperatorName:
1285+
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
1286+
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
1287+
case WellKnownMemberNames.DivisionAssignmentOperatorName:
1288+
case WellKnownMemberNames.ModulusAssignmentOperatorName:
1289+
case WellKnownMemberNames.BitwiseAndAssignmentOperatorName:
1290+
case WellKnownMemberNames.BitwiseOrAssignmentOperatorName:
1291+
case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName:
1292+
case WellKnownMemberNames.LeftShiftAssignmentOperatorName:
1293+
case WellKnownMemberNames.RightShiftAssignmentOperatorName:
1294+
case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName:
1295+
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
1296+
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
1297+
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
1298+
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
1299+
return IsValidInstanceUserDefinedOperatorSignature(1) ? MethodKind.UserDefinedOperator : MethodKind.Ordinary;
12811300
}
12821301
}
12831302

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2734,6 +2734,11 @@ private void CheckForUnmatchedOperators(BindingDiagnosticBag diagnostics)
27342734
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedSubtractionOperatorName, WellKnownMemberNames.SubtractionOperatorName, symmetricCheck: false);
27352735
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedExplicitConversionName, WellKnownMemberNames.ExplicitConversionName, symmetricCheck: false);
27362736

2737+
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedAdditionAssignmentOperatorName, WellKnownMemberNames.AdditionAssignmentOperatorName, symmetricCheck: false);
2738+
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedDivisionAssignmentOperatorName, WellKnownMemberNames.DivisionAssignmentOperatorName, symmetricCheck: false);
2739+
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName, WellKnownMemberNames.MultiplicationAssignmentOperatorName, symmetricCheck: false);
2740+
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName, WellKnownMemberNames.SubtractionAssignmentOperatorName, symmetricCheck: false);
2741+
27372742
// We also produce a warning if == / != is overridden without also overriding
27382743
// Equals and GetHashCode, or if Equals is overridden without GetHashCode.
27392744

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

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ protected SourceUserDefinedOperatorSymbolBase(
5252

5353
this.CheckUnsafeModifier(declarationModifiers, diagnostics);
5454

55-
bool isIncrementDecrement = IsIncrementDecrementDeclaration(syntax);
55+
(bool isIncrementDecrement, bool isCompoundAssignment) = IsAssignmentOperatorDeclaration(syntax);
5656

5757
if (isIncrementDecrement)
5858
{
@@ -66,6 +66,8 @@ protected SourceUserDefinedOperatorSymbolBase(
6666
}
6767
else
6868
{
69+
// PROTOTYPE: Cover explicit implementation scenarios
70+
6971
Binder.CheckFeatureAvailability(syntax, MessageID.IDS_FeatureUserDefinedCompoundAssignmentOperators, diagnostics, ((OperatorDeclarationSyntax)syntax).OperatorToken.GetLocation());
7072

7173
if (parameterCount is not (0 or 2))
@@ -74,6 +76,11 @@ protected SourceUserDefinedOperatorSymbolBase(
7476
}
7577
}
7678
}
79+
else if (isCompoundAssignment)
80+
{
81+
// PROTOTYPE: Cover explicit implementation scenarios
82+
Binder.CheckFeatureAvailability(syntax, MessageID.IDS_FeatureUserDefinedCompoundAssignmentOperators, diagnostics, ((OperatorDeclarationSyntax)syntax).OperatorToken.GetLocation());
83+
}
7784

7885
if (this.ContainingType.IsInterface &&
7986
!(IsAbstract || IsVirtual) && !IsExplicitInterfaceImplementation &&
@@ -97,12 +104,12 @@ protected SourceUserDefinedOperatorSymbolBase(
97104
// SPEC: static modifier
98105
if (this.IsExplicitInterfaceImplementation)
99106
{
100-
if (!this.IsStatic && !isIncrementDecrement)
107+
if (!this.IsStatic && !isIncrementDecrement && !isCompoundAssignment)
101108
{
102109
diagnostics.Add(ErrorCode.ERR_ExplicitImplementationOfOperatorsMustBeStatic, this.GetFirstLocation(), this);
103110
}
104111
}
105-
else if (isIncrementDecrement && !this.IsStatic)
112+
else if ((isIncrementDecrement || isCompoundAssignment) && !this.IsStatic)
106113
{
107114
if (this.DeclaredAccessibility != Accessibility.Public)
108115
{
@@ -176,9 +183,21 @@ protected SourceUserDefinedOperatorSymbolBase(
176183
ModifierUtils.CheckAccessibility(this.DeclarationModifiers, this, isExplicitInterfaceImplementation: false, diagnostics, location);
177184
}
178185

179-
private static bool IsIncrementDecrementDeclaration(CSharpSyntaxNode syntax)
186+
private static (bool isIncrementDecrement, bool isCompoundAssignment) IsAssignmentOperatorDeclaration(CSharpSyntaxNode syntax)
180187
{
181-
return syntax is OperatorDeclarationSyntax { OperatorToken.RawKind: (int)SyntaxKind.PlusPlusToken or (int)SyntaxKind.MinusMinusToken };
188+
if (syntax is OperatorDeclarationSyntax operatorDeclaration)
189+
{
190+
if (operatorDeclaration.OperatorToken.Kind() is SyntaxKind.PlusPlusToken or SyntaxKind.MinusMinusToken)
191+
{
192+
return (true, false);
193+
}
194+
else if (SyntaxFacts.IsOverloadableCompoundAssignmentOperator(operatorDeclaration.OperatorToken.Kind()))
195+
{
196+
return (false, true);
197+
}
198+
}
199+
200+
return (false, false);
182201
}
183202

184203
protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind methodKind, SourceMemberContainerTypeSymbol containingType, BaseMethodDeclarationSyntax syntax, Location location, BindingDiagnosticBag diagnostics)
@@ -191,6 +210,8 @@ protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind method
191210
DeclarationModifiers.Extern |
192211
DeclarationModifiers.Unsafe;
193212

213+
(bool isIncrementDecrement, bool isCompoundAssignment) = IsAssignmentOperatorDeclaration(syntax);
214+
194215
if (!isExplicitInterfaceImplementation)
195216
{
196217
allowedModifiers |= DeclarationModifiers.AccessibilityMask;
@@ -205,7 +226,7 @@ protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind method
205226
}
206227
}
207228

208-
if (IsIncrementDecrementDeclaration(syntax))
229+
if (isIncrementDecrement || isCompoundAssignment)
209230
{
210231
if (inInterface)
211232
{
@@ -228,6 +249,11 @@ protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind method
228249
allowedModifiers |= DeclarationModifiers.Abstract;
229250
}
230251

252+
if (isCompoundAssignment)
253+
{
254+
allowedModifiers &= ~DeclarationModifiers.Static;
255+
}
256+
231257
var result = ModifierUtils.MakeAndCheckNonTypeMemberModifiers(
232258
isOrdinaryMethod: false, isForInterfaceMember: inInterface,
233259
syntax.Modifiers, defaultAccess, allowedModifiers, location, diagnostics, modifierErrors: out _, hasExplicitAccessModifier: out _);
@@ -272,7 +298,7 @@ protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind method
272298
Binder.CheckFeatureAvailability(location.SourceTree, MessageID.IDS_DefaultInterfaceImplementation, diagnostics, location);
273299
}
274300
}
275-
else if (!isExplicitInterfaceImplementation && IsIncrementDecrementDeclaration(syntax))
301+
else if (!isExplicitInterfaceImplementation && (isIncrementDecrement || isCompoundAssignment))
276302
{
277303
if (syntax.HasAnyBody())
278304
{
@@ -504,6 +530,29 @@ private void CheckOperatorSignatures(BindingDiagnosticBag diagnostics)
504530

505531
break;
506532

533+
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
534+
case WellKnownMemberNames.AdditionAssignmentOperatorName:
535+
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
536+
case WellKnownMemberNames.DivisionAssignmentOperatorName:
537+
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
538+
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
539+
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
540+
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
541+
case WellKnownMemberNames.ModulusAssignmentOperatorName:
542+
case WellKnownMemberNames.BitwiseAndAssignmentOperatorName:
543+
case WellKnownMemberNames.BitwiseOrAssignmentOperatorName:
544+
case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName:
545+
case WellKnownMemberNames.LeftShiftAssignmentOperatorName:
546+
case WellKnownMemberNames.RightShiftAssignmentOperatorName:
547+
case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName:
548+
if (!this.ReturnsVoid)
549+
{
550+
diagnostics.Add(ErrorCode.ERR_OperatorMustReturnVoid, this.GetFirstLocation());
551+
}
552+
553+
// PROTOTYPE: Test/handle scenarios with ref modifiers on the parameter
554+
break;
555+
507556
default:
508557
CheckBinarySignature(diagnostics);
509558
break;
@@ -530,6 +579,21 @@ private bool DoesOperatorHaveCorrectArity(string name, int parameterCount)
530579
case WellKnownMemberNames.ImplicitConversionName:
531580
case WellKnownMemberNames.ExplicitConversionName:
532581
case WellKnownMemberNames.CheckedExplicitConversionName:
582+
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
583+
case WellKnownMemberNames.AdditionAssignmentOperatorName:
584+
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
585+
case WellKnownMemberNames.DivisionAssignmentOperatorName:
586+
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
587+
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
588+
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
589+
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
590+
case WellKnownMemberNames.ModulusAssignmentOperatorName:
591+
case WellKnownMemberNames.BitwiseAndAssignmentOperatorName:
592+
case WellKnownMemberNames.BitwiseOrAssignmentOperatorName:
593+
case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName:
594+
case WellKnownMemberNames.LeftShiftAssignmentOperatorName:
595+
case WellKnownMemberNames.RightShiftAssignmentOperatorName:
596+
case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName:
533597
return parameterCount == 1;
534598
default:
535599
return parameterCount == 2;

src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,37 @@ public static SyntaxKind GetOperatorKind(string operatorMetadataName)
11061106
return SyntaxKind.MinusToken;
11071107

11081108
case WellKnownMemberNames.UnaryPlusOperatorName: return SyntaxKind.PlusToken;
1109+
1110+
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
1111+
case WellKnownMemberNames.AdditionAssignmentOperatorName:
1112+
return SyntaxKind.PlusEqualsToken;
1113+
1114+
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
1115+
case WellKnownMemberNames.DivisionAssignmentOperatorName:
1116+
return SyntaxKind.SlashEqualsToken;
1117+
1118+
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
1119+
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
1120+
return SyntaxKind.AsteriskEqualsToken;
1121+
1122+
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
1123+
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
1124+
return SyntaxKind.MinusEqualsToken;
1125+
1126+
case WellKnownMemberNames.ModulusAssignmentOperatorName: return SyntaxKind.PercentEqualsToken;
1127+
1128+
case WellKnownMemberNames.BitwiseAndAssignmentOperatorName: return SyntaxKind.AmpersandEqualsToken;
1129+
1130+
case WellKnownMemberNames.BitwiseOrAssignmentOperatorName: return SyntaxKind.BarEqualsToken;
1131+
1132+
case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName: return SyntaxKind.CaretEqualsToken;
1133+
1134+
case WellKnownMemberNames.LeftShiftAssignmentOperatorName: return SyntaxKind.LessThanLessThanEqualsToken;
1135+
1136+
case WellKnownMemberNames.RightShiftAssignmentOperatorName: return SyntaxKind.GreaterThanGreaterThanEqualsToken;
1137+
1138+
case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName: return SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken;
1139+
11091140
default:
11101141
return SyntaxKind.None;
11111142
}
@@ -1123,6 +1154,10 @@ public static bool IsCheckedOperator(string operatorMetadataName)
11231154
case WellKnownMemberNames.CheckedMultiplyOperatorName:
11241155
case WellKnownMemberNames.CheckedSubtractionOperatorName:
11251156
case WellKnownMemberNames.CheckedExplicitConversionName:
1157+
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
1158+
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
1159+
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
1160+
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
11261161
return true;
11271162

11281163
default:

0 commit comments

Comments
 (0)