@@ -148,13 +148,39 @@ signature module AstSig<LocationSig Location> {
148148
149149 /** A for-loop that iterates over the elements of a collection. */
150150 class ForeachStmt extends LoopStmt {
151- /** Gets the variable declaration of this `foreach` loop. */
151+ /**
152+ * Gets the variable declaration of this `foreach` loop, if any.
153+ *
154+ * A `foreach` loop that binds more than one variable per iteration (for
155+ * example when destructuring is used, as in `for k, v := range m` in Go, or
156+ * `for (a, b) in ...` in Rust/Python/Swift) should instead opt in to the
157+ * synthesized per-iteration element node (see `foreachHasElementNode`) and
158+ * destructure that element into its variables, in which case this predicate
159+ * need not have a result.
160+ */
152161 Expr getVariable ( ) ;
153162
154163 /** Gets the collection expression of this `foreach` loop. */
155164 Expr getCollection ( ) ;
156165 }
157166
167+ /**
168+ * Holds if `foreach` has a synthesized per-iteration "element" node, that is,
169+ * an additional control flow node representing the element produced by each
170+ * iteration of the loop.
171+ *
172+ * When this holds, the shared library routes control flow from the loop
173+ * header (and from the non-empty collection) into the element node, but the
174+ * language is then responsible for wiring control flow out of the element
175+ * node and on to the loop body (typically destructuring the element into the
176+ * loop variables). In this mode the shared `getVariable` routing is not used.
177+ *
178+ * This is useful for languages whose loop variables are bound by extracting
179+ * components from an implicit "current element" value (as opposed to being
180+ * evaluated as ordinary target expressions).
181+ */
182+ default predicate foreachHasElementNode ( ForeachStmt foreach ) { none ( ) }
183+
158184 /**
159185 * A `break` statement.
160186 *
@@ -684,6 +710,8 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
684710
685711 private string patternMatchTrueTag ( ) { result = "[MatchTrue]" }
686712
713+ private string foreachElementTag ( ) { result = "[ForeachElement]" }
714+
687715 /**
688716 * Holds if an additional node tagged with `tag` should be created for
689717 * `n`. Edges targeting such nodes are labeled with `t` and therefore `t`
@@ -703,6 +731,10 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
703731 tag = loopHeaderTag ( ) and
704732 t instanceof DirectSuccessor
705733 or
734+ foreachHasElementNode ( n ) and
735+ tag = foreachElementTag ( ) and
736+ t instanceof DirectSuccessor
737+ or
706738 n instanceof PatternMatchExpr and
707739 tag = patternMatchTrueTag ( ) and
708740 t .( BooleanSuccessor ) .getValue ( ) = true
@@ -1640,7 +1672,21 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
16401672 n2 .isAfter ( loopstmt )
16411673 )
16421674 or
1643- exists ( ForeachStmt foreachstmt |
1675+ exists ( ForeachStmt foreachstmt , PreControlFlowNode iterentry |
1676+ // `iterentry` is where each iteration begins, after the loop header
1677+ // (or after the collection is found to be non-empty): the element
1678+ // node if the language opts in, otherwise the loop variable, or the
1679+ // body directly if there is no variable.
1680+ foreachHasElementNode ( foreachstmt ) and
1681+ iterentry .isAdditional ( foreachstmt , foreachElementTag ( ) )
1682+ or
1683+ not foreachHasElementNode ( foreachstmt ) and
1684+ (
1685+ iterentry .isBefore ( foreachstmt .getVariable ( ) )
1686+ or
1687+ not exists ( foreachstmt .getVariable ( ) ) and iterentry .isBefore ( foreachstmt .getBody ( ) )
1688+ )
1689+ |
16441690 n1 .isBefore ( foreachstmt ) and
16451691 n2 .isBefore ( foreachstmt .getCollection ( ) )
16461692 or
@@ -1654,8 +1700,14 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
16541700 or
16551701 n1 .isAfterValue ( foreachstmt .getCollection ( ) ,
16561702 any ( EmptinessSuccessor t | t .getValue ( ) = false ) ) and
1657- n2 . isBefore ( foreachstmt . getVariable ( ) )
1703+ n2 = iterentry
16581704 or
1705+ n1 .isAdditional ( foreachstmt , loopHeaderTag ( ) ) and
1706+ n2 = iterentry
1707+ or
1708+ // After the loop variable, enter the body. When the language opts in
1709+ // to the element node, it is instead responsible for wiring the
1710+ // element node through to the body itself.
16591711 n1 .isAfter ( foreachstmt .getVariable ( ) ) and
16601712 n2 .isBefore ( foreachstmt .getBody ( ) )
16611713 or
@@ -1668,9 +1720,6 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
16681720 or
16691721 not exists ( getLoopElse ( foreachstmt ) ) and n2 .isAfter ( foreachstmt )
16701722 )
1671- or
1672- n1 .isAdditional ( foreachstmt , loopHeaderTag ( ) ) and
1673- n2 .isBefore ( foreachstmt .getVariable ( ) )
16741723 )
16751724 or
16761725 exists ( ForStmt forstmt , PreControlFlowNode condentry |
0 commit comments