Skip to content

Commit

Permalink
checker, cgen: fix match branches return type ref mismatch, when the …
Browse files Browse the repository at this point in the history
…return type exists as interface or sumtype (fix #16203) (#19806)
  • Loading branch information
shove70 committed Nov 13, 2023
1 parent bcc2bfa commit c9df064
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 20 deletions.
10 changes: 7 additions & 3 deletions vlib/v/checker/checker.v
Expand Up @@ -3564,9 +3564,7 @@ fn (mut c Checker) ident(mut node ast.Ident) ast.Type {
typ = c.expr(mut obj.expr)
}
}
if c.inside_casting_to_str && obj.orig_type != 0
&& c.table.sym(obj.orig_type).kind == .interface_
&& c.table.sym(obj.smartcasts.last()).kind != .interface_ {
if c.inside_casting_to_str && c.is_interface_var(obj) {
typ = typ.deref()
}
is_option := typ.has_flag(.option) || typ.has_flag(.result)
Expand Down Expand Up @@ -4028,6 +4026,12 @@ pub fn (mut c Checker) is_comptime_var(node ast.Expr) bool {
&& (node.obj as ast.Var).ct_type_var != .no_comptime
}

[inline]
fn (mut c Checker) is_interface_var(var ast.ScopeObject) bool {
return var is ast.Var && var.orig_type != 0 && c.table.sym(var.orig_type).kind == .interface_
&& c.table.sym(var.smartcasts.last()).kind != .interface_
}

fn (mut c Checker) mark_as_referenced(mut node ast.Expr, as_interface bool) {
match mut node {
ast.Ident {
Expand Down
57 changes: 43 additions & 14 deletions vlib/v/checker/match.v
Expand Up @@ -74,22 +74,51 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
ret_type = node.expected_type
} else {
ret_type = expr_type
if expr_type.is_ptr() {
if stmt.expr is ast.Ident && stmt.expr.obj is ast.Var
&& c.is_interface_var(stmt.expr.obj) {
ret_type = expr_type.deref()
} else if mut stmt.expr is ast.PrefixExpr
&& stmt.expr.right is ast.Ident {
ident := stmt.expr.right as ast.Ident
if ident.obj is ast.Var && c.is_interface_var(ident.obj) {
ret_type = expr_type.deref()
}
}
}
}
} else {
if node.is_expr && ret_type.idx() != expr_type.idx() {
if (node.expected_type.has_flag(.option)
|| node.expected_type.has_flag(.result))
&& c.table.sym(stmt.typ).kind == .struct_
&& c.type_implements(stmt.typ, ast.error_type, node.pos) {
stmt.expr = ast.CastExpr{
expr: stmt.expr
typname: 'IError'
typ: ast.error_type
expr_type: stmt.typ
pos: node.pos
}
stmt.typ = ast.error_type
} else {
c.check_match_branch_last_stmt(stmt, ret_type, expr_type)
}
}
} else if node.is_expr && ret_type.idx() != expr_type.idx() {
if (node.expected_type.has_flag(.option)
|| node.expected_type.has_flag(.result))
&& c.table.sym(stmt.typ).kind == .struct_
&& c.type_implements(stmt.typ, ast.error_type, node.pos) {
stmt.expr = ast.CastExpr{
expr: stmt.expr
typname: 'IError'
typ: ast.error_type
expr_type: stmt.typ
pos: node.pos
if node.is_expr && stmt.typ != ast.error_type {
ret_sym := c.table.sym(ret_type)
stmt_sym := c.table.sym(stmt.typ)
if ret_sym.kind !in [.sum_type, .interface_]
&& stmt_sym.kind in [.sum_type, .interface_] {
c.error('return type mismatch, it should be `${ret_sym.name}`',
stmt.pos)
}
if ret_type.nr_muls() != stmt.typ.nr_muls()
&& stmt.typ.idx() !in [ast.voidptr_type_idx, ast.nil_type_idx] {
type_name := '&'.repeat(ret_type.nr_muls()) + ret_sym.name
c.error('return type mismatch, it should be `${type_name}`',
stmt.pos)
}
stmt.typ = ast.error_type
} else {
c.check_match_branch_last_stmt(stmt, ret_type, expr_type)
}
}
} else if stmt !in [ast.Return, ast.BranchStmt] {
Expand Down
35 changes: 35 additions & 0 deletions vlib/v/checker/tests/match_return_mismatch_type_err.out
@@ -1,7 +1,42 @@
vlib/v/checker/tests/match_return_mismatch_type_err.vv:27:6: warning: cannot assign a reference to a value (this will be an error soon) left=string false right=string true ptr=true
25 |
26 | mut res := ''
27 | res = match any {
| ^
28 | string { &any }
29 | else { &variable }
vlib/v/checker/tests/match_return_mismatch_type_err.vv:4:10: error: return type mismatch, it should be `string`
2 | a := match 1 {
3 | 1 { 'aa' }
4 | else { 22 }
| ~~
5 | }
6 | println(a)
vlib/v/checker/tests/match_return_mismatch_type_err.vv:18:10: error: return type mismatch, it should be `&string`
16 | _ = match any {
17 | string { &any }
18 | else { variable }
| ~~~~~~~~
19 | }
20 |
vlib/v/checker/tests/match_return_mismatch_type_err.vv:23:10: error: return type mismatch, it should be `string`
21 | _ = match any {
22 | string { any }
23 | else { &variable }
| ^
24 | }
25 |
vlib/v/checker/tests/match_return_mismatch_type_err.vv:43:10: error: return type mismatch, it should be `&string`
41 | _ = match any {
42 | string { &any }
43 | else { variable }
| ~~~~~~~~
44 | }
45 |
vlib/v/checker/tests/match_return_mismatch_type_err.vv:48:10: error: return type mismatch, it should be `string`
46 | _ = match any {
47 | string { any }
48 | else { &variable }
| ^
49 | }
50 | }
43 changes: 43 additions & 0 deletions vlib/v/checker/tests/match_return_mismatch_type_err.vv
Expand Up @@ -5,3 +5,46 @@ fn main() {
}
println(a)
}

// for test the returns both interface or non-interface
interface IAny {}

fn returns_both_interface_and_non_interface() {
any := IAny('abc')
variable := ''

_ = match any {
string { &any }
else { variable }
}

_ = match any {
string { any }
else { &variable }
}

mut res := ''
res = match any {
string { &any }
else { &variable }
}
println(res)
}

// for test the returns both sumtype or non-sumtype
type SAny = int | string

fn returns_both_sumtype_and_non_sumtype() {
any := SAny('abc')
variable := ''

_ = match any {
string { &any }
else { variable }
}

_ = match any {
string { any }
else { &variable }
}
}
10 changes: 7 additions & 3 deletions vlib/v/gen/c/cgen.v
Expand Up @@ -4349,6 +4349,12 @@ fn (mut g Gen) get_const_name(node ast.Ident) string {
}
}

[inline]
fn (mut g Gen) is_interface_var(var ast.ScopeObject) bool {
return var is ast.Var && var.orig_type != 0 && g.table.sym(var.orig_type).kind == .interface_
&& g.table.sym(var.smartcasts.last()).kind != .interface_
}

fn (mut g Gen) ident(node ast.Ident) {
prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once
g.prevent_sum_type_unwrapping_once = false
Expand Down Expand Up @@ -4485,9 +4491,7 @@ fn (mut g Gen) ident(node ast.Ident) {
g.write('(')
if obj_sym.kind == .sum_type && !is_auto_heap {
g.write('*')
} else if g.inside_casting_to_str && node.obj.orig_type != 0
&& g.table.sym(node.obj.orig_type).kind == .interface_
&& g.table.sym(node.obj.smartcasts.last()).kind != .interface_ {
} else if g.inside_casting_to_str && g.is_interface_var(node.obj) {
g.write('*')
}
}
Expand Down
16 changes: 16 additions & 0 deletions vlib/v/gen/c/match.v
Expand Up @@ -230,7 +230,23 @@ fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var str
if is_expr && tmp_var.len > 0 && g.table.sym(node.return_type).kind == .sum_type {
g.expected_cast_type = node.return_type
}
inside_casting_to_str_old := g.inside_casting_to_str
if is_expr && branch.stmts.len > 0 {
mut stmt := branch.stmts.last()
if mut stmt is ast.ExprStmt {
if mut stmt.expr is ast.Ident && stmt.expr.obj is ast.Var
&& g.is_interface_var(stmt.expr.obj) {
g.inside_casting_to_str = true
} else if mut stmt.expr is ast.PrefixExpr && stmt.expr.right is ast.Ident {
ident := stmt.expr.right as ast.Ident
if ident.obj is ast.Var && g.is_interface_var(ident.obj) {
g.inside_casting_to_str = true
}
}
}
}
g.stmts_with_tmp_var(branch.stmts, tmp_var)
g.inside_casting_to_str = inside_casting_to_str_old
g.expected_cast_type = 0
if g.inside_ternary == 0 {
g.writeln('}')
Expand Down
20 changes: 20 additions & 0 deletions vlib/v/tests/match_test.v
Expand Up @@ -301,3 +301,23 @@ fn test_noreturn() {
}
}
}

// for test the returns both interface and non-interface
interface Any {}

fn test_returns_both_interface_and_non_interface() {
any := Any('abc')

mut res := match any {
string { any }
else { 'literal' }
}
assert res == 'abc'

variable := ''
res = match any {
string { any }
else { variable }
}
assert res == 'abc'
}

0 comments on commit c9df064

Please sign in to comment.