diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll index 681b05cdf05..d59b51375a1 100644 --- a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -265,6 +265,31 @@ module Ast implements AstSig { 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; diff --git a/python/ql/test/library-tests/ControlFlow/bindings/annassign.py b/python/ql/test/library-tests/ControlFlow/bindings/annassign.py index 9f80b8bffbd..7a9ae3ab6c7 100644 --- a/python/ql/test/library-tests/ControlFlow/bindings/annassign.py +++ b/python/ql/test/library-tests/ControlFlow/bindings/annassign.py @@ -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 + diff --git a/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py b/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py index 46e7a8dd4ef..66fafdcb63d 100644 --- a/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py +++ b/python/ql/test/library-tests/ControlFlow/bindings/match_pattern.py @@ -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 diff --git a/python/ql/test/library-tests/ControlFlow/bindings/type_params.py b/python/ql/test/library-tests/ControlFlow/bindings/type_params.py index ab32370bd7d..a17f6e29dfd 100644 --- a/python/ql/test/library-tests/ControlFlow/bindings/type_params.py +++ b/python/ql/test/library-tests/ControlFlow/bindings/type_params.py @@ -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.