mirror of
https://github.com/apple/pkl.git
synced 2026-07-08 14:05:17 +02:00
Eagerly check listing/mapping in iterables (#752)
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 the 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.
This commit is contained in:
@@ -55,6 +55,9 @@ public final class VmModifier {
|
|||||||
|
|
||||||
public static final int GLOB = 0x1000;
|
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
|
// modifier sets
|
||||||
|
|
||||||
public static final int NONE = 0;
|
public static final int NONE = 0;
|
||||||
@@ -134,6 +137,10 @@ public final class VmModifier {
|
|||||||
return (modifiers & ENTRY) != 0;
|
return (modifiers & ENTRY) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isInIterable(int modifiers) {
|
||||||
|
return (modifiers & IS_IN_ITERABLE) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isType(int modifiers) {
|
public static boolean isType(int modifiers) {
|
||||||
return (modifiers & (CLASS | TYPE_ALIAS | IMPORT)) != 0 && (modifiers & GLOB) == 0;
|
return (modifiers & (CLASS | TYPE_ALIAS | IMPORT)) != 0 && (modifiers & GLOB) == 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -900,8 +900,12 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GeneratorMemberNode visitObjectSpread(ObjectSpreadContext ctx) {
|
public GeneratorMemberNode visitObjectSpread(ObjectSpreadContext ctx) {
|
||||||
return GeneratorSpreadNodeGen.create(
|
var scope = symbolTable.getCurrentScope();
|
||||||
createSourceSection(ctx), visitExpr(ctx.expr()), ctx.QSPREAD() != null);
|
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) {
|
private void insertWriteForGeneratorVarsToFrameSlotsNode(@Nullable MemberNode memberNode) {
|
||||||
@@ -992,7 +996,11 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
ignoreT1 ? null : visitTypeAnnotation(ctx.t1.typedIdentifier().typeAnnotation());
|
ignoreT1 ? null : visitTypeAnnotation(ctx.t1.typedIdentifier().typeAnnotation());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scope = symbolTable.getCurrentScope();
|
||||||
|
var visitingIterable = scope.isVisitingIterable();
|
||||||
|
scope.setVisitingIterable(true);
|
||||||
var iterableNode = visitExpr(ctx.e);
|
var iterableNode = visitExpr(ctx.e);
|
||||||
|
scope.setVisitingIterable(visitingIterable);
|
||||||
var memberNodes = doVisitForWhenBody(ctx.objectBody());
|
var memberNodes = doVisitForWhenBody(ctx.objectBody());
|
||||||
if (keyVariableSlot != -1) {
|
if (keyVariableSlot != -1) {
|
||||||
currentScope.popForGeneratorVariable();
|
currentScope.popForGeneratorVariable();
|
||||||
@@ -1197,11 +1205,15 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
scope -> {
|
scope -> {
|
||||||
var elementNode = visitExpr(ctx.expr());
|
var elementNode = visitExpr(ctx.expr());
|
||||||
|
|
||||||
|
var modifier =
|
||||||
|
scope.isVisitingIterable()
|
||||||
|
? VmModifier.ELEMENT | VmModifier.IS_IN_ITERABLE
|
||||||
|
: VmModifier.ELEMENT;
|
||||||
var member =
|
var member =
|
||||||
new ObjectMember(
|
new ObjectMember(
|
||||||
createSourceSection(ctx),
|
createSourceSection(ctx),
|
||||||
elementNode.getSourceSection(),
|
elementNode.getSourceSection(),
|
||||||
VmModifier.ELEMENT,
|
modifier,
|
||||||
null,
|
null,
|
||||||
scope.getQualifiedName());
|
scope.getQualifiedName());
|
||||||
|
|
||||||
@@ -1252,13 +1264,13 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
@Nullable ExprContext valueCtx,
|
@Nullable ExprContext valueCtx,
|
||||||
List<? extends ObjectBodyContext> objectBodyCtxs) {
|
List<? extends ObjectBodyContext> objectBodyCtxs) {
|
||||||
return scope -> {
|
return scope -> {
|
||||||
|
var modifier =
|
||||||
|
scope.isVisitingIterable()
|
||||||
|
? VmModifier.ENTRY | VmModifier.IS_IN_ITERABLE
|
||||||
|
: VmModifier.ENTRY;
|
||||||
var member =
|
var member =
|
||||||
new ObjectMember(
|
new ObjectMember(
|
||||||
sourceSection,
|
sourceSection, keyNode.getSourceSection(), modifier, null, scope.getQualifiedName());
|
||||||
keyNode.getSourceSection(),
|
|
||||||
VmModifier.ENTRY,
|
|
||||||
null,
|
|
||||||
scope.getQualifiedName());
|
|
||||||
|
|
||||||
if (valueCtx != null) { // ["key"] = value
|
if (valueCtx != null) { // ["key"] = value
|
||||||
var valueNode = visitExpr(valueCtx);
|
var valueNode = visitExpr(valueCtx);
|
||||||
@@ -1338,6 +1350,10 @@ public final class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
result += modifier;
|
result += modifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (symbolTable.getCurrentScope().isVisitingIterable()) {
|
||||||
|
result += VmModifier.IS_IN_ITERABLE;
|
||||||
|
}
|
||||||
|
|
||||||
// flag modifier combinations that are never valid right away
|
// flag modifier combinations that are never valid right away
|
||||||
|
|
||||||
if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) {
|
if (VmModifier.isExternal(result) && !ModuleKeys.isStdLibModule(moduleKey)) {
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ public final class SymbolTable {
|
|||||||
private int entryCount = 0;
|
private int entryCount = 0;
|
||||||
private final FrameDescriptor.Builder frameDescriptorBuilder;
|
private final FrameDescriptor.Builder frameDescriptorBuilder;
|
||||||
private final ConstLevel constLevel;
|
private final ConstLevel constLevel;
|
||||||
|
private boolean isVisitingIterable;
|
||||||
|
|
||||||
private Scope(
|
private Scope(
|
||||||
@Nullable Scope parent,
|
@Nullable Scope parent,
|
||||||
@@ -187,6 +188,7 @@ public final class SymbolTable {
|
|||||||
parent != null && parent.constLevel.biggerOrEquals(constLevel)
|
parent != null && parent.constLevel.biggerOrEquals(constLevel)
|
||||||
? parent.constLevel
|
? parent.constLevel
|
||||||
: constLevel;
|
: constLevel;
|
||||||
|
this.isVisitingIterable = parent != null && parent.isVisitingIterable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final @Nullable Scope getParent() {
|
public final @Nullable Scope getParent() {
|
||||||
@@ -337,6 +339,14 @@ public final class SymbolTable {
|
|||||||
public ConstLevel getConstLevel() {
|
public ConstLevel getConstLevel() {
|
||||||
return constLevel;
|
return constLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setVisitingIterable(boolean isVisitingIterable) {
|
||||||
|
this.isVisitingIterable = isVisitingIterable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVisitingIterable() {
|
||||||
|
return isVisitingIterable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface LexicalScope {}
|
private interface LexicalScope {}
|
||||||
|
|||||||
@@ -130,4 +130,21 @@ public abstract class Member {
|
|||||||
public final boolean isLocalOrExternalOrAbstract() {
|
public final boolean isLocalOrExternalOrAbstract() {
|
||||||
return VmModifier.isLocalOrExternalOrAbstract(modifiers);
|
return VmModifier.isLocalOrExternalOrAbstract(modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells if this member is declared inside the iterable of a for-generator, or an object spread.
|
||||||
|
* <p>
|
||||||
|
* This is {@code true} for {@code new {}} within:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* {@code
|
||||||
|
* for (x in new Listing { new {} }) {
|
||||||
|
* ^^^^^^
|
||||||
|
* // etc
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public boolean isInIterable() {
|
||||||
|
return VmModifier.isInIterable(modifiers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.pkl.core.runtime.VmUtils;
|
|||||||
import org.pkl.core.util.Nullable;
|
import org.pkl.core.util.Nullable;
|
||||||
|
|
||||||
public final class ObjectMember extends Member {
|
public final class ObjectMember extends Member {
|
||||||
|
|
||||||
@CompilationFinal private @Nullable Object constantValue;
|
@CompilationFinal private @Nullable Object constantValue;
|
||||||
@CompilationFinal private @Nullable MemberNode memberNode;
|
@CompilationFinal private @Nullable MemberNode memberNode;
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,25 @@ public final class PropertyTypeNode extends PklRootNode {
|
|||||||
return qualifiedPropertyName;
|
return qualifiedPropertyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isInIterable(VirtualFrame frame) {
|
||||||
|
var args = frame.getArguments();
|
||||||
|
return args.length >= 4 && args[3] instanceof Boolean b && b;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object execute(VirtualFrame frame) {
|
public Object execute(VirtualFrame frame) {
|
||||||
try {
|
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]);
|
return typeNode.execute(frame, frame.getArguments()[2]);
|
||||||
} catch (VmTypeMismatchException e) {
|
} catch (VmTypeMismatchException e) {
|
||||||
CompilerDirectives.transferToInterpreter();
|
CompilerDirectives.transferToInterpreter();
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ public abstract class TypeCheckedPropertyNode extends RegularMemberNode {
|
|||||||
|
|
||||||
// TODO: propagate SUPER_CALL_MARKER to disable constraint (but not type) check
|
// TODO: propagate SUPER_CALL_MARKER to disable constraint (but not type) check
|
||||||
if (callNode != null && VmUtils.shouldRunTypeCheck(frame)) {
|
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;
|
return result;
|
||||||
@@ -75,7 +76,8 @@ public abstract class TypeCheckedPropertyNode extends RegularMemberNode {
|
|||||||
typeAnnNode.getCallTarget(),
|
typeAnnNode.getCallTarget(),
|
||||||
VmUtils.getReceiverOrNull(frame),
|
VmUtils.getReceiverOrNull(frame),
|
||||||
property.getOwner(),
|
property.getOwner(),
|
||||||
result);
|
result,
|
||||||
|
member.isInIterable());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,10 @@ public final class TypedPropertyNode extends RegularMemberNode {
|
|||||||
var propertyValue = executeBody(frame);
|
var propertyValue = executeBody(frame);
|
||||||
if (VmUtils.shouldRunTypeCheck(frame)) {
|
if (VmUtils.shouldRunTypeCheck(frame)) {
|
||||||
return typeCheckCallNode.call(
|
return typeCheckCallNode.call(
|
||||||
VmUtils.getReceiver(frame), VmUtils.getOwner(frame), propertyValue);
|
VmUtils.getReceiver(frame),
|
||||||
|
VmUtils.getOwner(frame),
|
||||||
|
propertyValue,
|
||||||
|
member.isInIterable());
|
||||||
}
|
}
|
||||||
return propertyValue;
|
return propertyValue;
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+33
@@ -0,0 +1,33 @@
|
|||||||
|
class Aviary {
|
||||||
|
birds: Listing<Bird>
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
res1 {
|
||||||
|
new {
|
||||||
|
age = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res2 {
|
||||||
|
new {
|
||||||
|
age = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user