Skip to content

Commit bfd3683

Browse files
authored
Merge pull request #21372 from michaelnebel/csharp14/usercompoundassignment
C# 14: User defined compound assignment operators.
2 parents c95083b + f3f3ee6 commit bfd3683

36 files changed

Lines changed: 7538 additions & 746 deletions

File tree

csharp/downgrades/ea7ad33252e550241975676f09fcc7b0a703deab/old.dbscheme

Lines changed: 1505 additions & 0 deletions
Large diffs are not rendered by default.

csharp/downgrades/ea7ad33252e550241975676f09fcc7b0a703deab/semmlecode.csharp.dbscheme

Lines changed: 1504 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
description: Remove @assign_op_call_expr from @qualifiable_expr.
2+
compatibility: full
Lines changed: 54 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Collections.Generic;
2+
using System.Collections.ObjectModel;
13
using System.Text.RegularExpressions;
24
using Microsoft.CodeAnalysis;
35

@@ -18,114 +20,68 @@ public static string GetName(this ISymbol symbol, bool useMetadataName = false)
1820
return symbol.CanBeReferencedByName ? name : name.Substring(symbol.Name.LastIndexOf('.') + 1);
1921
}
2022

23+
private static readonly ReadOnlyDictionary<string, string> methodToOperator = new(new Dictionary<string, string>
24+
{
25+
{ "op_LogicalNot", "!" },
26+
{ "op_BitwiseAnd", "&" },
27+
{ "op_Equality", "==" },
28+
{ "op_Inequality", "!=" },
29+
{ "op_UnaryPlus", "+" },
30+
{ "op_Addition", "+" },
31+
{ "op_UnaryNegation", "-" },
32+
{ "op_Subtraction", "-" },
33+
{ "op_Multiply", "*" },
34+
{ "op_Multiplication", "*" },
35+
{ "op_Division", "/" },
36+
{ "op_Modulus", "%" },
37+
{ "op_GreaterThan", ">" },
38+
{ "op_GreaterThanOrEqual", ">=" },
39+
{ "op_LessThan", "<" },
40+
{ "op_LessThanOrEqual", "<=" },
41+
{ "op_Decrement", "--" },
42+
{ "op_Increment", "++" },
43+
{ "op_Implicit", "implicit conversion" },
44+
{ "op_Explicit", "explicit conversion" },
45+
{ "op_OnesComplement", "~" },
46+
{ "op_RightShift", ">>" },
47+
{ "op_UnsignedRightShift", ">>>" },
48+
{ "op_LeftShift", "<<" },
49+
{ "op_BitwiseOr", "|" },
50+
{ "op_ExclusiveOr", "^" },
51+
{ "op_True", "true" },
52+
{ "op_False", "false" }
53+
});
54+
2155
/// <summary>
2256
/// Convert an operator method name in to a symbolic name.
2357
/// A return value indicates whether the conversion succeeded.
2458
/// </summary>
2559
public static bool TryGetOperatorSymbol(this ISymbol symbol, out string operatorName)
2660
{
27-
static bool TryGetOperatorSymbolFromName(string methodName, out string operatorName)
61+
var methodName = symbol.GetName(useMetadataName: false);
62+
63+
// Most common use-case.
64+
if (methodToOperator.TryGetValue(methodName, out var opName))
2865
{
29-
var success = true;
30-
switch (methodName)
31-
{
32-
case "op_LogicalNot":
33-
operatorName = "!";
34-
break;
35-
case "op_BitwiseAnd":
36-
operatorName = "&";
37-
break;
38-
case "op_Equality":
39-
operatorName = "==";
40-
break;
41-
case "op_Inequality":
42-
operatorName = "!=";
43-
break;
44-
case "op_UnaryPlus":
45-
case "op_Addition":
46-
operatorName = "+";
47-
break;
48-
case "op_UnaryNegation":
49-
case "op_Subtraction":
50-
operatorName = "-";
51-
break;
52-
case "op_Multiply":
53-
operatorName = "*";
54-
break;
55-
case "op_Division":
56-
operatorName = "/";
57-
break;
58-
case "op_Modulus":
59-
operatorName = "%";
60-
break;
61-
case "op_GreaterThan":
62-
operatorName = ">";
63-
break;
64-
case "op_GreaterThanOrEqual":
65-
operatorName = ">=";
66-
break;
67-
case "op_LessThan":
68-
operatorName = "<";
69-
break;
70-
case "op_LessThanOrEqual":
71-
operatorName = "<=";
72-
break;
73-
case "op_Decrement":
74-
operatorName = "--";
75-
break;
76-
case "op_Increment":
77-
operatorName = "++";
78-
break;
79-
case "op_Implicit":
80-
operatorName = "implicit conversion";
81-
break;
82-
case "op_Explicit":
83-
operatorName = "explicit conversion";
84-
break;
85-
case "op_OnesComplement":
86-
operatorName = "~";
87-
break;
88-
case "op_RightShift":
89-
operatorName = ">>";
90-
break;
91-
case "op_UnsignedRightShift":
92-
operatorName = ">>>";
93-
break;
94-
case "op_LeftShift":
95-
operatorName = "<<";
96-
break;
97-
case "op_BitwiseOr":
98-
operatorName = "|";
99-
break;
100-
case "op_ExclusiveOr":
101-
operatorName = "^";
102-
break;
103-
case "op_True":
104-
operatorName = "true";
105-
break;
106-
case "op_False":
107-
operatorName = "false";
108-
break;
109-
default:
110-
var match = CheckedRegex().Match(methodName);
111-
if (match.Success)
112-
{
113-
TryGetOperatorSymbolFromName($"op_{match.Groups[1]}", out var uncheckedName);
114-
operatorName = $"checked {uncheckedName}";
115-
break;
116-
}
117-
operatorName = methodName;
118-
success = false;
119-
break;
120-
}
121-
return success;
66+
operatorName = opName;
67+
return true;
12268
}
12369

124-
var methodName = symbol.GetName(useMetadataName: false);
125-
return TryGetOperatorSymbolFromName(methodName, out operatorName);
70+
// Attempt to parse using a regexp.
71+
var match = OperatorRegex().Match(methodName);
72+
if (match.Success && methodToOperator.TryGetValue($"op_{match.Groups[2]}", out var rawOperatorName))
73+
{
74+
var prefix = match.Groups[1].Success ? "checked " : "";
75+
var postfix = match.Groups[3].Success ? "=" : "";
76+
operatorName = $"{prefix}{rawOperatorName}{postfix}";
77+
return true;
78+
}
79+
80+
operatorName = methodName;
81+
return false;
12682
}
12783

128-
[GeneratedRegex("^op_Checked(.*)$")]
129-
private static partial Regex CheckedRegex();
84+
[GeneratedRegex("^op_(Checked)?(.*?)(Assignment)?$")]
85+
private static partial Regex OperatorRegex();
13086
}
13187
}

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,41 @@ type.SpecialType is SpecialType.System_IntPtr ||
228228
return Literal.CreateGenerated(cx, parent, childIndex, type, defaultValue, location);
229229
}
230230

231+
/// <summary>
232+
/// Given an expression syntax node, attempt to resolve the target method symbol for it.
233+
/// The operation takes extension methods into account.
234+
/// </summary>
235+
/// <param name="node">The expression syntax node.</param>
236+
/// <returns>Returns the target method symbol, or null if it cannot be resolved.</returns>
237+
protected IMethodSymbol? GetTargetSymbol(ExpressionSyntax node)
238+
{
239+
var si = Context.GetSymbolInfo(node);
240+
if (si.Symbol is ISymbol symbol)
241+
{
242+
var method = symbol as IMethodSymbol;
243+
// Case for compiler-generated extension methods.
244+
return method?.TryGetExtensionMethod() ?? method;
245+
}
246+
247+
if (si.CandidateReason == CandidateReason.OverloadResolutionFailure && node is InvocationExpressionSyntax syntax)
248+
{
249+
// This seems to be a bug in Roslyn
250+
// For some reason, typeof(X).InvokeMember(...) fails to resolve the correct
251+
// InvokeMember() method, even though the number of parameters clearly identifies the correct method
252+
253+
var candidates = si.CandidateSymbols
254+
.OfType<IMethodSymbol>()
255+
.Where(method => method.Parameters.Length >= syntax.ArgumentList.Arguments.Count)
256+
.Where(method => method.Parameters.Count(p => !p.HasExplicitDefaultValue) <= syntax.ArgumentList.Arguments.Count);
257+
258+
return Context.ExtractionContext.IsStandalone ?
259+
candidates.FirstOrDefault() :
260+
candidates.SingleOrDefault();
261+
}
262+
263+
return si.Symbol as IMethodSymbol;
264+
}
265+
231266
/// <summary>
232267
/// Adapt the operator kind depending on whether it's a dynamic call or a user-operator call.
233268
/// </summary>
@@ -244,10 +279,10 @@ public static ExprKind UnaryOperatorKind(Context cx, ExprKind originalKind, Expr
244279
/// name if available.
245280
/// </summary>
246281
/// <param name="node">The expression.</param>
247-
public void OperatorCall(TextWriter trapFile, ExpressionSyntax node)
282+
public void AddOperatorCall(TextWriter trapFile, ExpressionSyntax node)
248283
{
249-
var @operator = Context.GetSymbolInfo(node);
250-
if (@operator.Symbol is IMethodSymbol method)
284+
var @operator = GetTargetSymbol(node);
285+
if (@operator is IMethodSymbol method)
251286
{
252287
var callType = GetCallType(Context, node);
253288
if (callType == CallType.Dynamic)

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Assignment.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ protected override void PopulateExpression(TextWriter trapFile)
2424
{
2525
Create(Context, Syntax.Left, this, 0);
2626
Create(Context, Syntax.Right, this, 1);
27-
2827
if (Kind != ExprKind.SIMPLE_ASSIGN && Kind != ExprKind.ASSIGN_COALESCE)
2928
{
30-
OperatorCall(trapFile, Syntax);
29+
AddOperatorCall(trapFile, Syntax);
3130
}
3231
}
3332

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Binary.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private void CreateDeferred(Context cx, ExpressionSyntax node, int child)
4040

4141
protected override void PopulateExpression(TextWriter trapFile)
4242
{
43-
OperatorCall(trapFile, Syntax);
43+
AddOperatorCall(trapFile, Syntax);
4444
CreateDeferred(Context, Syntax.Left, 0);
4545
CreateDeferred(Context, Syntax.Right, 1);
4646
}

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Cast.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected override void PopulateExpression(TextWriter trapFile)
2525
else
2626
{
2727
// Type conversion
28-
OperatorCall(trapFile, Syntax);
28+
AddOperatorCall(trapFile, Syntax);
2929
TypeMention.Create(Context, Syntax.Type, this, Type);
3030
}
3131
}

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ protected override void PopulateExpression(TextWriter trapFile)
4444

4545
var child = -1;
4646
string? memberName = null;
47-
var target = TargetSymbol;
47+
var target = GetTargetSymbol(Syntax);
4848
switch (Syntax.Expression)
4949
{
5050
case MemberAccessExpressionSyntax memberAccess when IsValidMemberAccessKind():
@@ -129,39 +129,6 @@ private static bool IsOperatorLikeCall(ExpressionNodeInfo info)
129129
method.TryGetExtensionMethod()?.MethodKind == MethodKind.UserDefinedOperator;
130130
}
131131

132-
public IMethodSymbol? TargetSymbol
133-
{
134-
get
135-
{
136-
var si = SymbolInfo;
137-
138-
if (si.Symbol is ISymbol symbol)
139-
{
140-
var method = symbol as IMethodSymbol;
141-
// Case for compiler-generated extension methods.
142-
return method?.TryGetExtensionMethod() ?? method;
143-
}
144-
145-
if (si.CandidateReason == CandidateReason.OverloadResolutionFailure)
146-
{
147-
// This seems to be a bug in Roslyn
148-
// For some reason, typeof(X).InvokeMember(...) fails to resolve the correct
149-
// InvokeMember() method, even though the number of parameters clearly identifies the correct method
150-
151-
var candidates = si.CandidateSymbols
152-
.OfType<IMethodSymbol>()
153-
.Where(method => method.Parameters.Length >= Syntax.ArgumentList.Arguments.Count)
154-
.Where(method => method.Parameters.Count(p => !p.HasExplicitDefaultValue) <= Syntax.ArgumentList.Arguments.Count);
155-
156-
return Context.ExtractionContext.IsStandalone ?
157-
candidates.FirstOrDefault() :
158-
candidates.SingleOrDefault();
159-
}
160-
161-
return si.Symbol as IMethodSymbol;
162-
}
163-
}
164-
165132
private static bool IsDelegateLikeCall(ExpressionNodeInfo info)
166133
{
167134
return IsDelegateLikeCall(info, symbol => IsFunctionPointer(symbol) || IsDelegateInvoke(symbol));

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/PostfixUnary.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ protected override void PopulateExpression(TextWriter trapFile)
2525
if ((operatorKind == ExprKind.POST_INCR || operatorKind == ExprKind.POST_DECR) &&
2626
Kind == ExprKind.OPERATOR_INVOCATION)
2727
{
28-
OperatorCall(trapFile, Syntax);
28+
AddOperatorCall(trapFile, Syntax);
2929
trapFile.mutator_invocation_mode(this, 2);
3030
}
3131
}

0 commit comments

Comments
 (0)