checker/gen: fix generic struct init (#8322)
							parent
							
								
									58b37519e0
								
							
						
					
					
						commit
						d477e525bb
					
				|  | @ -90,7 +90,7 @@ mut: | |||
| struct WebhookServer { | ||||
| 	vweb.Context | ||||
| mut: | ||||
| 	gen_vc &GenVC | ||||
| 	gen_vc &GenVC = 0 // initialized in init_once
 | ||||
| } | ||||
| 
 | ||||
| // storage for flag options
 | ||||
|  |  | |||
|  | @ -264,6 +264,7 @@ pub: | |||
| 	pos      token.Position | ||||
| 	is_short bool | ||||
| pub mut: | ||||
| 	unresolved           bool | ||||
| 	pre_comments         []Comment | ||||
| 	typ                  table.Type | ||||
| 	update_expr          Expr | ||||
|  |  | |||
|  | @ -0,0 +1,72 @@ | |||
| module ast | ||||
| 
 | ||||
| import v.table | ||||
| 
 | ||||
| pub fn resolve_init(node StructInit, typ table.Type, t &table.Table) Expr { | ||||
| 	type_sym := t.get_type_symbol(typ) | ||||
| 	if type_sym.kind == .array { | ||||
| 		array_info := type_sym.info as table.Array | ||||
| 		mut has_len := false | ||||
| 		mut has_cap := false | ||||
| 		mut has_default := false | ||||
| 		mut len_expr := Expr{} | ||||
| 		mut cap_expr := Expr{} | ||||
| 		mut default_expr := Expr{} | ||||
| 		mut exprs := []Expr{} | ||||
| 		for field in node.fields { | ||||
| 			match field.name { | ||||
| 				'len' { | ||||
| 					has_len = true | ||||
| 					len_expr = field.expr | ||||
| 				} | ||||
| 				'cap' { | ||||
| 					has_cap = true | ||||
| 					len_expr = field.expr | ||||
| 				} | ||||
| 				'default' { | ||||
| 					has_default = true | ||||
| 					len_expr = field.expr | ||||
| 				} | ||||
| 				else { | ||||
| 					exprs << field.expr | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		return ArrayInit{ | ||||
| 			// TODO: mod is not being set for now, we could need this in future
 | ||||
| 			// mod: mod
 | ||||
| 			pos: node.pos | ||||
| 			typ: typ | ||||
| 			elem_type: array_info.elem_type | ||||
| 			has_len: has_len | ||||
| 			has_cap: has_cap | ||||
| 			has_default: has_default | ||||
| 			len_expr: len_expr | ||||
| 			cap_expr: cap_expr | ||||
| 			default_expr: default_expr | ||||
| 			exprs: exprs | ||||
| 		} | ||||
| 	} else if type_sym.kind == .map { | ||||
| 		map_info := type_sym.info as table.Map | ||||
| 		mut keys := []Expr{} | ||||
| 		mut vals := []Expr{} | ||||
| 		for field in node.fields { | ||||
| 			keys << StringLiteral{ | ||||
| 				val: field.name | ||||
| 			} | ||||
| 			vals << field.expr | ||||
| 		} | ||||
| 		return MapInit{ | ||||
| 			typ: typ | ||||
| 			key_type: map_info.key_type | ||||
| 			value_type: map_info.value_type | ||||
| 			keys: keys | ||||
| 			vals: vals | ||||
| 		} | ||||
| 	} | ||||
| 	// struct / other (sumtype?)
 | ||||
| 	return StructInit{ | ||||
| 		...node | ||||
| 		unresolved: false | ||||
| 	} | ||||
| } | ||||
|  | @ -460,6 +460,7 @@ pub fn (mut c Checker) infer_fn_types(f table.Fn, mut call_expr ast.CallExpr) { | |||
| 		} | ||||
| 		if typ == table.void_type { | ||||
| 			c.error('could not infer generic type `$gt_name` in call to `$f.name`', call_expr.pos) | ||||
| 			return | ||||
| 		} | ||||
| 		if c.pref.is_verbose { | ||||
| 			s := c.table.type_to_str(typ) | ||||
|  |  | |||
|  | @ -508,7 +508,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { | |||
| 	if struct_init.typ == 0 { | ||||
| 		c.error('unknown type', struct_init.pos) | ||||
| 	} | ||||
| 	type_sym := c.table.get_type_symbol(struct_init.typ) | ||||
| 	utyp := c.unwrap_generic(struct_init.typ) | ||||
| 	type_sym := c.table.get_type_symbol(utyp) | ||||
| 	if type_sym.kind == .sum_type && struct_init.fields.len == 1 { | ||||
| 		sexpr := struct_init.fields[0].expr.str() | ||||
| 		c.error('cast to sum type using `${type_sym.name}($sexpr)` not `$type_sym.name{$sexpr}`', | ||||
|  | @ -517,15 +518,16 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { | |||
| 	if type_sym.kind == .interface_ { | ||||
| 		c.error('cannot instantiate interface `$type_sym.name`', struct_init.pos) | ||||
| 	} | ||||
| 	if type_sym.kind == .alias { | ||||
| 		info := type_sym.info as table.Alias | ||||
| 		if info.parent_type.is_number() { | ||||
| 	if type_sym.info is table.Alias { | ||||
| 		if type_sym.info.parent_type.is_number() { | ||||
| 			c.error('cannot instantiate number type alias `$type_sym.name`', struct_init.pos) | ||||
| 			return table.void_type | ||||
| 		} | ||||
| 	} | ||||
| 	if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.mod != c.mod | ||||
| 		&& type_sym.language != .c { | ||||
| 	// allow init structs from generic if they're private except the type is from builtin module
 | ||||
| 	if !type_sym.is_public && type_sym.kind != .placeholder && type_sym.language != .c | ||||
| 		&& (type_sym.mod != c.mod && !(struct_init.typ.has_flag(.generic) | ||||
| 		&& type_sym.mod != 'builtin')) { | ||||
| 		c.error('type `$type_sym.name` is private', struct_init.pos) | ||||
| 	} | ||||
| 	if type_sym.kind == .struct_ { | ||||
|  | @ -3349,14 +3351,11 @@ fn (mut c Checker) stmts(stmts []ast.Stmt) { | |||
| pub fn (c &Checker) unwrap_generic(typ table.Type) table.Type { | ||||
| 	if typ.has_flag(.generic) { | ||||
| 		sym := c.table.get_type_symbol(typ) | ||||
| 		mut idx := 0 | ||||
| 		for i, generic_param in c.cur_fn.generic_params { | ||||
| 			if generic_param.name == sym.name { | ||||
| 				idx = i | ||||
| 				break | ||||
| 				return c.cur_generic_types[i].derive(typ).clear_flag(.generic) | ||||
| 			} | ||||
| 		} | ||||
| 		return c.cur_generic_types[idx].derive(typ).clear_flag(.generic) | ||||
| 	} | ||||
| 	return typ | ||||
| } | ||||
|  | @ -3583,6 +3582,9 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { | |||
| 			return c.string_inter_lit(mut node) | ||||
| 		} | ||||
| 		ast.StructInit { | ||||
| 			if node.unresolved { | ||||
| 				return c.expr(ast.resolve_init(node, c.unwrap_generic(node.typ), c.table)) | ||||
| 			} | ||||
| 			return c.struct_init(mut node) | ||||
| 		} | ||||
| 		ast.Type { | ||||
|  |  | |||
|  | @ -0,0 +1,6 @@ | |||
| vlib/v/checker/tests/check_generic_int_init.vv:2:9: error: type `int` is private | ||||
|     1 | fn test<T>() T { | ||||
|     2 |     return T{} | ||||
|       |            ~~~ | ||||
|     3 | } | ||||
|     4 | | ||||
|  | @ -0,0 +1,7 @@ | |||
| fn test<T>() T { | ||||
| 	return T{} | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
| 	_ := test<int>() | ||||
| } | ||||
|  | @ -2721,55 +2721,7 @@ fn (mut g Gen) expr(node ast.Expr) { | |||
| 			g.match_expr(node) | ||||
| 		} | ||||
| 		ast.MapInit { | ||||
| 			key_typ_str := g.typ(node.key_type) | ||||
| 			value_typ_str := g.typ(node.value_type) | ||||
| 			value_typ := g.table.get_type_symbol(node.value_type) | ||||
| 			key_typ := g.table.get_type_symbol(node.key_type) | ||||
| 			hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) | ||||
| 			size := node.vals.len | ||||
| 			mut shared_styp := '' // only needed for shared &[]{...}
 | ||||
| 			mut styp := '' | ||||
| 			is_amp := g.is_amp | ||||
| 			g.is_amp = false | ||||
| 			if is_amp { | ||||
| 				g.out.go_back(1) // delete the `&` already generated in `prefix_expr()
 | ||||
| 			} | ||||
| 			if g.is_shared { | ||||
| 				mut shared_typ := node.typ.set_flag(.shared_f) | ||||
| 				shared_styp = g.typ(shared_typ) | ||||
| 				g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.val = ') | ||||
| 			} else if is_amp { | ||||
| 				styp = g.typ(node.typ) | ||||
| 				g.write('($styp*)memdup(ADDR($styp, ') | ||||
| 			} | ||||
| 			if size > 0 { | ||||
| 				if value_typ.kind == .function { | ||||
| 					g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){') | ||||
| 				} else { | ||||
| 					g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){') | ||||
| 				} | ||||
| 				for expr in node.keys { | ||||
| 					g.expr(expr) | ||||
| 					g.write(', ') | ||||
| 				} | ||||
| 				if value_typ.kind == .function { | ||||
| 					g.write('}), _MOV((voidptr[$size]){') | ||||
| 				} else { | ||||
| 					g.write('}), _MOV(($value_typ_str[$size]){') | ||||
| 				} | ||||
| 				for expr in node.vals { | ||||
| 					g.expr(expr) | ||||
| 					g.write(', ') | ||||
| 				} | ||||
| 				g.write('}))') | ||||
| 			} else { | ||||
| 				g.write('new_map_2(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)') | ||||
| 			} | ||||
| 			if g.is_shared { | ||||
| 				g.write('}, sizeof($shared_styp))') | ||||
| 			} else if is_amp { | ||||
| 				g.write('), sizeof($styp))') | ||||
| 			} | ||||
| 			g.map_init(node) | ||||
| 		} | ||||
| 		ast.None { | ||||
| 			g.write('opt_none()') | ||||
|  | @ -2863,9 +2815,13 @@ fn (mut g Gen) expr(node ast.Expr) { | |||
| 			g.string_inter_literal(node) | ||||
| 		} | ||||
| 		ast.StructInit { | ||||
| 			if node.unresolved { | ||||
| 				g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table)) | ||||
| 			} else { | ||||
| 				// `user := User{name: 'Bob'}`
 | ||||
| 				g.struct_init(node) | ||||
| 			} | ||||
| 		} | ||||
| 		ast.SelectorExpr { | ||||
| 			g.selector_expr(node) | ||||
| 		} | ||||
|  | @ -3645,6 +3601,58 @@ fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var str | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) map_init(node ast.MapInit) { | ||||
| 	key_typ_str := g.typ(node.key_type) | ||||
| 	value_typ_str := g.typ(node.value_type) | ||||
| 	value_typ := g.table.get_type_symbol(node.value_type) | ||||
| 	key_typ := g.table.get_type_symbol(node.key_type) | ||||
| 	hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) | ||||
| 	size := node.vals.len | ||||
| 	mut shared_styp := '' // only needed for shared &[]{...}
 | ||||
| 	mut styp := '' | ||||
| 	is_amp := g.is_amp | ||||
| 	g.is_amp = false | ||||
| 	if is_amp { | ||||
| 		g.out.go_back(1) // delete the `&` already generated in `prefix_expr()
 | ||||
| 	} | ||||
| 	if g.is_shared { | ||||
| 		mut shared_typ := node.typ.set_flag(.shared_f) | ||||
| 		shared_styp = g.typ(shared_typ) | ||||
| 		g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.val = ') | ||||
| 	} else if is_amp { | ||||
| 		styp = g.typ(node.typ) | ||||
| 		g.write('($styp*)memdup(ADDR($styp, ') | ||||
| 	} | ||||
| 	if size > 0 { | ||||
| 		if value_typ.kind == .function { | ||||
| 			g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){') | ||||
| 		} else { | ||||
| 			g.write('new_map_init_2($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){') | ||||
| 		} | ||||
| 		for expr in node.keys { | ||||
| 			g.expr(expr) | ||||
| 			g.write(', ') | ||||
| 		} | ||||
| 		if value_typ.kind == .function { | ||||
| 			g.write('}), _MOV((voidptr[$size]){') | ||||
| 		} else { | ||||
| 			g.write('}), _MOV(($value_typ_str[$size]){') | ||||
| 		} | ||||
| 		for expr in node.vals { | ||||
| 			g.expr(expr) | ||||
| 			g.write(', ') | ||||
| 		} | ||||
| 		g.write('}))') | ||||
| 	} else { | ||||
| 		g.write('new_map_2(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)') | ||||
| 	} | ||||
| 	if g.is_shared { | ||||
| 		g.write('}, sizeof($shared_styp))') | ||||
| 	} else if is_amp { | ||||
| 		g.write('), sizeof($styp))') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) select_expr(node ast.SelectExpr) { | ||||
| 	is_expr := node.is_expr || g.inside_ternary > 0 | ||||
| 	cur_line := if is_expr { | ||||
|  |  | |||
|  | @ -354,14 +354,11 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { | |||
| pub fn (g &Gen) unwrap_generic(typ table.Type) table.Type { | ||||
| 	if typ.has_flag(.generic) { | ||||
| 		sym := g.table.get_type_symbol(typ) | ||||
| 		mut idx := 0 | ||||
| 		for i, generic_param in g.cur_fn.generic_params { | ||||
| 			if generic_param.name == sym.name { | ||||
| 				idx = i | ||||
| 				break | ||||
| 				return g.cur_generic_types[i].derive(typ).clear_flag(.generic) | ||||
| 			} | ||||
| 		} | ||||
| 		return g.cur_generic_types[idx].derive(typ).clear_flag(.generic) | ||||
| 	} | ||||
| 	return typ | ||||
| } | ||||
|  |  | |||
|  | @ -558,6 +558,13 @@ fn (mut g JsGen) expr(node ast.Expr) { | |||
| 			g.write("string('$text')") | ||||
| 		} | ||||
| 		ast.StructInit { | ||||
| 			// TODO: once generic fns/unwrap_generic is implemented
 | ||||
| 			// if node.unresolved {
 | ||||
| 			// 	g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table))
 | ||||
| 			// } else {
 | ||||
| 			// 	// `user := User{name: 'Bob'}`
 | ||||
| 			// 	g.gen_struct_init(node)
 | ||||
| 			// }
 | ||||
| 			// `user := User{name: 'Bob'}`
 | ||||
| 			g.gen_struct_init(node) | ||||
| 		} | ||||
|  | @ -1037,11 +1044,7 @@ fn (mut g JsGen) gen_struct_decl(node ast.StructDecl) { | |||
| 		g.inc_indent() | ||||
| 		g.write('return `$js_name {') | ||||
| 		for i, field in node.fields { | ||||
| 			g.write(if i == 0 { | ||||
| 				' ' | ||||
| 			} else { | ||||
| 				', ' | ||||
| 			}) | ||||
| 			g.write(if i == 0 { ' ' } else { ', ' }) | ||||
| 			match g.typ(field.typ).split('.').last() { | ||||
| 				'string' { g.write('$field.name: "\${this["$field.name"].toString()}"') } | ||||
| 				else { g.write('$field.name: \${this["$field.name"].toString()} ') } | ||||
|  |  | |||
|  | @ -409,7 +409,8 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { | |||
| 		p.check(.rcbr) | ||||
| 	} | ||||
| 	p.is_amp = saved_is_amp | ||||
| 	node := ast.StructInit{ | ||||
| 	return ast.StructInit{ | ||||
| 		unresolved: typ.has_flag(.generic) | ||||
| 		typ: typ | ||||
| 		fields: fields | ||||
| 		update_expr: update_expr | ||||
|  | @ -419,7 +420,6 @@ fn (mut p Parser) struct_init(short_syntax bool) ast.StructInit { | |||
| 		is_short: no_keys | ||||
| 		pre_comments: pre_comments | ||||
| 	} | ||||
| 	return node | ||||
| } | ||||
| 
 | ||||
| fn (mut p Parser) interface_decl() ast.InterfaceDecl { | ||||
|  |  | |||
|  | @ -44,10 +44,27 @@ fn (v Foo) new<T>() T { | |||
| 
 | ||||
| fn test_generic_method_with_map_type() { | ||||
| 	foo := Foo{} | ||||
| 	assert foo.new<map[string]string>() == map[string]string{} | ||||
| 	mut a := foo.new<map[string]string>() | ||||
| 	assert a == map[string]string{} | ||||
| 	assert a.len == 0 | ||||
| 	a['a'] = 'a' | ||||
| 	assert a.len == 1 | ||||
| 	assert a['a'] == 'a' | ||||
| } | ||||
| 
 | ||||
| fn test_generic_method_with_array_type() { | ||||
| 	foo := Foo{} | ||||
| 	assert foo.new<[]string>() == []string{} | ||||
| 	mut a := foo.new<[]string>() | ||||
| 	assert a == []string{} | ||||
| 	assert a.len == 0 | ||||
| 	a << 'a' | ||||
| 	assert a.len == 1 | ||||
| 	assert a[0] == 'a' | ||||
| } | ||||
| 
 | ||||
| fn test_generic_method_with_struct_type() { | ||||
| 	foo := Foo{} | ||||
| 	mut a := foo.new<Person>() | ||||
| 	a.name = 'a' | ||||
| 	assert a.name == 'a' | ||||
| } | ||||
|  |  | |||
|  | @ -386,3 +386,25 @@ fn test_multi_generic_args() { | |||
| 	assert multi_generic_args("Super", 2021) | ||||
| } | ||||
| 
 | ||||
| fn new<T>() T { | ||||
| 	return T{} | ||||
| } | ||||
| 
 | ||||
| fn test_generic_init() { | ||||
| 	// array init
 | ||||
| 	mut a := new<[]string>() | ||||
| 	assert a.len == 0 | ||||
| 	a << 'a' | ||||
| 	assert a.len == 1 | ||||
| 	assert a[0] == 'a' | ||||
| 	// map init
 | ||||
| 	mut b := new<map[string]string>() | ||||
| 	assert b.len == 0 | ||||
| 	b['b'] = 'b' | ||||
| 	assert b.len == 1 | ||||
| 	assert b['b'] == 'b' | ||||
| 	// struct init
 | ||||
| 	mut c := new<User>() | ||||
| 	c.name = 'c' | ||||
| 	assert c.name == 'c' | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue