all: struct embedding
parent
dca3d13606
commit
2c75b1397c
|
@ -14,7 +14,8 @@
|
||||||
- parallel parser (and maybe checker/gen?)
|
- parallel parser (and maybe checker/gen?)
|
||||||
- `recover()` from panics
|
- `recover()` from panics
|
||||||
- IO streams
|
- IO streams
|
||||||
- struct and interface embedding
|
+ struct embedding
|
||||||
|
- interface embedding
|
||||||
- interfaces: allow struct fields (not just methods)
|
- 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)
|
- 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
|
- method expressions with an explicit receiver as the first argument
|
||||||
|
|
|
@ -121,7 +121,6 @@ pub:
|
||||||
|
|
||||||
pub struct StructField {
|
pub struct StructField {
|
||||||
pub:
|
pub:
|
||||||
name string
|
|
||||||
pos token.Position
|
pos token.Position
|
||||||
type_pos token.Position
|
type_pos token.Position
|
||||||
comments []Comment
|
comments []Comment
|
||||||
|
@ -129,7 +128,9 @@ pub:
|
||||||
has_default_expr bool
|
has_default_expr bool
|
||||||
attrs []table.Attr
|
attrs []table.Attr
|
||||||
is_public bool
|
is_public bool
|
||||||
|
is_embed bool
|
||||||
pub mut:
|
pub mut:
|
||||||
|
name string
|
||||||
typ table.Type
|
typ table.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +167,6 @@ pub struct StructDecl {
|
||||||
pub:
|
pub:
|
||||||
pos token.Position
|
pos token.Position
|
||||||
name string
|
name string
|
||||||
fields []StructField
|
|
||||||
is_pub bool
|
is_pub bool
|
||||||
mut_pos int // mut:
|
mut_pos int // mut:
|
||||||
pub_pos int // pub:
|
pub_pos int // pub:
|
||||||
|
@ -175,6 +175,15 @@ pub:
|
||||||
is_union bool
|
is_union bool
|
||||||
attrs []table.Attr
|
attrs []table.Attr
|
||||||
end_comments []Comment
|
end_comments []Comment
|
||||||
|
pub mut:
|
||||||
|
fields []StructField
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StructEmbedding {
|
||||||
|
pub:
|
||||||
|
name string
|
||||||
|
typ table.Type
|
||||||
|
pos token.Position
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InterfaceDecl {
|
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 {
|
if decl.language == .v && !c.is_builtin_mod {
|
||||||
c.check_valid_pascal_case(decl.name, 'struct name', decl.pos)
|
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 {
|
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)
|
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 {
|
for j in 0 .. i {
|
||||||
if field.name == decl.fields[j].name {
|
if field.name == decl.fields[j].name {
|
||||||
c.error('field name `$field.name` duplicate', field.pos)
|
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.') {
|
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`'),
|
c.error(util.new_suggestion(sym.source_name, c.table.known_type_names()).say('unknown type `$sym.source_name`'),
|
||||||
field.type_pos)
|
field.type_pos)
|
||||||
|
@ -485,6 +504,31 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
|
||||||
break
|
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 {
|
if !exists {
|
||||||
c.error('unknown field `$field.name` in struct literal of type `$type_sym.source_name`',
|
c.error('unknown field `$field.name` in struct literal of type `$type_sym.source_name`',
|
||||||
field.pos)
|
field.pos)
|
||||||
|
@ -514,7 +558,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
|
||||||
}
|
}
|
||||||
// Check uninitialized refs
|
// Check uninitialized refs
|
||||||
for field in info.fields {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if field.typ.is_ptr() && !c.pref.translated {
|
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
|
max_type = ft.len
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for field in node.fields.filter(it.is_embed) {
|
||||||
|
f.writeln('\t$field.name')
|
||||||
|
}
|
||||||
for i, field in node.fields {
|
for i, field in node.fields {
|
||||||
|
if field.is_embed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if i == node.mut_pos {
|
if i == node.mut_pos {
|
||||||
f.writeln('mut:')
|
f.writeln('mut:')
|
||||||
} else if i == node.pub_pos {
|
} 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
|
return
|
||||||
}
|
}
|
||||||
g.expr(node.expr)
|
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 {
|
if node.expr_type.is_ptr() || sym.kind == .chan {
|
||||||
g.write('->')
|
g.write('->')
|
||||||
} else {
|
} else {
|
||||||
|
@ -3874,6 +3885,12 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
|
||||||
mut initialized := false
|
mut initialized := false
|
||||||
for i, field in struct_init.fields {
|
for i, field in struct_init.fields {
|
||||||
inited_fields[field.name] = i
|
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_ {
|
if sym.kind != .struct_ {
|
||||||
field_name := c_name(field.name)
|
field_name := c_name(field.name)
|
||||||
g.write('.$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)
|
// g.zero_struct_fields(info, inited_fields)
|
||||||
// nr_fields = info.fields.len
|
// nr_fields = info.fields.len
|
||||||
for field in info.fields {
|
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 {
|
if field.name in inited_fields {
|
||||||
sfield := struct_init.fields[inited_fields[field.name]]
|
sfield := struct_init.fields[inited_fields[field.name]]
|
||||||
field_name := c_name(sfield.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 {')
|
g.type_definitions.writeln('struct $name {')
|
||||||
}
|
}
|
||||||
if info.fields.len > 0 {
|
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
|
// Some of these structs may want to contain
|
||||||
// optionals that may not be defined at this point
|
// optionals that may not be defined at this point
|
||||||
// if this is the case then we are going to
|
// 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`
|
// `module.Type`
|
||||||
// /if !(p.tok.lit in p.table.imports) {
|
// /if !(p.tok.lit in p.table.imports) {
|
||||||
if !p.known_import(name) {
|
if !p.known_import(name) {
|
||||||
println(p.table.imports)
|
|
||||||
p.error('unknown module `$p.tok.lit`')
|
p.error('unknown module `$p.tok.lit`')
|
||||||
}
|
}
|
||||||
if p.tok.lit in p.imports {
|
if p.tok.lit in p.imports {
|
||||||
|
|
|
@ -71,6 +71,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
|
||||||
// println('struct decl $name')
|
// println('struct decl $name')
|
||||||
mut ast_fields := []ast.StructField{}
|
mut ast_fields := []ast.StructField{}
|
||||||
mut fields := []table.Field{}
|
mut fields := []table.Field{}
|
||||||
|
mut embedded_structs := []table.Type{}
|
||||||
mut mut_pos := -1
|
mut mut_pos := -1
|
||||||
mut pub_pos := -1
|
mut pub_pos := -1
|
||||||
mut pub_mut_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_start_pos := p.tok.position()
|
||||||
field_name := p.check_name()
|
is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) ||
|
||||||
// p.warn('field $field_name')
|
p.peek_tok.kind == .dot) &&
|
||||||
for p.tok.kind == .comment {
|
language == .v
|
||||||
comments << p.comment()
|
mut field_name := ''
|
||||||
if p.tok.kind == .rcbr {
|
mut typ := table.Type(0)
|
||||||
break
|
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())
|
// 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 after type (same line)
|
||||||
comments << p.eat_comments()
|
comments << p.eat_comments()
|
||||||
if p.tok.kind == .lsbr {
|
if p.tok.kind == .lsbr {
|
||||||
|
@ -162,18 +195,20 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
|
||||||
}
|
}
|
||||||
mut default_expr := ast.Expr{}
|
mut default_expr := ast.Expr{}
|
||||||
mut has_default_expr := false
|
mut has_default_expr := false
|
||||||
if p.tok.kind == .assign {
|
if !is_embed {
|
||||||
// Default value
|
if p.tok.kind == .assign {
|
||||||
p.next()
|
// Default value
|
||||||
// default_expr = p.tok.lit
|
p.next()
|
||||||
// p.expr(0)
|
// default_expr = p.tok.lit
|
||||||
default_expr = p.expr(0)
|
// p.expr(0)
|
||||||
match mut default_expr {
|
default_expr = p.expr(0)
|
||||||
ast.EnumVal { default_expr.typ = typ }
|
match mut default_expr {
|
||||||
// TODO: implement all types??
|
ast.EnumVal { default_expr.typ = typ }
|
||||||
else {}
|
// TODO: implement all types??
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
has_default_expr = true
|
||||||
}
|
}
|
||||||
has_default_expr = true
|
|
||||||
}
|
}
|
||||||
// TODO merge table and ast Fields?
|
// TODO merge table and ast Fields?
|
||||||
ast_fields << ast.StructField{
|
ast_fields << ast.StructField{
|
||||||
|
@ -186,6 +221,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
|
||||||
has_default_expr: has_default_expr
|
has_default_expr: has_default_expr
|
||||||
attrs: p.attrs
|
attrs: p.attrs
|
||||||
is_public: is_field_pub
|
is_public: is_field_pub
|
||||||
|
is_embed: is_embed
|
||||||
}
|
}
|
||||||
fields << table.Field{
|
fields << table.Field{
|
||||||
name: field_name
|
name: field_name
|
||||||
|
@ -196,9 +232,9 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
|
||||||
is_mut: is_field_mut
|
is_mut: is_field_mut
|
||||||
is_global: is_field_global
|
is_global: is_field_global
|
||||||
attrs: p.attrs
|
attrs: p.attrs
|
||||||
|
is_embed: is_embed
|
||||||
}
|
}
|
||||||
p.attrs = []
|
p.attrs = []
|
||||||
// println('struct field $ti.name $field_name')
|
|
||||||
}
|
}
|
||||||
p.top_level_statement_end()
|
p.top_level_statement_end()
|
||||||
p.check(.rcbr)
|
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_pub bool
|
||||||
is_mut bool
|
is_mut bool
|
||||||
is_global 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 {
|
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