From dc12b1bd0a9df22648fe5db539a61e1352a4914c Mon Sep 17 00:00:00 2001 From: GGRei Date: Sun, 10 May 2026 00:36:09 +0200 Subject: [PATCH] cgen: avoid or-block err name collision (#27060) --- vlib/v/gen/c/cgen.v | 36 ++++++-- vlib/v/gen/c/coutput_test.v | 26 ++++++ .../testdata/const_init_or_block.c.must_have | 11 +-- ...onst_init_or_block_no_parallel.c.must_have | 9 +- .../c/testdata/or_block_err_var_collision.out | 1 + .../c/testdata/or_block_err_var_collision.vv | 9 ++ .../options/or_block_err_var_collision_test.v | 88 +++++++++++++++++++ 7 files changed, 165 insertions(+), 15 deletions(-) create mode 100644 vlib/v/gen/c/testdata/or_block_err_var_collision.out create mode 100644 vlib/v/gen/c/testdata/or_block_err_var_collision.vv create mode 100644 vlib/v/tests/options/or_block_err_var_collision_test.v diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 539a044be..44a52292a 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -11605,9 +11605,21 @@ fn (mut g Gen) or_block_on_value(var_name string, or_block ast.OrExpr, return_ty g.writeln('if (${cvar_name}${tmp_op}state != 0) {') } g.or_expr_return_type = return_type - if or_block.err_used || or_block_last_stmt_is_err(or_block.stmts) - || (g.fn_decl != unsafe { nil } && (g.fn_decl.is_main || g.fn_decl.is_test)) { - g.writeln('\tIError err = ${cvar_name}${tmp_op}err;') + mut or_block_has_special_err := false + if err_obj := or_block.scope.objects['err'] { + if err_obj is ast.Var { + or_block_has_special_err = err_obj.is_special + } + } + or_block_needs_err := or_block_has_special_err + && (or_block.err_used || or_block_last_stmt_is_err(or_block.stmts)) + fn_forces_err := g.fn_decl != unsafe { nil } && (g.fn_decl.is_main || g.fn_decl.is_test) + if or_block_needs_err || fn_forces_err { + err_tmp := g.new_tmp_var() + g.writeln('\tIError ${err_tmp} = ${cvar_name}${tmp_op}err;') + if or_block_needs_err || cvar_name != 'err' { + g.writeln('\tIError err = ${err_tmp};') + } } g.inside_or_block = true defer { @@ -11701,9 +11713,21 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty g.write_v_source_line_info_pos(or_block.pos) if or_block.kind == .block { g.or_expr_return_type = return_type.clear_option_and_result() - if or_block.err_used || or_block_last_stmt_is_err(or_block.stmts) - || (g.fn_decl != unsafe { nil } && (g.fn_decl.is_main || g.fn_decl.is_test)) { - g.writeln('\tIError err = ${cvar_name}${tmp_op}err;') + mut or_block_has_special_err := false + if err_obj := or_block.scope.objects['err'] { + if err_obj is ast.Var { + or_block_has_special_err = err_obj.is_special + } + } + or_block_needs_err := or_block_has_special_err + && (or_block.err_used || or_block_last_stmt_is_err(or_block.stmts)) + fn_forces_err := g.fn_decl != unsafe { nil } && (g.fn_decl.is_main || g.fn_decl.is_test) + if or_block_needs_err || fn_forces_err { + err_tmp := g.new_tmp_var() + g.writeln('\tIError ${err_tmp} = ${cvar_name}${tmp_op}err;') + if or_block_needs_err || cvar_name != 'err' { + g.writeln('\tIError err = ${err_tmp};') + } } g.inside_or_block = true diff --git a/vlib/v/gen/c/coutput_test.v b/vlib/v/gen/c/coutput_test.v index db2cd2837..6c8b2ca8f 100644 --- a/vlib/v/gen/c/coutput_test.v +++ b/vlib/v/gen/c/coutput_test.v @@ -199,6 +199,32 @@ fn test_c_must_have_files() { assert total_errors == 0 } +fn test_or_block_err_var_collision_does_not_emit_self_referential_err() { + os.chdir(vroot) or {} + path := os.join_path(testdata_folder, 'or_block_err_var_collision.vv') + cmd := '${os.quoted_path(vexe)} -o - ${os.quoted_path(path)}' + compilation := os.execute(cmd) + ensure_compilation_succeeded(compilation, cmd) + assert !compilation.output.contains('IError err = err.err;') + mut source_err_tmp := '' + mut has_visible_or_block_err := false + for line in compilation.output.split_into_lines() { + trimmed := line.trim_space() + if trimmed.starts_with('IError _t') && trimmed.ends_with(' = err.err;') { + source_err_tmp = trimmed.all_after('IError ').all_before(' = err.err;') + } + if trimmed.starts_with('IError _t') && trimmed.contains('.err;') { + err_tmp := trimmed.all_after('IError ').all_before(' = ') + if compilation.output.contains('IError err = ${err_tmp};') { + has_visible_or_block_err = true + } + } + } + assert source_err_tmp != '' + assert !compilation.output.contains('IError err = ${source_err_tmp};') + assert has_visible_or_block_err +} + fn test_imported_empty_interface_concat_does_not_emit_noop_array_cast_helper() { os.chdir(vroot) or {} path := os.join_path(vroot, diff --git a/vlib/v/gen/c/testdata/const_init_or_block.c.must_have b/vlib/v/gen/c/testdata/const_init_or_block.c.must_have index 439bd14eb..cc8f7fb02 100644 --- a/vlib/v/gen/c/testdata/const_init_or_block.c.must_have +++ b/vlib/v/gen/c/testdata/const_init_or_block.c.must_have @@ -1,11 +1,12 @@ { -_option_bool _t2 = main__t1(); -if (_t2.state != 0) { -IError err = _t2.err; +_option_bool _t3 = main__t1(); +if (_t3.state != 0) { +IError _t4 = _t3.err; +IError err = _t4; builtin___v_panic(builtin__IError_str(err)); VUNREACHABLE(); ; } -_const_main__barz = (((*(bool*)_t2.data))); -} \ No newline at end of file +_const_main__barz = (((*(bool*)_t3.data))); +} diff --git a/vlib/v/gen/c/testdata/const_init_or_block_no_parallel.c.must_have b/vlib/v/gen/c/testdata/const_init_or_block_no_parallel.c.must_have index cf2250432..cc8f7fb02 100644 --- a/vlib/v/gen/c/testdata/const_init_or_block_no_parallel.c.must_have +++ b/vlib/v/gen/c/testdata/const_init_or_block_no_parallel.c.must_have @@ -1,11 +1,12 @@ { -_option_bool _t2 = main__t1(); -if (_t2.state != 0) { -IError err = _t2.err; +_option_bool _t3 = main__t1(); +if (_t3.state != 0) { +IError _t4 = _t3.err; +IError err = _t4; builtin___v_panic(builtin__IError_str(err)); VUNREACHABLE(); ; } -_const_main__barz = (((*(bool*)_t2.data))); +_const_main__barz = (((*(bool*)_t3.data))); } diff --git a/vlib/v/gen/c/testdata/or_block_err_var_collision.out b/vlib/v/gen/c/testdata/or_block_err_var_collision.out new file mode 100644 index 000000000..9daeafb98 --- /dev/null +++ b/vlib/v/gen/c/testdata/or_block_err_var_collision.out @@ -0,0 +1 @@ +test diff --git a/vlib/v/gen/c/testdata/or_block_err_var_collision.vv b/vlib/v/gen/c/testdata/or_block_err_var_collision.vv new file mode 100644 index 000000000..a341f5ce5 --- /dev/null +++ b/vlib/v/gen/c/testdata/or_block_err_var_collision.vv @@ -0,0 +1,9 @@ +fn fail() !string { + return error('boom') +} + +fn main() { + err := ?string('test') + println(err or { 'fallback' }) + _ := fail() or { err.msg() } +} diff --git a/vlib/v/tests/options/or_block_err_var_collision_test.v b/vlib/v/tests/options/or_block_err_var_collision_test.v new file mode 100644 index 000000000..32d7530b7 --- /dev/null +++ b/vlib/v/tests/options/or_block_err_var_collision_test.v @@ -0,0 +1,88 @@ +fn result_ok(value string) !string { + return value +} + +fn result_error(message string) !string { + return error(message) +} + +fn test_or_block_with_err_source_fallback_not_executed() { + err := ?string('test') + got := err or { 'fallback' } + + assert got == 'test' +} + +fn test_or_block_with_err_source_fallback_executed() { + err := ?string(none) + got := err or { 'fallback' } + + assert got == 'fallback' +} + +fn test_or_block_err_value_keeps_special_meaning() { + got := result_error('boom') or { err.msg() } + + assert got == 'boom' +} + +fn test_or_block_err_source_reusable_after_block() { + err := ?string(none) + got := err or { 'fallback' } + again := err or { 'again' } + + assert got == 'fallback' + assert again == 'again' +} + +fn test_nested_or_block_err_shadowing() { + got := result_error('outer') or { + inner_msg := result_error('inner') or { err.msg() } + assert inner_msg == 'inner' + assert err.msg() == 'outer' + err.msg() + } + + assert got == 'outer' +} + +fn test_if_guard_inside_or_block_uses_guard_err() { + got := result_error('outer') or { + if value := result_error('guard') { + value + } else { + err.msg() + } + } + + assert got == 'guard' +} + +fn test_if_guard_with_option_source_named_err() { + err := ?string('value') + mut got := '' + if value := err { + got = value + } else { + got = 'fallback' + } + + assert got == 'value' +} + +fn test_map_index_or_block_on_value_path() { + mut values := map[string]?string{} + values['present'] = ?string('value') + + missing := values['missing'] or { 'fallback' } + present := values['present'] or { 'fallback' } + + assert missing == 'fallback' + assert present == 'value' +} + +fn test_result_call_or_block_fallback_not_executed() { + got := result_ok('ok') or { 'fallback' } + + assert got == 'ok' +} -- 2.39.5