all: implement interface fields (#8259)
parent
3628751199
commit
c2d501e8a9
|
@ -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)`)
|
||||||
|
|
|
@ -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*
|
||||||
|
|
16
doc/docs.md
16
doc/docs.md
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 '))
|
||||||
|
|
|
@ -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('')
|
||||||
sb.writeln(methods_wrapper.str())
|
if ityp.methods.len > 0 {
|
||||||
sb.writeln(methods_typ_def.str())
|
sb.writeln(methods_wrapper.str())
|
||||||
sb.writeln(methods_struct_def.str())
|
sb.writeln(methods_typ_def.str())
|
||||||
sb.writeln(methods_struct.str())
|
sb.writeln(methods_struct_def.str())
|
||||||
|
sb.writeln(methods_struct.str())
|
||||||
|
}
|
||||||
sb.writeln(cast_functions.str())
|
sb.writeln(cast_functions.str())
|
||||||
}
|
}
|
||||||
return sb.str()
|
return sb.str()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -460,73 +460,104 @@ 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 {
|
||||||
ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt`
|
if p.peek_tok.kind == .lpar {
|
||||||
if p.tok.kind == .key_mut {
|
ts = p.table.get_type_symbol(typ) // removing causes memory bug visible by `v -silent test-fmt`
|
||||||
if is_mut {
|
if p.tok.kind == .key_mut {
|
||||||
p.error_with_pos('redefinition of `mut` section', p.tok.position())
|
if is_mut {
|
||||||
return {}
|
p.error_with_pos('redefinition of `mut` section', p.tok.position())
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
p.check(.colon)
|
||||||
|
is_mut = true
|
||||||
}
|
}
|
||||||
p.next()
|
method_start_pos := p.tok.position()
|
||||||
p.check(.colon)
|
line_nr := p.tok.line_nr
|
||||||
is_mut = true
|
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.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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -651,7 +651,8 @@ 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 {
|
||||||
|
|
|
@ -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