From 5f2b2df5467ca2f25550d857c52e5b27f9c384ac Mon Sep 17 00:00:00 2001 From: spaceface Date: Tue, 26 Jan 2021 11:56:17 +0100 Subject: [PATCH] checker: don't disallow defining methods on interfaces (#8335) --- vlib/v/checker/checker.v | 11 ++++++---- .../v/checker/tests/no_interface_receiver.out | 6 ------ vlib/v/checker/tests/no_interface_receiver.vv | 5 ----- .../no_interface_receiver_duplicate_a.out | 6 ------ .../no_interface_receiver_duplicate_a.vv | 6 ------ .../no_interface_receiver_duplicate_b.out | 5 ----- .../no_interface_receiver_duplicate_b.vv | 6 ------ .../no_method_on_interface_propagation.out | 7 +++++++ .../no_method_on_interface_propagation.vv | 20 +++++++++++++++++++ vlib/v/gen/cgen.v | 8 ++++---- vlib/v/gen/fn.v | 2 +- vlib/v/parser/struct.v | 11 +++++----- vlib/v/table/types.v | 14 +++++++++++-- vlib/v/tests/methods_on_interfaces_test.v | 20 +++++++++++++++++++ 14 files changed, 77 insertions(+), 50 deletions(-) delete mode 100644 vlib/v/checker/tests/no_interface_receiver.out delete mode 100644 vlib/v/checker/tests/no_interface_receiver.vv delete mode 100644 vlib/v/checker/tests/no_interface_receiver_duplicate_a.out delete mode 100644 vlib/v/checker/tests/no_interface_receiver_duplicate_a.vv delete mode 100644 vlib/v/checker/tests/no_interface_receiver_duplicate_b.out delete mode 100644 vlib/v/checker/tests/no_interface_receiver_duplicate_b.vv create mode 100644 vlib/v/checker/tests/no_method_on_interface_propagation.out create mode 100644 vlib/v/checker/tests/no_method_on_interface_propagation.vv create mode 100644 vlib/v/tests/methods_on_interfaces_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index e6afd204c3..e61cf30d33 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1879,7 +1879,12 @@ fn (mut c Checker) type_implements(typ table.Type, inter_typ table.Type, pos tok typ_sym := c.table.get_type_symbol(typ) mut inter_sym := c.table.get_type_symbol(inter_typ) styp := c.table.type_to_str(typ) - for imethod in inter_sym.methods { + imethods := if inter_sym.kind == .interface_ { + (inter_sym.info as table.Interface).methods + } else { + inter_sym.methods + } + for imethod in imethods { if method := typ_sym.find_method(imethod.name) { msg := c.table.is_same_method(imethod, method) if msg.len > 0 { @@ -5263,9 +5268,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } if node.is_method { mut sym := c.table.get_type_symbol(node.receiver.typ) - if sym.kind == .interface_ { - c.error('interfaces cannot be used as method receiver', node.receiver_pos) - } else if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' { + if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' { // TODO `node.map in array_builtin_methods` c.error('method overrides built-in array method', node.pos) } else if sym.kind == .sum_type && node.name == 'type_name' { diff --git a/vlib/v/checker/tests/no_interface_receiver.out b/vlib/v/checker/tests/no_interface_receiver.out deleted file mode 100644 index f738d96e51..0000000000 --- a/vlib/v/checker/tests/no_interface_receiver.out +++ /dev/null @@ -1,6 +0,0 @@ -vlib/v/checker/tests/no_interface_receiver.vv:5:5: error: interfaces cannot be used as method receiver - 3 | } - 4 | - 5 | fn (a Animal) str() {} - | ~~~~~~~~ - diff --git a/vlib/v/checker/tests/no_interface_receiver.vv b/vlib/v/checker/tests/no_interface_receiver.vv deleted file mode 100644 index e0001aae5f..0000000000 --- a/vlib/v/checker/tests/no_interface_receiver.vv +++ /dev/null @@ -1,5 +0,0 @@ -interface Animal { - speak() -} - -fn (a Animal) str() {} diff --git a/vlib/v/checker/tests/no_interface_receiver_duplicate_a.out b/vlib/v/checker/tests/no_interface_receiver_duplicate_a.out deleted file mode 100644 index e96e3e83b2..0000000000 --- a/vlib/v/checker/tests/no_interface_receiver_duplicate_a.out +++ /dev/null @@ -1,6 +0,0 @@ -vlib/v/checker/tests/no_interface_receiver_duplicate_a.vv:5:5: error: interfaces cannot be used as method receiver - 3 | } - 4 | - 5 | fn (a Abc) fun() { - | ~~~~~ - 6 | } diff --git a/vlib/v/checker/tests/no_interface_receiver_duplicate_a.vv b/vlib/v/checker/tests/no_interface_receiver_duplicate_a.vv deleted file mode 100644 index 0c28871b47..0000000000 --- a/vlib/v/checker/tests/no_interface_receiver_duplicate_a.vv +++ /dev/null @@ -1,6 +0,0 @@ -interface Abc { - fun() -} - -fn (a Abc) fun() { -} diff --git a/vlib/v/checker/tests/no_interface_receiver_duplicate_b.out b/vlib/v/checker/tests/no_interface_receiver_duplicate_b.out deleted file mode 100644 index 8e1f155873..0000000000 --- a/vlib/v/checker/tests/no_interface_receiver_duplicate_b.out +++ /dev/null @@ -1,5 +0,0 @@ -vlib/v/checker/tests/no_interface_receiver_duplicate_b.vv:1:5: error: interfaces cannot be used as method receiver - 1 | fn (a Abc) fun() { - | ~~~~~ - 2 | } - 3 | diff --git a/vlib/v/checker/tests/no_interface_receiver_duplicate_b.vv b/vlib/v/checker/tests/no_interface_receiver_duplicate_b.vv deleted file mode 100644 index ff8e015c08..0000000000 --- a/vlib/v/checker/tests/no_interface_receiver_duplicate_b.vv +++ /dev/null @@ -1,6 +0,0 @@ -fn (a Abc) fun() { -} - -interface Abc { - fun() -} diff --git a/vlib/v/checker/tests/no_method_on_interface_propagation.out b/vlib/v/checker/tests/no_method_on_interface_propagation.out new file mode 100644 index 0000000000..232e9b2de4 --- /dev/null +++ b/vlib/v/checker/tests/no_method_on_interface_propagation.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/no_method_on_interface_propagation.vv:18:5: error: unknown method: `Cat.foo` + 16 | mut a := new_animal('persian') + 17 | if a is Cat { + 18 | a.foo() + | ~~~~~ + 19 | } + 20 | } \ No newline at end of file diff --git a/vlib/v/checker/tests/no_method_on_interface_propagation.vv b/vlib/v/checker/tests/no_method_on_interface_propagation.vv new file mode 100644 index 0000000000..3456b271b9 --- /dev/null +++ b/vlib/v/checker/tests/no_method_on_interface_propagation.vv @@ -0,0 +1,20 @@ +struct Cat { + breed string +} + +interface Animal { + breed string +} + +fn (a Animal) foo() {} + +fn new_animal(breed string) Animal { + return &Cat{breed} +} + +fn test_methods_on_interfaces_dont_exist_on_implementers() { + mut a := new_animal('persian') + if a is Cat { + a.foo() + } +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 72fc472d10..a681cefb46 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2923,7 +2923,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { g.writeln('(*($opt_base_typ*)') } if sym.kind == .interface_ { - g.write('*(') + g.write('(*(') } if sym.kind == .array_fixed { assert node.field_name == 'len' @@ -3002,7 +3002,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { g.write('$sum_type_dot$sum_type_deref_field)') } if sym.kind == .interface_ { - g.write(')') + g.write('))') } } @@ -5903,7 +5903,7 @@ fn (mut g Gen) interface_table() string { methods_struct_def.writeln('$methods_struct_name {') mut imethods := map[string]string{} // a map from speak -> _Speaker_speak_fn mut methodidx := map[string]int{} - for k, method in ityp.methods { + for k, method in inter_info.methods { methodidx[method.name] = k typ_name := '_${interface_name}_${method.name}_fn' ret_styp := g.typ(method.return_type) @@ -6049,7 +6049,7 @@ $staticprefix $interface_name* I_${cctype}_to_Interface_${interface_name}_ptr($c } // add line return after interface index declarations sb.writeln('') - if ityp.methods.len > 0 { + if inter_info.methods.len > 0 { sb.writeln(methods_wrapper.str()) sb.writeln(methods_typ_def.str()) sb.writeln(methods_struct_def.str()) diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index c5c82c972b..ec5eac1352 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -343,7 +343,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { typ_sym := g.table.get_type_symbol(g.unwrap_generic(node.receiver_type)) // mut receiver_type_name := util.no_dots(typ_sym.name) mut receiver_type_name := util.no_dots(g.cc_type2(g.unwrap_generic(node.receiver_type))) - if typ_sym.kind == .interface_ { + if typ_sym.kind == .interface_ && (typ_sym.info as table.Interface).defines_method(node.name) { // Speaker_name_table[s._interface_idx].speak(s._object) $if debug_interface_method_call ? { eprintln('>>> interface typ_sym.name: $typ_sym.name | receiver_type_name: $receiver_type_name') diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 2f7da7e4c8..996dd28650 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -453,6 +453,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { } typ := table.new_type(reg_idx) mut ts := p.table.get_type_symbol(typ) + mut info := ts.info as table.Interface // if methods were declared before, it's an error, ignore them ts.methods = []table.Fn{cap: 20} // Parse fields or methods @@ -470,7 +471,6 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { is_mut = true } if p.peek_tok.kind == .lpar { - ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt` method_start_pos := p.tok.position() line_nr := p.tok.line_nr name := p.check_name() @@ -511,13 +511,15 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { method.next_comments = mnext_comments methods << method // println('register method $name') - ts.register_method( + tmethod := table.Fn{ name: name params: args return_type: method.return_type is_variadic: is_variadic is_pub: true - ) + } + ts.register_method(tmethod) + info.methods << tmethod } else { // interface fields field_pos := p.tok.position() @@ -540,16 +542,15 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { comments: comments is_public: true } - mut info := ts.info as table.Interface info.fields << table.Field{ name: field_name typ: field_typ is_pub: true is_mut: is_mut } - ts.info = info } } + ts.info = info p.top_level_statement_end() p.check(.rcbr) pos.update_last_line(p.prev_tok.line_nr) diff --git a/vlib/v/table/types.v b/vlib/v/table/types.v index 2bbd4005df..8d83ba23ea 100644 --- a/vlib/v/table/types.v +++ b/vlib/v/table/types.v @@ -656,8 +656,9 @@ pub mut: pub struct Interface { pub mut: - types []Type - fields []Field + types []Type + fields []Field + methods []Fn } pub struct Enum { @@ -991,3 +992,12 @@ pub fn (s Struct) get_field(name string) Field { } panic('unknown field `$name`') } + +pub fn (i Interface) defines_method(name string) bool { + for method in i.methods { + if method.name == name { + return true + } + } + return false +} diff --git a/vlib/v/tests/methods_on_interfaces_test.v b/vlib/v/tests/methods_on_interfaces_test.v new file mode 100644 index 0000000000..b5c9aae025 --- /dev/null +++ b/vlib/v/tests/methods_on_interfaces_test.v @@ -0,0 +1,20 @@ +struct Cat { + breed string +} + +interface Animal { + breed string +} + +fn (a Animal) info() string { + return "I'm a ${a.breed} ${typeof(a).name}" +} + +fn new_animal(breed string) Animal { + return &Cat{ breed } +} + +fn test_methods_on_interfaces() { + mut a := new_animal('persian') + assert a.info() == "I'm a persian Animal" +}