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
@@ -59,6 +59,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. 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<T>`
In prior versions of Pkl, type arguments to the `Class` type were erased.
Any `Class` value would typecheck against `Class<T>` for any value of `T`.
In Pkl 0.33, this erasure has been removed.
A `Class` value typechecked against `Class<T>` 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 === XXX
== Bug Fixes [small]#🐜# == Bug Fixes [small]#🐜#
@@ -33,6 +33,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NonNull;
@@ -61,7 +62,8 @@ import org.pkl.core.util.MutableReference;
public abstract class TypeNode extends PklNode { 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(); VmClass getVmClass();
} }
@@ -410,8 +412,7 @@ public abstract class TypeNode extends PklNode {
} }
/** The `module` type for a final module. */ /** The `module` type for a final module. */
public static final class FinalModuleTypeNode extends ObjectSlotTypeNode public static final class FinalModuleTypeNode extends ObjectSlotTypeNode {
implements ClassTypeNode {
private final VmClass moduleClass; private final VmClass moduleClass;
public FinalModuleTypeNode(SourceSection sourceSection, 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. */ /** The `module` type for an open module. */
public static final class NonFinalModuleTypeNode extends ObjectSlotTypeNode public static final class NonFinalModuleTypeNode extends ObjectSlotTypeNode {
implements ClassTypeNode {
private final VmClass moduleClass; // only used by getVmClass() private final VmClass moduleClass; // only used by getVmClass()
@Child private ExpressionNode getModuleNode; @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 * String/Boolean/Int/Float and their supertypes, only `VmValue`s can possibly pass its type
* check. * check.
*/ */
public static final class FinalClassTypeNode extends ObjectSlotTypeNode implements ClassTypeNode { public static final class FinalClassTypeNode extends ObjectSlotTypeNode
implements UserClassTypeNode {
private final VmClass clazz; private final VmClass clazz;
public FinalClassTypeNode(SourceSection sourceSection, VmClass clazz) { public FinalClassTypeNode(SourceSection sourceSection, VmClass clazz) {
@@ -708,7 +709,7 @@ public abstract class TypeNode extends PklNode {
* check. * check.
*/ */
public abstract static class NonFinalClassTypeNode extends ObjectSlotTypeNode public abstract static class NonFinalClassTypeNode extends ObjectSlotTypeNode
implements ClassTypeNode { implements UserClassTypeNode {
protected final VmClass clazz; protected final VmClass clazz;
public NonFinalClassTypeNode(SourceSection sourceSection, 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 { public abstract static class ValidatingObjectSlotTypeNode extends ObjectSlotTypeNode {
protected ValidatingObjectSlotTypeNode(SourceSection sourceSection) { protected ValidatingObjectSlotTypeNode(SourceSection sourceSection) {
@@ -278,9 +278,8 @@ public abstract class UnresolvedTypeNode extends PklNode {
return FunctionNClassTypeNodeGen.create(sourceSection, resolvedTypeArgumentNodes); return FunctionNClassTypeNodeGen.create(sourceSection, resolvedTypeArgumentNodes);
} }
// erase `x: Class<Foo>` to `x: Class` for now (cf. function types)
if (clazz.isClassClass()) { if (clazz.isClassClass()) {
return new FinalClassTypeNode(sourceSection, clazz); return ClassClassTypeNodeGen.create(sourceSection, typeArgumentNodes[0].execute(frame));
} }
if (clazz.isVarArgsClass()) { 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 { public static final class Constraint extends VmTypeMismatchException {
private final SourceSection constraintBodySourceSection; private final SourceSection constraintBodySourceSection;
@@ -173,7 +173,7 @@ public final class CommandSpecParser {
if (optionsTypeNode instanceof TypeNode.TypedTypeNode) { if (optionsTypeNode instanceof TypeNode.TypedTypeNode) {
return BaseModule.getTypedClass(); return BaseModule.getTypedClass();
} }
if (!(optionsTypeNode instanceof TypeNode.ClassTypeNode node)) { if (!(optionsTypeNode instanceof TypeNode.UserClassTypeNode node)) {
throw exceptionBuilder() throw exceptionBuilder()
.withSourceSection(optionsTypeNode.getSourceSection()) .withSourceSection(optionsTypeNode.getSourceSection())
.evalError( .evalError(
@@ -358,6 +358,9 @@ Expected value of type `{0}`, but got a different `{1}`.
typeMismatchBecause=\ typeMismatchBecause=\
* Value is not of type `{0}` because: * 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=\ typeConstraintViolated=\
Type constraint `{0}` violated. Type constraint `{0}` violated.
@@ -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() .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 @Test
fun `concurrent evals`() { fun `concurrent evals`() {
val exceptions = mutableListOf<Throwable>() val exceptions = mutableListOf<Throwable>()
+1 -1
View File
@@ -361,7 +361,7 @@ abstract class BaseValueRenderer {
/// ///
/// See [ConvertProperty] for detailed information. /// See [ConvertProperty] for detailed information.
@Since { version = "0.31.0" } @Since { version = "0.31.0" }
convertPropertyTransformers: Mapping<Class, Mixin<ConvertProperty>> convertPropertyTransformers: Mapping<Class<ConvertProperty>, Mixin<ConvertProperty>>
/// The file extension associated with this output format, /// The file extension associated with this output format,
/// or [null] if this format does not have an extension. /// or [null] if this format does not have an extension.
+2 -2
View File
@@ -26,8 +26,8 @@
/// 3. `~/.config/pkl/settings.pkl` /// 3. `~/.config/pkl/settings.pkl`
/// 4. Path `pkl/settings.pkl` within the `$XDG_CONFIG_DIRS` search path /// 4. Path `pkl/settings.pkl` within the `$XDG_CONFIG_DIRS` search path
/// (dirs separated by `:` on Unix, `;` on Windows). /// (dirs separated by `:` on Unix, `;` on Windows).
/// 4. `/etc/xdg/pkl/settings.pkl` /// 5. `/etc/xdg/pkl/settings.pkl`
/// 5. `~/.pkl/settings.pkl` (legacy location used by Pkl 0.32 and lower) /// 6. `~/.pkl/settings.pkl` (legacy location used by Pkl 0.32 and lower)
@ModuleInfo { minPklVersion = "0.33.0" } @ModuleInfo { minPklVersion = "0.33.0" }
module pkl.settings module pkl.settings