apple/pkl · error · ParserError
unexpectedCurlyProbablyAmendsExpression
unexpectedCurlyProbablyAmendsExpression
Error message
Unexpected token: `'{'`.
If you meant to write an amends expression, wrap the parent in parentheses. Try: `({0}) '{' ... '}'` What it means
A `{` followed an expression that cannot be amended implicitly. Only parenthesized expressions, amends expressions, and `new` expressions can take a `{ ... }` body directly; for anything else the parser throws "Unexpected token: `'{'`" with a hint to wrap the parent in parentheses to write an amends expression.
Solutions
- Wrap the base expression in parentheses as the message suggests: `(foo.bar) { ... }`.
- If the base is a module-level or imported default, prefer explicit `... { ... }` amends syntax or `new { ... }` for fresh objects.
- If `{ ... }` was meant as a block, restructure — Pkl expressions do not take braces except for amends/new.
- Check whether you actually wanted object-literal syntax (`new Type { ... }`) rather than amending an arbitrary expression.
Example fix
// before
x = config.bird { name = "tweety" }
// after
x = (config.bird) { name = "tweety" } Defensive patterns
Strategy: validation
Validate before calling
// Only parenthesized/new/amends bases may take a trailing '{ }'
const BARE_AMEND = /^\s*(?!new\b)(?!\()[A-Za-z_][\w.]*\s*\{/;
function needsParens(line) { return BARE_AMEND.test(line); }
// if (needsParens('config.bird { name = "x" }')) fix by wrapping base in parens Prevention
- Wrap non-new bases in parentheses before a trailing `{ ... }`.
- Reserve bare `{ ... }` for `new` and `...` amends contexts.
- Configure your editor/linter to flag `ident.path {` patterns.
When it happens
Trigger: Writing `foo.bar { ... }` intending to amend the result; `myExpr { ... }` where `myExpr` is a plain identifier/qualified access; `if (cond) x { ... }` style trailing bodies; amending the result of a function call without parens: `fetch() { ... }`.
Common situations: Users expecting trailing-`{}` amend sugar to work on any expression; converting `new Foo { ... }` patterns to computed values; qualified accesses to defaults that should be amended via parentheses.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/d06994c166254de7.
Report an issue: GitHub.
Appendix: source
Thrown at pkl-parser/src/main/java/org/pkl/parser/ParserImpl.java:1069
}
@SuppressWarnings("DuplicatedCode")
private Expr parseExprRest(Expr expr) {
// non-null
if (lookahead == Token.NON_NULL) {
var end = next().span;
var res = new NonNullExpr(expr, expr.span().endWith(end));
return parseExprRest(res);
}
// amends
if (lookahead == Token.LBRACE) {
if (expr instanceof ParenthesizedExpr
|| expr instanceof AmendsExpr
|| expr instanceof NewExpr) {
var body = parseObjectBody();
return parseExprRest(new AmendsExpr(expr, body, expr.span().endWith(body.span())));
}
throw parserError("unexpectedCurlyProbablyAmendsExpression", expr.text(lexer.getSource()));
}
// qualified access
if (lookahead == Token.DOT || lookahead == Token.QDOT) {
var isNullable = next().token == Token.QDOT;
var identifier = parseIdentifier();
ArgumentList argumentList = null;
if (lookahead == Token.LPAREN && !precededBySemicolon && _lookahead.newLinesBetween == 0) {
argumentList = parseArgumentList();
}
var lastSpan = argumentList != null ? argumentList.span() : identifier.span();
var res =
new QualifiedAccessExpr(
expr, identifier, isNullable, argumentList, expr.span().endWith(lastSpan));
return parseExprRest(res);
}
// subscript (needs to be in the same line as the expression)
if (lookahead == Token.LBRACK && !precededBySemicolon && _lookahead.newLinesBetween == 0) {
next();View on GitHub (pinned to f3efcbfc9b)