Skip to content

Bind compound assignment operator declarations #77999

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ internal static string BinaryOperatorNameFromSyntaxKindIfAny(SyntaxKind kind, bo
}
}

internal static string CompoundAssignmentOperatorNameFromSyntaxKind(SyntaxKind kind, bool isChecked)
{
switch (kind)
{
case SyntaxKind.PlusEqualsToken: return isChecked ? WellKnownMemberNames.CheckedAdditionAssignmentOperatorName : WellKnownMemberNames.AdditionAssignmentOperatorName;
case SyntaxKind.MinusEqualsToken: return isChecked ? WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName : WellKnownMemberNames.SubtractionAssignmentOperatorName;
case SyntaxKind.AsteriskEqualsToken: return isChecked ? WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName : WellKnownMemberNames.MultiplicationAssignmentOperatorName;
case SyntaxKind.SlashEqualsToken: return isChecked ? WellKnownMemberNames.CheckedDivisionAssignmentOperatorName : WellKnownMemberNames.DivisionAssignmentOperatorName;
case SyntaxKind.PercentEqualsToken: return WellKnownMemberNames.ModulusAssignmentOperatorName;
case SyntaxKind.CaretEqualsToken: return WellKnownMemberNames.ExclusiveOrAssignmentOperatorName;
case SyntaxKind.AmpersandEqualsToken: return WellKnownMemberNames.BitwiseAndAssignmentOperatorName;
case SyntaxKind.BarEqualsToken: return WellKnownMemberNames.BitwiseOrAssignmentOperatorName;
case SyntaxKind.LessThanLessThanEqualsToken: return WellKnownMemberNames.LeftShiftAssignmentOperatorName;
case SyntaxKind.GreaterThanGreaterThanEqualsToken: return WellKnownMemberNames.RightShiftAssignmentOperatorName;
case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: return WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName;
default:
throw ExceptionUtilities.UnexpectedValue(kind);
}
}

public static string UnaryOperatorNameFromSyntaxKind(SyntaxKind kind, bool isChecked)
{
return UnaryOperatorNameFromSyntaxKindIfAny(kind, isChecked) ??
Expand Down Expand Up @@ -141,6 +161,10 @@ public static string OperatorNameFromDeclaration(Syntax.InternalSyntax.OperatorD
{
return OperatorFacts.UnaryOperatorNameFromSyntaxKind(opTokenKind, isChecked);
}
else if (SyntaxFacts.IsOverloadableCompoundAssignmentOperator(opTokenKind))
{
return OperatorFacts.CompoundAssignmentOperatorNameFromSyntaxKind(opTokenKind, isChecked);
}
else
{
// fallback for error recovery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,8 @@ private bool HasValidOperatorParameterRefKinds()

private bool IsValidInstanceUserDefinedOperatorSignature(int parameterCount)
{
// PROTOTYPE: Cover negative scenarios

if (!this.ReturnsVoid || this.IsGenericMethod || this.IsVararg || this.ParameterCount != parameterCount || this.IsParams())
{
return false;
Expand Down Expand Up @@ -1278,6 +1280,23 @@ private MethodKind ComputeMethodKind()
case WellKnownMemberNames.CheckedIncrementOperatorName:
case WellKnownMemberNames.IncrementOperatorName:
return IsValidInstanceUserDefinedOperatorSignature(0) ? MethodKind.UserDefinedOperator : MethodKind.Ordinary;

case WellKnownMemberNames.AdditionAssignmentOperatorName:
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
case WellKnownMemberNames.DivisionAssignmentOperatorName:
case WellKnownMemberNames.ModulusAssignmentOperatorName:
case WellKnownMemberNames.BitwiseAndAssignmentOperatorName:
case WellKnownMemberNames.BitwiseOrAssignmentOperatorName:
case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName:
case WellKnownMemberNames.LeftShiftAssignmentOperatorName:
case WellKnownMemberNames.RightShiftAssignmentOperatorName:
case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName:
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
return IsValidInstanceUserDefinedOperatorSignature(1) ? MethodKind.UserDefinedOperator : MethodKind.Ordinary;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2734,6 +2734,11 @@ private void CheckForUnmatchedOperators(BindingDiagnosticBag diagnostics)
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedSubtractionOperatorName, WellKnownMemberNames.SubtractionOperatorName, symmetricCheck: false);
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedExplicitConversionName, WellKnownMemberNames.ExplicitConversionName, symmetricCheck: false);

CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedAdditionAssignmentOperatorName, WellKnownMemberNames.AdditionAssignmentOperatorName, symmetricCheck: false);
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedDivisionAssignmentOperatorName, WellKnownMemberNames.DivisionAssignmentOperatorName, symmetricCheck: false);
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName, WellKnownMemberNames.MultiplicationAssignmentOperatorName, symmetricCheck: false);
CheckForUnmatchedOperator(diagnostics, WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName, WellKnownMemberNames.SubtractionAssignmentOperatorName, symmetricCheck: false);

// We also produce a warning if == / != is overridden without also overriding
// Equals and GetHashCode, or if Equals is overridden without GetHashCode.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected SourceUserDefinedOperatorSymbolBase(

this.CheckUnsafeModifier(declarationModifiers, diagnostics);

bool isIncrementDecrement = IsIncrementDecrementDeclaration(syntax);
(bool isIncrementDecrement, bool isCompoundAssignment) = IsAssignmentOperatorDeclaration(syntax);

if (isIncrementDecrement)
{
Expand All @@ -66,6 +66,8 @@ protected SourceUserDefinedOperatorSymbolBase(
}
else
{
// PROTOTYPE: Cover explicit implementation scenarios

Binder.CheckFeatureAvailability(syntax, MessageID.IDS_FeatureUserDefinedCompoundAssignmentOperators, diagnostics, ((OperatorDeclarationSyntax)syntax).OperatorToken.GetLocation());

if (parameterCount is not (0 or 2))
Expand All @@ -74,6 +76,11 @@ protected SourceUserDefinedOperatorSymbolBase(
}
}
}
else if (isCompoundAssignment)
{
// PROTOTYPE: Cover explicit implementation scenarios
Binder.CheckFeatureAvailability(syntax, MessageID.IDS_FeatureUserDefinedCompoundAssignmentOperators, diagnostics, ((OperatorDeclarationSyntax)syntax).OperatorToken.GetLocation());
}

if (this.ContainingType.IsInterface &&
!(IsAbstract || IsVirtual) && !IsExplicitInterfaceImplementation &&
Expand All @@ -97,12 +104,12 @@ protected SourceUserDefinedOperatorSymbolBase(
// SPEC: static modifier
if (this.IsExplicitInterfaceImplementation)
{
if (!this.IsStatic && !isIncrementDecrement)
if (!this.IsStatic && !isIncrementDecrement && !isCompoundAssignment)
{
diagnostics.Add(ErrorCode.ERR_ExplicitImplementationOfOperatorsMustBeStatic, this.GetFirstLocation(), this);
}
}
else if (isIncrementDecrement && !this.IsStatic)
else if ((isIncrementDecrement || isCompoundAssignment) && !this.IsStatic)
{
if (this.DeclaredAccessibility != Accessibility.Public)
{
Expand Down Expand Up @@ -176,9 +183,21 @@ protected SourceUserDefinedOperatorSymbolBase(
ModifierUtils.CheckAccessibility(this.DeclarationModifiers, this, isExplicitInterfaceImplementation: false, diagnostics, location);
}

private static bool IsIncrementDecrementDeclaration(CSharpSyntaxNode syntax)
private static (bool isIncrementDecrement, bool isCompoundAssignment) IsAssignmentOperatorDeclaration(CSharpSyntaxNode syntax)
{
return syntax is OperatorDeclarationSyntax { OperatorToken.RawKind: (int)SyntaxKind.PlusPlusToken or (int)SyntaxKind.MinusMinusToken };
if (syntax is OperatorDeclarationSyntax operatorDeclaration)
{
if (operatorDeclaration.OperatorToken.Kind() is SyntaxKind.PlusPlusToken or SyntaxKind.MinusMinusToken)
{
return (true, false);
}
else if (SyntaxFacts.IsOverloadableCompoundAssignmentOperator(operatorDeclaration.OperatorToken.Kind()))
{
return (false, true);
}
}

return (false, false);
}

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

(bool isIncrementDecrement, bool isCompoundAssignment) = IsAssignmentOperatorDeclaration(syntax);

if (!isExplicitInterfaceImplementation)
{
allowedModifiers |= DeclarationModifiers.AccessibilityMask;
Expand All @@ -205,7 +226,7 @@ protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind method
}
}

if (IsIncrementDecrementDeclaration(syntax))
if (isIncrementDecrement || isCompoundAssignment)
{
if (inInterface)
{
Expand All @@ -228,6 +249,11 @@ protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind method
allowedModifiers |= DeclarationModifiers.Abstract;
}

if (isCompoundAssignment)
{
allowedModifiers &= ~DeclarationModifiers.Static;
}

var result = ModifierUtils.MakeAndCheckNonTypeMemberModifiers(
isOrdinaryMethod: false, isForInterfaceMember: inInterface,
syntax.Modifiers, defaultAccess, allowedModifiers, location, diagnostics, modifierErrors: out _, hasExplicitAccessModifier: out _);
Expand Down Expand Up @@ -272,7 +298,7 @@ protected static DeclarationModifiers MakeDeclarationModifiers(MethodKind method
Binder.CheckFeatureAvailability(location.SourceTree, MessageID.IDS_DefaultInterfaceImplementation, diagnostics, location);
}
}
else if (!isExplicitInterfaceImplementation && IsIncrementDecrementDeclaration(syntax))
else if (!isExplicitInterfaceImplementation && (isIncrementDecrement || isCompoundAssignment))
{
if (syntax.HasAnyBody())
{
Expand Down Expand Up @@ -504,6 +530,29 @@ private void CheckOperatorSignatures(BindingDiagnosticBag diagnostics)

break;

case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
case WellKnownMemberNames.AdditionAssignmentOperatorName:
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
case WellKnownMemberNames.DivisionAssignmentOperatorName:
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
case WellKnownMemberNames.ModulusAssignmentOperatorName:
case WellKnownMemberNames.BitwiseAndAssignmentOperatorName:
case WellKnownMemberNames.BitwiseOrAssignmentOperatorName:
case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName:
case WellKnownMemberNames.LeftShiftAssignmentOperatorName:
case WellKnownMemberNames.RightShiftAssignmentOperatorName:
case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName:
if (!this.ReturnsVoid)
{
diagnostics.Add(ErrorCode.ERR_OperatorMustReturnVoid, this.GetFirstLocation());
}

// PROTOTYPE: Test/handle scenarios with ref modifiers on the parameter
break;

default:
CheckBinarySignature(diagnostics);
break;
Expand All @@ -530,6 +579,21 @@ private bool DoesOperatorHaveCorrectArity(string name, int parameterCount)
case WellKnownMemberNames.ImplicitConversionName:
case WellKnownMemberNames.ExplicitConversionName:
case WellKnownMemberNames.CheckedExplicitConversionName:
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
case WellKnownMemberNames.AdditionAssignmentOperatorName:
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
case WellKnownMemberNames.DivisionAssignmentOperatorName:
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
case WellKnownMemberNames.ModulusAssignmentOperatorName:
case WellKnownMemberNames.BitwiseAndAssignmentOperatorName:
case WellKnownMemberNames.BitwiseOrAssignmentOperatorName:
case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName:
case WellKnownMemberNames.LeftShiftAssignmentOperatorName:
case WellKnownMemberNames.RightShiftAssignmentOperatorName:
case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName:
return parameterCount == 1;
default:
return parameterCount == 2;
Expand Down
35 changes: 35 additions & 0 deletions src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1106,6 +1106,37 @@ public static SyntaxKind GetOperatorKind(string operatorMetadataName)
return SyntaxKind.MinusToken;

case WellKnownMemberNames.UnaryPlusOperatorName: return SyntaxKind.PlusToken;

case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
case WellKnownMemberNames.AdditionAssignmentOperatorName:
return SyntaxKind.PlusEqualsToken;

case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
case WellKnownMemberNames.DivisionAssignmentOperatorName:
return SyntaxKind.SlashEqualsToken;

case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
case WellKnownMemberNames.MultiplicationAssignmentOperatorName:
return SyntaxKind.AsteriskEqualsToken;

case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
case WellKnownMemberNames.SubtractionAssignmentOperatorName:
return SyntaxKind.MinusEqualsToken;

case WellKnownMemberNames.ModulusAssignmentOperatorName: return SyntaxKind.PercentEqualsToken;

case WellKnownMemberNames.BitwiseAndAssignmentOperatorName: return SyntaxKind.AmpersandEqualsToken;

case WellKnownMemberNames.BitwiseOrAssignmentOperatorName: return SyntaxKind.BarEqualsToken;

case WellKnownMemberNames.ExclusiveOrAssignmentOperatorName: return SyntaxKind.CaretEqualsToken;

case WellKnownMemberNames.LeftShiftAssignmentOperatorName: return SyntaxKind.LessThanLessThanEqualsToken;

case WellKnownMemberNames.RightShiftAssignmentOperatorName: return SyntaxKind.GreaterThanGreaterThanEqualsToken;

case WellKnownMemberNames.UnsignedRightShiftAssignmentOperatorName: return SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken;

default:
return SyntaxKind.None;
}
Expand All @@ -1123,6 +1154,10 @@ public static bool IsCheckedOperator(string operatorMetadataName)
case WellKnownMemberNames.CheckedMultiplyOperatorName:
case WellKnownMemberNames.CheckedSubtractionOperatorName:
case WellKnownMemberNames.CheckedExplicitConversionName:
case WellKnownMemberNames.CheckedAdditionAssignmentOperatorName:
case WellKnownMemberNames.CheckedDivisionAssignmentOperatorName:
case WellKnownMemberNames.CheckedMultiplicationAssignmentOperatorName:
case WellKnownMemberNames.CheckedSubtractionAssignmentOperatorName:
return true;

default:
Expand Down
Loading