From 7d9969ac1779351120e2720b0f6344ab8871c8d1 Mon Sep 17 00:00:00 2001 From: spaceface Date: Tue, 17 Aug 2021 20:00:27 +0200 Subject: [PATCH] all: support runtime interface conversions (#11212) --- vlib/v/ast/types.v | 2 + vlib/v/checker/checker.v | 9 +++- vlib/v/gen/c/cgen.v | 54 +++++++++++++++++-- vlib/v/gen/c/infix_expr.v | 31 ++++++++++- .../interface_runtime_conversions_test.v | 39 ++++++++++++++ 5 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 vlib/v/tests/interface_runtime_conversions_test.v diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index b1f5bce66b..03a85dd6e8 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -797,6 +797,8 @@ pub mut: fields []StructField methods []Fn ifaces []Type + // `I1 is I2` conversions + conversions map[int][]Type // generic interface support is_generic bool generic_types []Type diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 53005527d7..881bcb694a 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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) { 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 { mut s := 'cannot cast non-sum type `$expr_type_sym.name` using `as`' 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) if right_type != ast.Type(0) { 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)) 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) { expect_str := c.table.type_to_str(right_type) expr_str := c.table.type_to_str(expr_type) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index e9a8355a81..9d1f274e36 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -580,6 +580,7 @@ pub fn (mut g Gen) write_typeof_functions() { if inter_info.is_generic { 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 */ ') for t in inter_info.types { subtype := g.table.get_type_symbol(t) @@ -6436,8 +6437,8 @@ fn (mut g Gen) as_cast(node ast.AsCast) { // g.insert_before(' styp := g.typ(node.typ) sym := g.table.get_type_symbol(node.typ) - expr_type_sym := g.table.get_type_symbol(node.expr_type) - if expr_type_sym.info is ast.SumType { + mut expr_type_sym := g.table.get_type_symbol(node.expr_type) + if mut expr_type_sym.info is ast.SumType { dot := if node.expr_type.is_ptr() { '->' } else { '.' } g.write('/* as */ *($styp*)__as_cast(') g.write('(') @@ -6463,6 +6464,18 @@ fn (mut g Gen) as_cast(node ast.AsCast) { variant_sym := g.table.get_type_symbol(variant) 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 { g.expr(node.expr) } @@ -6486,6 +6499,7 @@ fn (g Gen) as_cast_name_table() string { // Generates interface table and interface indexes fn (mut g Gen) interface_table() string { mut sb := strings.new_builder(100) + mut conversion_functions := strings.new_builder(100) for ityp in g.table.type_symbols { if ityp.kind != .interface_ { 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 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 { - 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}') if iname_table_length == 0 { 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(conversion_functions.str()) return sb.str() } diff --git a/vlib/v/gen/c/infix_expr.v b/vlib/v/gen/c/infix_expr.v index 740c993a68..f10b70f583 100644 --- a/vlib/v/gen/c/infix_expr.v +++ b/vlib/v/gen/c/infix_expr.v @@ -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` 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 { '!=' } g.write('(') g.expr(node.left) @@ -405,7 +412,6 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { } else { g.write('.') } - sym := g.table.get_type_symbol(node.left_type) if sym.kind == .interface_ { g.write('_typ $cmp_op ') // `_Animal_Dog_index` @@ -423,6 +429,29 @@ fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { 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 `%` // It handles operator overloading when necessary fn (mut g Gen) infix_expr_arithmetic_op(node ast.InfixExpr) { diff --git a/vlib/v/tests/interface_runtime_conversions_test.v b/vlib/v/tests/interface_runtime_conversions_test.v new file mode 100644 index 0000000000..d0d7294dec --- /dev/null +++ b/vlib/v/tests/interface_runtime_conversions_test.v @@ -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{}) +}