ast, parser, checker: fix generic struct init with inconsistent generic types (#13359)
							parent
							
								
									89d399b035
								
							
						
					
					
						commit
						a61b4809dc
					
				|  | @ -411,6 +411,7 @@ pub mut: | |||
| 	has_update_expr      bool | ||||
| 	fields               []StructInitField | ||||
| 	embeds               []StructInitEmbed | ||||
| 	generic_types        []Type | ||||
| } | ||||
| 
 | ||||
| // import statement
 | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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[...]...)´
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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{} | ||||
|  |  | |||
|  | @ -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 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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'") | ||||
| } | ||||
		Loading…
	
		Reference in New Issue