From 852e24e61ef561c6914bfc7e0119bb3cff44fecb Mon Sep 17 00:00:00 2001 From: Jen Basch Date: Wed, 24 Jun 2026 14:24:29 -0700 Subject: [PATCH] Correctly type check `Class` --- docs/modules/release-notes/pages/0.33.adoc | 10 ++ .../java/org/pkl/core/ast/type/TypeNode.java | 140 ++++++++++++++++-- .../pkl/core/ast/type/UnresolvedTypeNode.java | 3 +- .../ast/type/VmTypeMismatchException.java | 63 ++++++++ .../pkl/core/runtime/CommandSpecParser.java | 2 +- .../input-helper/types/classTypeA.pkl | 2 + .../input-helper/types/classTypeB.pkl | 2 + .../input/types/classType.pkl | 26 ++++ .../input/types/classTypeErr1.pkl | 3 + .../input/types/classTypeErr10.pkl | 3 + .../input/types/classTypeErr11.pkl | 3 + .../input/types/classTypeErr12.pkl | 5 + .../input/types/classTypeErr13.pkl | 5 + .../input/types/classTypeErr2.pkl | 6 + .../input/types/classTypeErr3.pkl | 6 + .../input/types/classTypeErr4.pkl | 3 + .../input/types/classTypeErr5.pkl | 3 + .../input/types/classTypeErr6.pkl | 3 + .../input/types/classTypeErr7.pkl | 3 + .../input/types/classTypeErr8.pkl | 3 + .../input/types/classTypeErr9.pkl | 3 + .../output/types/classType.pcf | 13 ++ .../output/types/classTypeErr1.err | 14 ++ .../output/types/classTypeErr10.err | 14 ++ .../output/types/classTypeErr11.err | 16 ++ .../output/types/classTypeErr12.err | 16 ++ .../output/types/classTypeErr13.err | 16 ++ .../output/types/classTypeErr2.err | 15 ++ .../output/types/classTypeErr3.err | 15 ++ .../output/types/classTypeErr4.err | 16 ++ .../output/types/classTypeErr5.err | 16 ++ .../output/types/classTypeErr6.err | 16 ++ .../output/types/classTypeErr7.err | 16 ++ .../output/types/classTypeErr8.err | 16 ++ .../output/types/classTypeErr9.err | 14 ++ stdlib/base.pkl | 2 +- 36 files changed, 495 insertions(+), 17 deletions(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeA.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeB.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classType.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr1.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr10.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr11.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr12.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr13.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr2.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr3.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr4.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr5.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr6.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr7.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr8.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr9.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classType.pcf create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr1.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr10.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr11.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr12.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr13.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr2.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr3.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr4.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr5.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr6.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr7.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr8.err create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr9.err diff --git a/docs/modules/release-notes/pages/0.33.adoc b/docs/modules/release-notes/pages/0.33.adoc index c662a7a1b..3578ff660 100644 --- a/docs/modules/release-notes/pages/0.33.adoc +++ b/docs/modules/release-notes/pages/0.33.adoc @@ -23,6 +23,16 @@ Native `pkl` and `pkldoc` binaries for Intel Mac systems are no longer published To continue running new Pkl releases on these systems, use an appropriate Java runtime and the `jpkl` and `jpkldoc` Java executables. +=== Type check changes for `Class` + +In prior versions of Pkl, type arguments to the `Class` type were erased. +Any `Class` value would typecheck against `Class` for any value of `T`. + +In Pkl 0.33, this erasure has been removed. +A `Class` value typechecked against `Class` must be a subclass of `T`. + +If `T` does not resolve to a class type (i.e. it is a union type, nullable type, string literal type, parameterized type, or `nothing`), the type check will always fail. + === XXX == Bug Fixes [small]#🐜# diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java index 933188582..4d8327790 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java @@ -33,6 +33,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.jspecify.annotations.NonNull; @@ -61,10 +62,18 @@ public abstract class TypeNode extends PklNode { - public interface ClassTypeNode { + /** + * Type node that corresponds to a simple, unparameterized {@link VmClass}. + * + *

This includes generic classes written without any type arguments like {@code List}. + */ + public interface SimpleClassTypeNode { VmClass getVmClass(); } + /** Type node that corresponds to a user-defined class (or module class). */ + public interface UserClassTypeNode extends SimpleClassTypeNode {} + protected TypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -411,7 +420,7 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co /** The `module` type for a final module. */ public static final class FinalModuleTypeNode extends ObjectSlotTypeNode - implements ClassTypeNode { + implements UserClassTypeNode { private final VmClass moduleClass; public FinalModuleTypeNode(SourceSection sourceSection, VmClass moduleClass) { @@ -466,7 +475,7 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co /** The `module` type for an open module. */ public static final class NonFinalModuleTypeNode extends ObjectSlotTypeNode - implements ClassTypeNode { + implements UserClassTypeNode { private final VmClass moduleClass; // only used by getVmClass() @Child private ExpressionNode getModuleNode; @@ -580,7 +589,8 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } - public static final class TypedTypeNode extends ObjectSlotTypeNode { + public static final class TypedTypeNode extends ObjectSlotTypeNode + implements SimpleClassTypeNode { public TypedTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -608,7 +618,8 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } - public static final class DynamicTypeNode extends ObjectSlotTypeNode { + public static final class DynamicTypeNode extends ObjectSlotTypeNode + implements SimpleClassTypeNode { public DynamicTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -651,7 +662,8 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co * String/Boolean/Int/Float and their supertypes, only `VmValue`s can possibly pass its type * check. */ - public static final class FinalClassTypeNode extends ObjectSlotTypeNode implements ClassTypeNode { + public static final class FinalClassTypeNode extends ObjectSlotTypeNode + implements UserClassTypeNode { private final VmClass clazz; public FinalClassTypeNode(SourceSection sourceSection, VmClass clazz) { @@ -708,7 +720,7 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co * check. */ public abstract static class NonFinalClassTypeNode extends ObjectSlotTypeNode - implements ClassTypeNode { + implements UserClassTypeNode { protected final VmClass clazz; public NonFinalClassTypeNode(SourceSection sourceSection, VmClass clazz) { @@ -2937,7 +2949,8 @@ public VmTyped getMirror() { } } - public static final class AnyTypeNode extends WriteFrameSlotTypeNode { + public static final class AnyTypeNode extends WriteFrameSlotTypeNode + implements SimpleClassTypeNode { public AnyTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -2969,7 +2982,8 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } - public static final class StringTypeNode extends ObjectSlotTypeNode { + public static final class StringTypeNode extends ObjectSlotTypeNode + implements SimpleClassTypeNode { public StringTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -2997,7 +3011,8 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } - public static final class NumberTypeNode extends FrameSlotTypeNode { + public static final class NumberTypeNode extends FrameSlotTypeNode + implements SimpleClassTypeNode { public NumberTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -3056,7 +3071,7 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } - public static final class IntTypeNode extends IntSlotTypeNode { + public static final class IntTypeNode extends IntSlotTypeNode implements SimpleClassTypeNode { public IntTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -3084,7 +3099,7 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } - public static final class FloatTypeNode extends FrameSlotTypeNode { + public static final class FloatTypeNode extends FrameSlotTypeNode implements SimpleClassTypeNode { public FloatTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -3124,7 +3139,8 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } - public static final class BooleanTypeNode extends FrameSlotTypeNode { + public static final class BooleanTypeNode extends FrameSlotTypeNode + implements SimpleClassTypeNode { public BooleanTypeNode(SourceSection sourceSection) { super(sourceSection); } @@ -3164,6 +3180,104 @@ protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer co } } + public abstract static class ClassClassTypeNode extends ValidatingObjectSlotTypeNode { + + @Child private TypeNode typeNode; + @CompilationFinal private @Nullable VmClass clazz; + + public ClassClassTypeNode(SourceSection sourceSection, TypeNode typeNode) { + super(sourceSection); + this.typeNode = typeNode; + validate(); + } + + @Override + public String getValidationErrorKey() { + // getViolatingNode() always returns null, it's only used to re-calculate clazz + throw PklBugException.unreachableCode(); + } + + @Override + protected boolean isIncludedInTrace(Node node) { + // getViolatingNode() always returns null, it's only used to re-calculate clazz + throw PklBugException.unreachableCode(); + } + + @Override + public @Nullable Node getViolatingNode() { + // use the validation hook to recalculate clazz after typealias instantiation + CompilerDirectives.transferToInterpreterAndInvalidate(); + + var node = typeNode; + while (node instanceof TypeAliasTypeNode typeAliasTypeNode) { + node = typeAliasTypeNode.getAliasedTypeNode(); + } + + if (node instanceof SimpleClassTypeNode simpleClassTypeNode) { + clazz = simpleClassTypeNode.getVmClass(); + } else if (node instanceof UnknownTypeNode || node instanceof TypeVariableNode) { + clazz = BaseModule.getAnyClass(); + } else { + clazz = null; + } + return null; + } + + @Override + public VmClass getVmClass() { + return BaseModule.getClassClass(); + } + + @Specialization + protected Object eval(VmClass value) { + // Fast path: all classes match Class / Class / Class. + // In this case, skip the subclass check and behave like a bare `Class` type annotation. + if (clazz == BaseModule.getAnyClass()) { + return value; + } + + // clazz will be null iff the type arg is a not a valid class type + if (clazz == null) { + throw new VmTypeMismatchException.Class(sourceSection, value, typeNode.doExport()); + } + + if (!value.isSubclassOf(clazz)) { + throw new VmTypeMismatchException.Class(sourceSection, value, clazz); + } + + return value; + } + + @Fallback + protected Object fallback(Object value) { + throw typeMismatch(value, BaseModule.getClassClass()); + } + + @Override + protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) { + return consumer.accept(this); + } + + @Override + protected boolean doIsEquivalentTo(TypeNode other) { + if (!(other instanceof ClassClassTypeNode classClassTypeNode)) { + return false; + } + + return Objects.equals(clazz, classClassTypeNode.clazz); + } + + @Override + public VmList getTypeArgumentMirrors() { + return VmList.of(typeNode.getMirror()); + } + + @Override + protected PType doExport() { + return new PType.Class(BaseModule.getClassClass().export(), typeNode.doExport()); + } + } + public abstract static class ValidatingObjectSlotTypeNode extends ObjectSlotTypeNode { protected ValidatingObjectSlotTypeNode(SourceSection sourceSection) { 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 22544d37b..16cbaf407 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 @@ -278,9 +278,8 @@ public TypeNode execute(VirtualFrame frame) { return FunctionNClassTypeNodeGen.create(sourceSection, resolvedTypeArgumentNodes); } - // erase `x: Class` to `x: Class` for now (cf. function types) if (clazz.isClassClass()) { - return new FinalClassTypeNode(sourceSection, clazz); + return ClassClassTypeNodeGen.create(sourceSection, typeArgumentNodes[0].execute(frame)); } if (clazz.isVarArgsClass()) { diff --git a/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java b/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java index 4443ea261..ab18e0af3 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/type/VmTypeMismatchException.java @@ -169,6 +169,69 @@ protected Boolean hasHint() { } } + public static final class Class extends VmTypeMismatchException { + + private final String renderedExpected; + private final @Nullable VmClass expectedClass; + + public Class(SourceSection sourceSection, VmClass actualClass, VmClass expectedClass) { + super(sourceSection, actualClass); + this.expectedClass = expectedClass; + renderedExpected = "Class<" + expectedClass + ">"; + } + + public Class(SourceSection sourceSection, VmClass actualClass, PType expectedType) { + super(sourceSection, actualClass); + this.expectedClass = null; + renderedExpected = "Class<" + expectedType + ">"; + } + + @Override + @TruffleBoundary + public void buildMessage( + AnsiStringBuilder builder, String indent, boolean withPowerAssertions) { + var actualClass = (VmClass) actualValue; + var renderedActualClass = "Class<" + actualClass + ">"; + + // give better error than "expected Class, but got Class" in case of naming + // conflict + if (expectedClass != null + && actualClass.getQualifiedName().equals(expectedClass.getQualifiedName())) { + var actualModuleUri = actualClass.getModule().getModuleInfo().getModuleKey().getUri(); + var expectedModuleUri = expectedClass.getModule().getModuleInfo().getModuleKey().getUri(); + + builder + .append( + ErrorMessages.createIndented( + actualClass.getPClassInfo().isModuleClass() + ? "typeMismatchVersionConflict1" + : "typeMismatchVersionConflict2", + indent, + renderedExpected, + expectedModuleUri, + actualModuleUri)) + .append("\n"); + return; + } + + builder.append( + ErrorMessages.createIndented( + "typeMismatch", indent, renderedExpected, renderedActualClass)); + } + + @Override + protected Boolean hasHint() { + return expectedClass == null; + } + + @Override + public void buildHint(AnsiStringBuilder builder, String indent, boolean withPowerAssertions) { + if (expectedClass != null) return; + builder.append( + "A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types."); + } + } + public static final class Constraint extends VmTypeMismatchException { private final SourceSection constraintBodySourceSection; diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java b/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java index 720dc8944..915cf8043 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java @@ -173,7 +173,7 @@ private VmClass getOptionsClass(VmTyped command) { if (optionsTypeNode instanceof TypeNode.TypedTypeNode) { return BaseModule.getTypedClass(); } - if (!(optionsTypeNode instanceof TypeNode.ClassTypeNode node)) { + if (!(optionsTypeNode instanceof TypeNode.UserClassTypeNode node)) { throw exceptionBuilder() .withSourceSection(optionsTypeNode.getSourceSection()) .evalError( diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeA.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeA.pkl new file mode 100644 index 000000000..162589ac9 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeA.pkl @@ -0,0 +1,2 @@ +module classType +class Foo diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeB.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeB.pkl new file mode 100644 index 000000000..162589ac9 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input-helper/types/classTypeB.pkl @@ -0,0 +1,2 @@ +module classType +class Foo diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classType.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classType.pkl new file mode 100644 index 000000000..cfd5c2479 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classType.pkl @@ -0,0 +1,26 @@ +open module classType + +open class A +open class B +class C extends A +class D extends module +typealias E = A +typealias BB = B + +res0 = C is Class +res1 = C is Class +res2 = C is Class + +res3 = C is Class +res4 = C is Class +res5 = C is Class + +res15 = D is Class +res16 = D is Class +res17 = D is Class +res18 = D is Class +res19 = D is Class + +typealias F = List> +res20 = List(A, C) is F +res21 = List(new A {}, new B {}, new C {}).filterIsInstance(A).length diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr1.pkl new file mode 100644 index 000000000..916938b92 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr1.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res2 = C as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr10.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr10.pkl new file mode 100644 index 000000000..8bc812c23 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr10.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res14 = C as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr11.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr11.pkl new file mode 100644 index 000000000..99384be22 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr11.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res22 = List(A, C) as F diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr12.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr12.pkl new file mode 100644 index 000000000..68612b000 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr12.pkl @@ -0,0 +1,5 @@ +extends "classType.pkl" + +typealias G = F + +res22 = List(A, C) as G diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr13.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr13.pkl new file mode 100644 index 000000000..59d40d60c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr13.pkl @@ -0,0 +1,5 @@ +extends "classType.pkl" + +typealias G = F + +res22 = List(A, C) as G diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr2.pkl new file mode 100644 index 000000000..f04ec56fb --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr2.pkl @@ -0,0 +1,6 @@ +extends "classType.pkl" + +import ".../input-helper/types/classTypeA.pkl" +import ".../input-helper/types/classTypeB.pkl" + +res6 = classTypeA.getClass() as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr3.pkl new file mode 100644 index 000000000..73d613993 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr3.pkl @@ -0,0 +1,6 @@ +extends "classType.pkl" + +import ".../input-helper/types/classTypeA.pkl" +import ".../input-helper/types/classTypeB.pkl" + +res7 = classTypeA.Foo as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr4.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr4.pkl new file mode 100644 index 000000000..6c0b69561 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr4.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res8 = C as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr5.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr5.pkl new file mode 100644 index 000000000..df58a6b4c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr5.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res9 = C as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr6.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr6.pkl new file mode 100644 index 000000000..2ddef66fd --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr6.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res10 = C as Class<"foo"> diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr7.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr7.pkl new file mode 100644 index 000000000..3f11ed90c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr7.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res11 = C as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr8.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr8.pkl new file mode 100644 index 000000000..b9761d53a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr8.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res12 = C as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr9.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr9.pkl new file mode 100644 index 000000000..9a60c56b7 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/types/classTypeErr9.pkl @@ -0,0 +1,3 @@ +extends "classType.pkl" + +res13 = C as Class diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classType.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classType.pcf new file mode 100644 index 000000000..0451bdbf2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classType.pcf @@ -0,0 +1,13 @@ +res0 = true +res1 = true +res2 = true +res3 = true +res4 = true +res5 = true +res15 = true +res16 = true +res17 = true +res18 = false +res19 = false +res20 = true +res21 = 2 diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr1.err new file mode 100644 index 000000000..ab8934b38 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr1.err @@ -0,0 +1,14 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +x | res2 = C as Class + ^^^^^^^^ +at classTypeErr1#res2 (file:///$snippetsDir/input/types/classTypeErr1.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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr10.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr10.err new file mode 100644 index 000000000..d90642c6d --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr10.err @@ -0,0 +1,14 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +x | res14 = C as Class + ^^^^^^^^^^^^^ +at classTypeErr10#res14 (file:///$snippetsDir/input/types/classTypeErr10.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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr11.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr11.err new file mode 100644 index 000000000..5976e1e7b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr11.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +xx | typealias F = List> + ^^^^^^^^ +at classTypeErr11#res22 (file:///$snippetsDir/input/types/classType.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr12.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr12.err new file mode 100644 index 000000000..392c5f04e --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr12.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +xx | typealias F = List> + ^^^^^^^^ +at classTypeErr12#res22 (file:///$snippetsDir/input/types/classType.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr13.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr13.err new file mode 100644 index 000000000..a0ef0b6fd --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr13.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +xx | typealias F = List> + ^^^^^^^^ +at classTypeErr13#res22 (file:///$snippetsDir/input/types/classType.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr2.err new file mode 100644 index 000000000..e4a895b24 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr2.err @@ -0,0 +1,15 @@ +–– Pkl Error –– +Module version conflict: Expected value of type `Class` defined by module `file:///$snippetsDir/input-helper/types/classTypeB.pkl`, but got type `Class` defined by module `file:///$snippetsDir/input-helper/types/classTypeA.pkl`. + + +x | res6 = classTypeA.getClass() as Class + ^^^^^^^^^^^^^^^^^ +at classTypeErr2#res6 (file:///$snippetsDir/input/types/classTypeErr2.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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr3.err new file mode 100644 index 000000000..d1bca6603 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr3.err @@ -0,0 +1,15 @@ +–– Pkl Error –– +Module version conflict: Expected value of type `Class` defined in module `file:///$snippetsDir/input-helper/types/classTypeB.pkl`, but got type `Class` defined in module `file:///$snippetsDir/input-helper/types/classTypeA.pkl`. + + +x | res7 = classTypeA.Foo as Class + ^^^^^^^^^^^^^^^^^^^^^ +at classTypeErr3#res7 (file:///$snippetsDir/input/types/classTypeErr3.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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr4.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr4.err new file mode 100644 index 000000000..a864ee410 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr4.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +x | res8 = C as Class + ^^^^^^^^^^^^ +at classTypeErr4#res8 (file:///$snippetsDir/input/types/classTypeErr4.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr5.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr5.err new file mode 100644 index 000000000..557efbc0c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr5.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +x | res9 = C as Class + ^^^^^^^^^ +at classTypeErr5#res9 (file:///$snippetsDir/input/types/classTypeErr5.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr6.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr6.err new file mode 100644 index 000000000..330a81f74 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr6.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class<"foo">`, but got type `Class`. + +x | res10 = C as Class<"foo"> + ^^^^^^^^^^^^ +at classTypeErr6#res10 (file:///$snippetsDir/input/types/classTypeErr6.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr7.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr7.err new file mode 100644 index 000000000..d5ce5329c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr7.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +x | res11 = C as Class + ^^^^^^^^^^^^^^ +at classTypeErr7#res11 (file:///$snippetsDir/input/types/classTypeErr7.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr8.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr8.err new file mode 100644 index 000000000..5184eabe2 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr8.err @@ -0,0 +1,16 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +x | res12 = C as Class + ^^^^^^^^^^^^^^ +at classTypeErr8#res12 (file:///$snippetsDir/input/types/classTypeErr8.pkl) + +A `Class` type check can only succeed when its type argument is an un-parameterized class, a module, `unknown`, `module`, or an alias to one of those types. + +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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr9.err b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr9.err new file mode 100644 index 000000000..f2dd49574 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/types/classTypeErr9.err @@ -0,0 +1,14 @@ +–– Pkl Error –– +Expected value of type `Class`, but got type `Class`. + +x | res13 = C as Class + ^^^^^^^^^ +at classTypeErr9#res13 (file:///$snippetsDir/input/types/classTypeErr9.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) diff --git a/stdlib/base.pkl b/stdlib/base.pkl index ee3e94ef2..678bc5832 100644 --- a/stdlib/base.pkl +++ b/stdlib/base.pkl @@ -361,7 +361,7 @@ abstract class BaseValueRenderer { /// /// See [ConvertProperty] for detailed information. @Since { version = "0.31.0" } - convertPropertyTransformers: Mapping> + convertPropertyTransformers: Mapping, Mixin> /// The file extension associated with this output format, /// or [null] if this format does not have an extension.