diff --git a/0.3_roadmap.txt b/0.3_roadmap.txt index cfc5203242..5a33eaafb2 100644 --- a/0.3_roadmap.txt +++ b/0.3_roadmap.txt @@ -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(5)`) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee683020e4..7b821af0d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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* diff --git a/doc/docs.md b/doc/docs.md index 4de3195608..0bbf37a561 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -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') } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 413d16b1e2..041a94e258 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -230,6 +230,7 @@ pub: field_names []string is_pub bool methods []FnDecl + fields []StructField pos token.Position pre_comments []Comment } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 187013c27b..a0709bed36 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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) { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 1c27cea89c..2005fad8aa 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -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 ')) diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 52d17a70a1..c0b89d6e71 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -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() diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 855e4a2783..1630a6f4e5 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -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 { diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index 1dbed7b5ef..fc82eed556 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -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 diff --git a/vlib/v/table/table.v b/vlib/v/table/table.v index 938e78f45d..e35eef2055 100644 --- a/vlib/v/table/table.v +++ b/vlib/v/table/table.v @@ -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 diff --git a/vlib/v/table/types.v b/vlib/v/table/types.v index 3bda328f8d..d155a4f771 100644 --- a/vlib/v/table/types.v +++ b/vlib/v/table/types.v @@ -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 { diff --git a/vlib/v/tests/interface_fields_test.v b/vlib/v/tests/interface_fields_test.v new file mode 100644 index 0000000000..102b8fcac4 --- /dev/null +++ b/vlib/v/tests/interface_fields_test.v @@ -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??' +}