Commit c0ea9290c4

Andrew Kelley <superjoe30@gmail.com>
2016-01-23 10:14:01
main returns %void
1 parent 91d9110
example/cat/main.zig
@@ -4,14 +4,11 @@ import "std.zig";
 
 // Things to do to make this work:
 // * var args printing
-// * %void type
+// * update std API
 // * defer
 // * %return
 // * %% operator
-// * make main return %void
-// * how to reference error values %.Invalid
 // * cast err type to string
-// * update std API
 
 pub %.Invalid;
 
example/hello_world/hello.zig
@@ -2,8 +2,7 @@ export executable "hello";
 
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     //stderr.print_str("Hello, world!\n");
     print_str("Hello, world!\n");
-    return 0;
 }
example/hello_world/hello_libc.zig
@@ -5,7 +5,7 @@ extern {
     fn printf(__format: &const u8, ...) i32;
 }
 
-export fn main(argc: i32, argv: &&u8, env: &&u8) i32 => {
+export fn main(argc: i32, argv: &&u8) i32 => {
     printf(c"Hello, world!\n");
     return 0;
 }
src/all_types.hpp
@@ -60,6 +60,11 @@ struct ConstPtrValue {
     uint64_t len;
 };
 
+struct ConstErrValue {
+    ErrorTableEntry *err;
+    ConstExprValue *payload;
+};
+
 struct ConstExprValue {
     bool ok; // true if constant expression evalution worked
     bool depends_on_compile_var;
@@ -70,8 +75,8 @@ struct ConstExprValue {
         bool x_bool;
         FnTableEntry *x_fn;
         TypeTableEntry *x_type;
-        ErrorTableEntry *x_err;
         ConstExprValue *x_maybe;
+        ConstErrValue x_err;
         ConstEnumValue x_enum;
         ConstStructValue x_struct;
         ConstArrayValue x_array;
@@ -309,6 +314,7 @@ enum CastOp {
     CastOpIntWidenOrShorten,
     CastOpToUnknownSizeArray,
     CastOpMaybeWrap,
+    CastOpErrorWrap,
     CastOpPointerReinterpret,
     CastOpErrToInt,
 };
src/analyze.cpp
@@ -1130,6 +1130,61 @@ static bool num_lit_fits_in_other_type(CodeGen *g, AstNode *literal_node, TypeTa
     return false;
 }
 
+static bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type) {
+    if (expected_type == actual_type)
+        return true;
+
+    // pointer const
+    if (expected_type->id == TypeTableEntryIdPointer &&
+        actual_type->id == TypeTableEntryIdPointer &&
+        (!actual_type->data.pointer.is_const || expected_type->data.pointer.is_const))
+    {
+        return types_match_const_cast_only(expected_type->data.pointer.child_type,
+                actual_type->data.pointer.child_type);
+    }
+
+    // unknown size array const
+    if (expected_type->id == TypeTableEntryIdStruct &&
+        actual_type->id == TypeTableEntryIdStruct &&
+        expected_type->data.structure.is_unknown_size_array &&
+        actual_type->data.structure.is_unknown_size_array &&
+        (!actual_type->data.structure.fields[0].type_entry->data.pointer.is_const ||
+          expected_type->data.structure.fields[0].type_entry->data.pointer.is_const))
+    {
+        return types_match_const_cast_only(
+                expected_type->data.structure.fields[0].type_entry->data.pointer.child_type,
+                actual_type->data.structure.fields[0].type_entry->data.pointer.child_type);
+    }
+
+    // maybe
+    if (expected_type->id == TypeTableEntryIdMaybe &&
+        actual_type->id == TypeTableEntryIdMaybe)
+    {
+        return types_match_const_cast_only(
+                expected_type->data.maybe.child_type,
+                actual_type->data.maybe.child_type);
+    }
+
+    // error
+    if (expected_type->id == TypeTableEntryIdError &&
+        actual_type->id == TypeTableEntryIdError)
+    {
+        return types_match_const_cast_only(
+                expected_type->data.error.child_type,
+                actual_type->data.error.child_type);
+    }
+
+    // fn
+    if (expected_type->id == TypeTableEntryIdFn &&
+        actual_type->id == TypeTableEntryIdFn)
+    {
+        zig_panic("TODO types_match_const_cast_only for fns");
+    }
+
+
+    return false;
+}
+
 static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *parent_source_node,
         AstNode **child_nodes, TypeTableEntry **child_types, int child_count)
 {
@@ -1143,6 +1198,12 @@ static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *pa
         AstNode *cur_node = child_nodes[i];
         if (cur_type->id == TypeTableEntryIdInvalid) {
             return cur_type;
+        } else if (types_match_const_cast_only(prev_type, cur_type)) {
+            continue;
+        } else if (types_match_const_cast_only(cur_type, prev_type)) {
+            prev_type = cur_type;
+            prev_node = cur_node;
+            continue;
         } else if (prev_type->id == TypeTableEntryIdUnreachable) {
             prev_type = cur_type;
             prev_node = cur_node;
@@ -1163,6 +1224,16 @@ static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *pa
                 prev_type = cur_type;
                 prev_node = cur_node;
             }
+        } else if (prev_type->id == TypeTableEntryIdError &&
+                   types_match_const_cast_only(prev_type->data.error.child_type, cur_type))
+        {
+            continue;
+        } else if (cur_type->id == TypeTableEntryIdError &&
+                   types_match_const_cast_only(cur_type->data.error.child_type, prev_type))
+        {
+            prev_type = cur_type;
+            prev_node = cur_node;
+            continue;
         } else if (prev_type->id == TypeTableEntryIdNumLitFloat &&
                    cur_type->id == TypeTableEntryIdNumLitFloat)
         {
@@ -1189,8 +1260,6 @@ static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *pa
             } else {
                 return g->builtin_types.entry_invalid;
             }
-        } else if (prev_type == cur_type) {
-            continue;
         } else {
             add_node_error(g, parent_source_node,
                 buf_sprintf("incompatible types: '%s' and '%s'",
@@ -1202,61 +1271,6 @@ static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *pa
     return prev_type;
 }
 
-static bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type) {
-    if (expected_type == actual_type)
-        return true;
-
-    // pointer const
-    if (expected_type->id == TypeTableEntryIdPointer &&
-        actual_type->id == TypeTableEntryIdPointer &&
-        (!actual_type->data.pointer.is_const || expected_type->data.pointer.is_const))
-    {
-        return types_match_const_cast_only(expected_type->data.pointer.child_type,
-                actual_type->data.pointer.child_type);
-    }
-
-    // unknown size array const
-    if (expected_type->id == TypeTableEntryIdStruct &&
-        actual_type->id == TypeTableEntryIdStruct &&
-        expected_type->data.structure.is_unknown_size_array &&
-        actual_type->data.structure.is_unknown_size_array &&
-        (!actual_type->data.structure.fields[0].type_entry->data.pointer.is_const ||
-          expected_type->data.structure.fields[0].type_entry->data.pointer.is_const))
-    {
-        return types_match_const_cast_only(
-                expected_type->data.structure.fields[0].type_entry->data.pointer.child_type,
-                actual_type->data.structure.fields[0].type_entry->data.pointer.child_type);
-    }
-
-    // maybe
-    if (expected_type->id == TypeTableEntryIdMaybe &&
-        actual_type->id == TypeTableEntryIdMaybe)
-    {
-        return types_match_const_cast_only(
-                expected_type->data.maybe.child_type,
-                actual_type->data.maybe.child_type);
-    }
-
-    // error
-    if (expected_type->id == TypeTableEntryIdError &&
-        actual_type->id == TypeTableEntryIdError)
-    {
-        return types_match_const_cast_only(
-                expected_type->data.error.child_type,
-                actual_type->data.error.child_type);
-    }
-
-    // fn
-    if (expected_type->id == TypeTableEntryIdFn &&
-        actual_type->id == TypeTableEntryIdFn)
-    {
-        zig_panic("TODO types_match_const_cast_only for fns");
-    }
-
-
-    return false;
-}
-
 static bool types_match_with_implicit_cast(CodeGen *g, TypeTableEntry *expected_type,
         TypeTableEntry *actual_type, AstNode *literal_node, bool *reported_err)
 {
@@ -1272,6 +1286,14 @@ static bool types_match_with_implicit_cast(CodeGen *g, TypeTableEntry *expected_
         return true;
     }
 
+    // implicit conversion from error child type to error type
+    if (expected_type->id == TypeTableEntryIdError &&
+        types_match_with_implicit_cast(g, expected_type->data.error.child_type, actual_type,
+            literal_node, reported_err))
+    {
+        return true;
+    }
+
     // implicit widening conversion
     if (expected_type->id == TypeTableEntryIdInt &&
         actual_type->id == TypeTableEntryIdInt &&
@@ -1381,9 +1403,10 @@ static TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, ImportTableEn
         if (!child_nodes[i]) {
             continue;
         }
-        Expr *expr = get_resolved_expr(child_nodes[i]);
+        AstNode **child_node = child_nodes[i]->parent_field;
         TypeTableEntry *resolved_type = resolve_type_compatibility(g, import, block_context,
-                child_nodes[i], expected_type, child_types[i]);
+                *child_node, expected_type, child_types[i]);
+        Expr *expr = get_resolved_expr(*child_node);
         expr->type_entry = resolved_type;
         add_global_const_expr(g, expr);
     }
@@ -1812,7 +1835,7 @@ static TypeTableEntry *resolve_expr_const_val_as_fn(CodeGen *g, AstNode *node, F
 static TypeTableEntry *resolve_expr_const_val_as_err(CodeGen *g, AstNode *node, ErrorTableEntry *err) {
     Expr *expr = get_resolved_expr(node);
     expr->const_val.ok = true;
-    expr->const_val.data.x_err = err;
+    expr->const_val.data.x_err.err = err;
     return get_error_type(g, g->builtin_types.entry_void);
 }
 
@@ -2868,10 +2891,18 @@ static void eval_const_expr_implicit_cast(CodeGen *g, AstNode *node, AstNode *ex
             const_val->data.x_maybe = other_val;
             const_val->ok = true;
             break;
-        case CastOpErrToInt:
-            bignum_init_unsigned(&const_val->data.x_bignum, other_val->data.x_err->value);
+        case CastOpErrorWrap:
+            const_val->data.x_err.err = nullptr;
+            const_val->data.x_err.payload = other_val;
             const_val->ok = true;
             break;
+        case CastOpErrToInt:
+            {
+                uint64_t value = other_val->data.x_err.err ? other_val->data.x_err.err->value : 0;
+                bignum_init_unsigned(&const_val->data.x_bignum, value);
+                const_val->ok = true;
+                break;
+            }
     }
 }
 
@@ -2965,6 +2996,25 @@ static TypeTableEntry *analyze_cast_expr(CodeGen *g, ImportTableEntry *import, B
         }
     }
 
+    // explicit cast from child type of error type to error type
+    if (wanted_type->id == TypeTableEntryIdError) {
+        if (types_match_const_cast_only(wanted_type->data.error.child_type, actual_type)) {
+            node->data.fn_call_expr.cast_op = CastOpErrorWrap;
+            eval_const_expr_implicit_cast(g, node, expr_node);
+            return wanted_type;
+        } else if (actual_type->id == TypeTableEntryIdNumLitInt ||
+                   actual_type->id == TypeTableEntryIdNumLitFloat)
+        {
+            if (num_lit_fits_in_other_type(g, expr_node, wanted_type->data.error.child_type)) {
+                node->data.fn_call_expr.cast_op = CastOpErrorWrap;
+                eval_const_expr_implicit_cast(g, node, expr_node);
+                return wanted_type;
+            } else {
+                return g->builtin_types.entry_invalid;
+            }
+        }
+    }
+
     // explicit cast from number literal to another type
     if (actual_type->id == TypeTableEntryIdNumLitFloat ||
         actual_type->id == TypeTableEntryIdNumLitInt)
@@ -3579,6 +3629,42 @@ static TypeTableEntry *analyze_string_literal_expr(CodeGen *g, ImportTableEntry
     }
 }
 
+static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+        TypeTableEntry *expected_type, AstNode *node)
+{
+    BlockContext *child_context = new_block_context(node, context);
+    node->data.block.block_context = child_context;
+    TypeTableEntry *return_type = g->builtin_types.entry_void;
+
+    for (int i = 0; i < node->data.block.statements.length; i += 1) {
+        AstNode *child = node->data.block.statements.at(i);
+        if (child->type == NodeTypeLabel) {
+            LabelTableEntry *label_entry = child->data.label.label_entry;
+            assert(label_entry);
+            label_entry->entered_from_fallthrough = (return_type->id != TypeTableEntryIdUnreachable);
+            return_type = g->builtin_types.entry_void;
+            continue;
+        }
+        if (return_type->id == TypeTableEntryIdUnreachable) {
+            if (is_node_void_expr(child)) {
+                // {unreachable;void;void} is allowed.
+                // ignore void statements once we enter unreachable land.
+                analyze_expression(g, import, context, g->builtin_types.entry_void, child);
+                continue;
+            }
+            add_node_error(g, first_executing_node(child), buf_sprintf("unreachable code"));
+            break;
+        }
+        bool is_last = (i == node->data.block.statements.length - 1);
+        TypeTableEntry *passed_expected_type = is_last ? expected_type : nullptr;
+        return_type = analyze_expression(g, import, child_context, passed_expected_type, child);
+        if (!is_last && return_type->id == TypeTableEntryIdMetaType) {
+            add_node_error(g, child, buf_sprintf("expected expression, found type"));
+        }
+    }
+    return return_type;
+}
+
 // When you call analyze_expression, the node you pass might no longer be the child node
 // you thought it was due to implicit casting rewriting the AST.
 static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
@@ -3587,39 +3673,8 @@ static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import,
     TypeTableEntry *return_type = nullptr;
     switch (node->type) {
         case NodeTypeBlock:
-            {
-                BlockContext *child_context = new_block_context(node, context);
-                node->data.block.block_context = child_context;
-                return_type = g->builtin_types.entry_void;
-
-                for (int i = 0; i < node->data.block.statements.length; i += 1) {
-                    AstNode *child = node->data.block.statements.at(i);
-                    if (child->type == NodeTypeLabel) {
-                        LabelTableEntry *label_entry = child->data.label.label_entry;
-                        assert(label_entry);
-                        label_entry->entered_from_fallthrough = (return_type->id != TypeTableEntryIdUnreachable);
-                        return_type = g->builtin_types.entry_void;
-                        continue;
-                    }
-                    if (return_type->id == TypeTableEntryIdUnreachable) {
-                        if (is_node_void_expr(child)) {
-                            // {unreachable;void;void} is allowed.
-                            // ignore void statements once we enter unreachable land.
-                            analyze_expression(g, import, context, g->builtin_types.entry_void, child);
-                            continue;
-                        }
-                        add_node_error(g, first_executing_node(child), buf_sprintf("unreachable code"));
-                        break;
-                    }
-                    bool is_last = (i == node->data.block.statements.length - 1);
-                    TypeTableEntry *passed_expected_type = is_last ? expected_type : nullptr;
-                    return_type = analyze_expression(g, import, child_context, passed_expected_type, child);
-                    if (!is_last && return_type->id == TypeTableEntryIdMetaType) {
-                        add_node_error(g, child, buf_sprintf("expected expression, found type"));
-                    }
-                }
-                break;
-            }
+            return_type = analyze_block_expr(g, import, context, expected_type, node);
+            break;
 
         case NodeTypeReturnExpr:
             return_type = analyze_return_expr(g, import, context, expected_type, node);
src/codegen.cpp
@@ -268,6 +268,26 @@ static LLVMValueRef gen_enum_value_expr(CodeGen *g, AstNode *node, TypeTableEntr
     }
 }
 
+static LLVMValueRef gen_widen_or_shorten(CodeGen *g, AstNode *source_node, TypeTableEntry *actual_type,
+        TypeTableEntry *wanted_type, LLVMValueRef expr_val)
+{
+    if (actual_type->size_in_bits == wanted_type->size_in_bits) {
+        return expr_val;
+    } else if (actual_type->size_in_bits < wanted_type->size_in_bits) {
+        if (actual_type->data.integral.is_signed) {
+            add_debug_source_node(g, source_node);
+            return LLVMBuildSExt(g->builder, expr_val, wanted_type->type_ref, "");
+        } else {
+            add_debug_source_node(g, source_node);
+            return LLVMBuildZExt(g->builder, expr_val, wanted_type->type_ref, "");
+        }
+    } else {
+        assert(actual_type->size_in_bits > wanted_type->size_in_bits);
+        add_debug_source_node(g, source_node);
+        return LLVMBuildTrunc(g->builder, expr_val, wanted_type->type_ref, "");
+    }
+}
+
 static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) {
     assert(node->type == NodeTypeFnCallExpr);
 
@@ -288,7 +308,7 @@ static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) {
         case CastOpErrToInt:
             assert(actual_type->id == TypeTableEntryIdError);
             if (actual_type->data.error.child_type->size_in_bits == 0) {
-                return expr_val;
+                return gen_widen_or_shorten(g, node, g->err_tag_type, wanted_type, expr_val);
             } else {
                 zig_panic("TODO");
             }
@@ -309,6 +329,13 @@ static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) {
 
                 return cast_expr->tmp_ptr;
             }
+        case CastOpErrorWrap:
+            assert(wanted_type->id == TypeTableEntryIdError);
+            if (wanted_type->data.error.child_type->size_in_bits == 0) {
+                return LLVMConstNull(g->err_tag_type->type_ref);
+            } else {
+                zig_panic("TODO");
+            }
         case CastOpPtrToInt:
             add_debug_source_node(g, node);
             return LLVMBuildPtrToInt(g->builder, expr_val, wanted_type->type_ref, "");
@@ -316,21 +343,7 @@ static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) {
             add_debug_source_node(g, node);
             return LLVMBuildBitCast(g->builder, expr_val, wanted_type->type_ref, "");
         case CastOpIntWidenOrShorten:
-            if (actual_type->size_in_bits == wanted_type->size_in_bits) {
-                return expr_val;
-            } else if (actual_type->size_in_bits < wanted_type->size_in_bits) {
-                if (actual_type->data.integral.is_signed) {
-                    add_debug_source_node(g, node);
-                    return LLVMBuildSExt(g->builder, expr_val, wanted_type->type_ref, "");
-                } else {
-                    add_debug_source_node(g, node);
-                    return LLVMBuildZExt(g->builder, expr_val, wanted_type->type_ref, "");
-                }
-            } else {
-                assert(actual_type->size_in_bits > wanted_type->size_in_bits);
-                add_debug_source_node(g, node);
-                return LLVMBuildTrunc(g->builder, expr_val, wanted_type->type_ref, "");
-            }
+            return gen_widen_or_shorten(g, node, actual_type, wanted_type, expr_val);
         case CastOpToUnknownSizeArray:
             {
                 assert(cast_expr->tmp_ptr);
@@ -1279,7 +1292,7 @@ static LLVMValueRef gen_if_bool_expr_raw(CodeGen *g, AstNode *source_node, LLVMV
         return nullptr;
     }
 
-    assert(!use_expr_value);
+    assert(!use_expr_value || then_type->id == TypeTableEntryIdError);
 
     LLVMBasicBlockRef then_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "Then");
     LLVMBasicBlockRef endif_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "EndIf");
@@ -1292,7 +1305,12 @@ static LLVMValueRef gen_if_bool_expr_raw(CodeGen *g, AstNode *source_node, LLVMV
         LLVMBuildBr(g->builder, endif_block);
 
     LLVMPositionBuilderAtEnd(g->builder, endif_block);
-    return nullptr;
+
+    if (use_expr_value) {
+        return LLVMConstNull(g->err_tag_type->type_ref);
+    } else {
+        return nullptr;
+    }
 }
 
 static LLVMValueRef gen_if_bool_expr(CodeGen *g, AstNode *node) {
@@ -2132,7 +2150,8 @@ static LLVMValueRef gen_const_val(CodeGen *g, TypeTableEntry *type_entry, ConstE
         }
     } else if (type_entry->id == TypeTableEntryIdError) {
         if (type_entry->data.error.child_type->size_in_bits == 0) {
-            return LLVMConstInt(g->err_tag_type->type_ref, const_val->data.x_err->value, false);
+            uint64_t value = const_val->data.x_err.err ? const_val->data.x_err.err->value : 0;
+            return LLVMConstInt(g->err_tag_type->type_ref, value, false);
         } else {
             zig_panic("TODO");
         }
std/bootstrap.zig
@@ -29,5 +29,8 @@ fn call_main() unreachable => {
         const ptr = argv[i];
         args[i] = ptr[0...strlen(ptr)];
     }
-    exit(main(args))
+    // TODO: replace the i32 cast with:
+    // main(args) %% exit(1)
+    // exit(0)
+    exit(i32(main(args)))
 }
test/run_tests.cpp
@@ -114,7 +114,7 @@ import "syscall.zig";
 fn empty_function_1() => {}
 fn empty_function_2() => { return; }
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     empty_function_1();
     empty_function_2();
     this_is_a_function();
@@ -136,9 +136,8 @@ fn another_function() => {}
 
 /// this is a documentation comment
 /// doc comment line 2
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     print_str(/* mid-line comment /* nested */ */ "OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -147,10 +146,9 @@ pub fn main(args: [][]u8) i32 => {
 import "std.zig";
 import "foo.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     private_function();
     print_str("OK 2\n");
-    return 0;
 }
 
 fn private_function() => {
@@ -178,10 +176,9 @@ pub fn print_text() => {
 import "foo.zig";
 import "bar.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     foo_function();
     bar_function();
-    return 0;
 }
         )SOURCE", "OK\nOK\n");
 
@@ -214,7 +211,7 @@ pub fn foo_function() bool => {
     add_simple_case("if statements", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (1 != 0) {
         print_str("1 is true\n");
     } else {
@@ -228,7 +225,6 @@ pub fn main(args: [][]u8) i32 => {
     if (!(0 != 0)) {
         print_str("!0 is true\n");
     }
-    return 0;
 }
     )SOURCE", "1 is true\n!0 is true\n");
 
@@ -239,11 +235,10 @@ fn add(a: i32, b: i32) i32 => {
     a + b
 }
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (add(22, 11) == 33) {
         print_str("pass\n");
     }
-    return 0;
 }
     )SOURCE", "pass\n");
 
@@ -261,41 +256,38 @@ done:
     return;
 }
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     loop(3);
-    return 0;
 }
     )SOURCE", "loop\nloop\nloop\n");
 
     add_simple_case("local variables", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const a : i32 = 1;
     const b = i32(2);
     if (a + b == 3) {
         print_str("OK\n");
     }
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("bool literals", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (true)   { print_str("OK 1\n"); }
     if (false)  { print_str("BAD 1\n"); }
     if (!true)  { print_str("BAD 2\n"); }
     if (!false) { print_str("OK 2\n"); }
-    return 0;
 }
     )SOURCE", "OK 1\nOK 2\n");
 
     add_simple_case("separate block scopes", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (true) {
         const no_conflict : i32 = 5;
         if (no_conflict == 5) { print_str("OK 1\n"); }
@@ -306,16 +298,14 @@ pub fn main(args: [][]u8) i32 => {
         no_conflict
     };
     if (c == 10) { print_str("OK 2\n"); }
-    return 0;
 }
     )SOURCE", "OK 1\nOK 2\n");
 
     add_simple_case("void parameters", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     void_fun(1, void{}, 2);
-    return 0;
 }
 
 fn void_fun(a : i32, b : void, c : i32) => {
@@ -333,7 +323,7 @@ struct Foo {
     b : i32,
     c : void,
 }
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const foo = Foo {
         .a = void{},
         .b = 1,
@@ -346,7 +336,6 @@ pub fn main(args: [][]u8) i32 => {
         print_str("BAD\n");
     }
     print_str("OK\n");
-    return 0;
 }
 
     )SOURCE", "OK\n");
@@ -354,7 +343,7 @@ pub fn main(args: [][]u8) i32 => {
     add_simple_case("void arrays", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var array: [4]void;
     array[0] = void{};
     array[1] = array[2];
@@ -365,7 +354,6 @@ pub fn main(args: [][]u8) i32 => {
         print_str("BAD\n");
     }
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -373,27 +361,22 @@ pub fn main(args: [][]u8) i32 => {
     add_simple_case("mutable local variables", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var zero : i32 = 0;
     if (zero == 0) { print_str("zero\n"); }
 
     var i = i32(0);
-loop_start:
-    if (i == 3) {
-        goto done;
+    while (i != 3) {
+        print_str("loop\n");
+        i += 1;
     }
-    print_str("loop\n");
-    i = i + 1;
-    goto loop_start;
-done:
-    return 0;
 }
     )SOURCE", "zero\nloop\nloop\nloop\n");
 
     add_simple_case("arrays", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var array : [5]i32;
 
     var i : i32 = 0;
@@ -417,8 +400,6 @@ pub fn main(args: [][]u8) i32 => {
     if (get_array_len(array) != 5) {
         print_str("BAD\n");
     }
-
-    return 0;
 }
 fn get_array_len(a: []i32) isize => {
     a.len
@@ -429,9 +410,8 @@ fn get_array_len(a: []i32) isize => {
     add_simple_case("hello world without libc", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     print_str("Hello, world!\n");
-    return 0;
 }
     )SOURCE", "Hello, world!\n");
 
@@ -439,7 +419,7 @@ pub fn main(args: [][]u8) i32 => {
     add_simple_case("a + b + c", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (false || false || false) { print_str("BAD 1\n"); }
     if (true && true && false)   { print_str("BAD 2\n"); }
     if (1 | 2 | 4 != 7)          { print_str("BAD 3\n"); }
@@ -454,14 +434,13 @@ pub fn main(args: [][]u8) i32 => {
     if (i32(7) != --(i32(7)))    { print_str("BAD 12\n"); }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("short circuit", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (true || { print_str("BAD 1\n"); false }) {
       print_str("OK 1\n");
     }
@@ -476,15 +455,13 @@ pub fn main(args: [][]u8) i32 => {
     } else {
       print_str("OK 4\n");
     }
-
-    return 0;
 }
     )SOURCE", "OK 1\nOK 2\nOK 3\nOK 4\n");
 
     add_simple_case("modify operators", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var i : i32 = 0;
     i += 5;  if (i != 5)  { print_str("BAD +=\n"); }
     i -= 2;  if (i != 3)  { print_str("BAD -=\n"); }
@@ -500,7 +477,6 @@ pub fn main(args: [][]u8) i32 => {
     i |= 3;  if (i != 7)  { print_str("BAD |=\n"); }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -636,7 +612,7 @@ export fn main(argc: i32, argv: &&u8) i32 => {
     add_simple_case("structs", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var foo : Foo;
     @memset(&foo, 0, @sizeof(Foo));
     foo.a += 1;
@@ -650,7 +626,6 @@ pub fn main(args: [][]u8) i32 => {
     test_byval_assign();
     test_initializer();
     print_str("OK\n");
-    return 0;
 }
 struct Foo {
     a : i32,
@@ -711,23 +686,25 @@ import "std.zig";
 const g1 : i32 = 1233 + 1;
 var g2 : i32 = 0;
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (g2 != 0) { print_str("BAD\n"); }
     g2 = g1;
     if (g2 != 1234) { print_str("BAD\n"); }
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("while loop", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var i : i32 = 0;
     while (i < 4) {
         print_str("loop\n");
         i += 1;
     }
+    g();
+}
+fn g() i32 => {
     return f();
 }
 fn f() i32 => {
@@ -739,7 +716,7 @@ fn f() i32 => {
 
     add_simple_case("continue and break", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var i : i32 = 0;
     while (true) {
         print_str("loop\n");
@@ -749,13 +726,12 @@ pub fn main(args: [][]u8) i32 => {
         }
         break;
     }
-    return 0;
 }
     )SOURCE", "loop\nloop\nloop\nloop\n");
 
     add_simple_case("maybe type", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const x : ?bool = true;
 
     if (const y ?= x) {
@@ -783,19 +759,16 @@ pub fn main(args: [][]u8) i32 => {
     if (num != 13) {
         print_str("BAD\n");
     }
-
-    return 0;
 }
     )SOURCE", "x is true\n");
 
     add_simple_case("implicit cast after unreachable", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const x = outer();
     if (x == 1234) {
         print_str("OK\n");
     }
-    return 0;
 }
 fn inner() i32 => { 1234 }
 fn outer() isize => {
@@ -807,11 +780,10 @@ fn outer() isize => {
 import "std.zig";
 const x: u16 = 13;
 const z: @typeof(x) = 19;
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const y: @typeof(x) = 120;
     print_u64(@sizeof(@typeof(y)));
     print_str("\n");
-    return 0;
 }
     )SOURCE", "2\n");
 
@@ -823,20 +795,19 @@ struct Rand {
         r.seed
     }
 }
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const r = Rand {.seed = 1234};
     if (r.get_seed() != 1234) {
         print_str("BAD seed\n");
     }
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("pointer dereferencing", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var x = i32(3);
     const y = &x;
 
@@ -849,7 +820,6 @@ pub fn main(args: [][]u8) i32 => {
         print_str("BAD\n");
     }
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -858,17 +828,16 @@ import "std.zig";
 
 const ARRAY_SIZE : i8 = 20;
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var array : [ARRAY_SIZE]u8;
     print_u64(@sizeof(@typeof(array)));
     print_str("\n");
-    return 0;
 }
     )SOURCE", "20\n");
 
     add_simple_case("@min_value() and @max_value()", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     print_str("max u8: ");
     print_u64(@max_value(u8));
     print_str("\n");
@@ -932,8 +901,6 @@ pub fn main(args: [][]u8) i32 => {
     print_str("min i64: ");
     print_i64(@min_value(i64));
     print_str("\n");
-
-    return 0;
 }
     )SOURCE",
         "max u8: 255\n"
@@ -956,7 +923,7 @@ pub fn main(args: [][]u8) i32 => {
 
     add_simple_case("slicing", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var array : [20]i32;
 
     array[5] = 1234;
@@ -977,18 +944,16 @@ pub fn main(args: [][]u8) i32 => {
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
 
     add_simple_case("else if expression", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     if (f(1) == 1) {
         print_str("OK\n");
     }
-    return 0;
 }
 fn f(c: u8) u8 => {
     if (c == 0) {
@@ -1003,7 +968,7 @@ fn f(c: u8) u8 => {
 
     add_simple_case("overflow intrinsics", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var result: u8;
     if (!@add_with_overflow(u8, 250, 100, &result)) {
         print_str("BAD\n");
@@ -1015,13 +980,12 @@ pub fn main(args: [][]u8) i32 => {
         print_str("BAD\n");
     }
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("memcpy and memset intrinsics", R"SOURCE(
 import "std.zig";
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     var foo : [20]u8;
     var bar : [20]u8;
 
@@ -1033,7 +997,6 @@ pub fn main(args: [][]u8) i32 => {
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -1042,8 +1005,8 @@ import "std.zig";
 const z : @typeof(stdin_fileno) = 0;
 const x : @typeof(y) = 1234;
 const y : u16 = 5678;
-pub fn main(args: [][]u8) i32 => {
-    print_ok(x)
+pub fn main(args: [][]u8) %void => {
+    var x : i32 = print_ok(x);
 }
 fn print_ok(val: @typeof(x)) @typeof(foo) => {
     print_str("OK\n");
@@ -1073,7 +1036,7 @@ enum Bar {
     D,
 }
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const foo1 = Foo.One(13);
     const foo2 = Foo.Two(Point { .x = 1234, .y = 5678, });
     const bar = Bar.B;
@@ -1098,15 +1061,13 @@ pub fn main(args: [][]u8) i32 => {
     }
 
     print_str("OK\n");
-
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("array literal", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const HEX_MULT = []u16{4096, 256, 16, 1};
 
     if (HEX_MULT.len != 4) {
@@ -1118,14 +1079,13 @@ pub fn main(args: [][]u8) i32 => {
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("nested arrays", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const array_of_strings = [][]u8 {"hello", "this", "is", "my", "thing"};
     var i: @typeof(array_of_strings.len) = 0;
     while (i < array_of_strings.len) {
@@ -1133,14 +1093,13 @@ pub fn main(args: [][]u8) i32 => {
         print_str("\n");
         i += 1;
     }
-    return 0;
 }
     )SOURCE", "hello\nthis\nis\nmy\nthing\n");
 
     add_simple_case("for loops", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const array = []u8 {9, 8, 7, 6};
     for (item, array) {
         print_u64(item);
@@ -1159,20 +1118,18 @@ pub fn main(args: [][]u8) i32 => {
         print_i64(index);
         print_str("\n");
     }
-    return 0;
 }
     )SOURCE", "9\n8\n7\n6\n0\n1\n2\n3\n9\n8\n7\n6\n0\n1\n2\n3\n");
 
     add_simple_case("function pointers", R"SOURCE(
 import "std.zig";
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const fns = []@typeof(fn1) { fn1, fn2, fn3, fn4, };
     for (f, fns) {
         print_u64(f());
         print_str("\n");
     }
-    return 0;
 }
 
 fn fn1() u32 => {5}
@@ -1191,7 +1148,7 @@ enum Foo {
     D,
 }
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const foo = Foo.C;
     const val: i32 = switch (foo) {
         Foo.A => 1,
@@ -1204,7 +1161,6 @@ pub fn main(args: [][]u8) i32 => {
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -1213,7 +1169,7 @@ import "std.zig";
 
 const ten = 10;
 
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const one = 1;
     const eleven = ten + one;
 
@@ -1222,7 +1178,6 @@ pub fn main(args: [][]u8) i32 => {
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -1233,28 +1188,26 @@ struct Foo {
     y: bool,
 }
 var foo = Foo { .x = 13, .y = true, };
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     foo.x += 1;
     if (foo.x != 14) {
         print_str("BAD\n");
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
     add_simple_case("statically initialized array literal", R"SOURCE(
 import "std.zig";
 const x = []u8{1,2,3,4};
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const y : [4]u8 = x;
     if (y[3] != 4) {
         print_str("BAD\n");
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");
 
@@ -1262,7 +1215,7 @@ pub fn main(args: [][]u8) i32 => {
 import "std.zig";
 %.err1;
 %.err2;
-pub fn main(args: [][]u8) i32 => {
+pub fn main(args: [][]u8) %void => {
     const a = i32(%.err1);
     const b = i32(%.err2);
     if (a == b) {
@@ -1270,7 +1223,6 @@ pub fn main(args: [][]u8) i32 => {
     }
 
     print_str("OK\n");
-    return 0;
 }
     )SOURCE", "OK\n");