Produce more helpful output when module output is overwritten (#716)

Fix a bug where overwriting `output` causes a PklBugException.

This now produces a more helpful message pointing to the actual problem.

Co-authored-by: translatenix <119817707+translatenix@users.noreply.github.com>
This commit is contained in:
Josh B
2024-10-24 09:41:59 -07:00
committed by GitHub
parent cbbcca0d84
commit 4b6bc7bb7c
9 changed files with 64 additions and 7 deletions

View File

@@ -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);

View File

@@ -61,6 +61,8 @@ public final class PClassInfo<T> implements Serializable {
public static final PClassInfo<LinkedHashMap> Mapping =
pklBaseClassInfo("Mapping", LinkedHashMap.class);
public static final PClassInfo<PModule> Module = pklBaseClassInfo("Module", PModule.class);
public static final PClassInfo<PObject> ModuleOutput =
pklBaseClassInfo("ModuleOutput", PObject.class);
public static final PClassInfo<PClass> Class = pklBaseClassInfo("Class", PClass.class);
public static final PClassInfo<TypeAlias> TypeAlias =
pklBaseClassInfo("TypeAlias", TypeAlias.class);
@@ -216,6 +218,7 @@ public final class PClassInfo<T> 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),

View File

@@ -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.

View File

@@ -0,0 +1 @@
output: String = "abc"

View File

@@ -0,0 +1,3 @@
class Test {}
output: Test = new {}

View File

@@ -0,0 +1 @@
output = null

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)