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 has_update_expr bool
fields []StructInitField fields []StructInitField
embeds []StructInitEmbed embeds []StructInitEmbed
generic_types []Type
} }
// import statement // import statement

View File

@ -1811,6 +1811,49 @@ pub fn (mut t Table) unwrap_generic_type(typ Type, generic_names []string, concr
return typ 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 // generic struct instantiations to concrete types
pub fn (mut t Table) generic_insts_to_concrete() { pub fn (mut t Table) generic_insts_to_concrete() {
for mut typ in t.type_symbols { 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>', c.error('generic struct init must specify type parameter, e.g. Foo<int>',
node.pos) 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 { } else if struct_sym.info is ast.Alias {
parent_sym := c.table.sym(struct_sym.info.parent_type) parent_sym := c.table.sym(struct_sym.info.parent_type)
// e.g. ´x := MyMapAlias{}´, should be a cast to alias type ´x := MyMapAlias(map[...]...)´ // 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_name += ', '
bs_cname += '_' bs_cname += '_'
} }
if !is_instance {
p.struct_init_generic_types = concrete_types
}
concrete_types_pos := start_pos.extend(p.tok.pos()) concrete_types_pos := start_pos.extend(p.tok.pos())
p.check(.gt) p.check(.gt)
p.inside_generic_params = false p.inside_generic_params = false

View File

@ -27,67 +27,68 @@ mut:
scanner &scanner.Scanner scanner &scanner.Scanner
comments_mode scanner.CommentsMode = .skip_comments comments_mode scanner.CommentsMode = .skip_comments
// see comment in parse_file // see comment in parse_file
tok token.Token tok token.Token
prev_tok token.Token prev_tok token.Token
peek_tok token.Token peek_tok token.Token
table &ast.Table table &ast.Table
language ast.Language language ast.Language
fn_language ast.Language // .c for `fn C.abcd()` declarations fn_language ast.Language // .c for `fn C.abcd()` declarations
expr_level int // prevent too deep recursions for pathological programs expr_level int // prevent too deep recursions for pathological programs
inside_vlib_file bool // true for all vlib/ files inside_vlib_file bool // true for all vlib/ files
inside_test_file bool // when inside _test.v or _test.vv file inside_test_file bool // when inside _test.v or _test.vv file
inside_if bool inside_if bool
inside_if_expr bool inside_if_expr bool
inside_ct_if_expr bool inside_ct_if_expr bool
inside_or_expr bool inside_or_expr bool
inside_for bool inside_for bool
inside_fn bool // true even with implicit main inside_fn bool // true even with implicit main
inside_unsafe_fn bool inside_unsafe_fn bool
inside_str_interp bool inside_str_interp bool
inside_array_lit bool inside_array_lit bool
inside_in_array bool inside_in_array bool
inside_match bool // to separate `match A { }` from `Struct{}` inside_match bool // to separate `match A { }` from `Struct{}`
inside_select bool // to allow `ch <- Struct{} {` inside `select` inside_select bool // to allow `ch <- Struct{} {` inside `select`
inside_match_case bool // to separate `match_expr { }` from `Struct{}` inside_match_case bool // to separate `match_expr { }` from `Struct{}`
inside_match_body bool // to fix eval not used TODO inside_match_body bool // to fix eval not used TODO
inside_unsafe bool inside_unsafe bool
inside_sum_type bool // to prevent parsing inline sum type again inside_sum_type bool // to prevent parsing inline sum type again
inside_asm_template bool inside_asm_template bool
inside_asm bool inside_asm bool
inside_defer bool inside_defer bool
inside_generic_params bool // indicates if parsing between `<` and `>` of a method/function 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 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 or_is_handled bool // ignore `or` in this expression
builtin_mod bool // are we in the `builtin` module? builtin_mod bool // are we in the `builtin` module?
mod string // current module name 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 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__ 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__. is_generated bool // `[generated] module abc` - turn off compiler notices for that single .v file __only__.
attrs []ast.Attr // attributes before next decl stmt attrs []ast.Attr // attributes before next decl stmt
expr_mod string // for constructing full type names in parse_type() expr_mod string // for constructing full type names in parse_type()
scope &ast.Scope scope &ast.Scope
imports map[string]string // alias => mod_name imports map[string]string // alias => mod_name
ast_imports []ast.Import // mod_names ast_imports []ast.Import // mod_names
used_imports []string // alias used_imports []string // alias
auto_imports []string // imports, the user does not need to specify auto_imports []string // imports, the user does not need to specify
imported_symbols map[string]string imported_symbols map[string]string
is_amp bool // for generating the right code for `&Foo{}` is_amp bool // for generating the right code for `&Foo{}`
returns bool returns bool
is_stmt_ident bool // true while the beginning of a statement is an ident/selector is_stmt_ident bool // true while the beginning of a statement is an ident/selector
expecting_type bool // `is Type`, expecting type expecting_type bool // `is Type`, expecting type
errors []errors.Error errors []errors.Error
warnings []errors.Warning warnings []errors.Warning
notices []errors.Notice notices []errors.Notice
vet_errors []vet.Error vet_errors []vet.Error
cur_fn_name string cur_fn_name string
label_names []string label_names []string
name_error bool // indicates if the token is not a name or the name is on another line name_error bool // indicates if the token is not a name or the name is on another line
n_asm int // controls assembly labels n_asm int // controls assembly labels
global_labels []string global_labels []string
comptime_if_cond bool comptime_if_cond bool
defer_vars []ast.Ident defer_vars []ast.Ident
should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop
codegen_text string codegen_text string
struct_init_generic_types []ast.Type
} }
__global codegen_files = []&ast.File{} __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 { 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() 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() } typ := if short_syntax { ast.void_type } else { p.parse_type() }
p.expr_mod = '' p.expr_mod = ''
// sym := p.table.sym(typ) // 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() }) pos: first_pos.extend(if short_syntax { p.tok.pos() } else { p.prev_tok.pos() })
is_short: no_keys is_short: no_keys
pre_comments: pre_comments 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'")
}