Improve handling of frame slots (#1634)

This makes various improvements to the handling of frame slot vars, and
includes some bug fixes introduced by
https://github.com/apple/pkl/pull/1622

* Refactor SymbolTable to track for-generator and parameter slots in
each scope
* Execute let expressions in their own root node in some places
* Unify how frame slots are managed; they are all represented as
`FrameSlotVariable`, created in `AstBuilder`, and passed into
`SymbolTable`.
* Fix how let expressions are executed in custom this scopes (introduce
a new root node when needed)
This commit is contained in:
Daniel Chao
2026-06-10 20:02:42 -07:00
committed by GitHub
parent 27fe06c796
commit 84d2c32d10
30 changed files with 1000 additions and 278 deletions
@@ -52,9 +52,10 @@ import org.pkl.core.ast.builder.MethodResolution.ImplicitThisMethod;
import org.pkl.core.ast.builder.MethodResolution.LexicalMethod; import org.pkl.core.ast.builder.MethodResolution.LexicalMethod;
import org.pkl.core.ast.builder.SymbolTable.AnnotationScope; import org.pkl.core.ast.builder.SymbolTable.AnnotationScope;
import org.pkl.core.ast.builder.SymbolTable.ClassScope; import org.pkl.core.ast.builder.SymbolTable.ClassScope;
import org.pkl.core.ast.builder.SymbolTable.LetExpressionScope;
import org.pkl.core.ast.builder.SymbolTable.ModuleScope; import org.pkl.core.ast.builder.SymbolTable.ModuleScope;
import org.pkl.core.ast.builder.SymbolTable.ObjectScope; import org.pkl.core.ast.builder.SymbolTable.ObjectScope;
import org.pkl.core.ast.builder.VariableResolution.ForGeneratorOrLetVariable; import org.pkl.core.ast.builder.VariableResolution.ForGeneratorVariableOrLetBinding;
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty; import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
import org.pkl.core.ast.builder.VariableResolution.ImplicitThisProperty; import org.pkl.core.ast.builder.VariableResolution.ImplicitThisProperty;
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty; import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
@@ -121,6 +122,7 @@ import org.pkl.core.ast.expression.member.ReadLocalPropertyNode;
import org.pkl.core.ast.expression.member.ReadPropertyNodeGen; import org.pkl.core.ast.expression.member.ReadPropertyNodeGen;
import org.pkl.core.ast.expression.member.ReadSuperEntryNode; import org.pkl.core.ast.expression.member.ReadSuperEntryNode;
import org.pkl.core.ast.expression.member.ReadSuperPropertyNode; import org.pkl.core.ast.expression.member.ReadSuperPropertyNode;
import org.pkl.core.ast.expression.primary.ExecuteCustomThisWithRootNode;
import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode; import org.pkl.core.ast.expression.primary.GetEnclosingReceiverNode;
import org.pkl.core.ast.expression.primary.GetMemberKeyNode; import org.pkl.core.ast.expression.primary.GetMemberKeyNode;
import org.pkl.core.ast.expression.primary.GetModuleNode; import org.pkl.core.ast.expression.primary.GetModuleNode;
@@ -179,6 +181,7 @@ import org.pkl.core.module.ResolvedModuleKey;
import org.pkl.core.packages.PackageLoadError; import org.pkl.core.packages.PackageLoadError;
import org.pkl.core.runtime.BaseModule; import org.pkl.core.runtime.BaseModule;
import org.pkl.core.runtime.FrameDescriptorBuilder; import org.pkl.core.runtime.FrameDescriptorBuilder;
import org.pkl.core.runtime.FrameSlotVariable;
import org.pkl.core.runtime.ModuleInfo; import org.pkl.core.runtime.ModuleInfo;
import org.pkl.core.runtime.ModuleResolver; import org.pkl.core.runtime.ModuleResolver;
import org.pkl.core.runtime.VmBytes; import org.pkl.core.runtime.VmBytes;
@@ -417,14 +420,33 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
scope -> { scope -> {
var exprs = type.getExprs(); var exprs = type.getExprs();
var constraints = new TypeConstraintNode[exprs.size()]; var constraints = new TypeConstraintNode[exprs.size()];
for (int i = 0; i < constraints.length; i++) { for (var i = 0; i < constraints.length; i++) {
var currentFrameDescriptorSize = scope.frameDescriptorBuilder.getSize();
var expr = visitExpr(exprs.get(i)); var expr = visitExpr(exprs.get(i));
var writesFrameSlotVars =
scope.frameDescriptorBuilder.getSize() > currentFrameDescriptorSize;
// if a constraint expression writes to frame slots (only known case: is a `let` expr),
// create a new root node and execute the constraint within this root node
// e.g. String(let (x = this) x.length > 5)
if (writesFrameSlotVars) {
expr = getExprWithinCustomThis(scope, expr);
}
constraints[i] = TypeConstraintNodeGen.create(expr.getSourceSection(), expr); constraints[i] = TypeConstraintNodeGen.create(expr.getSourceSection(), expr);
} }
return new Constrained(createSourceSection(type), language, childNode, constraints); return new Constrained(createSourceSection(type), language, childNode, constraints);
}); });
} }
private ExpressionNode getExprWithinCustomThis(SymbolTable.Scope scope, ExpressionNode expr) {
return new ExecuteCustomThisWithRootNode(
expr.getSourceSection(),
expr,
scope.frameDescriptorBuilder.build(),
scope.getQualifiedName(),
scope.forGeneratorSlots,
scope.parameterSlots);
}
@Override @Override
public UnresolvedTypeNode visitUnionType(UnionType type) { public UnresolvedTypeNode visitUnionType(UnionType type) {
var elementTypes = type.getTypes(); var elementTypes = type.getTypes();
@@ -682,13 +704,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
MemberLookupMode.IMPLICIT_LEXICAL, MemberLookupMode.IMPLICIT_LEXICAL,
needsConst, needsConst,
p.levelsUp() == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(p.levelsUp())); p.levelsUp() == 0 ? new GetReceiverNode() : new GetEnclosingReceiverNode(p.levelsUp()));
} else if (resolution instanceof ForGeneratorOrLetVariable p) { } else if (resolution instanceof ForGeneratorVariableOrLetBinding p) {
// Parameters can possibly write to frame slots actually in a frame that is one level // 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, let exprs and for generator variables // higher than what we can tell at parse time. However, let exprs and for generator variables
// always write to frame slots in the same frame. // always write to frame slots in the same frame.
// //
// function foo(bar) = new Mixin { // function foo(bar) = new Mixin {
// [bar] = 1 <--- actually 1 level, not 0 // res = bar <--- actually 1 level, not 0
// for (elem in qux) { // for (elem in qux) {
// elem <--- actually 0 level // elem <--- actually 0 level
// } // }
@@ -1096,26 +1118,30 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var sourceSection = createSourceSection(letExpr); var sourceSection = createSourceSection(letExpr);
var parameter = letExpr.getParameter(); var parameter = letExpr.getParameter();
UnresolvedTypeNode typeNode = null; UnresolvedTypeNode typeNode = null;
String binding = null; @Nullable FrameSlotVariable binding = null;
var slot = -1;
var frameDescriptorBuilder = symbolTable.getCurrentScope().frameDescriptorBuilder; var frameDescriptorBuilder = symbolTable.getCurrentScope().frameDescriptorBuilder;
if (parameter instanceof TypedIdentifier par) { if (parameter instanceof TypedIdentifier par) {
typeNode = visitTypeAnnotation(par.getTypeAnnotation()); typeNode = visitTypeAnnotation(par.getTypeAnnotation());
slot = binding =
frameDescriptorBuilder.addSlot( frameDescriptorBuilder.addSlot(
FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null); FrameSlotKind.Illegal,
binding = par.getIdentifier().getValue(); toIdentifier(par.getIdentifier().getValue()),
LetExpressionScope.LET_BINDING_SLOT);
} }
var bindingExpr = visitExpr(letExpr.getBindingExpr()); var bindingExpr = visitExpr(letExpr.getBindingExpr());
var t = typeNode; var t = typeNode;
var s = slot; var b = binding;
return symbolTable.enterLetExpression( return symbolTable.enterLetExpression(
binding, binding,
slot,
scope -> { scope -> {
var bodyExpr = visitExpr(letExpr.getExpr()); var bodyExpr = visitExpr(letExpr.getExpr());
return LetExprNodeGen.create( return LetExprNodeGen.create(
sourceSection, scope.getQualifiedName(), t, bodyExpr, s, bindingExpr); sourceSection,
scope.getQualifiedName(),
t,
bodyExpr,
b == null ? -1 : b.slot(),
bindingExpr);
}); });
} }
@@ -1123,9 +1149,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
public ExpressionNode visitFunctionLiteralExpr(FunctionLiteralExpr expr) { public ExpressionNode visitFunctionLiteralExpr(FunctionLiteralExpr expr) {
var sourceSection = createSourceSection(expr); var sourceSection = createSourceSection(expr);
var params = expr.getParameterList(); var params = expr.getParameterList();
var descriptorBuilder = createFrameDescriptorBuilder(params); var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(params);
var paramCount = params.getParameters().size(); var paramCount = params.getParameters().size();
var descriptorBuilder = descriptorBuilderAndBindings.first;
var bindings = descriptorBuilderAndBindings.second;
if (paramCount > 5) { if (paramCount > 5) {
throw exceptionBuilder() throw exceptionBuilder()
.evalError("tooManyFunctionParameters") .evalError("tooManyFunctionParameters")
@@ -1133,8 +1160,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
.build(); .build();
} }
var bindings = getParameterNames(params);
var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope();
return symbolTable.enterLambda( return symbolTable.enterLambda(
@@ -1281,7 +1306,18 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) { public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) {
var keyNode = var keyNode =
symbolTable.enterEagerGenerator( symbolTable.enterEagerGenerator(
(scp) -> symbolTable.enterCustomThisScope(scope -> visitExpr(ctx.getPred()))); (scp) ->
symbolTable.enterCustomThisScope(
scope -> {
var currentFrameDescriptorSize = scope.frameDescriptorBuilder.getSize();
var expr = visitExpr(ctx.getPred());
var writesFrameSlotVars =
scope.frameDescriptorBuilder.getSize() > currentFrameDescriptorSize;
if (writesFrameSlotVars) {
return getExprWithinCustomThis(scope, expr);
}
return expr;
}));
var member = var member =
doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList()); doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList());
var isFrameStored = var isFrameStored =
@@ -1339,82 +1375,81 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return doVisitGeneratorMemberNodes(body.getMembers()); return doVisitGeneratorMemberNodes(body.getMembers());
} }
private @Nullable FrameSlotVariable makeBinding(
org.pkl.parser.syntax.@Nullable Parameter parameter) {
if (!(parameter instanceof TypedIdentifier typedIdentifier)) {
return null;
}
var name = typedIdentifier.getIdentifier().getValue();
return symbolTable
.getCurrentScope()
.frameDescriptorBuilder
.addSlot(FrameSlotKind.Illegal, toIdentifier(name), null);
}
@Override @Override
public GeneratorMemberNode visitForGenerator(ForGenerator ctx) { public GeneratorMemberNode visitForGenerator(ForGenerator ctx) {
var keyParameter = ctx.getP2() == null ? null : ctx.getP1(); var keyParameter = ctx.getP2() == null ? null : ctx.getP1();
var valueParameter = ctx.getP2() == null ? ctx.getP1() : ctx.getP2(); var valueParameter = ctx.getP2() == null ? ctx.getP1() : ctx.getP2();
TypedIdentifier keyTypedIdentifier = null; var keyBinding = makeBinding(keyParameter);
if (keyParameter instanceof TypedIdentifier ti) keyTypedIdentifier = ti; var valueBinding = makeBinding(valueParameter);
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 = var keyIdentifier =
keyTypedIdentifier == null keyParameter instanceof TypedIdentifier keyTypedIdentifier ? keyTypedIdentifier : null;
? null
: toIdentifier(keyTypedIdentifier.getIdentifier().getValue());
var valueIdentifier = var valueIdentifier =
valueTypedIdentifier == null valueParameter instanceof TypedIdentifier valueTypedIdentifier
? null ? valueTypedIdentifier
: toIdentifier(valueTypedIdentifier.getIdentifier().getValue()); : null;
if (valueIdentifier != null && valueIdentifier == keyIdentifier) { if (keyIdentifier != null
&& valueIdentifier != null
&& keyIdentifier
.getIdentifier()
.getValue()
.equals(valueIdentifier.getIdentifier().getValue())) {
throw exceptionBuilder() throw exceptionBuilder()
.evalError("duplicateDefinition", valueIdentifier) .evalError("duplicateDefinition", valueIdentifier.getIdentifier().getValue())
.withSourceSection(createSourceSection(valueTypedIdentifier.getIdentifier())) .withSourceSection(createSourceSection(valueIdentifier))
.build(); .build();
} }
var currentScope = symbolTable.getCurrentScope();
var generatorDescriptorBuilder = currentScope.newFrameDescriptorBuilder();
var keySlot = -1;
var valueSlot = -1;
if (keyIdentifier != null) {
keySlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, keyIdentifier, null);
}
if (valueIdentifier != null) {
valueSlot = generatorDescriptorBuilder.addSlot(FrameSlotKind.Illegal, valueIdentifier, null);
}
var unresolvedKeyTypeNode = var unresolvedKeyTypeNode =
keyTypedIdentifier == null keyIdentifier == null ? null : visitTypeAnnotation(keyIdentifier.getTypeAnnotation());
? null
: visitTypeAnnotation(keyTypedIdentifier.getTypeAnnotation());
var unresolvedValueTypeNode = var unresolvedValueTypeNode =
valueTypedIdentifier == null valueIdentifier == null ? null : visitTypeAnnotation(valueIdentifier.getTypeAnnotation());
? null
: visitTypeAnnotation(valueTypedIdentifier.getTypeAnnotation());
// if possible, initialize immediately to avoid later insert // if possible, initialize immediately to avoid later insert
var keyTypeNode = var keyTypeNode =
unresolvedKeyTypeNode == null && keySlot != -1 unresolvedKeyTypeNode == null && keyBinding != null
? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
.initWriteSlotNode(keySlot) .initWriteSlotNode(keyBinding.slot())
: null; : null;
// if possible, initialize immediately to avoid later insert // if possible, initialize immediately to avoid later insert
var valueTypeNode = var valueTypeNode =
unresolvedValueTypeNode == null && valueSlot != -1 unresolvedValueTypeNode == null && valueBinding != null
? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection())
.initWriteSlotNode(valueSlot) .initWriteSlotNode(valueBinding.slot())
: null; : null;
var iterableNode = symbolTable.enterEagerGenerator(scope -> visitExpr(ctx.getExpr())); var iterableNode = symbolTable.enterEagerGenerator(scope -> visitExpr(ctx.getExpr()));
var memberNodes = var outerScope = symbolTable.getCurrentScope();
symbolTable.enterForGenerator( return symbolTable.enterForGenerator(
params, generatorDescriptorBuilder, scope -> doVisitForWhenBody(ctx.getBody())); keyBinding,
return GeneratorForNodeGen.create( valueBinding,
createSourceSection(ctx), symbolTable.getCurrentScope().frameDescriptorBuilder,
generatorDescriptorBuilder.build(), scope -> {
iterableNode, var memberNodes = doVisitForWhenBody(ctx.getBody());
unresolvedKeyTypeNode, return GeneratorForNodeGen.create(
unresolvedValueTypeNode, createSourceSection(ctx),
memberNodes, scope.frameDescriptorBuilder.build(),
keyTypeNode, iterableNode,
valueTypeNode); unresolvedKeyTypeNode,
unresolvedValueTypeNode,
memberNodes,
keyTypeNode,
valueTypeNode,
keyBinding == null ? -1 : keyBinding.slot(),
valueBinding == null ? -1 : valueBinding.slot(),
outerScope.getForGeneratorSlots(),
scope.getParameterSlots());
});
} }
@Override @Override
@@ -1937,10 +1972,11 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var bodyContext = entry.getExpr(); var bodyContext = entry.getExpr();
var paramListCtx = entry.getParameterList(); var paramListCtx = entry.getParameterList();
var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx); var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(paramListCtx);
var paramCount = paramListCtx.getParameters().size(); var paramCount = paramListCtx.getParameters().size();
var bindings = getParameterNames(paramListCtx); var descriptorBuilder = descriptorBuilderAndBindings.first;
var bindings = descriptorBuilderAndBindings.second;
var annotations = doVisitAnnotations(entry.getAnnotations(), methodName); var annotations = doVisitAnnotations(entry.getAnnotations(), methodName);
@@ -2221,8 +2257,14 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
} }
private ExpressionNode doVisitObjectBody(ObjectBody body, ExpressionNode parentNode) { private ExpressionNode doVisitObjectBody(ObjectBody body, ExpressionNode parentNode) {
var parametersDescriptorAndBindings = createFrameDescriptorBuilderAndSlotVariables(body);
var bindings =
parametersDescriptorAndBindings == null
? new FrameSlotVariable[0]
: parametersDescriptorAndBindings.second;
return symbolTable.enterObjectScope( return symbolTable.enterObjectScope(
body, bindings,
(scope) -> { (scope) -> {
addObjectNamesToScope(scope, body); addObjectNamesToScope(scope, body);
var objectMembers = body.getMembers(); var objectMembers = body.getMembers();
@@ -2232,7 +2274,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
} }
var sourceSection = createSourceSection(body.parent()); var sourceSection = createSourceSection(body.parent());
var parametersDescriptorBuilder = createFrameDescriptorBuilder(body); var parametersDescriptor =
parametersDescriptorAndBindings == null
? null
: parametersDescriptorAndBindings.first.build();
var parameterTypes = doVisitParameterTypes(body); var parameterTypes = doVisitParameterTypes(body);
var members = EconomicMaps.<Object, ObjectMember>create(); var members = EconomicMaps.<Object, ObjectMember>create();
@@ -2278,8 +2323,6 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
} }
var currentScope = symbolTable.getCurrentScope(); var currentScope = symbolTable.getCurrentScope();
var parametersDescriptor =
parametersDescriptorBuilder == null ? null : parametersDescriptorBuilder.build();
if (!elements.isEmpty()) { if (!elements.isEmpty()) {
if (isConstantKeyNodes) { // true if zero key nodes if (isConstantKeyNodes) { // true if zero key nodes
addConstantEntries(members, keyNodes, values); addConstantEntries(members, keyNodes, values);
@@ -2516,8 +2559,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
} }
private ObjectMember doVisitObjectElement(ObjectElement element) { private ObjectMember doVisitObjectElement(ObjectElement element) {
var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); var outerScope = symbolTable.getCurrentScope();
return symbolTable.enterEntry( var isForGeneratorScope = outerScope.isForGeneratorScope();
return symbolTable.enterEntryOrElement(
null, null,
scope -> { scope -> {
var elementNode = visitExpr(element.getExpr()); var elementNode = visitExpr(element.getExpr());
@@ -2535,7 +2579,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
member.initConstantValue(constantNode); member.initConstantValue(constantNode);
} else { } else {
if (isForGeneratorScope) { if (isForGeneratorScope) {
elementNode = new RestoreForBindingsNode(elementNode); elementNode =
new RestoreForBindingsNode(
elementNode, scope.getParameterSlots(), scope.getForGeneratorSlots());
} }
member.initMemberNode( member.initMemberNode(
ElementOrEntryNodeGen.create( ElementOrEntryNodeGen.create(
@@ -2586,9 +2632,10 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
var methodName = org.pkl.core.runtime.Identifier.method(identifier.getValue(), true); var methodName = org.pkl.core.runtime.Identifier.method(identifier.getValue(), true);
var frameDescriptorBuilder = createFrameDescriptorBuilder(paramList); var frameDescriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(paramList);
var bindings = getParameterNames(paramList); var frameDescriptorBuilder = frameDescriptorBuilderAndBindings.first;
var bindings = frameDescriptorBuilderAndBindings.second;
return symbolTable.enterMethod( return symbolTable.enterMethod(
methodName, methodName,
@@ -2629,7 +2676,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
private GeneratorObjectLiteralNode doVisitGeneratorObjectBody( private GeneratorObjectLiteralNode doVisitGeneratorObjectBody(
ObjectBody body, ExpressionNode parentNode) { ObjectBody body, ExpressionNode parentNode) {
var parametersDescriptor = createFrameDescriptorBuilder(body); var parametersDescriptorBuilderAndFrameSlotVariables =
createFrameDescriptorBuilderAndSlotVariables(body);
var parameterTypes = doVisitParameterTypes(body); var parameterTypes = doVisitParameterTypes(body);
var memberNodes = doVisitGeneratorMemberNodes(body.getMembers()); var memberNodes = doVisitGeneratorMemberNodes(body.getMembers());
var currentScope = symbolTable.getCurrentScope(); var currentScope = symbolTable.getCurrentScope();
@@ -2639,7 +2687,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
language, language,
currentScope.getQualifiedName(), currentScope.getQualifiedName(),
currentScope.isCustomThisScope(), currentScope.isCustomThisScope(),
parametersDescriptor == null ? null : parametersDescriptor.build(), parametersDescriptorBuilderAndFrameSlotVariables == null
? null
: parametersDescriptorBuilderAndFrameSlotVariables.first.build(),
parameterTypes, parameterTypes,
memberNodes, memberNodes,
parentNode); parentNode);
@@ -2838,8 +2888,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
ExpressionNode keyNode, ExpressionNode keyNode,
@Nullable Expr valueCtx, @Nullable Expr valueCtx,
@Nullable List<? extends ObjectBody> objectBodyCtxs) { @Nullable List<? extends ObjectBody> objectBodyCtxs) {
var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); var outerScope = symbolTable.getCurrentScope();
return symbolTable.enterEntry( var isForGeneratorScope = outerScope.isForGeneratorScope();
return symbolTable.enterEntryOrElement(
keyNode, keyNode,
scope -> { scope -> {
var modifier = VmModifier.ENTRY; var modifier = VmModifier.ENTRY;
@@ -2856,7 +2907,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
member.initConstantValue(constantNode); member.initConstantValue(constantNode);
} else { } else {
if (isForGeneratorScope) { if (isForGeneratorScope) {
valueNode = new RestoreForBindingsNode(valueNode); valueNode =
new RestoreForBindingsNode(
valueNode, scope.getParameterSlots(), scope.getForGeneratorSlots());
} }
member.initMemberNode( member.initMemberNode(
ElementOrEntryNodeGen.create( ElementOrEntryNodeGen.create(
@@ -2869,7 +2922,9 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
objectBodyCtxs, objectBodyCtxs,
new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode())); new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode()));
if (isForGeneratorScope) { if (isForGeneratorScope) {
objectBody = new RestoreForBindingsNode(objectBody); objectBody =
new RestoreForBindingsNode(
objectBody, scope.getParameterSlots(), scope.getForGeneratorSlots());
} }
member.initMemberNode( member.initMemberNode(
ElementOrEntryNodeGen.create( ElementOrEntryNodeGen.create(
@@ -2903,39 +2958,34 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
return needsConst; return needsConst;
} }
private static List<String> getParameterNames(ParameterList parameterList) { private FrameSlotVariable[] getSlotVariables(
var result = new ArrayList<String>(parameterList.getParameters().size()); List<org.pkl.parser.syntax.Parameter> params, FrameDescriptorBuilder frameDescriptorBuilder) {
for (var param : parameterList.getParameters()) { var slotVariables = new FrameSlotVariable[params.size()];
var name = param instanceof TypedIdentifier id ? id.getIdentifier().getValue() : "_"; for (var i = 0; i < params.size(); i++) {
result.add(name); var param = params.get(i);
} org.pkl.core.runtime.Identifier identifier;
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) { if (param instanceof TypedIdentifier typedIdentifier) {
identifier = toIdentifier(typedIdentifier.getIdentifier().getValue()); identifier = toIdentifier(typedIdentifier.getIdentifier().getValue());
} else {
identifier = org.pkl.core.runtime.Identifier.ILLEGAL;
} }
builder.addSlot(FrameSlotKind.Illegal, identifier, null); slotVariables[i] = frameDescriptorBuilder.addSlot(FrameSlotKind.Illegal, identifier, null);
} }
return builder; return slotVariables;
} }
private FrameDescriptor.@Nullable Builder createFrameDescriptorBuilder(ObjectBody body) { private Pair<FrameDescriptorBuilder, FrameSlotVariable[]>
createFrameDescriptorBuilderAndSlotVariables(ParameterList params) {
var builder = new FrameDescriptorBuilder(params.getParameters().size());
return Pair.of(builder, getSlotVariables(params.getParameters(), builder));
}
private @Nullable Pair<@Nullable FrameDescriptorBuilder, FrameSlotVariable[]>
createFrameDescriptorBuilderAndSlotVariables(ObjectBody body) {
if (body.getParameters().isEmpty()) return null; if (body.getParameters().isEmpty()) return null;
var builder = FrameDescriptor.newBuilder(body.getParameters().size()); var builder = new FrameDescriptorBuilder(body.getParameters().size());
for (var param : body.getParameters()) { return Pair.of(builder, getSlotVariables(body.getParameters(), builder));
org.pkl.core.runtime.Identifier identifier = null;
if (param instanceof TypedIdentifier typedIdentifier) {
identifier = toIdentifier(typedIdentifier.getIdentifier().getValue());
}
builder.addSlot(FrameSlotKind.Illegal, identifier, null);
}
return builder;
} }
private void checkNotInsideForGenerator(Node ctx, String errorMessageKey) { private void checkNotInsideForGenerator(Node ctx, String errorMessageKey) {
@@ -15,6 +15,8 @@
*/ */
package org.pkl.core.ast.builder; package org.pkl.core.ast.builder;
import static org.pkl.core.util.ArrayUtils.EMPTY_INT_ARRAY;
import com.oracle.truffle.api.frame.FrameDescriptor; import com.oracle.truffle.api.frame.FrameDescriptor;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
@@ -26,19 +28,21 @@ import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.builder.MethodResolution.ImplicitBaseMethod; import org.pkl.core.ast.builder.MethodResolution.ImplicitBaseMethod;
import org.pkl.core.ast.builder.MethodResolution.ImplicitThisMethod; import org.pkl.core.ast.builder.MethodResolution.ImplicitThisMethod;
import org.pkl.core.ast.builder.MethodResolution.LexicalMethod; import org.pkl.core.ast.builder.MethodResolution.LexicalMethod;
import org.pkl.core.ast.builder.VariableResolution.ForGeneratorOrLetVariable; import org.pkl.core.ast.builder.VariableResolution.ForGeneratorVariableOrLetBinding;
import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty; import org.pkl.core.ast.builder.VariableResolution.ImplicitBaseProperty;
import org.pkl.core.ast.builder.VariableResolution.LexicalProperty; import org.pkl.core.ast.builder.VariableResolution.LexicalProperty;
import org.pkl.core.ast.builder.VariableResolution.Parameter;
import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.ast.member.ObjectMember;
import org.pkl.core.runtime.BaseModuleMembers; import org.pkl.core.runtime.BaseModuleMembers;
import org.pkl.core.runtime.FrameDescriptorBuilder; import org.pkl.core.runtime.FrameDescriptorBuilder;
import org.pkl.core.runtime.FrameSlotVariable;
import org.pkl.core.runtime.Identifier; import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.ModuleInfo; import org.pkl.core.runtime.ModuleInfo;
import org.pkl.core.runtime.VmDataSize; import org.pkl.core.runtime.VmDataSize;
import org.pkl.core.runtime.VmDuration; import org.pkl.core.runtime.VmDuration;
import org.pkl.core.util.ArrayUtils;
import org.pkl.core.util.LateInit; import org.pkl.core.util.LateInit;
import org.pkl.parser.Lexer; import org.pkl.parser.Lexer;
import org.pkl.parser.syntax.ObjectBody;
public final class SymbolTable { public final class SymbolTable {
@@ -94,7 +98,7 @@ public final class SymbolTable {
public <T> T enterMethod( public <T> T enterMethod(
Identifier name, Identifier name,
ConstLevel constLevel, ConstLevel constLevel,
List<String> bindings, FrameSlotVariable[] bindings,
FrameDescriptorBuilder frameDescriptorBuilder, FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters, List<TypeParameter> typeParameters,
Function<MethodScope, T> nodeFactory) { Function<MethodScope, T> nodeFactory) {
@@ -115,17 +119,22 @@ public final class SymbolTable {
} }
public <T> T enterForGenerator( public <T> T enterForGenerator(
List<String> params, @Nullable FrameSlotVariable keyBinding,
@Nullable FrameSlotVariable valueBinding,
FrameDescriptorBuilder frameDescriptorBuilder, FrameDescriptorBuilder frameDescriptorBuilder,
Function<ForGeneratorScope, T> nodeFactory) { Function<ForGeneratorScope, T> nodeFactory) {
return doEnter( return doEnter(
new ForGeneratorScope( new ForGeneratorScope(
currentScope, currentScope.qualifiedName, params, frameDescriptorBuilder), currentScope,
currentScope.qualifiedName,
keyBinding,
valueBinding,
frameDescriptorBuilder),
nodeFactory); nodeFactory);
} }
public <T> T enterLambda( public <T> T enterLambda(
List<String> bindings, FrameSlotVariable[] bindings,
FrameDescriptorBuilder frameDescriptorBuilder, FrameDescriptorBuilder frameDescriptorBuilder,
Function<LambdaScope, T> nodeFactory) { Function<LambdaScope, T> nodeFactory) {
@@ -144,7 +153,7 @@ public final class SymbolTable {
} }
public <T> T enterLetExpression( public <T> T enterLetExpression(
@Nullable String binding, int slot, Function<LetExpressionScope, T> nodeFactory) { @Nullable FrameSlotVariable binding, Function<LetExpressionScope, T> nodeFactory) {
// flatten names of let exprs inside other let exprs for presentation purposes // flatten names of let exprs inside other let exprs for presentation purposes
var parentScope = currentScope; var parentScope = currentScope;
@@ -154,7 +163,7 @@ public final class SymbolTable {
assert parentScope != null; assert parentScope != null;
var qualifiedName = parentScope.qualifiedName + "." + "<let expr>"; var qualifiedName = parentScope.qualifiedName + "." + "<let expr>";
return doEnter(new LetExpressionScope(currentScope, binding, slot, qualifiedName), nodeFactory); return doEnter(new LetExpressionScope(currentScope, binding, qualifiedName), nodeFactory);
} }
public <T> T enterProperty( public <T> T enterProperty(
@@ -165,16 +174,16 @@ public final class SymbolTable {
nodeFactory); nodeFactory);
} }
public <T> T enterEntry( public <T> T enterEntryOrElement(
@Nullable ExpressionNode keyNode, // null for listing elements @Nullable ExpressionNode keyNode, // null for listing elements
Function<EntryScope, T> nodeFactory) { Function<EntryOrElementScope, T> nodeFactory) {
var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode); var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode);
var builder = var builder =
currentScope instanceof ForGeneratorScope forScope currentScope instanceof ForGeneratorScope forScope
? forScope.frameDescriptorBuilder ? forScope.frameDescriptorBuilder
: new FrameDescriptorBuilder(); : new FrameDescriptorBuilder();
return doEnter(new EntryScope(currentScope, qualifiedName, builder), nodeFactory); return doEnter(new EntryOrElementScope(currentScope, qualifiedName, builder), nodeFactory);
} }
public <T> T enterCustomThisScope(Function<CustomThisScope, T> nodeFactory) { public <T> T enterCustomThisScope(Function<CustomThisScope, T> nodeFactory) {
@@ -193,9 +202,10 @@ public final class SymbolTable {
nodeFactory); nodeFactory);
} }
public <T> T enterObjectScope(ObjectBody body, Function<ObjectScope, T> nodeFactory) { public <T> T enterObjectScope(
FrameSlotVariable[] bindings, Function<ObjectScope, T> nodeFactory) {
return doEnter( return doEnter(
new ObjectScope(currentScope, body, currentScope.frameDescriptorBuilder), nodeFactory); new ObjectScope(currentScope, bindings, currentScope.frameDescriptorBuilder), nodeFactory);
} }
private <T, S extends Scope> T doEnter(S scope, Function<S, T> nodeFactory) { private <T, S extends Scope> T doEnter(S scope, Function<S, T> nodeFactory) {
@@ -224,17 +234,35 @@ public final class SymbolTable {
protected final FrameDescriptorBuilder frameDescriptorBuilder; protected final FrameDescriptorBuilder frameDescriptorBuilder;
private final ConstLevel constLevel; private final ConstLevel constLevel;
protected boolean isBaseModule; protected boolean isBaseModule;
// all for-generator slots in this scope (excludes args and let bindings).
protected final int[] forGeneratorSlots;
// all parameter slots in this scope; includes let bindings, function params, method params,
// but excludes object body params (they are written one level higher)
protected final int[] parameterSlots;
// The properties defined on this (lexical) scope // The properties defined on this (lexical) scope
protected final Map<String, Member> properties = new HashMap<>(); protected final Map<String, Member> properties = new HashMap<>();
// The methods defined on this (lexical) scope // The methods defined on this (lexical) scope
protected final Map<String, Member> methods = new HashMap<>(); protected final Map<String, Member> methods = new HashMap<>();
static int[] getSlots(FrameSlotVariable[] bindings) {
if (bindings.length == 0) {
return EMPTY_INT_ARRAY;
}
var ret = new int[bindings.length];
for (var i = 0; i < ret.length; i++) {
ret[i] = bindings[i].slot();
}
return ret;
}
private Scope( private Scope(
@Nullable Scope parent, @Nullable Scope parent,
@Nullable Identifier name, @Nullable Identifier name,
String qualifiedName, String qualifiedName,
ConstLevel constLevel, ConstLevel constLevel,
FrameDescriptorBuilder frameDescriptorBuilder) { FrameDescriptorBuilder frameDescriptorBuilder,
int[] forGeneratorSlots,
int[] parameterSlots) {
this.parent = parent; this.parent = parent;
this.name = name; this.name = name;
this.qualifiedName = qualifiedName; this.qualifiedName = qualifiedName;
@@ -247,6 +275,8 @@ public final class SymbolTable {
parent != null && parent.constLevel.biggerOrEquals(constLevel) parent != null && parent.constLevel.biggerOrEquals(constLevel)
? parent.constLevel ? parent.constLevel
: constLevel; : constLevel;
this.forGeneratorSlots = forGeneratorSlots;
this.parameterSlots = parameterSlots;
} }
public final @Nullable Scope getParent() { public final @Nullable Scope getParent() {
@@ -266,16 +296,26 @@ public final class SymbolTable {
return qualifiedName; return qualifiedName;
} }
public FrameDescriptor buildFrameDescriptor() { /**
return frameDescriptorBuilder.build(); * Returns the frame slots inhabited by for-generator variables in this scope.
*
* <p>Includes outer for-generator variables.
*/
public int[] getForGeneratorSlots() {
return forGeneratorSlots;
} }
/** /**
* Returns a new descriptor builder that contains the same slots as the current scope's frame * Returns the parameter slots in this scope.
* descriptor. *
* <p>Includes let bindings, object body params, method params, lambda params
*/ */
public FrameDescriptorBuilder newFrameDescriptorBuilder() { public int[] getParameterSlots() {
return new FrameDescriptorBuilder(buildFrameDescriptor()); return parameterSlots;
}
public FrameDescriptor buildFrameDescriptor() {
return frameDescriptorBuilder.build();
} }
public @Nullable TypeParameter getTypeParameter(String name) { public @Nullable TypeParameter getTypeParameter(String name) {
@@ -389,6 +429,13 @@ public final class SymbolTable {
} }
public final boolean isCustomThisScope() { public final boolean isCustomThisScope() {
if (this instanceof LetExpressionScope) {
var myParent = parent;
while (myParent instanceof LetExpressionScope) {
myParent = myParent.getParent();
}
return myParent instanceof CustomThisScope;
}
return this instanceof CustomThisScope; return this instanceof CustomThisScope;
} }
@@ -505,21 +552,45 @@ public final class SymbolTable {
} }
public static class ObjectScope extends Scope implements LexicalScope { public static class ObjectScope extends Scope implements LexicalScope {
private final Map<String, Integer> params; private final FrameSlotVariable[] bindings;
/**
* NOTE: object body params desugar to wrapping this object with a lambda call.
*
* <p>So, the object itself does not contribute to parameter slots in the object's frame
* descriptor.
*
* <p>This code:
*
* <pre>{@code
* foo { param ->
* res = param
* }
* }</pre>
*
* Is sugar for:
*
* <pre>{@code
* foo = (param) -> (super.foo.apply(param)) {
* res = param
* }
* }</pre>
*/
private ObjectScope( private ObjectScope(
Scope parent, ObjectBody body, FrameDescriptorBuilder frameDescriptorBuilder) { Scope parent, FrameSlotVariable[] bindings, FrameDescriptorBuilder frameDescriptorBuilder) {
super( super(
parent, parent,
parent.getNameOrNull(), parent.getNameOrNull(),
parent.getQualifiedName(), parent.getQualifiedName(),
ConstLevel.NONE, ConstLevel.NONE,
frameDescriptorBuilder); frameDescriptorBuilder,
params = collectParams(body); parent.forGeneratorSlots,
EMPTY_INT_ARRAY);
this.bindings = bindings;
} }
public boolean hasParams() { public boolean hasParams() {
return !params.isEmpty(); return bindings.length > 0;
} }
@Override @Override
@@ -532,10 +603,11 @@ public final class SymbolTable {
if (prop != null) { if (prop != null) {
return new VariableResolution.LexicalProperty(false, prop.modifiers, levelsUp); return new VariableResolution.LexicalProperty(false, prop.modifiers, levelsUp);
} }
var paramIndex = params.get(name); for (var binding : bindings) {
if (paramIndex != null) { if (binding.name().equals(name)) {
// params are on a higher level than the properties // params are on a higher level than the properties
return new VariableResolution.Parameter(paramIndex, levelsUp + 1); return new VariableResolution.Parameter(binding.slot(), levelsUp + 1);
}
} }
return null; return null;
} }
@@ -548,19 +620,6 @@ public final class SymbolTable {
} }
return null; 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;
}
} }
public abstract static class TypeParameterizableScope extends Scope { public abstract static class TypeParameterizableScope extends Scope {
@@ -572,8 +631,17 @@ public final class SymbolTable {
String qualifiedName, String qualifiedName,
ConstLevel constLevel, ConstLevel constLevel,
FrameDescriptorBuilder frameDescriptorBuilder, FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters) { List<TypeParameter> typeParameters,
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder); int[] forGeneratorSlots,
int[] parameterSlots) {
super(
parent,
name,
qualifiedName,
constLevel,
frameDescriptorBuilder,
forGeneratorSlots,
parameterSlots);
this.typeParameters = typeParameters; this.typeParameters = typeParameters;
} }
@@ -593,7 +661,14 @@ public final class SymbolTable {
private final boolean isAmend; private final boolean isAmend;
public ModuleScope(ModuleInfo moduleInfo, boolean isBaseModule) { public ModuleScope(ModuleInfo moduleInfo, boolean isBaseModule) {
super(null, null, moduleInfo.getModuleName(), ConstLevel.NONE, new FrameDescriptorBuilder()); super(
null,
null,
moduleInfo.getModuleName(),
ConstLevel.NONE,
new FrameDescriptorBuilder(),
EMPTY_INT_ARRAY,
EMPTY_INT_ARRAY);
this.isBaseModule = isBaseModule; this.isBaseModule = isBaseModule;
this.moduleInfo = moduleInfo; this.moduleInfo = moduleInfo;
this.isAmend = moduleInfo.isAmend(); this.isAmend = moduleInfo.isAmend();
@@ -622,17 +697,25 @@ public final class SymbolTable {
} }
public static final class MethodScope extends TypeParameterizableScope implements LexicalScope { public static final class MethodScope extends TypeParameterizableScope implements LexicalScope {
private final List<String> bindings; private final FrameSlotVariable[] bindings;
public MethodScope( MethodScope(
Scope parent, Scope parent,
Identifier name, Identifier name,
String qualifiedName, String qualifiedName,
ConstLevel constLevel, ConstLevel constLevel,
List<String> bindings, FrameSlotVariable[] bindings,
FrameDescriptorBuilder frameDescriptorBuilder, FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters) { List<TypeParameter> typeParameters) {
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder, typeParameters); super(
parent,
name,
qualifiedName,
constLevel,
frameDescriptorBuilder,
typeParameters,
EMPTY_INT_ARRAY,
getSlots(bindings));
this.bindings = bindings; this.bindings = bindings;
} }
@@ -648,14 +731,21 @@ public final class SymbolTable {
} }
public static final class LambdaScope extends Scope implements LexicalScope { public static final class LambdaScope extends Scope implements LexicalScope {
private final List<String> bindings; private final FrameSlotVariable[] bindings;
public LambdaScope( public LambdaScope(
Scope parent, Scope parent,
List<String> bindings, FrameSlotVariable[] bindings,
String qualifiedName, String qualifiedName,
FrameDescriptorBuilder frameDescriptorBuilder) { FrameDescriptorBuilder frameDescriptorBuilder) {
super(parent, null, qualifiedName, parent.getConstLevel(), frameDescriptorBuilder); super(
parent,
null,
qualifiedName,
parent.getConstLevel(),
frameDescriptorBuilder,
EMPTY_INT_ARRAY,
getSlots(bindings));
this.bindings = bindings; this.bindings = bindings;
} }
@@ -671,8 +761,8 @@ public final class SymbolTable {
} }
public static final class LetExpressionScope extends Scope implements LexicalScope { public static final class LetExpressionScope extends Scope implements LexicalScope {
private final @Nullable String binding; public static final Object LET_BINDING_SLOT = new Object();
private final int slot; private final @Nullable FrameSlotVariable binding;
private static @Nullable Identifier getParentName(Scope parent) { private static @Nullable Identifier getParentName(Scope parent) {
while (parent != null && parent.name == null) { while (parent != null && parent.name == null) {
@@ -681,25 +771,34 @@ public final class SymbolTable {
return parent == null ? null : parent.name; return parent == null ? null : parent.name;
} }
private static int[] getMyParameterSlots(Scope parent, @Nullable FrameSlotVariable binding) {
var parentSlots = parent.parameterSlots;
if (binding == null) {
return parentSlots;
}
return ArrayUtils.append(parentSlots, binding.slot());
}
public LetExpressionScope( public LetExpressionScope(
Scope parent, @Nullable String binding, int slot, String qualifiedName) { Scope parent, @Nullable FrameSlotVariable binding, String qualifiedName) {
super( super(
parent, parent,
getParentName(parent), getParentName(parent),
qualifiedName, qualifiedName,
parent.getConstLevel(), parent.getConstLevel(),
parent.frameDescriptorBuilder); parent.frameDescriptorBuilder,
parent.forGeneratorSlots,
getMyParameterSlots(parent, binding));
this.binding = binding; this.binding = binding;
this.slot = slot;
} }
@Override @Override
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) { public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
if (name.equals("_")) { if (name.equals("_") || binding == null) {
return null; return null;
} }
if (name.equals(binding)) { if (name.equals(binding.name())) {
return new ForGeneratorOrLetVariable(slot, levelsUp); return new ForGeneratorVariableOrLetBinding(binding.slot(), levelsUp);
} }
return null; return null;
} }
@@ -718,21 +817,87 @@ public final class SymbolTable {
return grandParent.frameDescriptorBuilder; return grandParent.frameDescriptorBuilder;
} }
private static int[] getForGeneratorSlots(Scope parent) {
var grandParent = parent.parent;
assert grandParent != null;
return grandParent.forGeneratorSlots;
}
private static int[] getParameterSlots(Scope parent) {
var grandParent = parent.parent;
assert grandParent != null;
return grandParent.parameterSlots;
}
private EagerGeneratorScope(Scope parent, String qualifiedName) { private EagerGeneratorScope(Scope parent, String qualifiedName) {
super(parent, null, qualifiedName, ConstLevel.NONE, getFrameDescriptorBuilder(parent)); super(
parent,
null,
qualifiedName,
ConstLevel.NONE,
getFrameDescriptorBuilder(parent),
getForGeneratorSlots(parent),
getParameterSlots(parent));
} }
} }
public static final class ForGeneratorScope extends Scope implements LexicalScope { public static final class ForGeneratorScope extends Scope implements LexicalScope {
final List<String> params; private final @Nullable FrameSlotVariable keyBinding;
private final @Nullable FrameSlotVariable valueBinding;
private static int[] getMyForGeneratorSlots(
Scope parentScope,
@Nullable FrameSlotVariable keyBinding,
@Nullable FrameSlotVariable valueBinding) {
var slots = parentScope.forGeneratorSlots;
if (keyBinding != null && valueBinding != null) {
return ArrayUtils.append(slots, keyBinding.slot(), valueBinding.slot());
}
if (keyBinding != null) {
return ArrayUtils.append(slots, keyBinding.slot());
}
if (valueBinding != null) {
return ArrayUtils.append(slots, valueBinding.slot());
}
return slots;
}
// for-generators execute in the frame above their enclosing object.
// so, the parameters of the scope outside that object is visible.
//
// e.g. this for-generator reads param `it` as levels up == 0
// ```
// (it) -> new Listing {
// for (elem in it) {
// doSomething(elem)
// }
// }
// ```
private static int[] getMyParameterSlots(Scope parent) {
if (parent instanceof ObjectScope) {
var grandParent = parent.parent;
assert grandParent != null;
return grandParent.parameterSlots;
}
return parent.parameterSlots;
}
public ForGeneratorScope( public ForGeneratorScope(
Scope parent, Scope parent,
String qualifiedName, String qualifiedName,
List<String> params, @Nullable FrameSlotVariable keyBinding,
@Nullable FrameSlotVariable valueBinding,
FrameDescriptorBuilder frameDescriptorBuilder) { FrameDescriptorBuilder frameDescriptorBuilder) {
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder); super(
this.params = params; parent,
null,
qualifiedName,
ConstLevel.NONE,
frameDescriptorBuilder,
getMyForGeneratorSlots(parent, keyBinding, valueBinding),
getMyParameterSlots(parent));
this.keyBinding = keyBinding;
this.valueBinding = valueBinding;
} }
@Override @Override
@@ -744,12 +909,11 @@ public final class SymbolTable {
@Override @Override
public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) { public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) {
if (!params.contains(name)) { if (keyBinding != null && keyBinding.name().equals(name)) {
return null; return new ForGeneratorVariableOrLetBinding(keyBinding.slot(), levelsUp);
} }
var index = frameDescriptorBuilder.findSlot(Identifier.get(name)); if (valueBinding != null && valueBinding.name().equals(name)) {
if (index >= 0) { return new ForGeneratorVariableOrLetBinding(valueBinding.slot(), levelsUp);
return new ForGeneratorOrLetVariable(index, levelsUp);
} }
return null; return null;
} }
@@ -767,14 +931,30 @@ public final class SymbolTable {
String qualifiedName, String qualifiedName,
ConstLevel constLevel, ConstLevel constLevel,
FrameDescriptorBuilder frameDescriptorBuilder) { FrameDescriptorBuilder frameDescriptorBuilder) {
super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder); super(
parent,
name,
qualifiedName,
constLevel,
frameDescriptorBuilder,
// object members inherit the for-generator slots of the parent for-generator, if it
// exists.
parent instanceof ForGeneratorScope ? parent.forGeneratorSlots : EMPTY_INT_ARRAY,
parent.parameterSlots);
} }
} }
public static final class EntryScope extends Scope { public static final class EntryOrElementScope extends Scope {
public EntryScope( public EntryOrElementScope(
Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) { Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) {
super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder); super(
parent,
null,
qualifiedName,
ConstLevel.NONE,
frameDescriptorBuilder,
parent instanceof ForGeneratorScope ? parent.forGeneratorSlots : EMPTY_INT_ARRAY,
parent.parameterSlots);
} }
} }
@@ -788,7 +968,15 @@ public final class SymbolTable {
int modifiers, int modifiers,
FrameDescriptorBuilder frameDescriptorBuilder, FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters) { List<TypeParameter> typeParameters) {
super(parent, name, qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder, typeParameters); super(
parent,
name,
qualifiedName,
ConstLevel.MODULE,
frameDescriptorBuilder,
typeParameters,
EMPTY_INT_ARRAY,
EMPTY_INT_ARRAY);
isClosed = VmModifier.isClosed(modifiers); isClosed = VmModifier.isClosed(modifiers);
} }
@@ -816,7 +1004,15 @@ public final class SymbolTable {
String qualifiedName, String qualifiedName,
FrameDescriptorBuilder frameDescriptorBuilder, FrameDescriptorBuilder frameDescriptorBuilder,
List<TypeParameter> typeParameters) { List<TypeParameter> typeParameters) {
super(parent, name, qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder, typeParameters); super(
parent,
name,
qualifiedName,
ConstLevel.MODULE,
frameDescriptorBuilder,
typeParameters,
parent.forGeneratorSlots,
parent.parameterSlots);
} }
} }
@@ -841,7 +1037,9 @@ public final class SymbolTable {
parent.getNameOrNull(), parent.getNameOrNull(),
parent.getQualifiedName(), parent.getQualifiedName(),
ConstLevel.NONE, ConstLevel.NONE,
frameDescriptorBuilder); frameDescriptorBuilder,
parent.forGeneratorSlots,
parent.parameterSlots);
} }
} }
@@ -849,18 +1047,22 @@ public final class SymbolTable {
public AnnotationScope( public AnnotationScope(
Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) { Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) {
super( super(
parent, parent.getNameOrNull(), qualifiedName, ConstLevel.MODULE, frameDescriptorBuilder); parent,
parent.getNameOrNull(),
qualifiedName,
ConstLevel.MODULE,
frameDescriptorBuilder,
EMPTY_INT_ARRAY,
EMPTY_INT_ARRAY);
} }
} }
private static @Nullable VariableResolution resolveParameter( private static @Nullable VariableResolution resolveParameter(
String name, List<String> bindings, int levelsUp) { String name, FrameSlotVariable[] bindings, int levelsUp) {
if (name.equals("_")) { for (var binding : bindings) {
return null; if (name.equals(binding.name())) {
} return new Parameter(binding.slot(), levelsUp);
var index = bindings.indexOf(name); }
if (index != -1) {
return new VariableResolution.Parameter(index, levelsUp);
} }
return null; return null;
} }
@@ -34,7 +34,7 @@ public sealed interface VariableResolution {
// method, lambda, object body param // method, lambda, object body param
record Parameter(int slot, int levelsUp) implements VariableResolution {} record Parameter(int slot, int levelsUp) implements VariableResolution {}
record ForGeneratorOrLetVariable(int slot, int levelsUp) implements VariableResolution {} record ForGeneratorVariableOrLetBinding(int slot, int levelsUp) implements VariableResolution {}
// Implicit base module lookup // Implicit base module lookup
record ImplicitBaseProperty() implements VariableResolution {} record ImplicitBaseProperty() implements VariableResolution {}
@@ -58,6 +58,7 @@ public abstract class LetExprNode extends ExpressionNode {
typeNode = new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()); typeNode = new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection());
} }
typeNode.initWriteSlotNode(slot); typeNode.initWriteSlotNode(slot);
frame.getFrameDescriptor().setSlotKind(slot, typeNode.getFrameSlotKind());
insert(typeNode); insert(typeNode);
} }
assert typeNode != null; assert typeNode != null;
@@ -28,6 +28,7 @@ import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode;
import org.pkl.core.runtime.*; import org.pkl.core.runtime.*;
import org.pkl.core.util.ArrayUtils;
public abstract class GeneratorForNode extends GeneratorMemberNode { public abstract class GeneratorForNode extends GeneratorMemberNode {
private final FrameDescriptor generatorDescriptor; private final FrameDescriptor generatorDescriptor;
@@ -37,6 +38,9 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
@Children private final GeneratorMemberNode[] childNodes; @Children private final GeneratorMemberNode[] childNodes;
@Child private @Nullable TypeNode keyTypeNode; @Child private @Nullable TypeNode keyTypeNode;
@Child private @Nullable TypeNode valueTypeNode; @Child private @Nullable TypeNode valueTypeNode;
private final int keySlot;
private final int valueSlot;
private final int[] slotsToCopy;
public GeneratorForNode( public GeneratorForNode(
SourceSection sourceSection, SourceSection sourceSection,
@@ -52,7 +56,11 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
@Nullable TypeNode keyTypeNode, @Nullable TypeNode keyTypeNode,
// If this node can be constructed at parse time, // If this node can be constructed at parse time,
// it should be passed instead of `unresolvedValueTypeNode`. // it should be passed instead of `unresolvedValueTypeNode`.
@Nullable TypeNode valueTypeNode) { @Nullable TypeNode valueTypeNode,
int keySlot,
int valueSlot,
int[] outerForGeneratorSlots,
int[] parameterSlots) {
super(sourceSection, false); super(sourceSection, false);
this.generatorDescriptor = generatorDescriptor; this.generatorDescriptor = generatorDescriptor;
this.iterableNode = iterableNode; this.iterableNode = iterableNode;
@@ -61,6 +69,9 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
this.childNodes = childNodes; this.childNodes = childNodes;
this.keyTypeNode = keyTypeNode; this.keyTypeNode = keyTypeNode;
this.valueTypeNode = valueTypeNode; this.valueTypeNode = valueTypeNode;
this.keySlot = keySlot;
this.valueSlot = valueSlot;
this.slotsToCopy = ArrayUtils.concat(parameterSlots, outerForGeneratorSlots);
} }
protected abstract void executeWithIterable( protected abstract void executeWithIterable(
@@ -154,12 +165,10 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
VirtualFrame frame, Object parent, ObjectData data, Object key, Object value) { VirtualFrame frame, Object parent, ObjectData data, Object key, Object value) {
// GraalJS uses the same implementation technique here: // GraalJS uses the same implementation technique here:
// https://github.com/oracle/graaljs/blob/44a11ce6e87/graal-js/src/com.oracle.truffle.js/ // https://github.com/oracle/graaljs/blob/44a11ce6e87/graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/nodes/function/IterationScopeNode.java#L86-L88
// src/com/oracle/truffle/js/nodes/function/IterationScopeNode.java#L86-L88
var newFrame = var newFrame =
Truffle.getRuntime().createVirtualFrame(frame.getArguments(), generatorDescriptor); Truffle.getRuntime().createVirtualFrame(frame.getArguments(), generatorDescriptor);
// the locals in `frame` (if any) are function arguments and/or outer for-generator bindings VmUtils.copyLocals(frame, newFrame, slotsToCopy);
VmUtils.copyLocals(frame, 0, newFrame, 0, frame.getFrameDescriptor().getNumberOfSlots());
if (keyTypeNode != null) { if (keyTypeNode != null) {
keyTypeNode.executeAndSet(newFrame, key); keyTypeNode.executeAndSet(newFrame, key);
} }
@@ -175,14 +184,12 @@ public abstract class GeneratorForNode extends GeneratorMemberNode {
private void initialize(VirtualFrame frame) { private void initialize(VirtualFrame frame) {
if (unresolvedKeyTypeNode != null) { if (unresolvedKeyTypeNode != null) {
CompilerDirectives.transferToInterpreterAndInvalidate(); CompilerDirectives.transferToInterpreterAndInvalidate();
var keySlot = frame.getFrameDescriptor().getNumberOfSlots();
keyTypeNode = insert(unresolvedKeyTypeNode.execute(frame)).initWriteSlotNode(keySlot); keyTypeNode = insert(unresolvedKeyTypeNode.execute(frame)).initWriteSlotNode(keySlot);
generatorDescriptor.setSlotKind(keySlot, keyTypeNode.getFrameSlotKind()); generatorDescriptor.setSlotKind(keySlot, keyTypeNode.getFrameSlotKind());
unresolvedKeyTypeNode = null; unresolvedKeyTypeNode = null;
} }
if (unresolvedValueTypeNode != null) { if (unresolvedValueTypeNode != null) {
CompilerDirectives.transferToInterpreterAndInvalidate(); CompilerDirectives.transferToInterpreterAndInvalidate();
var valueSlot = frame.getFrameDescriptor().getNumberOfSlots() + (keyTypeNode != null ? 1 : 0);
valueTypeNode = insert(unresolvedValueTypeNode.execute(frame)).initWriteSlotNode(valueSlot); valueTypeNode = insert(unresolvedValueTypeNode.execute(frame)).initWriteSlotNode(valueSlot);
generatorDescriptor.setSlotKind(valueSlot, valueTypeNode.getFrameSlotKind()); generatorDescriptor.setSlotKind(valueSlot, valueTypeNode.getFrameSlotKind());
unresolvedValueTypeNode = null; unresolvedValueTypeNode = null;
@@ -18,27 +18,26 @@ package org.pkl.core.ast.expression.generator;
import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.frame.VirtualFrame;
import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.VmUtils; import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.ArrayUtils;
/** /**
* Restores for-generator variable bindings when a member generated by a for-generator is executed. * Restores for-generator variable bindings when a member generated by a for-generator is executed.
*/ */
public final class RestoreForBindingsNode extends ExpressionNode { public final class RestoreForBindingsNode extends ExpressionNode {
private @Child ExpressionNode child; private @Child ExpressionNode child;
private final int[] slotsToCopy;
public RestoreForBindingsNode(ExpressionNode child) { public RestoreForBindingsNode(
ExpressionNode child, int[] parameterSlots, int[] forGeneratorSlots) {
super(child.getSourceSection()); super(child.getSourceSection());
this.child = child; this.child = child;
this.slotsToCopy = ArrayUtils.concat(parameterSlots, forGeneratorSlots);
} }
@Override @Override
public Object executeGeneric(VirtualFrame frame) { public Object executeGeneric(VirtualFrame frame) {
var generatorFrame = ObjectData.getGeneratorFrame(frame); var generatorFrame = ObjectData.getGeneratorFrame(frame);
// copying all slots includes function arguments, but the capture generator frame VmUtils.copyLocals(generatorFrame, frame, slotsToCopy);
// 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); return child.executeGeneric(frame);
} }
} }
@@ -0,0 +1,68 @@
/*
* 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.primary;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.SimpleRootNode;
import org.pkl.core.ast.builder.SymbolTable.CustomThisScope;
import org.pkl.core.runtime.VmLanguage;
import org.pkl.core.runtime.VmUtils;
/**
* A node that executes {@code expressionNode} within its own root node, with the custom this value
* set.
*
* <p>This is required, for example, when let expressions are used inside type constraints, because:
*
* <ul>
* <li>let expressions write to frame slots.
* <li>type nodes don't add slots to the enclosing frame.
* </ul>
*/
public final class ExecuteCustomThisWithRootNode extends ExpressionNode {
private @Child ExpressionNode customThisNode =
new CustomThisNode(VmUtils.unavailableSourceSection());
private @Child DirectCallNode callNode;
public ExecuteCustomThisWithRootNode(
SourceSection sourceSection,
ExpressionNode expressionNode,
FrameDescriptor frameDescriptor,
String qualifiedName,
int[] forGeneratorSlots,
int[] parameterSlots) {
super(sourceSection);
frameDescriptor.findOrAddAuxiliarySlot(CustomThisScope.FRAME_SLOT_ID);
var rootNode =
new SimpleRootNode(
VmLanguage.get(this),
frameDescriptor,
sourceSection,
qualifiedName,
new WithCustomThisExpression(expressionNode, forGeneratorSlots, parameterSlots));
this.callNode = DirectCallNode.create(rootNode.getCallTarget());
}
@Override
public Object executeGeneric(VirtualFrame frame) {
var customThis = customThisNode.executeGeneric(frame);
return callNode.call(VmUtils.getReceiver(frame), VmUtils.getOwner(frame), customThis, frame);
}
}
@@ -32,8 +32,8 @@ public final class GetModuleNode extends ExpressionNode {
public Object executeGeneric(VirtualFrame frame) { public Object executeGeneric(VirtualFrame frame) {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
var levelsUp = 0; var levelsUp = -1;
for (var current = VmUtils.getOwner(frame).getEnclosingOwner(); for (var current = VmUtils.getOwner(frame);
current != null; current != null;
current = current.getEnclosingOwner()) { current = current.getEnclosingOwner()) {
if (!current.isParseTimeInvisibleScope()) { if (!current.isParseTimeInvisibleScope()) {
@@ -0,0 +1,56 @@
/*
* 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.primary;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.ArrayUtils;
/** Entrypoint node of {@link ExecuteCustomThisWithRootNode}. */
public final class WithCustomThisExpression extends ExpressionNode {
private @Child ExpressionNode expressionNode;
private final int[] slotsToCopy;
@CompilationFinal private int customThisSlot = -1;
public WithCustomThisExpression(
ExpressionNode expressionNode, int[] forGeneratorSlots, int[] parameterSlots) {
super(expressionNode.getSourceSection());
this.expressionNode = expressionNode;
this.slotsToCopy = ArrayUtils.concat(parameterSlots, forGeneratorSlots);
}
public int getCustomThisSlot(VirtualFrame frame) {
if (customThisSlot == -1) {
CompilerDirectives.transferToInterpreterAndInvalidate();
customThisSlot = VmUtils.findCustomThisSlot(frame);
}
return customThisSlot;
}
@Override
public Object executeGeneric(VirtualFrame frame) {
var customThisSlot = getCustomThisSlot(frame);
// arguments passed in by `ExecuteCustomThisWithRootNode`
frame.setAuxiliarySlot(customThisSlot, frame.getArguments()[2]);
var originalFrame = (VirtualFrame) frame.getArguments()[3];
VmUtils.copyLocals(originalFrame, frame, slotsToCopy);
return expressionNode.executeGeneric(frame);
}
}
@@ -148,8 +148,7 @@ public final class FunctionNode extends RegularMemberNode {
var parameters = CollectionUtils.<String, PType>newLinkedHashMap(paramCount); var parameters = CollectionUtils.<String, PType>newLinkedHashMap(paramCount);
for (var i = 0; i < paramCount; i++) { for (var i = 0; i < paramCount; i++) {
var slotName = getFrameDescriptor().getSlotName(i); var slotName = getFrameDescriptor().getSlotName(i);
// Ignored parameters (`_`) have no name var paramName = slotName == Identifier.ILLEGAL ? "_#" + i : slotName.toString();
var paramName = slotName == null ? "_#" + i : slotName.toString();
parameters.put(paramName, TypeNode.export(parameterTypeNodes[i])); parameters.put(paramName, TypeNode.export(parameterTypeNodes[i]));
} }
@@ -20,10 +20,7 @@ import com.oracle.truffle.api.frame.FrameSlotKind;
import java.util.Arrays; import java.util.Arrays;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
/** /** A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also gives us the current size. */
* A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also lets us find the slot of a
* given {@link Identifier}.
*/
public class FrameDescriptorBuilder { public class FrameDescriptorBuilder {
private @Nullable Identifier[] names; private @Nullable Identifier[] names;
@@ -42,16 +39,6 @@ public class FrameDescriptorBuilder {
this.names = new Identifier[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) { private void ensureCapacity(int count) {
if (names.length < size + count) { if (names.length < size + count) {
var newLength = Math.max(size + count, size * 2); var newLength = Math.max(size + count, size * 2);
@@ -59,26 +46,19 @@ public class FrameDescriptorBuilder {
} }
} }
public int addSlot(FrameSlotKind kind, @Nullable Identifier name, @Nullable Object info) { public FrameSlotVariable addSlot(FrameSlotKind kind, Identifier name, @Nullable Object info) {
ensureCapacity(1); ensureCapacity(1);
names[size] = name; names[size] = name;
size++; size++;
return underlying.addSlot(kind, name, info); var slot = underlying.addSlot(kind, name, info);
} return new FrameSlotVariable(name.toString(), slot);
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() { public FrameDescriptor build() {
return underlying.build(); return underlying.build();
} }
public int getSize() {
return size;
}
} }
@@ -0,0 +1,18 @@
/*
* 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;
public record FrameSlotVariable(String name, int slot) {}
@@ -163,6 +163,8 @@ public final class Identifier implements Comparable<Identifier> {
public static final Identifier GLOB = get("glob"); public static final Identifier GLOB = get("glob");
public static final Identifier COMPLETION_CANDIDATES = get("completionCandidates"); public static final Identifier COMPLETION_CANDIDATES = get("completionCandidates");
public static final Identifier ILLEGAL = get("`");
// common in lambdas etc // common in lambdas etc
public static final Identifier IT = get("it"); public static final Identifier IT = get("it");
@@ -401,50 +401,42 @@ public final class VmUtils {
} }
/** /**
* Copies {@code numberOfLocalsToCopy} locals from {@code sourceFrame}, starting at {@code * Copies the slots specified by {@code slotsToCopy} locals from {@code sourceFrame} into {@code
* firstSourceSlot}, to {@code firstSourceSlot}, starting at {@code firstTargetSlot}. * targetFrame}.
*/ */
public static void copyLocals( public static void copyLocals(
VirtualFrame sourceFrame, VirtualFrame sourceFrame, VirtualFrame targetFrame, int[] slotsToCopy) {
int firstSourceSlot, if (slotsToCopy.length == 0) return;
VirtualFrame targetFrame,
int firstTargetSlot,
int numberOfLocalsToCopy) {
var sourceDescriptor = sourceFrame.getFrameDescriptor(); var sourceDescriptor = sourceFrame.getFrameDescriptor();
var targetDescriptor = targetFrame.getFrameDescriptor(); var targetDescriptor = targetFrame.getFrameDescriptor();
assert sourceDescriptor.getNumberOfSlots() <= targetDescriptor.getNumberOfSlots(); for (var slot : slotsToCopy) {
// Alternatively, locals could be copied with `numberOfLocalsToCopy`
// `ReadFrameSlotNode/WriteFrameSlotNode`'s.
for (int i = 0; i < numberOfLocalsToCopy; i++) {
var sourceSlot = firstSourceSlot + i;
var targetSlot = firstTargetSlot + i;
// If, for a particular call site of this method, // If, for a particular call site of this method,
// slot kinds of `sourceDescriptor` will reach a steady state, // slot kinds of `sourceDescriptor` will reach a steady state,
// then slot kinds of `targetDescriptor` will too. // then slot kinds of `targetDescriptor` will too.
var slotKind = sourceDescriptor.getSlotKind(sourceSlot); var slotKind = sourceDescriptor.getSlotKind(slot);
switch (slotKind) { switch (slotKind) {
case Boolean -> { case Boolean -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Boolean); targetDescriptor.setSlotKind(slot, FrameSlotKind.Boolean);
targetFrame.setBoolean(targetSlot, sourceFrame.getBoolean(sourceSlot)); targetFrame.setBoolean(slot, sourceFrame.getBoolean(slot));
} }
case Long -> { case Long -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Long); targetDescriptor.setSlotKind(slot, FrameSlotKind.Long);
targetFrame.setLong(targetSlot, sourceFrame.getLong(sourceSlot)); targetFrame.setLong(slot, sourceFrame.getLong(slot));
} }
case Double -> { case Double -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Double); targetDescriptor.setSlotKind(slot, FrameSlotKind.Double);
targetFrame.setDouble(targetSlot, sourceFrame.getDouble(sourceSlot)); targetFrame.setDouble(slot, sourceFrame.getDouble(slot));
} }
case Object -> { case Object -> {
targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Object); targetDescriptor.setSlotKind(slot, FrameSlotKind.Object);
targetFrame.setObject( targetFrame.setObject(
targetSlot, slot,
sourceFrame instanceof MaterializedFrame sourceFrame instanceof MaterializedFrame
// Even though sourceDescriptor.getSlotKind is now Object, // Even though sourceDescriptor.getSlotKind is now Object,
// it may have been a primitive kind when `sourceFrame`'s local was written. // it may have been a primitive kind when `sourceFrame`'s local was written.
// Hence we need to read the local with getValue() instead of getObject(). // Hence, we need to read the local with getValue() instead of getObject().
? sourceFrame.getValue(sourceSlot) ? sourceFrame.getValue(slot)
: sourceFrame.getObject(sourceSlot)); : sourceFrame.getObject(slot));
} }
default -> { default -> {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
@@ -978,10 +970,9 @@ public final class VmUtils {
} }
public static int findCustomThisSlot(VirtualFrame frame) { public static int findCustomThisSlot(VirtualFrame frame) {
return frame var result = frame.getFrameDescriptor().getAuxiliarySlots().get(CustomThisScope.FRAME_SLOT_ID);
.getFrameDescriptor() assert result != null;
.getAuxiliarySlots() return result;
.getOrDefault(CustomThisScope.FRAME_SLOT_ID, -1);
} }
@TruffleBoundary @TruffleBoundary
@@ -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.util;
import java.util.Arrays;
public final class ArrayUtils {
private ArrayUtils() {}
public static final int[] EMPTY_INT_ARRAY = new int[0];
public static int[] append(int[] array, int elem) {
if (array.length == 0) {
return new int[] {elem};
}
var ret = Arrays.copyOf(array, array.length + 1);
ret[array.length] = elem;
return ret;
}
public static int[] append(int[] array, int elem1, int elem2) {
if (array.length == 0) {
return new int[] {elem1, elem2};
}
var ret = Arrays.copyOf(array, array.length + 2);
ret[array.length] = elem1;
ret[array.length + 1] = elem2;
return ret;
}
public static int[] concat(int[] array1, int[] array2) {
var result = new int[array1.length + array2.length];
System.arraycopy(array1, 0, result, 0, array1.length);
System.arraycopy(array2, 0, result, array1.length, array2.length);
return result;
}
}
@@ -0,0 +1,30 @@
// let expr inside custom this scopes
typealias Foo = String(let (x = this) this == x)
typealias Foo2 = String(let (x = "foo") this.startsWith(x), let (y = "o") this.endsWith(y))
typealias Foo3 = String(let (x = "foo") this.startsWith(x))(let (y = "o") this.endsWith(y))
res1: Foo = "foo"
res2: Foo2 = "foo"
res3: Foo3 = "foo"
hidden bar {
new {
name = "fooey"
}
}
res4 = (bar) {
[[let (x = "foo") this.name.startsWith(x)]] {
name = "bob"
}
}
res5 = (bar) {
[[let (x = this) x.name.startsWith("foo")]] {
name = "bob"
}
}
@@ -0,0 +1,53 @@
res =
let (
x = new Listing {
for (elem in List(1, 2)) {
elem + 5
}
}
)
x
res2 =
let (qux = 1)
let (
x = new Listing {
for (num in List(1, 2)) {
qux + num
}
}
)
x
res3 =
let (qux = 1)
let (
x = new Listing {
for (num in List(1, 2)) {
qux + num
}
}
)
let (y = 5)
let (
z = new Listing {
for (nummm in List(1, 2)) {
when (qux + y == nummm) {
nummm + y
}
}
}
)
new { ...x; ...z }
res4 =
let (m: Mapping<String, String> = new Mapping {
["foo"] = "bar"
})
new Mapping {
for (k, _ in Map("foo", "bar")) {
[k] {
m[k]
}
}
}
@@ -0,0 +1,11 @@
prop = "qux"
res =
new Mapping {
default {
when (module.prop == "qux") {
myProp = "qux"
}
}
["bar"] {}
}
@@ -480,3 +480,16 @@ whenWithElse = new Dynamic {
} }
} }
} }
local func = (it) -> new Listing {
for (elem1 in List(1, 2)) {
it
new Listing {
for (elem2 in List(1, 2)) {
elem1 + elem2
}
}
}
}
nestedForsWithinLambda = func.apply("hi")
@@ -475,3 +475,42 @@ withinLetExpr =
[k] = v [k] = v
} }
} }
// object bodies that are children of for-generators inherit the frame descriptor of the parent object
nestedObjectInsideFor {
for (idx, qux in List(1, 2)) {
[idx] {
for (baz in List(1, 2)) {
when (baz == qux) {
baz
}
}
}
}
}
// object bodies inherit the frame descriptor of the parent object
doublyNestedObjectInsideFor {
for (idx, qux in List(1, 2)) {
[idx] {
new Listing {
for (baz in List(1, 2)) {
when (baz == qux) {
baz
}
}
}
}
}
}
insideObjectBodyWithParam: Mapping<String, Dynamic> = new {
default { key ->
for (key, value in Map("foo", List(new Dynamic { bar = 1 }))) {
[key] {
...value
}
}
}
["res"] {}
}
@@ -1,7 +1,7 @@
examples { examples {
local a = List("1", "2", "3", "4") local a = List("1", "2", "3", "4")
local b = List("a", "b", "c", "d") local b = List("a", "b", "c", "d")
["shadow key variable"] { ["shadow key variable"] {
new { new {
for (key, outerValue in a) { for (key, outerValue in a) {
@@ -0,0 +1,5 @@
res {
for (prefix in List("foo")) {
"foobar" as String(let (y = this) y.startsWith(prefix))
}
}
@@ -43,7 +43,8 @@ res5 = new {
// nested predicate // nested predicate
res6 = (people) { res6 = (people) {
[[(people) { [[name == "Barn Owl"]] { age = 99 } }.toList().find((it) -> it.age == 99).name == name]] { [[(people) { [[name == "Barn Owl"]] { age = 99 } }.toList().find((it) -> it.age == 99).name
== name]] {
age = 55 age = 55
} }
} }
@@ -86,3 +87,28 @@ res12 = (people) {
[[name == "Pigeon"]] { age = 122 } [[name == "Pigeon"]] { age = 122 }
} }
} }
res13 = (people) {
[[(() -> this.name == "Pigeon").apply()]] {
age = 99
}
}
res14 = (people) {
for (foo in List(1, 2)) {
[[foo == 1]] {
age = foo
}
}
}
res15 =
(
(it) -> (people) {
for (foo in List(1, 2)) {
[[foo == 1]] {
age = foo
}
}
}
).apply("hi")
@@ -0,0 +1,13 @@
res1 = "foo"
res2 = "foo"
res3 = "foo"
res4 {
new {
name = "bob"
}
}
res5 {
new {
name = "bob"
}
}
@@ -0,0 +1,17 @@
res {
6
7
}
res2 {
2
3
}
res3 {
2
3
}
res4 {
["foo"] {
"bar"
}
}
@@ -0,0 +1,6 @@
prop = "qux"
res {
["bar"] {
myProp = "qux"
}
}
@@ -335,3 +335,15 @@ whenWithElse {
20 20
40 40
} }
nestedForsWithinLambda {
"hi"
new {
2
3
}
"hi"
new {
3
4
}
}
@@ -323,3 +323,32 @@ withinLetExpr {
["a"] = 1 ["a"] = 1
["b"] = 2 ["b"] = 2
} }
nestedObjectInsideFor {
[0] {
1
}
[1] {
2
}
}
doublyNestedObjectInsideFor {
[0] {
new {
1
}
}
[1] {
new {
2
}
}
}
insideObjectBodyWithParam {
["res"] {
["foo"] {
new {
bar = 1
}
}
}
}
@@ -0,0 +1,3 @@
res {
"foobar"
}
@@ -153,3 +153,45 @@ res12 {
age = 33 age = 33
} }
} }
res13 {
new {
name = "Pigeon"
age = 99
}
new {
name = "Barn Owl"
age = 21
}
new {
name = "Parrot"
age = 33
}
}
res14 {
new {
name = "Pigeon"
age = 1
}
new {
name = "Barn Owl"
age = 1
}
new {
name = "Parrot"
age = 1
}
}
res15 {
new {
name = "Pigeon"
age = 1
}
new {
name = "Barn Owl"
age = 1
}
new {
name = "Parrot"
age = 1
}
}