all: struct embedding

pull/6698/head
Alexander Medvednikov 2020-10-30 07:09:26 +01:00
parent dca3d13606
commit 2c75b1397c
19 changed files with 279 additions and 29 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 | }

View File

@ -0,0 +1,5 @@
type Foo = int
struct Bar {
Foo
}

View File

@ -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 {

View File

@ -0,0 +1,13 @@
struct Foo {
x int
}
struct Test {
}
struct Bar {
Foo
Test
y int
z string
}

View File

@ -0,0 +1,12 @@
struct Foo {
x int
}
struct Test {}
struct Bar {
y int
Foo
z string
Test
}

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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 | }
| ^

View File

@ -0,0 +1,7 @@
struct Foo {
x int
}
struct C.Unknown {
Foo
}

View File

@ -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 | }

View File

@ -0,0 +1,9 @@
struct Abc {
foo int = 5
}
struct Xyz {
Abc
Abc
bar int
}

View File

@ -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 | }

View File

@ -0,0 +1,3 @@
struct WithEmbed {
custom.Foo
}

View File

@ -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 {

View File

@ -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
}