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,
|
||||
unless the type is intentionally overridden via `extends`.
|
||||
|
||||
[source%tested,{pkl}]
|
||||
[source%parsed,{pkl}]
|
||||
----
|
||||
amends "myOtherModule.pkl"
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ dependencies {
|
||||
add("generatorImplementation", libs.javaPoet)
|
||||
add("generatorImplementation", libs.truffleApi)
|
||||
add("generatorImplementation", libs.jspecify)
|
||||
add("generatorImplementation", projects.pklParser)
|
||||
|
||||
javaExecutableConfiguration(project(":pkl-cli", "javaExecutable"))
|
||||
}
|
||||
@@ -140,6 +141,36 @@ tasks.test {
|
||||
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
|
||||
tasks.registering(Test::class) {
|
||||
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 AMBIGUOUS_LOCALITY = 0x10000;
|
||||
|
||||
// modifier sets
|
||||
|
||||
public static final int NONE = 0;
|
||||
@@ -126,6 +128,10 @@ public final class VmModifier {
|
||||
return (modifiers & CONST) != 0;
|
||||
}
|
||||
|
||||
public static boolean isAmbiguousLocality(int modifiers) {
|
||||
return (modifiers & AMBIGUOUS_LOCALITY) != 0;
|
||||
}
|
||||
|
||||
public static boolean isElement(int modifiers) {
|
||||
return (modifiers & ELEMENT) != 0;
|
||||
}
|
||||
@@ -154,6 +160,10 @@ public final class VmModifier {
|
||||
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) {
|
||||
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.PklRootNode;
|
||||
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.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.DivisionNodeGen;
|
||||
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.InferParentWithinObjectMethodNode;
|
||||
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.InvokeObjectMethodNode;
|
||||
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.ReadSuperEntryNode;
|
||||
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.GetEnclosingReceiverNode;
|
||||
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.GetReceiverNode;
|
||||
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.ternary.IfElseNode;
|
||||
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.TraceNode;
|
||||
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.GetClassNodeGen;
|
||||
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.packages.PackageLoadError;
|
||||
import org.pkl.core.runtime.BaseModule;
|
||||
import org.pkl.core.runtime.FrameDescriptorBuilder;
|
||||
import org.pkl.core.runtime.ModuleInfo;
|
||||
import org.pkl.core.runtime.ModuleResolver;
|
||||
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.ObjectSpread;
|
||||
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.ParameterList;
|
||||
import org.pkl.parser.syntax.QualifiedIdentifier;
|
||||
@@ -285,7 +301,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
isBaseModule = ModuleKeys.isBaseModule(moduleKey);
|
||||
isStdLibModule = ModuleKeys.isStdLibModule(moduleKey);
|
||||
externalMemberRegistry = MemberRegistryFactory.get(moduleKey);
|
||||
symbolTable = new SymbolTable(moduleInfo);
|
||||
symbolTable = new SymbolTable(moduleInfo, isBaseModule);
|
||||
isMethodReturnTypeChecked = !isStdLibModule || IoUtils.isTestMode();
|
||||
}
|
||||
|
||||
@@ -490,10 +506,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
@Override
|
||||
public GetModuleNode visitModuleExpr(ModuleExpr expr) {
|
||||
var currentScope = symbolTable.getCurrentScope();
|
||||
// cannot use unqualified `module` in a const context
|
||||
if (symbolTable.getCurrentScope().getConstLevel().isConst()
|
||||
&& !(expr.parent() instanceof QualifiedAccessExpr)) {
|
||||
var scope = symbolTable.getCurrentScope();
|
||||
if (currentScope.getConstLevel().isConst() && !(expr.parent() instanceof QualifiedAccessExpr)) {
|
||||
var scope = currentScope;
|
||||
while (scope != null
|
||||
&& !(scope instanceof AnnotationScope)
|
||||
&& !(scope instanceof ClassScope)) {
|
||||
@@ -501,7 +517,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
if (scope == null) {
|
||||
throw exceptionBuilder()
|
||||
.evalError("moduleIsNotConst", symbolTable.getCurrentScope().getName().toString())
|
||||
.evalError("moduleIsNotConst", currentScope.getName().toString())
|
||||
.withSourceSection(createSourceSection(expr))
|
||||
.build();
|
||||
}
|
||||
@@ -636,44 +652,161 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionNode visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr) {
|
||||
var identifier = toIdentifier(expr.getIdentifier().getValue());
|
||||
var argList = expr.getArgumentList();
|
||||
|
||||
if (argList == null) {
|
||||
return createResolveVariableNode(createSourceSection(expr), identifier);
|
||||
private ExpressionNode resolveReadVariable(UnqualifiedAccessExpr expr) {
|
||||
var name = expr.getIdentifier().getValue();
|
||||
var scope = symbolTable.getCurrentScope();
|
||||
var sourceSection = createSourceSection(expr);
|
||||
var constLevel = scope.getConstLevel();
|
||||
var constDepth = scope.getConstDepth();
|
||||
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
|
||||
// TODO: support qualified calls (e.g., `import "pkl:base"; x =
|
||||
// base.List()/Set()/Map()/Bytes()`) for correctness
|
||||
private ExpressionNode resolvedMethodCall(UnqualifiedAccessExpr expr, ArgumentList argList) {
|
||||
var name = expr.getIdentifier().getValue();
|
||||
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) {
|
||||
return doVisitListLiteral(expr, argList);
|
||||
}
|
||||
|
||||
if (identifier == org.pkl.core.runtime.Identifier.SET) {
|
||||
} else if (identifier == org.pkl.core.runtime.Identifier.SET) {
|
||||
return doVisitSetLiteral(expr, argList);
|
||||
}
|
||||
|
||||
if (identifier == org.pkl.core.runtime.Identifier.MAP) {
|
||||
} else if (identifier == org.pkl.core.runtime.Identifier.MAP) {
|
||||
return doVisitMapLiteral(expr, argList);
|
||||
}
|
||||
|
||||
if (identifier == org.pkl.core.runtime.Identifier.BYTES_CONSTRUCTOR) {
|
||||
} else if (identifier == org.pkl.core.runtime.Identifier.BYTES_CONSTRUCTOR) {
|
||||
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();
|
||||
|
||||
return new ResolveMethodNode(
|
||||
createSourceSection(expr),
|
||||
identifier,
|
||||
visitArgumentList(argList),
|
||||
isBaseModule,
|
||||
scope.isCustomThisScope(),
|
||||
scope.getConstLevel(),
|
||||
scope.getConstDepth());
|
||||
@Override
|
||||
public ExpressionNode visitUnqualifiedAccessExpr(UnqualifiedAccessExpr expr) {
|
||||
var argList = expr.getArgumentList();
|
||||
return argList == null ? resolveReadVariable(expr) : resolvedMethodCall(expr, argList);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -980,12 +1113,14 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
public ExpressionNode visitLetExpr(LetExpr letExpr) {
|
||||
var sourceSection = createSourceSection(letExpr);
|
||||
var parameter = letExpr.getParameter();
|
||||
var frameBuilder = FrameDescriptor.newBuilder();
|
||||
var frameBuilder = new FrameDescriptorBuilder();
|
||||
UnresolvedTypeNode[] typeNodes;
|
||||
var bindings = new ArrayList<String>();
|
||||
if (parameter instanceof TypedIdentifier par) {
|
||||
typeNodes = new UnresolvedTypeNode[] {visitTypeAnnotation(par.getTypeAnnotation())};
|
||||
frameBuilder.addSlot(
|
||||
FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null);
|
||||
bindings.add(par.getIdentifier().getValue());
|
||||
} else {
|
||||
typeNodes = new UnresolvedTypeNode[0];
|
||||
}
|
||||
@@ -994,6 +1129,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
UnresolvedFunctionNode functionNode =
|
||||
symbolTable.enterLambda(
|
||||
bindings,
|
||||
frameBuilder,
|
||||
scope -> {
|
||||
var expr = visitExpr(letExpr.getExpr());
|
||||
@@ -1025,9 +1161,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
.build();
|
||||
}
|
||||
|
||||
var bindings = getParameterNames(params);
|
||||
|
||||
var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope();
|
||||
|
||||
return symbolTable.enterLambda(
|
||||
bindings,
|
||||
descriptorBuilder,
|
||||
scope -> {
|
||||
var exprNode = visitExpr(expr.getExpr());
|
||||
@@ -1168,7 +1307,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
@Override
|
||||
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 =
|
||||
doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList());
|
||||
var isFrameStored =
|
||||
@@ -1197,7 +1338,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
@Override
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -1210,8 +1351,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
? new GeneratorMemberNode[0]
|
||||
: doVisitForWhenBody(member.getElseClause());
|
||||
|
||||
return new GeneratorWhenNode(
|
||||
sourceSection, visitExpr(member.getPredicate()), thenNodes, elseNodes);
|
||||
// when predicates cannot see their direct scope
|
||||
var predicateNode =
|
||||
symbolTable.enterEagerGenerator((scope) -> visitExpr(member.getPredicate()));
|
||||
return new GeneratorWhenNode(sourceSection, predicateNode, thenNodes, elseNodes);
|
||||
}
|
||||
|
||||
private GeneratorMemberNode[] doVisitForWhenBody(ObjectBody body) {
|
||||
@@ -1233,6 +1376,16 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
TypedIdentifier valueTypedIdentifier = null;
|
||||
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 =
|
||||
keyTypedIdentifier == null
|
||||
? null
|
||||
@@ -1249,16 +1402,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
var currentScope = symbolTable.getCurrentScope();
|
||||
var generatorDescriptorBuilder = currentScope.newFrameDescriptorBuilder();
|
||||
var memberDescriptorBuilder = currentScope.newForGeneratorMemberDescriptorBuilder();
|
||||
var keySlot = -1;
|
||||
var valueSlot = -1;
|
||||
if (keyIdentifier != null) {
|
||||
keySlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
|
||||
memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
|
||||
}
|
||||
if (valueIdentifier != null) {
|
||||
valueSlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
|
||||
memberDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
|
||||
}
|
||||
var unresolvedKeyTypeNode =
|
||||
keyTypedIdentifier == null
|
||||
@@ -1280,12 +1430,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
|
||||
.initWriteSlotNode(valueSlot)
|
||||
: null;
|
||||
var iterableNode = visitExpr(ctx.getExpr());
|
||||
var iterableNode = symbolTable.enterEagerGenerator(scope -> visitExpr(ctx.getExpr()));
|
||||
var memberNodes =
|
||||
symbolTable.enterForGenerator(
|
||||
generatorDescriptorBuilder,
|
||||
memberDescriptorBuilder,
|
||||
scope -> doVisitForWhenBody(ctx.getBody()));
|
||||
params, generatorDescriptorBuilder, scope -> doVisitForWhenBody(ctx.getBody()));
|
||||
return GeneratorForNodeGen.create(
|
||||
createSourceSection(ctx),
|
||||
generatorDescriptorBuilder.build(),
|
||||
@@ -1300,12 +1448,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
@Override
|
||||
public PklRootNode visitModule(Module mod) {
|
||||
var moduleDecl = mod.getDecl();
|
||||
|
||||
var annotationNodes =
|
||||
moduleDecl != null
|
||||
? doVisitAnnotations(moduleDecl.getAnnotations())
|
||||
: new ExpressionNode[] {};
|
||||
|
||||
int modifiers;
|
||||
if (moduleDecl == null) {
|
||||
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 supermoduleNode =
|
||||
@@ -1344,7 +1503,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
new UnresolvedTypeNode.Declared(supermoduleNode.getSourceSection(), supermoduleNode);
|
||||
var moduleProperties =
|
||||
doVisitModuleProperties(
|
||||
mod.getImports(),
|
||||
importMembers,
|
||||
mod.getClasses(),
|
||||
mod.getTypeAliases(),
|
||||
List.of(),
|
||||
@@ -1374,7 +1533,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
var moduleProperties =
|
||||
doVisitModuleProperties(
|
||||
mod.getImports(),
|
||||
importMembers,
|
||||
mod.getClasses(),
|
||||
mod.getTypeAliases(),
|
||||
mod.getProperties(),
|
||||
@@ -1411,18 +1570,17 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
|
||||
private EconomicMap<Object, ObjectMember> doVisitModuleProperties(
|
||||
List<ImportClause> imports,
|
||||
ObjectMember[] imports,
|
||||
List<Class> classes,
|
||||
List<TypeAlias> typeAliases,
|
||||
List<ClassProperty> properties,
|
||||
Set<String> propertyNames,
|
||||
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);
|
||||
|
||||
for (var _import : imports) {
|
||||
var member = visitImportClause(_import);
|
||||
for (var member : imports) {
|
||||
checkDuplicateMember(member.getName(), member.getHeaderSection(), propertyNames);
|
||||
EconomicMaps.put(result, member.getName(), member);
|
||||
}
|
||||
@@ -1492,10 +1650,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
importName,
|
||||
ConstLevel.NONE,
|
||||
scope -> {
|
||||
var modifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST;
|
||||
if (imp.isGlob()) {
|
||||
modifiers = modifiers | VmModifier.GLOB;
|
||||
}
|
||||
var baseModifiers = VmModifier.IMPORT | VmModifier.LOCAL | VmModifier.CONST;
|
||||
var modifiers = imp.isGlob() ? baseModifiers | VmModifier.GLOB : baseModifiers;
|
||||
var result =
|
||||
new ObjectMember(
|
||||
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
|
||||
public ObjectMember visitClass(Class clazz) {
|
||||
var sourceSection = createSourceSection(clazz);
|
||||
var headerSection = createSourceSection(clazz.getHeaderSpan());
|
||||
|
||||
var bodyNode = clazz.getBody();
|
||||
|
||||
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 =
|
||||
doVisitModifiers(
|
||||
clazz.getModifiers(), VmModifier.VALID_CLASS_MODIFIERS, "invalidClassModifier")
|
||||
| VmModifier.CLASS;
|
||||
|
||||
var isLocalClass = VmModifier.isLocal(modifiers);
|
||||
|
||||
var className =
|
||||
org.pkl.core.runtime.Identifier.property(
|
||||
clazz.getName().getValue(), VmModifier.isLocal(modifiers));
|
||||
org.pkl.core.runtime.Identifier.property(clazz.getName().getValue(), isLocalClass);
|
||||
|
||||
var annotations = doVisitAnnotations(clazz.getAnnotations(), className);
|
||||
|
||||
return symbolTable.enterClass(
|
||||
className,
|
||||
modifiers,
|
||||
typeParameters,
|
||||
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();
|
||||
|
||||
// needs to be inside `enterClass` so that class' type parameters are in scope
|
||||
@@ -1572,7 +1749,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
sourceSection,
|
||||
headerSection,
|
||||
createDocSourceSection(clazz.getDocComment()),
|
||||
doVisitAnnotations(clazz.getAnnotations()),
|
||||
annotations,
|
||||
modifiers,
|
||||
classInfo,
|
||||
typeParameters,
|
||||
@@ -1582,13 +1759,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
doVisitClassProperties(properties, propertyNames),
|
||||
doVisitMethodDefs(methods));
|
||||
|
||||
var isLocal = VmModifier.isLocal(modifiers);
|
||||
|
||||
var result =
|
||||
new ObjectMember(
|
||||
sourceSection,
|
||||
headerSection,
|
||||
isLocal ? VmModifier.LOCAL_CLASS_OBJECT_MEMBER : VmModifier.CLASS_OBJECT_MEMBER,
|
||||
isLocalClass
|
||||
? VmModifier.LOCAL_CLASS_OBJECT_MEMBER
|
||||
: VmModifier.CLASS_OBJECT_MEMBER,
|
||||
scope.getName(),
|
||||
scope.getQualifiedName());
|
||||
|
||||
@@ -1611,6 +1788,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
@Override
|
||||
public Integer visitModifier(Modifier modifier) {
|
||||
return toModifier(modifier);
|
||||
}
|
||||
|
||||
private int toModifier(Modifier modifier) {
|
||||
return switch (modifier.getValue()) {
|
||||
case EXTERNAL -> VmModifier.EXTERNAL;
|
||||
case ABSTRACT -> VmModifier.ABSTRACT;
|
||||
@@ -1658,7 +1839,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
var expr = entry.getExpr();
|
||||
var objectBodies = entry.getBodyList();
|
||||
var docComment = createDocSourceSection(docCom);
|
||||
var annotationNodes = doVisitAnnotations(annotations);
|
||||
var sourceSection = createSourceSection(entry);
|
||||
var headerStart = !modifierList.isEmpty() ? modifierList.get(0).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 propertyName = org.pkl.core.runtime.Identifier.property(name.getValue(), isLocal);
|
||||
var annotationNodes = doVisitAnnotations(annotations, propertyName);
|
||||
|
||||
return symbolTable.enterProperty(
|
||||
propertyName,
|
||||
@@ -1760,9 +1941,14 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx);
|
||||
var paramCount = paramListCtx.getParameters().size();
|
||||
|
||||
var bindings = getParameterNames(paramListCtx);
|
||||
|
||||
var annotations = doVisitAnnotations(entry.getAnnotations(), methodName);
|
||||
|
||||
return symbolTable.enterMethod(
|
||||
methodName,
|
||||
getConstLevel(modifiers),
|
||||
bindings,
|
||||
descriptorBuilder,
|
||||
typeParameters,
|
||||
scope -> {
|
||||
@@ -1806,7 +1992,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
headerSection,
|
||||
scope.buildFrameDescriptor(),
|
||||
createDocSourceSection(entry.getDocComment()),
|
||||
doVisitAnnotations(entry.getAnnotations()),
|
||||
annotations,
|
||||
modifiers,
|
||||
methodName,
|
||||
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 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(
|
||||
name,
|
||||
@@ -1846,7 +2036,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
sourceSection,
|
||||
headerSection,
|
||||
createDocSourceSection(typeAlias.getDocComment()),
|
||||
doVisitAnnotations(typeAlias.getAnnotations()),
|
||||
annotations,
|
||||
modifiers,
|
||||
scopeName.toString(),
|
||||
scope.getQualifiedName(),
|
||||
@@ -1857,9 +2047,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
new ObjectMember(
|
||||
sourceSection,
|
||||
headerSection,
|
||||
isLocal
|
||||
? VmModifier.LOCAL_TYPEALIAS_OBJECT_MEMBER
|
||||
: VmModifier.TYPEALIAS_OBJECT_MEMBER,
|
||||
objectMemberModifiers,
|
||||
scopeName,
|
||||
scope.getQualifiedName());
|
||||
|
||||
@@ -1871,8 +2059,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionNode visitAnnotation(Annotation annotation) {
|
||||
public ExpressionNode visitAnnotation(
|
||||
Annotation annotation, org.pkl.core.runtime.@Nullable Identifier annotatedMemberName) {
|
||||
var verifyNode = new CheckIsAnnotationClassNode(visitType(annotation.getType()));
|
||||
|
||||
var bodyCtx = annotation.getBody();
|
||||
@@ -1890,13 +2078,16 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
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()];
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodes[i] = visitAnnotation(annotations.get(i));
|
||||
nodes[i] = visitAnnotation(annotations.get(i), annotatedMemberName);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
@@ -2004,9 +2195,33 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
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) {
|
||||
return symbolTable.enterObjectScope(
|
||||
body,
|
||||
(scope) -> {
|
||||
addObjectNamesToScope(scope, body);
|
||||
var objectMembers = body.getMembers();
|
||||
if (objectMembers.isEmpty()) {
|
||||
return EmptyObjectLiteralNodeGen.create(
|
||||
@@ -2290,8 +2505,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
}
|
||||
|
||||
private Pair<ExpressionNode, ObjectMember> doVisitObjectEntry(ObjectEntry entry) {
|
||||
var keyNode = visitExpr(entry.getKey());
|
||||
|
||||
var keyNode = symbolTable.enterEagerGenerator((scp) -> visitExpr(entry.getKey()));
|
||||
var member =
|
||||
doVisitObjectEntryBody(
|
||||
createSourceSection(entry), keyNode, entry.getValue(), entry.getBodyList());
|
||||
@@ -2371,9 +2585,12 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
|
||||
var frameDescriptorBuilder = createFrameDescriptorBuilder(paramList);
|
||||
|
||||
var bindings = getParameterNames(paramList);
|
||||
|
||||
return symbolTable.enterMethod(
|
||||
methodName,
|
||||
getConstLevel(modifiers),
|
||||
bindings,
|
||||
frameDescriptorBuilder,
|
||||
List.of(),
|
||||
scope -> {
|
||||
@@ -2590,7 +2807,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
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()];
|
||||
for (int i = 0; i < typeNodes.length; i++) {
|
||||
if (params.get(i) instanceof TypedIdentifier typedIdentifier) {
|
||||
@@ -2683,8 +2900,17 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
return needsConst;
|
||||
}
|
||||
|
||||
private FrameDescriptor.Builder createFrameDescriptorBuilder(ParameterList params) {
|
||||
var builder = FrameDescriptor.newBuilder(params.getParameters().size());
|
||||
private static List<String> getParameterNames(ParameterList parameterList) {
|
||||
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()) {
|
||||
org.pkl.core.runtime.Identifier identifier = null;
|
||||
if (param instanceof TypedIdentifier typedIdentifier) {
|
||||
@@ -2757,18 +2983,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
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) {
|
||||
URI parsedUri;
|
||||
try {
|
||||
@@ -2850,4 +3064,47 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
||||
private static SourceSection 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;
|
||||
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||
import com.oracle.truffle.api.frame.FrameDescriptor.Builder;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.pkl.core.TypeParameter;
|
||||
import org.pkl.core.ast.ConstantNode;
|
||||
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.runtime.BaseModuleMembers;
|
||||
import org.pkl.core.runtime.FrameDescriptorBuilder;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.ModuleInfo;
|
||||
import org.pkl.core.runtime.VmDataSize;
|
||||
import org.pkl.core.runtime.VmDuration;
|
||||
import org.pkl.core.util.LateInit;
|
||||
import org.pkl.parser.Lexer;
|
||||
import org.pkl.parser.syntax.ObjectBody;
|
||||
|
||||
public final class SymbolTable {
|
||||
|
||||
private Scope currentScope;
|
||||
|
||||
public SymbolTable(ModuleInfo moduleInfo) {
|
||||
currentScope = new ModuleScope(moduleInfo);
|
||||
public SymbolTable(ModuleInfo moduleInfo, boolean isBaseModule) {
|
||||
currentScope = new ModuleScope(moduleInfo, isBaseModule);
|
||||
}
|
||||
|
||||
public Scope getCurrentScope() {
|
||||
@@ -53,6 +62,7 @@ public final class SymbolTable {
|
||||
|
||||
public ObjectMember enterClass(
|
||||
Identifier name,
|
||||
int modifiers,
|
||||
List<TypeParameter> typeParameters,
|
||||
Function<ClassScope, ObjectMember> nodeFactory) {
|
||||
return doEnter(
|
||||
@@ -60,7 +70,8 @@ public final class SymbolTable {
|
||||
currentScope,
|
||||
name,
|
||||
toQualifiedName(name),
|
||||
FrameDescriptor.newBuilder(),
|
||||
modifiers,
|
||||
new FrameDescriptorBuilder(),
|
||||
typeParameters),
|
||||
nodeFactory);
|
||||
}
|
||||
@@ -74,7 +85,7 @@ public final class SymbolTable {
|
||||
currentScope,
|
||||
name,
|
||||
toQualifiedName(name),
|
||||
FrameDescriptor.newBuilder(),
|
||||
new FrameDescriptorBuilder(),
|
||||
typeParameters),
|
||||
nodeFactory);
|
||||
}
|
||||
@@ -82,7 +93,8 @@ public final class SymbolTable {
|
||||
public <T> T enterMethod(
|
||||
Identifier name,
|
||||
ConstLevel constLevel,
|
||||
Builder frameDescriptorBuilder,
|
||||
List<String> bindings,
|
||||
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||
List<TypeParameter> typeParameters,
|
||||
Function<MethodScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
@@ -91,26 +103,30 @@ public final class SymbolTable {
|
||||
name,
|
||||
toQualifiedName(name),
|
||||
constLevel,
|
||||
bindings,
|
||||
frameDescriptorBuilder,
|
||||
typeParameters),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterEagerGenerator(Function<EagerGeneratorScope, T> nodeFactory) {
|
||||
return doEnter(new EagerGeneratorScope(currentScope, currentScope.qualifiedName), nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterForGenerator(
|
||||
FrameDescriptor.Builder frameDescriptorBuilder,
|
||||
FrameDescriptor.Builder memberDescriptorBuilder,
|
||||
List<String> params,
|
||||
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||
Function<ForGeneratorScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new ForGeneratorScope(
|
||||
currentScope,
|
||||
currentScope.qualifiedName,
|
||||
frameDescriptorBuilder,
|
||||
memberDescriptorBuilder),
|
||||
currentScope, currentScope.qualifiedName, params, frameDescriptorBuilder),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
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
|
||||
var parentScope = currentScope;
|
||||
@@ -122,14 +138,15 @@ public final class SymbolTable {
|
||||
var qualifiedName = parentScope.qualifiedName + "." + parentScope.getNextLambdaName();
|
||||
|
||||
return doEnter(
|
||||
new LambdaScope(currentScope, qualifiedName, frameDescriptorBuilder), nodeFactory);
|
||||
new LambdaScope(currentScope, bindings, qualifiedName, frameDescriptorBuilder),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterProperty(
|
||||
Identifier name, ConstLevel constLevel, Function<PropertyScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new PropertyScope(
|
||||
currentScope, name, toQualifiedName(name), constLevel, FrameDescriptor.newBuilder()),
|
||||
currentScope, name, toQualifiedName(name), constLevel, new FrameDescriptorBuilder()),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
@@ -140,8 +157,8 @@ public final class SymbolTable {
|
||||
var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode);
|
||||
var builder =
|
||||
currentScope instanceof ForGeneratorScope forScope
|
||||
? forScope.memberDescriptorBuilder
|
||||
: FrameDescriptor.newBuilder();
|
||||
? forScope.frameDescriptorBuilder
|
||||
: new FrameDescriptorBuilder();
|
||||
return doEnter(new EntryScope(currentScope, qualifiedName, builder), nodeFactory);
|
||||
}
|
||||
|
||||
@@ -150,13 +167,20 @@ public final class SymbolTable {
|
||||
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(
|
||||
new AnnotationScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||
new AnnotationScope(currentScope, qualifiedName, currentScope.frameDescriptorBuilder),
|
||||
nodeFactory);
|
||||
}
|
||||
|
||||
public <T> T enterObjectScope(Function<ObjectScope, T> nodeFactory) {
|
||||
return doEnter(new ObjectScope(currentScope, currentScope.frameDescriptorBuilder), nodeFactory);
|
||||
public <T> T enterObjectScope(ObjectBody body, Function<ObjectScope, T> nodeFactory) {
|
||||
return doEnter(
|
||||
new ObjectScope(currentScope, body, currentScope.frameDescriptorBuilder), 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());
|
||||
}
|
||||
|
||||
public record Member(Identifier name, int modifiers) {}
|
||||
|
||||
public abstract static class Scope {
|
||||
private final @Nullable Scope parent;
|
||||
private final @Nullable Identifier name;
|
||||
private final String qualifiedName;
|
||||
private int lambdaCount = 0;
|
||||
private int entryCount = 0;
|
||||
private final FrameDescriptor.Builder frameDescriptorBuilder;
|
||||
protected final FrameDescriptorBuilder frameDescriptorBuilder;
|
||||
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(
|
||||
@Nullable Scope parent,
|
||||
@Nullable Identifier name,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.qualifiedName = qualifiedName;
|
||||
this.frameDescriptorBuilder = frameDescriptorBuilder;
|
||||
if (parent != null) {
|
||||
this.isBaseModule = parent.isBaseModule;
|
||||
}
|
||||
// const level can never decrease
|
||||
this.constLevel =
|
||||
parent != null && parent.constLevel.biggerOrEquals(constLevel)
|
||||
@@ -225,24 +259,8 @@ public final class SymbolTable {
|
||||
* Returns a new descriptor builder that contains the same slots as the current scope's frame
|
||||
* descriptor.
|
||||
*/
|
||||
public FrameDescriptor.Builder newFrameDescriptorBuilder() {
|
||||
return newFrameDescriptorBuilder(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 FrameDescriptorBuilder newFrameDescriptorBuilder() {
|
||||
return new FrameDescriptorBuilder(buildFrameDescriptor());
|
||||
}
|
||||
|
||||
public @Nullable TypeParameter getTypeParameter(String name) {
|
||||
@@ -276,7 +294,10 @@ public final class SymbolTable {
|
||||
var depth = -1;
|
||||
var lexicalScope = getLexicalScope();
|
||||
while (lexicalScope.getConstLevel() == ConstLevel.ALL) {
|
||||
// LambdaScope inherits constLevel but doesn't create a const scope barrier
|
||||
if (!(lexicalScope instanceof LambdaScope)) {
|
||||
depth += 1;
|
||||
}
|
||||
var parent = lexicalScope.getParent();
|
||||
if (parent == null) {
|
||||
return depth;
|
||||
@@ -354,18 +375,161 @@ public final class SymbolTable {
|
||||
public ConstLevel getConstLevel() {
|
||||
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 {
|
||||
private ObjectScope(Scope parent, Builder frameDescriptorBuilder) {
|
||||
private final Map<String, Integer> params;
|
||||
|
||||
private ObjectScope(
|
||||
Scope parent, ObjectBody body, FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||
super(
|
||||
parent,
|
||||
parent.getNameOrNull(),
|
||||
parent.getQualifiedName(),
|
||||
ConstLevel.NONE,
|
||||
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,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
Builder frameDescriptorBuilder,
|
||||
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||
List<TypeParameter> typeParameters) {
|
||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
||||
this.typeParameters = typeParameters;
|
||||
@@ -393,47 +557,112 @@ public final class SymbolTable {
|
||||
}
|
||||
|
||||
public static final class ModuleScope extends Scope implements LexicalScope {
|
||||
|
||||
private final ModuleInfo moduleInfo;
|
||||
@LateInit private boolean isClosed;
|
||||
private final boolean isAmend;
|
||||
|
||||
public ModuleScope(ModuleInfo moduleInfo) {
|
||||
super(null, null, moduleInfo.getModuleName(), ConstLevel.NONE, FrameDescriptor.newBuilder());
|
||||
public ModuleScope(ModuleInfo moduleInfo, boolean isBaseModule) {
|
||||
super(null, null, moduleInfo.getModuleName(), ConstLevel.NONE, new FrameDescriptorBuilder());
|
||||
this.isBaseModule = isBaseModule;
|
||||
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(
|
||||
Scope parent,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
Builder frameDescriptorBuilder,
|
||||
List<String> bindings,
|
||||
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||
List<TypeParameter> 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 {
|
||||
private final List<String> bindings;
|
||||
|
||||
public LambdaScope(
|
||||
Scope parent, String qualifiedName, FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
||||
Scope parent,
|
||||
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 {
|
||||
private final FrameDescriptor.Builder memberDescriptorBuilder;
|
||||
final List<String> params;
|
||||
|
||||
public ForGeneratorScope(
|
||||
Scope parent,
|
||||
String qualifiedName,
|
||||
FrameDescriptor.Builder frameDescriptorBuilder,
|
||||
FrameDescriptor.Builder memberDescriptorBuilder) {
|
||||
List<String> params,
|
||||
FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
||||
this.memberDescriptorBuilder = memberDescriptorBuilder;
|
||||
}
|
||||
|
||||
public FrameDescriptor buildMemberDescriptor() {
|
||||
return memberDescriptorBuilder.build();
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -442,6 +671,23 @@ public final class SymbolTable {
|
||||
assert parent != null;
|
||||
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 {
|
||||
@@ -450,26 +696,46 @@ public final class SymbolTable {
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
ConstLevel constLevel,
|
||||
FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class EntryScope extends Scope {
|
||||
public EntryScope(
|
||||
Scope parent, String qualifiedName, FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ClassScope extends TypeParameterizableScope implements LexicalScope {
|
||||
private final boolean isClosed;
|
||||
|
||||
public ClassScope(
|
||||
Scope parent,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
Builder frameDescriptorBuilder,
|
||||
int modifiers,
|
||||
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||
List<TypeParameter> 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,
|
||||
Identifier name,
|
||||
String qualifiedName,
|
||||
FrameDescriptor.Builder frameDescriptorBuilder,
|
||||
FrameDescriptorBuilder frameDescriptorBuilder,
|
||||
List<TypeParameter> 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(
|
||||
parent,
|
||||
parent.getNameOrNull(),
|
||||
@@ -509,14 +775,23 @@ public final class SymbolTable {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class AnnotationScope extends Scope implements LexicalScope {
|
||||
public AnnotationScope(Scope parent, FrameDescriptor.Builder frameDescriptorBuilder) {
|
||||
public static final class AnnotationScope extends Scope {
|
||||
public AnnotationScope(
|
||||
Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) {
|
||||
super(
|
||||
parent,
|
||||
parent.getNameOrNull(),
|
||||
parent.getQualifiedName(),
|
||||
ConstLevel.MODULE,
|
||||
frameDescriptorBuilder);
|
||||
parent, parent.getNameOrNull(), qualifiedName, 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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,14 +33,12 @@ public final class RestoreForBindingsNode extends ExpressionNode {
|
||||
@Override
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
var generatorFrame = ObjectData.getGeneratorFrame(frame);
|
||||
var numSlots = frame.getFrameDescriptor().getNumberOfSlots();
|
||||
// This value is constant and could be a constructor argument.
|
||||
var startSlot = generatorFrame.getFrameDescriptor().getNumberOfSlots() - numSlots;
|
||||
assert startSlot >= 0;
|
||||
// Copy locals that are for-generator variables into this frame.
|
||||
// Slots before `startSlot` (if any) are function arguments
|
||||
// and must not be copied to preserve scoping rules.
|
||||
VmUtils.copyLocals(generatorFrame, startSlot, frame, 0, numSlots);
|
||||
// copying all slots includes function arguments, but the capture generator frame
|
||||
// and the host frame are guaranteed to have the same arguments and number of slots
|
||||
// (guaranteed by AstBuilder).
|
||||
assert frame.getFrameDescriptor().getNumberOfSlots()
|
||||
== generatorFrame.getFrameDescriptor().getNumberOfSlots();
|
||||
VmUtils.copyLocals(generatorFrame, 0, frame, 0, frame.getFrameDescriptor().getNumberOfSlots());
|
||||
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.PklRootNode;
|
||||
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.Lambda;
|
||||
import org.pkl.core.ast.type.TypeNode;
|
||||
@@ -37,11 +37,13 @@ public final class AmendFunctionNode extends PklNode {
|
||||
private final boolean isCustomThisScope;
|
||||
private final PklRootNode initialFunctionRootNode;
|
||||
@CompilationFinal private int customThisSlot = -1;
|
||||
private final boolean hasObjectParams;
|
||||
|
||||
public AmendFunctionNode(ObjectLiteralNode hostNode, TypeNode[] parameterTypeNodes) {
|
||||
super(hostNode.getSourceSection());
|
||||
|
||||
isCustomThisScope = hostNode.isCustomThisScope;
|
||||
hasObjectParams = parameterTypeNodes.length > 0;
|
||||
|
||||
var builder = FrameDescriptor.newBuilder();
|
||||
var hostDescriptor = hostNode.parametersDescriptor;
|
||||
@@ -68,7 +70,7 @@ public final class AmendFunctionNode extends PklNode {
|
||||
new AmendFunctionBodyNode(
|
||||
sourceSection,
|
||||
hostNode.copy(
|
||||
ReadFrameSlotNodeGen.create(
|
||||
ReadExactFrameSlotNodeGen.create(
|
||||
hostNode.getParentNode().getSourceSection(), objectToAmendSlot)),
|
||||
parameterSlots,
|
||||
objectToAmendSlot,
|
||||
@@ -88,7 +90,7 @@ public final class AmendFunctionNode extends PklNode {
|
||||
new AmendFunctionBodyNode(
|
||||
sourceSection,
|
||||
hostNode.copy(
|
||||
ReadFrameSlotNodeGen.create(
|
||||
ReadExactFrameSlotNodeGen.create(
|
||||
hostNode.getParentNode().getSourceSection(), objectToAmendSlot)),
|
||||
parameterSlots,
|
||||
objectToAmendSlot,
|
||||
@@ -108,7 +110,9 @@ public final class AmendFunctionNode extends PklNode {
|
||||
isCustomThisScope ? frame.getAuxiliarySlot(customThisSlot) : VmUtils.getReceiver(frame),
|
||||
functionToAmend.getParameterCount(),
|
||||
initialFunctionRootNode,
|
||||
new Context(functionToAmend, null));
|
||||
new Context(functionToAmend, null),
|
||||
true,
|
||||
hasObjectParams);
|
||||
}
|
||||
|
||||
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");
|
||||
* 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.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 {
|
||||
private final VmObjectLike owner;
|
||||
@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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,64 +16,81 @@
|
||||
package org.pkl.core.ast.expression.member;
|
||||
|
||||
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.nodes.DirectCallNode;
|
||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.PklBugException;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
import org.pkl.core.ast.member.ObjectMember;
|
||||
import org.pkl.core.runtime.Identifier;
|
||||
import org.pkl.core.runtime.VmObjectLike;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
|
||||
/** Reads a local non-constant property that is known to exist in the lexical scope of this node. */
|
||||
public final class ReadLocalPropertyNode extends ExpressionNode {
|
||||
private final ObjectMember property;
|
||||
private final Identifier name;
|
||||
private final int levelsUp;
|
||||
private final boolean needsConst;
|
||||
@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);
|
||||
CompilerAsserts.neverPartOfCompilation();
|
||||
|
||||
this.property = property;
|
||||
this.name = name;
|
||||
this.levelsUp = levelsUp;
|
||||
|
||||
assert property.getNameOrNull() != null;
|
||||
assert property.getConstantValue() == null : "Use a ConstantNode instead.";
|
||||
|
||||
callNode = DirectCallNode.create(property.getCallTarget());
|
||||
this.needsConst = needsConst;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ExplodeLoop
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
var owner = VmUtils.getOwner(frame);
|
||||
Object receiver;
|
||||
|
||||
if (levelsUp == 0) {
|
||||
receiver = VmUtils.getReceiver(frame);
|
||||
} else {
|
||||
for (int i = 1; i < levelsUp; i++) {
|
||||
owner = owner.getEnclosingOwner();
|
||||
assert owner != null;
|
||||
var owner = VmUtils.getOwner(frame, levelsUp);
|
||||
var property = getProperty(owner);
|
||||
var constantValue = property.getConstantValue();
|
||||
if (constantValue != null) {
|
||||
return constantValue;
|
||||
}
|
||||
|
||||
receiver = owner.getEnclosingReceiver();
|
||||
owner = owner.getEnclosingOwner();
|
||||
}
|
||||
|
||||
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);
|
||||
var receiver = (VmObjectLike) VmUtils.getReceiver(frame, levelsUp);
|
||||
var result = receiver.getCachedValue(property);
|
||||
|
||||
if (result == null) {
|
||||
result = callNode.call(objReceiver, owner, property.getName());
|
||||
objReceiver.setCachedValue(property, result);
|
||||
result = getCallNode(property).call(receiver, owner, property.getName());
|
||||
receiver.setCachedValue(property, 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();
|
||||
}
|
||||
|
||||
// only ever need to check once per node because `needsConst` is only true in the case of implicit
|
||||
// receivers inside class (and module) bodies, and the const-ness of a resolved property cannot be
|
||||
// changed by subclasses.
|
||||
// Only ever need to check once per node because `needsConst` is only true in the case of:
|
||||
//
|
||||
// * 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) {
|
||||
if (needsConst && !isConstChecked) {
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
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.
|
||||
return;
|
||||
}
|
||||
if (!property.isConst()) {
|
||||
throw exceptionBuilder().evalError("propertyMustBeConst", propertyName.toString()).build();
|
||||
}
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.pkl.core.ast.expression.primary;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
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";
|
||||
}
|
||||
|
||||
@ExplodeLoop
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
var owner = VmUtils.getOwner(frame);
|
||||
for (var i = 1; i < levelsUp; i++) {
|
||||
owner = owner.getEnclosingOwner();
|
||||
assert owner != null;
|
||||
}
|
||||
var result = owner.getEnclosingOwner();
|
||||
assert result != null;
|
||||
return result;
|
||||
return VmUtils.getOwner(frame, levelsUp);
|
||||
}
|
||||
}
|
||||
|
||||
+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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.pkl.core.ast.expression.primary;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
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";
|
||||
}
|
||||
|
||||
@ExplodeLoop
|
||||
public Object executeGeneric(VirtualFrame frame) {
|
||||
var owner = VmUtils.getOwner(frame);
|
||||
for (var i = 1; i < levelsUp; i++) {
|
||||
owner = owner.getEnclosingOwner();
|
||||
assert owner != null;
|
||||
}
|
||||
var result = owner.getEnclosingReceiver();
|
||||
assert result != null;
|
||||
return result;
|
||||
return VmUtils.getReceiver(frame, levelsUp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
* 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")
|
||||
public final class GetModuleNode extends ExpressionNode {
|
||||
|
||||
public GetModuleNode(SourceSection sourceSection) {
|
||||
super(sourceSection);
|
||||
}
|
||||
@@ -35,8 +36,10 @@ public final class GetModuleNode extends ExpressionNode {
|
||||
for (var current = VmUtils.getOwner(frame).getEnclosingOwner();
|
||||
current != null;
|
||||
current = current.getEnclosingOwner()) {
|
||||
if (!current.isParseTimeInvisibleScope()) {
|
||||
levelsUp += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return replace(levelsUp == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(levelsUp))
|
||||
.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");
|
||||
* 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.frame.FrameSlotTypeException;
|
||||
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
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;
|
||||
|
||||
public abstract class ReadEnclosingFrameSlotNode extends ExpressionNode {
|
||||
public abstract class ReadExactFrameSlotNode extends ExpressionNode {
|
||||
private final int slot;
|
||||
private final int levelsUp;
|
||||
|
||||
protected ReadEnclosingFrameSlotNode(SourceSection sourceSection, int slot, int levelsUp) {
|
||||
protected ReadExactFrameSlotNode(SourceSection sourceSection, int slot) {
|
||||
super(sourceSection);
|
||||
this.slot = slot;
|
||||
this.levelsUp = levelsUp;
|
||||
|
||||
assert levelsUp > 0 : "should be using ReadFrameSlotNode for levelsUp == 0";
|
||||
}
|
||||
|
||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||
protected long evalInt(VirtualFrame frame) throws FrameSlotTypeException {
|
||||
return getCapturedFrame(frame).getLong(slot);
|
||||
return frame.getLong(slot);
|
||||
}
|
||||
|
||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||
protected double evalFloat(VirtualFrame frame) throws FrameSlotTypeException {
|
||||
return getCapturedFrame(frame).getDouble(slot);
|
||||
return frame.getDouble(slot);
|
||||
}
|
||||
|
||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||
protected boolean evalBoolean(VirtualFrame frame) throws FrameSlotTypeException {
|
||||
return getCapturedFrame(frame).getBoolean(slot);
|
||||
return frame.getBoolean(slot);
|
||||
}
|
||||
|
||||
@Specialization(rewriteOn = FrameSlotTypeException.class)
|
||||
protected Object evalObject(VirtualFrame frame) throws FrameSlotTypeException {
|
||||
return getCapturedFrame(frame).getObject(slot);
|
||||
return frame.getObject(slot);
|
||||
}
|
||||
|
||||
@Specialization(replaces = {"evalInt", "evalFloat", "evalBoolean", "evalObject"})
|
||||
protected Object evalGeneric(VirtualFrame frame) {
|
||||
return getCapturedFrame(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();
|
||||
return frame.getValue(slot);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,12 +15,14 @@
|
||||
*/
|
||||
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.frame.FrameSlotTypeException;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.pkl.core.ast.ExpressionNode;
|
||||
|
||||
@NodeChild(value = "enclosingFrame", type = GetEnclosingFrameNode.class)
|
||||
public abstract class ReadFrameSlotNode extends ExpressionNode {
|
||||
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.IoUtils;
|
||||
import org.pkl.core.util.MutableReference;
|
||||
import org.pkl.core.util.Pair;
|
||||
import org.pkl.core.util.SyntaxHighlighter;
|
||||
import org.pkl.parser.Parser;
|
||||
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.ImportClause;
|
||||
import org.pkl.parser.syntax.ModuleDecl;
|
||||
import org.pkl.parser.syntax.Node;
|
||||
import org.pkl.parser.syntax.ReplInput;
|
||||
|
||||
public class ReplServer implements AutoCloseable {
|
||||
private static final String expressionPreamble = "`THE REPL TEXT EXPR` = ";
|
||||
|
||||
private final IndirectCallNode callNode = Truffle.getRuntime().createIndirectCallNode();
|
||||
private final Context polyglotContext;
|
||||
private final VmLanguage language;
|
||||
@@ -72,6 +76,7 @@ public class ReplServer implements AutoCloseable {
|
||||
private final PackageResolver packageResolver;
|
||||
private final @Nullable ProjectDependenciesManager projectDependenciesManager;
|
||||
private final boolean color;
|
||||
private final Parser parser = new Parser();
|
||||
|
||||
public ReplServer(
|
||||
SecurityManager securityManager,
|
||||
@@ -178,7 +183,132 @@ public class ReplServer implements AutoCloseable {
|
||||
.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(
|
||||
ReplState replState,
|
||||
String requestId,
|
||||
@@ -198,53 +328,9 @@ public class ReplServer implements AutoCloseable {
|
||||
}
|
||||
|
||||
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()) {
|
||||
try {
|
||||
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)));
|
||||
}
|
||||
handleNode(replState, results, uri, tree, text, evalDefinitions, forceResults);
|
||||
}
|
||||
|
||||
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 int paramCount;
|
||||
private final PklRootNode rootNode;
|
||||
private final boolean hasObjectParams;
|
||||
private final boolean isFunctionAmend;
|
||||
|
||||
public VmFunction(
|
||||
MaterializedFrame enclosingFrame,
|
||||
@@ -37,11 +39,24 @@ public final class VmFunction extends VmObjectLike {
|
||||
int paramCount,
|
||||
PklRootNode rootNode,
|
||||
@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);
|
||||
this.thisValue = thisValue;
|
||||
this.paramCount = paramCount;
|
||||
this.rootNode = rootNode;
|
||||
this.hasObjectParams = hasObjectParams;
|
||||
this.extraStorage = extraStorage;
|
||||
this.isFunctionAmend = isFunctionAmend;
|
||||
}
|
||||
|
||||
public RootCallTarget getCallTarget() {
|
||||
@@ -71,7 +86,9 @@ public final class VmFunction extends VmObjectLike {
|
||||
thisValue,
|
||||
newParamCount,
|
||||
newRootNode == null ? rootNode : newRootNode,
|
||||
newExtraStorage);
|
||||
newExtraStorage,
|
||||
isFunctionAmend,
|
||||
hasObjectParams);
|
||||
}
|
||||
|
||||
public Object getThisValue() {
|
||||
@@ -187,4 +204,9 @@ public final class VmFunction extends VmObjectLike {
|
||||
public String toString() {
|
||||
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. */
|
||||
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
|
||||
* 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);
|
||||
}
|
||||
|
||||
public static VmObjectLike getObjectReceiver(VirtualFrame frame, int levelsUp) {
|
||||
return (VmObjectLike) getReceiver(frame, levelsUp);
|
||||
}
|
||||
|
||||
public static VmTyped getTypedObjectReceiver(Frame frame) {
|
||||
return (VmTyped) getReceiver(frame);
|
||||
}
|
||||
@@ -159,6 +163,39 @@ public final class VmUtils {
|
||||
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`. */
|
||||
public static Object getMemberKey(Frame frame) {
|
||||
return frame.getArguments()[2];
|
||||
@@ -375,6 +412,7 @@ public final class VmUtils {
|
||||
int numberOfLocalsToCopy) {
|
||||
var sourceDescriptor = sourceFrame.getFrameDescriptor();
|
||||
var targetDescriptor = targetFrame.getFrameDescriptor();
|
||||
assert sourceDescriptor.getNumberOfSlots() <= targetDescriptor.getNumberOfSlots();
|
||||
// Alternatively, locals could be copied with `numberOfLocalsToCopy`
|
||||
// `ReadFrameSlotNode/WriteFrameSlotNode`'s.
|
||||
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");
|
||||
* 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 org.pkl.core.ast.ExpressionNode;
|
||||
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.VmExceptionBuilder;
|
||||
import org.pkl.core.runtime.VmUtils;
|
||||
@@ -115,7 +115,7 @@ public abstract class ExternalMemberRegistry {
|
||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||
|
||||
var sourceSection = VmUtils.unavailableSourceSection();
|
||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||
return factory.create(new GetReceiverNode(), param1Node);
|
||||
}
|
||||
|
||||
@@ -125,8 +125,8 @@ public abstract class ExternalMemberRegistry {
|
||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||
|
||||
var sourceSection = VmUtils.unavailableSourceSection();
|
||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
||||
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||
return factory.create(new GetReceiverNode(), param1Node, param2Node);
|
||||
}
|
||||
|
||||
@@ -136,9 +136,9 @@ public abstract class ExternalMemberRegistry {
|
||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||
|
||||
var sourceSection = VmUtils.unavailableSourceSection();
|
||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
||||
var param3Node = ReadFrameSlotNodeGen.create(sourceSection, 2);
|
||||
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||
var param3Node = ReadExactFrameSlotNodeGen.create(sourceSection, 2);
|
||||
return factory.create(new GetReceiverNode(), param1Node, param2Node, param3Node);
|
||||
}
|
||||
|
||||
@@ -148,10 +148,10 @@ public abstract class ExternalMemberRegistry {
|
||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||
|
||||
var sourceSection = VmUtils.unavailableSourceSection();
|
||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
||||
var param3Node = ReadFrameSlotNodeGen.create(sourceSection, 2);
|
||||
var param4Node = ReadFrameSlotNodeGen.create(sourceSection, 3);
|
||||
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||
var param3Node = ReadExactFrameSlotNodeGen.create(sourceSection, 2);
|
||||
var param4Node = ReadExactFrameSlotNodeGen.create(sourceSection, 3);
|
||||
return factory.create(new GetReceiverNode(), param1Node, param2Node, param3Node, param4Node);
|
||||
}
|
||||
|
||||
@@ -161,11 +161,11 @@ public abstract class ExternalMemberRegistry {
|
||||
if (factory == null) throw cannotFindMemberImpl(qualifiedName, headerSection);
|
||||
|
||||
var sourceSection = VmUtils.unavailableSourceSection();
|
||||
var param1Node = ReadFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadFrameSlotNodeGen.create(sourceSection, 1);
|
||||
var param3Node = ReadFrameSlotNodeGen.create(sourceSection, 2);
|
||||
var param4Node = ReadFrameSlotNodeGen.create(sourceSection, 3);
|
||||
var param5Node = ReadFrameSlotNodeGen.create(sourceSection, 4);
|
||||
var param1Node = ReadExactFrameSlotNodeGen.create(sourceSection, 0);
|
||||
var param2Node = ReadExactFrameSlotNodeGen.create(sourceSection, 1);
|
||||
var param3Node = ReadExactFrameSlotNodeGen.create(sourceSection, 2);
|
||||
var param4Node = ReadExactFrameSlotNodeGen.create(sourceSection, 3);
|
||||
var param5Node = ReadExactFrameSlotNodeGen.create(sourceSection, 4);
|
||||
return factory.create(
|
||||
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")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `strip expression preamble in error message`() {
|
||||
val result = makeFailingEvalRequest("foo")
|
||||
assertThat(result)
|
||||
.isEqualTo(
|
||||
"""
|
||||
–– Pkl Error ––
|
||||
Cannot find property `foo`.
|
||||
|
||||
1 | foo
|
||||
^^^
|
||||
at (repl:id)
|
||||
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
private fun makeEvalRequest(text: String): String {
|
||||
val responses = server.handleRequest(ReplRequest.Eval("id", text, false, false))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user