{"record":{"id":"34c7ab0a178c9efe","repo":"apple/pkl","slug":"notaunion","errorCode":"notAUnion","errorMessage":"Only type unions can have a default marker (*).","messagePattern":"Only type unions can have a default marker \\(\\*\\)\\.","errorType":"error_code","errorClass":"ParserError","httpStatus":null,"severity":"error","filePath":"pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java","lineNumber":1362,"sourceCode":"  private Type parseType() {\n    return parseType(null);\n  }\n\n  private Type parseType(@Nullable String expectation) {\n    var defaultIndex = -1;\n    Span start = null;\n    if (lookahead == Token.STAR) {\n      defaultIndex = 0;\n      start = next().span;\n    }\n    var first = parseTypeAtom(expectation);\n    if (start == null) {\n      start = first.span();\n    }\n\n    if (lookahead != Token.UNION) {\n      if (defaultIndex == 0) {\n        throw new ParserError(ErrorMessages.create(\"notAUnion\"), start.endWith(first.span()));\n      }\n      return first;\n    }\n\n    var types = new ArrayList<Type>();\n    types.add(first);\n    var end = start;\n    var i = 1;\n    while (lookahead == Token.UNION) {\n      next();\n      if (lookahead == Token.STAR) {\n        if (defaultIndex != -1) {\n          throw parserError(\"multipleUnionDefaults\");\n        }\n        defaultIndex = i;\n        next();\n      }\n      var type = parseTypeAtom(expectation);","sourceCodeStart":1344,"sourceCodeEnd":1380,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java#L1344-L1380","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nx: *String\n// after\nx: *String | Int  // or just: x: String","handlingStrategy":"validation","validationCode":"function hasStrayDefaultMarker(typeExpr) {\n  // '*' is only valid immediately before a member of a '|' union\n  return /(^|[\\s,&(])\\*[^|]*$/.test(typeExpr) && !typeExpr.includes('|');\n}\n// flag type expressions containing '*' but no '|' before parsing","typeGuard":"null","tryCatchPattern":"try {\n  const result = parser.parseType(source);\n} catch (e) {\n  if (e.errorId === 'notAUnion') {\n    // strip the '*' or complete the union with '|'\n  } else { throw e; }\n}","preventionTips":["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"],"tags":["parser","pkl","type-union","syntax-error"],"backgroundTag":"invalid-token-placement","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}