@@ -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 *
@@ -697,6 +723,8 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
697723
698724 private string patternMatchTrueTag ( ) { result = "[MatchTrue]" }
699725
726+ private string foreachElementTag ( ) { result = "[ForeachElement]" }
727+
700728 /**
701729 * Holds if an additional node tagged with `tag` should be created for
702730 * `n`. Edges targeting such nodes are labeled with `t` and therefore `t`
@@ -716,6 +744,10 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
716744 tag = loopHeaderTag ( ) and
717745 t instanceof DirectSuccessor
718746 or
747+ foreachHasElementNode ( n ) and
748+ tag = foreachElementTag ( ) and
749+ t instanceof DirectSuccessor
750+ or
719751 n instanceof PatternMatchExpr and
720752 tag = patternMatchTrueTag ( ) and
721753 t .( BooleanSuccessor ) .getValue ( ) = true
@@ -1785,7 +1817,21 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
17851817 n2 .isAfter ( loopstmt )
17861818 )
17871819 or
1788- exists ( ForeachStmt foreachstmt |
1820+ exists ( ForeachStmt foreachstmt , PreControlFlowNode iterentry |
1821+ // `iterentry` is where each iteration begins, after the loop header
1822+ // (or after the collection is found to be non-empty): the element
1823+ // node if the language opts in, otherwise the loop variable, or the
1824+ // body directly if there is no variable.
1825+ foreachHasElementNode ( foreachstmt ) and
1826+ iterentry .isAdditional ( foreachstmt , foreachElementTag ( ) )
1827+ or
1828+ not foreachHasElementNode ( foreachstmt ) and
1829+ (
1830+ iterentry .isBefore ( foreachstmt .getVariable ( ) )
1831+ or
1832+ not exists ( foreachstmt .getVariable ( ) ) and iterentry .isBefore ( foreachstmt .getBody ( ) )
1833+ )
1834+ |
17891835 n1 .isBefore ( foreachstmt ) and
17901836 n2 .isBefore ( foreachstmt .getCollection ( ) )
17911837 or
@@ -1799,8 +1845,14 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
17991845 or
18001846 n1 .isAfterValue ( foreachstmt .getCollection ( ) ,
18011847 any ( EmptinessSuccessor t | t .getValue ( ) = false ) ) and
1802- n2 . isBefore ( foreachstmt . getVariable ( ) )
1848+ n2 = iterentry
18031849 or
1850+ n1 .isAdditional ( foreachstmt , loopHeaderTag ( ) ) and
1851+ n2 = iterentry
1852+ or
1853+ // After the loop variable, enter the body. When the language opts in
1854+ // to the element node, it is instead responsible for wiring the
1855+ // element node through to the body itself.
18041856 n1 .isAfter ( foreachstmt .getVariable ( ) ) and
18051857 n2 .isBefore ( foreachstmt .getBody ( ) )
18061858 or
@@ -1813,9 +1865,6 @@ module Make0<LocationSig Location, AstSig<Location> Ast> {
18131865 or
18141866 not exists ( getLoopElse ( foreachstmt ) ) and n2 .isAfter ( foreachstmt )
18151867 )
1816- or
1817- n1 .isAdditional ( foreachstmt , loopHeaderTag ( ) ) and
1818- n2 .isBefore ( foreachstmt .getVariable ( ) )
18191868 )
18201869 or
18211870 exists ( ForStmt forstmt , PreControlFlowNode condentry |
0 commit comments