Overhaul implementation of for-generators (#844)

Motivation:
* fix known bugs and limitations of for-generators
* improve code health by removing complex workarounds

Changes:
* simplify AstBuilder code related to for-generators
  * track for-generators via `SymbolTable.enterForGenerator()`
  * add `RestoreForBindingsNode` during initial AST construction
    instead of calling `MemberNode.replaceBody()` later on
  * simplify some unnecessarily complex code
* remove workarounds and band-aids such as:
  * `isInIterable`
  * `executeAndSetEagerly`
  * adding dummy slots in `AmendFunctionNode`
* overhaul implementation of for-generators
  * store keys and values of for-generator iterations in regular instead of auxiliary frame slots
    * set them via `TypeNode.executeAndSet()`
    * `ResolveVariableNode` no longer needs to search auxiliary slots
    * `Read(Enclosing)AuxiliarySlot` is no longer needed
  * at the start of each for-generator iteration, create a new `VirtualFrame`
    that is a copy of the current frame (arguments + slots)
    and stores the iteration key and value in additional slots.
  * execute for-generator iteration with the newly created frame
    * `childNode.execute(newFrame)`
    * Pkl objects created during the iteration will materialize this frame
  * store newly created frames in `owner.extraStorage`
    if their for-generator slots may be accessed when a generated member is executed
    * resolving variable names to for-generator variables at parse time would make this analysis more precise
  * when a generated member is executed,
	  * retrieve the corresponding frame stored in `owner.extraStorage`
	  * copy the retrieved frame's for-generator slots into slots of the current frame

Result:
* for-generators are implemented in a correct, reasonably simple, and reasonably efficient way
  * complexity is fully contained within package `generator` and `AstBuilder`
* for-generator keys and values can be accessed from all nested scopes:
  * key and value expressions of generated members
  * condition expressions of nested when-generators
  * iterable expressions of nested for-generators
* for-generator keys and values can be accessed from within objects created by the expressions listed above
* sibling for-generators can use the same key/value variable names
* parent/child for-generators can use the same key/value variable names
* fixes https://github.com/apple/pkl/issues/741
This commit is contained in:
odenix
2025-01-28 14:06:42 -08:00
committed by GitHub
parent 11169d6691
commit 90df0662af
57 changed files with 923 additions and 1153 deletions

View File

@@ -1,11 +0,0 @@
a = List(1, 2, 3, 4)
b = List("a", "b", "c", "d")
foo {
for (_dup, i in a) {
for (_dup, j in b) {
i + j
}
}
}

View File

@@ -0,0 +1,18 @@
function myMethod(arg) = new {
arg = "property" // same name as method arg
for (key, value in List("one", "two", arg)) { // `arg` resolves to method arg
[Pair(arg, key)] = // `arg` resolves to method arg
Pair(arg, value) // `arg` resolves to object property
}
}
local myLambda = (arg) -> new Dynamic {
arg = "property" // same name as lambda arg
for (key, value in List("one", "two", arg)) { // `arg` resolves to lambda arg
[Pair(arg, key)] = // `arg` resolves to lambda arg
Pair(arg, value) // `arg` resolves to object property
}
}
res1 = myMethod("three")
res2 = myLambda.apply("three")

View File

@@ -0,0 +1,25 @@
// https://github.com/apple/pkl/issues/741
bar = new {}
res1 {
for (i in List(1)) {
...(bar) {
baz {
new { i }
}
}.baz
}
}
res2 {
for (i in List(1)) {
for (elem in (bar) {
baz {
new { i }
}
}.baz) {
elem
}
}
}

View File

@@ -0,0 +1,33 @@
examples {
local a = List("1", "2", "3", "4")
local b = List("a", "b", "c", "d")
["shadow key variable"] {
new {
for (key, outerValue in a) {
for (key, innerValue in b) {
List(outerValue, key, innerValue)
}
}
}
}
["shadow value variable"] {
new {
for (outerKey, value in a) {
for (innerKey, value in b) {
List(outerKey, value, innerKey)
}
}
}
}
["sibling for-generators can use same variable names"] {
new {
for (key, value in a) {
List(key, value)
}
for (key, value in b) {
List(key, value)
}
}
}
}

View File

@@ -1,6 +0,0 @@
Pkl Error
Duplicate definition of member `_dup`.
x | for (_dup, j in b) {
^^^^
at forGeneratorDuplicateParams2#foo (file:///$snippetsDir/input/errors/forGeneratorDuplicateParams2.pkl)

View File

@@ -0,0 +1,12 @@
res1 {
arg = "property"
[Pair("three", 0)] = Pair("property", "one")
[Pair("three", 1)] = Pair("property", "two")
[Pair("three", 2)] = Pair("property", "three")
}
res2 {
arg = "property"
[Pair("three", 0)] = Pair("property", "one")
[Pair("three", 1)] = Pair("property", "two")
[Pair("three", 2)] = Pair("property", "three")
}

View File

@@ -0,0 +1,11 @@
bar {}
res1 {
new {
1
}
}
res2 {
new {
1
}
}

View File

@@ -0,0 +1,54 @@
examples {
["shadow key variable"] {
new {
List("1", 0, "a")
List("1", 1, "b")
List("1", 2, "c")
List("1", 3, "d")
List("2", 0, "a")
List("2", 1, "b")
List("2", 2, "c")
List("2", 3, "d")
List("3", 0, "a")
List("3", 1, "b")
List("3", 2, "c")
List("3", 3, "d")
List("4", 0, "a")
List("4", 1, "b")
List("4", 2, "c")
List("4", 3, "d")
}
}
["shadow value variable"] {
new {
List(0, "a", 0)
List(0, "b", 1)
List(0, "c", 2)
List(0, "d", 3)
List(1, "a", 0)
List(1, "b", 1)
List(1, "c", 2)
List(1, "d", 3)
List(2, "a", 0)
List(2, "b", 1)
List(2, "c", 2)
List(2, "d", 3)
List(3, "a", 0)
List(3, "b", 1)
List(3, "c", 2)
List(3, "d", 3)
}
}
["sibling for-generators can use same variable names"] {
new {
List(0, "1")
List(1, "2")
List(2, "3")
List(3, "4")
List(0, "a")
List(1, "b")
List(2, "c")
List(3, "d")
}
}
}