@@ -785,9 +785,15 @@ pub fn (f &Fn) new_method_with_receiver_type(new_type_ Type) Fn {
785785 unsafe {
786786 mut new_method := f
787787 new_method.params = f.params.clone()
788- for i in 1 .. new_method.params.len {
789- if new_method.params[i].typ == new_method.params[0].typ {
790- new_method.params[i].typ = recv_type
788+ // Only transform self-referential parameters for interface method declarations
789+ // (no_body == true). For concrete receiver methods defined outside the interface,
790+ // the parameters keep their original types, so methods like
791+ // `fn (mut n Node) add(child &Node)` stay valid when Node is embedded.
792+ if f.no_body {
793+ for i in 1 .. new_method.params.len {
794+ if new_method.params[i].typ == new_method.params[0].typ {
795+ new_method.params[i].typ = recv_type
796+ }
791797 }
792798 }
793799 new_method.from_embedded_type = if f.from_embedded_type != 0 {
@@ -5410,31 +5410,30 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
54105410 g.expr(ast.Expr(node.left))
54115411 }
54125412 } else if is_interface && node.from_embed_types.len > 0 {
5413+ // Calling a method defined on an embedded interface. The embedded
5414+ // interface has a different C struct layout than the outer interface,
5415+ // so we must convert (not reinterpret-cast) the receiver.
54135416 if g.out.last_n(1) == '&' {
54145417 g.go_back(1)
54155418 }
5416- if receiver_type.is_ptr() && left_type.is_ptr() {
5417- // (main__IFoo*)bar
5418- g.write2('(', g.table.sym(node.from_embed_types.last()).cname)
5419- g.write('*)')
5420- g.expr(ast.Expr(node.left))
5421- } else if receiver_type.is_ptr() && !left_type.is_ptr() {
5422- // (main__IFoo*)&bar
5423- g.write2('(', g.table.sym(node.from_embed_types.last()).cname)
5424- g.write('*)&')
5425- g.expr(ast.Expr(node.left))
5426- } else if !receiver_type.is_ptr() && left_type.is_ptr() {
5427- // *((main__IFoo*)bar)
5428- g.write2('*((', g.table.sym(node.from_embed_types.last()).cname)
5429- g.write('*)')
5430- g.expr(ast.Expr(node.left))
5431- g.write(')')
5419+ embed_type := node.from_embed_types.last()
5420+ embed_value_type := embed_type.set_nr_muls(0)
5421+ if receiver_needs_ref {
5422+ // Mutating receiver: create a temporary converted interface value,
5423+ // whose field pointers refer to the same underlying object, and pass
5424+ // its address. Modifications through those pointers are visible.
5425+ stmt_str := g.go_before_last_stmt()
5426+ g.empty_line = true
5427+ tmp := g.new_tmp_var()
5428+ g.write('${g.styp(embed_value_type)} ${tmp} = ')
5429+ g.gen_interface_to_interface_conversion(ast.Expr(node.left), left_type,
5430+ embed_value_type)
5431+ g.writeln(';')
5432+ g.write(stmt_str)
5433+ g.write('&${tmp}')
54325434 } else {
5433- // *((main__IFoo*)&bar)
5434- g.write2('*((', g.table.sym(node.from_embed_types.last()).cname)
5435- g.write('*)&')
5436- g.expr(node.left)
5437- g.write(')')
5435+ g.gen_interface_to_interface_conversion(ast.Expr(node.left), left_type,
5436+ embed_value_type)
54385437 }
54395438 } else {
54405439 if is_free_method && !receiver_type.is_ptr() {
@@ -0,0 +1,55 @@
1+interface Node {
2+ id string
3+ name string
4+mut:
5+ children []&Node
6+}
7+
8+fn (mut node Node) append_child(child &Node) {
9+ node.children << child
10+}
11+
12+interface Element {
13+ Node
14+ attributes map[string]string
15+}
16+
17+@[heap]
18+struct NodeBase {
19+ id string
20+ name string
21+mut:
22+ children []&Node
23+}
24+
25+@[heap]
26+struct Text {
27+ NodeBase
28+ text string
29+}
30+
31+@[heap]
32+struct HTMLBodyElement {
33+ NodeBase
34+ attributes map[string]string
35+}
36+
37+fn test_receiver_method_on_embedded_interface() {
38+ mut element := &Element(&HTMLBodyElement{
39+ name: 'body'
40+ })
41+ element.append_child(&Node(&Text{
42+ name: 'text'
43+ text: 'Hello, World!'
44+ }))
45+
46+ assert element.name == 'body'
47+ assert element.children.len == 1
48+ child := element.children[0]
49+ if child is Text {
50+ assert child.name == 'text'
51+ assert child.text == 'Hello, World!'
52+ } else {
53+ assert false, 'child should be Text'
54+ }
55+}