Skip to content

parser: fix @@ escape before complex at-expressions in templates - #27701

Merged
medvednikov merged 1 commit into
vlang:masterfrom
EvgenyGuguev:fix/tmpl-double-at-escape
Jul 8, 2026
Merged

medvednikov merged 1 commit into
vlang:masterfrom
EvgenyGuguev:fix/tmpl-double-at-escape

Conversation

@EvgenyGuguev

Copy link
Copy Markdown
Contributor

What & why

Fixes #27700.

In compile-time templates ($tmpl / $veb.html()), the documented @@ escape
for a literal @ rendered incorrectly whenever it preceded a complex
at-expression (ident(...), ident[...], ident.field):

  • @@get('/x') rendered as @{get('/x')} instead of the literal @get('/x').

This is a real-world pain point for Datastar hypermedia apps written with
veb, where HTML attributes carry action verbs
(data-on:click="@get('/increment')") that must be escaped as @@get(...)` in
a V template.

Root cause

In rewrite_complex_template_at_expressions (vlib/v/parser/tmpl.v), the
@@ branch wrote a single @ and advanced the index by only 1, leaving the
second @ to be re-parsed as the start of a complex at-expression (wrapped
into @{...}). The bare-ident case (@@world) already worked because
find_tmpl_complex_at_expr_end returns -1 for a bare identifier, so the
existing escape test never exercised the complex-suffix case.

The fix

Write both @ characters and advance the index by 2, deferring the
@@ → @ collapse to insert_template_code (which already handles it).

Tests

  • Added a focused regression test:
    test_escape_double_at_before_complex_at_expression covering
    @@get('/x') and @@post('/save').
  • The existing @@world / @{expr} / @url behaviors are unchanged.
  • v test vlib/v/tests/ → 2175 passed, 6 skipped, 0 failures (full template
    suite green, including the new regression). GitHub CI runs the complete
    suite on this PR.

In compile-time templates ($tmpl / $veb.html()), the documented `@@` escape
for a literal `@` was broken whenever it directly preceded a complex
at-expression: `@@get('/x')` rendered as `@{get('/x')}` instead of the
literal `@get('/x')`. This affected any `@@` followed by `ident(...)`,
`ident[...]` or `ident.field`.

Root cause: in rewrite_complex_template_at_expressions (vlib/v/parser/tmpl.v),
the `@@` branch wrote a single `@` and advanced the index by only 1. The
second `@` was then re-parsed as the start of a complex at-expression and
wrapped in `@{...}`. The bare-ident case (`@@world`) already worked because
find_tmpl_complex_at_expr_end returns -1 for a bare identifier, which is why
the existing escape test did not catch this.

Fix: write both `@` characters and advance the index by 2, deferring the
final `@@` -> `@` collapse to insert_template_code (which already handles
it). Added a regression test covering `@@get('/x')` and `@@post('/save')`.

Related to vlang#22299 (bare-ident `@@` case, fixed).
@medvednikov

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: cbb060ff35

ℹ️ 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".

@medvednikov
medvednikov merged commit 7790e9d into vlang:master Jul 8, 2026
6 of 93 checks passed
@medvednikov

Copy link
Copy Markdown
Member

Thanks!

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.

Template: @@ escape broken before complex at-expression (@@get('/x') renders @{get('/x')})

2 participants