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>
This commit is contained in:
Copilot
2026-05-12 12:22:57 +00:00
committed by yoff
parent 93bd4e3b85
commit bd20042636
4 changed files with 34 additions and 8 deletions

View File

@@ -265,6 +265,31 @@ module Ast implements AstSig<Py::Location> {
override AstNode getChild(int index) { index = 0 and result = this.getOperation() }
}
/**
* An annotated assignment statement (`x: T = expr`, or `x: T` without
* value). The evaluation order follows CPython: annotation first, then
* the optional value, then the target binding.
*/
additional class AnnAssignStmt extends Stmt {
private Py::AnnAssign annAssign;
AnnAssignStmt() { this = TPyStmt(annAssign) }
Expr getAnnotation() { result.asExpr() = annAssign.getAnnotation() }
Expr getValue() { result.asExpr() = annAssign.getValue() }
Expr getTarget() { result.asExpr() = annAssign.getTarget() }
override AstNode getChild(int index) {
index = 0 and result = this.getAnnotation()
or
index = 1 and result = this.getValue()
or
index = 2 and result = this.getTarget()
}
}
/** An assignment expression / walrus operator (`x := expr`). */
additional class NamedExpr extends Expr {
private Py::AssignExpr assignExpr;

View File

@@ -1,12 +1,13 @@
# Annotated assignment (PEP 526). Both with and without an initializer.
a: int = 1 # $ MISSING: cfgdefines=a
b: str = "hi" # $ MISSING: cfgdefines=b
a: int = 1 # $ cfgdefines=a
b: str = "hi" # $ cfgdefines=b
# Annotation without value: the AST records `c` as defined,
# but currently the new CFG has no node for it.
c: int # $ MISSING: cfgdefines=c
# and the new CFG now visits it via the AnnAssignStmt wrapper.
c: int # $ cfgdefines=c
class K: # $ cfgdefines=K
field: int = 0 # $ MISSING: cfgdefines=field
field: int = 0 # $ cfgdefines=field

View File

@@ -18,6 +18,6 @@ def f(subject): # $ cfgdefines=f
class Point: # $ cfgdefines=Point
__match_args__ = ("x", "y") # $ cfgdefines=__match_args__
x: int # $ MISSING: cfgdefines=x
y: int # $ MISSING: cfgdefines=y
x: int # $ cfgdefines=x
y: int # $ cfgdefines=y

View File

@@ -5,7 +5,7 @@ def func[T](x: T) -> T: # $ cfgdefines=func MISSING: cfgdefines=T
class Box[T]: # $ cfgdefines=Box MISSING: cfgdefines=T
item: T # $ MISSING: cfgdefines=item
item: T # $ cfgdefines=item
# Multi-parameter, with bound and variadics.