checker: check interface implementation
							parent
							
								
									b627bb933c
								
							
						
					
					
						commit
						215657e16a
					
				| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 | }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
interface Animal {
 | 
			
		||||
	name() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Cat {}
 | 
			
		||||
 | 
			
		||||
fn foo(a Animal) {}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
	foo(Cat{})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 | }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
interface Animal {
 | 
			
		||||
	name() string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Cat {}
 | 
			
		||||
 | 
			
		||||
fn (c Cat) name() {}
 | 
			
		||||
 | 
			
		||||
fn foo(a Animal) {}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
	c := Cat{}
 | 
			
		||||
	foo(c)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 | }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
interface Animal {
 | 
			
		||||
	name()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Cat {}
 | 
			
		||||
 | 
			
		||||
fn (c Cat) name(s string) {}
 | 
			
		||||
 | 
			
		||||
fn foo(a Animal) {}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
	foo(Cat{})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 | }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
interface Animal {
 | 
			
		||||
	speak(s string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Cat {}
 | 
			
		||||
 | 
			
		||||
fn (c Cat) speak() {}
 | 
			
		||||
 | 
			
		||||
fn foo(a Animal) {}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
	foo(Cat{})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 | }
 | 
			
		||||
| 
						 | 
				
			
			@ -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{})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 | }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
interface Animal {
 | 
			
		||||
	speak(s string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Cat {}
 | 
			
		||||
 | 
			
		||||
fn (c Cat) speak() {}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
	mut animals := []Animal{}
 | 
			
		||||
	animals << Cat{}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 |
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue