Skip to content

Commit 5d60a0d

Browse files
Copilotyoff
authored andcommitted
Python: wire AnnAssign into the shared CFG (green)
Adds an `AnnAssignStmt` wrapper in `AstNodeImpl.qll` so that PEP 526 annotated assignments (`x: int = 1`, `x: int`) participate in the control flow graph. Evaluation order follows CPython: annotation, optional value, target binding. Without this, `x: int = 1` had no CFG node for `x` even though `Name.defines(v)` returns true for it on the AST side. SSA built on the new CFG would therefore miss every annotated-assignment write. Removes the corresponding MISSING: annotations from the CFG-binding gap test: - annassign.py — all four cases now green. - match_pattern.py — class-body annotated fields (`x: int`, `y: int`). - type_params.py — `item: T` inside class. Verified: all 24 ControlFlow/evaluation-order tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 336c7a4 commit 5d60a0d

4 files changed

Lines changed: 34 additions & 8 deletions

File tree

python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,31 @@ module Ast implements AstSig<Py::Location> {
265265
override AstNode getChild(int index) { index = 0 and result = this.getOperation() }
266266
}
267267

268+
/**
269+
* An annotated assignment statement (`x: T = expr`, or `x: T` without
270+
* value). The evaluation order follows CPython: annotation first, then
271+
* the optional value, then the target binding.
272+
*/
273+
additional class AnnAssignStmt extends Stmt {
274+
private Py::AnnAssign annAssign;
275+
276+
AnnAssignStmt() { this = TPyStmt(annAssign) }
277+
278+
Expr getAnnotation() { result.asExpr() = annAssign.getAnnotation() }
279+
280+
Expr getValue() { result.asExpr() = annAssign.getValue() }
281+
282+
Expr getTarget() { result.asExpr() = annAssign.getTarget() }
283+
284+
override AstNode getChild(int index) {
285+
index = 0 and result = this.getAnnotation()
286+
or
287+
index = 1 and result = this.getValue()
288+
or
289+
index = 2 and result = this.getTarget()
290+
}
291+
}
292+
268293
/** An assignment expression / walrus operator (`x := expr`). */
269294
additional class NamedExpr extends Expr {
270295
private Py::AssignExpr assignExpr;
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# Annotated assignment (PEP 526). Both with and without an initializer.
22

3-
a: int = 1 # $ MISSING: cfgdefines=a
4-
b: str = "hi" # $ MISSING: cfgdefines=b
3+
a: int = 1 # $ cfgdefines=a
4+
b: str = "hi" # $ cfgdefines=b
55

66
# Annotation without value: the AST records `c` as defined,
7-
# but currently the new CFG has no node for it.
8-
c: int # $ MISSING: cfgdefines=c
7+
# and the new CFG now visits it via the AnnAssignStmt wrapper.
8+
c: int # $ cfgdefines=c
99

1010
class K: # $ cfgdefines=K
11-
field: int = 0 # $ MISSING: cfgdefines=field
11+
field: int = 0 # $ cfgdefines=field
12+
1213

python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ def f(subject): # $ cfgdefines=f
1818

1919
class Point: # $ cfgdefines=Point
2020
__match_args__ = ("x", "y") # $ cfgdefines=__match_args__
21-
x: int # $ MISSING: cfgdefines=x
22-
y: int # $ MISSING: cfgdefines=y
21+
x: int # $ cfgdefines=x
22+
y: int # $ cfgdefines=y
2323

python/ql/test/library-tests/ControlFlow/bindings/type_params.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ def func[T](x: T) -> T: # $ cfgdefines=func MISSING: cfgdefines=T
55

66

77
class Box[T]: # $ cfgdefines=Box MISSING: cfgdefines=T
8-
item: T # $ MISSING: cfgdefines=item
8+
item: T # $ cfgdefines=item
99

1010

1111
# Multi-parameter, with bound and variadics.

0 commit comments

Comments
 (0)