diff --git a/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java b/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java index 635413b5..b5362a39 100644 --- a/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java +++ b/pkl-core/src/main/java/org/pkl/core/EvaluatorImpl.java @@ -137,7 +137,7 @@ public class EvaluatorImpl implements Evaluator { return doEvaluate( moduleSource, (module) -> { - var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT); + var output = readModuleOutput(module); return VmUtils.readTextProperty(output); }); } @@ -147,7 +147,7 @@ public class EvaluatorImpl implements Evaluator { return doEvaluate( moduleSource, (module) -> { - var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT); + var output = readModuleOutput(module); var value = VmUtils.readMember(output, Identifier.VALUE); if (value instanceof VmValue vmValue) { vmValue.force(false); @@ -162,7 +162,7 @@ public class EvaluatorImpl implements Evaluator { return doEvaluate( moduleSource, (module) -> { - var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT); + var output = readModuleOutput(module); var filesOrNull = VmUtils.readMember(output, Identifier.FILES); if (filesOrNull instanceof VmNull) { return Map.of(); @@ -243,7 +243,7 @@ public class EvaluatorImpl implements Evaluator { return doEvaluate( moduleSource, (module) -> { - var output = (VmTyped) VmUtils.readMember(module, Identifier.OUTPUT); + var output = readModuleOutput(module); var value = VmUtils.readMember(output, Identifier.VALUE); var valueClassInfo = VmUtils.getClass(value).getPClassInfo(); if (valueClassInfo.equals(classInfo)) { @@ -365,13 +365,40 @@ public class EvaluatorImpl implements Evaluator { "evaluationTimedOut", (timeout.getSeconds() + timeout.getNano() / 1_000_000_000d))); } + private VmTyped readModuleOutput(VmTyped module) { + var value = VmUtils.readMember(module, Identifier.OUTPUT); + if (value instanceof VmTyped typedOutput + && typedOutput.getVmClass().getPClassInfo() == PClassInfo.ModuleOutput) { + return typedOutput; + } + + var moduleUri = module.getModuleInfo().getModuleKey().getUri(); + var builder = + new VmExceptionBuilder() + .evalError( + "invalidModuleOutput", + "output", + PClassInfo.ModuleOutput.getDisplayName(), + VmUtils.getClass(value).getPClassInfo().getDisplayName(), + moduleUri); + var outputMember = module.getMember(Identifier.OUTPUT); + assert outputMember != null; + var uriOfValueMember = outputMember.getSourceSection().getSource().getURI(); + // If `output` was explicitly re-assigned, show that in the stack trace. + if (!uriOfValueMember.equals(PClassInfo.pklBaseUri)) { + builder.withSourceSection(outputMember.getBodySection()).withMemberName("output"); + } + throw builder.build(); + } + private VmException moduleOutputValueTypeMismatch( VmTyped module, PClassInfo expectedClassInfo, Object value, VmTyped output) { var moduleUri = module.getModuleInfo().getModuleKey().getUri(); var builder = new VmExceptionBuilder() .evalError( - "invalidModuleOutputValue", + "invalidModuleOutput", + "output.value", expectedClassInfo.getDisplayName(), VmUtils.getClass(value).getPClassInfo().getDisplayName(), moduleUri); diff --git a/pkl-core/src/main/java/org/pkl/core/PClassInfo.java b/pkl-core/src/main/java/org/pkl/core/PClassInfo.java index 1ae5970f..34eeae2b 100644 --- a/pkl-core/src/main/java/org/pkl/core/PClassInfo.java +++ b/pkl-core/src/main/java/org/pkl/core/PClassInfo.java @@ -61,6 +61,8 @@ public final class PClassInfo implements Serializable { public static final PClassInfo Mapping = pklBaseClassInfo("Mapping", LinkedHashMap.class); public static final PClassInfo Module = pklBaseClassInfo("Module", PModule.class); + public static final PClassInfo ModuleOutput = + pklBaseClassInfo("ModuleOutput", PObject.class); public static final PClassInfo Class = pklBaseClassInfo("Class", PClass.class); public static final PClassInfo TypeAlias = pklBaseClassInfo("TypeAlias", TypeAlias.class); @@ -216,6 +218,7 @@ public final class PClassInfo implements Serializable { entry(Listing.className, Listing), entry(Mapping.className, Mapping), entry(Module.className, Module), + entry(ModuleOutput.className, ModuleOutput), entry(Class.className, Class), entry(TypeAlias.className, TypeAlias), entry(Regex.className, Regex), diff --git a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties index e46c4d11..3fcb9c1f 100644 --- a/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties +++ b/pkl-core/src/main/resources/org/pkl/core/errorMessages.properties @@ -833,8 +833,8 @@ A type union cannot have more than one default type. notAUnion=\ Only type unions can have a default marker (*). -invalidModuleOutputValue=\ -Expected `output.value` of module `{2}` to be of type `{0}`, but got type `{1}`. +invalidModuleOutput=\ +Expected `{0}` of module `{3}` to be of type `{1}`, but got type `{2}`. cannotResolveDependencyWithoutHierarchicalUris=\ Cannot import dependency because project URI `{0}` does not have a hierarchical path. diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput1.pkl new file mode 100644 index 00000000..d2c46289 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput1.pkl @@ -0,0 +1 @@ +output: String = "abc" diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput2.pkl new file mode 100644 index 00000000..3a95b6f8 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput2.pkl @@ -0,0 +1,3 @@ +class Test {} + +output: Test = new {} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput3.pkl new file mode 100644 index 00000000..b6e1e3a5 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/invalidOutput3.pkl @@ -0,0 +1 @@ +output = null diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput1.err new file mode 100644 index 00000000..0db29574 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput1.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Expected `output` of module `file:///$snippetsDir/input/errors/invalidOutput1.pkl` to be of type `ModuleOutput`, but got type `String`. + +x | output: String = "abc" + ^^^^^ +at output (file:///$snippetsDir/input/errors/invalidOutput1.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput2.err new file mode 100644 index 00000000..ab0b7d1c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput2.err @@ -0,0 +1,6 @@ +–– Pkl Error –– +Expected `output` of module `file:///$snippetsDir/input/errors/invalidOutput2.pkl` to be of type `ModuleOutput`, but got type `invalidOutput2#Test`. + +x | output: Test = new {} + ^^^^^^ +at output (file:///$snippetsDir/input/errors/invalidOutput2.pkl) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput3.err new file mode 100644 index 00000000..b05e3443 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/invalidOutput3.err @@ -0,0 +1,10 @@ +–– Pkl Error –– +Expected value of type `ModuleOutput`, but got `null`. + +xx | hidden output: ModuleOutput = new { + ^^^^^^^^^^^^ +at pkl.base#Module.output (pkl:base) + +x | output = null + ^^^^ +at invalidOutput3#output (file:///$snippetsDir/input/errors/invalidOutput3.pkl)