Correctly type check Class<T> (#1698)

This commit is contained in:
Jen Basch
2026-08-26 05:29:34 +00:00
committed by GitHub
parent a90eb74c2d
commit cb01d6275b
39 changed files with 503 additions and 13 deletions
@@ -0,0 +1,2 @@
module classType
class Foo
@@ -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>()