Repository navigation
Give template segments the location of their text, and keep template locations through the v0 AST - #9166
Conversation
Changelog🐛 Bug fix
💅 Polish
Semantic Diff2 commits · Full page Give template segments the location of their textfa0d7d4 · cknitt + Claude · Nov 01, 2026, 11:14 UTC
AST modified (template segment locations) let y = `a${b}c`
-expression 8..16 segments "a" 8..10, "c" 13..15
-covering `a${b}c` `a }c
+expression 8..16 segments "a" 9..10, "c" 14..15
+covering `a${b}c` a cDiagnostic modified (invalid escape in a template) let invalid = `\unicode`
Syntax error!
-A.res:1:15-23
+A.res:1:16-23Review
✨ Keep the location of templates without interpolations through ast0506cb25 · cknitt + Claude · Nov 01, 2026, 08:57 UTC · new
AST modified (template after the v0 round trip) let z = `template`
parsed: expression [1,0+8]..[1,0+18]
-after the round trip: expression [1,0+8]..[1,0+17]
+after the round trip: expression [1,0+8]..[1,0+18] |
|
@codex review |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #9166 +/- ##
=======================================
Coverage 81.74% 81.75%
=======================================
Files 460 460
Lines 64944 64948 +4
=======================================
+ Hits 53091 53097 +6
+ Misses 11853 11851 -2
🚀 New features to boost your workflow:
|
Why:
A template segment's location started at the delimiter before it (a
backquote or `}`) and ended before the one after it (`${` or a
backquote), so it was neither the text nor the text with its
delimiters. The old shape is what made the v0 bridge lose a character
of a template's location (#9164). Segments now cover their text only.
AST modified (template segment locations):
Example:
let y = `a${b}c`
Before (text):
expression 8..16 segments "a" 8..10, "c" 13..15
covering `a${b}c` `a }c
After (text):
expression 8..16 segments "a" 9..10, "c" 14..15
covering `a${b}c` a c
Diagnostic modified (invalid escape in a template):
Example:
let invalid = `\unicode`
Before (diagnostic):
Syntax error!
A.res:1:15-23
After (diagnostic):
Syntax error!
A.res:1:16-23
Review: What else reads segment locations? Invalid escape errors, now
pointing at the text; `Ast_raw.payload_loc`, which now widens a %raw
template by one character on each side; and the v0 bridge, which keeps
each segment's location as it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gJFAva9685fjvkajub
Signed-off-by: Christoph Knittel <ck@cca.io>
Why:
On the v0 wire, a template without interpolations was one string
constant at its segment's location, which ends before the closing
backquote, so the template lost one character of its location after a
PPX round trip. The constant now takes the template's location, and the
segment's location is derived from it on the way back (#9164).
AST modified (template after the v0 round trip):
Example:
let z = `template`
Before (text):
parsed: expression [1,0+8]..[1,0+18]
after the round trip: expression [1,0+8]..[1,0+17]
After (text):
parsed: expression [1,0+8]..[1,0+18]
after the round trip: expression [1,0+8]..[1,0+18]
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gJFAva9685fjvkajub
Signed-off-by: Christoph Knittel <ck@cca.io>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 67bb8b553c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
67bb8b5 to
fa0d7d4
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-9166 |
Fixes #9164.
Two commits: the first gives template segments the location of their text; the second keeps a template's location when the parsetree goes through the v0 AST for PPXs. They depend on each other, so they ship together: with only the first one, the v0 round trip would lose both backquotes of a template instead of one.
1. Template segments get the location of their text
Why
A template literal is parsed into an expression with one or more segments: the pieces of text between the interpolations. Each segment has its own location. Until now, that location had a lopsided shape: it started at the delimiter in front of the text (the opening backquote, or the
}that closes an interpolation) but stopped before the delimiter after it (${or the closing backquote).Columns are 0-based below.
^marks the characters a location covers.So a segment's location was neither its text nor its text with its delimiters, and code that needed the full literal had to know the shape.
Ast_raw.payload_loc, for example, added one character at the end, but not at the start, to get a%rawtemplate's location.What changes
A segment's location is now exactly its text, which also matches how JS tooling such as Acorn and Babel place template elements:
More examples:
`template`at 8`template8..17template9..17`` at 8`8..9`${b}${c}`at 8`8..9,}12..13,}16..17Parser (
parse_template_expr): the scanner already starts a template token after its delimiter. The parser read the segment's start position before scanning the token, at the delimiter; it now reads it after. The template's own location used to be computed from its first and last segments; it now runs from the opening backquote to the end of the literal, so it doesn't depend on the segments. Every caller except error recovery already overrode it.Ast_raw.payload_loc: a%rawtemplate's segment is the text between the backquotes, so the payload location now widens by one character on each side.Invalid escapes in templates are reported on the text, no longer starting at the backquote:
parsetree.ml: the doc comment ofPexp_templatestates the rule.Tagged templates (
sql`select ${x}`) use the same parser code, so theirraw_sourcesfollow the same rule.2. Templates keep their location through the v0 AST (#9164)
Why
PPXs receive the parsetree in the frozen v0 format:
Ast_mapper_to0converts it before they run, andAst_mapper_from0converts their output back. The v0 AST has no template node. A template without interpolations is a single string constant markedres.template, and a constant has one location only, while the current tree has two: the expression's and its segment's.Ast_mapper_to0stored the segment's location on that constant, andAst_mapper_from0then used it for both. So the template came back without its closing backquote (#9164). With the text-only segments from part 1 it would also lose the opening one:Templates with interpolations were never affected. In v0 they are a chain of
^applications, whose outer node carries the expression's location, and each segment constant keeps its own:What changes
Ast_mapper_to0: the single constant of a template without interpolations takes the template's location, backquotes included.Ast_mapper_from0: the segment's location is derived from it as the text between the backquotes, one character in from each end. Ghost locations, and ones too short to hold two backquotes, are kept as they are, so templates that PPXs build withLocation.nonestay as they were.Interpolated templates, tagged templates and
%rawtemplates have their own v0 encodings and don't change.Tests
res_test.mlparses plain, empty, interpolated, escaped, multi-line, nested and tagged templates. For each segment it cuts the segment's location out of the source and checks the result is exactly the segment's text. It also checks that the template's location covers the whole literal, backquote to backquote. Before part 1 it fails on the first case:segment "template" has location text "template"`.template_locations_roundtrip_through_ast0inounit_ast_mapper0_tests.mlround-trips a whole file through v0 and checks the tree is identical, locations included:Without part 2 it fails on the first binding.
The expectation of
syntaxErrors_invalid_ordinary_template_escapemoves by one column, as shown above.Once this and #9161 are in,
todo_roundtrips_through_ast0can include a template message.🤖 Generated with Claude Code
https://claude.ai/code/session_01Kbf3gJFAva9685fjvkajub