Skip to content

Commit 064a391

Browse files
committed
Add error hint when accessing the default value of a union type with no default
1 parent b92c773 commit 064a391

File tree

9 files changed

+139
-5
lines changed

9 files changed

+139
-5
lines changed

pkl-core/src/main/java/org/pkl/core/ast/member/DefaultPropertyBodyNode.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2025 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.
@@ -19,6 +19,9 @@
1919
import com.oracle.truffle.api.frame.VirtualFrame;
2020
import com.oracle.truffle.api.source.SourceSection;
2121
import org.pkl.core.ast.ExpressionNode;
22+
import org.pkl.core.ast.type.TypeNode.ConstrainedTypeNode;
23+
import org.pkl.core.ast.type.TypeNode.TypeAliasTypeNode;
24+
import org.pkl.core.ast.type.TypeNode.UnionTypeNode;
2225
import org.pkl.core.runtime.Identifier;
2326
import org.pkl.core.runtime.VmUtils;
2427
import org.pkl.core.util.Nullable;
@@ -50,8 +53,36 @@ public Object executeGeneric(VirtualFrame frame) {
5053
}
5154

5255
CompilerDirectives.transferToInterpreter();
53-
throw exceptionBuilder()
54-
.undefinedPropertyValue(propertyName, VmUtils.getReceiver(frame))
55-
.build();
56+
var builder =
57+
exceptionBuilder().undefinedPropertyValue(propertyName, VmUtils.getReceiver(frame));
58+
59+
// attempt to give a hint when a union type is missing a default
60+
if (typeNode != null) {
61+
String aliasName = null;
62+
var tn = typeNode.getTypeNode();
63+
while (true) {
64+
if (tn instanceof TypeAliasTypeNode typeAlias){
65+
aliasName = typeAlias.getVmTypeAlias().getSimpleName();
66+
tn = typeAlias.getVmTypeAlias().getTypeNode();
67+
} else if (tn instanceof ConstrainedTypeNode constrained) {
68+
tn = constrained.getBaseTypeNode();
69+
} else {
70+
break;
71+
}
72+
}
73+
if (tn instanceof UnionTypeNode union) {
74+
var unionText = union.getSourceSection().getCharacters().toString();
75+
if (aliasName != null) {
76+
builder.withHint(
77+
String.format(
78+
"Union type `%s` (from typealias `%s`) has no default member.",
79+
unionText, aliasName));
80+
} else {
81+
builder.withHint(String.format("Union type `%s` has no default member.", unionText));
82+
}
83+
}
84+
}
85+
86+
throw builder.build();
5687
}
5788
}

pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2598,6 +2598,10 @@ public Object executeAndSet(VirtualFrame frame, Object value) {
25982598
return childNode.createDefaultValue(language, headerSection, qualifiedName);
25992599
}
26002600

2601+
public TypeNode getBaseTypeNode() {
2602+
return childNode;
2603+
}
2604+
26012605
public SourceSection getBaseTypeSection() {
26022606
return childNode.getSourceSection();
26032607
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
local typealias B = Listing<String> | String
2+
local typealias C = B(true)
3+
4+
local class A {
5+
a: Listing<String> | String
6+
b: B
7+
c: C
8+
}
9+
10+
result =
11+
new A {
12+
c {}
13+
}.c
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
local typealias B = Listing<String> | String
2+
local typealias C = B
3+
4+
local class A {
5+
a: Listing<String> | String
6+
b: B
7+
c: C
8+
}
9+
10+
result =
11+
new A {
12+
b {}
13+
}.b
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
local typealias B = Listing<String> | String
2+
local typealias C = B
3+
4+
local class A {
5+
a: Listing<String> | String
6+
b: B
7+
c: C
8+
}
9+
10+
result =
11+
new A {
12+
a {}
13+
}.a
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
–– Pkl Error ––
2+
Tried to read property `c` but its value is undefined.
3+
4+
x | c: C
5+
^
6+
at amendUnionAliasAliasWithoutDefault#A.c (file:///$snippetsDir/input/errors/amendUnionAliasAliasWithoutDefault.pkl)
7+
8+
Union type `Listing<String> | String` (from typealias `B`) has no default member.
9+
10+
xx | new A {
11+
^^^^^^^
12+
at amendUnionAliasAliasWithoutDefault#result (file:///$snippetsDir/input/errors/amendUnionAliasAliasWithoutDefault.pkl)
13+
14+
xxx | renderer.renderDocument(value)
15+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
at pkl.base#Module.output.text (pkl:base)
17+
18+
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
19+
^^^^
20+
at pkl.base#Module.output.bytes (pkl:base)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
–– Pkl Error ––
2+
Tried to read property `b` but its value is undefined.
3+
4+
x | b: B
5+
^
6+
at amendUnionAliasWithoutDefault#A.b (file:///$snippetsDir/input/errors/amendUnionAliasWithoutDefault.pkl)
7+
8+
Union type `Listing<String> | String` (from typealias `B`) has no default member.
9+
10+
xx | new A {
11+
^^^^^^^
12+
at amendUnionAliasWithoutDefault#result (file:///$snippetsDir/input/errors/amendUnionAliasWithoutDefault.pkl)
13+
14+
xxx | renderer.renderDocument(value)
15+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
at pkl.base#Module.output.text (pkl:base)
17+
18+
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
19+
^^^^
20+
at pkl.base#Module.output.bytes (pkl:base)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
–– Pkl Error ––
2+
Tried to read property `a` but its value is undefined.
3+
4+
x | a: Listing<String> | String
5+
^
6+
at amendUnionWithoutDefault#A.a (file:///$snippetsDir/input/errors/amendUnionWithoutDefault.pkl)
7+
8+
Union type `Listing<String> | String` has no default member.
9+
10+
xx | new A {
11+
^^^^^^^
12+
at amendUnionWithoutDefault#result (file:///$snippetsDir/input/errors/amendUnionWithoutDefault.pkl)
13+
14+
xxx | renderer.renderDocument(value)
15+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
at pkl.base#Module.output.text (pkl:base)
17+
18+
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
19+
^^^^
20+
at pkl.base#Module.output.bytes (pkl:base)

pkl-core/src/test/files/LanguageSnippetTests/output/errors/noDefault2.err

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ x | foo: Foo|"baz"
55
^^^
66
at noDefault2#foo (file:///$snippetsDir/input/errors/noDefault2.pkl)
77

8-
The above error occurred when rendering path `foo` of module `file:///$snippetsDir/input/errors/noDefault2.pkl`.
8+
Union type `Foo|"baz"` has no default member.
99

1010
xxx | renderer.renderDocument(value)
1111
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

0 commit comments

Comments
 (0)