Skip to content

Commit bfda4cc

Browse files
Fix forcing of VmValue during error rendering (#1629)
1 parent c2652e0 commit bfda4cc

4 files changed

Lines changed: 92 additions & 7 deletions

File tree

pkl-core/src/main/java/org/pkl/core/ast/expression/generator/GeneratorElementNode.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
2525
import org.pkl.core.runtime.VmClass;
2626
import org.pkl.core.runtime.VmDynamic;
2727
import org.pkl.core.runtime.VmListing;
28+
import org.pkl.core.runtime.VmUtils;
2829

2930
@ImportStatic(BaseModule.class)
3031
public abstract class GeneratorElementNode extends GeneratorMemberNode {
@@ -63,6 +64,7 @@ protected void evalListingClass(VirtualFrame frame, VmClass parent, ObjectData d
6364
@SuppressWarnings("unused")
6465
void fallback(VirtualFrame frame, Object parent, ObjectData data) {
6566
CompilerDirectives.transferToInterpreter();
66-
throw exceptionBuilder().evalError("objectCannotHaveElement", parent).build();
67+
var parentClass = parent instanceof VmClass ? (VmClass) parent : VmUtils.getClass(parent);
68+
throw exceptionBuilder().evalError("objectCannotHaveElement", parentClass).build();
6769
}
6870
}

pkl-core/src/main/java/org/pkl/core/util/ErrorMessages.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,14 @@
2020
import java.util.ResourceBundle;
2121
import java.util.stream.*;
2222
import org.jspecify.annotations.Nullable;
23+
import org.pkl.core.runtime.VmValue;
24+
import org.pkl.core.runtime.VmValueRenderer;
2325

2426
public final class ErrorMessages {
2527
private ErrorMessages() {}
2628

29+
private static final VmValueRenderer renderer = VmValueRenderer.singleLine(Integer.MAX_VALUE);
30+
2731
public static String create(String messageName, @Nullable Object... args) {
2832
var locale = Locale.getDefault();
2933
String errorMessage =
@@ -33,7 +37,18 @@ public static String create(String messageName, @Nullable Object... args) {
3337
if (args.length == 0) return errorMessage;
3438

3539
var formatter = new MessageFormat(errorMessage, locale);
36-
return formatter.format(args);
40+
// TODO: we render VmValues here with VmValueRenderer, but that's not enough to properly
41+
// render all kinds of values, like Pkl Strings, for example
42+
@Nullable Object[] actualArgs = new @Nullable Object[args.length];
43+
for (var i = 0; i < args.length; i++) {
44+
var arg = args[i];
45+
if (arg instanceof VmValue) {
46+
actualArgs[i] = renderer.render(arg);
47+
} else {
48+
actualArgs[i] = arg;
49+
}
50+
}
51+
return formatter.format(actualArgs);
3752
}
3853

3954
public static String createIndented(String messageName, String indent, @Nullable Object... args) {
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
res1 = "Cannot instantiate, or amend an instance of, external class `Int`."
22
res2 = "Cannot instantiate, or amend an instance of, external class `List`."
33
res3 = "Cannot instantiate, or amend an instance of, external class `List`."
4-
res4 = "Object of type `new Person {}` cannot have an element."
4+
res4 = "Object of type `wrongParent#Person` cannot have an element."
55
res5 = "Cannot instantiate abstract class `ValueRenderer`."
6-
res6 = "Object of type `new Mapping {}` cannot have an element."
6+
res6 = "Object of type `Mapping` cannot have an element."
77
res7 = "Cannot instantiate, or amend an instance of, external class `Int`."
88
res8 = "Cannot instantiate, or amend an instance of, external class `List`."
99
res9 = "Cannot instantiate, or amend an instance of, external class `List`."
10-
res10 = "Object of type `new Person {}` cannot have an element."
10+
res10 = "Object of type `wrongParent#Person` cannot have an element."
1111
res11 = "Cannot instantiate abstract class `ValueRenderer`."
12-
res12 = "Object of type `new Mapping {}` cannot have an element."
12+
res12 = "Object of type `Mapping` cannot have an element."
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.pkl.core.util
17+
18+
import org.assertj.core.api.Assertions.assertThat
19+
import org.assertj.core.api.Assertions.assertThatCode
20+
import org.junit.jupiter.api.Test
21+
import org.pkl.core.runtime.VmClass
22+
import org.pkl.core.runtime.VmValue
23+
import org.pkl.core.runtime.VmValueConverter
24+
import org.pkl.core.runtime.VmValueVisitor
25+
26+
class ErrorMessagesTest {
27+
private class UnforceableValue : VmValue() {
28+
var forced = false
29+
private set
30+
31+
override fun force(allowUndefinedValues: Boolean) {
32+
forced = true
33+
throw RuntimeException("value must not be forced while rendering an error message")
34+
}
35+
36+
override fun accept(visitor: VmValueVisitor) {
37+
visitor.visitString("lazy")
38+
}
39+
40+
override fun <T : Any> accept(converter: VmValueConverter<T>, path: Iterable<Any>): T =
41+
throw UnsupportedOperationException()
42+
43+
override fun equals(obj: Any?): Boolean = this === obj
44+
45+
override fun toString(): String {
46+
force(true)
47+
return "lazy"
48+
}
49+
50+
override fun getVmClass(): VmClass = throw UnsupportedOperationException()
51+
52+
override fun export(): Any = throw UnsupportedOperationException()
53+
54+
override fun hashCode(): Int = System.identityHashCode(this)
55+
}
56+
57+
@Test
58+
fun `renders VmValue arguments without forcing them`() {
59+
val value = UnforceableValue()
60+
61+
lateinit var message: String
62+
assertThatCode { message = ErrorMessages.create("cannotIterateOverThisValue", value) }
63+
.doesNotThrowAnyException()
64+
65+
assertThat(value.forced).isFalse()
66+
assertThat(message).contains("lazy")
67+
}
68+
}

0 commit comments

Comments
 (0)