cgen: print actual values on a failed assert (when possible)

pull/5154/head
Delyan Angelov 2020-06-01 14:43:31 +03:00
parent 39bd058acf
commit 945439dab6
5 changed files with 170 additions and 82 deletions

View File

@ -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}')
}

View File

@ -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}')
}
}

View File

@ -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 {

View File

@ -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()

View File

@ -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')
}