mirror of
https://github.com/apple/pkl.git
synced 2026-05-28 17:49:15 +02:00
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:
@@ -193,7 +193,7 @@ Too many lines separate `foo` and `baz`.
|
|||||||
Properties that override an existing property shouldn't have doc comments nor type annotations,
|
Properties that override an existing property shouldn't have doc comments nor type annotations,
|
||||||
unless the type is intentionally overridden via `extends`.
|
unless the type is intentionally overridden via `extends`.
|
||||||
|
|
||||||
[source%tested,{pkl}]
|
[source%parsed,{pkl}]
|
||||||
----
|
----
|
||||||
amends "myOtherModule.pkl"
|
amends "myOtherModule.pkl"
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ dependencies {
|
|||||||
add("generatorImplementation", libs.javaPoet)
|
add("generatorImplementation", libs.javaPoet)
|
||||||
add("generatorImplementation", libs.truffleApi)
|
add("generatorImplementation", libs.truffleApi)
|
||||||
add("generatorImplementation", libs.jspecify)
|
add("generatorImplementation", libs.jspecify)
|
||||||
|
add("generatorImplementation", projects.pklParser)
|
||||||
|
|
||||||
javaExecutableConfiguration(project(":pkl-cli", "javaExecutable"))
|
javaExecutableConfiguration(project(":pkl-cli", "javaExecutable"))
|
||||||
}
|
}
|
||||||
@@ -140,6 +141,36 @@ tasks.test {
|
|||||||
maxHeapSize = "1g"
|
maxHeapSize = "1g"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val generateBaseModuleMembers by
|
||||||
|
tasks.registering(JavaExec::class) {
|
||||||
|
group = "build"
|
||||||
|
|
||||||
|
val outputDir = layout.buildDirectory.dir("generated/sources/baseModuleMembers")
|
||||||
|
|
||||||
|
val basePklFile = layout.projectDirectory.file("../stdlib/base.pkl")
|
||||||
|
|
||||||
|
inputs
|
||||||
|
.file(basePklFile)
|
||||||
|
.withPropertyName("basePkl")
|
||||||
|
.withPathSensitivity(PathSensitivity.RELATIVE)
|
||||||
|
|
||||||
|
outputs.dir(outputDir)
|
||||||
|
|
||||||
|
classpath =
|
||||||
|
generatorSourceSet.get().runtimeClasspath + tasks.processResources.get().outputs.files
|
||||||
|
mainClass = "org.pkl.core.generator.BaseModuleMembersGenerator"
|
||||||
|
|
||||||
|
argumentProviders.add(
|
||||||
|
CommandLineArgumentProvider {
|
||||||
|
listOf(basePklFile.asFile.absolutePath, outputDir.get().asFile.absolutePath)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets.main { java.srcDir(layout.buildDirectory.dir("generated/sources/baseModuleMembers")) }
|
||||||
|
|
||||||
|
tasks.compileJava { dependsOn(generateBaseModuleMembers) }
|
||||||
|
|
||||||
val testJavaExecutable by
|
val testJavaExecutable by
|
||||||
tasks.registering(Test::class) {
|
tasks.registering(Test::class) {
|
||||||
configureExecutableTest("LanguageSnippetTestsEngine")
|
configureExecutableTest("LanguageSnippetTestsEngine")
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,6 +55,8 @@ public final class VmModifier {
|
|||||||
|
|
||||||
public static final int GLOB = 0x1000;
|
public static final int GLOB = 0x1000;
|
||||||
|
|
||||||
|
public static final int AMBIGUOUS_LOCALITY = 0x10000;
|
||||||
|
|
||||||
// modifier sets
|
// modifier sets
|
||||||
|
|
||||||
public static final int NONE = 0;
|
public static final int NONE = 0;
|
||||||
@@ -126,6 +128,10 @@ public final class VmModifier {
|
|||||||
return (modifiers & CONST) != 0;
|
return (modifiers & CONST) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isAmbiguousLocality(int modifiers) {
|
||||||
|
return (modifiers & AMBIGUOUS_LOCALITY) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isElement(int modifiers) {
|
public static boolean isElement(int modifiers) {
|
||||||
return (modifiers & ELEMENT) != 0;
|
return (modifiers & ELEMENT) != 0;
|
||||||
}
|
}
|
||||||
@@ -154,6 +160,10 @@ public final class VmModifier {
|
|||||||
return (modifiers & (CONST | FIXED)) != 0;
|
return (modifiers & (CONST | FIXED)) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean hasSameModifier(int modifiersA, int modifiersB, int modifier) {
|
||||||
|
return (modifiersA & modifier) == (modifiersB & modifier);
|
||||||
|
}
|
||||||
|
|
||||||
public static Set<Modifier> export(int modifiers, boolean isClass) {
|
public static Set<Modifier> export(int modifiers, boolean isClass) {
|
||||||
var result = EnumSet.noneOf(Modifier.class);
|
var result = EnumSet.noneOf(Modifier.class);
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,18 @@ import org.pkl.core.ast.ExpressionNode;
|
|||||||
import org.pkl.core.ast.MemberLookupMode;
|
import org.pkl.core.ast.MemberLookupMode;
|
||||||
import org.pkl.core.ast.PklRootNode;
|
import org.pkl.core.ast.PklRootNode;
|
||||||
import org.pkl.core.ast.VmModifier;
|
import org.pkl.core.ast.VmModifier;
|
||||||
|
import org.pkl.core.ast.builder.MethodResolution.ImplicitBaseMethod;
|
||||||
|
import org.pkl.core.ast.builder.MethodResolution.ImplicitThisMethod;
|
||||||
|
import org.pkl.core.ast.builder.MethodResolution.LexicalMethod;
|
||||||
import org.pkl.core.ast.builder.SymbolTable.AnnotationScope;
|
import org.pkl.core.ast.builder.SymbolTable.AnnotationScope;
|
||||||
import org.pkl.core.ast.builder.SymbolTable.ClassScope;
|
import org.pkl.core.ast.builder.SymbolTable.ClassScope;
|
||||||
|
import org.pkl.core.ast.builder.SymbolTable.ModuleScope;
|
||||||
|
import org.pkl.core.ast.builder.SymbolTable.ObjectScope;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.ForGeneratorVariable;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.ImplicitThisProperty;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.Parameter;
|
||||||
import org.pkl.core.ast.expression.binary.AdditionNodeGen;
|
import org.pkl.core.ast.expression.binary.AdditionNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.DivisionNodeGen;
|
import org.pkl.core.ast.expression.binary.DivisionNodeGen;
|
||||||
import org.pkl.core.ast.expression.binary.EqualNodeGen;
|
import org.pkl.core.ast.expression.binary.EqualNodeGen;
|
||||||
@@ -101,12 +111,16 @@ import org.pkl.core.ast.expression.literal.TrueLiteralNode;
|
|||||||
import org.pkl.core.ast.expression.member.InferParentWithinMethodNode;
|
import org.pkl.core.ast.expression.member.InferParentWithinMethodNode;
|
||||||
import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNode;
|
import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNode;
|
||||||
import org.pkl.core.ast.expression.member.InferParentWithinPropertyNodeGen;
|
import org.pkl.core.ast.expression.member.InferParentWithinPropertyNodeGen;
|
||||||
|
import org.pkl.core.ast.expression.member.InvokeClassMethodNode;
|
||||||
|
import org.pkl.core.ast.expression.member.InvokeMethodDirectNode;
|
||||||
import org.pkl.core.ast.expression.member.InvokeMethodVirtualNodeGen;
|
import org.pkl.core.ast.expression.member.InvokeMethodVirtualNodeGen;
|
||||||
|
import org.pkl.core.ast.expression.member.InvokeObjectMethodNode;
|
||||||
import org.pkl.core.ast.expression.member.InvokeSuperMethodNodeGen;
|
import org.pkl.core.ast.expression.member.InvokeSuperMethodNodeGen;
|
||||||
|
import org.pkl.core.ast.expression.member.ReadAmbiguousLocalityPropertyNode;
|
||||||
|
import org.pkl.core.ast.expression.member.ReadLocalPropertyNode;
|
||||||
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
|
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
|
||||||
import org.pkl.core.ast.expression.member.ReadSuperEntryNode;
|
import org.pkl.core.ast.expression.member.ReadSuperEntryNode;
|
||||||
import org.pkl.core.ast.expression.member.ReadSuperPropertyNode;
|
import org.pkl.core.ast.expression.member.ReadSuperPropertyNode;
|
||||||
import org.pkl.core.ast.expression.member.ResolveMethodNode;
|
|
||||||
import org.pkl.core.ast.expression.primary.GetEnclosingOwnerNode;
|
import org.pkl.core.ast.expression.primary.GetEnclosingOwnerNode;
|
||||||
import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode;
|
import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode;
|
||||||
import org.pkl.core.ast.expression.primary.GetMemberKeyNode;
|
import org.pkl.core.ast.expression.primary.GetMemberKeyNode;
|
||||||
@@ -114,7 +128,6 @@ import org.pkl.core.ast.expression.primary.GetModuleNode;
|
|||||||
import org.pkl.core.ast.expression.primary.GetOwnerNode;
|
import org.pkl.core.ast.expression.primary.GetOwnerNode;
|
||||||
import org.pkl.core.ast.expression.primary.GetReceiverNode;
|
import org.pkl.core.ast.expression.primary.GetReceiverNode;
|
||||||
import org.pkl.core.ast.expression.primary.OuterNode;
|
import org.pkl.core.ast.expression.primary.OuterNode;
|
||||||
import org.pkl.core.ast.expression.primary.ResolveVariableNode;
|
|
||||||
import org.pkl.core.ast.expression.primary.ThisNode;
|
import org.pkl.core.ast.expression.primary.ThisNode;
|
||||||
import org.pkl.core.ast.expression.ternary.IfElseNode;
|
import org.pkl.core.ast.expression.ternary.IfElseNode;
|
||||||
import org.pkl.core.ast.expression.unary.AbstractImportNode;
|
import org.pkl.core.ast.expression.unary.AbstractImportNode;
|
||||||
@@ -131,6 +144,9 @@ import org.pkl.core.ast.expression.unary.ReadOrNullNodeGen;
|
|||||||
import org.pkl.core.ast.expression.unary.ThrowNodeGen;
|
import org.pkl.core.ast.expression.unary.ThrowNodeGen;
|
||||||
import org.pkl.core.ast.expression.unary.TraceNode;
|
import org.pkl.core.ast.expression.unary.TraceNode;
|
||||||
import org.pkl.core.ast.expression.unary.UnaryMinusNodeGen;
|
import org.pkl.core.ast.expression.unary.UnaryMinusNodeGen;
|
||||||
|
import org.pkl.core.ast.frame.GetEnclosingFrameNode;
|
||||||
|
import org.pkl.core.ast.frame.ReadExactFrameSlotNodeGen;
|
||||||
|
import org.pkl.core.ast.frame.ReadFrameSlotNodeGen;
|
||||||
import org.pkl.core.ast.internal.GetBaseModuleClassNode;
|
import org.pkl.core.ast.internal.GetBaseModuleClassNode;
|
||||||
import org.pkl.core.ast.internal.GetClassNodeGen;
|
import org.pkl.core.ast.internal.GetClassNodeGen;
|
||||||
import org.pkl.core.ast.internal.ToStringNodeGen;
|
import org.pkl.core.ast.internal.ToStringNodeGen;
|
||||||
@@ -163,6 +179,7 @@ import org.pkl.core.module.ModuleKeys;
|
|||||||
import org.pkl.core.module.ResolvedModuleKey;
|
import org.pkl.core.module.ResolvedModuleKey;
|
||||||
import org.pkl.core.packages.PackageLoadError;
|
import org.pkl.core.packages.PackageLoadError;
|
||||||
import org.pkl.core.runtime.BaseModule;
|
import org.pkl.core.runtime.BaseModule;
|
||||||
|
import org.pkl.core.runtime.FrameDescriptorBuilder;
|
||||||
import org.pkl.core.runtime.ModuleInfo;
|
import org.pkl.core.runtime.ModuleInfo;
|
||||||
import org.pkl.core.runtime.ModuleResolver;
|
import org.pkl.core.runtime.ModuleResolver;
|
||||||
import org.pkl.core.runtime.VmBytes;
|
import org.pkl.core.runtime.VmBytes;
|
||||||
@@ -239,7 +256,6 @@ import org.pkl.parser.syntax.ObjectMember.ObjectMethod;
|
|||||||
import org.pkl.parser.syntax.ObjectMember.ObjectProperty;
|
import org.pkl.parser.syntax.ObjectMember.ObjectProperty;
|
||||||
import org.pkl.parser.syntax.ObjectMember.ObjectSpread;
|
import org.pkl.parser.syntax.ObjectMember.ObjectSpread;
|
||||||
import org.pkl.parser.syntax.ObjectMember.WhenGenerator;
|
import org.pkl.parser.syntax.ObjectMember.WhenGenerator;
|
||||||
import org.pkl.parser.syntax.Parameter;
|
|
||||||
import org.pkl.parser.syntax.Parameter.TypedIdentifier;
|
import org.pkl.parser.syntax.Parameter.TypedIdentifier;
|
||||||
import org.pkl.parser.syntax.ParameterList;
|
import org.pkl.parser.syntax.ParameterList;
|
||||||
import org.pkl.parser.syntax.QualifiedIdentifier;
|
import org.pkl.parser.syntax.QualifiedIdentifier;
|
||||||
@@ -285,7 +301,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
isBaseModule = ModuleKeys.isBaseModule(moduleKey);
|
isBaseModule = ModuleKeys.isBaseModule(moduleKey);
|
||||||
isStdLibModule = ModuleKeys.isStdLibModule(moduleKey);
|
isStdLibModule = ModuleKeys.isStdLibModule(moduleKey);
|
||||||
externalMemberRegistry = MemberRegistryFactory.get(moduleKey);
|
externalMemberRegistry = MemberRegistryFactory.get(moduleKey);
|
||||||
symbolTable = new SymbolTable(moduleInfo);
|
symbolTable = new SymbolTable(moduleInfo, isBaseModule);
|
||||||
isMethodReturnTypeChecked = !isStdLibModule || IoUtils.isTestMode();
|
isMethodReturnTypeChecked = !isStdLibModule || IoUtils.isTestMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,10 +506,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GetModuleNode visitModuleExpr(ModuleExpr expr) {
|
public GetModuleNode visitModuleExpr(ModuleExpr expr) {
|
||||||
|
var currentScope = symbolTable.getCurrentScope();
|
||||||
// cannot use unqualified `module` in a const context
|
// cannot use unqualified `module` in a const context
|
||||||
if (symbolTable.getCurrentScope().getConstLevel().isConst()
|
if (currentScope.getConstLevel().isConst() && !(expr.parent() instanceof QualifiedAccessExpr)) {
|
||||||
&& !(expr.parent() instanceof QualifiedAccessExpr)) {
|
var scope = currentScope;
|
||||||
var scope = symbolTable.getCurrentScope();
|
|
||||||
while (scope != null
|
while (scope != null
|
||||||
&& !(scope instanceof AnnotationScope)
|
&& !(scope instanceof AnnotationScope)
|
||||||
&& !(scope instanceof ClassScope)) {
|
&& !(scope instanceof ClassScope)) {
|
||||||
@@ -501,7 +517,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
}
|
}
|
||||||
if (scope == null) {
|
if (scope == null) {
|
||||||
throw exceptionBuilder()
|
throw exceptionBuilder()
|
||||||
.evalError("moduleIsNotConst", symbolTable.getCurrentScope().getName().toString())
|
.evalError("moduleIsNotConst", currentScope.getName().toString())
|
||||||
.withSourceSection(createSourceSection(expr))
|
.withSourceSection(createSourceSection(expr))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@@ -636,44 +652,161 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private ExpressionNode resolveReadVariable(UnqualifiedAccessExpr expr) {
|
||||||
public ExpressionNode visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr) {
|
var name = expr.getIdentifier().getValue();
|
||||||
var identifier = toIdentifier(expr.getIdentifier().getValue());
|
var scope = symbolTable.getCurrentScope();
|
||||||
var argList = expr.getArgumentList();
|
var sourceSection = createSourceSection(expr);
|
||||||
|
var constLevel = scope.getConstLevel();
|
||||||
if (argList == null) {
|
var constDepth = scope.getConstDepth();
|
||||||
return createResolveVariableNode(createSourceSection(expr), identifier);
|
var resolution = scope.resolveVariable(name);
|
||||||
|
if (resolution instanceof LexicalProperty p) {
|
||||||
|
var needsConst =
|
||||||
|
switch (constLevel) {
|
||||||
|
case NONE -> false;
|
||||||
|
case MODULE -> p.isModuleScope();
|
||||||
|
case ALL -> p.levelsUp() > constDepth;
|
||||||
|
};
|
||||||
|
if (p.isAmbiguousLocality()) {
|
||||||
|
return new ReadAmbiguousLocalityPropertyNode(
|
||||||
|
sourceSection, org.pkl.core.runtime.Identifier.get(name), p.levelsUp(), needsConst);
|
||||||
|
}
|
||||||
|
if (p.isLocal()) {
|
||||||
|
return new ReadLocalPropertyNode(
|
||||||
|
sourceSection,
|
||||||
|
org.pkl.core.runtime.Identifier.localProperty(name),
|
||||||
|
p.levelsUp(),
|
||||||
|
needsConst);
|
||||||
|
}
|
||||||
|
return ReadPropertyNodeGen.create(
|
||||||
|
sourceSection,
|
||||||
|
org.pkl.core.runtime.Identifier.get(name),
|
||||||
|
MemberLookupMode.IMPLICIT_LEXICAL,
|
||||||
|
needsConst,
|
||||||
|
p.levelsUp() == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(p.levelsUp()));
|
||||||
|
} else if (resolution instanceof ForGeneratorVariable p) {
|
||||||
|
// Parameters can possibly write to frame slots actually in a frame that is one level
|
||||||
|
// higher than what we can tell at parse time. However, for generator variables always
|
||||||
|
// write to frame slots in the same frame.
|
||||||
|
//
|
||||||
|
// function foo(bar) = new Mixin {
|
||||||
|
// [bar] = 1 <--- actually 1 level, not 0
|
||||||
|
// for (elem in qux) {
|
||||||
|
// elem <--- actually 0 level
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// This is clearer when considering the desugared code:
|
||||||
|
//
|
||||||
|
// function foo(bar) = (it) -> (it) {
|
||||||
|
// res = bar
|
||||||
|
// for (elem in qux) {
|
||||||
|
// elem
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return p.levelsUp() == 0
|
||||||
|
? ReadExactFrameSlotNodeGen.create(sourceSection, p.slot())
|
||||||
|
: ReadFrameSlotNodeGen.create(
|
||||||
|
sourceSection, p.slot(), new GetEnclosingFrameNode(p.levelsUp()));
|
||||||
|
} else if (resolution instanceof Parameter p) {
|
||||||
|
return ReadFrameSlotNodeGen.create(
|
||||||
|
sourceSection, p.slot(), new GetEnclosingFrameNode(p.levelsUp()));
|
||||||
|
} else if (resolution instanceof ImplicitBaseProperty) {
|
||||||
|
return ReadPropertyNodeGen.create(
|
||||||
|
sourceSection,
|
||||||
|
org.pkl.core.runtime.Identifier.get(name),
|
||||||
|
MemberLookupMode.IMPLICIT_BASE,
|
||||||
|
false,
|
||||||
|
new ConstantValueNode(BaseModule.getModule()));
|
||||||
|
} else if (resolution instanceof ImplicitThisProperty) {
|
||||||
|
var isCustomThisScope = scope.isCustomThisScope();
|
||||||
|
var needsConst = constLevel == ConstLevel.ALL && constDepth == -1 && !isCustomThisScope;
|
||||||
|
return ReadPropertyNodeGen.create(
|
||||||
|
sourceSection,
|
||||||
|
org.pkl.core.runtime.Identifier.get(name),
|
||||||
|
MemberLookupMode.IMPLICIT_THIS,
|
||||||
|
needsConst,
|
||||||
|
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThisScope));
|
||||||
|
} else {
|
||||||
|
throw PklBugException.unreachableCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make sure that no user-defined List/Set/Map method is in scope
|
private ExpressionNode resolvedMethodCall(UnqualifiedAccessExpr expr, ArgumentList argList) {
|
||||||
// TODO: support qualified calls (e.g., `import "pkl:base"; x =
|
var name = expr.getIdentifier().getValue();
|
||||||
// base.List()/Set()/Map()/Bytes()`) for correctness
|
var scope = symbolTable.getCurrentScope();
|
||||||
|
var sourceSection = createSourceSection(expr);
|
||||||
|
var constLevel = scope.getConstLevel();
|
||||||
|
var constDepth = scope.getConstDepth();
|
||||||
|
var resolution = scope.resolveMethod(name);
|
||||||
|
if (resolution instanceof LexicalMethod method) {
|
||||||
|
var levelsUp = method.levelsUp();
|
||||||
|
var identifier = org.pkl.core.runtime.Identifier.method(name, method.isLocal());
|
||||||
|
var args = visitArgumentList(argList);
|
||||||
|
var needsConst =
|
||||||
|
switch (constLevel) {
|
||||||
|
case NONE -> false;
|
||||||
|
case MODULE -> method.isModuleScope();
|
||||||
|
case ALL -> method.levelsUp() > constDepth;
|
||||||
|
};
|
||||||
|
if (method.isObjectMethod()) {
|
||||||
|
return new InvokeObjectMethodNode(sourceSection, identifier, levelsUp, args, needsConst);
|
||||||
|
}
|
||||||
|
if (method.isOnClosedClass() || method.isLocal() || method.isExternal()) {
|
||||||
|
return new InvokeClassMethodNode(sourceSection, identifier, levelsUp, args, needsConst);
|
||||||
|
}
|
||||||
|
return InvokeMethodVirtualNodeGen.create(
|
||||||
|
sourceSection,
|
||||||
|
identifier,
|
||||||
|
args,
|
||||||
|
MemberLookupMode.IMPLICIT_LEXICAL,
|
||||||
|
needsConst,
|
||||||
|
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp),
|
||||||
|
GetClassNodeGen.create(null));
|
||||||
|
} else if (resolution instanceof ImplicitBaseMethod) {
|
||||||
|
// TODO: support qualified calls (e.g., `import("pkl:base").List()`) for
|
||||||
|
// correctness
|
||||||
|
var identifier = org.pkl.core.runtime.Identifier.get(name);
|
||||||
|
// Assumption: `pkl:base` does not call List/Set/Map/Bytes constructors.
|
||||||
|
// `resolveMethod` never returns `ImplicitBase` when resolving within
|
||||||
|
// pkl:base itself.
|
||||||
if (identifier == org.pkl.core.runtime.Identifier.LIST) {
|
if (identifier == org.pkl.core.runtime.Identifier.LIST) {
|
||||||
return doVisitListLiteral(expr, argList);
|
return doVisitListLiteral(expr, argList);
|
||||||
}
|
} else if (identifier == org.pkl.core.runtime.Identifier.SET) {
|
||||||
|
|
||||||
if (identifier == org.pkl.core.runtime.Identifier.SET) {
|
|
||||||
return doVisitSetLiteral(expr, argList);
|
return doVisitSetLiteral(expr, argList);
|
||||||
}
|
} else if (identifier == org.pkl.core.runtime.Identifier.MAP) {
|
||||||
|
|
||||||
if (identifier == org.pkl.core.runtime.Identifier.MAP) {
|
|
||||||
return doVisitMapLiteral(expr, argList);
|
return doVisitMapLiteral(expr, argList);
|
||||||
}
|
} else if (identifier == org.pkl.core.runtime.Identifier.BYTES_CONSTRUCTOR) {
|
||||||
|
|
||||||
if (identifier == org.pkl.core.runtime.Identifier.BYTES_CONSTRUCTOR) {
|
|
||||||
return doVisitBytesLiteral(expr, argList);
|
return doVisitBytesLiteral(expr, argList);
|
||||||
|
} else {
|
||||||
|
var baseModule = BaseModule.getModule();
|
||||||
|
var method = baseModule.getVmClass().getDeclaredMethod(identifier);
|
||||||
|
assert method != null;
|
||||||
|
return new InvokeMethodDirectNode(
|
||||||
|
createSourceSection(expr),
|
||||||
|
method,
|
||||||
|
new ConstantValueNode(baseModule),
|
||||||
|
visitArgumentList(argList));
|
||||||
|
}
|
||||||
|
} else if (resolution instanceof ImplicitThisMethod) {
|
||||||
|
var isCustomThis = scope.isCustomThisScope();
|
||||||
|
var needsConst = constLevel == ConstLevel.ALL && constDepth == -1 && !isCustomThis;
|
||||||
|
return InvokeMethodVirtualNodeGen.create(
|
||||||
|
sourceSection,
|
||||||
|
org.pkl.core.runtime.Identifier.get(name),
|
||||||
|
visitArgumentList(argList),
|
||||||
|
MemberLookupMode.IMPLICIT_THIS,
|
||||||
|
needsConst,
|
||||||
|
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThis),
|
||||||
|
GetClassNodeGen.create(null));
|
||||||
|
} else {
|
||||||
|
throw PklBugException.unreachableCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var scope = symbolTable.getCurrentScope();
|
@Override
|
||||||
|
public ExpressionNode visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr) {
|
||||||
return new ResolveMethodNode(
|
var argList = expr.getArgumentList();
|
||||||
createSourceSection(expr),
|
return argList == null ? resolveReadVariable(expr) : resolvedMethodCall(expr, argList);
|
||||||
identifier,
|
|
||||||
visitArgumentList(argList),
|
|
||||||
isBaseModule,
|
|
||||||
scope.isCustomThisScope(),
|
|
||||||
scope.getConstLevel(),
|
|
||||||
scope.getConstDepth());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -980,12 +1113,14 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
public ExpressionNode visitLetExpr(LetExpr letExpr) {
|
public ExpressionNode visitLetExpr(LetExpr letExpr) {
|
||||||
var sourceSection = createSourceSection(letExpr);
|
var sourceSection = createSourceSection(letExpr);
|
||||||
var parameter = letExpr.getParameter();
|
var parameter = letExpr.getParameter();
|
||||||
var frameBuilder = FrameDescriptor.newBuilder();
|
var frameBuilder = new FrameDescriptorBuilder();
|
||||||
UnresolvedTypeNode[] typeNodes;
|
UnresolvedTypeNode[] typeNodes;
|
||||||
|
var bindings = new ArrayList<String>();
|
||||||
if (parameter instanceof TypedIdentifier par) {
|
if (parameter instanceof TypedIdentifier par) {
|
||||||
typeNodes = new UnresolvedTypeNode[] {visitTypeAnnotation(par.getTypeAnnotation())};
|
typeNodes = new UnresolvedTypeNode[] {visitTypeAnnotation(par.getTypeAnnotation())};
|
||||||
frameBuilder.addSlot(
|
frameBuilder.addSlot(
|
||||||
FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null);
|
FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null);
|
||||||
|
bindings.add(par.getIdentifier().getValue());
|
||||||
} else {
|
} else {
|
||||||
typeNodes = new UnresolvedTypeNode[0];
|
typeNodes = new UnresolvedTypeNode[0];
|
||||||
}
|
}
|
||||||
@@ -994,6 +1129,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
UnresolvedFunctionNode functionNode =
|
UnresolvedFunctionNode functionNode =
|
||||||
symbolTable.enterLambda(
|
symbolTable.enterLambda(
|
||||||
|
bindings,
|
||||||
frameBuilder,
|
frameBuilder,
|
||||||
scope -> {
|
scope -> {
|
||||||
var expr = visitExpr(letExpr.getExpr());
|
var expr = visitExpr(letExpr.getExpr());
|
||||||
@@ -1025,9 +1161,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bindings = getParameterNames(params);
|
||||||
|
|
||||||
var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope();
|
var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope();
|
||||||
|
|
||||||
return symbolTable.enterLambda(
|
return symbolTable.enterLambda(
|
||||||
|
bindings,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
scope -> {
|
scope -> {
|
||||||
var exprNode = visitExpr(expr.getExpr());
|
var exprNode = visitExpr(expr.getExpr());
|
||||||
@@ -1168,7 +1307,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) {
|
public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) {
|
||||||
var keyNode = symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.getPred()));
|
var keyNode =
|
||||||
|
symbolTable.enterEagerGenerator(
|
||||||
|
(scp) -> symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.getPred())));
|
||||||
var member =
|
var member =
|
||||||
doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList());
|
doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList());
|
||||||
var isFrameStored =
|
var isFrameStored =
|
||||||
@@ -1197,7 +1338,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GeneratorMemberNode visitObjectSpread(ObjectSpread member) {
|
public GeneratorMemberNode visitObjectSpread(ObjectSpread member) {
|
||||||
var expr = visitExpr(member.getExpr());
|
var expr = symbolTable.enterEagerGenerator((ignored) -> visitExpr(member.getExpr()));
|
||||||
return GeneratorSpreadNodeGen.create(createSourceSection(member), expr, member.isNullable());
|
return GeneratorSpreadNodeGen.create(createSourceSection(member), expr, member.isNullable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1210,8 +1351,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
? new GeneratorMemberNode[0]
|
? new GeneratorMemberNode[0]
|
||||||
: doVisitForWhenBody(member.getElseClause());
|
: doVisitForWhenBody(member.getElseClause());
|
||||||
|
|
||||||
return new GeneratorWhenNode(
|
// when predicates cannot see their direct scope
|
||||||
sourceSection, visitExpr(member.getPredicate()), thenNodes, elseNodes);
|
var predicateNode =
|
||||||
|
symbolTable.enterEagerGenerator((scope) -> visitExpr(member.getPredicate()));
|
||||||
|
return new GeneratorWhenNode(sourceSection, predicateNode, thenNodes, elseNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private GeneratorMemberNode[] doVisitForWhenBody(ObjectBody body) {
|
private GeneratorMemberNode[] doVisitForWhenBody(ObjectBody body) {
|
||||||
@@ -1233,6 +1376,16 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
TypedIdentifier valueTypedIdentifier = null;
|
TypedIdentifier valueTypedIdentifier = null;
|
||||||
if (valueParameter instanceof TypedIdentifier ti) valueTypedIdentifier = ti;
|
if (valueParameter instanceof TypedIdentifier ti) valueTypedIdentifier = ti;
|
||||||
|
|
||||||
|
var params = new ArrayList<String>();
|
||||||
|
if (ctx.getP1() instanceof TypedIdentifier ti) {
|
||||||
|
params.add(ti.getIdentifier().getValue());
|
||||||
|
}
|
||||||
|
if (ctx.getP2() != null) {
|
||||||
|
if (ctx.getP2() instanceof TypedIdentifier ti) {
|
||||||
|
params.add(ti.getIdentifier().getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var keyIdentifier =
|
var keyIdentifier =
|
||||||
keyTypedIdentifier == null
|
keyTypedIdentifier == null
|
||||||
? null
|
? null
|
||||||
@@ -1249,16 +1402,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
}
|
}
|
||||||
var currentScope = symbolTable.getCurrentScope();
|
var currentScope = symbolTable.getCurrentScope();
|
||||||
var generatorDescriptorBuilder = currentScope.newFrameDescriptorBuilder();
|
var generatorDescriptorBuilder = currentScope.newFrameDescriptorBuilder();
|
||||||
var memberDescriptorBuilder = currentScope.newForGeneratorMemberDescriptorBuilder();
|
|
||||||
var keySlot = -1;
|
var keySlot = -1;
|
||||||
var valueSlot = -1;
|
var valueSlot = -1;
|
||||||
if (keyIdentifier != null) {
|
if (keyIdentifier != null) {
|
||||||
keySlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
|
keySlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
|
||||||
memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
|
|
||||||
}
|
}
|
||||||
if (valueIdentifier != null) {
|
if (valueIdentifier != null) {
|
||||||
valueSlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
|
valueSlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
|
||||||
memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
|
|
||||||
}
|
}
|
||||||
var unresolvedKeyTypeNode =
|
var unresolvedKeyTypeNode =
|
||||||
keyTypedIdentifier == null
|
keyTypedIdentifier == null
|
||||||
@@ -1280,12 +1430,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
|
? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
|
||||||
.initWriteSlotNode(valueSlot)
|
.initWriteSlotNode(valueSlot)
|
||||||
: null;
|
: null;
|
||||||
var iterableNode = visitExpr(ctx.getExpr());
|
var iterableNode = symbolTable.enterEagerGenerator(scope -> visitExpr(ctx.getExpr()));
|
||||||
var memberNodes =
|
var memberNodes =
|
||||||
symbolTable.enterForGenerator(
|
symbolTable.enterForGenerator(
|
||||||
generatorDescriptorBuilder,
|
params, generatorDescriptorBuilder, scope -> doVisitForWhenBody(ctx.getBody()));
|
||||||
memberDescriptorBuilder,
|
|
||||||
scope -> doVisitForWhenBody(ctx.getBody()));
|
|
||||||
return GeneratorForNodeGen.create(
|
return GeneratorForNodeGen.create(
|
||||||
createSourceSection(ctx),
|
createSourceSection(ctx),
|
||||||
generatorDescriptorBuilder.build(),
|
generatorDescriptorBuilder.build(),
|
||||||
@@ -1300,12 +1448,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
@Override
|
@Override
|
||||||
public PklRootNode visitModule(Module mod) {
|
public PklRootNode visitModule(Module mod) {
|
||||||
var moduleDecl = mod.getDecl();
|
var moduleDecl = mod.getDecl();
|
||||||
|
|
||||||
var annotationNodes =
|
|
||||||
moduleDecl != null
|
|
||||||
? doVisitAnnotations(moduleDecl.getAnnotations())
|
|
||||||
: new ExpressionNode[] {};
|
|
||||||
|
|
||||||
int modifiers;
|
int modifiers;
|
||||||
if (moduleDecl == null) {
|
if (moduleDecl == null) {
|
||||||
modifiers = VmModifier.NONE;
|
modifiers = VmModifier.NONE;
|
||||||
@@ -1324,6 +1466,23 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scope = (ModuleScope) symbolTable.getCurrentScope();
|
||||||
|
scope.setModifiers(modifiers);
|
||||||
|
|
||||||
|
// visit imports first so that we already have the object member name available
|
||||||
|
var imports = mod.getImports();
|
||||||
|
var importMembers = new ObjectMember[imports.size()];
|
||||||
|
for (var i = 0; i < imports.size(); i++) {
|
||||||
|
importMembers[i] = visitImportClause(imports.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
registerModuleScopeNames(mod, importMembers);
|
||||||
|
|
||||||
|
var annotationNodes =
|
||||||
|
moduleDecl != null
|
||||||
|
? doVisitAnnotations(moduleDecl.getAnnotations(), null)
|
||||||
|
: new ExpressionNode[] {};
|
||||||
|
|
||||||
var extendsOrAmendsClause = moduleDecl != null ? moduleDecl.getExtendsOrAmendsDecl() : null;
|
var extendsOrAmendsClause = moduleDecl != null ? moduleDecl.getExtendsOrAmendsDecl() : null;
|
||||||
|
|
||||||
var supermoduleNode =
|
var supermoduleNode =
|
||||||
@@ -1344,7 +1503,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
new UnresolvedTypeNode.Declared(supermoduleNode.getSourceSection(), supermoduleNode);
|
new UnresolvedTypeNode.Declared(supermoduleNode.getSourceSection(), supermoduleNode);
|
||||||
var moduleProperties =
|
var moduleProperties =
|
||||||
doVisitModuleProperties(
|
doVisitModuleProperties(
|
||||||
mod.getImports(),
|
importMembers,
|
||||||
mod.getClasses(),
|
mod.getClasses(),
|
||||||
mod.getTypeAliases(),
|
mod.getTypeAliases(),
|
||||||
List.of(),
|
List.of(),
|
||||||
@@ -1374,7 +1533,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
var moduleProperties =
|
var moduleProperties =
|
||||||
doVisitModuleProperties(
|
doVisitModuleProperties(
|
||||||
mod.getImports(),
|
importMembers,
|
||||||
mod.getClasses(),
|
mod.getClasses(),
|
||||||
mod.getTypeAliases(),
|
mod.getTypeAliases(),
|
||||||
mod.getProperties(),
|
mod.getProperties(),
|
||||||
@@ -1411,18 +1570,17 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private EconomicMap<Object, ObjectMember> doVisitModuleProperties(
|
private EconomicMap<Object, ObjectMember> doVisitModuleProperties(
|
||||||
List<ImportClause> imports,
|
ObjectMember[] imports,
|
||||||
List<Class> classes,
|
List<Class> classes,
|
||||||
List<TypeAlias> typeAliases,
|
List<TypeAlias> typeAliases,
|
||||||
List<ClassProperty> properties,
|
List<ClassProperty> properties,
|
||||||
Set<String> propertyNames,
|
Set<String> propertyNames,
|
||||||
ModuleInfo moduleInfo) {
|
ModuleInfo moduleInfo) {
|
||||||
|
|
||||||
var totalSize = imports.size() + classes.size() + typeAliases.size() + properties.size();
|
var totalSize = imports.length + classes.size() + typeAliases.size() + properties.size();
|
||||||
var result = EconomicMaps.<Object, ObjectMember>create(totalSize);
|
var result = EconomicMaps.<Object, ObjectMember>create(totalSize);
|
||||||
|
|
||||||
for (var _import : imports) {
|
for (var member : imports) {
|
||||||
var member = visitImportClause(_import);
|
|
||||||
checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames);
|
checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames);
|
||||||
EconomicMaps.put(result, member.getName(), member);
|
EconomicMaps.put(result, member.getName(), member);
|
||||||
}
|
}
|
||||||
@@ -1492,10 +1650,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
importName,
|
importName,
|
||||||
ConstLevel.NONE,
|
ConstLevel.NONE,
|
||||||
scope -> {
|
scope -> {
|
||||||
var modifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST;
|
var baseModifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST;
|
||||||
if (imp.isGlob()) {
|
var modifiers = imp.isGlob() ? baseModifiers | VmModifier.GLOB : baseModifiers;
|
||||||
modifiers = modifiers | VmModifier.GLOB;
|
|
||||||
}
|
|
||||||
var result =
|
var result =
|
||||||
new ObjectMember(
|
new ObjectMember(
|
||||||
importNode.getSourceSection(),
|
importNode.getSourceSection(),
|
||||||
@@ -1512,31 +1668,52 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerClassScopeNames(
|
||||||
|
SymbolTable.Scope scope, List<ClassProperty> properties, List<ClassMethod> methods) {
|
||||||
|
for (var prop : properties) {
|
||||||
|
var local = hasLocalModifier(prop.getModifiers());
|
||||||
|
scope.addProperty(
|
||||||
|
org.pkl.core.runtime.Identifier.property(prop.getName().getValue(), local),
|
||||||
|
local ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
}
|
||||||
|
for (var method : methods) {
|
||||||
|
var local = hasLocalModifier(method.getModifiers());
|
||||||
|
scope.addMethod(
|
||||||
|
org.pkl.core.runtime.Identifier.method(method.getName().getValue(), local),
|
||||||
|
local ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ObjectMember visitClass(Class clazz) {
|
public ObjectMember visitClass(Class clazz) {
|
||||||
var sourceSection = createSourceSection(clazz);
|
var sourceSection = createSourceSection(clazz);
|
||||||
var headerSection = createSourceSection(clazz.getHeaderSpan());
|
var headerSection = createSourceSection(clazz.getHeaderSpan());
|
||||||
|
|
||||||
var bodyNode = clazz.getBody();
|
|
||||||
|
|
||||||
var typeParameters = visitTypeParameterList(clazz.getTypeParameterList());
|
var typeParameters = visitTypeParameterList(clazz.getTypeParameterList());
|
||||||
|
|
||||||
List<ClassProperty> properties = bodyNode != null ? bodyNode.getProperties() : List.of();
|
|
||||||
List<ClassMethod> methods = bodyNode != null ? bodyNode.getMethods() : List.of();
|
|
||||||
|
|
||||||
var modifiers =
|
var modifiers =
|
||||||
doVisitModifiers(
|
doVisitModifiers(
|
||||||
clazz.getModifiers(), VmModifier.VALID_CLASS_MODIFIERS, "invalidClassModifier")
|
clazz.getModifiers(), VmModifier.VALID_CLASS_MODIFIERS, "invalidClassModifier")
|
||||||
| VmModifier.CLASS;
|
| VmModifier.CLASS;
|
||||||
|
|
||||||
|
var isLocalClass = VmModifier.isLocal(modifiers);
|
||||||
|
|
||||||
var className =
|
var className =
|
||||||
org.pkl.core.runtime.Identifier.property(
|
org.pkl.core.runtime.Identifier.property(clazz.getName().getValue(), isLocalClass);
|
||||||
clazz.getName().getValue(), VmModifier.isLocal(modifiers));
|
|
||||||
|
var annotations = doVisitAnnotations(clazz.getAnnotations(), className);
|
||||||
|
|
||||||
return symbolTable.enterClass(
|
return symbolTable.enterClass(
|
||||||
className,
|
className,
|
||||||
|
modifiers,
|
||||||
typeParameters,
|
typeParameters,
|
||||||
scope -> {
|
scope -> {
|
||||||
|
var bodyNode = clazz.getBody();
|
||||||
|
|
||||||
|
List<ClassProperty> properties = bodyNode != null ? bodyNode.getProperties() : List.of();
|
||||||
|
List<ClassMethod> methods = bodyNode != null ? bodyNode.getMethods() : List.of();
|
||||||
|
registerClassScopeNames(scope, properties, methods);
|
||||||
|
|
||||||
var supertypeCtx = clazz.getSuperClass();
|
var supertypeCtx = clazz.getSuperClass();
|
||||||
|
|
||||||
// needs to be inside `enterClass` so that class' type parameters are in scope
|
// needs to be inside `enterClass` so that class' type parameters are in scope
|
||||||
@@ -1572,7 +1749,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
sourceSection,
|
sourceSection,
|
||||||
headerSection,
|
headerSection,
|
||||||
createDocSourceSection(clazz.getDocComment()),
|
createDocSourceSection(clazz.getDocComment()),
|
||||||
doVisitAnnotations(clazz.getAnnotations()),
|
annotations,
|
||||||
modifiers,
|
modifiers,
|
||||||
classInfo,
|
classInfo,
|
||||||
typeParameters,
|
typeParameters,
|
||||||
@@ -1582,13 +1759,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
doVisitClassProperties(properties, propertyNames),
|
doVisitClassProperties(properties, propertyNames),
|
||||||
doVisitMethodDefs(methods));
|
doVisitMethodDefs(methods));
|
||||||
|
|
||||||
var isLocal = VmModifier.isLocal(modifiers);
|
|
||||||
|
|
||||||
var result =
|
var result =
|
||||||
new ObjectMember(
|
new ObjectMember(
|
||||||
sourceSection,
|
sourceSection,
|
||||||
headerSection,
|
headerSection,
|
||||||
isLocal ? VmModifier.LOCAL_CLASS_OBJECT_MEMBER : VmModifier.CLASS_OBJECT_MEMBER,
|
isLocalClass
|
||||||
|
? VmModifier.LOCAL_CLASS_OBJECT_MEMBER
|
||||||
|
: VmModifier.CLASS_OBJECT_MEMBER,
|
||||||
scope.getName(),
|
scope.getName(),
|
||||||
scope.getQualifiedName());
|
scope.getQualifiedName());
|
||||||
|
|
||||||
@@ -1611,6 +1788,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer visitModifier(Modifier modifier) {
|
public Integer visitModifier(Modifier modifier) {
|
||||||
|
return toModifier(modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int toModifier(Modifier modifier) {
|
||||||
return switch (modifier.getValue()) {
|
return switch (modifier.getValue()) {
|
||||||
case EXTERNAL -> VmModifier.EXTERNAL;
|
case EXTERNAL -> VmModifier.EXTERNAL;
|
||||||
case ABSTRACT -> VmModifier.ABSTRACT;
|
case ABSTRACT -> VmModifier.ABSTRACT;
|
||||||
@@ -1658,7 +1839,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
var expr = entry.getExpr();
|
var expr = entry.getExpr();
|
||||||
var objectBodies = entry.getBodyList();
|
var objectBodies = entry.getBodyList();
|
||||||
var docComment = createDocSourceSection(docCom);
|
var docComment = createDocSourceSection(docCom);
|
||||||
var annotationNodes = doVisitAnnotations(annotations);
|
|
||||||
var sourceSection = createSourceSection(entry);
|
var sourceSection = createSourceSection(entry);
|
||||||
var headerStart = !modifierList.isEmpty() ? modifierList.get(0).span() : name.span();
|
var headerStart = !modifierList.isEmpty() ? modifierList.get(0).span() : name.span();
|
||||||
var headerEnd = typeAnnotation != null ? typeAnnotation.span() : name.span();
|
var headerEnd = typeAnnotation != null ? typeAnnotation.span() : name.span();
|
||||||
@@ -1670,6 +1850,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
var isLocal = VmModifier.isLocal(modifiers);
|
var isLocal = VmModifier.isLocal(modifiers);
|
||||||
var propertyName = org.pkl.core.runtime.Identifier.property(name.getValue(), isLocal);
|
var propertyName = org.pkl.core.runtime.Identifier.property(name.getValue(), isLocal);
|
||||||
|
var annotationNodes = doVisitAnnotations(annotations, propertyName);
|
||||||
|
|
||||||
return symbolTable.enterProperty(
|
return symbolTable.enterProperty(
|
||||||
propertyName,
|
propertyName,
|
||||||
@@ -1760,9 +1941,14 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx);
|
var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx);
|
||||||
var paramCount = paramListCtx.getParameters().size();
|
var paramCount = paramListCtx.getParameters().size();
|
||||||
|
|
||||||
|
var bindings = getParameterNames(paramListCtx);
|
||||||
|
|
||||||
|
var annotations = doVisitAnnotations(entry.getAnnotations(), methodName);
|
||||||
|
|
||||||
return symbolTable.enterMethod(
|
return symbolTable.enterMethod(
|
||||||
methodName,
|
methodName,
|
||||||
getConstLevel(modifiers),
|
getConstLevel(modifiers),
|
||||||
|
bindings,
|
||||||
descriptorBuilder,
|
descriptorBuilder,
|
||||||
typeParameters,
|
typeParameters,
|
||||||
scope -> {
|
scope -> {
|
||||||
@@ -1806,7 +1992,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
headerSection,
|
headerSection,
|
||||||
scope.buildFrameDescriptor(),
|
scope.buildFrameDescriptor(),
|
||||||
createDocSourceSection(entry.getDocComment()),
|
createDocSourceSection(entry.getDocComment()),
|
||||||
doVisitAnnotations(entry.getAnnotations()),
|
annotations,
|
||||||
modifiers,
|
modifiers,
|
||||||
methodName,
|
methodName,
|
||||||
scope.getQualifiedName(),
|
scope.getQualifiedName(),
|
||||||
@@ -1835,6 +2021,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
var name = org.pkl.core.runtime.Identifier.property(typeAlias.getName().getValue(), isLocal);
|
var name = org.pkl.core.runtime.Identifier.property(typeAlias.getName().getValue(), isLocal);
|
||||||
|
|
||||||
var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList());
|
var typeParameters = visitTypeParameterList(typeAlias.getTypeParameterList());
|
||||||
|
var objectMemberModifiers =
|
||||||
|
isLocal ? VmModifier.LOCAL_TYPEALIAS_OBJECT_MEMBER : VmModifier.TYPEALIAS_OBJECT_MEMBER;
|
||||||
|
|
||||||
|
var annotations = doVisitAnnotations(typeAlias.getAnnotations(), name);
|
||||||
|
|
||||||
return symbolTable.enterTypeAlias(
|
return symbolTable.enterTypeAlias(
|
||||||
name,
|
name,
|
||||||
@@ -1846,7 +2036,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
sourceSection,
|
sourceSection,
|
||||||
headerSection,
|
headerSection,
|
||||||
createDocSourceSection(typeAlias.getDocComment()),
|
createDocSourceSection(typeAlias.getDocComment()),
|
||||||
doVisitAnnotations(typeAlias.getAnnotations()),
|
annotations,
|
||||||
modifiers,
|
modifiers,
|
||||||
scopeName.toString(),
|
scopeName.toString(),
|
||||||
scope.getQualifiedName(),
|
scope.getQualifiedName(),
|
||||||
@@ -1857,9 +2047,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
new ObjectMember(
|
new ObjectMember(
|
||||||
sourceSection,
|
sourceSection,
|
||||||
headerSection,
|
headerSection,
|
||||||
isLocal
|
objectMemberModifiers,
|
||||||
? VmModifier.LOCAL_TYPEALIAS_OBJECT_MEMBER
|
|
||||||
: VmModifier.TYPEALIAS_OBJECT_MEMBER,
|
|
||||||
scopeName,
|
scopeName,
|
||||||
scope.getQualifiedName());
|
scope.getQualifiedName());
|
||||||
|
|
||||||
@@ -1871,8 +2059,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public ExpressionNode visitAnnotation(
|
||||||
public ExpressionNode visitAnnotation(Annotation annotation) {
|
Annotation annotation, org.pkl.core.runtime.@Nullable Identifier annotatedMemberName) {
|
||||||
var verifyNode = new CheckIsAnnotationClassNode(visitType(annotation.getType()));
|
var verifyNode = new CheckIsAnnotationClassNode(visitType(annotation.getType()));
|
||||||
|
|
||||||
var bodyCtx = annotation.getBody();
|
var bodyCtx = annotation.getBody();
|
||||||
@@ -1890,13 +2078,16 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
verifyNode);
|
verifyNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return symbolTable.enterAnnotationScope((scope) -> doVisitObjectBody(bodyCtx, verifyNode));
|
return symbolTable.enterAnnotationScope(
|
||||||
|
annotatedMemberName, (scope) -> doVisitObjectBody(bodyCtx, verifyNode));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExpressionNode[] doVisitAnnotations(List<? extends Annotation> annotations) {
|
private ExpressionNode[] doVisitAnnotations(
|
||||||
|
List<? extends Annotation> annotations,
|
||||||
|
org.pkl.core.runtime.@Nullable Identifier annotatedMemberName) {
|
||||||
var nodes = new ExpressionNode[annotations.size()];
|
var nodes = new ExpressionNode[annotations.size()];
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
nodes[i] = visitAnnotation(annotations.get(i));
|
nodes[i] = visitAnnotation(annotations.get(i), annotatedMemberName);
|
||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
@@ -2004,9 +2195,33 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
return parentNode;
|
return parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addObjectNamesToScope(ObjectScope scope, ObjectBody body) {
|
||||||
|
for (var member : body.getMembers()) {
|
||||||
|
if (member instanceof ObjectProperty prop) {
|
||||||
|
var local = hasLocalModifier(prop.getModifiers());
|
||||||
|
scope.addProperty(
|
||||||
|
org.pkl.core.runtime.Identifier.property(prop.getIdentifier().getValue(), local),
|
||||||
|
local ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
} else if (member instanceof ObjectMethod method) {
|
||||||
|
var local = hasLocalModifier(method.getModifiers());
|
||||||
|
scope.addMethod(
|
||||||
|
org.pkl.core.runtime.Identifier.method(method.getIdentifier().getValue(), local),
|
||||||
|
local ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
} else if (member instanceof WhenGenerator whenGenerator) {
|
||||||
|
addObjectNamesToScope(scope, whenGenerator.getThenClause());
|
||||||
|
var elseClause = whenGenerator.getElseClause();
|
||||||
|
if (elseClause != null) {
|
||||||
|
addObjectNamesToScope(scope, elseClause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ExpressionNode doVisitObjectBody(ObjectBody body, ExpressionNode parentNode) {
|
private ExpressionNode doVisitObjectBody(ObjectBody body, ExpressionNode parentNode) {
|
||||||
return symbolTable.enterObjectScope(
|
return symbolTable.enterObjectScope(
|
||||||
|
body,
|
||||||
(scope) -> {
|
(scope) -> {
|
||||||
|
addObjectNamesToScope(scope, body);
|
||||||
var objectMembers = body.getMembers();
|
var objectMembers = body.getMembers();
|
||||||
if (objectMembers.isEmpty()) {
|
if (objectMembers.isEmpty()) {
|
||||||
return EmptyObjectLiteralNodeGen.create(
|
return EmptyObjectLiteralNodeGen.create(
|
||||||
@@ -2290,8 +2505,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Pair<ExpressionNode, ObjectMember> doVisitObjectEntry(ObjectEntry entry) {
|
private Pair<ExpressionNode, ObjectMember> doVisitObjectEntry(ObjectEntry entry) {
|
||||||
var keyNode = visitExpr(entry.getKey());
|
var keyNode = symbolTable.enterEagerGenerator((scp) -> visitExpr(entry.getKey()));
|
||||||
|
|
||||||
var member =
|
var member =
|
||||||
doVisitObjectEntryBody(
|
doVisitObjectEntryBody(
|
||||||
createSourceSection(entry), keyNode, entry.getValue(), entry.getBodyList());
|
createSourceSection(entry), keyNode, entry.getValue(), entry.getBodyList());
|
||||||
@@ -2371,9 +2585,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
var frameDescriptorBuilder = createFrameDescriptorBuilder(paramList);
|
var frameDescriptorBuilder = createFrameDescriptorBuilder(paramList);
|
||||||
|
|
||||||
|
var bindings = getParameterNames(paramList);
|
||||||
|
|
||||||
return symbolTable.enterMethod(
|
return symbolTable.enterMethod(
|
||||||
methodName,
|
methodName,
|
||||||
getConstLevel(modifiers),
|
getConstLevel(modifiers),
|
||||||
|
bindings,
|
||||||
frameDescriptorBuilder,
|
frameDescriptorBuilder,
|
||||||
List.of(),
|
List.of(),
|
||||||
scope -> {
|
scope -> {
|
||||||
@@ -2590,7 +2807,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
return doVisitParameterTypes(paramList.getParameters());
|
return doVisitParameterTypes(paramList.getParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnresolvedTypeNode[] doVisitParameterTypes(List<Parameter> params) {
|
private UnresolvedTypeNode[] doVisitParameterTypes(List<org.pkl.parser.syntax.Parameter> params) {
|
||||||
var typeNodes = new UnresolvedTypeNode[params.size()];
|
var typeNodes = new UnresolvedTypeNode[params.size()];
|
||||||
for (int i = 0; i < typeNodes.length; i++) {
|
for (int i = 0; i < typeNodes.length; i++) {
|
||||||
if (params.get(i) instanceof TypedIdentifier typedIdentifier) {
|
if (params.get(i) instanceof TypedIdentifier typedIdentifier) {
|
||||||
@@ -2683,8 +2900,17 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
return needsConst;
|
return needsConst;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FrameDescriptor.Builder createFrameDescriptorBuilder(ParameterList params) {
|
private static List<String> getParameterNames(ParameterList parameterList) {
|
||||||
var builder = FrameDescriptor.newBuilder(params.getParameters().size());
|
var result = new ArrayList<String>(parameterList.getParameters().size());
|
||||||
|
for (var param : parameterList.getParameters()) {
|
||||||
|
var name = param instanceof TypedIdentifier id ? id.getIdentifier().getValue() : "_";
|
||||||
|
result.add(name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrameDescriptorBuilder createFrameDescriptorBuilder(ParameterList params) {
|
||||||
|
var builder = new FrameDescriptorBuilder(params.getParameters().size());
|
||||||
for (var param : params.getParameters()) {
|
for (var param : params.getParameters()) {
|
||||||
org.pkl.core.runtime.Identifier identifier = null;
|
org.pkl.core.runtime.Identifier identifier = null;
|
||||||
if (param instanceof TypedIdentifier typedIdentifier) {
|
if (param instanceof TypedIdentifier typedIdentifier) {
|
||||||
@@ -2757,18 +2983,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
return org.pkl.core.runtime.Identifier.get(text);
|
return org.pkl.core.runtime.Identifier.get(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExpressionNode createResolveVariableNode(
|
|
||||||
SourceSection section, org.pkl.core.runtime.Identifier propertyName) {
|
|
||||||
var scope = symbolTable.getCurrentScope();
|
|
||||||
return new ResolveVariableNode(
|
|
||||||
section,
|
|
||||||
propertyName,
|
|
||||||
isBaseModule,
|
|
||||||
scope.isCustomThisScope(),
|
|
||||||
scope.getConstLevel(),
|
|
||||||
scope.getConstDepth());
|
|
||||||
}
|
|
||||||
|
|
||||||
private URI resolveImport(String importUri, StringConstant ctx) {
|
private URI resolveImport(String importUri, StringConstant ctx) {
|
||||||
URI parsedUri;
|
URI parsedUri;
|
||||||
try {
|
try {
|
||||||
@@ -2850,4 +3064,47 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
private static SourceSection unavailableSourceSection() {
|
private static SourceSection unavailableSourceSection() {
|
||||||
return VmUtils.unavailableSourceSection();
|
return VmUtils.unavailableSourceSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void registerModuleScopeNames(Module mod, ObjectMember[] importMembers) {
|
||||||
|
var scope = symbolTable.getCurrentScope();
|
||||||
|
|
||||||
|
for (var imp : importMembers) {
|
||||||
|
scope.addProperty(imp.getName(), imp.getModifiers());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var clazz : mod.getClasses()) {
|
||||||
|
var isLocal = hasLocalModifier(clazz.getModifiers());
|
||||||
|
scope.addProperty(
|
||||||
|
org.pkl.core.runtime.Identifier.property(clazz.getName().getValue(), isLocal),
|
||||||
|
isLocal ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var typealias : mod.getTypeAliases()) {
|
||||||
|
var isLocal = hasLocalModifier(typealias.getModifiers());
|
||||||
|
scope.addProperty(
|
||||||
|
org.pkl.core.runtime.Identifier.property(typealias.getName().getValue(), isLocal),
|
||||||
|
isLocal ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var prop : mod.getProperties()) {
|
||||||
|
var isLocal = hasLocalModifier(prop.getModifiers());
|
||||||
|
scope.addProperty(
|
||||||
|
org.pkl.core.runtime.Identifier.property(prop.getName().getValue(), isLocal),
|
||||||
|
isLocal ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var method : mod.getMethods()) {
|
||||||
|
var isLocal = hasLocalModifier(method.getModifiers());
|
||||||
|
scope.addMethod(
|
||||||
|
org.pkl.core.runtime.Identifier.method(method.getName().getValue(), isLocal),
|
||||||
|
isLocal ? VmModifier.LOCAL : VmModifier.NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasLocalModifier(List<Modifier> modifiers) {
|
||||||
|
for (var m : modifiers) {
|
||||||
|
if (m.getValue() == ModifierValue.LOCAL) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025-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.ast.builder;
|
||||||
|
|
||||||
|
import org.pkl.core.ast.VmModifier;
|
||||||
|
|
||||||
|
public sealed interface MethodResolution {
|
||||||
|
record ImplicitBaseMethod() implements MethodResolution {}
|
||||||
|
|
||||||
|
record LexicalMethod(
|
||||||
|
boolean isObjectMethod,
|
||||||
|
boolean isOnClosedClass,
|
||||||
|
boolean isModuleScope,
|
||||||
|
int modifiers,
|
||||||
|
int levelsUp)
|
||||||
|
implements MethodResolution {
|
||||||
|
public boolean isLocal() {
|
||||||
|
return VmModifier.isLocal(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExternal() {
|
||||||
|
return VmModifier.isExternal(modifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record ImplicitThisMethod() implements MethodResolution {}
|
||||||
|
}
|
||||||
@@ -16,26 +16,35 @@
|
|||||||
package org.pkl.core.ast.builder;
|
package org.pkl.core.ast.builder;
|
||||||
|
|
||||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||||
import com.oracle.truffle.api.frame.FrameDescriptor.Builder;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.pkl.core.TypeParameter;
|
import org.pkl.core.TypeParameter;
|
||||||
import org.pkl.core.ast.ConstantNode;
|
import org.pkl.core.ast.ConstantNode;
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.ast.expression.generator.GeneratorMemberNode;
|
import org.pkl.core.ast.VmModifier;
|
||||||
|
import org.pkl.core.ast.builder.MethodResolution.ImplicitBaseMethod;
|
||||||
|
import org.pkl.core.ast.builder.MethodResolution.ImplicitThisMethod;
|
||||||
|
import org.pkl.core.ast.builder.MethodResolution.LexicalMethod;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
|
||||||
|
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
|
||||||
import org.pkl.core.ast.member.ObjectMember;
|
import org.pkl.core.ast.member.ObjectMember;
|
||||||
|
import org.pkl.core.runtime.BaseModuleMembers;
|
||||||
|
import org.pkl.core.runtime.FrameDescriptorBuilder;
|
||||||
import org.pkl.core.runtime.Identifier;
|
import org.pkl.core.runtime.Identifier;
|
||||||
import org.pkl.core.runtime.ModuleInfo;
|
import org.pkl.core.runtime.ModuleInfo;
|
||||||
import org.pkl.core.runtime.VmDataSize;
|
import org.pkl.core.runtime.VmDataSize;
|
||||||
import org.pkl.core.runtime.VmDuration;
|
import org.pkl.core.runtime.VmDuration;
|
||||||
|
import org.pkl.core.util.LateInit;
|
||||||
import org.pkl.parser.Lexer;
|
import org.pkl.parser.Lexer;
|
||||||
|
import org.pkl.parser.syntax.ObjectBody;
|
||||||
|
|
||||||
public final class SymbolTable {
|
public final class SymbolTable {
|
||||||
|
|
||||||
private Scope currentScope;
|
private Scope currentScope;
|
||||||
|
|
||||||
public SymbolTable(ModuleInfo moduleInfo) {
|
public SymbolTable(ModuleInfo moduleInfo, boolean isBaseModule) {
|
||||||
currentScope = new ModuleScope(moduleInfo);
|
currentScope = new ModuleScope(moduleInfo, isBaseModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scope getCurrentScope() {
|
public Scope getCurrentScope() {
|
||||||
@@ -53,6 +62,7 @@ public final class SymbolTable {
|
|||||||
|
|
||||||
public ObjectMember enterClass(
|
public ObjectMember enterClass(
|
||||||
Identifier name,
|
Identifier name,
|
||||||
|
int modifiers,
|
||||||
List<TypeParameter> typeParameters,
|
List<TypeParameter> typeParameters,
|
||||||
Function<ClassScope, ObjectMember> nodeFactory) {
|
Function<ClassScope, ObjectMember> nodeFactory) {
|
||||||
return doEnter(
|
return doEnter(
|
||||||
@@ -60,7 +70,8 @@ public final class SymbolTable {
|
|||||||
currentScope,
|
currentScope,
|
||||||
name,
|
name,
|
||||||
toQualifiedName(name),
|
toQualifiedName(name),
|
||||||
FrameDescriptor.newBuilder(),
|
modifiers,
|
||||||
|
new FrameDescriptorBuilder(),
|
||||||
typeParameters),
|
typeParameters),
|
||||||
nodeFactory);
|
nodeFactory);
|
||||||
}
|
}
|
||||||
@@ -74,7 +85,7 @@ public final class SymbolTable {
|
|||||||
currentScope,
|
currentScope,
|
||||||
name,
|
name,
|
||||||
toQualifiedName(name),
|
toQualifiedName(name),
|
||||||
FrameDescriptor.newBuilder(),
|
new FrameDescriptorBuilder(),
|
||||||
typeParameters),
|
typeParameters),
|
||||||
nodeFactory);
|
nodeFactory);
|
||||||
}
|
}
|
||||||
@@ -82,7 +93,8 @@ public final class SymbolTable {
|
|||||||
public <T> T enterMethod(
|
public <T> T enterMethod(
|
||||||
Identifier name,
|
Identifier name,
|
||||||
ConstLevel constLevel,
|
ConstLevel constLevel,
|
||||||
Builder frameDescriptorBuilder,
|
List<String> bindings,
|
||||||
|
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||||
List<TypeParameter> typeParameters,
|
List<TypeParameter> typeParameters,
|
||||||
Function<MethodScope, T> nodeFactory) {
|
Function<MethodScope, T> nodeFactory) {
|
||||||
return doEnter(
|
return doEnter(
|
||||||
@@ -91,26 +103,30 @@ public final class SymbolTable {
|
|||||||
name,
|
name,
|
||||||
toQualifiedName(name),
|
toQualifiedName(name),
|
||||||
constLevel,
|
constLevel,
|
||||||
|
bindings,
|
||||||
frameDescriptorBuilder,
|
frameDescriptorBuilder,
|
||||||
typeParameters),
|
typeParameters),
|
||||||
nodeFactory);
|
nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> T enterEagerGenerator(Function<EagerGeneratorScope, T> nodeFactory) {
|
||||||
|
return doEnter(new EagerGeneratorScope(currentScope, currentScope.qualifiedName), nodeFactory);
|
||||||
|
}
|
||||||
|
|
||||||
public <T> T enterForGenerator(
|
public <T> T enterForGenerator(
|
||||||
FrameDescriptor.Builder frameDescriptorBuilder,
|
List<String> params,
|
||||||
FrameDescriptor.Builder memberDescriptorBuilder,
|
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||||
Function<ForGeneratorScope, T> nodeFactory) {
|
Function<ForGeneratorScope, T> nodeFactory) {
|
||||||
return doEnter(
|
return doEnter(
|
||||||
new ForGeneratorScope(
|
new ForGeneratorScope(
|
||||||
currentScope,
|
currentScope, currentScope.qualifiedName, params, frameDescriptorBuilder),
|
||||||
currentScope.qualifiedName,
|
|
||||||
frameDescriptorBuilder,
|
|
||||||
memberDescriptorBuilder),
|
|
||||||
nodeFactory);
|
nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T enterLambda(
|
public <T> T enterLambda(
|
||||||
FrameDescriptor.Builder frameDescriptorBuilder, Function<LambdaScope, T> nodeFactory) {
|
List<String> bindings,
|
||||||
|
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||||
|
Function<LambdaScope, T> nodeFactory) {
|
||||||
|
|
||||||
// flatten names of lambdas nested inside other lambdas for presentation purposes
|
// flatten names of lambdas nested inside other lambdas for presentation purposes
|
||||||
var parentScope = currentScope;
|
var parentScope = currentScope;
|
||||||
@@ -122,14 +138,15 @@ public final class SymbolTable {
|
|||||||
var qualifiedName = parentScope.qualifiedName + "." + parentScope.getNextLambdaName();
|
var qualifiedName = parentScope.qualifiedName + "." + parentScope.getNextLambdaName();
|
||||||
|
|
||||||
return doEnter(
|
return doEnter(
|
||||||
new LambdaScope(currentScope, qualifiedName, frameDescriptorBuilder), nodeFactory);
|
new LambdaScope(currentScope, bindings, qualifiedName, frameDescriptorBuilder),
|
||||||
|
nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T enterProperty(
|
public <T> T enterProperty(
|
||||||
Identifier name, ConstLevel constLevel, Function<PropertyScope, T> nodeFactory) {
|
Identifier name, ConstLevel constLevel, Function<PropertyScope, T> nodeFactory) {
|
||||||
return doEnter(
|
return doEnter(
|
||||||
new PropertyScope(
|
new PropertyScope(
|
||||||
currentScope, name, toQualifiedName(name), constLevel, FrameDescriptor.newBuilder()),
|
currentScope, name, toQualifiedName(name), constLevel, new FrameDescriptorBuilder()),
|
||||||
nodeFactory);
|
nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,8 +157,8 @@ public final class SymbolTable {
|
|||||||
var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode);
|
var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode);
|
||||||
var builder =
|
var builder =
|
||||||
currentScope instanceof ForGeneratorScope forScope
|
currentScope instanceof ForGeneratorScope forScope
|
||||||
? forScope.memberDescriptorBuilder
|
? forScope.frameDescriptorBuilder
|
||||||
: FrameDescriptor.newBuilder();
|
: new FrameDescriptorBuilder();
|
||||||
return doEnter(new EntryScope(currentScope, qualifiedName, builder), nodeFactory);
|
return doEnter(new EntryScope(currentScope, qualifiedName, builder), nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,13 +167,20 @@ public final class SymbolTable {
|
|||||||
new CustomThisScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
new CustomThisScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T enterAnnotationScope(Function<AnnotationScope, T> nodeFactory) {
|
public <T> T enterAnnotationScope(
|
||||||
|
@Nullable Identifier annotatedTargetName, Function<AnnotationScope, T> nodeFactory) {
|
||||||
|
var qualifiedName =
|
||||||
|
annotatedTargetName == null
|
||||||
|
? currentScope.getQualifiedName()
|
||||||
|
: toQualifiedName(annotatedTargetName);
|
||||||
return doEnter(
|
return doEnter(
|
||||||
new AnnotationScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
new AnnotationScope(currentScope, qualifiedName, currentScope.frameDescriptorBuilder),
|
||||||
|
nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T enterObjectScope(Function<ObjectScope, T> nodeFactory) {
|
public <T> T enterObjectScope(ObjectBody body, Function<ObjectScope, T> nodeFactory) {
|
||||||
return doEnter(new ObjectScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
return doEnter(
|
||||||
|
new ObjectScope(currentScope, body, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T, S extends Scope> T doEnter(S scope, Function<S, T> nodeFactory) {
|
private <T, S extends Scope> T doEnter(S scope, Function<S, T> nodeFactory) {
|
||||||
@@ -174,25 +198,35 @@ public final class SymbolTable {
|
|||||||
return currentScope.qualifiedName + separator + Lexer.maybeQuoteIdentifier(name.toString());
|
return currentScope.qualifiedName + separator + Lexer.maybeQuoteIdentifier(name.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record Member(Identifier name, int modifiers) {}
|
||||||
|
|
||||||
public abstract static class Scope {
|
public abstract static class Scope {
|
||||||
private final @Nullable Scope parent;
|
private final @Nullable Scope parent;
|
||||||
private final @Nullable Identifier name;
|
private final @Nullable Identifier name;
|
||||||
private final String qualifiedName;
|
private final String qualifiedName;
|
||||||
private int lambdaCount = 0;
|
private int lambdaCount = 0;
|
||||||
private int entryCount = 0;
|
private int entryCount = 0;
|
||||||
private final FrameDescriptor.Builder frameDescriptorBuilder;
|
protected final FrameDescriptorBuilder frameDescriptorBuilder;
|
||||||
private final ConstLevel constLevel;
|
private final ConstLevel constLevel;
|
||||||
|
protected boolean isBaseModule;
|
||||||
|
// The properties defined on this (lexical) scope
|
||||||
|
protected final Map<String, Member> properties = new HashMap<>();
|
||||||
|
// The methods defined on this (lexical) scope
|
||||||
|
protected final Map<String, Member> methods = new HashMap<>();
|
||||||
|
|
||||||
private Scope(
|
private Scope(
|
||||||
@Nullable Scope parent,
|
@Nullable Scope parent,
|
||||||
@Nullable Identifier name,
|
@Nullable Identifier name,
|
||||||
String qualifiedName,
|
String qualifiedName,
|
||||||
ConstLevel constLevel,
|
ConstLevel constLevel,
|
||||||
FrameDescriptor.Builder frameDescriptorBuilder) {
|
FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.qualifiedName = qualifiedName;
|
this.qualifiedName = qualifiedName;
|
||||||
this.frameDescriptorBuilder = frameDescriptorBuilder;
|
this.frameDescriptorBuilder = frameDescriptorBuilder;
|
||||||
|
if (parent != null) {
|
||||||
|
this.isBaseModule = parent.isBaseModule;
|
||||||
|
}
|
||||||
// const level can never decrease
|
// const level can never decrease
|
||||||
this.constLevel =
|
this.constLevel =
|
||||||
parent != null && parent.constLevel.biggerOrEquals(constLevel)
|
parent != null && parent.constLevel.biggerOrEquals(constLevel)
|
||||||
@@ -225,26 +259,10 @@ public final class SymbolTable {
|
|||||||
* Returns a new descriptor builder that contains the same slots as the current scope's frame
|
* Returns a new descriptor builder that contains the same slots as the current scope's frame
|
||||||
* descriptor.
|
* descriptor.
|
||||||
*/
|
*/
|
||||||
public FrameDescriptor.Builder newFrameDescriptorBuilder() {
|
public FrameDescriptorBuilder newFrameDescriptorBuilder() {
|
||||||
return new FrameDescriptorBuilder(buildFrameDescriptor());
|
return new FrameDescriptorBuilder(buildFrameDescriptor());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a new descriptor builder for a {@link GeneratorMemberNode} in the current scope. */
|
|
||||||
public FrameDescriptor.Builder newForGeneratorMemberDescriptorBuilder() {
|
|
||||||
return this instanceof ForGeneratorScope forScope
|
|
||||||
? newFrameDescriptorBuilder(forScope.buildMemberDescriptor())
|
|
||||||
: FrameDescriptor.newBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FrameDescriptor.Builder newFrameDescriptorBuilder(FrameDescriptor descriptor) {
|
|
||||||
var builder = FrameDescriptor.newBuilder();
|
|
||||||
for (var i = 0; i < descriptor.getNumberOfSlots(); i++) {
|
|
||||||
builder.addSlot(
|
|
||||||
descriptor.getSlotKind(i), descriptor.getSlotName(i), descriptor.getSlotInfo(i));
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable TypeParameter getTypeParameter(String name) {
|
public @Nullable TypeParameter getTypeParameter(String name) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -276,7 +294,10 @@ public final class SymbolTable {
|
|||||||
var depth = -1;
|
var depth = -1;
|
||||||
var lexicalScope = getLexicalScope();
|
var lexicalScope = getLexicalScope();
|
||||||
while (lexicalScope.getConstLevel() == ConstLevel.ALL) {
|
while (lexicalScope.getConstLevel() == ConstLevel.ALL) {
|
||||||
|
// LambdaScope inherits constLevel but doesn't create a const scope barrier
|
||||||
|
if (!(lexicalScope instanceof LambdaScope)) {
|
||||||
depth += 1;
|
depth += 1;
|
||||||
|
}
|
||||||
var parent = lexicalScope.getParent();
|
var parent = lexicalScope.getParent();
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
return depth;
|
return depth;
|
||||||
@@ -354,18 +375,161 @@ public final class SymbolTable {
|
|||||||
public ConstLevel getConstLevel() {
|
public ConstLevel getConstLevel() {
|
||||||
return constLevel;
|
return constLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addProperty(Identifier name, int modifiers) {
|
||||||
|
var prevProperty = this.properties.put(name.toString(), new Member(name, modifiers));
|
||||||
|
if (prevProperty != null
|
||||||
|
&& !VmModifier.hasSameModifier(prevProperty.modifiers, modifiers, VmModifier.LOCAL)) {
|
||||||
|
// this can happen in when generators:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// when (cond) {
|
||||||
|
// local prop = 1
|
||||||
|
// } else {
|
||||||
|
// prop = 2
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// this can't happen with methods; object methods can only be `local`.
|
||||||
|
this.properties.put(
|
||||||
|
name.toString(), new Member(name, modifiers | VmModifier.AMBIGUOUS_LOCALITY));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface LexicalScope {}
|
public void addMethod(Identifier name, int modifiers) {
|
||||||
|
this.methods.put(name.toString(), new Member(name, modifiers));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final VariableResolution resolveVariable(String name) {
|
||||||
|
var resolved = resolveLexical((scope, levelUp) -> scope.doResolveProperty(name, levelUp));
|
||||||
|
if (resolved != null) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
if (!isBaseModule) {
|
||||||
|
if (BaseModuleMembers.hasProperty(name)) {
|
||||||
|
return new ImplicitBaseProperty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new VariableResolution.ImplicitThisProperty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final MethodResolution resolveMethod(String name) {
|
||||||
|
var resolved = resolveLexical((scope, levelUp) -> scope.doResolveMethod(name, levelUp));
|
||||||
|
if (resolved != null) {
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
if (!isBaseModule) {
|
||||||
|
if (BaseModuleMembers.hasMethod(name)) {
|
||||||
|
return new ImplicitBaseMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ImplicitThisMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface ResolutionFunction<T> {
|
||||||
|
@Nullable T apply(LexicalScope scope, int levelUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @Nullable <R> R resolveLexical(ResolutionFunction<R> fun) {
|
||||||
|
var levelsUp = 0;
|
||||||
|
var shouldSkip = false;
|
||||||
|
for (var scope = this; scope != null; scope = scope.getParent()) {
|
||||||
|
// for headers resolve variables one scope up
|
||||||
|
if (scope instanceof EagerGeneratorScope) {
|
||||||
|
shouldSkip = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// annotations on class members don't level up
|
||||||
|
if (scope instanceof AnnotationScope && scope.getParent() instanceof ClassScope) {
|
||||||
|
levelsUp--;
|
||||||
|
}
|
||||||
|
if (scope instanceof LexicalScope lex) {
|
||||||
|
if (shouldSkip && !(scope instanceof ForGeneratorScope)) {
|
||||||
|
if (scope instanceof ObjectScope objectScope && objectScope.hasParams()) {
|
||||||
|
levelsUp++;
|
||||||
|
}
|
||||||
|
shouldSkip = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var result = fun.apply(lex, levelsUp);
|
||||||
|
if (result != null) return result;
|
||||||
|
if (scope instanceof MethodScope || scope instanceof ForGeneratorScope) {
|
||||||
|
// fors and methods don't level up
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
levelsUp++;
|
||||||
|
if (scope instanceof ObjectScope objectScope && objectScope.hasParams()) {
|
||||||
|
levelsUp++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface LexicalScope {
|
||||||
|
@Nullable VariableResolution doResolveProperty(String name, int levelsUp);
|
||||||
|
|
||||||
|
@Nullable MethodResolution doResolveMethod(String name, int levelsUp);
|
||||||
|
}
|
||||||
|
|
||||||
public static class ObjectScope extends Scope implements LexicalScope {
|
public static class ObjectScope extends Scope implements LexicalScope {
|
||||||
private ObjectScope(Scope parent, Builder frameDescriptorBuilder) {
|
private final Map<String, Integer> params;
|
||||||
|
|
||||||
|
private ObjectScope(
|
||||||
|
Scope parent, ObjectBody body, FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
super(
|
super(
|
||||||
parent,
|
parent,
|
||||||
parent.getNameOrNull(),
|
parent.getNameOrNull(),
|
||||||
parent.getQualifiedName(),
|
parent.getQualifiedName(),
|
||||||
ConstLevel.NONE,
|
ConstLevel.NONE,
|
||||||
frameDescriptorBuilder);
|
frameDescriptorBuilder);
|
||||||
|
params = collectParams(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasParams() {
|
||||||
|
return !params.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
|
||||||
|
// Underscore is a discard identifier and should not be resolvable
|
||||||
|
if (name.equals("_")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var prop = properties.get(name);
|
||||||
|
if (prop != null) {
|
||||||
|
return new VariableResolution.LexicalProperty(false, prop.modifiers, levelsUp);
|
||||||
|
}
|
||||||
|
var paramIndex = params.get(name);
|
||||||
|
if (paramIndex != null) {
|
||||||
|
// params are on a higher level than the properties
|
||||||
|
return new VariableResolution.Parameter(paramIndex, levelsUp + 1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
|
||||||
|
var method = methods.get(name);
|
||||||
|
if (method != null) {
|
||||||
|
return new LexicalMethod(true, false, false, method.modifiers, levelsUp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, Integer> collectParams(ObjectBody body) {
|
||||||
|
var params = new HashMap<String, Integer>();
|
||||||
|
for (var i = 0; i < body.getParameters().size(); i++) {
|
||||||
|
var param = body.getParameters().get(i);
|
||||||
|
if (param instanceof org.pkl.parser.syntax.Parameter.TypedIdentifier ti) {
|
||||||
|
params.put(ti.getIdentifier().getValue(), i);
|
||||||
|
} else {
|
||||||
|
params.put("_", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +541,7 @@ public final class SymbolTable {
|
|||||||
Identifier name,
|
Identifier name,
|
||||||
String qualifiedName,
|
String qualifiedName,
|
||||||
ConstLevel constLevel,
|
ConstLevel constLevel,
|
||||||
Builder frameDescriptorBuilder,
|
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||||
List<TypeParameter> typeParameters) {
|
List<TypeParameter> typeParameters) {
|
||||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
||||||
this.typeParameters = typeParameters;
|
this.typeParameters = typeParameters;
|
||||||
@@ -393,47 +557,112 @@ public final class SymbolTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final class ModuleScope extends Scope implements LexicalScope {
|
public static final class ModuleScope extends Scope implements LexicalScope {
|
||||||
|
|
||||||
private final ModuleInfo moduleInfo;
|
private final ModuleInfo moduleInfo;
|
||||||
|
@LateInit private boolean isClosed;
|
||||||
|
private final boolean isAmend;
|
||||||
|
|
||||||
public ModuleScope(ModuleInfo moduleInfo) {
|
public ModuleScope(ModuleInfo moduleInfo, boolean isBaseModule) {
|
||||||
super(null, null, moduleInfo.getModuleName(), ConstLevel.NONE, FrameDescriptor.newBuilder());
|
super(null, null, moduleInfo.getModuleName(), ConstLevel.NONE, new FrameDescriptorBuilder());
|
||||||
|
this.isBaseModule = isBaseModule;
|
||||||
this.moduleInfo = moduleInfo;
|
this.moduleInfo = moduleInfo;
|
||||||
|
this.isAmend = moduleInfo.isAmend();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiers(int modifiers) {
|
||||||
|
this.isClosed = VmModifier.isClosed(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
|
||||||
|
var member = properties.get(name);
|
||||||
|
if (member != null) {
|
||||||
|
return new LexicalProperty(true, member.modifiers, levelsUp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
|
||||||
|
var method = methods.get(name);
|
||||||
|
if (method == null) return null;
|
||||||
|
var isObjectMethod = isAmend && VmModifier.isLocal(method.modifiers);
|
||||||
|
return new LexicalMethod(isObjectMethod, isClosed, true, method.modifiers, levelsUp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class MethodScope extends TypeParameterizableScope {
|
public static final class MethodScope extends TypeParameterizableScope implements LexicalScope {
|
||||||
|
private final List<String> bindings;
|
||||||
|
|
||||||
public MethodScope(
|
public MethodScope(
|
||||||
Scope parent,
|
Scope parent,
|
||||||
Identifier name,
|
Identifier name,
|
||||||
String qualifiedName,
|
String qualifiedName,
|
||||||
ConstLevel constLevel,
|
ConstLevel constLevel,
|
||||||
Builder frameDescriptorBuilder,
|
List<String> bindings,
|
||||||
|
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||||
List<TypeParameter> typeParameters) {
|
List<TypeParameter> typeParameters) {
|
||||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder, typeParameters);
|
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder, typeParameters);
|
||||||
|
this.bindings = bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
|
||||||
|
return resolveParameter(name, bindings, levelsUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class LambdaScope extends Scope implements LexicalScope {
|
public static final class LambdaScope extends Scope implements LexicalScope {
|
||||||
|
private final List<String> bindings;
|
||||||
|
|
||||||
public LambdaScope(
|
public LambdaScope(
|
||||||
Scope parent, String qualifiedName, FrameDescriptor.Builder frameDescriptorBuilder) {
|
Scope parent,
|
||||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
List<String> bindings,
|
||||||
|
String qualifiedName,
|
||||||
|
FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
|
super(parent, null, qualifiedName, parent.getConstLevel(), frameDescriptorBuilder);
|
||||||
|
this.bindings = bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
|
||||||
|
return resolveParameter(name, bindings, levelsUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A generator scope that is resolved eagerly and one level above
|
||||||
|
public static final class EagerGeneratorScope extends Scope {
|
||||||
|
private static FrameDescriptorBuilder getFrameDescriptorBuilder(Scope parent) {
|
||||||
|
var grandParent = parent.parent;
|
||||||
|
assert grandParent != null;
|
||||||
|
return grandParent.frameDescriptorBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EagerGeneratorScope(Scope parent, String qualifiedName) {
|
||||||
|
super(parent, null, qualifiedName, ConstLevel.NONE, getFrameDescriptorBuilder(parent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ForGeneratorScope extends Scope implements LexicalScope {
|
public static final class ForGeneratorScope extends Scope implements LexicalScope {
|
||||||
private final FrameDescriptor.Builder memberDescriptorBuilder;
|
final List<String> params;
|
||||||
|
|
||||||
public ForGeneratorScope(
|
public ForGeneratorScope(
|
||||||
Scope parent,
|
Scope parent,
|
||||||
String qualifiedName,
|
String qualifiedName,
|
||||||
FrameDescriptor.Builder frameDescriptorBuilder,
|
List<String> params,
|
||||||
FrameDescriptor.Builder memberDescriptorBuilder) {
|
FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
||||||
this.memberDescriptorBuilder = memberDescriptorBuilder;
|
this.params = params;
|
||||||
}
|
|
||||||
|
|
||||||
public FrameDescriptor buildMemberDescriptor() {
|
|
||||||
return memberDescriptorBuilder.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -442,6 +671,23 @@ public final class SymbolTable {
|
|||||||
assert parent != null;
|
assert parent != null;
|
||||||
return parent.getNextEntryName(keyNode);
|
return parent.getNextEntryName(keyNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
|
||||||
|
if (!params.contains(name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var index = frameDescriptorBuilder.findSlot(Identifier.get(name));
|
||||||
|
if (index >= 0) {
|
||||||
|
return new VariableResolution.ForGeneratorVariable(index, levelsUp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class PropertyScope extends Scope {
|
public static final class PropertyScope extends Scope {
|
||||||
@@ -450,26 +696,46 @@ public final class SymbolTable {
|
|||||||
Identifier name,
|
Identifier name,
|
||||||
String qualifiedName,
|
String qualifiedName,
|
||||||
ConstLevel constLevel,
|
ConstLevel constLevel,
|
||||||
FrameDescriptor.Builder frameDescriptorBuilder) {
|
FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class EntryScope extends Scope {
|
public static final class EntryScope extends Scope {
|
||||||
public EntryScope(
|
public EntryScope(
|
||||||
Scope parent, String qualifiedName, FrameDescriptor.Builder frameDescriptorBuilder) {
|
Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ClassScope extends TypeParameterizableScope implements LexicalScope {
|
public static final class ClassScope extends TypeParameterizableScope implements LexicalScope {
|
||||||
|
private final boolean isClosed;
|
||||||
|
|
||||||
public ClassScope(
|
public ClassScope(
|
||||||
Scope parent,
|
Scope parent,
|
||||||
Identifier name,
|
Identifier name,
|
||||||
String qualifiedName,
|
String qualifiedName,
|
||||||
Builder frameDescriptorBuilder,
|
int modifiers,
|
||||||
|
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||||
List<TypeParameter> typeParameters) {
|
List<TypeParameter> typeParameters) {
|
||||||
super(parent, name, qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder, typeParameters);
|
super(parent, name, qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder, typeParameters);
|
||||||
|
isClosed = VmModifier.isClosed(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
|
||||||
|
|
||||||
|
var member = properties.get(name);
|
||||||
|
if (member == null) return null;
|
||||||
|
return new LexicalProperty(false, member.modifiers, levelsUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable MethodResolution doResolveMethod(String name, int levelsUp) {
|
||||||
|
|
||||||
|
var member = methods.get(name);
|
||||||
|
if (member == null) return null;
|
||||||
|
return new LexicalMethod(false, isClosed, false, member.modifiers, levelsUp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +744,7 @@ public final class SymbolTable {
|
|||||||
Scope parent,
|
Scope parent,
|
||||||
Identifier name,
|
Identifier name,
|
||||||
String qualifiedName,
|
String qualifiedName,
|
||||||
FrameDescriptor.Builder frameDescriptorBuilder,
|
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||||
List<TypeParameter> typeParameters) {
|
List<TypeParameter> typeParameters) {
|
||||||
super(parent, name, qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder, typeParameters);
|
super(parent, name, qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder, typeParameters);
|
||||||
}
|
}
|
||||||
@@ -499,7 +765,7 @@ public final class SymbolTable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public CustomThisScope(Scope parent, FrameDescriptor.Builder frameDescriptorBuilder) {
|
public CustomThisScope(Scope parent, FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
super(
|
super(
|
||||||
parent,
|
parent,
|
||||||
parent.getNameOrNull(),
|
parent.getNameOrNull(),
|
||||||
@@ -509,14 +775,23 @@ public final class SymbolTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class AnnotationScope extends Scope implements LexicalScope {
|
public static final class AnnotationScope extends Scope {
|
||||||
public AnnotationScope(Scope parent, FrameDescriptor.Builder frameDescriptorBuilder) {
|
public AnnotationScope(
|
||||||
|
Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||||
super(
|
super(
|
||||||
parent,
|
parent, parent.getNameOrNull(), qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder);
|
||||||
parent.getNameOrNull(),
|
|
||||||
parent.getQualifiedName(),
|
|
||||||
ConstLevel.MODULE,
|
|
||||||
frameDescriptorBuilder);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @Nullable VariableResolution resolveParameter(
|
||||||
|
String name, List<String> bindings, int levelsUp) {
|
||||||
|
if (name.equals("_")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var index = bindings.indexOf(name);
|
||||||
|
if (index != -1) {
|
||||||
|
return new VariableResolution.Parameter(index, levelsUp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2025-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.ast.builder;
|
||||||
|
|
||||||
|
import org.pkl.core.ast.VmModifier;
|
||||||
|
|
||||||
|
public sealed interface VariableResolution {
|
||||||
|
|
||||||
|
record LexicalProperty(boolean isModuleScope, int modifiers, int levelsUp)
|
||||||
|
implements VariableResolution {
|
||||||
|
|
||||||
|
public boolean isLocal() {
|
||||||
|
return VmModifier.isLocal(modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAmbiguousLocality() {
|
||||||
|
return VmModifier.isAmbiguousLocality(modifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let, lambda, object body param
|
||||||
|
record Parameter(int slot, int levelsUp) implements VariableResolution {}
|
||||||
|
|
||||||
|
record ForGeneratorVariable(int slot, int levelsUp) implements VariableResolution {}
|
||||||
|
|
||||||
|
// Implicit base module lookup
|
||||||
|
record ImplicitBaseProperty() implements VariableResolution {}
|
||||||
|
|
||||||
|
record ImplicitThisProperty() implements VariableResolution {}
|
||||||
|
}
|
||||||
+7
-9
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -33,14 +33,12 @@ public final class RestoreForBindingsNode extends ExpressionNode {
|
|||||||
@Override
|
@Override
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
public Object executeGeneric(VirtualFrame frame) {
|
||||||
var generatorFrame = ObjectData.getGeneratorFrame(frame);
|
var generatorFrame = ObjectData.getGeneratorFrame(frame);
|
||||||
var numSlots = frame.getFrameDescriptor().getNumberOfSlots();
|
// copying all slots includes function arguments, but the capture generator frame
|
||||||
// This value is constant and could be a constructor argument.
|
// and the host frame are guaranteed to have the same arguments and number of slots
|
||||||
var startSlot = generatorFrame.getFrameDescriptor().getNumberOfSlots() - numSlots;
|
// (guaranteed by AstBuilder).
|
||||||
assert startSlot >= 0;
|
assert frame.getFrameDescriptor().getNumberOfSlots()
|
||||||
// Copy locals that are for-generator variables into this frame.
|
== generatorFrame.getFrameDescriptor().getNumberOfSlots();
|
||||||
// Slots before `startSlot` (if any) are function arguments
|
VmUtils.copyLocals(generatorFrame, 0, frame, 0, frame.getFrameDescriptor().getNumberOfSlots());
|
||||||
// and must not be copied to preserve scoping rules.
|
|
||||||
VmUtils.copyLocals(generatorFrame, startSlot, frame, 0, numSlots);
|
|
||||||
return child.executeGeneric(frame);
|
return child.executeGeneric(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import org.pkl.core.ast.ExpressionNode;
|
|||||||
import org.pkl.core.ast.PklNode;
|
import org.pkl.core.ast.PklNode;
|
||||||
import org.pkl.core.ast.PklRootNode;
|
import org.pkl.core.ast.PklRootNode;
|
||||||
import org.pkl.core.ast.SimpleRootNode;
|
import org.pkl.core.ast.SimpleRootNode;
|
||||||
import org.pkl.core.ast.frame.ReadFrameSlotNodeGen;
|
import org.pkl.core.ast.frame.ReadExactFrameSlotNodeGen;
|
||||||
import org.pkl.core.ast.member.FunctionNode;
|
import org.pkl.core.ast.member.FunctionNode;
|
||||||
import org.pkl.core.ast.member.Lambda;
|
import org.pkl.core.ast.member.Lambda;
|
||||||
import org.pkl.core.ast.type.TypeNode;
|
import org.pkl.core.ast.type.TypeNode;
|
||||||
@@ -37,11 +37,13 @@ public final class AmendFunctionNode extends PklNode {
|
|||||||
private final boolean isCustomThisScope;
|
private final boolean isCustomThisScope;
|
||||||
private final PklRootNode initialFunctionRootNode;
|
private final PklRootNode initialFunctionRootNode;
|
||||||
@CompilationFinal private int customThisSlot = -1;
|
@CompilationFinal private int customThisSlot = -1;
|
||||||
|
private final boolean hasObjectParams;
|
||||||
|
|
||||||
public AmendFunctionNode(ObjectLiteralNode hostNode, TypeNode[] parameterTypeNodes) {
|
public AmendFunctionNode(ObjectLiteralNode hostNode, TypeNode[] parameterTypeNodes) {
|
||||||
super(hostNode.getSourceSection());
|
super(hostNode.getSourceSection());
|
||||||
|
|
||||||
isCustomThisScope = hostNode.isCustomThisScope;
|
isCustomThisScope = hostNode.isCustomThisScope;
|
||||||
|
hasObjectParams = parameterTypeNodes.length > 0;
|
||||||
|
|
||||||
var builder = FrameDescriptor.newBuilder();
|
var builder = FrameDescriptor.newBuilder();
|
||||||
var hostDescriptor = hostNode.parametersDescriptor;
|
var hostDescriptor = hostNode.parametersDescriptor;
|
||||||
@@ -68,7 +70,7 @@ public final class AmendFunctionNode extends PklNode {
|
|||||||
new AmendFunctionBodyNode(
|
new AmendFunctionBodyNode(
|
||||||
sourceSection,
|
sourceSection,
|
||||||
hostNode.copy(
|
hostNode.copy(
|
||||||
ReadFrameSlotNodeGen.create(
|
ReadExactFrameSlotNodeGen.create(
|
||||||
hostNode.getParentNode().getSourceSection(), objectToAmendSlot)),
|
hostNode.getParentNode().getSourceSection(), objectToAmendSlot)),
|
||||||
parameterSlots,
|
parameterSlots,
|
||||||
objectToAmendSlot,
|
objectToAmendSlot,
|
||||||
@@ -88,7 +90,7 @@ public final class AmendFunctionNode extends PklNode {
|
|||||||
new AmendFunctionBodyNode(
|
new AmendFunctionBodyNode(
|
||||||
sourceSection,
|
sourceSection,
|
||||||
hostNode.copy(
|
hostNode.copy(
|
||||||
ReadFrameSlotNodeGen.create(
|
ReadExactFrameSlotNodeGen.create(
|
||||||
hostNode.getParentNode().getSourceSection(), objectToAmendSlot)),
|
hostNode.getParentNode().getSourceSection(), objectToAmendSlot)),
|
||||||
parameterSlots,
|
parameterSlots,
|
||||||
objectToAmendSlot,
|
objectToAmendSlot,
|
||||||
@@ -108,7 +110,9 @@ public final class AmendFunctionNode extends PklNode {
|
|||||||
isCustomThisScope ? frame.getAuxiliarySlot(customThisSlot) : VmUtils.getReceiver(frame),
|
isCustomThisScope ? frame.getAuxiliarySlot(customThisSlot) : VmUtils.getReceiver(frame),
|
||||||
functionToAmend.getParameterCount(),
|
functionToAmend.getParameterCount(),
|
||||||
initialFunctionRootNode,
|
initialFunctionRootNode,
|
||||||
new Context(functionToAmend, null));
|
new Context(functionToAmend, null),
|
||||||
|
true,
|
||||||
|
hasObjectParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AmendFunctionBodyNode extends ExpressionNode {
|
private static class AmendFunctionBodyNode extends ExpressionNode {
|
||||||
|
|||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.ast.expression.member;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.CallTarget;
|
||||||
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
|
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.nodes.DirectCallNode;
|
||||||
|
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||||
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
|
import org.pkl.core.runtime.Identifier;
|
||||||
|
import org.pkl.core.runtime.VmObjectLike;
|
||||||
|
import org.pkl.core.runtime.VmUtils;
|
||||||
|
|
||||||
|
public abstract sealed class AbstractInvokeMethodLexicalNode extends ExpressionNode
|
||||||
|
permits InvokeObjectMethodNode, InvokeClassMethodNode {
|
||||||
|
|
||||||
|
protected final Identifier methodName;
|
||||||
|
protected final int levelsUp;
|
||||||
|
private final boolean needsConst;
|
||||||
|
@Children private ExpressionNode[] argumentNodes;
|
||||||
|
@Child private DirectCallNode callNode;
|
||||||
|
@CompilationFinal protected boolean isConstChecked;
|
||||||
|
|
||||||
|
protected AbstractInvokeMethodLexicalNode(
|
||||||
|
SourceSection sourceSection,
|
||||||
|
Identifier methodName,
|
||||||
|
int levelsUp,
|
||||||
|
ExpressionNode[] argumentNodes,
|
||||||
|
boolean needsConst) {
|
||||||
|
super(sourceSection);
|
||||||
|
this.methodName = methodName;
|
||||||
|
this.levelsUp = levelsUp;
|
||||||
|
this.argumentNodes = argumentNodes;
|
||||||
|
this.needsConst = needsConst;
|
||||||
|
this.isConstChecked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@ExplodeLoop
|
||||||
|
public final Object executeGeneric(VirtualFrame frame) {
|
||||||
|
var args = new Object[2 + argumentNodes.length];
|
||||||
|
var capturedFrame = VmUtils.getFrame(frame, levelsUp);
|
||||||
|
var owner = VmUtils.getOwner(capturedFrame);
|
||||||
|
var receiver = VmUtils.getReceiver(capturedFrame);
|
||||||
|
checkConst(owner);
|
||||||
|
args[0] = receiver;
|
||||||
|
args[1] = owner;
|
||||||
|
for (var i = 0; i < argumentNodes.length; i++) {
|
||||||
|
args[2 + i] = argumentNodes[i].executeGeneric(frame);
|
||||||
|
}
|
||||||
|
return getCallNode(owner).call(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkConst(VmObjectLike owner) {
|
||||||
|
if (!needsConst || isConstChecked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
doCheckConst(owner);
|
||||||
|
isConstChecked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract CallTarget getCallTarget(VmObjectLike owner);
|
||||||
|
|
||||||
|
protected abstract void doCheckConst(VmObjectLike owner);
|
||||||
|
|
||||||
|
protected final VmObjectLike getOwner(VirtualFrame frame) {
|
||||||
|
return VmUtils.getOwner(frame, levelsUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DirectCallNode getCallNode(VmObjectLike owner) {
|
||||||
|
if (callNode == null) {
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
callNode = DirectCallNode.create(getCallTarget(owner));
|
||||||
|
insert(callNode);
|
||||||
|
}
|
||||||
|
return callNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024-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.ast.expression.member;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.CallTarget;
|
||||||
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
|
import org.pkl.core.runtime.Identifier;
|
||||||
|
import org.pkl.core.runtime.VmObjectLike;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A non-virtual call of closed methods (methods whose enclosing class/module is not open nor
|
||||||
|
* abstract, and is lexically scoped).
|
||||||
|
*
|
||||||
|
* <p>For local methods, use {@link InvokeObjectMethodNode}.
|
||||||
|
*/
|
||||||
|
public final class InvokeClassMethodNode extends AbstractInvokeMethodLexicalNode {
|
||||||
|
public InvokeClassMethodNode(
|
||||||
|
SourceSection sourceSection,
|
||||||
|
Identifier methodName,
|
||||||
|
int levelsUp,
|
||||||
|
ExpressionNode[] argumentNodes,
|
||||||
|
boolean needsConst) {
|
||||||
|
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doCheckConst(VmObjectLike owner) {
|
||||||
|
var method = owner.getVmClass().getDeclaredMethod(methodName);
|
||||||
|
assert method != null;
|
||||||
|
if (!method.isConst()) {
|
||||||
|
throw exceptionBuilder().evalError("methodMustBeConst", methodName).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CallTarget getCallTarget(VmObjectLike owner) {
|
||||||
|
var method = owner.getVmClass().getDeclaredMethod(methodName);
|
||||||
|
assert method != null;
|
||||||
|
return method.getCallTarget(getSourceSection());
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -23,7 +23,7 @@ import org.pkl.core.ast.ExpressionNode;
|
|||||||
import org.pkl.core.ast.member.ClassMethod;
|
import org.pkl.core.ast.member.ClassMethod;
|
||||||
import org.pkl.core.runtime.VmObjectLike;
|
import org.pkl.core.runtime.VmObjectLike;
|
||||||
|
|
||||||
/** A non-virtual ("direct") method call. */
|
/** A non-virtual ("direct") method call. Used only for methods on {@code pkl:base}. */
|
||||||
public final class InvokeMethodDirectNode extends ExpressionNode {
|
public final class InvokeMethodDirectNode extends ExpressionNode {
|
||||||
private final VmObjectLike owner;
|
private final VmObjectLike owner;
|
||||||
@Child private ExpressionNode receiverNode;
|
@Child private ExpressionNode receiverNode;
|
||||||
|
|||||||
-75
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2024-2025 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.ast.expression.member;
|
|
||||||
|
|
||||||
import com.oracle.truffle.api.CallTarget;
|
|
||||||
import com.oracle.truffle.api.frame.Frame;
|
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
|
||||||
import com.oracle.truffle.api.nodes.DirectCallNode;
|
|
||||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
|
||||||
import org.pkl.core.runtime.VmUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A non-virtual method call whose call target is in the lexical scope of the call site. Mainly used
|
|
||||||
* for calling `local` methods.
|
|
||||||
*/
|
|
||||||
public final class InvokeMethodLexicalNode extends ExpressionNode {
|
|
||||||
@Children private final ExpressionNode[] argumentNodes;
|
|
||||||
private final int levelsUp;
|
|
||||||
|
|
||||||
@Child private DirectCallNode callNode;
|
|
||||||
|
|
||||||
InvokeMethodLexicalNode(
|
|
||||||
SourceSection sourceSection,
|
|
||||||
CallTarget callTarget,
|
|
||||||
int levelsUp,
|
|
||||||
ExpressionNode[] argumentNodes) {
|
|
||||||
|
|
||||||
super(sourceSection);
|
|
||||||
this.levelsUp = levelsUp;
|
|
||||||
this.argumentNodes = argumentNodes;
|
|
||||||
|
|
||||||
callNode = DirectCallNode.create(callTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@ExplodeLoop
|
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
|
||||||
var args = new Object[2 + argumentNodes.length];
|
|
||||||
var enclosingFrame = getEnclosingFrame(frame);
|
|
||||||
args[0] = VmUtils.getReceiver(enclosingFrame);
|
|
||||||
args[1] = VmUtils.getOwner(enclosingFrame);
|
|
||||||
for (var i = 0; i < argumentNodes.length; i++) {
|
|
||||||
args[2 + i] = argumentNodes[i].executeGeneric(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callNode.call(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExplodeLoop
|
|
||||||
private Frame getEnclosingFrame(VirtualFrame frame) {
|
|
||||||
if (levelsUp == 0) return frame;
|
|
||||||
|
|
||||||
var owner = VmUtils.getOwner(frame);
|
|
||||||
for (var i = 1; i < levelsUp; i++) {
|
|
||||||
owner = owner.getEnclosingOwner();
|
|
||||||
assert owner != null;
|
|
||||||
}
|
|
||||||
return owner.getEnclosingFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.ast.expression.member;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.CallTarget;
|
||||||
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
|
import org.pkl.core.ast.VmModifier;
|
||||||
|
import org.pkl.core.runtime.Identifier;
|
||||||
|
import org.pkl.core.runtime.VmObjectLike;
|
||||||
|
|
||||||
|
/** A non-virtual call of a local method. */
|
||||||
|
public final class InvokeObjectMethodNode extends AbstractInvokeMethodLexicalNode {
|
||||||
|
public InvokeObjectMethodNode(
|
||||||
|
SourceSection sourceSection,
|
||||||
|
Identifier methodName,
|
||||||
|
int levelsUp,
|
||||||
|
ExpressionNode[] argumentNodes,
|
||||||
|
boolean needsConst) {
|
||||||
|
super(sourceSection, methodName, levelsUp, argumentNodes, needsConst);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void doCheckConst(VmObjectLike owner) {
|
||||||
|
var member = owner.getMember(methodName);
|
||||||
|
assert member != null;
|
||||||
|
if (!VmModifier.isConst(member.getModifiers())) {
|
||||||
|
throw exceptionBuilder().evalError("methodMustBeConst", methodName).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CallTarget getCallTarget(VmObjectLike owner) {
|
||||||
|
var method = owner.getMember(methodName);
|
||||||
|
assert method != null && method.isLocal();
|
||||||
|
return (CallTarget) method.getCallTarget().call(owner, owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* 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.ast.expression.member;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
|
import org.pkl.core.ast.MemberLookupMode;
|
||||||
|
import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode;
|
||||||
|
import org.pkl.core.ast.expression.primary.GetReceiverNode;
|
||||||
|
import org.pkl.core.runtime.Identifier;
|
||||||
|
import org.pkl.core.runtime.VmUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads either a local, or a non-local property.
|
||||||
|
*
|
||||||
|
* <p>Sometimes, it is not possible to determine whether a property is local or not at parse time.
|
||||||
|
* This happens for members declared inside generator bodies. For example:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* foo {
|
||||||
|
* when (cond) {
|
||||||
|
* local res = 1
|
||||||
|
* } else {
|
||||||
|
* res = 2
|
||||||
|
* }
|
||||||
|
* // Depending on how the when generator is executed, `res` is either a local property,
|
||||||
|
* // or a non-local
|
||||||
|
* bar = res
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public final class ReadAmbiguousLocalityPropertyNode extends ExpressionNode {
|
||||||
|
|
||||||
|
private final Identifier name;
|
||||||
|
private final int levelsUp;
|
||||||
|
private final boolean needsConst;
|
||||||
|
private @Child ExpressionNode readLocalPropertyNode;
|
||||||
|
private @Child ExpressionNode readPropertyNode;
|
||||||
|
|
||||||
|
public ReadAmbiguousLocalityPropertyNode(
|
||||||
|
SourceSection sourceSection, Identifier name, int levelsUp, boolean needsConst) {
|
||||||
|
super(sourceSection);
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
this.levelsUp = levelsUp;
|
||||||
|
this.needsConst = needsConst;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExpressionNode getReadLocalPropertyNode() {
|
||||||
|
if (readLocalPropertyNode == null) {
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
readLocalPropertyNode =
|
||||||
|
insert(
|
||||||
|
new ReadLocalPropertyNode(
|
||||||
|
sourceSection, name.toLocalProperty(), levelsUp, needsConst));
|
||||||
|
}
|
||||||
|
return readLocalPropertyNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExpressionNode getReadPropertyNode() {
|
||||||
|
if (readPropertyNode == null) {
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
readPropertyNode =
|
||||||
|
insert(
|
||||||
|
ReadPropertyNodeGen.create(
|
||||||
|
sourceSection,
|
||||||
|
name,
|
||||||
|
MemberLookupMode.IMPLICIT_LEXICAL,
|
||||||
|
needsConst,
|
||||||
|
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp)));
|
||||||
|
}
|
||||||
|
return readPropertyNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExpressionNode getUnderlying(VirtualFrame frame) {
|
||||||
|
var receiver = VmUtils.getObjectReceiver(frame, levelsUp);
|
||||||
|
return receiver.hasMember(name) ? getReadPropertyNode() : getReadLocalPropertyNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: `replace()` is actually incorrect (we can't know that the next eval of this node will
|
||||||
|
// continue resolving to the same node).
|
||||||
|
// However, Pkl <= 0.31 works this way (ResolveVariableNode always calls `replace()`).
|
||||||
|
// Also, it's likely that a future version of Pkl will _not_ have generator members be visible
|
||||||
|
// to the enclosing object body.
|
||||||
|
// If that scoping rule is implemented, `ReadAmbiguousLocalityPropertyNode` will go away entirely.
|
||||||
|
// See pkl-core/src/test/files/LanguageSnippetTests/input/basic/localProperty5.pkl
|
||||||
|
@Override
|
||||||
|
public Object executeGeneric(VirtualFrame frame) {
|
||||||
|
return replace(getUnderlying(frame)).executeGeneric(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
-29
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,64 +16,81 @@
|
|||||||
package org.pkl.core.ast.expression.member;
|
package org.pkl.core.ast.expression.member;
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerAsserts;
|
import com.oracle.truffle.api.CompilerAsserts;
|
||||||
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
|
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.nodes.DirectCallNode;
|
import com.oracle.truffle.api.nodes.DirectCallNode;
|
||||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
|
import org.pkl.core.PklBugException;
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.ast.member.ObjectMember;
|
import org.pkl.core.ast.member.ObjectMember;
|
||||||
|
import org.pkl.core.runtime.Identifier;
|
||||||
import org.pkl.core.runtime.VmObjectLike;
|
import org.pkl.core.runtime.VmObjectLike;
|
||||||
import org.pkl.core.runtime.VmUtils;
|
import org.pkl.core.runtime.VmUtils;
|
||||||
|
|
||||||
/** Reads a local non-constant property that is known to exist in the lexical scope of this node. */
|
/** Reads a local non-constant property that is known to exist in the lexical scope of this node. */
|
||||||
public final class ReadLocalPropertyNode extends ExpressionNode {
|
public final class ReadLocalPropertyNode extends ExpressionNode {
|
||||||
private final ObjectMember property;
|
private final Identifier name;
|
||||||
private final int levelsUp;
|
private final int levelsUp;
|
||||||
|
private final boolean needsConst;
|
||||||
@Child private DirectCallNode callNode;
|
@Child private DirectCallNode callNode;
|
||||||
|
@CompilationFinal private ObjectMember property;
|
||||||
|
|
||||||
public ReadLocalPropertyNode(SourceSection sourceSection, ObjectMember property, int levelsUp) {
|
public ReadLocalPropertyNode(
|
||||||
|
SourceSection sourceSection, Identifier name, int levelsUp, boolean needsConst) {
|
||||||
|
|
||||||
super(sourceSection);
|
super(sourceSection);
|
||||||
CompilerAsserts.neverPartOfCompilation();
|
CompilerAsserts.neverPartOfCompilation();
|
||||||
|
|
||||||
this.property = property;
|
this.name = name;
|
||||||
this.levelsUp = levelsUp;
|
this.levelsUp = levelsUp;
|
||||||
|
this.needsConst = needsConst;
|
||||||
assert property.getNameOrNull() != null;
|
|
||||||
assert property.getConstantValue() == null : "Use a ConstantNode instead.";
|
|
||||||
|
|
||||||
callNode = DirectCallNode.create(property.getCallTarget());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ExplodeLoop
|
@ExplodeLoop
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
public Object executeGeneric(VirtualFrame frame) {
|
||||||
var owner = VmUtils.getOwner(frame);
|
var owner = VmUtils.getOwner(frame, levelsUp);
|
||||||
Object receiver;
|
var property = getProperty(owner);
|
||||||
|
var constantValue = property.getConstantValue();
|
||||||
if (levelsUp == 0) {
|
if (constantValue != null) {
|
||||||
receiver = VmUtils.getReceiver(frame);
|
return constantValue;
|
||||||
} else {
|
|
||||||
for (int i = 1; i < levelsUp; i++) {
|
|
||||||
owner = owner.getEnclosingOwner();
|
|
||||||
assert owner != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver = owner.getEnclosingReceiver();
|
var receiver = (VmObjectLike) VmUtils.getReceiver(frame, levelsUp);
|
||||||
owner = owner.getEnclosingOwner();
|
var result = receiver.getCachedValue(property);
|
||||||
}
|
|
||||||
|
|
||||||
assert receiver instanceof VmObjectLike
|
|
||||||
: "Assumption: This node isn't used in Truffle ASTs of `external` pkl.base classes whose values aren't VmObject's.";
|
|
||||||
|
|
||||||
var objReceiver = (VmObjectLike) receiver;
|
|
||||||
var result = objReceiver.getCachedValue(property);
|
|
||||||
|
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
result = callNode.call(objReceiver, owner, property.getName());
|
result = getCallNode(property).call(receiver, owner, property.getName());
|
||||||
objReceiver.setCachedValue(property, result);
|
receiver.setCachedValue(property, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ObjectMember getProperty(VmObjectLike owner) {
|
||||||
|
if (property == null) {
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
property = owner.getMember(name);
|
||||||
|
if (property == null) {
|
||||||
|
// should never happen
|
||||||
|
CompilerDirectives.transferToInterpreter();
|
||||||
|
throw new PklBugException("Couldn't find local variable `" + name + "`.");
|
||||||
|
}
|
||||||
|
if (needsConst && !property.isConst()) {
|
||||||
|
throw exceptionBuilder().evalError("propertyMustBeConst", name.toString()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectCallNode getCallNode(ObjectMember property) {
|
||||||
|
if (callNode == null) {
|
||||||
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
|
callNode = DirectCallNode.create(property.getCallTarget());
|
||||||
|
insert(callNode);
|
||||||
|
}
|
||||||
|
return callNode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,20 +118,27 @@ public abstract class ReadPropertyNode extends ExpressionNode {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// only ever need to check once per node because `needsConst` is only true in the case of implicit
|
// Only ever need to check once per node because `needsConst` is only true in the case of:
|
||||||
// receivers inside class (and module) bodies, and the const-ness of a resolved property cannot be
|
//
|
||||||
// changed by subclasses.
|
// * implicit receiver in class (and module) bodies
|
||||||
|
// * `local const` object members
|
||||||
|
//
|
||||||
|
// and the const-ness of a resolved property cannot be changed by subclasses / amending objects.
|
||||||
private void checkConst(VmObjectLike receiver) {
|
private void checkConst(VmObjectLike receiver) {
|
||||||
if (needsConst && !isConstChecked) {
|
if (needsConst && !isConstChecked) {
|
||||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
var property = receiver.getVmClass().getProperty(propertyName);
|
var property = receiver.getVmClass().getProperty(propertyName);
|
||||||
if (property == null) {
|
if (property != null && !property.isConst()) {
|
||||||
|
throw exceptionBuilder().evalError("propertyMustBeConst", propertyName.toString()).build();
|
||||||
|
}
|
||||||
|
var objectMember = receiver.getMember(propertyName);
|
||||||
|
if (objectMember != null && !objectMember.isConst()) {
|
||||||
|
throw exceptionBuilder().evalError("propertyMustBeConst", propertyName.toString()).build();
|
||||||
|
}
|
||||||
|
if (property == null && objectMember == null) {
|
||||||
// fall through; `cannotFindProperty` gets thrown when we attempt to read the property.
|
// fall through; `cannotFindProperty` gets thrown when we attempt to read the property.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!property.isConst()) {
|
|
||||||
throw exceptionBuilder().evalError("propertyMustBeConst", propertyName.toString()).build();
|
|
||||||
}
|
|
||||||
isConstChecked = true;
|
isConstChecked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2024-2025 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.ast.expression.member;
|
|
||||||
|
|
||||||
import com.oracle.truffle.api.CallTarget;
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
|
||||||
import com.oracle.truffle.api.nodes.NodeInfo;
|
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
|
||||||
import org.pkl.core.ast.ConstantValueNode;
|
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
|
||||||
import org.pkl.core.ast.MemberLookupMode;
|
|
||||||
import org.pkl.core.ast.builder.ConstLevel;
|
|
||||||
import org.pkl.core.ast.expression.primary.*;
|
|
||||||
import org.pkl.core.ast.internal.GetClassNodeGen;
|
|
||||||
import org.pkl.core.ast.member.Member;
|
|
||||||
import org.pkl.core.runtime.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a method name in a method call with implicit receiver, for example `bar` in `x = bar()`
|
|
||||||
* (but not `foo.bar()`).
|
|
||||||
*
|
|
||||||
* <p>A method name can refer to any of the following: - a (potentially `local`) method in the
|
|
||||||
* lexical scope - a base module method - a method accessible through `this`
|
|
||||||
*
|
|
||||||
* <p>This node's task is to make a one-time decision between these alternatives for the call site
|
|
||||||
* it represents.
|
|
||||||
*/
|
|
||||||
// TODO: Consider doing this at parse time (cf. ResolveVariableNode).
|
|
||||||
@NodeInfo(shortName = "resolveMethod")
|
|
||||||
public final class ResolveMethodNode extends ExpressionNode {
|
|
||||||
private final Identifier methodName;
|
|
||||||
private final ExpressionNode[] argumentNodes;
|
|
||||||
// Tells if the call site is inside the base module.
|
|
||||||
private final boolean isBaseModule;
|
|
||||||
// Tells if the call site is inside a [CustomThisScope].
|
|
||||||
private final boolean isCustomThisScope;
|
|
||||||
private final ConstLevel constLevel;
|
|
||||||
private final int constDepth;
|
|
||||||
|
|
||||||
public ResolveMethodNode(
|
|
||||||
SourceSection sourceSection,
|
|
||||||
Identifier methodName,
|
|
||||||
ExpressionNode[] argumentNodes,
|
|
||||||
boolean isBaseModule,
|
|
||||||
boolean isCustomThisScope,
|
|
||||||
ConstLevel constLevel,
|
|
||||||
int constDepth) {
|
|
||||||
|
|
||||||
super(sourceSection);
|
|
||||||
|
|
||||||
this.methodName = methodName;
|
|
||||||
this.argumentNodes = argumentNodes;
|
|
||||||
this.isBaseModule = isBaseModule;
|
|
||||||
this.isCustomThisScope = isCustomThisScope;
|
|
||||||
this.constLevel = constLevel;
|
|
||||||
this.constDepth = constDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
|
||||||
return replace(doResolve(VmUtils.getOwner(frame))).executeGeneric(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
@TruffleBoundary
|
|
||||||
private ExpressionNode doResolve(VmObjectLike initialOwner) {
|
|
||||||
var levelsUp = 0;
|
|
||||||
Identifier localMethodName = methodName.toLocalMethod();
|
|
||||||
|
|
||||||
// Search lexical scope.
|
|
||||||
for (var currOwner = initialOwner;
|
|
||||||
currOwner != null;
|
|
||||||
currOwner = currOwner.getEnclosingOwner()) {
|
|
||||||
|
|
||||||
if (currOwner.isPrototype()) {
|
|
||||||
var localMethod = currOwner.getVmClass().getDeclaredMethod(localMethodName);
|
|
||||||
if (localMethod != null) {
|
|
||||||
assert localMethod.isLocal();
|
|
||||||
checkConst(currOwner, localMethod, levelsUp);
|
|
||||||
return new InvokeMethodLexicalNode(
|
|
||||||
sourceSection, localMethod.getCallTarget(sourceSection), levelsUp, argumentNodes);
|
|
||||||
}
|
|
||||||
var method = currOwner.getVmClass().getDeclaredMethod(methodName);
|
|
||||||
if (method != null) {
|
|
||||||
assert !method.isLocal();
|
|
||||||
checkConst(currOwner, method, levelsUp);
|
|
||||||
if (method.getDeclaringClass().isClosed()) {
|
|
||||||
return new InvokeMethodLexicalNode(
|
|
||||||
sourceSection, method.getCallTarget(sourceSection), levelsUp, argumentNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return InvokeMethodVirtualNodeGen.create(
|
|
||||||
sourceSection,
|
|
||||||
methodName,
|
|
||||||
argumentNodes,
|
|
||||||
MemberLookupMode.IMPLICIT_LEXICAL,
|
|
||||||
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp),
|
|
||||||
GetClassNodeGen.create(null));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var localMethod = currOwner.getMember(localMethodName);
|
|
||||||
if (localMethod != null) {
|
|
||||||
assert localMethod.isLocal();
|
|
||||||
checkConst(currOwner, localMethod, levelsUp);
|
|
||||||
var methodCallTarget =
|
|
||||||
// TODO: is it OK to pass owner as receiver here?
|
|
||||||
// (calls LocalMethodNode, which only resolves types)
|
|
||||||
(CallTarget) localMethod.getCallTarget().call(currOwner, currOwner);
|
|
||||||
|
|
||||||
return new InvokeMethodLexicalNode(
|
|
||||||
sourceSection, methodCallTarget, levelsUp, argumentNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
levelsUp += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search base module (unless call site is itself inside base module).
|
|
||||||
if (!isBaseModule) {
|
|
||||||
var baseModule = BaseModule.getModule();
|
|
||||||
// use `getDeclaredMethod()` so as not to resolve to anything declared in class
|
|
||||||
// pkl.base#Module
|
|
||||||
var method = baseModule.getVmClass().getDeclaredMethod(methodName);
|
|
||||||
if (method != null) {
|
|
||||||
assert !method.isLocal();
|
|
||||||
return new InvokeMethodDirectNode(
|
|
||||||
sourceSection, method, new ConstantValueNode(baseModule), argumentNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assuming this method exists at all, it must be a method accessible through `this`.
|
|
||||||
//
|
|
||||||
// Calling a method off implicit `this` needs a const check if the node is not in a const scope
|
|
||||||
// (see ResolveVariableNode for an explanation)
|
|
||||||
//
|
|
||||||
// Edge case: always allow method calls for custom `this` scopes (member predicates, type
|
|
||||||
// constraints)
|
|
||||||
// because they do not refer to a lexical `this`.
|
|
||||||
boolean needsConst = constLevel == ConstLevel.ALL && constDepth == -1 && !isCustomThisScope;
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return InvokeMethodVirtualNodeGen.create(
|
|
||||||
sourceSection,
|
|
||||||
methodName,
|
|
||||||
argumentNodes,
|
|
||||||
MemberLookupMode.IMPLICIT_THIS,
|
|
||||||
needsConst,
|
|
||||||
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThisScope),
|
|
||||||
GetClassNodeGen.create(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("DuplicatedCode")
|
|
||||||
private void checkConst(VmObjectLike currOwner, Member method, int levelsUp) {
|
|
||||||
if (!constLevel.isConst()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var memberIsOutsideConstScope = levelsUp > constDepth;
|
|
||||||
var invalid =
|
|
||||||
switch (constLevel) {
|
|
||||||
case ALL -> memberIsOutsideConstScope && !method.isConst();
|
|
||||||
case MODULE -> currOwner.isModuleObject() && !method.isConst();
|
|
||||||
default -> false;
|
|
||||||
};
|
|
||||||
if (invalid) {
|
|
||||||
throw exceptionBuilder().evalError("methodMustBeConst", methodName.toString()).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-11
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
package org.pkl.core.ast.expression.primary;
|
package org.pkl.core.ast.expression.primary;
|
||||||
|
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.runtime.VmUtils;
|
import org.pkl.core.runtime.VmUtils;
|
||||||
|
|
||||||
@@ -29,15 +28,7 @@ public final class GetEnclosingOwnerNode extends ExpressionNode {
|
|||||||
assert levelsUp > 0 : "shouldn't be using GetEnclosingOwnerNode for levelsUp == 0";
|
assert levelsUp > 0 : "shouldn't be using GetEnclosingOwnerNode for levelsUp == 0";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExplodeLoop
|
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
public Object executeGeneric(VirtualFrame frame) {
|
||||||
var owner = VmUtils.getOwner(frame);
|
return VmUtils.getOwner(frame, levelsUp);
|
||||||
for (var i = 1; i < levelsUp; i++) {
|
|
||||||
owner = owner.getEnclosingOwner();
|
|
||||||
assert owner != null;
|
|
||||||
}
|
|
||||||
var result = owner.getEnclosingOwner();
|
|
||||||
assert result != null;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-11
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
package org.pkl.core.ast.expression.primary;
|
package org.pkl.core.ast.expression.primary;
|
||||||
|
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.runtime.VmUtils;
|
import org.pkl.core.runtime.VmUtils;
|
||||||
|
|
||||||
@@ -29,15 +28,7 @@ public final class GetEnclosingReceiverNode extends ExpressionNode {
|
|||||||
assert levelsUp > 0 : "shouldn't be using GetEnclosingReceiverNode for levelsUp == 0";
|
assert levelsUp > 0 : "shouldn't be using GetEnclosingReceiverNode for levelsUp == 0";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExplodeLoop
|
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
public Object executeGeneric(VirtualFrame frame) {
|
||||||
var owner = VmUtils.getOwner(frame);
|
return VmUtils.getReceiver(frame, levelsUp);
|
||||||
for (var i = 1; i < levelsUp; i++) {
|
|
||||||
owner = owner.getEnclosingOwner();
|
|
||||||
assert owner != null;
|
|
||||||
}
|
|
||||||
var result = owner.getEnclosingReceiver();
|
|
||||||
assert result != null;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -24,6 +24,7 @@ import org.pkl.core.runtime.VmUtils;
|
|||||||
|
|
||||||
@NodeInfo(shortName = "module")
|
@NodeInfo(shortName = "module")
|
||||||
public final class GetModuleNode extends ExpressionNode {
|
public final class GetModuleNode extends ExpressionNode {
|
||||||
|
|
||||||
public GetModuleNode(SourceSection sourceSection) {
|
public GetModuleNode(SourceSection sourceSection) {
|
||||||
super(sourceSection);
|
super(sourceSection);
|
||||||
}
|
}
|
||||||
@@ -35,8 +36,10 @@ public final class GetModuleNode extends ExpressionNode {
|
|||||||
for (var current = VmUtils.getOwner(frame).getEnclosingOwner();
|
for (var current = VmUtils.getOwner(frame).getEnclosingOwner();
|
||||||
current != null;
|
current != null;
|
||||||
current = current.getEnclosingOwner()) {
|
current = current.getEnclosingOwner()) {
|
||||||
|
if (!current.isParseTimeInvisibleScope()) {
|
||||||
levelsUp += 1;
|
levelsUp += 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return replace(levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp))
|
return replace(levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp))
|
||||||
.executeGeneric(frame);
|
.executeGeneric(frame);
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 2024-2025 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.ast.expression.primary;
|
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives;
|
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
|
||||||
import org.pkl.core.ast.ConstantValueNode;
|
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
|
||||||
import org.pkl.core.ast.MemberLookupMode;
|
|
||||||
import org.pkl.core.ast.builder.ConstLevel;
|
|
||||||
import org.pkl.core.ast.expression.member.ReadLocalPropertyNode;
|
|
||||||
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
|
|
||||||
import org.pkl.core.ast.frame.ReadEnclosingFrameSlotNodeGen;
|
|
||||||
import org.pkl.core.ast.frame.ReadFrameSlotNodeGen;
|
|
||||||
import org.pkl.core.ast.member.Member;
|
|
||||||
import org.pkl.core.runtime.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a variable name, for example `foo` in `x = foo`.
|
|
||||||
*
|
|
||||||
* <p>A variable name can refer to any of the following:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>a method/lambda parameter or for-generator/let-expression variable in the lexical scope
|
|
||||||
* <li>a (potentially `local`) property in the lexical scope
|
|
||||||
* <li>a `pkl.base` module property
|
|
||||||
* <li>a property accessible through `this`
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>This node's task is to make a one-time decision between these alternatives for the call site
|
|
||||||
* it represents.
|
|
||||||
*/
|
|
||||||
// TODO: Move this to parse time
|
|
||||||
// * more capable because more information is available
|
|
||||||
// and AST customization beyond replacing this node is possible
|
|
||||||
// * useful for runtime AST transformations, for example to implement property-based testing
|
|
||||||
// * more efficient
|
|
||||||
//
|
|
||||||
// TODO: In REPL, undo replace if environment changes to make the following work.
|
|
||||||
// Perhaps instrumenting this node in REPL would be a good solution.
|
|
||||||
// x = { y = z }
|
|
||||||
// :force x // Property not found: z
|
|
||||||
// z = 1
|
|
||||||
// :force x // should work but doesn't
|
|
||||||
public final class ResolveVariableNode extends ExpressionNode {
|
|
||||||
private final Identifier variableName;
|
|
||||||
private final boolean isBaseModule;
|
|
||||||
private final boolean isCustomThisScope;
|
|
||||||
private final ConstLevel constLevel;
|
|
||||||
private final int constDepth;
|
|
||||||
|
|
||||||
public ResolveVariableNode(
|
|
||||||
SourceSection sourceSection,
|
|
||||||
Identifier variableName,
|
|
||||||
boolean isBaseModule,
|
|
||||||
boolean isCustomThisScope,
|
|
||||||
ConstLevel constLevel,
|
|
||||||
int constDepth) {
|
|
||||||
super(sourceSection);
|
|
||||||
this.variableName = variableName;
|
|
||||||
this.isBaseModule = isBaseModule;
|
|
||||||
this.isCustomThisScope = isCustomThisScope;
|
|
||||||
this.constLevel = constLevel;
|
|
||||||
this.constDepth = constDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object executeGeneric(VirtualFrame frame) {
|
|
||||||
return replace(doResolve(frame)).executeGeneric(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionNode doResolve(VirtualFrame frame) {
|
|
||||||
// don't compile this (only runs once)
|
|
||||||
// invalidation will be done by Node.replace() in the caller
|
|
||||||
CompilerDirectives.transferToInterpreter();
|
|
||||||
|
|
||||||
var localPropertyName = variableName.toLocalProperty();
|
|
||||||
var currFrame = frame;
|
|
||||||
var currOwner = VmUtils.getOwner(currFrame);
|
|
||||||
var levelsUp = 0;
|
|
||||||
|
|
||||||
// Search lexical scope for a matching function parameter, for-generator variable, or object
|
|
||||||
// property.
|
|
||||||
do {
|
|
||||||
var slot = findFrameSlot(currFrame, variableName, localPropertyName);
|
|
||||||
if (slot != -1) {
|
|
||||||
return levelsUp == 0
|
|
||||||
? ReadFrameSlotNodeGen.create(getSourceSection(), slot)
|
|
||||||
: ReadEnclosingFrameSlotNodeGen.create(getSourceSection(), slot, levelsUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
var localMember = currOwner.getMember(localPropertyName);
|
|
||||||
if (localMember != null) {
|
|
||||||
assert localMember.isLocal();
|
|
||||||
|
|
||||||
checkConst(currOwner, localMember, levelsUp);
|
|
||||||
|
|
||||||
var value = localMember.getConstantValue();
|
|
||||||
if (value != null) {
|
|
||||||
// This is the only code path that resolves local constant properties.
|
|
||||||
// Since this code path doesn't call VmObject.getCachedValue(),
|
|
||||||
// there is no point in calling VmObject.setCachedValue() either.
|
|
||||||
return new ConstantValueNode(sourceSection, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ReadLocalPropertyNode(sourceSection, localMember, levelsUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
var member = currOwner.getMember(variableName);
|
|
||||||
if (member != null) {
|
|
||||||
assert !member.isLocal();
|
|
||||||
|
|
||||||
checkConst(currOwner, member, levelsUp);
|
|
||||||
|
|
||||||
// Non-local properties are late-bound, which is why we can't ever return ConstantNode here.
|
|
||||||
//
|
|
||||||
// Assuming this node isn't used in Truffle ASTs of `external` pkl.base classes whose values
|
|
||||||
// aren't VmObject's,
|
|
||||||
// we only ever need VmObject-compatible specializations here.
|
|
||||||
// We don't exploit this fact here but ReadLocalPropertyNode (used above) does.
|
|
||||||
return ReadPropertyNodeGen.create(
|
|
||||||
sourceSection,
|
|
||||||
variableName,
|
|
||||||
MemberLookupMode.IMPLICIT_LEXICAL,
|
|
||||||
// we already checked for const-safety, no need to recheck
|
|
||||||
false,
|
|
||||||
levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp));
|
|
||||||
}
|
|
||||||
|
|
||||||
currFrame = currOwner.getEnclosingFrame();
|
|
||||||
currOwner = VmUtils.getOwnerOrNull(currFrame);
|
|
||||||
levelsUp += 1;
|
|
||||||
} while (currOwner != null);
|
|
||||||
|
|
||||||
// Search base module, unless call site is inside base module.
|
|
||||||
if (!isBaseModule) {
|
|
||||||
var baseModule = BaseModule.getModule();
|
|
||||||
|
|
||||||
var cachedValue = baseModule.getCachedValue(variableName);
|
|
||||||
if (cachedValue != null) {
|
|
||||||
return new ConstantValueNode(sourceSection, cachedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
var member = baseModule.getMember(variableName);
|
|
||||||
|
|
||||||
if (member != null) {
|
|
||||||
var constantValue = member.getConstantValue();
|
|
||||||
if (constantValue != null) {
|
|
||||||
baseModule.setCachedValue(variableName, constantValue);
|
|
||||||
return new ConstantValueNode(sourceSection, constantValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
var computedValue = member.getCallTarget().call(baseModule, baseModule);
|
|
||||||
baseModule.setCachedValue(variableName, computedValue);
|
|
||||||
return new ConstantValueNode(sourceSection, computedValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assuming this property exists at all, it must be a property accessible through `this`.
|
|
||||||
///
|
|
||||||
// Reading a property off of implicit `this` needs a const check if this node is not in a const
|
|
||||||
// scope.
|
|
||||||
// open class A {
|
|
||||||
// a = 1
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// class B extends A {
|
|
||||||
// const b = a // <-- implicit this lookup of `a`, which is not in a const scope.
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// A const scope exists if there is an object body, for example.
|
|
||||||
//
|
|
||||||
// class B extends A {
|
|
||||||
// const b = new { a } // <-- `new {}` creates a const scope.
|
|
||||||
// }
|
|
||||||
boolean needsConst = constLevel == ConstLevel.ALL && constDepth == -1 && !isCustomThisScope;
|
|
||||||
return ReadPropertyNodeGen.create(
|
|
||||||
sourceSection,
|
|
||||||
variableName,
|
|
||||||
MemberLookupMode.IMPLICIT_THIS,
|
|
||||||
needsConst,
|
|
||||||
VmUtils.createThisNode(VmUtils.unavailableSourceSection(), isCustomThisScope));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("DuplicatedCode")
|
|
||||||
private void checkConst(VmObjectLike currOwner, Member member, int levelsUp) {
|
|
||||||
if (!constLevel.isConst()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var memberIsOutsideConstScope = levelsUp > constDepth;
|
|
||||||
var invalid =
|
|
||||||
switch (constLevel) {
|
|
||||||
case ALL -> memberIsOutsideConstScope && !member.isConst();
|
|
||||||
case MODULE -> currOwner.isModuleObject() && !member.isConst();
|
|
||||||
default -> false;
|
|
||||||
};
|
|
||||||
if (invalid) {
|
|
||||||
throw exceptionBuilder().evalError("propertyMustBeConst", variableName.toString()).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int findFrameSlot(VirtualFrame frame, Object identifier1, Object identifier2) {
|
|
||||||
var descriptor = frame.getFrameDescriptor();
|
|
||||||
// Search backwards. The for-generator implementation exploits this
|
|
||||||
// to shadow a slot by appending a slot with the same name.
|
|
||||||
for (var i = descriptor.getNumberOfSlots() - 1; i >= 0; i--) {
|
|
||||||
var slotName = descriptor.getSlotName(i);
|
|
||||||
if (slotName == identifier1 || slotName == identifier2) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.ast.frame;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
|
import org.pkl.core.runtime.VmUtils;
|
||||||
|
|
||||||
|
public final class GetEnclosingFrameNode extends ExpressionNode {
|
||||||
|
private final int levelsUp;
|
||||||
|
|
||||||
|
public GetEnclosingFrameNode(int levelsUp) {
|
||||||
|
this.levelsUp = levelsUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VirtualFrame executeGeneric(VirtualFrame frame) {
|
||||||
|
return VmUtils.getFrame(frame, levelsUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInstrumentable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-27
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,59 +17,40 @@ package org.pkl.core.ast.frame;
|
|||||||
|
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
import com.oracle.truffle.api.frame.FrameSlotTypeException;
|
import com.oracle.truffle.api.frame.FrameSlotTypeException;
|
||||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.runtime.VmUtils;
|
|
||||||
|
|
||||||
public abstract class ReadEnclosingFrameSlotNode extends ExpressionNode {
|
public abstract class ReadExactFrameSlotNode extends ExpressionNode {
|
||||||
private final int slot;
|
private final int slot;
|
||||||
private final int levelsUp;
|
|
||||||
|
|
||||||
protected ReadEnclosingFrameSlotNode(SourceSection sourceSection, int slot, int levelsUp) {
|
protected ReadExactFrameSlotNode(SourceSection sourceSection, int slot) {
|
||||||
super(sourceSection);
|
super(sourceSection);
|
||||||
this.slot = slot;
|
this.slot = slot;
|
||||||
this.levelsUp = levelsUp;
|
|
||||||
|
|
||||||
assert levelsUp > 0 : "should be using ReadFrameSlotNode for levelsUp == 0";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||||
protected long evalInt(VirtualFrame frame) throws FrameSlotTypeException {
|
protected long evalInt(VirtualFrame frame) throws FrameSlotTypeException {
|
||||||
return getCapturedFrame(frame).getLong(slot);
|
return frame.getLong(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||||
protected double evalFloat(VirtualFrame frame) throws FrameSlotTypeException {
|
protected double evalFloat(VirtualFrame frame) throws FrameSlotTypeException {
|
||||||
return getCapturedFrame(frame).getDouble(slot);
|
return frame.getDouble(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||||
protected boolean evalBoolean(VirtualFrame frame) throws FrameSlotTypeException {
|
protected boolean evalBoolean(VirtualFrame frame) throws FrameSlotTypeException {
|
||||||
return getCapturedFrame(frame).getBoolean(slot);
|
return frame.getBoolean(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||||
protected Object evalObject(VirtualFrame frame) throws FrameSlotTypeException {
|
protected Object evalObject(VirtualFrame frame) throws FrameSlotTypeException {
|
||||||
return getCapturedFrame(frame).getObject(slot);
|
return frame.getObject(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(replaces = {"evalInt", "evalFloat", "evalBoolean", "evalObject"})
|
@Specialization(replaces = {"evalInt", "evalFloat", "evalBoolean", "evalObject"})
|
||||||
protected Object evalGeneric(VirtualFrame frame) {
|
protected Object evalGeneric(VirtualFrame frame) {
|
||||||
return getCapturedFrame(frame).getValue(slot);
|
return frame.getValue(slot);
|
||||||
}
|
|
||||||
|
|
||||||
// could be factored out into a separate node s.t. it
|
|
||||||
// won't be repeated in case of FrameSlotTypeException
|
|
||||||
@ExplodeLoop
|
|
||||||
protected final MaterializedFrame getCapturedFrame(VirtualFrame frame) {
|
|
||||||
var owner = VmUtils.getOwner(frame);
|
|
||||||
for (var i = 0; i < levelsUp - 1; i++) {
|
|
||||||
owner = owner.getEnclosingOwner();
|
|
||||||
assert owner != null; // guaranteed by AstBuilder
|
|
||||||
}
|
|
||||||
return owner.getEnclosingFrame();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -15,12 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.pkl.core.ast.frame;
|
package org.pkl.core.ast.frame;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.dsl.NodeChild;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
import com.oracle.truffle.api.frame.FrameSlotTypeException;
|
import com.oracle.truffle.api.frame.FrameSlotTypeException;
|
||||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.source.SourceSection;
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
|
|
||||||
|
@NodeChild(value = "enclosingFrame", type = GetEnclosingFrameNode.class)
|
||||||
public abstract class ReadFrameSlotNode extends ExpressionNode {
|
public abstract class ReadFrameSlotNode extends ExpressionNode {
|
||||||
private final int slot;
|
private final int slot;
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import org.pkl.core.util.AnsiStringBuilder;
|
|||||||
import org.pkl.core.util.EconomicMaps;
|
import org.pkl.core.util.EconomicMaps;
|
||||||
import org.pkl.core.util.IoUtils;
|
import org.pkl.core.util.IoUtils;
|
||||||
import org.pkl.core.util.MutableReference;
|
import org.pkl.core.util.MutableReference;
|
||||||
|
import org.pkl.core.util.Pair;
|
||||||
import org.pkl.core.util.SyntaxHighlighter;
|
import org.pkl.core.util.SyntaxHighlighter;
|
||||||
import org.pkl.parser.Parser;
|
import org.pkl.parser.Parser;
|
||||||
import org.pkl.parser.ParserError;
|
import org.pkl.parser.ParserError;
|
||||||
@@ -58,9 +59,12 @@ import org.pkl.parser.syntax.ClassProperty;
|
|||||||
import org.pkl.parser.syntax.Expr;
|
import org.pkl.parser.syntax.Expr;
|
||||||
import org.pkl.parser.syntax.ImportClause;
|
import org.pkl.parser.syntax.ImportClause;
|
||||||
import org.pkl.parser.syntax.ModuleDecl;
|
import org.pkl.parser.syntax.ModuleDecl;
|
||||||
|
import org.pkl.parser.syntax.Node;
|
||||||
import org.pkl.parser.syntax.ReplInput;
|
import org.pkl.parser.syntax.ReplInput;
|
||||||
|
|
||||||
public class ReplServer implements AutoCloseable {
|
public class ReplServer implements AutoCloseable {
|
||||||
|
private static final String expressionPreamble = "`THE REPL TEXT EXPR` = ";
|
||||||
|
|
||||||
private final IndirectCallNode callNode = Truffle.getRuntime().createIndirectCallNode();
|
private final IndirectCallNode callNode = Truffle.getRuntime().createIndirectCallNode();
|
||||||
private final Context polyglotContext;
|
private final Context polyglotContext;
|
||||||
private final VmLanguage language;
|
private final VmLanguage language;
|
||||||
@@ -72,6 +76,7 @@ public class ReplServer implements AutoCloseable {
|
|||||||
private final PackageResolver packageResolver;
|
private final PackageResolver packageResolver;
|
||||||
private final @Nullable ProjectDependenciesManager projectDependenciesManager;
|
private final @Nullable ProjectDependenciesManager projectDependenciesManager;
|
||||||
private final boolean color;
|
private final boolean color;
|
||||||
|
private final Parser parser = new Parser();
|
||||||
|
|
||||||
public ReplServer(
|
public ReplServer(
|
||||||
SecurityManager securityManager,
|
SecurityManager securityManager,
|
||||||
@@ -178,7 +183,132 @@ public class ReplServer implements AutoCloseable {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"StatementWithEmptyBody"})
|
/**
|
||||||
|
* Create a fake module that declares all the local properties that exist in ReplState, so that
|
||||||
|
* AstBuilder can correctly resolve variables. Additionally, return a {@link Node} with its span
|
||||||
|
* calculated in terms of this fake module.
|
||||||
|
*
|
||||||
|
* <p>This created module is never executed.
|
||||||
|
*/
|
||||||
|
private Pair<String, Node> buildSyntheticModuleText(Node syntaxNode, String srcText) {
|
||||||
|
if (syntaxNode instanceof ModuleDecl) {
|
||||||
|
return Pair.of(srcText, syntaxNode);
|
||||||
|
}
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var nodeText = syntaxNode.text(srcText.toCharArray());
|
||||||
|
var adjustedNode = syntaxNode;
|
||||||
|
if (syntaxNode instanceof Expr) {
|
||||||
|
sb.append(expressionPreamble).append(nodeText).append('\n');
|
||||||
|
} else {
|
||||||
|
sb.append(nodeText).append('\n');
|
||||||
|
}
|
||||||
|
var mod = parser.parseModule(sb.toString());
|
||||||
|
if (syntaxNode instanceof Expr) {
|
||||||
|
adjustedNode = mod.getProperties().get(0).getExpr();
|
||||||
|
} else if (syntaxNode instanceof ImportClause) {
|
||||||
|
adjustedNode = mod.getImports().get(0);
|
||||||
|
} else if (syntaxNode instanceof ClassProperty) {
|
||||||
|
adjustedNode = mod.getProperties().get(0);
|
||||||
|
} else if (syntaxNode instanceof Class) {
|
||||||
|
adjustedNode = mod.getClasses().get(0);
|
||||||
|
} else if (syntaxNode instanceof org.pkl.parser.syntax.TypeAlias) {
|
||||||
|
adjustedNode = mod.getTypeAliases().get(0);
|
||||||
|
} else if (syntaxNode instanceof org.pkl.parser.syntax.ClassMethod) {
|
||||||
|
adjustedNode = mod.getMethods().get(0);
|
||||||
|
}
|
||||||
|
var cursor = EconomicMaps.getEntries(replState.module.getMembers());
|
||||||
|
while (cursor.advance()) {
|
||||||
|
var key = cursor.getKey();
|
||||||
|
var value = cursor.getValue();
|
||||||
|
if (value.isLocal()) {
|
||||||
|
sb.append("local ").append(key).append(" = Undefined()\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert adjustedNode != null;
|
||||||
|
return Pair.of(sb.toString(), adjustedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
|
private void handleNode(
|
||||||
|
ReplState replState,
|
||||||
|
List<Object> results,
|
||||||
|
URI uri,
|
||||||
|
Node node,
|
||||||
|
String srcText,
|
||||||
|
boolean evalDefinitions,
|
||||||
|
boolean forceResults) {
|
||||||
|
var pair = buildSyntheticModuleText(node, srcText);
|
||||||
|
var syntheticModuleText = pair.first;
|
||||||
|
var adjustedNode = pair.second;
|
||||||
|
var module = ModuleKeys.synthetic(uri, workingDir.toUri(), uri, syntheticModuleText, false);
|
||||||
|
ResolvedModuleKey resolved;
|
||||||
|
try {
|
||||||
|
resolved = module.resolve(securityManager);
|
||||||
|
} catch (SecurityManagerException e) {
|
||||||
|
throw new VmExceptionBuilder().withCause(e).build();
|
||||||
|
} catch (IOException e) {
|
||||||
|
// resolving a synthetic module should never cause IOException
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
var builder =
|
||||||
|
new AstBuilder(
|
||||||
|
VmUtils.loadSource(resolved),
|
||||||
|
language,
|
||||||
|
replState.module.getModuleInfo(),
|
||||||
|
moduleResolver);
|
||||||
|
var mod = parser.parseModule(syntheticModuleText);
|
||||||
|
try {
|
||||||
|
builder.visitModule(mod);
|
||||||
|
if (adjustedNode instanceof Expr expr) {
|
||||||
|
var exprNode = builder.visitExpr(expr);
|
||||||
|
evaluateExpr(replState, exprNode, forceResults, results);
|
||||||
|
} else if (adjustedNode instanceof ImportClause importClause) {
|
||||||
|
addStaticModuleProperty(builder.visitImportClause(importClause));
|
||||||
|
} else if (adjustedNode instanceof ClassProperty classProperty) {
|
||||||
|
var propertyNode = builder.visitClassProperty(classProperty);
|
||||||
|
var property = addModuleProperty(propertyNode);
|
||||||
|
if (evalDefinitions) {
|
||||||
|
evaluateMemberDef(replState, property, forceResults, results);
|
||||||
|
}
|
||||||
|
} else if (adjustedNode instanceof Class clazz) {
|
||||||
|
addStaticModuleProperty(builder.visitClass(clazz));
|
||||||
|
} else if (adjustedNode instanceof org.pkl.parser.syntax.TypeAlias typeAlias) {
|
||||||
|
addStaticModuleProperty(builder.visitTypeAlias(typeAlias));
|
||||||
|
} else if (adjustedNode instanceof org.pkl.parser.syntax.ClassMethod classMethod) {
|
||||||
|
addModuleMethodDef(builder.visitClassMethod(classMethod));
|
||||||
|
} else if (adjustedNode instanceof ModuleDecl) {
|
||||||
|
// do nothing for now
|
||||||
|
} else {
|
||||||
|
results.add(
|
||||||
|
new ReplResponse.InternalError(new IllegalStateException("Unexpected parse result")));
|
||||||
|
}
|
||||||
|
} catch (VmException e) {
|
||||||
|
// TODO: patch stack trace for constants
|
||||||
|
results.add(new EvalError(renderException(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip out the {@code `THE REPL TEXT EXPR` = } preamble that we insert in front of expressions;
|
||||||
|
*/
|
||||||
|
private String renderException(VmException e) {
|
||||||
|
var rendered = errorRenderer.render(e);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var lines = rendered.split("\n");
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var line = lines[i];
|
||||||
|
if (line.contains(expressionPreamble)) {
|
||||||
|
sb.append(line.replace(expressionPreamble, "")).append('\n');
|
||||||
|
var decoration = lines[i + 1];
|
||||||
|
sb.append(decoration.replace(" ".repeat(expressionPreamble.length()), "")).append('\n');
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
sb.append(line).append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private List<Object> evaluate(
|
private List<Object> evaluate(
|
||||||
ReplState replState,
|
ReplState replState,
|
||||||
String requestId,
|
String requestId,
|
||||||
@@ -198,53 +328,9 @@ public class ReplServer implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var results = new ArrayList<>();
|
var results = new ArrayList<>();
|
||||||
var module = ModuleKeys.synthetic(uri, workingDir.toUri(), uri, text, false);
|
|
||||||
ResolvedModuleKey resolved;
|
|
||||||
try {
|
|
||||||
resolved = module.resolve(securityManager);
|
|
||||||
} catch (SecurityManagerException e) {
|
|
||||||
throw new VmExceptionBuilder().withCause(e).build();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// resolving a synthetic module should never cause IOException
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder =
|
|
||||||
new AstBuilder(
|
|
||||||
VmUtils.loadSource(resolved),
|
|
||||||
language,
|
|
||||||
replState.module.getModuleInfo(),
|
|
||||||
moduleResolver);
|
|
||||||
|
|
||||||
for (var tree : replInputContext.getNodes()) {
|
for (var tree : replInputContext.getNodes()) {
|
||||||
try {
|
handleNode(replState, results, uri, tree, text, evalDefinitions, forceResults);
|
||||||
if (tree instanceof Expr expr) {
|
|
||||||
var exprNode = builder.visitExpr(expr);
|
|
||||||
evaluateExpr(replState, exprNode, forceResults, results);
|
|
||||||
} else if (tree instanceof ImportClause importClause) {
|
|
||||||
addStaticModuleProperty(builder.visitImportClause(importClause));
|
|
||||||
} else if (tree instanceof ClassProperty classProperty) {
|
|
||||||
var propertyNode = builder.visitClassProperty(classProperty);
|
|
||||||
var property = addModuleProperty(propertyNode);
|
|
||||||
if (evalDefinitions) {
|
|
||||||
evaluateMemberDef(replState, property, forceResults, results);
|
|
||||||
}
|
|
||||||
} else if (tree instanceof Class clazz) {
|
|
||||||
addStaticModuleProperty(builder.visitClass(clazz));
|
|
||||||
} else if (tree instanceof org.pkl.parser.syntax.TypeAlias typeAlias) {
|
|
||||||
addStaticModuleProperty(builder.visitTypeAlias(typeAlias));
|
|
||||||
} else if (tree instanceof org.pkl.parser.syntax.ClassMethod classMethod) {
|
|
||||||
addModuleMethodDef(builder.visitClassMethod(classMethod));
|
|
||||||
} else if (tree instanceof ModuleDecl) {
|
|
||||||
// do nothing for now
|
|
||||||
} else {
|
|
||||||
results.add(
|
|
||||||
new ReplResponse.InternalError(new IllegalStateException("Unexpected parse result")));
|
|
||||||
}
|
|
||||||
} catch (VmException e) {
|
|
||||||
// TODO: patch stack trace for constants
|
|
||||||
results.add(new EvalError(errorRenderer.render(e)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.runtime;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||||
|
import com.oracle.truffle.api.frame.FrameSlotKind;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also lets us find the slot of a
|
||||||
|
* given {@link Identifier}.
|
||||||
|
*/
|
||||||
|
public class FrameDescriptorBuilder {
|
||||||
|
|
||||||
|
private @Nullable Identifier[] names;
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
private final FrameDescriptor.Builder underlying;
|
||||||
|
|
||||||
|
private static final int DEFAULT_CAPACITY = 8;
|
||||||
|
|
||||||
|
public FrameDescriptorBuilder() {
|
||||||
|
this(DEFAULT_CAPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameDescriptorBuilder(int capacity) {
|
||||||
|
underlying = FrameDescriptor.newBuilder(capacity);
|
||||||
|
this.names = new Identifier[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameDescriptorBuilder(FrameDescriptor descriptor) {
|
||||||
|
this(descriptor.getNumberOfSlots());
|
||||||
|
for (var i = 0; i < descriptor.getNumberOfSlots(); i++) {
|
||||||
|
addSlot(
|
||||||
|
descriptor.getSlotKind(i),
|
||||||
|
(Identifier) descriptor.getSlotName(i),
|
||||||
|
descriptor.getSlotInfo(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureCapacity(int count) {
|
||||||
|
if (names.length < size + count) {
|
||||||
|
var newLength = Math.max(size + count, size * 2);
|
||||||
|
names = Arrays.copyOf(names, newLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int addSlot(FrameSlotKind kind, @Nullable Identifier name, @Nullable Object info) {
|
||||||
|
ensureCapacity(1);
|
||||||
|
names[size] = name;
|
||||||
|
size++;
|
||||||
|
return underlying.addSlot(kind, name, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int findSlot(Identifier identifier) {
|
||||||
|
// go backwards to account for shadowed variables
|
||||||
|
// (this happens in the case of nested for generators).
|
||||||
|
for (var i = size - 1; i >= 0; i--) {
|
||||||
|
var name = names[i];
|
||||||
|
if (name != null && name.equals(identifier)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FrameDescriptor build() {
|
||||||
|
return underlying.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,8 @@ public final class VmFunction extends VmObjectLike {
|
|||||||
private final Object thisValue;
|
private final Object thisValue;
|
||||||
private final int paramCount;
|
private final int paramCount;
|
||||||
private final PklRootNode rootNode;
|
private final PklRootNode rootNode;
|
||||||
|
private final boolean hasObjectParams;
|
||||||
|
private final boolean isFunctionAmend;
|
||||||
|
|
||||||
public VmFunction(
|
public VmFunction(
|
||||||
MaterializedFrame enclosingFrame,
|
MaterializedFrame enclosingFrame,
|
||||||
@@ -37,11 +39,24 @@ public final class VmFunction extends VmObjectLike {
|
|||||||
int paramCount,
|
int paramCount,
|
||||||
PklRootNode rootNode,
|
PklRootNode rootNode,
|
||||||
@Nullable Object extraStorage) {
|
@Nullable Object extraStorage) {
|
||||||
|
this(enclosingFrame, thisValue, paramCount, rootNode, extraStorage, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VmFunction(
|
||||||
|
MaterializedFrame enclosingFrame,
|
||||||
|
Object thisValue,
|
||||||
|
int paramCount,
|
||||||
|
PklRootNode rootNode,
|
||||||
|
@Nullable Object extraStorage,
|
||||||
|
boolean isFunctionAmend,
|
||||||
|
boolean hasObjectParams) {
|
||||||
super(enclosingFrame);
|
super(enclosingFrame);
|
||||||
this.thisValue = thisValue;
|
this.thisValue = thisValue;
|
||||||
this.paramCount = paramCount;
|
this.paramCount = paramCount;
|
||||||
this.rootNode = rootNode;
|
this.rootNode = rootNode;
|
||||||
|
this.hasObjectParams = hasObjectParams;
|
||||||
this.extraStorage = extraStorage;
|
this.extraStorage = extraStorage;
|
||||||
|
this.isFunctionAmend = isFunctionAmend;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RootCallTarget getCallTarget() {
|
public RootCallTarget getCallTarget() {
|
||||||
@@ -71,7 +86,9 @@ public final class VmFunction extends VmObjectLike {
|
|||||||
thisValue,
|
thisValue,
|
||||||
newParamCount,
|
newParamCount,
|
||||||
newRootNode == null ? rootNode : newRootNode,
|
newRootNode == null ? rootNode : newRootNode,
|
||||||
newExtraStorage);
|
newExtraStorage,
|
||||||
|
isFunctionAmend,
|
||||||
|
hasObjectParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getThisValue() {
|
public Object getThisValue() {
|
||||||
@@ -187,4 +204,9 @@ public final class VmFunction extends VmObjectLike {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return VmValueRenderer.singleLine(Integer.MAX_VALUE).render(this);
|
return VmValueRenderer.singleLine(Integer.MAX_VALUE).render(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isParseTimeInvisibleScope() {
|
||||||
|
return isFunctionAmend && !hasObjectParams;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,41 @@ public abstract class VmObjectLike extends VmValue {
|
|||||||
/** Returns the declared members of this object. */
|
/** Returns the declared members of this object. */
|
||||||
public abstract UnmodifiableEconomicMap<Object, ObjectMember> getMembers();
|
public abstract UnmodifiableEconomicMap<Object, ObjectMember> getMembers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if it's impossible to determine if this is object describes a scope during parse time.
|
||||||
|
*
|
||||||
|
* <p>An amended lambda hides a new lexical scope if there are also no params.
|
||||||
|
*
|
||||||
|
* <p>Assuming {@code foo} is a lambda, this snippet:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* qux = 3
|
||||||
|
*
|
||||||
|
* foo {
|
||||||
|
* bar = qux
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* Desugars to:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* qux = 3
|
||||||
|
*
|
||||||
|
* foo = () -> (super.foo.apply()) {
|
||||||
|
* bar = qux
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* So, {@code qux} is <i>two</i> levels higher, not one. However, it's not possible to figure this
|
||||||
|
* out at parse time alone.
|
||||||
|
*
|
||||||
|
* <p>This method tells if we need to skip this object when traversing up lexical scopes.
|
||||||
|
*/
|
||||||
|
public boolean isParseTimeInvisibleScope() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads from the properties cache for this object. The cache contains the values of all members
|
* Reads from the properties cache for this object. The cache contains the values of all members
|
||||||
* defined in this object or an ancestor thereof which have been requested with this object as the
|
* defined in this object or an ancestor thereof which have been requested with this object as the
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ public final class VmUtils {
|
|||||||
return (VmObjectLike) getReceiver(frame);
|
return (VmObjectLike) getReceiver(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static VmObjectLike getObjectReceiver(VirtualFrame frame, int levelsUp) {
|
||||||
|
return (VmObjectLike) getReceiver(frame, levelsUp);
|
||||||
|
}
|
||||||
|
|
||||||
public static VmTyped getTypedObjectReceiver(Frame frame) {
|
public static VmTyped getTypedObjectReceiver(Frame frame) {
|
||||||
return (VmTyped) getReceiver(frame);
|
return (VmTyped) getReceiver(frame);
|
||||||
}
|
}
|
||||||
@@ -159,6 +163,39 @@ public final class VmUtils {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static VmObjectLike getOwner(VirtualFrame frame, int levelsUp) {
|
||||||
|
return getOwner(getFrame(frame, levelsUp));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getReceiver(VirtualFrame frame, int levelsUp) {
|
||||||
|
return getReceiver(getFrame(frame, levelsUp));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VirtualFrame getFrame(VirtualFrame frame, int levelsUp) {
|
||||||
|
frame = skipInvisibleScopes(frame);
|
||||||
|
if (levelsUp == 0) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
var owner = getOwner(frame);
|
||||||
|
for (var i = 0; i < levelsUp; i++) {
|
||||||
|
frame = owner.getEnclosingFrame();
|
||||||
|
owner = getOwner(frame);
|
||||||
|
if (owner.isParseTimeInvisibleScope()) {
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VirtualFrame skipInvisibleScopes(VirtualFrame frame) {
|
||||||
|
var owner = getOwner(frame);
|
||||||
|
while (owner.isParseTimeInvisibleScope()) {
|
||||||
|
frame = owner.getEnclosingFrame();
|
||||||
|
owner = getOwner(frame);
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns a `ObjectMember`'s key while executing the corresponding `MemberNode`. */
|
/** Returns a `ObjectMember`'s key while executing the corresponding `MemberNode`. */
|
||||||
public static Object getMemberKey(Frame frame) {
|
public static Object getMemberKey(Frame frame) {
|
||||||
return frame.getArguments()[2];
|
return frame.getArguments()[2];
|
||||||
@@ -375,6 +412,7 @@ public final class VmUtils {
|
|||||||
int numberOfLocalsToCopy) {
|
int numberOfLocalsToCopy) {
|
||||||
var sourceDescriptor = sourceFrame.getFrameDescriptor();
|
var sourceDescriptor = sourceFrame.getFrameDescriptor();
|
||||||
var targetDescriptor = targetFrame.getFrameDescriptor();
|
var targetDescriptor = targetFrame.getFrameDescriptor();
|
||||||
|
assert sourceDescriptor.getNumberOfSlots() <= targetDescriptor.getNumberOfSlots();
|
||||||
// Alternatively, locals could be copied with `numberOfLocalsToCopy`
|
// Alternatively, locals could be copied with `numberOfLocalsToCopy`
|
||||||
// `ReadFrameSlotNode/WriteFrameSlotNode`'s.
|
// `ReadFrameSlotNode/WriteFrameSlotNode`'s.
|
||||||
for (int i = 0; i < numberOfLocalsToCopy; i++) {
|
for (int i = 0; i < numberOfLocalsToCopy; i++) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -20,7 +20,7 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import org.pkl.core.ast.ExpressionNode;
|
import org.pkl.core.ast.ExpressionNode;
|
||||||
import org.pkl.core.ast.expression.primary.GetReceiverNode;
|
import org.pkl.core.ast.expression.primary.GetReceiverNode;
|
||||||
import org.pkl.core.ast.frame.ReadFrameSlotNodeGen;
|
import org.pkl.core.ast.frame.ReadExactFrameSlotNodeGen;
|
||||||
import org.pkl.core.runtime.VmException;
|
import org.pkl.core.runtime.VmException;
|
||||||
import org.pkl.core.runtime.VmExceptionBuilder;
|
import org.pkl.core.runtime.VmExceptionBuilder;
|
||||||
import org.pkl.core.runtime.VmUtils;
|
import org.pkl.core.runtime.VmUtils;
|
||||||
@@ -115,7 +115,7 @@ public abstract class ExternalMemberRegistry {
|
|||||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||||
|
|
||||||
var sourceSection = VmUtils.unavailableSourceSection();
|
var sourceSection = VmUtils.unavailableSourceSection();
|
||||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||||
return factory.create(new GetReceiverNode(), param1Node);
|
return factory.create(new GetReceiverNode(), param1Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,8 +125,8 @@ public abstract class ExternalMemberRegistry {
|
|||||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||||
|
|
||||||
var sourceSection = VmUtils.unavailableSourceSection();
|
var sourceSection = VmUtils.unavailableSourceSection();
|
||||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||||
return factory.create(new GetReceiverNode(), param1Node, param2Node);
|
return factory.create(new GetReceiverNode(), param1Node, param2Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,9 +136,9 @@ public abstract class ExternalMemberRegistry {
|
|||||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||||
|
|
||||||
var sourceSection = VmUtils.unavailableSourceSection();
|
var sourceSection = VmUtils.unavailableSourceSection();
|
||||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||||
var param3Node = ReadFrameSlotNodeGen.create(sourceSection, 2);
|
var param3Node = ReadExactFrameSlotNodeGen.create(sourceSection, 2);
|
||||||
return factory.create(new GetReceiverNode(), param1Node, param2Node, param3Node);
|
return factory.create(new GetReceiverNode(), param1Node, param2Node, param3Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,10 +148,10 @@ public abstract class ExternalMemberRegistry {
|
|||||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||||
|
|
||||||
var sourceSection = VmUtils.unavailableSourceSection();
|
var sourceSection = VmUtils.unavailableSourceSection();
|
||||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||||
var param3Node = ReadFrameSlotNodeGen.create(sourceSection, 2);
|
var param3Node = ReadExactFrameSlotNodeGen.create(sourceSection, 2);
|
||||||
var param4Node = ReadFrameSlotNodeGen.create(sourceSection, 3);
|
var param4Node = ReadExactFrameSlotNodeGen.create(sourceSection, 3);
|
||||||
return factory.create(new GetReceiverNode(), param1Node, param2Node, param3Node, param4Node);
|
return factory.create(new GetReceiverNode(), param1Node, param2Node, param3Node, param4Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,11 +161,11 @@ public abstract class ExternalMemberRegistry {
|
|||||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||||
|
|
||||||
var sourceSection = VmUtils.unavailableSourceSection();
|
var sourceSection = VmUtils.unavailableSourceSection();
|
||||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||||
var param3Node = ReadFrameSlotNodeGen.create(sourceSection, 2);
|
var param3Node = ReadExactFrameSlotNodeGen.create(sourceSection, 2);
|
||||||
var param4Node = ReadFrameSlotNodeGen.create(sourceSection, 3);
|
var param4Node = ReadExactFrameSlotNodeGen.create(sourceSection, 3);
|
||||||
var param5Node = ReadFrameSlotNodeGen.create(sourceSection, 4);
|
var param5Node = ReadExactFrameSlotNodeGen.create(sourceSection, 4);
|
||||||
return factory.create(
|
return factory.create(
|
||||||
new GetReceiverNode(), param1Node, param2Node, param3Node, param4Node, param5Node);
|
new GetReceiverNode(), param1Node, param2Node, param3Node, param4Node, param5Node);
|
||||||
}
|
}
|
||||||
|
|||||||
+34
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorInMixinWithObjectParam.pkl
Vendored
+18
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -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")
|
||||||
+4
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
foo {
|
||||||
|
bar {
|
||||||
|
"elem"
|
||||||
|
new {
|
||||||
|
res {
|
||||||
|
"elem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new {
|
||||||
|
name = "res1Name"
|
||||||
|
value = "res1Value"
|
||||||
|
}
|
||||||
|
new {
|
||||||
|
name = "res2Name"
|
||||||
|
value = "res2Value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
@@ -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")
|
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 {
|
private fun makeEvalRequest(text: String): String {
|
||||||
val responses = server.handleRequest(ReplRequest.Eval("id", text, false, false))
|
val responses = server.handleRequest(ReplRequest.Eval("id", text, false, false))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user