C# 14: User defined compound assignment operators.#21372
C# 14: User defined compound assignment operators.#21372michaelnebel wants to merge 7 commits intogithub:mainfrom
Conversation
07345fe to
a9a8936
Compare
76f65d5 to
fafe360
Compare
|
DCA looks good
|
There was a problem hiding this comment.
Pull request overview
This PR adds C# 14 support for user-defined compound assignment operators (for example a += b where operator += is declared) by extracting them as operator invocations rather than desugaring them into a = a + b, and updates QL dispatch/dataflow plus tests accordingly.
Changes:
- Add new extractor handling for compound assignment expressions to emit direct operator-call extraction for user-defined (instance) compound assignment operators.
- Extend the C# QL library to represent and dispatch
CompoundAssignmentOperatorCalldistinctly from ordinaryOperatorCall. - Update/extend AST and dataflow library tests and expected outputs for compound assignment operator declarations and invocations (including extension-based operators).
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| csharp/ql/test/library-tests/operators/operators.cs | Adds user-defined compound assignment operator declarations and invocations for AST coverage. |
| csharp/ql/test/library-tests/operators/PrintAst.expected | Updates AST expectations to include compound assignment operators/calls. |
| csharp/ql/test/library-tests/operators/Operators1.expected | Updates operator location expectations after AST shifts. |
| csharp/ql/test/library-tests/operators/Operators2.expected | Updates operator location expectations after AST shifts. |
| csharp/ql/test/library-tests/operators/Operators3.expected | Updates conversion-operator location expectations after AST shifts. |
| csharp/ql/test/library-tests/operators/Operators4.expected | Updates conversion-operator location expectations after AST shifts. |
| csharp/ql/test/library-tests/dataflow/operators/Operator.cs | Adds a dataflow test for user-defined compound assignment operator calls. |
| csharp/ql/test/library-tests/dataflow/operators/operatorFlow.expected | Updates expected dataflow graph to reflect compound assignment operator modeling. |
| csharp/ql/test/library-tests/dataflow/extensions/extensions.cs | Adds an extension-based compound assignment operator and a flow test covering it. |
| csharp/ql/test/library-tests/dataflow/extensions/ExtensionFlow.expected | Updates expected dataflow output for the new extension compound assignment test. |
| csharp/ql/lib/semmle/code/csharp/exprs/Call.qll | Introduces CompoundAssignmentOperatorCall in the QL expression hierarchy. |
| csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll | Adjusts dispatch to treat compound assignment operator calls as dispatchable calls with a qualifier. |
| csharp/ql/lib/semmle/code/csharp/Callable.qll | Adds QL operator classes for compound assignment operator declarations. |
| csharp/ql/lib/change-notes/2026-03-06-compound-assignment-operations.md | Adds changelog entry for the new C# 14 support. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/UserCompoundAssignmentInvocation.cs | New extractor expression node for user-defined compound assignment operator invocations. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Invocation.cs | Refactors call target resolution to use a shared helper. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Factory.cs | Routes compound assignment syntax kinds through new compound-assignment extraction logic. |
| csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/CompoundAssignment.cs | New helper to decide between desugaring vs direct operator-call extraction for compound assignments. |
| csharp/extractor/Semmle.Extraction.CSharp/CodeAnalysisExtensions/SymbolExtensions.cs | Adds ExpressionNodeInfo.GetTargetSymbol helper for consistent target resolution. |
| csharp/extractor/Semmle.Extraction.CSharp.Util/SymbolExtensions.cs | Extends operator-symbol parsing to recognize checked and assignment operator method names. |
You can also share your feedback on Copilot code review. Take the survey.
| * Either a unary operator (`UnaryOperator`), a binary operator | ||
| * (`BinaryOperator`), or a conversion operator (`ConversionOperator`). | ||
| * (`BinaryOperator`), a conversion operator (`ConversionOperator`), or | ||
| * a (`CompoundAssignmentOperator`). |
There was a problem hiding this comment.
Minor wording/grammar: “or a (CompoundAssignmentOperator).” reads oddly and is missing the noun. Consider changing to “or a compound assignment operator (CompoundAssignmentOperator).”
| * a (`CompoundAssignmentOperator`). | |
| * a compound assignment operator (`CompoundAssignmentOperator`). |
| * class A { | ||
| * public void operator+=(A other) { | ||
| * ... | ||
| * } | ||
| * | ||
| * public A Add(A other) { | ||
| * return this += other; | ||
| * } |
There was a problem hiding this comment.
The example in this doc comment appears to be invalid C#: the operator is declared to return void, but the sample then uses return this += other;. Consider adjusting the example to either not return the compound assignment, or to show a (valid) return pattern that matches the operator’s return type.
| IntVector iv3 = new IntVector(4); // vector of 4 x 0 | ||
| iv3 += iv2; // iv3 contains 4 x 2 | ||
|
|
||
| // The following operations doesn't do anything. |
There was a problem hiding this comment.
Grammar nit: “The following operations doesn't do anything.” → “The following operations don't do anything.”
| // The following operations doesn't do anything. | |
| // The following operations don't do anything. |
hvitved
left a comment
There was a problem hiding this comment.
LGTM, some trivial comments.
| return symbol.CanBeReferencedByName ? name : name.Substring(symbol.Name.LastIndexOf('.') + 1); | ||
| } | ||
|
|
||
| private static readonly Dictionary<string, string> methodToOperator = new Dictionary<string, string> |
There was a problem hiding this comment.
Nit: Can be a ReadOnlyDictionary.
| * A user-defined compound assignment operator. | ||
| * | ||
| * Either an addition operator (`AddCompoundAssignmentOperator`), a checked addition operator | ||
| * (`CheckedAddCompoundAssignmentOperator`) a subtraction operator (`SubCompoundAssignmentOperator`), a checked |
|
|
||
| override Expr getArgument(int i) { | ||
| exists(int j | result = this.getChildExpr(j) | | ||
| if this.isOrdinaryStaticCall() then (j = i and j >= 0) else (j = i - 1 and j >= -1) |
There was a problem hiding this comment.
Alternatively i >=0 and if this.isOrdinaryStaticCall() then j = i else j = i - 1
|
Putting this back in Draft as there has been significant changes in main (removal of expanded assignments). |
…ht operator symbol (operators can also be declared in extensions).
…ssignable definition).
fafe360 to
4018db1
Compare
In this PR we introduce support for user-defined compound assignment operators.
Compound assignment operator calls are not implicitly assignments, but instead the left hand side of the compound assignment is updated. That is, user-defined compound operator calls behave more like instance method calls than static operator calls.
Had we instead declared
then
a1 += a2is also valid, but instead it meansa1 + a2and assign the result toa1.Notes on the implementation