mirror of
https://github.com/apple/pkl.git
synced 2026-08-27 14:14:02 +02:00
Correctly type check Class<T> (#1698)
This commit is contained in:
@@ -33,6 +33,7 @@ import java.util.ArrayList;
|
||||
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,7 +62,8 @@ import org.pkl.core.util.MutableReference;
|
||||
|
||||
public abstract class TypeNode extends PklNode {
|
||||
|
||||
public interface ClassTypeNode {
|
||||
/** Type node that corresponds to a user-defined class (or module class). */
|
||||
public interface UserClassTypeNode {
|
||||
VmClass getVmClass();
|
||||
}
|
||||
|
||||
@@ -410,8 +412,7 @@ public abstract class TypeNode extends PklNode {
|
||||
}
|
||||
|
||||
/** The `module` type for a final module. */
|
||||
public static final class FinalModuleTypeNode extends ObjectSlotTypeNode
|
||||
implements ClassTypeNode {
|
||||
public static final class FinalModuleTypeNode extends ObjectSlotTypeNode {
|
||||
private final VmClass moduleClass;
|
||||
|
||||
public FinalModuleTypeNode(SourceSection sourceSection, VmClass moduleClass) {
|
||||
@@ -465,8 +466,7 @@ public abstract class TypeNode extends PklNode {
|
||||
}
|
||||
|
||||
/** The `module` type for an open module. */
|
||||
public static final class NonFinalModuleTypeNode extends ObjectSlotTypeNode
|
||||
implements ClassTypeNode {
|
||||
public static final class NonFinalModuleTypeNode extends ObjectSlotTypeNode {
|
||||
private final VmClass moduleClass; // only used by getVmClass()
|
||||
@Child private ExpressionNode getModuleNode;
|
||||
|
||||
@@ -651,7 +651,8 @@ public abstract class TypeNode extends PklNode {
|
||||
* 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 +709,7 @@ public abstract class TypeNode extends PklNode {
|
||||
* check.
|
||||
*/
|
||||
public abstract static class NonFinalClassTypeNode extends ObjectSlotTypeNode
|
||||
implements ClassTypeNode {
|
||||
implements UserClassTypeNode {
|
||||
protected final VmClass clazz;
|
||||
|
||||
public NonFinalClassTypeNode(SourceSection sourceSection, VmClass clazz) {
|
||||
@@ -3164,6 +3165,97 @@ public abstract class TypeNode extends PklNode {
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class ClassClassTypeNode extends ObjectSlotTypeNode {
|
||||
|
||||
@Child private TypeNode typeNode;
|
||||
@CompilationFinal private boolean initialized = false;
|
||||
@CompilationFinal private @Nullable VmClass clazz = null;
|
||||
|
||||
public ClassClassTypeNode(SourceSection sourceSection, TypeNode typeNode) {
|
||||
super(sourceSection);
|
||||
this.typeNode = typeNode;
|
||||
}
|
||||
|
||||
private void initVmClass() {
|
||||
if (initialized) return;
|
||||
|
||||
CompilerDirectives.transferToInterpreterAndInvalidate();
|
||||
initialized = true;
|
||||
|
||||
var node = typeNode;
|
||||
while (node instanceof TypeAliasTypeNode typeAliasTypeNode) {
|
||||
node = typeAliasTypeNode.getAliasedTypeNode();
|
||||
}
|
||||
|
||||
if (node instanceof UnknownTypeNode || node instanceof TypeVariableNode) {
|
||||
clazz = BaseModule.getAnyClass();
|
||||
} else if (!node.isParametric()) {
|
||||
clazz = node.getVmClass();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VmClass getVmClass() {
|
||||
return BaseModule.getClassClass();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
protected Object eval(VmClass value) {
|
||||
// safe to init clazz here (instead of on init and typealias instantiate)
|
||||
// because in the typealias case this node will never execute prior to instantiation
|
||||
initVmClass();
|
||||
|
||||
// Fast path: all classes match Class<Any> / Class<unknown> / Class<type arg>.
|
||||
// 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.ClassType(sourceSection, value, typeNode.doExport());
|
||||
}
|
||||
|
||||
if (!value.isSubclassOf(clazz)) {
|
||||
throw new VmTypeMismatchException.ClassType(sourceSection, value, clazz);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Fallback
|
||||
protected Object fallback(Object value) {
|
||||
throw typeMismatch(value, BaseModule.getClassClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean acceptTypeNode(boolean visitTypeArguments, TypeNodeConsumer consumer) {
|
||||
if (visitTypeArguments) {
|
||||
return consumer.accept(this) && typeNode.acceptTypeNode(visitTypeArguments, 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) {
|
||||
|
||||
@@ -278,9 +278,8 @@ public abstract class UnresolvedTypeNode extends PklNode {
|
||||
return FunctionNClassTypeNodeGen.create(sourceSection, resolvedTypeArgumentNodes);
|
||||
}
|
||||
|
||||
// erase `x: Class<Foo>` 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()) {
|
||||
|
||||
@@ -169,6 +169,69 @@ public abstract class VmTypeMismatchException extends ControlFlowException {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ClassType extends VmTypeMismatchException {
|
||||
|
||||
private final String renderedExpected;
|
||||
private final @Nullable VmClass expectedClass;
|
||||
|
||||
public ClassType(SourceSection sourceSection, VmClass actualClass, VmClass expectedClass) {
|
||||
super(sourceSection, actualClass);
|
||||
this.expectedClass = expectedClass;
|
||||
renderedExpected = "Class<" + expectedClass + ">";
|
||||
}
|
||||
|
||||
public ClassType(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<foo.Bar>, but got Class<foo.Bar>" 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
|
||||
@TruffleBoundary
|
||||
public void buildHint(AnsiStringBuilder builder, String indent, boolean withPowerAssertions) {
|
||||
if (expectedClass != null) return;
|
||||
builder.append(ErrorMessages.createIndented("classTypeMismatchHint", indent));
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Constraint extends VmTypeMismatchException {
|
||||
|
||||
private final SourceSection constraintBodySourceSection;
|
||||
|
||||
@@ -173,7 +173,7 @@ public final class CommandSpecParser {
|
||||
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(
|
||||
|
||||
@@ -358,6 +358,9 @@ Expected value of type `{0}`, but got a different `{1}`.
|
||||
typeMismatchBecause=\
|
||||
* Value is not of type `{0}` because:
|
||||
|
||||
classTypeMismatchHint=\
|
||||
A `Class` type check can only succeed when its type argument is an un-parameterized class, `unknown`, `module`, or an alias to one of those types.
|
||||
|
||||
typeConstraintViolated=\
|
||||
Type constraint `{0}` violated.
|
||||
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
module classType
|
||||
class Foo
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
module classType
|
||||
class Foo
|
||||
@@ -0,0 +1,36 @@
|
||||
open module classType
|
||||
|
||||
import "pkl:reflect"
|
||||
|
||||
open class A
|
||||
open class B
|
||||
class C extends A
|
||||
class D extends module
|
||||
typealias E = A
|
||||
typealias BB = B
|
||||
|
||||
res0 = C is Class<C>
|
||||
res0a = C is Class<(C)>
|
||||
res0b = C is Class<((C))>
|
||||
res1 = C is Class<A>
|
||||
res2 = C is Class<E>
|
||||
|
||||
res3 = C is Class
|
||||
res4 = C is Class<unknown>
|
||||
res5 = C is Class<Any>
|
||||
|
||||
res15 = D is Class<module>
|
||||
res16 = D is Class<Object>
|
||||
res17 = D is Class<Typed>
|
||||
res18 = D is Class<Dynamic>
|
||||
res19 = D is Class<Int>
|
||||
|
||||
typealias F<T> = List<Class<T>>
|
||||
res20 = List(A, C) is F<A>
|
||||
res21 = List(new A {}, new B {}, new C {}).filterIsInstance(A).length
|
||||
|
||||
hidden $res22: Class<A> = A
|
||||
res22 =
|
||||
let (t = reflect.Module(module).moduleClass.properties["$res22"].type as reflect.DeclaredType)
|
||||
let (arg = t.typeArguments.single as reflect.DeclaredType)
|
||||
"\(t.referent.enclosingDeclaration.uri)#\(t.referent.name)<\(arg.referent.enclosingDeclaration.uri)#\(arg.referent.name)>"
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res2 = C as Class<B>
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res14 = C as Class<module>
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res22 = List(A, C) as F<A | B>
|
||||
@@ -0,0 +1,5 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
typealias G<T> = F<T>
|
||||
|
||||
res22 = List(A, C) as G<A | B>
|
||||
@@ -0,0 +1,5 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
typealias G<T> = F<T | B>
|
||||
|
||||
res22 = List(A, C) as G<A>
|
||||
@@ -0,0 +1,6 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
import ".../input-helper/types/classTypeA.pkl"
|
||||
import ".../input-helper/types/classTypeB.pkl"
|
||||
|
||||
res6 = classTypeA.getClass() as Class<classTypeB>
|
||||
@@ -0,0 +1,6 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
import ".../input-helper/types/classTypeA.pkl"
|
||||
import ".../input-helper/types/classTypeB.pkl"
|
||||
|
||||
res7 = classTypeA.Foo as Class<classTypeB.Foo>
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res8 = C as Class<A | B>
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res9 = C as Class<A?>
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res10 = C as Class<"foo">
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res11 = C as Class<nothing>
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res12 = C as Class<A(true)>
|
||||
@@ -0,0 +1,3 @@
|
||||
extends "classType.pkl"
|
||||
|
||||
res13 = C as Class<BB>
|
||||
@@ -0,0 +1,16 @@
|
||||
res0 = true
|
||||
res0a = true
|
||||
res0b = 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
|
||||
res22 = "pkl:base#Class<file:///$snippetsDir/input/types/classType.pkl#A>"
|
||||
@@ -0,0 +1,14 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#B>`, but got type `Class<classType#C>`.
|
||||
|
||||
x | res2 = C as Class<B>
|
||||
^^^^^^^^
|
||||
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)
|
||||
@@ -0,0 +1,14 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classTypeErr10>`, but got type `Class<classType#C>`.
|
||||
|
||||
x | res14 = C as Class<module>
|
||||
^^^^^^^^^^^^^
|
||||
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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#A | classType#B>`, but got type `Class<classType#A>`.
|
||||
|
||||
xx | typealias F<T> = List<Class<T>>
|
||||
^^^^^^^^
|
||||
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, `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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#A | classType#B>`, but got type `Class<classType#A>`.
|
||||
|
||||
xx | typealias F<T> = List<Class<T>>
|
||||
^^^^^^^^
|
||||
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, `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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#A | classType#B>`, but got type `Class<classType#A>`.
|
||||
|
||||
xx | typealias F<T> = List<Class<T>>
|
||||
^^^^^^^^
|
||||
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, `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)
|
||||
@@ -0,0 +1,15 @@
|
||||
–– Pkl Error ––
|
||||
Module version conflict: Expected value of type `Class<classType>` defined by module `file:///$snippetsDir/input-helper/types/classTypeB.pkl`, but got type `Class<classType>` defined by module `file:///$snippetsDir/input-helper/types/classTypeA.pkl`.
|
||||
|
||||
|
||||
x | res6 = classTypeA.getClass() as Class<classTypeB>
|
||||
^^^^^^^^^^^^^^^^^
|
||||
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)
|
||||
@@ -0,0 +1,15 @@
|
||||
–– Pkl Error ––
|
||||
Module version conflict: Expected value of type `Class<classType#Foo>` defined in module `file:///$snippetsDir/input-helper/types/classTypeB.pkl`, but got type `Class<classType#Foo>` defined in module `file:///$snippetsDir/input-helper/types/classTypeA.pkl`.
|
||||
|
||||
|
||||
x | res7 = classTypeA.Foo as Class<classTypeB.Foo>
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#A | classType#B>`, but got type `Class<classType#C>`.
|
||||
|
||||
x | res8 = C as Class<A | B>
|
||||
^^^^^^^^^^^^
|
||||
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, `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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#A?>`, but got type `Class<classType#C>`.
|
||||
|
||||
x | res9 = C as Class<A?>
|
||||
^^^^^^^^^
|
||||
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, `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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<"foo">`, but got type `Class<classType#C>`.
|
||||
|
||||
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, `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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<nothing>`, but got type `Class<classType#C>`.
|
||||
|
||||
x | res11 = C as Class<nothing>
|
||||
^^^^^^^^^^^^^^
|
||||
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, `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)
|
||||
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#A(true)>`, but got type `Class<classType#C>`.
|
||||
|
||||
x | res12 = C as Class<A(true)>
|
||||
^^^^^^^^^^^^^^
|
||||
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, `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)
|
||||
@@ -0,0 +1,14 @@
|
||||
–– Pkl Error ––
|
||||
Expected value of type `Class<classType#B>`, but got type `Class<classType#C>`.
|
||||
|
||||
x | res13 = C as Class<BB>
|
||||
^^^^^^^^^
|
||||
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)
|
||||
@@ -772,6 +772,24 @@ class EvaluatorTest {
|
||||
.doesNotThrowAnyException()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `eval schema containing a parameterized Class type`() {
|
||||
val evaluator = Evaluator.preconfigured()
|
||||
val schema =
|
||||
evaluator.evaluateSchema(
|
||||
text(
|
||||
"""
|
||||
foo: Class<Int>
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
)
|
||||
val fooType = schema.moduleClass.properties["foo"]?.type as? PType.Class
|
||||
assertThat(fooType?.pClass?.info).isEqualTo(PClassInfo.Class)
|
||||
val argType = fooType?.typeArguments?.single() as? PType.Class
|
||||
assertThat(argType?.pClass?.info).isEqualTo(PClassInfo.Int)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `concurrent evals`() {
|
||||
val exceptions = mutableListOf<Throwable>()
|
||||
|
||||
Reference in New Issue
Block a user