all: implement interface fields (#8259)

pull/8201/head^2
spaceface 2021-01-23 07:57:17 +01:00 committed by GitHub
parent 3628751199
commit c2d501e8a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 317 additions and 103 deletions

View File

@ -19,7 +19,7 @@
+ IO streams
+ struct embedding
- interface embedding
- interfaces: allow struct fields (not just methods)
+ interfaces: allow struct fields (not just methods)
- vfmt: fix common errors automatically to save time (make vars mutable and vice versa, add missing imports etc)
- method expressions with an explicit receiver as the first argument: `fn handle(f OnClickFn) { f() } button := Button{} handle(btn.click)`
+ short generics syntax (`foo(5)` instead of `foo<int>(5)`)

View File

@ -1,5 +1,6 @@
## V 0.2.3
*Not yet released*
- Allow interfaces to define fields, not just methods.
## V 0.2.2
*22 Jan 2021*

View File

@ -1839,7 +1839,9 @@ struct Dog {
breed string
}
struct Cat {}
struct Cat {
breed string
}
fn (d Dog) speak() string {
return 'woof'
@ -1849,30 +1851,32 @@ fn (c Cat) speak() string {
return 'meow'
}
// unlike Go and like TypeScript, V's interfaces can define fields, not just methods.
interface Speaker {
breed string
speak() string
}
dog := Dog{'Leonberger'}
cat := Cat{}
cat := Cat{'Siamese'}
mut arr := []Speaker{}
arr << dog
arr << cat
for item in arr {
item.speak()
println('a $item.breed ${typeof(item).name} says: $item.speak()')
}
```
A type implements an interface by implementing its methods.
A type implements an interface by implementing its methods and fields.
There is no explicit declaration of intent, no "implements" keyword.
We can test the underlying type of an interface using dynamic cast operators:
```v oksyntax
fn announce(s Speaker) {
if s is Dog {
println('a $s.breed') // `s` is automatically cast to `Dog` (smart cast)
println('a $s.breed dog') // `s` is automatically cast to `Dog` (smart cast)
} else if s is Cat {
println('a cat')
println('a $s.breed cat')
} else {
println('something else')
}

View File

@ -230,6 +230,7 @@ pub:
field_names []string
is_pub bool
methods []FnDecl
fields []StructField
pos token.Position
pre_comments []Comment
}

View File

@ -376,6 +376,56 @@ pub fn (mut c Checker) interface_decl(decl ast.InterfaceDecl) {
for method in decl.methods {
c.check_valid_snake_case(method.name, 'method name', method.pos)
}
// TODO: copy pasta from StructDecl
for i, field in decl.fields {
c.check_valid_snake_case(field.name, 'field name', field.pos)
sym := c.table.get_type_symbol(field.typ)
for j in 0 .. i {
if field.name == decl.fields[j].name {
c.error('field name `$field.name` duplicate', field.pos)
}
}
if sym.kind == .placeholder && !sym.name.starts_with('C.') {
c.error(util.new_suggestion(sym.name, c.table.known_type_names()).say('unknown type `$sym.name`'),
field.type_pos)
}
// Separate error condition for `int_literal` and `float_literal` because `util.suggestion` may give different
// suggestions due to f32 comparision issue.
if sym.kind in [.int_literal, .float_literal] {
msg := if sym.kind == .int_literal {
'unknown type `$sym.name`.\nDid you mean `int`?'
} else {
'unknown type `$sym.name`.\nDid you mean `f64`?'
}
c.error(msg, field.type_pos)
}
if sym.kind == .array {
array_info := sym.array_info()
elem_sym := c.table.get_type_symbol(array_info.elem_type)
if elem_sym.kind == .placeholder {
c.error(util.new_suggestion(elem_sym.name, c.table.known_type_names()).say('unknown type `$elem_sym.name`'),
field.type_pos)
}
}
if sym.kind == .struct_ {
info := sym.info as table.Struct
if info.is_ref_only && !field.typ.is_ptr() {
c.error('`$sym.name` type can only be used as a reference: `&$sym.name`',
field.type_pos)
}
}
if sym.kind == .map {
info := sym.map_info()
key_sym := c.table.get_type_symbol(info.key_type)
value_sym := c.table.get_type_symbol(info.value_type)
if key_sym.kind == .placeholder {
c.error('unknown type `$key_sym.name`', field.type_pos)
}
if value_sym.kind == .placeholder {
c.error('unknown type `$value_sym.name`', field.type_pos)
}
}
}
}
pub fn (mut c Checker) struct_decl(mut decl ast.StructDecl) {
@ -1095,6 +1145,15 @@ fn (mut c Checker) fail_if_immutable(expr ast.Expr) (string, token.Position) {
explicit_lock_needed = true
}
}
.interface_ {
// TODO: mutability checks on interface fields?
interface_info := typ_sym.info as table.Interface
interface_info.find_field(expr.field_name) or {
type_str := c.table.type_to_str(expr.expr_type)
c.error('unknown field `${type_str}.$expr.field_name`', expr.pos)
return '', pos
}
}
.array, .string {
// This should only happen in `builtin`
// TODO Remove `crypto.rand` when possible (see vlib/crypto/rand/rand.v,
@ -1573,7 +1632,7 @@ pub fn (mut c Checker) call_method(mut call_expr ast.CallExpr) table.Type {
}
// call struct field fn type
// TODO: can we use SelectorExpr for all? this dosent really belong here
if field := c.table.struct_find_field(left_type_sym, method_name) {
if field := c.table.find_field(left_type_sym, method_name) {
field_type_sym := c.table.get_type_symbol(field.typ)
if field_type_sym.kind == .function {
// call_expr.is_method = false
@ -1971,6 +2030,20 @@ fn (mut c Checker) type_implements(typ table.Type, inter_typ table.Type, pos tok
pos)
}
if mut inter_sym.info is table.Interface {
for ifield in inter_sym.info.fields {
if field := typ_sym.find_field(ifield.name) {
if ifield.typ != field.typ {
exp := c.table.type_to_str(ifield.typ)
got := c.table.type_to_str(field.typ)
c.error('`$styp` incorrectly implements field `$ifield.name` of interface `$inter_sym.name`, expected `$exp`, got `$got`',
pos)
return false
}
continue
}
c.error("`$styp` doesn't implement field `$ifield.name` of interface `$inter_sym.name`",
pos)
}
if typ !in inter_sym.info.types && typ_sym.kind != .interface_ {
inter_sym.info.types << typ
}
@ -2158,7 +2231,7 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
}
}
} else {
if f := c.table.struct_find_field(sym, field_name) {
if f := c.table.find_field(sym, field_name) {
has_field = true
field = f
} else {
@ -2168,7 +2241,7 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
mut embed_of_found_fields := []table.Type{}
for embed in sym.info.embeds {
embed_sym := c.table.get_type_symbol(embed)
if f := c.table.struct_find_field(embed_sym, field_name) {
if f := c.table.find_field(embed_sym, field_name) {
found_fields << f
embed_of_found_fields << embed
}
@ -2209,7 +2282,7 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
selector_expr.typ = field.typ
return field.typ
}
if sym.kind !in [.struct_, .aggregate] {
if sym.kind !in [.struct_, .aggregate, .interface_] {
if sym.kind != .placeholder {
c.error('`$sym.name` is not a struct', selector_expr.pos)
}
@ -3881,7 +3954,7 @@ pub fn (mut c Checker) ident(mut ident ast.Ident) table.Type {
return table.int_type
}
if c.inside_sql {
if field := c.table.struct_find_field(c.cur_orm_ts, ident.name) {
if field := c.table.find_field(c.cur_orm_ts, ident.name) {
return field.typ
}
}
@ -4219,7 +4292,7 @@ fn (c Checker) smartcast_sumtype(expr ast.Expr, cur_type table.Type, to_type tab
mut sum_type_casts := []table.Type{}
expr_sym := c.table.get_type_symbol(expr.expr_type)
mut orig_type := 0
if field := c.table.struct_find_field(expr_sym, expr.field_name) {
if field := c.table.find_field(expr_sym, expr.field_name) {
if field.is_mut {
root_ident := expr.root_ident()
if v := scope.find_var(root_ident.name) {

View File

@ -782,10 +782,18 @@ pub fn (mut f Fmt) interface_decl(node ast.InterfaceDecl) {
}
name := node.name.after('.')
f.write('interface $name {')
if node.methods.len > 0 || node.pos.line_nr < node.pos.last_line {
if node.fields.len > 0 || node.methods.len > 0 || node.pos.line_nr < node.pos.last_line {
f.writeln('')
}
f.comments_after_last_field(node.pre_comments)
for field in node.fields {
// TODO: alignment, comments, etc.
mut ft := f.no_cur_mod(f.table.type_to_str(field.typ))
if !ft.contains('C.') && !ft.contains('JS.') && !ft.contains('fn (') {
ft = f.short_module(ft)
}
f.writeln('\t$field.name $ft')
}
for method in node.methods {
f.write('\t')
f.write(method.stringify(f.table, f.cur_mod, f.mod2alias).after('fn '))

View File

@ -659,12 +659,6 @@ fn (g &Gen) type_sidx(t table.Type) string {
//
pub fn (mut g Gen) write_typedef_types() {
g.typedefs.writeln('
typedef struct {
void* _object;
int _interface_idx;
} _Interface;
')
for typ in g.table.types {
match typ.kind {
.alias {
@ -685,7 +679,16 @@ typedef struct {
g.type_definitions.writeln('typedef array $typ.cname;')
}
.interface_ {
g.type_definitions.writeln('typedef _Interface ${c_name(typ.name)};')
info := typ.info as table.Interface
g.type_definitions.writeln('typedef struct {')
g.type_definitions.writeln('\tvoid* _object;')
g.type_definitions.writeln('\tint _interface_idx;')
for field in info.fields {
styp := g.typ(field.typ)
cname := c_name(field.name)
g.type_definitions.writeln('\t$styp* $cname;')
}
g.type_definitions.writeln('} ${c_name(typ.name)};')
}
.chan {
if typ.name != 'chan' {
@ -2927,6 +2930,9 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
opt_base_typ := g.base_type(node.expr_type)
g.writeln('(*($opt_base_typ*)')
}
if sym.kind == .interface_ {
g.write('*(')
}
if sym.kind == .array_fixed {
assert node.field_name == 'len'
info := sym.info as table.ArrayFixed
@ -2941,7 +2947,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
}
mut sum_type_deref_field := ''
mut sum_type_dot := '.'
if f := g.table.struct_find_field(sym, node.field_name) {
if f := g.table.find_field(sym, node.field_name) {
field_sym := g.table.get_type_symbol(f.typ)
if field_sym.kind == .sum_type {
if !prevent_sum_type_unwrapping_once {
@ -3003,6 +3009,9 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
if sum_type_deref_field != '' {
g.write('$sum_type_dot$sum_type_deref_field)')
}
if sym.kind == .interface_ {
g.write(')')
}
}
fn (mut g Gen) enum_expr(node ast.Expr) {
@ -5888,7 +5897,7 @@ fn (mut g Gen) interface_table() string {
}
// TODO g.fn_args(method.args[1..], method.is_variadic)
methods_typ_def.writeln(');')
methods_struct_def.writeln('\t$typ_name ${c_name(method.name)};')
methods_struct_def.writeln('\t$typ_name _method_${c_name(method.name)};')
imethods[method.name] = typ_name
}
methods_struct_def.writeln('};')
@ -5909,7 +5918,6 @@ fn (mut g Gen) interface_table() string {
}
}
mut cast_functions := strings.new_builder(100)
cast_functions.write('// Casting functions for interface "$interface_name"')
mut methods_wrapper := strings.new_builder(100)
methods_wrapper.writeln('// Methods wrapper for interface "$interface_name"')
mut already_generated_mwrappers := map[string]int{}
@ -5931,23 +5939,31 @@ fn (mut g Gen) interface_table() string {
already_generated_mwrappers[interface_index_name] = current_iinidx
current_iinidx++
// eprintln('>>> current_iinidx: ${current_iinidx-iinidx_minimum_base} | interface_index_name: $interface_index_name')
sb.writeln('$staticprefix _Interface I_${cctype}_to_Interface_${interface_name}($cctype* x);')
sb.writeln('$staticprefix _Interface* I_${cctype}_to_Interface_${interface_name}_ptr($cctype* x);')
sb.writeln('$staticprefix $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x);')
sb.writeln('$staticprefix $interface_name* I_${cctype}_to_Interface_${interface_name}_ptr($cctype* x);')
mut cast_struct := strings.new_builder(100)
cast_struct.writeln('($interface_name) {')
cast_struct.writeln('\t\t._object = (void*) (x),')
cast_struct.writeln('\t\t._interface_idx = $interface_index_name,')
for field in inter_info.fields {
cname := c_name(field.name)
field_styp := g.typ(field.typ)
cast_struct.writeln('\t\t.$cname = ($field_styp*)((char*)x + __offsetof($cctype, $cname)),')
}
cast_struct.write('\t}')
cast_struct_str := cast_struct.str()
cast_functions.writeln('
$staticprefix _Interface I_${cctype}_to_Interface_${interface_name}($cctype* x) {
return (_Interface) {
._object = (void*) (x),
._interface_idx = $interface_index_name
};
// Casting functions for converting "$cctype" to interface "$interface_name"
$staticprefix inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x) {
return $cast_struct_str;
}
$staticprefix _Interface* I_${cctype}_to_Interface_${interface_name}_ptr($cctype* x) {
$staticprefix $interface_name* I_${cctype}_to_Interface_${interface_name}_ptr($cctype* x) {
// TODO Remove memdup
return (_Interface*) memdup(&(_Interface) {
._object = (void*) (x),
._interface_idx = $interface_index_name
}, sizeof(_Interface));
return ($interface_name*) memdup(&$cast_struct_str, sizeof($interface_name));
}')
if g.pref.build_mode != .build_module {
methods_struct.writeln('\t{')
}
@ -5991,7 +6007,7 @@ $staticprefix _Interface* I_${cctype}_to_Interface_${interface_name}_ptr($cctype
method_call += '_method_wrapper'
}
if g.pref.build_mode != .build_module {
methods_struct.writeln('\t\t.${c_name(method.name)} = $method_call,')
methods_struct.writeln('\t\t._method_${c_name(method.name)} = $method_call,')
}
}
if g.pref.build_mode != .build_module {
@ -6014,10 +6030,12 @@ $staticprefix _Interface* I_${cctype}_to_Interface_${interface_name}_ptr($cctype
}
// add line return after interface index declarations
sb.writeln('')
sb.writeln(methods_wrapper.str())
sb.writeln(methods_typ_def.str())
sb.writeln(methods_struct_def.str())
sb.writeln(methods_struct.str())
if ityp.methods.len > 0 {
sb.writeln(methods_wrapper.str())
sb.writeln(methods_typ_def.str())
sb.writeln(methods_struct_def.str())
sb.writeln(methods_struct.str())
}
sb.writeln(cast_functions.str())
}
return sb.str()

View File

@ -354,7 +354,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) {
g.expr(node.left)
dot := if node.left_type.is_ptr() { '->' } else { '.' }
mname := c_name(node.name)
g.write('${dot}_interface_idx].${mname}(')
g.write('${dot}_interface_idx]._method_${mname}(')
g.expr(node.left)
g.write('${dot}_object')
if node.args.len > 0 {

View File

@ -460,73 +460,104 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
mut ts := p.table.get_type_symbol(typ)
// if methods were declared before, it's an error, ignore them
ts.methods = []table.Fn{cap: 20}
// Parse methods
// Parse fields or methods
mut fields := []ast.StructField{cap: 20}
mut methods := []ast.FnDecl{cap: 20}
mut is_mut := false
for p.tok.kind != .rcbr && p.tok.kind != .eof {
ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt`
if p.tok.kind == .key_mut {
if is_mut {
p.error_with_pos('redefinition of `mut` section', p.tok.position())
return {}
if p.peek_tok.kind == .lpar {
ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt`
if p.tok.kind == .key_mut {
if is_mut {
p.error_with_pos('redefinition of `mut` section', p.tok.position())
return {}
}
p.next()
p.check(.colon)
is_mut = true
}
p.next()
p.check(.colon)
is_mut = true
method_start_pos := p.tok.position()
line_nr := p.tok.line_nr
name := p.check_name()
if ts.has_method(name) {
p.error_with_pos('duplicate method `$name`', method_start_pos)
return ast.InterfaceDecl{}
}
if util.contains_capital(name) {
p.error('interface methods cannot contain uppercase letters, use snake_case instead')
return ast.InterfaceDecl{}
}
// field_names << name
args2, _, is_variadic := p.fn_args() // TODO merge table.Param and ast.Arg to avoid this
mut args := [table.Param{
name: 'x'
is_mut: is_mut
typ: typ
is_hidden: true
}]
args << args2
mut method := ast.FnDecl{
name: name
mod: p.mod
params: args
file: p.file_name
return_type: table.void_type
is_variadic: is_variadic
is_pub: true
pos: method_start_pos.extend(p.prev_tok.position())
scope: p.scope
}
if p.tok.kind.is_start_of_type() && p.tok.line_nr == line_nr {
method.return_type = p.parse_type()
}
mcomments := p.eat_line_end_comments()
mnext_comments := p.eat_comments()
method.comments = mcomments
method.next_comments = mnext_comments
methods << method
// println('register method $name')
ts.register_method(
name: name
params: args
return_type: method.return_type
is_variadic: is_variadic
is_pub: true
)
} else {
// interface fields
field_pos := p.tok.position()
field_name := p.check_name()
mut type_pos := p.tok.position()
field_typ := p.parse_type()
type_pos = type_pos.extend(p.prev_tok.position())
mut comments := []ast.Comment{}
for p.tok.kind == .comment {
comments << p.comment()
if p.tok.kind == .rcbr {
break
}
}
fields << ast.StructField{
name: field_name
pos: field_pos
type_pos: type_pos
typ: field_typ
comments: comments
}
mut info := ts.info as table.Interface
info.fields << table.Field{
name: field_name
typ: field_typ
}
ts.info = info
}
method_start_pos := p.tok.position()
line_nr := p.tok.line_nr
name := p.check_name()
if ts.has_method(name) {
p.error_with_pos('duplicate method `$name`', method_start_pos)
return ast.InterfaceDecl{}
}
if util.contains_capital(name) {
p.error('interface methods cannot contain uppercase letters, use snake_case instead')
return ast.InterfaceDecl{}
}
// field_names << name
args2, _, is_variadic := p.fn_args() // TODO merge table.Param and ast.Arg to avoid this
mut args := [table.Param{
name: 'x'
is_mut: is_mut
typ: typ
is_hidden: true
}]
args << args2
mut method := ast.FnDecl{
name: name
mod: p.mod
params: args
file: p.file_name
return_type: table.void_type
is_variadic: is_variadic
is_pub: true
pos: method_start_pos.extend(p.prev_tok.position())
scope: p.scope
}
if p.tok.kind.is_start_of_type() && p.tok.line_nr == line_nr {
method.return_type = p.parse_type()
}
mcomments := p.eat_line_end_comments()
mnext_comments := p.eat_comments()
method.comments = mcomments
method.next_comments = mnext_comments
methods << method
// println('register method $name')
ts.register_method(
name: name
params: args
return_type: method.return_type
is_variadic: is_variadic
is_pub: true
)
}
p.top_level_statement_end()
p.check(.rcbr)
pos.update_last_line(p.prev_tok.line_nr)
return ast.InterfaceDecl{
name: interface_name
fields: fields
methods: methods
is_pub: is_pub
pos: pos

View File

@ -238,7 +238,7 @@ fn (t &Table) register_aggregate_field(mut sym TypeSymbol, name string) ?Field {
mut new_field := Field{}
for typ in agg_info.types {
ts := t.get_type_symbol(typ)
if type_field := t.struct_find_field(ts, name) {
if type_field := t.find_field(ts, name) {
if !found_once {
found_once = true
new_field = type_field
@ -255,15 +255,15 @@ fn (t &Table) register_aggregate_field(mut sym TypeSymbol, name string) ?Field {
pub fn (t &Table) struct_has_field(s &TypeSymbol, name string) bool {
// println('struct_has_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx')
if _ := t.struct_find_field(s, name) {
if _ := t.find_field(s, name) {
return true
}
return false
}
// search from current type up through each parent looking for field
pub fn (t &Table) struct_find_field(s &TypeSymbol, name string) ?Field {
// println('struct_find_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx')
pub fn (t &Table) find_field(s &TypeSymbol, name string) ?Field {
// println('find_field($s.name, $name) types.len=$t.types.len s.parent_idx=$s.parent_idx')
mut ts := s
for {
if mut ts.info is Struct {
@ -276,6 +276,10 @@ pub fn (t &Table) struct_find_field(s &TypeSymbol, name string) ?Field {
}
field := t.register_aggregate_field(mut ts, name) or { return error(err) }
return field
} else if mut ts.info is Interface {
if field := ts.info.find_field(name) {
return field
}
}
if ts.parent_idx == 0 {
break

View File

@ -651,7 +651,8 @@ pub mut:
pub struct Interface {
pub mut:
types []Type
types []Type
fields []Field
}
pub struct Enum {
@ -943,6 +944,15 @@ pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) {
return has_str_method, expects_ptr, nr_args
}
pub fn (t &TypeSymbol) find_field(name string) ?Field {
match t.info {
Aggregate { return t.info.find_field(name) }
Struct { return t.info.find_field(name) }
Interface { return t.info.find_field(name) }
else { return none }
}
}
fn (a &Aggregate) find_field(name string) ?Field {
for field in a.fields {
if field.name == name {
@ -952,6 +962,15 @@ fn (a &Aggregate) find_field(name string) ?Field {
return none
}
pub fn (i &Interface) find_field(name string) ?Field {
for field in i.fields {
if field.name == name {
return field
}
}
return none
}
pub fn (s Struct) find_field(name string) ?Field {
for field in s.fields {
if field.name == name {

View File

@ -0,0 +1,55 @@
interface Animal {
breed string
}
struct Cat {
padding int // ensures that the field offsets can be different
mut:
breed string
}
struct Dog {
padding [256]byte
padding2 int
mut:
breed string
}
fn use_interface(a Animal) {
assert a.breed in ['Persian', 'Labrador']
if a is Cat {
assert a.breed == 'Persian'
} else {
assert a.breed == 'Labrador'
}
}
fn mutate_interface(mut a Animal) {
if a is Cat {
a.breed = 'Siamese'
} else {
a.breed = 'Golden Retriever'
}
if a is Cat {
assert a.breed == 'Siamese'
} else {
assert a.breed == 'Golden Retriever'
}
a.breed = 'what??'
assert a.breed == 'what??'
}
fn test_interface_fields() {
mut c := Cat{
breed: 'Persian'
}
mut d := Dog{
breed: 'Labrador'
}
use_interface(c)
use_interface(d)
mutate_interface(mut c)
mutate_interface(mut d)
assert c.breed == 'what??'
assert d.breed == 'what??'
}