apple/pkl · error · ParserError
notAUnion
notAUnion
Error message
Only type unions can have a default marker (*).
What it means
Pkl reserves the `*` prefix on types to mark the default member of a type union. The parser collects default markers while parsing a type list, and if it saw one (defaultIndex == 0 means a marker appeared) but the `|` union token never followed — so the result isn't a union — it rejects the marker with notAUnion, spanning from the type's start through the first type.
Source
Thrown at pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java:1362
private Type parseType() {
return parseType(null);
}
private Type parseType(@Nullable String expectation) {
var defaultIndex = -1;
Span start = null;
if (lookahead == Token.STAR) {
defaultIndex = 0;
start = next().span;
}
var first = parseTypeAtom(expectation);
if (start == null) {
start = first.span();
}
if (lookahead != Token.UNION) {
if (defaultIndex == 0) {
throw new ParserError(ErrorMessages.create("notAUnion"), start.endWith(first.span()));
}
return first;
}
var types = new ArrayList<Type>();
types.add(first);
var end = start;
var i = 1;
while (lookahead == Token.UNION) {
next();
if (lookahead == Token.STAR) {
if (defaultIndex != -1) {
throw parserError("multipleUnionDefaults");
}
defaultIndex = i;
next();
}
var type = parseTypeAtom(expectation);View on GitHub (pinned to f3efcbfc9b)
Solutions
- Either remove the `*` if a plain single type was intended (`String` instead of `*String`)
- Or complete the union: `*String | Int` — the default marker is only valid inside a `|` union
- Check the parser/emitter that generated the type expression for a dropped `|` token
Example fix
// before x: *String // after x: *String | Int // or just: x: String
Defensive patterns
Strategy: validation
Validate before calling
function hasStrayDefaultMarker(typeExpr) {
// '*' is only valid immediately before a member of a '|' union
return /(^|[\s,&(])\*[^|]*$/.test(typeExpr) && !typeExpr.includes('|');
}
// flag type expressions containing '*' but no '|' before parsing Type guard
null
Try / catch
try {
const result = parser.parseType(source);
} catch (e) {
if (e.errorId === 'notAUnion') {
// strip the '*' or complete the union with '|'
} else { throw e; }
} Prevention
- Remember: '*' is only meaningful inside `A | B` unions
- Search generated type strings for '*' not followed by a union
- Review snippets ported from other languages that use '*' prefixes
When it happens
Trigger: Writing `*String` or `foo: *Int` where no union `|` follows — the `*` is applied to a lone type; also `*String?` in an amortized/`*T&...` conjunction context where no UNION token is produced.
Common situations: Confusing Pkl's union-default `*A|B` syntax with other languages' pointer/deref or splat `*` prefixes; a typo dropping the `| B` half of a union; old snippets from pre-union Pkl drafts.
Related errors
- missingDelimiter
- unexpectedEndOfFile
- stringContentMustBeginOnNewLine
- closingStringDelimiterMustBeginOnNewLine
- ErrorMessages.create(errorKey, args)
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/34c7ab0a178c9efe.
Report an issue: GitHub.