scanner: fix multi-level string interpolation in if/match branch (#24805)

This commit is contained in:
Krchi 2025-06-28 22:09:07 +08:00 committed by GitHub
parent bd465b5254
commit f7b6a420d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 102 additions and 1 deletions

View file

@ -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 {

View file

@ -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 {

View file

@ -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'
}