| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module c |
| 5 | |
| 6 | import v.ast |
| 7 | |
| 8 | enum AssertMetainfoKind { |
| 9 | pass |
| 10 | fail |
| 11 | panic |
| 12 | } |
| 13 | |
| 14 | fn (mut g Gen) assert_stmt(original_assert_statement ast.AssertStmt) { |
| 15 | if !original_assert_statement.is_used { |
| 16 | return |
| 17 | } |
| 18 | mut node := original_assert_statement |
| 19 | g.writeln('// assert') |
| 20 | |
| 21 | mut save_left := ast.empty_expr |
| 22 | mut save_right := ast.empty_expr |
| 23 | |
| 24 | if mut node.expr is ast.InfixExpr { |
| 25 | // Don't extract subexpressions to ctemps for && and || operators, |
| 26 | // as this would break short-circuit evaluation semantics. |
| 27 | // The right side of && must only execute after the left side is true. |
| 28 | if node.expr.op !in [.and, .logical_or] { |
| 29 | mut left_expr_type := node.expr.left_type |
| 30 | mut right_expr_type := node.expr.right_type |
| 31 | if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { |
| 32 | resolved_left_type := g.resolved_expr_type(node.expr.left, node.expr.left_type) |
| 33 | if resolved_left_type != 0 { |
| 34 | resolved_sym := g.table.sym(resolved_left_type) |
| 35 | left_sym := g.table.sym(left_expr_type) |
| 36 | if resolved_sym.kind !in [.sum_type, .interface] |
| 37 | || left_sym.kind in [.sum_type, .interface] { |
| 38 | left_expr_type = resolved_left_type |
| 39 | } |
| 40 | } |
| 41 | resolved_right_type := g.resolved_expr_type(node.expr.right, node.expr.right_type) |
| 42 | if resolved_right_type != 0 { |
| 43 | resolved_sym := g.table.sym(resolved_right_type) |
| 44 | right_sym := g.table.sym(right_expr_type) |
| 45 | if resolved_sym.kind !in [.sum_type, .interface] |
| 46 | || right_sym.kind in [.sum_type, .interface] { |
| 47 | right_expr_type = resolved_right_type |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | if subst_expr := g.assert_subexpression_to_ctemp(node.expr.left, left_expr_type) { |
| 52 | save_left = node.expr.left |
| 53 | node.expr.left = subst_expr |
| 54 | } |
| 55 | // For || and && operators, do not pre-evaluate the right side |
| 56 | // to allow short-circuit evaluation to work correctly. |
| 57 | if node.expr.op !in [.logical_or, .and] { |
| 58 | if subst_expr := g.assert_subexpression_to_ctemp(node.expr.right, right_expr_type) { |
| 59 | save_right = node.expr.right |
| 60 | node.expr.right = subst_expr |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | } |
| 65 | metaname := g.gen_assert_metainfo_common(node) |
| 66 | g.set_current_pos_as_last_stmt_pos() |
| 67 | g.inside_ternary++ |
| 68 | if g.pref.is_test { |
| 69 | g.write('if (') |
| 70 | prev_inside_ternary := g.inside_ternary |
| 71 | g.inside_ternary = 0 |
| 72 | g.expr(node.expr) |
| 73 | g.inside_ternary = prev_inside_ternary |
| 74 | g.write(')') |
| 75 | g.decrement_inside_ternary() |
| 76 | g.writeln(' {') |
| 77 | g.gen_assert_metainfo(node, .pass, metaname) |
| 78 | g.writeln('\tmain__TestRunner_name_table[test_runner._typ]._method_assert_pass(test_runner._object, &${metaname});') |
| 79 | g.writeln('} else {') |
| 80 | g.gen_assert_metainfo(node, .fail, metaname) |
| 81 | g.writeln('\tmain__TestRunner_name_table[test_runner._typ]._method_assert_fail(test_runner._object, &${metaname});') |
| 82 | g.gen_assert_postfailure_mode(node) |
| 83 | g.writeln('}') |
| 84 | } else { |
| 85 | g.write('if (!(') |
| 86 | prev_inside_ternary := g.inside_ternary |
| 87 | g.inside_ternary = 0 |
| 88 | g.expr(node.expr) |
| 89 | g.inside_ternary = prev_inside_ternary |
| 90 | g.write('))') |
| 91 | g.decrement_inside_ternary() |
| 92 | g.writeln(' {') |
| 93 | g.gen_assert_metainfo(node, .panic, metaname) |
| 94 | g.writeln('\tbuiltin____print_assert_failure(&${metaname});') |
| 95 | g.gen_assert_postfailure_mode(node) |
| 96 | g.writeln('}') |
| 97 | } |
| 98 | |
| 99 | if mut node.expr is ast.InfixExpr { |
| 100 | restore_left := node.expr.left is ast.CTempVar |
| 101 | restore_right := node.expr.right is ast.CTempVar |
| 102 | if restore_left { |
| 103 | node.expr.left = save_left |
| 104 | } |
| 105 | if restore_right { |
| 106 | node.expr.right = save_right |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | fn (mut g Gen) assert_subexpression_to_ctemp(expr ast.Expr, expr_type ast.Type) ?ast.Expr { |
| 112 | match expr { |
| 113 | ast.CallExpr { |
| 114 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 115 | } |
| 116 | ast.ParExpr { |
| 117 | if expr.expr is ast.CallExpr { |
| 118 | return g.new_ctemp_var_then_gen(ast.Expr(expr.expr), expr_type) |
| 119 | } |
| 120 | } |
| 121 | ast.PostfixExpr { |
| 122 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 123 | } |
| 124 | ast.SelectorExpr { |
| 125 | if expr.expr is ast.CallExpr { |
| 126 | sym := g.table.final_sym(g.unwrap_generic(expr.expr.return_type)) |
| 127 | if sym.kind == .struct { |
| 128 | if (sym.info as ast.Struct).is_union { |
| 129 | return none |
| 130 | } |
| 131 | } |
| 132 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 133 | } |
| 134 | if g.need_tmp_var_in_expr(expr) { |
| 135 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 136 | } |
| 137 | } |
| 138 | ast.LockExpr { |
| 139 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 140 | } |
| 141 | ast.ArrayInit { |
| 142 | if expr.has_callexpr || g.need_tmp_var_in_expr(expr) { |
| 143 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 144 | } |
| 145 | } |
| 146 | ast.StructInit { |
| 147 | if g.need_tmp_var_in_expr(expr) { |
| 148 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 149 | } |
| 150 | } |
| 151 | else { |
| 152 | if g.need_tmp_var_in_expr(expr) { |
| 153 | return g.new_ctemp_var_then_gen(expr, expr_type) |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | return none |
| 159 | } |
| 160 | |
| 161 | fn (mut g Gen) gen_assert_postfailure_mode(node ast.AssertStmt) { |
| 162 | g.write_v_source_line_info_stmt(node) |
| 163 | if g.pref.assert_failure_mode == .continues |
| 164 | || g.fn_decl.attrs.any(it.name == 'assert_continues') { |
| 165 | return |
| 166 | } |
| 167 | if g.pref.is_test && !g.inside_defer_generation && g.cur_fn != unsafe { nil } |
| 168 | && g.cur_fn.scope != unsafe { nil } { |
| 169 | g.write_defer_stmts_when_needed(g.innermost_active_defer_scope(node.pos), true, node.pos) |
| 170 | } |
| 171 | if g.pref.assert_failure_mode == .aborts || g.fn_decl.attrs.any(it.name == 'assert_aborts') { |
| 172 | g.writeln('\tabort();') |
| 173 | } |
| 174 | if g.pref.assert_failure_mode == .backtraces |
| 175 | || g.fn_decl.attrs.any(it.name == 'assert_backtraces') { |
| 176 | if _ := g.table.fns['print_backtrace'] { |
| 177 | g.writeln('\tbuiltin__print_backtrace();') |
| 178 | } |
| 179 | } |
| 180 | if g.pref.is_test { |
| 181 | g.writeln('\tlongjmp(g_jump_buffer, 1);') |
| 182 | } |
| 183 | if g.pref.assert_failure_mode != .continues { |
| 184 | g.writeln('\tbuiltin___v_panic(_S("Assertion failed..."));') |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt, kind AssertMetainfoKind, metaname string) { |
| 189 | if kind == .pass { |
| 190 | return |
| 191 | } |
| 192 | if node.extra is ast.EmptyExpr { |
| 193 | g.writeln('\t${metaname}.has_msg = false;') |
| 194 | g.writeln('\t${metaname}.message = _SLIT0;') |
| 195 | } else { |
| 196 | g.writeln('\t${metaname}.has_msg = true;') |
| 197 | g.write('\t${metaname}.message = ') |
| 198 | g.gen_assert_single_expr(node.extra, ast.string_type) |
| 199 | g.writeln(';') |
| 200 | } |
| 201 | match node.expr { |
| 202 | ast.InfixExpr { |
| 203 | mut left_type := if node.expr.left_ct_expr { |
| 204 | g.type_resolver.get_type_or_default(node.expr.left, node.expr.left_type) |
| 205 | } else { |
| 206 | node.expr.left_type |
| 207 | } |
| 208 | mut right_type := if node.expr.right_ct_expr { |
| 209 | g.type_resolver.get_type(node.expr.right) |
| 210 | } else { |
| 211 | node.expr.right_type |
| 212 | } |
| 213 | if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 { |
| 214 | resolved_left := g.resolved_expr_type(node.expr.left, node.expr.left_type) |
| 215 | if resolved_left != 0 { |
| 216 | resolved_sym := g.table.sym(resolved_left) |
| 217 | left_sym := g.table.sym(left_type) |
| 218 | if resolved_sym.kind !in [.sum_type, .interface] |
| 219 | || left_sym.kind in [.sum_type, .interface] { |
| 220 | left_type = resolved_left |
| 221 | } |
| 222 | } |
| 223 | resolved_right := g.resolved_expr_type(node.expr.right, node.expr.right_type) |
| 224 | if resolved_right != 0 { |
| 225 | resolved_sym := g.table.sym(resolved_right) |
| 226 | right_sym := g.table.sym(right_type) |
| 227 | if resolved_sym.kind !in [.sum_type, .interface] |
| 228 | || right_sym.kind in [.sum_type, .interface] { |
| 229 | right_type = resolved_right |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | g.write('\t${metaname}.lvalue = ') |
| 234 | g.gen_assert_single_expr(node.expr.left, left_type) |
| 235 | g.writeln(';') |
| 236 | // For && and || operators, use a placeholder for rvalue |
| 237 | // to avoid issues with go_before_last_stmt() which can generate |
| 238 | // temporary variables outside the conditional scope. |
| 239 | // - For &&: if left is false, right is short-circuited |
| 240 | // - For ||: if left is true, right is short-circuited |
| 241 | if node.expr.op in [.logical_or, .and] { |
| 242 | g.writeln('\t${metaname}.rvalue = _S("<short-circuited>");') |
| 243 | } else { |
| 244 | g.write('\t${metaname}.rvalue = ') |
| 245 | g.gen_assert_single_expr(node.expr.right, right_type) |
| 246 | g.writeln(';') |
| 247 | } |
| 248 | } |
| 249 | else {} |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | fn (mut g Gen) gen_assert_metainfo_common(node ast.AssertStmt) string { |
| 254 | mod_path := cestring(g.file.path) |
| 255 | fn_name := g.fn_decl.name |
| 256 | line_nr := node.pos.line_nr |
| 257 | mut src := node.expr.str() |
| 258 | if node.extra !is ast.EmptyExpr { |
| 259 | src += ', ' + node.extra.str() |
| 260 | } |
| 261 | src = cestring(src) |
| 262 | metaname := 'v_assert_meta_info_${g.new_tmp_var()}' |
| 263 | g.writeln('VAssertMetaInfo ${metaname} = {0};') |
| 264 | g.writeln('${metaname}.fpath = ${ctoslit(mod_path)};') |
| 265 | g.writeln('${metaname}.line_nr = ${line_nr};') |
| 266 | g.writeln('${metaname}.fn_name = ${ctoslit(fn_name)};') |
| 267 | metasrc := cnewlines(ctoslit(src)) |
| 268 | g.writeln('${metaname}.src = ${metasrc};') |
| 269 | g.writeln('${metaname}.has_msg = false;') |
| 270 | match node.expr { |
| 271 | ast.InfixExpr { |
| 272 | expr_op_str := ctoslit(node.expr.op.str()) |
| 273 | expr_left_str := cnewlines(ctoslit(node.expr.left.str())) |
| 274 | expr_right_str := cnewlines(ctoslit(node.expr.right.str())) |
| 275 | g.writeln('${metaname}.op = ${expr_op_str};') |
| 276 | g.writeln('${metaname}.llabel = ${expr_left_str};') |
| 277 | g.writeln('${metaname}.rlabel = ${expr_right_str};') |
| 278 | } |
| 279 | ast.CallExpr { |
| 280 | g.writeln('${metaname}.op = _S("call");') |
| 281 | } |
| 282 | else {} |
| 283 | } |
| 284 | |
| 285 | return metaname |
| 286 | } |
| 287 | |
| 288 | fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { |
| 289 | // eprintln('> gen_assert_single_expr typ: ${typ} | expr: ${expr} | typeof(expr): ${typeof(expr)}') |
| 290 | expr_str := '${expr}' |
| 291 | match expr { |
| 292 | ast.CastExpr { |
| 293 | if typ.is_float() || g.table.final_sym(typ).is_float() { |
| 294 | g.gen_expr_to_string(expr.expr, typ) |
| 295 | } else { |
| 296 | g.write(ctoslit(expr_str)) |
| 297 | } |
| 298 | } |
| 299 | ast.ParExpr { |
| 300 | g.gen_assert_single_expr(expr.expr, typ) |
| 301 | } |
| 302 | ast.IfExpr, ast.MatchExpr, ast.RangeExpr { |
| 303 | g.write(ctoslit(expr_str)) |
| 304 | } |
| 305 | ast.IndexExpr { |
| 306 | g.gen_expr_to_string(expr, typ) |
| 307 | } |
| 308 | ast.PrefixExpr { |
| 309 | if expr.right is ast.CastExpr { |
| 310 | // TODO: remove this check; |
| 311 | // vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails |
| 312 | // without special casing ast.CastExpr here |
| 313 | g.write(ctoslit(expr_str)) |
| 314 | } else if expr.right is ast.Ident { |
| 315 | g.write(ctoslit(expr_str)) |
| 316 | } else { |
| 317 | g.gen_expr_to_string(expr, typ) |
| 318 | } |
| 319 | } |
| 320 | ast.TypeNode { |
| 321 | sym := g.table.sym(g.unwrap_generic(typ)) |
| 322 | g.write(ctoslit('${sym.name}')) |
| 323 | } |
| 324 | else { |
| 325 | mut should_clone := true |
| 326 | if typ == ast.string_type |
| 327 | && expr in [ast.IndexExpr, ast.CallExpr, ast.StringLiteral, ast.StringInterLiteral] { |
| 328 | should_clone = false |
| 329 | } |
| 330 | if expr is ast.CTempVar && expr.orig is ast.CallExpr { |
| 331 | should_clone = false |
| 332 | if expr.orig.or_block.kind == .propagate_option { |
| 333 | should_clone = true |
| 334 | } |
| 335 | if expr.orig.is_method && expr.orig.args.len == 0 && expr.orig.name == 'type_name' { |
| 336 | should_clone = true |
| 337 | } |
| 338 | } |
| 339 | if should_clone { |
| 340 | g.write('builtin__string_clone(') |
| 341 | } |
| 342 | g.gen_expr_to_string(expr, typ) |
| 343 | if should_clone { |
| 344 | g.write(')') |
| 345 | } |
| 346 | } |
| 347 | } |
| 348 | } |
| 349 | |