From d39866d4f7dfe76bd0cebd54026217c5b28282f6 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 27 Feb 2021 17:11:17 +0300 Subject: [PATCH] cgen: optionals/autofree fixes --- .github/workflows/ci.yml | 3 +- vlib/v/builder/cc.v | 2 +- vlib/v/gen/c/assert.v | 108 ++++++++++++++++++ vlib/v/gen/c/cgen.v | 114 ++----------------- vlib/v/gen/c/fn.v | 26 +++-- vlib/v/tests/option_test.v | 84 +++++++------- vlib/v/tests/valgrind/1.strings_and_arrays.v | 11 +- 7 files changed, 185 insertions(+), 163 deletions(-) create mode 100644 vlib/v/gen/c/assert.v diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e51fdf033..b18c333a15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -295,9 +295,10 @@ jobs: run: ./v -cc gcc -cflags "-Werror" test-self - name: Build examples run: ./v build-examples - - name: Build examples with -autofree + - name: Build examples/certain tests with -autofree run: | ./v -autofree -experimental -o tetris examples/tetris/tetris.v + ./v -autofree vlib/v/tests/option_test.v - name: Build modules run: | ./v build-module vlib/os diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index 519c6ea13b..464752d3b7 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -16,7 +16,7 @@ const ( ================== C error. This should never happen. -If you were not working with C interop, please raise an issue on GitHub: +If you were not working with C interop, this is a compiler bug, please raise an issue on GitHub: https://github.com/vlang/v/issues/new/choose diff --git a/vlib/v/gen/c/assert.v b/vlib/v/gen/c/assert.v new file mode 100644 index 0000000000..8c4091821c --- /dev/null +++ b/vlib/v/gen/c/assert.v @@ -0,0 +1,108 @@ +// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module c + +import v.ast +import v.table + +fn (mut g Gen) gen_assert_stmt(original_assert_statement ast.AssertStmt) { + mut node := original_assert_statement + g.writeln('// assert') + if mut node.expr is ast.InfixExpr { + if mut node.expr.left is ast.CallExpr { + node.expr.left = g.new_ctemp_var_then_gen(node.expr.left, node.expr.left_type) + } + if mut node.expr.right is ast.CallExpr { + node.expr.right = g.new_ctemp_var_then_gen(node.expr.right, node.expr.right_type) + } + } + g.inside_ternary++ + if g.is_test { + g.write('if (') + g.expr(node.expr) + g.write(')') + g.decrement_inside_ternary() + g.writeln(' {') + g.writeln('\tg_test_oks++;') + metaname_ok := g.gen_assert_metainfo(node) + g.writeln('\tmain__cb_assertion_ok(&$metaname_ok);') + g.writeln('} else {') + g.writeln('\tg_test_fails++;') + metaname_fail := g.gen_assert_metainfo(node) + g.writeln('\tmain__cb_assertion_failed(&$metaname_fail);') + g.writeln('\tlongjmp(g_jump_buffer, 1);') + g.writeln('\t// TODO') + g.writeln('\t// Maybe print all vars in a test function if it fails?') + g.writeln('}') + } else { + g.write('if (!(') + g.expr(node.expr) + g.write('))') + g.decrement_inside_ternary() + g.writeln(' {') + metaname_panic := g.gen_assert_metainfo(node) + g.writeln('\t__print_assert_failure(&$metaname_panic);') + g.writeln('\tv_panic(_SLIT("Assertion failed..."));') + g.writeln('}') + } +} + +fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string { + mod_path := cestring(g.file.path) + fn_name := g.fn_decl.name + line_nr := node.pos.line_nr + src := cestring(node.expr.str()) + metaname := 'v_assert_meta_info_$g.new_tmp_var()' + g.writeln('\tVAssertMetaInfo $metaname = {0};') + g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};') + g.writeln('\t${metaname}.line_nr = $line_nr;') + g.writeln('\t${metaname}.fn_name = ${ctoslit(fn_name)};') + g.writeln('\t${metaname}.src = ${cnewlines(ctoslit(src))};') + match mut node.expr { + ast.InfixExpr { + g.writeln('\t${metaname}.op = ${ctoslit(node.expr.op.str())};') + g.writeln('\t${metaname}.llabel = ${cnewlines(ctoslit(node.expr.left.str()))};') + g.writeln('\t${metaname}.rlabel = ${cnewlines(ctoslit(node.expr.right.str()))};') + g.write('\t${metaname}.lvalue = ') + g.gen_assert_single_expr(node.expr.left, node.expr.left_type) + g.writeln(';') + // + g.write('\t${metaname}.rvalue = ') + g.gen_assert_single_expr(node.expr.right, node.expr.right_type) + g.writeln(';') + } + ast.CallExpr { + g.writeln('\t${metaname}.op = _SLIT("call");') + } + else {} + } + return metaname +} + +fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ table.Type) { + unknown_value := '*unknown value*' + match expr { + ast.CastExpr, ast.IndexExpr, ast.MatchExpr { + g.write(ctoslit(unknown_value)) + } + ast.PrefixExpr { + if expr.right is ast.CastExpr { + // TODO: remove this check; + // vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails + // without special casing ast.CastExpr here + g.write(ctoslit(unknown_value)) + } else { + g.gen_expr_to_string(expr, typ) + } + } + ast.Type { + sym := g.table.get_type_symbol(typ) + g.write(ctoslit('$sym.name')) + } + else { + g.gen_expr_to_string(expr, typ) + } + } + g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ') +} diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 0faeea9d84..ae881a4086 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1637,111 +1637,10 @@ fn (mut g Gen) gen_attrs(attrs []table.Attr) { } } -fn (mut g Gen) gen_assert_stmt(original_assert_statement ast.AssertStmt) { - mut node := original_assert_statement - g.writeln('// assert') - if mut node.expr is ast.InfixExpr { - if mut node.expr.left is ast.CallExpr { - node.expr.left = g.new_ctemp_var_then_gen(node.expr.left, node.expr.left_type) - } - if mut node.expr.right is ast.CallExpr { - node.expr.right = g.new_ctemp_var_then_gen(node.expr.right, node.expr.right_type) - } - } - g.inside_ternary++ - if g.is_test { - g.write('if (') - g.expr(node.expr) - g.write(')') - g.decrement_inside_ternary() - g.writeln(' {') - g.writeln('\tg_test_oks++;') - metaname_ok := g.gen_assert_metainfo(node) - g.writeln('\tmain__cb_assertion_ok(&$metaname_ok);') - g.writeln('} else {') - g.writeln('\tg_test_fails++;') - metaname_fail := g.gen_assert_metainfo(node) - g.writeln('\tmain__cb_assertion_failed(&$metaname_fail);') - g.writeln('\tlongjmp(g_jump_buffer, 1);') - g.writeln('\t// TODO') - g.writeln('\t// Maybe print all vars in a test function if it fails?') - g.writeln('}') - } else { - g.write('if (!(') - g.expr(node.expr) - g.write('))') - g.decrement_inside_ternary() - g.writeln(' {') - metaname_panic := g.gen_assert_metainfo(node) - g.writeln('\t__print_assert_failure(&$metaname_panic);') - g.writeln('\tv_panic(_SLIT("Assertion failed..."));') - g.writeln('}') - } -} - fn cnewlines(s string) string { return s.replace('\n', r'\n') } -fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string { - mod_path := cestring(g.file.path) - fn_name := g.fn_decl.name - line_nr := node.pos.line_nr - src := cestring(node.expr.str()) - metaname := 'v_assert_meta_info_$g.new_tmp_var()' - g.writeln('\tVAssertMetaInfo $metaname = {0};') - g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};') - g.writeln('\t${metaname}.line_nr = $line_nr;') - g.writeln('\t${metaname}.fn_name = ${ctoslit(fn_name)};') - g.writeln('\t${metaname}.src = ${cnewlines(ctoslit(src))};') - match mut node.expr { - ast.InfixExpr { - g.writeln('\t${metaname}.op = ${ctoslit(node.expr.op.str())};') - g.writeln('\t${metaname}.llabel = ${cnewlines(ctoslit(node.expr.left.str()))};') - g.writeln('\t${metaname}.rlabel = ${cnewlines(ctoslit(node.expr.right.str()))};') - g.write('\t${metaname}.lvalue = ') - g.gen_assert_single_expr(node.expr.left, node.expr.left_type) - g.writeln(';') - // - g.write('\t${metaname}.rvalue = ') - g.gen_assert_single_expr(node.expr.right, node.expr.right_type) - g.writeln(';') - } - ast.CallExpr { - g.writeln('\t${metaname}.op = _SLIT("call");') - } - else {} - } - return metaname -} - -fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ table.Type) { - unknown_value := '*unknown value*' - match expr { - ast.CastExpr, ast.IndexExpr, ast.MatchExpr { - g.write(ctoslit(unknown_value)) - } - ast.PrefixExpr { - if expr.right is ast.CastExpr { - // TODO: remove this check; - // vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails - // without special casing ast.CastExpr here - g.write(ctoslit(unknown_value)) - } else { - g.gen_expr_to_string(expr, typ) - } - } - ast.Type { - sym := g.table.get_type_symbol(typ) - g.write(ctoslit('$sym.name')) - } - else { - g.gen_expr_to_string(expr, typ) - } - } - g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ') -} - fn (mut g Gen) write_fn_ptr_decl(func &table.FnType, ptr_name string) { ret_styp := g.typ(func.func.return_type) g.write('$ret_styp (*$ptr_name) (') @@ -1830,8 +1729,9 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { // return; // } // int pos = *(int*)_t190.data; - mut tmp_opt := '' - is_optional := g.is_autofree && (assign_stmt.op in [.decl_assign, .assign]) + // mut tmp_opt := '' + /* + is_optional := false && g.is_autofree && (assign_stmt.op in [.decl_assign, .assign]) && assign_stmt.left_types.len == 1 && assign_stmt.right[0] is ast.CallExpr if is_optional { // g.write('/* optional assignment */') @@ -1849,6 +1749,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { // return } } + */ // json_test failed w/o this check if return_type != table.void_type && return_type != 0 { sym := g.table.get_type_symbol(return_type) @@ -2175,7 +2076,9 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { // Unwrap the optional now that the testing code has been prepended. // `pos := s.index(... // `int pos = *(int)_t10.data;` - if g.is_autofree { + // if g.is_autofree { + /* + if is_optional { g.write('*($styp*)') g.write(tmp_opt + '.data/*FFz*/') g.right_is_opt = false @@ -2185,6 +2088,7 @@ fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { } return } + */ } g.is_shared = var_type.has_flag(.shared_f) if !cloned { @@ -5619,7 +5523,7 @@ fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type table. if is_none_ok { g.writeln('if (!${cvar_name}.ok && !${cvar_name}.is_none) {') } else { - g.writeln('if (!${cvar_name}.ok) {') + g.writeln('if (!${cvar_name}.ok) { /*or block*/ ') } if or_block.kind == .block { if g.inside_or_block { diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index c1fb82d453..e6e284f61a 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -391,6 +391,7 @@ fn (mut g Gen) fn_args(args []table.Param, is_variadic bool) ([]string, []string } fn (mut g Gen) call_expr(node ast.CallExpr) { + // g.write('/*call expr*/') // NOTE: everything could be done this way // see my comment in parser near anon_fn if node.left is ast.AnonFn { @@ -408,20 +409,25 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { defer { g.inside_call = false } - gen_or := node.or_block.kind != .absent && !g.is_autofree - // if gen_or { - // g.writeln('/*start*/') - // } + gen_or := node.or_block.kind != .absent // && !g.is_autofree is_gen_or_and_assign_rhs := gen_or && g.is_assign_rhs - cur_line := if is_gen_or_and_assign_rhs && !g.is_autofree { + cur_line := if is_gen_or_and_assign_rhs { // && !g.is_autofree { + // `x := foo() or { ...}` + // cut everything that has been generated to prepend optional variable creation line := g.go_before_stmt(0) g.out.write_string(tabs[g.indent]) + // g.write('/*is_gen_or_and_assign_rhs*/') line } else { '' } + if gen_or && g.pref.autofree && g.inside_return { + // TODO optional return af hack (tmp_count gets increased in .return_statement()) + g.tmp_count-- + } tmp_opt := if gen_or { g.new_tmp_var() } else { '' } - if gen_or { + if gen_or && !g.inside_return { + // if is_gen_or_and_assign_rhs { styp := g.typ(node.return_type.set_flag(.optional)) g.write('$styp $tmp_opt = ') } @@ -437,16 +443,16 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { g.fn_call(node) } if gen_or { // && !g.autofree { - if !g.is_autofree { - g.or_block(tmp_opt, node.or_block, node.return_type) - } + // if !g.is_autofree { + g.or_block(tmp_opt, node.or_block, node.return_type) + //} if is_gen_or_and_assign_rhs { unwrapped_typ := node.return_type.clear_flag(.optional) unwrapped_styp := g.typ(unwrapped_typ) if unwrapped_typ == table.void_type { g.write('\n $cur_line') } else if g.table.get_type_symbol(node.return_type).kind == .multi_return { - g.write('\n $cur_line $tmp_opt') + g.write('\n $cur_line $tmp_opt /*U*/') } else { g.write('\n $cur_line *($unwrapped_styp*)${tmp_opt}.data') } diff --git a/vlib/v/tests/option_test.v b/vlib/v/tests/option_test.v index 4c84e22716..ca72286ff8 100644 --- a/vlib/v/tests/option_test.v +++ b/vlib/v/tests/option_test.v @@ -50,9 +50,7 @@ fn test_option_for_base_type_without_variable() { 0 } assert val == 42 - val = ret_none() or { - return - } + val = ret_none() or { return } assert false // This is invalid: // x := 5 or { @@ -101,30 +99,32 @@ fn foo_str() ?string { } fn propagate_optional(b bool) ?int { - a := err_call(b)? + a := err_call(b) ? return a } fn propagate_different_type(b bool) ?bool { - err_call(b)? + err_call(b) ? return true } fn test_propagation() { - a := propagate_optional(true) or { - 0 - } + println(1) + a := propagate_optional(true) or { 0 } + println(2) assert a == 42 + println(3) if _ := propagate_optional(false) { assert false } - b := propagate_different_type(true) or { - false - } + println(4) + b := propagate_different_type(true) or { false } assert b == true + println(5) if _ := propagate_different_type(false) { assert false } + println(6) } fn test_q() { @@ -132,23 +132,17 @@ fn test_q() { } fn or_return_val() int { - a := ret_none() or { - return 1 - } + a := ret_none() or { return 1 } return a } fn or_return_error() ?int { - a := ret_none() or { - return error('Nope') - } + a := ret_none() or { return error('Nope') } return a } fn or_return_none() ?int { - a := ret_none() or { - return none - } + a := ret_none() or { return none } return a } @@ -193,9 +187,7 @@ mut: } fn test_field_or() { - name := foo_str() or { - 'nada' - } + name := foo_str() or { 'nada' } assert name == 'something' /* QTODO @@ -225,8 +217,6 @@ mut: opt ?Thing } - - fn test_opt_field() { /* QTODO @@ -251,13 +241,9 @@ fn test_opt_ptr() { else { } a := 3 - mut r := opt_ptr(&a) or { - &int(0) - } + mut r := opt_ptr(&a) or { &int(0) } assert r == &a - r = opt_ptr(&int(0)) or { - return - } + r = opt_ptr(&int(0)) or { return } assert false } @@ -283,34 +269,34 @@ fn test_multi_return_opt() { */ fn test_optional_val_with_empty_or() { - ret_none() or {} + ret_none() or { } assert true } fn test_optional_void_return_types_of_anon_fn() { - f := fn(i int) ? { + f := fn (i int) ? { if i == 0 { - return error("0") + return error('0') } return } f(0) or { - assert err == "0" + assert err == '0' return } } struct Foo { - f fn(int) ? + f fn (int) ? } fn test_option_void_return_types_of_anon_fn_in_struct() { - foo := Foo { - f: fn(i int) ? { + foo := Foo{ + f: fn (i int) ? { if i == 0 { - return error("0") + return error('0') } return @@ -318,7 +304,7 @@ fn test_option_void_return_types_of_anon_fn_in_struct() { } foo.f(0) or { - assert err == "0" + assert err == '0' return } } @@ -377,3 +363,21 @@ struct MultiOptionalFieldTest { a ?int b ?int } + +/* +fn foo() ?int { + return 0 +} + +fn foo2() ?int { + for _ in 0 .. 5 { + return foo() or { continue } + } + return 0 +} + +fn test_return_or() { + x := foo2() or { return } + assert x == 0 +} +*/ diff --git a/vlib/v/tests/valgrind/1.strings_and_arrays.v b/vlib/v/tests/valgrind/1.strings_and_arrays.v index 115fb2d9a8..1676b3134b 100644 --- a/vlib/v/tests/valgrind/1.strings_and_arrays.v +++ b/vlib/v/tests/valgrind/1.strings_and_arrays.v @@ -210,15 +210,11 @@ fn if_expr() string { } fn return_if_expr() string { - return if true { - get_string('a' + 'b') - } else { - get_string('c' + 'd') - } + return if true { get_string('a' + 'b') } else { get_string('c' + 'd') } } fn loop_map() { - m := { + m := map{ 'UK': 'London' 'France': 'Paris' } @@ -337,6 +333,9 @@ fn comp_if() { println(s) } +fn anon_fn() { +} + fn main() { println('start') simple()