gen: if expressions with multiple statements

pull/4895/head
Enzo Baldisserri 2020-05-14 17:15:25 +02:00 committed by GitHub
parent 2a9cbbe157
commit fd0d833e33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 437 additions and 254 deletions

View File

@ -333,6 +333,10 @@ pub fn (mut c Checker) struct_init(mut struct_init ast.StructInit) table.Type {
pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) table.Type {
// println('checker: infix expr(op $infix_expr.op.str())')
former_expected_type := c.expected_type
defer {
c.expected_type = former_expected_type
}
c.expected_type = table.void_type
left_type := c.expr(infix_expr.left)
infix_expr.left_type = left_type
@ -1977,64 +1981,56 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, type_sym table.TypeSymbol
}
pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type {
mut expr_required := false
if c.expected_type != table.void_type {
// | c.assigned_var_name != '' {
// sym := c.table.get_type_symbol(c.expected_type)
// println('$c.file.path $node.pos.line_nr IF is expr: checker exp type = ' + sym.name)
node.is_expr = true
expr_required = true
}
former_expected_type := c.expected_type
node.typ = table.void_type
mut first_typ := 0
is_ternary := node.is_expr && node.branches.len >= 2 && node.has_else
for i, branch in node.branches {
if branch.cond is ast.ParExpr {
c.error('unnecessary `()` in an if condition. use `if expr {` instead of `if (expr) {`.',
branch.pos)
}
typ := c.expr(branch.cond)
if i < node.branches.len - 1 || !node.has_else {
typ_sym := c.table.get_type_symbol(typ)
// if typ_sym.kind != .bool {
if typ.idx() != table.bool_type_idx {
c.error('non-bool (`$typ_sym.name`) used as if condition', node.pos)
}
}
if is_ternary && i < node.branches.len - 1 && branch.stmts.len > 0 {
last_stmt := branch.stmts[branch.stmts.len - 1]
if last_stmt is ast.ExprStmt {
last_expr := last_stmt as ast.ExprStmt
first_typ = c.expr(last_expr.expr)
if !node.has_else || i < node.branches.len - 1 {
// check condition type is boolean
cond_typ := c.expr(branch.cond)
if cond_typ.idx() != table.bool_type_idx {
typ_sym := c.table.get_type_symbol(cond_typ)
c.error('non-bool type `$typ_sym.name` used as if condition', branch.pos)
}
}
c.stmts(branch.stmts)
}
if node.has_else && node.is_expr {
last_branch := node.branches[node.branches.len - 1]
if last_branch.stmts.len > 0 && node.branches[0].stmts.len > 0 {
match last_branch.stmts[last_branch.stmts.len - 1] {
ast.ExprStmt {
// type_sym := p.table.get_type_symbol(it.typ)
// p.warn('if expr ret $type_sym.name')
t := c.expr(it.expr)
if is_ternary && t != first_typ {
c.error('mismatched types `${c.table.type_to_str(first_typ)}` and `${c.table.type_to_str(t)}`',
if expr_required {
if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt {
last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt
c.expected_type = former_expected_type
expr_type := c.expr(last_expr.expr)
if expr_type != node.typ {
// first branch of if expression
if node.typ == table.void_type {
node.is_expr = true
node.typ = expr_type
} else {
c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(expr_type)}`',
node.pos)
}
node.typ = t
return t
}
else {}
}
} else {
c.error('`if` expression needs returns in both branches', node.pos)
c.error('`if` expression requires an expression as the last statement of every branch',
branch.pos)
}
}
// won't yet work due to eg: if true { println('foo') }
/*
if node.is_expr && !node.has_else {
c.error('`if` expression needs `else` clause. remove return values or add `else`', node.pos)
}
*/
if expr_required {
if !node.has_else {
c.error('`if` expression needs `else` clause', node.pos)
}
return node.typ
}
return table.bool_type
}

View File

@ -0,0 +1,14 @@
vlib/v/checker/tests/if_expr_last_stmt.v:4:7: error: `if` expression requires an expression as the last statement of every branch
2 | _ := if true {
3 | 1
4 | } else if false {
| ~~~~~~~~~~~~~
5 | } else {
6 | }
vlib/v/checker/tests/if_expr_last_stmt.v:5:7: error: `if` expression requires an expression as the last statement of every branch
3 | 1
4 | } else if false {
5 | } else {
| ~~~~
6 | }
7 | }

View File

@ -0,0 +1,7 @@
fn main() {
_ := if true {
1
} else if false {
} else {
}
}

View File

@ -1,4 +1,4 @@
vlib/v/checker/tests/ternary_mismatch.v:2:7: error: mismatched types `string` and `int`
vlib/v/checker/tests/if_expr_mismatch.v:2:7: error: mismatched types `string` and `int`
1 | fn main() {
2 | s := if true { '12' } else { 12 }
| ~~

View File

@ -0,0 +1,5 @@
vlib/v/checker/tests/if_expr_no_else.v:2:10: error: `if` expression needs `else` clause
1 | fn main() {
2 | _ := if true { 1 }
| ~~
3 | }

View File

@ -0,0 +1,3 @@
fn main() {
_ := if true { 1 }
}

View File

@ -41,11 +41,6 @@ const (
]
)
// 'new'
fn foo(t token.Token) {
util.full_hash()
}
struct Gen {
out strings.Builder
cheaders strings.Builder
@ -76,7 +71,9 @@ mut:
is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc
optionals []string // to avoid duplicates TODO perf, use map
inside_ternary int // ?: comma separated statements on a single line
stmt_start_pos int
ternary_names map[string]string
ternary_level_names map[string][]string
stmt_path_pos []int
right_is_opt bool
autofree bool
indent int
@ -477,25 +474,35 @@ pub fn (mut g Gen) reset_tmp_count() {
g.tmp_count = 0
}
fn (mut g Gen) decrement_inside_ternary() {
key := g.inside_ternary.str()
for name in g.ternary_level_names[key] {
g.ternary_names.delete(name)
}
g.ternary_level_names.delete(key)
g.inside_ternary--
}
fn (mut g Gen) stmts(stmts []ast.Stmt) {
g.indent++
if g.inside_ternary > 0 {
g.write(' ( ')
g.writeln('(')
}
for i, stmt in stmts {
g.stmt(stmt)
if g.inside_ternary > 0 && i < stmts.len - 1 {
g.write(', ')
g.writeln(',')
}
}
if g.inside_ternary > 0 {
g.write(' ) ')
}
g.indent--
if g.inside_ternary > 0 {
g.writeln('')
g.write(')')
}
}
fn (mut g Gen) stmt(node ast.Stmt) {
g.stmt_start_pos = g.out.len
g.stmt_path_pos << g.out.len
// println('cgen.stmt()')
// g.writeln('//// stmt start')
match node {
@ -557,17 +564,10 @@ fn (mut g Gen) stmt(node ast.Stmt) {
}
ast.ExprStmt {
g.expr(it.expr)
expr := it.expr
// no ; after an if expression }
match expr {
ast.IfExpr {}
else {
if g.inside_ternary == 0 {
g.writeln(';')
}
}
}
}
ast.FnDecl {
mut skip := false
pos := g.out.buf.len
@ -695,6 +695,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
verror('cgen.stmt(): unhandled node ' + typeof(node))
}
}
g.stmt_path_pos.delete(g.stmt_path_pos.len - 1)
}
fn (mut g Gen) write_defer_stmts() {
@ -836,7 +837,7 @@ fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) {
g.write('if (')
g.expr(a.expr)
g.write(')')
g.inside_ternary--
g.decrement_inside_ternary()
s_assertion := a.expr.str().replace('"', "\'")
mut mod_path := g.file.path
$if windows {
@ -862,7 +863,6 @@ fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) {
}
fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
// g.write('/*assign_stmt*/')
if assign_stmt.is_static {
g.write('static ')
}
@ -873,16 +873,14 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
ast.MatchExpr { return_type = it.return_type }
else {}
}
mut is_multi := false
// json_test failed w/o this check
if return_type != table.void_type && return_type != 0 {
sym := g.table.get_type_symbol(return_type)
// the left vs. right is ugly and should be removed
is_multi = sym.kind == .multi_return || assign_stmt.left.len > assign_stmt.right.len ||
assign_stmt.left.len > 1
}
if is_multi {
if sym.kind == .multi_return || assign_stmt.left.len > assign_stmt.right.len || assign_stmt.left.len >
1 {
// multi return
// TODO Handle in if_expr
mut or_stmts := []ast.Stmt{}
is_optional := return_type.flag_is(.optional)
mr_var_name := 'mr_$assign_stmt.pos.pos'
@ -920,7 +918,9 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
g.writeln(' = ${mr_var_name}.arg$i;')
}
}
} else {
return
}
}
// `a := 1` | `a,b := 1,2`
for i, ident in assign_stmt.left {
val := assign_stmt.right[i]
@ -976,20 +976,38 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
}
else {}
}
is_decl := assign_stmt.op == .decl_assign
// g.write('/*assign_stmt*/')
if is_decl && right_sym.kind != .function {
g.write('$styp ')
is_inside_ternary := g.inside_ternary != 0
cur_line := if is_inside_ternary {
g.register_ternary_name(ident.name)
g.empty_line = false
g.go_before_ternary()
} else {
''
}
is_decl := assign_stmt.op == .decl_assign
if right_sym.kind == .function {
if is_inside_ternary {
g.out.write(tabs[g.indent - g.inside_ternary])
}
func := right_sym.info as table.FnType
ret_styp := g.typ(func.func.return_type)
g.write('$ret_styp (*$ident.name) (')
g.write('$ret_styp (*${g.get_ternary_name(ident.name)}) (')
def_pos := g.definitions.len
g.fn_args(func.func.args, func.func.is_variadic)
g.definitions.go_back(g.definitions.len - def_pos)
g.write(')')
} else {
if is_decl {
if is_inside_ternary {
g.out.write(tabs[g.indent - g.inside_ternary])
}
g.write('$styp ')
}
g.ident(ident)
}
if is_inside_ternary {
g.write(';\n$cur_line')
g.out.write(tabs[g.indent])
g.ident(ident)
}
if g.autofree && right_sym.kind in [.array, .string] {
@ -1019,11 +1037,32 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) {
}
}
g.is_assign_rhs = false
if g.inside_ternary == 0 {
g.writeln(';')
}
}
}
fn (mut g Gen) register_ternary_name(name string) {
level_key := g.inside_ternary.str()
if level_key !in g.ternary_level_names {
g.ternary_level_names[level_key] = []string{}
}
new_name := g.new_tmp_var()
g.ternary_names[name] = new_name
g.ternary_level_names[level_key] << name
}
fn (mut g Gen) get_ternary_name(name string) string {
if g.inside_ternary == 0 {
return name
}
if name !in g.ternary_names {
return name
}
return g.ternary_names[name]
}
fn (mut g Gen) gen_clone_assignment(val ast.Expr, right_sym table.TypeSymbol, add_eq bool) bool {
mut is_ident := false
match val {
@ -1771,7 +1810,7 @@ fn (mut g Gen) match_expr(node ast.MatchExpr) {
}
}
if is_expr {
g.inside_ternary--
g.decrement_inside_ternary()
}
}
@ -1787,7 +1826,7 @@ fn (mut g Gen) ident(node ast.Ident) {
// TODO globals hack
g.write('_const_')
}
name := c_name(node.name)
mut name := c_name(node.name)
if node.info is ast.IdentVar {
ident_var := node.info as ast.IdentVar
// x ?int
@ -1801,24 +1840,11 @@ fn (mut g Gen) ident(node ast.Ident) {
return
}
}
g.write(name)
g.write(g.get_ternary_name(name))
}
fn (mut g Gen) if_expr(node ast.IfExpr) {
// println('if_expr pos=$node.pos.line_nr')
// g.writeln('/* if is_expr=$node.is_expr */')
// If expression? Assign the value to a temp var.
// Previously ?: was used, but it's too unreliable.
type_sym := g.table.get_type_symbol(node.typ)
mut tmp := ''
if type_sym.kind != .void {
tmp = g.new_tmp_var()
// g.writeln('$ti.name $tmp;')
}
// one line ?:
// TODO clean this up once `is` is supported
// TODO: make sure only one stmt in each branch
if node.is_expr && node.branches.len >= 2 && node.has_else && type_sym.kind != .void {
if node.is_expr || g.inside_ternary != 0 {
g.inside_ternary++
g.write('(')
for i, branch in node.branches {
@ -1831,9 +1857,13 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
}
g.stmts(branch.stmts)
}
if node.branches.len == 1 {
g.write(': 0')
}
g.write(')')
g.inside_ternary--
} else {
g.decrement_inside_ternary()
return
}
mut is_guard := false
for i, branch in node.branches {
if i == 0 {
@ -1858,16 +1888,12 @@ fn (mut g Gen) if_expr(node ast.IfExpr) {
} else if i == node.branches.len - 1 && node.has_else {
g.writeln('} else {')
}
// Assign ret value
// if i == node.stmts.len - 1 && type_sym.kind != .void {}
// g.writeln('$tmp =')
g.stmts(branch.stmts)
}
if is_guard {
g.write('}')
}
g.writeln('}')
}
}
fn (mut g Gen) index_expr(node ast.IndexExpr) {
@ -2710,8 +2736,7 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) {
// `nums.map(it % 2 == 0)`
fn (mut g Gen) gen_map(node ast.CallExpr) {
tmp := g.new_tmp_var()
s := g.out.after(g.stmt_start_pos) // the already generated part of current statement
g.out.go_back(s.len)
s := g.go_before_stmt(0)
// println('filter s="$s"')
ret_typ := g.typ(node.return_type)
// inp_typ := g.typ(node.receiver_type)
@ -2746,8 +2771,7 @@ fn (mut g Gen) gen_map(node ast.CallExpr) {
// `nums.filter(it % 2 == 0)`
fn (mut g Gen) gen_filter(node ast.CallExpr) {
tmp := g.new_tmp_var()
s := g.out.after(g.stmt_start_pos) // the already generated part of current statement
g.out.go_back(s.len)
s := g.go_before_stmt(0)
// println('filter s="$s"')
sym := g.table.get_type_symbol(node.return_type)
if sym.kind != .array {
@ -2772,9 +2796,25 @@ fn (mut g Gen) gen_filter(node ast.CallExpr) {
g.write(tmp)
}
fn (mut g Gen) insert_before(s string) {
cur_line := g.out.after(g.stmt_start_pos)
[inline]
fn (g &Gen) nth_stmt_pos(n int) int {
return g.stmt_path_pos[g.stmt_path_pos.len - (1 + n)]
}
fn (mut g Gen) go_before_stmt(n int) string {
stmt_pos := g.nth_stmt_pos(n)
cur_line := g.out.after(stmt_pos)
g.out.go_back(cur_line.len)
return cur_line
}
[inline]
fn (mut g Gen) go_before_ternary() string {
return g.go_before_stmt(g.inside_ternary)
}
fn (mut g Gen) insert_before_stmt(s string) {
cur_line := g.go_before_stmt(0)
g.writeln(s)
g.write(cur_line)
}

View File

@ -408,7 +408,7 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
g.gen_json_for_type(node.args[0].typ)
json_type_str = g.table.get_type_symbol(node.args[0].typ).name
} else {
g.insert_before('// json.decode')
g.insert_before_stmt('// json.decode')
ast_type := node.args[0].expr as ast.Type
// `json.decode(User, s)` => json.decode_User(s)
sym := g.table.get_type_symbol(ast_type.typ)
@ -512,7 +512,7 @@ fn (mut g Gen) fn_call(node ast.CallExpr) {
g.call_args(node.args, node.expected_arg_types)
g.write(')')
} else {
g.write('${name}(')
g.write('${g.get_ternary_name(name)}(')
if is_json_decode {
g.write('json__json_parse(')
// Skip the first argument in json.decode which is a type

View File

@ -8,10 +8,11 @@ import v.table
import v.token
fn (mut p Parser) if_expr() ast.IfExpr {
p.inside_if_expr = true
was_inside_if_expr := p.inside_if_expr
defer {
p.inside_if_expr = false
p.inside_if_expr = was_inside_if_expr
}
p.inside_if_expr = true
pos := p.tok.position()
mut branches := []ast.IfBranch{}
mut has_else := false

View File

@ -11,3 +11,120 @@ fn test_if_expression_precedence_true_condition() {
res := 1 + if b > c { b } else { c } + 1
assert res == b + 2
}
fn test_if_expression_with_stmts() {
a := if true {
b := 1
b
} else {
b := 4
b
}
assert a == 1
mut b := 0
b = if false {
42
} else {
24
}
assert b == 24
}
fn noop() {}
fn test_if_expression_with_function_assign() {
a := if true {
my_fn := noop
my_fn()
0
} else {
1
}
assert a == 0
}
fn get_bool_str(b bool) string {
return b.str()
}
fn test_if_expression_mutate_var() {
mut b := false
r := b && if true {
b = true
true
} else {
true
}
assert r == false
// test in function call
assert get_bool_str(b && if true {
b = true
true
} else {
true
}) == 'false'
// test on method call
assert (b && if true {
b = true
true
} else {
true
}).str() == 'false'
// test on array
mut a := [1, 2]
assert a.len == 2 && if true {
a << 3
true
} else {
false
}
}
fn test_simple_nested_if_expressions() {
a := if false {
b := 1
if b == 0 {
0
} else {
b
}
} else {
println('Hello world !')
if 1 == 1 {
t := 12
t + 42
} else {
43
}
}
assert a == 54
}
fn test_complex_nested_if_expressions() {
mut a := false
a = 1 == 2 || true && if true {
g := 6
h := if false { 3 } else { 5 }
mut d := false
if h == 2 {
d = g + 4 == 5
}
if d {
if true {
d = false
} else {
d = true
}
}
d
} else {
x := 6
y := 8
if x + y > 0 {
x > 0
} else {
false
}
}
assert a == false
}