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,144 @@
/*
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.pkl.core.generator;
import com.palantir.javapoet.CodeBlock;
import com.palantir.javapoet.JavaFile;
import com.palantir.javapoet.MethodSpec;
import com.palantir.javapoet.TypeName;
import com.palantir.javapoet.TypeSpec;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Modifier;
import org.pkl.parser.Parser;
import org.pkl.parser.syntax.Modifier.ModifierValue;
public final class BaseModuleMembersGenerator {
record Members(Set<String> properties, Set<String> methods) {}
public static void main(String[] args) {
if (args.length < 2) {
throw new IllegalArgumentException(
"Usage: BaseModuleMembersGenerator <path-to-base.pkl> <output-dir>");
}
var members = buildMembers(args[0]);
generateJavaCode(members, args[1]);
}
private static void generateJavaCode(Members members, String outputDir) {
var privateConstructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
var hasPropertyMethod =
buildHasMethod("hasProperty", members.properties().stream().sorted().toList());
var hasMethodMethod = buildHasMethod("hasMethod", members.methods().stream().sorted().toList());
var classSpec =
TypeSpec.classBuilder("BaseModuleMembers")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(privateConstructor)
.addMethod(hasPropertyMethod)
.addMethod(hasMethodMethod)
.build();
var javaFile =
JavaFile.builder("org.pkl.core.runtime", classSpec)
.addFileComment("DO NOT EDIT — generated by BaseModuleMembersGenerator")
.build();
try {
javaFile.writeTo(Path.of(outputDir));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static MethodSpec buildHasMethod(String methodName, List<String> names) {
var code = CodeBlock.builder();
code.add("return switch (name) {\n");
code.indent();
code.add("case $S", names.get(0));
if (names.size() == 1) {
code.add(" -> true;\n");
} else {
code.add(",\n");
code.indent();
for (var i = 1; i < names.size() - 1; i++) {
code.add("$S,\n", names.get(i));
}
code.add("$S -> true;\n", names.get(names.size() - 1));
code.unindent();
}
code.add("default -> false;\n");
code.unindent();
code.add("};\n");
return MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(TypeName.BOOLEAN)
.addParameter(String.class, "name")
.addCode(code.build())
.build();
}
private static String getBaseModuleText(String path) {
try {
return Files.readString(Path.of(path));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static Members buildMembers(String basePklPath) {
var text = getBaseModuleText(basePklPath);
var parsed = new Parser().parseModule(text);
var properties = new HashSet<String>();
var methods = new HashSet<String>();
for (var property : parsed.getProperties()) {
if (isLocal(property.getModifiers())) {
continue;
}
properties.add(property.getName().getValue());
}
for (var clazz : parsed.getClasses()) {
if (isLocal(clazz.getModifiers())) {
continue;
}
properties.add(clazz.getName().getValue());
}
for (var typealias : parsed.getTypeAliases()) {
if (isLocal(typealias.getModifiers())) {
continue;
}
properties.add(typealias.getName().getValue());
}
for (var method : parsed.getMethods()) {
if (isLocal(method.getModifiers())) {
continue;
}
methods.add(method.getName().getValue());
}
return new Members(properties, methods);
}
private static boolean isLocal(List<org.pkl.parser.syntax.Modifier> modifiers) {
return modifiers.stream().anyMatch((it) -> it.getValue() == ModifierValue.LOCAL);
}
}