1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

all: reimplement struct embedding with methods (#7506)

This commit is contained in:
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 {
Widget
title string
title string
}
mut button := Button{

View File

@ -103,16 +103,17 @@ pub:
// `foo.bar`
pub struct SelectorExpr {
pub:
pos token.Position
expr Expr // expr.field_name
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
mut_pos token.Position
pos token.Position
expr Expr // expr.field_name
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
mut_pos token.Position
pub mut:
expr_type table.Type // type of `Foo` in `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`
scope &Scope
expr_type table.Type // type of `Foo` in `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`
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.
@ -144,7 +145,6 @@ pub:
has_default_expr bool
attrs []table.Attr
is_public bool
is_embed bool
pub mut:
name string
typ table.Type
@ -192,10 +192,17 @@ pub:
is_union bool
attrs []table.Attr
end_comments []Comment
embeds []Embed
pub mut:
fields []StructField
}
pub struct Embed {
pub:
typ table.Type
pos token.Position
}
pub struct StructEmbedding {
pub:
name string
@ -225,6 +232,18 @@ pub mut:
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:
pos token.Position
@ -233,6 +252,7 @@ pub:
pub mut:
typ table.Type
fields []StructInitField
embeds []StructInitEmbed
}
// import statement
@ -320,6 +340,7 @@ pub mut:
generic_list_pos token.Position
free_receiver bool // true if the receiver expression needs to be freed
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{} }
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 {
if decl.language == .v && !field.is_embed {
if decl.language == .v {
c.check_valid_snake_case(field.name, 'field name', field.pos)
}
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 {
if field.name == decl.fields[j].name {
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{}
for i, field in struct_init.fields {
mut info_field := table.Field{}
mut embed_type := table.Type(0)
mut is_embed := false
mut field_name := ''
if struct_init.is_short {
if i >= info.fields.len {
@ -548,31 +540,17 @@ 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: '' }
}
}
if !exists {
for embed in info.embeds {
embed_sym := c.table.get_type_symbol(embed)
if embed_sym.embed_name() == field_name {
exists = true
embed_type = embed
is_embed = true
break
}
}
}
*/
if !exists {
c.error('unknown field `$field.name` in struct literal of type `$type_sym.name`',
field.pos)
@ -584,29 +562,43 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
continue
}
}
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 is_embed {
c.expected_type = embed_type
expr_type := c.expr(field.expr)
expr_type_sym := c.table.get_type_symbol(expr_type)
if expr_type != table.void_type && expr_type_sym.kind != .placeholder {
c.check_expected(expr_type, embed_type) 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
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
}
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 {
.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)
c.error('unknown field `${type_str}.$expr.field_name`', expr.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())
}
}
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 &&
left_type_sym.mod != '' { // method.mod != c.mod {
// 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
call_expr.receiver_type = left_type.derive(method.params[0].typ).set_flag(.generic)
} else {
// note: correct receiver type is automatically set here on struct embed calls
call_expr.receiver_type = method.params[0].typ
}
call_expr.return_type = 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
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`'
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 {
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
return field.typ
} else {
if sym.kind == .aggregate {
unknown_field_msg = err
}
}
if sym.kind !in [.struct_, .aggregate] {
if sym.kind != .placeholder {
c.error('`$sym.name` is not a struct', selector_expr.pos)
}
} else {
if sym.kind == .struct_ {
sss := sym.info as table.Struct
suggestion := util.new_suggestion(field_name, sss.fields.map(it.name))
if sym.info is table.Struct {
suggestion := util.new_suggestion(field_name, sym.info.fields.map(it.name))
c.error(suggestion.say(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
}
}
for field in node.fields.filter(it.is_embed) {
f.writeln('\t$field.name')
for embed in node.embeds {
styp := f.table.type_to_str(embed.typ)
f.writeln('\t$styp')
}
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

@ -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 {
Foo
Test
y int
z string
y int
z string
}

View File

@ -2664,93 +2664,7 @@ fn (mut g Gen) expr(node ast.Expr) {
g.struct_init(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.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)')
}
g.selector_expr(node)
}
ast.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) {
match node {
ast.EnumVal { g.write(node.val) }
@ -4347,16 +4348,6 @@ 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 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_ {
field_name := c_name(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 {
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)
// nr_fields = info.fields.len
for field in info.fields {
@ -4404,10 +4412,6 @@ fn (mut g Gen) struct_init(struct_init ast.StructInit) {
if equal_fields.len == 0 {
continue
}
tfield := equal_fields[0]
if tfield.embed_alias_for.len != 0 {
continue
}
}
if field.name in inited_fields {
sfield := struct_init.fields[inited_fields[field.name]]
@ -4792,8 +4796,8 @@ fn (mut g Gen) write_types(types []table.TypeSymbol) {
} else {
g.type_definitions.writeln('struct $name {')
}
if typ.info.fields.len > 0 {
for field in typ.info.fields.filter(it.embed_alias_for == '') {
if typ.info.fields.len > 0 || typ.info.embeds.len > 0 {
for field in typ.info.fields {
// 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
@ -4887,6 +4891,14 @@ fn (g &Gen) sort_structs(typesa []table.TypeSymbol) []table.TypeSymbol {
// if info.is_interface {
// 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 {
dep := g.table.get_type_symbol(field.typ).name
// 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)
} else {
g.expr(node.left)
if node.from_embed_type != 0 {
embed_name := typ_sym.embed_name()
g.write('.$embed_name')
}
}
if has_cast {
g.write(')')

View File

@ -77,7 +77,9 @@ 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 embed_types := []table.Type{}
mut embeds := []ast.Embed{}
mut embed_field_names := []string{}
mut mut_pos := -1
mut pub_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()
is_embed := ((p.tok.lit.len > 1 && p.tok.lit[0].is_capital()) ||
p.peek_tok.kind == .dot) &&
language == .v
language == .v && ast_fields.len == 0
mut field_name := ''
mut typ := table.Type(0)
mut type_pos := token.Position{}
mut field_pos := token.Position{}
if is_embed {
// struct embedding
type_pos = p.tok.position()
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)
type_pos = type_pos.extend(p.prev_tok.position())
sym := p.table.get_type_symbol(typ)
if typ in embed_types {
p.error_with_pos('cannot embed `$sym.name` more than once', type_pos)
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 {
// struct field
field_name = p.check_name()
@ -225,20 +230,20 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
}
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?
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
}
// save embeds as table fields too, it will be used in generation phase
fields << table.Field{
name: field_name
typ: typ
@ -248,7 +253,6 @@ 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 = []
}
@ -270,6 +274,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
cname: util.no_dots(name)
mod: p.mod
info: table.Struct{
embeds: embed_types
fields: fields
is_typedef: attrs.contains('typedef')
is_union: is_union
@ -301,6 +306,7 @@ fn (mut p Parser) struct_decl() ast.StructDecl {
attrs: attrs
end_comments: end_comments
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 mut:
embeds []Type
fields []Field
is_typedef bool // C. [typedef]
is_union bool
@ -663,8 +664,6 @@ 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 {
@ -850,6 +849,17 @@ pub fn (t &Table) fn_signature(func &Fn, opts FnSignatureOpts) string {
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 {
t.find_method(name) or { return false }
return true

View File

@ -7,7 +7,6 @@ struct Foo {
fn (f Foo) foo() {}
struct Bar {
Foo
}
@ -15,7 +14,7 @@ struct Bar {
fn test_embed() {
b := Bar{}
assert b.x == 0
//b.foo() // TODO methods
b.foo()
}
fn test_embed_direct_access() {
@ -27,7 +26,7 @@ fn test_default_value() {
b := Bar{Foo: Foo{}}
assert b.y == 5
}
/*
/* TODO
fn test_initialize() {
b := Bar{x: 1, y: 2}
assert b.x == 1
@ -59,5 +58,19 @@ fn test_generic_embed() {
b := BarGenericContainer{}
assert b.BarGeneric.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
}