From f7b6a420d8a366db6129bc41129e751571e72034 Mon Sep 17 00:00:00 2001 From: Krchi <997054144@qq.com> Date: Sat, 28 Jun 2025 22:09:07 +0800 Subject: [PATCH] scanner: fix multi-level string interpolation in if/match branch (#24805) --- vlib/v/checker/str.v | 3 + vlib/v/scanner/scanner.v | 33 ++++++++- .../string_interpolation_multi_level_test.v | 67 +++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_multi_level_test.v diff --git a/vlib/v/checker/str.v b/vlib/v/checker/str.v index d926f0c9e..45a174bd0 100644 --- a/vlib/v/checker/str.v +++ b/vlib/v/checker/str.v @@ -45,7 +45,10 @@ fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Type { inside_interface_deref_save := c.inside_interface_deref c.inside_interface_deref = true for i, mut expr in node.exprs { + expected_type := c.expected_type + c.expected_type = ast.string_type mut ftyp := c.expr(mut expr) + c.expected_type = expected_type ftyp = c.type_resolver.get_type_or_default(expr, c.check_expr_option_or_result_call(expr, ftyp)) if ftyp == ast.void_type || ftyp == 0 { diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index ab243c944..f2a4506f6 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -35,6 +35,8 @@ pub mut: is_inter_end bool is_enclosed_inter bool is_nested_enclosed_inter bool + string_count int + str_dollar_needs_rcbr []bool = [] line_comment string last_lt int = -1 // position of latest < is_print_line_on_error bool @@ -797,6 +799,12 @@ pub fn (mut s Scanner) text_scan() token.Token { return s.new_token(.question, '?', 1) } single_quote, double_quote { + if s.string_count == 1 && s.str_dollar_needs_rcbr.len == 0 { + s.string_count = 0 + return s.new_token(.string, '', 1) + } else { + s.string_count++ + } start_line := s.line_nr ident_string := s.ident_string() return s.new_multiline_token(.string, ident_string, ident_string.len + 2, @@ -823,10 +831,14 @@ pub fn (mut s Scanner) text_scan() token.Token { // Skip { in `${` in strings if s.is_inside_string || s.is_enclosed_inter { if s.text[s.pos - 1] == `$` { + s.str_dollar_needs_rcbr << true continue } else { + s.str_dollar_needs_rcbr << false s.inter_cbr_count++ } + } else { + s.str_dollar_needs_rcbr << false } return s.new_token(.lcbr, '', 1) } @@ -840,7 +852,12 @@ pub fn (mut s Scanner) text_scan() token.Token { `}` { // s = `hello $name !` // s = `hello ${name} !` - if (s.is_enclosed_inter || s.is_nested_enclosed_inter) && s.inter_cbr_count == 0 { + if ((s.is_enclosed_inter || s.is_nested_enclosed_inter) && s.inter_cbr_count == 0) + || (s.all_tokens.last().kind != .string && s.str_dollar_needs_rcbr.len > 0 + && s.str_dollar_needs_rcbr.last()) { + if s.str_dollar_needs_rcbr.len > 0 { + s.str_dollar_needs_rcbr.delete_last() + } if s.pos < s.text.len - 1 { s.pos++ } else { @@ -854,6 +871,7 @@ pub fn (mut s Scanner) text_scan() token.Token { } else { s.is_enclosed_inter = false } + s.string_count-- return s.new_token(.string, '', 1) } if s.is_nested_enclosed_inter { @@ -865,6 +883,14 @@ pub fn (mut s Scanner) text_scan() token.Token { ident_string := s.ident_string() return s.new_token(.string, ident_string, ident_string.len + 2) // + two quotes } else { + if s.str_dollar_needs_rcbr.len > 0 { + if s.str_dollar_needs_rcbr.last() { + s.str_dollar_needs_rcbr.delete_last() + s.pos++ + return s.new_token(.string, '', 1) + } + s.str_dollar_needs_rcbr.delete_last() + } if s.inter_cbr_count > 0 { s.inter_cbr_count-- } @@ -1255,9 +1281,14 @@ pub fn (mut s Scanner) ident_string() string { // end of string if c == s.quote && (is_raw || backslash_count & 1 == 0) { // handle '123\\' backslash at the end + s.string_count-- break } if c == s.inter_quote && (s.is_inter_start || s.is_enclosed_inter) { + s.string_count-- + break + } + if c == s.quote && s.string_count == 0 { break } if c == b_cr { diff --git a/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_multi_level_test.v b/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_multi_level_test.v new file mode 100644 index 000000000..43d0c6112 --- /dev/null +++ b/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_multi_level_test.v @@ -0,0 +1,67 @@ +fn foo() ?string { + return none +} + +fn test_string_interpolation_multi_level() { + x := 0 + + assert '${if x == 0 { + '${x}' + } else { + '${'${x + 1}'}' + }}' == '0' + + assert '${foo() or { '${if x == 0 { + '${x}' + } else { + '${x + 1}' + }}' }}' == '0' + + println('${match true { + true { '${x}' } + else { '${x + 1}' } + }}' == '0') + + // codegen error + // assert '${match true { true { '${x}' } else { '${x + 1}' } }}' == '0' + + assert '${if x == 0 { + '${match x { + 1 { x == 1 } + else { x != 0 } + }}' + } else { + '' + }}' == 'false' + + assert '${if x == 0 { + '${if x == 1 { + '${if x == 2 { + '${x + 2}' + } else { + '${1}' + }}' + } else { + '${if x == 0 { + '${match x { + 2 { + 'true' + } + else { + '${x + 3}' + } + }}' + } else { + '${false}' + }}' + }}' + } else { + '' + }}' == '3' + + assert '${if m := foo() { + '${m}' + } else { + '${x + 1}' + }}' == '1' +} -- 2.39.5