medvednikov

/

vqPublic
0 commits21 issue12 pull requests0 contributorsDiscussionsProjectsCI

ast, cgen: fix receiver methods on embedded interfaces (fix #19550) #12

OpenMacho0xwants to mergepr/27476intomaster· last Jun 16
3 files changed+84-24
vlib/v/ast/ast.v+9-3

@@ -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 {

vlib/v/gen/c/fn.v+20-21

@@ -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() {

vlib/v/tests/interfaces/interface_receiver_method_on_embedded_interface_test.vnew file+55-0

@@ -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+}