From 4ad633a3338f53009f71025a1e2cd51dea424556 Mon Sep 17 00:00:00 2001 From: GGRei Date: Sat, 2 May 2026 14:58:39 +0200 Subject: [PATCH] checker: validate or-block defaults before outer casts (#27059) --- vlib/v/checker/checker.v | 28 +++- .../or_block_cast_wrong_default_type_err.out | 6 + .../or_block_cast_wrong_default_type_err.vv | 7 + ...k_selector_cast_wrong_default_type_err.out | 6 + ...ck_selector_cast_wrong_default_type_err.vv | 8 ++ vlib/v/tests/or_block_result_cast_test.v | 132 ++++++++++++++++++ 6 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 vlib/v/checker/tests/or_block_cast_wrong_default_type_err.out create mode 100644 vlib/v/checker/tests/or_block_cast_wrong_default_type_err.vv create mode 100644 vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.out create mode 100644 vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.vv create mode 100644 vlib/v/tests/or_block_result_cast_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 3ca989c5e..ed74237e1 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2553,7 +2553,20 @@ fn (mut c Checker) check_expr_option_or_result_call(expr ast.Expr, ret_type ast. } else { last_cur_or_expr := c.cur_or_expr c.cur_or_expr = &expr.or_block - c.check_or_expr(expr.or_block, ret_type, expr_ret_type, expr) + or_block_value_type := expr_ret_type.clear_option_and_result() + or_block_unaliased_value_type := + c.table.fully_unaliased_type(or_block_value_type) + mut or_block_ret_type := or_block_value_type + if ret_type == ast.void_type { + or_block_ret_type = ret_type + } else if ret_type.has_option_or_result() { + ret_value_type := + c.table.fully_unaliased_type(ret_type).clear_option_and_result() + if ret_value_type == or_block_unaliased_value_type { + or_block_ret_type = ret_type + } + } + c.check_or_expr(expr.or_block, or_block_ret_type, expr_ret_type, expr) c.cur_or_expr = last_cur_or_expr } return if expr.or_block.kind == .absent { @@ -2586,7 +2599,18 @@ fn (mut c Checker) check_expr_option_or_result_call(expr ast.Expr, ret_type ast. if expr.or_block.kind != .absent { last_cur_or_expr := c.cur_or_expr c.cur_or_expr = &expr.or_block - c.check_or_expr(expr.or_block, ret_type, expr.typ, expr) + or_block_value_type := expr.typ.clear_option_and_result() + or_block_unaliased_value_type := + c.table.fully_unaliased_type(or_block_value_type) + mut or_block_ret_type := or_block_value_type + if ret_type.has_option_or_result() { + ret_value_type := + c.table.fully_unaliased_type(ret_type).clear_option_and_result() + if ret_value_type == or_block_unaliased_value_type { + or_block_ret_type = ret_type + } + } + c.check_or_expr(expr.or_block, or_block_ret_type, expr.typ, expr) c.cur_or_expr = last_cur_or_expr } } diff --git a/vlib/v/checker/tests/or_block_cast_wrong_default_type_err.out b/vlib/v/checker/tests/or_block_cast_wrong_default_type_err.out new file mode 100644 index 000000000..311c9300c --- /dev/null +++ b/vlib/v/checker/tests/or_block_cast_wrong_default_type_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/or_block_cast_wrong_default_type_err.vv:6:27: error: wrong return type `string` in the `or {}` block, expected `f64` + 4 | + 5 | fn main() { + 6 | _ := int(read_f64() or { 'bad' }) + | ~~~~~ + 7 | } diff --git a/vlib/v/checker/tests/or_block_cast_wrong_default_type_err.vv b/vlib/v/checker/tests/or_block_cast_wrong_default_type_err.vv new file mode 100644 index 000000000..c6e60719f --- /dev/null +++ b/vlib/v/checker/tests/or_block_cast_wrong_default_type_err.vv @@ -0,0 +1,7 @@ +fn read_f64() !f64 { + return error('oops') +} + +fn main() { + _ := int(read_f64() or { 'bad' }) +} diff --git a/vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.out b/vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.out new file mode 100644 index 000000000..5b787752d --- /dev/null +++ b/vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.vv:7:20: error: wrong return type `string` in the `or {}` block, expected `f64` + 5 | fn main() { + 6 | h := SelectorCastHolder{} + 7 | _ := int(h.x or { 'bad' }) + | ~~~~~ + 8 | } diff --git a/vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.vv b/vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.vv new file mode 100644 index 000000000..0b3c3dbfb --- /dev/null +++ b/vlib/v/checker/tests/or_block_selector_cast_wrong_default_type_err.vv @@ -0,0 +1,8 @@ +struct SelectorCastHolder { + x ?f64 +} + +fn main() { + h := SelectorCastHolder{} + _ := int(h.x or { 'bad' }) +} diff --git a/vlib/v/tests/or_block_result_cast_test.v b/vlib/v/tests/or_block_result_cast_test.v new file mode 100644 index 000000000..d4434203b --- /dev/null +++ b/vlib/v/tests/or_block_result_cast_test.v @@ -0,0 +1,132 @@ +type MyF64 = f64 + +struct SelectorCastHolder { + x ?f64 + ax ?MyF64 +} + +struct GenericSelectorCastHolder[T] { + x ?T +} + +fn result_f64() !f64 { + return error('no f64') +} + +fn result_f32() !f32 { + return error('no f32') +} + +fn option_f64() ?f64 { + return none +} + +fn result_my_f64() !MyF64 { + return error('no MyF64') +} + +fn option_my_f64() ?MyF64 { + return none +} + +fn result_generic[T]() !T { + return error('no generic') +} + +fn cast_or_block_return() int { + return int(result_f64() or { return 7 }) +} + +fn keep_none() ?f64 { + return option_f64() or { none } +} + +fn keep_error() !f64 { + return result_f64() or { error('fallback') } +} + +fn selector_cast_return(h SelectorCastHolder) int { + return int(h.x or { return 7 }) +} + +fn selector_keep_none(h SelectorCastHolder) ?f64 { + return h.x or { none } +} + +fn selector_keep_error(h SelectorCastHolder) !f64 { + return h.x or { error('fallback') } +} + +fn test_or_block_result_cast() { + a := int(result_f64() or { 256.0 }) + b := int(result_f64() or { f64(256.0) }) + c := int(result_f32() or { f32(42.0) }) + d := int(option_f64() or { 128.0 }) + e := int(option_f64() or { f64(128.0) }) + + assert a == 256 + assert b == 256 + assert c == 42 + assert d == 128 + assert e == 128 +} + +fn test_or_block_result_cast_alias_payload() { + a := int(result_my_f64() or { MyF64(64.0) }) + b := int(option_my_f64() or { MyF64(32.0) }) + + assert a == 64 + assert b == 32 +} + +fn test_or_block_result_cast_generic_call() { + a := int(result_generic[f64]() or { f64(16.0) }) + + assert a == 16 +} + +fn test_or_block_cast_preserves_parent_exits() { + assert cast_or_block_return() == 7 + assert keep_none() == none + + if _ := keep_error() { + assert false + } else { + assert err.msg() == 'fallback' + } +} + +fn test_or_block_selector_cast() { + h := SelectorCastHolder{} + gh := GenericSelectorCastHolder[f64]{} + + a := int(h.x or { 3.0 }) + b := int(h.x or { f64(4.0) }) + c := int(h.ax or { MyF64(5.0) }) + d := int(gh.x or { f64(6.0) }) + + assert a == 3 + assert b == 4 + assert c == 5 + assert d == 6 +} + +fn test_or_block_selector_cast_preserves_parent_exits() { + h := SelectorCastHolder{} + assert selector_cast_return(h) == 7 + assert selector_keep_none(h) == none + + if _ := selector_keep_error(h) { + assert false + } else { + assert err.msg() == 'fallback' + } +} + +fn test_or_block_result_void_context() { + mut seen := false + result_f64() or { seen = true } + assert seen + + result_f64() or {} +} -- 2.39.5