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