Python: dispatch toString/getLocation/getEnclosingCallable per branch

Replace the three big disjunctive predicates on AstNode with empty
defaults plus per-newtype-branch override classes:

  AstNode.toString()              { none() }
  AstNode.getLocation()           { none() }
  AstNode.getEnclosingCallable()  { none() }

Six private subclasses (one per newtype branch — TStmt, TExpr,
TScope, TPattern, TBoolExprPair, TBlockStmt) override these with
the branch-specific implementation. This mirrors the per-class
dispatch already used for getChild.

No behaviour change: all 24 NewCfg evaluation-order tests pass and
all 11 shared-CFG consistency queries still report 0 violations on
CPython.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2026-05-07 11:06:40 +00:00
parent 9781ee8d66
commit f2151fe232

View File

@@ -77,60 +77,13 @@ module Ast implements AstSig<Py::Location> {
/** An AST node visible to the shared CFG. */
class AstNode extends TAstNode {
/** Gets a textual representation of this AST node. */
string toString() {
exists(Py::Stmt s | this = TStmt(s) and result = s.toString())
or
exists(Py::Expr e | this = TExpr(e) and result = e.toString())
or
exists(Py::Scope sc | this = TScope(sc) and result = sc.toString())
or
exists(Py::Pattern p | this = TPattern(p) and result = p.toString())
or
exists(Py::BoolExpr be | this = TBoolExprPair(be, _) and result = be.getOperator())
or
exists(string slot | this = TBlockStmt(_, slot) and result = "block:" + slot)
}
string toString() { none() }
/** Gets the location of this AST node. */
Py::Location getLocation() {
exists(Py::Stmt s | this = TStmt(s) and result = s.getLocation())
or
exists(Py::Expr e | this = TExpr(e) and result = e.getLocation())
or
exists(Py::Scope sc | this = TScope(sc) and result = sc.getLocation())
or
exists(Py::Pattern p | this = TPattern(p) and result = p.getLocation())
or
exists(Py::BoolExpr be, int index |
this = TBoolExprPair(be, index) and result = be.getValue(index).getLocation()
)
or
// BlockStmt has no native location; approximate with the first
// item's location.
exists(Py::AstNode parent, string slot |
this = TBlockStmt(parent, slot) and
result = getBodyStmtList(parent, slot).getItem(0).getLocation()
)
}
Py::Location getLocation() { none() }
/** Gets the enclosing callable that contains this node, if any. */
Callable getEnclosingCallable() {
exists(Py::Stmt s | this = TStmt(s) and result.asScope() = s.getScope())
or
exists(Py::Expr e | this = TExpr(e) and result.asScope() = e.getScope())
or
exists(Py::Scope sc | this = TScope(sc) and result.asScope() = sc.getEnclosingScope())
or
exists(Py::Pattern p | this = TPattern(p) and result.asScope() = p.getScope())
or
exists(Py::BoolExpr be | this = TBoolExprPair(be, _) and result.asScope() = be.getScope())
or
exists(Py::AstNode parent | this = TBlockStmt(parent, _) |
result.asScope() = parent.(Py::Scope)
or
result.asScope() = parent.(Py::Stmt).getScope()
)
}
Callable getEnclosingCallable() { none() }
/** Gets the underlying Python `Stmt`, if this node wraps one. */
Py::Stmt asStmt() { this = TStmt(result) }
@@ -152,6 +105,94 @@ module Ast implements AstSig<Py::Location> {
AstNode getChild(int index) { none() }
}
/** Implementation of `AstNode` predicates for `TStmt` nodes. */
private class TStmtAstNode extends AstNode, TStmt {
private Py::Stmt s;
TStmtAstNode() { this = TStmt(s) }
override string toString() { result = s.toString() }
override Py::Location getLocation() { result = s.getLocation() }
override Callable getEnclosingCallable() { result.asScope() = s.getScope() }
}
/** Implementation of `AstNode` predicates for `TExpr` nodes. */
private class TExprAstNode extends AstNode, TExpr {
private Py::Expr e;
TExprAstNode() { this = TExpr(e) }
override string toString() { result = e.toString() }
override Py::Location getLocation() { result = e.getLocation() }
override Callable getEnclosingCallable() { result.asScope() = e.getScope() }
}
/** Implementation of `AstNode` predicates for `TScope` nodes. */
private class TScopeAstNode extends AstNode, TScope {
private Py::Scope sc;
TScopeAstNode() { this = TScope(sc) }
override string toString() { result = sc.toString() }
override Py::Location getLocation() { result = sc.getLocation() }
override Callable getEnclosingCallable() { result.asScope() = sc.getEnclosingScope() }
}
/** Implementation of `AstNode` predicates for `TPattern` nodes. */
private class TPatternAstNode extends AstNode, TPattern {
private Py::Pattern p;
TPatternAstNode() { this = TPattern(p) }
override string toString() { result = p.toString() }
override Py::Location getLocation() { result = p.getLocation() }
override Callable getEnclosingCallable() { result.asScope() = p.getScope() }
}
/** Implementation of `AstNode` predicates for synthetic `TBoolExprPair` nodes. */
private class TBoolExprPairAstNode extends AstNode, TBoolExprPair {
private Py::BoolExpr be;
private int index;
TBoolExprPairAstNode() { this = TBoolExprPair(be, index) }
override string toString() { result = be.getOperator() }
override Py::Location getLocation() { result = be.getValue(index).getLocation() }
override Callable getEnclosingCallable() { result.asScope() = be.getScope() }
}
/** Implementation of `AstNode` predicates for synthetic `TBlockStmt` nodes. */
private class TBlockStmtAstNode extends AstNode, TBlockStmt {
private Py::AstNode parent;
private string slot;
TBlockStmtAstNode() { this = TBlockStmt(parent, slot) }
override string toString() { result = "block:" + slot }
// BlockStmt has no native location; approximate with the first
// item's location.
override Py::Location getLocation() {
result = getBodyStmtList(parent, slot).getItem(0).getLocation()
}
override Callable getEnclosingCallable() {
result.asScope() = parent.(Py::Scope)
or
result.asScope() = parent.(Py::Stmt).getScope()
}
}
/** Gets the immediately enclosing callable that contains `node`. */
Callable getEnclosingCallable(AstNode node) { result = node.getEnclosingCallable() }