From fa0e4c8f357952d5191dcd4c2c14d45dfd093a24 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 24 May 2026 15:17:15 +0300 Subject: [PATCH] checker: report unwrapped option in infix arithmetic (fix #27082) (#27225) --- vlib/v/checker/infix.v | 14 ++++++++-- vlib/v/checker/tests/infix_err.out | 28 ------------------- .../v/checker/tests/option_arithmetic_err.out | 7 +++++ vlib/v/checker/tests/option_arithmetic_err.vv | 14 ++++++++++ .../checker/tests/struct_field_option_err.out | 4 +-- .../v/checker/tests/var_option_wrong_type.out | 6 ++++ 6 files changed, 40 insertions(+), 33 deletions(-) create mode 100644 vlib/v/checker/tests/option_arithmetic_err.out create mode 100644 vlib/v/checker/tests/option_arithmetic_err.vv diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index 861547128..32416eb3c 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -693,8 +693,16 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { right_name := c.table.type_to_str(unwrapped_right_type) c.error('mismatched types `${left_name}` and `${right_name}`', left_right_pos) - } else if promoted_type.has_option_or_result() { - s := c.table.type_to_str(promoted_type) + } else if promoted_type.has_flag(.result) || unwrapped_left_type.has_flag(.result) + || unwrapped_right_type.has_flag(.result) { + result_type := if unwrapped_left_type.has_flag(.result) { + unwrapped_left_type + } else if unwrapped_right_type.has_flag(.result) { + unwrapped_right_type + } else { + promoted_type + } + s := c.table.type_to_str(result_type) c.error('`${node.op}` cannot be used with `${s}`', node.pos) } else if promoted_type.is_float() { if node.op in [.mod, .xor, .amp, .pipe] { @@ -1129,7 +1137,7 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } // TODO: move this to symmetric_check? Right now it would break `return 0` for `fn()?int ` if node.left !in [ast.Ident, ast.IndexExpr, ast.SelectorExpr, ast.ComptimeSelector] - || node.op in [.eq, .ne] { + || node.op in [.eq, .ne, .plus, .minus, .mul, .power, .div, .mod, .xor, .amp, .pipe] { c.check_option_infix_expr(mut node, left_type, right_type, left_sym, right_sym) } diff --git a/vlib/v/checker/tests/infix_err.out b/vlib/v/checker/tests/infix_err.out index 29594753a..1d9305516 100644 --- a/vlib/v/checker/tests/infix_err.out +++ b/vlib/v/checker/tests/infix_err.out @@ -26,13 +26,6 @@ vlib/v/checker/tests/infix_err.vv:14:5: error: `?string` cannot be used as `stri | ~~~ 15 | _ = f() + f() 16 | -vlib/v/checker/tests/infix_err.vv:15:9: error: `+` cannot be used with `?string` - 13 | _ = '' + f() - 14 | _ = f() + '' - 15 | _ = f() + f() - | ^ - 16 | - 17 | _ = 4 + g() vlib/v/checker/tests/infix_err.vv:15:5: error: `?string` cannot be used as `string`, unwrap the option first 13 | _ = '' + f() 14 | _ = f() + '' @@ -40,13 +33,6 @@ vlib/v/checker/tests/infix_err.vv:15:5: error: `?string` cannot be used as `stri | ~~~ 16 | 17 | _ = 4 + g() -vlib/v/checker/tests/infix_err.vv:17:7: error: `+` cannot be used with `?int` - 15 | _ = f() + f() - 16 | - 17 | _ = 4 + g() - | ^ - 18 | _ = int(0) + g() - 19 | _ = g() + int(3) vlib/v/checker/tests/infix_err.vv:17:9: error: `?int` cannot be used as `int literal`, unwrap the option first 15 | _ = f() + f() 16 | @@ -61,13 +47,6 @@ vlib/v/checker/tests/infix_err.vv:18:14: error: `?int` cannot be used as `int`, | ~~~ 19 | _ = g() + int(3) 20 | _ = g() + 3 -vlib/v/checker/tests/infix_err.vv:19:9: error: `+` cannot be used with `?int` - 17 | _ = 4 + g() - 18 | _ = int(0) + g() - 19 | _ = g() + int(3) - | ^ - 20 | _ = g() + 3 - 21 | vlib/v/checker/tests/infix_err.vv:19:5: error: `?int` cannot be used as `int`, unwrap the option first 17 | _ = 4 + g() 18 | _ = int(0) + g() @@ -75,13 +54,6 @@ vlib/v/checker/tests/infix_err.vv:19:5: error: `?int` cannot be used as `int`, u | ~~~ 20 | _ = g() + 3 21 | -vlib/v/checker/tests/infix_err.vv:20:9: error: `+` cannot be used with `?int` - 18 | _ = int(0) + g() - 19 | _ = g() + int(3) - 20 | _ = g() + 3 - | ^ - 21 | - 22 | // binary operands vlib/v/checker/tests/infix_err.vv:20:5: error: `?int` cannot be used as `int literal`, unwrap the option first 18 | _ = int(0) + g() 19 | _ = g() + int(3) diff --git a/vlib/v/checker/tests/option_arithmetic_err.out b/vlib/v/checker/tests/option_arithmetic_err.out new file mode 100644 index 000000000..3493a8504 --- /dev/null +++ b/vlib/v/checker/tests/option_arithmetic_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/option_arithmetic_err.vv:6:31: error: `?f64` cannot be used as `f64`, unwrap the option first + 4 | + 5 | fn (f Foo) bar(m map[rune]f64) f64 { + 6 | return m[`x`] or { 0.0 } * f.value + | ~~~~~ + 7 | } + 8 | diff --git a/vlib/v/checker/tests/option_arithmetic_err.vv b/vlib/v/checker/tests/option_arithmetic_err.vv new file mode 100644 index 000000000..a982185ee --- /dev/null +++ b/vlib/v/checker/tests/option_arithmetic_err.vv @@ -0,0 +1,14 @@ +struct Foo { + value ?f64 +} + +fn (f Foo) bar(m map[rune]f64) f64 { + return m[`x`] or { 0.0 } * f.value +} + +fn main() { + f := Foo{ + value: 1.0 + } + println(f.bar(map[rune]f64{})) +} diff --git a/vlib/v/checker/tests/struct_field_option_err.out b/vlib/v/checker/tests/struct_field_option_err.out index 4b9ba67e3..1530a27b5 100644 --- a/vlib/v/checker/tests/struct_field_option_err.out +++ b/vlib/v/checker/tests/struct_field_option_err.out @@ -5,11 +5,11 @@ vlib/v/checker/tests/struct_field_option_err.vv:3:6: error: struct field does no | ^ 4 | bar ?int = 1 5 | baz int = 1 -vlib/v/checker/tests/struct_field_option_err.vv:12:12: error: `+` cannot be used with `?int` +vlib/v/checker/tests/struct_field_option_err.vv:12:8: error: `?int` cannot be used as `int literal`, unwrap the option first 10 | 11 | _ = f.bar 12 | _ = f.bar + 1 - | ^ + | ~~~ 13 | 14 | _ = f.bar! vlib/v/checker/tests/struct_field_option_err.vv:14:11: error: to propagate a Result, the call must also return a Result type diff --git a/vlib/v/checker/tests/var_option_wrong_type.out b/vlib/v/checker/tests/var_option_wrong_type.out index cf4792a50..d05cca2fe 100644 --- a/vlib/v/checker/tests/var_option_wrong_type.out +++ b/vlib/v/checker/tests/var_option_wrong_type.out @@ -4,6 +4,12 @@ vlib/v/checker/tests/var_option_wrong_type.vv:3:23: error: mismatched types `?f6 3 | var3 := var_none or { var_none + 'foo' } | ~~~~~~~~~~~~~~~~ 4 | println(var3) +vlib/v/checker/tests/var_option_wrong_type.vv:3:23: error: `?f64` cannot be used as `string`, unwrap the option first + 1 | var_none := ?f64(none) + 2 | + 3 | var3 := var_none or { var_none + 'foo' } + | ~~~~~~~~ + 4 | println(var3) vlib/v/checker/tests/var_option_wrong_type.vv:3:23: error: infix expr: cannot use `string` (right expression) as `f64` 1 | var_none := ?f64(none) 2 | -- 2.39.5