all: struct embedding
							parent
							
								
									dca3d13606
								
							
						
					
					
						commit
						2c75b1397c
					
				|  | @ -14,7 +14,8 @@ | |||
| - parallel parser (and maybe checker/gen?) | ||||
| - `recover()` from panics | ||||
| - IO streams | ||||
| - struct and interface embedding | ||||
| + struct embedding | ||||
| - interface embedding | ||||
| - interfaces: allow struct fields (not just methods) | ||||
| - vfmt: fix common errors automatically to save time (make vars mutable and vice versa, add missing imports etc) | ||||
| - method expressions with an explicit receiver as the first argument | ||||
|  |  | |||
|  | @ -121,7 +121,6 @@ pub: | |||
| 
 | ||||
| pub struct StructField { | ||||
| pub: | ||||
| 	name             string | ||||
| 	pos              token.Position | ||||
| 	type_pos         token.Position | ||||
| 	comments         []Comment | ||||
|  | @ -129,7 +128,9 @@ pub: | |||
| 	has_default_expr bool | ||||
| 	attrs            []table.Attr | ||||
| 	is_public        bool | ||||
| 	is_embed         bool | ||||
| pub mut: | ||||
| 	name             string | ||||
| 	typ              table.Type | ||||
| } | ||||
| 
 | ||||
|  | @ -166,7 +167,6 @@ pub struct StructDecl { | |||
| pub: | ||||
| 	pos          token.Position | ||||
| 	name         string | ||||
| 	fields       []StructField | ||||
| 	is_pub       bool | ||||
| 	mut_pos      int // mut:
 | ||||
| 	pub_pos      int // pub:
 | ||||
|  | @ -175,6 +175,15 @@ pub: | |||
| 	is_union     bool | ||||
| 	attrs        []table.Attr | ||||
| 	end_comments []Comment | ||||
| pub mut: | ||||
| 	fields       []StructField | ||||
| } | ||||
| 
 | ||||
| pub struct StructEmbedding { | ||||
| pub: | ||||
| 	name string | ||||
| 	typ  table.Type | ||||
| 	pos  token.Position | ||||
| } | ||||
| 
 | ||||
| pub struct InterfaceDecl { | ||||
|  |  | |||
|  | @ -327,16 +327,35 @@ pub fn (mut c Checker) struct_decl(decl ast.StructDecl) { | |||
| 	if decl.language == .v && !c.is_builtin_mod { | ||||
| 		c.check_valid_pascal_case(decl.name, 'struct name', decl.pos) | ||||
| 	} | ||||
| 	struct_sym := c.table.find_type(decl.name) or { | ||||
| 		table.TypeSymbol{} | ||||
| 	} | ||||
| 	mut struct_info := struct_sym.info as table.Struct | ||||
| 	for i, field in decl.fields { | ||||
| 		if decl.language == .v { | ||||
| 		if decl.language == .v && !field.is_embed { | ||||
| 			c.check_valid_snake_case(field.name, 'field name', field.pos) | ||||
| 		} | ||||
| 		sym := c.table.get_type_symbol(field.typ) | ||||
| 		if field.is_embed { | ||||
| 			if sym.info is table.Struct as sym_info { | ||||
| 				for embed_field in sym_info.fields { | ||||
| 					already_exists := struct_info.fields.filter(it.name == embed_field.name).len > 0 | ||||
| 					if !already_exists { | ||||
| 						struct_info.fields << { | ||||
| 							embed_field | | ||||
| 							embed_alias_for: field.name | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} else { | ||||
| 				c.error('`$sym.name` is not a struct', field.pos) | ||||
| 			} | ||||
| 		} | ||||
| 		for j in 0 .. i { | ||||
| 			if field.name == decl.fields[j].name { | ||||
| 				c.error('field name `$field.name` duplicate', field.pos) | ||||
| 			} | ||||
| 		} | ||||
| 		sym := c.table.get_type_symbol(field.typ) | ||||
| 		if sym.kind == .placeholder && decl.language != .c && !sym.name.starts_with('C.') { | ||||
| 			c.error(util.new_suggestion(sym.source_name, c.table.known_type_names()).say('unknown type `$sym.source_name`'), | ||||
| 				field.type_pos) | ||||
|  | @ -485,6 +504,31 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { | |||
| 							break | ||||
| 						} | ||||
| 					} | ||||
| 					/* | ||||
| 					if c.pref.is_verbose { | ||||
| 						for f in info.fields { | ||||
| 							if f.name == field_name { | ||||
| 								if f.embed_alias_for.len != 0 { | ||||
| 									mut has_embed_init := false | ||||
| 									for embedding in struct_init.fields { | ||||
| 										if embedding.name == f.embed_alias_for { | ||||
| 											has_embed_init = true | ||||
| 										} | ||||
| 									} | ||||
| 									if !has_embed_init { | ||||
| 										n := { | ||||
| 											f | | ||||
| 											embed_alias_for: '' | ||||
| 										} | ||||
| 										println(field) | ||||
| 										// struct_init.fields << { f | embed_alias_for: '' }
 | ||||
| 									} | ||||
| 								} | ||||
| 								break | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 					*/ | ||||
| 					if !exists { | ||||
| 						c.error('unknown field `$field.name` in struct literal of type `$type_sym.source_name`', | ||||
| 							field.pos) | ||||
|  | @ -514,7 +558,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type { | |||
| 			} | ||||
| 			// Check uninitialized refs
 | ||||
| 			for field in info.fields { | ||||
| 				if field.has_default_expr || field.name in inited_fields { | ||||
| 				if field.has_default_expr || field.name in inited_fields || field.embed_alias_for != | ||||
| 					'' { | ||||
| 					continue | ||||
| 				} | ||||
| 				if field.typ.is_ptr() && !c.pref.translated { | ||||
|  |  | |||
|  | @ -0,0 +1,6 @@ | |||
| vlib/v/checker/tests/struct_embed_invalid_type.vv:4:2: error: `Foo` is not a struct | ||||
|     2 | | ||||
|     3 | struct Bar { | ||||
|     4 |     Foo | ||||
|       |     ~~~ | ||||
|     5 | } | ||||
|  | @ -0,0 +1,5 @@ | |||
| type Foo = int | ||||
| 
 | ||||
| struct Bar { | ||||
| 	Foo | ||||
| } | ||||
|  | @ -598,7 +598,13 @@ pub fn (mut f Fmt) struct_decl(node ast.StructDecl) { | |||
| 			max_type = ft.len | ||||
| 		} | ||||
| 	} | ||||
| 	for field in node.fields.filter(it.is_embed) { | ||||
| 		f.writeln('\t$field.name') | ||||
| 	} | ||||
| 	for i, field in node.fields { | ||||
| 		if field.is_embed { | ||||
| 			continue | ||||
| 		} | ||||
| 		if i == node.mut_pos { | ||||
| 			f.writeln('mut:') | ||||
| 		} else if i == node.pub_pos { | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| struct Foo { | ||||
| 	x int | ||||
| } | ||||
| 
 | ||||
| struct Test { | ||||
| } | ||||
| 
 | ||||
| struct Bar { | ||||
| 	Foo | ||||
| 	Test | ||||
| 	y    int | ||||
| 	z    string | ||||
| } | ||||
|  | @ -0,0 +1,12 @@ | |||
| struct Foo { | ||||
| 	x int | ||||
| } | ||||
| 
 | ||||
| struct Test {} | ||||
| 
 | ||||
| struct Bar { | ||||
| 	y    int | ||||
| 	Foo | ||||
| 	z    string | ||||
| 	Test | ||||
| } | ||||
|  | @ -2323,6 +2323,17 @@ fn (mut g Gen) expr(node ast.Expr) { | |||
| 				return | ||||
| 			} | ||||
| 			g.expr(node.expr) | ||||
| 			// struct embedding
 | ||||
| 			if sym.kind == .struct_ { | ||||
| 				sym_info := sym.info as table.Struct | ||||
| 				x := sym_info.fields.filter(it.name == node.field_name) | ||||
| 				if x.len > 0 { | ||||
| 					field := x[0] | ||||
| 					if field.embed_alias_for != '' { | ||||
| 						g.write('.$field.embed_alias_for') | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			if node.expr_type.is_ptr() || sym.kind == .chan { | ||||
| 				g.write('->') | ||||
| 			} else { | ||||
|  | @ -3874,6 +3885,12 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { | |||
| 	mut initialized := false | ||||
| 	for i, field in struct_init.fields { | ||||
| 		inited_fields[field.name] = i | ||||
| 		if sym.info is table.Struct as struct_info { | ||||
| 			tfield := struct_info.fields.filter(it.name == field.name)[0] | ||||
| 			if tfield.embed_alias_for.len != 0 { | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
| 		if sym.kind != .struct_ { | ||||
| 			field_name := c_name(field.name) | ||||
| 			g.write('.$field_name = ') | ||||
|  | @ -3913,6 +3930,12 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) { | |||
| 		// g.zero_struct_fields(info, inited_fields)
 | ||||
| 		// nr_fields = info.fields.len
 | ||||
| 		for field in info.fields { | ||||
| 			if sym.info is table.Struct as struct_info { | ||||
| 				tfield := struct_info.fields.filter(it.name == field.name)[0] | ||||
| 				if tfield.embed_alias_for.len != 0 { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 			if field.name in inited_fields { | ||||
| 				sfield := struct_init.fields[inited_fields[field.name]] | ||||
| 				field_name := c_name(sfield.name) | ||||
|  | @ -4230,7 +4253,7 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) { | |||
| 					g.type_definitions.writeln('struct $name {') | ||||
| 				} | ||||
| 				if info.fields.len > 0 { | ||||
| 					for field in info.fields { | ||||
| 					for field in info.fields.filter(it.embed_alias_for == '') { | ||||
| 						// Some of these structs may want to contain
 | ||||
| 						// optionals that may not be defined at this point
 | ||||
| 						// if this is the case then we are going to
 | ||||
|  |  | |||
|  | @ -199,7 +199,6 @@ pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool, check | |||
| 		// `module.Type`
 | ||||
| 		// /if !(p.tok.lit in p.table.imports) {
 | ||||
| 		if !p.known_import(name) { | ||||
| 			println(p.table.imports) | ||||
| 			p.error('unknown module `$p.tok.lit`') | ||||
| 		} | ||||
| 		if p.tok.lit in p.imports { | ||||
|  |  | |||
|  | @ -71,6 +71,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { | |||
| 	// println('struct decl $name')
 | ||||
| 	mut ast_fields := []ast.StructField{} | ||||
| 	mut fields := []table.Field{} | ||||
| 	mut embedded_structs := []table.Type{} | ||||
| 	mut mut_pos := -1 | ||||
| 	mut pub_pos := -1 | ||||
| 	mut pub_mut_pos := -1 | ||||
|  | @ -142,18 +143,50 @@ fn (mut p Parser) struct_decl() ast.StructDecl { | |||
| 				} | ||||
| 			} | ||||
| 			field_start_pos := p.tok.position() | ||||
| 			field_name := p.check_name() | ||||
| 			// p.warn('field $field_name')
 | ||||
| 			for p.tok.kind == .comment { | ||||
| 				comments << p.comment() | ||||
| 				if p.tok.kind == .rcbr { | ||||
| 					break | ||||
| 			is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) || | ||||
| 				p.peek_tok.kind == .dot) && | ||||
| 				language == .v | ||||
| 			mut field_name := '' | ||||
| 			mut typ := table.Type(0) | ||||
| 			mut type_pos := token.Position{} | ||||
| 			mut field_pos := token.Position{} | ||||
| 			if is_embed { | ||||
| 				// struct embedding
 | ||||
| 				typ = p.parse_type() | ||||
| 				sym := p.table.get_type_symbol(typ) | ||||
| 				// main.Abc<int> => Abc
 | ||||
| 				mut symbol_name := sym.name.split('.')[1] | ||||
| 				// remove generic part from name
 | ||||
| 				if '<' in symbol_name { | ||||
| 					symbol_name = symbol_name.split('<')[0] | ||||
| 				} | ||||
| 				for p.tok.kind == .comment { | ||||
| 					comments << p.comment() | ||||
| 					if p.tok.kind == .rcbr { | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
| 				type_pos = p.prev_tok.position() | ||||
| 				field_pos = p.prev_tok.position() | ||||
| 				field_name = symbol_name | ||||
| 				if typ in embedded_structs { | ||||
| 					p.error_with_pos('cannot embed `$field_name` more than once', type_pos) | ||||
| 				} | ||||
| 				embedded_structs << typ | ||||
| 			} else { | ||||
| 				// struct field
 | ||||
| 				field_name = p.check_name() | ||||
| 				for p.tok.kind == .comment { | ||||
| 					comments << p.comment() | ||||
| 					if p.tok.kind == .rcbr { | ||||
| 						break | ||||
| 					} | ||||
| 				} | ||||
| 				typ = p.parse_type() | ||||
| 				type_pos = p.prev_tok.position() | ||||
| 				field_pos = field_start_pos.extend(type_pos) | ||||
| 			} | ||||
| 			// println(p.tok.position())
 | ||||
| 			typ := p.parse_type() | ||||
| 			type_pos := p.prev_tok.position() | ||||
| 			field_pos := field_start_pos.extend(type_pos) | ||||
| 			// Comments after type (same line)
 | ||||
| 			comments << p.eat_comments() | ||||
| 			if p.tok.kind == .lsbr { | ||||
|  | @ -162,18 +195,20 @@ fn (mut p Parser) struct_decl() ast.StructDecl { | |||
| 			} | ||||
| 			mut default_expr := ast.Expr{} | ||||
| 			mut has_default_expr := false | ||||
| 			if p.tok.kind == .assign { | ||||
| 				// Default value
 | ||||
| 				p.next() | ||||
| 				// default_expr = p.tok.lit
 | ||||
| 				// p.expr(0)
 | ||||
| 				default_expr = p.expr(0) | ||||
| 				match mut default_expr { | ||||
| 					ast.EnumVal { default_expr.typ = typ } | ||||
| 					// TODO: implement all types??
 | ||||
| 					else {} | ||||
| 			if !is_embed { | ||||
| 				if p.tok.kind == .assign { | ||||
| 					// Default value
 | ||||
| 					p.next() | ||||
| 					// default_expr = p.tok.lit
 | ||||
| 					// p.expr(0)
 | ||||
| 					default_expr = p.expr(0) | ||||
| 					match mut default_expr { | ||||
| 						ast.EnumVal { default_expr.typ = typ } | ||||
| 						// TODO: implement all types??
 | ||||
| 						else {} | ||||
| 					} | ||||
| 					has_default_expr = true | ||||
| 				} | ||||
| 				has_default_expr = true | ||||
| 			} | ||||
| 			// TODO merge table and ast Fields?
 | ||||
| 			ast_fields << ast.StructField{ | ||||
|  | @ -186,6 +221,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl { | |||
| 				has_default_expr: has_default_expr | ||||
| 				attrs: p.attrs | ||||
| 				is_public: is_field_pub | ||||
| 				is_embed: is_embed | ||||
| 			} | ||||
| 			fields << table.Field{ | ||||
| 				name: field_name | ||||
|  | @ -196,9 +232,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl { | |||
| 				is_mut: is_field_mut | ||||
| 				is_global: is_field_global | ||||
| 				attrs: p.attrs | ||||
| 				is_embed: is_embed | ||||
| 			} | ||||
| 			p.attrs = [] | ||||
| 			// println('struct field $ti.name $field_name')
 | ||||
| 		} | ||||
| 		p.top_level_statement_end() | ||||
| 		p.check(.rcbr) | ||||
|  |  | |||
|  | @ -0,0 +1,5 @@ | |||
| vlib/v/parser/tests/c_struct_no_embed.vv:7:1: error: bad type syntax | ||||
|     5 | struct C.Unknown { | ||||
|     6 |     Foo | ||||
|     7 | } | ||||
|       | ^ | ||||
|  | @ -0,0 +1,7 @@ | |||
| struct Foo { | ||||
| 	x int | ||||
| } | ||||
| 
 | ||||
| struct C.Unknown { | ||||
| 	Foo | ||||
| } | ||||
|  | @ -0,0 +1,7 @@ | |||
| vlib/v/parser/tests/struct_embed_duplicate.vv:7:2: error: cannot embed `Abc` more than once | ||||
|     5 | struct Xyz { | ||||
|     6 |     Abc | ||||
|     7 |     Abc | ||||
|       |     ~~~ | ||||
|     8 |     bar int | ||||
|     9 | } | ||||
|  | @ -0,0 +1,9 @@ | |||
| struct Abc { | ||||
| 	foo int = 5 | ||||
| } | ||||
| 
 | ||||
| struct Xyz { | ||||
| 	Abc | ||||
| 	Abc | ||||
| 	bar int | ||||
| } | ||||
|  | @ -0,0 +1,5 @@ | |||
| vlib/v/parser/tests/struct_embed_unknown_module.vv:2:2: error: unknown module `custom` | ||||
|     1 | struct WithEmbed { | ||||
|     2 |     custom.Foo | ||||
|       |     ~~~~~~ | ||||
|     3 | } | ||||
|  | @ -0,0 +1,3 @@ | |||
| struct WithEmbed { | ||||
| 	custom.Foo | ||||
| } | ||||
|  | @ -757,6 +757,8 @@ pub mut: | |||
| 	is_pub           bool | ||||
| 	is_mut           bool | ||||
| 	is_global        bool | ||||
| 	is_embed         bool | ||||
| 	embed_alias_for  string // name of the struct which contains this field name
 | ||||
| } | ||||
| 
 | ||||
| fn (f &Field) equals(o &Field) bool { | ||||
|  |  | |||
|  | @ -0,0 +1,57 @@ | |||
| import flag | ||||
| 
 | ||||
| struct Foo { | ||||
| 	x int | ||||
| 	y int = 5 | ||||
| } | ||||
| 
 | ||||
| struct Bar { | ||||
| 	Foo | ||||
| } | ||||
| 
 | ||||
| fn test_embed() { | ||||
| 	b := Bar{} | ||||
| 	assert b.x == 0 | ||||
| } | ||||
| 
 | ||||
| fn test_embed_direct_access() { | ||||
| 	b := Bar{Foo: Foo{}} | ||||
| 	assert b.Foo.y == 5 | ||||
| } | ||||
| 
 | ||||
| fn test_default_value() { | ||||
| 	b := Bar{Foo: Foo{}} | ||||
| 	assert b.y == 5 | ||||
| } | ||||
| /* | ||||
| fn test_initialize() { | ||||
| 	b := Bar{x: 1, y: 2} | ||||
| 	assert b.x == 1 | ||||
| 	assert b.y == 2 | ||||
| } | ||||
| */ | ||||
| struct Bar3 { | ||||
| 	Foo | ||||
| 	y string = 'test' | ||||
| } | ||||
| 
 | ||||
| fn test_overwrite_field() { | ||||
| 	b := Bar3{} | ||||
| 	assert b.y == 'test' | ||||
| } | ||||
| 
 | ||||
| struct TestEmbedFromModule { | ||||
| 	flag.Flag | ||||
| } | ||||
| 
 | ||||
| struct BarGeneric<T> { | ||||
| pub: | ||||
| 	foo T | ||||
| } | ||||
| struct BarGenericContainer { | ||||
| 	BarGeneric<int> | ||||
| } | ||||
| fn test_generic_embed() { | ||||
| 	b := BarGenericContainer{} | ||||
| 	assert b.BarGeneric.foo == 0 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue