all: support runtime interface conversions (#11212)

pull/11224/head
spaceface 2021-08-17 20:00:27 +02:00 committed by GitHub
parent 7c9a1defa4
commit 7d9969ac17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 129 additions and 6 deletions

View File

@ -797,6 +797,8 @@ pub mut:
fields []StructField fields []StructField
methods []Fn methods []Fn
ifaces []Type ifaces []Type
// `I1 is I2` conversions
conversions map[int][]Type
// generic interface support // generic interface support
is_generic bool is_generic bool
generic_types []Type generic_types []Type

View File

@ -5260,6 +5260,8 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type {
if !c.table.sumtype_has_variant(node.expr_type, node.typ) { if !c.table.sumtype_has_variant(node.expr_type, node.typ) {
c.error('cannot cast `$expr_type_sym.name` to `$type_sym.name`', node.pos) c.error('cannot cast `$expr_type_sym.name` to `$type_sym.name`', node.pos)
} }
} else if expr_type_sym.kind == .interface_ && type_sym.kind == .interface_ {
c.ensure_type_exists(node.typ, node.pos) or {}
} else if node.expr_type != node.typ { } else if node.expr_type != node.typ {
mut s := 'cannot cast non-sum type `$expr_type_sym.name` using `as`' mut s := 'cannot cast non-sum type `$expr_type_sym.name` using `as`'
if type_sym.kind == .sum_type { if type_sym.kind == .sum_type {
@ -6569,9 +6571,14 @@ fn (mut c Checker) smartcast_if_conds(node ast.Expr, mut scope ast.Scope) {
right_type = c.unwrap_generic(right_type) right_type = c.unwrap_generic(right_type)
if right_type != ast.Type(0) { if right_type != ast.Type(0) {
left_sym := c.table.get_type_symbol(node.left_type) left_sym := c.table.get_type_symbol(node.left_type)
right_sym := c.table.get_type_symbol(right_type)
expr_type := c.unwrap_generic(c.expr(node.left)) expr_type := c.unwrap_generic(c.expr(node.left))
if left_sym.kind == .interface_ { if left_sym.kind == .interface_ {
c.type_implements(right_type, expr_type, node.pos) if right_sym.kind != .interface_ {
c.type_implements(right_type, expr_type, node.pos)
} else {
return
}
} else if !c.check_types(right_type, expr_type) { } else if !c.check_types(right_type, expr_type) {
expect_str := c.table.type_to_str(right_type) expect_str := c.table.type_to_str(right_type)
expr_str := c.table.type_to_str(expr_type) expr_str := c.table.type_to_str(expr_type)

View File

@ -580,6 +580,7 @@ pub fn (mut g Gen) write_typeof_functions() {
if inter_info.is_generic { if inter_info.is_generic {
continue continue
} }
g.definitions.writeln('static char * v_typeof_interface_${typ.cname}(int sidx);')
g.writeln('static char * v_typeof_interface_${typ.cname}(int sidx) { /* $typ.name */ ') g.writeln('static char * v_typeof_interface_${typ.cname}(int sidx) { /* $typ.name */ ')
for t in inter_info.types { for t in inter_info.types {
subtype := g.table.get_type_symbol(t) subtype := g.table.get_type_symbol(t)
@ -6436,8 +6437,8 @@ fn (mut g Gen) as_cast(node ast.AsCast) {
// g.insert_before(' // g.insert_before('
styp := g.typ(node.typ) styp := g.typ(node.typ)
sym := g.table.get_type_symbol(node.typ) sym := g.table.get_type_symbol(node.typ)
expr_type_sym := g.table.get_type_symbol(node.expr_type) mut expr_type_sym := g.table.get_type_symbol(node.expr_type)
if expr_type_sym.info is ast.SumType { if mut expr_type_sym.info is ast.SumType {
dot := if node.expr_type.is_ptr() { '->' } else { '.' } dot := if node.expr_type.is_ptr() { '->' } else { '.' }
g.write('/* as */ *($styp*)__as_cast(') g.write('/* as */ *($styp*)__as_cast(')
g.write('(') g.write('(')
@ -6463,6 +6464,18 @@ fn (mut g Gen) as_cast(node ast.AsCast) {
variant_sym := g.table.get_type_symbol(variant) variant_sym := g.table.get_type_symbol(variant)
g.as_cast_type_names[idx] = variant_sym.name g.as_cast_type_names[idx] = variant_sym.name
} }
} else if expr_type_sym.kind == .interface_ && sym.kind == .interface_ {
g.write('I_${expr_type_sym.cname}_as_I_${sym.cname}(')
g.expr(node.expr)
g.write(')')
mut info := expr_type_sym.info as ast.Interface
if node.typ !in info.conversions {
left_variants := g.table.iface_types[expr_type_sym.name]
right_variants := g.table.iface_types[sym.name]
info.conversions[node.typ] = left_variants.filter(it in right_variants)
}
expr_type_sym.info = info
} else { } else {
g.expr(node.expr) g.expr(node.expr)
} }
@ -6486,6 +6499,7 @@ fn (g Gen) as_cast_name_table() string {
// Generates interface table and interface indexes // Generates interface table and interface indexes
fn (mut g Gen) interface_table() string { fn (mut g Gen) interface_table() string {
mut sb := strings.new_builder(100) mut sb := strings.new_builder(100)
mut conversion_functions := strings.new_builder(100)
for ityp in g.table.type_symbols { for ityp in g.table.type_symbols {
if ityp.kind != .interface_ { if ityp.kind != .interface_ {
continue continue
@ -6669,11 +6683,42 @@ static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype
} }
iin_idx := already_generated_mwrappers[interface_index_name] - iinidx_minimum_base iin_idx := already_generated_mwrappers[interface_index_name] - iinidx_minimum_base
if g.pref.build_mode != .build_module { if g.pref.build_mode != .build_module {
sb.writeln('int $interface_index_name = $iin_idx;') sb.writeln('const int $interface_index_name = $iin_idx;')
} else { } else {
sb.writeln('int $interface_index_name;') sb.writeln('extern const int $interface_index_name;')
} }
} }
for vtyp, variants in inter_info.conversions {
vsym := g.table.get_type_symbol(vtyp)
conversion_functions.write_string('static inline bool I_${interface_name}_is_I_${vsym.cname}($interface_name x) {\n\treturn ')
for i, variant in variants {
variant_sym := g.table.get_type_symbol(variant)
if i > 0 {
conversion_functions.write_string(' || ')
}
conversion_functions.write_string('(x._typ == _${interface_name}_${variant_sym.cname}_index)')
}
conversion_functions.writeln(';\n}')
conversion_functions.writeln('static inline $vsym.cname I_${interface_name}_as_I_${vsym.cname}($interface_name x) {')
for variant in variants {
variant_sym := g.table.get_type_symbol(variant)
conversion_functions.writeln('\tif (x._typ == _${interface_name}_${variant_sym.cname}_index) return I_${variant_sym.cname}_to_Interface_${vsym.cname}(x._$variant_sym.cname);')
}
pmessage := 'string__plus(string__plus(tos3("`as_cast`: cannot convert "), tos3(v_typeof_interface_${interface_name}(x._typ))), tos3(" to ${util.strip_main_name(vsym.name)}"))'
if g.pref.is_debug {
// TODO: actually return a valid position here
conversion_functions.write_string('\tpanic_debug(1, tos3("builtin.v"), tos3("builtin"), tos3("__as_cast"), ')
conversion_functions.write_string(pmessage)
conversion_functions.writeln(');')
} else {
conversion_functions.write_string('\t_v_panic(')
conversion_functions.write_string(pmessage)
conversion_functions.writeln(');')
}
conversion_functions.writeln('\treturn ($vsym.cname){0};')
conversion_functions.writeln('}')
}
sb.writeln('// ^^^ number of types for interface $interface_name: ${current_iinidx - iinidx_minimum_base}') sb.writeln('// ^^^ number of types for interface $interface_name: ${current_iinidx - iinidx_minimum_base}')
if iname_table_length == 0 { if iname_table_length == 0 {
methods_struct.writeln('') methods_struct.writeln('')
@ -6691,6 +6736,7 @@ static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype
} }
sb.writeln(cast_functions.str()) sb.writeln(cast_functions.str())
} }
sb.writeln(conversion_functions.str())
return sb.str() return sb.str()
} }

View File

@ -396,6 +396,13 @@ fn (mut g Gen) infix_expr_in_optimization(left ast.Expr, right ast.ArrayInit) {
// infix_expr_is_op generates code for `is` and `!is` // infix_expr_is_op generates code for `is` and `!is`
fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) {
sym := g.table.get_type_symbol(node.left_type)
right_sym := g.table.get_type_symbol(node.right_type)
if sym.kind == .interface_ && right_sym.kind == .interface_ {
g.gen_interface_is_op(node)
return
}
cmp_op := if node.op == .key_is { '==' } else { '!=' } cmp_op := if node.op == .key_is { '==' } else { '!=' }
g.write('(') g.write('(')
g.expr(node.left) g.expr(node.left)
@ -405,7 +412,6 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) {
} else { } else {
g.write('.') g.write('.')
} }
sym := g.table.get_type_symbol(node.left_type)
if sym.kind == .interface_ { if sym.kind == .interface_ {
g.write('_typ $cmp_op ') g.write('_typ $cmp_op ')
// `_Animal_Dog_index` // `_Animal_Dog_index`
@ -423,6 +429,29 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) {
g.expr(node.right) g.expr(node.right)
} }
fn (mut g Gen) gen_interface_is_op(node ast.InfixExpr) {
mut left_sym := g.table.get_type_symbol(node.left_type)
right_sym := g.table.get_type_symbol(node.right_type)
mut info := left_sym.info as ast.Interface
common_variants := info.conversions[node.right_type] or {
left_variants := g.table.iface_types[left_sym.name]
right_variants := g.table.iface_types[right_sym.name]
c := left_variants.filter(it in right_variants)
info.conversions[node.right_type] = c
c
}
left_sym.info = info
if common_variants.len == 0 {
g.write('false')
return
}
g.write('I_${left_sym.cname}_is_I_${right_sym.cname}(')
g.expr(node.left)
g.write(')')
}
// infix_expr_arithmetic_op generates code for `+`, `-`, `*`, `/`, and `%` // infix_expr_arithmetic_op generates code for `+`, `-`, `*`, `/`, and `%`
// It handles operator overloading when necessary // It handles operator overloading when necessary
fn (mut g Gen) infix_expr_arithmetic_op(node ast.InfixExpr) { fn (mut g Gen) infix_expr_arithmetic_op(node ast.InfixExpr) {

View File

@ -0,0 +1,39 @@
interface Widget {
}
interface ResizableWidget {
Widget
resize(x int, y int) int
}
fn draw(w Widget) {
// w.resize(10, 20) // <- this won't work, since all Widgets may not implement resize()
// however, we can check if the underlying type of w implements a different interface:
if w is ResizableWidget {
assert w is WidgetB
rw := w as ResizableWidget
assert rw is WidgetB
// if so, we can now safely call that extra method
assert rw.resize(10, 20) == 200
} else {
assert w is WidgetA
}
}
// implements Widget, but not ResizableWidget
struct WidgetA {
}
// implements both Widget and ResizableWidget
struct WidgetB {
}
fn (w WidgetB) resize(x int, y int) int {
return x * y
}
fn test_interface_runtime_conversions() {
draw(WidgetA{})
draw(WidgetB{})
}