Resolve variables at parse time (#1429)

This replaces `ResolveVariableNode` and `ResolveMethodNode` with their resolution.
When we build the truffle node tree, we determine whether names resolve to:

* lexical scope
* base module
* implicit this

Then, we use this information to directly construct the underlying nodes (`ReadPropertyNode`, `ReadLocalPropertyNode`, etc).

Additionally, `AstBuilder` determines whether the property access must be const or not.

This introduces a `BaseModuleMembers` registry, which gets generated as part of Java compilation.
This commit is contained in:
Islon Scherer
2026-05-26 23:08:20 +02:00
committed by GitHub
parent b2f005d11d
commit dbf04f6598
45 changed files with 1930 additions and 835 deletions
@@ -0,0 +1,34 @@
import "pkl:reflect"
const function plusThree(it) = it + 3
const function plusFour(it) = it + 4
local const myProp = "myProp"
class MyClass {
@MyAnnotation {
func = (it) -> plusThree(it)
prop = myProp
}
foo: Int
}
@MyAnnotation {
func = (it) -> plusFour(it)
prop = myProp
}
hidden qux: Int
class MyAnnotation extends Annotation {
func: ((Any) -> Any)
prop: Any
}
local nestedAnnotation = reflect.Class(MyClass).properties["foo"].annotations.first as MyAnnotation
local moduleAnnotation = reflect.Module(module).moduleClass.properties["qux"].annotations.first as MyAnnotation
res1 = nestedAnnotation.func.apply(15)
res2 = nestedAnnotation.prop
res3 = moduleAnnotation.func.apply(15)
res4 = moduleAnnotation.prop
@@ -0,0 +1,7 @@
local foo = "hello"
bar {
new Mixin {
[foo] = "world"
}.apply(new Dynamic {})
}
@@ -0,0 +1,8 @@
qux = "outer"
foo {
when (true) {
local qux = "then branch"
prop = qux
}
}
@@ -0,0 +1,29 @@
// `qux` has ambiguous locality; don't know if it should be local read or not
hidden res1 {
cond = true
prop {
when (cond) {
local qux = "then branch"
} else {
qux = "else branch"
}
theBranch = qux
}
}
res2 = (res1) {
cond = false
}
res3 {
cond = true
prop {
when (cond) {
local qux = "then branch"
} else {
qux = cond
}
theBranch = qux
}
}
@@ -0,0 +1,18 @@
foo {
bar = new Listing { "elem" } |> mapEnvWithObjectParam(new Dynamic {
res1Name = "res1Value"
res2Name = "res2Value"
})
}
function mapEnvWithObjectParam(_env: Dynamic) = new Mixin { it ->
new {
res = it
}
for (k, v in _env) {
new {
name = k
value = v
}
}
}
@@ -0,0 +1,15 @@
local function myFunc(foo: String, bar: String, baz: String): Any = new Listing {
for (
qux in new Listing {
for (param1 in List(1)) {
List("\(param1) \(foo) \(bar) \(baz)")
}
}
) {
for (param2 in qux) {
"Hello \(foo) \(bar) \(baz) \(param2)"
}
}
}
res = myFunc("arg1", "arg2", "arg3")
@@ -0,0 +1,4 @@
res1 = 18
res2 = "myProp"
res3 = 19
res4 = "myProp"
@@ -0,0 +1,5 @@
bar {
new {
["hello"] = "world"
}
}
@@ -0,0 +1,4 @@
qux = "outer"
foo {
prop = "then branch"
}
@@ -0,0 +1,13 @@
res2 {
cond = false
prop {
qux = "else branch"
theBranch = "else branch"
}
}
res3 {
cond = true
prop {
theBranch = "then branch"
}
}
@@ -0,0 +1,18 @@
foo {
bar {
"elem"
new {
res {
"elem"
}
}
new {
name = "res1Name"
value = "res1Value"
}
new {
name = "res2Name"
value = "res2Value"
}
}
}
@@ -0,0 +1,3 @@
res {
"Hello arg1 arg2 arg3 1 arg1 arg2 arg3"
}
@@ -223,6 +223,24 @@ class ReplServerTest {
assertThat((response as ReplResponse.EvalSuccess).result).isEqualTo("\u001B[32m5\u001B[0m.ms")
}
@Test
fun `strip expression preamble in error message`() {
val result = makeFailingEvalRequest("foo")
assertThat(result)
.isEqualTo(
"""
–– Pkl Error ––
Cannot find property `foo`.
1 | foo
^^^
at (repl:id)
"""
.trimIndent()
)
}
private fun makeEvalRequest(text: String): String {
val responses = server.handleRequest(ReplRequest.Eval("id", text, false, false))