Skip to content

Commit e432094

Browse files
committed
v2: transformer/cleanc fixes to pass string_test.v
1 parent 06bf99b commit e432094

3 files changed

Lines changed: 108 additions & 4 deletions

File tree

‎vlib/v2/gen/cleanc/cleanc.v‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -717,8 +717,28 @@ fn escape_char_literal_content(raw string) string {
717717

718718
fn escape_c_string_literal_segment(raw string) string {
719719
mut sb := strings.new_builder(raw.len + 8)
720-
for ch in raw {
720+
mut i := 0
721+
for i < raw.len {
722+
ch := raw[i]
723+
// V scanner stores escape sequences as raw pairs: `\` + char.
724+
// Process them as units to avoid breaking `\\` + `"` sequences.
725+
if ch == `\\` && i + 1 < raw.len {
726+
next := raw[i + 1]
727+
if next == `"` {
728+
// V escape \" → emit C escape \" (same representation)
729+
sb.write_u8(`\\`)
730+
sb.write_u8(`"`)
731+
i += 2
732+
continue
733+
}
734+
// All other V escapes (\n, \t, \\, \0, etc.) → pass through as-is
735+
sb.write_u8(ch)
736+
sb.write_u8(next)
737+
i += 2
738+
continue
739+
}
721740
if ch == `"` {
741+
// Unescaped " (e.g. from single-quoted V strings) → escape for C
722742
sb.write_u8(`\\`)
723743
sb.write_u8(`"`)
724744
} else if ch == `\r` {
@@ -727,6 +747,7 @@ fn escape_c_string_literal_segment(raw string) string {
727747
} else {
728748
sb.write_u8(ch)
729749
}
750+
i++
730751
}
731752
return sb.str()
732753
}

‎vlib/v2/gen/cleanc/types.v‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,14 @@ fn (mut g Gen) get_expr_type(node ast.Expr) string {
10161016
}
10171017
}
10181018
}
1019+
// Tuple: always infer from element types, env position can be wrong.
1020+
if node is ast.Tuple {
1021+
mut elem_types := []string{cap: node.exprs.len}
1022+
for expr in node.exprs {
1023+
elem_types << g.get_expr_type(expr)
1024+
}
1025+
return g.register_tuple_alias(elem_types)
1026+
}
10191027
// Try environment lookup
10201028
if t := g.get_expr_type_from_env(node) {
10211029
// For array element-returning methods, prefer element type inference over the

‎vlib/v2/transformer/transformer.v‎

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,16 @@ fn (mut t Transformer) expand_direct_or_expr_assign(stmt ast.AssignStmt, or_expr
13881388
}
13891389
}
13901390

1391+
// Check for string range with or block: s[0..20] or { 'fallback' }
1392+
// The checker types this as `string`, but it needs `string__substr_with_check`
1393+
// which returns `!string` (Result).
1394+
mut is_string_range_or := false
1395+
if call_expr is ast.IndexExpr {
1396+
if call_expr.expr is ast.RangeExpr && t.is_string_expr(call_expr.lhs) {
1397+
is_string_range_or = true
1398+
}
1399+
}
1400+
13911401
// Check if expression returns Result or Option using expression-based lookup
13921402
// This works for both function calls and method calls
13931403
mut is_result := t.expr_returns_result(call_expr)
@@ -1401,7 +1411,11 @@ fn (mut t Transformer) expand_direct_or_expr_assign(stmt ast.AssignStmt, or_expr
14011411
}
14021412

14031413
if !is_result && !is_option {
1404-
return none
1414+
if is_string_range_or {
1415+
is_result = true
1416+
} else {
1417+
return none
1418+
}
14051419
}
14061420

14071421
// Native backends (arm64/x64) don't use Option/Result structs.
@@ -1486,6 +1500,11 @@ fn (mut t Transformer) expand_direct_or_expr_assign(stmt ast.AssignStmt, or_expr
14861500
if base_type == '' && fn_name != '' {
14871501
base_type = t.get_fn_return_base_type(fn_name)
14881502
}
1503+
// String range or-block: the checker typed this as plain string,
1504+
// so base_type lookup fails. Force it to 'string'.
1505+
if is_string_range_or && base_type == '' {
1506+
base_type = 'string'
1507+
}
14891508
is_void_result := base_type == '' || base_type == 'void'
14901509
_ = is_option // suppress unused warning
14911510
// Generate temp variable name
@@ -1512,14 +1531,29 @@ fn (mut t Transformer) expand_direct_or_expr_assign(stmt ast.AssignStmt, or_expr
15121531
}
15131532
}
15141533
}
1534+
// String range or-block: register as _result_string since the checker
1535+
// didn't record a Result type for this expression.
1536+
if is_string_range_or {
1537+
t.register_temp_var(temp_name, types.ResultType{
1538+
base_type: types.string_
1539+
})
1540+
}
15151541

15161542
// Build the expanded statements
15171543
mut stmts := []ast.Stmt{}
15181544
// 1. _t1 := call_expr
1545+
transformed_call := t.transform_expr(call_expr)
1546+
// For string range or-blocks, rename string__substr to string__substr_with_check
1547+
// which returns !string (Result) instead of string.
1548+
rhs_expr := if is_string_range_or {
1549+
t.rename_substr_to_checked(transformed_call)
1550+
} else {
1551+
transformed_call
1552+
}
15191553
stmts << ast.AssignStmt{
15201554
op: .decl_assign
15211555
lhs: [ast.Expr(temp_ident)]
1522-
rhs: [t.transform_expr(call_expr)]
1556+
rhs: [rhs_expr]
15231557
pos: stmt.pos
15241558
}
15251559
// 2. if _t1.is_error { ... } (for Result) or if _t1.state != 0 { ... } (for Option)
@@ -2446,6 +2480,14 @@ fn (mut t Transformer) expand_single_or_expr(or_expr ast.OrExpr, mut prefix_stmt
24462480
return result
24472481
}
24482482

2483+
// Check for string range with or block: s[0..20] or { 'fallback' }
2484+
mut is_string_range_or := false
2485+
if call_expr is ast.IndexExpr {
2486+
if call_expr.expr is ast.RangeExpr && t.is_string_expr(call_expr.lhs) {
2487+
is_string_range_or = true
2488+
}
2489+
}
2490+
24492491
// Check if expression returns Result or Option using expression-based lookup
24502492
// This works for both function calls and method calls
24512493
mut is_result := t.expr_returns_result(call_expr)
@@ -2617,12 +2659,28 @@ fn (mut t Transformer) expand_single_or_expr(or_expr ast.OrExpr, mut prefix_stmt
26172659
if base_type == '' && (is_result || is_option) {
26182660
base_type = 'int'
26192661
}
2662+
// String range or-block: register as _result_string since the checker
2663+
// didn't record a Result type for this expression.
2664+
if is_string_range_or {
2665+
t.register_temp_var(temp_name, types.ResultType{
2666+
base_type: types.string_
2667+
})
2668+
base_type = 'string'
2669+
}
26202670
is_void_result := base_type == '' || base_type == 'void'
26212671
// 1. _t1 := call_expr
2672+
transformed_call2 := t.transform_expr(call_expr)
2673+
// For string range or-blocks, rename string__substr to string__substr_with_check
2674+
// which returns !string (Result) instead of string.
2675+
rhs_expr2 := if is_string_range_or {
2676+
t.rename_substr_to_checked(transformed_call2)
2677+
} else {
2678+
transformed_call2
2679+
}
26222680
prefix_stmts << ast.AssignStmt{
26232681
op: .decl_assign
26242682
lhs: [ast.Expr(temp_ident)]
2625-
rhs: [t.transform_expr(call_expr)]
2683+
rhs: [rhs_expr2]
26262684
}
26272685
// 2. if _t1.is_error { ... } (for Result) or if _t1.state != 0 { ... } (for Option)
26282686
error_cond := if is_result {
@@ -4840,6 +4898,23 @@ fn (mut t Transformer) transform_array_init_with_exprs(arr ast.ArrayInitExpr, ex
48404898
}
48414899
}
48424900

4901+
// rename_substr_to_checked renames a string__substr call to string__substr_with_check.
4902+
// Used for string range with or-blocks: s[0..20] or { 'fallback' } needs the checked
4903+
// variant which returns !string (Result) instead of plain string.
4904+
fn (t &Transformer) rename_substr_to_checked(expr ast.Expr) ast.Expr {
4905+
if expr is ast.CallExpr {
4906+
if expr.lhs is ast.Ident && expr.lhs.name == 'string__substr' {
4907+
return ast.CallExpr{
4908+
lhs: ast.Ident{
4909+
name: 'string__substr_with_check'
4910+
}
4911+
args: expr.args
4912+
}
4913+
}
4914+
}
4915+
return expr
4916+
}
4917+
48434918
// is_string_expr returns true if the expression is known to be a string
48444919
fn (t &Transformer) is_string_expr(expr ast.Expr) bool {
48454920
if expr is ast.StringLiteral {

0 commit comments

Comments
 (0)