Skip to content

checker,gen: fix generic type inference from a callback with Result return type#27679

Open
rilaaax wants to merge 1 commit into
vlang:masterfrom
rilaaax:fix-generic-fn-callback-result-return-inference
Open

checker,gen: fix generic type inference from a callback with Result return type#27679
rilaaax wants to merge 1 commit into
vlang:masterfrom
rilaaax:fix-generic-fn-callback-result-return-inference

Conversation

@rilaaax

@rilaaax rilaaax commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #27108

Problem

fn run[T](cb fn () !T) !T {
	mut r := T{}
	r = cb()!
	return r
}

fn main() {
	cb := fn () !int {
		return -1
	}
	run(cb)!
}

fails with a C compilation error:

error: initializing '_result_int' (aka 'struct _result_int') with an expression of incompatible type 'int'

The issue is specific to the combination of: a generic parameter that is itself a callback (fn () !T), where the callback's return type carries the generic marker inside a Result (!T).

A generic function with a plain (non-callback) parameter and a !T return already works fine.
A callback parameter with a plain (non-Result) return already works fine too.

Note: the wrong inference happens unconditionally whenever a generic function takes a callback parameter typed fn () !T — it isn't specific to using T{} in the body. It only surfaces as a visible compile error when the body does something that needs T's plain (unwrapped) C representation, like zero-initializing T{}; without that, the mismatch can go unnoticed since the code still happens to work despite T being wrong internally.

Cause

V infers T here by structurally comparing the parameter's function type against the argument's actual function type. Two separate places do this comparison (once in the checker, once in a duplicate implementation in cgen), and in both, the branch that extracts the concrete type from the return position took the argument's return type as-is, without stripping its .result flag first. So for a closure returning !int, T got inferred as !int (the Result-wrapped type) instead of int. This produced two inconsistent monomorphized versions of the generic function — one correctly named/typed for T=int, and a second, wrongly named for T=!int, which is the one actually invoked at the call site, leading to invalid C.

Fix

Strip the option/result flags before binding the inferred type, in both places, using the existing clear_option_and_result() helper (already used a few lines above for the analogous case where the parameter itself, rather than just its return type, is Result-wrapped).

Test

Regression test added.

NB: credits to Claude

@rilaaax
rilaaax force-pushed the fix-generic-fn-callback-result-return-inference branch from 80a5506 to 36250cf Compare July 9, 2026 13:38
@GGRei GGRei closed this Jul 11, 2026
@GGRei GGRei reopened this Jul 11, 2026
@GGRei

GGRei commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36250cf2d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if param_sym.info.func.return_type.has_flag(.generic)
&& c.table.sym(param_sym.info.func.return_type).name == gt_name {
typ = arg_sym.info.func.return_type
typ = arg_sym.info.func.return_type.clear_option_and_result()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not strip plain generic callback returns

This unwrap runs for every callback return that binds a generic, but it is only correct when the formal return type is itself ?T/!T. For an API such as fn use[T](cb fn () T) T, callers can legitimately pass a fn () !int/fn () ?int and infer T as the option/result type; this line now infers T=int, so the call is checked/generated as fn () int instead of preserving T=!int. Please condition the unwrap on the formal return carrying option/result flags, and mirror the same guard in the cgen duplicate.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C error on returning generic anon fn result

2 participants