1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

v: implement interface embedding (#9935)

This commit is contained in:
Delyan Angelov 2021-05-02 03:00:47 +03:00 committed by GitHub
parent 3363c3ef65
commit 4b818fa2be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1217 additions and 29 deletions

View File

@ -260,18 +260,31 @@ pub:
pos token.Position
}
pub struct InterfaceEmbedding {
pub:
name string
typ Type
pos token.Position
comments []Comment
}
pub struct InterfaceDecl {
pub:
name string
typ Type
name_pos token.Position
language Language
field_names []string
is_pub bool
methods []FnDecl
mut_pos int // mut:
fields []StructField
pos token.Position
pre_comments []Comment
pub mut:
methods []FnDecl
fields []StructField
//
ifaces []InterfaceEmbedding
are_ifaces_expanded bool
}
pub struct StructInitField {
@ -352,7 +365,6 @@ pub struct FnDecl {
pub:
name string
mod string
params []Param
is_deprecated bool
is_pub bool
is_variadic bool
@ -379,6 +391,7 @@ pub:
attrs []Attr
skip_gen bool // this function doesn't need to be generated (for example [if foo])
pub mut:
params []Param
stmts []Stmt
defer_stmts []DeferStmt
return_type Type

View File

@ -19,6 +19,7 @@ pub mut:
cflags []cflag.CFlag
redefined_fns []string
fn_generic_types map[string][][]Type // for generic functions
interfaces map[int]InterfaceDecl
cmod_prefix string // needed for ast.type_to_str(Type) while vfmt; contains `os.`
is_fmt bool
used_fns map[string]bool // filled in by the checker, when pref.skip_unused = true;
@ -60,7 +61,6 @@ pub fn (t &Table) panic(message string) {
pub struct Fn {
pub:
params []Param
return_type Type
is_variadic bool
language Language
@ -77,8 +77,12 @@ pub:
mod string
ctdefine string // compile time define. "myflag", when [if myflag] tag
attrs []Attr
//
pos token.Position
return_type_pos token.Position
pub mut:
name string
params []Param
source_fn voidptr // set in the checker, while processing fn declarations
usages int
}
@ -96,9 +100,24 @@ pub:
name string
is_mut bool
is_auto_rec bool
typ Type
type_pos token.Position
is_hidden bool // interface first arg
pub mut:
typ Type
}
pub fn (f Fn) new_method_with_receiver_type(new_type Type) Fn {
mut new_method := f
new_method.params = f.params.clone()
new_method.params[0].typ = new_type
return new_method
}
pub fn (f FnDecl) new_method_with_receiver_type(new_type Type) FnDecl {
mut new_method := f
new_method.params = f.params.clone()
new_method.params[0].typ = new_type
return new_method
}
fn (p &Param) equals(o &Param) bool {
@ -213,6 +232,10 @@ pub fn (mut t Table) register_fn(new_fn Fn) {
t.fns[new_fn.name] = new_fn
}
pub fn (mut t Table) register_interface(idecl InterfaceDecl) {
t.interfaces[idecl.typ] = idecl
}
pub fn (mut t TypeSymbol) register_method(new_fn Fn) int {
// returns a method index, stored in the ast.FnDecl
// for faster lookup in the checker's fn_decl method

View File

@ -734,6 +734,7 @@ pub mut:
types []Type
fields []StructField
methods []Fn
ifaces []Type
}
pub struct Enum {

View File

@ -360,25 +360,152 @@ pub fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) {
}
}
pub fn (mut c Checker) interface_decl(decl ast.InterfaceDecl) {
c.check_valid_pascal_case(decl.name, 'interface name', decl.pos)
for method in decl.methods {
if decl.language == .v {
c.check_valid_snake_case(method.name, 'method name', method.pos)
}
c.ensure_type_exists(method.return_type, method.return_type_pos) or { return }
for param in method.params {
c.ensure_type_exists(param.typ, param.pos) or { return }
}
pub fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int, iface_embeds []ast.InterfaceEmbedding) []ast.InterfaceEmbedding {
// eprintln('> expand_iface_embeds: idecl.name: $idecl.name | level: $level | iface_embeds.len: $iface_embeds.len')
if level > 100 {
c.error('too many interface embedding levels: $level, for interface `$idecl.name`',
idecl.pos)
return []
}
for i, field in decl.fields {
if decl.language == .v {
c.check_valid_snake_case(field.name, 'field name', field.pos)
if iface_embeds.len == 0 {
return []
}
mut res := map[int]ast.InterfaceEmbedding{}
mut ares := []ast.InterfaceEmbedding{}
for ie in iface_embeds {
if iface_decl := c.table.interfaces[ie.typ] {
mut list := iface_decl.ifaces
if !iface_decl.are_ifaces_expanded {
list = c.expand_iface_embeds(idecl, level + 1, iface_decl.ifaces)
c.table.interfaces[ie.typ].ifaces = list
c.table.interfaces[ie.typ].are_ifaces_expanded = true
}
for partial in list {
res[partial.typ] = partial
}
}
c.ensure_type_exists(field.typ, field.pos) or { return }
for j in 0 .. i {
if field.name == decl.fields[j].name {
c.error('field name `$field.name` duplicate', field.pos)
res[ie.typ] = ie
}
for _, v in res {
ares << v
}
return ares
}
pub fn (mut c Checker) interface_decl(mut decl ast.InterfaceDecl) {
c.check_valid_pascal_case(decl.name, 'interface name', decl.pos)
mut decl_sym := c.table.get_type_symbol(decl.typ)
if mut decl_sym.info is ast.Interface {
if decl.ifaces.len > 0 {
all_ifaces := c.expand_iface_embeds(decl, 0, decl.ifaces)
// eprintln('> decl.name: $decl.name | decl.ifaces.len: $decl.ifaces.len | all_ifaces: $all_ifaces.len')
decl.ifaces = all_ifaces
mut emnames := map[string]int{}
mut emnames_ds := map[string]bool{}
mut emnames_ds_info := map[string]bool{}
mut efnames := map[string]int{}
mut efnames_ds_info := map[string]bool{}
for i, m in decl.methods {
emnames[m.name] = i
emnames_ds[m.name] = true
emnames_ds_info[m.name] = true
}
for i, f in decl.fields {
efnames[f.name] = i
efnames_ds_info[f.name] = true
}
//
for iface in all_ifaces {
isym := c.table.get_type_symbol(iface.typ)
if isym.kind != .interface_ {
c.error('interface `$decl.name` tries to embed `$isym.name`, but `$isym.name` is not an interface, but `$isym.kind`',
iface.pos)
continue
}
for f in isym.info.fields {
if !efnames_ds_info[f.name] {
efnames_ds_info[f.name] = true
decl_sym.info.fields << f
}
}
for m in isym.info.methods {
if !emnames_ds_info[m.name] {
emnames_ds_info[m.name] = true
decl_sym.info.methods << m.new_method_with_receiver_type(decl.typ)
}
}
for m in isym.methods {
if !emnames_ds[m.name] {
emnames_ds[m.name] = true
decl_sym.methods << m.new_method_with_receiver_type(decl.typ)
}
}
if iface_decl := c.table.interfaces[iface.typ] {
for f in iface_decl.fields {
if f.name in efnames {
// already existing method name, check for conflicts
ifield := decl.fields[efnames[f.name]]
if field := c.table.find_field_with_embeds(isym, f.name) {
if ifield.typ != field.typ {
exp := c.table.type_to_str(ifield.typ)
got := c.table.type_to_str(field.typ)
c.error('embedded interface `$iface_decl.name` conflicts existing field: `$ifield.name`, expecting type: `$exp`, got type: `$got`',
ifield.pos)
}
}
} else {
efnames[f.name] = decl.fields.len
decl.fields << f
}
}
for m in iface_decl.methods {
if m.name in emnames {
// already existing field name, check for conflicts
imethod := decl.methods[emnames[m.name]]
if em_fn := decl_sym.find_method(imethod.name) {
if m_fn := isym.find_method(m.name) {
msg := c.table.is_same_method(m_fn, em_fn)
if msg.len > 0 {
em_sig := c.table.fn_signature(em_fn, skip_receiver: true)
m_sig := c.table.fn_signature(m_fn, skip_receiver: true)
c.error('embedded interface `$iface_decl.name` causes conflict: $msg, for interface method `$em_sig` vs `$m_sig`',
imethod.pos)
}
}
}
} else {
emnames[m.name] = decl.methods.len
mut new_method := m.new_method_with_receiver_type(decl.typ)
new_method.pos = iface.pos
decl.methods << new_method
}
}
}
}
}
for i, method in decl.methods {
if decl.language == .v {
c.check_valid_snake_case(method.name, 'method name', method.pos)
}
c.ensure_type_exists(method.return_type, method.return_type_pos) or { return }
for param in method.params {
c.ensure_type_exists(param.typ, param.pos) or { return }
}
for j in 0 .. i {
if method.name == decl.methods[j].name {
c.error('duplicate method name `$method.name`', method.pos)
}
}
}
for i, field in decl.fields {
if decl.language == .v {
c.check_valid_snake_case(field.name, 'field name', field.pos)
}
c.ensure_type_exists(field.typ, field.pos) or { return }
for j in 0 .. i {
if field.name == decl.fields[j].name {
c.error('field name `$field.name` duplicate', field.pos)
}
}
}
}
@ -3660,7 +3787,7 @@ fn (mut c Checker) stmt(node ast.Stmt) {
c.import_stmt(node)
}
ast.InterfaceDecl {
c.interface_decl(node)
c.interface_decl(mut node)
}
ast.Module {
c.mod = node.name
@ -6319,6 +6446,11 @@ pub fn (mut c Checker) warn(s string, pos token.Position) {
}
pub fn (mut c Checker) error(message string, pos token.Position) {
$if checker_exit_on_first_error ? {
eprintln('\n\n>> checker error: $message, pos: $pos')
print_backtrace()
exit(1)
}
if c.pref.translated && message.starts_with('mismatched types') {
// TODO move this
return

View File

@ -0,0 +1,7 @@
vlib/v/checker/tests/interface_too_many_embedding_levels.vv:9:1: error: too many interface embedding levels: 101, for interface `I103`
7 | }
8 |
9 | interface I103 {
| ~~~~~~~~~~~~~~~~
10 | I102
11 | }

View File

@ -0,0 +1,431 @@
interface I1 {
I0
}
interface I2 {
I1
}
interface I103 {
I102
}
interface I102 {
I101
}
interface I101 {
I100
}
interface I3 {
I2
}
interface I4 {
I3
}
interface I5 {
I4
}
interface I6 {
I5
}
interface I7 {
I6
}
interface I8 {
I7
}
interface I9 {
I8
}
interface I10 {
I9
}
interface I11 {
I10
}
interface I12 {
I11
}
interface I13 {
I12
}
interface I14 {
I13
}
interface I15 {
I14
}
interface I16 {
I15
}
interface I17 {
I16
}
interface I18 {
I17
}
interface I19 {
I18
}
interface I20 {
I19
}
interface I21 {
I20
}
interface I22 {
I21
}
interface I23 {
I22
}
interface I24 {
I23
}
interface I25 {
I24
}
interface I26 {
I25
}
interface I27 {
I26
}
interface I28 {
I27
}
interface I29 {
I28
}
interface I30 {
I29
}
interface I31 {
I30
}
interface I32 {
I31
}
interface I33 {
I32
}
interface I34 {
I33
}
interface I35 {
I34
}
interface I36 {
I35
}
interface I37 {
I36
}
interface I38 {
I37
}
interface I39 {
I38
}
interface I40 {
I39
}
interface I41 {
I40
}
interface I42 {
I41
}
interface I43 {
I42
}
interface I44 {
I43
}
interface I45 {
I44
}
interface I46 {
I45
}
interface I47 {
I46
}
interface I48 {
I47
}
interface I49 {
I48
}
interface I50 {
I49
}
interface I51 {
I50
}
interface I52 {
I51
}
interface I53 {
I52
}
interface I54 {
I53
}
interface I55 {
I54
}
interface I56 {
I55
}
interface I57 {
I56
}
interface I58 {
I57
}
interface I59 {
I58
}
interface I60 {
I59
}
interface I61 {
I60
}
interface I62 {
I61
}
interface I63 {
I62
}
interface I64 {
I63
}
interface I65 {
I64
}
interface I66 {
I65
}
interface I67 {
I66
}
interface I68 {
I67
}
interface I69 {
I68
}
interface I70 {
I69
}
interface I71 {
I70
}
interface I72 {
I71
}
interface I73 {
I72
}
interface I74 {
I73
}
interface I75 {
I74
}
interface I76 {
I75
}
interface I77 {
I76
}
interface I78 {
I77
}
interface I79 {
I78
}
interface I80 {
I79
}
interface I81 {
I80
}
interface I82 {
I81
}
interface I83 {
I82
}
interface I84 {
I83
}
interface I85 {
I84
}
interface I86 {
I85
}
interface I87 {
I86
}
interface I88 {
I87
}
interface I89 {
I88
}
interface I90 {
I89
}
interface I91 {
I90
}
interface I92 {
I91
}
interface I93 {
I92
}
interface I94 {
I93
}
interface I95 {
I94
}
interface I96 {
I95
}
interface I97 {
I96
}
interface I98 {
I97
}
interface I99 {
I98
}
interface I100 {
I99
}
interface I0 {
m999() int
}
struct Abc {
x int = 123
}
fn (s Abc) m999() int {
return 999
}
fn main() {
a := Abc{}
dump(a)
i := I103(a)
dump(i)
assert i.m999() == 999
}

View File

@ -1180,6 +1180,11 @@ pub fn (mut f Fmt) interface_decl(node ast.InterfaceDecl) {
f.writeln('')
}
f.comments_after_last_field(node.pre_comments)
for iface in node.ifaces {
f.write('\t$iface.name')
f.comments(iface.comments, inline: true, has_nl: false, level: .indent)
f.writeln('')
}
for i, field in node.fields {
if i == node.mut_pos {
f.writeln('mut:')

View File

@ -6386,9 +6386,9 @@ $staticprefix inline $interface_name I_${cctype}_to_Interface_${interface_name}(
// .speak = Cat_speak
mut method_call := '${cctype}_$method.name'
if !method.params[0].typ.is_ptr() {
// inline void Cat_speak_method_wrapper(Cat c) { return Cat_speak(*c); }
methods_wrapper.write_string('static inline ${g.typ(method.return_type)}')
methods_wrapper.write_string(' ${method_call}_method_wrapper(')
// inline void Cat_speak_Interface_Animal_method_wrapper(Cat c) { return Cat_speak(*c); }
iwpostfix := '_Interface_${interface_name}_method_wrapper'
methods_wrapper.write_string('static inline ${g.typ(method.return_type)} $method_call${iwpostfix}(')
//
params_start_pos := g.out.len
mut params := method.params.clone()
@ -6406,8 +6406,8 @@ $staticprefix inline $interface_name I_${cctype}_to_Interface_${interface_name}(
}
methods_wrapper.writeln('${method_call}(*${fargs.join(', ')});')
methods_wrapper.writeln('}')
// .speak = Cat_speak_method_wrapper
method_call += '_method_wrapper'
// .speak = Cat_speak_Interface_Animal_method_wrapper
method_call += iwpostfix
}
if g.pref.build_mode != .build_module {
methods_struct.writeln('\t\t._method_${c_name(method.name)} = (void*) $method_call,')

View File

@ -483,7 +483,24 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
mut methods := []ast.FnDecl{cap: 20}
mut is_mut := false
mut mut_pos := -1
mut ifaces := []ast.InterfaceEmbedding{}
for p.tok.kind != .rcbr && p.tok.kind != .eof {
if p.tok.kind == .name && p.tok.lit.len > 0 && p.tok.lit[0].is_capital() {
iface_pos := p.tok.position()
iface_name := p.tok.lit
iface_type := p.parse_type()
comments := p.eat_comments({})
ifaces << ast.InterfaceEmbedding{
name: iface_name
typ: iface_type
pos: iface_pos
comments: comments
}
if p.tok.kind == .rcbr {
break
}
continue
}
if p.tok.kind == .key_mut {
if is_mut {
p.error_with_pos('redefinition of `mut` section', p.tok.position())
@ -498,6 +515,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
method_start_pos := p.tok.position()
line_nr := p.tok.line_nr
name := p.check_name()
if name == 'type_name' {
p.error_with_pos('cannot override built-in method `type_name`', method_start_pos)
return ast.InterfaceDecl{}
@ -545,6 +563,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
tmethod := ast.Fn{
name: name
params: args
pos: method.pos
return_type: method.return_type
is_variadic: is_variadic
is_pub: true
@ -581,19 +600,24 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
}
}
}
info.ifaces = ifaces.map(it.typ)
ts.info = info
p.top_level_statement_end()
p.check(.rcbr)
pos = pos.extend_with_last_line(p.prev_tok.position(), p.prev_tok.line_nr)
return ast.InterfaceDecl{
res := ast.InterfaceDecl{
name: interface_name
language: language
typ: typ
fields: fields
methods: methods
ifaces: ifaces
is_pub: is_pub
pos: pos
pre_comments: pre_comments
mut_pos: mut_pos
name_pos: name_pos
}
p.table.register_interface(res)
return res
}

View File

@ -24,6 +24,9 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s
if prefs.backend != .js && !prefs.should_compile_asm(file) {
continue
}
if file.starts_with('.#') {
continue
}
if file.contains('_d_') {
if prefs.compile_defines_all.len == 0 {
continue

View File

@ -0,0 +1,415 @@
interface I99 {
I98
}
interface I1 {
I0
}
interface I2 {
I1
}
interface I3 {
I2
}
interface I4 {
I3
}
interface I5 {
I4
}
interface I6 {
I5
}
interface I7 {
I6
}
interface I8 {
I7
}
interface I9 {
I8
}
interface I10 {
I9
}
interface I11 {
I10
}
interface I12 {
I11
}
interface I13 {
I12
}
interface I14 {
I13
}
interface I15 {
I14
}
interface I16 {
I15
}
interface I17 {
I16
}
interface I18 {
I17
}
interface I19 {
I18
}
interface I20 {
I19
}
interface I21 {
I20
}
interface I22 {
I21
}
interface I23 {
I22
}
interface I24 {
I23
}
interface I25 {
I24
}
interface I26 {
I25
}
interface I27 {
I26
}
interface I28 {
I27
}
interface I29 {
I28
}
interface I30 {
I29
}
interface I31 {
I30
}
interface I32 {
I31
}
interface I33 {
I32
}
interface I34 {
I33
}
interface I35 {
I34
}
interface I36 {
I35
}
interface I37 {
I36
}
interface I38 {
I37
}
interface I39 {
I38
}
interface I40 {
I39
}
interface I41 {
I40
}
interface I42 {
I41
}
interface I43 {
I42
}
interface I44 {
I43
}
interface I45 {
I44
}
interface I46 {
I45
}
interface I47 {
I46
}
interface I48 {
I47
}
interface I49 {
I48
}
interface I50 {
I49
}
interface I51 {
I50
}
interface I52 {
I51
}
interface I53 {
I52
}
interface I54 {
I53
}
interface I55 {
I54
}
interface I56 {
I55
}
interface I57 {
I56
}
interface I58 {
I57
}
interface I59 {
I58
}
interface I60 {
I59
}
interface I61 {
I60
}
interface I62 {
I61
}
interface I63 {
I62
}
interface I64 {
I63
}
interface I65 {
I64
}
interface I66 {
I65
}
interface I67 {
I66
}
interface I68 {
I67
}
interface I69 {
I68
}
interface I70 {
I69
}
interface I71 {
I70
}
interface I72 {
I71
}
interface I73 {
I72
}
interface I74 {
I73
}
interface I75 {
I74
}
interface I76 {
I75
}
interface I77 {
I76
}
interface I78 {
I77
}
interface I79 {
I78
}
interface I80 {
I79
}
interface I81 {
I80
}
interface I82 {
I81
}
interface I83 {
I82
}
interface I84 {
I83
}
interface I85 {
I84
}
interface I86 {
I85
}
interface I87 {
I86
}
interface I88 {
I87
}
interface I89 {
I88
}
interface I90 {
I89
}
interface I91 {
I90
}
interface I92 {
I91
}
interface I93 {
I92
}
interface I94 {
I93
}
interface I95 {
I94
}
interface I96 {
I95
}
interface I97 {
I96
}
interface I98 {
I97
}
interface I0 {
m999() int
}
struct Abc {
x int = 123
}
fn (s Abc) m999() int {
return 999
}
fn test_deep_nested_interface_embeddings() {
a := Abc{}
dump(a)
i := I99(a)
dump(i)
assert i.m999() == 999
}

View File

@ -0,0 +1,78 @@
// This test orders the interface definitions intentionally
// in such a way that interface `Re` is first, and `Fe` is
// last. The goal is testing that the embedding expansion
// works independently from the source order, and that both
// can be checked/compiled/used at the same time.
interface Re {
I1
I2
m_ie() int
}
interface I1 {
I0
m1() int
}
interface I2 {
I0
m2() int
}
interface I0 {
m0() int
}
interface Fe {
I1
I2
m_ie() int
}
struct StructIE {
x int = 456
}
fn (s StructIE) m0() int {
println(@METHOD)
return 0
}
fn (s StructIE) m1() int {
println(@METHOD)
return 1
}
fn (s StructIE) m2() int {
println(@METHOD)
return 2
}
fn (s StructIE) m_ie() int {
println(@METHOD)
return 3
}
fn test_ie_recursive_forward() {
i := Fe(StructIE{})
eprintln(i)
assert 0 == i.m0()
assert 1 == i.m1()
assert 2 == i.m2()
assert 3 == i.m_ie()
if i is StructIE {
assert i.x == 456
}
}
fn test_ie_recursive_backward() {
i := Re(StructIE{})
eprintln(i)
assert 0 == i.m0()
assert 1 == i.m1()
assert 2 == i.m2()
assert 3 == i.m_ie()
if i is StructIE {
assert i.x == 456
}
}

View File

@ -0,0 +1,56 @@
interface WalkerTalker {
// Abc
Walker // adsas
// zxczxc
Talker // xyzdef
// asdasdas
nspeeches int
}
interface Talker {
nspeeches int
talk(msg string)
}
interface Walker {
nsteps int
walk(newx int, newy int)
}
struct Abc {
mut:
x int
y int
phrases []string
nsteps int = 1000
nspeeches int = 1000
}
fn (mut s Abc) talk(msg string) {
s.phrases << msg
s.nspeeches++
}
fn (mut s Abc) walk(x int, y int) {
s.x = x
s.y = y
s.nsteps++
}
fn test_walker_talker() {
mut wt := WalkerTalker(Abc{
x: 1
y: 1
phrases: ['hi']
})
wt.talk('my name is Wally')
wt.walk(100, 100)
if mut wt is Abc {
dump(wt)
assert wt.x == 100
assert wt.y == 100
assert wt.phrases.last().ends_with('Wally')
assert wt.nspeeches == 1001
assert wt.nsteps == 1001
}
}