Fix parameterized type checks in alias constraints (#1906)

Fixes an issue where a constraint inside a typealias that writes to frame slots is unable to resolve type arguments.
This commit is contained in:
Goutam Adwant authored and GitHub committed 2026-10-19 23:29:30 +00:00
1 parent 08f0c3781b
commit ce977f1ffe
4 files changed
+50 -1

No files matched your search

@@ -18,6 +18,7 @@ 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.nodes.Node;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.SimpleRootNode;
@@ -43,6 +44,10 @@ public final class ExecuteCustomThisWithRootNode extends ExpressionNode {
// shouldn't be marked `@Child` because this node is actually the child of the SimpleRootNode
// created in the constructor.
private final ExpressionNode expressionNode;
private final FrameDescriptor frameDescriptor;
private final String qualifiedName;
private final int[] forGeneratorSlots;
private final int[] parameterSlots;
public ExecuteCustomThisWithRootNode(
SourceSection sourceSection,
@@ -53,6 +58,10 @@ public final class ExecuteCustomThisWithRootNode extends ExpressionNode {
int[] parameterSlots) {
super(sourceSection);
this.expressionNode = expressionNode;
this.frameDescriptor = frameDescriptor;
this.qualifiedName = qualifiedName;
this.forGeneratorSlots = forGeneratorSlots;
this.parameterSlots = parameterSlots;
frameDescriptor.findOrAddAuxiliarySlot(VmUtils.CUSTOM_THIS_FRAME_SLOT_ID);
var rootNode =
new SimpleRootNode(
@@ -68,6 +77,18 @@ public final class ExecuteCustomThisWithRootNode extends ExpressionNode {
return expressionNode;
}
@Override
public Node deepCopy() {
// The expression belongs to a separate root and is not copied as a child of this node.
return new ExecuteCustomThisWithRootNode(
sourceSection,
(ExpressionNode) expressionNode.deepCopy(),
frameDescriptor,
qualifiedName,
forGeneratorSlots,
parameterSlots);
}
@Override
public Object executeGeneric(VirtualFrame frame) {
var customThis = customThisNode.executeGeneric(frame);
@@ -19,6 +19,7 @@ import com.google.errorprone.annotations.concurrent.GuardedBy;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.nodes.NodeVisitor;
import com.oracle.truffle.api.source.SourceSection;
import java.net.URI;
import java.util.ArrayList;
@@ -30,6 +31,7 @@ import org.pkl.core.PObject;
import org.pkl.core.TypeAlias;
import org.pkl.core.TypeParameter;
import org.pkl.core.ast.VmModifier;
import org.pkl.core.ast.expression.primary.ExecuteCustomThisWithRootNode;
import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.TypeNode.ConstrainedTypeNode;
import org.pkl.core.ast.type.TypeNode.TypeVariableNode;
@@ -200,7 +202,9 @@ public final class VmTypeAlias extends VmValue {
var clone = (TypeNode) typeNode.deepCopy();
clone.accept(
node -> {
new NodeVisitor() {
@Override
public boolean visit(com.oracle.truffle.api.nodes.Node node) {
if (node instanceof TypeVariableNode typeVarNode) {
var index = typeVarNode.getTypeParameterIndex();
node.replace(
@@ -217,8 +221,12 @@ public final class VmTypeAlias extends VmValue {
? new UnresolvedTypeNode.Unknown(sourceSection)
: new UnresolvedTypeNode.Resolved(
sourceSection, deepCopy(typeArgumentNodes[index])));
} else if (node instanceof ExecuteCustomThisWithRootNode customThisNode) {
// A constraint with frame slots runs in a separate root, outside this tree.
customThisNode.getExpressionNode().accept(this);
}
return true;
}
});
return clone;
@@ -0,0 +1,14 @@
import "pkl:test"
class Foo { bar: Int }
const function isOk(f: Foo) = f.bar > 0
typealias WithInferredNew<X> = Int(isOk(new { bar = 1 }) && this is X)
typealias WithLet<X> = Int((let (n = 1) n == 1) && this is X)
wrongType = 3 is WithInferredNew<String>
matchingType = 3 is WithInferredNew<Int>
wrongTypeAgain = 3 is WithInferredNew<String>
letWrongType = 3 is WithLet<String>
letMatchingType = 3 is WithLet<Int>
castWrongType = test.catch(() -> 3 as WithInferredNew<String>)
@@ -0,0 +1,6 @@
wrongType = false
matchingType = true
wrongTypeAgain = false
letWrongType = false
letMatchingType = true
castWrongType = "Type constraint `isOk(new { bar = 1 }) && this is X` violated. Value: 3"