Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
Expand All @@ -19,6 +19,9 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.pkl.core.ast.ExpressionNode;
import org.pkl.core.ast.type.TypeNode.ConstrainedTypeNode;
import org.pkl.core.ast.type.TypeNode.TypeAliasTypeNode;
import org.pkl.core.ast.type.TypeNode.UnionTypeNode;
import org.pkl.core.runtime.Identifier;
import org.pkl.core.runtime.VmUtils;
import org.pkl.core.util.Nullable;
Expand Down Expand Up @@ -50,8 +53,30 @@ public Object executeGeneric(VirtualFrame frame) {
}

CompilerDirectives.transferToInterpreter();

// attempt to give a hint when a union type is missing a default
String aliasName = null;
String unionTypeSource = null;
if (typeNode != null) {
var tn = typeNode.getTypeNode();
while (true) {
if (tn instanceof TypeAliasTypeNode typeAlias) {
aliasName = typeAlias.getVmTypeAlias().getSimpleName();
tn = typeAlias.getVmTypeAlias().getTypeNode();
} else if (tn instanceof ConstrainedTypeNode constrained) {
tn = constrained.getBaseTypeNode();
} else {
break;
}
}
if (tn instanceof UnionTypeNode union) {
unionTypeSource = union.getSourceSection().getCharacters().toString();
}
}

throw exceptionBuilder()
.undefinedPropertyValue(propertyName, VmUtils.getReceiver(frame))
.undefinedPropertyValue(
propertyName, VmUtils.getReceiver(frame), unionTypeSource, aliasName)
.build();
}
}
4 changes: 4 additions & 0 deletions pkl-core/src/main/java/org/pkl/core/ast/type/TypeNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -2598,6 +2598,10 @@ public Object executeAndSet(VirtualFrame frame, Object value) {
return childNode.createDefaultValue(language, headerSection, qualifiedName);
}

public TypeNode getBaseTypeNode() {
return childNode;
}

public SourceSection getBaseTypeSection() {
return childNode.getSourceSection();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 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.
Expand Down Expand Up @@ -124,9 +124,19 @@ public VmExceptionBuilder undefinedValue() {
return withExternalMessage("undefinedValue");
}

public VmExceptionBuilder undefinedPropertyValue(Identifier propertyName, Object receiver) {
public VmExceptionBuilder undefinedPropertyValue(
Identifier propertyName,
Object receiver,
@Nullable String unionTypeSource,
@Nullable String typeAliasName) {
kind = VmException.Kind.UNDEFINED_VALUE;
this.receiver = receiver;
if (unionTypeSource != null && typeAliasName != null) {
return withExternalMessage(
"undefinedPropertyValueUnionAlias", propertyName, unionTypeSource, typeAliasName);
} else if (unionTypeSource != null) {
return withExternalMessage("undefinedPropertyValueUnion", propertyName, unionTypeSource);
}
return withExternalMessage("undefinedPropertyValue", propertyName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,14 @@ Undefined value.
undefinedPropertyValue=\
Tried to read property `{0}` but its value is undefined.

undefinedPropertyValueUnion=\
Tried to read property `{0}` but its value is undefined.\n\
Union type `{1}` has no default member.

undefinedPropertyValueUnionAlias=\
Tried to read property `{0}` but its value is undefined.\n\
Union type `{1}` (from type alias `{2}`) has no default member.

cannotFindModule=\
Cannot find module `{0}`.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
local typealias B = Listing<String> | String

b: B
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
local typealias B = Listing<String> | String
local typealias C = B(true)

c: C
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
–– Pkl Error ––
Tried to read property `foo` but its value is undefined.
Union type `Foo|"baz"` has no default member.

x | foo: Foo|"baz"
^^^
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
–– Pkl Error ––
Tried to read property `b` but its value is undefined.
Union type `Listing<String> | String` (from type alias `B`) has no default member.
Copy link
Member

@bioball bioball Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message doesn't explain what "Default member" is. If we want this as the error message, probably should have a suggestion of how to mark a union default member.

But, this error message feels a little strange to me for a couple reasons. This throws too:

foo: Int | Boolean | String

But having a default member doesn't help, because none of these types have default values.

Also, this error message feels oddly specific; the issue here is that the property doesn't have a default value.
There's no default value because:

  1. The class property has no declared default value
  2. The property's declared type has no default value
  3. The object instance did not assign to this property

So there's three possible resolutions:

  1. Add an explicit default in the class property (only if this is modifiable code)
  2. Change the type to have an explicit default (only if one of the member types has a default value, and this is modifiable code)
  3. Define the property in your instance

I think this current error message would probably be misleading too often.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the condition here to check that the union has no default member and updated the message a bit. How does it look now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still feels odd to me:

  1. Adding a default marker doesn't do anything for union types like Int | Boolean | String
  2. The code might not be modifiable, so we shouldn't have verbage that suggests updating the type as a fix.

To address #1368, it feels like the error message should be something like this?

Cannot instantiate type `Listing<String> | String` because it has no default value.

But we'd really only want to show this error message when we're trying to amend it with an object body.


x | b: B
^
at noDefault3#b (file:///$snippetsDir/input/errors/noDefault3.pkl)

The above error occurred when rendering path `b` of module `file:///$snippetsDir/input/errors/noDefault3.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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
–– Pkl Error ––
Tried to read property `c` but its value is undefined.
Union type `Listing<String> | String` (from type alias `B`) has no default member.

x | c: C
^
at noDefault4#c (file:///$snippetsDir/input/errors/noDefault4.pkl)

The above error occurred when rendering path `c` of module `file:///$snippetsDir/input/errors/noDefault4.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)
Loading