checker, cgen: add sumtype-like smartcasting capabilites to interfaces (#9256)

pull/9647/head
spaceface 2021-04-09 10:00:05 +02:00 committed by GitHub
parent 78e3bb748b
commit 4feb09fa5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 74 additions and 102 deletions

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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()
| ~~~~~

View File

@ -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()
}

View File

@ -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')
}

View File

@ -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)

View File

@ -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' {