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 + IO streams
+ struct embedding + struct embedding
- interface 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) - 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)` - 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)`) + short generics syntax (`foo(5)` instead of `foo<int>(5)`)

View File

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

View File

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

View File

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

View File

@ -376,6 +376,56 @@ pub fn (mut c Checker) interface_decl(decl ast.InterfaceDecl) {
for method in decl.methods { for method in decl.methods {
c.check_valid_snake_case(method.name, 'method name', method.pos) 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) { 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 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 { .array, .string {
// This should only happen in `builtin` // This should only happen in `builtin`
// TODO Remove `crypto.rand` when possible (see vlib/crypto/rand/rand.v, // 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 // call struct field fn type
// TODO: can we use SelectorExpr for all? this dosent really belong here // 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) field_type_sym := c.table.get_type_symbol(field.typ)
if field_type_sym.kind == .function { if field_type_sym.kind == .function {
// call_expr.is_method = false // 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) pos)
} }
if mut inter_sym.info is table.Interface { 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_ { if typ !in inter_sym.info.types && typ_sym.kind != .interface_ {
inter_sym.info.types << typ inter_sym.info.types << typ
} }
@ -2158,7 +2231,7 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) table.T
} }
} }
} else { } else {
if f := c.table.struct_find_field(sym, field_name) { if f := c.table.find_field(sym, field_name) {
has_field = true has_field = true
field = f field = f
} else { } 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{} mut embed_of_found_fields := []table.Type{}
for embed in sym.info.embeds { for embed in sym.info.embeds {
embed_sym := c.table.get_type_symbol(embed) 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 found_fields << f
embed_of_found_fields << embed 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 selector_expr.typ = field.typ
return field.typ return field.typ
} }
if sym.kind !in [.struct_, .aggregate] { if sym.kind !in [.struct_, .aggregate, .interface_] {
if sym.kind != .placeholder { if sym.kind != .placeholder {
c.error('`$sym.name` is not a struct', selector_expr.pos) 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 return table.int_type
} }
if c.inside_sql { 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 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{} mut sum_type_casts := []table.Type{}
expr_sym := c.table.get_type_symbol(expr.expr_type) expr_sym := c.table.get_type_symbol(expr.expr_type)
mut orig_type := 0 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 { if field.is_mut {
root_ident := expr.root_ident() root_ident := expr.root_ident()
if v := scope.find_var(root_ident.name) { 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('.') name := node.name.after('.')
f.write('interface $name {') 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.writeln('')
} }
f.comments_after_last_field(node.pre_comments) 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 { for method in node.methods {
f.write('\t') f.write('\t')
f.write(method.stringify(f.table, f.cur_mod, f.mod2alias).after('fn ')) 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() { 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 { for typ in g.table.types {
match typ.kind { match typ.kind {
.alias { .alias {
@ -685,7 +679,16 @@ typedef struct {
g.type_definitions.writeln('typedef array $typ.cname;') g.type_definitions.writeln('typedef array $typ.cname;')
} }
.interface_ { .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 { .chan {
if 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) opt_base_typ := g.base_type(node.expr_type)
g.writeln('(*($opt_base_typ*)') g.writeln('(*($opt_base_typ*)')
} }
if sym.kind == .interface_ {
g.write('*(')
}
if sym.kind == .array_fixed { if sym.kind == .array_fixed {
assert node.field_name == 'len' assert node.field_name == 'len'
info := sym.info as table.ArrayFixed 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_deref_field := ''
mut sum_type_dot := '.' 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) field_sym := g.table.get_type_symbol(f.typ)
if field_sym.kind == .sum_type { if field_sym.kind == .sum_type {
if !prevent_sum_type_unwrapping_once { if !prevent_sum_type_unwrapping_once {
@ -3003,6 +3009,9 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
if sum_type_deref_field != '' { if sum_type_deref_field != '' {
g.write('$sum_type_dot$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) { 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) // TODO g.fn_args(method.args[1..], method.is_variadic)
methods_typ_def.writeln(');') 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 imethods[method.name] = typ_name
} }
methods_struct_def.writeln('};') methods_struct_def.writeln('};')
@ -5909,7 +5918,6 @@ fn (mut g Gen) interface_table() string {
} }
} }
mut cast_functions := strings.new_builder(100) mut cast_functions := strings.new_builder(100)
cast_functions.write('// Casting functions for interface "$interface_name"')
mut methods_wrapper := strings.new_builder(100) mut methods_wrapper := strings.new_builder(100)
methods_wrapper.writeln('// Methods wrapper for interface "$interface_name"') methods_wrapper.writeln('// Methods wrapper for interface "$interface_name"')
mut already_generated_mwrappers := map[string]int{} 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 already_generated_mwrappers[interface_index_name] = current_iinidx
current_iinidx++ current_iinidx++
// eprintln('>>> current_iinidx: ${current_iinidx-iinidx_minimum_base} | interface_index_name: $interface_index_name') // 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_name 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}_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(' cast_functions.writeln('
$staticprefix _Interface I_${cctype}_to_Interface_${interface_name}($cctype* x) { // Casting functions for converting "$cctype" to interface "$interface_name"
return (_Interface) { $staticprefix inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x) {
._object = (void*) (x), return $cast_struct_str;
._interface_idx = $interface_index_name
};
} }
$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 // TODO Remove memdup
return (_Interface*) memdup(&(_Interface) { return ($interface_name*) memdup(&$cast_struct_str, sizeof($interface_name));
._object = (void*) (x),
._interface_idx = $interface_index_name
}, sizeof(_Interface));
}') }')
if g.pref.build_mode != .build_module { if g.pref.build_mode != .build_module {
methods_struct.writeln('\t{') methods_struct.writeln('\t{')
} }
@ -5991,7 +6007,7 @@ $staticprefix _Interface* I_${cctype}_to_Interface_${interface_name}_ptr($cctype
method_call += '_method_wrapper' method_call += '_method_wrapper'
} }
if g.pref.build_mode != .build_module { 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 { 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 // add line return after interface index declarations
sb.writeln('') sb.writeln('')
if ityp.methods.len > 0 {
sb.writeln(methods_wrapper.str()) sb.writeln(methods_wrapper.str())
sb.writeln(methods_typ_def.str()) sb.writeln(methods_typ_def.str())
sb.writeln(methods_struct_def.str()) sb.writeln(methods_struct_def.str())
sb.writeln(methods_struct.str()) sb.writeln(methods_struct.str())
}
sb.writeln(cast_functions.str()) sb.writeln(cast_functions.str())
} }
return sb.str() return sb.str()

View File

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

View File

@ -460,10 +460,12 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
mut ts := p.table.get_type_symbol(typ) mut ts := p.table.get_type_symbol(typ)
// if methods were declared before, it's an error, ignore them // if methods were declared before, it's an error, ignore them
ts.methods = []table.Fn{cap: 20} 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 methods := []ast.FnDecl{cap: 20}
mut is_mut := false mut is_mut := false
for p.tok.kind != .rcbr && p.tok.kind != .eof { for p.tok.kind != .rcbr && p.tok.kind != .eof {
if p.peek_tok.kind == .lpar {
ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt` ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt`
if p.tok.kind == .key_mut { if p.tok.kind == .key_mut {
if is_mut { if is_mut {
@ -521,12 +523,41 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
is_variadic: is_variadic is_variadic: is_variadic
is_pub: true 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
}
} }
p.top_level_statement_end() p.top_level_statement_end()
p.check(.rcbr) p.check(.rcbr)
pos.update_last_line(p.prev_tok.line_nr) pos.update_last_line(p.prev_tok.line_nr)
return ast.InterfaceDecl{ return ast.InterfaceDecl{
name: interface_name name: interface_name
fields: fields
methods: methods methods: methods
is_pub: is_pub is_pub: is_pub
pos: pos pos: pos

View File

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

View File

@ -652,6 +652,7 @@ pub mut:
pub struct Interface { pub struct Interface {
pub mut: pub mut:
types []Type types []Type
fields []Field
} }
pub struct Enum { 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 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 { fn (a &Aggregate) find_field(name string) ?Field {
for field in a.fields { for field in a.fields {
if field.name == name { if field.name == name {
@ -952,6 +962,15 @@ fn (a &Aggregate) find_field(name string) ?Field {
return none 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 { pub fn (s Struct) find_field(name string) ?Field {
for field in s.fields { for field in s.fields {
if field.name == name { 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??'
}