mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
all: implement interface fields (#8259)
This commit is contained in:
parent
3628751199
commit
c2d501e8a9
@ -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)`)
|
||||
|
@ -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*
|
||||
|
16
doc/docs.md
16
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')
|
||||
}
|
||||
|
@ -230,6 +230,7 @@ pub:
|
||||
field_names []string
|
||||
is_pub bool
|
||||
methods []FnDecl
|
||||
fields []StructField
|
||||
pos token.Position
|
||||
pre_comments []Comment
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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 '))
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
55
vlib/v/tests/interface_fields_test.v
Normal file
55
vlib/v/tests/interface_fields_test.v
Normal 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??'
|
||||
}
|
Loading…
Reference in New Issue
Block a user