vlang / v

Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies. Supports automatic C => V translation. https://vlang.io
MIT License
35.81k stars 2.17k forks source link

cgen/checker: Mistranslated generic struct - perhaps. #15837

Closed IngwiePhoenix closed 1 year ago

IngwiePhoenix commented 2 years ago

Hello!

So, first my info:

$ v doctor
OS: linux, Debian GNU/Linux 11 (bullseye) (WSL 2)
Processor: 24 cpus, 64bit, little endian, AMD Ryzen 9 3900X 12-Core Processor
CC version: cc (Debian 10.2.1-6) 10.2.1 20210110

getwd: /mnt/f/Work/IngwiePhoenix/surreal.v/messages
vmodules: /home/ingwie/.vmodules
vroot: /opt/v
vexe: /opt/v/v
vexe mtime: 2022-09-20 19:00:44
is vroot writable: true
is vmodules writable: true
V full version: V 0.3.1 d848311

Git version: git version 2.30.2
Git vroot status: weekly.2022.38-5-gd848311f
.git/config present: true
thirdparty/tcc status: thirdparty-linux-amd64 12f392c3

What did you do? I tried to compile this module based on the SurrealDB RPC. To test it, I wrote a super simple hello world program that just includes the module, so that I could see if it even compiles.

rpc.v:

module rpc

import rand

// Module: messages.rpc
struct MiniTuple<A, B> {
    left  A
    right B
}

fn (t MiniTuple<A, B>) string() string {
    return '[' + t.left + ', ' + t.right + ']'
}

struct RpcMsg<T> {
    id     string
    method string
    params T
}

// Generators
fn handle_simple(method string) RpcMsg<[]string> {
    return RpcMsg<[]string>{
        id: rand.uuid_v4()
        method: method
        params: []
    }
}

fn handle_auth<T>(method string, data T) RpcMsg<T> {
    return RpcMsg<T>{
        id: rand.uuid_v4()
        method: method
        params: data
    }
}

fn handle_data1(method string, target string) RpcMsg<string> {
    return RpcMsg<string>{
        id: rand.uuid_v4()
        method: method
        params: target
    }
}

fn handle_data2<T>(method string, target string, data T) RpcMsg<MiniTuple<string, T>> {
    return RpcMsg< MiniTuple<string, T> >{
        id: rand.uuid_v4()
        method: method
        params: MiniTuple<string, T>{target, data}
    }
}

// Methods
// via: https://github.com/orgs/surrealdb/discussions/104#discussioncomment-3675580
pub fn ping() RpcMsg<[]string> {
    return handle_simple('ping')
}

pub fn info() RpcMsg<[]string> {
    return handle_simple('info')
}

pub fn use(ns string, db string) RpcMsg<[]string> {
    return RpcMsg<[]string>{
        id: rand.uuid_v4()
        method: 'use'
        params: [ns, db]
    }
}

pub fn signin<T>(data T) RpcMsg<T> {
    return handle_auth<T>('signin', data)
}

pub fn signup<T>(data T) RpcMsg<T> {
    return handle_auth<T>('signup', data)
}

pub fn authenticate(jwt string) RpcMsg<string> {
    return handle_data1('authenticate', jwt)
}

pub fn invalidate() RpcMsg<[]string> {
    return handle_simple('invalidate')
}

pub fn kill(uuid string) RpcMsg<string> {
    return handle_data1('kill', uuid)
}

pub fn live(table string) RpcMsg<string> {
    return handle_data1('live', table)
}

pub fn let<T>(key string, value T) RpcMsg<MiniTuple<string, T>> {
    return handle_data2<T>('let', key, value)
}

pub fn set<T>(key string, value T) RpcMsg<MiniTuple<string, T>> {
    return let<T>(key, value)
}

pub fn query<T>(sql string, kvs map[string]T) RpcMsg<MiniTuple<string, map[string]T>> {
    return handle_data2('query', sql, kvs)
}

pub fn do_select(target string) RpcMsg<string> {
    return handle_data1('select', target)
}

pub fn create<T>(target string, data T) RpcMsg<MiniTuple<string, T>> {
    return handle_data2<T>('create', target, data)
}

pub fn update<T>(target string, data T) RpcMsg<MiniTuple<string, T>> {
    return handle_data2<T>('update', target, data)
}

pub fn change<T>(target string, data T) RpcMsg<MiniTuple<string, T>> {
    return handle_data2<T>('change', target, data)
}

pub fn modify<T>(target string, data T) RpcMsg<MiniTuple<string, T>> {
    return handle_data2<T>('modify', target, data)
}

What did you expect to see? Only the warning about the unused module, nothing more.

What did you see instead?

$ v -cc gcc -cg main.v
main.v:3:8: warning: module 'rpc' is imported but never used
    1 | module main
    2 |
    3 | import rpc
      |        ~~~
    4 |
    5 | fn main() {
/tmp/v_1000/main.5577954034448643962.tmp.c:1258:2: error: unknown type name ‘T’
 1258 |  T right;
      |  ^
/tmp/v_1000/main.5577954034448643962.tmp.c: In function ‘rpc__MiniTuple_T_string_T_string_T_string_T’:
/tmp/v_1000/main.5577954034448643962.tmp.c:15328:103: error: incompatible type for argument 2 of ‘string__plus’
15328 |  string _t1 = string__plus(string__plus(string__plus(string__plus(_SLIT("["), t.left), _SLIT(", ")), t.right), _SLIT("]"));
      |                                                                                                      ~^~~~~~
      |                                                                                                       |
      |                                                                                                       int
/tmp/v_1000/main.5577954034448643962.tmp.c:10571:38: note: expected ‘string’ but argument is of type ‘int’
10571 | string string__plus(string s, string a) {
      |                               ~~~~~~~^
/tmp/v_1000/main.5577954034448643962.tmp.c: In function ‘rpc__MiniTuple_T_string_Map_string_T_string_T_string_Map_string_T’:
/tmp/v_1000/main.5577954034448643962.tmp.c:15332:103: error: incompatible type for argument 2 of ‘string__plus’
15332 |  string _t1 = string__plus(string__plus(string__plus(string__plus(_SLIT("["), t.left), _SLIT(", ")), t.right), _SLIT("]"));
      |                                                                                                      ~^~~~~~
      |                                                                                                       |
      |                                                                                                       Map_string_T {aka map}
/tmp/v_1000/main.5577954034448643962.tmp.c:10571:38: note: expected ‘string’ but argument is of type ‘Map_string_T’ {aka ‘map’}
10571 | string string__plus(string s, string a) {
      |                               ~~~~~~~^
builder error:
==================
C error. This should never happen.

My bet is right here:

/tmp/v_1000/main.5577954034448643962.tmp.c:1258:2: error: unknown type name ‘T’
 1258 |  T right;
      |  ^

This seems like it's part of the translation of MiniTuple<A, B> from the invocation RpcMsg<MiniTuple<string, T>>{ ... }.

So I am not sure if this goes to cgen, or checker - so I listed both because it is probably somewhere in that area and I don't know how to work around that.

The idea of RpcMsg was to have .params be dynamic in most cases. It will be JSON encoded and sent via WebSockets, so this field needs a little more dynamicness. My idea was to use a generic here to speed this up a little instead of having to write multiple structs with their own .string() to sit out pieces of JSON. This... might've backfired. ^^;

Kind regards, Ingwie

yuyi98 commented 2 years ago

RpcMsg<MiniTuple<string, T>> This nested generic structure definition is not currently supported.

IngwiePhoenix commented 2 years ago

I see... So I have to do that differently. Alright, I'll see what comes to mind.

Thanks!

IngwiePhoenix commented 2 years ago

That said, might want to introduce an error for this situation, perhaps?

JalonSolov commented 2 years ago

Can you include your main.v so this can be tested?

Delta456 commented 1 year ago

The following compiles successfully with the new generic syntax and the latest commit:

module rpc

import rand

// Module: messages.rpc
struct MiniTuple[A, B] {
    left  A
    right B
}

fn (t MiniTuple[A, B]) string() string {
    return '[' + t.left + ', ' + t.right + ']'
}

struct RpcMsg[T] {
    id     string
    method string
    params T
}

// Generators
fn handle_simple(method string) RpcMsg[[]string] {
    return RpcMsg[[]string]{
        id: rand.uuid_v4()
        method: method
        params: []
    }
}

fn handle_auth[T](method string, data T) RpcMsg[T] {
    return RpcMsg[T]{
        id: rand.uuid_v4()
        method: method
        params: data
    }
}

fn handle_data1(method string, target string) RpcMsg[string] {
    return RpcMsg[string]{
        id: rand.uuid_v4()
        method: method
        params: target
    }
}

fn handle_data2[T](method string, target string, data T) RpcMsg[MiniTuple[string, T]] {
    return RpcMsg[ MiniTuple[string, T] ]{
        id: rand.uuid_v4()
        method: method
        params: MiniTuple[string, T]{target, data}
    }
}

// Methods
// via: https://github.com/orgs/surrealdb/discussions/104#discussioncomment-3675580
pub fn ping() RpcMsg[[]string] {
    return handle_simple('ping')
}

pub fn info() RpcMsg[[]string] {
    return handle_simple('info')
}

pub fn use(ns string, db string) RpcMsg[[]string] {
    return RpcMsg[[]string]{
        id: rand.uuid_v4()
        method: 'use'
        params: [ns, db]
    }
}

pub fn signin[T](data T) RpcMsg[T] {
    return handle_auth[T]('signin', data)
}

pub fn signup[T](data T) RpcMsg[T] {
    return handle_auth[T]('signup', data)
}

pub fn authenticate(jwt string) RpcMsg[string] {
    return handle_data1('authenticate', jwt)
}

pub fn invalidate() RpcMsg[[]string] {
    return handle_simple('invalidate')
}

pub fn kill(uuid string) RpcMsg[string] {
    return handle_data1('kill', uuid)
}

pub fn live(table string) RpcMsg[string] {
    return handle_data1('live', table)
}

pub fn let[T](key string, value T) RpcMsg[MiniTuple[string, T]] {
    return handle_data2[T]('let', key, value)
}

pub fn set[T](key string, value T) RpcMsg[MiniTuple[string, T]] {
    return let[T](key, value)
}

pub fn query[T](sql string, kvs map[string]T) RpcMsg[MiniTuple[string, map[string]T]] {
    return handle_data2('query', sql, kvs)
}

pub fn do_select(target string) RpcMsg[string] {
    return handle_data1('select', target)
}

pub fn create[T](target string, data T) RpcMsg[MiniTuple[string, T]] {
    return handle_data2[T]('create', target, data)
}

pub fn update[T](target string, data T) RpcMsg[MiniTuple[string, T]] {
    return handle_data2[T]('update', target, data)
}

pub fn change[T](target string, data T) RpcMsg[MiniTuple[string, T]] {
    return handle_data2[T]('change', target, data)
}

pub fn modify[T](target string, data T) RpcMsg[MiniTuple[string, T]] {
    return handle_data2[T]('modify', target, data)
}