ast, parser, checker: fix generic struct init with inconsistent generic types (#13359)

pull/13360/head
yuyi 2022-02-04 20:24:38 +08:00 committed by GitHub
parent 89d399b035
commit a61b4809dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 136 additions and 61 deletions

View File

@ -411,6 +411,7 @@ pub mut:
has_update_expr bool
fields []StructInitField
embeds []StructInitEmbed
generic_types []Type
}
// import statement

View File

@ -1811,6 +1811,49 @@ pub fn (mut t Table) unwrap_generic_type(typ Type, generic_names []string, concr
return typ
}
// Foo<U>{ bar: U } to Foo<T>{ 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 {

View File

@ -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<int>',
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[...]...)´

View File

@ -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

View File

@ -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{}

View File

@ -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
}
}

View File

@ -0,0 +1,22 @@
struct Response<U> {
result U
}
pub fn send<T>(res T) string {
msg := Response<T>{
result: res
}
return '$msg'
}
fn test_generics_struct_init_with_inconsistent_generic_types() {
mut ret := send(123)
println(ret)
assert ret.contains('Response<int>{')
assert ret.contains('result: 123')
ret = send('abc')
println(ret)
assert ret.contains('Response<string>{')
assert ret.contains("result: 'abc'")
}