From 84d2c32d105f8dbc9b8ee5bdf04e0de4482f6d8d Mon Sep 17 00:00:00 2001 From: Daniel Chao Date: Wed, 10 Jun 2026 20:02:42 -0700 Subject: [PATCH] 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) --- .../org/pkl/core/ast/builder/AstBuilder.java | 280 +++++++------ .../org/pkl/core/ast/builder/SymbolTable.java | 372 ++++++++++++++---- .../core/ast/builder/VariableResolution.java | 2 +- .../ast/expression/binary/LetExprNode.java | 1 + .../generator/GeneratorForNode.java | 21 +- .../generator/RestoreForBindingsNode.java | 13 +- .../ExecuteCustomThisWithRootNode.java | 68 ++++ .../ast/expression/primary/GetModuleNode.java | 4 +- .../primary/WithCustomThisExpression.java | 56 +++ .../org/pkl/core/ast/member/FunctionNode.java | 3 +- .../core/runtime/FrameDescriptorBuilder.java | 36 +- .../pkl/core/runtime/FrameSlotVariable.java | 18 + .../java/org/pkl/core/runtime/Identifier.java | 2 + .../java/org/pkl/core/runtime/VmUtils.java | 49 +-- .../java/org/pkl/core/util/ArrayUtils.java | 50 +++ .../LanguageSnippetTests/input/basic/let2.pkl | 30 ++ .../LanguageSnippetTests/input/basic/let3.pkl | 53 +++ .../input/basic/moduleRef4.pkl | 11 + .../input/generators/elementGenerators.pkl | 13 + .../input/generators/entryGenerators.pkl | 39 ++ .../forGeneratorVariableShadowing.pkl | 2 +- .../forGeneratorsTypeConstraints.pkl | 5 + .../generators/predicateMembersListing.pkl | 28 +- .../output/basic/let2.pcf | 13 + .../output/basic/let3.pcf | 17 + .../output/basic/moduleRef4.pcf | 6 + .../output/generators/elementGenerators.pcf | 12 + .../output/generators/entryGenerators.pcf | 29 ++ .../forGeneratorsTypeConstraints.pcf | 3 + .../generators/predicateMembersListing.pcf | 42 ++ 30 files changed, 1000 insertions(+), 278 deletions(-) create mode 100644 pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java create mode 100644 pkl-core/src/main/java/org/pkl/core/ast/expression/primary/WithCustomThisExpression.java create mode 100644 pkl-core/src/main/java/org/pkl/core/runtime/FrameSlotVariable.java create mode 100644 pkl-core/src/main/java/org/pkl/core/util/ArrayUtils.java create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/basic/let2.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/basic/let3.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/basic/moduleRef4.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorsTypeConstraints.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/basic/let2.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/basic/let3.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/basic/moduleRef4.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/generators/forGeneratorsTypeConstraints.pcf diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java index f12dcb8e8..e6ee30a8f 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/AstBuilder.java @@ -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.SymbolTable.AnnotationScope; 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.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.ImplicitThisProperty; 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.ReadSuperEntryNode; 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.GetMemberKeyNode; 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.runtime.BaseModule; import org.pkl.core.runtime.FrameDescriptorBuilder; +import org.pkl.core.runtime.FrameSlotVariable; import org.pkl.core.runtime.ModuleInfo; import org.pkl.core.runtime.ModuleResolver; import org.pkl.core.runtime.VmBytes; @@ -417,14 +420,33 @@ public class AstBuilder extends AbstractAstBuilder { scope -> { var exprs = type.getExprs(); 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 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); } 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 public UnresolvedTypeNode visitUnionType(UnionType type) { var elementTypes = type.getTypes(); @@ -682,13 +704,13 @@ public class AstBuilder extends AbstractAstBuilder { MemberLookupMode.IMPLICIT_LEXICAL, needsConst, 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 // 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. // // function foo(bar) = new Mixin { - // [bar] = 1 <--- actually 1 level, not 0 + // res = bar <--- actually 1 level, not 0 // for (elem in qux) { // elem <--- actually 0 level // } @@ -1096,26 +1118,30 @@ public class AstBuilder extends AbstractAstBuilder { var sourceSection = createSourceSection(letExpr); var parameter = letExpr.getParameter(); UnresolvedTypeNode typeNode = null; - String binding = null; - var slot = -1; + @Nullable FrameSlotVariable binding = null; var frameDescriptorBuilder = symbolTable.getCurrentScope().frameDescriptorBuilder; if (parameter instanceof TypedIdentifier par) { typeNode = visitTypeAnnotation(par.getTypeAnnotation()); - slot = + binding = frameDescriptorBuilder.addSlot( - FrameSlotKind.Illegal, toIdentifier(par.getIdentifier().getValue()), null); - binding = par.getIdentifier().getValue(); + FrameSlotKind.Illegal, + toIdentifier(par.getIdentifier().getValue()), + LetExpressionScope.LET_BINDING_SLOT); } var bindingExpr = visitExpr(letExpr.getBindingExpr()); var t = typeNode; - var s = slot; + var b = binding; return symbolTable.enterLetExpression( binding, - slot, scope -> { var bodyExpr = visitExpr(letExpr.getExpr()); 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 { public ExpressionNode visitFunctionLiteralExpr(FunctionLiteralExpr expr) { var sourceSection = createSourceSection(expr); var params = expr.getParameterList(); - var descriptorBuilder = createFrameDescriptorBuilder(params); + var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(params); var paramCount = params.getParameters().size(); - + var descriptorBuilder = descriptorBuilderAndBindings.first; + var bindings = descriptorBuilderAndBindings.second; if (paramCount > 5) { throw exceptionBuilder() .evalError("tooManyFunctionParameters") @@ -1133,8 +1160,6 @@ public class AstBuilder extends AbstractAstBuilder { .build(); } - var bindings = getParameterNames(params); - var isCustomThisScope = symbolTable.getCurrentScope().isCustomThisScope(); return symbolTable.enterLambda( @@ -1281,7 +1306,18 @@ public class AstBuilder extends AbstractAstBuilder { public GeneratorMemberNode visitMemberPredicate(MemberPredicate ctx) { var keyNode = 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 = doVisitObjectEntryBody(createSourceSection(ctx), keyNode, ctx.getExpr(), ctx.getBodyList()); var isFrameStored = @@ -1339,82 +1375,81 @@ public class AstBuilder extends AbstractAstBuilder { 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 public GeneratorMemberNode visitForGenerator(ForGenerator ctx) { + var keyParameter = ctx.getP2() == null ? null : ctx.getP1(); var valueParameter = ctx.getP2() == null ? ctx.getP1() : ctx.getP2(); - TypedIdentifier keyTypedIdentifier = null; - if (keyParameter instanceof TypedIdentifier ti) keyTypedIdentifier = ti; - TypedIdentifier valueTypedIdentifier = null; - if (valueParameter instanceof TypedIdentifier ti) valueTypedIdentifier = ti; - - var params = new ArrayList(); - 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 keyBinding = makeBinding(keyParameter); + var valueBinding = makeBinding(valueParameter); var keyIdentifier = - keyTypedIdentifier == null - ? null - : toIdentifier(keyTypedIdentifier.getIdentifier().getValue()); + keyParameter instanceof TypedIdentifier keyTypedIdentifier ? keyTypedIdentifier : null; var valueIdentifier = - valueTypedIdentifier == null - ? null - : toIdentifier(valueTypedIdentifier.getIdentifier().getValue()); - if (valueIdentifier != null && valueIdentifier == keyIdentifier) { + valueParameter instanceof TypedIdentifier valueTypedIdentifier + ? valueTypedIdentifier + : null; + if (keyIdentifier != null + && valueIdentifier != null + && keyIdentifier + .getIdentifier() + .getValue() + .equals(valueIdentifier.getIdentifier().getValue())) { throw exceptionBuilder() - .evalError("duplicateDefinition", valueIdentifier) - .withSourceSection(createSourceSection(valueTypedIdentifier.getIdentifier())) + .evalError("duplicateDefinition", valueIdentifier.getIdentifier().getValue()) + .withSourceSection(createSourceSection(valueIdentifier)) .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 = - keyTypedIdentifier == null - ? null - : visitTypeAnnotation(keyTypedIdentifier.getTypeAnnotation()); + keyIdentifier == null ? null : visitTypeAnnotation(keyIdentifier.getTypeAnnotation()); var unresolvedValueTypeNode = - valueTypedIdentifier == null - ? null - : visitTypeAnnotation(valueTypedIdentifier.getTypeAnnotation()); + valueIdentifier == null ? null : visitTypeAnnotation(valueIdentifier.getTypeAnnotation()); // if possible, initialize immediately to avoid later insert var keyTypeNode = - unresolvedKeyTypeNode == null && keySlot != -1 + unresolvedKeyTypeNode == null && keyBinding != null ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) - .initWriteSlotNode(keySlot) + .initWriteSlotNode(keyBinding.slot()) : null; // if possible, initialize immediately to avoid later insert var valueTypeNode = - unresolvedValueTypeNode == null && valueSlot != -1 + unresolvedValueTypeNode == null && valueBinding != null ? new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()) - .initWriteSlotNode(valueSlot) + .initWriteSlotNode(valueBinding.slot()) : null; var iterableNode = symbolTable.enterEagerGenerator(scope -> visitExpr(ctx.getExpr())); - var memberNodes = - symbolTable.enterForGenerator( - params, generatorDescriptorBuilder, scope -> doVisitForWhenBody(ctx.getBody())); - return GeneratorForNodeGen.create( - createSourceSection(ctx), - generatorDescriptorBuilder.build(), - iterableNode, - unresolvedKeyTypeNode, - unresolvedValueTypeNode, - memberNodes, - keyTypeNode, - valueTypeNode); + var outerScope = symbolTable.getCurrentScope(); + return symbolTable.enterForGenerator( + keyBinding, + valueBinding, + symbolTable.getCurrentScope().frameDescriptorBuilder, + scope -> { + var memberNodes = doVisitForWhenBody(ctx.getBody()); + return GeneratorForNodeGen.create( + createSourceSection(ctx), + scope.frameDescriptorBuilder.build(), + iterableNode, + unresolvedKeyTypeNode, + unresolvedValueTypeNode, + memberNodes, + keyTypeNode, + valueTypeNode, + keyBinding == null ? -1 : keyBinding.slot(), + valueBinding == null ? -1 : valueBinding.slot(), + outerScope.getForGeneratorSlots(), + scope.getParameterSlots()); + }); } @Override @@ -1937,10 +1972,11 @@ public class AstBuilder extends AbstractAstBuilder { var bodyContext = entry.getExpr(); var paramListCtx = entry.getParameterList(); - var descriptorBuilder = createFrameDescriptorBuilder(paramListCtx); + var descriptorBuilderAndBindings = createFrameDescriptorBuilderAndSlotVariables(paramListCtx); var paramCount = paramListCtx.getParameters().size(); - var bindings = getParameterNames(paramListCtx); + var descriptorBuilder = descriptorBuilderAndBindings.first; + var bindings = descriptorBuilderAndBindings.second; var annotations = doVisitAnnotations(entry.getAnnotations(), methodName); @@ -2221,8 +2257,14 @@ public class AstBuilder extends AbstractAstBuilder { } private ExpressionNode doVisitObjectBody(ObjectBody body, ExpressionNode parentNode) { + var parametersDescriptorAndBindings = createFrameDescriptorBuilderAndSlotVariables(body); + var bindings = + parametersDescriptorAndBindings == null + ? new FrameSlotVariable[0] + : parametersDescriptorAndBindings.second; + return symbolTable.enterObjectScope( - body, + bindings, (scope) -> { addObjectNamesToScope(scope, body); var objectMembers = body.getMembers(); @@ -2232,7 +2274,10 @@ public class AstBuilder extends AbstractAstBuilder { } var sourceSection = createSourceSection(body.parent()); - var parametersDescriptorBuilder = createFrameDescriptorBuilder(body); + var parametersDescriptor = + parametersDescriptorAndBindings == null + ? null + : parametersDescriptorAndBindings.first.build(); var parameterTypes = doVisitParameterTypes(body); var members = EconomicMaps.create(); @@ -2278,8 +2323,6 @@ public class AstBuilder extends AbstractAstBuilder { } var currentScope = symbolTable.getCurrentScope(); - var parametersDescriptor = - parametersDescriptorBuilder == null ? null : parametersDescriptorBuilder.build(); if (!elements.isEmpty()) { if (isConstantKeyNodes) { // true if zero key nodes addConstantEntries(members, keyNodes, values); @@ -2516,8 +2559,9 @@ public class AstBuilder extends AbstractAstBuilder { } private ObjectMember doVisitObjectElement(ObjectElement element) { - var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); - return symbolTable.enterEntry( + var outerScope = symbolTable.getCurrentScope(); + var isForGeneratorScope = outerScope.isForGeneratorScope(); + return symbolTable.enterEntryOrElement( null, scope -> { var elementNode = visitExpr(element.getExpr()); @@ -2535,7 +2579,9 @@ public class AstBuilder extends AbstractAstBuilder { member.initConstantValue(constantNode); } else { if (isForGeneratorScope) { - elementNode = new RestoreForBindingsNode(elementNode); + elementNode = + new RestoreForBindingsNode( + elementNode, scope.getParameterSlots(), scope.getForGeneratorSlots()); } member.initMemberNode( ElementOrEntryNodeGen.create( @@ -2586,9 +2632,10 @@ public class AstBuilder extends AbstractAstBuilder { 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( methodName, @@ -2629,7 +2676,8 @@ public class AstBuilder extends AbstractAstBuilder { private GeneratorObjectLiteralNode doVisitGeneratorObjectBody( ObjectBody body, ExpressionNode parentNode) { - var parametersDescriptor = createFrameDescriptorBuilder(body); + var parametersDescriptorBuilderAndFrameSlotVariables = + createFrameDescriptorBuilderAndSlotVariables(body); var parameterTypes = doVisitParameterTypes(body); var memberNodes = doVisitGeneratorMemberNodes(body.getMembers()); var currentScope = symbolTable.getCurrentScope(); @@ -2639,7 +2687,9 @@ public class AstBuilder extends AbstractAstBuilder { language, currentScope.getQualifiedName(), currentScope.isCustomThisScope(), - parametersDescriptor == null ? null : parametersDescriptor.build(), + parametersDescriptorBuilderAndFrameSlotVariables == null + ? null + : parametersDescriptorBuilderAndFrameSlotVariables.first.build(), parameterTypes, memberNodes, parentNode); @@ -2838,8 +2888,9 @@ public class AstBuilder extends AbstractAstBuilder { ExpressionNode keyNode, @Nullable Expr valueCtx, @Nullable List objectBodyCtxs) { - var isForGeneratorScope = symbolTable.getCurrentScope().isForGeneratorScope(); - return symbolTable.enterEntry( + var outerScope = symbolTable.getCurrentScope(); + var isForGeneratorScope = outerScope.isForGeneratorScope(); + return symbolTable.enterEntryOrElement( keyNode, scope -> { var modifier = VmModifier.ENTRY; @@ -2856,7 +2907,9 @@ public class AstBuilder extends AbstractAstBuilder { member.initConstantValue(constantNode); } else { if (isForGeneratorScope) { - valueNode = new RestoreForBindingsNode(valueNode); + valueNode = + new RestoreForBindingsNode( + valueNode, scope.getParameterSlots(), scope.getForGeneratorSlots()); } member.initMemberNode( ElementOrEntryNodeGen.create( @@ -2869,7 +2922,9 @@ public class AstBuilder extends AbstractAstBuilder { objectBodyCtxs, new ReadSuperEntryNode(unavailableSourceSection(), new GetMemberKeyNode())); if (isForGeneratorScope) { - objectBody = new RestoreForBindingsNode(objectBody); + objectBody = + new RestoreForBindingsNode( + objectBody, scope.getParameterSlots(), scope.getForGeneratorSlots()); } member.initMemberNode( ElementOrEntryNodeGen.create( @@ -2903,39 +2958,34 @@ public class AstBuilder extends AbstractAstBuilder { return needsConst; } - private static List getParameterNames(ParameterList parameterList) { - var result = new ArrayList(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; + private FrameSlotVariable[] getSlotVariables( + List params, FrameDescriptorBuilder frameDescriptorBuilder) { + var slotVariables = new FrameSlotVariable[params.size()]; + for (var i = 0; i < params.size(); i++) { + var param = params.get(i); + org.pkl.core.runtime.Identifier identifier; if (param instanceof TypedIdentifier typedIdentifier) { 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 + 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; - var builder = FrameDescriptor.newBuilder(body.getParameters().size()); - for (var param : body.getParameters()) { - 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; + var builder = new FrameDescriptorBuilder(body.getParameters().size()); + return Pair.of(builder, getSlotVariables(body.getParameters(), builder)); } private void checkNotInsideForGenerator(Node ctx, String errorMessageKey) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java index aa0e99653..762af9de0 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/SymbolTable.java @@ -15,6 +15,8 @@ */ package org.pkl.core.ast.builder; +import static org.pkl.core.util.ArrayUtils.EMPTY_INT_ARRAY; + import com.oracle.truffle.api.frame.FrameDescriptor; import java.util.*; 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.ImplicitThisMethod; 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.LexicalProperty; +import org.pkl.core.ast.builder.VariableResolution.Parameter; import org.pkl.core.ast.member.ObjectMember; import org.pkl.core.runtime.BaseModuleMembers; import org.pkl.core.runtime.FrameDescriptorBuilder; +import org.pkl.core.runtime.FrameSlotVariable; 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.ArrayUtils; import org.pkl.core.util.LateInit; import org.pkl.parser.Lexer; -import org.pkl.parser.syntax.ObjectBody; public final class SymbolTable { @@ -94,7 +98,7 @@ public final class SymbolTable { public T enterMethod( Identifier name, ConstLevel constLevel, - List bindings, + FrameSlotVariable[] bindings, FrameDescriptorBuilder frameDescriptorBuilder, List typeParameters, Function nodeFactory) { @@ -115,17 +119,22 @@ public final class SymbolTable { } public T enterForGenerator( - List params, + @Nullable FrameSlotVariable keyBinding, + @Nullable FrameSlotVariable valueBinding, FrameDescriptorBuilder frameDescriptorBuilder, Function nodeFactory) { return doEnter( new ForGeneratorScope( - currentScope, currentScope.qualifiedName, params, frameDescriptorBuilder), + currentScope, + currentScope.qualifiedName, + keyBinding, + valueBinding, + frameDescriptorBuilder), nodeFactory); } public T enterLambda( - List bindings, + FrameSlotVariable[] bindings, FrameDescriptorBuilder frameDescriptorBuilder, Function nodeFactory) { @@ -144,7 +153,7 @@ public final class SymbolTable { } public T enterLetExpression( - @Nullable String binding, int slot, Function nodeFactory) { + @Nullable FrameSlotVariable binding, Function nodeFactory) { // flatten names of let exprs inside other let exprs for presentation purposes var parentScope = currentScope; @@ -154,7 +163,7 @@ public final class SymbolTable { assert parentScope != null; var qualifiedName = parentScope.qualifiedName + "." + ""; - return doEnter(new LetExpressionScope(currentScope, binding, slot, qualifiedName), nodeFactory); + return doEnter(new LetExpressionScope(currentScope, binding, qualifiedName), nodeFactory); } public T enterProperty( @@ -165,16 +174,16 @@ public final class SymbolTable { nodeFactory); } - public T enterEntry( + public T enterEntryOrElement( @Nullable ExpressionNode keyNode, // null for listing elements - Function nodeFactory) { + Function nodeFactory) { var qualifiedName = currentScope.getQualifiedName() + currentScope.getNextEntryName(keyNode); var builder = currentScope instanceof ForGeneratorScope forScope ? forScope.frameDescriptorBuilder : new FrameDescriptorBuilder(); - return doEnter(new EntryScope(currentScope, qualifiedName, builder), nodeFactory); + return doEnter(new EntryOrElementScope(currentScope, qualifiedName, builder), nodeFactory); } public T enterCustomThisScope(Function nodeFactory) { @@ -193,9 +202,10 @@ public final class SymbolTable { nodeFactory); } - public T enterObjectScope(ObjectBody body, Function nodeFactory) { + public T enterObjectScope( + FrameSlotVariable[] bindings, Function nodeFactory) { return doEnter( - new ObjectScope(currentScope, body, currentScope.frameDescriptorBuilder), nodeFactory); + new ObjectScope(currentScope, bindings, currentScope.frameDescriptorBuilder), nodeFactory); } private T doEnter(S scope, Function nodeFactory) { @@ -224,17 +234,35 @@ public final class SymbolTable { protected final FrameDescriptorBuilder frameDescriptorBuilder; private final ConstLevel constLevel; 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 protected final Map properties = new HashMap<>(); // The methods defined on this (lexical) scope protected final Map 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( @Nullable Scope parent, @Nullable Identifier name, String qualifiedName, ConstLevel constLevel, - FrameDescriptorBuilder frameDescriptorBuilder) { + FrameDescriptorBuilder frameDescriptorBuilder, + int[] forGeneratorSlots, + int[] parameterSlots) { this.parent = parent; this.name = name; this.qualifiedName = qualifiedName; @@ -247,6 +275,8 @@ public final class SymbolTable { parent != null && parent.constLevel.biggerOrEquals(constLevel) ? parent.constLevel : constLevel; + this.forGeneratorSlots = forGeneratorSlots; + this.parameterSlots = parameterSlots; } public final @Nullable Scope getParent() { @@ -266,16 +296,26 @@ public final class SymbolTable { return qualifiedName; } - public FrameDescriptor buildFrameDescriptor() { - return frameDescriptorBuilder.build(); + /** + * Returns the frame slots inhabited by for-generator variables in this scope. + * + *

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 - * descriptor. + * Returns the parameter slots in this scope. + * + *

Includes let bindings, object body params, method params, lambda params */ - public FrameDescriptorBuilder newFrameDescriptorBuilder() { - return new FrameDescriptorBuilder(buildFrameDescriptor()); + public int[] getParameterSlots() { + return parameterSlots; + } + + public FrameDescriptor buildFrameDescriptor() { + return frameDescriptorBuilder.build(); } public @Nullable TypeParameter getTypeParameter(String name) { @@ -389,6 +429,13 @@ public final class SymbolTable { } 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; } @@ -505,21 +552,45 @@ public final class SymbolTable { } public static class ObjectScope extends Scope implements LexicalScope { - private final Map params; + private final FrameSlotVariable[] bindings; + /** + * NOTE: object body params desugar to wrapping this object with a lambda call. + * + *

So, the object itself does not contribute to parameter slots in the object's frame + * descriptor. + * + *

This code: + * + *

{@code
+     * foo { param ->
+     *   res = param
+     * }
+     * }
+ * + * Is sugar for: + * + *
{@code
+     * foo = (param) -> (super.foo.apply(param)) {
+     *   res = param
+     * }
+     * }
+ */ private ObjectScope( - Scope parent, ObjectBody body, FrameDescriptorBuilder frameDescriptorBuilder) { + Scope parent, FrameSlotVariable[] bindings, FrameDescriptorBuilder frameDescriptorBuilder) { super( parent, parent.getNameOrNull(), parent.getQualifiedName(), ConstLevel.NONE, - frameDescriptorBuilder); - params = collectParams(body); + frameDescriptorBuilder, + parent.forGeneratorSlots, + EMPTY_INT_ARRAY); + this.bindings = bindings; } public boolean hasParams() { - return !params.isEmpty(); + return bindings.length > 0; } @Override @@ -532,10 +603,11 @@ public final class SymbolTable { 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); + for (var binding : bindings) { + if (binding.name().equals(name)) { + // params are on a higher level than the properties + return new VariableResolution.Parameter(binding.slot(), levelsUp + 1); + } } return null; } @@ -548,19 +620,6 @@ public final class SymbolTable { } return null; } - - private static Map collectParams(ObjectBody body) { - var params = new HashMap(); - 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 { @@ -572,8 +631,17 @@ public final class SymbolTable { String qualifiedName, ConstLevel constLevel, FrameDescriptorBuilder frameDescriptorBuilder, - List typeParameters) { - super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder); + List typeParameters, + int[] forGeneratorSlots, + int[] parameterSlots) { + super( + parent, + name, + qualifiedName, + constLevel, + frameDescriptorBuilder, + forGeneratorSlots, + parameterSlots); this.typeParameters = typeParameters; } @@ -593,7 +661,14 @@ public final class SymbolTable { private final boolean isAmend; 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.moduleInfo = moduleInfo; this.isAmend = moduleInfo.isAmend(); @@ -622,17 +697,25 @@ public final class SymbolTable { } public static final class MethodScope extends TypeParameterizableScope implements LexicalScope { - private final List bindings; + private final FrameSlotVariable[] bindings; - public MethodScope( + MethodScope( Scope parent, Identifier name, String qualifiedName, ConstLevel constLevel, - List bindings, + FrameSlotVariable[] bindings, FrameDescriptorBuilder frameDescriptorBuilder, List typeParameters) { - super(parent, name, qualifiedName, constLevel, frameDescriptorBuilder, typeParameters); + super( + parent, + name, + qualifiedName, + constLevel, + frameDescriptorBuilder, + typeParameters, + EMPTY_INT_ARRAY, + getSlots(bindings)); this.bindings = bindings; } @@ -648,14 +731,21 @@ public final class SymbolTable { } public static final class LambdaScope extends Scope implements LexicalScope { - private final List bindings; + private final FrameSlotVariable[] bindings; public LambdaScope( Scope parent, - List bindings, + FrameSlotVariable[] bindings, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) { - super(parent, null, qualifiedName, parent.getConstLevel(), frameDescriptorBuilder); + super( + parent, + null, + qualifiedName, + parent.getConstLevel(), + frameDescriptorBuilder, + EMPTY_INT_ARRAY, + getSlots(bindings)); this.bindings = bindings; } @@ -671,8 +761,8 @@ public final class SymbolTable { } public static final class LetExpressionScope extends Scope implements LexicalScope { - private final @Nullable String binding; - private final int slot; + public static final Object LET_BINDING_SLOT = new Object(); + private final @Nullable FrameSlotVariable binding; private static @Nullable Identifier getParentName(Scope parent) { while (parent != null && parent.name == null) { @@ -681,25 +771,34 @@ public final class SymbolTable { 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( - Scope parent, @Nullable String binding, int slot, String qualifiedName) { + Scope parent, @Nullable FrameSlotVariable binding, String qualifiedName) { super( parent, getParentName(parent), qualifiedName, parent.getConstLevel(), - parent.frameDescriptorBuilder); + parent.frameDescriptorBuilder, + parent.forGeneratorSlots, + getMyParameterSlots(parent, binding)); this.binding = binding; - this.slot = slot; } @Override public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) { - if (name.equals("_")) { + if (name.equals("_") || binding == null) { return null; } - if (name.equals(binding)) { - return new ForGeneratorOrLetVariable(slot, levelsUp); + if (name.equals(binding.name())) { + return new ForGeneratorVariableOrLetBinding(binding.slot(), levelsUp); } return null; } @@ -718,21 +817,87 @@ public final class SymbolTable { 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) { - 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 { - final List 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( Scope parent, String qualifiedName, - List params, + @Nullable FrameSlotVariable keyBinding, + @Nullable FrameSlotVariable valueBinding, FrameDescriptorBuilder frameDescriptorBuilder) { - super(parent, null, qualifiedName, ConstLevel.NONE, frameDescriptorBuilder); - this.params = params; + super( + parent, + null, + qualifiedName, + ConstLevel.NONE, + frameDescriptorBuilder, + getMyForGeneratorSlots(parent, keyBinding, valueBinding), + getMyParameterSlots(parent)); + this.keyBinding = keyBinding; + this.valueBinding = valueBinding; } @Override @@ -744,12 +909,11 @@ public final class SymbolTable { @Override public @Nullable VariableResolution doResolveProperty(String name, int levelsUp) { - if (!params.contains(name)) { - return null; + if (keyBinding != null && keyBinding.name().equals(name)) { + return new ForGeneratorVariableOrLetBinding(keyBinding.slot(), levelsUp); } - var index = frameDescriptorBuilder.findSlot(Identifier.get(name)); - if (index >= 0) { - return new ForGeneratorOrLetVariable(index, levelsUp); + if (valueBinding != null && valueBinding.name().equals(name)) { + return new ForGeneratorVariableOrLetBinding(valueBinding.slot(), levelsUp); } return null; } @@ -767,14 +931,30 @@ public final class SymbolTable { String qualifiedName, ConstLevel constLevel, 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 EntryScope( + public static final class EntryOrElementScope extends Scope { + public EntryOrElementScope( 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, FrameDescriptorBuilder frameDescriptorBuilder, List 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); } @@ -816,7 +1004,15 @@ public final class SymbolTable { String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder, List 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.getQualifiedName(), ConstLevel.NONE, - frameDescriptorBuilder); + frameDescriptorBuilder, + parent.forGeneratorSlots, + parent.parameterSlots); } } @@ -849,18 +1047,22 @@ public final class SymbolTable { public AnnotationScope( Scope parent, String qualifiedName, FrameDescriptorBuilder frameDescriptorBuilder) { 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( - String name, List bindings, int levelsUp) { - if (name.equals("_")) { - return null; - } - var index = bindings.indexOf(name); - if (index != -1) { - return new VariableResolution.Parameter(index, levelsUp); + String name, FrameSlotVariable[] bindings, int levelsUp) { + for (var binding : bindings) { + if (name.equals(binding.name())) { + return new Parameter(binding.slot(), levelsUp); + } } return null; } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/builder/VariableResolution.java b/pkl-core/src/main/java/org/pkl/core/ast/builder/VariableResolution.java index e2b2977db..81c827b62 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/builder/VariableResolution.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/builder/VariableResolution.java @@ -34,7 +34,7 @@ public sealed interface VariableResolution { // method, lambda, object body param 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 record ImplicitBaseProperty() implements VariableResolution {} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LetExprNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LetExprNode.java index c6a7e9ec2..0555dbee2 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LetExprNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/binary/LetExprNode.java @@ -58,6 +58,7 @@ public abstract class LetExprNode extends ExpressionNode { typeNode = new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection()); } typeNode.initWriteSlotNode(slot); + frame.getFrameDescriptor().setSlotKind(slot, typeNode.getFrameSlotKind()); insert(typeNode); } assert typeNode != null; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java index 71adba869..d51915f07 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorForNode.java @@ -28,6 +28,7 @@ import org.pkl.core.ast.ExpressionNode; import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.*; +import org.pkl.core.util.ArrayUtils; public abstract class GeneratorForNode extends GeneratorMemberNode { private final FrameDescriptor generatorDescriptor; @@ -37,6 +38,9 @@ public abstract class GeneratorForNode extends GeneratorMemberNode { @Children private final GeneratorMemberNode[] childNodes; @Child private @Nullable TypeNode keyTypeNode; @Child private @Nullable TypeNode valueTypeNode; + private final int keySlot; + private final int valueSlot; + private final int[] slotsToCopy; public GeneratorForNode( SourceSection sourceSection, @@ -52,7 +56,11 @@ public abstract class GeneratorForNode extends GeneratorMemberNode { @Nullable TypeNode keyTypeNode, // If this node can be constructed at parse time, // it should be passed instead of `unresolvedValueTypeNode`. - @Nullable TypeNode valueTypeNode) { + @Nullable TypeNode valueTypeNode, + int keySlot, + int valueSlot, + int[] outerForGeneratorSlots, + int[] parameterSlots) { super(sourceSection, false); this.generatorDescriptor = generatorDescriptor; this.iterableNode = iterableNode; @@ -61,6 +69,9 @@ public abstract class GeneratorForNode extends GeneratorMemberNode { this.childNodes = childNodes; this.keyTypeNode = keyTypeNode; this.valueTypeNode = valueTypeNode; + this.keySlot = keySlot; + this.valueSlot = valueSlot; + this.slotsToCopy = ArrayUtils.concat(parameterSlots, outerForGeneratorSlots); } protected abstract void executeWithIterable( @@ -154,12 +165,10 @@ public abstract class GeneratorForNode extends GeneratorMemberNode { VirtualFrame frame, Object parent, ObjectData data, Object key, Object value) { // GraalJS uses the same implementation technique here: - // 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 + // 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 var newFrame = Truffle.getRuntime().createVirtualFrame(frame.getArguments(), generatorDescriptor); - // the locals in `frame` (if any) are function arguments and/or outer for-generator bindings - VmUtils.copyLocals(frame, 0, newFrame, 0, frame.getFrameDescriptor().getNumberOfSlots()); + VmUtils.copyLocals(frame, newFrame, slotsToCopy); if (keyTypeNode != null) { keyTypeNode.executeAndSet(newFrame, key); } @@ -175,14 +184,12 @@ public abstract class GeneratorForNode extends GeneratorMemberNode { private void initialize(VirtualFrame frame) { if (unresolvedKeyTypeNode != null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - var keySlot = frame.getFrameDescriptor().getNumberOfSlots(); keyTypeNode = insert(unresolvedKeyTypeNode.execute(frame)).initWriteSlotNode(keySlot); generatorDescriptor.setSlotKind(keySlot, keyTypeNode.getFrameSlotKind()); unresolvedKeyTypeNode = null; } if (unresolvedValueTypeNode != null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - var valueSlot = frame.getFrameDescriptor().getNumberOfSlots() + (keyTypeNode != null ? 1 : 0); valueTypeNode = insert(unresolvedValueTypeNode.execute(frame)).initWriteSlotNode(valueSlot); generatorDescriptor.setSlotKind(valueSlot, valueTypeNode.getFrameSlotKind()); unresolvedValueTypeNode = null; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/RestoreForBindingsNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/RestoreForBindingsNode.java index 82a4d423d..6e2c740af 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/RestoreForBindingsNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/RestoreForBindingsNode.java @@ -18,27 +18,26 @@ package org.pkl.core.ast.expression.generator; 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; /** * Restores for-generator variable bindings when a member generated by a for-generator is executed. */ public final class RestoreForBindingsNode extends ExpressionNode { private @Child ExpressionNode child; + private final int[] slotsToCopy; - public RestoreForBindingsNode(ExpressionNode child) { + public RestoreForBindingsNode( + ExpressionNode child, int[] parameterSlots, int[] forGeneratorSlots) { super(child.getSourceSection()); this.child = child; + this.slotsToCopy = ArrayUtils.concat(parameterSlots, forGeneratorSlots); } @Override public Object executeGeneric(VirtualFrame frame) { var generatorFrame = ObjectData.getGeneratorFrame(frame); - // 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()); + VmUtils.copyLocals(generatorFrame, frame, slotsToCopy); return child.executeGeneric(frame); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java new file mode 100644 index 000000000..573664da6 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/ExecuteCustomThisWithRootNode.java @@ -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. + * + *

This is required, for example, when let expressions are used inside type constraints, because: + * + *

    + *
  • let expressions write to frame slots. + *
  • type nodes don't add slots to the enclosing frame. + *
+ */ +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); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java index 8bea07663..dd40605d8 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/GetModuleNode.java @@ -32,8 +32,8 @@ public final class GetModuleNode extends ExpressionNode { public Object executeGeneric(VirtualFrame frame) { CompilerDirectives.transferToInterpreter(); - var levelsUp = 0; - for (var current = VmUtils.getOwner(frame).getEnclosingOwner(); + var levelsUp = -1; + for (var current = VmUtils.getOwner(frame); current != null; current = current.getEnclosingOwner()) { if (!current.isParseTimeInvisibleScope()) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/WithCustomThisExpression.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/WithCustomThisExpression.java new file mode 100644 index 000000000..5511d3426 --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/primary/WithCustomThisExpression.java @@ -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); + } +} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java index 3af392691..49ab5394a 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/FunctionNode.java @@ -148,8 +148,7 @@ public final class FunctionNode extends RegularMemberNode { var parameters = CollectionUtils.newLinkedHashMap(paramCount); for (var i = 0; i < paramCount; i++) { var slotName = getFrameDescriptor().getSlotName(i); - // Ignored parameters (`_`) have no name - var paramName = slotName == null ? "_#" + i : slotName.toString(); + var paramName = slotName == Identifier.ILLEGAL ? "_#" + i : slotName.toString(); parameters.put(paramName, TypeNode.export(parameterTypeNodes[i])); } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/FrameDescriptorBuilder.java b/pkl-core/src/main/java/org/pkl/core/runtime/FrameDescriptorBuilder.java index eee64577f..d781fab22 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/FrameDescriptorBuilder.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/FrameDescriptorBuilder.java @@ -20,10 +20,7 @@ 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}. - */ +/** A wrapper for Truffle's {@link FrameDescriptor.Builder}, but also gives us the current size. */ public class FrameDescriptorBuilder { private @Nullable Identifier[] names; @@ -42,16 +39,6 @@ public class FrameDescriptorBuilder { 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); @@ -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); 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; + var slot = underlying.addSlot(kind, name, info); + return new FrameSlotVariable(name.toString(), slot); } public FrameDescriptor build() { return underlying.build(); } + + public int getSize() { + return size; + } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/FrameSlotVariable.java b/pkl-core/src/main/java/org/pkl/core/runtime/FrameSlotVariable.java new file mode 100644 index 000000000..44624aecf --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/runtime/FrameSlotVariable.java @@ -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) {} diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java index cb59defe2..78cafda13 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/Identifier.java @@ -163,6 +163,8 @@ public final class Identifier implements Comparable { public static final Identifier GLOB = get("glob"); public static final Identifier COMPLETION_CANDIDATES = get("completionCandidates"); + public static final Identifier ILLEGAL = get("`"); + // common in lambdas etc public static final Identifier IT = get("it"); diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java index 155cd5fad..2bddcb414 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java @@ -401,50 +401,42 @@ public final class VmUtils { } /** - * Copies {@code numberOfLocalsToCopy} locals from {@code sourceFrame}, starting at {@code - * firstSourceSlot}, to {@code firstSourceSlot}, starting at {@code firstTargetSlot}. + * Copies the slots specified by {@code slotsToCopy} locals from {@code sourceFrame} into {@code + * targetFrame}. */ public static void copyLocals( - VirtualFrame sourceFrame, - int firstSourceSlot, - VirtualFrame targetFrame, - int firstTargetSlot, - int numberOfLocalsToCopy) { + VirtualFrame sourceFrame, VirtualFrame targetFrame, int[] slotsToCopy) { + if (slotsToCopy.length == 0) return; 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++) { - var sourceSlot = firstSourceSlot + i; - var targetSlot = firstTargetSlot + i; + for (var slot : slotsToCopy) { // If, for a particular call site of this method, // slot kinds of `sourceDescriptor` will reach a steady state, // then slot kinds of `targetDescriptor` will too. - var slotKind = sourceDescriptor.getSlotKind(sourceSlot); + var slotKind = sourceDescriptor.getSlotKind(slot); switch (slotKind) { case Boolean -> { - targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Boolean); - targetFrame.setBoolean(targetSlot, sourceFrame.getBoolean(sourceSlot)); + targetDescriptor.setSlotKind(slot, FrameSlotKind.Boolean); + targetFrame.setBoolean(slot, sourceFrame.getBoolean(slot)); } case Long -> { - targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Long); - targetFrame.setLong(targetSlot, sourceFrame.getLong(sourceSlot)); + targetDescriptor.setSlotKind(slot, FrameSlotKind.Long); + targetFrame.setLong(slot, sourceFrame.getLong(slot)); } case Double -> { - targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Double); - targetFrame.setDouble(targetSlot, sourceFrame.getDouble(sourceSlot)); + targetDescriptor.setSlotKind(slot, FrameSlotKind.Double); + targetFrame.setDouble(slot, sourceFrame.getDouble(slot)); } case Object -> { - targetDescriptor.setSlotKind(targetSlot, FrameSlotKind.Object); + targetDescriptor.setSlotKind(slot, FrameSlotKind.Object); targetFrame.setObject( - targetSlot, + slot, sourceFrame instanceof MaterializedFrame // Even though sourceDescriptor.getSlotKind is now Object, // 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(). - ? sourceFrame.getValue(sourceSlot) - : sourceFrame.getObject(sourceSlot)); + // Hence, we need to read the local with getValue() instead of getObject(). + ? sourceFrame.getValue(slot) + : sourceFrame.getObject(slot)); } default -> { CompilerDirectives.transferToInterpreter(); @@ -978,10 +970,9 @@ public final class VmUtils { } public static int findCustomThisSlot(VirtualFrame frame) { - return frame - .getFrameDescriptor() - .getAuxiliarySlots() - .getOrDefault(CustomThisScope.FRAME_SLOT_ID, -1); + var result = frame.getFrameDescriptor().getAuxiliarySlots().get(CustomThisScope.FRAME_SLOT_ID); + assert result != null; + return result; } @TruffleBoundary diff --git a/pkl-core/src/main/java/org/pkl/core/util/ArrayUtils.java b/pkl-core/src/main/java/org/pkl/core/util/ArrayUtils.java new file mode 100644 index 000000000..ffd4ddd6c --- /dev/null +++ b/pkl-core/src/main/java/org/pkl/core/util/ArrayUtils.java @@ -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; + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/let2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/let2.pkl new file mode 100644 index 000000000..902fb0267 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/let2.pkl @@ -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" + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/let3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/let3.pkl new file mode 100644 index 000000000..2caf4b2ec --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/let3.pkl @@ -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 = new Mapping { + ["foo"] = "bar" + }) + new Mapping { + for (k, _ in Map("foo", "bar")) { + [k] { + m[k] + } + } + } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/basic/moduleRef4.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/moduleRef4.pkl new file mode 100644 index 000000000..ac78da9f0 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/basic/moduleRef4.pkl @@ -0,0 +1,11 @@ +prop = "qux" + +res = + new Mapping { + default { + when (module.prop == "qux") { + myProp = "qux" + } + } + ["bar"] {} + } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/elementGenerators.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/elementGenerators.pkl index 5398204ee..bc22f1d7a 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/elementGenerators.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/elementGenerators.pkl @@ -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") diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/entryGenerators.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/entryGenerators.pkl index 0b1f2dc86..4fce185bd 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/entryGenerators.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/entryGenerators.pkl @@ -475,3 +475,42 @@ withinLetExpr = [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 = new { + default { key -> + for (key, value in Map("foo", List(new Dynamic { bar = 1 }))) { + [key] { + ...value + } + } + } + ["res"] {} +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorVariableShadowing.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorVariableShadowing.pkl index 1a90b7125..9c4adf19e 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorVariableShadowing.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorVariableShadowing.pkl @@ -1,7 +1,7 @@ examples { local a = List("1", "2", "3", "4") local b = List("a", "b", "c", "d") - + ["shadow key variable"] { new { for (key, outerValue in a) { diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorsTypeConstraints.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorsTypeConstraints.pkl new file mode 100644 index 000000000..6fd1ee2f5 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorsTypeConstraints.pkl @@ -0,0 +1,5 @@ +res { + for (prefix in List("foo")) { + "foobar" as String(let (y = this) y.startsWith(prefix)) + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/predicateMembersListing.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/predicateMembersListing.pkl index 00ab2c30a..0ea352469 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/predicateMembersListing.pkl +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/predicateMembersListing.pkl @@ -43,7 +43,8 @@ res5 = new { // nested predicate 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 } } @@ -86,3 +87,28 @@ res12 = (people) { [[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") diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/let2.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/let2.pcf new file mode 100644 index 000000000..b13435df8 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/let2.pcf @@ -0,0 +1,13 @@ +res1 = "foo" +res2 = "foo" +res3 = "foo" +res4 { + new { + name = "bob" + } +} +res5 { + new { + name = "bob" + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/let3.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/let3.pcf new file mode 100644 index 000000000..6fb61c7ff --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/let3.pcf @@ -0,0 +1,17 @@ +res { + 6 + 7 +} +res2 { + 2 + 3 +} +res3 { + 2 + 3 +} +res4 { + ["foo"] { + "bar" + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/basic/moduleRef4.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/moduleRef4.pcf new file mode 100644 index 000000000..dae74bcf3 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/basic/moduleRef4.pcf @@ -0,0 +1,6 @@ +prop = "qux" +res { + ["bar"] { + myProp = "qux" + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/elementGenerators.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/elementGenerators.pcf index 8f4d411d6..170461468 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/elementGenerators.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/elementGenerators.pcf @@ -335,3 +335,15 @@ whenWithElse { 20 40 } +nestedForsWithinLambda { + "hi" + new { + 2 + 3 + } + "hi" + new { + 3 + 4 + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/entryGenerators.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/entryGenerators.pcf index 0be757745..f6c9a53df 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/entryGenerators.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/entryGenerators.pcf @@ -323,3 +323,32 @@ withinLetExpr { ["a"] = 1 ["b"] = 2 } +nestedObjectInsideFor { + [0] { + 1 + } + [1] { + 2 + } +} +doublyNestedObjectInsideFor { + [0] { + new { + 1 + } + } + [1] { + new { + 2 + } + } +} +insideObjectBodyWithParam { + ["res"] { + ["foo"] { + new { + bar = 1 + } + } + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/forGeneratorsTypeConstraints.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/forGeneratorsTypeConstraints.pcf new file mode 100644 index 000000000..9e9123840 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/forGeneratorsTypeConstraints.pcf @@ -0,0 +1,3 @@ +res { + "foobar" +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/predicateMembersListing.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/predicateMembersListing.pcf index 87393d3e8..a9db3a827 100644 --- a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/predicateMembersListing.pcf +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/predicateMembersListing.pcf @@ -153,3 +153,45 @@ res12 { 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 + } +}