checker: don't disallow defining methods on interfaces (#8335)

pull/8353/head
spaceface 2021-01-26 11:56:17 +01:00 committed by GitHub
parent 3959ba5751
commit 5f2b2df546
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 77 additions and 50 deletions

View File

@ -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) typ_sym := c.table.get_type_symbol(typ)
mut inter_sym := c.table.get_type_symbol(inter_typ) mut inter_sym := c.table.get_type_symbol(inter_typ)
styp := c.table.type_to_str(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) { if method := typ_sym.find_method(imethod.name) {
msg := c.table.is_same_method(imethod, method) msg := c.table.is_same_method(imethod, method)
if msg.len > 0 { if msg.len > 0 {
@ -5263,9 +5268,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
} }
if node.is_method { if node.is_method {
mut sym := c.table.get_type_symbol(node.receiver.typ) mut sym := c.table.get_type_symbol(node.receiver.typ)
if sym.kind == .interface_ { if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' {
c.error('interfaces cannot be used as method receiver', node.receiver_pos)
} else if sym.kind == .array && !c.is_builtin_mod && node.name == 'map' {
// TODO `node.map in array_builtin_methods` // TODO `node.map in array_builtin_methods`
c.error('method overrides built-in array method', node.pos) c.error('method overrides built-in array method', node.pos)
} else if sym.kind == .sum_type && node.name == 'type_name' { } else if sym.kind == .sum_type && node.name == 'type_name' {

View File

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

View File

@ -1,5 +0,0 @@
interface Animal {
speak()
}
fn (a Animal) str() {}

View File

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

View File

@ -1,6 +0,0 @@
interface Abc {
fun()
}
fn (a Abc) fun() {
}

View File

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

View File

@ -1,6 +0,0 @@
fn (a Abc) fun() {
}
interface Abc {
fun()
}

View File

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

View File

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

View File

@ -2923,7 +2923,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
g.writeln('(*($opt_base_typ*)') g.writeln('(*($opt_base_typ*)')
} }
if sym.kind == .interface_ { if sym.kind == .interface_ {
g.write('*(') g.write('(*(')
} }
if sym.kind == .array_fixed { if sym.kind == .array_fixed {
assert node.field_name == 'len' 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)') g.write('$sum_type_dot$sum_type_deref_field)')
} }
if sym.kind == .interface_ { 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 {') methods_struct_def.writeln('$methods_struct_name {')
mut imethods := map[string]string{} // a map from speak -> _Speaker_speak_fn mut imethods := map[string]string{} // a map from speak -> _Speaker_speak_fn
mut methodidx := map[string]int{} mut methodidx := map[string]int{}
for k, method in ityp.methods { for k, method in inter_info.methods {
methodidx[method.name] = k methodidx[method.name] = k
typ_name := '_${interface_name}_${method.name}_fn' typ_name := '_${interface_name}_${method.name}_fn'
ret_styp := g.typ(method.return_type) 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 // add line return after interface index declarations
sb.writeln('') sb.writeln('')
if ityp.methods.len > 0 { if inter_info.methods.len > 0 {
sb.writeln(methods_wrapper.str()) sb.writeln(methods_wrapper.str())
sb.writeln(methods_typ_def.str()) sb.writeln(methods_typ_def.str())
sb.writeln(methods_struct_def.str()) sb.writeln(methods_struct_def.str())

View File

@ -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)) 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(typ_sym.name)
mut receiver_type_name := util.no_dots(g.cc_type2(g.unwrap_generic(node.receiver_type))) 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) // Speaker_name_table[s._interface_idx].speak(s._object)
$if debug_interface_method_call ? { $if debug_interface_method_call ? {
eprintln('>>> interface typ_sym.name: $typ_sym.name | receiver_type_name: $receiver_type_name') eprintln('>>> interface typ_sym.name: $typ_sym.name | receiver_type_name: $receiver_type_name')

View File

@ -453,6 +453,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
} }
typ := table.new_type(reg_idx) typ := table.new_type(reg_idx)
mut ts := p.table.get_type_symbol(typ) 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 // if methods were declared before, it's an error, ignore them
ts.methods = []table.Fn{cap: 20} ts.methods = []table.Fn{cap: 20}
// Parse fields or methods // Parse fields or methods
@ -470,7 +471,6 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
is_mut = true is_mut = true
} }
if p.peek_tok.kind == .lpar { 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() method_start_pos := p.tok.position()
line_nr := p.tok.line_nr line_nr := p.tok.line_nr
name := p.check_name() name := p.check_name()
@ -511,13 +511,15 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
method.next_comments = mnext_comments method.next_comments = mnext_comments
methods << method methods << method
// println('register method $name') // println('register method $name')
ts.register_method( tmethod := table.Fn{
name: name name: name
params: args params: args
return_type: method.return_type return_type: method.return_type
is_variadic: is_variadic is_variadic: is_variadic
is_pub: true is_pub: true
) }
ts.register_method(tmethod)
info.methods << tmethod
} else { } else {
// interface fields // interface fields
field_pos := p.tok.position() field_pos := p.tok.position()
@ -540,16 +542,15 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
comments: comments comments: comments
is_public: true is_public: true
} }
mut info := ts.info as table.Interface
info.fields << table.Field{ info.fields << table.Field{
name: field_name name: field_name
typ: field_typ typ: field_typ
is_pub: true is_pub: true
is_mut: is_mut is_mut: is_mut
} }
ts.info = info
} }
} }
ts.info = info
p.top_level_statement_end() p.top_level_statement_end()
p.check(.rcbr) p.check(.rcbr)
pos.update_last_line(p.prev_tok.line_nr) pos.update_last_line(p.prev_tok.line_nr)

View File

@ -656,8 +656,9 @@ pub mut:
pub struct Interface { pub struct Interface {
pub mut: pub mut:
types []Type types []Type
fields []Field fields []Field
methods []Fn
} }
pub struct Enum { pub struct Enum {
@ -991,3 +992,12 @@ pub fn (s Struct) get_field(name string) Field {
} }
panic('unknown field `$name`') 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
}

View File

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