diff --git a/cmd/tools/preludes/tests_assertions.v b/cmd/tools/preludes/tests_assertions.v index bee5e9a923..3746f7f257 100644 --- a/cmd/tools/preludes/tests_assertions.v +++ b/cmd/tools/preludes/tests_assertions.v @@ -9,22 +9,26 @@ import os // / customizing the look & feel of the assertions results easier, // / since it is done in normal V code, instead of in embedded C ... // ////////////////////////////////////////////////////////////////// -fn cb_assertion_failed(filename string, line int, sourceline string, funcname string) { +fn cb_assertion_failed(i &VAssertMetaInfo) { // color_on := term.can_show_color_on_stderr() use_relative_paths := match os.getenv('VERROR_PATHS') { - 'absolute'{ + 'absolute' { false + } else { + true } - else { - true} } - final_filename := if use_relative_paths { filename } else { os.real_path(filename) } - final_funcname := funcname.replace('main__', '').replace('__', '.') - eprintln('$final_filename:$line: failed assert in ${final_funcname}') - eprintln('Source : $sourceline') + final_filename := if use_relative_paths { i.fpath } else { os.real_path(i.fpath) } + final_funcname := i.fn_name.replace('main__', '').replace('__', '.') + eprintln('$final_filename:${i.line_nr+1}: failed assert in ${final_funcname}') + eprintln('Source : ${i.src}') + if i.op != 'call' { + eprintln(' left value: ${i.llabel} = ${i.lvalue}') + eprintln(' right value: ${i.rlabel} = ${i.rvalue}') + } } -fn cb_assertion_ok(filename string, line int, sourceline string, funcname string) { +fn cb_assertion_ok(i &VAssertMetaInfo) { // do nothing for now on an OK assertion - // println('OK ${line:5d}|$sourceline ') - } + // println('OK ${(i.line_nr+1):5d}|${i.src}') +} diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index d7515cf26a..e146a1bcae 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -220,3 +220,25 @@ fn __as_cast(obj voidptr, obj_type, expected_type int) voidptr { } return obj } + +// VAssertMetaInfo is used during assertions. An instance of it +// is filled in by compile time generated code, when an assertion fails. +struct VAssertMetaInfo { +pub: + fpath string // the source file path of the assertion + line_nr int // the line number of the assertion + fn_name string // the function name in which the assertion is + src string // the actual source line of the assertion + op string // the operation of the assertion, i.e. '==', '<', 'call', etc ... + llabel string // the left side of the infix expressions as source + rlabel string // the right side of the infix expressions as source + lvalue string // the stringified *actual value* of the left side of a failed assertion + rvalue string // the stringified *actual value* of the right side of a failed assertion +} +fn __print_assert_failure(i &VAssertMetaInfo) { + eprintln('${i.fpath}:${i.line_nr+1}: FAIL: fn ${i.fn_name}: assert ${i.src}') + if i.op != 'call' { + eprintln(' left value: ${i.llabel} = ${i.lvalue}') + eprintln(' right value: ${i.rlabel} = ${i.rvalue}') + } +} diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 3603a0035d..ba26ac0095 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -85,6 +85,10 @@ pub fn (node &FnDecl) str(t &table.Table) string { return f.str() } +pub fn (x &InfixExpr) str() string { + return '${x.left.str()} $x.op.str() ${x.right.str()}' +} + // string representaiton of expr pub fn (x Expr) str() string { match x { diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 9fcb2f2cfb..f6e5b02ef8 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -897,6 +897,14 @@ fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type, expected_type table.Type) g.expr(expr) } +// cestring returns a V string, properly escaped for embeddeding in a C string literal. +fn cestring(s string) string { + return s.replace('\\', '\\\\').replace('"', "\'") +} +// ctoslit returns a 'tos_lit("$s")' call, where s is properly escaped. +fn ctoslit(s string) string { + return 'tos_lit("' + cestring(s) + '")' +} fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) { g.writeln('// assert') g.inside_ternary++ @@ -904,18 +912,15 @@ fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) { g.expr(a.expr) g.write(')') g.decrement_inside_ternary() - s_assertion := a.expr.str().replace('"', "\'") - mut mod_path := g.file.path - $if windows { - mod_path = g.file.path.replace('\\', '\\\\') - } if g.is_test { g.writeln('{') g.writeln(' g_test_oks++;') - g.writeln(' cb_assertion_ok( tos_lit("${mod_path}"), ${a.pos.line_nr+1}, tos_lit("assert ${s_assertion}"), tos_lit("${g.fn_decl.name}()") );') + metaname_ok := g.gen_assert_metainfo(a) + g.writeln(' cb_assertion_ok(&${metaname_ok});') g.writeln('}else{') g.writeln(' g_test_fails++;') - g.writeln(' cb_assertion_failed( tos_lit("${mod_path}"), ${a.pos.line_nr+1}, tos_lit("assert ${s_assertion}"), tos_lit("${g.fn_decl.name}()") );') + metaname_fail := g.gen_assert_metainfo(a) + g.writeln(' cb_assertion_failed(&${metaname_fail});') g.writeln(' exit(1);') g.writeln(' // TODO') g.writeln(' // Maybe print all vars in a test function if it fails?') @@ -923,12 +928,59 @@ fn (mut g Gen) gen_assert_stmt(a ast.AssertStmt) { return } g.writeln('{}else{') - g.writeln(' eprintln( tos_lit("${mod_path}:${a.pos.line_nr+1}: FAIL: fn ${g.fn_decl.name}(): assert $s_assertion"));') + metaname_panic := g.gen_assert_metainfo(a) + g.writeln(' __print_assert_failure(&${metaname_panic});') g.writeln(' v_panic(tos_lit("Assertion failed..."));') - g.writeln(' exit(1);') + g.writeln(' exit(1);') g.writeln('}') } +fn (mut g Gen) gen_assert_metainfo(a ast.AssertStmt) string { + mod_path := cestring(g.file.path) + fn_name := g.fn_decl.name + line_nr := a.pos.line_nr + src := cestring(a.expr.str()) + metaname := 'v_assert_meta_info_${g.new_tmp_var()}' + g.writeln(' VAssertMetaInfo $metaname;') + g.writeln(' memset(&$metaname, 0, sizeof(VAssertMetaInfo));') + g.writeln(' ${metaname}.fpath = ${ctoslit(mod_path)};') + g.writeln(' ${metaname}.line_nr = ${line_nr};') + g.writeln(' ${metaname}.fn_name = ${ctoslit(fn_name)};') + g.writeln(' ${metaname}.src = ${ctoslit(src)};') + match a.expr { + ast.InfixExpr { + g.writeln(' ${metaname}.op = ${ctoslit(it.op.str())};') + g.writeln(' ${metaname}.llabel = ${ctoslit(it.left.str())};') + g.writeln(' ${metaname}.rlabel = ${ctoslit(it.right.str())};') + g.write(' ${metaname}.lvalue = ') + g.gen_assert_single_expr(it.left, it.left_type) + g.writeln(';') + // + g.write(' ${metaname}.rvalue = ') + g.gen_assert_single_expr(it.right, it.right_type) + g.writeln(';') + } + ast.CallExpr { + g.writeln(' ${metaname}.op = tos_lit("call");') + } + else{} + } + return metaname +} + +fn (mut g Gen) gen_assert_single_expr(e ast.Expr, t table.Type) { + unknown_value := '*unknown value*' + match e { + ast.CallExpr { g.write( ctoslit(unknown_value) ) } + ast.CastExpr { g.write( ctoslit(unknown_value) ) } + ast.IndexExpr { g.write( ctoslit(unknown_value) ) } + ast.PrefixExpr { g.write( ctoslit(unknown_value) ) } + ast.MatchExpr { g.write( ctoslit(unknown_value) ) } + else{ g.gen_expr_to_string(e, t) } + } + g.write(' /* typeof: ' +typeof(e) + ' type: ' + t.str() + ' */ ') +} + fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { if assign_stmt.is_static { g.write('static ') @@ -3018,67 +3070,7 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { g.expr(expr) } } else if specs[i] == `s` { - sym := g.table.get_type_symbol(node.expr_types[i]) - sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() - if node.expr_types[i].flag_is(.variadic) { - str_fn_name := g.gen_str_for_type(node.expr_types[i]) - g.write('${str_fn_name}(') - g.expr(expr) - g.write(')') - } else if sym.kind == .enum_ { - is_var := match node.exprs[i] { - ast.SelectorExpr { true } - ast.Ident { true } - else { false } - } - if is_var { - str_fn_name := g.gen_str_for_type(node.expr_types[i]) - g.write('${str_fn_name}(') - g.enum_expr(expr) - g.write(')') - } else { - g.write('tos_lit("') - g.enum_expr(expr) - g.write('")') - } - } else if sym_has_str_method || sym.kind in [.array, .array_fixed, .map, .struct_] { - is_p := node.expr_types[i].is_ptr() - val_type := if is_p { node.expr_types[i].deref() } else { node.expr_types[i] } - str_fn_name := g.gen_str_for_type(val_type) - if is_p && str_method_expects_ptr { - g.write('string_add(_SLIT("&"), ${str_fn_name}( (') - } - if is_p && !str_method_expects_ptr { - g.write('string_add(_SLIT("&"), ${str_fn_name}( *(') - } - if !is_p && !str_method_expects_ptr { - g.write('${str_fn_name}( ') - } - if !is_p && str_method_expects_ptr { - g.write('${str_fn_name}( &') - } - g.expr(expr) - if sym.kind == .struct_ && !sym_has_str_method { - if is_p { - g.write('),0))') - } else { - g.write(',0)') - } - } else { - if is_p { - g.write(')))') - } else { - g.write(')') - } - } - } else if g.typ(node.expr_types[i]).starts_with('Option') { - str_fn_name := 'OptionBase_str' - g.write('${str_fn_name}(*(OptionBase*)&') - g.expr(expr) - g.write(')') - } else { - verror('cannot convert to string') - } + g.gen_expr_to_string(expr, node.expr_types[i]) } else { g.expr(expr) } @@ -3092,6 +3084,71 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { g.write(')') } +fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype table.Type) ?bool { + sym := g.table.get_type_symbol(etype) + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + if etype.flag_is(.variadic) { + str_fn_name := g.gen_str_for_type(etype) + g.write('${str_fn_name}(') + g.expr(expr) + g.write(')') + } else if sym.kind == .enum_ { + is_var := match expr { + ast.SelectorExpr { true } + ast.Ident { true } + else { false } + } + if is_var { + str_fn_name := g.gen_str_for_type(etype) + g.write('${str_fn_name}(') + g.enum_expr(expr) + g.write(')') + } else { + g.write('tos_lit("') + g.enum_expr(expr) + g.write('")') + } + } else if sym_has_str_method || sym.kind in [.array, .array_fixed, .map, .struct_] { + is_p := etype.is_ptr() + val_type := if is_p { etype.deref() } else { etype } + str_fn_name := g.gen_str_for_type(val_type) + if is_p && str_method_expects_ptr { + g.write('string_add(_SLIT("&"), ${str_fn_name}( (') + } + if is_p && !str_method_expects_ptr { + g.write('string_add(_SLIT("&"), ${str_fn_name}( *(') + } + if !is_p && !str_method_expects_ptr { + g.write('${str_fn_name}( ') + } + if !is_p && str_method_expects_ptr { + g.write('${str_fn_name}( &') + } + g.expr(expr) + if sym.kind == .struct_ && !sym_has_str_method { + if is_p { + g.write('),0))') + } else { + g.write(',0)') + } + } else { + if is_p { + g.write(')))') + } else { + g.write(')') + } + } + } else if g.typ(etype).starts_with('Option') { + str_fn_name := 'OptionBase_str' + g.write('${str_fn_name}(*(OptionBase*)&') + g.expr(expr) + g.write(')') + } else { + return error('cannot convert to string') + } + return true +} + // `nums.map(it % 2 == 0)` fn (mut g Gen) gen_map(node ast.CallExpr) { tmp := g.new_tmp_var() diff --git a/vlib/v/tests/sum_type_test.v b/vlib/v/tests/sum_type_test.v index d9ee2c1402..9d520059f8 100644 --- a/vlib/v/tests/sum_type_test.v +++ b/vlib/v/tests/sum_type_test.v @@ -9,7 +9,8 @@ struct IntegerLiteral { } fn handle(e Expr) string { - assert e is IntegerLiteral + is_literal := e is IntegerLiteral + assert is_literal if e is IntegerLiteral { println('int') }