From 4e292a6edfa68c4c79e142eb043d0f5c0680d767 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 26 Feb 2026 21:03:28 +0300 Subject: [PATCH] veb: fix @import gives `invalid character` but should `undefined ident` (fixes #23623) --- .../tests/template_keyword_ident_err.out | 20 +++++++++++++++ .../tests/template_keyword_ident_err.vv | 18 +++++++++++++ .../templates/template_keyword_ident_err.html | 1 + vlib/v/parser/tmpl.v | 25 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 vlib/v/checker/tests/template_keyword_ident_err.out create mode 100644 vlib/v/checker/tests/template_keyword_ident_err.vv create mode 100644 vlib/v/checker/tests/templates/template_keyword_ident_err.html diff --git a/vlib/v/checker/tests/template_keyword_ident_err.out b/vlib/v/checker/tests/template_keyword_ident_err.out new file mode 100644 index 000000000..6626384ab --- /dev/null +++ b/vlib/v/checker/tests/template_keyword_ident_err.out @@ -0,0 +1,20 @@ +./templates/template_keyword_ident_err.html:1:31: error: undefined ident: `import` (veb action: index) + 1 | @import 'example' + | ^ +called from vlib/v/checker/tests/template_keyword_ident_err.vv:12:9 + 10 | + 11 | pub fn (mut app Application) index() veb.Result { + 12 | return $veb.html('./templates/template_keyword_ident_err.html') + | ^ + 13 | } + 14 | +./templates/template_keyword_ident_err.html:1:31: error: expression does not return a value (veb action: index) + 1 | @import 'example' + | ^ +called from vlib/v/checker/tests/template_keyword_ident_err.vv:12:9 + 10 | + 11 | pub fn (mut app Application) index() veb.Result { + 12 | return $veb.html('./templates/template_keyword_ident_err.html') + | ^ + 13 | } + 14 | diff --git a/vlib/v/checker/tests/template_keyword_ident_err.vv b/vlib/v/checker/tests/template_keyword_ident_err.vv new file mode 100644 index 000000000..015646640 --- /dev/null +++ b/vlib/v/checker/tests/template_keyword_ident_err.vv @@ -0,0 +1,18 @@ +module main + +import veb + +pub struct Context { + veb.Context +} + +pub struct Application {} + +pub fn (mut app Application) index() veb.Result { + return $veb.html('./templates/template_keyword_ident_err.html') +} + +fn main() { + mut app := &Application{} + veb.run[Application, Context](mut app, 8080) +} diff --git a/vlib/v/checker/tests/templates/template_keyword_ident_err.html b/vlib/v/checker/tests/templates/template_keyword_ident_err.html new file mode 100644 index 000000000..56304a012 --- /dev/null +++ b/vlib/v/checker/tests/templates/template_keyword_ident_err.html @@ -0,0 +1 @@ +@import 'example' diff --git a/vlib/v/parser/tmpl.v b/vlib/v/parser/tmpl.v index f67ea8cd0..e69503bfb 100644 --- a/vlib/v/parser/tmpl.v +++ b/vlib/v/parser/tmpl.v @@ -284,6 +284,7 @@ fn insert_template_code(fn_name string, tmpl_str_start string, line string) stri i++ } mut rline := sb.str() + rline = normalize_keyword_template_interpolations(rline) comptime_call_str := rline.find_between('\${', '}') if comptime_call_str.contains("\\'") { rline = rline.replace(comptime_call_str, comptime_call_str.replace("\\'", r"'")) @@ -294,6 +295,30 @@ fn insert_template_code(fn_name string, tmpl_str_start string, line string) stri return rline } +fn normalize_keyword_template_interpolations(line string) string { + mut sb := strings.new_builder(line.len) + mut i := 0 + for i < line.len { + ch := line[i] + if ch == `$` && i + 1 < line.len && (line[i + 1].is_letter() || line[i + 1] == `_`) { + mut j := i + 1 + for j < line.len && (line[j].is_letter() || line[j].is_digit() || line[j] == `_`) { + j++ + } + name := line[i + 1..j] + if token.is_key(name) { + // Force keyword names into the escaped identifier form to avoid parser/scanner issues. + sb.write_string('\${@${name}}') + i = j + continue + } + } + sb.write_u8(ch) + i++ + } + return sb.str() +} + // struct to track dependecies and cache templates for reuse without io struct DependencyCache { pub mut: -- 2.39.5