From fd0d833e3356117eeacf95c450603533b16e6df3 Mon Sep 17 00:00:00 2001 From: Enzo Baldisserri Date: Thu, 14 May 2020 17:15:25 +0200 Subject: [PATCH] gen: if expressions with multiple statements --- vlib/v/checker/checker.v | 70 ++- vlib/v/checker/tests/if_expr_last_stmt.out | 14 + vlib/v/checker/tests/if_expr_last_stmt.vv | 7 + ...nary_mismatch.out => if_expr_mismatch.out} | 2 +- ...ernary_mismatch.vv => if_expr_mismatch.vv} | 0 vlib/v/checker/tests/if_expr_no_else.out | 5 + vlib/v/checker/tests/if_expr_no_else.vv | 3 + vlib/v/gen/cgen.v | 464 ++++++++++-------- vlib/v/gen/fn.v | 4 +- vlib/v/parser/if.v | 5 +- vlib/v/tests/if_expression_test.v | 117 +++++ 11 files changed, 437 insertions(+), 254 deletions(-) create mode 100644 vlib/v/checker/tests/if_expr_last_stmt.out create mode 100644 vlib/v/checker/tests/if_expr_last_stmt.vv rename vlib/v/checker/tests/{ternary_mismatch.out => if_expr_mismatch.out} (57%) rename vlib/v/checker/tests/{ternary_mismatch.vv => if_expr_mismatch.vv} (100%) create mode 100644 vlib/v/checker/tests/if_expr_no_else.out create mode 100644 vlib/v/checker/tests/if_expr_no_else.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 7fec79f1fb..caf7dd7e78 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -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 requires an expression as the last statement of every branch', + branch.pos) } - } else { - c.error('`if` expression needs returns in both branches', node.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 } diff --git a/vlib/v/checker/tests/if_expr_last_stmt.out b/vlib/v/checker/tests/if_expr_last_stmt.out new file mode 100644 index 0000000000..ab8ad293eb --- /dev/null +++ b/vlib/v/checker/tests/if_expr_last_stmt.out @@ -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 | } diff --git a/vlib/v/checker/tests/if_expr_last_stmt.vv b/vlib/v/checker/tests/if_expr_last_stmt.vv new file mode 100644 index 0000000000..beecb65c0c --- /dev/null +++ b/vlib/v/checker/tests/if_expr_last_stmt.vv @@ -0,0 +1,7 @@ +fn main() { + _ := if true { + 1 + } else if false { + } else { + } +} diff --git a/vlib/v/checker/tests/ternary_mismatch.out b/vlib/v/checker/tests/if_expr_mismatch.out similarity index 57% rename from vlib/v/checker/tests/ternary_mismatch.out rename to vlib/v/checker/tests/if_expr_mismatch.out index c51adf0822..04ce5c54b9 100644 --- a/vlib/v/checker/tests/ternary_mismatch.out +++ b/vlib/v/checker/tests/if_expr_mismatch.out @@ -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 } | ~~ diff --git a/vlib/v/checker/tests/ternary_mismatch.vv b/vlib/v/checker/tests/if_expr_mismatch.vv similarity index 100% rename from vlib/v/checker/tests/ternary_mismatch.vv rename to vlib/v/checker/tests/if_expr_mismatch.vv diff --git a/vlib/v/checker/tests/if_expr_no_else.out b/vlib/v/checker/tests/if_expr_no_else.out new file mode 100644 index 0000000000..17d5ad0ce3 --- /dev/null +++ b/vlib/v/checker/tests/if_expr_no_else.out @@ -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 | } diff --git a/vlib/v/checker/tests/if_expr_no_else.vv b/vlib/v/checker/tests/if_expr_no_else.vv new file mode 100644 index 0000000000..d3e5144888 --- /dev/null +++ b/vlib/v/checker/tests/if_expr_no_else.vv @@ -0,0 +1,3 @@ +fn main() { + _ := if true { 1 } +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 6e7cb157a0..757eb8d446 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -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,15 +564,8 @@ 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(';') - } - } + if g.inside_ternary == 0 { + g.writeln(';') } } ast.FnDecl { @@ -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,157 +873,196 @@ 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 { - // multi return - mut or_stmts := []ast.Stmt{} - is_optional := return_type.flag_is(.optional) - mr_var_name := 'mr_$assign_stmt.pos.pos' - mr_styp := g.typ(return_type) - g.write('$mr_styp $mr_var_name = ') - g.is_assign_rhs = true - g.expr(assign_stmt.right[0]) - g.is_assign_rhs = false - if is_optional { - val := assign_stmt.right[0] - match val { - ast.CallExpr { - or_stmts = it.or_block.stmts - return_type = it.return_type - g.or_block(mr_var_name, or_stmts, return_type) - } - else {} - } - } - g.writeln(';') - for i, ident in assign_stmt.left { - if ident.kind == .blank_ident { - continue - } - ident_var_info := ident.var_info() - styp := g.typ(ident_var_info.typ) - if assign_stmt.op == .decl_assign { - g.write('$styp ') - } - g.expr(ident) - if is_optional { - mr_base_styp := g.base_type(return_type) - g.writeln(' = (*(${mr_base_styp}*)${mr_var_name}.data).arg$i;') - } else { - g.writeln(' = ${mr_var_name}.arg$i;') - } - } - } else { - // `a := 1` | `a,b := 1,2` - for i, ident in assign_stmt.left { - val := assign_stmt.right[i] - ident_var_info := ident.var_info() - styp := g.typ(ident_var_info.typ) - mut is_call := false + 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{} - blank_assign := ident.kind == .blank_ident - match val { - ast.CallExpr { - is_call = true - or_stmts = it.or_block.stmts - return_type = it.return_type - } - // TODO: no buffer fiddling - ast.AnonFn { - if blank_assign { - g.write('{') - } - ret_styp := g.typ(it.decl.return_type) - g.write('$ret_styp (*$ident.name) (') - def_pos := g.definitions.len - g.fn_args(it.decl.args, it.decl.is_variadic) - g.definitions.go_back(g.definitions.len - def_pos) - g.write(') = ') - g.expr(*it) - g.writeln(';') - if blank_assign { - g.write('}') - } - continue - } - else {} - } - gen_or := is_call && return_type.flag_is(.optional) + is_optional := return_type.flag_is(.optional) + mr_var_name := 'mr_$assign_stmt.pos.pos' + mr_styp := g.typ(return_type) + g.write('$mr_styp $mr_var_name = ') g.is_assign_rhs = true - if blank_assign { - if is_call { - g.expr(val) - } else { - g.write('{$styp _ = ') - g.expr(val) - g.writeln(';}') - } - } else { - right_sym := g.table.get_type_symbol(assign_stmt.right_types[i]) - mut is_fixed_array_init := false - mut has_val := false + g.expr(assign_stmt.right[0]) + g.is_assign_rhs = false + if is_optional { + val := assign_stmt.right[0] match val { - ast.ArrayInit { - is_fixed_array_init = it.is_fixed - has_val = it.has_val + ast.CallExpr { + or_stmts = it.or_block.stmts + return_type = it.return_type + g.or_block(mr_var_name, or_stmts, return_type) } else {} } - is_decl := assign_stmt.op == .decl_assign - // g.write('/*assign_stmt*/') - if is_decl && right_sym.kind != .function { + } + g.writeln(';') + for i, ident in assign_stmt.left { + if ident.kind == .blank_ident { + continue + } + ident_var_info := ident.var_info() + styp := g.typ(ident_var_info.typ) + if assign_stmt.op == .decl_assign { g.write('$styp ') } - if right_sym.kind == .function { - func := right_sym.info as table.FnType - ret_styp := g.typ(func.func.return_type) - g.write('$ret_styp (*$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(')') + g.expr(ident) + if is_optional { + mr_base_styp := g.base_type(return_type) + g.writeln(' = (*(${mr_base_styp}*)${mr_var_name}.data).arg$i;') } else { - g.ident(ident) - } - if g.autofree && right_sym.kind in [.array, .string] { - if g.gen_clone_assignment(val, right_sym, true) { - g.writeln(';') - // g.expr_var_name = '' - return - } - } - if is_fixed_array_init { - if has_val { - g.write(' = ') - g.expr(val) - } else { - g.write(' = {0}') - } - } else { - g.write(' = ') - if is_decl { - g.expr(val) - } else { - g.expr_with_cast(val, assign_stmt.left_types[i], ident_var_info.typ) - } - } - if gen_or { - g.or_block(ident.name, or_stmts, return_type) + g.writeln(' = ${mr_var_name}.arg$i;') } } - g.is_assign_rhs = false + return + } + } + // `a := 1` | `a,b := 1,2` + for i, ident in assign_stmt.left { + val := assign_stmt.right[i] + ident_var_info := ident.var_info() + styp := g.typ(ident_var_info.typ) + mut is_call := false + mut or_stmts := []ast.Stmt{} + blank_assign := ident.kind == .blank_ident + match val { + ast.CallExpr { + is_call = true + or_stmts = it.or_block.stmts + return_type = it.return_type + } + // TODO: no buffer fiddling + ast.AnonFn { + if blank_assign { + g.write('{') + } + ret_styp := g.typ(it.decl.return_type) + g.write('$ret_styp (*$ident.name) (') + def_pos := g.definitions.len + g.fn_args(it.decl.args, it.decl.is_variadic) + g.definitions.go_back(g.definitions.len - def_pos) + g.write(') = ') + g.expr(*it) + g.writeln(';') + if blank_assign { + g.write('}') + } + continue + } + else {} + } + gen_or := is_call && return_type.flag_is(.optional) + g.is_assign_rhs = true + if blank_assign { + if is_call { + g.expr(val) + } else { + g.write('{$styp _ = ') + g.expr(val) + g.writeln(';}') + } + } else { + right_sym := g.table.get_type_symbol(assign_stmt.right_types[i]) + mut is_fixed_array_init := false + mut has_val := false + match val { + ast.ArrayInit { + is_fixed_array_init = it.is_fixed + has_val = it.has_val + } + else {} + } + 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 (*${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] { + if g.gen_clone_assignment(val, right_sym, true) { + g.writeln(';') + // g.expr_var_name = '' + return + } + } + if is_fixed_array_init { + if has_val { + g.write(' = ') + g.expr(val) + } else { + g.write(' = {0}') + } + } else { + g.write(' = ') + if is_decl { + g.expr(val) + } else { + g.expr_with_cast(val, assign_stmt.left_types[i], ident_var_info.typ) + } + } + if gen_or { + g.or_block(ident.name, or_stmts, return_type) + } + } + 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,43 +1857,43 @@ 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 { - mut is_guard := false - for i, branch in node.branches { - if i == 0 { - match branch.cond { - ast.IfGuardExpr { - is_guard = true - g.write('{ /* if guard */ ${g.typ(it.expr_type)} $it.var_name = ') - g.expr(it.expr) - g.writeln(';') - g.writeln('if (${it.var_name}.ok) {') - } - else { - g.write('if (') - g.expr(branch.cond) - g.writeln(') {') - } - } - } else if i < node.branches.len - 1 || !node.has_else { - g.write('} else if (') - g.expr(branch.cond) - g.writeln(') {') - } 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('}') + g.decrement_inside_ternary() + return } + mut is_guard := false + for i, branch in node.branches { + if i == 0 { + match branch.cond { + ast.IfGuardExpr { + is_guard = true + g.write('{ /* if guard */ ${g.typ(it.expr_type)} $it.var_name = ') + g.expr(it.expr) + g.writeln(';') + g.writeln('if (${it.var_name}.ok) {') + } + else { + g.write('if (') + g.expr(branch.cond) + g.writeln(') {') + } + } + } else if i < node.branches.len - 1 || !node.has_else { + g.write('} else if (') + g.expr(branch.cond) + g.writeln(') {') + } else if i == node.branches.len - 1 && node.has_else { + g.writeln('} else {') + } + 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) } diff --git a/vlib/v/gen/fn.v b/vlib/v/gen/fn.v index 3b8d78e091..4ccd25b084 100644 --- a/vlib/v/gen/fn.v +++ b/vlib/v/gen/fn.v @@ -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 diff --git a/vlib/v/parser/if.v b/vlib/v/parser/if.v index 73c51778ab..97ddaedeb6 100644 --- a/vlib/v/parser/if.v +++ b/vlib/v/parser/if.v @@ -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 diff --git a/vlib/v/tests/if_expression_test.v b/vlib/v/tests/if_expression_test.v index c34ded99a2..3bd8b0062b 100644 --- a/vlib/v/tests/if_expression_test.v +++ b/vlib/v/tests/if_expression_test.v @@ -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 +}