From a06684b2c0017ed924e6f416cdd1772c31fb632f Mon Sep 17 00:00:00 2001 From: Daniel Chao Date: Tue, 20 Oct 2026 08:42:18 -0700 Subject: [PATCH] Improve error message thrown for "cannot instantiate" (#1916) If the parent comes from an implicit node (e.g. read super entry node), Pkl will currently bury its source section when rendering the error message. --- .../generator/GeneratorObjectLiteralNode.java | 8 +++++--- .../literal/EmptyObjectLiteralNode.java | 4 ++-- .../literal/SpecializedObjectLiteralNode.java | 4 ++-- .../member/AbstractInferParentNode.java | 2 +- .../java/org/pkl/core/runtime/VmUtils.java | 7 ++++--- .../pkl/core/stdlib/base/DynamicNodes.java | 2 +- .../org/pkl/core/stdlib/base/MapNodes.java | 2 +- .../errors/cannotInstantiateExternalClass.pkl | 13 +++++++++++++ .../errors/cannotInstantiateExternalClass.err | 19 +++++++++++++++++++ 9 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/input/errors/cannotInstantiateExternalClass.pkl create mode 100644 pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotInstantiateExternalClass.err diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java index f527ea850..c2cd46937 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorObjectLiteralNode.java @@ -98,7 +98,7 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode { @Specialization(guards = "checkObjectCannotHaveParameters()") protected VmTyped evalTyped(VirtualFrame frame, VmTyped parent) { - VmUtils.checkIsInstantiable(parent.getVmClass(), getParentNode()); + VmUtils.checkIsInstantiable(parent.getVmClass(), getParentNode(), this); var data = executeChildren(frame, parent, 0); if (data.hasNoMembers()) { return parent; @@ -207,7 +207,7 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode { @Specialization(guards = {"isTypedObjectClass(parent)", "checkObjectCannotHaveParameters()"}) protected VmTyped evalTypedObjectClass(VirtualFrame frame, VmClass parent) { - VmUtils.checkIsInstantiable(parent, getParentNode()); + VmUtils.checkIsInstantiable(parent, getParentNode(), this); var data = executeChildren(frame, parent, 0); if (data.hasNoMembers()) { return parent.getPrototype(); @@ -225,7 +225,9 @@ public abstract class GeneratorObjectLiteralNode extends ObjectLiteralNode { value = getNullDefaultValue(vmNull); } VmUtils.checkIsInstantiable( - value instanceof VmClass vmClass ? vmClass : VmUtils.getClass(value), getParentNode()); + value instanceof VmClass vmClass ? vmClass : VmUtils.getClass(value), + getParentNode(), + this); throw exceptionBuilder().unreachableCode().build(); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EmptyObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EmptyObjectLiteralNode.java index 312f35330..94ba1b2e0 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EmptyObjectLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/EmptyObjectLiteralNode.java @@ -52,7 +52,7 @@ public abstract class EmptyObjectLiteralNode extends ExpressionNode { return VmDynamic.empty(); } - VmUtils.checkIsInstantiable(parent, getParentNode()); + VmUtils.checkIsInstantiable(parent, getParentNode(), this); return parent.getPrototype(); } @@ -70,7 +70,7 @@ public abstract class EmptyObjectLiteralNode extends ExpressionNode { @TruffleBoundary protected void fallback(Object parent) { assert !(parent instanceof VmClass); - VmUtils.checkIsInstantiable(VmUtils.getClass(parent), getParentNode()); + VmUtils.checkIsInstantiable(VmUtils.getClass(parent), getParentNode(), this); throw exceptionBuilder().unreachableCode().build(); } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java index 23e83fba4..b3cb4e933 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/SpecializedObjectLiteralNode.java @@ -72,7 +72,7 @@ public abstract class SpecializedObjectLiteralNode extends ObjectLiteralNode { @Idempotent protected boolean checkIsValidTypedAmendment(Object parent) { var parentClass = parent instanceof VmClass vmClass ? vmClass : VmUtils.getClass(parent); - VmUtils.checkIsInstantiable(parentClass, getParentNode()); + VmUtils.checkIsInstantiable(parentClass, getParentNode(), this); for (var member : EconomicMaps.getValues(members)) { if (member.isLocal()) continue; @@ -290,7 +290,7 @@ public abstract class SpecializedObjectLiteralNode extends ObjectLiteralNode { Object parent, @Nullable ObjectMember firstElemOrEntry, boolean isElementsOnly) { var parentIsClass = parent instanceof VmClass; var parentClass = parentIsClass ? (VmClass) parent : VmUtils.getClass(parent); - VmUtils.checkIsInstantiable(parentClass, getParentNode()); + VmUtils.checkIsInstantiable(parentClass, getParentNode(), this); var property = findFirstNonDefaultProperty(members); if (property != null diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java index 0d7e35a87..4d57173ec 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/member/AbstractInferParentNode.java @@ -59,7 +59,7 @@ public abstract class AbstractInferParentNode extends ExpressionNode { // try to produce a more specific error message than "cannotInstantiateType" var clazz = typeNode.getType().getVmClass(); if (clazz != null) { - VmUtils.checkIsInstantiable(clazz, typeNode); + VmUtils.checkIsInstantiable(clazz, typeNode, this); } throw exceptionBuilder() diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java index 2293e786f..4b5d01f76 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmUtils.java @@ -903,7 +903,7 @@ public final class VmUtils { return resolvedNodes; } - public static void checkIsInstantiable(VmClass parentClass, @Nullable Node parentNode) { + public static void checkIsInstantiable(VmClass parentClass, Node parentNode, Node callerNode) { if (parentClass.isInstantiable()) return; CompilerDirectives.transferToInterpreter(); @@ -911,14 +911,15 @@ public final class VmUtils { if (parentClass.isAbstract()) { throw new VmExceptionBuilder() .evalError("cannotInstantiateAbstractClass", parentClass) - .withOptionalLocation(parentNode) + .withOptionalLocation( + parentNode.getSourceSection().isAvailable() ? parentNode : callerNode) .build(); } assert parentClass.isExternal(); throw new VmExceptionBuilder() .evalError("cannotInstantiateExternalClass", parentClass) - .withOptionalLocation(parentNode) + .withOptionalLocation(parentNode.getSourceSection().isAvailable() ? parentNode : callerNode) .build(); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/DynamicNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/DynamicNodes.java index 17affbb8e..7d1ee415a 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/DynamicNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/DynamicNodes.java @@ -108,7 +108,7 @@ public final class DynamicNodes { throw exceptionBuilder().evalError("notASubclassOfTyped", clazz).build(); } - VmUtils.checkIsInstantiable(clazz, this); + VmUtils.checkIsInstantiable(clazz, this, this); var result = new VmTyped( diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MapNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MapNodes.java index a207658ca..ccd60e5e3 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/MapNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/MapNodes.java @@ -255,7 +255,7 @@ public final class MapNodes { throw exceptionBuilder().evalError("notASubclassOfTyped", clazz).build(); } - VmUtils.checkIsInstantiable(clazz, null); + VmUtils.checkIsInstantiable(clazz, this, this); var result = new VmTyped( diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/cannotInstantiateExternalClass.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/cannotInstantiateExternalClass.pkl new file mode 100644 index 000000000..62e2f1bfa --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/cannotInstantiateExternalClass.pkl @@ -0,0 +1,13 @@ +class Holder { + name: String +} + +base: Holder = new { + name = "base" +} + +result: Holder = (base) { + name { + "foo" + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotInstantiateExternalClass.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotInstantiateExternalClass.err new file mode 100644 index 000000000..293299a7b --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/cannotInstantiateExternalClass.err @@ -0,0 +1,19 @@ +–– Pkl Error –– +Cannot instantiate, or amend an instance of, external class `String`. + +xx | name { + ^^^^^^ +at cannotInstantiateExternalClass#result.name (file:///$snippetsDir/input/errors/cannotInstantiateExternalClass.pkl) + +To create an instance of an external class, use its literal syntax or constructor method. +Examples: +* `123` uses literal syntax to create an instance of class `Int`. +* `Pair(1, 2)` uses the `Pair()` constructor method to create an instance of class `Pair`. + +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)