From a61b4809dc4ef7d364cfa4adeda395afc156ef05 Mon Sep 17 00:00:00 2001 From: yuyi Date: Fri, 4 Feb 2022 20:24:38 +0800 Subject: [PATCH] ast, parser, checker: fix generic struct init with inconsistent generic types (#13359) --- vlib/v/ast/ast.v | 1 + vlib/v/ast/table.v | 43 ++++++ vlib/v/checker/struct.v | 3 + vlib/v/parser/parse_type.v | 3 + vlib/v/parser/parser.v | 123 +++++++++--------- vlib/v/parser/struct.v | 2 + ...nit_with_inconsistent_generic_types_test.v | 22 ++++ 7 files changed, 136 insertions(+), 61 deletions(-) create mode 100644 vlib/v/tests/generics_struct_init_with_inconsistent_generic_types_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index aed4c8ccbb..f4eae42555 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -411,6 +411,7 @@ pub mut: has_update_expr bool fields []StructInitField embeds []StructInitEmbed + generic_types []Type } // import statement diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 62d2493c4d..a449979c70 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1811,6 +1811,49 @@ pub fn (mut t Table) unwrap_generic_type(typ Type, generic_names []string, concr return typ } +// Foo{ bar: U } to Foo{ bar: T } +pub fn (mut t Table) replace_generic_type(typ Type, generic_types []Type) { + mut ts := t.sym(typ) + match mut ts.info { + Array { + mut elem_type := ts.info.elem_type + mut elem_sym := t.sym(elem_type) + mut dims := 1 + for mut elem_sym.info is Array { + info := elem_sym.info as Array + elem_type = info.elem_type + elem_sym = t.sym(elem_type) + dims++ + } + t.replace_generic_type(elem_type, generic_types) + } + ArrayFixed { + t.replace_generic_type(ts.info.elem_type, generic_types) + } + Chan { + t.replace_generic_type(ts.info.elem_type, generic_types) + } + Map { + t.replace_generic_type(ts.info.key_type, generic_types) + t.replace_generic_type(ts.info.value_type, generic_types) + } + Struct, Interface, SumType { + generic_names := ts.info.generic_types.map(t.sym(it).name) + for i in 0 .. ts.info.fields.len { + if ts.info.fields[i].typ.has_flag(.generic) { + if t_typ := t.resolve_generic_to_concrete(ts.info.fields[i].typ, generic_names, + generic_types) + { + ts.info.fields[i].typ = t_typ + } + } + } + ts.info.generic_types = generic_types + } + else {} + } +} + // generic struct instantiations to concrete types pub fn (mut t Table) generic_insts_to_concrete() { for mut typ in t.type_symbols { diff --git a/vlib/v/checker/struct.v b/vlib/v/checker/struct.v index 715a8ebd1e..c8f8177ab4 100644 --- a/vlib/v/checker/struct.v +++ b/vlib/v/checker/struct.v @@ -135,6 +135,9 @@ pub fn (mut c Checker) struct_init(mut node ast.StructInit) ast.Type { c.error('generic struct init must specify type parameter, e.g. Foo', node.pos) } + if node.generic_types.len > 0 && struct_sym.info.generic_types != node.generic_types { + c.table.replace_generic_type(node.typ, node.generic_types) + } } else if struct_sym.info is ast.Alias { parent_sym := c.table.sym(struct_sym.info.parent_type) // e.g. ´x := MyMapAlias{}´, should be a cast to alias type ´x := MyMapAlias(map[...]...)´ diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index 269a284c05..0125dc156e 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -638,6 +638,9 @@ pub fn (mut p Parser) parse_generic_inst_type(name string) ast.Type { bs_name += ', ' bs_cname += '_' } + if !is_instance { + p.struct_init_generic_types = concrete_types + } concrete_types_pos := start_pos.extend(p.tok.pos()) p.check(.gt) p.inside_generic_params = false diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 429fb51567..26a26d9684 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -27,67 +27,68 @@ mut: scanner &scanner.Scanner comments_mode scanner.CommentsMode = .skip_comments // see comment in parse_file - tok token.Token - prev_tok token.Token - peek_tok token.Token - table &ast.Table - language ast.Language - fn_language ast.Language // .c for `fn C.abcd()` declarations - expr_level int // prevent too deep recursions for pathological programs - inside_vlib_file bool // true for all vlib/ files - inside_test_file bool // when inside _test.v or _test.vv file - inside_if bool - inside_if_expr bool - inside_ct_if_expr bool - inside_or_expr bool - inside_for bool - inside_fn bool // true even with implicit main - inside_unsafe_fn bool - inside_str_interp bool - inside_array_lit bool - inside_in_array bool - inside_match bool // to separate `match A { }` from `Struct{}` - inside_select bool // to allow `ch <- Struct{} {` inside `select` - inside_match_case bool // to separate `match_expr { }` from `Struct{}` - inside_match_body bool // to fix eval not used TODO - inside_unsafe bool - inside_sum_type bool // to prevent parsing inline sum type again - inside_asm_template bool - inside_asm bool - inside_defer bool - inside_generic_params bool // indicates if parsing between `<` and `>` of a method/function - inside_receiver_param bool // indicates if parsing the receiver parameter inside the first `(` and `)` of a method - or_is_handled bool // ignore `or` in this expression - builtin_mod bool // are we in the `builtin` module? - mod string // current module name - is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree - has_globals bool // `[has_globals] module abc` - allow globals declarations, even without -enable-globals, in that single .v file __only__ - is_generated bool // `[generated] module abc` - turn off compiler notices for that single .v file __only__. - attrs []ast.Attr // attributes before next decl stmt - expr_mod string // for constructing full type names in parse_type() - scope &ast.Scope - imports map[string]string // alias => mod_name - ast_imports []ast.Import // mod_names - used_imports []string // alias - auto_imports []string // imports, the user does not need to specify - imported_symbols map[string]string - is_amp bool // for generating the right code for `&Foo{}` - returns bool - is_stmt_ident bool // true while the beginning of a statement is an ident/selector - expecting_type bool // `is Type`, expecting type - errors []errors.Error - warnings []errors.Warning - notices []errors.Notice - vet_errors []vet.Error - cur_fn_name string - label_names []string - name_error bool // indicates if the token is not a name or the name is on another line - n_asm int // controls assembly labels - global_labels []string - comptime_if_cond bool - defer_vars []ast.Ident - should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop - codegen_text string + tok token.Token + prev_tok token.Token + peek_tok token.Token + table &ast.Table + language ast.Language + fn_language ast.Language // .c for `fn C.abcd()` declarations + expr_level int // prevent too deep recursions for pathological programs + inside_vlib_file bool // true for all vlib/ files + inside_test_file bool // when inside _test.v or _test.vv file + inside_if bool + inside_if_expr bool + inside_ct_if_expr bool + inside_or_expr bool + inside_for bool + inside_fn bool // true even with implicit main + inside_unsafe_fn bool + inside_str_interp bool + inside_array_lit bool + inside_in_array bool + inside_match bool // to separate `match A { }` from `Struct{}` + inside_select bool // to allow `ch <- Struct{} {` inside `select` + inside_match_case bool // to separate `match_expr { }` from `Struct{}` + inside_match_body bool // to fix eval not used TODO + inside_unsafe bool + inside_sum_type bool // to prevent parsing inline sum type again + inside_asm_template bool + inside_asm bool + inside_defer bool + inside_generic_params bool // indicates if parsing between `<` and `>` of a method/function + inside_receiver_param bool // indicates if parsing the receiver parameter inside the first `(` and `)` of a method + or_is_handled bool // ignore `or` in this expression + builtin_mod bool // are we in the `builtin` module? + mod string // current module name + is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree + has_globals bool // `[has_globals] module abc` - allow globals declarations, even without -enable-globals, in that single .v file __only__ + is_generated bool // `[generated] module abc` - turn off compiler notices for that single .v file __only__. + attrs []ast.Attr // attributes before next decl stmt + expr_mod string // for constructing full type names in parse_type() + scope &ast.Scope + imports map[string]string // alias => mod_name + ast_imports []ast.Import // mod_names + used_imports []string // alias + auto_imports []string // imports, the user does not need to specify + imported_symbols map[string]string + is_amp bool // for generating the right code for `&Foo{}` + returns bool + is_stmt_ident bool // true while the beginning of a statement is an ident/selector + expecting_type bool // `is Type`, expecting type + errors []errors.Error + warnings []errors.Warning + notices []errors.Notice + vet_errors []vet.Error + cur_fn_name string + label_names []string + name_error bool // indicates if the token is not a name or the name is on another line + n_asm int // controls assembly labels + global_labels []string + comptime_if_cond bool + defer_vars []ast.Ident + should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop + codegen_text string + struct_init_generic_types []ast.Type } __global codegen_files = []&ast.File{} diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index e66bc886a2..dde842e6e5 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -337,6 +337,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { fn (mut p Parser) struct_init(typ_str string, short_syntax bool) ast.StructInit { first_pos := (if short_syntax && p.prev_tok.kind == .lcbr { p.prev_tok } else { p.tok }).pos() + p.struct_init_generic_types = []ast.Type{} typ := if short_syntax { ast.void_type } else { p.parse_type() } p.expr_mod = '' // sym := p.table.sym(typ) @@ -426,6 +427,7 @@ fn (mut p Parser) struct_init(typ_str string, short_syntax bool) ast.StructInit pos: first_pos.extend(if short_syntax { p.tok.pos() } else { p.prev_tok.pos() }) is_short: no_keys pre_comments: pre_comments + generic_types: p.struct_init_generic_types } } diff --git a/vlib/v/tests/generics_struct_init_with_inconsistent_generic_types_test.v b/vlib/v/tests/generics_struct_init_with_inconsistent_generic_types_test.v new file mode 100644 index 0000000000..12729746ef --- /dev/null +++ b/vlib/v/tests/generics_struct_init_with_inconsistent_generic_types_test.v @@ -0,0 +1,22 @@ +struct Response { + result U +} + +pub fn send(res T) string { + msg := Response{ + result: res + } + return '$msg' +} + +fn test_generics_struct_init_with_inconsistent_generic_types() { + mut ret := send(123) + println(ret) + assert ret.contains('Response{') + assert ret.contains('result: 123') + + ret = send('abc') + println(ret) + assert ret.contains('Response{') + assert ret.contains("result: 'abc'") +}