diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index 3b8990da0f..5fd469e756 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -1456,7 +1456,7 @@ fn (t Tree) call_expr(node ast.CallExpr) &Node { obj.add('concrete_types', t.array_node_type(node.concrete_types)) obj.add('or_block', t.or_expr(node.or_block)) obj.add('concrete_list_pos', t.position(node.concrete_list_pos)) - obj.add('from_embed_type', t.type_node(node.from_embed_type)) + obj.add('from_embed_types', t.array_node_type(node.from_embed_types)) obj.add('comments', t.array_node_comment(node.comments)) obj.add('pos', t.position(node.pos)) obj.add('name_pos', t.position(node.name_pos)) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 68f88a581d..d7b669d869 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -529,7 +529,7 @@ pub mut: concrete_list_pos token.Position free_receiver bool // true if the receiver expression needs to be freed scope &Scope - from_embed_type Type // holds the type of the embed that the method is called from + from_embed_types []Type // holds the type of the embed that the method is called from comments []Comment } diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index cf81119918..cf25e2cc9d 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -409,6 +409,43 @@ pub fn (t &Table) type_find_method_from_embeds(sym &TypeSymbol, method_name stri return none } +pub fn (t &Table) type_find_method_from_embeds_recursive(sym &TypeSymbol, method_name string) ?(Fn, []Type) { + if sym.info is Struct { + mut found_methods := []Fn{} + mut embed_of_found_methods := []Type{} + for embed in sym.info.embeds { + embed_sym := t.get_type_symbol(embed) + if m := t.type_find_method(embed_sym, method_name) { + found_methods << m + embed_of_found_methods << embed + } else { + method, types := t.type_find_method_from_embeds_recursive(embed_sym, method_name) or { + continue + } + found_methods << method + embed_of_found_methods << embed + embed_of_found_methods << types + } + } + if found_methods.len == 1 { + return found_methods[0], embed_of_found_methods + } else if found_methods.len > 1 { + return error('ambiguous method `$method_name`') + } + } else if sym.info is Aggregate { + for typ in sym.info.types { + agg_sym := t.get_type_symbol(typ) + method, embed_types := t.type_find_method_from_embeds_recursive(agg_sym, method_name) or { + continue + } + if embed_types.len != 0 { + return method, embed_types + } + } + } + return none +} + fn (t &Table) register_aggregate_field(mut sym TypeSymbol, name string) ?StructField { if sym.kind != .aggregate { t.panic('Unexpected type symbol: $sym.kind') diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 4836a1de78..58a843d2d8 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2083,17 +2083,18 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { } if !has_method { has_method = true - mut embed_type := ast.Type(0) - method, embed_type = c.table.type_find_method_from_embeds(left_sym, method_name) or { + mut embed_types := []ast.Type{} + method, embed_types = c.table.type_find_method_from_embeds_recursive(left_sym, + method_name) or { if err.msg != '' { c.error(err.msg, node.pos) } has_method = false - ast.Fn{}, ast.Type(0) + ast.Fn{}, []ast.Type{} } - if embed_type != 0 { + if embed_types.len != 0 { is_method_from_embed = true - node.from_embed_type = embed_type + node.from_embed_types = embed_types } } if left_sym.kind == .aggregate { @@ -2262,7 +2263,7 @@ pub fn (mut c Checker) method_call(mut node ast.CallExpr) ast.Type { } } if is_method_from_embed { - node.receiver_type = node.from_embed_type.derive(method.params[0].typ) + node.receiver_type = node.from_embed_types.last().derive(method.params[0].typ) } else if is_generic { // We need the receiver to be T in cgen. // TODO: cant we just set all these to the concrete type in checker? then no need in gen diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index bd04db3277..8c8499a557 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -946,7 +946,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } if node.receiver_type.is_ptr() && (!node.left_type.is_ptr() || node.left_type.has_flag(.variadic) - || node.from_embed_type != 0 + || node.from_embed_types.len != 0 || (node.left_type.has_flag(.shared_f) && node.name != 'str')) { // The receiver is a reference, but the caller provided a value // Add `&` automatically. @@ -960,11 +960,11 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } } } else if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name != 'str' - && node.from_embed_type == 0 { + && node.from_embed_types.len == 0 { if !node.left_type.has_flag(.shared_f) { g.write('/*rec*/*') } - } else if !is_range_slice && node.from_embed_type == 0 && node.name != 'str' { + } else if !is_range_slice && node.from_embed_types.len == 0 && node.name != 'str' { diff := node.left_type.nr_muls() - node.receiver_type.nr_muls() if diff < 0 { // TODO @@ -996,8 +996,9 @@ fn (mut g Gen) method_call(node ast.CallExpr) { } else { g.expr(node.left) } - if node.from_embed_type != 0 { - embed_name := typ_sym.embed_name() + for embed in node.from_embed_types { + embed_sym := g.table.get_type_symbol(embed) + embed_name := embed_sym.embed_name() if node.left_type.is_ptr() { g.write('->') } else { diff --git a/vlib/v/tests/nested_struct_embed_method_call_test.v b/vlib/v/tests/nested_struct_embed_method_call_test.v new file mode 100644 index 0000000000..95647e35d7 --- /dev/null +++ b/vlib/v/tests/nested_struct_embed_method_call_test.v @@ -0,0 +1,22 @@ +struct Foo1 { + x int +} + +struct Foo2 { + Foo1 +} + +struct Foo3 { + Foo2 +} + +fn (f Foo1) bar() string { + println('Foo1.bar()') + return 'Foo1.bar()' +} + +fn test_nested_struct_embed_method_call() { + f3 := Foo3{} + ret := f3.bar() + assert ret == 'Foo1.bar()' +} diff --git a/vlib/v/tests/nested_struct_embed_test.v b/vlib/v/tests/nested_struct_embed_selector_test.v similarity index 100% rename from vlib/v/tests/nested_struct_embed_test.v rename to vlib/v/tests/nested_struct_embed_selector_test.v