-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
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.