From b1bd4c4a1c8501b21d2065eae071062652c3f0d5 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Thu, 24 Oct 2024 03:19:12 -0300 Subject: [PATCH] checker: fix generic fn call return type resolve on var assignment (fix #22612) (#22627) --- vlib/v/checker/assign.v | 9 ++ vlib/v/checker/fn.v | 146 ++++++++++++------ vlib/v/gen/c/assign.v | 2 +- vlib/v/gen/c/fn.v | 6 +- vlib/v/tests/generics/generic_fn_param_test.v | 42 +++++ 5 files changed, 155 insertions(+), 50 deletions(-) create mode 100644 vlib/v/tests/generics/generic_fn_param_test.v diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 8ec4c2b89..de762f7c9 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -418,6 +418,15 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { && c.is_generic_expr(right) { // mark variable as generic var because its type changes according to fn return generic resolution type left.obj.ct_type_var = .generic_var + fn_ret_type := c.resolve_return_type(right) + if fn_ret_type != ast.void_type + && c.table.final_sym(fn_ret_type).kind != .multi_return { + c.comptime.type_map['g.${left.name}.${left.obj.pos.pos}'] = if right.or_block.kind == .absent { + fn_ret_type + } else { + fn_ret_type.clear_option_and_result() + } + } } } } diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index f60fc0234..4ca708d37 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1691,37 +1691,12 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. c.error('a non generic function called like a generic one', node.concrete_list_pos) } - if func.generic_names.len > 0 { - if has_generic || node.concrete_types.any(it.has_flag(.generic)) { - if typ := c.table.convert_generic_type(func.return_type, func.generic_names, - node.concrete_types) - { - if typ.has_flag(.generic) { - node.return_type = typ - } - } - c.register_trace_call(node, func) - return node.return_type - } else { - if node.concrete_types.len > 0 && !node.concrete_types.any(it.has_flag(.generic)) { - if typ := c.table.convert_generic_type(func.return_type, func.generic_names, - node.concrete_types) - { - node.return_type = typ - c.register_trace_call(node, func) - return typ - } - } - if typ := c.table.convert_generic_type(func.return_type, func.generic_names, - concrete_types) - { - if typ.has_flag(.generic) { - node.return_type = typ - } - c.register_trace_call(node, func) - return typ - } - } + // resolve generic fn return type + if func.generic_names.len > 0 && node.return_type != ast.void_type { + ret_type := c.resolve_fn_return_type(func, node) + c.register_trace_call(node, func) + node.return_type = ret_type + return ret_type } c.register_trace_call(node, func) return func.return_type @@ -2673,6 +2648,9 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { c.resolve_fn_generic_args(method, mut node) } + if node.concrete_types.len > 0 && method.generic_names.len == 0 { + c.error('a non generic function called like a generic one', node.concrete_list_pos) + } // resolve return generics struct to concrete type if method.generic_names.len > 0 && method.return_type.has_flag(.generic) && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len == 0 { @@ -2681,21 +2659,12 @@ fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { } else { node.return_type = method.return_type } - - if node.concrete_types.len > 0 && node.concrete_types.all(!it.has_flag(.generic)) - && method.return_type.has_flag(.generic) && method.generic_names.len > 0 - && method.generic_names.len == node.concrete_types.len { - if typ := c.table.convert_generic_type(method.return_type, method.generic_names, - concrete_types) - { - node.return_type = typ - } else { - node.return_type = c.table.unwrap_generic_type(method.return_type, method.generic_names, - concrete_types) - } - } - if node.concrete_types.len > 0 && method.generic_names.len == 0 { - c.error('a non generic function called like a generic one', node.concrete_list_pos) + // resolve generic fn return type + if method.generic_names.len > 0 && method.return_type.has_flag(.generic) { + ret_type := c.resolve_fn_return_type(method, node) + c.register_trace_call(node, method) + node.return_type = ret_type + return ret_type } if method.generic_names.len > 0 { if !left_type.has_flag(.generic) { @@ -3560,3 +3529,88 @@ fn scope_register_var_name(mut s ast.Scope, pos token.Pos, typ ast.Type, name st is_used: true }) } + +// resolve_fn_return_type resolves the generic return type of fn with its related CallExpr +fn (mut c Checker) resolve_fn_return_type(func &ast.Fn, node ast.CallExpr) ast.Type { + mut ret_type := func.return_type + if node.is_method { + // resolve possible generic types + concrete_types := node.concrete_types.map(c.unwrap_generic(it)) + // generic method being called from a non-generic func + if func.generic_names.len > 0 && func.return_type.has_flag(.generic) + && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len == 0 { + ret_type = c.table.unwrap_generic_type(func.return_type, func.generic_names, + concrete_types) + } + // generic method called without generic type to be resolved on call + if node.concrete_types.len > 0 && node.concrete_types.all(!it.has_flag(.generic)) + && func.return_type.has_flag(.generic) && func.generic_names.len > 0 + && func.generic_names.len == node.concrete_types.len { + if typ := c.table.convert_generic_type(func.return_type, func.generic_names, + concrete_types) + { + return typ + } else { + return c.table.unwrap_generic_type(func.return_type, func.generic_names, + concrete_types) + } + } + } else { + concrete_types := node.concrete_types.map(c.unwrap_generic(it)) + // generic func called from non-generic func + if node.concrete_types.len > 0 && func.return_type != 0 && c.table.cur_fn != unsafe { nil } + && c.table.cur_fn.generic_names.len == 0 { + if typ := c.table.convert_generic_type(func.return_type, func.generic_names, + concrete_types) + { + return typ + } + return ret_type + } + if func.generic_names.len > 0 { + has_generic := node.raw_concrete_types.any(it.has_flag(.generic)) + has_any_generic := node.concrete_types.any(it.has_flag(.generic)) + // fn call with any generic type to be resolved on call (e.g. foo[T]()) + if has_generic || has_any_generic { + if typ := c.table.convert_generic_type(func.return_type, func.generic_names, + node.concrete_types) + { + if typ.has_flag(.generic) { + return typ + } + } + } else { + // fn call with all generic types already resolved to its concrete ones (e.g. foo[int]()) + if node.concrete_types.len > 0 && !has_any_generic { + if typ := c.table.convert_generic_type(func.return_type, func.generic_names, + node.concrete_types) + { + return typ + } + } + // use fresh resolved concrete_types list + if typ := c.table.convert_generic_type(func.return_type, func.generic_names, + concrete_types) + { + return typ + } + } + } + } + return ret_type +} + +// resolve_return_type resolves the generic return type of CallExpr +fn (mut c Checker) resolve_return_type(node ast.CallExpr) ast.Type { + if node.is_method { + left_sym := c.table.sym(c.unwrap_generic(node.left_type)) + if method := c.table.find_method(left_sym, node.name) { + return c.resolve_fn_return_type(method, node) + } + } else { + if func := c.table.find_fn(node.name) { + return c.resolve_fn_return_type(func, node) + } + } + return node.return_type +} diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 9b4081eb3..02a6b2ceb 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -305,7 +305,7 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } } else if left.obj.ct_type_var == .generic_var && val is ast.CallExpr { if val.return_type_generic != 0 && val.return_type_generic.has_flag(.generic) { - fn_ret_type := g.resolve_fn_return_type(val) + fn_ret_type := g.resolve_return_type(val) if fn_ret_type != ast.void_type { var_type = fn_ret_type val_type = var_type diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 62ca33b3b..557102236 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1283,8 +1283,8 @@ fn (mut g Gen) get_gn_var_type(var ast.Ident) ast.Type { return ast.void_type } -// resolve_fn_return_type resolves the generic return type of fn -fn (mut g Gen) resolve_fn_return_type(node ast.CallExpr) ast.Type { +// resolve_return_type resolves the generic return type of CallExpr +fn (mut g Gen) resolve_return_type(node ast.CallExpr) ast.Type { if node.is_method { if func := g.table.find_method(g.table.sym(node.left_type), node.name) { if func.generic_names.len > 0 { @@ -1375,7 +1375,7 @@ fn (g &Gen) get_generic_array_element_type(array ast.Array) ast.Type { return typ } -fn (mut g Gen) resolve_comptime_args(func ast.Fn, mut node_ ast.CallExpr, concrete_types []ast.Type) map[int]ast.Type { +fn (mut g Gen) resolve_comptime_args(func &ast.Fn, mut node_ ast.CallExpr, concrete_types []ast.Type) map[int]ast.Type { mut comptime_args := map[int]ast.Type{} has_dynamic_vars := (g.cur_fn != unsafe { nil } && g.cur_fn.generic_names.len > 0) || g.comptime.comptime_for_field_var != '' diff --git a/vlib/v/tests/generics/generic_fn_param_test.v b/vlib/v/tests/generics/generic_fn_param_test.v new file mode 100644 index 000000000..eda3d7e4d --- /dev/null +++ b/vlib/v/tests/generics/generic_fn_param_test.v @@ -0,0 +1,42 @@ +module main + +struct Decoder { + json string +} + +pub fn decode[T](val string) !T { + mut decoder := Decoder{ + json: val + } + + mut result := T{} + decoder.decode_value(mut &result)! + return result +} + +fn (mut decoder Decoder) decode_value[T](mut val T) ! { + $if T is $array { + mut array_element := create_array_element(val) + + decoder.decode_value(mut array_element)! + } $else $if T is $map { + mut map_value := create_map_value(val) + + decoder.decode_value(mut map_value)! + } +} + +fn create_array_element[T](array []T) T { + return T{} +} + +fn create_map_value[K, V](map_ map[K]V) V { + return V{} +} + +fn test_main() { + decode[[]int]('[1, 2, 3]')! + decode[[]string]('["1", "2", "3"]')! + decode[map[string]int]('{"a": 1}')! + decode[map[string]string]('{"val": "2"}')! +} -- 2.39.5