fix: do not skip a function body inside a template substitution - #53
Merged
Merged
Conversation
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
force-pushed
the
es6/fix-template-reparse
branch
from
August 28, 2026 05:11
b3414a7 to
100129d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #52.
A function body inside a substitution parsed fine but exploded the moment the function around it was called:
Defining
outerwas 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.
skipFunctionBodyalready 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