Shared CFG: add defaulted getWhileElse/getForeachElse to AstSig

Lifts the shared-controlflow changes out of the larger Python new-CFG
PR so they can be reviewed independently. No behavioural change for
existing languages (Java, C#, Ruby, Swift, Go, ...) — both new
predicates default to `none()` and the Make0 loop-edge case
extensions only fire when a language overrides them.

Python's upcoming AstNodeImpl wiring uses these predicates to model
`while-else` and `for-else`, where the `else` block runs when the
loop condition becomes false (rather than via a `break`).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
yoff
2026-06-02 13:30:51 +00:00
committed by yoff
parent aaa3b363e1
commit d5583712cb
2 changed files with 41 additions and 3 deletions

View File

@@ -0,0 +1,4 @@
---
category: feature
---
* The `AstSig` signature gains two new defaulted predicates `getWhileElse` and `getForeachElse`, allowing languages (like Python) to model `while-else` / `for-else` constructs where the `else` branch is taken when the loop condition becomes false (rather than via a `break`). Existing languages that do not provide these predicates retain the previous behaviour.

View File

@@ -211,6 +211,20 @@ signature module AstSig<LocationSig Location> {
*/
default AstNode getTryElse(TryStmt try) { none() }
/**
* Gets the `else` block of this `while` loop statement, if any.
*
* Only some languages (e.g. Python) support `while-else` constructs.
*/
default AstNode getWhileElse(WhileStmt loop) { none() }
/**
* Gets the `else` block of this `foreach` loop statement, if any.
*
* Only some languages (e.g. Python) support `for-else` constructs.
*/
default AstNode getForeachElse(ForeachStmt loop) { none() }
/** A catch clause in a try statement. */
class CatchClause extends AstNode {
/** Gets the variable declared by this catch clause. */
@@ -1549,19 +1563,32 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
n2.isBefore(loopstmt.getBody())
or
n1.isAfterFalse(cond) and
n2.isAfter(loopstmt)
(
n2.isBefore(getWhileElse(loopstmt))
or
not exists(getWhileElse(loopstmt)) and n2.isAfter(loopstmt)
)
or
n1.isAfter(loopstmt.getBody()) and
n2.isAdditional(loopstmt, loopHeaderTag())
)
or
exists(WhileStmt whilestmt |
n1.isAfter(getWhileElse(whilestmt)) and
n2.isAfter(whilestmt)
)
or
exists(ForeachStmt foreachstmt |
n1.isBefore(foreachstmt) and
n2.isBefore(foreachstmt.getCollection())
or
n1.isAfterValue(foreachstmt.getCollection(),
any(EmptinessSuccessor t | t.getValue() = true)) and
n2.isAfter(foreachstmt)
(
n2.isBefore(getForeachElse(foreachstmt))
or
not exists(getForeachElse(foreachstmt)) and n2.isAfter(foreachstmt)
)
or
n1.isAfterValue(foreachstmt.getCollection(),
any(EmptinessSuccessor t | t.getValue() = false)) and
@@ -1574,10 +1601,17 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
n2.isAdditional(foreachstmt, loopHeaderTag())
or
n1.isAdditional(foreachstmt, loopHeaderTag()) and
n2.isAfter(foreachstmt)
(
n2.isBefore(getForeachElse(foreachstmt))
or
not exists(getForeachElse(foreachstmt)) and n2.isAfter(foreachstmt)
)
or
n1.isAdditional(foreachstmt, loopHeaderTag()) and
n2.isBefore(foreachstmt.getVariable())
or
n1.isAfter(getForeachElse(foreachstmt)) and
n2.isAfter(foreachstmt)
)
or
exists(ForStmt forstmt, PreControlFlowNode condentry |