Skip to content
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

Fix SyntaxGenerator for checked operators #63411

Merged
merged 10 commits into from
Apr 10, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ public override SyntaxNode MethodDeclaration(
}

public override SyntaxNode OperatorDeclaration(OperatorKind kind, IEnumerable<SyntaxNode>? parameters = null, SyntaxNode? returnType = null, Accessibility accessibility = Accessibility.NotApplicable, DeclarationModifiers modifiers = default, IEnumerable<SyntaxNode>? statements = null)
{
return OperatorDeclaration((int)GetTokenKind(kind), isChecked: false, parameters, returnType, accessibility, modifiers, statements);
}

private protected override SyntaxNode OperatorDeclaration(int syntaxKind, bool isChecked, IEnumerable<SyntaxNode>? parameters = null, SyntaxNode? returnType = null, Accessibility accessibility = Accessibility.NotApplicable, DeclarationModifiers modifiers = default, IEnumerable<SyntaxNode>? statements = null)
Youssef1313 marked this conversation as resolved.
Show resolved Hide resolved
{
var hasBody = !modifiers.IsAbstract && (!modifiers.IsPartial || statements != null);
var returnTypeNode = returnType != null ? (TypeSyntax)returnType : SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword));
Expand All @@ -251,23 +256,39 @@ public override SyntaxNode OperatorDeclaration(OperatorKind kind, IEnumerable<Sy
var modifierList = AsModifierList(accessibility, modifiers, SyntaxKind.OperatorDeclaration);
var attributes = default(SyntaxList<AttributeListSyntax>);

if (kind is OperatorKind.ImplicitConversion or OperatorKind.ExplicitConversion)
if ((SyntaxKind)syntaxKind is SyntaxKind.ImplicitKeyword or SyntaxKind.ExplicitKeyword)
{
return SyntaxFactory.ConversionOperatorDeclaration(
attributes, modifierList, SyntaxFactory.Token(GetTokenKind(kind)),
attributes, modifierList, SyntaxFactory.Token((SyntaxKind)syntaxKind),
explicitInterfaceSpecifier: null,
SyntaxFactory.Token(SyntaxKind.OperatorKeyword),
returnTypeNode, parameterList, body, semicolon);
checkedKeyword: isChecked ? SyntaxFactory.Token(SyntaxKind.CheckedKeyword) : default,
returnTypeNode, parameterList, body, expressionBody: null, semicolon);
}
else
{
return SyntaxFactory.OperatorDeclaration(
attributes, modifierList, returnTypeNode,
explicitInterfaceSpecifier: null,
SyntaxFactory.Token(SyntaxKind.OperatorKeyword),
SyntaxFactory.Token(GetTokenKind(kind)),
parameterList, body, semicolon);
checkedKeyword: isChecked ? SyntaxFactory.Token(SyntaxKind.CheckedKeyword) : default,
SyntaxFactory.Token((SyntaxKind)syntaxKind),
parameterList, body, expressionBody: null, semicolon);
}
}

private protected override int GetOperatorSyntaxKind(IMethodSymbol method, out bool isChecked)
{
var operatorKind = CSharp.SyntaxFacts.GetOperatorKind(method.Name);
if (operatorKind == SyntaxKind.None)
{
throw new ArgumentException("Unknown operator kind.");
}

isChecked = method.Name.StartsWith("op_Checked");
Youssef1313 marked this conversation as resolved.
Show resolved Hide resolved
return (int)operatorKind;
}

Youssef1313 marked this conversation as resolved.
Show resolved Hide resolved
private static SyntaxKind GetTokenKind(OperatorKind kind)
=> kind switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
Expand Down Expand Up @@ -958,6 +959,20 @@ public void TestOperatorDeclaration()
"explicit operator bool (global::System.Int32 p0, global::System.String p1)\r\n{\r\n}");
}

[Fact, WorkItem(63410, "https://github.com/dotnet/roslyn/issues/63410")]
public void TestCheckedOperator()
{
var comp = CSharpCompilation.Create(null, new[] { SyntaxFactory.ParseSyntaxTree("""
public class C
{
public static C operator checked ++(C x) => x;
public static C operator ++(C x) => x;
}
""") });
var operatorSymbol = (IMethodSymbol)comp.GetTypeByMetadataName("C").GetMembers(WellKnownMemberNames.CheckedIncrementOperatorName).Single();
VerifySyntax<OperatorDeclarationSyntax>(Generator.OperatorDeclaration(operatorSymbol), "public static global::C operator checked ++(global::C x)\r\n{\r\n}");
}

[Fact]
public void TestConstructorDeclaration()
{
Expand Down
52 changes: 19 additions & 33 deletions src/Workspaces/Core/Portable/Editing/SyntaxGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,18 @@ public virtual SyntaxNode OperatorDeclaration(
throw new NotImplementedException();
}

private protected virtual SyntaxNode OperatorDeclaration(
int syntaxKind,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i'd prefer just passing hte operator name along. thsi also includes 'checked' so it can subsume the first two parameters.

bool isChecked,
IEnumerable<SyntaxNode>? parameters = null,
SyntaxNode? returnType = null,
Accessibility accessibility = Accessibility.NotApplicable,
DeclarationModifiers modifiers = default,
IEnumerable<SyntaxNode>? statements = null)
{
throw new NotImplementedException();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this guy?

Copy link
Member Author

@Youssef1313 Youssef1313 Aug 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CyrusNajmabadi A new method that allows me to pass isChecked. I couldn't think of another good way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that makes sense. w hy is this virtual though and not abstract?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I incorrectly assumed the type is subclass-able when I saw this:

public virtual SyntaxNode OperatorDeclaration(
OperatorKind kind,
IEnumerable<SyntaxNode>? parameters = null,
SyntaxNode? returnType = null,
Accessibility accessibility = Accessibility.NotApplicable,
DeclarationModifiers modifiers = default,
IEnumerable<SyntaxNode>? statements = null)
{
throw new NotImplementedException();
}

Given there are already non-public abstract methods, it's indeed okay to make the new methods abstract.

This is fixed in the last commit now.


/// <summary>
/// Creates a method declaration matching an existing method symbol.
/// </summary>
Expand All @@ -212,8 +224,10 @@ public SyntaxNode OperatorDeclaration(IMethodSymbol method, IEnumerable<SyntaxNo
throw new ArgumentException("Method is not an operator.");
}

var kind = GetOperatorSyntaxKind(method, out var isChecked);
var decl = OperatorDeclaration(
GetOperatorKind(method),
kind,
isChecked,
parameters: method.Parameters.Select(p => ParameterDeclaration(p)),
returnType: method.ReturnType.IsSystemVoid() ? null : TypeExpression(method.ReturnType),
accessibility: method.DeclaredAccessibility,
Expand All @@ -223,38 +237,10 @@ public SyntaxNode OperatorDeclaration(IMethodSymbol method, IEnumerable<SyntaxNo
return decl;
}

private static OperatorKind GetOperatorKind(IMethodSymbol method)
=> method.Name switch
{
WellKnownMemberNames.ImplicitConversionName => OperatorKind.ImplicitConversion,
WellKnownMemberNames.ExplicitConversionName => OperatorKind.ExplicitConversion,
WellKnownMemberNames.AdditionOperatorName => OperatorKind.Addition,
WellKnownMemberNames.BitwiseAndOperatorName => OperatorKind.BitwiseAnd,
WellKnownMemberNames.BitwiseOrOperatorName => OperatorKind.BitwiseOr,
WellKnownMemberNames.DecrementOperatorName => OperatorKind.Decrement,
WellKnownMemberNames.DivisionOperatorName => OperatorKind.Division,
WellKnownMemberNames.EqualityOperatorName => OperatorKind.Equality,
WellKnownMemberNames.ExclusiveOrOperatorName => OperatorKind.ExclusiveOr,
WellKnownMemberNames.FalseOperatorName => OperatorKind.False,
WellKnownMemberNames.GreaterThanOperatorName => OperatorKind.GreaterThan,
WellKnownMemberNames.GreaterThanOrEqualOperatorName => OperatorKind.GreaterThanOrEqual,
WellKnownMemberNames.IncrementOperatorName => OperatorKind.Increment,
WellKnownMemberNames.InequalityOperatorName => OperatorKind.Inequality,
WellKnownMemberNames.LeftShiftOperatorName => OperatorKind.LeftShift,
WellKnownMemberNames.LessThanOperatorName => OperatorKind.LessThan,
WellKnownMemberNames.LessThanOrEqualOperatorName => OperatorKind.LessThanOrEqual,
WellKnownMemberNames.LogicalNotOperatorName => OperatorKind.LogicalNot,
WellKnownMemberNames.ModulusOperatorName => OperatorKind.Modulus,
WellKnownMemberNames.MultiplyOperatorName => OperatorKind.Multiply,
WellKnownMemberNames.OnesComplementOperatorName => OperatorKind.OnesComplement,
WellKnownMemberNames.RightShiftOperatorName => OperatorKind.RightShift,
WellKnownMemberNames.UnsignedRightShiftOperatorName => OperatorKind.UnsignedRightShift,
WellKnownMemberNames.SubtractionOperatorName => OperatorKind.Subtraction,
WellKnownMemberNames.TrueOperatorName => OperatorKind.True,
WellKnownMemberNames.UnaryNegationOperatorName => OperatorKind.UnaryNegation,
WellKnownMemberNames.UnaryPlusOperatorName => OperatorKind.UnaryPlus,
_ => throw new ArgumentException("Unknown operator kind."),
};
private protected virtual int GetOperatorSyntaxKind(IMethodSymbol method, out bool isChecked)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why virtual and not abstract (applies to all methods like this).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CyrusNajmabadi SyntaxGenerator is public. Adding new abstract members is breaking.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SyntaxGenerator has internal-abstracts. It cannot be publicly subclassed :) you're fine adding a new abstract to it.

{
throw new NotImplementedException();
}

/// <summary>
/// Creates a parameter declaration.
Expand Down