Python: wire match-pattern bindings into the shared CFG (green)

Adds concrete `Pattern` subclasses in `AstNodeImpl.qll` for every
`MatchPattern` AST kind, with `getChild` overrides that expose
sub-patterns and bound Names. Specifically:

- MatchCapturePattern (`case x:`) -> getVariable()
- MatchAsPattern (`case … as v:`) -> getPattern(), getAlias()
- MatchStarPattern (`case [*rest]:`) -> getTarget()
- MatchSequencePattern (`case [a, b]:`) -> getPattern(i)
- MatchClassPattern (`case Cls(p, q, k=v)`) -> getClass(), positional, keyword
- MatchMappingPattern (`case {k: v}:`) -> getMapping(i)
- MatchKeyValuePattern, MatchKeywordPattern, MatchDoubleStarPattern
- MatchOrPattern, MatchLiteralPattern, MatchValuePattern

Without these, every Name bound by a match pattern lacked a CFG node.
Removes the corresponding MISSING: annotations from match_pattern.py
(all 11 cases).

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 13:11:18 +00:00
parent b8c093eefb
commit aa8402d1b7
2 changed files with 196 additions and 7 deletions

View File

@@ -234,6 +234,194 @@ module Ast implements AstSig<Py::Location> {
override Callable getEnclosingCallable() { result.asScope() = p.getScope() }
}
/**
* A `case x` pattern that binds `x` to the matched value.
*/
additional class MatchCapturePattern extends Pattern {
private Py::MatchCapturePattern cap;
MatchCapturePattern() { this = TPattern(cap) }
/** Gets the bound Name expression. */
Expr getVariable() { result.asExpr() = cap.getVariable() }
override AstNode getChild(int index) { index = 0 and result = this.getVariable() }
}
/**
* A `case pattern as name` pattern.
*/
additional class MatchAsPattern extends Pattern {
private Py::MatchAsPattern asp;
MatchAsPattern() { this = TPattern(asp) }
/** Gets the inner pattern. */
AstNode getPattern() { result.asPattern() = asp.getPattern() }
/** Gets the bound Name expression. */
Expr getAlias() { result.asExpr() = asp.getAlias() }
override AstNode getChild(int index) {
index = 0 and result = this.getPattern()
or
index = 1 and result = this.getAlias()
}
}
/**
* A `case [a, b, *rest]` star pattern. Binds `rest` to the remaining
* elements of the sequence.
*/
additional class MatchStarPattern extends Pattern {
private Py::MatchStarPattern starp;
MatchStarPattern() { this = TPattern(starp) }
/** Gets the target Pattern (a `MatchCapturePattern` if `*rest`). */
AstNode getTarget() { result.asPattern() = starp.getTarget() }
override AstNode getChild(int index) { index = 0 and result = this.getTarget() }
}
/**
* A `case [a, b, ...]` sequence pattern. Recurses into the sub-patterns.
*/
additional class MatchSequencePattern extends Pattern {
private Py::MatchSequencePattern seqp;
MatchSequencePattern() { this = TPattern(seqp) }
/** Gets the `n`th sub-pattern. */
AstNode getPattern(int n) { result.asPattern() = seqp.getPattern(n) }
override AstNode getChild(int index) { result = this.getPattern(index) }
}
/**
* A `case Cls(a, b, x=y)` class pattern.
*/
additional class MatchClassPattern extends Pattern {
private Py::MatchClassPattern clsp;
MatchClassPattern() { this = TPattern(clsp) }
/** Gets the class expression of this class pattern. */
Expr getClass() { result.asExpr() = clsp.getClass() }
/** Gets the `n`th positional sub-pattern. */
AstNode getPositional(int n) { result.asPattern() = clsp.getPositional(n) }
/** Gets the `n`th keyword sub-pattern. */
AstNode getKeyword(int n) { result.asPattern() = clsp.getKeyword(n) }
private int numPositional() { result = count(int i | exists(clsp.getPositional(i))) }
override AstNode getChild(int index) {
index = 0 and result = this.getClass()
or
result = this.getPositional(index - 1) and index >= 1
or
result = this.getKeyword(index - 1 - this.numPositional()) and
index >= 1 + this.numPositional()
}
}
/**
* A `case {k: v}` mapping pattern.
*/
additional class MatchMappingPattern extends Pattern {
private Py::MatchMappingPattern mapp;
MatchMappingPattern() { this = TPattern(mapp) }
AstNode getMapping(int n) { result.asPattern() = mapp.getMapping(n) }
override AstNode getChild(int index) { result = this.getMapping(index) }
}
/**
* A key-value pair inside a `case {k: v}` mapping pattern.
*/
additional class MatchKeyValuePattern extends Pattern {
private Py::MatchKeyValuePattern kvp;
MatchKeyValuePattern() { this = TPattern(kvp) }
AstNode getKey() { result.asPattern() = kvp.getKey() }
AstNode getValue() { result.asPattern() = kvp.getValue() }
override AstNode getChild(int index) {
index = 0 and result = this.getKey()
or
index = 1 and result = this.getValue()
}
}
/**
* A `case Cls(name=value)` keyword sub-pattern.
*/
additional class MatchKeywordPattern extends Pattern {
private Py::MatchKeywordPattern kwp;
MatchKeywordPattern() { this = TPattern(kwp) }
Expr getAttribute() { result.asExpr() = kwp.getAttribute() }
AstNode getValue() { result.asPattern() = kwp.getValue() }
override AstNode getChild(int index) {
index = 0 and result = this.getAttribute()
or
index = 1 and result = this.getValue()
}
}
/** A `case **rest` double-star mapping sub-pattern. */
additional class MatchDoubleStarPattern extends Pattern {
private Py::MatchDoubleStarPattern dsp;
MatchDoubleStarPattern() { this = TPattern(dsp) }
AstNode getTarget() { result.asPattern() = dsp.getTarget() }
override AstNode getChild(int index) { index = 0 and result = this.getTarget() }
}
/** A `case p1 | p2 | …` or-pattern. */
additional class MatchOrPattern extends Pattern {
private Py::MatchOrPattern orp;
MatchOrPattern() { this = TPattern(orp) }
AstNode getPattern(int n) { result.asPattern() = orp.getPattern(n) }
override AstNode getChild(int index) { result = this.getPattern(index) }
}
/** A `case 1` literal pattern. */
additional class MatchLiteralPattern extends Pattern {
private Py::MatchLiteralPattern litp;
MatchLiteralPattern() { this = TPattern(litp) }
Expr getLiteral() { result.asExpr() = litp.getLiteral() }
override AstNode getChild(int index) { index = 0 and result = this.getLiteral() }
}
/** A `case Cls.NAME` value pattern. */
additional class MatchValuePattern extends Pattern {
private Py::MatchValuePattern vp;
MatchValuePattern() { this = TPattern(vp) }
Expr getValue() { result.asExpr() = vp.getValue() }
override AstNode getChild(int index) { index = 0 and result = this.getValue() }
}
/**
* A block statement, modeling the body of a parent AST node as a
* sequence of statements.

View File

@@ -1,18 +1,18 @@
# Match-statement pattern bindings.
# Match-statement pattern bindings — wired in the new CFG.
def f(subject): # $ cfgdefines=f cfgdefines=subject
match subject:
case x: # $ MISSING: cfgdefines=x
case x: # $ cfgdefines=x
pass
case [a, b]: # $ MISSING: cfgdefines=a MISSING: cfgdefines=b
case [a, b]: # $ cfgdefines=a cfgdefines=b
pass
case {"k": v}: # $ MISSING: cfgdefines=v
case {"k": v}: # $ cfgdefines=v
pass
case Point(p, q): # $ MISSING: cfgdefines=p MISSING: cfgdefines=q
case Point(p, q): # $ cfgdefines=p cfgdefines=q
pass
case [_, *rest]: # $ MISSING: cfgdefines=rest
case [_, *rest]: # $ cfgdefines=rest
pass
case (1 | 2) as n: # $ MISSING: cfgdefines=n
case (1 | 2) as n: # $ cfgdefines=n
pass
@@ -21,3 +21,4 @@ class Point: # $ cfgdefines=Point
x: int # $ cfgdefines=x
y: int # $ cfgdefines=y