From a22727df147da930f7f0053aa1b61433f57e1471 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 24 May 2026 23:17:39 +0300 Subject: [PATCH] checker: explain smartcast narrowing in `or {}`/`?` error on unwrapped option (fix #27130) (#27238) --- vlib/v/checker/checker.v | 24 +++++++++++++++---- .../or_block_smartcast_unwrapped_err.out | 19 +++++++++++++++ .../tests/or_block_smartcast_unwrapped_err.vv | 14 +++++++++++ 3 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 vlib/v/checker/tests/or_block_smartcast_unwrapped_err.out create mode 100644 vlib/v/checker/tests/or_block_smartcast_unwrapped_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index a31f7c0a7..c66ec1040 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -6410,10 +6410,12 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { // Got a var with type T, return current generic type if node.or_expr.kind != .absent { if !info.typ.has_flag(.option) { + hint := c.smartcast_unwrap_hint(node.scope.find_var(node.name) or { unsafe { nil } }) if node.or_expr.kind == .propagate_option { - c.error('cannot use `?` on non-option variable', node.pos) + c.error('cannot use `?` on non-option variable${hint}', node.pos) } else if node.or_expr.kind == .block { - c.error('cannot use `or {}` block on non-option variable', node.pos) + c.error('cannot use `or {}` block on non-option variable${hint}', + node.pos) } } unwrapped_typ := typ.clear_option_and_result() @@ -6559,10 +6561,13 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type { return typ.clear_flag(.result) } if !typ.has_flag(.option) { + hint := c.smartcast_unwrap_hint(&obj) if node.or_expr.kind == .propagate_option { - c.error('cannot use `?` on non-option variable', node.pos) + c.error('cannot use `?` on non-option variable${hint}', + node.pos) } else if node.or_expr.kind == .block { - c.error('cannot use `or {}` block on non-option variable', node.pos) + c.error('cannot use `or {}` block on non-option variable${hint}', + node.pos) } } unwrapped_typ := typ.clear_option_and_result() @@ -7354,6 +7359,17 @@ fn (mut c Checker) visible_var_type_for_read(v ast.Var) ast.Type { return c.exposed_smartcast_type(v.orig_type, v.smartcasts.last(), v.is_mut) } +fn (mut c Checker) smartcast_unwrap_hint(v &ast.Var) string { + if v == unsafe { nil } { + return '' + } + if v.smartcasts.len == 0 || !v.typ.has_flag(.option) { + return '' + } + narrowed := c.table.type_to_str(v.smartcasts.last()) + return ' `${v.name}` (it was already unwrapped to `${narrowed}` by an earlier none check; use `${v.name}` directly)' +} + @[inline] fn (mut c Checker) exposed_smartcast_type(orig_type ast.Type, smartcast_type ast.Type, is_mut bool) ast.Type { if is_mut || orig_type == 0 || smartcast_type == 0 || orig_type.is_ptr() { diff --git a/vlib/v/checker/tests/or_block_smartcast_unwrapped_err.out b/vlib/v/checker/tests/or_block_smartcast_unwrapped_err.out new file mode 100644 index 000000000..c1a4529ac --- /dev/null +++ b/vlib/v/checker/tests/or_block_smartcast_unwrapped_err.out @@ -0,0 +1,19 @@ +vlib/v/checker/tests/or_block_smartcast_unwrapped_err.vv:13:2: warning: unwrapping option is redundant as the function returns option + 11 | return none + 12 | } + 13 | return name? + | ~~~~~~~~~~~ + 14 | } +vlib/v/checker/tests/or_block_smartcast_unwrapped_err.vv:5:11: error: cannot use `or {}` block on non-option variable `name` (it was already unwrapped to `string` by an earlier none check; use `name` directly) + 3 | return + 4 | } + 5 | name_ := name or { '' }.trim_space() + | ~~~~ + 6 | println(name_) + 7 | } +vlib/v/checker/tests/or_block_smartcast_unwrapped_err.vv:13:9: error: cannot use `?` on non-option variable `name` (it was already unwrapped to `string` by an earlier none check; use `name` directly) + 11 | return none + 12 | } + 13 | return name? + | ~~~~ + 14 | } diff --git a/vlib/v/checker/tests/or_block_smartcast_unwrapped_err.vv b/vlib/v/checker/tests/or_block_smartcast_unwrapped_err.vv new file mode 100644 index 000000000..b2395e081 --- /dev/null +++ b/vlib/v/checker/tests/or_block_smartcast_unwrapped_err.vv @@ -0,0 +1,14 @@ +fn valid_name(name ?string) ! { + if name == none { + return + } + name_ := name or { '' }.trim_space() + println(name_) +} + +fn propagate_name(name ?string) ?string { + if name == none { + return none + } + return name? +} -- 2.39.5