all: implement multiple generics (#8231)

pull/8273/head
Daniel Däschle 2021-01-22 13:49:56 +01:00 committed by GitHub
parent b10b76bb0d
commit 500ebf77e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 499 additions and 245 deletions

View File

@ -885,19 +885,19 @@ jobs:
./v -o v2 cmd/v ./v -o v2 cmd/v
./v2 -o v3 cmd/v ./v2 -o v3 cmd/v
vls-compiles: #vls-compiles:
runs-on: ubuntu-20.04 # runs-on: ubuntu-20.04
timeout-minutes: 30 # timeout-minutes: 30
steps: # steps:
- uses: actions/checkout@v2 # - uses: actions/checkout@v2
- name: Build V # - name: Build V
run: make -j2 && ./v -cc gcc -o v cmd/v # run: make -j2 && ./v -cc gcc -o v cmd/v
- name: Clone VLS # - name: Clone VLS
run: git clone --depth 1 https://github.com/vlang/vls # run: git clone --depth 1 https://github.com/vlang/vls
- name: Build VLS # - name: Build VLS
run: cd vls; ../v cmd/vls ; cd .. # run: cd vls; ../v cmd/vls ; cd ..
- name: Build VLS with -prod # - name: Build VLS with -prod
run: cd vls; ../v -prod cmd/vls ; cd .. # run: cd vls; ../v -prod cmd/vls ; cd ..
vab-compiles: vab-compiles:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

View File

@ -324,7 +324,7 @@ pub:
pos token.Position // function declaration position pos token.Position // function declaration position
body_pos token.Position // function bodys position body_pos token.Position // function bodys position
file string file string
is_generic bool generic_params []GenericParam
is_direct_arr bool // direct array access is_direct_arr bool // direct array access
attrs []table.Attr attrs []table.Attr
pub mut: pub mut:
@ -336,6 +336,11 @@ pub mut:
scope &Scope scope &Scope
} }
pub struct GenericParam {
pub:
name string
}
// break, continue // break, continue
pub struct BranchStmt { pub struct BranchStmt {
pub: pub:
@ -362,7 +367,7 @@ pub mut:
receiver_type table.Type // User receiver_type table.Type // User
return_type table.Type return_type table.Type
should_be_skipped bool should_be_skipped bool
generic_type table.Type // TODO array, to support multiple types generic_types []table.Type
generic_list_pos token.Position generic_list_pos token.Position
free_receiver bool // true if the receiver expression needs to be freed free_receiver bool // true if the receiver expression needs to be freed
scope &Scope scope &Scope

View File

@ -59,8 +59,16 @@ pub fn (node &FnDecl) stringify(t &table.Table, cur_mod string, m2a map[string]s
if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] { if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] {
f.write(' ') f.write(' ')
} }
if node.is_generic { if node.generic_params.len > 0 {
f.write('<T>') f.write('<')
for i, param in node.generic_params {
is_last := i == node.generic_params.len - 1
f.write(param.name)
if !is_last {
f.write(', ')
}
}
f.write('>')
} }
f.write('(') f.write('(')
for i, arg in node.params { for i, arg in node.params {

View File

@ -427,48 +427,60 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) table.T
} }
pub fn (mut c Checker) infer_fn_types(f table.Fn, mut call_expr ast.CallExpr) { pub fn (mut c Checker) infer_fn_types(f table.Fn, mut call_expr ast.CallExpr) {
gt_name := 'T' mut inferred_types := []table.Type{}
mut typ := table.void_type for gi, gt_name in f.generic_names {
for i, param in f.params { // skip known types
if call_expr.args.len == 0 { if gi < call_expr.generic_types.len {
break inferred_types << call_expr.generic_types[gi]
continue
} }
arg := if i != 0 && call_expr.is_method { call_expr.args[i - 1] } else { call_expr.args[i] } mut typ := table.void_type
if param.typ.has_flag(.generic) { for i, param in f.params {
typ = arg.typ if call_expr.args.len == 0 {
break break
} }
arg_sym := c.table.get_type_symbol(arg.typ) arg := if i != 0 && call_expr.is_method {
param_type_sym := c.table.get_type_symbol(param.typ) call_expr.args[i - 1]
if arg_sym.kind == .array && param_type_sym.kind == .array { } else {
mut arg_elem_info := arg_sym.info as table.Array call_expr.args[i]
mut param_elem_info := param_type_sym.info as table.Array }
mut arg_elem_sym := c.table.get_type_symbol(arg_elem_info.elem_type) if param.typ.has_flag(.generic) {
mut param_elem_sym := c.table.get_type_symbol(param_elem_info.elem_type) typ = arg.typ
for { break
if arg_elem_sym.kind == .array && }
param_elem_sym.kind == .array && param_elem_sym.name != 'T' arg_sym := c.table.get_type_symbol(arg.typ)
{ param_type_sym := c.table.get_type_symbol(param.typ)
arg_elem_info = arg_elem_sym.info as table.Array if arg_sym.kind == .array && param_type_sym.kind == .array {
arg_elem_sym = c.table.get_type_symbol(arg_elem_info.elem_type) mut arg_elem_info := arg_sym.info as table.Array
param_elem_info = param_elem_sym.info as table.Array mut param_elem_info := param_type_sym.info as table.Array
param_elem_sym = c.table.get_type_symbol(param_elem_info.elem_type) mut arg_elem_sym := c.table.get_type_symbol(arg_elem_info.elem_type)
} else { mut param_elem_sym := c.table.get_type_symbol(param_elem_info.elem_type)
typ = arg_elem_info.elem_type for {
break if arg_elem_sym.kind == .array &&
} param_elem_sym.kind == .array && c.cur_fn.generic_params.filter(it.name ==
param_elem_sym.name).len == 0
{
arg_elem_info = arg_elem_sym.info as table.Array
arg_elem_sym = c.table.get_type_symbol(arg_elem_info.elem_type)
param_elem_info = param_elem_sym.info as table.Array
param_elem_sym = c.table.get_type_symbol(param_elem_info.elem_type)
} else {
typ = arg_elem_info.elem_type
break
}
}
break
} }
break
} }
} if typ == table.void_type {
if typ == table.void_type { c.error('could not infer generic type `$gt_name` in call to `$f.name`', call_expr.pos)
c.error('could not infer generic type `$gt_name` in call to `$f.name`', call_expr.pos) }
} else {
if c.pref.is_verbose { if c.pref.is_verbose {
s := c.table.type_to_str(typ) s := c.table.type_to_str(typ)
println('inferred `$f.name<$s>`') println('inferred `$f.name<$s>`')
} }
c.table.register_fn_gen_type(f.name, typ) inferred_types << typ
call_expr.generic_type = typ call_expr.generic_types << typ
} }
c.table.register_fn_gen_type(f.name, inferred_types)
} }

View File

@ -53,14 +53,14 @@ pub mut:
rlocked_names []string // vars that are currently read-locked rlocked_names []string // vars that are currently read-locked
in_for_count int // if checker is currently in a for loop in_for_count int // if checker is currently in a for loop
// checked_ident string // to avoid infinite checker loops // checked_ident string // to avoid infinite checker loops
returns bool returns bool
scope_returns bool scope_returns bool
mod string // current module name mod string // current module name
is_builtin_mod bool // are we in `builtin`? is_builtin_mod bool // are we in `builtin`?
inside_unsafe bool inside_unsafe bool
inside_const bool inside_const bool
skip_flags bool // should `#flag` and `#include` be skipped skip_flags bool // should `#flag` and `#include` be skipped
cur_generic_type table.Type cur_generic_types []table.Type
mut: mut:
expr_level int // to avoid infinite recursion segfaults due to compiler bugs expr_level int // to avoid infinite recursion segfaults due to compiler bugs
inside_sql bool // to handle sql table fields pseudo variables inside_sql bool // to handle sql table fields pseudo variables
@ -521,8 +521,9 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
struct_init.pos) struct_init.pos)
} }
} }
if type_sym.name.len == 1 && !c.cur_fn.is_generic { if type_sym.name.len == 1 && c.cur_fn.generic_params.len == 0 {
c.error('unknown struct `$type_sym.name`', struct_init.pos) c.error('unknown struct `$type_sym.name`', struct_init.pos)
return 0
} }
match type_sym.kind { match type_sym.kind {
.placeholder { .placeholder {
@ -1254,15 +1255,23 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
if left_type_sym.kind == .sum_type && method_name == 'type_name' { if left_type_sym.kind == .sum_type && method_name == 'type_name' {
return table.string_type return table.string_type
} }
if call_expr.generic_type.has_flag(.generic) { mut has_generic_generic := false // x.foo<T>() instead of x.foo<int>()
mut generic_types := []table.Type{}
for generic_type in call_expr.generic_types {
if generic_type.has_flag(.generic) {
has_generic_generic = true
generic_types << c.unwrap_generic(generic_type)
} else {
generic_types << generic_type
}
}
if has_generic_generic {
if c.mod != '' { if c.mod != '' {
// Need to prepend the module when adding a generic type to a function // Need to prepend the module when adding a generic type to a function
// `fn_gen_types['mymod.myfn'] == ['string', 'int']` c.table.register_fn_gen_type(c.mod + '.' + call_expr.name, generic_types)
c.table.register_fn_gen_type(c.mod + '.' + call_expr.name, c.cur_generic_type)
} else { } else {
c.table.register_fn_gen_type(call_expr.name, c.cur_generic_type) c.table.register_fn_gen_type(call_expr.name, generic_types)
} }
// call_expr.generic_type = c.unwrap_generic(call_expr.generic_type)
} }
// TODO: remove this for actual methods, use only for compiler magic // TODO: remove this for actual methods, use only for compiler magic
// FIXME: Argument count != 1 will break these // FIXME: Argument count != 1 will break these
@ -1451,7 +1460,7 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
c.type_implements(got_arg_typ, exp_arg_typ, arg.expr.position()) c.type_implements(got_arg_typ, exp_arg_typ, arg.expr.position())
continue continue
} }
if method.is_generic { if method.generic_names.len > 0 {
continue continue
} }
c.check_expected_call_arg(got_arg_typ, exp_arg_typ) or { c.check_expected_call_arg(got_arg_typ, exp_arg_typ) or {
@ -1505,15 +1514,16 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
call_expr.receiver_type = method.params[0].typ call_expr.receiver_type = method.params[0].typ
} }
call_expr.return_type = method.return_type call_expr.return_type = method.return_type
if method.is_generic && call_expr.generic_type == table.void_type { if method.generic_names.len != call_expr.generic_types.len {
// no type arguments given in call, attempt implicit instantiation // no type arguments given in call, attempt implicit instantiation
c.infer_fn_types(method, mut call_expr) c.infer_fn_types(method, mut call_expr)
} }
if call_expr.generic_type != table.void_type && method.return_type != 0 { // table.t_type { if call_expr.generic_types.len > 0 && method.return_type != 0 {
// Handle `foo<T>() T` => `foo<int>() int` => return int // Handle `foo<T>() T` => `foo<int>() int` => return int
return_sym := c.table.get_type_symbol(method.return_type) return_sym := c.table.get_type_symbol(method.return_type)
if return_sym.name == 'T' { if return_sym.name in method.generic_names {
mut typ := call_expr.generic_type generic_index := method.generic_names.index(return_sym.name)
mut typ := call_expr.generic_types[generic_index]
typ = typ.set_nr_muls(method.return_type.nr_muls()) typ = typ.set_nr_muls(method.return_type.nr_muls())
if method.return_type.has_flag(.optional) { if method.return_type.has_flag(.optional) {
typ = typ.set_flag(.optional) typ = typ.set_flag(.optional)
@ -1523,24 +1533,26 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
} else if return_sym.kind == .array { } else if return_sym.kind == .array {
elem_info := return_sym.info as table.Array elem_info := return_sym.info as table.Array
elem_sym := c.table.get_type_symbol(elem_info.elem_type) elem_sym := c.table.get_type_symbol(elem_info.elem_type)
if elem_sym.name == 'T' { if elem_sym.name in method.generic_names {
idx := c.table.find_or_register_array(call_expr.generic_type) generic_index := method.generic_names.index(elem_sym.name)
typ := call_expr.generic_types[generic_index]
idx := c.table.find_or_register_array(typ)
return table.new_type(idx) return table.new_type(idx)
} }
} else if return_sym.kind == .chan { } else if return_sym.kind == .chan {
elem_info := return_sym.info as table.Chan elem_info := return_sym.info as table.Chan
elem_sym := c.table.get_type_symbol(elem_info.elem_type) elem_sym := c.table.get_type_symbol(elem_info.elem_type)
if elem_sym.name == 'T' { if elem_sym.name in method.generic_names {
idx := c.table.find_or_register_chan(elem_info.elem_type, elem_info.elem_type.nr_muls() > idx := c.table.find_or_register_chan(elem_info.elem_type, elem_info.elem_type.nr_muls() >
0) 0)
return table.new_type(idx) return table.new_type(idx)
} }
} }
} }
if call_expr.generic_type.is_full() && !method.is_generic { if call_expr.generic_types.len > 0 && method.generic_names.len == 0 {
c.error('a non generic function called like a generic one', call_expr.generic_list_pos) c.error('a non generic function called like a generic one', call_expr.generic_list_pos)
} }
if method.is_generic { if method.generic_names.len > 0 {
return call_expr.return_type return call_expr.return_type
} }
return method.return_type return method.return_type
@ -1594,19 +1606,24 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
// TODO: impl typeof properly (probably not going to be a fn call) // TODO: impl typeof properly (probably not going to be a fn call)
return table.string_type return table.string_type
} }
if call_expr.generic_type.has_flag(.generic) { mut has_generic_generic := false // foo<T>() instead of foo<int>()
mut generic_types := []table.Type{}
for generic_type in call_expr.generic_types {
if generic_type.has_flag(.generic) {
has_generic_generic = true
generic_types << c.unwrap_generic(generic_type)
} else {
generic_types << generic_type
}
}
if has_generic_generic {
if c.mod != '' { if c.mod != '' {
// Need to prepend the module when adding a generic type to a function // Need to prepend the module when adding a generic type to a function
// `fn_gen_types['mymod.myfn'] == ['string', 'int']` c.table.register_fn_gen_type(c.mod + '.' + fn_name, generic_types)
c.table.register_fn_gen_type(c.mod + '.' + fn_name, c.cur_generic_type)
} else { } else {
c.table.register_fn_gen_type(fn_name, c.cur_generic_type) c.table.register_fn_gen_type(fn_name, generic_types)
} }
// call_expr.generic_type = c.unwrap_generic(call_expr.generic_type)
} }
// if c.fileis('json_test.v') {
// println(fn_name)
// }
if fn_name == 'json.encode' { if fn_name == 'json.encode' {
} else if fn_name == 'json.decode' && call_expr.args.len > 0 { } else if fn_name == 'json.decode' && call_expr.args.len > 0 {
expr := call_expr.args[0].expr expr := call_expr.args[0].expr
@ -1671,7 +1688,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
if c.pref.is_script && !found { if c.pref.is_script && !found {
os_name := 'os.$fn_name' os_name := 'os.$fn_name'
if f1 := c.table.find_fn(os_name) { if f1 := c.table.find_fn(os_name) {
if f1.is_generic && call_expr.generic_type != table.void_type { if f1.generic_names.len == call_expr.generic_types.len {
c.table.fn_gen_types[os_name] = c.table.fn_gen_types['${call_expr.mod}.$call_expr.name'] c.table.fn_gen_types[os_name] = c.table.fn_gen_types['${call_expr.mod}.$call_expr.name']
} }
call_expr.name = os_name call_expr.name = os_name
@ -1716,22 +1733,17 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
// builtin C.m*, C.s* only - temp // builtin C.m*, C.s* only - temp
c.warn('function `$f.name` must be called from an `unsafe` block', call_expr.pos) c.warn('function `$f.name` must be called from an `unsafe` block', call_expr.pos)
} }
if f.is_generic { for generic_type in call_expr.generic_types {
sym := c.table.get_type_symbol(call_expr.generic_type) sym := c.table.get_type_symbol(generic_type)
if sym.kind == .placeholder { if sym.kind == .placeholder {
c.error('unknown type `$sym.name`', call_expr.generic_list_pos) c.error('unknown type `$sym.name`', call_expr.generic_list_pos)
} }
} }
if f.is_generic && f.return_type.has_flag(.generic) { if f.generic_names.len > 0 && f.return_type.has_flag(.generic) {
rts := c.table.get_type_symbol(f.return_type) rts := c.table.get_type_symbol(f.return_type)
if rts.kind == .struct_ { if rts.info is table.Struct {
rts_info := rts.info as table.Struct if rts.info.generic_types.len > 0 {
if rts_info.generic_types.len > 0 { gts := c.table.get_type_symbol(call_expr.generic_types[0])
// TODO: multiple generic types
// for gt in rts_info.generic_types {
// gtss := c.table.get_type_symbol(gt)
// }
gts := c.table.get_type_symbol(call_expr.generic_type)
nrt := '$rts.name<$gts.name>' nrt := '$rts.name<$gts.name>'
idx := c.table.type_idxs[nrt] idx := c.table.type_idxs[nrt]
if idx == 0 { if idx == 0 {
@ -1836,56 +1848,101 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type {
if typ_sym.kind == .void && arg_typ_sym.kind == .string { if typ_sym.kind == .void && arg_typ_sym.kind == .string {
continue continue
} }
if f.is_generic { if f.generic_names.len > 0 {
continue continue
} }
c.error('$err in argument ${i + 1} to `$fn_name`', call_arg.pos) c.error('$err in argument ${i + 1} to `$fn_name`', call_arg.pos)
} }
} }
if f.is_generic && call_expr.generic_type == table.void_type { if f.generic_names.len != call_expr.generic_types.len {
// no type arguments given in call, attempt implicit instantiation // no type arguments given in call, attempt implicit instantiation
c.infer_fn_types(f, mut call_expr) c.infer_fn_types(f, mut call_expr)
} }
if call_expr.generic_type != table.void_type && f.return_type != 0 { // table.t_type { if call_expr.generic_types.len > 0 && f.return_type != 0 {
// TODO: this logic needs to be cleaned up; maybe make it reusable?
// Handle `foo<T>() T` => `foo<int>() int` => return int // Handle `foo<T>() T` => `foo<int>() int` => return int
return_sym := c.table.get_type_symbol(f.return_type) mut return_sym := c.table.get_type_symbol(f.return_type)
if return_sym.name == 'T' { if return_sym.name in f.generic_names {
mut typ := call_expr.generic_type index := f.generic_names.index(return_sym.name)
mut typ := call_expr.generic_types[index]
typ = typ.set_nr_muls(f.return_type.nr_muls()) typ = typ.set_nr_muls(f.return_type.nr_muls())
if f.return_type.has_flag(.optional) { if f.return_type.has_flag(.optional) {
typ = typ.set_flag(.optional) typ = typ.set_flag(.optional)
} }
call_expr.return_type = typ call_expr.return_type = typ
return typ return typ
} else if return_sym.kind == .array && return_sym.name.contains('T') { } else if return_sym.kind == .array {
mut info := return_sym.info as table.Array return_info := return_sym.info as table.Array
mut sym := c.table.get_type_symbol(info.elem_type) mut sym := c.table.get_type_symbol(return_info.elem_type)
mut dims := 1 mut dims := 1
for { for {
if sym.kind == .array { if mut sym.info is table.Array {
info = sym.info as table.Array sym = c.table.get_type_symbol(sym.info.elem_type)
sym = c.table.get_type_symbol(info.elem_type)
dims++ dims++
} else { } else {
break break
} }
} }
idx := c.table.find_or_register_array_with_dims(call_expr.generic_type, dims) if sym.name in f.generic_names {
typ := table.new_type(idx) generic_index := f.generic_names.index(sym.name)
call_expr.return_type = typ generic_type := call_expr.generic_types[generic_index]
return typ idx := c.table.find_or_register_array_with_dims(generic_type, dims)
} else if return_sym.kind == .chan && return_sym.name.contains('T') { typ := table.new_type(idx)
idx := c.table.find_or_register_chan(call_expr.generic_type, call_expr.generic_type.nr_muls() > call_expr.return_type = typ
0) return typ
}
} else if return_sym.kind == .chan {
return_info := return_sym.info as table.Chan
elem_sym := c.table.get_type_symbol(return_info.elem_type)
if elem_sym.name in f.generic_names {
generic_index := f.generic_names.index(elem_sym.name)
generic_type := call_expr.generic_types[generic_index]
idx := c.table.find_or_register_chan(generic_type, generic_type.nr_muls() > 0)
typ := table.new_type(idx)
call_expr.return_type = typ
return typ
}
} else if mut return_sym.info is table.MultiReturn {
mut types := []table.Type{}
for return_type in return_sym.info.types {
multi_return_sym := c.table.get_type_symbol(return_type)
if multi_return_sym.name in f.generic_names {
generic_index := f.generic_names.index(multi_return_sym.name)
types << call_expr.generic_types[generic_index]
}
}
idx := c.table.find_or_register_multi_return(types)
typ := table.new_type(idx) typ := table.new_type(idx)
call_expr.return_type = typ call_expr.return_type = typ
return typ return typ
} else if mut return_sym.info is table.Map {
mut type_changed := false
mut unwrapped_key_type := return_sym.info.key_type
mut unwrapped_value_type := return_sym.info.value_type
if return_sym.info.key_type.has_flag(.generic) {
key_sym := c.table.get_type_symbol(return_sym.info.key_type)
index := f.generic_names.index(key_sym.name)
unwrapped_key_type = call_expr.generic_types[index]
type_changed = true
}
if return_sym.info.value_type.has_flag(.generic) {
value_sym := c.table.get_type_symbol(return_sym.info.value_type)
index := f.generic_names.index(value_sym.name)
unwrapped_value_type = call_expr.generic_types[index]
type_changed = true
}
if type_changed {
idx := c.table.find_or_register_map(unwrapped_key_type, unwrapped_value_type)
typ := table.new_type(idx)
call_expr.return_type = typ
return typ
}
} }
} }
if call_expr.generic_type.is_full() && !f.is_generic { if call_expr.generic_types.len > 0 && f.generic_names.len == 0 {
c.error('a non generic function called like a generic one', call_expr.generic_list_pos) c.error('a non generic function called like a generic one', call_expr.generic_list_pos)
} }
if f.is_generic { if f.generic_names.len > 0 {
return call_expr.return_type return call_expr.return_type
} }
return f.return_type return f.return_type
@ -2047,8 +2104,10 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
mut name_type := 0 mut name_type := 0
match mut selector_expr.expr { match mut selector_expr.expr {
ast.Ident { ast.Ident {
if selector_expr.expr.name == 'T' { name := selector_expr.expr.name
name_type = table.Type(c.table.find_type_idx('T')).set_flag(.generic) valid_generic := c.cur_fn.generic_params.filter(it.name == name).len != 0
if valid_generic {
name_type = table.Type(c.table.find_type_idx(name)).set_flag(.generic)
} }
} }
// Note: in future typeof() should be a type known at compile-time // Note: in future typeof() should be a type known at compile-time
@ -2184,9 +2243,11 @@ pub fn (mut c Checker) return_stmt(mut return_stmt ast.Return) {
} }
exp_is_optional := expected_type.has_flag(.optional) exp_is_optional := expected_type.has_flag(.optional)
mut expected_types := [expected_type] mut expected_types := [expected_type]
if expected_type_sym.kind == .multi_return { if expected_type_sym.info is table.MultiReturn {
mr_info := expected_type_sym.info as table.MultiReturn expected_types = expected_type_sym.info.types
expected_types = mr_info.types if c.cur_generic_types.len > 0 {
expected_types = expected_types.map(c.unwrap_generic(it))
}
} }
mut got_types := []table.Type{} mut got_types := []table.Type{}
for expr in return_stmt.exprs { for expr in return_stmt.exprs {
@ -3230,11 +3291,17 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) {
c.expected_type = table.void_type c.expected_type = table.void_type
} }
[inline]
pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type { pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type {
if typ.has_flag(.generic) { if typ.has_flag(.generic) {
// return c.cur_generic_type sym := c.table.get_type_symbol(typ)
return c.cur_generic_type.derive(typ).clear_flag(.generic) mut idx := 0
for i, generic_param in c.cur_fn.generic_params {
if generic_param.name == sym.name {
idx = i
break
}
}
return c.cur_generic_types[idx].derive(typ).clear_flag(.generic)
} }
return typ return typ
} }
@ -3695,10 +3762,7 @@ pub fn (mut c Checker) ident(mut ident ast.Ident) table.Type {
// second use // second use
if ident.kind in [.constant, .global, .variable] { if ident.kind in [.constant, .global, .variable] {
info := ident.info as ast.IdentVar info := ident.info as ast.IdentVar
// if info.typ == table.t_type {
// Got a var with type T, return current generic type // Got a var with type T, return current generic type
// return c.cur_generic_type
// }
return info.typ return info.typ
} else if ident.kind == .function { } else if ident.kind == .function {
info := ident.info as ast.IdentFn info := ident.info as ast.IdentFn
@ -4630,7 +4694,8 @@ fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool {
!different !different
} }
} else { } else {
c.error('invalid `\$if` condition: $cond.left.type_name()', cond.pos) c.error('invalid `\$if` condition: ${cond.left.type_name()}1',
cond.pos)
} }
} }
else { else {
@ -5223,6 +5288,7 @@ fn (mut c Checker) fetch_and_verify_orm_fields(info table.Struct, pos token.Posi
fn (mut c Checker) post_process_generic_fns() { fn (mut c Checker) post_process_generic_fns() {
// Loop thru each generic function concrete type. // Loop thru each generic function concrete type.
// Check each specific fn instantiation. // Check each specific fn instantiation.
for i in 0 .. c.file.generic_fns.len { for i in 0 .. c.file.generic_fns.len {
if c.table.fn_gen_types.len == 0 { if c.table.fn_gen_types.len == 0 {
// no concrete types, so just skip: // no concrete types, so just skip:
@ -5230,20 +5296,20 @@ fn (mut c Checker) post_process_generic_fns() {
} }
mut node := c.file.generic_fns[i] mut node := c.file.generic_fns[i]
c.mod = node.mod c.mod = node.mod
for gen_type in c.table.fn_gen_types[node.name] { for gen_types in c.table.fn_gen_types[node.name] {
c.cur_generic_type = gen_type c.cur_generic_types = gen_types
c.fn_decl(mut node) c.fn_decl(mut node)
if node.name in ['vweb.run_app', 'vweb.run'] { if node.name in ['vweb.run_app', 'vweb.run'] {
c.vweb_gen_types << gen_type c.vweb_gen_types << gen_types
} }
} }
c.cur_generic_type = 0 c.cur_generic_types = []
} }
} }
fn (mut c Checker) fn_decl(mut node ast.FnDecl) { fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
c.returns = false c.returns = false
if node.is_generic && c.cur_generic_type == 0 { if node.generic_params.len > 0 && c.cur_generic_types.len == 0 {
// Just remember the generic function for now. // Just remember the generic function for now.
// It will be processed later in c.post_process_generic_fns, // It will be processed later in c.post_process_generic_fns,
// after all other normal functions are processed. // after all other normal functions are processed.

View File

@ -1626,9 +1626,15 @@ pub fn (mut f Fmt) at_expr(node ast.AtExpr) {
} }
fn (mut f Fmt) write_generic_if_require(node ast.CallExpr) { fn (mut f Fmt) write_generic_if_require(node ast.CallExpr) {
if node.generic_type != 0 && node.generic_type != table.void_type { if node.generic_types.len > 0 {
f.write('<') f.write('<')
f.write(f.table.type_to_str(node.generic_type)) for i, generic_type in node.generic_types {
is_last := i == node.generic_types.len - 1
f.write(f.table.type_to_str(generic_type))
if !is_last {
f.write(', ')
}
}
f.write('>') f.write('>')
} }
} }

View File

@ -0,0 +1,2 @@
fn test<A, B, D, E>() {
}

View File

@ -102,15 +102,15 @@ mut:
is_builtin_mod bool is_builtin_mod bool
hotcode_fn_names []string hotcode_fn_names []string
embedded_files []ast.EmbeddedFile embedded_files []ast.EmbeddedFile
// cur_fn ast.FnDecl cur_fn ast.FnDecl
cur_generic_type table.Type // `int`, `string`, etc in `foo<T>()` cur_generic_types []table.Type // `int`, `string`, etc in `foo<T>()`
sql_i int sql_i int
sql_stmt_name string sql_stmt_name string
sql_side SqlExprSide // left or right, to distinguish idents in `name == name` sql_side SqlExprSide // left or right, to distinguish idents in `name == name`
inside_vweb_tmpl bool inside_vweb_tmpl bool
inside_return bool inside_return bool
inside_or_block bool inside_or_block bool
strs_to_free0 []string // strings.Builder strs_to_free0 []string // strings.Builder
// strs_to_free []string // strings.Builder // strs_to_free []string // strings.Builder
inside_call bool inside_call bool
for_in_mul_val_name string for_in_mul_val_name string
@ -747,7 +747,7 @@ pub fn (mut g Gen) write_multi_return_types() {
} }
g.typedefs.writeln('typedef struct $sym.cname $sym.cname;') g.typedefs.writeln('typedef struct $sym.cname $sym.cname;')
g.type_definitions.writeln('struct $sym.cname {') g.type_definitions.writeln('struct $sym.cname {')
info := sym.info as table.MultiReturn info := sym.mr_info()
for i, mr_typ in info.types { for i, mr_typ in info.types {
type_name := g.typ(mr_typ) type_name := g.typ(mr_typ)
g.type_definitions.writeln('\t$type_name arg$i;') g.type_definitions.writeln('\t$type_name arg$i;')
@ -1029,7 +1029,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
// their functions in main.c. // their functions in main.c.
if node.mod != 'main' && if node.mod != 'main' &&
node.mod != 'help' && !should_bundle_module && !g.file.path.ends_with('_test.v') && node.mod != 'help' && !should_bundle_module && !g.file.path.ends_with('_test.v') &&
!node.is_generic node.generic_params.len == 0
{ {
skip = true skip = true
} }
@ -2881,7 +2881,7 @@ fn (mut g Gen) expr(node ast.Expr) {
fn (mut g Gen) type_name(type_ table.Type) { fn (mut g Gen) type_name(type_ table.Type) {
mut typ := type_ mut typ := type_
if typ.has_flag(.generic) { if typ.has_flag(.generic) {
typ = g.cur_generic_type typ = g.cur_generic_types[0]
} }
s := g.table.type_to_str(typ) s := g.table.type_to_str(typ)
g.write('_SLIT("${util.strip_main_name(s)}")') g.write('_SLIT("${util.strip_main_name(s)}")')
@ -5642,10 +5642,15 @@ fn (mut g Gen) go_stmt(node ast.GoStmt, joinable bool) string {
expr := node.call_expr expr := node.call_expr
mut name := expr.name // util.no_dots(expr.name) mut name := expr.name // util.no_dots(expr.name)
// TODO: fn call is duplicated. merge with fn_call(). // TODO: fn call is duplicated. merge with fn_call().
if expr.generic_type != table.void_type && expr.generic_type != 0 { for i, generic_type in expr.generic_types {
// Using _T_ to differentiate between get<string> and get_string if generic_type != table.void_type && generic_type != 0 {
// `foo<int>()` => `foo_T_int()` // Using _T_ to differentiate between get<string> and get_string
name += '_T_' + g.typ(expr.generic_type) // `foo<int>()` => `foo_T_int()`
if i == 0 {
name += '_T'
}
name += '_' + g.typ(generic_type)
}
} }
if expr.is_method { if expr.is_method {
receiver_sym := g.table.get_type_symbol(expr.receiver_type) receiver_sym := g.table.get_type_symbol(expr.receiver_type)

View File

@ -28,20 +28,20 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl, skip bool) {
// if g.fileis('vweb.v') { // if g.fileis('vweb.v') {
// println('\ngen_fn_decl() $it.name $it.is_generic $g.cur_generic_type') // println('\ngen_fn_decl() $it.name $it.is_generic $g.cur_generic_type')
// } // }
if it.is_generic && g.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion if it.generic_params.len > 0 && g.cur_generic_types.len == 0 { // need the cur_generic_type check to avoid inf. recursion
// loop thru each generic type and generate a function // loop thru each generic type and generate a function
for gen_type in g.table.fn_gen_types[it.name] { for gen_types in g.table.fn_gen_types[it.name] {
sym := g.table.get_type_symbol(gen_type)
if g.pref.is_verbose { if g.pref.is_verbose {
println('gen fn `$it.name` for type `$sym.name`') syms := gen_types.map(g.table.get_type_symbol(it))
println('gen fn `$it.name` for type `${syms.map(it.name).join(', ')}`')
} }
g.cur_generic_type = gen_type g.cur_generic_types = gen_types
g.gen_fn_decl(it, skip) g.gen_fn_decl(it, skip)
} }
g.cur_generic_type = 0 g.cur_generic_types = []
return return
} }
// g.cur_fn = it g.cur_fn = it
fn_start_pos := g.out.len fn_start_pos := g.out.len
g.write_v_source_line_info(it.pos) g.write_v_source_line_info(it.pos)
msvc_attrs := g.write_fn_attrs(it.attrs) msvc_attrs := g.write_fn_attrs(it.attrs)
@ -69,11 +69,14 @@ fn (mut g Gen) gen_fn_decl(it ast.FnDecl, skip bool) {
name = c_name(name) name = c_name(name)
} }
mut type_name := g.typ(it.return_type) mut type_name := g.typ(it.return_type)
if g.cur_generic_type != 0 { if g.cur_generic_types.len > 0 {
// foo<T>() => foo_T_int(), foo_T_string() etc // foo<T>() => foo_T_int(), foo_T_string() etc
gen_name := g.typ(g.cur_generic_type)
// Using _T_ to differentiate between get<string> and get_string // Using _T_ to differentiate between get<string> and get_string
name += '_T_' + gen_name name += '_T'
for generic_type in g.cur_generic_types {
gen_name := g.typ(generic_type)
name += '_' + gen_name
}
} }
// if g.pref.show_cc && it.is_builtin { // if g.pref.show_cc && it.is_builtin {
// println(name) // println(name)
@ -314,11 +317,17 @@ fn (mut g Gen) call_expr(node ast.CallExpr) {
} }
} }
[inline]
pub fn (g &Gen) unwrap_generic(typ table.Type) table.Type { pub fn (g &Gen) unwrap_generic(typ table.Type) table.Type {
if typ.has_flag(.generic) { if typ.has_flag(.generic) {
// return g.cur_generic_type sym := g.table.get_type_symbol(typ)
return g.cur_generic_type.derive(typ).clear_flag(.generic) mut idx := 0
for i, generic_param in g.cur_fn.generic_params {
if generic_param.name == sym.name {
idx = i
break
}
}
return g.cur_generic_types[0].derive(typ).clear_flag(.generic)
} }
return typ return typ
} }
@ -442,10 +451,15 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
} }
} }
} }
if node.generic_type != table.void_type && node.generic_type != 0 { for i, generic_type in node.generic_types {
// Using _T_ to differentiate between get<string> and get_string if generic_type != table.void_type && generic_type != 0 {
// `foo<int>()` => `foo_T_int()` // Using _T_ to differentiate between get<string> and get_string
name += '_T_' + g.typ(node.generic_type) // `foo<int>()` => `foo_T_int()`
if i == 0 {
name += '_T'
}
name += '_' + g.typ(generic_type)
}
} }
// TODO2 // TODO2
// g.generate_tmp_autofree_arg_vars(node, name) // g.generate_tmp_autofree_arg_vars(node, name)
@ -586,10 +600,13 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
} else { } else {
name = c_name(name) name = c_name(name)
} }
if node.generic_type != table.void_type && node.generic_type != 0 { for i, generic_type in node.generic_types {
// Using _T_ to differentiate between get<string> and get_string // Using _T_ to differentiate between get<string> and get_string
// `foo<int>()` => `foo_T_int()` // `foo<int>()` => `foo_T_int()`
name += '_T_' + g.typ(node.generic_type) if i == 0 {
name += '_T'
}
name += '_' + g.typ(generic_type)
} }
// TODO2 // TODO2
// cgen shouldn't modify ast nodes, this should be moved // cgen shouldn't modify ast nodes, this should be moved

View File

@ -9,7 +9,6 @@ import v.token
import v.util import v.util
pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExpr { pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExpr {
// pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.Expr {
first_pos := p.tok.position() first_pos := p.tok.position()
mut fn_name := if language == .c { mut fn_name := if language == .c {
'C.$p.check_name()' 'C.$p.check_name()'
@ -29,24 +28,20 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
p.expr_mod = '' p.expr_mod = ''
or_kind = .block or_kind = .block
} }
mut generic_type := table.void_type mut generic_types := []table.Type{}
mut generic_list_pos := p.tok.position() mut generic_list_pos := p.tok.position()
if p.tok.kind == .lt { if p.tok.kind == .lt {
// `foo<int>(10)` // `foo<int>(10)`
p.next() // `<`
p.expr_mod = '' p.expr_mod = ''
generic_type = p.parse_type() generic_types = p.parse_generic_type_list()
p.check(.gt) // `>`
generic_list_pos = generic_list_pos.extend(p.prev_tok.position()) generic_list_pos = generic_list_pos.extend(p.prev_tok.position())
// In case of `foo<T>()` // In case of `foo<T>()`
// T is unwrapped and registered in the checker. // T is unwrapped and registered in the checker.
if !generic_type.has_flag(.generic) { full_generic_fn_name := if fn_name.contains('.') { fn_name } else { p.prepend_mod(fn_name) }
full_generic_fn_name := if fn_name.contains('.') { has_generic_generic := generic_types.filter(it.has_flag(.generic)).len > 0
fn_name if !has_generic_generic {
} else { // will be added in checker
p.prepend_mod(fn_name) p.table.register_fn_gen_type(full_generic_fn_name, generic_types)
}
p.table.register_fn_gen_type(full_generic_fn_name, generic_type)
} }
} }
p.check(.lpar) p.check(.lpar)
@ -100,7 +95,7 @@ pub fn (mut p Parser) call_expr(language table.Language, mod string) ast.CallExp
mod: p.mod mod: p.mod
pos: pos pos: pos
language: language language: language
generic_type: generic_type generic_types: generic_types
generic_list_pos: generic_list_pos generic_list_pos: generic_list_pos
or_block: ast.OrExpr{ or_block: ast.OrExpr{
stmts: or_stmts stmts: or_stmts
@ -291,12 +286,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
p.next() p.next()
} }
// <T> // <T>
is_generic := p.tok.kind == .lt generic_params := p.parse_generic_params()
if is_generic {
p.next()
p.next()
p.check(.gt)
}
// Args // Args
args2, are_args_type_only, is_variadic := p.fn_args() args2, are_args_type_only, is_variadic := p.fn_args()
params << args2 params << args2
@ -353,7 +343,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
params: params params: params
return_type: return_type return_type: return_type
is_variadic: is_variadic is_variadic: is_variadic
is_generic: is_generic generic_names: generic_params.map(it.name)
is_pub: is_pub is_pub: is_pub
is_deprecated: is_deprecated is_deprecated: is_deprecated
is_unsafe: is_unsafe is_unsafe: is_unsafe
@ -377,7 +367,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
params: params params: params
return_type: return_type return_type: return_type
is_variadic: is_variadic is_variadic: is_variadic
is_generic: is_generic generic_names: generic_params.map(it.name)
is_pub: is_pub is_pub: is_pub
is_deprecated: is_deprecated is_deprecated: is_deprecated
is_unsafe: is_unsafe is_unsafe: is_unsafe
@ -416,12 +406,12 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
is_deprecated: is_deprecated is_deprecated: is_deprecated
is_direct_arr: is_direct_arr is_direct_arr: is_direct_arr
is_pub: is_pub is_pub: is_pub
is_generic: is_generic
is_variadic: is_variadic is_variadic: is_variadic
receiver: ast.Field{ receiver: ast.Field{
name: rec_name name: rec_name
typ: rec_type typ: rec_type
} }
generic_params: generic_params
receiver_pos: receiver_pos receiver_pos: receiver_pos
is_method: is_method is_method: is_method
method_type_pos: rec_type_pos method_type_pos: rec_type_pos
@ -440,6 +430,62 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
return fn_decl return fn_decl
} }
fn (mut p Parser) parse_generic_params() []ast.GenericParam {
mut param_names := []string{}
if p.tok.kind != .lt {
return []ast.GenericParam{}
}
p.check(.lt)
mut first_done := false
mut count := 0
for p.tok.kind !in [.gt, .eof] {
if first_done {
p.check(.comma)
}
name := p.tok.lit
if name.len > 0 && !name[0].is_capital() {
p.error('generic parameter needs to be uppercase')
}
if name.len > 1 {
p.error('generic parameter name needs to be exactly one char')
}
if is_generic_name_reserved(p.tok.lit) {
p.error('`$p.tok.lit` is a reserved name and cannot be used for generics')
}
if name in param_names {
p.error('duplicated generic parameter `$name`')
}
if count > 8 {
p.error('cannot have more than 9 generic parameters')
}
p.check(.name)
param_names << name
first_done = true
count++
}
p.check(.gt)
return param_names.map(ast.GenericParam{it})
}
// is_valid_generic_character returns true if the character is reserved for someting else.
fn is_generic_name_reserved(name string) bool {
// C is used for cinterop
if name == 'C' {
return true
}
return false
}
// is_generic_name returns true if the current token is a generic name.
fn is_generic_name(name string) bool {
return name.len == 1 && name.is_capital() && !is_generic_name_reserved(name)
}
// is_generic_name returns true if the current token is a generic name.
fn (p Parser) is_generic_name() bool {
return p.tok.kind == .name && is_generic_name(p.tok.lit)
}
fn (mut p Parser) anon_fn() ast.AnonFn { fn (mut p Parser) anon_fn() ast.AnonFn {
pos := p.tok.position() pos := p.tok.position()
p.check(.key_fn) p.check(.key_fn)

View File

@ -1099,9 +1099,9 @@ fn (p &Parser) is_generic_call() bool {
// use heuristics to detect `func<T>()` from `var < expr` // use heuristics to detect `func<T>()` from `var < expr`
return !lit0_is_capital && p.peek_tok.kind == .lt && (match p.peek_tok2.kind { return !lit0_is_capital && p.peek_tok.kind == .lt && (match p.peek_tok2.kind {
.name { .name {
// maybe `f<int>`, `f<map[` // maybe `f<int>`, `f<map[`, f<string,
(p.peek_tok2.kind == .name && (p.peek_tok2.kind == .name &&
p.peek_tok3.kind == .gt) || p.peek_tok3.kind in [.gt, .comma]) ||
(p.peek_tok2.lit == 'map' && p.peek_tok3.kind == .lsbr) (p.peek_tok2.lit == 'map' && p.peek_tok3.kind == .lsbr)
} }
.lsbr { .lsbr {
@ -1309,7 +1309,7 @@ pub fn (mut p Parser) name_expr() ast.Expr {
return p.struct_init(false) // short_syntax: false return p.struct_init(false) // short_syntax: false
} else if p.peek_tok.kind == .dot && (lit0_is_capital && !known_var && language == .v) { } else if p.peek_tok.kind == .dot && (lit0_is_capital && !known_var && language == .v) {
// T.name // T.name
if p.tok.lit == 'T' { if p.is_generic_name() {
pos := p.tok.position() pos := p.tok.position()
name := p.check_name() name := p.check_name()
p.check(.dot) p.check(.dot)
@ -1494,18 +1494,18 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
} }
// Method call // Method call
// TODO move to fn.v call_expr() // TODO move to fn.v call_expr()
mut generic_type := table.void_type mut generic_types := []table.Type{}
mut generic_list_pos := p.tok.position() mut generic_list_pos := p.tok.position()
if is_generic_call { if is_generic_call {
// `g.foo<int>(10)` // `g.foo<int>(10)`
p.next() // `<` generic_types = p.parse_generic_type_list()
generic_type = p.parse_type()
p.check(.gt) // `>`
generic_list_pos = generic_list_pos.extend(p.prev_tok.position()) generic_list_pos = generic_list_pos.extend(p.prev_tok.position())
// In case of `foo<T>()` // In case of `foo<T>()`
// T is unwrapped and registered in the checker. // T is unwrapped and registered in the checker.
if !generic_type.has_flag(.generic) { has_generic_generic := generic_types.filter(it.has_flag(.generic)).len > 0
p.table.register_fn_gen_type(field_name, generic_type) if !has_generic_generic {
// will be added in checker
p.table.register_fn_gen_type(field_name, generic_types)
} }
} }
if p.tok.kind == .lpar { if p.tok.kind == .lpar {
@ -1550,7 +1550,7 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
args: args args: args
pos: pos pos: pos
is_method: true is_method: true
generic_type: generic_type generic_types: generic_types
generic_list_pos: generic_list_pos generic_list_pos: generic_list_pos
or_block: ast.OrExpr{ or_block: ast.OrExpr{
stmts: or_stmts stmts: or_stmts
@ -1592,6 +1592,24 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
return sel_expr return sel_expr
} }
fn (mut p Parser) parse_generic_type_list() []table.Type {
mut types := []table.Type{}
if p.tok.kind != .lt {
return types
}
p.next() // `<`
mut first_done := false
for p.tok.kind !in [.eof, .gt] {
if first_done {
p.check(.comma)
}
types << p.parse_type()
first_done = true
}
p.check(.gt) // `>`
return types
}
// `.green` // `.green`
// `pref.BuildMode.default_mode` // `pref.BuildMode.default_mode`
fn (mut p Parser) enum_val() ast.EnumVal { fn (mut p Parser) enum_val() ast.EnumVal {

View File

@ -32,7 +32,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr {
node = p.sql_expr() node = p.sql_expr()
p.inside_match = false p.inside_match = false
} else { } else {
if p.inside_if && p.tok.lit == 'T' { if p.inside_if && p.is_generic_name() {
// $if T is string {} // $if T is string {}
p.expecting_type = true p.expecting_type = true
} }

View File

@ -0,0 +1,3 @@
vlib/v/parser/tests/duplicated_generic_err.vv:1:12: error: duplicated generic parameter `A`
1 | fn test<A, A>() {}
| ^

View File

@ -0,0 +1 @@
fn test<A, A>() {}

View File

@ -0,0 +1,3 @@
vlib/v/parser/tests/generic_lowercase_err.vv:1:22: error: generic parameter needs to be uppercase
1 | fn lowercase_generic<a>() {}
| ^

View File

@ -0,0 +1 @@
fn lowercase_generic<a>() {}

View File

@ -0,0 +1,3 @@
vlib/v/parser/tests/long_generic_err.vv:1:17: error: generic parameter name needs to be exactly one char
1 | fn long_generic<Abc>() {}
| ~~~

View File

@ -0,0 +1 @@
fn long_generic<Abc>() {}

View File

@ -0,0 +1,3 @@
vlib/v/parser/tests/too_many_generics_err.vv:1:40: error: cannot have more than 9 generic parameters
1 | fn too_many<A, B, D, E, F, G, H, I, J, K>() {}
| ^

View File

@ -0,0 +1 @@
fn too_many<A, B, D, E, F, G, H, I, J, K>() {}

View File

@ -16,7 +16,7 @@ pub mut:
modules []string // Topologically sorted list of all modules registered by the application modules []string // Topologically sorted list of all modules registered by the application
cflags []cflag.CFlag cflags []cflag.CFlag
redefined_fns []string redefined_fns []string
fn_gen_types map[string][]Type // for generic functions fn_gen_types map[string][][]Type // for generic functions
cmod_prefix string // needed for table.type_to_str(Type) while vfmt; contains `os.` cmod_prefix string // needed for table.type_to_str(Type) while vfmt; contains `os.`
is_fmt bool is_fmt bool
} }
@ -27,7 +27,7 @@ pub:
return_type Type return_type Type
is_variadic bool is_variadic bool
language Language language Language
is_generic bool generic_names []string
is_pub bool is_pub bool
is_deprecated bool is_deprecated bool
is_unsafe bool is_unsafe bool
@ -42,8 +42,8 @@ pub mut:
fn (f &Fn) method_equals(o &Fn) bool { fn (f &Fn) method_equals(o &Fn) bool {
return f.params[1..].equals(o.params[1..]) && f.return_type == o.return_type && f.is_variadic == return f.params[1..].equals(o.params[1..]) && f.return_type == o.return_type && f.is_variadic ==
o.is_variadic && f.language == o.language && f.is_generic == o.is_generic && f.is_pub == o.is_variadic && f.language == o.language && f.generic_names == o.generic_names &&
o.is_pub && f.mod == o.mod && f.name == o.name f.is_pub == o.is_pub && f.mod == o.mod && f.name == o.name
} }
pub struct Param { pub struct Param {
@ -723,14 +723,12 @@ pub fn (t &Table) mktyp(typ Type) Type {
} }
} }
pub fn (mut table Table) register_fn_gen_type(fn_name string, typ Type) { pub fn (mut table Table) register_fn_gen_type(fn_name string, types []Type) {
mut a := table.fn_gen_types[fn_name] mut a := table.fn_gen_types[fn_name]
if typ in a { if types in a {
return return
} }
a << typ a << types
// sym := table.get_type_symbol(typ)
// println('registering fn ($fn_name) gen type $sym.name')
table.fn_gen_types[fn_name] = a table.fn_gen_types[fn_name] = a
} }

View File

@ -29,13 +29,13 @@ fn test_ct_expressions() {
} }
} }
fn generic_t_is<T>() T { fn generic_t_is<O>() O {
$if T is string { $if O is string {
return 'It\'s a string!' return 'It\'s a string!'
} $else { } $else {
return T{} return O{}
} }
return T{} return O{}
} }
struct GenericTIsTest {} struct GenericTIsTest {}

View File

@ -27,7 +27,6 @@ fn test_identity() {
fn test_plus() { fn test_plus() {
a := plus<int>(2, 3) a := plus<int>(2, 3)
println(a)
assert a == 5 assert a == 5
assert plus<int>(10, 1) == 11 assert plus<int>(10, 1) == 11
assert plus<string>('a', 'b') == 'ab' assert plus<string>('a', 'b') == 'ab'
@ -47,11 +46,9 @@ fn test_foo() {
} }
fn create<T>() { fn create<T>() {
a := T{} _ := T{}
println(a.name)
mut xx := T{} mut xx := T{}
xx.name = 'foo' xx.name = 'foo'
println(xx.name)
assert xx.name == 'foo' assert xx.name == 'foo'
xx.init() xx.init()
} }
@ -73,11 +70,11 @@ fn (c City) init() {
} }
fn mut_arg<T>(mut x T) { fn mut_arg<T>(mut x T) {
println(x.name) // = 'foo' // println(x.name) // = 'foo'
} }
fn mut_arg2<T>(mut x T) T { fn mut_arg2<T>(mut x T) T {
println(x.name) // = 'foo' // println(x.name) // = 'foo'
return *x return *x
} }
@ -128,7 +125,6 @@ fn test_ptr() {
assert *ptr('aa') == 'aa' assert *ptr('aa') == 'aa'
} }
/*
fn map_f<T,U>(l []T, f fn(T)U) []U { fn map_f<T,U>(l []T, f fn(T)U) []U {
mut r := []U{} mut r := []U{}
for e in l { for e in l {
@ -137,6 +133,7 @@ fn map_f<T,U>(l []T, f fn(T)U) []U {
return r return r
} }
/*
fn foldl<T>(l []T, nil T, f fn(T,T)T) T { fn foldl<T>(l []T, nil T, f fn(T,T)T) T {
mut r := nil mut r := nil
for e in l { for e in l {
@ -144,7 +141,7 @@ fn foldl<T>(l []T, nil T, f fn(T,T)T) T {
} }
return r return r
} }
*/
fn square(x int) int { fn square(x int) int {
return x*x return x*x
} }
@ -153,18 +150,17 @@ fn mul_int(x int, y int) int {
return x*y return x*y
} }
fn assert_eq<T>(a, b T) { fn assert_eq<T>(a T, b T) {
r := a == b r := a == b
println('$a == $b: ${r.str()}')
assert r assert r
} }
fn print_nice<T>(x T, indent int) { fn print_nice<T>(x T, indent int) string {
mut space := '' mut space := ''
for _ in 0..indent { for _ in 0..indent {
space = space + ' ' space = space + ' '
} }
println('$space$x') return '$space$x'
} }
fn test_generic_fn() { fn test_generic_fn() {
@ -174,9 +170,9 @@ fn test_generic_fn() {
assert_eq(plus(i64(4), i64(6)), i64(10)) assert_eq(plus(i64(4), i64(6)), i64(10))
a := [1,2,3,4] a := [1,2,3,4]
b := map_f(a, square) b := map_f(a, square)
assert_eq(sum(b), 30) // 1+4+9+16 = 30 assert_eq(sum(b), 30) // 1+4+9+16 = 30
assert_eq(foldl(b, 1, mul_int), 576) // 1*4*9*16 = 576 //assert_eq(foldl(b, 1, mul_int), 576) // 1*4*9*16 = 576
print_nice('str', 8) assert print_nice('str', 8) == ' str'
} }
struct Point { struct Point {
@ -185,7 +181,7 @@ mut:
y f64 y f64
} }
fn (mut p Point) translate<T>(x, y T) { fn (mut p Point) translate<T>(x T, y T) {
p.x += x p.x += x
p.y += y p.y += y
} }
@ -213,7 +209,7 @@ fn test_generic_fn_in_for_in_expression() {
assert value == 'a' assert value == 'a'
} }
} }
*/
// test generic struct // test generic struct
struct DB { struct DB {
driver string driver string
@ -247,9 +243,7 @@ fn test_generic_struct() {
name: 'joe' name: 'joe'
} }
} }
// a.model.name = 'joe'
assert a.model.name == 'joe' assert a.model.name == 'joe'
println('a.model.name: $a.model.name')
mut b := Repo<Group, Permission>{ mut b := Repo<Group, Permission>{
permission: Permission{ permission: Permission{
name: 'superuser' name: 'superuser'
@ -258,15 +252,8 @@ fn test_generic_struct() {
b.model.name = 'admins' b.model.name = 'admins'
assert b.model.name == 'admins' assert b.model.name == 'admins'
assert b.permission.name == 'superuser' assert b.permission.name == 'superuser'
println('b.model.name: $b.model.name')
println('b.permission.name: $b.permission.name')
assert typeof(a.model).name == 'User' assert typeof(a.model).name == 'User'
assert typeof(b.model).name == 'Group' assert typeof(b.model).name == 'Group'
println('typeof(a.model): ' + typeof(a.model).name)
println('typeof(b.model): ' + typeof(b.model).name)
// mut x := new_repo<User>(DB{})
// x.model.name = 'joe2'
// println(x.model.name)
} }
struct Foo<T> { struct Foo<T> {
@ -322,3 +309,71 @@ fn test_generic_fn_with_variadics(){
p('Good', 'morning', 'world') p('Good', 'morning', 'world')
} }
*/ */
struct Context {}
struct App {
mut:
context Context
}
fn test<T>(mut app T) {
nested_test<T>(mut app)
}
fn nested_test<T>(mut app T) {
app.context = Context {}
}
fn test_pass_generic_to_nested_function() {
mut app := App{}
test(mut app)
}
/*
struct NestedGeneric {}
fn (ng NestedGeneric) nested_test<T>(mut app T) {
app.context = Context {}
}
fn method_test<T>(mut app T) {
ng := NestedGeneric{}
ng.nested_test<T>(app)
}
fn test_pass_generic_to_nested_method() {
mut app := App{}
method_test(mut app)
}*/
fn generic_return_map<M>() map[string]M {
return {'': M{}}
}
fn test_generic_return_map() {
assert typeof(generic_return_map<string>()).name == 'map[string]string'
}
/*
fn generic_return_nested_map<M>() map[string]map[string]M {
return {'': {'': M{}}}
}
fn test_generic_return_nested_map() {
assert typeof(generic_return_nested_map<string>()).name == 'map[string]map[string]string'
}*/
/*
fn multi_return<A, B>() (A, B) {
return A{}, B{}
}
struct Foo1{}
struct Foo2{}
struct Foo3{}
struct Foo4{}
fn test_multi_return() {
// TODO: multi_return<Foo1, Foo2>()
// TODO: temulti_returnst<Foo3, Foo4>()
}*/