emberjs/ember.js · error
${node.path.type} "${node.path.type === 'StringLiteral' ? no
Error message
${node.path.type} "${node.path.type === 'StringLiteral' ? node.path.original : value}" cannot be called as a sub-expression, replace (${value}) with ${value} What it means
In a call/sub-expression position like {{value}} the path must be an identifier-based path, not a Handlebars literal. When node.path is a StringLiteral, NumberLiteral, BooleanLiteral, UndefinedLiteral or NullLiteral, the parser cannot treat the literal as a callable path and throws, suggesting to drop the parentheses (i.e. use the literal directly, not as a call).
Source
Thrown at packages/@glimmer/syntax/lib/parser/handlebars-node-visitors.ts:635
case 'StringLiteral':
case 'UndefinedLiteral':
case 'NullLiteral':
case 'NumberLiteral':
case 'BooleanLiteral': {
let value: string;
if (node.path.type === 'BooleanLiteral') {
value = node.path.original.toString();
} else if (node.path.type === 'StringLiteral') {
value = `"${node.path.original}"`;
} else if (node.path.type === 'NullLiteral') {
value = 'null';
} else if (node.path.type === 'NumberLiteral') {
value = node.path.value.toString();
} else {
value = 'undefined';
}
throw generateSyntaxError(
`${node.path.type} "${
node.path.type === 'StringLiteral' ? node.path.original : value
}" cannot be called as a sub-expression, replace (${value}) with ${value}`,
compiler.source.spanFor(node.path.loc)
);
}
}
const params = node.params.map((e) => compiler.acceptNode<HBS.Expression['type']>(e));
// if there is no hash, position it as a collapsed node immediately after the last param (or the
// path, if there are also no params)
const end = isPresentArray(params) ? getLast(params).loc : path.loc;
const hash = node.hash
? compiler.Hash(node.hash)
: b.hash({
pairs: [],View on GitHub (pinned to 26f97246a8)
Solutions
- Remove the parentheses and use the literal directly: ("foo") → "foo", (42) → 42, (true) → true.
- If you meant to call a helper, put an identifier path in the call position and pass literals as arguments: {{uppercase "foo"}} instead of {{("foo")}}.
- Fix the generator/macro that places literals in the path position of call nodes.
Example fix
// before
{{("hello")}}
// after
{{"hello"}}
// or, if a helper call was intended:
{{uppercase "hello"}} Defensive patterns
Strategy: validation
Validate before calling
// reject literal paths inside parenthesized sub-expressions
function assertNoLiteralCalls(template) {
const bad = template.match(/\(\s*("[^"]*"|'[^']*'|\d+|true|false|null|undefined)\s*\)/g);
if (bad) throw new Error(`Literals cannot be called as sub-expressions: ${bad.join(', ')}`);
}
assertNoLiteralCalls(template); Prevention
- Use literals directly ({{"text"}}, {{42}}) — never wrapped in parentheses
- Place literals as helper arguments, not in the call path position: {{helper "arg"}}
- Ensure code generators emit identifier paths for call nodes
- Add a precompile/lint step to CI
When it happens
Trigger: Invoking the parser with a mustache or sub-expression whose path is a literal — e.g. {{("foo")}}, {{(42)}}, {{(true)}} — or passing {{123}} where the visitor's acceptCallNodes (called with {path, params, hash}) detects isHBSLiteral(node.path). The message interpolates the literal's type and value.
Common situations: Programmatic template generation that wraps values in parentheses assuming expression syntax; confused template syntax where a literal was meant to be passed as a param but got placed in the path slot; copy/paste from languages where ("str") is a valid expression.
Related errors
- Changing context using "../" is not supported in Glimmer
- Mixing '.' and '/' in paths is not supported in Glimmer; use
- '.' is not a supported path in Glimmer; check for a path wit
- Attempted to parse a path expression, but it was not valid.
- Attempted to parse a path expression, but it was not valid.
AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01).
Data as JSON: /api/errors/38d7dbca9d36f071.
Report an issue: GitHub.