Commit d8f6388b63

Andrew Kelley <superjoe30@gmail.com>
2016-02-10 02:50:53
if statements can be const expr evaluated
also introduce error for unnecessary if statement but if the condition depends on a compile variable, then the if statement is OK
1 parent f45c374
example/cat/main.zig
@@ -13,7 +13,7 @@ pub fn main(args: [][]u8) -> %void {
     for (arg, args[1...]) {
         if (arg == "-") {
             catted_anything = true;
-            %return cat_stream(stdin);
+            cat_stream(stdin) %% |err| return err;
         } else if (arg[0] == '-') {
             return usage(exe);
         } else {
@@ -24,11 +24,11 @@ pub fn main(args: [][]u8) -> %void {
             defer is.close();
 
             catted_anything = true;
-            %return cat_stream(is);
+            cat_stream(is) %% |err| return err;
         }
     }
     if (!catted_anything) {
-        %return cat_stream(stdin)
+        cat_stream(stdin) %% |err| return err;
     }
 }
 
src/all_types.hpp
@@ -1049,6 +1049,19 @@ struct BuiltinFnEntry {
     LLVMValueRef fn_val;
 };
 
+enum CIntType {
+    CIntTypeShort,
+    CIntTypeUShort,
+    CIntTypeInt,
+    CIntTypeUInt,
+    CIntTypeLong,
+    CIntTypeULong,
+    CIntTypeLongLong,
+    CIntTypeULongLong,
+
+    CIntTypeCount,
+};
+
 struct CodeGen {
     LLVMModuleRef module;
     ZigList<ErrorMsg*> errors;
@@ -1072,7 +1085,7 @@ struct CodeGen {
     struct {
         TypeTableEntry *entry_bool;
         TypeTableEntry *entry_int[2][4]; // [signed,unsigned][8,16,32,64]
-        TypeTableEntry *entry_c_int[8];
+        TypeTableEntry *entry_c_int[CIntTypeCount];
         TypeTableEntry *entry_c_long_double;
         TypeTableEntry *entry_u8;
         TypeTableEntry *entry_u16;
@@ -1196,16 +1209,6 @@ struct BlockContext {
     Buf *c_import_buf;
 };
 
-enum CIntType {
-    CIntTypeShort,
-    CIntTypeUShort,
-    CIntTypeInt,
-    CIntTypeUInt,
-    CIntTypeLong,
-    CIntTypeULong,
-    CIntTypeLongLong,
-    CIntTypeULongLong,
-};
 
 
 #endif
src/analyze.cpp
@@ -2159,6 +2159,7 @@ static TypeTableEntry *analyze_container_init_expr(CodeGen *g, ImportTableEntry
                     &get_resolved_expr(val_field_node->data.struct_val_field.expr)->const_val;
                 if (field_val->ok) {
                     const_val->data.x_struct.fields[field_index] = field_val;
+                    const_val->depends_on_compile_var = const_val->depends_on_compile_var || field_val->depends_on_compile_var;
                 } else {
                     const_val->ok = false;
                 }
@@ -2197,6 +2198,8 @@ static TypeTableEntry *analyze_container_init_expr(CodeGen *g, ImportTableEntry
                 ConstExprValue *elem_const_val = &get_resolved_expr(*elem_node)->const_val;
                 if (elem_const_val->ok) {
                     const_val->data.x_array.fields[i] = elem_const_val;
+                    const_val->depends_on_compile_var = const_val->depends_on_compile_var ||
+                        elem_const_val->depends_on_compile_var;
                 } else {
                     const_val->ok = false;
                 }
@@ -2431,9 +2434,12 @@ static TypeTableEntry *resolve_expr_const_val_as_err(CodeGen *g, AstNode *node,
     return g->builtin_types.entry_pure_error;
 }
 
-static TypeTableEntry *resolve_expr_const_val_as_bool(CodeGen *g, AstNode *node, bool value) {
+static TypeTableEntry *resolve_expr_const_val_as_bool(CodeGen *g, AstNode *node, bool value,
+        bool depends_on_compile_var)
+{
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
+    expr->const_val.depends_on_compile_var = depends_on_compile_var;
     expr->const_val.data.x_bool = value;
     return g->builtin_types.entry_bool;
 }
@@ -2817,7 +2823,8 @@ static TypeTableEntry *analyze_bool_bin_op_expr(CodeGen *g, ImportTableEntry *im
         zig_unreachable();
     }
 
-    return resolve_expr_const_val_as_bool(g, node, answer);
+    bool depends_on_compile_var = op1_val->depends_on_compile_var || op2_val->depends_on_compile_var;
+    return resolve_expr_const_val_as_bool(g, node, answer, depends_on_compile_var);
 }
 
 static TypeTableEntry *analyze_logic_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
@@ -2844,7 +2851,8 @@ static TypeTableEntry *analyze_logic_bin_op_expr(CodeGen *g, ImportTableEntry *i
     }
 
     bool answer = eval_bool_bin_op_bool(op1_val->data.x_bool, bin_op_type, op2_val->data.x_bool);
-    return resolve_expr_const_val_as_bool(g, node, answer);
+    bool depends_on_compile_var = op1_val->depends_on_compile_var || op2_val->depends_on_compile_var;
+    return resolve_expr_const_val_as_bool(g, node, answer, depends_on_compile_var);
 }
 
 static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
@@ -3001,6 +3009,8 @@ static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import,
                 }
                 ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
                 const_val->ok = true;
+                const_val->depends_on_compile_var = op1_val->depends_on_compile_var ||
+                    op2_val->depends_on_compile_var;
 
                 ConstExprValue *all_fields = allocate<ConstExprValue>(2);
                 ConstExprValue *ptr_field = &all_fields[0];
@@ -3444,51 +3454,111 @@ static TypeTableEntry *analyze_continue_expr(CodeGen *g, ImportTableEntry *impor
     return g->builtin_types.entry_unreachable;
 }
 
-static TypeTableEntry *analyze_if_then_else(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *then_block, AstNode *else_node, AstNode *parent_node)
+static TypeTableEntry *analyze_if(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+        TypeTableEntry *expected_type, AstNode *node,
+        AstNode **then_node, AstNode **else_node, bool cond_is_const, bool cond_bool_val)
 {
-    TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type, then_block);
-
-    TypeTableEntry *else_type;
-    if (else_node) {
-        else_type = analyze_expression(g, import, context, expected_type, else_node);
-    } else {
-        else_type = resolve_type_compatibility(g, import, context, parent_node, expected_type,
-                g->builtin_types.entry_void);
+    if (!*else_node) {
+        *else_node = create_ast_void_node(g, import, node);
+        normalize_parent_ptrs(node);
     }
 
+    TypeTableEntry *then_type = analyze_expression(g, import, context, expected_type, *then_node);
+    TypeTableEntry *else_type = analyze_expression(g, import, context, expected_type, *else_node);
 
+    if (then_type->id == TypeTableEntryIdInvalid || else_type->id == TypeTableEntryIdInvalid) {
+        return g->builtin_types.entry_invalid;
+    }
+
+    TypeTableEntry *result_type;
     if (expected_type) {
-        return (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
+        result_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
     } else {
-        AstNode *op_nodes[] = {then_block, else_node};
+        AstNode *op_nodes[] = {*then_node, *else_node};
         TypeTableEntry *op_types[] = {then_type, else_type};
-        return resolve_peer_type_compatibility(g, import, context, parent_node, op_nodes, op_types, 2);
+        result_type = resolve_peer_type_compatibility(g, import, context, node, op_nodes, op_types, 2);
+    }
+
+    if (!cond_is_const) {
+        return result_type;
     }
+
+    ConstExprValue *other_const_val;
+    if (cond_bool_val) {
+        other_const_val = &get_resolved_expr(*then_node)->const_val;
+    } else {
+        other_const_val = &get_resolved_expr(*else_node)->const_val;
+    }
+    if (!other_const_val->ok) {
+        return result_type;
+    }
+
+    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+    *const_val = *other_const_val;
+    return result_type;
 }
 
 static TypeTableEntry *analyze_if_bool_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
         TypeTableEntry *expected_type, AstNode *node)
 {
-    analyze_expression(g, import, context, g->builtin_types.entry_bool, node->data.if_bool_expr.condition);
+    AstNode **cond = &node->data.if_bool_expr.condition;
+    TypeTableEntry *cond_type = analyze_expression(g, import, context, g->builtin_types.entry_bool, *cond);
+
+    if (cond_type->id == TypeTableEntryIdInvalid) {
+        return cond_type;
+    }
 
-    return analyze_if_then_else(g, import, context, expected_type,
-            node->data.if_bool_expr.then_block,
-            node->data.if_bool_expr.else_node,
-            node);
+    ConstExprValue *cond_val = &get_resolved_expr(*cond)->const_val;
+    if (cond_val->ok && !cond_val->depends_on_compile_var) {
+        const char *str_val = cond_val->data.x_bool ? "true" : "false";
+        add_node_error(g, first_executing_node(*cond),
+                buf_sprintf("condition is always %s; unnecessary if statement", str_val));
+    }
+
+    bool cond_is_const = cond_val->ok;
+    bool cond_bool_val = cond_val->data.x_bool;
+
+    AstNode **then_node = &node->data.if_bool_expr.then_block;
+    AstNode **else_node = &node->data.if_bool_expr.else_node;
+
+    return analyze_if(g, import, context, expected_type, node,
+            then_node, else_node, cond_is_const, cond_bool_val);
 }
 
-static TypeTableEntry *analyze_if_var_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+static TypeTableEntry *analyze_if_var_expr(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
         TypeTableEntry *expected_type, AstNode *node)
 {
     assert(node->type == NodeTypeIfVarExpr);
 
-    BlockContext *child_context = new_block_context(node, context);
+    BlockContext *child_context = new_block_context(node, parent_context);
 
     analyze_variable_declaration_raw(g, import, child_context, node, &node->data.if_var_expr.var_decl, true);
+    VariableTableEntry *var = node->data.if_var_expr.var_decl.variable;
+    if (var->type->id == TypeTableEntryIdInvalid) {
+        return g->builtin_types.entry_invalid;
+    }
+    AstNode *var_expr_node = node->data.if_var_expr.var_decl.expr;
+    ConstExprValue *var_const_val = &get_resolved_expr(var_expr_node)->const_val;
+    bool cond_is_const = var_const_val->ok;
+    bool cond_bool_val = cond_is_const ? (var_const_val->data.x_maybe != nullptr) : false;
+
+
+    AstNode **then_node = &node->data.if_var_expr.then_block;
+    AstNode **else_node = &node->data.if_var_expr.else_node;
 
-    return analyze_if_then_else(g, import, child_context, expected_type,
-            node->data.if_var_expr.then_block, node->data.if_var_expr.else_node, node);
+    return analyze_if(g, import, child_context, expected_type,
+            node, then_node, else_node, cond_is_const, cond_bool_val);
+}
+
+static bool int_type_depends_on_compile_var(CodeGen *g, TypeTableEntry *int_type) {
+    assert(int_type->id == TypeTableEntryIdInt);
+
+    for (int i = 0; i < CIntTypeCount; i += 1) {
+        if (int_type == g->builtin_types.entry_c_int[i]) {
+            return true;
+        }
+    }
+    return false;
 }
 
 static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *import, BlockContext *context,
@@ -3504,6 +3574,7 @@ static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *impor
     } else if (type_entry->id == TypeTableEntryIdInt) {
         ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
         const_val->ok = true;
+        const_val->depends_on_compile_var = int_type_depends_on_compile_var(g, type_entry);
         if (is_max) {
             if (type_entry->data.integral.is_signed) {
                 int64_t val;
@@ -3558,7 +3629,7 @@ static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *impor
         zig_panic("TODO analyze_min_max_value float");
         return type_entry;
     } else if (type_entry->id == TypeTableEntryIdBool) {
-        return resolve_expr_const_val_as_bool(g, node, is_max);
+        return resolve_expr_const_val_as_bool(g, node, is_max, false);
     } else {
         add_node_error(g, node,
                 buf_sprintf(err_format, buf_ptr(&type_entry->name)));
@@ -3573,6 +3644,8 @@ static void eval_const_expr_implicit_cast(CodeGen *g, AstNode *node, AstNode *ex
     if (!other_val->ok) {
         return;
     }
+    const_val->depends_on_compile_var = other_val->depends_on_compile_var;
+
     assert(other_val != const_val);
     switch (node->data.fn_call_expr.cast_op) {
         case CastOpNoCast:
@@ -4132,11 +4205,11 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
                 const_val->depends_on_compile_var = true;
 
                 if (buf_eql_str(&var_name, "is_big_endian")) {
-                    return resolve_expr_const_val_as_bool(g, node, g->is_big_endian);
+                    return resolve_expr_const_val_as_bool(g, node, g->is_big_endian, true);
                 } else if (buf_eql_str(&var_name, "is_release")) {
-                    return resolve_expr_const_val_as_bool(g, node, g->is_release_build);
+                    return resolve_expr_const_val_as_bool(g, node, g->is_release_build, true);
                 } else if (buf_eql_str(&var_name, "is_test")) {
-                    return resolve_expr_const_val_as_bool(g, node, g->is_test_build);
+                    return resolve_expr_const_val_as_bool(g, node, g->is_test_build, true);
                 } else {
                     add_node_error(g, *str_node,
                         buf_sprintf("unrecognized compile variable: '%s'", buf_ptr(&var_name)));
@@ -4353,7 +4426,7 @@ static TypeTableEntry *analyze_prefix_op_expr(CodeGen *g, ImportTableEntry *impo
                 }
 
                 bool answer = !target_const_val->data.x_bool;
-                return resolve_expr_const_val_as_bool(g, node, answer);
+                return resolve_expr_const_val_as_bool(g, node, answer, target_const_val->depends_on_compile_var);
             }
         case PrefixOpBinNot:
             {
@@ -4390,6 +4463,7 @@ static TypeTableEntry *analyze_prefix_op_expr(CodeGen *g, ImportTableEntry *impo
                     }
                     ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
                     const_val->ok = true;
+                    const_val->depends_on_compile_var = target_const_val->depends_on_compile_var;
                     bignum_negate(&const_val->data.x_bignum, &target_const_val->data.x_bignum);
                     return expr_type;
                 } else {
@@ -4880,7 +4954,7 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
                     node->data.char_literal.value);
             break;
         case NodeTypeBoolLiteral:
-            return_type = resolve_expr_const_val_as_bool(g, node, node->data.bool_literal.value);
+            return_type = resolve_expr_const_val_as_bool(g, node, node->data.bool_literal.value, false);
             break;
         case NodeTypeNullLiteral:
             return_type = analyze_null_literal_expr(g, import, context, expected_type, node);
src/codegen.cpp
@@ -1737,81 +1737,60 @@ static LLVMValueRef gen_return_expr(CodeGen *g, AstNode *node) {
     zig_unreachable();
 }
 
-static LLVMValueRef gen_defer(CodeGen *g, AstNode *node) {
-    assert(node->type == NodeTypeDefer);
-
-
-    return nullptr;
-}
-
 static LLVMValueRef gen_if_bool_expr_raw(CodeGen *g, AstNode *source_node, LLVMValueRef cond_value,
         AstNode *then_node, AstNode *else_node)
 {
-    TypeTableEntry *then_type = get_expr_type(then_node);
-    bool use_expr_value = (then_type->id != TypeTableEntryIdUnreachable &&
-                           then_type->id != TypeTableEntryIdVoid);
-
-    if (else_node) {
-        LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then");
-        LLVMBasicBlockRef else_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Else");
-
-        LLVMBasicBlockRef endif_block;
-        bool then_endif_reachable = get_expr_type(then_node)->id != TypeTableEntryIdUnreachable;
-        bool else_endif_reachable = get_expr_type(else_node)->id != TypeTableEntryIdUnreachable;
-        if (then_endif_reachable || else_endif_reachable) {
-            endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf");
-        }
+    assert(then_node);
+    assert(else_node);
 
-        LLVMBuildCondBr(g->builder, cond_value, then_block, else_block);
-
-        LLVMPositionBuilderAtEnd(g->builder, then_block);
-        LLVMValueRef then_expr_result = gen_expr(g, then_node);
-        if (then_endif_reachable) {
-            LLVMBuildBr(g->builder, endif_block);
-        }
-        LLVMBasicBlockRef after_then_block = LLVMGetInsertBlock(g->builder);
-
-        LLVMPositionBuilderAtEnd(g->builder, else_block);
-        LLVMValueRef else_expr_result = gen_expr(g, else_node);
-        if (else_endif_reachable) {
-            LLVMBuildBr(g->builder, endif_block);
-        }
-        LLVMBasicBlockRef after_else_block = LLVMGetInsertBlock(g->builder);
+    TypeTableEntry *then_type = get_expr_type(then_node);
+    TypeTableEntry *else_type = get_expr_type(else_node);
 
-        if (then_endif_reachable || else_endif_reachable) {
-            LLVMPositionBuilderAtEnd(g->builder, endif_block);
-            if (use_expr_value) {
-                LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(then_expr_result), "");
-                LLVMValueRef incoming_values[2] = {then_expr_result, else_expr_result};
-                LLVMBasicBlockRef incoming_blocks[2] = {after_then_block, after_else_block};
-                LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
+    bool use_then_value = type_has_bits(then_type);
+    bool use_else_value = type_has_bits(else_type);
 
-                return phi;
-            }
-        }
+    LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then");
+    LLVMBasicBlockRef else_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Else");
 
-        return nullptr;
+    LLVMBasicBlockRef endif_block;
+    bool then_endif_reachable = then_type->id != TypeTableEntryIdUnreachable;
+    bool else_endif_reachable = else_type->id != TypeTableEntryIdUnreachable;
+    if (then_endif_reachable || else_endif_reachable) {
+        endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf");
     }
 
-    assert(!use_expr_value || then_type->id == TypeTableEntryIdErrorUnion);
-
-    LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then");
-    LLVMBasicBlockRef endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf");
-
-    LLVMBuildCondBr(g->builder, cond_value, then_block, endif_block);
+    LLVMBuildCondBr(g->builder, cond_value, then_block, else_block);
 
     LLVMPositionBuilderAtEnd(g->builder, then_block);
-    gen_expr(g, then_node);
-    if (get_expr_type(then_node)->id != TypeTableEntryIdUnreachable)
+    LLVMValueRef then_expr_result = gen_expr(g, then_node);
+    if (then_endif_reachable) {
         LLVMBuildBr(g->builder, endif_block);
+    }
+    LLVMBasicBlockRef after_then_block = LLVMGetInsertBlock(g->builder);
 
-    LLVMPositionBuilderAtEnd(g->builder, endif_block);
-
-    if (use_expr_value) {
-        return LLVMConstNull(g->err_tag_type->type_ref);
-    } else {
-        return nullptr;
+    LLVMPositionBuilderAtEnd(g->builder, else_block);
+    LLVMValueRef else_expr_result = gen_expr(g, else_node);
+    if (else_endif_reachable) {
+        LLVMBuildBr(g->builder, endif_block);
     }
+    LLVMBasicBlockRef after_else_block = LLVMGetInsertBlock(g->builder);
+
+    if (then_endif_reachable || else_endif_reachable) {
+        LLVMPositionBuilderAtEnd(g->builder, endif_block);
+        if (use_then_value && use_else_value) {
+            LLVMValueRef phi = LLVMBuildPhi(g->builder, LLVMTypeOf(then_expr_result), "");
+            LLVMValueRef incoming_values[2] = {then_expr_result, else_expr_result};
+            LLVMBasicBlockRef incoming_blocks[2] = {after_then_block, after_else_block};
+            LLVMAddIncoming(phi, incoming_values, incoming_blocks, 2);
+            return phi;
+        } else if (use_then_value) {
+            return then_expr_result;
+        } else if (use_else_value) {
+            return else_expr_result;
+        }
+    }
+
+    return nullptr;
 }
 
 static LLVMValueRef gen_if_bool_expr(CodeGen *g, AstNode *node) {
@@ -1866,14 +1845,6 @@ static LLVMValueRef gen_if_var_expr(CodeGen *g, AstNode *node) {
     return return_value;
 }
 
-//static int block_exit_path_count(BlockContext *block_context) {
-//    int sum = 0;
-//    for (int i = 0; i < BlockExitPathCount; i += 1) {
-//        sum += block_context->block_exit_paths[i] ? 1 : 0;
-//    }
-//    return sum;
-//}
-
 static LLVMValueRef gen_block(CodeGen *g, AstNode *block_node, TypeTableEntry *implicit_return_type) {
     assert(block_node->type == NodeTypeBlock);
 
@@ -2553,7 +2524,8 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
         case NodeTypeReturnExpr:
             return gen_return_expr(g, node);
         case NodeTypeDefer:
-            return gen_defer(g, node);
+            // nothing to do
+            return nullptr;
         case NodeTypeVariableDeclaration:
             return gen_var_decl_expr(g, node);
         case NodeTypePrefixOpExpr:
@@ -3191,6 +3163,7 @@ static const CIntTypeInfo c_int_type_infos[] = {
 
 static int get_c_type_size_in_bits(CodeGen *g, CIntType id) {
     // TODO other architectures besides x86_64
+    // other operating systems besides linux
     switch (id) {
         case CIntTypeShort:
         case CIntTypeUShort:
@@ -3203,6 +3176,8 @@ static int get_c_type_size_in_bits(CodeGen *g, CIntType id) {
         case CIntTypeLongLong:
         case CIntTypeULongLong:
             return 64;
+        case CIntTypeCount:
+            zig_unreachable();
     }
     zig_unreachable();
 }
std/test_runner.zig
@@ -16,6 +16,7 @@ pub fn run_tests() -> %void {
         %%stderr.print_str(" ");
         %%stderr.print_str(test_fn.name);
         %%stderr.print_str("...");
+        %%stderr.flush();
 
         test_fn.func();
 
test/run_tests.cpp
@@ -225,26 +225,6 @@ pub fn foo_function() -> bool {
         )SOURCE");
     }
 
-    add_simple_case("if statements", R"SOURCE(
-import "std.zig";
-
-pub fn main(args: [][]u8) -> %void {
-    if (1 != 0) {
-        %%stdout.printf("1 is true\n");
-    } else {
-        %%stdout.printf("1 is false\n");
-    }
-    if (0 != 0) {
-        %%stdout.printf("0 is true\n");
-    } else if (1 - 1 != 0) {
-        %%stdout.printf("1 - 1 is true\n");
-    }
-    if (!(0 != 0)) {
-        %%stdout.printf("!0 is true\n");
-    }
-}
-    )SOURCE", "1 is true\n!0 is true\n");
-
     add_simple_case("params", R"SOURCE(
 import "std.zig";
 
@@ -259,46 +239,6 @@ pub fn main(args: [][]u8) -> %void {
 }
     )SOURCE", "pass\n");
 
-    add_simple_case("local variables", R"SOURCE(
-import "std.zig";
-
-pub fn main(args: [][]u8) -> %void {
-    const a : i32 = 1;
-    const b = i32(2);
-    if (a + b == 3) {
-        %%stdout.printf("OK\n");
-    }
-}
-    )SOURCE", "OK\n");
-
-    add_simple_case("bool literals", R"SOURCE(
-import "std.zig";
-
-pub fn main(args: [][]u8) -> %void {
-    if (true)   { %%stdout.printf("OK 1\n"); }
-    if (false)  { %%stdout.printf("BAD 1\n"); }
-    if (!true)  { %%stdout.printf("BAD 2\n"); }
-    if (!false) { %%stdout.printf("OK 2\n"); }
-}
-    )SOURCE", "OK 1\nOK 2\n");
-
-    add_simple_case("separate block scopes", R"SOURCE(
-import "std.zig";
-
-pub fn main(args: [][]u8) -> %void {
-    if (true) {
-        const no_conflict : i32 = 5;
-        if (no_conflict == 5) { %%stdout.printf("OK 1\n"); }
-    }
-
-    const c = {
-        const no_conflict = i32(10);
-        no_conflict
-    };
-    if (c == 10) { %%stdout.printf("OK 2\n"); }
-}
-    )SOURCE", "OK 1\nOK 2\n");
-
     add_simple_case("void parameters", R"SOURCE(
 import "std.zig";
 
@@ -314,48 +254,6 @@ fn void_fun(a : i32, b : void, c : i32) {
 }
     )SOURCE", "OK\n");
 
-    add_simple_case("void struct fields", R"SOURCE(
-import "std.zig";
-struct Foo {
-    a : void,
-    b : i32,
-    c : void,
-}
-pub fn main(args: [][]u8) -> %void {
-    const foo = Foo {
-        .a = void{},
-        .b = 1,
-        .c = void{},
-    };
-    if (foo.b != 1) {
-        %%stdout.printf("BAD\n");
-    }
-    if (@sizeof(Foo) != 4) {
-        %%stdout.printf("BAD\n");
-    }
-    %%stdout.printf("OK\n");
-}
-
-    )SOURCE", "OK\n");
-
-    add_simple_case("void arrays", R"SOURCE(
-import "std.zig";
-
-pub fn main(args: [][]u8) -> %void {
-    var array: [4]void = undefined;
-    array[0] = void{};
-    array[1] = array[2];
-    if (@sizeof(@typeof(array)) != 0) {
-        %%stdout.printf("BAD sizeof\n");
-    }
-    if (array.len != 4) {
-        %%stdout.printf("BAD len\n");
-    }
-    %%stdout.printf("OK\n");
-}
-    )SOURCE", "OK\n");
-
-
     add_simple_case("mutable local variables", R"SOURCE(
 import "std.zig";
 
@@ -414,27 +312,6 @@ pub fn main(args: [][]u8) -> %void {
     )SOURCE", "Hello, world!\n");
 
 
-    add_simple_case("a + b + c", R"SOURCE(
-import "std.zig";
-
-pub fn main(args: [][]u8) -> %void {
-    if (false || false || false) { %%stdout.printf("BAD 1\n"); }
-    if (true && true && false)   { %%stdout.printf("BAD 2\n"); }
-    if (1 | 2 | 4 != 7)          { %%stdout.printf("BAD 3\n"); }
-    if (3 ^ 6 ^ 8 != 13)         { %%stdout.printf("BAD 4\n"); }
-    if (7 & 14 & 28 != 4)        { %%stdout.printf("BAD 5\n"); }
-    if (9  << 1 << 2 != 9  << 3) { %%stdout.printf("BAD 6\n"); }
-    if (90 >> 1 >> 2 != 90 >> 3) { %%stdout.printf("BAD 7\n"); }
-    if (100 - 1 + 1000 != 1099)  { %%stdout.printf("BAD 8\n"); }
-    if (5 * 4 / 2 % 3 != 1)      { %%stdout.printf("BAD 9\n"); }
-    if (i32(i32(5)) != 5)        { %%stdout.printf("BAD 10\n"); }
-    if (!!false)                 { %%stdout.printf("BAD 11\n"); }
-    if (i32(7) != --(i32(7)))    { %%stdout.printf("BAD 12\n"); }
-
-    %%stdout.printf("OK\n");
-}
-    )SOURCE", "OK\n");
-
     add_simple_case("short circuit", R"SOURCE(
 import "std.zig";
 
@@ -729,39 +606,6 @@ pub fn main(args: [][]u8) -> %void {
 }
     )SOURCE", "loop\nloop\nloop\nloop\n");
 
-    add_simple_case("maybe type", R"SOURCE(
-import "std.zig";
-pub fn main(args: [][]u8) -> %void {
-    const x : ?bool = true;
-
-    if (const y ?= x) {
-        if (y) {
-            %%stdout.printf("x is true\n");
-        } else {
-            %%stdout.printf("x is false\n");
-        }
-    } else {
-        %%stdout.printf("x is none\n");
-    }
-
-    const next_x : ?i32 = null;
-
-    const z = next_x ?? 1234;
-
-    if (z != 1234) {
-        %%stdout.printf("BAD\n");
-    }
-
-    const final_x : ?i32 = 13;
-
-    const num = final_x ?? unreachable{};
-
-    if (num != 13) {
-        %%stdout.printf("BAD\n");
-    }
-}
-    )SOURCE", "x is true\n");
-
     add_simple_case("implicit cast after unreachable", R"SOURCE(
 import "std.zig";
 pub fn main(args: [][]u8) -> %void {
@@ -971,73 +815,6 @@ fn print_ok(val: @typeof(x)) -> @typeof(foo) {
 const foo : i32 = 0;
     )SOURCE", "OK\n");
 
-    add_simple_case("enum type", R"SOURCE(
-import "std.zig";
-
-struct Point {
-    x: u64,
-    y: u64,
-}
-
-enum Foo {
-    One: i32,
-    Two: Point,
-    Three: void,
-}
-
-enum Bar {
-    A,
-    B,
-    C,
-    D,
-}
-
-pub fn main(args: [][]u8) -> %void {
-    const foo1 = Foo.One(13);
-    const foo2 = Foo.Two(Point { .x = 1234, .y = 5678, });
-    const bar = Bar.B;
-
-    if (bar != Bar.B) {
-        %%stdout.printf("BAD 1\n");
-    }
-
-    if (@member_count(Foo) != 3) {
-        %%stdout.printf("BAD 2\n");
-    }
-
-    if (@member_count(Bar) != 4) {
-        %%stdout.printf("BAD 3\n");
-    }
-
-    if (@sizeof(Foo) != 24) {
-        %%stdout.printf("BAD 4\n");
-    }
-    if (@sizeof(Bar) != 1) {
-        %%stdout.printf("BAD 5\n");
-    }
-
-    %%stdout.printf("OK\n");
-}
-    )SOURCE", "OK\n");
-
-    add_simple_case("array literal", R"SOURCE(
-import "std.zig";
-
-pub fn main(args: [][]u8) -> %void {
-    const HEX_MULT = []u16{4096, 256, 16, 1};
-
-    if (HEX_MULT.len != 4) {
-        %%stdout.printf("BAD\n");
-    }
-
-    if (HEX_MULT[1] != 256) {
-        %%stdout.printf("BAD\n");
-    }
-
-    %%stdout.printf("OK\n");
-}
-    )SOURCE", "OK\n");
-
     add_simple_case("nested arrays", R"SOURCE(
 import "std.zig";
 
@@ -1092,23 +869,6 @@ fn fn3() -> u32 {7}
 fn fn4() -> u32 {8}
     )SOURCE", "5\n6\n7\n8\n");
 
-    add_simple_case("const number literal", R"SOURCE(
-import "std.zig";
-
-const ten = 10;
-
-pub fn main(args: [][]u8) -> %void {
-    const one = 1;
-    const eleven = ten + one;
-
-    if (eleven != 11) {
-        %%stdout.printf("BAD\n");
-    }
-
-    %%stdout.printf("OK\n");
-}
-    )SOURCE", "OK\n");
-
     add_simple_case("statically initialized struct", R"SOURCE(
 import "std.zig";
 struct Foo {
@@ -1139,21 +899,6 @@ pub fn main(args: [][]u8) -> %void {
 }
     )SOURCE", "OK\n");
 
-    add_simple_case("error values", R"SOURCE(
-import "std.zig";
-error err1;
-error err2;
-pub fn main(args: [][]u8) -> %void {
-    const a = i32(error.err1);
-    const b = i32(error.err2);
-    if (a == b) {
-        %%stdout.printf("BAD\n");
-    }
-
-    %%stdout.printf("OK\n");
-}
-    )SOURCE", "OK\n");
-
     add_simple_case("return with implicit cast from while loop", R"SOURCE(
 import "std.zig";
 pub fn main(args: [][]u8) -> %void {
@@ -1988,6 +1733,13 @@ struct Foo {
 }
 fn get() -> isize { 1 }
     )SOURCE", 1, ".tmp_source.zig:3:9: error: unable to evaluate constant expression");
+
+
+    add_compile_fail_case("unnecessary if statement", R"SOURCE(
+fn f() {
+    if (true) { }
+}
+    )SOURCE", 1, ".tmp_source.zig:3:9: error: condition is always true; unnecessary if statement");
 }
 
 //////////////////////////////////////////////////////////////////////////////
test/self_hosted.zig
@@ -5,7 +5,6 @@ fn empty_function() {}
 
 
 
-
 /**
     * multi line doc comment
     */
@@ -19,6 +18,200 @@ fn comments() {
 fn comments_f1(s: []u8) {}
 
 
+#attribute("test")
+fn if_statements() {
+    should_be_equal(1, 1);
+    first_eql_third(2, 1, 2);
+}
+fn should_be_equal(a: i32, b: i32) {
+    if (a != b) {
+        unreachable{};
+    } else {
+        return;
+    }
+}
+fn first_eql_third(a: i32, b: i32, c: i32) {
+    if (a == b) {
+        unreachable{};
+    } else if (b == c) {
+        unreachable{};
+    } else if (a == c) {
+        return;
+    } else {
+        unreachable{};
+    }
+}
+
+
+#attribute("test")
+fn local_variables() {
+    test_loc_vars(2);
+}
+fn test_loc_vars(b: i32) {
+    const a: i32 = 1;
+    if (a + b != 3) unreachable{};
+}
+
+#attribute("test")
+fn bool_literals() {
+    should_be_true(true);
+    should_be_false(false);
+}
+fn should_be_true(b: bool) {
+    if (!b) unreachable{};
+}
+fn should_be_false(b: bool) {
+    if (b) unreachable{};
+}
+
+
+#attribute("test")
+fn separate_block_scopes() {
+    {
+        const no_conflict : i32 = 5;
+        assert(no_conflict == 5);
+    }
+
+    const c = {
+        const no_conflict = i32(10);
+        no_conflict
+    };
+    assert(c == 10);
+}
+
+
+#attribute("test")
+fn void_struct_fields() {
+    const foo = VoidStructFieldsFoo {
+        .a = void{},
+        .b = 1,
+        .c = void{},
+    };
+    assert(foo.b == 1);
+    assert(@sizeof(VoidStructFieldsFoo) == 4);
+}
+struct VoidStructFieldsFoo {
+    a : void,
+    b : i32,
+    c : void,
+}
+
+
+#attribute("test")
+fn void_arrays() {
+    var array: [4]void = undefined;
+    array[0] = void{};
+    array[1] = array[2];
+    assert(@sizeof(@typeof(array)) == 0);
+    assert(array.len == 4);
+}
+
+
+#attribute("test")
+fn three_expr_in_a_row() {
+    assert_false(false || false || false);
+    assert_false(true && true && false);
+    assert_false(1 | 2 | 4 != 7);
+    assert_false(3 ^ 6 ^ 8 != 13);
+    assert_false(7 & 14 & 28 != 4);
+    assert_false(9  << 1 << 2 != 9  << 3);
+    assert_false(90 >> 1 >> 2 != 90 >> 3);
+    assert_false(100 - 1 + 1000 != 1099);
+    assert_false(5 * 4 / 2 % 3 != 1);
+    assert_false(i32(i32(5)) != 5);
+    assert_false(!!false);
+    assert_false(i32(7) != --(i32(7)));
+}
+fn assert_false(b: bool) {
+    assert(!b);
+}
+
+
+#attribute("test")
+fn maybe_type() {
+    const x : ?bool = true;
+
+    if (const y ?= x) {
+        if (y) {
+            // OK
+        } else {
+            unreachable{};
+        }
+    } else {
+        unreachable{};
+    }
+
+    const next_x : ?i32 = null;
+
+    const z = next_x ?? 1234;
+
+    assert(z == 1234);
+
+    const final_x : ?i32 = 13;
+
+    const num = final_x ?? unreachable{};
+
+    assert(num == 13);
+}
+
+
+#attribute("test")
+fn enum_type() {
+    const foo1 = EnumTypeFoo.One(13);
+    const foo2 = EnumTypeFoo.Two(EnumType { .x = 1234, .y = 5678, });
+    const bar = EnumTypeBar.B;
+
+    assert(bar == EnumTypeBar.B);
+    assert(@member_count(EnumTypeFoo) == 3);
+    assert(@member_count(EnumTypeBar) == 4);
+    assert(@sizeof(EnumTypeFoo) == 24);
+    assert(@sizeof(EnumTypeBar) == 1);
+}
+struct EnumType {
+    x: u64,
+    y: u64,
+}
+enum EnumTypeFoo {
+    One: i32,
+    Two: EnumType,
+    Three: void,
+}
+enum EnumTypeBar {
+    A,
+    B,
+    C,
+    D,
+}
+
+
+#attribute("test")
+fn array_literal() {
+    const HEX_MULT = []u16{4096, 256, 16, 1};
+
+    assert(HEX_MULT.len == 4);
+    assert(HEX_MULT[1] == 256);
+}
+
+
+#attribute("test")
+fn const_number_literal() {
+    const one = 1;
+    const eleven = ten + one;
+
+    assert(eleven == 11);
+}
+const ten = 10;
+
+
+#attribute("test")
+fn error_values() {
+    const a = i32(error.err1);
+    const b = i32(error.err2);
+    assert(a != b);
+}
+error err1;
+error err2;
+
 
 
 #attribute("test")
@@ -42,11 +235,14 @@ fn call_struct_field(foo: Foo) -> i32 {
 
 #attribute("test")
 fn redefinition_of_error_values_allowed() {
-    if (error.AnError == error.SecondError) unreachable{}
+    should_be_not_equal(error.AnError, error.SecondError);
 }
 error AnError;
 error AnError;
 error SecondError;
+fn should_be_not_equal(a: error, b: error) {
+    if (a == b) unreachable{}
+}
 
 
 
@@ -98,14 +294,14 @@ fn continue_in_for_loop() {
 fn cast_bool_to_int() {
     const t = true;
     const f = false;
-    if (i32(t) != i32(1)) unreachable{}
-    if (i32(f) != i32(0)) unreachable{}
+    assert(i32(t) == i32(1));
+    assert(i32(f) == i32(0));
     non_const_cast_bool_to_int(t, f);
 }
 
 fn non_const_cast_bool_to_int(t: bool, f: bool) {
-    if (i32(t) != i32(1)) unreachable{}
-    if (i32(f) != i32(0)) unreachable{}
+    assert(i32(t) == i32(1));
+    assert(i32(f) == i32(0));
 }
 
 
@@ -240,7 +436,7 @@ fn const_expr_eval_on_single_expr_blocks_fn(x: i32, b: bool) -> i32 {
 #attribute("test")
 fn builtin_const_eval() {
     const x : i32 = @const_eval(1 + 2 + 3);
-    if (x != @const_eval(6)) unreachable{};
+    assert(x == @const_eval(6));
 }
 
 #attribute("test")
@@ -279,3 +475,10 @@ struct ArrayDotLenConstExpr {
     y: [@const_eval(some_array.len)]u8,
 }
 const some_array = []u8 {0, 1, 2, 3};
+
+
+
+
+fn assert(b: bool) {
+    if (!b) unreachable{}
+}