checker: check interface implementation

pull/4752/head
Enzo Baldisserri 2020-05-06 11:29:37 +02:00 committed by GitHub
parent b627bb933c
commit 215657e16a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 221 additions and 31 deletions

View File

@ -395,12 +395,25 @@ pub fn (mut c Checker) infix_expr(infix_expr mut ast.InfixExpr) table.Type {
if left.kind == .array {
// `array << elm`
c.fail_if_immutable(infix_expr.left)
left_value_type := c.table.value_type(left_type)
left_value_sym := c.table.get_type_symbol(left_value_type)
if left_value_sym.kind == .interface_ {
if right.kind != .array {
// []Animal << Cat
c.type_implements(right_type, left_value_type, infix_expr.right.position())
} else {
// []Animal << Cat
c.type_implements(c.table.value_type(right_type), left_value_type,
infix_expr.right.position())
}
return table.void_type
}
// the expressions have different types (array_x and x)
if c.table.check(right_type, c.table.value_type(left_type)) { // , right_type) {
if c.table.check(right_type, left_value_type) { // , right_type) {
// []T << T
return table.void_type
}
if right.kind == .array && c.table.check(c.table.value_type(left_type), c.table.value_type(right_type)) {
if right.kind == .array && c.table.check(left_value_type, c.table.value_type(right_type)) {
// []T << []T
return table.void_type
}
@ -665,6 +678,7 @@ pub fn (mut c Checker) call_method(call_expr mut ast.CallExpr) table.Type {
for i, arg in call_expr.args {
exp_arg_typ := if method.is_variadic && i >= method.args.len - 1 { method.args[method.args.len -
1].typ } else { method.args[i + 1].typ }
exp_arg_sym := c.table.get_type_symbol(exp_arg_typ)
c.expected_type = exp_arg_typ
got_arg_typ := c.expr(arg.expr)
call_expr.args[i].typ = got_arg_typ
@ -672,9 +686,12 @@ pub fn (mut c Checker) call_method(call_expr mut ast.CallExpr) table.Type {
1 > i {
c.error('when forwarding a varg variable, it must be the final argument', call_expr.pos)
}
if exp_arg_sym.kind == .interface_ {
c.type_implements(got_arg_typ, exp_arg_typ, arg.expr.position())
continue
}
if !c.table.check(got_arg_typ, exp_arg_typ) {
got_arg_sym := c.table.get_type_symbol(got_arg_typ)
exp_arg_sym := c.table.get_type_symbol(exp_arg_typ)
// str method, allow type with str method if fn arg is string
if exp_arg_sym.kind == .string && got_arg_sym.has_method('str') {
continue
@ -831,6 +848,17 @@ pub fn (mut c Checker) call_fn(call_expr mut ast.CallExpr) table.Type {
if f.is_variadic && typ.flag_is(.variadic) && call_expr.args.len - 1 > i {
c.error('when forwarding a varg variable, it must be the final argument', call_expr.pos)
}
// Handle expected interface
if arg_typ_sym.kind == .interface_ {
c.type_implements(typ, arg.typ, call_arg.expr.position())
continue
}
// Handle expected interface array
/*
if exp_type_sym.kind == .array && t.get_type_symbol(t.value_type(exp_idx)).kind == .interface_ {
return true
}
*/
if !c.table.check(typ, arg.typ) {
// str method, allow type with str method if fn arg is string
if arg_typ_sym.kind == .string && typ_sym.has_method('str') {
@ -844,7 +872,6 @@ pub fn (mut c Checker) call_fn(call_expr mut ast.CallExpr) table.Type {
}
if typ_sym.kind == .array_fixed {
}
// println('fixed')
c.error('cannot use type `$typ_sym.str()` as type `$arg_typ_sym.str()` in argument ${i+1} to `$fn_name`',
call_expr.pos)
}
@ -852,6 +879,25 @@ pub fn (mut c Checker) call_fn(call_expr mut ast.CallExpr) table.Type {
return f.return_type
}
fn (mut c Checker) type_implements(typ, inter_typ table.Type, pos token.Position) {
typ_sym := c.table.get_type_symbol(typ)
inter_sym := c.table.get_type_symbol(inter_typ)
styp := c.table.type_to_str(typ)
for imethod in inter_sym.methods {
if method := typ_sym.find_method(imethod.name) {
if !imethod.is_same_method_as(method) {
c.error('`$styp` incorrectly implements method `$imethod.name` of interface `$inter_sym.name`, expected `${c.table.fn_to_str(imethod)}`', pos)
}
continue
}
c.error("`$styp` doesn't implement method `$imethod.name`", pos)
}
mut inter_info := inter_sym.info as table.Interface
if typ !in inter_info.types && typ_sym.kind != .interface_ {
inter_info.types << typ
}
}
pub fn (mut c Checker) check_expr_opt_call(x ast.Expr, xtype table.Type, is_return_used bool) {
match x {
ast.CallExpr {

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/unimplemented_interface_a.v:10:6: error: `Cat` doesn't implement method `name`
8 |
9 | fn main() {
10 | foo(Cat{})
| ~~~~~
11 | }

View File

@ -0,0 +1,11 @@
interface Animal {
name() string
}
struct Cat {}
fn foo(a Animal) {}
fn main() {
foo(Cat{})
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/unimplemented_interface_b.v:13:6: error: `Cat` incorrectly implements method `name` of interface `Animal`, expected `name() string`
11 | fn main() {
12 | c := Cat{}
13 | foo(c)
| ^
14 | }

View File

@ -0,0 +1,14 @@
interface Animal {
name() string
}
struct Cat {}
fn (c Cat) name() {}
fn foo(a Animal) {}
fn main() {
c := Cat{}
foo(c)
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/unimplemented_interface_c.v:12:6: error: `Cat` incorrectly implements method `name` of interface `Animal`, expected `name()`
10 |
11 | fn main() {
12 | foo(Cat{})
| ~~~~~
13 | }

View File

@ -0,0 +1,13 @@
interface Animal {
name()
}
struct Cat {}
fn (c Cat) name(s string) {}
fn foo(a Animal) {}
fn main() {
foo(Cat{})
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/unimplemented_interface_d.v:12:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`, expected `speak(s string)`
10 |
11 | fn main() {
12 | foo(Cat{})
| ~~~~~
13 | }

View File

@ -0,0 +1,13 @@
interface Animal {
speak(s string)
}
struct Cat {}
fn (c Cat) speak() {}
fn foo(a Animal) {}
fn main() {
foo(Cat{})
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/unimplemented_interface_e.v:12:6: error: `Cat` incorrectly implements method `speak` of interface `Animal`, expected `speak(s string)`
10 |
11 | fn main() {
12 | foo(Cat{})
| ~~~~~
13 | }

View File

@ -0,0 +1,13 @@
interface Animal {
speak(s string)
}
struct Cat {}
fn (c Cat) speak(s &string) {}
fn foo(a Animal) {}
fn main() {
foo(Cat{})
}

View File

@ -0,0 +1,6 @@
vlib/v/checker/tests/unimplemented_interface_f.v:11:13: error: `Cat` incorrectly implements method `speak` of interface `Animal`, expected `speak(s string)`
9 | fn main() {
10 | mut animals := []Animal{}
11 | animals << Cat{}
| ~~~~~
12 | }

View File

@ -0,0 +1,12 @@
interface Animal {
speak(s string)
}
struct Cat {}
fn (c Cat) speak() {}
fn main() {
mut animals := []Animal{}
animals << Cat{}
}

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/unimplemented_interface_g.v:12:13: error: `Cat` incorrectly implements method `speak` of interface `Animal`, expected `speak(s string)`
10 | mut animals := []Animal{}
11 | mut cats := []Cat{}
12 | animals << cats
| ~~~~
13 | }
14 |

View File

@ -0,0 +1,14 @@
interface Animal {
speak(s string)
}
struct Cat {}
fn (c Cat) speak() {}
fn main() {
mut animals := []Animal{}
mut cats := []Cat{}
animals << cats
}

View File

@ -641,6 +641,26 @@ pub fn (table &Table) type_to_str(t Type) string {
return res
}
pub fn(t &Table) fn_to_str(func &Fn) string {
mut sb := strings.new_builder(20)
sb.write('${func.name}(')
for i in 1 .. func.args.len {
arg := func.args[i]
sb.write('$arg.name')
if i == func.args.len - 1 || func.args[i + 1].typ != arg.typ {
sb.write(' ${t.type_to_str(arg.typ)}')
}
if i != func.args.len - 1 {
sb.write(', ')
}
}
sb.write(')')
if func.return_type != table.void_type {
sb.write(' ${t.type_to_str(func.return_type)}')
}
return sb.str()
}
pub fn (t &TypeSymbol) has_method(name string) bool {
t.find_method(name) or {
return false

View File

@ -71,6 +71,21 @@ pub fn (f &Fn) signature() string {
return sig
}
pub fn (f &Fn) is_same_method_as(func &Fn) bool {
if f.return_type != func.return_type {
return false
}
if f.args.len != func.args.len {
return false
}
for i in 1 .. f.args.len {
if f.args[i].typ != func.args[i].typ {
return false
}
}
return true
}
pub fn (t &Table) find_fn(name string) ?Fn {
f := t.fns[name]
if f.name.str != 0 {
@ -147,15 +162,6 @@ pub fn (t &Table) struct_find_field(s &TypeSymbol, name string) ?Field {
return none
}
pub fn (t &Table) interface_add_type(inter mut Interface, typ Type) bool {
// TODO Verify `typ` implements `inter`
typ_sym := t.get_type_symbol(typ)
if typ !in inter.types && typ_sym.kind != .interface_ {
inter.types << typ
}
return true
}
[inline]
pub fn (t &Table) find_type_idx(name string) int {
return t.type_idxs[name]
@ -470,17 +476,6 @@ pub fn (t &Table) check(got, expected Type) bool {
// # NOTE: use symbols from this point on for perf
got_type_sym := t.get_type_symbol(got)
exp_type_sym := t.get_type_symbol(expected)
// Handle expected interface
if exp_type_sym.kind == .interface_ {
mut info := exp_type_sym.info as Interface
return t.interface_add_type(info, got)
}
// Handle expected interface array
/*
if exp_type_sym.kind == .array && t.get_type_symbol(t.value_type(exp_idx)).kind == .interface_ {
return true
}
*/
//
if exp_type_sym.kind == .function && got_type_sym.kind == .int {
// TODO temporary

View File

@ -137,18 +137,14 @@ interface Animal {
speak(s string)
}
// utility function to convert to string, as a sample
fn (a Animal) str() string {
return 'Animal: type:${typeof(a)}, name:' + a.name() + '.'
}
fn test_interface_array() {
println('Test on array of animals ...')
mut animals := []Animal{}
animals = [ Cat{}, Dog{breed: 'Labrador Retriever'} ]
animals << Cat{}
assert true
println('Animals array contains: ${animals.str()}') // explicit call to 'str' function
println('Animals array contains: ${animals}') // implicit call to 'str' function
// TODO .str() from the real types should be called
// println('Animals array contains: ${animals.str()}') // explicit call to 'str' function
// println('Animals array contains: ${animals}') // implicit call to 'str' function
assert animals.len == 3
}