all: reimplement struct embedding with methods (#7506)

pull/7525/head
Daniel Däschle 2020-12-23 19:12:49 +01:00 committed by GitHub
parent 7e8add24dd
commit b27f5c378c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 411 additions and 239 deletions

View File

@ -1209,7 +1209,7 @@ mut:
struct Button { struct Button {
Widget Widget
title string title string
} }
mut button := Button{ mut button := Button{

View File

@ -103,16 +103,17 @@ pub:
// `foo.bar` // `foo.bar`
pub struct SelectorExpr { pub struct SelectorExpr {
pub: pub:
pos token.Position pos token.Position
expr Expr // expr.field_name expr Expr // expr.field_name
field_name string field_name string
is_mut bool // is used for the case `if mut ident.selector is MyType {`, it indicates if the root ident is mutable is_mut bool // is used for the case `if mut ident.selector is MyType {`, it indicates if the root ident is mutable
mut_pos token.Position mut_pos token.Position
pub mut: pub mut:
expr_type table.Type // type of `Foo` in `Foo.bar` expr_type table.Type // type of `Foo` in `Foo.bar`
typ table.Type // type of the entire thing (`Foo.bar`) typ table.Type // type of the entire thing (`Foo.bar`)
name_type table.Type // T in `T.name` or typeof in `typeof(expr).name` name_type table.Type // T in `T.name` or typeof in `typeof(expr).name`
scope &Scope scope &Scope
from_embed_type table.Type // holds the type of the embed that the method is called from
} }
// root_ident returns the origin ident where the selector started. // root_ident returns the origin ident where the selector started.
@ -144,7 +145,6 @@ 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 name string
typ table.Type typ table.Type
@ -192,10 +192,17 @@ pub:
is_union bool is_union bool
attrs []table.Attr attrs []table.Attr
end_comments []Comment end_comments []Comment
embeds []Embed
pub mut: pub mut:
fields []StructField fields []StructField
} }
pub struct Embed {
pub:
typ table.Type
pos token.Position
}
pub struct StructEmbedding { pub struct StructEmbedding {
pub: pub:
name string name string
@ -225,6 +232,18 @@ pub mut:
expected_type table.Type expected_type table.Type
} }
pub struct StructInitEmbed {
pub:
expr Expr
pos token.Position
comments []Comment
next_comments []Comment
pub mut:
name string
typ table.Type
expected_type table.Type
}
pub struct StructInit { pub struct StructInit {
pub: pub:
pos token.Position pos token.Position
@ -233,6 +252,7 @@ pub:
pub mut: pub mut:
typ table.Type typ table.Type
fields []StructInitField fields []StructInitField
embeds []StructInitEmbed
} }
// import statement // import statement
@ -320,6 +340,7 @@ pub mut:
generic_list_pos token.Position generic_list_pos token.Position
free_receiver bool // true if the receiver expression needs to be freed free_receiver bool // true if the receiver expression needs to be freed
scope &Scope scope &Scope
from_embed_type table.Type // holds the type of the embed that the method is called from
} }
/* /*

View File

@ -371,27 +371,17 @@ pub fn (mut c Checker) struct_decl(decl ast.StructDecl) {
} }
mut struct_sym := c.table.find_type(decl.name) or { table.TypeSymbol{} } mut struct_sym := c.table.find_type(decl.name) or { table.TypeSymbol{} }
if mut struct_sym.info is table.Struct { if mut struct_sym.info is table.Struct {
for embed in decl.embeds {
embed_sym := c.table.get_type_symbol(embed.typ)
if embed_sym.kind != .struct_ {
c.error('`$embed_sym.name` is not a struct', embed.pos)
}
}
for i, field in decl.fields { for i, field in decl.fields {
if decl.language == .v && !field.is_embed { if decl.language == .v {
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) sym := c.table.get_type_symbol(field.typ)
if field.is_embed {
if mut sym.info is table.Struct {
for embed_field in sym.info.fields {
already_exists := struct_sym.info.fields.filter(it.name == embed_field.name).len >
0
if !already_exists {
struct_sym.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)
@ -528,6 +518,8 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
mut inited_fields := []string{} mut inited_fields := []string{}
for i, field in struct_init.fields { for i, field in struct_init.fields {
mut info_field := table.Field{} mut info_field := table.Field{}
mut embed_type := table.Type(0)
mut is_embed := false
mut field_name := '' mut field_name := ''
if struct_init.is_short { if struct_init.is_short {
if i >= info.fields.len { if i >= info.fields.len {
@ -548,31 +540,17 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
break break
} }
} }
/* if !exists {
if c.pref.is_verbose { for embed in info.embeds {
for f in info.fields { embed_sym := c.table.get_type_symbol(embed)
if f.name == field_name { if embed_sym.embed_name() == field_name {
if f.embed_alias_for.len != 0 { exists = true
mut has_embed_init := false embed_type = embed
for embedding in struct_init.fields { is_embed = true
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 break
} }
} }
} }
*/
if !exists { if !exists {
c.error('unknown field `$field.name` in struct literal of type `$type_sym.name`', c.error('unknown field `$field.name` in struct literal of type `$type_sym.name`',
field.pos) field.pos)
@ -584,29 +562,43 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
continue continue
} }
} }
inited_fields << field_name if is_embed {
field_type_sym := c.table.get_type_symbol(info_field.typ) c.expected_type = embed_type
c.expected_type = info_field.typ expr_type := c.expr(field.expr)
expr_type := c.expr(field.expr) expr_type_sym := c.table.get_type_symbol(expr_type)
expr_type_sym := c.table.get_type_symbol(expr_type) if expr_type != table.void_type && expr_type_sym.kind != .placeholder {
if field_type_sym.kind == .interface_ { c.check_expected(expr_type, embed_type) or {
c.type_implements(expr_type, info_field.typ, field.pos) c.error('cannot assign to field `$info_field.name`: $err',
} else if expr_type != table.void_type && expr_type_sym.kind != .placeholder { field.pos)
c.check_expected(expr_type, info_field.typ) or { }
c.error('cannot assign to field `$info_field.name`: $err', field.pos)
} }
struct_init.fields[i].typ = expr_type
struct_init.fields[i].expected_type = embed_type
} else {
inited_fields << field_name
field_type_sym := c.table.get_type_symbol(info_field.typ)
c.expected_type = info_field.typ
expr_type := c.expr(field.expr)
expr_type_sym := c.table.get_type_symbol(expr_type)
if field_type_sym.kind == .interface_ {
c.type_implements(expr_type, info_field.typ, field.pos)
} else if expr_type != table.void_type && expr_type_sym.kind != .placeholder {
c.check_expected(expr_type, info_field.typ) or {
c.error('cannot assign to field `$info_field.name`: $err',
field.pos)
}
}
if info_field.typ.is_ptr() && !expr_type.is_ptr() && !expr_type.is_pointer() &&
!expr_type.is_number() {
c.error('ref', field.pos)
}
struct_init.fields[i].typ = expr_type
struct_init.fields[i].expected_type = info_field.typ
} }
if info_field.typ.is_ptr() && !expr_type.is_ptr() && !expr_type.is_pointer() &&
!expr_type.is_number() {
c.error('ref', field.pos)
}
struct_init.fields[i].typ = expr_type
struct_init.fields[i].expected_type = info_field.typ
} }
// 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 || field.embed_alias_for != if field.has_default_expr || field.name in inited_fields {
'' {
continue continue
} }
if field.typ.is_ptr() && !c.pref.translated { if field.typ.is_ptr() && !c.pref.translated {
@ -992,7 +984,23 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
match typ_sym.kind { match typ_sym.kind {
.struct_ { .struct_ {
struct_info := typ_sym.info as table.Struct struct_info := typ_sym.info as table.Struct
field_info := struct_info.find_field(expr.field_name) or { mut has_field := true
mut field_info := struct_info.find_field(expr.field_name) or {
has_field = false
table.Field{}
}
if !has_field {
for embed in struct_info.embeds {
embed_sym := c.table.get_type_symbol(embed)
embed_struct_info := embed_sym.info as table.Struct
if embed_field_info := embed_struct_info.find_field(expr.field_name) {
has_field = true
field_info = embed_field_info
break
}
}
}
if !has_field {
type_str := c.table.type_to_str(expr.expr_type) type_str := c.table.type_to_str(expr.expr_type)
c.error('unknown field `${type_str}.$expr.field_name`', expr.pos) c.error('unknown field `${type_str}.$expr.field_name`', expr.pos)
return '', pos return '', pos
@ -1238,7 +1246,36 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
c.error('cannot $method_name `$arg_sym.name` to `$left_type_sym.name`', arg_expr.position()) c.error('cannot $method_name `$arg_sym.name` to `$left_type_sym.name`', arg_expr.position())
} }
} }
if method := c.table.type_find_method(left_type_sym, method_name) { mut method := table.Fn{}
mut has_method := false
if m := c.table.type_find_method(left_type_sym, method_name) {
method = m
has_method = true
} else {
if left_type_sym.info is table.Struct {
mut found_methods := []table.Fn{}
mut embed_of_found_methods := []table.Type{}
for embed in left_type_sym.info.embeds {
embed_sym := c.table.get_type_symbol(embed)
if m := c.table.type_find_method(embed_sym, method_name) {
found_methods << m
embed_of_found_methods << embed
}
}
if found_methods.len == 1 {
method = found_methods[0]
has_method = true
call_expr.from_embed_type = embed_of_found_methods[0]
} else if found_methods.len > 1 {
c.error('ambiguous method `$method_name`', call_expr.pos)
}
}
if left_type_sym.kind == .aggregate {
// the error message contains the problematic type
unknown_method_msg = err
}
}
if has_method {
if !method.is_pub && !c.is_builtin_mod && !c.pref.is_test && left_type_sym.mod != c.mod && if !method.is_pub && !c.is_builtin_mod && !c.pref.is_test && left_type_sym.mod != c.mod &&
left_type_sym.mod != '' { // method.mod != c.mod { left_type_sym.mod != '' { // method.mod != c.mod {
// If a private method is called outside of the module // If a private method is called outside of the module
@ -1337,15 +1374,11 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
// TODO: cant we just set all these to the concrete type in checker? then no need in gen // TODO: cant we just set all these to the concrete type in checker? then no need in gen
call_expr.receiver_type = left_type.derive(method.params[0].typ).set_flag(.generic) call_expr.receiver_type = left_type.derive(method.params[0].typ).set_flag(.generic)
} else { } else {
// note: correct receiver type is automatically set here on struct embed calls
call_expr.receiver_type = method.params[0].typ call_expr.receiver_type = method.params[0].typ
} }
call_expr.return_type = method.return_type call_expr.return_type = method.return_type
return method.return_type return method.return_type
} else {
if left_type_sym.kind == .aggregate {
// the error message contains the problematic type
unknown_method_msg = err
}
} }
// TODO: str methods // TODO: str methods
if method_name == 'str' { if method_name == 'str' {
@ -1846,7 +1879,47 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
} }
} }
mut unknown_field_msg := 'type `$sym.name` has no field or method `$field_name`' mut unknown_field_msg := 'type `$sym.name` has no field or method `$field_name`'
if field := c.table.struct_find_field(sym, field_name) { mut has_field := false
mut field := table.Field{}
if field_name.len > 0 && field_name[0].is_capital() && sym.info is table.Struct {
// x.Foo.y => access the embedded struct
sym_info := sym.info as table.Struct
for embed in sym_info.embeds {
embed_sym := c.table.get_type_symbol(embed)
if embed_sym.embed_name() == field_name {
selector_expr.typ = embed
return embed
}
}
} else {
if f := c.table.struct_find_field(sym, field_name) {
has_field = true
field = f
} else {
if sym.info is table.Struct {
mut found_fields := []table.Field{}
mut embed_of_found_fields := []table.Type{}
for embed in sym.info.embeds {
embed_sym := c.table.get_type_symbol(embed)
if f := c.table.struct_find_field(embed_sym, field_name) {
found_fields << f
embed_of_found_fields << embed
}
}
if found_fields.len == 1 {
field = found_fields[0]
has_field = true
selector_expr.from_embed_type = embed_of_found_fields[0]
} else if found_fields.len > 1 {
c.error('ambiguous field `$field_name`', selector_expr.pos)
}
}
if sym.kind == .aggregate {
unknown_field_msg = err
}
}
}
if has_field {
if sym.mod != c.mod && !field.is_pub && sym.language != .c { if sym.mod != c.mod && !field.is_pub && sym.language != .c {
c.error('field `${sym.name}.$field_name` is not public', selector_expr.pos) c.error('field `${sym.name}.$field_name` is not public', selector_expr.pos)
} }
@ -1860,19 +1933,14 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
} }
selector_expr.typ = field.typ selector_expr.typ = field.typ
return field.typ return field.typ
} else {
if sym.kind == .aggregate {
unknown_field_msg = err
}
} }
if sym.kind !in [.struct_, .aggregate] { if sym.kind !in [.struct_, .aggregate] {
if sym.kind != .placeholder { if sym.kind != .placeholder {
c.error('`$sym.name` is not a struct', selector_expr.pos) c.error('`$sym.name` is not a struct', selector_expr.pos)
} }
} else { } else {
if sym.kind == .struct_ { if sym.info is table.Struct {
sss := sym.info as table.Struct suggestion := util.new_suggestion(field_name, sym.info.fields.map(it.name))
suggestion := util.new_suggestion(field_name, sss.fields.map(it.name))
c.error(suggestion.say(unknown_field_msg), selector_expr.pos) c.error(suggestion.say(unknown_field_msg), selector_expr.pos)
} }
c.error(unknown_field_msg, selector_expr.pos) c.error(unknown_field_msg, selector_expr.pos)

View File

@ -0,0 +1,13 @@
vlib/v/checker/tests/ambiguous_field_method_err.vv:22:4: error: ambiguous method `test`
20 | fn main() {
21 | b := Bar{}
22 | b.test()
| ~~~~~~
23 | n := b.name
24 | }
vlib/v/checker/tests/ambiguous_field_method_err.vv:23:9: error: ambiguous field `name`
21 | b := Bar{}
22 | b.test()
23 | n := b.name
| ~~~~
24 | }

View File

@ -0,0 +1,24 @@
struct Foo {
name int = 5
}
struct Bar {
Foo
Foo2
}
struct Foo2 {
name string
}
fn (f Foo2) test() {
println(f)
}
fn (f Foo) test() {
println(f)
}
fn main() {
b := Bar{}
b.test()
n := b.name
}

View File

@ -630,13 +630,11 @@ 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) { for embed in node.embeds {
f.writeln('\t$field.name') styp := f.table.type_to_str(embed.typ)
f.writeln('\t$styp')
} }
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 {

View File

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

View File

@ -7,6 +7,6 @@ struct Test {}
struct Bar { struct Bar {
Foo Foo
Test Test
y int y int
z string z string
} }

View File

@ -2664,93 +2664,7 @@ fn (mut g Gen) expr(node ast.Expr) {
g.struct_init(node) g.struct_init(node)
} }
ast.SelectorExpr { ast.SelectorExpr {
prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once g.selector_expr(node)
g.prevent_sum_type_unwrapping_once = false
if node.name_type > 0 {
g.type_name(node.name_type)
return
}
if node.expr_type == 0 {
g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos)
}
sym := g.table.get_type_symbol(node.expr_type)
// if node expr is a root ident and an optional
mut is_optional := node.expr is ast.Ident && node.expr_type.has_flag(.optional)
if is_optional {
opt_base_typ := g.base_type(node.expr_type)
g.writeln('(*($opt_base_typ*)')
}
if sym.kind == .array_fixed {
assert node.field_name == 'len'
info := sym.info as table.ArrayFixed
g.write('$info.size')
return
}
if sym.kind == .chan && node.field_name == 'len' {
g.write('sync__Channel_len(')
g.expr(node.expr)
g.write(')')
return
}
mut sum_type_deref_field := ''
if f := g.table.struct_find_field(sym, node.field_name) {
field_sym := g.table.get_type_symbol(f.typ)
if field_sym.kind == .sum_type {
if !prevent_sum_type_unwrapping_once {
// check first if field is sum type because scope searching is expensive
scope := g.file.scope.innermost(node.pos.pos)
if field := scope.find_struct_field(node.expr_type, node.field_name) {
// union sum type deref
for i, typ in field.sum_type_casts {
g.write('(*')
cast_sym := g.table.get_type_symbol(typ)
if i != 0 {
dot := if field.typ.is_ptr() { '->' } else { '.' }
sum_type_deref_field += ')$dot'
}
if mut cast_sym.info is table.Aggregate {
agg_sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx])
sum_type_deref_field += '_$agg_sym.cname'
// sum_type_deref_field += '_${cast_sym.info.types[g.aggregate_type_idx]}'
} else {
sum_type_deref_field += '_$cast_sym.cname'
}
}
}
}
}
}
g.expr(node.expr)
if is_optional {
g.write('.data)')
}
// 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 {
// g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /')
g.write('.')
}
if node.expr_type.has_flag(.shared_f) {
g.write('val.')
}
if node.expr_type == 0 {
verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `$node.expr` | field: `$node.field_name` | file: $g.file.path | line: $node.pos.line_nr')
}
g.write(c_name(node.field_name))
if sum_type_deref_field != '' {
g.write('.$sum_type_deref_field)')
}
} }
ast.Type { ast.Type {
// match sum Type // match sum Type
@ -2813,6 +2727,93 @@ fn (mut g Gen) typeof_expr(node ast.TypeOf) {
} }
} }
fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once
g.prevent_sum_type_unwrapping_once = false
if node.name_type > 0 {
g.type_name(node.name_type)
return
}
if node.expr_type == 0 {
g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos)
}
sym := g.table.get_type_symbol(node.expr_type)
// if node expr is a root ident and an optional
mut is_optional := node.expr is ast.Ident && node.expr_type.has_flag(.optional)
if is_optional {
opt_base_typ := g.base_type(node.expr_type)
g.writeln('(*($opt_base_typ*)')
}
if sym.kind == .array_fixed {
assert node.field_name == 'len'
info := sym.info as table.ArrayFixed
g.write('$info.size')
return
}
if sym.kind == .chan && node.field_name == 'len' {
g.write('sync__Channel_len(')
g.expr(node.expr)
g.write(')')
return
}
mut sum_type_deref_field := ''
if f := g.table.struct_find_field(sym, node.field_name) {
field_sym := g.table.get_type_symbol(f.typ)
if field_sym.kind == .sum_type {
if !prevent_sum_type_unwrapping_once {
// check first if field is sum type because scope searching is expensive
scope := g.file.scope.innermost(node.pos.pos)
if field := scope.find_struct_field(node.expr_type, node.field_name) {
// union sum type deref
for i, typ in field.sum_type_casts {
g.write('(*')
cast_sym := g.table.get_type_symbol(typ)
if i != 0 {
dot := if field.typ.is_ptr() { '->' } else { '.' }
sum_type_deref_field += ')$dot'
}
if mut cast_sym.info is table.Aggregate {
agg_sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx])
sum_type_deref_field += '_$agg_sym.cname'
// sum_type_deref_field += '_${cast_sym.info.types[g.aggregate_type_idx]}'
} else {
sum_type_deref_field += '_$cast_sym.cname'
}
}
}
}
}
}
g.expr(node.expr)
if is_optional {
g.write('.data)')
}
// struct embedding
if sym.info is table.Struct {
if node.from_embed_type != 0 {
embed_sym := g.table.get_type_symbol(node.from_embed_type)
embed_name := embed_sym.embed_name()
g.write('.$embed_name')
}
}
if node.expr_type.is_ptr() || sym.kind == .chan {
g.write('->')
} else {
// g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /')
g.write('.')
}
if node.expr_type.has_flag(.shared_f) {
g.write('val.')
}
if node.expr_type == 0 {
verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `$node.expr` | field: `$node.field_name` | file: $g.file.path | line: $node.pos.line_nr')
}
g.write(c_name(node.field_name))
if sum_type_deref_field != '' {
g.write('.$sum_type_deref_field)')
}
}
fn (mut g Gen) enum_expr(node ast.Expr) { fn (mut g Gen) enum_expr(node ast.Expr) {
match node { match node {
ast.EnumVal { g.write(node.val) } ast.EnumVal { g.write(node.val) }
@ -4347,16 +4348,6 @@ 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 mut sym.info is table.Struct {
equal_fields := sym.info.fields.filter(it.name == field.name)
if equal_fields.len == 0 {
continue
}
tfield := equal_fields[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 = ')
@ -4396,6 +4387,23 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
if info.is_union && struct_init.fields.len > 1 { if info.is_union && struct_init.fields.len > 1 {
verror('union must not have more than 1 initializer') verror('union must not have more than 1 initializer')
} }
for embed in info.embeds {
embed_sym := g.table.get_type_symbol(embed)
embed_name := embed_sym.embed_name()
if embed_name !in inited_fields {
default_init := ast.StructInit{
typ: embed
}
g.write('.$embed_name = ')
g.struct_init(default_init)
if is_multiline {
g.writeln(',')
} else {
g.write(',')
}
initialized = true
}
}
// 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 {
@ -4404,10 +4412,6 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
if equal_fields.len == 0 { if equal_fields.len == 0 {
continue continue
} }
tfield := equal_fields[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]]
@ -4792,8 +4796,8 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) {
} else { } else {
g.type_definitions.writeln('struct $name {') g.type_definitions.writeln('struct $name {')
} }
if typ.info.fields.len > 0 { if typ.info.fields.len > 0 || typ.info.embeds.len > 0 {
for field in typ.info.fields.filter(it.embed_alias_for == '') { for field in typ.info.fields {
// 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
@ -4887,6 +4891,14 @@ fn (g &Gen) sort_structs(typesa []table.TypeSymbol) []table.TypeSymbol {
// if info.is_interface { // if info.is_interface {
// continue // continue
// } // }
for embed in t.info.embeds {
dep := g.table.get_type_symbol(embed).name
// skip if not in types list or already in deps
if dep !in type_names || dep in field_deps {
continue
}
field_deps << dep
}
for field in t.info.fields { for field in t.info.fields {
dep := g.table.get_type_symbol(field.typ).name dep := g.table.get_type_symbol(field.typ).name
// skip if not in types list or already in deps // skip if not in types list or already in deps

View File

@ -457,6 +457,10 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
g.write('/*af receiver arg*/' + arg_name) g.write('/*af receiver arg*/' + arg_name)
} else { } else {
g.expr(node.left) g.expr(node.left)
if node.from_embed_type != 0 {
embed_name := typ_sym.embed_name()
g.write('.$embed_name')
}
} }
if has_cast { if has_cast {
g.write(')') g.write(')')

View File

@ -77,7 +77,9 @@ 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 embed_types := []table.Type{}
mut embeds := []ast.Embed{}
mut embed_field_names := []string{}
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
@ -156,35 +158,38 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
field_start_pos := p.tok.position() field_start_pos := p.tok.position()
is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) || is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) ||
p.peek_tok.kind == .dot) && p.peek_tok.kind == .dot) &&
language == .v language == .v && ast_fields.len == 0
mut field_name := '' mut field_name := ''
mut typ := table.Type(0) mut typ := table.Type(0)
mut type_pos := token.Position{} mut type_pos := token.Position{}
mut field_pos := token.Position{} mut field_pos := token.Position{}
if is_embed { if is_embed {
// struct embedding // struct embedding
type_pos = p.tok.position()
typ = p.parse_type() 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 { for p.tok.kind == .comment {
comments << p.comment() comments << p.comment()
if p.tok.kind == .rcbr { if p.tok.kind == .rcbr {
break break
} }
} }
type_pos = p.prev_tok.position() type_pos = type_pos.extend(p.prev_tok.position())
field_pos = p.prev_tok.position() sym := p.table.get_type_symbol(typ)
field_name = symbol_name if typ in embed_types {
if typ in embedded_structs { p.error_with_pos('cannot embed `$sym.name` more than once', type_pos)
p.error_with_pos('cannot embed `$field_name` more than once', type_pos)
return ast.StructDecl{} return ast.StructDecl{}
} }
embedded_structs << typ field_name = sym.embed_name()
if field_name in embed_field_names {
p.error_with_pos('duplicate field `$field_name`', type_pos)
return ast.StructDecl{}
}
embed_field_names << field_name
embed_types << typ
embeds << ast.Embed{
typ: typ
pos: type_pos
}
} else { } else {
// struct field // struct field
field_name = p.check_name() field_name = p.check_name()
@ -225,20 +230,20 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
} }
has_default_expr = true has_default_expr = true
} }
// TODO merge table and ast Fields?
ast_fields << ast.StructField{
name: field_name
pos: field_pos
type_pos: type_pos
typ: typ
comments: comments
default_expr: default_expr
has_default_expr: has_default_expr
attrs: p.attrs
is_public: is_field_pub
}
} }
// TODO merge table and ast Fields? // save embeds as table fields too, it will be used in generation phase
ast_fields << ast.StructField{
name: field_name
pos: field_pos
type_pos: type_pos
typ: typ
comments: comments
default_expr: default_expr
has_default_expr: has_default_expr
attrs: p.attrs
is_public: is_field_pub
is_embed: is_embed
}
fields << table.Field{ fields << table.Field{
name: field_name name: field_name
typ: typ typ: typ
@ -248,7 +253,6 @@ 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 = []
} }
@ -270,6 +274,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
cname: util.no_dots(name) cname: util.no_dots(name)
mod: p.mod mod: p.mod
info: table.Struct{ info: table.Struct{
embeds: embed_types
fields: fields fields: fields
is_typedef: attrs.contains('typedef') is_typedef: attrs.contains('typedef')
is_union: is_union is_union: is_union
@ -301,6 +306,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
attrs: attrs attrs: attrs
end_comments: end_comments end_comments: end_comments
gen_types: generic_types gen_types: generic_types
embeds: embeds
} }
} }

View File

@ -0,0 +1,6 @@
vlib/v/parser/tests/duplicate_field_embed_err.vv:8:2: error: duplicate field `ModFileAndFolder`
6 | struct Bar {
7 | ModFileAndFolder
8 | vmod.ModFileAndFolder
| ~~~~~~~~~~~~~~~~~~~~~
9 | }

View File

@ -0,0 +1,9 @@
import v.vmod
struct ModFileAndFolder {
name int = 5
}
struct Bar {
ModFileAndFolder
vmod.ModFileAndFolder
}

View File

@ -606,6 +606,7 @@ pub fn (kinds []Kind) str() string {
pub struct Struct { pub struct Struct {
pub mut: pub mut:
embeds []Type
fields []Field fields []Field
is_typedef bool // C. [typedef] is_typedef bool // C. [typedef]
is_union bool is_union bool
@ -663,8 +664,6 @@ 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 {
@ -850,6 +849,17 @@ pub fn (t &Table) fn_signature(func &Fn, opts FnSignatureOpts) string {
return sb.str() return sb.str()
} }
pub fn (t &TypeSymbol) embed_name() string {
// main.Abc<int> => Abc<int>
mut embed_name := t.name.split('.').last()
// remove generic part from name
// Abc<int> => Abc
if '<' in embed_name {
embed_name = embed_name.split('<')[0]
}
return embed_name
}
pub fn (t &TypeSymbol) has_method(name string) bool { pub fn (t &TypeSymbol) has_method(name string) bool {
t.find_method(name) or { return false } t.find_method(name) or { return false }
return true return true

View File

@ -7,7 +7,6 @@ struct Foo {
fn (f Foo) foo() {} fn (f Foo) foo() {}
struct Bar { struct Bar {
Foo Foo
} }
@ -15,7 +14,7 @@ struct Bar {
fn test_embed() { fn test_embed() {
b := Bar{} b := Bar{}
assert b.x == 0 assert b.x == 0
//b.foo() // TODO methods b.foo()
} }
fn test_embed_direct_access() { fn test_embed_direct_access() {
@ -27,7 +26,7 @@ fn test_default_value() {
b := Bar{Foo: Foo{}} b := Bar{Foo: Foo{}}
assert b.y == 5 assert b.y == 5
} }
/* /* TODO
fn test_initialize() { fn test_initialize() {
b := Bar{x: 1, y: 2} b := Bar{x: 1, y: 2}
assert b.x == 1 assert b.x == 1
@ -59,5 +58,19 @@ fn test_generic_embed() {
b := BarGenericContainer{} b := BarGenericContainer{}
assert b.BarGeneric.foo == 0 assert b.BarGeneric.foo == 0
assert b.foo == 0 assert b.foo == 0
println('ok') }
struct Upper {
mut:
x int
}
struct UpperHolder {
Upper
}
fn test_assign() {
mut h := UpperHolder{}
h.x = 5
assert h.x == 5
} }