From 1c1a39e37f934cd68d9872ff5a9fda381873c25c Mon Sep 17 00:00:00 2001 From: adityabagchi24 Date: Wed, 1 Jul 2026 22:40:03 +0530 Subject: [PATCH] Fix type argument lost in constraint expressions within generic typealiases (#1709) Co-authored-by: Daniel Chao --- .../ast/member/UnresolvedFunctionNode.java | 2 +- .../pkl/core/ast/type/UnresolvedTypeNode.java | 23 +++++++++++++++++ .../org/pkl/core/runtime/VmTypeAlias.java | 10 ++++++++ .../input/types/typeAliasConstraint3.pkl | 3 +++ .../output/types/typeAliasConstraint3.err | 25 +++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAliasConstraint3.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasConstraint3.err diff --git a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java index f96930c62..99f24b528 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/member/UnresolvedFunctionNode.java @@ -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, diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java index 1665cad49..e5cbd35d2 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/UnresolvedTypeNode.java @@ -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; + } + } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java index d5354d006..b0f82a931 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmTypeAlias.java @@ -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; }); diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAliasConstraint3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAliasConstraint3.pkl new file mode 100644 index 000000000..5f5a16741 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/typeAliasConstraint3.pkl @@ -0,0 +1,3 @@ +typealias MyList = List(this[0] is T) + +foo: MyList = List("uh oh") diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasConstraint3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasConstraint3.err new file mode 100644 index 000000000..aafc48d7d --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/typeAliasConstraint3.err @@ -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 = List(this[0] is T) + ^^^^^^^^^^^^ +at typeAliasConstraint3#foo (file:///$snippetsDir/input/types/typeAliasConstraint3.pkl) + +x | foo: MyList = 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)