Skip to content

Generic type mismatch when generic functions from multiple structs created #25217

@timurgordon

Description

@timurgordon

Describe the bug

I'm creating a jsonrpc module where I want to register functions as procedures. I use generic [T] for my functions param and generic [U] for return type.

fn new_cat(params NewCat) Cat {}
fn new_dog(params NewDog) Dog {}

handler.register_procedure[NewCat, Cat]('new_cat', new_cat) works.

However, if I attempt to register another function with different generic structs such as:
handler.register_procedure[NewDog, Dog]('new_dog', new_dog)

I get the following error:

cannot use `Dog` as `Cat` in argument 1 to `x.json2.encode`
   84 |     return Response{
   85 |         id: request.id
   86 |         result: json.encode[U](result)
      |                                ~~~~~~
   87 |     }
   88 | }

if I remove the [U] in json.encode[U](result)
I get different error per compiler, see current behavior for full errors.

Using json instead of x.json2 does not change behavior

Reproduction Steps

vbug.vsh

#!/usr/bin/env -S v -n -w -d use_openssl run

import x.json2 as json

pub type ProcedureHandler = fn (request Request) !Response

pub struct Request {
pub mut:
	jsonrpc string = '2.0' @[required]
	method string @[required]
	params string
	id int @[required]
}

pub struct Response {
pub:
	jsonrpc string = '2.0' @[required]
	result ?string
	error_ ?RPCError @[json: 'error']
	id int @[required]
}

pub struct RPCError {
pub mut:
	code int
	message string
	data ?string
}

pub fn (err RPCError) msg() string {
	return err.message
}

pub fn (err RPCError) code() int {
	return err.code
}

pub struct Handler {
pub mut:
	// A map where keys are method names and values are the corresponding procedure handler functions
	procedures map[string]ProcedureHandler
}

pub fn (mut handler Handler) register_procedure[T, U](method string, function fn (T) !U) {
	procedure := Procedure[T, U]{
		function: function
		method:   method
	}
	handler.procedures[procedure.method] = procedure.handle
}

pub fn (handler Handler) handle(message string) !string {
	req := json.decode[Request](message) or { return invalid_request }
	
	procedure_func := handler.procedures[req.method] or {
		return method_not_found
	}

	// Execute the procedure handler with the request payload
	resp := procedure_func(req) or {
		if err is RPCError {
			Response {
				id: req.id, 
				error_: *err
			}
		}
		else {
			panic(err)
		}
	 }
	return json.encode(resp)
}

pub struct Procedure[T, U] {
pub mut:
	method string
	function fn (T) !U
}

pub fn (p Procedure[T, U]) handle(request Request) !Response {
	payload := json.decode[T](request.params) or { return invalid_params }
	result := p.function(payload) or { return internal_error }
	return Response{
		id: request.id
		result: json.encode(result)
	}
}

pub const invalid_request = RPCError{
	code:    -32600
	message: 'Invalid Request'
	data:    'The JSON sent is not a valid Request object.'
}

pub const method_not_found = RPCError{
	code:    -32601
	message: 'Method not found'
	data:    'The method does not exist / is not available.'
}

pub const invalid_params = RPCError{
	code:    -32602
	message: 'Invalid params'
	data:    'Invalid method parameter(s).'
}

pub const internal_error = RPCError{
	code:    -32603
	message: 'Internal Error'
	data:    'Internal JSON-RPC error.'
}

pub struct Dog {
	name string
	age int 
}

pub struct NewDog {
	name string
	birthday string
}

pub fn new_dog(params NewDog) !Dog {
	return Dog{name: params.name}
}

pub struct Cat {
	name string
	color string
}

pub struct NewCat {
	name string
	color string
}

pub fn new_cat(params NewCat) !Cat {
	return Cat{
		name: params.name
		color: params.color
	}
}

fn main() {
	mut handler := Handler{}

	// works
	handler.register_procedure[NewDog, Dog]('new_dog', new_dog)
	
	request := Request{
		id: 1
		method: 'new_dog' 
		params: '{"name": "leo", "birthday": "01/01/2010"}'
	}

	response := handler.handle(json.encode(request))!

	println(response)
	
	// Uncomment to see error
	// handler.register_procedure[NewCat, Cat]('new_cat', new_cat)
}

Expected Behavior

I expect the types I pass as generic not to be mixed in the compiled c code.

Current Behavior

TCC:

================== C compilation error (from tcc): ==============
cc: /tmp/v_501/vbug.01K42KK3VJZ9AAGHBWH9D6FJ8X.tmp.c:26896: error: '{' expected (got ";")
=================================================================
_result_main__Response main__Procedure_T_main__NewDog_main__Dog_handle_T_main__NewDog_main__Dog(main__Procedure_T_main__NewDog_main__Dog p, main__Request request) {
	_result_main__NewDog _t1 = x__json2__decode_T_main__NewDog(request.params);
	if (_t1.is_error) {
		return (_result_main__Response){ .is_error=true, .err=I_main__RPCError_to_Interface_IError(&_const_main__invalid_params), .data={E_STRUCT} };
	}
	
 	main__NewDog payload = (*(main__NewDog*)_t1.data);
	_result_main__Cat _t3 = p.function(payload);
	if (_t3.is_error) {
		return (_result_main__Response){ .is_error=true, .err=I_main__RPCError_to_Interface_IError(&_const_main__internal_error), .data={E_STRUCT} };
	}
	
 	main__Cat result = (*(main__Cat*)_t3.data);
	_option_string _t6;
	_option_ok(&(string[]) { x__json2__encode_T_main__Dog(result) }, (_option*)(&_t6), sizeof(string));
	_option_main__RPCError _t8 = {0};
	_option_none(&(main__RPCError[]) { ((main__RPCError){.code = 0,.message = (string){.str=(byteptr)"", .is_lit=1},.data = (_option_string){ .state=2, .err=_const_none__, .data={E_STRUCT} },})}, (_option*)&_t8, sizeof(main__RPCError));
	_option_main__RPCError _t7;
	_t7 = (_t8);
	_result_main__Response _t5 = {0};
	_result_ok(&(main__Response[]) { ((main__Response){.jsonrpc = _S("2.0"),.result = _t6,.error_ = _t7,.id = request.id,}) }, (_result*)(&_t5), sizeof(main__Response));
	return _t5;
}

CC:

================== C compilation error (from cc): ==============
cc:        |                                         ~~~ ^~~~
cc: /tmp/v_501/vbug.01K42KPYEP8GWN4AVWYY8MM9B5.tmp.c:27373:20: error: initializing '_result_main__Cat' (aka 'struct _result_main__Cat') with an expression of incompatible type '_result_main__Dog' (aka 'struct _result_main__Dog')
cc:  27373 |         _result_main__Cat _t3 = p.function(payload);
cc:        |                           ^     ~~~~~~~~~~~~~~~~~~~
cc: /tmp/v_501/vbug.01K42KPYEP8GWN4AVWYY8MM9B5.tmp.c:27380:56: error: passing 'main__Cat' (aka 'struct main__Cat') to parameter of incompatible type 'main__Dog' (aka 'struct main__Dog')
cc:  27380 |         _option_ok(&(string[]) { x__json2__encode_T_main__Dog(result) }, (_option*)(&_t6), sizeof(string));
cc:        |                                                               ^~~~~~
cc: /tmp/v_501/vbug.01K42KPYEP8GWN4AVWYY8MM9B5.tmp.c:19387:47: note: passing argument to parameter 'val' here
cc:  19387 | string x__json2__encode_T_main__Dog(main__Dog val) {
cc:        |                                               ^
cc: /tmp/v_501/vbug.01K42KPYEP8GWN4AVWYY8MM9B5.tmp.c:27403:56: error: passing 'main__Cat' (aka 'struct main__Cat') to parameter of incompatible type 'main__Dog' (aka 'struct main__Dog')
cc:  27403 |         _option_ok(&(string[]) { x__json2__encode_T_main__Dog(result) }, (_option*)(&_t6), sizeof(string));
...
cc: 3 warnings and 3 errors generated.
(note: the original output was 25 lines long; it was truncated to its first 12 lines + the last line)
================================================================

Possible Solution

Probably to do with generic code gen.

Additional Information/Context

No response

V version

V 0.4.11 487feb9

Environment details (OS name and version, etc.)

V full version V 0.4.11 487feb9
OS macos, macOS, 15.3.2, 24D81
Processor 10 cpus, 64bit, little endian, Apple M1 Pro
Memory 0.14GB/16GB
V executable /Users/timurgordon/code/github/vlang/v/v
V last modified time 2025-09-01 10:24:49
V home dir OK, value: /Users/timurgordon/code/github/vlang/v
VMODULES OK, value: /Users/timurgordon/.vmodules
VTMP OK, value: /tmp/v_501
Current working dir OK, value: /Users/timurgordon/code/github/freeflowuniverse/herolib/lib/hero/heromodels
Git version git version 2.39.5 (Apple Git-154)
V git status weekly.2025.35-27-g487feb9b
.git/config present true
cc version Apple clang version 16.0.0 (clang-1600.0.26.6)
gcc version Apple clang version 16.0.0 (clang-1600.0.26.6)
clang version Homebrew clang version 20.1.7
tcc version tcc version 0.9.28rc 2024-02-05 HEAD@105d70f7 (AArch64 Darwin)
tcc git status thirdparty-macos-arm64 e447816
emcc version N/A
glibc version N/A

Note

You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote.
Other reactions and those to comments will not be taken into account.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugThis tag is applied to issues which reports bugs.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions