mirror of
https://github.com/apple/pkl.git
synced 2026-09-17 23:31:30 +02:00
Add inference for let bindings (#1824)
This commit is contained in:
@@ -109,6 +109,7 @@ import org.pkl.core.ast.expression.literal.MapLiteralNode;
|
|||||||
import org.pkl.core.ast.expression.literal.PropertiesLiteralNodeGen;
|
import org.pkl.core.ast.expression.literal.PropertiesLiteralNodeGen;
|
||||||
import org.pkl.core.ast.expression.literal.SetLiteralNode;
|
import org.pkl.core.ast.expression.literal.SetLiteralNode;
|
||||||
import org.pkl.core.ast.expression.literal.TrueLiteralNode;
|
import org.pkl.core.ast.expression.literal.TrueLiteralNode;
|
||||||
|
import org.pkl.core.ast.expression.member.InferParentWithinLetBindingNodeGen;
|
||||||
import org.pkl.core.ast.expression.member.InferParentWithinMethodArgumentNodeGen;
|
import org.pkl.core.ast.expression.member.InferParentWithinMethodArgumentNodeGen;
|
||||||
import org.pkl.core.ast.expression.member.InferParentWithinMethodNodeGen;
|
import org.pkl.core.ast.expression.member.InferParentWithinMethodNodeGen;
|
||||||
import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNodeGen;
|
import org.pkl.core.ast.expression.member.InferParentWithinObjectMethodNodeGen;
|
||||||
@@ -1073,6 +1074,7 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ExpressionNode doVisitNewExprWithInferredParent(NewExpr expr) {
|
private ExpressionNode doVisitNewExprWithInferredParent(NewExpr expr) {
|
||||||
|
var sourceSection = createSourceSection(expr.newSpan());
|
||||||
ExpressionNode inferredParentNode;
|
ExpressionNode inferredParentNode;
|
||||||
|
|
||||||
Node child = expr;
|
Node child = expr;
|
||||||
@@ -1083,8 +1085,8 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
while (parent instanceof IfExpr ifExpr
|
while (parent instanceof IfExpr ifExpr
|
||||||
&& (ifExpr.getThen() == child || ifExpr.getEls() == child)
|
&& (ifExpr.getThen() == child || ifExpr.getEls() == child)
|
||||||
|| parent instanceof TraceExpr
|
|| parent instanceof TraceExpr
|
||||||
|| parent instanceof LetExpr letExpr && letExpr.getExpr() == child) {
|
|| parent instanceof LetExpr letExpr && letExpr.getExpr() == child
|
||||||
|
|| parent instanceof ParenthesizedExpr) {
|
||||||
child = parent;
|
child = parent;
|
||||||
parent = parent.parent();
|
parent = parent.parent();
|
||||||
}
|
}
|
||||||
@@ -1092,15 +1094,13 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
if (parent instanceof ClassProperty || parent instanceof ObjectProperty) {
|
if (parent instanceof ClassProperty || parent instanceof ObjectProperty) {
|
||||||
inferredParentNode =
|
inferredParentNode =
|
||||||
InferParentWithinPropertyNodeGen.create(
|
InferParentWithinPropertyNodeGen.create(
|
||||||
createSourceSection(expr.newSpan()), scope.getName(), new GetOwnerNode());
|
sourceSection, scope.getName(), new GetOwnerNode());
|
||||||
} else if (parent instanceof ObjectElement
|
} else if (parent instanceof ObjectElement
|
||||||
|| parent instanceof ObjectEntry objectEntry && objectEntry.getValue() == child) {
|
|| parent instanceof ObjectEntry objectEntry && objectEntry.getValue() == child) {
|
||||||
inferredParentNode =
|
inferredParentNode =
|
||||||
ApplyVmFunction1NodeGen.create(
|
ApplyVmFunction1NodeGen.create(
|
||||||
ReadPropertyNodeGen.create(
|
ReadPropertyNodeGen.create(
|
||||||
createSourceSection(expr.newSpan()),
|
sourceSection, org.pkl.core.runtime.Identifier.DEFAULT, new GetReceiverNode()),
|
||||||
org.pkl.core.runtime.Identifier.DEFAULT,
|
|
||||||
new GetReceiverNode()),
|
|
||||||
new GetMemberKeyNode());
|
new GetMemberKeyNode());
|
||||||
} else if (parent instanceof ClassMethod || parent instanceof ObjectMethod) {
|
} else if (parent instanceof ClassMethod || parent instanceof ObjectMethod) {
|
||||||
var isObjectMethod =
|
var isObjectMethod =
|
||||||
@@ -1110,29 +1110,24 @@ public class AstBuilder extends AbstractAstBuilder<Object> {
|
|||||||
inferredParentNode =
|
inferredParentNode =
|
||||||
isObjectMethod
|
isObjectMethod
|
||||||
? InferParentWithinObjectMethodNodeGen.create(
|
? InferParentWithinObjectMethodNodeGen.create(
|
||||||
createSourceSection(expr.newSpan()), language, scopeName, new GetOwnerNode())
|
sourceSection, language, scopeName, new GetOwnerNode())
|
||||||
: InferParentWithinMethodNodeGen.create(
|
: InferParentWithinMethodNodeGen.create(
|
||||||
createSourceSection(expr.newSpan()), language, scopeName, new GetOwnerNode());
|
sourceSection, language, scopeName, new GetOwnerNode());
|
||||||
} else if (parent instanceof LetExpr letExpr && letExpr.getBindingExpr() == child) {
|
} else if (parent instanceof LetExpr letExpr && letExpr.getBindingExpr() == child) {
|
||||||
// TODO correctly infer parent, e.g. `let (x: Person = new {}) ...`
|
inferredParentNode = InferParentWithinLetBindingNodeGen.create(sourceSection, language);
|
||||||
throw exceptionBuilder()
|
|
||||||
.evalError("cannotInferParent")
|
|
||||||
.withSourceSection(createSourceSection(expr.newSpan()))
|
|
||||||
.build();
|
|
||||||
} else if (parent instanceof ArgumentList argumentList) {
|
} else if (parent instanceof ArgumentList argumentList) {
|
||||||
// cases we can't cover currently
|
// cases we can't cover currently
|
||||||
// - FunctionN.apply: the parameter type nodes are not stored
|
// - FunctionN.apply: the parameter type nodes are not stored
|
||||||
// - pkl.base intrinsic constructors: List(), Set(), Map(), Bytes()
|
// - pkl.base intrinsic constructors: List(), Set(), Map(), Bytes()
|
||||||
// - generic methods: pkl.base#Pair(), etc.
|
// - generic methods: pkl.base#Pair(), etc.
|
||||||
// these will throw cannotInferParent at runtime
|
// these will throw cannotInferParent at runtime
|
||||||
var sourceSection = createSourceSection(expr.newSpan());
|
|
||||||
var argIndex = argumentList.getArguments().indexOf(child);
|
var argIndex = argumentList.getArguments().indexOf(child);
|
||||||
inferredParentNode =
|
inferredParentNode =
|
||||||
InferParentWithinMethodArgumentNodeGen.create(sourceSection, language, argIndex);
|
InferParentWithinMethodArgumentNodeGen.create(sourceSection, language, argIndex);
|
||||||
} else {
|
} else {
|
||||||
throw exceptionBuilder()
|
throw exceptionBuilder()
|
||||||
.evalError("cannotInferParent")
|
.evalError("cannotInferParent")
|
||||||
.withSourceSection(createSourceSection(expr.newSpan()))
|
.withSourceSection(sourceSection)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,22 +49,29 @@ public abstract class LetExprNode extends ExpressionNode {
|
|||||||
this.slot = slot;
|
this.slot = slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeNode getTypeNode(VirtualFrame frame) {
|
public TypeNode getTypeNode(VirtualFrame frame) {
|
||||||
if (typeNode == null) {
|
if (typeNode != null) return typeNode;
|
||||||
|
|
||||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||||
if (unresolvedTypeNode != null) {
|
if (unresolvedTypeNode != null && slot >= 0) {
|
||||||
typeNode = unresolvedTypeNode.execute(frame);
|
typeNode = unresolvedTypeNode.execute(frame);
|
||||||
} else {
|
} else {
|
||||||
typeNode = new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection());
|
typeNode = new TypeNode.UnknownTypeNode(VmUtils.unavailableSourceSection());
|
||||||
}
|
}
|
||||||
|
if (slot >= 0) {
|
||||||
typeNode.initWriteSlotNode(slot);
|
typeNode.initWriteSlotNode(slot);
|
||||||
frame.getFrameDescriptor().setSlotKind(slot, typeNode.getFrameSlotKind());
|
frame.getFrameDescriptor().setSlotKind(slot, typeNode.getFrameSlotKind());
|
||||||
insert(typeNode);
|
|
||||||
}
|
}
|
||||||
assert typeNode != null;
|
assert typeNode != null;
|
||||||
return typeNode;
|
return insert(typeNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getQualifiedName() {
|
||||||
|
return qualifiedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ExpressionNode getBindingNode();
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
protected Object eval(VirtualFrame frame, Object value) {
|
protected Object eval(VirtualFrame frame, Object value) {
|
||||||
if (slot != -1) {
|
if (slot != -1) {
|
||||||
|
|||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pkl.core.ast.expression.member;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
|
import com.oracle.truffle.api.source.SourceSection;
|
||||||
|
import org.pkl.core.ast.expression.binary.LetExprNode;
|
||||||
|
import org.pkl.core.ast.type.TypeNode;
|
||||||
|
import org.pkl.core.runtime.VmLanguage;
|
||||||
|
|
||||||
|
public abstract class InferParentWithinLetBindingNode extends AbstractInferParentNode {
|
||||||
|
public InferParentWithinLetBindingNode(SourceSection sourceSection, VmLanguage language) {
|
||||||
|
super(sourceSection, language);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LetExprNode getLetNode() {
|
||||||
|
Node child = this;
|
||||||
|
LetExprNode letNode = null;
|
||||||
|
for (var node = getParent(); node != null; node = node.getParent()) {
|
||||||
|
if (node instanceof LetExprNode let && let.getBindingNode() == child) {
|
||||||
|
letNode = let;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
child = node;
|
||||||
|
}
|
||||||
|
assert letNode != null
|
||||||
|
: "AstBuilder created an InferParentWithinLetBindingNode outside of a let binding";
|
||||||
|
return letNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep specializations in sync with other AbstractInferParentNode subclasses
|
||||||
|
|
||||||
|
@Specialization(guards = {"typeNode.isFinalType()"})
|
||||||
|
protected final Object evalCached(
|
||||||
|
@SuppressWarnings("unused") VirtualFrame frame,
|
||||||
|
@Cached(value = "getLetNode()", neverDefault = true, adopt = false)
|
||||||
|
@Shared
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
LetExprNode letNode,
|
||||||
|
@Cached(value = "letNode.getTypeNode(frame)", neverDefault = true, adopt = false)
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
TypeNode typeNode,
|
||||||
|
@Cached(
|
||||||
|
value =
|
||||||
|
"getDefaultValue(frame, typeNode, letNode.getSourceSection(), letNode.getQualifiedName())",
|
||||||
|
neverDefault = true)
|
||||||
|
Object defaultValue) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
protected final Object eval(
|
||||||
|
VirtualFrame frame,
|
||||||
|
@Cached(value = "getLetNode()", neverDefault = true, adopt = false) @Shared
|
||||||
|
LetExprNode letNode,
|
||||||
|
@Cached(value = "letNode.getTypeNode(frame)", neverDefault = true, adopt = false)
|
||||||
|
TypeNode typeNode) {
|
||||||
|
return getDefaultValue(frame, typeNode, letNode.getSourceSection(), letNode.getQualifiedName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
|
|||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||||
import com.oracle.truffle.api.dsl.Cached;
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
import com.oracle.truffle.api.dsl.Fallback;
|
import com.oracle.truffle.api.dsl.Fallback;
|
||||||
|
import com.oracle.truffle.api.dsl.Idempotent;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
import com.oracle.truffle.api.frame.FrameDescriptor;
|
import com.oracle.truffle.api.frame.FrameDescriptor;
|
||||||
import com.oracle.truffle.api.frame.FrameSlotKind;
|
import com.oracle.truffle.api.frame.FrameSlotKind;
|
||||||
@@ -140,6 +141,7 @@ public abstract class TypeNode extends PklNode {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Idempotent
|
||||||
public final boolean isFinalType() {
|
public final boolean isFinalType() {
|
||||||
var ret = new MutableBoolean(true);
|
var ret = new MutableBoolean(true);
|
||||||
acceptTypeNode(
|
acceptTypeNode(
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
open module letTyped
|
||||||
|
|
||||||
import "pkl:test"
|
import "pkl:test"
|
||||||
|
|
||||||
|
y: Int = 0
|
||||||
|
|
||||||
res1 =
|
res1 =
|
||||||
let (x: Int = 42)
|
let (x: Int = 42)
|
||||||
x + 1
|
x + 1
|
||||||
@@ -17,19 +21,74 @@ res4 =
|
|||||||
let (xs: List<Null | Boolean | "Pigeon"> = List("Pigeon", true, null))
|
let (xs: List<Null | Boolean | "Pigeon"> = List("Pigeon", true, null))
|
||||||
xs
|
xs
|
||||||
|
|
||||||
res5 = test.catch(() ->
|
res5 =
|
||||||
|
test.catch(() ->
|
||||||
let (x: String = 42)
|
let (x: String = 42)
|
||||||
x + 1)
|
x + 1
|
||||||
|
)
|
||||||
|
|
||||||
res6 = test.catch(() ->
|
res6 =
|
||||||
|
test.catch(() ->
|
||||||
let (str: String(endsWith("A")) = "Pigeon".reverse())
|
let (str: String(endsWith("A")) = "Pigeon".reverse())
|
||||||
str.reverse())
|
str.reverse()
|
||||||
|
)
|
||||||
|
|
||||||
res7 = test.catch(() ->
|
res7 =
|
||||||
|
test.catch(() ->
|
||||||
let (x: Duration(unit == "min") = 3.min + 2.min)
|
let (x: Duration(unit == "min") = 3.min + 2.min)
|
||||||
let (y: Float(!isPositive) = 1.2 + 1.3)
|
let (y: Float(!isPositive) = 1.2 + 1.3)
|
||||||
x * y)
|
x * y
|
||||||
|
)
|
||||||
|
|
||||||
res8 = test.catch(() ->
|
res8 =
|
||||||
|
test.catch(() ->
|
||||||
let (xs: List<Null | Boolean | "Pigeon"> = List("Barn Owl", true, null))
|
let (xs: List<Null | Boolean | "Pigeon"> = List("Barn Owl", true, null))
|
||||||
xs)
|
xs
|
||||||
|
)
|
||||||
|
|
||||||
|
// test basic `new {}` inference
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
y: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
res9 =
|
||||||
|
let (x: Foo = new { y = 1 })
|
||||||
|
x.y
|
||||||
|
|
||||||
|
// test inference with let in binding expression
|
||||||
|
|
||||||
|
res10 =
|
||||||
|
let (x: Foo = let (z = 0) new { y = z })
|
||||||
|
x.y
|
||||||
|
res10a =
|
||||||
|
let (x: Foo = (new { y = 0 }))
|
||||||
|
x.y
|
||||||
|
res10b =
|
||||||
|
let (x = new { y = 0 })
|
||||||
|
x.getClass().toString()
|
||||||
|
|
||||||
|
// test inference against underscore binding
|
||||||
|
|
||||||
|
res11 = let (_ = new { y = 1 }) 0 // binding is a new Dynamic
|
||||||
|
|
||||||
|
// test inference of non-instantiable types
|
||||||
|
|
||||||
|
abstract class Bar {
|
||||||
|
y: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
res12a = test.catch(() -> let (x: Int = new { y = 1 }) x)
|
||||||
|
res12b = test.catch(() -> let (x: Bar = new { y = 1 }) x)
|
||||||
|
res12c = test.catch(() -> let (x: nothing = new { y = 1 }) x)
|
||||||
|
|
||||||
|
// test inference of a self type
|
||||||
|
|
||||||
|
function make(_y: Int): module =
|
||||||
|
let (z: module = new { y = _y })
|
||||||
|
z
|
||||||
|
|
||||||
|
class Child extends module
|
||||||
|
|
||||||
|
res13a = make(2).getClass().toString()
|
||||||
|
res13b = new Child {}.make(3).getClass().toString()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
y = 0
|
||||||
res1 = 43
|
res1 = 43
|
||||||
res2 = "Pigeon"
|
res2 = "Pigeon"
|
||||||
res3 = 12.5.min
|
res3 = 12.5.min
|
||||||
@@ -6,3 +7,13 @@ res5 = "Expected value of type `String`, but got type `Int`. Value: 42"
|
|||||||
res6 = "Type constraint `endsWith(\"A\")` violated. Value: \"noegiP\""
|
res6 = "Type constraint `endsWith(\"A\")` violated. Value: \"noegiP\""
|
||||||
res7 = "Type constraint `!isPositive` violated. Value: 2.5"
|
res7 = "Type constraint `!isPositive` violated. Value: 2.5"
|
||||||
res8 = "Expected value of type `Null | Boolean | \"Pigeon\"`, but got type `String`. Value: \"Barn Owl\""
|
res8 = "Expected value of type `Null | Boolean | \"Pigeon\"`, but got type `String`. Value: \"Barn Owl\""
|
||||||
|
res9 = 1
|
||||||
|
res10 = 0
|
||||||
|
res10a = 0
|
||||||
|
res10b = "Dynamic"
|
||||||
|
res11 = 0
|
||||||
|
res12a = "Cannot instantiate, or amend an instance of, external class `Int`."
|
||||||
|
res12b = "Cannot instantiate abstract class `letTyped#Bar`."
|
||||||
|
res12c = "Cannot instantiate type `nothing`."
|
||||||
|
res13a = "letTyped"
|
||||||
|
res13b = "letTyped#Child"
|
||||||
|
|||||||
Reference in New Issue
Block a user