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 58fe3bee2..312f35330 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,13 @@ */ package org.pkl.core.ast.expression.literal; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Fallback; import com.oracle.truffle.api.dsl.ImportStatic; import com.oracle.truffle.api.dsl.NodeChild; import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; import org.pkl.core.ast.ExpressionNode; import org.pkl.core.runtime.*; @@ -32,6 +34,8 @@ public abstract class EmptyObjectLiteralNode extends ExpressionNode { super(sourceSection); } + protected abstract Object executeWithParent(VirtualFrame frame, Object parent); + protected abstract ExpressionNode getParentNode(); @Specialization @@ -69,4 +73,26 @@ public abstract class EmptyObjectLiteralNode extends ExpressionNode { VmUtils.checkIsInstantiable(VmUtils.getClass(parent), getParentNode()); throw exceptionBuilder().unreachableCode().build(); } + + @Override + public final Object executeGeneric(VirtualFrame frame) { + Object parent; + try { + parent = getParentNode().executeGeneric(frame); + } catch (VmException e) { + CompilerDirectives.transferToInterpreter(); + // include object amendment in the error message if error found during eval of parent + if (e.getSourceSection() != null + && !e.getSourceSection().equals(sourceSection) + // don't include if the originating error's source section is within the bounds of our own + // source section. + && !(VmUtils.sourceSectionContains(sourceSection, e.getSourceSection()))) { + e.getInsertedStackFrames() + .putIfAbsent( + getRootNode().getCallTarget(), VmUtils.createStackFrame(sourceSection, null)); + } + throw e; + } + return executeWithParent(frame, parent); + } } diff --git a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java index 7e56ff737..c627bc2c6 100644 --- a/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java +++ b/pkl-core/src/main/java/org/pkl/core/ast/expression/literal/ObjectLiteralNode.java @@ -28,6 +28,7 @@ import org.pkl.core.ast.type.TypeNode; import org.pkl.core.ast.type.UnresolvedTypeNode; import org.pkl.core.runtime.BaseModule; import org.pkl.core.runtime.VmClass; +import org.pkl.core.runtime.VmException; import org.pkl.core.runtime.VmFunction; import org.pkl.core.runtime.VmLanguage; import org.pkl.core.runtime.VmNull; @@ -58,6 +59,8 @@ public abstract class ObjectLiteralNode extends ExpressionNode { this.parameterTypes = parameterTypes; } + protected abstract Object executeWithParent(VirtualFrame frame, Object parent); + protected abstract ExpressionNode getParentNode(); protected abstract ObjectLiteralNode copy(ExpressionNode newParentNode); @@ -114,4 +117,27 @@ public abstract class ObjectLiteralNode extends ExpressionNode { LoopNode.reportLoopCount(this, count); return value; } + + @Override + public final Object executeGeneric(VirtualFrame frame) { + Object parent; + try { + parent = getParentNode().executeGeneric(frame); + } catch (VmException e) { + CompilerDirectives.transferToInterpreter(); + // include object amendment in the error message if error found during eval of parent + if (e.getSourceSection() != null + && !e.getSourceSection().equals(sourceSection) + // don't include if the originating error's source section is within the bounds of our own + // source section. + && !(VmUtils.sourceSectionContains(sourceSection, e.getSourceSection()))) { + e.getInsertedStackFrames() + .putIfAbsent( + getRootNode().getCallTarget(), + VmUtils.createStackFrame(sourceSection, qualifiedScopeName)); + } + throw e; + } + return executeWithParent(frame, parent); + } } 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 70218c100..e7610cdd7 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 @@ -1166,4 +1166,12 @@ public final class VmUtils { return builder.toString(); } + + public static boolean sourceSectionContains(SourceSection thisSource, SourceSection otherSource) { + if (!thisSource.getSource().equals(otherSource.getSource())) { + return false; + } + return thisSource.getCharIndex() <= otherSource.getCharIndex() + && thisSource.getCharEndIndex() >= otherSource.getCharEndIndex(); + } } diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault1.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault1.pkl new file mode 100644 index 000000000..e3ac95c6c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault1.pkl @@ -0,0 +1,7 @@ +class Example { + aa: Listing | String +} + +res: Example = new { + aa { "hello" } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault2.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault2.pkl new file mode 100644 index 000000000..ad2a3bc5a --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault2.pkl @@ -0,0 +1,9 @@ +open class Parent { + bb: *String | Int +} + +class Example extends Parent { + bb {} +} + +res: Example = new {} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault3.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault3.pkl new file mode 100644 index 000000000..3ce8bc9d4 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/errors/amendPropertyWithNoDefault3.pkl @@ -0,0 +1,8 @@ +class Example { + aa: Listing | String + bb: Listing = aa +} + +res: Example = new { + bb {} +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault1.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault1.err new file mode 100644 index 000000000..83d6285ab --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault1.err @@ -0,0 +1,20 @@ +–– Pkl Error –– +Tried to read property `aa` but its value is undefined. + +x | aa: Listing | String + ^^ +at amendPropertyWithNoDefault1#Example.aa (file:///$snippetsDir/input/errors/amendPropertyWithNoDefault1.pkl) + +The above error occurred when rendering path `res.aa` of module `file:///$snippetsDir/input/errors/amendPropertyWithNoDefault1.pkl`. + +x | aa { "hello" } + ^^^^^^^^^^^^^^ +at amendPropertyWithNoDefault1#res.aa (file:///$snippetsDir/input/errors/amendPropertyWithNoDefault1.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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault2.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault2.err new file mode 100644 index 000000000..2af07f088 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault2.err @@ -0,0 +1,20 @@ +–– Pkl Error –– +Tried to read property `bb` but its value is undefined. + +x | bb: *String | Int + ^^ +at amendPropertyWithNoDefault2#Parent.bb (file:///$snippetsDir/input/errors/amendPropertyWithNoDefault2.pkl) + +The above error occurred when rendering path `res.bb` of module `file:///$snippetsDir/input/errors/amendPropertyWithNoDefault2.pkl`. + +x | bb {} + ^^^^^ +at (file:///$snippetsDir/input/errors/amendPropertyWithNoDefault2.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) diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault3.err b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault3.err new file mode 100644 index 000000000..0dc1788ae --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/errors/amendPropertyWithNoDefault3.err @@ -0,0 +1,24 @@ +–– Pkl Error –– +Tried to read property `aa` but its value is undefined. + +x | aa: Listing | String + ^^ +at amendPropertyWithNoDefault3#Example.aa (file:///$snippetsDir/input/errors/amendPropertyWithNoDefault3.pkl) + +The above error occurred when rendering path `res.aa` of module `file:///$snippetsDir/input/errors/amendPropertyWithNoDefault3.pkl`. + +x | bb: Listing = aa + ^^ +at amendPropertyWithNoDefault3#Example.bb (file:///$snippetsDir/input/errors/amendPropertyWithNoDefault3.pkl) + +x | bb {} + ^^^^^ +at (file:///$snippetsDir/input/errors/amendPropertyWithNoDefault3.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)