Skip to content

Commit f123072

Browse files
Copilotyoff
authored andcommitted
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>
1 parent ba9dc9f commit f123072

2 files changed

Lines changed: 196 additions & 7 deletions

File tree

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

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,194 @@ module Ast implements AstSig<Py::Location> {
234234
override Callable getEnclosingCallable() { result.asScope() = p.getScope() }
235235
}
236236

237+
/**
238+
* A `case x` pattern that binds `x` to the matched value.
239+
*/
240+
additional class MatchCapturePattern extends Pattern {
241+
private Py::MatchCapturePattern cap;
242+
243+
MatchCapturePattern() { this = TPattern(cap) }
244+
245+
/** Gets the bound Name expression. */
246+
Expr getVariable() { result.asExpr() = cap.getVariable() }
247+
248+
override AstNode getChild(int index) { index = 0 and result = this.getVariable() }
249+
}
250+
251+
/**
252+
* A `case pattern as name` pattern.
253+
*/
254+
additional class MatchAsPattern extends Pattern {
255+
private Py::MatchAsPattern asp;
256+
257+
MatchAsPattern() { this = TPattern(asp) }
258+
259+
/** Gets the inner pattern. */
260+
AstNode getPattern() { result.asPattern() = asp.getPattern() }
261+
262+
/** Gets the bound Name expression. */
263+
Expr getAlias() { result.asExpr() = asp.getAlias() }
264+
265+
override AstNode getChild(int index) {
266+
index = 0 and result = this.getPattern()
267+
or
268+
index = 1 and result = this.getAlias()
269+
}
270+
}
271+
272+
/**
273+
* A `case [a, b, *rest]` star pattern. Binds `rest` to the remaining
274+
* elements of the sequence.
275+
*/
276+
additional class MatchStarPattern extends Pattern {
277+
private Py::MatchStarPattern starp;
278+
279+
MatchStarPattern() { this = TPattern(starp) }
280+
281+
/** Gets the target Pattern (a `MatchCapturePattern` if `*rest`). */
282+
AstNode getTarget() { result.asPattern() = starp.getTarget() }
283+
284+
override AstNode getChild(int index) { index = 0 and result = this.getTarget() }
285+
}
286+
287+
/**
288+
* A `case [a, b, ...]` sequence pattern. Recurses into the sub-patterns.
289+
*/
290+
additional class MatchSequencePattern extends Pattern {
291+
private Py::MatchSequencePattern seqp;
292+
293+
MatchSequencePattern() { this = TPattern(seqp) }
294+
295+
/** Gets the `n`th sub-pattern. */
296+
AstNode getPattern(int n) { result.asPattern() = seqp.getPattern(n) }
297+
298+
override AstNode getChild(int index) { result = this.getPattern(index) }
299+
}
300+
301+
/**
302+
* A `case Cls(a, b, x=y)` class pattern.
303+
*/
304+
additional class MatchClassPattern extends Pattern {
305+
private Py::MatchClassPattern clsp;
306+
307+
MatchClassPattern() { this = TPattern(clsp) }
308+
309+
/** Gets the class expression of this class pattern. */
310+
Expr getClass() { result.asExpr() = clsp.getClass() }
311+
312+
/** Gets the `n`th positional sub-pattern. */
313+
AstNode getPositional(int n) { result.asPattern() = clsp.getPositional(n) }
314+
315+
/** Gets the `n`th keyword sub-pattern. */
316+
AstNode getKeyword(int n) { result.asPattern() = clsp.getKeyword(n) }
317+
318+
private int numPositional() { result = count(int i | exists(clsp.getPositional(i))) }
319+
320+
override AstNode getChild(int index) {
321+
index = 0 and result = this.getClass()
322+
or
323+
result = this.getPositional(index - 1) and index >= 1
324+
or
325+
result = this.getKeyword(index - 1 - this.numPositional()) and
326+
index >= 1 + this.numPositional()
327+
}
328+
}
329+
330+
/**
331+
* A `case {k: v}` mapping pattern.
332+
*/
333+
additional class MatchMappingPattern extends Pattern {
334+
private Py::MatchMappingPattern mapp;
335+
336+
MatchMappingPattern() { this = TPattern(mapp) }
337+
338+
AstNode getMapping(int n) { result.asPattern() = mapp.getMapping(n) }
339+
340+
override AstNode getChild(int index) { result = this.getMapping(index) }
341+
}
342+
343+
/**
344+
* A key-value pair inside a `case {k: v}` mapping pattern.
345+
*/
346+
additional class MatchKeyValuePattern extends Pattern {
347+
private Py::MatchKeyValuePattern kvp;
348+
349+
MatchKeyValuePattern() { this = TPattern(kvp) }
350+
351+
AstNode getKey() { result.asPattern() = kvp.getKey() }
352+
353+
AstNode getValue() { result.asPattern() = kvp.getValue() }
354+
355+
override AstNode getChild(int index) {
356+
index = 0 and result = this.getKey()
357+
or
358+
index = 1 and result = this.getValue()
359+
}
360+
}
361+
362+
/**
363+
* A `case Cls(name=value)` keyword sub-pattern.
364+
*/
365+
additional class MatchKeywordPattern extends Pattern {
366+
private Py::MatchKeywordPattern kwp;
367+
368+
MatchKeywordPattern() { this = TPattern(kwp) }
369+
370+
Expr getAttribute() { result.asExpr() = kwp.getAttribute() }
371+
372+
AstNode getValue() { result.asPattern() = kwp.getValue() }
373+
374+
override AstNode getChild(int index) {
375+
index = 0 and result = this.getAttribute()
376+
or
377+
index = 1 and result = this.getValue()
378+
}
379+
}
380+
381+
/** A `case **rest` double-star mapping sub-pattern. */
382+
additional class MatchDoubleStarPattern extends Pattern {
383+
private Py::MatchDoubleStarPattern dsp;
384+
385+
MatchDoubleStarPattern() { this = TPattern(dsp) }
386+
387+
AstNode getTarget() { result.asPattern() = dsp.getTarget() }
388+
389+
override AstNode getChild(int index) { index = 0 and result = this.getTarget() }
390+
}
391+
392+
/** A `case p1 | p2 | …` or-pattern. */
393+
additional class MatchOrPattern extends Pattern {
394+
private Py::MatchOrPattern orp;
395+
396+
MatchOrPattern() { this = TPattern(orp) }
397+
398+
AstNode getPattern(int n) { result.asPattern() = orp.getPattern(n) }
399+
400+
override AstNode getChild(int index) { result = this.getPattern(index) }
401+
}
402+
403+
/** A `case 1` literal pattern. */
404+
additional class MatchLiteralPattern extends Pattern {
405+
private Py::MatchLiteralPattern litp;
406+
407+
MatchLiteralPattern() { this = TPattern(litp) }
408+
409+
Expr getLiteral() { result.asExpr() = litp.getLiteral() }
410+
411+
override AstNode getChild(int index) { index = 0 and result = this.getLiteral() }
412+
}
413+
414+
/** A `case Cls.NAME` value pattern. */
415+
additional class MatchValuePattern extends Pattern {
416+
private Py::MatchValuePattern vp;
417+
418+
MatchValuePattern() { this = TPattern(vp) }
419+
420+
Expr getValue() { result.asExpr() = vp.getValue() }
421+
422+
override AstNode getChild(int index) { index = 0 and result = this.getValue() }
423+
}
424+
237425
/**
238426
* A block statement, modeling the body of a parent AST node as a
239427
* sequence of statements.
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
# Match-statement pattern bindings.
1+
# Match-statement pattern bindings — wired in the new CFG.
22

33
def f(subject): # $ cfgdefines=f cfgdefines=subject
44
match subject:
5-
case x: # $ MISSING: cfgdefines=x
5+
case x: # $ cfgdefines=x
66
pass
7-
case [a, b]: # $ MISSING: cfgdefines=a MISSING: cfgdefines=b
7+
case [a, b]: # $ cfgdefines=a cfgdefines=b
88
pass
9-
case {"k": v}: # $ MISSING: cfgdefines=v
9+
case {"k": v}: # $ cfgdefines=v
1010
pass
11-
case Point(p, q): # $ MISSING: cfgdefines=p MISSING: cfgdefines=q
11+
case Point(p, q): # $ cfgdefines=p cfgdefines=q
1212
pass
13-
case [_, *rest]: # $ MISSING: cfgdefines=rest
13+
case [_, *rest]: # $ cfgdefines=rest
1414
pass
15-
case (1 | 2) as n: # $ MISSING: cfgdefines=n
15+
case (1 | 2) as n: # $ cfgdefines=n
1616
pass
1717

1818

@@ -21,3 +21,4 @@ class Point: # $ cfgdefines=Point
2121
x: int # $ cfgdefines=x
2222
y: int # $ cfgdefines=y
2323

24+

0 commit comments

Comments
 (0)