diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 7d01b4d03f..77cd5acc65 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -26,6 +26,13 @@ pub fn (node &FnDecl) fkey() string { return node.name } +pub fn (node &Fn) fkey() string { + if node.is_method { + return '${int(node.receiver_type)}.$node.name' + } + return node.name +} + pub fn (node &CallExpr) fkey() string { if node.is_method { return '${int(node.receiver_type)}.$node.name' diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 13c0636d8b..fe62ba722c 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -89,6 +89,7 @@ pub: is_main bool // `fn main(){}` is_test bool // `fn test_abc(){}` is_keep_alive bool // passed memory must not be freed (by GC) before function returns + is_method bool // true for `fn (x T) name()`, and for interface declarations (which are also for methods) no_body bool // a pure declaration like `fn abc(x int)`; used in .vh files, C./JS. fns. mod string file string @@ -97,6 +98,7 @@ pub: return_type_pos token.Position pub mut: return_type Type + receiver_type Type // != 0, when .is_method == true name string params []Param source_fn voidptr // set in the checker, while processing fn declarations @@ -1608,7 +1610,7 @@ pub fn (mut t Table) unwrap_generic_type(typ Type, generic_names []string, concr } if final_concrete_types.len > 0 { for method in ts.methods { - t.register_fn_concrete_types(method.name, final_concrete_types) + t.register_fn_concrete_types(method.fkey(), final_concrete_types) } } } @@ -1730,7 +1732,7 @@ pub fn (mut t Table) generic_insts_to_concrete() { parent_sym := t.sym(parent_info.parent_type) for method in parent_sym.methods { if method.generic_names.len == info.concrete_types.len { - t.register_fn_concrete_types(method.name, info.concrete_types) + t.register_fn_concrete_types(method.fkey(), info.concrete_types) } } } else { diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index af71bc69d0..4942a7bee5 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -851,7 +851,7 @@ pub fn (mut c Checker) infer_fn_generic_types(func ast.Fn, mut node ast.CallExpr node.concrete_types << typ } - if c.table.register_fn_concrete_types(func.name, inferred_types) { + if c.table.register_fn_concrete_types(func.fkey(), inferred_types) { c.need_recheck_generic_fns = true } } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f9d643c7f2..99ec71a5c1 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -290,12 +290,20 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { c.timers.start('checker_post_process_generic_fns') last_file := c.file // post process generic functions. must be done after all files have been - // checked, to ensure all generic calls are processed as this information - // is needed when the generic type is auto inferred from the call argument - // Check more times if there are more new registered fn concrete types + // checked, to ensure all generic calls are processed, as this information + // is needed when the generic type is auto inferred from the call argument. + // we may have to loop several times, if there were more concrete types found. + mut post_process_generic_fns_iterations := 0 for { + $if trace_post_process_generic_fns_loop ? { + eprintln('>>>>>>>>> recheck_generic_fns loop iteration: $post_process_generic_fns_iterations') + } for file in ast_files { if file.generic_fns.len > 0 { + $if trace_post_process_generic_fns_loop ? { + eprintln('>> file.path: ${file.path:-40} | file.generic_fns:' + + file.generic_fns.map(it.name).str()) + } c.change_current_file(file) c.post_process_generic_fns() } @@ -304,6 +312,10 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { break } c.need_recheck_generic_fns = false + post_process_generic_fns_iterations++ + } + $if trace_post_process_generic_fns_loop ? { + eprintln('>>>>>>>>> recheck_generic_fns loop done, iteration: $post_process_generic_fns_iterations') } // restore the original c.file && c.mod after post processing c.change_current_file(last_file) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 59a136df4e..5bdd212ddc 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -7,6 +7,11 @@ import v.util import v.token fn (mut c Checker) fn_decl(mut node ast.FnDecl) { + $if trace_post_process_generic_fns_types ? { + if node.generic_names.len > 0 { + eprintln('>>> post processing node.name: ${node.name:-30} | $node.generic_names <=> $c.table.cur_concrete_types') + } + } if node.generic_names.len > 0 && c.table.cur_concrete_types.len == 0 { // Just remember the generic function for now. // It will be processed later in c.post_process_generic_fns, @@ -429,13 +434,14 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) if concrete_types.len > 0 { mut no_exists := true if fn_name.contains('.') { - no_exists = c.table.register_fn_concrete_types(fn_name, concrete_types) + no_exists = c.table.register_fn_concrete_types(node.fkey(), concrete_types) } else { - no_exists = c.table.register_fn_concrete_types(c.mod + '.' + fn_name, concrete_types) + no_exists = c.table.register_fn_concrete_types(c.mod + '.' + node.fkey(), + concrete_types) // if the generic fn does not exist in the current fn calling module, continue // to look in builtin module if !no_exists { - no_exists = c.table.register_fn_concrete_types(fn_name, concrete_types) + no_exists = c.table.register_fn_concrete_types(node.fkey(), concrete_types) } } if no_exists { @@ -579,10 +585,12 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) } } if !found && c.pref.is_vsh { + // TOOD: test this hack more extensively os_name := 'os.$fn_name' if f := c.table.find_fn(os_name) { if f.generic_names.len == node.concrete_types.len { - c.table.fn_generic_types[os_name] = c.table.fn_generic_types['${node.mod}.$node.name'] + node_alias_name := node.fkey() + c.table.fn_generic_types[os_name] = c.table.fn_generic_types[node_alias_name] } node.name = os_name found = true @@ -1009,7 +1017,7 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { } } if concrete_types.len > 0 { - if c.table.register_fn_concrete_types(node.name, concrete_types) { + if c.table.register_fn_concrete_types(node.fkey(), concrete_types) { c.need_recheck_generic_fns = true } } @@ -1298,6 +1306,10 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { // no type arguments given in call, attempt implicit instantiation c.infer_fn_generic_types(method, mut node) concrete_types = node.concrete_types + } else { + if node.concrete_types.len > 0 && !node.concrete_types[0].has_flag(.generic) { + c.table.register_fn_concrete_types(method.fkey(), node.concrete_types) + } } // resolve return generics struct to concrete type if method.generic_names.len > 0 && method.return_type.has_flag(.generic) @@ -1431,9 +1443,10 @@ fn (mut c Checker) post_process_generic_fns() { for i in 0 .. c.file.generic_fns.len { mut node := c.file.generic_fns[i] c.mod = node.mod - gtypes := c.table.fn_generic_types[node.name] + fkey := node.fkey() + gtypes := c.table.fn_generic_types[fkey] $if trace_post_process_generic_fns ? { - eprintln('> post_process_generic_fns $node.mod | $node.name | $gtypes') + eprintln('> post_process_generic_fns $node.mod | $node.name | fkey: $fkey | gtypes: $gtypes') } for concrete_types in gtypes { c.table.cur_concrete_types = concrete_types diff --git a/vlib/v/checker/interface.v b/vlib/v/checker/interface.v index 5cce1e8ad3..63858b6c1e 100644 --- a/vlib/v/checker/interface.v +++ b/vlib/v/checker/interface.v @@ -222,8 +222,9 @@ fn (mut c Checker) resolve_generic_interface(typ ast.Type, interface_type ast.Ty } // add concrete types to method for imethod in inter_sym.info.methods { - if inferred_types !in c.table.fn_generic_types[imethod.name] { - c.table.fn_generic_types[imethod.name] << inferred_types + im_fkey := imethod.fkey() + if inferred_types !in c.table.fn_generic_types[im_fkey] { + c.table.fn_generic_types[im_fkey] << inferred_types } } inter_sym.info.concrete_types = inferred_types diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index cf3b746311..b2d0425cc2 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -475,6 +475,12 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { b.writeln('\n// THE END.') g.timers.show('cgen common') res := b.str() + $if trace_all_generic_fn_keys ? { + gkeys := g.table.fn_generic_types.keys() + for gkey in gkeys { + eprintln('>> g.table.fn_generic_types key: $gkey') + } + } unsafe { b.free() } unsafe { g.free_builders() } return res diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index fc14868a3b..0db4fa2e10 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -163,7 +163,12 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { // } if node.generic_names.len > 0 && g.cur_concrete_types.len == 0 { // need the cur_concrete_type check to avoid inf. recursion // loop thru each generic type and generate a function - for concrete_types in g.table.fn_generic_types[node.name] { + nkey := node.fkey() + generic_types_by_fn := g.table.fn_generic_types[nkey] + $if trace_post_process_generic_fns ? { + eprintln('>> gen_fn_decl, nkey: $nkey | generic_types_by_fn: $generic_types_by_fn') + } + for concrete_types in generic_types_by_fn { if g.pref.is_verbose { syms := concrete_types.map(g.table.sym(it)) the_type := syms.map(it.name).join(', ') diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index f7fa762cb7..fce5eb090c 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -569,7 +569,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { node := it if node.generic_names.len > 0 && g.cur_concrete_types.len == 0 { // need the cur_concrete_type check to avoid inf. recursion // loop thru each generic type and generate a function - for concrete_types in g.table.fn_generic_types[node.name] { + for concrete_types in g.table.fn_generic_types[node.fkey()] { if g.pref.is_verbose { syms := concrete_types.map(g.table.sym(it)) the_type := syms.map(it.name).join(', ') diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 7137a384fc..0f5787918a 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -404,6 +404,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl { is_main: is_main is_test: is_test is_keep_alive: is_keep_alive + is_method: true + receiver_type: rec.typ // attrs: p.attrs is_conditional: conditional_ctdefine_idx != -1 @@ -452,6 +454,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { is_main: is_main is_test: is_test is_keep_alive: is_keep_alive + is_method: false // attrs: p.attrs is_conditional: conditional_ctdefine_idx != -1 @@ -531,7 +534,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { label_names: p.label_names } if generic_names.len > 0 { - p.table.register_fn_generic_types(name) + p.table.register_fn_generic_types(fn_decl.fkey()) } p.label_names = [] p.close_scope() @@ -679,6 +682,7 @@ fn (mut p Parser) anon_fn() ast.AnonFn { params: args is_variadic: is_variadic return_type: return_type + is_method: false } name := 'anon_fn_${p.unique_prefix}_${p.table.fn_type_signature(func)}_$p.tok.pos' keep_fn_name := p.cur_fn_name diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index 32a741682c..ae956965cb 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -252,6 +252,7 @@ pub fn (mut p Parser) parse_fn_type(name string) ast.Type { is_variadic: is_variadic return_type: return_type return_type_pos: return_type_pos + is_method: false } // MapFooFn typedefs are manually added in cheaders.v // because typedefs get generated after the map struct is generated diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 1e5a56913b..e1adf12c6b 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -584,6 +584,8 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { return_type: method.return_type is_variadic: is_variadic is_pub: true + is_method: true + receiver_type: typ } ts.register_method(tmethod) info.methods << tmethod diff --git a/vlib/v/tests/generics_method_on_generic_structs_test.v b/vlib/v/tests/generics_method_on_generic_structs_test.v new file mode 100644 index 0000000000..ccb57a8da0 --- /dev/null +++ b/vlib/v/tests/generics_method_on_generic_structs_test.v @@ -0,0 +1,32 @@ +import datatypes +import math.mathutil + +struct Foo { + a int +} + +struct Bar { + a int +} + +fn (b Bar) pop() {} + +fn test_bar_foo_works_even_when_datatypes_is_imported_that_also_has_pop_methods() { + mut a := Bar{} + println(a) + assert true +} + +fn test_datatypes_can_be_used_without_interfering_with_local_generic_structs() { + mut stack := datatypes.Stack{} + stack.push(1) + println(stack) + assert true +} + +fn test_generic_type_inference_on_generic_function_from_another_module_still_works() { + x := -123 + a := mathutil.abs(x) + assert x == -123 + assert a == 123 +}