@@ -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_expr 2 ]
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
48444919fn (t &Transformer) is_string_expr (expr ast.Expr) bool {
48454920 if expr is ast.StringLiteral {
0 commit comments