Skip to content

fix: do not skip a function body inside a template substitution - #53

Merged
marevol merged 1 commit into
es6/fix-template-substitutionfrom
es6/fix-template-reparse
Aug 28, 2026
Merged

fix: do not skip a function body inside a template substitution#53
marevol merged 1 commit into
es6/fix-template-substitutionfrom
es6/fix-template-reparse

Conversation

@marevol

@marevol marevol commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Stacked on #52.

A function body inside a substitution parsed fine but exploded the moment the function around it was called:

function outer() { return `${ (function () { return 7; })() }`; }
print(outer());
// ParserException: Missing close quote

Defining outer was enough to be fine; only invoking it failed, so this was the on-demand re-parse rather than the first parse.

Cause

When the parser skips a nested function body it restarts the lexer at the body's closing brace. A lexer started there has no template state, so the brace that ends the substitution is read as an ordinary one and the closing quote is read as opening a new template, which then runs to the end of the file.

Handing the state to the restarted lexer is not enough to fix it. The state has to be the state at that brace, and the lexer has read ahead by then — the same reason position and line are taken from the token rather than from the lexer. The lexer is typically past the whole template by then and reports no open substitution at all.

Fix

The body is not skipped there. skipFunctionBody already declines in two other cases and its caller is written for that: it parses the body and throws it away. A substitution is a small enough place that giving up the skip costs little, and the nested function is still compiled on demand in its own right — its source range is its own text, with no template around it.

Also covered: an object-literal method, a brace-bodied arrow, two bodies in one substitution, a nested template, and braces within the body.

Verification

./gradlew build testOptimistic testPessimistic

suite before after
test 665, 0 fail 665, 0 fail
testOptimistic 1715, 0 fail 1715, 0 fail
testPessimistic 1715, 0 fail 1715, 0 fail

A function body inside a substitution parsed fine but exploded the moment the
function around it was called:

    function outer() { return `${ (function () { return 7; })() }`; }
    print(outer());

    ParserException: Missing close quote

Defining outer was enough to be fine; only invoking it failed, so this was the
on-demand re-parse rather than the first parse. When the parser skips a nested
function body it restarts the lexer at the body's closing brace. A lexer started
there has no template state, so the brace that ends the substitution is read as
an ordinary one and the closing quote is read as opening a new template, which
then runs to the end of the file.

Handing the state to the restarted lexer is not enough to fix it. The state has
to be the state at that brace, and the lexer has read ahead by then - the same
reason position and line are taken from the token rather than from the lexer.
The lexer is typically past the whole template by then and reports no open
substitution at all.

So the body is not skipped there. skipFunctionBody already declines in two other
cases and its caller is written for that: it parses the body and throws it away.
A substitution is a small enough place that giving up the skip costs little, and
the nested function is still compiled on demand in its own right - its source
range is its own text, with no template around it.

Also covered: an object-literal method, a brace-bodied arrow, two bodies in one
substitution, a nested template, and braces within the body.

./gradlew build testOptimistic testPessimistic:

  suite            before         after
  test             665, 0 fail    665, 0 fail
  testOptimistic   1715, 0 fail   1715, 0 fail
  testPessimistic  1715, 0 fail   1715, 0 fail
@marevol
marevol force-pushed the es6/fix-template-reparse branch from b3414a7 to 100129d Compare August 28, 2026 05:11
@marevol marevol self-assigned this Aug 28, 2026
@marevol
marevol merged commit a1c87a7 into master Aug 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant