From 4feb09fa5bbd7460c50cad4f05c1dcd19f1840ae Mon Sep 17 00:00:00 2001 From: spaceface Date: Fri, 9 Apr 2021 10:00:05 +0200 Subject: [PATCH] checker, cgen: add sumtype-like smartcasting capabilites to interfaces (#9256) --- vlib/v/ast/ast.v | 27 ++++--- vlib/v/checker/checker.v | 70 +++++++------------ .../no_method_on_interface_propagation.out | 2 +- .../no_method_on_interface_propagation.vv | 2 +- vlib/v/gen/c/auto_str_methods.v | 4 +- vlib/v/gen/c/cgen.v | 65 ++++++++--------- vlib/v/gen/c/fn.v | 6 +- 7 files changed, 74 insertions(+), 102 deletions(-) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index d6f1ef2648..46b640885a 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -479,12 +479,12 @@ pub: is_arg bool // fn args should not be autofreed is_auto_deref bool pub mut: - typ Type - orig_type Type // original sumtype type; 0 if it's not a sumtype - sum_type_casts []Type // nested sum types require nested smart casting, for that a list of types is needed + typ Type + orig_type Type // original sumtype type; 0 if it's not a sumtype + smartcasts []Type // nested sum types require nested smart casting, for that a list of types is needed // TODO: move this to a real docs site later // 10 <- original type (orig_type) - // [11, 12, 13] <- cast order (sum_type_casts) + // [11, 12, 13] <- cast order (smartcasts) // 12 <- the current casted type (typ) pos token.Position is_used bool @@ -499,15 +499,15 @@ pub mut: // struct fields change type in scopes pub struct ScopeStructField { pub: - struct_type Type // type of struct - name string - pos token.Position - typ Type - sum_type_casts []Type // nested sum types require nested smart casting, for that a list of types is needed - orig_type Type // original sumtype type; 0 if it's not a sumtype + struct_type Type // type of struct + name string + pos token.Position + typ Type + smartcasts []Type // nested sum types require nested smart casting, for that a list of types is needed + orig_type Type // original sumtype type; 0 if it's not a sumtype // TODO: move this to a real docs site later // 10 <- original type (orig_type) - // [11, 12, 13] <- cast order (sum_type_casts) + // [11, 12, 13] <- cast order (smartcasts) // 12 <- the current casted type (typ) } @@ -691,9 +691,8 @@ pub: body_pos token.Position comments []Comment pub mut: - stmts []Stmt - smartcast bool // true when cond is `x is SumType`, set in checker.if_expr // no longer needed with union sum types TODO: remove - scope &Scope + stmts []Stmt + scope &Scope } pub struct UnsafeExpr { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 7a2b7e39d4..27fe697b00 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2350,9 +2350,7 @@ fn (mut c Checker) type_implements(typ ast.Type, inter_typ ast.Type, pos token.P c.error("`$styp` doesn't implement field `$ifield.name` of interface `$inter_sym.name`", pos) } - if utyp !in inter_sym.info.types && typ_sym.kind != .interface_ { - inter_sym.info.types << utyp - } + inter_sym.info.types << utyp } return true } @@ -2593,10 +2591,10 @@ pub fn (mut c Checker) selector_expr(mut selector_expr ast.SelectorExpr) ast.Typ c.error('field `${sym.name}.$field_name` is not public', selector_expr.pos) } field_sym := c.table.get_type_symbol(field.typ) - if field_sym.kind == .sum_type { + if field_sym.kind in [.sum_type, .interface_] { if !prevent_sum_type_unwrapping_once { if scope_field := selector_expr.scope.find_struct_field(utyp, field_name) { - return scope_field.sum_type_casts.last() + return scope_field.smartcasts.last() } } } @@ -3729,9 +3727,8 @@ fn (mut c Checker) for_stmt(mut node ast.ForStmt) { left_type := c.expr(infix.left) left_sym := c.table.get_type_symbol(left_type) if is_variable { - if left_sym.kind == .sum_type { - c.smartcast_sumtype(infix.left, infix.left_type, right_expr.typ, mut - node.scope) + if left_sym.kind in [.sum_type, .interface_] { + c.smartcast(infix.left, infix.left_type, right_expr.typ, mut node.scope) } } } @@ -4658,10 +4655,10 @@ pub fn (mut c Checker) ident(mut ident ast.Ident) ast.Type { c.error('undefined variable `$ident.name` (used before declaration)', ident.pos) } - is_sum_type_cast := obj.sum_type_casts.len != 0 + is_sum_type_cast := obj.smartcasts.len != 0 && !c.prevent_sum_type_unwrapping_once c.prevent_sum_type_unwrapping_once = false - mut typ := if is_sum_type_cast { obj.sum_type_casts.last() } else { obj.typ } + mut typ := if is_sum_type_cast { obj.smartcasts.last() } else { obj.typ } if typ == 0 { if mut obj.expr is ast.Ident { if obj.expr.kind == .unresolved { @@ -4988,9 +4985,9 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym } branch_exprs[key] = val + 1 } - // when match is sum type matching, then register smart cast for every branch + // when match is type matching, then register smart cast for every branch if expr_types.len > 0 { - if cond_type_sym.kind == .sum_type { + if cond_type_sym.kind in [.sum_type, .interface_] { mut expr_type := ast.Type(0) if expr_types.len > 1 { mut agg_name := strings.new_builder(20) @@ -5025,7 +5022,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym } else { expr_type = expr_types[0].typ } - c.smartcast_sumtype(node.cond, node.cond_type, expr_type, mut branch.scope) + c.smartcast(node.cond, node.cond_type, expr_type, mut branch.scope) } } } @@ -5106,11 +5103,13 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym } // smartcast takes the expression with the current type which should be smartcasted to the target type in the given scope -fn (c &Checker) smartcast_sumtype(expr ast.Expr, cur_type ast.Type, to_type ast.Type, mut scope ast.Scope) { - match mut expr { +fn (c Checker) smartcast(expr ast.Expr, cur_type ast.Type, to_type_ ast.Type, mut scope ast.Scope) { + sym := c.table.get_type_symbol(cur_type) + to_type := if sym.kind == .interface_ { to_type_.to_ptr() } else { to_type_ } + match expr { ast.SelectorExpr { mut is_mut := false - mut sum_type_casts := []ast.Type{} + mut smartcasts := []ast.Type{} expr_sym := c.table.get_type_symbol(expr.expr_type) mut orig_type := 0 if field := c.table.find_field(expr_sym, expr.field_name) { @@ -5125,16 +5124,16 @@ fn (c &Checker) smartcast_sumtype(expr ast.Expr, cur_type ast.Type, to_type ast. } } if field := scope.find_struct_field(expr.expr_type, expr.field_name) { - sum_type_casts << field.sum_type_casts + smartcasts << field.smartcasts } // smartcast either if the value is immutable or if the mut argument is explicitly given if !is_mut || expr.is_mut { - sum_type_casts << to_type + smartcasts << to_type scope.register_struct_field(ast.ScopeStructField{ struct_type: expr.expr_type name: expr.field_name typ: cur_type - sum_type_casts: sum_type_casts + smartcasts: smartcasts pos: expr.pos orig_type: orig_type }) @@ -5142,12 +5141,12 @@ fn (c &Checker) smartcast_sumtype(expr ast.Expr, cur_type ast.Type, to_type ast. } ast.Ident { mut is_mut := false - mut sum_type_casts := []ast.Type{} + mut smartcasts := []ast.Type{} mut is_already_casted := false mut orig_type := 0 if mut expr.obj is ast.Var { is_mut = expr.obj.is_mut - sum_type_casts << expr.obj.sum_type_casts + smartcasts << expr.obj.smartcasts is_already_casted = expr.obj.pos.pos == expr.pos.pos if orig_type == 0 { orig_type = expr.obj.typ @@ -5155,14 +5154,14 @@ fn (c &Checker) smartcast_sumtype(expr ast.Expr, cur_type ast.Type, to_type ast. } // smartcast either if the value is immutable or if the mut argument is explicitly given if (!is_mut || expr.is_mut) && !is_already_casted { - sum_type_casts << to_type + smartcasts << to_type scope.register(ast.Var{ name: expr.name typ: cur_type pos: expr.pos is_used: true is_mut: expr.is_mut - sum_type_casts: sum_type_casts + smartcasts: smartcasts orig_type: orig_type }) } @@ -5378,29 +5377,8 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { } if is_variable { if left_sym.kind in [.interface_, .sum_type] { - if branch.cond.left is ast.Ident && left_sym.kind == .interface_ { - // TODO: rewrite interface smartcast - left := branch.cond.left as ast.Ident - mut is_mut := false - mut sum_type_casts := []ast.Type{} - if v := branch.scope.find_var(left.name) { - is_mut = v.is_mut - sum_type_casts << v.sum_type_casts - } - branch.scope.register(ast.Var{ - name: left.name - typ: right_expr.typ.to_ptr() - sum_type_casts: sum_type_casts - pos: left.pos - is_used: true - is_mut: is_mut - }) - // TODO: needs to be removed - node.branches[i].smartcast = true - } else { - c.smartcast_sumtype(branch.cond.left, branch.cond.left_type, - right_expr.typ, mut branch.scope) - } + c.smartcast(branch.cond.left, branch.cond.left_type, right_expr.typ, mut + branch.scope) } } } diff --git a/vlib/v/checker/tests/no_method_on_interface_propagation.out b/vlib/v/checker/tests/no_method_on_interface_propagation.out index 232e9b2de4..03e11baa9b 100644 --- a/vlib/v/checker/tests/no_method_on_interface_propagation.out +++ b/vlib/v/checker/tests/no_method_on_interface_propagation.out @@ -1,5 +1,5 @@ vlib/v/checker/tests/no_method_on_interface_propagation.vv:18:5: error: unknown method: `Cat.foo` - 16 | mut a := new_animal('persian') + 16 | a := new_animal('persian') 17 | if a is Cat { 18 | a.foo() | ~~~~~ diff --git a/vlib/v/checker/tests/no_method_on_interface_propagation.vv b/vlib/v/checker/tests/no_method_on_interface_propagation.vv index 3456b271b9..a9673df3a2 100644 --- a/vlib/v/checker/tests/no_method_on_interface_propagation.vv +++ b/vlib/v/checker/tests/no_method_on_interface_propagation.vv @@ -13,7 +13,7 @@ fn new_animal(breed string) Animal { } fn test_methods_on_interfaces_dont_exist_on_implementers() { - mut a := new_animal('persian') + a := new_animal('persian') if a is Cat { a.foo() } diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index da6e31a999..7a9a647da2 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -644,9 +644,9 @@ fn (mut g Gen) gen_str_for_interface(info ast.Interface, styp string, str_fn_nam deref := if sym_has_str_method && str_method_expects_ptr { ' ' } else { '*' } value_fmt := if typ == ast.string_type { "'%.*s\\000'" } else { '%.*s\\000' } - g.auto_str_funcs.write_string('\tif (x._interface_idx == _${styp}_${subtype.cname}_index)') + g.auto_str_funcs.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') g.auto_str_funcs.write_string(' return _STR("${clean_interface_v_type_name}($value_fmt)", 2, ') - g.auto_str_funcs.write_string('${func_name}(${deref}($subtype.cname*)x._object') + g.auto_str_funcs.write_string('${func_name}(${deref}($subtype.cname*)x._$subtype.cname') if should_use_indent_func(subtype.kind) && !sym_has_str_method { g.auto_str_funcs.write_string(', indent_count') } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index e618f597fc..d2f0d0b666 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -824,14 +824,20 @@ static inline void __${typ.cname}_pushval($typ.cname ch, $el_stype val) { pub fn (mut g Gen) write_interface_typesymbol_declaration(sym ast.TypeSymbol) { info := sym.info as ast.Interface g.type_definitions.writeln('typedef struct {') - g.type_definitions.writeln('\tvoid* _object;') - g.type_definitions.writeln('\tint _interface_idx;') + g.type_definitions.writeln('\tunion {') + g.type_definitions.writeln('\t\tvoid* _object;') + for variant in info.types { + vcname := g.table.get_type_symbol(variant).cname + g.type_definitions.writeln('\t\t$vcname* _$vcname;') + } + g.type_definitions.writeln('\t};') + g.type_definitions.writeln('\tint _typ;') 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(sym.name)};\n') + g.type_definitions.writeln('} ${c_name(sym.name)};') } pub fn (mut g Gen) write_fn_typesymbol_declaration(sym ast.TypeSymbol) { @@ -1703,7 +1709,7 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_typ scope := g.file.scope.innermost(expr.position().pos) if expr is ast.Ident { if v := scope.find_var(expr.name) { - if v.sum_type_casts.len > 0 { + if v.smartcasts.len > 0 { is_already_sum_type = true } } @@ -3224,7 +3230,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { mut sum_type_dot := '.' 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 field_sym.kind in [.sum_type, .interface_] { if !prevent_sum_type_unwrapping_once { // check first if field is sum type because scope searching is expensive scope := g.file.scope.innermost(node.pos.pos) @@ -3232,9 +3238,11 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { if field.orig_type.is_ptr() { sum_type_dot = '->' } - // union sum type deref - for i, typ in field.sum_type_casts { - g.write('(*') + for i, typ in field.smartcasts { + g.write('(') + if field_sym.kind == .sum_type { + g.write('*') + } cast_sym := g.table.get_type_symbol(typ) if i != 0 { dot := if field.typ.is_ptr() { '->' } else { '.' } @@ -3966,9 +3974,9 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str if branch.exprs[sumtype_index] is ast.TypeNode { typ := branch.exprs[sumtype_index] as ast.TypeNode branch_sym := g.table.get_type_symbol(typ.typ) - g.write('${dot_or_ptr}_interface_idx == _${sym.cname}_${branch_sym.cname}_index') + g.write('${dot_or_ptr}_typ == _${sym.cname}_${branch_sym.cname}_index') } else if branch.exprs[sumtype_index] is ast.None && sym.name == 'IError' { - g.write('${dot_or_ptr}_interface_idx == _IError_None___index') + g.write('${dot_or_ptr}_typ == _IError_None___index') } } if is_expr && tmp_var.len == 0 { @@ -4323,12 +4331,16 @@ fn (mut g Gen) ident(node ast.Ident) { } scope := g.file.scope.innermost(node.pos.pos) if v := scope.find_var(node.name) { - if v.sum_type_casts.len > 0 { + if v.smartcasts.len > 0 { + v_sym := g.table.get_type_symbol(v.typ) if !prevent_sum_type_unwrapping_once { - for _ in v.sum_type_casts { - g.write('(*') + for _ in v.smartcasts { + g.write('(') + if v_sym.kind == .sum_type { + g.write('*') + } } - for i, typ in v.sum_type_casts { + for i, typ in v.smartcasts { cast_sym := g.table.get_type_symbol(typ) mut is_ptr := false if i == 0 { @@ -4576,23 +4588,6 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } } } - if branch.smartcast && branch.stmts.len > 0 { - infix := branch.cond as ast.InfixExpr - if mut infix.left is ast.Ident { - right_type := infix.right as ast.TypeNode - left_type := infix.left_type - it_type := g.typ(right_type.typ) - g.write('\t$it_type* _sc_tmp_$branch.pos.pos = ($it_type*)') - g.expr(infix.left) - if left_type.is_ptr() { - g.write('->') - } else { - g.write('.') - } - g.writeln('_object;') - g.writeln('\t$it_type* $infix.left.name = _sc_tmp_$branch.pos.pos;') - } - } if needs_tmp_var { g.stmts_with_tmp_var(branch.stmts, tmp) } else { @@ -5602,7 +5597,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Ty is_none_ok := mr_styp == 'void' g.writeln(';') if is_none_ok { - g.writeln('if (${cvar_name}.state != 0 && ${cvar_name}.err._interface_idx != _IError_None___index) {') + g.writeln('if (${cvar_name}.state != 0 && ${cvar_name}.err._typ != _IError_None___index) {') } else { g.writeln('if (${cvar_name}.state != 0) { /*or block*/ ') } @@ -6115,7 +6110,7 @@ fn (mut g Gen) is_expr(node ast.InfixExpr) { } sym := g.table.get_type_symbol(node.left_type) if sym.kind == .interface_ { - g.write('_interface_idx $eq ') + g.write('_typ $eq ') // `_Animal_Dog_index` sub_type := match mut node.right { ast.TypeNode { node.right.typ } @@ -6207,8 +6202,8 @@ fn (mut g Gen) interface_table() string { sb.writeln('$staticprefix $interface_name I_${cctype}_to_Interface_${interface_name}($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,') + cast_struct.writeln('\t\t._$cctype = x,') + cast_struct.writeln('\t\t._typ = $interface_index_name,') for field in inter_info.fields { cname := c_name(field.name) field_styp := g.typ(field.typ) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 20513c547f..a587451e27 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -195,7 +195,7 @@ fn (mut g Gen) gen_fn_decl(node ast.FnDecl, skip bool) { // if g.pref.show_cc && it.is_builtin { // println(name) // } - // type_name := g.table.Type_to_str(it.return_type) + // type_name := g.ast.Type_to_str(it.return_type) // Live functions are protected by a mutex, because otherwise they // can be changed by the live reload thread, *while* they are // running, with unpredictable results (usually just crashing). @@ -494,7 +494,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]._method_${mname}(') + g.write('${dot}_typ]._method_${mname}(') g.expr(node.left) g.write('${dot}_object') if node.args.len > 0 { @@ -578,7 +578,7 @@ fn (mut g Gen) method_call(node ast.CallExpr) { g.write('tos3( /* $left_sym.name */ v_typeof_interface_${typ_sym.cname}( (') g.expr(node.left) dot := if node.left_type.is_ptr() { '->' } else { '.' } - g.write(')${dot}_interface_idx ))') + g.write(')${dot}_typ ))') return } if node.name == 'str' {