From 6143bd6232d24909ed6a8519ab81cc3873b21790 Mon Sep 17 00:00:00 2001 From: yuyi Date: Tue, 30 Mar 2021 15:36:45 +0800 Subject: [PATCH] generics: check generic_fn called arg mismatch (#9510) --- vlib/v/checker/check_types.v | 89 +++++++++++++++++-- vlib/v/checker/checker.v | 69 ++++++-------- .../tests/generics_fn_called_arg_mismatch.out | 27 ++++++ .../tests/generics_fn_called_arg_mismatch.vv | 10 +++ 4 files changed, 145 insertions(+), 50 deletions(-) create mode 100644 vlib/v/checker/tests/generics_fn_called_arg_mismatch.out create mode 100644 vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 89d7c3208c..13bf6828d4 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -276,6 +276,9 @@ pub fn (mut c Checker) check_types(got table.Type, expected table.Type) bool { return false } } + if expected.has_flag(.generic) { + return false + } return true } @@ -486,10 +489,10 @@ pub fn (mut c Checker) infer_fn_types(f table.Fn, mut call_expr ast.CallExpr) { c.table.register_fn_gen_type(f.name, inferred_types) } -// resolve_generic_type resolves generics to real types T => int. +// resolve_generic_by_names resolves generics to real types T => int. // Even map[string]map[string]T can be resolved. // This is used for resolving the generic return type of CallExpr white `unwrap_generic` is used to resolve generic usage in FnDecl. -fn (mut c Checker) resolve_generic_type(generic_type table.Type, generic_names []string, generic_types []table.Type) ?table.Type { +fn (mut c Checker) resolve_generic_by_names(generic_type table.Type, generic_names []string, generic_types []table.Type) ?table.Type { mut sym := c.table.get_type_symbol(generic_type) if sym.name in generic_names { index := generic_names.index(sym.name) @@ -509,14 +512,14 @@ fn (mut c Checker) resolve_generic_type(generic_type table.Type, generic_names [ elem_sym = c.table.get_type_symbol(elem_type) dims++ } - if typ := c.resolve_generic_type(elem_type, generic_names, generic_types) { + if typ := c.resolve_generic_by_names(elem_type, generic_names, generic_types) { idx := c.table.find_or_register_array_with_dims(typ, dims) array_typ := table.new_type(idx) return array_typ } } else if sym.kind == .chan { info := sym.info as table.Chan - if typ := c.resolve_generic_type(info.elem_type, generic_names, generic_types) { + if typ := c.resolve_generic_by_names(info.elem_type, generic_names, generic_types) { idx := c.table.find_or_register_chan(typ, typ.nr_muls() > 0) chan_typ := table.new_type(idx) return chan_typ @@ -525,7 +528,7 @@ fn (mut c Checker) resolve_generic_type(generic_type table.Type, generic_names [ mut types := []table.Type{} mut type_changed := false for ret_type in sym.info.types { - if typ := c.resolve_generic_type(ret_type, generic_names, generic_types) { + if typ := c.resolve_generic_by_names(ret_type, generic_names, generic_types) { types << typ type_changed = true } else { @@ -541,11 +544,83 @@ fn (mut c Checker) resolve_generic_type(generic_type table.Type, generic_names [ mut type_changed := false mut unwrapped_key_type := sym.info.key_type mut unwrapped_value_type := sym.info.value_type - if typ := c.resolve_generic_type(sym.info.key_type, generic_names, generic_types) { + if typ := c.resolve_generic_by_names(sym.info.key_type, generic_names, generic_types) { unwrapped_key_type = typ type_changed = true } - if typ := c.resolve_generic_type(sym.info.value_type, generic_names, generic_types) { + if typ := c.resolve_generic_by_names(sym.info.value_type, generic_names, generic_types) { + unwrapped_value_type = typ + type_changed = true + } + if type_changed { + idx := c.table.find_or_register_map(unwrapped_key_type, unwrapped_value_type) + typ := table.new_type(idx) + return typ + } + } + return none +} + +// resolve_generic_by_types resolves generics to real types T => int. +// Even map[string]map[string]T can be resolved. +// This is used for resolving the generic return type of CallExpr white `unwrap_generic` is used to resolve generic usage in FnDecl. +fn (mut c Checker) resolve_generic_by_types(generic_type table.Type, from_types []table.Type, to_types []table.Type) ?table.Type { + mut sym := c.table.get_type_symbol(generic_type) + if generic_type in from_types { + index := from_types.index(generic_type) + mut typ := to_types[index] + typ = typ.set_nr_muls(generic_type.nr_muls()) + if generic_type.has_flag(.optional) { + typ = typ.set_flag(.optional) + } + return typ + } else if sym.kind == .array { + info := sym.info as table.Array + mut elem_type := info.elem_type + mut elem_sym := c.table.get_type_symbol(elem_type) + mut dims := 1 + for mut elem_sym.info is table.Array { + elem_type = elem_sym.info.elem_type + elem_sym = c.table.get_type_symbol(elem_type) + dims++ + } + if typ := c.resolve_generic_by_types(elem_type, from_types, to_types) { + idx := c.table.find_or_register_array_with_dims(typ, dims) + array_typ := table.new_type(idx) + return array_typ + } + } else if sym.kind == .chan { + info := sym.info as table.Chan + if typ := c.resolve_generic_by_types(info.elem_type, from_types, to_types) { + idx := c.table.find_or_register_chan(typ, typ.nr_muls() > 0) + chan_typ := table.new_type(idx) + return chan_typ + } + } else if mut sym.info is table.MultiReturn { + mut types := []table.Type{} + mut type_changed := false + for ret_type in sym.info.types { + if typ := c.resolve_generic_by_types(ret_type, from_types, to_types) { + types << typ + type_changed = true + } else { + types << ret_type + } + } + if type_changed { + idx := c.table.find_or_register_multi_return(types) + typ := table.new_type(idx) + return typ + } + } else if mut sym.info is table.Map { + mut type_changed := false + mut unwrapped_key_type := sym.info.key_type + mut unwrapped_value_type := sym.info.value_type + if typ := c.resolve_generic_by_types(sym.info.key_type, from_types, to_types) { + unwrapped_key_type = typ + type_changed = true + } + if typ := c.resolve_generic_by_types(sym.info.value_type, from_types, to_types) { unwrapped_value_type = typ type_changed = true } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 1bd80d4397..7dc4e2b65d 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1598,7 +1598,7 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type { c.infer_fn_types(method, mut call_expr) } if call_expr.generic_types.len > 0 && method.return_type != 0 { - if typ := c.resolve_generic_type(method.return_type, method.generic_names, + if typ := c.resolve_generic_by_names(method.return_type, method.generic_names, call_expr.generic_types) { call_expr.return_type = typ @@ -1955,8 +1955,11 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { mut fields := rts.info.fields.clone() if rts.info.generic_types.len == generic_types.len { for i, _ in fields { - fields[i].typ = c.unwrap_generic_ex(fields[i].typ, rts.info.generic_types, + if t_typ := c.resolve_generic_by_types(fields[i].typ, rts.info.generic_types, generic_types) + { + fields[i].typ = t_typ + } } mut info := rts.info info.generic_types = [] @@ -2098,7 +2101,26 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { continue } if f.generic_names.len > 0 { - continue + if param.typ.has_flag(.generic) + && f.generic_names.len == call_expr.generic_types.len { + if unwrap_typ := c.resolve_generic_by_names(param.typ, f.generic_names, + call_expr.generic_types) + { + if (unwrap_typ.idx() == typ.idx()) + || (unwrap_typ.is_int() && typ.is_int()) + || (unwrap_typ.is_float() && typ.is_float()) { + continue + } + expected_sym := c.table.get_type_symbol(unwrap_typ) + got_sym := c.table.get_type_symbol(typ) + c.error('argument ${i + 1} got `$got_sym.name`, expected `$expected_sym.name`', + call_arg.pos) + } else { + continue + } + } else { + continue + } } c.error('$err.msg in argument ${i + 1} to `$fn_name`', call_arg.pos) } @@ -2116,7 +2138,7 @@ pub fn (mut c Checker) call_fn(mut call_expr ast.CallExpr) table.Type { c.infer_fn_types(f, mut call_expr) } if call_expr.generic_types.len > 0 && f.return_type != 0 { - if typ := c.resolve_generic_type(f.return_type, f.generic_names, call_expr.generic_types) { + if typ := c.resolve_generic_by_names(f.return_type, f.generic_names, call_expr.generic_types) { call_expr.return_type = typ return typ } @@ -3901,45 +3923,6 @@ pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type { return typ } -// `unwrap_generic()` is used in generic_fn decl, `unwrap_generic_ex()` can be used not in generic_fn decl -// e.g. from_types: to_types: -pub fn (mut c Checker) unwrap_generic_ex(typ table.Type, from_types []table.Type, to_types []table.Type) table.Type { - sym := c.table.get_type_symbol(typ) - mut typ_ := typ - mut nr_dims := 0 - if sym.kind == .array { - typ_, nr_dims = c.array_element_info(typ) - } - if from_types.len == to_types.len { - for j, gp in from_types { - if gp == typ_ { - if sym.kind == .array { - idx := c.table.find_or_register_array_with_dims(to_types[j], nr_dims) - return table.new_type(idx) - } else { - return to_types[j].derive(typ).clear_flag(.generic) - } - } - } - } - return typ -} - -fn (mut c Checker) array_element_info(typ table.Type) (table.Type, int) { - mut typ_ := typ - mut dims := 0 - for { - sym := c.table.get_type_symbol(typ_) - if sym.info is table.Array { - typ_ = sym.info.elem_type - dims++ - } else { - break - } - } - return typ_, dims -} - // TODO node must be mut pub fn (mut c Checker) expr(node ast.Expr) table.Type { c.expr_level++ diff --git a/vlib/v/checker/tests/generics_fn_called_arg_mismatch.out b/vlib/v/checker/tests/generics_fn_called_arg_mismatch.out new file mode 100644 index 0000000000..59fd5de7ca --- /dev/null +++ b/vlib/v/checker/tests/generics_fn_called_arg_mismatch.out @@ -0,0 +1,27 @@ +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:6:15: error: argument 1 got `int literal`, expected `bool` + 4 | + 5 | fn main() { + 6 | foo(1) + | ^ + 7 | foo(2.2) + 8 | foo(true) +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:7:15: error: argument 1 got `float literal`, expected `bool` + 5 | fn main() { + 6 | foo(1) + 7 | foo(2.2) + | ~~~ + 8 | foo(true) + 9 | foo('aaa') +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:8:17: error: argument 1 got `bool`, expected `string` + 6 | foo(1) + 7 | foo(2.2) + 8 | foo(true) + | ~~~~ + 9 | foo('aaa') + 10 | } +vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv:9:14: error: argument 1 got `string`, expected `int` + 7 | foo(2.2) + 8 | foo(true) + 9 | foo('aaa') + | ~~~~~ + 10 | } diff --git a/vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv b/vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv new file mode 100644 index 0000000000..1960a54fe5 --- /dev/null +++ b/vlib/v/checker/tests/generics_fn_called_arg_mismatch.vv @@ -0,0 +1,10 @@ +fn foo(b T) { + println(b) +} + +fn main() { + foo(1) + foo(2.2) + foo(true) + foo('aaa') +}