From f29d7723d5a51cd99b371df73cb9102a70abd721 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 27 Jun 2026 01:53:45 +0300 Subject: [PATCH] checker: reject multi-return elements that disagree on option-ness (fix #27562) (#27565) --- vlib/v/checker/check_types.v | 12 +++++++++ vlib/v/checker/checker.v | 9 +++++-- ...lti_return_option_element_mismatch_err.out | 21 +++++++++++++++ ...ulti_return_option_element_mismatch_err.vv | 26 +++++++++++++++++++ ...i_return_option_result_return_expr_err.out | 7 +++++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 vlib/v/checker/tests/multi_return_option_element_mismatch_err.out create mode 100644 vlib/v/checker/tests/multi_return_option_element_mismatch_err.vv diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 597048fa2..58002257d 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -577,6 +577,18 @@ fn (mut c Checker) check_basic(got ast.Type, expected ast.Type) bool { return false } for i in 0 .. exp_types.len { + // Each multi-return element lowers to a distinct C struct field, so an + // optional/result element (`?T`/`!T`, a `_option_T`/`_result_T` struct) is + // not layout-compatible with a plain `T`. `check_types` below matches by type + // index and ignores these flags, which would let `(string, ?string)` + // masquerade as `(string, string)` and emit mismatched `multi_return_*` + // structs. Keep this a strict layout check (both directions): the safe + // `T` -> `?T` element promotion is opted into only where codegen wraps it (the + // `return` and `or {}` paths, via `can_use_expected_multi_return_*`). + if got_types[i].has_flag(.option) != exp_types[i].has_flag(.option) + || got_types[i].has_flag(.result) != exp_types[i].has_flag(.result) { + return false + } if !c.check_types(got_types[i], exp_types[i]) { return false } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 12dc314b8..98037fa6b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2874,8 +2874,13 @@ fn (mut c Checker) check_or_last_stmt(mut stmt ast.Stmt, ret_type ast.Type, defa } c.expected_or_type = ast.void_type - type_fits := c.check_types(last_stmt_typ, ret_type) - && last_stmt_typ.nr_muls() == ret_type.nr_muls() + // A multi-return `or {}` default may supply a plain `T` element where the + // expected slot is `?T`; the or-block codegen wraps it (see `concat_expr`'s + // `inside_or_block` path), so allow that promotion explicitly here. The + // strict `check_types` multi-return check above otherwise rejects it. + type_fits := (c.check_types(last_stmt_typ, ret_type) + && last_stmt_typ.nr_muls() == ret_type.nr_muls()) + || c.can_use_expected_multi_return_expr_type(last_stmt_typ, ret_type, stmt.expr) is_noreturn := is_noreturn_callexpr(stmt.expr) if type_fits || is_noreturn { return diff --git a/vlib/v/checker/tests/multi_return_option_element_mismatch_err.out b/vlib/v/checker/tests/multi_return_option_element_mismatch_err.out new file mode 100644 index 000000000..5861183fb --- /dev/null +++ b/vlib/v/checker/tests/multi_return_option_element_mismatch_err.out @@ -0,0 +1,21 @@ +vlib/v/checker/tests/multi_return_option_element_mismatch_err.vv:5:47: error: wrong return type `(string, ?string)` in the `or {}` block, expected `(string, string)` + 3 | // mismatched `multi_return_*` C structs. See issue #27562. + 4 | fn parse(s string) ?(string, string) { + 5 | subtype, parameter := s.split_once(';') or { s, ?string(none) } + | ^ + 6 | return subtype, parameter + 7 | } +vlib/v/checker/tests/multi_return_option_element_mismatch_err.vv:10:25: error: mismatched types `(string, string)` and `(string, ?string)` + 8 | + 9 | fn extract(s string) (string, ?string) { + 10 | rawurl, description := if s.contains(' ') { + | ~~ + 11 | s[..1], s[1..] + 12 | } else { +vlib/v/checker/tests/multi_return_option_element_mismatch_err.vv:20:25: error: mismatched types `(string, ?string)` and `(string, string)` + 18 | // the same mismatch must be caught regardless of which branch is the optional one + 19 | fn extract_rev(s string) (string, ?string) { + 20 | rawurl, description := if s.contains(' ') { + | ~~ + 21 | s, ?string(none) + 22 | } else { diff --git a/vlib/v/checker/tests/multi_return_option_element_mismatch_err.vv b/vlib/v/checker/tests/multi_return_option_element_mismatch_err.vv new file mode 100644 index 000000000..4bcba9a7f --- /dev/null +++ b/vlib/v/checker/tests/multi_return_option_element_mismatch_err.vv @@ -0,0 +1,26 @@ +// An optional multi-return element (`?T`) lowers to a different C struct than a +// plain `T`, so mixing them must be rejected at the V level instead of emitting +// mismatched `multi_return_*` C structs. See issue #27562. +fn parse(s string) ?(string, string) { + subtype, parameter := s.split_once(';') or { s, ?string(none) } + return subtype, parameter +} + +fn extract(s string) (string, ?string) { + rawurl, description := if s.contains(' ') { + s[..1], s[1..] + } else { + s, ?string(none) + } + return rawurl, description +} + +// the same mismatch must be caught regardless of which branch is the optional one +fn extract_rev(s string) (string, ?string) { + rawurl, description := if s.contains(' ') { + s, ?string(none) + } else { + s[..1], s[1..] + } + return rawurl, description +} diff --git a/vlib/v/checker/tests/multi_return_option_result_return_expr_err.out b/vlib/v/checker/tests/multi_return_option_result_return_expr_err.out index b5b001a80..556e630d5 100644 --- a/vlib/v/checker/tests/multi_return_option_result_return_expr_err.out +++ b/vlib/v/checker/tests/multi_return_option_result_return_expr_err.out @@ -5,6 +5,13 @@ vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv:10:9: error: | ~~ 11 | } 12 | +vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv:15:10: error: return type mismatch, it should be `(int, int)`, but it is instead `(!int, int literal)` + 13 | fn return_match_with_result_value(x bool) (int, int) { + 14 | return match x { + 15 | true { result_int(), 1 } + | ~~~~~~~~~~ + 16 | else { 2, 2 } + 17 | } vlib/v/checker/tests/multi_return_option_result_return_expr_err.vv:14:9: error: cannot use `!int` as type `int` in return argument 12 | 13 | fn return_match_with_result_value(x bool) (int, int) { -- 2.39.5