From f7decfe39943eea3f2564fd57c47473dbac57973 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Tue, 6 Oct 2020 14:34:02 +0100 Subject: [PATCH] checker: infer generic type T from matching fn call argument (#6298) --- vlib/v/checker/check_types.v | 17 +++++++++++++ vlib/v/checker/checker.v | 38 +++++++++++++++++++++++++--- vlib/v/tests/generic_fn_infer_test.v | 33 ++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 vlib/v/tests/generic_fn_infer_test.v diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 60a8e777e9..8dbf5ad84b 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -372,3 +372,20 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) table.T pub fn (c &Checker) check_sumtype_compatibility(a, b table.Type) bool { return c.table.sumtype_has_variant(a, b) || c.table.sumtype_has_variant(b, a) } + +pub fn (mut c Checker) infer_fn_types(f table.Fn, mut call_expr ast.CallExpr) { + gt_name := 'T' + mut typ := table.void_type + for i, arg in f.params { + if arg.type_source_name == gt_name { + typ = call_expr.args[i].typ + break + } + } + if typ == table.void_type { + c.error('could not infer generic type `$gt_name` in call to `$f.name`', call_expr.pos) + } else { + c.table.register_fn_gen_type(f.name, typ) + call_expr.generic_type = typ + } +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 948c7ec959..8809805442 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -56,6 +56,7 @@ mut: inside_sql bool // to handle sql table fields pseudo variables cur_orm_ts table.TypeSymbol error_details []string + generic_funcs []&ast.FnDecl } pub fn new_checker(table &table.Table, pref &pref.Preferences) Checker { @@ -80,6 +81,7 @@ pub fn (mut c Checker) check(ast_file ast.File) { c.stmt(stmt) } c.check_scope_vars(c.file.scope) + c.post_process_generic_fns() } pub fn (mut c Checker) check_scope_vars(sc &ast.Scope) { @@ -1505,6 +1507,10 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { } } } + if f.is_generic && call_expr.generic_type == table.void_type { + // no type arguments given in call, attempt implicit instantiation + c.infer_fn_types(f, mut call_expr) + } if call_expr.generic_type != table.void_type && f.return_type != 0 { // table.t_type { // Handle `foo() T` => `foo() int` => return int return_sym := c.table.get_type_symbol(f.return_type) @@ -3962,10 +3968,16 @@ fn (mut c Checker) fetch_and_verify_orm_fields(info table.Struct, pos token.Posi return fields } -fn (mut c Checker) fn_decl(mut node ast.FnDecl) { - c.returns = false - if node.is_generic && c.cur_generic_type == 0 { // need the cur_generic_type check to avoid inf. recursion - // loop thru each generic type and generate a function +fn (mut c Checker) post_process_generic_fns() { + // Loop thru each generic function concrete type. + // Check each specific fn instantiation. + for i in 0 .. c.generic_funcs.len { + mut node := c.generic_funcs[i] + if c.table.fn_gen_types.len == 0 { + // no concrete types, so just skip: + continue + } + // eprintln('>> post_process_generic_fns $c.file.path | $node.name , c.table.fn_gen_types.len: $c.table.fn_gen_types.len') for gen_type in c.table.fn_gen_types[node.name] { c.cur_generic_type = gen_type // sym:=c.table.get_type_symbol(gen_type) @@ -3973,6 +3985,24 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { c.fn_decl(mut node) } c.cur_generic_type = 0 + c.generic_funcs[i] = 0 + } + // The generic funtions for each file/mod should be + // postprocessed just once in the checker, while the file/mod + // context is still the same. + c.generic_funcs = [] +} + +fn (mut c Checker) fn_decl(mut node ast.FnDecl) { + c.returns = false + if node.is_generic && c.cur_generic_type == 0 { + // Just remember the generic function for now. + // It will be processed later in c.post_process_generic_fns, + // after all other normal functions are processed. + // This is done so that all generic function calls can + // have a chance to populate c.table.fn_gen_types with + // the correct concrete types. + c.generic_funcs << node return } if node.language == .v && !c.is_builtin_mod { diff --git a/vlib/v/tests/generic_fn_infer_test.v b/vlib/v/tests/generic_fn_infer_test.v new file mode 100644 index 0000000000..3b9ab40c82 --- /dev/null +++ b/vlib/v/tests/generic_fn_infer_test.v @@ -0,0 +1,33 @@ +fn call(v T) { +} + +fn simple(p T) T { + return p +} + +fn test_infer() { + call(3) + i := 4 + r := simple(i) + assert r == 4 +} + +fn test_explicit_calls_should_also_work() { + call(2) + assert true + simple(5) + assert true +} + +// +fn choose4(a, b, c, d T) T { + // NB: a similar construct is used in prime31's via engine + return a +} + +fn test_calling_generic_fn_with_many_params() { + x := choose4(1, 2, 3, 4) + assert x == 1 + y := choose4('abc', 'xyz', 'def', 'ghi') + assert y == 'abc' +}