From c7c09d1b8023efd294d80b9180d749a8e2d87eb1 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sun, 24 May 2026 22:03:13 +0300 Subject: [PATCH] cgen: fix C error on `return $tmpl(...)` when fn returns Result/Option (fix #27094) (#27235) --- vlib/v/gen/c/cgen.v | 23 +++++++++++++++-- vlib/v/gen/c/comptime.v | 14 +++++------ vlib/v/tests/tmpl_return_result_test.v | 34 ++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 vlib/v/tests/tmpl_return_result_test.v diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 33967aa7b..f56b717b7 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -10315,12 +10315,31 @@ fn (mut g Gen) return_stmt(node ast.Return) { g.inside_return_tmpl = true g.expr(expr0) g.inside_return_tmpl = false - ret_typ := g.ret_styp(g.unwrap_generic(g.fn_decl.return_type)) + fn_ret_type := g.fn_decl.return_type + ret_typ := g.ret_styp(g.unwrap_generic(fn_ret_type)) + tmpl_fn_name := g.fn_decl.name.replace('.', '__').to_lower() + expr0.pos.pos.str() g.write_defer_stmts_when_needed(node.scope, true, node.pos) if !g.is_builtin_mod { g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) } - g.writeln('return (${ret_typ}){0};') + // `$veb.html()` returns `veb.Result` and the template body + // already writes the rendered string to the response, so just + // return a zero-initialized Result here. Other tmpl kinds + // (`$tmpl(...)`) yield a string in `_tmpl_res_*`. + if expr0.kind == .html { + g.writeln('return (${ret_typ}){0};') + } else if fn_ret_type.has_option_or_result() { + tmp := g.new_tmp_var() + g.writeln('${ret_typ} ${tmp} = {0};') + if fn_ret_type.has_flag(.result) { + g.writeln('builtin___result_ok(&(string[]) { _tmpl_res_${tmpl_fn_name} }, (_result*)(&${tmp}), sizeof(string));') + } else { + g.writeln('builtin___option_ok(&(string[]) { _tmpl_res_${tmpl_fn_name} }, (_option*)(&${tmp}), sizeof(string));') + } + g.writeln('return ${tmp};') + } else { + g.writeln('return _tmpl_res_${tmpl_fn_name};') + } return } } diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 89c1315b9..002b50aab 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -431,15 +431,13 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { g.writeln('veb__Context_html(${g.veb_context_html_arg()}, _tmpl_res_${fn_name});') g.writeln('strings__Builder_free(&sb_${fn_name});') g.writeln('builtin__string_free(&_tmpl_res_${fn_name});') - } else { - // return $tmpl string - if g.inside_return_tmpl { - g.writeln('return _tmpl_res_${fn_name};') - } else { - g.write(cur_line) - g.write('_tmpl_res_${fn_name}') - } + } else if !g.inside_return_tmpl { + g.write(cur_line) + g.write('_tmpl_res_${fn_name}') } + // when inside_return_tmpl, the cgen.v Return handler emits the + // return statement after defer/autofree so it can wrap the result + // for Option/Result return types (see issue #27094). return } mut left_type := g.resolved_expr_type(node.left, node.left_type) diff --git a/vlib/v/tests/tmpl_return_result_test.v b/vlib/v/tests/tmpl_return_result_test.v new file mode 100644 index 000000000..7d48513dc --- /dev/null +++ b/vlib/v/tests/tmpl_return_result_test.v @@ -0,0 +1,34 @@ +// Regression test for https://github.com/vlang/v/issues/27094 +// `return $tmpl(...)` from a function whose return type is a Result/Option +// used to emit C code that returned the raw `string` without wrapping it in +// the surrounding `_result_string` / `_option_string` struct. + +fn gen_result(flag int) !string { + if flag <= 0 { + return error('non-positive flag') + } + name := 'abc' + return $tmpl('tmpl/template.txt') +} + +fn gen_option(flag int) ?string { + if flag <= 0 { + return none + } + name := 'abc' + return $tmpl('tmpl/template.txt') +} + +fn test_tmpl_in_return_result() { + assert gen_result(1)! == 'abc\n' + if _ := gen_result(0) { + assert false + } else { + assert err.msg() == 'non-positive flag' + } +} + +fn test_tmpl_in_return_option() { + assert gen_option(1)? == 'abc\n' + assert gen_option(0) or { 'none' } == 'none' +} -- 2.39.5