diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f4d18bbf15..87f9f925e1 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 { diff --git a/vlib/v/checker/tests/unimplemented_interface_a.out b/vlib/v/checker/tests/unimplemented_interface_a.out new file mode 100644 index 0000000000..73774f25a9 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_a.out @@ -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 | } diff --git a/vlib/v/checker/tests/unimplemented_interface_a.vv b/vlib/v/checker/tests/unimplemented_interface_a.vv new file mode 100644 index 0000000000..fad1194d51 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_a.vv @@ -0,0 +1,11 @@ +interface Animal { + name() string +} + +struct Cat {} + +fn foo(a Animal) {} + +fn main() { + foo(Cat{}) +} diff --git a/vlib/v/checker/tests/unimplemented_interface_b.out b/vlib/v/checker/tests/unimplemented_interface_b.out new file mode 100644 index 0000000000..fa14d4cdf0 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_b.out @@ -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 | } diff --git a/vlib/v/checker/tests/unimplemented_interface_b.vv b/vlib/v/checker/tests/unimplemented_interface_b.vv new file mode 100644 index 0000000000..0b07a7866b --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_b.vv @@ -0,0 +1,14 @@ +interface Animal { + name() string +} + +struct Cat {} + +fn (c Cat) name() {} + +fn foo(a Animal) {} + +fn main() { + c := Cat{} + foo(c) +} diff --git a/vlib/v/checker/tests/unimplemented_interface_c.out b/vlib/v/checker/tests/unimplemented_interface_c.out new file mode 100644 index 0000000000..2129ed11bb --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_c.out @@ -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 | } diff --git a/vlib/v/checker/tests/unimplemented_interface_c.vv b/vlib/v/checker/tests/unimplemented_interface_c.vv new file mode 100644 index 0000000000..46960da210 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_c.vv @@ -0,0 +1,13 @@ +interface Animal { + name() +} + +struct Cat {} + +fn (c Cat) name(s string) {} + +fn foo(a Animal) {} + +fn main() { + foo(Cat{}) +} diff --git a/vlib/v/checker/tests/unimplemented_interface_d.out b/vlib/v/checker/tests/unimplemented_interface_d.out new file mode 100644 index 0000000000..a0085d9335 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_d.out @@ -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 | } diff --git a/vlib/v/checker/tests/unimplemented_interface_d.vv b/vlib/v/checker/tests/unimplemented_interface_d.vv new file mode 100644 index 0000000000..51b3724fd6 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_d.vv @@ -0,0 +1,13 @@ +interface Animal { + speak(s string) +} + +struct Cat {} + +fn (c Cat) speak() {} + +fn foo(a Animal) {} + +fn main() { + foo(Cat{}) +} diff --git a/vlib/v/checker/tests/unimplemented_interface_e.out b/vlib/v/checker/tests/unimplemented_interface_e.out new file mode 100644 index 0000000000..3c58a122b0 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_e.out @@ -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 | } diff --git a/vlib/v/checker/tests/unimplemented_interface_e.vv b/vlib/v/checker/tests/unimplemented_interface_e.vv new file mode 100644 index 0000000000..34d3d797fc --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_e.vv @@ -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{}) +} diff --git a/vlib/v/checker/tests/unimplemented_interface_f.out b/vlib/v/checker/tests/unimplemented_interface_f.out new file mode 100644 index 0000000000..8a6837a58e --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_f.out @@ -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 | } diff --git a/vlib/v/checker/tests/unimplemented_interface_f.vv b/vlib/v/checker/tests/unimplemented_interface_f.vv new file mode 100644 index 0000000000..3fd767d843 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_f.vv @@ -0,0 +1,12 @@ +interface Animal { + speak(s string) +} + +struct Cat {} + +fn (c Cat) speak() {} + +fn main() { + mut animals := []Animal{} + animals << Cat{} +} diff --git a/vlib/v/checker/tests/unimplemented_interface_g.out b/vlib/v/checker/tests/unimplemented_interface_g.out new file mode 100644 index 0000000000..1a148a8418 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_g.out @@ -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 | diff --git a/vlib/v/checker/tests/unimplemented_interface_g.vv b/vlib/v/checker/tests/unimplemented_interface_g.vv new file mode 100644 index 0000000000..fca4beb557 --- /dev/null +++ b/vlib/v/checker/tests/unimplemented_interface_g.vv @@ -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 +} + diff --git a/vlib/v/table/atypes.v b/vlib/v/table/atypes.v index f130a11547..eb6f750f8e 100644 --- a/vlib/v/table/atypes.v +++ b/vlib/v/table/atypes.v @@ -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 diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index 7a7d6bd808..37ba4018a3 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -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 diff --git a/vlib/v/tests/interface_test.v b/vlib/v/tests/interface_test.v index 799cbf3ef4..2c874971a9 100644 --- a/vlib/v/tests/interface_test.v +++ b/vlib/v/tests/interface_test.v @@ -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 }