diff --git a/pkl-core/src/main/java/org/pkl/core/ast/VmModifier.java b/pkl-core/src/main/java/org/pkl/core/ast/VmModifier.java index d13995828..097ce0666 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/VmModifier.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/VmModifier.java @@ -55,6 +55,9 @@ private VmModifier() {} public static final int GLOB = 0x1000; + // To be removed when https://github.com/apple/pkl/issues/741 is fixed + public static final int IS_IN_ITERABLE = 0x100000; + // modifier sets public static final int NONE = 0; @@ -134,6 +137,10 @@ public static boolean isEntry(int modifiers) { return (modifiers & ENTRY) != 0; } + public static boolean isInIterable(int modifiers) { + return (modifiers & IS_IN_ITERABLE) != 0; + } + public static boolean isType(int modifiers) { return (modifiers & (CLASS | TYPE_ALIAS | IMPORT)) != 0 && (modifiers & GLOB) == 0; } 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 44b125804..b80ccc4cf 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 @@ -900,8 +900,12 @@ public GeneratorMemberNode visitObjectEntry(ObjectEntryContext ctx) { @Override public GeneratorMemberNode visitObjectSpread(ObjectSpreadContext ctx) { - return GeneratorSpreadNodeGen.create( - createSourceSection(ctx), visitExpr(ctx.expr()), ctx.QSPREAD() != null); + var scope = symbolTable.getCurrentScope(); + var visitingIterable = scope.isVisitingIterable(); + scope.setVisitingIterable(true); + var expr = visitExpr(ctx.expr()); + scope.setVisitingIterable(visitingIterable); + return GeneratorSpreadNodeGen.create(createSourceSection(ctx), expr, ctx.QSPREAD() != null); } private void insertWriteForGeneratorVarsToFrameSlotsNode(@Nullable MemberNode memberNode) { @@ -992,7 +996,11 @@ public GeneratorForNode visitForGenerator(ForGeneratorContext ctx) { ignoreT1 ? null : visitTypeAnnotation(ctx.t1.typedIdentifier().typeAnnotation()); } + var scope = symbolTable.getCurrentScope(); + var visitingIterable = scope.isVisitingIterable(); + scope.setVisitingIterable(true); var iterableNode = visitExpr(ctx.e); + scope.setVisitingIterable(visitingIterable); var memberNodes = doVisitForWhenBody(ctx.objectBody()); if (keyVariableSlot != -1) { currentScope.popForGeneratorVariable(); @@ -1197,11 +1205,15 @@ private ObjectMember doVisitObjectElement(ObjectElementContext ctx) { scope -> { var elementNode = visitExpr(ctx.expr()); + var modifier = + scope.isVisitingIterable() + ? VmModifier.ELEMENT | VmModifier.IS_IN_ITERABLE + : VmModifier.ELEMENT; var member = new ObjectMember( createSourceSection(ctx), elementNode.getSourceSection(), - VmModifier.ELEMENT, + modifier, null, scope.getQualifiedName()); @@ -1252,13 +1264,13 @@ private Function> objectMemberIns @Nullable ExprContext valueCtx, List objectBodyCtxs) { return scope -> { + var modifier = + scope.isVisitingIterable() + ? VmModifier.ENTRY | VmModifier.IS_IN_ITERABLE + : VmModifier.ENTRY; var member = new ObjectMember( - sourceSection, - keyNode.getSourceSection(), - VmModifier.ENTRY, - null, - scope.getQualifiedName()); + sourceSection, keyNode.getSourceSection(), modifier, null, scope.getQualifiedName()); if (valueCtx != null) { // ["key"] = value var valueNode = visitExpr(valueCtx); @@ -1338,6 +1350,10 @@ private int doVisitModifiers( result += modifier; } + if (symbolTable.getCurrentScope().isVisitingIterable()) { + result += VmModifier.IS_IN_ITERABLE; + } + // flag modifier combinations that are never valid right away if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) { 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 c1aa3d6a0..6fec38568 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 @@ -171,6 +171,7 @@ public abstract static class Scope { private int entryCount = 0; private final FrameDescriptor.Builder frameDescriptorBuilder; private final ConstLevel constLevel; + private boolean isVisitingIterable; private Scope( @Nullable Scope parent, @@ -187,6 +188,7 @@ private Scope( parent != null && parent.constLevel.biggerOrEquals(constLevel) ? parent.constLevel : constLevel; + this.isVisitingIterable = parent != null && parent.isVisitingIterable; } public final @Nullable Scope getParent() { @@ -337,6 +339,14 @@ public final boolean isLexicalScope() { public ConstLevel getConstLevel() { return constLevel; } + + public void setVisitingIterable(boolean isVisitingIterable) { + this.isVisitingIterable = isVisitingIterable; + } + + public boolean isVisitingIterable() { + return isVisitingIterable; + } } private interface LexicalScope {} diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/Member.java b/pkl-core/src/main/java/org/pkl/core/ast/member/Member.java index 70d2a4b2c..8363b9d5a 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/Member.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/Member.java @@ -130,4 +130,21 @@ public final boolean isConstOrFixed() { public final boolean isLocalOrExternalOrAbstract() { return VmModifier.isLocalOrExternalOrAbstract(modifiers); } + + /** + * Tells if this member is declared inside the iterable of a for-generator, or an object spread. + *

+ * This is {@code true} for {@code new {}} within: + * + *

+   * {@code
+   * for (x in new Listing { new {} }) {
+   *                         ^^^^^^
+   *   // etc
+   * }
+   * 
+ */ + public boolean isInIterable() { + return VmModifier.isInIterable(modifiers); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java index 42a7d46d1..5d6d5a7f2 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/ObjectMember.java @@ -27,6 +27,7 @@ import org.pkl.core.util.Nullable; public final class ObjectMember extends Member { + @CompilationFinal private @Nullable Object constantValue; @CompilationFinal private @Nullable MemberNode memberNode; diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java index bfadb6702..09bd3caab 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/PropertyTypeNode.java @@ -61,9 +61,25 @@ public String getName() { return qualifiedPropertyName; } + private boolean isInIterable(VirtualFrame frame) { + var args = frame.getArguments(); + return args.length >= 4 && args[3] instanceof Boolean b && b; + } + @Override public Object execute(VirtualFrame frame) { try { + if (isInIterable(frame)) { + // There is currently a bug around resolving variables within the iterable of a for + // generator or spread syntax (https://github.com/apple/pkl/issues/741) + // + // Normally, mappings/listings are type-checked lazily. However, this results in said + // bug getting widened, for any object members declared in the iterable. + // + // As a workaround for now, prevent the bug from being any worse by ensuring that these + // object members are eagerly typechecked. + return typeNode.executeEagerly(frame, frame.getArguments()[2]); + } return typeNode.execute(frame, frame.getArguments()[2]); } catch (VmTypeMismatchException e) { CompilerDirectives.transferToInterpreter(); diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/TypeCheckedPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/TypeCheckedPropertyNode.java index 320b3baff..08bc1b535 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/TypeCheckedPropertyNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/TypeCheckedPropertyNode.java @@ -55,7 +55,8 @@ protected Object evalTypedObjectCached( // TODO: propagate SUPER_CALL_MARKER to disable constraint (but not type) check if (callNode != null && VmUtils.shouldRunTypeCheck(frame)) { - return callNode.call(VmUtils.getReceiverOrNull(frame), property.getOwner(), result); + return callNode.call( + VmUtils.getReceiverOrNull(frame), property.getOwner(), result, member.isInIterable()); } return result; @@ -75,7 +76,8 @@ protected Object eval( typeAnnNode.getCallTarget(), VmUtils.getReceiverOrNull(frame), property.getOwner(), - result); + result, + member.isInIterable()); } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/TypedPropertyNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/TypedPropertyNode.java index 3c092c36b..8677d02ba 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/TypedPropertyNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/TypedPropertyNode.java @@ -47,7 +47,10 @@ public Object execute(VirtualFrame frame) { var propertyValue = executeBody(frame); if (VmUtils.shouldRunTypeCheck(frame)) { return typeCheckCallNode.call( - VmUtils.getReceiver(frame), VmUtils.getOwner(frame), propertyValue); + VmUtils.getReceiver(frame), + VmUtils.getOwner(frame), + propertyValue, + member.isInIterable()); } return propertyValue; } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorNestedReference.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorNestedReference.pkl new file mode 100644 index 000000000..dda47f769 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/generators/forGeneratorNestedReference.pkl @@ -0,0 +1,33 @@ +class Aviary { + birds: Listing +} + +class Bird { + age: Int +} + +function Bird(_age: Int) = new Bird { age = _age } + +res1 { + for (i in IntSeq(1, 1)) { + for (j in + new Aviary { + birds { + Bird(i) + } + }.birds + ) { + j + } + } +} + +res2 { + for (i in IntSeq(1, 1)) { + ...new Aviary { + birds { + Bird(i) + } + }.birds + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/generators/forGeneratorNestedReference.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/forGeneratorNestedReference.pcf new file mode 100644 index 000000000..18c86a66c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/generators/forGeneratorNestedReference.pcf @@ -0,0 +1,10 @@ +res1 { + new { + age = 1 + } +} +res2 { + new { + age = 1 + } +}