Fix type argument lost in constraint expressions within generic typealiases (#1709)

Co-authored-by: Daniel Chao <daniel.h.chao@gmail.com>
This commit is contained in:
adityabagchi24
2026-07-01 22:40:03 +05:30
committed by GitHub
parent bfa6a989be
commit 1c1a39e37f
5 changed files with 62 additions and 1 deletions
@@ -31,7 +31,7 @@ public final class UnresolvedFunctionNode extends PklNode {
private final int parameterCount;
@Children private final @Nullable UnresolvedTypeNode[] unresolvedParameterTypeNodes;
@Child private @Nullable UnresolvedTypeNode unresolvedReturnTypeNode;
private final ExpressionNode bodyNode;
@Child private ExpressionNode bodyNode;
public UnresolvedFunctionNode(
VmLanguage language,
@@ -429,6 +429,10 @@ public abstract class UnresolvedTypeNode extends PklNode {
this.typeParameter = typeParameter;
}
public int getTypeParameterIndex() {
return typeParameter.getIndex();
}
@Override
public TypeNode execute(VirtualFrame frame) {
CompilerDirectives.transferToInterpreter();
@@ -436,4 +440,23 @@ public abstract class UnresolvedTypeNode extends PklNode {
return new TypeVariableNode(sourceSection, typeParameter);
}
}
/**
* An unresolved type node that is pre-resolved to a concrete type node. Used during type alias
* instantiation to replace type variable references inside constraint expressions (e.g., {@code
* every((it) -> it is T)}) with the corresponding concrete type argument.
*/
public static final class Resolved extends UnresolvedTypeNode {
@Child private TypeNode typeNode;
public Resolved(SourceSection sourceSection, TypeNode typeNode) {
super(sourceSection);
this.typeNode = typeNode;
}
@Override
public TypeNode execute(VirtualFrame frame) {
return typeNode;
}
}
}
@@ -34,6 +34,7 @@ import org.pkl.core.ast.type.TypeNode;
import org.pkl.core.ast.type.TypeNode.ConstrainedTypeNode;
import org.pkl.core.ast.type.TypeNode.TypeVariableNode;
import org.pkl.core.ast.type.TypeNode.UnknownTypeNode;
import org.pkl.core.ast.type.UnresolvedTypeNode;
public final class VmTypeAlias extends VmValue {
private final SourceSection sourceSection;
@@ -204,6 +205,15 @@ public final class VmTypeAlias extends VmValue {
typeArgumentNodes.length == 0
? new UnknownTypeNode(sourceSection)
: typeArgumentNodes[index]);
} else if (node instanceof UnresolvedTypeNode.TypeVariable unresolvedTypeVar) {
// Type variables inside constraint expressions (e.g. `every((it) -> it is T)`)
// are still unresolved at instantiation time. Replace them with a resolved
// unresolved type node that returns the concrete type argument.
var index = unresolvedTypeVar.getTypeParameterIndex();
node.replace(
typeArgumentNodes.length == 0
? new UnresolvedTypeNode.Unknown(sourceSection)
: new UnresolvedTypeNode.Resolved(sourceSection, typeArgumentNodes[index]));
}
return true;
});
@@ -0,0 +1,3 @@
typealias MyList<T> = List(this[0] is T)
foo: MyList<Int> = List("uh oh")
@@ -0,0 +1,25 @@
–– Pkl Error ––
Type constraint `this[0] is T` violated.
Value: List("uh oh")
this[0] is T
│ │ │
│ │ false
│ "uh oh"
List("uh oh")
x | typealias MyList<T> = List(this[0] is T)
^^^^^^^^^^^^
at typeAliasConstraint3#foo (file:///$snippetsDir/input/types/typeAliasConstraint3.pkl)
x | foo: MyList<Int> = List("uh oh")
^^^^^^^^^^^^^
at typeAliasConstraint3#foo (file:///$snippetsDir/input/types/typeAliasConstraint3.pkl)
xxx | renderer.renderDocument(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (pkl:base)
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
^^^^
at pkl.base#Module.output.bytes (pkl:base)