Commit a2e3293930

Andrew Kelley <superjoe30@gmail.com>
2016-11-04 20:36:30
WIP moving all analysis to IR
1 parent bc6c33b
src/all_types.hpp
@@ -738,8 +738,6 @@ struct AstNodeSymbolExpr {
 
     // populated by semantic analyzer
     Expr resolved_expr;
-    // set this to instead of analyzing the node, pretend it's a type entry and it's this one.
-    TypeTableEntry *override_type_entry;
     TypeEnumField *enum_field;
     uint32_t err_value;
 };
@@ -1439,7 +1437,6 @@ enum IrInstructionId {
     IrInstructionIdElemPtr,
     IrInstructionIdVarPtr,
     IrInstructionIdCall,
-    IrInstructionIdBuiltinCall,
     IrInstructionIdConst,
     IrInstructionIdReturn,
     IrInstructionIdCast,
@@ -1449,6 +1446,7 @@ enum IrInstructionId {
     IrInstructionIdTypeOf,
     IrInstructionIdToPtrType,
     IrInstructionIdPtrTypeChild,
+    IrInstructionIdSetFnTest,
 };
 
 struct IrInstruction {
@@ -1630,13 +1628,6 @@ struct IrInstructionCall {
     IrInstruction **args;
 };
 
-struct IrInstructionBuiltinCall {
-    IrInstruction base;
-
-    BuiltinFnEntry *fn;
-    IrInstruction **args;
-};
-
 struct IrInstructionConst {
     IrInstruction base;
 };
@@ -1698,6 +1689,13 @@ struct IrInstructionPtrTypeChild {
     IrInstruction *value;
 };
 
+struct IrInstructionSetFnTest {
+    IrInstruction base;
+
+    IrInstruction *fn_value;
+    IrInstruction *is_test;
+};
+
 enum LValPurpose {
     LValPurposeNone,
     LValPurposeAssign,
src/analyze.cpp
@@ -17,41 +17,9 @@
 #include "parser.hpp"
 #include "zig_llvm.hpp"
 
-static TypeTableEntry *analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node);
-static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, TypeTableEntry *expected_type, AstNode *node, bool pointer_only);
-static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, TypeTableEntry *expected_type, AstNode *node);
+static void resolve_enum_type(CodeGen *g, ImportTableEntry *import, TypeTableEntry *enum_type);
 static void resolve_struct_type(CodeGen *g, ImportTableEntry *import, TypeTableEntry *struct_type);
-static TypeTableEntry *unwrapped_node_type(AstNode *node);
-static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node, Buf *err_name);
-static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node);
-static TypeTableEntry *resolve_expr_const_val_as_fn(CodeGen *g, AstNode *node, FnTableEntry *fn,
-        bool depends_on_compile_var);
-static TypeTableEntry *resolve_expr_const_val_as_generic_fn(CodeGen *g, AstNode *node,
-        TypeTableEntry *type_entry, bool depends_on_compile_var);
-static TypeTableEntry *resolve_expr_const_val_as_type(CodeGen *g, AstNode *node, TypeTableEntry *type,
-        bool depends_on_compile_var);
-static TypeTableEntry *resolve_expr_const_val_as_unsigned_num_lit(CodeGen *g, AstNode *node,
-        TypeTableEntry *expected_type, uint64_t x, bool depends_on_compile_var);
-static TypeTableEntry *resolve_expr_const_val_as_bool(CodeGen *g, AstNode *node, bool value,
-        bool depends_on_compile_var);
-static TypeTableEntry *analyze_decl_ref(CodeGen *g, AstNode *source_node, AstNode *decl_node,
-        bool pointer_only, BlockContext *block_context, bool depends_on_compile_var);
-static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *source_node,
-        AstNodeVariableDeclaration *variable_declaration,
-        bool expr_is_maybe, AstNode *decl_node, bool var_is_ptr);
 static void scan_decls(CodeGen *g, ImportTableEntry *import, BlockContext *context, AstNode *node);
-static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry);
-static void resolve_use_decl(CodeGen *g, AstNode *node);
-static void preview_use_decl(CodeGen *g, AstNode *node);
-static VariableTableEntry *add_local_var(CodeGen *g, AstNode *source_node, ImportTableEntry *import,
-        BlockContext *context, Buf *name, TypeTableEntry *type_entry, bool is_const,
-        AstNode *val_node);
 
 AstNode *first_executing_node(AstNode *node) {
     switch (node->type) {
@@ -868,39 +836,52 @@ TypeTableEntry *get_underlying_type(TypeTableEntry *type_entry) {
     }
 }
 
-// If the node does not have a constant expression value with a metatype, generates an error
-// and returns invalid type. Otherwise, returns the type of the constant expression value.
-// Must be called after analyze_expression on the same node.
-static TypeTableEntry *resolve_type(CodeGen *g, AstNode *node) {
-    if (node->type == NodeTypeSymbol && node->data.symbol_expr.override_type_entry) {
-        return node->data.symbol_expr.override_type_entry;
+static IrInstruction *analyze_const_value(CodeGen *g, BlockContext *scope, AstNode *node,
+        TypeTableEntry *expected_type)
+{
+    IrExecutable ir_executable = {0};
+    IrExecutable analyzed_executable = {0};
+    IrInstruction *pass1 = ir_gen(g, node, scope, &ir_executable);
+
+    if (pass1->type_entry->id == TypeTableEntryIdInvalid)
+        return g->invalid_instruction;
+
+    if (g->verbose) {
+        fprintf(stderr, "{\n");
+        ir_print(stderr, &ir_executable, 4);
+        fprintf(stderr, "}\n");
     }
-    Expr *expr = get_resolved_expr(node);
-    assert(expr->type_entry);
-    if (expr->type_entry->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    } else if (expr->type_entry->id == TypeTableEntryIdMetaType) {
-        // OK
-    } else {
-        add_node_error(g, node, buf_sprintf("expected type, found expression"));
-        return g->builtin_types.entry_invalid;
+    TypeTableEntry *result_type = ir_analyze(g, &ir_executable, &analyzed_executable, expected_type, node);
+    if (result_type->id == TypeTableEntryIdInvalid)
+        return g->invalid_instruction;
+
+    if (g->verbose) {
+        fprintf(stderr, "{ // (analyzed)\n");
+        ir_print(stderr, &analyzed_executable, 4);
+        fprintf(stderr, "}\n");
     }
 
-    ConstExprValue *const_val = &expr->const_val;
-    if (!const_val->ok) {
+    IrInstruction *result = ir_exec_const_result(&analyzed_executable);
+    if (!result) {
         add_node_error(g, node, buf_sprintf("unable to evaluate constant expression"));
-        return g->builtin_types.entry_invalid;
+        return g->invalid_instruction;
     }
 
-    return const_val->data.x_type;
+    return result;
 }
 
 static TypeTableEntry *analyze_type_expr_pointer_only(CodeGen *g, ImportTableEntry *import,
         BlockContext *context, AstNode *node, bool pointer_only)
 {
-    AstNode **node_ptr = node->parent_field;
-    analyze_expression_pointer_only(g, import, context, nullptr, *node_ptr, pointer_only);
-    return resolve_type(g, *node_ptr);
+    if (pointer_only)
+        zig_panic("TODO");
+
+    IrInstruction *result = analyze_const_value(g, context, node, g->builtin_types.entry_type);
+    if (result->type_entry->id == TypeTableEntryIdInvalid)
+        return g->builtin_types.entry_invalid;
+
+    assert(result->static_value.ok);
+    return result->static_value.data.x_type;
 }
 
 // Calls analyze_expression on node, and then resolve_type.
@@ -1607,6 +1588,34 @@ static void preview_fn_proto_instance(CodeGen *g, ImportTableEntry *import, AstN
     }
 }
 
+static void add_top_level_decl(CodeGen *g, ImportTableEntry *import, BlockContext *block_context,
+        AstNode *node, Buf *name)
+{
+    assert(import);
+
+    TopLevelDecl *tld = get_as_top_level_decl(node);
+    tld->import = import;
+    tld->name = name;
+
+    bool want_to_resolve = (g->check_unused || g->is_test_build || tld->visib_mod == VisibModExport);
+    bool is_generic_container = (node->type == NodeTypeContainerDecl &&
+            node->data.struct_decl.generic_params.length > 0);
+    if (want_to_resolve && !is_generic_container) {
+        g->resolve_queue.append(node);
+    }
+
+    node->block_context = block_context;
+
+    auto entry = block_context->decl_table.maybe_get(name);
+    if (entry) {
+        AstNode *other_decl_node = entry->value;
+        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("redefinition of '%s'", buf_ptr(name)));
+        add_error_note(g, msg, other_decl_node, buf_sprintf("previous definition is here"));
+    } else {
+        block_context->decl_table.put(name, node);
+    }
+}
+
 static void scan_struct_decl(CodeGen *g, ImportTableEntry *import, BlockContext *context, AstNode *node) {
     assert(node->type == NodeTypeContainerDecl);
 
@@ -1629,30 +1638,27 @@ static void scan_struct_decl(CodeGen *g, ImportTableEntry *import, BlockContext
     }
 }
 
-static void resolve_struct_instance(CodeGen *g, ImportTableEntry *import, AstNode *node) {
-    TypeTableEntry *type_entry = node->data.struct_decl.type_entry;
-    assert(type_entry);
+static void count_inline_and_var_args(AstNode *proto_node) {
+    assert(proto_node->type == NodeTypeFnProto);
 
-    // struct/enum member fns will get resolved independently
+    size_t *inline_arg_count = &proto_node->data.fn_proto.inline_arg_count;
+    size_t *inline_or_var_type_arg_count = &proto_node->data.fn_proto.inline_or_var_type_arg_count;
 
-    switch (node->data.struct_decl.kind) {
-        case ContainerKindStruct:
-            resolve_struct_type(g, import, type_entry);
-            break;
-        case ContainerKindEnum:
-            resolve_enum_type(g, import, type_entry);
-            break;
-        case ContainerKindUnion:
-            resolve_union_type(g, import, type_entry);
-            break;
-    }
-}
+    *inline_arg_count = 0;
+    *inline_or_var_type_arg_count = 0;
 
-static void resolve_struct_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
-    if (node->data.struct_decl.generic_params.length > 0) {
-        return preview_generic_fn_proto(g, import, node);
-    } else {
-        return resolve_struct_instance(g, import, node);
+    // TODO run these nodes through the type analysis system rather than looking for
+    // specialized ast nodes. this would get fooled by `{var}` instead of `var` which
+    // is supposed to be equivalent
+    for (size_t i = 0; i < proto_node->data.fn_proto.params.length; i += 1) {
+        AstNode *param_node = proto_node->data.fn_proto.params.at(i);
+        assert(param_node->type == NodeTypeParamDecl);
+        if (param_node->data.param_decl.is_inline) {
+            *inline_arg_count += 1;
+            *inline_or_var_type_arg_count += 1;
+        } else if (param_node->data.param_decl.type->type == NodeTypeVarLiteral) {
+            *inline_or_var_type_arg_count += 1;
+        }
     }
 }
 
@@ -1680,6 +1686,137 @@ static void preview_error_value_decl(CodeGen *g, AstNode *node) {
     node->data.error_value_decl.top_level_decl.resolution = TldResolutionOk;
 }
 
+static void scan_decls(CodeGen *g, ImportTableEntry *import, BlockContext *context, AstNode *node) {
+    switch (node->type) {
+        case NodeTypeRoot:
+            for (size_t i = 0; i < import->root->data.root.top_level_decls.length; i += 1) {
+                AstNode *child = import->root->data.root.top_level_decls.at(i);
+                scan_decls(g, import, context, child);
+            }
+            break;
+        case NodeTypeContainerDecl:
+            {
+                Buf *name = node->data.struct_decl.name;
+                add_top_level_decl(g, import, context, node, name);
+                if (node->data.struct_decl.generic_params.length == 0) {
+                    scan_struct_decl(g, import, context, node);
+                }
+            }
+            break;
+        case NodeTypeFnDef:
+            node->data.fn_def.fn_proto->data.fn_proto.fn_def_node = node;
+            scan_decls(g, import, context, node->data.fn_def.fn_proto);
+            break;
+        case NodeTypeVariableDeclaration:
+            {
+                Buf *name = node->data.variable_declaration.symbol;
+                add_top_level_decl(g, import, context, node, name);
+                break;
+            }
+        case NodeTypeTypeDecl:
+            {
+                Buf *name = node->data.type_decl.symbol;
+                add_top_level_decl(g, import, context, node, name);
+                break;
+            }
+        case NodeTypeFnProto:
+            {
+                // if the name is missing, we immediately announce an error
+                Buf *fn_name = node->data.fn_proto.name;
+                if (buf_len(fn_name) == 0) {
+                    node->data.fn_proto.skip = true;
+                    add_node_error(g, node, buf_sprintf("missing function name"));
+                    break;
+                }
+                count_inline_and_var_args(node);
+
+                add_top_level_decl(g, import, context, node, fn_name);
+                break;
+            }
+        case NodeTypeUse:
+            {
+                TopLevelDecl *tld = get_as_top_level_decl(node);
+                tld->import = import;
+                node->block_context = context;
+                g->use_queue.append(node);
+                tld->import->use_decls.append(node);
+                break;
+            }
+        case NodeTypeErrorValueDecl:
+            // error value declarations do not depend on other top level decls
+            preview_error_value_decl(g, node);
+            break;
+        case NodeTypeParamDecl:
+        case NodeTypeFnDecl:
+        case NodeTypeReturnExpr:
+        case NodeTypeDefer:
+        case NodeTypeBlock:
+        case NodeTypeBinOpExpr:
+        case NodeTypeUnwrapErrorExpr:
+        case NodeTypeFnCallExpr:
+        case NodeTypeArrayAccessExpr:
+        case NodeTypeSliceExpr:
+        case NodeTypeNumberLiteral:
+        case NodeTypeStringLiteral:
+        case NodeTypeCharLiteral:
+        case NodeTypeBoolLiteral:
+        case NodeTypeNullLiteral:
+        case NodeTypeUndefinedLiteral:
+        case NodeTypeZeroesLiteral:
+        case NodeTypeThisLiteral:
+        case NodeTypeSymbol:
+        case NodeTypePrefixOpExpr:
+        case NodeTypeIfBoolExpr:
+        case NodeTypeIfVarExpr:
+        case NodeTypeWhileExpr:
+        case NodeTypeForExpr:
+        case NodeTypeSwitchExpr:
+        case NodeTypeSwitchProng:
+        case NodeTypeSwitchRange:
+        case NodeTypeLabel:
+        case NodeTypeGoto:
+        case NodeTypeBreak:
+        case NodeTypeContinue:
+        case NodeTypeAsmExpr:
+        case NodeTypeFieldAccessExpr:
+        case NodeTypeStructField:
+        case NodeTypeContainerInitExpr:
+        case NodeTypeStructValueField:
+        case NodeTypeArrayType:
+        case NodeTypeErrorType:
+        case NodeTypeTypeLiteral:
+        case NodeTypeVarLiteral:
+            zig_unreachable();
+    }
+}
+
+static void resolve_struct_instance(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+    TypeTableEntry *type_entry = node->data.struct_decl.type_entry;
+    assert(type_entry);
+
+    // struct/enum member fns will get resolved independently
+
+    switch (node->data.struct_decl.kind) {
+        case ContainerKindStruct:
+            resolve_struct_type(g, import, type_entry);
+            break;
+        case ContainerKindEnum:
+            resolve_enum_type(g, import, type_entry);
+            break;
+        case ContainerKindUnion:
+            resolve_union_type(g, import, type_entry);
+            break;
+    }
+}
+
+static void resolve_struct_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+    if (node->data.struct_decl.generic_params.length > 0) {
+        return preview_generic_fn_proto(g, import, node);
+    } else {
+        return resolve_struct_instance(g, import, node);
+    }
+}
+
 TypeTableEntry *validate_var_type(CodeGen *g, AstNode *source_node, TypeTableEntry *type_entry) {
     TypeTableEntry *underlying_type = get_underlying_type(type_entry);
     switch (underlying_type->id) {
@@ -1718,75 +1855,116 @@ TypeTableEntry *validate_var_type(CodeGen *g, AstNode *source_node, TypeTableEnt
     zig_unreachable();
 }
 
-static void resolve_var_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
-    assert(node->type == NodeTypeVariableDeclaration);
-
-    AstNodeVariableDeclaration *var_decl = &node->data.variable_declaration;
-    BlockContext *scope = node->block_context;
-    bool is_const = var_decl->is_const;
-    bool is_export = (var_decl->top_level_decl.visib_mod == VisibModExport);
-    bool is_extern = var_decl->is_extern;
-
-    assert(!scope->fn_entry);
-
-    TypeTableEntry *explicit_type = nullptr;
-    if (var_decl->type) {
-        TypeTableEntry *proposed_type = analyze_type_expr(g, import, scope, var_decl->type);
-        explicit_type = validate_var_type(g, var_decl->type, proposed_type);
-    }
+// Set name to nullptr to make the variable anonymous (not visible to programmer).
+// TODO merge with definition of add_local_var in ir.cpp
+static VariableTableEntry *add_local_var_shadowable(CodeGen *g, AstNode *source_node, ImportTableEntry *import,
+        BlockContext *context, Buf *name, TypeTableEntry *type_entry, bool is_const, AstNode *val_node,
+        bool shadowable)
+{
+    VariableTableEntry *variable_entry = allocate<VariableTableEntry>(1);
+    variable_entry->type = type_entry;
+    variable_entry->block_context = context;
+    variable_entry->import = import;
+    variable_entry->shadowable = shadowable;
+    variable_entry->mem_slot_index = SIZE_MAX;
 
-    TypeTableEntry *implicit_type = nullptr;
-    if (explicit_type && explicit_type->id == TypeTableEntryIdInvalid) {
-        implicit_type = explicit_type;
-    } else if (var_decl->expr) {
-        IrExecutable ir_executable = {0};
-        IrExecutable analyzed_executable = {0};
-        IrInstruction *result = ir_gen(g, var_decl->expr, scope, &ir_executable);
-        if (result == g->invalid_instruction) {
-            // ignore the poison value
-            implicit_type = g->builtin_types.entry_invalid;
-        } else {
-            if (g->verbose) {
-                fprintf(stderr, "var %s = {\n", buf_ptr(var_decl->symbol));
-                ir_print(stderr, &ir_executable, 4);
-                fprintf(stderr, "}\n");
-            }
-            implicit_type = ir_analyze(g, &ir_executable, &analyzed_executable,
-                explicit_type, var_decl->type);
-            if (g->verbose) {
-                fprintf(stderr, "var %s = { // (analyzed)\n", buf_ptr(var_decl->symbol));
-                ir_print(stderr, &analyzed_executable, 4);
-                fprintf(stderr, "}\n");
-            }
+    if (name) {
+        buf_init_from_buf(&variable_entry->name, name);
 
-            if (implicit_type->id == TypeTableEntryIdUnreachable) {
-                add_node_error(g, node,
-                    buf_sprintf("variable initialization is unreachable"));
-                implicit_type = g->builtin_types.entry_invalid;
-            } else if ((!is_const || is_export) &&
-                    (implicit_type->id == TypeTableEntryIdNumLitFloat ||
-                    implicit_type->id == TypeTableEntryIdNumLitInt))
-            {
-                add_node_error(g, node, buf_sprintf("unable to infer variable type"));
-                implicit_type = g->builtin_types.entry_invalid;
-            } else if (implicit_type->id == TypeTableEntryIdMetaType && !is_const) {
-                add_node_error(g, node, buf_sprintf("variable of type 'type' must be constant"));
-                implicit_type = g->builtin_types.entry_invalid;
-            }
-            if (implicit_type->id != TypeTableEntryIdInvalid) {
-                Expr *expr = get_resolved_expr(var_decl->expr);
-                IrInstruction *result = ir_exec_const_result(&analyzed_executable);
-                if (result) {
-                    assert(result->static_value.ok);
-                    expr->const_val = result->static_value;
-                    expr->type_entry = result->type_entry;
+        if (type_entry->id != TypeTableEntryIdInvalid) {
+            VariableTableEntry *existing_var = find_variable(g, context, name);
+            if (existing_var && !existing_var->shadowable) {
+                ErrorMsg *msg = add_node_error(g, source_node,
+                        buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
+                add_error_note(g, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
+                variable_entry->type = g->builtin_types.entry_invalid;
+            } else {
+                auto primitive_table_entry = g->primitive_type_table.maybe_get(name);
+                if (primitive_table_entry) {
+                    TypeTableEntry *type = primitive_table_entry->value;
+                    add_node_error(g, source_node,
+                            buf_sprintf("variable shadows type '%s'", buf_ptr(&type->name)));
+                    variable_entry->type = g->builtin_types.entry_invalid;
                 } else {
-                    add_node_error(g, first_executing_node(var_decl->expr),
-                        buf_sprintf("global variable initializer requires constant expression"));
-                    implicit_type = g->builtin_types.entry_invalid;
+                    AstNode *decl_node = find_decl(context, name);
+                    if (decl_node && decl_node->type != NodeTypeVariableDeclaration) {
+                        ErrorMsg *msg = add_node_error(g, source_node,
+                                buf_sprintf("redefinition of '%s'", buf_ptr(name)));
+                        add_error_note(g, msg, decl_node, buf_sprintf("previous definition is here"));
+                        variable_entry->type = g->builtin_types.entry_invalid;
+                    }
                 }
             }
         }
+
+        context->var_table.put(&variable_entry->name, variable_entry);
+    } else {
+        // TODO replace _anon with @anon and make sure all tests still pass
+        buf_init_from_str(&variable_entry->name, "_anon");
+    }
+    if (context->fn_entry) {
+        context->fn_entry->variable_list.append(variable_entry);
+    }
+
+    variable_entry->src_is_const = is_const;
+    variable_entry->gen_is_const = is_const;
+    variable_entry->decl_node = source_node;
+    variable_entry->val_node = val_node;
+
+
+    return variable_entry;
+}
+
+static VariableTableEntry *add_local_var(CodeGen *g, AstNode *source_node, ImportTableEntry *import,
+        BlockContext *context, Buf *name, TypeTableEntry *type_entry, bool is_const, AstNode *val_node)
+{
+    return add_local_var_shadowable(g, source_node, import, context, name, type_entry, is_const, val_node, false);
+}
+
+static void resolve_var_decl(CodeGen *g, ImportTableEntry *import, AstNode *node) {
+    assert(node->type == NodeTypeVariableDeclaration);
+
+    AstNodeVariableDeclaration *var_decl = &node->data.variable_declaration;
+    BlockContext *scope = node->block_context;
+    bool is_const = var_decl->is_const;
+    bool is_export = (var_decl->top_level_decl.visib_mod == VisibModExport);
+    bool is_extern = var_decl->is_extern;
+
+    assert(!scope->fn_entry);
+
+    TypeTableEntry *explicit_type = nullptr;
+    if (var_decl->type) {
+        TypeTableEntry *proposed_type = analyze_type_expr(g, import, scope, var_decl->type);
+        explicit_type = validate_var_type(g, var_decl->type, proposed_type);
+    }
+
+    TypeTableEntry *implicit_type = nullptr;
+    if (explicit_type && explicit_type->id == TypeTableEntryIdInvalid) {
+        implicit_type = explicit_type;
+    } else if (var_decl->expr) {
+        IrInstruction *result = analyze_const_value(g, scope, var_decl->expr, explicit_type);
+        assert(result);
+        implicit_type = result->type_entry;
+
+        if (implicit_type->id == TypeTableEntryIdUnreachable) {
+            add_node_error(g, node, buf_sprintf("variable initialization is unreachable"));
+            implicit_type = g->builtin_types.entry_invalid;
+        } else if ((!is_const || is_export) &&
+                (implicit_type->id == TypeTableEntryIdNumLitFloat ||
+                implicit_type->id == TypeTableEntryIdNumLitInt))
+        {
+            add_node_error(g, node, buf_sprintf("unable to infer variable type"));
+            implicit_type = g->builtin_types.entry_invalid;
+        } else if (implicit_type->id == TypeTableEntryIdMetaType && !is_const) {
+            add_node_error(g, node, buf_sprintf("variable of type 'type' must be constant"));
+            implicit_type = g->builtin_types.entry_invalid;
+        }
+        if (implicit_type->id != TypeTableEntryIdInvalid) {
+            Expr *expr = get_resolved_expr(var_decl->expr);
+            assert(result->static_value.ok);
+            expr->const_val = result->static_value;
+            expr->type_entry = result->type_entry;
+        }
     } else if (!is_extern) {
         add_node_error(g, node, buf_sprintf("variables must be initialized"));
         implicit_type = g->builtin_types.entry_invalid;
@@ -1906,30 +2084,6 @@ void resolve_top_level_decl(CodeGen *g, AstNode *node, bool pointer_only) {
     tld->dep_loop_flag = false;
 }
 
-static FnTableEntry *get_context_fn_entry(BlockContext *context) {
-    assert(context->fn_entry);
-    return context->fn_entry;
-}
-
-static TypeTableEntry *unwrapped_node_type(AstNode *node) {
-    Expr *expr = get_resolved_expr(node);
-    if (expr->type_entry->id == TypeTableEntryIdInvalid) {
-        return expr->type_entry;
-    }
-    assert(expr->type_entry->id == TypeTableEntryIdMetaType);
-    ConstExprValue *const_val = &expr->const_val;
-    assert(const_val->ok);
-    return const_val->data.x_type;
-}
-
-static TypeTableEntry *get_return_type(BlockContext *context) {
-    FnTableEntry *fn_entry = get_context_fn_entry(context);
-    AstNode *fn_proto_node = fn_entry->proto_node;
-    assert(fn_proto_node->type == NodeTypeFnProto);
-    AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type;
-    return unwrapped_node_type(return_type_node);
-}
-
 static bool type_has_codegen_value(TypeTableEntry *type_entry) {
     switch (type_entry->id) {
         case TypeTableEntryIdInvalid:
@@ -1968,18 +2122,6 @@ static bool type_has_codegen_value(TypeTableEntry *type_entry) {
     zig_unreachable();
 }
 
-static void add_global_const_expr(CodeGen *g, AstNode *expr_node) {
-    Expr *expr = get_resolved_expr(expr_node);
-    if (expr->const_val.ok &&
-        type_has_codegen_value(expr->type_entry) &&
-        !expr->has_global_const &&
-        type_has_bits(expr->type_entry))
-    {
-        g->global_const_list.append(expr_node);
-        expr->has_global_const = true;
-    }
-}
-
 static bool num_lit_fits_in_other_type(CodeGen *g, AstNode *literal_node, TypeTableEntry *other_type) {
     TypeTableEntry *other_type_underlying = get_underlying_type(other_type);
 
@@ -2105,85 +2247,6 @@ bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *
     return false;
 }
 
-static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *parent_source_node,
-        AstNode **child_nodes, TypeTableEntry **child_types, size_t child_count)
-{
-    TypeTableEntry *prev_type = child_types[0];
-    AstNode *prev_node = child_nodes[0];
-    if (prev_type->id == TypeTableEntryIdInvalid) {
-        return prev_type;
-    }
-    for (size_t i = 1; i < child_count; i += 1) {
-        TypeTableEntry *cur_type = child_types[i];
-        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;
-        } else if (cur_type->id == TypeTableEntryIdUnreachable) {
-            continue;
-        } else if (prev_type->id == TypeTableEntryIdInt &&
-                   cur_type->id == TypeTableEntryIdInt &&
-                   prev_type->data.integral.is_signed == cur_type->data.integral.is_signed)
-        {
-            if (cur_type->data.integral.bit_count > prev_type->data.integral.bit_count) {
-                prev_type = cur_type;
-                prev_node = cur_node;
-            }
-            continue;
-        } else if (prev_type->id == TypeTableEntryIdFloat &&
-                   cur_type->id == TypeTableEntryIdFloat)
-        {
-            if (cur_type->data.floating.bit_count > prev_type->data.floating.bit_count) {
-                prev_type = cur_type;
-                prev_node = cur_node;
-            }
-        } else if (prev_type->id == TypeTableEntryIdErrorUnion &&
-                   types_match_const_cast_only(prev_type->data.error.child_type, cur_type))
-        {
-            continue;
-        } else if (cur_type->id == TypeTableEntryIdErrorUnion &&
-                   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 == TypeTableEntryIdNumLitInt ||
-                    prev_type->id == TypeTableEntryIdNumLitFloat)
-        {
-            if (num_lit_fits_in_other_type(g, prev_node, cur_type)) {
-                prev_type = cur_type;
-                prev_node = cur_node;
-                continue;
-            } else {
-                return g->builtin_types.entry_invalid;
-            }
-        } else if (cur_type->id == TypeTableEntryIdNumLitInt ||
-                   cur_type->id == TypeTableEntryIdNumLitFloat)
-        {
-            if (num_lit_fits_in_other_type(g, cur_node, prev_type)) {
-                continue;
-            } else {
-                return g->builtin_types.entry_invalid;
-            }
-        } else {
-            add_node_error(g, parent_source_node,
-                buf_sprintf("incompatible types: '%s' and '%s'",
-                    buf_ptr(&prev_type->name), buf_ptr(&cur_type->name)));
-
-            return g->builtin_types.entry_invalid;
-        }
-    }
-    return prev_type;
-}
-
 static bool types_match_with_implicit_cast(CodeGen *g, TypeTableEntry *expected_type,
         TypeTableEntry *actual_type, AstNode *literal_node, bool *reported_err)
 {
@@ -2272,104 +2335,6 @@ static bool types_match_with_implicit_cast(CodeGen *g, TypeTableEntry *expected_
     return false;
 }
 
-static AstNode *create_ast_node(CodeGen *g, ImportTableEntry *import, NodeType kind, AstNode *source_node) {
-    AstNode *node = allocate<AstNode>(1);
-    node->type = kind;
-    node->owner = import;
-    node->create_index = g->next_node_index;
-    g->next_node_index += 1;
-    node->line = source_node->line;
-    node->column = source_node->column;
-    return node;
-}
-
-static AstNode *create_ast_type_node(CodeGen *g, ImportTableEntry *import, TypeTableEntry *type_entry,
-        AstNode *source_node)
-{
-    AstNode *node = create_ast_node(g, import, NodeTypeSymbol, source_node);
-    node->data.symbol_expr.override_type_entry = type_entry;
-    return node;
-}
-
-static AstNode *create_ast_void_node(CodeGen *g, ImportTableEntry *import, AstNode *source_node) {
-    AstNode *node = create_ast_node(g, import, NodeTypeContainerInitExpr, source_node);
-    node->data.container_init_expr.kind = ContainerInitKindArray;
-    node->data.container_init_expr.type = create_ast_type_node(g, import, g->builtin_types.entry_void,
-            source_node);
-    normalize_parent_ptrs(node);
-    return node;
-}
-
-static TypeTableEntry *create_and_analyze_cast_node(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, TypeTableEntry *cast_to_type, AstNode *node)
-{
-    AstNode *new_parent_node = create_ast_node(g, import, NodeTypeFnCallExpr, node);
-    *node->parent_field = new_parent_node;
-    new_parent_node->parent_field = node->parent_field;
-
-    new_parent_node->data.fn_call_expr.fn_ref_expr = create_ast_type_node(g, import, cast_to_type, node);
-    new_parent_node->data.fn_call_expr.params.append(node);
-    normalize_parent_ptrs(new_parent_node);
-
-    return analyze_expression(g, import, context, cast_to_type, new_parent_node);
-}
-
-static TypeTableEntry *resolve_type_compatibility(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node,
-        TypeTableEntry *expected_type, TypeTableEntry *actual_type)
-{
-    if (expected_type == nullptr)
-        return actual_type; // anything will do
-    if (expected_type == actual_type)
-        return expected_type; // match
-    if (expected_type->id == TypeTableEntryIdInvalid || actual_type->id == TypeTableEntryIdInvalid)
-        return g->builtin_types.entry_invalid;
-    if (actual_type->id == TypeTableEntryIdUnreachable)
-        return actual_type;
-
-    bool reported_err = false;
-    if (types_match_with_implicit_cast(g, expected_type, actual_type, node, &reported_err)) {
-        return create_and_analyze_cast_node(g, import, context, expected_type, node);
-    }
-
-    if (!reported_err) {
-        add_node_error(g, first_executing_node(node),
-            buf_sprintf("expected type '%s', got '%s'",
-                buf_ptr(&expected_type->name),
-                buf_ptr(&actual_type->name)));
-    }
-
-    return g->builtin_types.entry_invalid;
-}
-
-TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, ImportTableEntry *import,
-        BlockContext *block_context, AstNode *parent_source_node,
-        AstNode **child_nodes, TypeTableEntry **child_types, size_t child_count)
-{
-    assert(child_count > 0);
-
-    TypeTableEntry *expected_type = determine_peer_type_compatibility(g, parent_source_node,
-            child_nodes, child_types, child_count);
-
-    if (expected_type->id == TypeTableEntryIdInvalid) {
-        return expected_type;
-    }
-
-    for (size_t i = 0; i < child_count; i += 1) {
-        if (!child_nodes[i]) {
-            continue;
-        }
-        AstNode **child_node = child_nodes[i]->parent_field;
-        TypeTableEntry *resolved_type = resolve_type_compatibility(g, import, block_context,
-                *child_node, expected_type, child_types[i]);
-        Expr *expr = get_resolved_expr(*child_node);
-        expr->type_entry = resolved_type;
-        add_global_const_expr(g, *child_node);
-    }
-
-    return expected_type;
-}
-
 BlockContext *new_block_context(AstNode *node, BlockContext *parent) {
     BlockContext *context = allocate<BlockContext>(1);
     context->node = node;
@@ -2432,67 +2397,6 @@ TypeEnumField *find_enum_type_field(TypeTableEntry *enum_type, Buf *name) {
     return nullptr;
 }
 
-static TypeTableEntry *analyze_enum_value_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode *field_access_node, AstNode *value_node, TypeTableEntry *enum_type, Buf *field_name,
-        AstNode *out_node)
-{
-    assert(field_access_node->type == NodeTypeFieldAccessExpr);
-
-    TypeEnumField *type_enum_field = find_enum_type_field(enum_type, field_name);
-    if (type_enum_field->type_entry->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    field_access_node->data.field_access_expr.type_enum_field = type_enum_field;
-
-    if (type_enum_field) {
-        if (value_node) {
-            AstNode **value_node_ptr = value_node->parent_field;
-            TypeTableEntry *value_type = analyze_expression(g, import, context,
-                    type_enum_field->type_entry, value_node);
-
-            if (value_type->id == TypeTableEntryIdInvalid) {
-                return g->builtin_types.entry_invalid;
-            }
-
-            StructValExprCodeGen *codegen = &field_access_node->data.field_access_expr.resolved_struct_val_expr;
-            codegen->type_entry = enum_type;
-            codegen->source_node = field_access_node;
-
-            ConstExprValue *value_const_val = &get_resolved_expr(*value_node_ptr)->const_val;
-            if (value_const_val->ok) {
-                ConstExprValue *const_val = &get_resolved_expr(out_node)->const_val;
-                const_val->ok = true;
-                const_val->data.x_enum.tag = type_enum_field->value;
-                const_val->data.x_enum.payload = value_const_val;
-            } else {
-                if (context->fn_entry) {
-                    context->fn_entry->struct_val_expr_alloca_list.append(codegen);
-                } else {
-                    add_node_error(g, *value_node_ptr, buf_sprintf("unable to evaluate constant expression"));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        } else if (type_enum_field->type_entry->id != TypeTableEntryIdVoid) {
-            add_node_error(g, field_access_node,
-                buf_sprintf("enum value '%s.%s' requires parameter of type '%s'",
-                    buf_ptr(&enum_type->name),
-                    buf_ptr(field_name),
-                    buf_ptr(&type_enum_field->type_entry->name)));
-        } else {
-            Expr *expr = get_resolved_expr(out_node);
-            expr->const_val.ok = true;
-            expr->const_val.data.x_enum.tag = type_enum_field->value;
-            expr->const_val.data.x_enum.payload = nullptr;
-        }
-    } else {
-        add_node_error(g, field_access_node,
-            buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
-                buf_ptr(&enum_type->name)));
-    }
-    return enum_type;
-}
-
 TypeStructField *find_struct_type_field(TypeTableEntry *type_entry, Buf *name) {
     assert(type_entry->id == TypeTableEntryIdStruct);
     assert(type_entry->data.structure.complete);
@@ -2587,2703 +2491,50 @@ void resolve_container_type(CodeGen *g, TypeTableEntry *type_entry) {
     }
 }
 
-static TypeTableEntry *analyze_container_member_access_inner(CodeGen *g,
-    TypeTableEntry *bare_struct_type, Buf *field_name, AstNode *node, TypeTableEntry *struct_type)
-{
-    assert(node->type == NodeTypeFieldAccessExpr);
-    if (!is_slice(bare_struct_type)) {
-        BlockContext *container_block_context = get_container_block_context(bare_struct_type);
-        assert(container_block_context);
-        auto entry = container_block_context->decl_table.maybe_get(field_name);
-        AstNode *fn_decl_node = entry ? entry->value : nullptr;
-        if (fn_decl_node && fn_decl_node->type == NodeTypeFnProto) {
-            resolve_top_level_decl(g, fn_decl_node, false);
-            TopLevelDecl *tld = get_as_top_level_decl(fn_decl_node);
-            if (tld->resolution == TldResolutionInvalid) {
-                return g->builtin_types.entry_invalid;
-            }
-
-            node->data.field_access_expr.is_member_fn = true;
-            FnTableEntry *fn_entry = fn_decl_node->data.fn_proto.fn_table_entry;
-            if (fn_entry->type_entry->id == TypeTableEntryIdGenericFn) {
-                return resolve_expr_const_val_as_generic_fn(g, node, fn_entry->type_entry, false);
-            } else {
-                return resolve_expr_const_val_as_fn(g, node, fn_entry, false);
-            }
-        }
+bool type_is_codegen_pointer(TypeTableEntry *type) {
+    if (type->id == TypeTableEntryIdPointer) return true;
+    if (type->id == TypeTableEntryIdFn) return true;
+    if (type->id == TypeTableEntryIdMaybe) {
+        if (type->data.maybe.child_type->id == TypeTableEntryIdPointer) return true;
+        if (type->data.maybe.child_type->id == TypeTableEntryIdFn) return true;
     }
-    add_node_error(g, node,
-        buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&bare_struct_type->name)));
-    return g->builtin_types.entry_invalid;
+    return false;
 }
 
-static TypeTableEntry *analyze_container_member_access(CodeGen *g,
-        Buf *field_name, AstNode *node, TypeTableEntry *struct_type)
-{
-    TypeTableEntry *bare_type = container_ref_type(struct_type);
-    if (!type_is_complete(bare_type)) {
-        resolve_container_type(g, bare_type);
-    }
 
-    node->data.field_access_expr.bare_container_type = bare_type;
 
-    if (bare_type->id == TypeTableEntryIdStruct) {
-        node->data.field_access_expr.type_struct_field = find_struct_type_field(bare_type, field_name);
-        if (node->data.field_access_expr.type_struct_field) {
-            return node->data.field_access_expr.type_struct_field->type_entry;
-        } else {
-            return analyze_container_member_access_inner(g, bare_type, field_name,
-                node, struct_type);
-        }
-    } else if (bare_type->id == TypeTableEntryIdEnum) {
-        node->data.field_access_expr.type_enum_field = find_enum_type_field(bare_type, field_name);
-        if (node->data.field_access_expr.type_enum_field) {
-            return node->data.field_access_expr.type_enum_field->type_entry;
-        } else {
-            return analyze_container_member_access_inner(g, bare_type, field_name,
-                node, struct_type);
-        }
-    } else if (bare_type->id == TypeTableEntryIdUnion) {
-        zig_panic("TODO");
-    } else {
-        zig_unreachable();
-    }
-}
+static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
+    ImportTableEntry *import = fn_table_entry->import_entry;
+    AstNode *node = fn_table_entry->fn_def_node;
+    assert(node->type == NodeTypeFnDef);
 
-static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeFieldAccessExpr);
-
-    AstNode **struct_expr_node = &node->data.field_access_expr.struct_expr;
-    TypeTableEntry *struct_type = analyze_expression(g, import, context, nullptr, *struct_expr_node);
-    Buf *field_name = node->data.field_access_expr.field_name;
-
-    if (struct_type->id == TypeTableEntryIdInvalid) {
-        return struct_type;
-    } else if (is_container_ref(struct_type)) {
-        return analyze_container_member_access(g, field_name, node, struct_type);
-    } else if (struct_type->id == TypeTableEntryIdArray) {
-        if (buf_eql_str(field_name, "len")) {
-            return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-                    struct_type->data.array.len, false);
-        } else {
-            add_node_error(g, node,
-                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
-                    buf_ptr(&struct_type->name)));
-            return g->builtin_types.entry_invalid;
-        }
-    } else if (struct_type->id == TypeTableEntryIdMetaType) {
-        TypeTableEntry *child_type = resolve_type(g, *struct_expr_node);
+    AstNode *fn_proto_node = node->data.fn_def.fn_proto;
+    assert(fn_proto_node->type == NodeTypeFnProto);
 
-        if (child_type->id == TypeTableEntryIdInvalid) {
-            return g->builtin_types.entry_invalid;
-        } else if (child_type->id == TypeTableEntryIdEnum) {
-            AstNode *container_init_node = node->data.field_access_expr.container_init_expr_node;
-            AstNode *value_node;
-            if (container_init_node) {
-                assert(container_init_node->type == NodeTypeContainerInitExpr);
-                size_t param_count = container_init_node->data.container_init_expr.entries.length;
-                if (param_count > 1) {
-                    AstNode *first_invalid_node = container_init_node->data.container_init_expr.entries.at(1);
-                    add_node_error(g, first_executing_node(first_invalid_node),
-                            buf_sprintf("enum values accept only one parameter"));
-                    return child_type;
-                } else {
-                    if (param_count == 1) {
-                        value_node = container_init_node->data.container_init_expr.entries.at(0);
-                    } else {
-                        value_node = nullptr;
-                    }
-                    container_init_node->data.container_init_expr.enum_type = child_type;
-                }
-            } else {
-                value_node = nullptr;
-            }
-            return analyze_enum_value_expr(g, import, context, node, value_node, child_type, field_name, node);
-        } else if (child_type->id == TypeTableEntryIdStruct) {
-            BlockContext *container_block_context = get_container_block_context(child_type);
-            auto entry = container_block_context->decl_table.maybe_get(field_name);
-            AstNode *decl_node = entry ? entry->value : nullptr;
-            if (decl_node) {
-                bool pointer_only = false;
-                return analyze_decl_ref(g, node, decl_node, pointer_only, context, false);
-            } else {
-                add_node_error(g, node,
-                    buf_sprintf("container '%s' has no member called '%s'",
-                        buf_ptr(&child_type->name), buf_ptr(field_name)));
-                return g->builtin_types.entry_invalid;
-            }
-        } else if (child_type->id == TypeTableEntryIdPureError) {
-            return analyze_error_literal_expr(g, import, context, node, field_name);
-        } else if (child_type->id == TypeTableEntryIdInt) {
-            bool depends_on_compile_var =
-                get_resolved_expr(*struct_expr_node)->const_val.depends_on_compile_var;
-            if (buf_eql_str(field_name, "bit_count")) {
-                return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-                        child_type->data.integral.bit_count, depends_on_compile_var);
-            } else if (buf_eql_str(field_name, "is_signed")) {
-                return resolve_expr_const_val_as_bool(g, node, child_type->data.integral.is_signed,
-                        depends_on_compile_var);
-            } else {
-                add_node_error(g, node,
-                    buf_sprintf("type '%s' has no member called '%s'",
-                        buf_ptr(&child_type->name), buf_ptr(field_name)));
-                return g->builtin_types.entry_invalid;
-            }
-        } else {
-            add_node_error(g, node,
-                buf_sprintf("type '%s' does not support field access", buf_ptr(&struct_type->name)));
-            return g->builtin_types.entry_invalid;
-        }
-    } else if (struct_type->id == TypeTableEntryIdNamespace) {
-        ConstExprValue *const_val = &get_resolved_expr(*struct_expr_node)->const_val;
-        assert(const_val->ok);
-        ImportTableEntry *namespace_import = const_val->data.x_import;
-        AstNode *decl_node = find_decl(namespace_import->block_context, field_name);
-        if (!decl_node) {
-            // we must now resolve all the use decls
-            for (size_t i = 0; i < namespace_import->use_decls.length; i += 1) {
-                AstNode *use_decl_node = namespace_import->use_decls.at(i);
-                if (!get_resolved_expr(use_decl_node->data.use.expr)->type_entry) {
-                    preview_use_decl(g, use_decl_node);
-                }
-                resolve_use_decl(g, use_decl_node);
-            }
-            decl_node = find_decl(namespace_import->block_context, field_name);
-        }
-        if (decl_node) {
-            TopLevelDecl *tld = get_as_top_level_decl(decl_node);
-            if (tld->visib_mod == VisibModPrivate && decl_node->owner != import) {
-                ErrorMsg *msg = add_node_error(g, node,
-                    buf_sprintf("'%s' is private", buf_ptr(field_name)));
-                add_error_note(g, msg, decl_node, buf_sprintf("declared here"));
-            }
-            bool pointer_only = false;
-            return analyze_decl_ref(g, node, decl_node, pointer_only, context,
-                    const_val->depends_on_compile_var);
-        } else {
-            const char *import_name = namespace_import->path ? buf_ptr(namespace_import->path) : "(C import)";
-            add_node_error(g, node,
-                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), import_name));
-            return g->builtin_types.entry_invalid;
-        }
-    } else {
-        add_node_error(g, node,
-            buf_sprintf("type '%s' does not support field access", buf_ptr(&struct_type->name)));
-        return g->builtin_types.entry_invalid;
+    if (fn_proto_node->data.fn_proto.skip) {
+        // we detected an error with this function definition which prevents us
+        // from further analyzing it.
+        fn_table_entry->anal_state = FnAnalStateSkipped;
+        return;
     }
-}
+    fn_table_entry->anal_state = FnAnalStateProbing;
 
-static TypeTableEntry *analyze_slice_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode *node)
-{
-    assert(node->type == NodeTypeSliceExpr);
-
-    TypeTableEntry *array_type = analyze_expression(g, import, context, nullptr,
-            node->data.slice_expr.array_ref_expr);
-
-    TypeTableEntry *return_type;
-
-    if (array_type->id == TypeTableEntryIdInvalid) {
-        return_type = g->builtin_types.entry_invalid;
-    } else if (array_type->id == TypeTableEntryIdArray) {
-        return_type = get_slice_type(g, array_type->data.array.child_type,
-                node->data.slice_expr.is_const);
-    } else if (array_type->id == TypeTableEntryIdPointer) {
-        return_type = get_slice_type(g, array_type->data.pointer.child_type,
-                node->data.slice_expr.is_const);
-    } else if (array_type->id == TypeTableEntryIdStruct &&
-               array_type->data.structure.is_slice)
-    {
-        return_type = get_slice_type(g,
-                array_type->data.structure.fields[0].type_entry->data.pointer.child_type,
-                node->data.slice_expr.is_const);
-    } else {
-        add_node_error(g, node,
-            buf_sprintf("slice of non-array type '%s'", buf_ptr(&array_type->name)));
-        return_type = g->builtin_types.entry_invalid;
-    }
+    BlockContext *context = node->data.fn_def.block_context;
 
-    if (return_type->id != TypeTableEntryIdInvalid) {
-        node->data.slice_expr.resolved_struct_val_expr.type_entry = return_type;
-        node->data.slice_expr.resolved_struct_val_expr.source_node = node;
-        context->fn_entry->struct_val_expr_alloca_list.append(&node->data.slice_expr.resolved_struct_val_expr);
-    }
+    TypeTableEntry *fn_type = fn_table_entry->type_entry;
+    FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
+    AstNodeFnProto *fn_proto = &fn_proto_node->data.fn_proto;
+    for (size_t i = 0; i < fn_proto->params.length; i += 1) {
+        AstNode *param_decl_node = fn_proto->params.at(i);
+        assert(param_decl_node->type == NodeTypeParamDecl);
 
-    analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.slice_expr.start);
+        // define local variables for parameters
+        AstNodeParamDecl *param_decl = &param_decl_node->data.param_decl;
+        TypeTableEntry *type = fn_type_id->param_info[i].type;
 
-    if (node->data.slice_expr.end) {
-        analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.slice_expr.end);
-    }
-
-    return return_type;
-}
-
-static TypeTableEntry *analyze_array_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode *node, LValPurpose purpose)
-{
-    TypeTableEntry *array_type = analyze_expression(g, import, context, nullptr,
-            node->data.array_access_expr.array_ref_expr);
-
-    TypeTableEntry *return_type;
-
-    if (array_type->id == TypeTableEntryIdInvalid) {
-        return_type = g->builtin_types.entry_invalid;
-    } else if (array_type->id == TypeTableEntryIdArray) {
-        if (array_type->data.array.len == 0) {
-            add_node_error(g, node, buf_sprintf("out of bounds array access"));
-        }
-        return_type = array_type->data.array.child_type;
-    } else if (array_type->id == TypeTableEntryIdPointer) {
-        if (array_type->data.pointer.is_const && purpose == LValPurposeAssign) {
-            add_node_error(g, node, buf_sprintf("cannot assign to constant"));
-            return g->builtin_types.entry_invalid;
-        }
-        return_type = array_type->data.pointer.child_type;
-    } else if (array_type->id == TypeTableEntryIdStruct &&
-               array_type->data.structure.is_slice)
-    {
-        TypeTableEntry *pointer_type = array_type->data.structure.fields[0].type_entry;
-        if (pointer_type->data.pointer.is_const && purpose == LValPurposeAssign) {
-            add_node_error(g, node, buf_sprintf("cannot assign to constant"));
-            return g->builtin_types.entry_invalid;
-        }
-        return_type = pointer_type->data.pointer.child_type;
-    } else {
-        add_node_error(g, node,
-                buf_sprintf("array access of non-array type '%s'", buf_ptr(&array_type->name)));
-        return_type = g->builtin_types.entry_invalid;
-    }
-
-    analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.array_access_expr.subscript);
-
-    return return_type;
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_type(CodeGen *g, AstNode *node, TypeTableEntry *type,
-        bool depends_on_compile_var)
-{
-    Expr *expr = get_resolved_expr(node);
-    expr->const_val.ok = true;
-    expr->const_val.data.x_type = type;
-    expr->const_val.depends_on_compile_var = depends_on_compile_var;
-    return g->builtin_types.entry_type;
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_other_expr(CodeGen *g, AstNode *node, AstNode *other,
-        bool depends_on_compile_var)
-{
-    Expr *expr = get_resolved_expr(node);
-    Expr *other_expr = get_resolved_expr(other);
-    expr->const_val = other_expr->const_val;
-    expr->const_val.depends_on_compile_var = expr->const_val.depends_on_compile_var ||
-        depends_on_compile_var;
-    return other_expr->type_entry;
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_fn(CodeGen *g, AstNode *node, FnTableEntry *fn,
-        bool depends_on_compile_var)
-{
-    Expr *expr = get_resolved_expr(node);
-    expr->const_val.ok = true;
-    expr->const_val.data.x_fn = fn;
-    expr->const_val.depends_on_compile_var = depends_on_compile_var;
-    return fn->type_entry;
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_generic_fn(CodeGen *g, AstNode *node,
-        TypeTableEntry *type_entry, bool depends_on_compile_var)
-{
-    Expr *expr = get_resolved_expr(node);
-    expr->const_val.ok = true;
-    expr->const_val.data.x_type = type_entry;
-    expr->const_val.depends_on_compile_var = depends_on_compile_var;
-    return type_entry;
-}
-
-
-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 = err;
-    return g->builtin_types.entry_pure_error;
-}
-
-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;
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_c_string_lit(CodeGen *g, AstNode *node, Buf *str) {
-    Expr *expr = get_resolved_expr(node);
-    expr->const_val.ok = true;
-
-    size_t len_with_null = buf_len(str) + 1;
-    expr->const_val.data.x_ptr.ptr = allocate<ConstExprValue*>(len_with_null);
-    expr->const_val.data.x_ptr.len = len_with_null;
-    expr->const_val.data.x_ptr.is_c_str = true;
-
-    ConstExprValue *all_chars = allocate<ConstExprValue>(len_with_null);
-    for (size_t i = 0; i < buf_len(str); i += 1) {
-        ConstExprValue *this_char = &all_chars[i];
-        this_char->ok = true;
-        bignum_init_unsigned(&this_char->data.x_bignum, buf_ptr(str)[i]);
-        expr->const_val.data.x_ptr.ptr[i] = this_char;
-    }
-
-    ConstExprValue *null_char = &all_chars[len_with_null - 1];
-    null_char->ok = true;
-    bignum_init_unsigned(&null_char->data.x_bignum, 0);
-    expr->const_val.data.x_ptr.ptr[len_with_null - 1] = null_char;
-
-    return get_pointer_to_type(g, g->builtin_types.entry_u8, true);
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_string_lit(CodeGen *g, AstNode *node, Buf *str) {
-    Expr *expr = get_resolved_expr(node);
-    expr->const_val.ok = true;
-    expr->const_val.data.x_array.fields = allocate<ConstExprValue*>(buf_len(str));
-
-    ConstExprValue *all_chars = allocate<ConstExprValue>(buf_len(str));
-    for (size_t i = 0; i < buf_len(str); i += 1) {
-        ConstExprValue *this_char = &all_chars[i];
-        this_char->ok = true;
-        bignum_init_unsigned(&this_char->data.x_bignum, buf_ptr(str)[i]);
-        expr->const_val.data.x_array.fields[i] = this_char;
-    }
-    return get_array_type(g, g->builtin_types.entry_u8, buf_len(str));
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_bignum(CodeGen *g, AstNode *node,
-        TypeTableEntry *expected_type, BigNum *bignum, 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;
-
-    bignum_init_bignum(&expr->const_val.data.x_bignum, bignum);
-    if (bignum->kind == BigNumKindInt) {
-        return g->builtin_types.entry_num_lit_int;
-    } else if (bignum->kind == BigNumKindFloat) {
-        return g->builtin_types.entry_num_lit_float;
-    } else {
-        zig_unreachable();
-    }
-}
-
-static TypeTableEntry *resolve_expr_const_val_as_unsigned_num_lit(CodeGen *g, AstNode *node,
-        TypeTableEntry *expected_type, uint64_t x, 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;
-
-    bignum_init_unsigned(&expr->const_val.data.x_bignum, x);
-
-    return g->builtin_types.entry_num_lit_int;
-}
-
-static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *node, Buf *err_name)
-{
-    auto err_table_entry = g->error_table.maybe_get(err_name);
-
-    if (err_table_entry) {
-        return resolve_expr_const_val_as_err(g, node, err_table_entry->value);
-    }
-
-    add_node_error(g, node,
-            buf_sprintf("use of undeclared error value '%s'", buf_ptr(err_name)));
-
-    return g->builtin_types.entry_invalid;
-}
-
-static bool var_is_pure(VariableTableEntry *var, BlockContext *context) {
-    if (var->block_context->fn_entry == context->fn_entry) {
-        // variable was declared in the current function, so it's OK.
-        return true;
-    }
-    return var->src_is_const && var->type->deep_const;
-}
-
-static TypeTableEntry *analyze_var_ref(CodeGen *g, AstNode *source_node, VariableTableEntry *var,
-        BlockContext *context, bool depends_on_compile_var)
-{
-    get_resolved_expr(source_node)->variable = var;
-    if (!var_is_pure(var, context)) {
-        mark_impure_fn(g, context, source_node);
-    }
-    if (var->src_is_const && var->val_node) {
-        ConstExprValue *other_const_val = &get_resolved_expr(var->val_node)->const_val;
-        if (other_const_val->ok) {
-            return resolve_expr_const_val_as_other_expr(g, source_node, var->val_node,
-                    depends_on_compile_var || var->force_depends_on_compile_var);
-        }
-    }
-    return var->type;
-}
-
-static TypeTableEntry *analyze_decl_ref(CodeGen *g, AstNode *source_node, AstNode *decl_node,
-        bool pointer_only, BlockContext *block_context, bool depends_on_compile_var)
-{
-    resolve_top_level_decl(g, decl_node, pointer_only);
-    TopLevelDecl *tld = get_as_top_level_decl(decl_node);
-    if (tld->resolution == TldResolutionInvalid) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (decl_node->type == NodeTypeVariableDeclaration) {
-        VariableTableEntry *var = decl_node->data.variable_declaration.variable;
-        return analyze_var_ref(g, source_node, var, block_context, depends_on_compile_var);
-    } else if (decl_node->type == NodeTypeFnProto) {
-        FnTableEntry *fn_entry = decl_node->data.fn_proto.fn_table_entry;
-        assert(fn_entry->type_entry);
-        if (fn_entry->type_entry->id == TypeTableEntryIdGenericFn) {
-            return resolve_expr_const_val_as_generic_fn(g, source_node, fn_entry->type_entry, depends_on_compile_var);
-        } else {
-            return resolve_expr_const_val_as_fn(g, source_node, fn_entry, depends_on_compile_var);
-        }
-    } else if (decl_node->type == NodeTypeContainerDecl) {
-        if (decl_node->data.struct_decl.generic_params.length > 0) {
-            TypeTableEntry *type_entry = decl_node->data.struct_decl.generic_fn_type;
-            assert(type_entry);
-            return resolve_expr_const_val_as_generic_fn(g, source_node, type_entry, depends_on_compile_var);
-        } else {
-            return resolve_expr_const_val_as_type(g, source_node, decl_node->data.struct_decl.type_entry,
-                    depends_on_compile_var);
-        }
-    } else if (decl_node->type == NodeTypeTypeDecl) {
-        return resolve_expr_const_val_as_type(g, source_node, decl_node->data.type_decl.child_type_entry,
-                depends_on_compile_var);
-    } else {
-        zig_unreachable();
-    }
-}
-
-static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node, bool pointer_only)
-{
-    Buf *variable_name = node->data.symbol_expr.symbol;
-
-    auto primitive_table_entry = g->primitive_type_table.maybe_get(variable_name);
-    if (primitive_table_entry) {
-        return resolve_expr_const_val_as_type(g, node, primitive_table_entry->value, false);
-    }
-
-    VariableTableEntry *var = find_variable(g, context, variable_name);
-    if (var) {
-        TypeTableEntry *var_type = analyze_var_ref(g, node, var, context, false);
-        return var_type;
-    }
-
-    AstNode *decl_node = find_decl(context, variable_name);
-    if (decl_node) {
-        return analyze_decl_ref(g, node, decl_node, pointer_only, context, false);
-    }
-
-    if (import->any_imports_failed) {
-        // skip the error message since we had a failing import in this file
-        // if an import breaks we don't need 9999 undeclared identifier errors
-        return g->builtin_types.entry_invalid;
-    }
-
-    mark_impure_fn(g, context, node);
-    add_node_error(g, node, buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
-    return g->builtin_types.entry_invalid;
-}
-
-static bool is_op_allowed(TypeTableEntry *type, BinOpType op) {
-    switch (op) {
-        case BinOpTypeAssign:
-            return true;
-        case BinOpTypeAssignTimes:
-        case BinOpTypeAssignTimesWrap:
-        case BinOpTypeAssignDiv:
-        case BinOpTypeAssignMod:
-            return type->id == TypeTableEntryIdInt || type->id == TypeTableEntryIdFloat;
-        case BinOpTypeAssignPlus:
-        case BinOpTypeAssignPlusWrap:
-        case BinOpTypeAssignMinus:
-        case BinOpTypeAssignMinusWrap:
-            return type->id == TypeTableEntryIdInt ||
-                   type->id == TypeTableEntryIdFloat ||
-                   type->id == TypeTableEntryIdPointer;
-        case BinOpTypeAssignBitShiftLeft:
-        case BinOpTypeAssignBitShiftLeftWrap:
-        case BinOpTypeAssignBitShiftRight:
-        case BinOpTypeAssignBitAnd:
-        case BinOpTypeAssignBitXor:
-        case BinOpTypeAssignBitOr:
-            return type->id == TypeTableEntryIdInt;
-        case BinOpTypeAssignBoolAnd:
-        case BinOpTypeAssignBoolOr:
-            return type->id == TypeTableEntryIdBool;
-
-        case BinOpTypeInvalid:
-        case BinOpTypeBoolOr:
-        case BinOpTypeBoolAnd:
-        case BinOpTypeCmpEq:
-        case BinOpTypeCmpNotEq:
-        case BinOpTypeCmpLessThan:
-        case BinOpTypeCmpGreaterThan:
-        case BinOpTypeCmpLessOrEq:
-        case BinOpTypeCmpGreaterOrEq:
-        case BinOpTypeBinOr:
-        case BinOpTypeBinXor:
-        case BinOpTypeBinAnd:
-        case BinOpTypeBitShiftLeft:
-        case BinOpTypeBitShiftLeftWrap:
-        case BinOpTypeBitShiftRight:
-        case BinOpTypeAdd:
-        case BinOpTypeAddWrap:
-        case BinOpTypeSub:
-        case BinOpTypeSubWrap:
-        case BinOpTypeMult:
-        case BinOpTypeMultWrap:
-        case BinOpTypeDiv:
-        case BinOpTypeMod:
-        case BinOpTypeUnwrapMaybe:
-        case BinOpTypeArrayCat:
-        case BinOpTypeArrayMult:
-            zig_unreachable();
-    }
-    zig_unreachable();
-}
-
-static TypeTableEntry *analyze_lvalue(CodeGen *g, ImportTableEntry *import, BlockContext *block_context,
-        AstNode *lhs_node, LValPurpose purpose, bool is_ptr_const)
-{
-    TypeTableEntry *expected_rhs_type = nullptr;
-    lhs_node->block_context = block_context;
-    if (lhs_node->type == NodeTypeSymbol) {
-        bool pointer_only = purpose == LValPurposeAddressOf;
-        expected_rhs_type = analyze_symbol_expr(g, import, block_context, nullptr, lhs_node, pointer_only);
-        if (expected_rhs_type->id == TypeTableEntryIdInvalid) {
-            return g->builtin_types.entry_invalid;
-        }
-        if (purpose != LValPurposeAddressOf) {
-            Buf *name = lhs_node->data.symbol_expr.symbol;
-            VariableTableEntry *var = find_variable(g, block_context, name);
-            if (var) {
-                if (var->src_is_const) {
-                    add_node_error(g, lhs_node, buf_sprintf("cannot assign to constant"));
-                    expected_rhs_type = g->builtin_types.entry_invalid;
-                } else {
-                    expected_rhs_type = var->type;
-                    get_resolved_expr(lhs_node)->variable = var;
-                }
-            } else {
-                add_node_error(g, lhs_node,
-                        buf_sprintf("use of undeclared identifier '%s'", buf_ptr(name)));
-                expected_rhs_type = g->builtin_types.entry_invalid;
-            }
-        }
-    } else if (lhs_node->type == NodeTypeArrayAccessExpr) {
-        expected_rhs_type = analyze_array_access_expr(g, import, block_context, lhs_node, purpose);
-    } else if (lhs_node->type == NodeTypeFieldAccessExpr) {
-        expected_rhs_type = analyze_field_access_expr(g, import, block_context, nullptr, lhs_node);
-    } else if (lhs_node->type == NodeTypePrefixOpExpr &&
-            lhs_node->data.prefix_op_expr.prefix_op == PrefixOpDereference)
-    {
-        assert(purpose == LValPurposeAssign);
-        AstNode *target_node = lhs_node->data.prefix_op_expr.primary_expr;
-        TypeTableEntry *type_entry = analyze_expression(g, import, block_context, nullptr, target_node);
-        if (type_entry->id == TypeTableEntryIdInvalid) {
-            expected_rhs_type = type_entry;
-        } else if (type_entry->id == TypeTableEntryIdPointer) {
-            expected_rhs_type = type_entry->data.pointer.child_type;
-        } else {
-            add_node_error(g, target_node,
-                buf_sprintf("indirection requires pointer operand ('%s' invalid)",
-                    buf_ptr(&type_entry->name)));
-            expected_rhs_type = g->builtin_types.entry_invalid;
-        }
-    } else {
-        if (purpose == LValPurposeAssign) {
-            add_node_error(g, lhs_node, buf_sprintf("invalid assignment target"));
-            expected_rhs_type = g->builtin_types.entry_invalid;
-        } else if (purpose == LValPurposeAddressOf) {
-            TypeTableEntry *type_entry = analyze_expression(g, import, block_context, nullptr, lhs_node);
-            if (type_entry->id == TypeTableEntryIdInvalid) {
-                expected_rhs_type = g->builtin_types.entry_invalid;
-            } else if (type_entry->id == TypeTableEntryIdMetaType) {
-                expected_rhs_type = type_entry;
-            } else {
-                add_node_error(g, lhs_node, buf_sprintf("invalid addressof target"));
-                expected_rhs_type = g->builtin_types.entry_invalid;
-            }
-        }
-    }
-    assert(expected_rhs_type);
-    return expected_rhs_type;
-}
-
-static TypeTableEntry *analyze_bool_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode *node)
-{
-    assert(node->type == NodeTypeBinOpExpr);
-    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
-
-    AstNode **op1 = &node->data.bin_op_expr.op1;
-    AstNode **op2 = &node->data.bin_op_expr.op2;
-    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
-    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
-
-    AstNode *op_nodes[] = {*op1, *op2};
-    TypeTableEntry *op_types[] = {op1_type, op2_type};
-
-    TypeTableEntry *resolved_type = resolve_peer_type_compatibility(g, import, context, node,
-            op_nodes, op_types, 2);
-
-    bool is_equality_cmp = (bin_op_type == BinOpTypeCmpEq || bin_op_type == BinOpTypeCmpNotEq);
-
-    switch (resolved_type->id) {
-        case TypeTableEntryIdInvalid:
-            return g->builtin_types.entry_invalid;
-
-        case TypeTableEntryIdNumLitFloat:
-        case TypeTableEntryIdNumLitInt:
-        case TypeTableEntryIdInt:
-        case TypeTableEntryIdFloat:
-            break;
-
-        case TypeTableEntryIdBool:
-        case TypeTableEntryIdMetaType:
-        case TypeTableEntryIdVoid:
-        case TypeTableEntryIdPointer:
-        case TypeTableEntryIdPureError:
-        case TypeTableEntryIdFn:
-        case TypeTableEntryIdTypeDecl:
-        case TypeTableEntryIdNamespace:
-        case TypeTableEntryIdBlock:
-        case TypeTableEntryIdGenericFn:
-            if (!is_equality_cmp) {
-                add_node_error(g, node,
-                    buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
-                return g->builtin_types.entry_invalid;
-            }
-            break;
-
-        case TypeTableEntryIdEnum:
-            if (!is_equality_cmp || resolved_type->data.enumeration.gen_field_count != 0) {
-                add_node_error(g, node,
-                    buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
-                return g->builtin_types.entry_invalid;
-            }
-            break;
-
-        case TypeTableEntryIdUnreachable:
-        case TypeTableEntryIdArray:
-        case TypeTableEntryIdStruct:
-        case TypeTableEntryIdUndefLit:
-        case TypeTableEntryIdNullLit:
-        case TypeTableEntryIdMaybe:
-        case TypeTableEntryIdErrorUnion:
-        case TypeTableEntryIdUnion:
-            add_node_error(g, node,
-                buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
-            return g->builtin_types.entry_invalid;
-
-        case TypeTableEntryIdVar:
-            zig_unreachable();
-    }
-
-    ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
-    ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
-    if (!op1_val->ok || !op2_val->ok) {
-        return g->builtin_types.entry_bool;
-    }
-
-
-    ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
-    eval_const_expr_bin_op(op1_val, op1_type, bin_op_type, op2_val, op2_type, out_val);
-    return g->builtin_types.entry_bool;
-
-}
-
-static TypeTableEntry *analyze_logic_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        AstNode *node)
-{
-    assert(node->type == NodeTypeBinOpExpr);
-    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
-
-    AstNode *op1 = node->data.bin_op_expr.op1;
-    AstNode *op2 = node->data.bin_op_expr.op2;
-    TypeTableEntry *op1_type = analyze_expression(g, import, context, g->builtin_types.entry_bool, op1);
-    TypeTableEntry *op2_type = analyze_expression(g, import, context, g->builtin_types.entry_bool, op2);
-
-    if (op1_type->id == TypeTableEntryIdInvalid ||
-        op2_type->id == TypeTableEntryIdInvalid)
-    {
-        return g->builtin_types.entry_invalid;
-    }
-
-    ConstExprValue *op1_val = &get_resolved_expr(op1)->const_val;
-    ConstExprValue *op2_val = &get_resolved_expr(op2)->const_val;
-    if (!op1_val->ok || !op2_val->ok) {
-        return g->builtin_types.entry_bool;
-    }
-
-    ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
-    eval_const_expr_bin_op(op1_val, op1_type, bin_op_type, op2_val, op2_type, out_val);
-    return g->builtin_types.entry_bool;
-}
-
-static TypeTableEntry *analyze_array_mult(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeBinOpExpr);
-    assert(node->data.bin_op_expr.bin_op == BinOpTypeArrayMult);
-
-    AstNode **op1 = node->data.bin_op_expr.op1->parent_field;
-    AstNode **op2 = node->data.bin_op_expr.op2->parent_field;
-
-    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
-    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
-
-    if (op1_type->id == TypeTableEntryIdInvalid ||
-        op2_type->id == TypeTableEntryIdInvalid)
-    {
-        return g->builtin_types.entry_invalid;
-    }
-
-    ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
-    ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
-
-    AstNode *bad_node;
-    if (!op1_val->ok) {
-        bad_node = *op1;
-    } else if (!op2_val->ok) {
-        bad_node = *op2;
-    } else {
-        bad_node = nullptr;
-    }
-    if (bad_node) {
-        add_node_error(g, bad_node, buf_sprintf("array multiplication requires constant expression"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (op1_type->id != TypeTableEntryIdArray) {
-        add_node_error(g, *op1,
-            buf_sprintf("expected array type, got '%s'", buf_ptr(&op1_type->name)));
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (op2_type->id != TypeTableEntryIdNumLitInt &&
-        op2_type->id != TypeTableEntryIdInt)
-    {
-        add_node_error(g, *op2, buf_sprintf("expected integer type, got '%s'", buf_ptr(&op2_type->name)));
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (op2_val->data.x_bignum.is_negative) {
-        add_node_error(g, *op2, buf_sprintf("expected positive number"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    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;
-
-    TypeTableEntry *child_type = op1_type->data.array.child_type;
-    BigNum old_array_len;
-    bignum_init_unsigned(&old_array_len, op1_type->data.array.len);
-
-    BigNum new_array_len;
-    if (bignum_mul(&new_array_len, &old_array_len, &op2_val->data.x_bignum)) {
-        add_node_error(g, node, buf_sprintf("operation results in overflow"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    uint64_t old_array_len_bare = op1_type->data.array.len;
-    uint64_t operand_amt = op2_val->data.x_bignum.data.x_uint;
-
-    uint64_t new_array_len_bare = new_array_len.data.x_uint;
-    const_val->data.x_array.fields = allocate<ConstExprValue*>(new_array_len_bare);
-
-    uint64_t i = 0;
-    for (uint64_t x = 0; x < operand_amt; x += 1) {
-        for (uint64_t y = 0; y < old_array_len_bare; y += 1) {
-            const_val->data.x_array.fields[i] = op1_val->data.x_array.fields[y];
-            i += 1;
-        }
-    }
-
-    return get_array_type(g, child_type, new_array_len_bare);
-}
-
-static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeBinOpExpr);
-    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
-    switch (bin_op_type) {
-        case BinOpTypeAssign:
-        case BinOpTypeAssignTimes:
-        case BinOpTypeAssignTimesWrap:
-        case BinOpTypeAssignDiv:
-        case BinOpTypeAssignMod:
-        case BinOpTypeAssignPlus:
-        case BinOpTypeAssignPlusWrap:
-        case BinOpTypeAssignMinus:
-        case BinOpTypeAssignMinusWrap:
-        case BinOpTypeAssignBitShiftLeft:
-        case BinOpTypeAssignBitShiftLeftWrap:
-        case BinOpTypeAssignBitShiftRight:
-        case BinOpTypeAssignBitAnd:
-        case BinOpTypeAssignBitXor:
-        case BinOpTypeAssignBitOr:
-        case BinOpTypeAssignBoolAnd:
-        case BinOpTypeAssignBoolOr:
-            {
-                AstNode *lhs_node = node->data.bin_op_expr.op1;
-
-                TypeTableEntry *expected_rhs_type = analyze_lvalue(g, import, context, lhs_node,
-                        LValPurposeAssign, false);
-                if (expected_rhs_type->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_invalid;
-                } else if (!is_op_allowed(expected_rhs_type, node->data.bin_op_expr.bin_op)) {
-                    if (expected_rhs_type->id != TypeTableEntryIdInvalid) {
-                        add_node_error(g, lhs_node,
-                            buf_sprintf("operator not allowed for type '%s'",
-                                buf_ptr(&expected_rhs_type->name)));
-                    }
-                }
-
-                analyze_expression(g, import, context, expected_rhs_type, node->data.bin_op_expr.op2);
-                // not const ok because expression has side effects
-                return g->builtin_types.entry_void;
-            }
-        case BinOpTypeBoolOr:
-        case BinOpTypeBoolAnd:
-            return analyze_logic_bin_op_expr(g, import, context, node);
-        case BinOpTypeCmpEq:
-        case BinOpTypeCmpNotEq:
-        case BinOpTypeCmpLessThan:
-        case BinOpTypeCmpGreaterThan:
-        case BinOpTypeCmpLessOrEq:
-        case BinOpTypeCmpGreaterOrEq:
-            return analyze_bool_bin_op_expr(g, import, context, node);
-        case BinOpTypeBinOr:
-        case BinOpTypeBinXor:
-        case BinOpTypeBinAnd:
-        case BinOpTypeBitShiftLeft:
-        case BinOpTypeBitShiftLeftWrap:
-        case BinOpTypeBitShiftRight:
-        case BinOpTypeAdd:
-        case BinOpTypeAddWrap:
-        case BinOpTypeSub:
-        case BinOpTypeSubWrap:
-        case BinOpTypeMult:
-        case BinOpTypeMultWrap:
-        case BinOpTypeDiv:
-        case BinOpTypeMod:
-            {
-                AstNode **op1 = node->data.bin_op_expr.op1->parent_field;
-                AstNode **op2 = node->data.bin_op_expr.op2->parent_field;
-                TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, *op1);
-                TypeTableEntry *rhs_type = analyze_expression(g, import, context, nullptr, *op2);
-
-                AstNode *op_nodes[] = {*op1, *op2};
-                TypeTableEntry *op_types[] = {lhs_type, rhs_type};
-
-                TypeTableEntry *resolved_type = resolve_peer_type_compatibility(g, import, context, node,
-                        op_nodes, op_types, 2);
-
-                if (resolved_type->id == TypeTableEntryIdInvalid) {
-                    return resolved_type;
-                }
-
-                if (resolved_type->id == TypeTableEntryIdInt ||
-                    resolved_type->id == TypeTableEntryIdNumLitInt)
-                {
-                    // int
-                } else if ((resolved_type->id == TypeTableEntryIdFloat ||
-                           resolved_type->id == TypeTableEntryIdNumLitFloat) &&
-                    (bin_op_type == BinOpTypeAdd ||
-                     bin_op_type == BinOpTypeSub ||
-                     bin_op_type == BinOpTypeMult ||
-                     bin_op_type == BinOpTypeDiv ||
-                     bin_op_type == BinOpTypeMod))
-                {
-                    // float
-                } else {
-                    add_node_error(g, node, buf_sprintf("invalid operands to binary expression: '%s' and '%s'",
-                            buf_ptr(&lhs_type->name), buf_ptr(&rhs_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-
-                ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
-                ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
-                if (!op1_val->ok || !op2_val->ok) {
-                    return resolved_type;
-                }
-
-                ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
-                int err;
-                if ((err = eval_const_expr_bin_op(op1_val, resolved_type, bin_op_type,
-                                op2_val, resolved_type, out_val)))
-                {
-                    if (err == ErrorDivByZero) {
-                        add_node_error(g, node, buf_sprintf("division by zero is undefined"));
-                        return g->builtin_types.entry_invalid;
-                    } else if (err == ErrorOverflow) {
-                        add_node_error(g, node, buf_sprintf("value cannot be represented in any integer type"));
-                        return g->builtin_types.entry_invalid;
-                    }
-                    return g->builtin_types.entry_invalid;
-                }
-
-                num_lit_fits_in_other_type(g, node, resolved_type);
-                return resolved_type;
-            }
-        case BinOpTypeUnwrapMaybe:
-            {
-                AstNode *op1 = node->data.bin_op_expr.op1;
-                AstNode *op2 = node->data.bin_op_expr.op2;
-                TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, op1);
-
-                if (lhs_type->id == TypeTableEntryIdInvalid) {
-                    return lhs_type;
-                } else if (lhs_type->id == TypeTableEntryIdMaybe) {
-                    TypeTableEntry *child_type = lhs_type->data.maybe.child_type;
-                    analyze_expression(g, import, context, child_type, op2);
-                    return child_type;
-                } else {
-                    add_node_error(g, op1,
-                        buf_sprintf("expected maybe type, got '%s'",
-                            buf_ptr(&lhs_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case BinOpTypeArrayCat:
-            {
-                AstNode **op1 = node->data.bin_op_expr.op1->parent_field;
-                AstNode **op2 = node->data.bin_op_expr.op2->parent_field;
-
-                TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
-                TypeTableEntry *child_type;
-                if (op1_type->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_invalid;
-                } else if (op1_type->id == TypeTableEntryIdArray) {
-                    child_type = op1_type->data.array.child_type;
-                } else if (op1_type->id == TypeTableEntryIdPointer &&
-                           op1_type->data.pointer.child_type == g->builtin_types.entry_u8) {
-                    child_type = op1_type->data.pointer.child_type;
-                } else {
-                    add_node_error(g, *op1, buf_sprintf("expected array or C string literal, got '%s'",
-                                buf_ptr(&op1_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-
-                TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
-
-                if (op2_type->id == TypeTableEntryIdInvalid) {
-                    return g->builtin_types.entry_invalid;
-                } else if (op2_type->id == TypeTableEntryIdArray) {
-                    if (op2_type->data.array.child_type != child_type) {
-                        add_node_error(g, *op2, buf_sprintf("expected array of type '%s', got '%s'",
-                                    buf_ptr(&child_type->name),
-                                    buf_ptr(&op2_type->name)));
-                        return g->builtin_types.entry_invalid;
-                    }
-                } else if (op2_type->id == TypeTableEntryIdPointer &&
-                        op2_type->data.pointer.child_type == g->builtin_types.entry_u8) {
-                } else {
-                    add_node_error(g, *op2, buf_sprintf("expected array or C string literal, got '%s'",
-                                buf_ptr(&op2_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-
-                ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
-                ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
-
-                AstNode *bad_node;
-                if (!op1_val->ok) {
-                    bad_node = *op1;
-                } else if (!op2_val->ok) {
-                    bad_node = *op2;
-                } else {
-                    bad_node = nullptr;
-                }
-                if (bad_node) {
-                    add_node_error(g, bad_node, buf_sprintf("array concatenation requires constant expression"));
-                    return g->builtin_types.entry_invalid;
-                }
-
-                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;
-
-                if (op1_type->id == TypeTableEntryIdArray) {
-                    uint64_t new_len = op1_type->data.array.len + op2_type->data.array.len;
-                    const_val->data.x_array.fields = allocate<ConstExprValue*>(new_len);
-                    uint64_t next_index = 0;
-                    for (uint64_t i = 0; i < op1_type->data.array.len; i += 1, next_index += 1) {
-                        const_val->data.x_array.fields[next_index] = op1_val->data.x_array.fields[i];
-                    }
-                    for (uint64_t i = 0; i < op2_type->data.array.len; i += 1, next_index += 1) {
-                        const_val->data.x_array.fields[next_index] = op2_val->data.x_array.fields[i];
-                    }
-                    return get_array_type(g, child_type, new_len);
-                } else if (op1_type->id == TypeTableEntryIdPointer) {
-                    if (!op1_val->data.x_ptr.is_c_str) {
-                        add_node_error(g, *op1,
-                                buf_sprintf("expected array or C string literal, got '%s'",
-                                    buf_ptr(&op1_type->name)));
-                        return g->builtin_types.entry_invalid;
-                    } else if (!op2_val->data.x_ptr.is_c_str) {
-                        add_node_error(g, *op2,
-                                buf_sprintf("expected array or C string literal, got '%s'",
-                                    buf_ptr(&op2_type->name)));
-                        return g->builtin_types.entry_invalid;
-                    }
-                    const_val->data.x_ptr.is_c_str = true;
-                    const_val->data.x_ptr.len = op1_val->data.x_ptr.len + op2_val->data.x_ptr.len - 1;
-                    const_val->data.x_ptr.ptr = allocate<ConstExprValue*>(const_val->data.x_ptr.len);
-                    uint64_t next_index = 0;
-                    for (uint64_t i = 0; i < op1_val->data.x_ptr.len - 1; i += 1, next_index += 1) {
-                        const_val->data.x_ptr.ptr[next_index] = op1_val->data.x_ptr.ptr[i];
-                    }
-                    for (uint64_t i = 0; i < op2_val->data.x_ptr.len; i += 1, next_index += 1) {
-                        const_val->data.x_ptr.ptr[next_index] = op2_val->data.x_ptr.ptr[i];
-                    }
-                    return op1_type;
-                } else {
-                    zig_unreachable();
-                }
-            }
-        case BinOpTypeArrayMult:
-            return analyze_array_mult(g, import, context, expected_type, node);
-        case BinOpTypeInvalid:
-            zig_unreachable();
-    }
-    zig_unreachable();
-}
-
-// Set name to nullptr to make the variable anonymous (not visible to programmer).
-// TODO merge with definition of add_local_var in ir.cpp
-static VariableTableEntry *add_local_var_shadowable(CodeGen *g, AstNode *source_node, ImportTableEntry *import,
-        BlockContext *context, Buf *name, TypeTableEntry *type_entry, bool is_const, AstNode *val_node,
-        bool shadowable)
-{
-    VariableTableEntry *variable_entry = allocate<VariableTableEntry>(1);
-    variable_entry->type = type_entry;
-    variable_entry->block_context = context;
-    variable_entry->import = import;
-    variable_entry->shadowable = shadowable;
-    variable_entry->mem_slot_index = SIZE_MAX;
-
-    if (name) {
-        buf_init_from_buf(&variable_entry->name, name);
-
-        if (type_entry->id != TypeTableEntryIdInvalid) {
-            VariableTableEntry *existing_var = find_variable(g, context, name);
-            if (existing_var && !existing_var->shadowable) {
-                ErrorMsg *msg = add_node_error(g, source_node,
-                        buf_sprintf("redeclaration of variable '%s'", buf_ptr(name)));
-                add_error_note(g, msg, existing_var->decl_node, buf_sprintf("previous declaration is here"));
-                variable_entry->type = g->builtin_types.entry_invalid;
-            } else {
-                auto primitive_table_entry = g->primitive_type_table.maybe_get(name);
-                if (primitive_table_entry) {
-                    TypeTableEntry *type = primitive_table_entry->value;
-                    add_node_error(g, source_node,
-                            buf_sprintf("variable shadows type '%s'", buf_ptr(&type->name)));
-                    variable_entry->type = g->builtin_types.entry_invalid;
-                } else {
-                    AstNode *decl_node = find_decl(context, name);
-                    if (decl_node && decl_node->type != NodeTypeVariableDeclaration) {
-                        ErrorMsg *msg = add_node_error(g, source_node,
-                                buf_sprintf("redefinition of '%s'", buf_ptr(name)));
-                        add_error_note(g, msg, decl_node, buf_sprintf("previous definition is here"));
-                        variable_entry->type = g->builtin_types.entry_invalid;
-                    }
-                }
-            }
-        }
-
-        context->var_table.put(&variable_entry->name, variable_entry);
-    } else {
-        // TODO replace _anon with @anon and make sure all tests still pass
-        buf_init_from_str(&variable_entry->name, "_anon");
-    }
-    if (context->fn_entry) {
-        context->fn_entry->variable_list.append(variable_entry);
-    }
-
-    variable_entry->src_is_const = is_const;
-    variable_entry->gen_is_const = is_const;
-    variable_entry->decl_node = source_node;
-    variable_entry->val_node = val_node;
-
-
-    return variable_entry;
-}
-
-static VariableTableEntry *add_local_var(CodeGen *g, AstNode *source_node, ImportTableEntry *import,
-        BlockContext *context, Buf *name, TypeTableEntry *type_entry, bool is_const, AstNode *val_node)
-{
-    return add_local_var_shadowable(g, source_node, import, context, name, type_entry, is_const, val_node, false);
-}
-
-static TypeTableEntry *analyze_unwrap_error_expr(CodeGen *g, ImportTableEntry *import,
-        BlockContext *parent_context, TypeTableEntry *expected_type, AstNode *node)
-{
-    AstNode *op1 = node->data.unwrap_err_expr.op1;
-    AstNode *op2 = node->data.unwrap_err_expr.op2;
-    AstNode *var_node = node->data.unwrap_err_expr.symbol;
-
-    TypeTableEntry *lhs_type = analyze_expression(g, import, parent_context, nullptr, op1);
-    if (lhs_type->id == TypeTableEntryIdInvalid) {
-        return lhs_type;
-    } else if (lhs_type->id == TypeTableEntryIdErrorUnion) {
-        TypeTableEntry *child_type = lhs_type->data.error.child_type;
-        BlockContext *child_context;
-        if (var_node) {
-            child_context = new_block_context(node, parent_context);
-            var_node->block_context = child_context;
-            Buf *var_name = var_node->data.symbol_expr.symbol;
-            node->data.unwrap_err_expr.var = add_local_var(g, var_node, import, child_context, var_name,
-                    g->builtin_types.entry_pure_error, true, nullptr);
-        } else {
-            child_context = parent_context;
-        }
-
-        analyze_expression(g, import, child_context, child_type, op2);
-        return child_type;
-    } else {
-        add_node_error(g, op1,
-            buf_sprintf("expected error type, got '%s'", buf_ptr(&lhs_type->name)));
-        return g->builtin_types.entry_invalid;
-    }
-}
-
-
-static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, AstNode *source_node,
-        AstNodeVariableDeclaration *variable_declaration,
-        bool expr_is_maybe, AstNode *decl_node, bool var_is_ptr)
-{
-    bool is_const = variable_declaration->is_const;
-    bool is_export = (variable_declaration->top_level_decl.visib_mod == VisibModExport);
-    bool is_extern = variable_declaration->is_extern;
-
-    TypeTableEntry *explicit_type = nullptr;
-    if (variable_declaration->type != nullptr) {
-        explicit_type = analyze_type_expr(g, import, context, variable_declaration->type);
-        if (explicit_type->id == TypeTableEntryIdUnreachable) {
-            add_node_error(g, variable_declaration->type,
-                buf_sprintf("variable of type 'unreachable' not allowed"));
-            explicit_type = g->builtin_types.entry_invalid;
-        }
-    }
-
-    TypeTableEntry *implicit_type = nullptr;
-    if (explicit_type && explicit_type->id == TypeTableEntryIdInvalid) {
-        implicit_type = explicit_type;
-    } else if (variable_declaration->expr) {
-        implicit_type = analyze_expression(g, import, context, explicit_type, variable_declaration->expr);
-        if (implicit_type->id == TypeTableEntryIdInvalid) {
-            // ignore the poison value
-        } else if (expr_is_maybe) {
-            if (implicit_type->id == TypeTableEntryIdMaybe) {
-                if (var_is_ptr) {
-                    // TODO if the expression is constant, can't get pointer to it
-                    implicit_type = get_pointer_to_type(g, implicit_type->data.maybe.child_type, false);
-                } else {
-                    implicit_type = implicit_type->data.maybe.child_type;
-                }
-            } else {
-                add_node_error(g, variable_declaration->expr, buf_sprintf("expected maybe type"));
-                implicit_type = g->builtin_types.entry_invalid;
-            }
-        } else if (implicit_type->id == TypeTableEntryIdUnreachable) {
-            add_node_error(g, source_node,
-                buf_sprintf("variable initialization is unreachable"));
-            implicit_type = g->builtin_types.entry_invalid;
-        } else if ((!is_const || is_export) &&
-                (implicit_type->id == TypeTableEntryIdNumLitFloat ||
-                 implicit_type->id == TypeTableEntryIdNumLitInt))
-        {
-            add_node_error(g, source_node, buf_sprintf("unable to infer variable type"));
-            implicit_type = g->builtin_types.entry_invalid;
-        } else if (implicit_type->id == TypeTableEntryIdMetaType && !is_const) {
-            add_node_error(g, source_node, buf_sprintf("variable of type 'type' must be constant"));
-            implicit_type = g->builtin_types.entry_invalid;
-        }
-        if (implicit_type->id != TypeTableEntryIdInvalid && !context->fn_entry) {
-            ConstExprValue *const_val = &get_resolved_expr(variable_declaration->expr)->const_val;
-            if (!const_val->ok) {
-                add_node_error(g, first_executing_node(variable_declaration->expr),
-                        buf_sprintf("global variable initializer requires constant expression"));
-            }
-        }
-    } else if (!is_extern) {
-        add_node_error(g, source_node, buf_sprintf("variables must be initialized"));
-        implicit_type = g->builtin_types.entry_invalid;
-    }
-
-    TypeTableEntry *type = explicit_type != nullptr ? explicit_type : implicit_type;
-    assert(type != nullptr); // should have been caught by the parser
-
-    VariableTableEntry *var = add_local_var(g, source_node, import, context,
-            variable_declaration->symbol, type, is_const,
-            expr_is_maybe ? nullptr : variable_declaration->expr);
-
-    variable_declaration->variable = var;
-
-    return var;
-}
-
-static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, TypeTableEntry *expected_type, AstNode *node)
-{
-    AstNodeVariableDeclaration *variable_declaration = &node->data.variable_declaration;
-    return analyze_variable_declaration_raw(g, import, context, node, variable_declaration,
-            false, nullptr, false);
-}
-
-static TypeTableEntry *analyze_null_literal_expr(CodeGen *g, ImportTableEntry *import,
-        BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeNullLiteral);
-
-    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-    const_val->ok = true;
-
-    return g->builtin_types.entry_null;
-}
-
-static TypeTableEntry *analyze_undefined_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeUndefinedLiteral);
-
-    Expr *expr = get_resolved_expr(node);
-    ConstExprValue *const_val = &expr->const_val;
-
-    const_val->ok = true;
-    const_val->special = ConstValSpecialUndef;
-
-    return expected_type ? expected_type : g->builtin_types.entry_undef;
-}
-
-static TypeTableEntry *analyze_zeroes_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    Expr *expr = get_resolved_expr(node);
-    ConstExprValue *const_val = &expr->const_val;
-
-    const_val->ok = true;
-    const_val->special = ConstValSpecialZeroes;
-
-    return expected_type ? expected_type : g->builtin_types.entry_undef;
-}
-
-static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry *import,
-        BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
-{
-    return resolve_expr_const_val_as_bignum(g, node, expected_type, node->data.number_literal.bignum, false);
-}
-
-static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    AstNode *size_node = node->data.array_type.size;
-
-    TypeTableEntry *child_type = analyze_type_expr_pointer_only(g, import, context,
-            node->data.array_type.child_type, true);
-
-    if (child_type->id == TypeTableEntryIdUnreachable) {
-        add_node_error(g, node, buf_create_from_str("array of unreachable not allowed"));
-        return g->builtin_types.entry_invalid;
-    } else if (child_type->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (size_node) {
-        child_type = analyze_type_expr(g, import, context, node->data.array_type.child_type);
-        TypeTableEntry *size_type = analyze_expression(g, import, context,
-                g->builtin_types.entry_usize, size_node);
-        if (size_type->id == TypeTableEntryIdInvalid) {
-            return g->builtin_types.entry_invalid;
-        }
-
-        ConstExprValue *const_val = &get_resolved_expr(size_node)->const_val;
-        if (const_val->ok) {
-            if (const_val->data.x_bignum.is_negative) {
-                add_node_error(g, size_node,
-                    buf_sprintf("array size %s is negative",
-                        buf_ptr(bignum_to_buf(&const_val->data.x_bignum))));
-                return g->builtin_types.entry_invalid;
-            } else {
-                return resolve_expr_const_val_as_type(g, node,
-                        get_array_type(g, child_type, const_val->data.x_bignum.data.x_uint), false);
-            }
-        } else if (context->fn_entry) {
-            return resolve_expr_const_val_as_type(g, node,
-                    get_slice_type(g, child_type, node->data.array_type.is_const), false);
-        } else {
-            add_node_error(g, first_executing_node(size_node),
-                    buf_sprintf("unable to evaluate constant expression"));
-            return g->builtin_types.entry_invalid;
-        }
-    } else {
-        TypeTableEntry *slice_type = get_slice_type(g, child_type, node->data.array_type.is_const);
-        return resolve_expr_const_val_as_type(g, node, slice_type, false);
-    }
-}
-
-static TypeTableEntry *analyze_fn_proto_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    TypeTableEntry *type_entry = analyze_fn_proto_type(g, import, context, expected_type, node,
-            false, false, nullptr);
-
-    if (type_entry->id == TypeTableEntryIdInvalid) {
-        return type_entry;
-    }
-
-    return resolve_expr_const_val_as_type(g, node, type_entry, false);
-}
-
-static TypeTableEntry *analyze_while_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeWhileExpr);
-
-    AstNode **condition_node = &node->data.while_expr.condition;
-    AstNode *while_body_node = node->data.while_expr.body;
-    AstNode **continue_expr_node = &node->data.while_expr.continue_expr;
-
-    TypeTableEntry *condition_type = analyze_expression(g, import, context,
-            g->builtin_types.entry_bool, *condition_node);
-
-    if (*continue_expr_node) {
-        analyze_expression(g, import, context, g->builtin_types.entry_void, *continue_expr_node);
-    }
-
-    BlockContext *child_context = new_block_context(node, context);
-    child_context->parent_loop_node = node;
-
-    analyze_expression(g, import, child_context, g->builtin_types.entry_void, while_body_node);
-
-
-    TypeTableEntry *expr_return_type = g->builtin_types.entry_void;
-
-    if (condition_type->id == TypeTableEntryIdInvalid) {
-        expr_return_type = g->builtin_types.entry_invalid;
-    } else {
-        // if the condition is a simple constant expression and there are no break statements
-        // then the return type is unreachable
-        ConstExprValue *const_val = &get_resolved_expr(*condition_node)->const_val;
-        if (const_val->ok) {
-            if (const_val->data.x_bool) {
-                node->data.while_expr.condition_always_true = true;
-                if (!node->data.while_expr.contains_break) {
-                    expr_return_type = g->builtin_types.entry_unreachable;
-                }
-            }
-        }
-    }
-
-    return expr_return_type;
-}
-
-static TypeTableEntry *analyze_break_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeBreak);
-
-    AstNode *loop_node = context->parent_loop_node;
-    if (loop_node) {
-        if (loop_node->type == NodeTypeWhileExpr) {
-            loop_node->data.while_expr.contains_break = true;
-        } else if (loop_node->type == NodeTypeForExpr) {
-            loop_node->data.for_expr.contains_break = true;
-        } else {
-            zig_unreachable();
-        }
-    } else {
-        add_node_error(g, node, buf_sprintf("'break' expression outside loop"));
-    }
-    return g->builtin_types.entry_unreachable;
-}
-
-static TypeTableEntry *analyze_continue_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    AstNode *loop_node = context->parent_loop_node;
-    if (loop_node) {
-        if (loop_node->type == NodeTypeWhileExpr) {
-            loop_node->data.while_expr.contains_continue = true;
-        } else if (loop_node->type == NodeTypeForExpr) {
-            loop_node->data.for_expr.contains_continue = true;
-        } else {
-            zig_unreachable();
-        }
-    } else {
-        add_node_error(g, node, buf_sprintf("'continue' expression outside loop"));
-    }
-    return g->builtin_types.entry_unreachable;
-}
-
-static TypeTableEntry *add_error_if_type_is_num_lit(CodeGen *g, TypeTableEntry *type_entry, AstNode *source_node) {
-    if (type_entry->id == TypeTableEntryIdNumLitInt ||
-        type_entry->id == TypeTableEntryIdNumLitFloat)
-    {
-        add_node_error(g, source_node, buf_sprintf("unable to infer expression type"));
-        return g->builtin_types.entry_invalid;
-    } else {
-        return type_entry;
-    }
-}
-
-static TypeTableEntry *analyze_if(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
-        TypeTableEntry *expected_type, AstNode *node,
-        AstNode **then_node, AstNode **else_node, bool cond_is_const, bool cond_bool_val)
-{
-    if (!*else_node) {
-        *else_node = create_ast_void_node(g, import, node);
-        normalize_parent_ptrs(node);
-    }
-
-    BlockContext *then_context;
-    BlockContext *else_context;
-    if (cond_is_const) {
-        if (cond_bool_val) {
-            then_context = parent_context;
-            else_context = new_block_context(node, parent_context);
-
-            else_context->codegen_excluded = true;
-        } else {
-            then_context = new_block_context(node, parent_context);
-            else_context = parent_context;
-
-            then_context->codegen_excluded = true;
-        }
-    } else {
-        then_context = parent_context;
-        else_context = parent_context;
-    }
-
-    TypeTableEntry *then_type = nullptr;
-    TypeTableEntry *else_type = nullptr;
-
-    if (!then_context->codegen_excluded) {
-        then_type = analyze_expression(g, import, then_context, expected_type, *then_node);
-        if (then_type->id == TypeTableEntryIdInvalid) {
-            return g->builtin_types.entry_invalid;
-        }
-    }
-    if (!else_context->codegen_excluded) {
-        else_type = analyze_expression(g, import, else_context, expected_type, *else_node);
-        if (else_type->id == TypeTableEntryIdInvalid) {
-            return g->builtin_types.entry_invalid;
-        }
-    }
-
-    TypeTableEntry *result_type;
-    if (then_context->codegen_excluded) {
-        result_type = else_type;
-    } else if (else_context->codegen_excluded) {
-        result_type = then_type;
-    } else if (expected_type) {
-        result_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
-    } else {
-        AstNode *op_nodes[] = {*then_node, *else_node};
-        TypeTableEntry *op_types[] = {then_type, else_type};
-        result_type = resolve_peer_type_compatibility(g, import, parent_context, node, op_nodes, op_types, 2);
-    }
-
-    if (!cond_is_const) {
-        return add_error_if_type_is_num_lit(g, result_type, node);
-    }
-
-    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 add_error_if_type_is_num_lit(g, result_type, node);
-    }
-
-    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-    *const_val = *other_const_val;
-    // the condition depends on a compile var, so the entire if statement does too
-    const_val->depends_on_compile_var = true;
-    return result_type;
-}
-
-static TypeTableEntry *analyze_if_bool_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    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;
-    }
-
-    ConstExprValue *cond_val = &get_resolved_expr(*cond)->const_val;
-    if (cond_val->special == ConstValSpecialUndef) {
-        add_node_error(g, first_executing_node(*cond), buf_sprintf("branch on undefined value"));
-        return cond_type;
-    }
-    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 *parent_context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeIfVarExpr);
-
-    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,
-        nullptr, node->data.if_var_expr.var_is_ptr);
-    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(g, import, child_context, expected_type,
-            node, then_node, else_node, cond_is_const, cond_bool_val);
-}
-
-bool type_is_codegen_pointer(TypeTableEntry *type) {
-    if (type->id == TypeTableEntryIdPointer) return true;
-    if (type->id == TypeTableEntryIdFn) return true;
-    if (type->id == TypeTableEntryIdMaybe) {
-        if (type->data.maybe.child_type->id == TypeTableEntryIdPointer) return true;
-        if (type->data.maybe.child_type->id == TypeTableEntryIdFn) return true;
-    }
-    return false;
-}
-
-static TypeTableEntry *bad_method_call(CodeGen *g, AstNode *node, TypeTableEntry *container_type,
-        TypeTableEntry *expected_param_type, FnTableEntry *fn_table_entry)
-{
-    ErrorMsg *msg = add_node_error(g, node,
-        buf_sprintf("function called as method of '%s', but first parameter is of type '%s'",
-            buf_ptr(&container_type->name),
-            buf_ptr(&expected_param_type->name)));
-    if (fn_table_entry) {
-        add_error_note(g, msg, fn_table_entry->proto_node, buf_sprintf("function declared here"));
-    }
-    return g->builtin_types.entry_invalid;
-}
-
-// Before calling this function, set node->data.fn_call_expr.fn_table_entry if the function is known
-// at compile time. Otherwise this is a function pointer call.
-static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *fn_type,
-        AstNode *struct_node)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-
-    if (fn_type->id == TypeTableEntryIdInvalid) {
-        return fn_type;
-    }
-
-    // The function call might include inline parameters which we need to ignore according to the
-    // fn_type.
-    FnTableEntry *fn_table_entry = node->data.fn_call_expr.fn_entry;
-    AstNode *generic_proto_node = fn_table_entry ?
-        fn_table_entry->proto_node->data.fn_proto.generic_proto_node : nullptr;
-
-    // count parameters
-    size_t struct_node_1_or_0 = struct_node ? 1 : 0;
-    size_t src_param_count = fn_type->data.fn.fn_type_id.param_count +
-        (generic_proto_node ? generic_proto_node->data.fn_proto.inline_arg_count : 0);
-    size_t call_param_count = node->data.fn_call_expr.params.length;
-    size_t expect_arg_count = src_param_count - struct_node_1_or_0;
-
-    bool ok_invocation = true;
-
-    if (fn_type->data.fn.fn_type_id.is_var_args) {
-        if (call_param_count < expect_arg_count) {
-            ok_invocation = false;
-            add_node_error(g, node,
-                buf_sprintf("expected at least %zu arguments, got %zu", src_param_count, call_param_count));
-        }
-    } else if (expect_arg_count != call_param_count) {
-        ok_invocation = false;
-        add_node_error(g, node,
-                buf_sprintf("expected %zu arguments, got %zu", expect_arg_count, call_param_count));
-    }
-
-    bool all_args_const_expr = true;
-
-    if (struct_node) {
-        Expr *struct_expr = get_resolved_expr(struct_node);
-        ConstExprValue *struct_const_val = &struct_expr->const_val;
-        if (!struct_const_val->ok) {
-            all_args_const_expr = false;
-        }
-
-        FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[0];
-        TypeTableEntry *expected_param_type = param_info->type;
-        TypeTableEntry *container_bare_type = container_ref_type(struct_expr->type_entry);
-        if (is_container_ref(expected_param_type)) {
-            TypeTableEntry *param_bare_type = container_ref_type(expected_param_type);
-            if (param_bare_type != container_bare_type) {
-                return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
-            }
-        } else {
-            return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
-        }
-    }
-
-    // analyze each parameter. in the case of a method, we already analyzed the
-    // first parameter in order to figure out which struct we were calling a method on.
-    size_t next_type_i = struct_node_1_or_0;
-    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
-        size_t proto_i = call_i + struct_node_1_or_0;
-        AstNode **param_node = &node->data.fn_call_expr.params.at(call_i);
-        // determine the expected type for each parameter
-        TypeTableEntry *expected_param_type = nullptr;
-        if (proto_i < src_param_count) {
-            if (generic_proto_node &&
-                generic_proto_node->data.fn_proto.params.at(proto_i)->data.param_decl.is_inline)
-            {
-                continue;
-            }
-
-            FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[next_type_i];
-            next_type_i += 1;
-
-            expected_param_type = param_info->type;
-        }
-        TypeTableEntry *param_type = analyze_expression(g, import, context, expected_param_type, *param_node);
-        if (param_type->id == TypeTableEntryIdInvalid) {
-            return param_type;
-        }
-
-        ConstExprValue *const_arg_val = &get_resolved_expr(*param_node)->const_val;
-        if (!const_arg_val->ok) {
-            all_args_const_expr = false;
-        }
-    }
-
-    TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type;
-
-    if (return_type->id == TypeTableEntryIdInvalid) {
-        return return_type;
-    }
-
-    ConstExprValue *result_val = &get_resolved_expr(node)->const_val;
-    if (ok_invocation && fn_table_entry && fn_table_entry->is_pure && fn_table_entry->want_pure != WantPureFalse) {
-        if (fn_table_entry->anal_state == FnAnalStateReady) {
-            analyze_fn_body(g, fn_table_entry);
-            if (fn_table_entry->proto_node->data.fn_proto.skip) {
-                return g->builtin_types.entry_invalid;
-            }
-        }
-        if (all_args_const_expr) {
-            if (fn_table_entry->is_pure && fn_table_entry->anal_state == FnAnalStateComplete) {
-                if (eval_fn(g, node, fn_table_entry, result_val, 1000, struct_node)) {
-                    // function evaluation generated an error
-                    return g->builtin_types.entry_invalid;
-                }
-                return return_type;
-            }
-        }
-    }
-    if (!ok_invocation || !fn_table_entry || !fn_table_entry->is_pure || fn_table_entry->want_pure == WantPureFalse) {
-        // calling an impure fn is impure
-        mark_impure_fn(g, context, node);
-        if (fn_table_entry && fn_table_entry->want_pure == WantPureTrue) {
-            return g->builtin_types.entry_invalid;
-        }
-    }
-
-    // TODO
-    //if (handle_is_ptr(return_type)) {
-    //    if (context->fn_entry) {
-    //        context->fn_entry->cast_alloca_list.append(node);
-    //    } else if (!result_val->ok) {
-    //        add_node_error(g, node, buf_sprintf("unable to evaluate constant expression"));
-    //    }
-    //}
-
-    return return_type;
-}
-
-static TypeTableEntry *analyze_fn_call_with_inline_args(CodeGen *g, ImportTableEntry *import,
-        BlockContext *parent_context, TypeTableEntry *expected_type, AstNode *call_node,
-        FnTableEntry *fn_table_entry, AstNode *struct_node)
-{
-    assert(call_node->type == NodeTypeFnCallExpr);
-    assert(fn_table_entry);
-
-    AstNode *decl_node = fn_table_entry->proto_node;
-
-    // count parameters
-    size_t struct_node_1_or_0 = (struct_node ? 1 : 0);
-    size_t src_param_count = decl_node->data.fn_proto.params.length;
-    size_t call_param_count = call_node->data.fn_call_expr.params.length;
-
-    if (src_param_count != call_param_count + struct_node_1_or_0) {
-        add_node_error(g, call_node,
-            buf_sprintf("expected %zu arguments, got %zu", src_param_count - struct_node_1_or_0, call_param_count));
-        return g->builtin_types.entry_invalid;
-    }
-
-    size_t inline_or_var_type_arg_count = decl_node->data.fn_proto.inline_or_var_type_arg_count;
-    assert(inline_or_var_type_arg_count > 0);
-
-    BlockContext *child_context = decl_node->owner->block_context;
-    size_t next_generic_param_index = 0;
-
-    GenericFnTypeId *generic_fn_type_id = allocate<GenericFnTypeId>(1);
-    generic_fn_type_id->decl_node = decl_node;
-    generic_fn_type_id->generic_param_count = inline_or_var_type_arg_count;
-    generic_fn_type_id->generic_params = allocate<GenericParamValue>(inline_or_var_type_arg_count);
-
-    size_t next_impl_i = 0;
-    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
-        size_t proto_i = call_i + struct_node_1_or_0;
-        AstNode *generic_param_decl_node = decl_node->data.fn_proto.params.at(proto_i);
-        assert(generic_param_decl_node->type == NodeTypeParamDecl);
-
-        AstNode **generic_param_type_node = &generic_param_decl_node->data.param_decl.type;
-        TypeTableEntry *expected_param_type = analyze_type_expr(g, decl_node->owner, child_context,
-                *generic_param_type_node);
-        if (expected_param_type->id == TypeTableEntryIdInvalid) {
-            return expected_param_type;
-        }
-
-        bool is_var_type = (expected_param_type->id == TypeTableEntryIdVar);
-        bool is_inline = generic_param_decl_node->data.param_decl.is_inline;
-        if (!is_inline && !is_var_type) {
-            next_impl_i += 1;
-            continue;
-        }
-
-
-        AstNode **param_node = &call_node->data.fn_call_expr.params.at(call_i);
-        TypeTableEntry *param_type = analyze_expression(g, import, parent_context,
-                is_var_type ? nullptr : expected_param_type, *param_node);
-        if (param_type->id == TypeTableEntryIdInvalid) {
-            return param_type;
-        }
-
-        // set child_context so that the previous param is in scope
-        child_context = new_block_context(generic_param_decl_node, child_context);
-
-        ConstExprValue *const_val = &get_resolved_expr(*param_node)->const_val;
-        if (is_inline && !const_val->ok) {
-            add_node_error(g, *param_node,
-                    buf_sprintf("unable to evaluate constant expression for inline parameter"));
-
-            return g->builtin_types.entry_invalid;
-        }
-
-        VariableTableEntry *var = add_local_var_shadowable(g, generic_param_decl_node, decl_node->owner, child_context,
-                generic_param_decl_node->data.param_decl.name, param_type, true, *param_node, true);
-        // This generic function instance could be called with anything, so when this variable is read it
-        // needs to know that it depends on compile time variable data.
-        var->force_depends_on_compile_var = true;
-
-        GenericParamValue *generic_param_value =
-            &generic_fn_type_id->generic_params[next_generic_param_index];
-        generic_param_value->type = param_type;
-        generic_param_value->node = is_inline ? *param_node : nullptr;
-        generic_param_value->impl_index = next_impl_i;
-        next_generic_param_index += 1;
-
-        if (!is_inline) {
-            next_impl_i += 1;
-        }
-    }
-
-    assert(next_generic_param_index == inline_or_var_type_arg_count);
-
-    auto entry = g->generic_table.maybe_get(generic_fn_type_id);
-    FnTableEntry *impl_fn;
-    if (entry) {
-        AstNode *impl_decl_node = entry->value;
-        assert(impl_decl_node->type == NodeTypeFnProto);
-        impl_fn = impl_decl_node->data.fn_proto.fn_table_entry;
-    } else {
-        AstNode *decl_node = generic_fn_type_id->decl_node;
-        AstNode *impl_fn_def_node = ast_clone_subtree_special(decl_node->data.fn_proto.fn_def_node,
-                &g->next_node_index, AstCloneSpecialOmitInlineParams);
-        AstNode *impl_decl_node = impl_fn_def_node->data.fn_def.fn_proto;
-        impl_decl_node->data.fn_proto.inline_arg_count = 0;
-        impl_decl_node->data.fn_proto.inline_or_var_type_arg_count = 0;
-        impl_decl_node->data.fn_proto.generic_proto_node = decl_node;
-
-        // replace var arg types with actual types
-        for (size_t generic_arg_i = 0; generic_arg_i < inline_or_var_type_arg_count; generic_arg_i += 1) {
-            GenericParamValue *generic_param_value = &generic_fn_type_id->generic_params[generic_arg_i];
-            if (!generic_param_value->node) {
-                size_t impl_i = generic_param_value->impl_index;
-                AstNode *impl_param_decl_node = impl_decl_node->data.fn_proto.params.at(impl_i);
-                assert(impl_param_decl_node->type == NodeTypeParamDecl);
-
-                impl_param_decl_node->data.param_decl.type = create_ast_type_node(g, import,
-                        generic_param_value->type, impl_param_decl_node);
-                normalize_parent_ptrs(impl_param_decl_node);
-            }
-        }
-
-        preview_fn_proto_instance(g, import, impl_decl_node, child_context);
-        g->generic_table.put(generic_fn_type_id, impl_decl_node);
-        impl_fn = impl_decl_node->data.fn_proto.fn_table_entry;
-    }
-
-    call_node->data.fn_call_expr.fn_entry = impl_fn;
-    return analyze_fn_call_ptr(g, import, parent_context, expected_type, call_node,
-            impl_fn->type_entry, struct_node);
-}
-
-static TypeTableEntry *analyze_generic_fn_call(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
-        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *generic_fn_type)
-{
-    assert(node->type == NodeTypeFnCallExpr);
-    assert(generic_fn_type->id == TypeTableEntryIdGenericFn);
-
-    AstNode *decl_node = generic_fn_type->data.generic_fn.decl_node;
-    assert(decl_node->type == NodeTypeContainerDecl);
-    ZigList<AstNode *> *generic_params = &decl_node->data.struct_decl.generic_params;
-
-    size_t expected_param_count = generic_params->length;
-    size_t actual_param_count = node->data.fn_call_expr.params.length;
-
-    if (actual_param_count != expected_param_count) {
-        add_node_error(g, first_executing_node(node),
-                buf_sprintf("expected %zu arguments, got %zu", expected_param_count, actual_param_count));
-        return g->builtin_types.entry_invalid;
-    }
-
-    GenericFnTypeId *generic_fn_type_id = allocate<GenericFnTypeId>(1);
-    generic_fn_type_id->decl_node = decl_node;
-    generic_fn_type_id->generic_param_count = actual_param_count;
-    generic_fn_type_id->generic_params = allocate<GenericParamValue>(actual_param_count);
-
-    BlockContext *child_context = decl_node->owner->block_context;
-    for (size_t i = 0; i < actual_param_count; i += 1) {
-        AstNode *generic_param_decl_node = generic_params->at(i);
-        assert(generic_param_decl_node->type == NodeTypeParamDecl);
-
-        AstNode **generic_param_type_node = &generic_param_decl_node->data.param_decl.type;
-
-        TypeTableEntry *expected_param_type = analyze_type_expr(g, decl_node->owner,
-                child_context, *generic_param_type_node);
-        if (expected_param_type->id == TypeTableEntryIdInvalid) {
-            return expected_param_type;
-        }
-
-
-
-        AstNode **param_node = &node->data.fn_call_expr.params.at(i);
-
-        TypeTableEntry *param_type = analyze_expression(g, import, parent_context, expected_param_type,
-                *param_node);
-        if (param_type->id == TypeTableEntryIdInvalid) {
-            return param_type;
-        }
-
-        // set child_context so that the previous param is in scope
-        child_context = new_block_context(generic_param_decl_node, child_context);
-
-        ConstExprValue *const_val = &get_resolved_expr(*param_node)->const_val;
-        if (const_val->ok) {
-            VariableTableEntry *var = add_local_var(g, generic_param_decl_node, decl_node->owner, child_context,
-                    generic_param_decl_node->data.param_decl.name, param_type, true, *param_node);
-            var->force_depends_on_compile_var = true;
-        } else {
-            add_node_error(g, *param_node, buf_sprintf("unable to evaluate constant expression"));
-
-            return g->builtin_types.entry_invalid;
-        }
-
-        GenericParamValue *generic_param_value = &generic_fn_type_id->generic_params[i];
-        generic_param_value->type = param_type;
-        generic_param_value->node = *param_node;
-    }
-
-    auto entry = g->generic_table.maybe_get(generic_fn_type_id);
-    if (entry) {
-        AstNode *impl_decl_node = entry->value;
-        assert(impl_decl_node->type == NodeTypeContainerDecl);
-        TypeTableEntry *type_entry = impl_decl_node->data.struct_decl.type_entry;
-        return resolve_expr_const_val_as_type(g, node, type_entry, false);
-    }
-
-    // make a type from the generic parameters supplied
-    assert(decl_node->type == NodeTypeContainerDecl);
-    AstNode *impl_decl_node = ast_clone_subtree(decl_node, &g->next_node_index);
-    g->generic_table.put(generic_fn_type_id, impl_decl_node);
-    scan_struct_decl(g, import, child_context, impl_decl_node);
-    TypeTableEntry *type_entry = impl_decl_node->data.struct_decl.type_entry;
-    resolve_struct_type(g, import, type_entry);
-    return resolve_expr_const_val_as_type(g, node, type_entry, false);
-}
-
-static TypeTableEntry *analyze_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
-
-    if (node->data.fn_call_expr.is_builtin) {
-        zig_panic("moved builtin fn call code to ir.cpp");
-    }
-
-    TypeTableEntry *invoke_type_entry = analyze_expression(g, import, context, nullptr, fn_ref_expr);
-    if (invoke_type_entry->id == TypeTableEntryIdInvalid) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    // use constant expression evaluator to figure out the function at compile time.
-    // otherwise we treat this as a function pointer.
-    ConstExprValue *const_val = &get_resolved_expr(fn_ref_expr)->const_val;
-
-    if (const_val->ok) {
-        if (invoke_type_entry->id == TypeTableEntryIdMetaType) {
-            zig_unreachable();
-        } else if (invoke_type_entry->id == TypeTableEntryIdFn) {
-            AstNode *struct_node;
-            if (fn_ref_expr->type == NodeTypeFieldAccessExpr &&
-                fn_ref_expr->data.field_access_expr.is_member_fn)
-            {
-                struct_node = fn_ref_expr->data.field_access_expr.struct_expr;
-            } else {
-                struct_node = nullptr;
-            }
-
-            FnTableEntry *fn_table_entry = const_val->data.x_fn;
-            node->data.fn_call_expr.fn_entry = fn_table_entry;
-            return analyze_fn_call_ptr(g, import, context, expected_type, node,
-                    fn_table_entry->type_entry, struct_node);
-        } else if (invoke_type_entry->id == TypeTableEntryIdGenericFn) {
-            TypeTableEntry *generic_fn_type = const_val->data.x_type;
-            AstNode *decl_node = generic_fn_type->data.generic_fn.decl_node;
-            if (decl_node->type == NodeTypeFnProto) {
-                AstNode *struct_node;
-                if (fn_ref_expr->type == NodeTypeFieldAccessExpr &&
-                    fn_ref_expr->data.field_access_expr.is_member_fn)
-                {
-                    struct_node = fn_ref_expr->data.field_access_expr.struct_expr;
-                } else {
-                    struct_node = nullptr;
-                }
-
-                FnTableEntry *fn_table_entry = decl_node->data.fn_proto.fn_table_entry;
-                if (fn_table_entry->proto_node->data.fn_proto.skip) {
-                    return g->builtin_types.entry_invalid;
-                }
-                return analyze_fn_call_with_inline_args(g, import, context, expected_type, node,
-                        fn_table_entry, struct_node);
-            } else {
-                return analyze_generic_fn_call(g, import, context, expected_type, node, const_val->data.x_type);
-            }
-        } else {
-            add_node_error(g, fn_ref_expr,
-                buf_sprintf("type '%s' not a function", buf_ptr(&invoke_type_entry->name)));
-            return g->builtin_types.entry_invalid;
-        }
-    }
-
-    // function pointer
-    if (invoke_type_entry->id == TypeTableEntryIdFn) {
-        return analyze_fn_call_ptr(g, import, context, expected_type, node, invoke_type_entry, nullptr);
-    } else {
-        add_node_error(g, fn_ref_expr,
-            buf_sprintf("type '%s' not a function", buf_ptr(&invoke_type_entry->name)));
-        return g->builtin_types.entry_invalid;
-    }
-}
-
-static TypeTableEntry *analyze_switch_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    AstNode **expr_node = &node->data.switch_expr.expr;
-    TypeTableEntry *expr_type = analyze_expression(g, import, context, nullptr, *expr_node);
-    ConstExprValue *expr_val = &get_resolved_expr(*expr_node)->const_val;
-    if (expr_val->ok && !expr_val->depends_on_compile_var) {
-        add_node_error(g, first_executing_node(*expr_node),
-                buf_sprintf("value is constant; unnecessary switch statement"));
-    }
-    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-
-
-    size_t prong_count = node->data.switch_expr.prongs.length;
-    AstNode **peer_nodes = allocate<AstNode*>(prong_count);
-    TypeTableEntry **peer_types = allocate<TypeTableEntry*>(prong_count);
-
-    bool any_errors = false;
-    if (expr_type->id == TypeTableEntryIdInvalid) {
-        return expr_type;
-    } else if (expr_type->id == TypeTableEntryIdUnreachable) {
-        add_node_error(g, first_executing_node(*expr_node),
-                buf_sprintf("switch on unreachable expression not allowed"));
-        return g->builtin_types.entry_invalid;
-    }
-
-
-    size_t *field_use_counts = nullptr;
-    HashMap<int, AstNode *, int_hash, int_eq> err_use_nodes = {};
-    if (expr_type->id == TypeTableEntryIdEnum) {
-        field_use_counts = allocate<size_t>(expr_type->data.enumeration.src_field_count);
-    } else if (expr_type->id == TypeTableEntryIdErrorUnion) {
-        err_use_nodes.init(10);
-    }
-
-    size_t *const_chosen_prong_index = &node->data.switch_expr.const_chosen_prong_index;
-    *const_chosen_prong_index = SIZE_MAX;
-    AstNode *else_prong = nullptr;
-    for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
-        AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i);
-
-        TypeTableEntry *var_type;
-        bool var_is_target_expr;
-        if (prong_node->data.switch_prong.items.length == 0) {
-            if (else_prong) {
-                add_node_error(g, prong_node, buf_sprintf("multiple else prongs in switch expression"));
-                any_errors = true;
-            } else {
-                else_prong = prong_node;
-            }
-            var_type = expr_type;
-            var_is_target_expr = true;
-            if (*const_chosen_prong_index == SIZE_MAX && expr_val->ok) {
-                *const_chosen_prong_index = prong_i;
-            }
-        } else {
-            bool all_agree_on_var_type = true;
-            var_type = nullptr;
-
-            for (size_t item_i = 0; item_i < prong_node->data.switch_prong.items.length; item_i += 1) {
-                AstNode *item_node = prong_node->data.switch_prong.items.at(item_i);
-                if (item_node->type == NodeTypeSwitchRange) {
-                    zig_panic("TODO range in switch statement");
-                }
-
-                if (expr_type->id == TypeTableEntryIdEnum) {
-                    if (item_node->type == NodeTypeSymbol) {
-                        Buf *field_name = item_node->data.symbol_expr.symbol;
-                        TypeEnumField *type_enum_field = find_enum_type_field(expr_type, field_name);
-                        if (type_enum_field) {
-                            item_node->data.symbol_expr.enum_field = type_enum_field;
-                            if (!var_type) {
-                                var_type = type_enum_field->type_entry;
-                            }
-                            if (type_enum_field->type_entry != var_type) {
-                                all_agree_on_var_type = false;
-                            }
-                            uint32_t field_index = type_enum_field->value;
-                            assert(field_use_counts);
-                            field_use_counts[field_index] += 1;
-                            if (field_use_counts[field_index] > 1) {
-                                add_node_error(g, item_node,
-                                    buf_sprintf("duplicate switch value: '%s'",
-                                        buf_ptr(type_enum_field->name)));
-                                any_errors = true;
-                            }
-                            if (!any_errors && expr_val->ok) {
-                                if (expr_val->data.x_enum.tag == type_enum_field->value) {
-                                    *const_chosen_prong_index = prong_i;
-                                }
-                            }
-                        } else {
-                            add_node_error(g, item_node,
-                                    buf_sprintf("enum '%s' has no field '%s'",
-                                        buf_ptr(&expr_type->name), buf_ptr(field_name)));
-                            any_errors = true;
-                        }
-                    } else {
-                        add_node_error(g, item_node, buf_sprintf("expected enum tag name"));
-                        any_errors = true;
-                    }
-                } else if (expr_type->id == TypeTableEntryIdErrorUnion) {
-                    if (item_node->type == NodeTypeSymbol) {
-                        Buf *err_name = item_node->data.symbol_expr.symbol;
-                        bool is_ok_case = buf_eql_str(err_name, "Ok");
-                        auto err_table_entry = is_ok_case ? nullptr: g->error_table.maybe_get(err_name);
-                        if (is_ok_case || err_table_entry) {
-                            uint32_t err_value = is_ok_case ? 0 : err_table_entry->value->value;
-                            item_node->data.symbol_expr.err_value = err_value;
-                            TypeTableEntry *this_var_type;
-                            if (is_ok_case) {
-                                this_var_type = expr_type->data.error.child_type;
-                            } else {
-                                this_var_type = g->builtin_types.entry_pure_error;
-                            }
-                            if (!var_type) {
-                                var_type = this_var_type;
-                            }
-                            if (this_var_type != var_type) {
-                                all_agree_on_var_type = false;
-                            }
-
-                            // detect duplicate switch values
-                            auto existing_entry = err_use_nodes.maybe_get(err_value);
-                            if (existing_entry) {
-                                add_node_error(g, existing_entry->value,
-                                        buf_sprintf("duplicate switch value: '%s'", buf_ptr(err_name)));
-                                any_errors = true;
-                            } else {
-                                err_use_nodes.put(err_value, item_node);
-                            }
-
-                            if (!any_errors && expr_val->ok) {
-                                if (expr_val->data.x_err.err->value == err_value) {
-                                    *const_chosen_prong_index = prong_i;
-                                }
-                            }
-                        } else {
-                            add_node_error(g, item_node,
-                                    buf_sprintf("use of undeclared error value '%s'", buf_ptr(err_name)));
-                            any_errors = true;
-                        }
-                    } else {
-                        add_node_error(g, item_node, buf_sprintf("expected error value name"));
-                        any_errors = true;
-                    }
-                } else {
-                    if (!any_errors && expr_val->ok) {
-                        // note: there is now a function in eval.cpp for doing const expr comparison
-                        zig_panic("TODO determine if const exprs are equal");
-                    }
-                    TypeTableEntry *item_type = analyze_expression(g, import, context, expr_type, item_node);
-                    if (item_type->id != TypeTableEntryIdInvalid) {
-                        ConstExprValue *const_val = &get_resolved_expr(item_node)->const_val;
-                        if (!const_val->ok) {
-                            add_node_error(g, item_node,
-                                buf_sprintf("unable to evaluate constant expression"));
-                            any_errors = true;
-                        }
-                    }
-                }
-            }
-            if (!var_type || !all_agree_on_var_type) {
-                var_type = expr_type;
-                var_is_target_expr = true;
-            } else {
-                var_is_target_expr = false;
-            }
-        }
-
-        BlockContext *child_context = new_block_context(node, context);
-        prong_node->data.switch_prong.block_context = child_context;
-        AstNode *var_node = prong_node->data.switch_prong.var_symbol;
-        if (var_node) {
-            assert(var_node->type == NodeTypeSymbol);
-            Buf *var_name = var_node->data.symbol_expr.symbol;
-            var_node->block_context = child_context;
-            prong_node->data.switch_prong.var = add_local_var(g, var_node, import,
-                    child_context, var_name, var_type, true, nullptr);
-            prong_node->data.switch_prong.var_is_target_expr = var_is_target_expr;
-        }
-    }
-
-    for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
-        AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i);
-        BlockContext *child_context = prong_node->data.switch_prong.block_context;
-        child_context->codegen_excluded = expr_val->ok && (*const_chosen_prong_index != prong_i);
-
-        if (child_context->codegen_excluded) {
-            peer_types[prong_i] = g->builtin_types.entry_unreachable;
-        } else {
-            peer_types[prong_i] = analyze_expression(g, import, child_context, expected_type,
-                    prong_node->data.switch_prong.expr);
-        }
-        // This must go after the analyze_expression for
-        // prong_node->data.switch_prong.expr because of AST rewriting.
-        peer_nodes[prong_i] = prong_node->data.switch_prong.expr;
-    }
-
-    if (expr_type->id == TypeTableEntryIdEnum && !else_prong) {
-        for (uint32_t i = 0; i < expr_type->data.enumeration.src_field_count; i += 1) {
-            if (field_use_counts[i] == 0) {
-                add_node_error(g, node,
-                    buf_sprintf("enumeration value '%s' not handled in switch",
-                        buf_ptr(expr_type->data.enumeration.fields[i].name)));
-                any_errors = true;
-            }
-        }
-    }
-
-    if (any_errors) {
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (prong_count == 0) {
-        add_node_error(g, node, buf_sprintf("switch statement has no prongs"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    TypeTableEntry *result_type = resolve_peer_type_compatibility(g, import, context, node,
-            peer_nodes, peer_types, prong_count);
-
-    if (expr_val->ok) {
-        assert(*const_chosen_prong_index != SIZE_MAX);
-
-        *const_val = get_resolved_expr(peer_nodes[*const_chosen_prong_index])->const_val;
-        // the target expr depends on a compile var because we have an error on unnecessary
-        // switch statement, so the entire switch statement does too
-        const_val->depends_on_compile_var = true;
-
-        if (!const_val->ok) {
-            return add_error_if_type_is_num_lit(g, result_type, node);
-        }
-    } else {
-        return add_error_if_type_is_num_lit(g, result_type, node);
-    }
-
-    return result_type;
-}
-
-static TypeTableEntry *analyze_return_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    if (!node->data.return_expr.expr) {
-        node->data.return_expr.expr = create_ast_void_node(g, import, node);
-        normalize_parent_ptrs(node);
-    }
-
-    TypeTableEntry *expected_return_type = get_return_type(context);
-
-    switch (node->data.return_expr.kind) {
-        case ReturnKindUnconditional:
-            zig_panic("TODO moved to ir.cpp");
-        case ReturnKindError:
-            {
-                TypeTableEntry *expected_err_type;
-                if (expected_type) {
-                    expected_err_type = get_error_type(g, expected_type);
-                } else {
-                    expected_err_type = nullptr;
-                }
-                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_err_type,
-                        node->data.return_expr.expr);
-                if (resolved_type->id == TypeTableEntryIdInvalid) {
-                    return resolved_type;
-                } else if (resolved_type->id == TypeTableEntryIdErrorUnion) {
-                    if (expected_return_type->id != TypeTableEntryIdErrorUnion &&
-                        expected_return_type->id != TypeTableEntryIdPureError)
-                    {
-                        ErrorMsg *msg = add_node_error(g, node,
-                            buf_sprintf("%%return statement in function with return type '%s'",
-                                buf_ptr(&expected_return_type->name)));
-                        AstNode *return_type_node = context->fn_entry->fn_def_node->data.fn_def.fn_proto->data.fn_proto.return_type;
-                        add_error_note(g, msg, return_type_node, buf_sprintf("function return type here"));
-                    }
-
-                    return resolved_type->data.error.child_type;
-                } else {
-                    add_node_error(g, node->data.return_expr.expr,
-                        buf_sprintf("expected error type, got '%s'", buf_ptr(&resolved_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-        case ReturnKindMaybe:
-            {
-                TypeTableEntry *expected_maybe_type;
-                if (expected_type) {
-                    expected_maybe_type = get_maybe_type(g, expected_type);
-                } else {
-                    expected_maybe_type = nullptr;
-                }
-                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_maybe_type,
-                        node->data.return_expr.expr);
-                if (resolved_type->id == TypeTableEntryIdInvalid) {
-                    return resolved_type;
-                } else if (resolved_type->id == TypeTableEntryIdMaybe) {
-                    if (expected_return_type->id != TypeTableEntryIdMaybe) {
-                        ErrorMsg *msg = add_node_error(g, node,
-                            buf_sprintf("?return statement in function with return type '%s'",
-                                buf_ptr(&expected_return_type->name)));
-                        AstNode *return_type_node = context->fn_entry->fn_def_node->data.fn_def.fn_proto->data.fn_proto.return_type;
-                        add_error_note(g, msg, return_type_node, buf_sprintf("function return type here"));
-                    }
-
-                    return resolved_type->data.maybe.child_type;
-                } else {
-                    add_node_error(g, node->data.return_expr.expr,
-                        buf_sprintf("expected maybe type, got '%s'", buf_ptr(&resolved_type->name)));
-                    return g->builtin_types.entry_invalid;
-                }
-            }
-    }
-    zig_unreachable();
-}
-
-static void validate_voided_expr(CodeGen *g, AstNode *source_node, TypeTableEntry *type_entry) {
-    if (type_entry->id == TypeTableEntryIdMetaType) {
-        add_node_error(g, first_executing_node(source_node), buf_sprintf("expected expression, found type"));
-    } else if (type_entry->id == TypeTableEntryIdErrorUnion) {
-        add_node_error(g, first_executing_node(source_node), buf_sprintf("statement ignores error value"));
-    }
-}
-
-static TypeTableEntry *analyze_defer(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    if (!parent_context->fn_entry) {
-        add_node_error(g, node, buf_sprintf("defer expression outside function definition"));
-        return g->builtin_types.entry_invalid;
-    }
-
-    if (!node->data.defer.expr) {
-        add_node_error(g, node, buf_sprintf("defer expects an expression"));
-        return g->builtin_types.entry_void;
-    }
-
-    node->data.defer.child_block = new_block_context(node, parent_context);
-
-    TypeTableEntry *resolved_type = analyze_expression(g, import, parent_context, nullptr,
-            node->data.defer.expr);
-    validate_voided_expr(g, node->data.defer.expr, resolved_type);
-
-    return g->builtin_types.entry_void;
-}
-
-static TypeTableEntry *analyze_string_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    if (node->data.string_literal.c) {
-        return resolve_expr_const_val_as_c_string_lit(g, node, node->data.string_literal.buf);
-    } else {
-        return resolve_expr_const_val_as_string_lit(g, node, node->data.string_literal.buf);
-    }
-}
-
-static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    BlockContext *child_context = new_block_context(node, parent_context);
-    node->data.block.child_block = child_context;
-    TypeTableEntry *return_type = g->builtin_types.entry_void;
-
-    for (size_t i = 0; i < node->data.block.statements.length; i += 1) {
-        AstNode *child = node->data.block.statements.at(i);
-        if (child->type == NodeTypeLabel) {
-            FnTableEntry *fn_table_entry = child_context->fn_entry;
-            assert(fn_table_entry);
-
-            LabelTableEntry *label = allocate<LabelTableEntry>(1);
-            label->decl_node = child;
-            label->entered_from_fallthrough = (return_type->id != TypeTableEntryIdUnreachable);
-
-            child->block_context = child_context;
-            child->data.label.label_entry = label;
-            fn_table_entry->all_labels.append(label);
-
-            child_context->label_table.put(child->data.label.name, label);
-
-            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, child_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 (child->type == NodeTypeDefer && return_type->id != TypeTableEntryIdInvalid) {
-            // defer starts a new block context
-            child_context = child->data.defer.child_block;
-            assert(child_context);
-        }
-        if (!is_last) {
-            validate_voided_expr(g, child, return_type);
-        }
-    }
-    node->data.block.nested_block = child_context;
-
-    ConstExprValue *const_val = &node->data.block.resolved_expr.const_val;
-    if (node->data.block.statements.length == 0) {
-        const_val->ok = true;
-    } else if (node->data.block.statements.length == 1) {
-        AstNode *only_node = node->data.block.statements.at(0);
-        ConstExprValue *other_const_val = &get_resolved_expr(only_node)->const_val;
-        if (other_const_val->ok) {
-            *const_val = *other_const_val;
-        }
-    }
-
-    return return_type;
-}
-
-static TypeTableEntry *analyze_asm_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    mark_impure_fn(g, context, node);
-
-    node->data.asm_expr.return_count = 0;
-    TypeTableEntry *return_type = g->builtin_types.entry_void;
-    for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1) {
-        AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
-        if (asm_output->return_type) {
-            node->data.asm_expr.return_count += 1;
-            return_type = analyze_type_expr(g, import, context, asm_output->return_type);
-            if (node->data.asm_expr.return_count > 1) {
-                add_node_error(g, node,
-                    buf_sprintf("inline assembly allows up to one output value"));
-                break;
-            }
-        } else {
-            Buf *variable_name = asm_output->variable_name;
-            VariableTableEntry *var = find_variable(g, context, variable_name);
-            if (var) {
-                asm_output->variable = var;
-                return var->type;
-            } else {
-                add_node_error(g, node,
-                        buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
-                return g->builtin_types.entry_invalid;
-            }
-        }
-    }
-    for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) {
-        AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
-        analyze_expression(g, import, context, nullptr, asm_input->expr);
-    }
-
-    return return_type;
-}
-
-static TypeTableEntry *analyze_goto_pass1(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    assert(node->type == NodeTypeGoto);
-
-    FnTableEntry *fn_table_entry = context->fn_entry;
-    assert(fn_table_entry);
-
-    fn_table_entry->goto_list.append(node);
-
-    return g->builtin_types.entry_unreachable;
-}
-
-static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEntry *import,
-        BlockContext *context, TypeTableEntry *expected_type, AstNode *node, bool pointer_only)
-{
-    assert(!expected_type || expected_type->id != TypeTableEntryIdInvalid);
-    TypeTableEntry *return_type = nullptr;
-    node->block_context = context;
-    switch (node->type) {
-        case NodeTypeBlock:
-            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);
-            break;
-        case NodeTypeDefer:
-            return_type = analyze_defer(g, import, context, expected_type, node);
-            break;
-        case NodeTypeVariableDeclaration:
-            analyze_variable_declaration(g, import, context, expected_type, node);
-            return_type = g->builtin_types.entry_void;
-            break;
-        case NodeTypeGoto:
-            return_type = analyze_goto_pass1(g, import, context, expected_type, node);
-            break;
-        case NodeTypeBreak:
-            return_type = analyze_break_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeContinue:
-            return_type = analyze_continue_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeAsmExpr:
-            return_type = analyze_asm_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeBinOpExpr:
-            return_type = analyze_bin_op_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeUnwrapErrorExpr:
-            return_type = analyze_unwrap_error_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeFnCallExpr:
-            return_type = analyze_fn_call_expr(g, import, context, expected_type, node);
-            break;
-
-        case NodeTypeArrayAccessExpr:
-            // for reading array access; assignment handled elsewhere
-            return_type = analyze_array_access_expr(g, import, context, node, LValPurposeAddressOf);
-            break;
-        case NodeTypeSliceExpr:
-            return_type = analyze_slice_expr(g, import, context, node);
-            break;
-        case NodeTypeFieldAccessExpr:
-            return_type = analyze_field_access_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeContainerInitExpr:
-            zig_panic("analyze container init moved to ir.cpp");
-            break;
-        case NodeTypeNumberLiteral:
-            return_type = analyze_number_literal_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeStringLiteral:
-            return_type = analyze_string_literal_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeCharLiteral:
-            return_type = resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-                    node->data.char_literal.value, false);
-            break;
-        case NodeTypeBoolLiteral:
-            zig_panic("moved to ir.cpp");
-            break;
-        case NodeTypeNullLiteral:
-            return_type = analyze_null_literal_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeUndefinedLiteral:
-            return_type = analyze_undefined_literal_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeZeroesLiteral:
-            return_type = analyze_zeroes_literal_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeThisLiteral:
-            zig_panic("moved to ir.cpp");
-            break;
-        case NodeTypeSymbol:
-            return_type = analyze_symbol_expr(g, import, context, expected_type, node, pointer_only);
-            break;
-        case NodeTypePrefixOpExpr:
-            zig_panic("moved to ir.cpp");
-            break;
-        case NodeTypeIfBoolExpr:
-            return_type = analyze_if_bool_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeIfVarExpr:
-            return_type = analyze_if_var_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeWhileExpr:
-            return_type = analyze_while_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeForExpr:
-            zig_panic("moved to ir.cpp");
-            break;
-        case NodeTypeArrayType:
-            return_type = analyze_array_type(g, import, context, expected_type, node);
-            break;
-        case NodeTypeFnProto:
-            return_type = analyze_fn_proto_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeErrorType:
-            return_type = resolve_expr_const_val_as_type(g, node, g->builtin_types.entry_pure_error, false);
-            break;
-        case NodeTypeTypeLiteral:
-            return_type = resolve_expr_const_val_as_type(g, node, g->builtin_types.entry_type, false);
-            break;
-        case NodeTypeSwitchExpr:
-            return_type = analyze_switch_expr(g, import, context, expected_type, node);
-            break;
-        case NodeTypeVarLiteral:
-            return_type = resolve_expr_const_val_as_type(g, node, g->builtin_types.entry_var, false);
-            break;
-        case NodeTypeSwitchProng:
-        case NodeTypeSwitchRange:
-        case NodeTypeFnDecl:
-        case NodeTypeParamDecl:
-        case NodeTypeRoot:
-        case NodeTypeFnDef:
-        case NodeTypeUse:
-        case NodeTypeLabel:
-        case NodeTypeContainerDecl:
-        case NodeTypeStructField:
-        case NodeTypeStructValueField:
-        case NodeTypeErrorValueDecl:
-        case NodeTypeTypeDecl:
-            zig_unreachable();
-    }
-    assert(return_type);
-    // resolve_type_compatibility might do implicit cast which means node is now a child
-    // of the actual node that we want to return the type of.
-    //AstNode **field = node->parent_field;
-    TypeTableEntry *resolved_type = resolve_type_compatibility(g, import, context, node,
-            expected_type, return_type);
-
-    Expr *expr = get_resolved_expr(node);
-    expr->type_entry = return_type;
-
-    add_global_const_expr(g, node);
-
-    return resolved_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,
-        TypeTableEntry *expected_type, AstNode *node)
-{
-    return analyze_expression_pointer_only(g, import, context, expected_type, node, false);
-}
-
-static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
-    ImportTableEntry *import = fn_table_entry->import_entry;
-    AstNode *node = fn_table_entry->fn_def_node;
-    assert(node->type == NodeTypeFnDef);
-
-    AstNode *fn_proto_node = node->data.fn_def.fn_proto;
-    assert(fn_proto_node->type == NodeTypeFnProto);
-
-    if (fn_proto_node->data.fn_proto.skip) {
-        // we detected an error with this function definition which prevents us
-        // from further analyzing it.
-        fn_table_entry->anal_state = FnAnalStateSkipped;
-        return;
-    }
-    fn_table_entry->anal_state = FnAnalStateProbing;
-
-    BlockContext *context = node->data.fn_def.block_context;
-
-    TypeTableEntry *fn_type = fn_table_entry->type_entry;
-    AstNodeFnProto *fn_proto = &fn_proto_node->data.fn_proto;
-    for (size_t i = 0; i < fn_proto->params.length; i += 1) {
-        AstNode *param_decl_node = fn_proto->params.at(i);
-        assert(param_decl_node->type == NodeTypeParamDecl);
-
-        // define local variables for parameters
-        AstNodeParamDecl *param_decl = &param_decl_node->data.param_decl;
-        TypeTableEntry *type = unwrapped_node_type(param_decl->type);
-
-        if (param_decl->is_noalias && !type_is_codegen_pointer(type)) {
-            add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
-        }
+        if (param_decl->is_noalias && !type_is_codegen_pointer(type)) {
+            add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
+        }
 
         if (fn_type->data.fn.fn_type_id.is_extern && handle_is_ptr(type)) {
             add_node_error(g, param_decl_node,
@@ -5340,162 +2591,6 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
     fn_table_entry->anal_state = FnAnalStateComplete;
 }
 
-static void add_top_level_decl(CodeGen *g, ImportTableEntry *import, BlockContext *block_context,
-        AstNode *node, Buf *name)
-{
-    assert(import);
-
-    TopLevelDecl *tld = get_as_top_level_decl(node);
-    tld->import = import;
-    tld->name = name;
-
-    bool want_to_resolve = (g->check_unused || g->is_test_build || tld->visib_mod == VisibModExport);
-    bool is_generic_container = (node->type == NodeTypeContainerDecl &&
-            node->data.struct_decl.generic_params.length > 0);
-    if (want_to_resolve && !is_generic_container) {
-        g->resolve_queue.append(node);
-    }
-
-    node->block_context = block_context;
-
-    auto entry = block_context->decl_table.maybe_get(name);
-    if (entry) {
-        AstNode *other_decl_node = entry->value;
-        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("redefinition of '%s'", buf_ptr(name)));
-        add_error_note(g, msg, other_decl_node, buf_sprintf("previous definition is here"));
-    } else {
-        block_context->decl_table.put(name, node);
-    }
-}
-
-static void count_inline_and_var_args(AstNode *proto_node) {
-    assert(proto_node->type == NodeTypeFnProto);
-
-    size_t *inline_arg_count = &proto_node->data.fn_proto.inline_arg_count;
-    size_t *inline_or_var_type_arg_count = &proto_node->data.fn_proto.inline_or_var_type_arg_count;
-
-    *inline_arg_count = 0;
-    *inline_or_var_type_arg_count = 0;
-
-    // TODO run these nodes through the type analysis system rather than looking for
-    // specialized ast nodes. this would get fooled by `{var}` instead of `var` which
-    // is supposed to be equivalent
-    for (size_t i = 0; i < proto_node->data.fn_proto.params.length; i += 1) {
-        AstNode *param_node = proto_node->data.fn_proto.params.at(i);
-        assert(param_node->type == NodeTypeParamDecl);
-        if (param_node->data.param_decl.is_inline) {
-            *inline_arg_count += 1;
-            *inline_or_var_type_arg_count += 1;
-        } else if (param_node->data.param_decl.type->type == NodeTypeVarLiteral) {
-            *inline_or_var_type_arg_count += 1;
-        }
-    }
-}
-
-static void scan_decls(CodeGen *g, ImportTableEntry *import, BlockContext *context, AstNode *node) {
-    switch (node->type) {
-        case NodeTypeRoot:
-            for (size_t i = 0; i < import->root->data.root.top_level_decls.length; i += 1) {
-                AstNode *child = import->root->data.root.top_level_decls.at(i);
-                scan_decls(g, import, context, child);
-            }
-            break;
-        case NodeTypeContainerDecl:
-            {
-                Buf *name = node->data.struct_decl.name;
-                add_top_level_decl(g, import, context, node, name);
-                if (node->data.struct_decl.generic_params.length == 0) {
-                    scan_struct_decl(g, import, context, node);
-                }
-            }
-            break;
-        case NodeTypeFnDef:
-            node->data.fn_def.fn_proto->data.fn_proto.fn_def_node = node;
-            scan_decls(g, import, context, node->data.fn_def.fn_proto);
-            break;
-        case NodeTypeVariableDeclaration:
-            {
-                Buf *name = node->data.variable_declaration.symbol;
-                add_top_level_decl(g, import, context, node, name);
-                break;
-            }
-        case NodeTypeTypeDecl:
-            {
-                Buf *name = node->data.type_decl.symbol;
-                add_top_level_decl(g, import, context, node, name);
-                break;
-            }
-        case NodeTypeFnProto:
-            {
-                // if the name is missing, we immediately announce an error
-                Buf *fn_name = node->data.fn_proto.name;
-                if (buf_len(fn_name) == 0) {
-                    node->data.fn_proto.skip = true;
-                    add_node_error(g, node, buf_sprintf("missing function name"));
-                    break;
-                }
-                count_inline_and_var_args(node);
-
-                add_top_level_decl(g, import, context, node, fn_name);
-                break;
-            }
-        case NodeTypeUse:
-            {
-                TopLevelDecl *tld = get_as_top_level_decl(node);
-                tld->import = import;
-                node->block_context = context;
-                g->use_queue.append(node);
-                tld->import->use_decls.append(node);
-                break;
-            }
-        case NodeTypeErrorValueDecl:
-            // error value declarations do not depend on other top level decls
-            preview_error_value_decl(g, node);
-            break;
-        case NodeTypeParamDecl:
-        case NodeTypeFnDecl:
-        case NodeTypeReturnExpr:
-        case NodeTypeDefer:
-        case NodeTypeBlock:
-        case NodeTypeBinOpExpr:
-        case NodeTypeUnwrapErrorExpr:
-        case NodeTypeFnCallExpr:
-        case NodeTypeArrayAccessExpr:
-        case NodeTypeSliceExpr:
-        case NodeTypeNumberLiteral:
-        case NodeTypeStringLiteral:
-        case NodeTypeCharLiteral:
-        case NodeTypeBoolLiteral:
-        case NodeTypeNullLiteral:
-        case NodeTypeUndefinedLiteral:
-        case NodeTypeZeroesLiteral:
-        case NodeTypeThisLiteral:
-        case NodeTypeSymbol:
-        case NodeTypePrefixOpExpr:
-        case NodeTypeIfBoolExpr:
-        case NodeTypeIfVarExpr:
-        case NodeTypeWhileExpr:
-        case NodeTypeForExpr:
-        case NodeTypeSwitchExpr:
-        case NodeTypeSwitchProng:
-        case NodeTypeSwitchRange:
-        case NodeTypeLabel:
-        case NodeTypeGoto:
-        case NodeTypeBreak:
-        case NodeTypeContinue:
-        case NodeTypeAsmExpr:
-        case NodeTypeFieldAccessExpr:
-        case NodeTypeStructField:
-        case NodeTypeContainerInitExpr:
-        case NodeTypeStructValueField:
-        case NodeTypeArrayType:
-        case NodeTypeErrorType:
-        case NodeTypeTypeLiteral:
-        case NodeTypeVarLiteral:
-            zig_unreachable();
-    }
-}
-
 static void add_symbols_from_import(CodeGen *g, AstNode *src_use_node, AstNode *dst_use_node) {
     TopLevelDecl *tld = get_as_top_level_decl(dst_use_node);
     AstNode *use_target_node = src_use_node->data.use.expr;
@@ -5565,11 +2660,11 @@ static void resolve_use_decl(CodeGen *g, AstNode *node) {
 static void preview_use_decl(CodeGen *g, AstNode *node) {
     assert(node->type == NodeTypeUse);
     TopLevelDecl *tld = get_as_top_level_decl(node);
-    TypeTableEntry *use_expr_type = analyze_expression(g, tld->import, tld->import->block_context,
-            g->builtin_types.entry_namespace, node->data.use.expr);
-    if (use_expr_type->id == TypeTableEntryIdInvalid) {
+
+    IrInstruction *result = analyze_const_value(g, tld->import->block_context, node->data.use.expr,
+            g->builtin_types.entry_namespace);
+    if (result->type_entry->id == TypeTableEntryIdInvalid)
         tld->import->any_imports_failed = true;
-    }
 }
 
 ImportTableEntry *add_source_file(CodeGen *g, PackageTableEntry *package,
src/analyze.hpp
@@ -45,10 +45,6 @@ ImportTableEntry *add_source_file(CodeGen *g, PackageTableEntry *package,
 
 AstNode *first_executing_node(AstNode *node);
 
-TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, ImportTableEntry *import,
-        BlockContext *block_context, AstNode *parent_source_node,
-        AstNode **child_nodes, TypeTableEntry **child_types, size_t child_count);
-
 
 // TODO move these over, these used to be static
 bool types_match_const_cast_only(TypeTableEntry *expected_type, TypeTableEntry *actual_type);
src/ast_render.cpp
@@ -261,17 +261,6 @@ static void print_indent(AstRender *ar) {
     }
 }
 
-static bool is_node_void(AstNode *node) {
-    if (node->type == NodeTypeSymbol) {
-        if (node->data.symbol_expr.override_type_entry) {
-            return node->data.symbol_expr.override_type_entry->id == TypeTableEntryIdVoid;
-        } else if (buf_eql_str(node->data.symbol_expr.symbol, "void")) {
-            return true;
-        }
-    }
-    return false;
-}
-
 static bool is_alpha_under(uint8_t c) {
     return (c >= 'a' && c <= 'z') ||
         (c >= 'A' && c <= 'Z') || c == '_';
@@ -406,10 +395,8 @@ static void render_node(AstRender *ar, AstNode *node) {
                 fprintf(ar->f, ")");
 
                 AstNode *return_type_node = node->data.fn_proto.return_type;
-                if (!is_node_void(return_type_node)) {
-                    fprintf(ar->f, " -> ");
-                    render_node(ar, return_type_node);
-                }
+                fprintf(ar->f, " -> ");
+                render_node(ar, return_type_node);
                 break;
             }
         case NodeTypeFnDef:
@@ -521,14 +508,7 @@ static void render_node(AstRender *ar, AstNode *node) {
                 break;
             }
         case NodeTypeSymbol:
-            {
-                TypeTableEntry *override_type = node->data.symbol_expr.override_type_entry;
-                if (override_type) {
-                    fprintf(ar->f, "%s", buf_ptr(&override_type->name));
-                } else {
-                    print_symbol(ar, node->data.symbol_expr.symbol);
-                }
-            }
+            print_symbol(ar, node->data.symbol_expr.symbol);
             break;
         case NodeTypePrefixOpExpr:
             {
@@ -623,10 +603,8 @@ static void render_node(AstRender *ar, AstNode *node) {
                     assert(field_node->type == NodeTypeStructField);
                     print_indent(ar);
                     print_symbol(ar, field_node->data.struct_field.name);
-                    if (!is_node_void(field_node->data.struct_field.type)) {
-                        fprintf(ar->f, ": ");
-                        render_node(ar, field_node->data.struct_field.type);
-                    }
+                    fprintf(ar->f, ": ");
+                    render_node(ar, field_node->data.struct_field.type);
                     fprintf(ar->f, ",\n");
                 }
 
src/codegen.cpp
@@ -2965,6 +2965,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdToPtrType:
         case IrInstructionIdPtrTypeChild:
         case IrInstructionIdFieldPtr:
+        case IrInstructionIdSetFnTest:
             zig_unreachable();
         case IrInstructionIdReturn:
             return ir_render_return(g, executable, (IrInstructionReturn *)instruction);
@@ -2996,7 +2997,6 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_struct_field_ptr(g, executable, (IrInstructionStructFieldPtr *)instruction);
         case IrInstructionIdSwitchBr:
         case IrInstructionIdPhi:
-        case IrInstructionIdBuiltinCall:
         case IrInstructionIdContainerInitList:
         case IrInstructionIdContainerInitFields:
         case IrInstructionIdReadField:
@@ -5303,8 +5303,6 @@ static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) {
 }
 
 static void get_c_type_node(CodeGen *g, AstNode *type_node, Buf *out_buf) {
-    assert(type_node->type != NodeTypeSymbol || !type_node->data.symbol_expr.override_type_entry);
-
     Expr *expr = get_resolved_expr(type_node);
     assert(expr->type_entry);
     assert(expr->type_entry->id == TypeTableEntryIdMetaType);
src/ir.cpp
@@ -149,10 +149,6 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionCall *) {
     return IrInstructionIdCall;
 }
 
-static constexpr IrInstructionId ir_instruction_id(IrInstructionBuiltinCall *) {
-    return IrInstructionIdBuiltinCall;
-}
-
 static constexpr IrInstructionId ir_instruction_id(IrInstructionConst *) {
     return IrInstructionIdConst;
 }
@@ -189,6 +185,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionPtrTypeChild *)
     return IrInstructionIdPtrTypeChild;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionSetFnTest *) {
+    return IrInstructionIdSetFnTest;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrExecutable *exec, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -353,7 +353,7 @@ static IrInstruction *ir_build_const_scope(IrBuilder *irb, AstNode *source_node,
 
 static IrInstruction *ir_build_const_bool(IrBuilder *irb, AstNode *source_node, bool value) {
     IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, source_node);
-    const_instruction->base.type_entry = irb->codegen->builtin_types.entry_block;
+    const_instruction->base.type_entry = irb->codegen->builtin_types.entry_bool;
     const_instruction->base.static_value.ok = true;
     const_instruction->base.static_value.data.x_bool = value;
     return &const_instruction->base;
@@ -505,20 +505,6 @@ static IrInstruction *ir_build_call_from(IrBuilder *irb, IrInstruction *old_inst
     return new_instruction;
 }
 
-static IrInstruction *ir_build_builtin_call(IrBuilder *irb, AstNode *source_node,
-        BuiltinFnEntry *fn, IrInstruction **args)
-{
-    IrInstructionBuiltinCall *call_instruction = ir_build_instruction<IrInstructionBuiltinCall>(irb, source_node);
-    call_instruction->fn = fn;
-    call_instruction->args = args;
-
-    for (size_t i = 0; i < fn->param_count; i += 1) {
-        ir_ref_instruction(args[i]);
-    }
-
-    return &call_instruction->base;
-}
-
 static IrInstruction *ir_build_phi(IrBuilder *irb, AstNode *source_node,
         size_t incoming_count, IrBasicBlock **incoming_blocks, IrInstruction **incoming_values)
 {
@@ -717,6 +703,19 @@ static IrInstruction *ir_build_ptr_type_child(IrBuilder *irb, AstNode *source_no
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_set_fn_test(IrBuilder *irb, AstNode *source_node, IrInstruction *fn_value,
+        IrInstruction *is_test)
+{
+    IrInstructionSetFnTest *instruction = ir_build_instruction<IrInstructionSetFnTest>(irb, source_node);
+    instruction->fn_value = fn_value;
+    instruction->is_test = is_test;
+
+    ir_ref_instruction(fn_value);
+    ir_ref_instruction(is_test);
+
+    return &instruction->base;
+}
+
 //static size_t get_conditional_defer_count(BlockContext *inner_block, BlockContext *outer_block) {
 //    size_t result = 0;
 //    while (inner_block != outer_block) {
@@ -1066,10 +1065,6 @@ static IrInstruction *ir_gen_decl_ref(IrBuilder *irb, AstNode *source_node, AstN
 static IrInstruction *ir_gen_symbol(IrBuilder *irb, AstNode *node, LValPurpose lval) {
     assert(node->type == NodeTypeSymbol);
 
-    if (node->data.symbol_expr.override_type_entry) {
-        zig_panic("TODO have parseh directly generate IR");
-    }
-
     Buf *variable_name = node->data.symbol_expr.symbol;
 
     auto primitive_table_entry = irb->codegen->primitive_type_table.maybe_get(variable_name);
@@ -1163,26 +1158,71 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, AstNode *node) {
 
     builtin_fn->ref_count += 1;
 
-    if (builtin_fn->id == BuiltinFnIdUnreachable) {
-        return ir_build_unreachable(irb, node);
-    } else if (builtin_fn->id == BuiltinFnIdTypeof) {
-        AstNode *arg_node = node->data.fn_call_expr.params.at(0);
-        IrInstruction *arg = ir_gen_node(irb, arg_node, node->block_context);
-        if (arg == irb->codegen->invalid_instruction)
-            return arg;
-        return ir_build_typeof(irb, node, arg);
-    }
+    switch (builtin_fn->id) {
+        case BuiltinFnIdInvalid:
+            zig_unreachable();
+        case BuiltinFnIdUnreachable:
+            return ir_build_unreachable(irb, node);
+        case BuiltinFnIdTypeof:
+            {
+                AstNode *arg_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg = ir_gen_node(irb, arg_node, node->block_context);
+                if (arg == irb->codegen->invalid_instruction)
+                    return arg;
+                return ir_build_typeof(irb, node, arg);
+            }
+        case BuiltinFnIdSetFnTest:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, node->block_context);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
 
-    IrInstruction **args = allocate<IrInstruction *>(actual_param_count);
-    for (size_t i = 0; i < actual_param_count; i += 1) {
-        AstNode *arg_node = node->data.fn_call_expr.params.at(i);
-        IrInstruction *arg = ir_gen_node(irb, arg_node, node->block_context);
-        if (arg == irb->codegen->invalid_instruction)
-            return arg;
-        args[i] = arg;
-    }
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, node->block_context);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
 
-    return ir_build_builtin_call(irb, node, builtin_fn, args);
+                return ir_build_set_fn_test(irb, node, arg0_value, arg1_value);
+            }
+        case BuiltinFnIdMemcpy:
+        case BuiltinFnIdMemset:
+        case BuiltinFnIdSizeof:
+        case BuiltinFnIdAlignof:
+        case BuiltinFnIdMaxValue:
+        case BuiltinFnIdMinValue:
+        case BuiltinFnIdMemberCount:
+        case BuiltinFnIdAddWithOverflow:
+        case BuiltinFnIdSubWithOverflow:
+        case BuiltinFnIdMulWithOverflow:
+        case BuiltinFnIdShlWithOverflow:
+        case BuiltinFnIdCInclude:
+        case BuiltinFnIdCDefine:
+        case BuiltinFnIdCUndef:
+        case BuiltinFnIdCompileVar:
+        case BuiltinFnIdCompileErr:
+        case BuiltinFnIdConstEval:
+        case BuiltinFnIdCtz:
+        case BuiltinFnIdClz:
+        case BuiltinFnIdImport:
+        case BuiltinFnIdCImport:
+        case BuiltinFnIdErrName:
+        case BuiltinFnIdBreakpoint:
+        case BuiltinFnIdReturnAddress:
+        case BuiltinFnIdFrameAddress:
+        case BuiltinFnIdEmbedFile:
+        case BuiltinFnIdCmpExchange:
+        case BuiltinFnIdFence:
+        case BuiltinFnIdDivExact:
+        case BuiltinFnIdTruncate:
+        case BuiltinFnIdIntType:
+        case BuiltinFnIdSetFnVisible:
+        case BuiltinFnIdSetFnStaticEval:
+        case BuiltinFnIdSetFnNoInline:
+        case BuiltinFnIdSetDebugSafety:
+            zig_panic("TODO IR gen more builtin functions");
+    }
+    zig_unreachable();
 }
 
 static IrInstruction *ir_gen_fn_call(IrBuilder *irb, AstNode *node) {
@@ -2044,6 +2084,53 @@ static TypeTableEntry *ir_resolve_type(IrAnalyze *ira, IrInstruction *type_value
     return const_val->data.x_type;
 }
 
+static bool ir_resolve_bool(IrAnalyze *ira, IrInstruction *bool_value, bool *out) {
+    if (bool_value == ira->codegen->invalid_instruction)
+        return false;
+
+    if (bool_value->type_entry->id == TypeTableEntryIdInvalid)
+        return false;
+
+    if (bool_value->type_entry->id != TypeTableEntryIdBool) {
+        add_node_error(ira->codegen, bool_value->source_node,
+                buf_sprintf("expected type 'bool', found '%s'", buf_ptr(&bool_value->type_entry->name)));
+        return false;
+    }
+
+    ConstExprValue *const_val = &bool_value->static_value;
+    if (!const_val->ok) {
+        add_node_error(ira->codegen, bool_value->source_node,
+                buf_sprintf("unable to evaluate constant expression"));
+        return false;
+    }
+
+    *out = const_val->data.x_bool;
+    return true;
+}
+
+static FnTableEntry *ir_resolve_fn(IrAnalyze *ira, IrInstruction *fn_value) {
+    if (fn_value == ira->codegen->invalid_instruction)
+        return nullptr;
+
+    if (fn_value->type_entry->id == TypeTableEntryIdInvalid)
+        return nullptr;
+
+    if (fn_value->type_entry->id != TypeTableEntryIdFn) {
+        add_node_error(ira->codegen, fn_value->source_node,
+                buf_sprintf("expected function type, found '%s'", buf_ptr(&fn_value->type_entry->name)));
+        return nullptr;
+    }
+
+    ConstExprValue *const_val = &fn_value->static_value;
+    if (!const_val->ok) {
+        add_node_error(ira->codegen, fn_value->source_node,
+                buf_sprintf("unable to evaluate constant expression"));
+        return nullptr;
+    }
+
+    return const_val->data.x_fn;
+}
+
 static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_instr,
     IrInstruction *dest_type, IrInstruction *value)
 {
@@ -2837,11 +2924,18 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction
         } else if (fn_ref->type_entry->id == TypeTableEntryIdFn) {
             // TODO fully port over the fn call analyze code to IR
             FnTableEntry *fn_table_entry = fn_ref->static_value.data.x_fn;
+            TypeTableEntry *fn_type = fn_table_entry->type_entry;
+
+            IrInstruction **casted_args = allocate<IrInstruction *>(call_instruction->arg_count);
+            for (size_t i = 0; i < call_instruction->arg_count; i += 1) {
+                TypeTableEntry *param_type = fn_type->data.fn.fn_type_id.param_info[i].type;
+                IrInstruction *old_arg = call_instruction->args[i]->other;
+                casted_args[i] = ir_get_casted_value(ira, old_arg, param_type);
+            }
 
             ir_build_call_from(&ira->new_irb, &call_instruction->base,
-                    call_instruction->fn, call_instruction->arg_count, call_instruction->args);
+                    call_instruction->fn, call_instruction->arg_count, casted_args);
 
-            TypeTableEntry *fn_type = fn_table_entry->type_entry;
             TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type;
             return return_type;
         } else {
@@ -3075,1121 +3169,8 @@ static TypeTableEntry *ir_analyze_instruction_un_op(IrAnalyze *ira, IrInstructio
     zig_unreachable();
 }
 
-//static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-//        AstNode *node, const char *err_format, bool is_max)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//    assert(node->data.fn_call_expr.params.length == 1);
-//
-//    AstNode *type_node = node->data.fn_call_expr.params.at(0);
-//    TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-//
-//    if (type_entry->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    } else if (type_entry->id == TypeTableEntryIdInt) {
-//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
-//        return g->builtin_types.entry_num_lit_int;
-//    } else if (type_entry->id == TypeTableEntryIdFloat) {
-//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
-//        return g->builtin_types.entry_num_lit_float;
-//    } else if (type_entry->id == TypeTableEntryIdBool) {
-//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
-//        return type_entry;
-//    } else {
-//        add_node_error(g, node,
-//                buf_sprintf(err_format, buf_ptr(&type_entry->name)));
-//        return g->builtin_types.entry_invalid;
-//    }
-//}
-
-//static TypeTableEntry *analyze_import(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-//        AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    if (context->fn_entry) {
-//        add_node_error(g, node, buf_sprintf("@import invalid inside function bodies"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
-//    Buf *import_target_str = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
-//    if (!import_target_str) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    Buf *import_target_path;
-//    Buf *search_dir;
-//    assert(import->package);
-//    PackageTableEntry *target_package;
-//    auto package_entry = import->package->package_table.maybe_get(import_target_str);
-//    if (package_entry) {
-//        target_package = package_entry->value;
-//        import_target_path = &target_package->root_src_path;
-//        search_dir = &target_package->root_src_dir;
-//    } else {
-//        // try it as a filename
-//        target_package = import->package;
-//        import_target_path = import_target_str;
-//        search_dir = &import->package->root_src_dir;
-//    }
-//
-//    Buf full_path = BUF_INIT;
-//    os_path_join(search_dir, import_target_path, &full_path);
-//
-//    Buf *import_code = buf_alloc();
-//    Buf *abs_full_path = buf_alloc();
-//    int err;
-//    if ((err = os_path_real(&full_path, abs_full_path))) {
-//        if (err == ErrorFileNotFound) {
-//            add_node_error(g, node,
-//                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
-//            return g->builtin_types.entry_invalid;
-//        } else {
-//            g->error_during_imports = true;
-//            add_node_error(g, node,
-//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
-//            return g->builtin_types.entry_invalid;
-//        }
-//    }
-//
-//    auto import_entry = g->import_table.maybe_get(abs_full_path);
-//    if (import_entry) {
-//        return resolve_expr_const_val_as_import(g, node, import_entry->value);
-//    }
-//
-//    if ((err = os_fetch_file_path(abs_full_path, import_code))) {
-//        if (err == ErrorFileNotFound) {
-//            add_node_error(g, node,
-//                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
-//            return g->builtin_types.entry_invalid;
-//        } else {
-//            add_node_error(g, node,
-//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
-//            return g->builtin_types.entry_invalid;
-//        }
-//    }
-//    ImportTableEntry *target_import = add_source_file(g, target_package,
-//            abs_full_path, search_dir, import_target_path, import_code);
-//
-//    scan_decls(g, target_import, target_import->block_context, target_import->root);
-//
-//    return resolve_expr_const_val_as_import(g, node, target_import);
-//}
-//
-//static TypeTableEntry *analyze_c_import(CodeGen *g, ImportTableEntry *parent_import,
-//        BlockContext *parent_context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    if (parent_context->fn_entry) {
-//        add_node_error(g, node, buf_sprintf("@c_import invalid inside function bodies"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    AstNode *block_node = node->data.fn_call_expr.params.at(0);
-//
-//    BlockContext *child_context = new_block_context(node, parent_context);
-//    child_context->c_import_buf = buf_alloc();
-//
-//    TypeTableEntry *resolved_type = analyze_expression(g, parent_import, child_context,
-//            g->builtin_types.entry_void, block_node);
-//
-//    if (resolved_type->id == TypeTableEntryIdInvalid) {
-//        return resolved_type;
-//    }
-//
-//    find_libc_include_path(g);
-//
-//    ImportTableEntry *child_import = allocate<ImportTableEntry>(1);
-//    child_import->c_import_node = node;
-//
-//    ZigList<ErrorMsg *> errors = {0};
-//
-//    int err;
-//    if ((err = parse_h_buf(child_import, &errors, child_context->c_import_buf, g, node))) {
-//        zig_panic("unable to parse h file: %s\n", err_str(err));
-//    }
-//
-//    if (errors.length > 0) {
-//        ErrorMsg *parent_err_msg = add_node_error(g, node, buf_sprintf("C import failed"));
-//        for (size_t i = 0; i < errors.length; i += 1) {
-//            ErrorMsg *err_msg = errors.at(i);
-//            err_msg_add_note(parent_err_msg, err_msg);
-//        }
-//
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (g->verbose) {
-//        fprintf(stderr, "\nc_import:\n");
-//        fprintf(stderr, "-----------\n");
-//        ast_render(stderr, child_import->root, 4);
-//    }
-//
-//    child_import->di_file = parent_import->di_file;
-//    child_import->block_context = new_block_context(child_import->root, nullptr);
-//
-//    scan_decls(g, child_import, child_import->block_context, child_import->root);
-//    return resolve_expr_const_val_as_import(g, node, child_import);
-//}
-//
-//static TypeTableEntry *analyze_err_name(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    AstNode *err_value = node->data.fn_call_expr.params.at(0);
-//    TypeTableEntry *resolved_type = analyze_expression(g, import, context,
-//            g->builtin_types.entry_pure_error, err_value);
-//
-//    if (resolved_type->id == TypeTableEntryIdInvalid) {
-//        return resolved_type;
-//    }
-//
-//    g->generate_error_name_table = true;
-//
-//    TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
-//    return str_type;
-//}
-//
-//static TypeTableEntry *analyze_embed_file(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    AstNode **first_param_node = &node->data.fn_call_expr.params.at(0);
-//    Buf *rel_file_path = resolve_const_expr_str(g, import, context, first_param_node);
-//    if (!rel_file_path) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    // figure out absolute path to resource
-//    Buf source_dir_path = BUF_INIT;
-//    os_path_dirname(import->path, &source_dir_path);
-//
-//    Buf file_path = BUF_INIT;
-//    os_path_resolve(&source_dir_path, rel_file_path, &file_path);
-//
-//    // load from file system into const expr
-//    Buf file_contents = BUF_INIT;
-//    int err;
-//    if ((err = os_fetch_file_path(&file_path, &file_contents))) {
-//        if (err == ErrorFileNotFound) {
-//            add_node_error(g, node,
-//                    buf_sprintf("unable to find '%s'", buf_ptr(&file_path)));
-//            return g->builtin_types.entry_invalid;
-//        } else {
-//            add_node_error(g, node,
-//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&file_path), err_str(err)));
-//            return g->builtin_types.entry_invalid;
-//        }
-//    }
-//
-//    // TODO add dependency on the file we embedded so that we know if it changes
-//    // we'll have to invalidate the cache
-//
-//    return resolve_expr_const_val_as_string_lit(g, node, &file_contents);
-//}
-//
-//static TypeTableEntry *analyze_cmpxchg(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    AstNode **ptr_arg = &node->data.fn_call_expr.params.at(0);
-//    AstNode **cmp_arg = &node->data.fn_call_expr.params.at(1);
-//    AstNode **new_arg = &node->data.fn_call_expr.params.at(2);
-//    AstNode **success_order_arg = &node->data.fn_call_expr.params.at(3);
-//    AstNode **failure_order_arg = &node->data.fn_call_expr.params.at(4);
-//
-//    TypeTableEntry *ptr_type = analyze_expression(g, import, context, nullptr, *ptr_arg);
-//    if (ptr_type->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    } else if (ptr_type->id != TypeTableEntryIdPointer) {
-//        add_node_error(g, *ptr_arg,
-//            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&ptr_type->name)));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
-//    TypeTableEntry *cmp_type = analyze_expression(g, import, context, child_type, *cmp_arg);
-//    TypeTableEntry *new_type = analyze_expression(g, import, context, child_type, *new_arg);
-//
-//    TypeTableEntry *success_order_type = analyze_expression(g, import, context,
-//            g->builtin_types.entry_atomic_order_enum, *success_order_arg);
-//    TypeTableEntry *failure_order_type = analyze_expression(g, import, context,
-//            g->builtin_types.entry_atomic_order_enum, *failure_order_arg);
-//
-//    if (cmp_type->id == TypeTableEntryIdInvalid ||
-//        new_type->id == TypeTableEntryIdInvalid ||
-//        success_order_type->id == TypeTableEntryIdInvalid ||
-//        failure_order_type->id == TypeTableEntryIdInvalid)
-//    {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    ConstExprValue *success_order_val = &get_resolved_expr(*success_order_arg)->const_val;
-//    ConstExprValue *failure_order_val = &get_resolved_expr(*failure_order_arg)->const_val;
-//    if (!success_order_val->ok) {
-//        add_node_error(g, *success_order_arg, buf_sprintf("unable to evaluate constant expression"));
-//        return g->builtin_types.entry_invalid;
-//    } else if (!failure_order_val->ok) {
-//        add_node_error(g, *failure_order_arg, buf_sprintf("unable to evaluate constant expression"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (success_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
-//        add_node_error(g, *success_order_arg,
-//                buf_sprintf("success atomic ordering must be Monotonic or stricter"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    if (failure_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
-//        add_node_error(g, *failure_order_arg,
-//                buf_sprintf("failure atomic ordering must be Monotonic or stricter"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    if (failure_order_val->data.x_enum.tag > success_order_val->data.x_enum.tag) {
-//        add_node_error(g, *failure_order_arg,
-//                buf_sprintf("failure atomic ordering must be no stricter than success"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    if (failure_order_val->data.x_enum.tag == AtomicOrderRelease ||
-//        failure_order_val->data.x_enum.tag == AtomicOrderAcqRel)
-//    {
-//        add_node_error(g, *failure_order_arg,
-//                buf_sprintf("failure atomic ordering must not be Release or AcqRel"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    return g->builtin_types.entry_bool;
-//}
-//
-//static TypeTableEntry *analyze_fence(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    AstNode **atomic_order_arg = &node->data.fn_call_expr.params.at(0);
-//    TypeTableEntry *atomic_order_type = analyze_expression(g, import, context,
-//            g->builtin_types.entry_atomic_order_enum, *atomic_order_arg);
-//
-//    if (atomic_order_type->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    ConstExprValue *atomic_order_val = &get_resolved_expr(*atomic_order_arg)->const_val;
-//
-//    if (!atomic_order_val->ok) {
-//        add_node_error(g, *atomic_order_arg, buf_sprintf("unable to evaluate constant expression"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    return g->builtin_types.entry_void;
-//}
-//
-//static TypeTableEntry *analyze_div_exact(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
-//    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
-//
-//    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
-//    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
-//
-//    AstNode *op_nodes[] = {*op1, *op2};
-//    TypeTableEntry *op_types[] = {op1_type, op2_type};
-//    TypeTableEntry *result_type = resolve_peer_type_compatibility(g, import, context, node,
-//            op_nodes, op_types, 2);
-//
-//    if (result_type->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    } else if (result_type->id == TypeTableEntryIdInt) {
-//        return result_type;
-//    } else if (result_type->id == TypeTableEntryIdNumLitInt) {
-//        // check for division by zero
-//        // check for non exact division
-//        zig_panic("TODO");
-//    } else {
-//        add_node_error(g, node,
-//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&result_type->name)));
-//        return g->builtin_types.entry_invalid;
-//    }
-//}
-//
-//static TypeTableEntry *analyze_truncate(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeFnCallExpr);
-//
-//    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
-//    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
-//
-//    TypeTableEntry *dest_type = analyze_type_expr(g, import, context, *op1);
-//    TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, *op2);
-//
-//    if (dest_type->id == TypeTableEntryIdInvalid || src_type->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    } else if (dest_type->id != TypeTableEntryIdInt) {
-//        add_node_error(g, *op1,
-//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&dest_type->name)));
-//        return g->builtin_types.entry_invalid;
-//    } else if (src_type->id != TypeTableEntryIdInt) {
-//        add_node_error(g, *op2,
-//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&src_type->name)));
-//        return g->builtin_types.entry_invalid;
-//    } else if (src_type->data.integral.is_signed != dest_type->data.integral.is_signed) {
-//        const char *sign_str = dest_type->data.integral.is_signed ? "signed" : "unsigned";
-//        add_node_error(g, *op2,
-//                buf_sprintf("expected %s integer type, got '%s'", sign_str, buf_ptr(&src_type->name)));
-//        return g->builtin_types.entry_invalid;
-//    } else if (src_type->data.integral.bit_count <= dest_type->data.integral.bit_count) {
-//        add_node_error(g, *op2,
-//                buf_sprintf("type '%s' has same or fewer bits than destination type '%s'",
-//                    buf_ptr(&src_type->name), buf_ptr(&dest_type->name)));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    // TODO const expr eval
-//
-//    return dest_type;
-//}
-//
-//static TypeTableEntry *analyze_compile_err(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
-//    Buf *err_msg = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
-//    if (!err_msg) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    add_node_error(g, node, err_msg);
-//
-//    return g->builtin_types.entry_invalid;
-//}
-//
-//static TypeTableEntry *analyze_int_type(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    AstNode **is_signed_node = &node->data.fn_call_expr.params.at(0);
-//    AstNode **bit_count_node = &node->data.fn_call_expr.params.at(1);
-//
-//    TypeTableEntry *bool_type = g->builtin_types.entry_bool;
-//    TypeTableEntry *usize_type = g->builtin_types.entry_usize;
-//    TypeTableEntry *is_signed_type = analyze_expression(g, import, context, bool_type, *is_signed_node);
-//    TypeTableEntry *bit_count_type = analyze_expression(g, import, context, usize_type, *bit_count_node);
-//
-//    if (is_signed_type->id == TypeTableEntryIdInvalid ||
-//        bit_count_type->id == TypeTableEntryIdInvalid)
-//    {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    ConstExprValue *is_signed_val = &get_resolved_expr(*is_signed_node)->const_val;
-//    ConstExprValue *bit_count_val = &get_resolved_expr(*bit_count_node)->const_val;
-//
-//    AstNode *bad_node = nullptr;
-//    if (!is_signed_val->ok) {
-//        bad_node = *is_signed_node;
-//    } else if (!bit_count_val->ok) {
-//        bad_node = *bit_count_node;
-//    }
-//    if (bad_node) {
-//        add_node_error(g, bad_node, buf_sprintf("unable to evaluate constant expression"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    bool depends_on_compile_var = is_signed_val->depends_on_compile_var || bit_count_val->depends_on_compile_var;
-//
-//    TypeTableEntry *int_type = get_int_type(g, is_signed_val->data.x_bool,
-//            bit_count_val->data.x_bignum.data.x_uint);
-//    return resolve_expr_const_val_as_type(g, node, int_type, depends_on_compile_var);
-//
-//}
-//
-//static TypeTableEntry *analyze_set_fn_test(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-//
-//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-//    if (!fn_entry) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &fn_entry->is_test);
-//    if (!ok) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (fn_entry->fn_test_set_node) {
-//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function test attribute set twice"));
-//        add_error_note(g, msg, fn_entry->fn_test_set_node, buf_sprintf("first set here"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    fn_entry->fn_test_set_node = node;
-//
-//    g->test_fn_count += 1;
-//    return g->builtin_types.entry_void;
-//}
-//
-//static TypeTableEntry *analyze_set_fn_no_inline(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-//
-//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-//    if (!fn_entry) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    bool is_noinline;
-//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &is_noinline);
-//    if (!ok) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (fn_entry->fn_no_inline_set_node) {
-//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function no inline attribute set twice"));
-//        add_error_note(g, msg, fn_entry->fn_no_inline_set_node, buf_sprintf("first set here"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    fn_entry->fn_no_inline_set_node = node;
-//
-//    if (fn_entry->fn_inline == FnInlineAlways) {
-//        add_node_error(g, node, buf_sprintf("function is both inline and noinline"));
-//        fn_entry->proto_node->data.fn_proto.skip = true;
-//        return g->builtin_types.entry_invalid;
-//    } else if (is_noinline) {
-//        fn_entry->fn_inline = FnInlineNever;
-//    }
-//
-//    return g->builtin_types.entry_void;
-//}
-//
-//static TypeTableEntry *analyze_set_fn_static_eval(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-//
-//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-//    if (!fn_entry) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    bool want_static_eval;
-//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_static_eval);
-//    if (!ok) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (fn_entry->fn_static_eval_set_node) {
-//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function static eval attribute set twice"));
-//        add_error_note(g, msg, fn_entry->fn_static_eval_set_node, buf_sprintf("first set here"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    fn_entry->fn_static_eval_set_node = node;
-//
-//    if (want_static_eval && !context->fn_entry->is_pure) {
-//        add_node_error(g, node, buf_sprintf("attribute appears too late within function"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (want_static_eval) {
-//        fn_entry->want_pure = WantPureTrue;
-//        fn_entry->want_pure_attr_node = node;
-//    } else {
-//        fn_entry->want_pure = WantPureFalse;
-//        fn_entry->is_pure = false;
-//    }
-//
-//    return g->builtin_types.entry_void;
-//}
-//
-//static TypeTableEntry *analyze_set_fn_visible(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *context, AstNode *node)
-//{
-//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
-//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-//
-//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
-//    if (!fn_entry) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    bool want_export;
-//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_export);
-//    if (!ok) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (fn_entry->fn_export_set_node) {
-//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function visibility set twice"));
-//        add_error_note(g, msg, fn_entry->fn_export_set_node, buf_sprintf("first set here"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    fn_entry->fn_export_set_node = node;
-//
-//    AstNodeFnProto *fn_proto = &fn_entry->proto_node->data.fn_proto;
-//    if (fn_proto->top_level_decl.visib_mod != VisibModExport) {
-//        ErrorMsg *msg = add_node_error(g, node,
-//            buf_sprintf("function must be marked export to set function visibility"));
-//        add_error_note(g, msg, fn_entry->proto_node, buf_sprintf("function declared here"));
-//        return g->builtin_types.entry_void;
-//    }
-//    if (!want_export) {
-//        fn_proto->top_level_decl.visib_mod = VisibModPub;
-//    }
-//
-//    return g->builtin_types.entry_void;
-//}
-//
-//static TypeTableEntry *analyze_set_debug_safety(CodeGen *g, ImportTableEntry *import,
-//        BlockContext *parent_context, AstNode *node)
-//{
-//    AstNode **target_node = &node->data.fn_call_expr.params.at(0);
-//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
-//
-//    TypeTableEntry *target_type = analyze_expression(g, import, parent_context, nullptr, *target_node);
-//    BlockContext *target_context;
-//    ConstExprValue *const_val = &get_resolved_expr(*target_node)->const_val;
-//    if (target_type->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//    if (!const_val->ok) {
-//        add_node_error(g, *target_node, buf_sprintf("unable to evaluate constant expression"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    if (target_type->id == TypeTableEntryIdBlock) {
-//        target_context = const_val->data.x_block;
-//    } else if (target_type->id == TypeTableEntryIdFn) {
-//        target_context = const_val->data.x_fn->fn_def_node->data.fn_def.block_context;
-//    } else if (target_type->id == TypeTableEntryIdMetaType) {
-//        TypeTableEntry *type_arg = const_val->data.x_type;
-//        if (type_arg->id == TypeTableEntryIdStruct) {
-//            target_context = type_arg->data.structure.block_context;
-//        } else if (type_arg->id == TypeTableEntryIdEnum) {
-//            target_context = type_arg->data.enumeration.block_context;
-//        } else if (type_arg->id == TypeTableEntryIdUnion) {
-//            target_context = type_arg->data.unionation.block_context;
-//        } else {
-//            add_node_error(g, *target_node,
-//                buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&type_arg->name)));
-//            return g->builtin_types.entry_invalid;
-//        }
-//    } else {
-//        add_node_error(g, *target_node,
-//            buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&target_type->name)));
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    bool want_debug_safety;
-//    bool ok = resolve_const_expr_bool(g, import, parent_context, value_node, &want_debug_safety);
-//    if (!ok) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (target_context->safety_set_node) {
-//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("debug safety for scope set twice"));
-//        add_error_note(g, msg, target_context->safety_set_node, buf_sprintf("first set here"));
-//        return g->builtin_types.entry_invalid;
-//    }
-//    target_context->safety_set_node = node;
-//
-//    target_context->safety_off = !want_debug_safety;
-//
-//    return g->builtin_types.entry_void;
-//}
-
-
-//static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
-//        TypeTableEntry *expected_type, AstNode *node)
-//{
-//
-//    switch (builtin_fn->id) {
-//        case BuiltinFnIdInvalid:
-//            zig_unreachable();
-//        case BuiltinFnIdAddWithOverflow:
-//        case BuiltinFnIdSubWithOverflow:
-//        case BuiltinFnIdMulWithOverflow:
-//        case BuiltinFnIdShlWithOverflow:
-//            {
-//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-//                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
-//                if (int_type->id == TypeTableEntryIdInvalid) {
-//                    return g->builtin_types.entry_bool;
-//                } else if (int_type->id == TypeTableEntryIdInt) {
-//                    AstNode *op1_node = node->data.fn_call_expr.params.at(1);
-//                    AstNode *op2_node = node->data.fn_call_expr.params.at(2);
-//                    AstNode *result_node = node->data.fn_call_expr.params.at(3);
-//
-//                    analyze_expression(g, import, context, int_type, op1_node);
-//                    analyze_expression(g, import, context, int_type, op2_node);
-//                    analyze_expression(g, import, context, get_pointer_to_type(g, int_type, false),
-//                            result_node);
-//                } else {
-//                    add_node_error(g, type_node,
-//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
-//                }
-//
-//                // TODO constant expression evaluation
-//
-//                return g->builtin_types.entry_bool;
-//            }
-//        case BuiltinFnIdMemcpy:
-//            {
-//                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
-//                AstNode *src_node = node->data.fn_call_expr.params.at(1);
-//                AstNode *len_node = node->data.fn_call_expr.params.at(2);
-//                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
-//                TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, src_node);
-//                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
-//
-//                if (dest_type->id != TypeTableEntryIdInvalid &&
-//                    dest_type->id != TypeTableEntryIdPointer)
-//                {
-//                    add_node_error(g, dest_node,
-//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
-//                }
-//
-//                if (src_type->id != TypeTableEntryIdInvalid &&
-//                    src_type->id != TypeTableEntryIdPointer)
-//                {
-//                    add_node_error(g, src_node,
-//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&src_type->name)));
-//                }
-//
-//                if (dest_type->id == TypeTableEntryIdPointer &&
-//                    src_type->id == TypeTableEntryIdPointer)
-//                {
-//                    uint64_t dest_align = get_memcpy_align(g, dest_type->data.pointer.child_type);
-//                    uint64_t src_align = get_memcpy_align(g, src_type->data.pointer.child_type);
-//                    if (dest_align != src_align) {
-//                        add_node_error(g, dest_node, buf_sprintf(
-//                            "misaligned memcpy, '%s' has alignment '%" PRIu64 ", '%s' has alignment %" PRIu64,
-//                                    buf_ptr(&dest_type->name), dest_align,
-//                                    buf_ptr(&src_type->name), src_align));
-//                    }
-//                }
-//
-//                return builtin_fn->return_type;
-//            }
-//        case BuiltinFnIdMemset:
-//            {
-//                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
-//                AstNode *char_node = node->data.fn_call_expr.params.at(1);
-//                AstNode *len_node = node->data.fn_call_expr.params.at(2);
-//                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
-//                analyze_expression(g, import, context, builtin_fn->param_types[1], char_node);
-//                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
-//
-//                if (dest_type->id != TypeTableEntryIdInvalid &&
-//                    dest_type->id != TypeTableEntryIdPointer)
-//                {
-//                    add_node_error(g, dest_node,
-//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
-//                }
-//
-//                return builtin_fn->return_type;
-//            }
-//        case BuiltinFnIdSizeof:
-//            {
-//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-//                if (type_entry->id == TypeTableEntryIdInvalid) {
-//                    return g->builtin_types.entry_invalid;
-//                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
-//                    add_node_error(g, first_executing_node(type_node),
-//                            buf_sprintf("no size available for type '%s'", buf_ptr(&type_entry->name)));
-//                    return g->builtin_types.entry_invalid;
-//                } else {
-//                    uint64_t size_in_bytes = type_size(g, type_entry);
-//                    bool depends_on_compile_var = (type_entry == g->builtin_types.entry_usize ||
-//                            type_entry == g->builtin_types.entry_isize);
-//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-//                            size_in_bytes, depends_on_compile_var);
-//                }
-//            }
-//        case BuiltinFnIdAlignof:
-//            {
-//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-//                if (type_entry->id == TypeTableEntryIdInvalid) {
-//                    return g->builtin_types.entry_invalid;
-//                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
-//                    add_node_error(g, first_executing_node(type_node),
-//                            buf_sprintf("no align available for type '%s'", buf_ptr(&type_entry->name)));
-//                    return g->builtin_types.entry_invalid;
-//                } else {
-//                    uint64_t align_in_bytes = LLVMABISizeOfType(g->target_data_ref, type_entry->type_ref);
-//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-//                            align_in_bytes, false);
-//                }
-//            }
-//        case BuiltinFnIdMaxValue:
-//            return analyze_min_max_value(g, import, context, node,
-//                    "no max value available for type '%s'", true);
-//        case BuiltinFnIdMinValue:
-//            return analyze_min_max_value(g, import, context, node,
-//                    "no min value available for type '%s'", false);
-//        case BuiltinFnIdMemberCount:
-//            {
-//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
-//
-//                if (type_entry->id == TypeTableEntryIdInvalid) {
-//                    return type_entry;
-//                } else if (type_entry->id == TypeTableEntryIdEnum) {
-//                    uint64_t value_count = type_entry->data.enumeration.src_field_count;
-//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
-//                            value_count, false);
-//                } else {
-//                    add_node_error(g, node,
-//                            buf_sprintf("no value count available for type '%s'", buf_ptr(&type_entry->name)));
-//                    return g->builtin_types.entry_invalid;
-//                }
-//            }
-//        case BuiltinFnIdCInclude:
-//            {
-//                if (!context->c_import_buf) {
-//                    add_node_error(g, node, buf_sprintf("@c_include valid only in c_import blocks"));
-//                    return g->builtin_types.entry_invalid;
-//                }
-//
-//                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
-//                TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
-//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, str_type, *str_node);
-//
-//                if (resolved_type->id == TypeTableEntryIdInvalid) {
-//                    return resolved_type;
-//                }
-//
-//                ConstExprValue *const_str_val = &get_resolved_expr(*str_node)->const_val;
-//
-//                if (!const_str_val->ok) {
-//                    add_node_error(g, *str_node, buf_sprintf("@c_include requires constant expression"));
-//                    return g->builtin_types.entry_void;
-//                }
-//
-//                buf_appendf(context->c_import_buf, "#include <");
-//                ConstExprValue *ptr_field = const_str_val->data.x_struct.fields[0];
-//                uint64_t len = ptr_field->data.x_ptr.len;
-//                for (uint64_t i = 0; i < len; i += 1) {
-//                    ConstExprValue *char_val = ptr_field->data.x_ptr.ptr[i];
-//                    uint64_t big_c = char_val->data.x_bignum.data.x_uint;
-//                    assert(big_c <= UINT8_MAX);
-//                    uint8_t c = big_c;
-//                    buf_append_char(context->c_import_buf, c);
-//                }
-//                buf_appendf(context->c_import_buf, ">\n");
-//
-//                return g->builtin_types.entry_void;
-//            }
-//        case BuiltinFnIdCDefine:
-//            zig_panic("TODO");
-//        case BuiltinFnIdCUndef:
-//            zig_panic("TODO");
-//
-//        case BuiltinFnIdCompileVar:
-//            {
-//                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
-//
-//                Buf *var_name = resolve_const_expr_str(g, import, context, str_node);
-//                if (!var_name) {
-//                    return g->builtin_types.entry_invalid;
-//                }
-//
-//                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-//                const_val->ok = true;
-//                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, true);
-//                } else if (buf_eql_str(var_name, "is_release")) {
-//                    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, true);
-//                } else if (buf_eql_str(var_name, "os")) {
-//                    const_val->data.x_enum.tag = g->target_os_index;
-//                    return g->builtin_types.entry_os_enum;
-//                } else if (buf_eql_str(var_name, "arch")) {
-//                    const_val->data.x_enum.tag = g->target_arch_index;
-//                    return g->builtin_types.entry_arch_enum;
-//                } else if (buf_eql_str(var_name, "environ")) {
-//                    const_val->data.x_enum.tag = g->target_environ_index;
-//                    return g->builtin_types.entry_environ_enum;
-//                } else if (buf_eql_str(var_name, "object_format")) {
-//                    const_val->data.x_enum.tag = g->target_oformat_index;
-//                    return g->builtin_types.entry_oformat_enum;
-//                } else {
-//                    add_node_error(g, *str_node,
-//                        buf_sprintf("unrecognized compile variable: '%s'", buf_ptr(var_name)));
-//                    return g->builtin_types.entry_invalid;
-//                }
-//            }
-//        case BuiltinFnIdConstEval:
-//            {
-//                AstNode **expr_node = node->data.fn_call_expr.params.at(0)->parent_field;
-//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_type, *expr_node);
-//                if (resolved_type->id == TypeTableEntryIdInvalid) {
-//                    return resolved_type;
-//                }
-//
-//                ConstExprValue *const_expr_val = &get_resolved_expr(*expr_node)->const_val;
-//
-//                if (!const_expr_val->ok) {
-//                    add_node_error(g, *expr_node, buf_sprintf("unable to evaluate constant expression"));
-//                    return g->builtin_types.entry_invalid;
-//                }
-//
-//                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-//                *const_val = *const_expr_val;
-//
-//                return resolved_type;
-//            }
-//        case BuiltinFnIdCtz:
-//        case BuiltinFnIdClz:
-//            {
-//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-//                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
-//                if (int_type->id == TypeTableEntryIdInvalid) {
-//                    return int_type;
-//                } else if (int_type->id == TypeTableEntryIdInt) {
-//                    AstNode **expr_node = node->data.fn_call_expr.params.at(1)->parent_field;
-//                    TypeTableEntry *resolved_type = analyze_expression(g, import, context, int_type, *expr_node);
-//                    if (resolved_type->id == TypeTableEntryIdInvalid) {
-//                        return resolved_type;
-//                    }
-//
-//                    // TODO const expr eval
-//
-//                    return resolved_type;
-//                } else {
-//                    add_node_error(g, type_node,
-//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
-//                    return g->builtin_types.entry_invalid;
-//                }
-//            }
-//        case BuiltinFnIdImport:
-//            return analyze_import(g, import, context, node);
-//        case BuiltinFnIdCImport:
-//            return analyze_c_import(g, import, context, node);
-//        case BuiltinFnIdErrName:
-//            return analyze_err_name(g, import, context, node);
-//        case BuiltinFnIdBreakpoint:
-//            mark_impure_fn(g, context, node);
-//            return g->builtin_types.entry_void;
-//        case BuiltinFnIdReturnAddress:
-//        case BuiltinFnIdFrameAddress:
-//            mark_impure_fn(g, context, node);
-//            return builtin_fn->return_type;
-//        case BuiltinFnIdEmbedFile:
-//            return analyze_embed_file(g, import, context, node);
-//        case BuiltinFnIdCmpExchange:
-//            return analyze_cmpxchg(g, import, context, node);
-//        case BuiltinFnIdFence:
-//            return analyze_fence(g, import, context, node);
-//        case BuiltinFnIdDivExact:
-//            return analyze_div_exact(g, import, context, node);
-//        case BuiltinFnIdTruncate:
-//            return analyze_truncate(g, import, context, node);
-//        case BuiltinFnIdCompileErr:
-//            return analyze_compile_err(g, import, context, node);
-//        case BuiltinFnIdIntType:
-//            return analyze_int_type(g, import, context, node);
-//        case BuiltinFnIdSetFnTest:
-//            return analyze_set_fn_test(g, import, context, node);
-//        case BuiltinFnIdSetFnNoInline:
-//            return analyze_set_fn_no_inline(g, import, context, node);
-//        case BuiltinFnIdSetFnStaticEval:
-//            return analyze_set_fn_static_eval(g, import, context, node);
-//        case BuiltinFnIdSetFnVisible:
-//            return analyze_set_fn_visible(g, import, context, node);
-//        case BuiltinFnIdSetDebugSafety:
-//            return analyze_set_debug_safety(g, import, context, node);
-//    }
-//    zig_unreachable();
-//}
-
-//static TypeTableEntry *analyze_container_init_expr(CodeGen *g, ImportTableEntry *import,
-//    BlockContext *context, AstNode *node)
-//{
-//    assert(node->type == NodeTypeContainerInitExpr);
-//
-//    AstNodeContainerInitExpr *container_init_expr = &node->data.container_init_expr;
-//
-//    ContainerInitKind kind = container_init_expr->kind;
-//
-//    if (container_init_expr->type->type == NodeTypeFieldAccessExpr) {
-//        container_init_expr->type->data.field_access_expr.container_init_expr_node = node;
-//    }
-//
-//    TypeTableEntry *container_meta_type = analyze_expression(g, import, context, nullptr,
-//            container_init_expr->type);
-//
-//    if (container_meta_type->id == TypeTableEntryIdInvalid) {
-//        return g->builtin_types.entry_invalid;
-//    }
-//
-//    if (node->data.container_init_expr.enum_type) {
-//        get_resolved_expr(node)->const_val = get_resolved_expr(container_init_expr->type)->const_val;
-//        return node->data.container_init_expr.enum_type;
-//    }
-//
-//    TypeTableEntry *container_type = resolve_type(g, container_init_expr->type);
-//
-//    if (container_type->id == TypeTableEntryIdInvalid) {
-//        return container_type;
-//    } else if (container_type->id == TypeTableEntryIdStruct &&
-//               !container_type->data.structure.is_slice &&
-//               (kind == ContainerInitKindStruct || (kind == ContainerInitKindArray &&
-//                                                    container_init_expr->entries.length == 0)))
-//    {
-//        StructValExprCodeGen *codegen = &container_init_expr->resolved_struct_val_expr;
-//        codegen->type_entry = container_type;
-//        codegen->source_node = node;
-//
-//
-//        size_t expr_field_count = container_init_expr->entries.length;
-//        size_t actual_field_count = container_type->data.structure.src_field_count;
-//
-//        AstNode *non_const_expr_culprit = nullptr;
-//
-//        size_t *field_use_counts = allocate<size_t>(actual_field_count);
-//        ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-//        const_val->ok = true;
-//        const_val->data.x_struct.fields = allocate<ConstExprValue*>(actual_field_count);
-//        for (size_t i = 0; i < expr_field_count; i += 1) {
-//            AstNode *val_field_node = container_init_expr->entries.at(i);
-//            assert(val_field_node->type == NodeTypeStructValueField);
-//
-//            val_field_node->block_context = context;
-//
-//            TypeStructField *type_field = find_struct_type_field(container_type,
-//                    val_field_node->data.struct_val_field.name);
-//
-//            if (!type_field) {
-//                add_node_error(g, val_field_node,
-//                    buf_sprintf("no member named '%s' in '%s'",
-//                        buf_ptr(val_field_node->data.struct_val_field.name), buf_ptr(&container_type->name)));
-//                continue;
-//            }
-//
-//            if (type_field->type_entry->id == TypeTableEntryIdInvalid) {
-//                return g->builtin_types.entry_invalid;
-//            }
-//
-//            size_t field_index = type_field->src_index;
-//            field_use_counts[field_index] += 1;
-//            if (field_use_counts[field_index] > 1) {
-//                add_node_error(g, val_field_node, buf_sprintf("duplicate field"));
-//                continue;
-//            }
-//
-//            val_field_node->data.struct_val_field.type_struct_field = type_field;
-//
-//            analyze_expression(g, import, context, type_field->type_entry,
-//                    val_field_node->data.struct_val_field.expr);
-//
-//            if (const_val->ok) {
-//                ConstExprValue *field_val =
-//                    &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;
-//                    non_const_expr_culprit = val_field_node->data.struct_val_field.expr;
-//                }
-//            }
-//        }
-//        if (!const_val->ok) {
-//            assert(non_const_expr_culprit);
-//            if (context->fn_entry) {
-//                context->fn_entry->struct_val_expr_alloca_list.append(codegen);
-//            } else {
-//                add_node_error(g, non_const_expr_culprit, buf_sprintf("unable to evaluate constant expression"));
-//            }
-//        }
-//
-//        for (size_t i = 0; i < actual_field_count; i += 1) {
-//            if (field_use_counts[i] == 0) {
-//                add_node_error(g, node,
-//                    buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i].name)));
-//            }
-//        }
-//        return container_type;
-//    } else if (container_type->id == TypeTableEntryIdStruct &&
-//               container_type->data.structure.is_slice &&
-//               kind == ContainerInitKindArray)
-//    {
-//        size_t elem_count = container_init_expr->entries.length;
-//
-//        TypeTableEntry *pointer_type = container_type->data.structure.fields[0].type_entry;
-//        assert(pointer_type->id == TypeTableEntryIdPointer);
-//        TypeTableEntry *child_type = pointer_type->data.pointer.child_type;
-//
-//        ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
-//        const_val->ok = true;
-//        const_val->data.x_array.fields = allocate<ConstExprValue*>(elem_count);
-//
-//        for (size_t i = 0; i < elem_count; i += 1) {
-//            AstNode **elem_node = &container_init_expr->entries.at(i);
-//            analyze_expression(g, import, context, child_type, *elem_node);
-//
-//            if (const_val->ok) {
-//                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;
-//                }
-//            }
-//        }
-//
-//        TypeTableEntry *fixed_size_array_type = get_array_type(g, child_type, elem_count);
-//
-//        StructValExprCodeGen *codegen = &container_init_expr->resolved_struct_val_expr;
-//        codegen->type_entry = fixed_size_array_type;
-//        codegen->source_node = node;
-//        if (!const_val->ok) {
-//            if (!context->fn_entry) {
-//                add_node_error(g, node,
-//                    buf_sprintf("unable to evaluate constant expression"));
-//            } else {
-//                context->fn_entry->struct_val_expr_alloca_list.append(codegen);
-//            }
-//        }
-//
-//        return fixed_size_array_type;
-//    } else if (container_type->id == TypeTableEntryIdArray) {
-//        zig_panic("TODO array container init");
-//        return container_type;
-//    } else if (container_type->id == TypeTableEntryIdVoid) {
-//        if (container_init_expr->entries.length != 0) {
-//            add_node_error(g, node, buf_sprintf("void expression expects no arguments"));
-//            return g->builtin_types.entry_invalid;
-//        } else {
-//            return resolve_expr_const_val_as_void(g, node);
-//        }
-//    } else {
-//        add_node_error(g, node,
-//            buf_sprintf("type '%s' does not support %s initialization syntax",
-//                buf_ptr(&container_type->name), err_container_init_syntax_name(kind)));
-//        return g->builtin_types.entry_invalid;
-//    }
-//}
-
-static TypeTableEntry *ir_analyze_instruction_br(IrAnalyze *ira, IrInstructionBr *br_instruction) {
-    IrBasicBlock *old_dest_block = br_instruction->dest_block;
+static TypeTableEntry *ir_analyze_instruction_br(IrAnalyze *ira, IrInstructionBr *br_instruction) {
+    IrBasicBlock *old_dest_block = br_instruction->dest_block;
 
     // TODO detect backward jumps
 
@@ -4235,55 +3216,6 @@ static TypeTableEntry *ir_analyze_instruction_cond_br(IrAnalyze *ira, IrInstruct
     return ira->codegen->builtin_types.entry_unreachable;
 }
 
-static TypeTableEntry *ir_analyze_instruction_builtin_call(IrAnalyze *ira,
-        IrInstructionBuiltinCall *builtin_call_instruction)
-{
-    switch (builtin_call_instruction->fn->id) {
-        case BuiltinFnIdInvalid:
-        case BuiltinFnIdUnreachable:
-        case BuiltinFnIdTypeof:
-            zig_unreachable();
-        case BuiltinFnIdMemcpy:
-        case BuiltinFnIdMemset:
-        case BuiltinFnIdSizeof:
-        case BuiltinFnIdAlignof:
-        case BuiltinFnIdMaxValue:
-        case BuiltinFnIdMinValue:
-        case BuiltinFnIdMemberCount:
-        case BuiltinFnIdAddWithOverflow:
-        case BuiltinFnIdSubWithOverflow:
-        case BuiltinFnIdMulWithOverflow:
-        case BuiltinFnIdShlWithOverflow:
-        case BuiltinFnIdCInclude:
-        case BuiltinFnIdCDefine:
-        case BuiltinFnIdCUndef:
-        case BuiltinFnIdCompileVar:
-        case BuiltinFnIdCompileErr:
-        case BuiltinFnIdConstEval:
-        case BuiltinFnIdCtz:
-        case BuiltinFnIdClz:
-        case BuiltinFnIdImport:
-        case BuiltinFnIdCImport:
-        case BuiltinFnIdErrName:
-        case BuiltinFnIdBreakpoint:
-        case BuiltinFnIdReturnAddress:
-        case BuiltinFnIdFrameAddress:
-        case BuiltinFnIdEmbedFile:
-        case BuiltinFnIdCmpExchange:
-        case BuiltinFnIdFence:
-        case BuiltinFnIdDivExact:
-        case BuiltinFnIdTruncate:
-        case BuiltinFnIdIntType:
-        case BuiltinFnIdSetFnTest:
-        case BuiltinFnIdSetFnVisible:
-        case BuiltinFnIdSetFnStaticEval:
-        case BuiltinFnIdSetFnNoInline:
-        case BuiltinFnIdSetDebugSafety:
-            zig_panic("TODO analyze more builtin functions");
-    }
-    zig_unreachable();
-}
-
 static TypeTableEntry *ir_analyze_instruction_unreachable(IrAnalyze *ira,
         IrInstructionUnreachable *unreachable_instruction)
 {
@@ -4731,6 +3663,34 @@ static TypeTableEntry *ir_analyze_instruction_ptr_type_child(IrAnalyze *ira,
     return ira->codegen->builtin_types.entry_type;
 }
 
+static TypeTableEntry *ir_analyze_instruction_set_fn_test(IrAnalyze *ira,
+        IrInstructionSetFnTest *set_fn_test_instruction)
+{
+    IrInstruction *fn_value = set_fn_test_instruction->fn_value->other;
+    IrInstruction *is_test_value = set_fn_test_instruction->is_test->other;
+
+    FnTableEntry *fn_entry = ir_resolve_fn(ira, fn_value);
+    if (!fn_entry)
+        return ira->codegen->builtin_types.entry_invalid;
+
+    if (!ir_resolve_bool(ira, is_test_value, &fn_entry->is_test))
+        return ira->codegen->builtin_types.entry_invalid;
+
+    AstNode *source_node = set_fn_test_instruction->base.source_node;
+    if (fn_entry->fn_test_set_node) {
+        ErrorMsg *msg = add_node_error(ira->codegen, source_node,
+                buf_sprintf("function test attribute set twice"));
+        add_error_note(ira->codegen, msg, fn_entry->fn_test_set_node, buf_sprintf("first set here"));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+    fn_entry->fn_test_set_node = source_node;
+
+    ira->codegen->test_fn_count += 1;
+
+    ir_build_const_from(ira, &set_fn_test_instruction->base, false);
+    return ira->codegen->builtin_types.entry_void;
+}
+
 static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -4763,8 +3723,6 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_br(ira, (IrInstructionBr *)instruction);
         case IrInstructionIdCondBr:
             return ir_analyze_instruction_cond_br(ira, (IrInstructionCondBr *)instruction);
-        case IrInstructionIdBuiltinCall:
-            return ir_analyze_instruction_builtin_call(ira, (IrInstructionBuiltinCall *)instruction);
         case IrInstructionIdUnreachable:
             return ir_analyze_instruction_unreachable(ira, (IrInstructionUnreachable *)instruction);
         case IrInstructionIdPhi:
@@ -4775,175 +3733,3488 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_to_ptr_type(ira, (IrInstructionToPtrType *)instruction);
         case IrInstructionIdPtrTypeChild:
             return ir_analyze_instruction_ptr_type_child(ira, (IrInstructionPtrTypeChild *)instruction);
+        case IrInstructionIdSetFnTest:
+            return ir_analyze_instruction_set_fn_test(ira, (IrInstructionSetFnTest *)instruction);
+        case IrInstructionIdSwitchBr:
+        case IrInstructionIdCast:
+        case IrInstructionIdContainerInitList:
+        case IrInstructionIdContainerInitFields:
+        case IrInstructionIdStructFieldPtr:
+            zig_panic("TODO analyze more instructions");
+    }
+    zig_unreachable();
+}
+
+static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *instruction) {
+    TypeTableEntry *instruction_type = ir_analyze_instruction_nocast(ira, instruction);
+    instruction->type_entry = instruction_type;
+    if (instruction->other) {
+        instruction->other->type_entry = instruction_type;
+    } else {
+        assert(instruction_type->id == TypeTableEntryIdInvalid ||
+               instruction_type->id == TypeTableEntryIdUnreachable);
+        instruction->other = instruction;
+    }
+    return instruction_type;
+}
+
+// This function attempts to evaluate IR code while doing type checking and other analysis.
+// It emits a new IrExecutable which is partially evaluated IR code.
+TypeTableEntry *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutable *new_exec,
+        TypeTableEntry *expected_type, AstNode *expected_type_source_node)
+{
+    IrAnalyze ir_analyze_data = {};
+    IrAnalyze *ira = &ir_analyze_data;
+    ira->codegen = codegen;
+    ira->explicit_return_type = expected_type;
+
+    ira->old_irb.codegen = codegen;
+    ira->old_irb.exec = old_exec;
+
+    ira->new_irb.codegen = codegen;
+    ira->new_irb.exec = new_exec;
+
+    ira->exec_context.mem_slot_count = ira->old_irb.exec->mem_slot_count;
+    ira->exec_context.mem_slot_list = allocate<ConstExprValue>(ira->exec_context.mem_slot_count);
+
+    IrBasicBlock *old_entry_bb = ira->old_irb.exec->basic_block_list.at(0);
+    IrBasicBlock *new_entry_bb = ir_get_new_bb(ira, old_entry_bb);
+    ir_ref_bb(new_entry_bb);
+    ira->old_irb.current_basic_block = old_entry_bb;
+    ira->new_irb.current_basic_block = new_entry_bb;
+    ira->block_queue_index = 0;
+    ira->instruction_index = 0;
+
+    while (ira->block_queue_index < ira->old_bb_queue.length) {
+        IrInstruction *old_instruction = ira->old_irb.current_basic_block->instruction_list.at(ira->instruction_index);
+
+        if (old_instruction->ref_count == 0 && !ir_has_side_effects(old_instruction)) {
+            ira->instruction_index += 1;
+            continue;
+        }
+
+        TypeTableEntry *return_type = ir_analyze_instruction(ira, old_instruction);
+
+        // unreachable instructions do their own control flow.
+        if (return_type->id == TypeTableEntryIdUnreachable)
+            continue;
+
+        ira->instruction_index += 1;
+    }
+
+    return ir_resolve_peer_types(ira, expected_type_source_node, ira->implicit_return_type_list.items,
+            ira->implicit_return_type_list.length);
+}
+
+bool ir_has_side_effects(IrInstruction *instruction) {
+    switch (instruction->id) {
+        case IrInstructionIdInvalid:
+            zig_unreachable();
+        case IrInstructionIdBr:
+        case IrInstructionIdCondBr:
         case IrInstructionIdSwitchBr:
+        case IrInstructionIdDeclVar:
+        case IrInstructionIdStorePtr:
+        case IrInstructionIdCall:
+        case IrInstructionIdReturn:
+        case IrInstructionIdUnreachable:
+        case IrInstructionIdSetFnTest:
+            return true;
+        case IrInstructionIdPhi:
+        case IrInstructionIdUnOp:
+        case IrInstructionIdBinOp:
+        case IrInstructionIdLoadPtr:
+        case IrInstructionIdConst:
         case IrInstructionIdCast:
         case IrInstructionIdContainerInitList:
         case IrInstructionIdContainerInitFields:
+        case IrInstructionIdFieldPtr:
+        case IrInstructionIdElemPtr:
+        case IrInstructionIdVarPtr:
+        case IrInstructionIdTypeOf:
+        case IrInstructionIdToPtrType:
+        case IrInstructionIdPtrTypeChild:
+        case IrInstructionIdReadField:
         case IrInstructionIdStructFieldPtr:
-            zig_panic("TODO analyze more instructions");
+            return false;
     }
     zig_unreachable();
 }
 
-static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *instruction) {
-    TypeTableEntry *instruction_type = ir_analyze_instruction_nocast(ira, instruction);
-    instruction->type_entry = instruction_type;
-    if (instruction->other) {
-        instruction->other->type_entry = instruction_type;
-    } else {
-        assert(instruction_type->id == TypeTableEntryIdInvalid ||
-               instruction_type->id == TypeTableEntryIdUnreachable);
-        instruction->other = instruction;
-    }
-    return instruction_type;
-}
-
-// This function attempts to evaluate IR code while doing type checking and other analysis.
-// It emits a new IrExecutable which is partially evaluated IR code.
-TypeTableEntry *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutable *new_exec,
-        TypeTableEntry *expected_type, AstNode *expected_type_source_node)
-{
-    IrAnalyze ir_analyze_data = {};
-    IrAnalyze *ira = &ir_analyze_data;
-    ira->codegen = codegen;
-    ira->explicit_return_type = expected_type;
+IrInstruction *ir_exec_const_result(IrExecutable *exec) {
+    if (exec->basic_block_list.length != 1)
+        return nullptr;
 
-    ira->old_irb.codegen = codegen;
-    ira->old_irb.exec = old_exec;
+    IrBasicBlock *bb = exec->basic_block_list.at(0);
+    if (bb->instruction_list.length != 1)
+        return nullptr;
 
-    ira->new_irb.codegen = codegen;
-    ira->new_irb.exec = new_exec;
+    IrInstruction *only_inst = bb->instruction_list.at(0);
+    if (only_inst->id != IrInstructionIdReturn)
+        return nullptr;
 
-    ira->exec_context.mem_slot_count = ira->old_irb.exec->mem_slot_count;
-    ira->exec_context.mem_slot_list = allocate<ConstExprValue>(ira->exec_context.mem_slot_count);
+    IrInstructionReturn *ret_inst = (IrInstructionReturn *)only_inst;
+    IrInstruction *value = ret_inst->value;
+    assert(value->static_value.ok);
+    return value;
+}
 
-    IrBasicBlock *old_entry_bb = ira->old_irb.exec->basic_block_list.at(0);
-    IrBasicBlock *new_entry_bb = ir_get_new_bb(ira, old_entry_bb);
-    ir_ref_bb(new_entry_bb);
-    ira->old_irb.current_basic_block = old_entry_bb;
-    ira->new_irb.current_basic_block = new_entry_bb;
-    ira->block_queue_index = 0;
-    ira->instruction_index = 0;
+//static TypeTableEntry *analyze_min_max_value(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node, const char *err_format, bool is_max)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//    assert(node->data.fn_call_expr.params.length == 1);
+//
+//    AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//    TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//
+//    if (type_entry->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (type_entry->id == TypeTableEntryIdInt) {
+//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
+//        return g->builtin_types.entry_num_lit_int;
+//    } else if (type_entry->id == TypeTableEntryIdFloat) {
+//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
+//        return g->builtin_types.entry_num_lit_float;
+//    } else if (type_entry->id == TypeTableEntryIdBool) {
+//        eval_min_max_value(g, type_entry, &get_resolved_expr(node)->const_val, is_max);
+//        return type_entry;
+//    } else {
+//        add_node_error(g, node,
+//                buf_sprintf(err_format, buf_ptr(&type_entry->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
 
-    while (ira->block_queue_index < ira->old_bb_queue.length) {
-        IrInstruction *old_instruction = ira->old_irb.current_basic_block->instruction_list.at(ira->instruction_index);
+//static TypeTableEntry *analyze_import(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    if (context->fn_entry) {
+//        add_node_error(g, node, buf_sprintf("@import invalid inside function bodies"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
+//    Buf *import_target_str = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
+//    if (!import_target_str) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    Buf *import_target_path;
+//    Buf *search_dir;
+//    assert(import->package);
+//    PackageTableEntry *target_package;
+//    auto package_entry = import->package->package_table.maybe_get(import_target_str);
+//    if (package_entry) {
+//        target_package = package_entry->value;
+//        import_target_path = &target_package->root_src_path;
+//        search_dir = &target_package->root_src_dir;
+//    } else {
+//        // try it as a filename
+//        target_package = import->package;
+//        import_target_path = import_target_str;
+//        search_dir = &import->package->root_src_dir;
+//    }
+//
+//    Buf full_path = BUF_INIT;
+//    os_path_join(search_dir, import_target_path, &full_path);
+//
+//    Buf *import_code = buf_alloc();
+//    Buf *abs_full_path = buf_alloc();
+//    int err;
+//    if ((err = os_path_real(&full_path, abs_full_path))) {
+//        if (err == ErrorFileNotFound) {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
+//            return g->builtin_types.entry_invalid;
+//        } else {
+//            g->error_during_imports = true;
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    auto import_entry = g->import_table.maybe_get(abs_full_path);
+//    if (import_entry) {
+//        return resolve_expr_const_val_as_import(g, node, import_entry->value);
+//    }
+//
+//    if ((err = os_fetch_file_path(abs_full_path, import_code))) {
+//        if (err == ErrorFileNotFound) {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to find '%s'", buf_ptr(import_target_path)));
+//            return g->builtin_types.entry_invalid;
+//        } else {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&full_path), err_str(err)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//    ImportTableEntry *target_import = add_source_file(g, target_package,
+//            abs_full_path, search_dir, import_target_path, import_code);
+//
+//    scan_decls(g, target_import, target_import->block_context, target_import->root);
+//
+//    return resolve_expr_const_val_as_import(g, node, target_import);
+//}
+//
+//static TypeTableEntry *analyze_c_import(CodeGen *g, ImportTableEntry *parent_import,
+//        BlockContext *parent_context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    if (parent_context->fn_entry) {
+//        add_node_error(g, node, buf_sprintf("@c_import invalid inside function bodies"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    AstNode *block_node = node->data.fn_call_expr.params.at(0);
+//
+//    BlockContext *child_context = new_block_context(node, parent_context);
+//    child_context->c_import_buf = buf_alloc();
+//
+//    TypeTableEntry *resolved_type = analyze_expression(g, parent_import, child_context,
+//            g->builtin_types.entry_void, block_node);
+//
+//    if (resolved_type->id == TypeTableEntryIdInvalid) {
+//        return resolved_type;
+//    }
+//
+//    find_libc_include_path(g);
+//
+//    ImportTableEntry *child_import = allocate<ImportTableEntry>(1);
+//    child_import->c_import_node = node;
+//
+//    ZigList<ErrorMsg *> errors = {0};
+//
+//    int err;
+//    if ((err = parse_h_buf(child_import, &errors, child_context->c_import_buf, g, node))) {
+//        zig_panic("unable to parse h file: %s\n", err_str(err));
+//    }
+//
+//    if (errors.length > 0) {
+//        ErrorMsg *parent_err_msg = add_node_error(g, node, buf_sprintf("C import failed"));
+//        for (size_t i = 0; i < errors.length; i += 1) {
+//            ErrorMsg *err_msg = errors.at(i);
+//            err_msg_add_note(parent_err_msg, err_msg);
+//        }
+//
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (g->verbose) {
+//        fprintf(stderr, "\nc_import:\n");
+//        fprintf(stderr, "-----------\n");
+//        ast_render(stderr, child_import->root, 4);
+//    }
+//
+//    child_import->di_file = parent_import->di_file;
+//    child_import->block_context = new_block_context(child_import->root, nullptr);
+//
+//    scan_decls(g, child_import, child_import->block_context, child_import->root);
+//    return resolve_expr_const_val_as_import(g, node, child_import);
+//}
+//
+//static TypeTableEntry *analyze_err_name(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode *err_value = node->data.fn_call_expr.params.at(0);
+//    TypeTableEntry *resolved_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_pure_error, err_value);
+//
+//    if (resolved_type->id == TypeTableEntryIdInvalid) {
+//        return resolved_type;
+//    }
+//
+//    g->generate_error_name_table = true;
+//
+//    TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
+//    return str_type;
+//}
+//
+//static TypeTableEntry *analyze_embed_file(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **first_param_node = &node->data.fn_call_expr.params.at(0);
+//    Buf *rel_file_path = resolve_const_expr_str(g, import, context, first_param_node);
+//    if (!rel_file_path) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    // figure out absolute path to resource
+//    Buf source_dir_path = BUF_INIT;
+//    os_path_dirname(import->path, &source_dir_path);
+//
+//    Buf file_path = BUF_INIT;
+//    os_path_resolve(&source_dir_path, rel_file_path, &file_path);
+//
+//    // load from file system into const expr
+//    Buf file_contents = BUF_INIT;
+//    int err;
+//    if ((err = os_fetch_file_path(&file_path, &file_contents))) {
+//        if (err == ErrorFileNotFound) {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to find '%s'", buf_ptr(&file_path)));
+//            return g->builtin_types.entry_invalid;
+//        } else {
+//            add_node_error(g, node,
+//                    buf_sprintf("unable to open '%s': %s", buf_ptr(&file_path), err_str(err)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    // TODO add dependency on the file we embedded so that we know if it changes
+//    // we'll have to invalidate the cache
+//
+//    return resolve_expr_const_val_as_string_lit(g, node, &file_contents);
+//}
+//
+//static TypeTableEntry *analyze_cmpxchg(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **ptr_arg = &node->data.fn_call_expr.params.at(0);
+//    AstNode **cmp_arg = &node->data.fn_call_expr.params.at(1);
+//    AstNode **new_arg = &node->data.fn_call_expr.params.at(2);
+//    AstNode **success_order_arg = &node->data.fn_call_expr.params.at(3);
+//    AstNode **failure_order_arg = &node->data.fn_call_expr.params.at(4);
+//
+//    TypeTableEntry *ptr_type = analyze_expression(g, import, context, nullptr, *ptr_arg);
+//    if (ptr_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (ptr_type->id != TypeTableEntryIdPointer) {
+//        add_node_error(g, *ptr_arg,
+//            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&ptr_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
+//    TypeTableEntry *cmp_type = analyze_expression(g, import, context, child_type, *cmp_arg);
+//    TypeTableEntry *new_type = analyze_expression(g, import, context, child_type, *new_arg);
+//
+//    TypeTableEntry *success_order_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_atomic_order_enum, *success_order_arg);
+//    TypeTableEntry *failure_order_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_atomic_order_enum, *failure_order_arg);
+//
+//    if (cmp_type->id == TypeTableEntryIdInvalid ||
+//        new_type->id == TypeTableEntryIdInvalid ||
+//        success_order_type->id == TypeTableEntryIdInvalid ||
+//        failure_order_type->id == TypeTableEntryIdInvalid)
+//    {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *success_order_val = &get_resolved_expr(*success_order_arg)->const_val;
+//    ConstExprValue *failure_order_val = &get_resolved_expr(*failure_order_arg)->const_val;
+//    if (!success_order_val->ok) {
+//        add_node_error(g, *success_order_arg, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    } else if (!failure_order_val->ok) {
+//        add_node_error(g, *failure_order_arg, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (success_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
+//        add_node_error(g, *success_order_arg,
+//                buf_sprintf("success atomic ordering must be Monotonic or stricter"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (failure_order_val->data.x_enum.tag < AtomicOrderMonotonic) {
+//        add_node_error(g, *failure_order_arg,
+//                buf_sprintf("failure atomic ordering must be Monotonic or stricter"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (failure_order_val->data.x_enum.tag > success_order_val->data.x_enum.tag) {
+//        add_node_error(g, *failure_order_arg,
+//                buf_sprintf("failure atomic ordering must be no stricter than success"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (failure_order_val->data.x_enum.tag == AtomicOrderRelease ||
+//        failure_order_val->data.x_enum.tag == AtomicOrderAcqRel)
+//    {
+//        add_node_error(g, *failure_order_arg,
+//                buf_sprintf("failure atomic ordering must not be Release or AcqRel"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    return g->builtin_types.entry_bool;
+//}
+//
+//static TypeTableEntry *analyze_fence(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **atomic_order_arg = &node->data.fn_call_expr.params.at(0);
+//    TypeTableEntry *atomic_order_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_atomic_order_enum, *atomic_order_arg);
+//
+//    if (atomic_order_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *atomic_order_val = &get_resolved_expr(*atomic_order_arg)->const_val;
+//
+//    if (!atomic_order_val->ok) {
+//        add_node_error(g, *atomic_order_arg, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_div_exact(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
+//    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
+//    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//    AstNode *op_nodes[] = {*op1, *op2};
+//    TypeTableEntry *op_types[] = {op1_type, op2_type};
+//    TypeTableEntry *result_type = resolve_peer_type_compatibility(g, import, context, node,
+//            op_nodes, op_types, 2);
+//
+//    if (result_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (result_type->id == TypeTableEntryIdInt) {
+//        return result_type;
+//    } else if (result_type->id == TypeTableEntryIdNumLitInt) {
+//        // check for division by zero
+//        // check for non exact division
+//        zig_panic("TODO");
+//    } else {
+//        add_node_error(g, node,
+//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&result_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
+//
+//static TypeTableEntry *analyze_truncate(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    AstNode **op1 = &node->data.fn_call_expr.params.at(0);
+//    AstNode **op2 = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *dest_type = analyze_type_expr(g, import, context, *op1);
+//    TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//    if (dest_type->id == TypeTableEntryIdInvalid || src_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    } else if (dest_type->id != TypeTableEntryIdInt) {
+//        add_node_error(g, *op1,
+//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&dest_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    } else if (src_type->id != TypeTableEntryIdInt) {
+//        add_node_error(g, *op2,
+//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&src_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    } else if (src_type->data.integral.is_signed != dest_type->data.integral.is_signed) {
+//        const char *sign_str = dest_type->data.integral.is_signed ? "signed" : "unsigned";
+//        add_node_error(g, *op2,
+//                buf_sprintf("expected %s integer type, got '%s'", sign_str, buf_ptr(&src_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    } else if (src_type->data.integral.bit_count <= dest_type->data.integral.bit_count) {
+//        add_node_error(g, *op2,
+//                buf_sprintf("type '%s' has same or fewer bits than destination type '%s'",
+//                    buf_ptr(&src_type->name), buf_ptr(&dest_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    // TODO const expr eval
+//
+//    return dest_type;
+//}
+//
+//static TypeTableEntry *analyze_compile_err(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode *first_param_node = node->data.fn_call_expr.params.at(0);
+//    Buf *err_msg = resolve_const_expr_str(g, import, context, first_param_node->parent_field);
+//    if (!err_msg) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    add_node_error(g, node, err_msg);
+//
+//    return g->builtin_types.entry_invalid;
+//}
+//
+//static TypeTableEntry *analyze_int_type(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **is_signed_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **bit_count_node = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *bool_type = g->builtin_types.entry_bool;
+//    TypeTableEntry *usize_type = g->builtin_types.entry_usize;
+//    TypeTableEntry *is_signed_type = analyze_expression(g, import, context, bool_type, *is_signed_node);
+//    TypeTableEntry *bit_count_type = analyze_expression(g, import, context, usize_type, *bit_count_node);
+//
+//    if (is_signed_type->id == TypeTableEntryIdInvalid ||
+//        bit_count_type->id == TypeTableEntryIdInvalid)
+//    {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *is_signed_val = &get_resolved_expr(*is_signed_node)->const_val;
+//    ConstExprValue *bit_count_val = &get_resolved_expr(*bit_count_node)->const_val;
+//
+//    AstNode *bad_node = nullptr;
+//    if (!is_signed_val->ok) {
+//        bad_node = *is_signed_node;
+//    } else if (!bit_count_val->ok) {
+//        bad_node = *bit_count_node;
+//    }
+//    if (bad_node) {
+//        add_node_error(g, bad_node, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool depends_on_compile_var = is_signed_val->depends_on_compile_var || bit_count_val->depends_on_compile_var;
+//
+//    TypeTableEntry *int_type = get_int_type(g, is_signed_val->data.x_bool,
+//            bit_count_val->data.x_bignum.data.x_uint);
+//    return resolve_expr_const_val_as_type(g, node, int_type, depends_on_compile_var);
+//
+//}
+//
+//static TypeTableEntry *analyze_set_fn_no_inline(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
+//    if (!fn_entry) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool is_noinline;
+//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &is_noinline);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (fn_entry->fn_no_inline_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function no inline attribute set twice"));
+//        add_error_note(g, msg, fn_entry->fn_no_inline_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    fn_entry->fn_no_inline_set_node = node;
+//
+//    if (fn_entry->fn_inline == FnInlineAlways) {
+//        add_node_error(g, node, buf_sprintf("function is both inline and noinline"));
+//        fn_entry->proto_node->data.fn_proto.skip = true;
+//        return g->builtin_types.entry_invalid;
+//    } else if (is_noinline) {
+//        fn_entry->fn_inline = FnInlineNever;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_set_fn_static_eval(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
+//    if (!fn_entry) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool want_static_eval;
+//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_static_eval);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (fn_entry->fn_static_eval_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function static eval attribute set twice"));
+//        add_error_note(g, msg, fn_entry->fn_static_eval_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    fn_entry->fn_static_eval_set_node = node;
+//
+//    if (want_static_eval && !context->fn_entry->is_pure) {
+//        add_node_error(g, node, buf_sprintf("attribute appears too late within function"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (want_static_eval) {
+//        fn_entry->want_pure = WantPureTrue;
+//        fn_entry->want_pure_attr_node = node;
+//    } else {
+//        fn_entry->want_pure = WantPureFalse;
+//        fn_entry->is_pure = false;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_set_fn_visible(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node)
+//{
+//    AstNode **fn_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    FnTableEntry *fn_entry = resolve_const_expr_fn(g, import, context, fn_node);
+//    if (!fn_entry) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool want_export;
+//    bool ok = resolve_const_expr_bool(g, import, context, value_node, &want_export);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (fn_entry->fn_export_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("function visibility set twice"));
+//        add_error_note(g, msg, fn_entry->fn_export_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    fn_entry->fn_export_set_node = node;
+//
+//    AstNodeFnProto *fn_proto = &fn_entry->proto_node->data.fn_proto;
+//    if (fn_proto->top_level_decl.visib_mod != VisibModExport) {
+//        ErrorMsg *msg = add_node_error(g, node,
+//            buf_sprintf("function must be marked export to set function visibility"));
+//        add_error_note(g, msg, fn_entry->proto_node, buf_sprintf("function declared here"));
+//        return g->builtin_types.entry_void;
+//    }
+//    if (!want_export) {
+//        fn_proto->top_level_decl.visib_mod = VisibModPub;
+//    }
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_set_debug_safety(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *parent_context, AstNode *node)
+//{
+//    AstNode **target_node = &node->data.fn_call_expr.params.at(0);
+//    AstNode **value_node = &node->data.fn_call_expr.params.at(1);
+//
+//    TypeTableEntry *target_type = analyze_expression(g, import, parent_context, nullptr, *target_node);
+//    BlockContext *target_context;
+//    ConstExprValue *const_val = &get_resolved_expr(*target_node)->const_val;
+//    if (target_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (!const_val->ok) {
+//        add_node_error(g, *target_node, buf_sprintf("unable to evaluate constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    if (target_type->id == TypeTableEntryIdBlock) {
+//        target_context = const_val->data.x_block;
+//    } else if (target_type->id == TypeTableEntryIdFn) {
+//        target_context = const_val->data.x_fn->fn_def_node->data.fn_def.block_context;
+//    } else if (target_type->id == TypeTableEntryIdMetaType) {
+//        TypeTableEntry *type_arg = const_val->data.x_type;
+//        if (type_arg->id == TypeTableEntryIdStruct) {
+//            target_context = type_arg->data.structure.block_context;
+//        } else if (type_arg->id == TypeTableEntryIdEnum) {
+//            target_context = type_arg->data.enumeration.block_context;
+//        } else if (type_arg->id == TypeTableEntryIdUnion) {
+//            target_context = type_arg->data.unionation.block_context;
+//        } else {
+//            add_node_error(g, *target_node,
+//                buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&type_arg->name)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    } else {
+//        add_node_error(g, *target_node,
+//            buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&target_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    bool want_debug_safety;
+//    bool ok = resolve_const_expr_bool(g, import, parent_context, value_node, &want_debug_safety);
+//    if (!ok) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (target_context->safety_set_node) {
+//        ErrorMsg *msg = add_node_error(g, node, buf_sprintf("debug safety for scope set twice"));
+//        add_error_note(g, msg, target_context->safety_set_node, buf_sprintf("first set here"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//    target_context->safety_set_node = node;
+//
+//    target_context->safety_off = !want_debug_safety;
+//
+//    return g->builtin_types.entry_void;
+//}
 
-        if (old_instruction->ref_count == 0 && !ir_has_side_effects(old_instruction)) {
-            ira->instruction_index += 1;
-            continue;
-        }
 
-        TypeTableEntry *return_type = ir_analyze_instruction(ira, old_instruction);
+//static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//
+//    switch (builtin_fn->id) {
+//        case BuiltinFnIdInvalid:
+//            zig_unreachable();
+//        case BuiltinFnIdAddWithOverflow:
+//        case BuiltinFnIdSubWithOverflow:
+//        case BuiltinFnIdMulWithOverflow:
+//        case BuiltinFnIdShlWithOverflow:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
+//                if (int_type->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_bool;
+//                } else if (int_type->id == TypeTableEntryIdInt) {
+//                    AstNode *op1_node = node->data.fn_call_expr.params.at(1);
+//                    AstNode *op2_node = node->data.fn_call_expr.params.at(2);
+//                    AstNode *result_node = node->data.fn_call_expr.params.at(3);
+//
+//                    analyze_expression(g, import, context, int_type, op1_node);
+//                    analyze_expression(g, import, context, int_type, op2_node);
+//                    analyze_expression(g, import, context, get_pointer_to_type(g, int_type, false),
+//                            result_node);
+//                } else {
+//                    add_node_error(g, type_node,
+//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
+//                }
+//
+//                // TODO constant expression evaluation
+//
+//                return g->builtin_types.entry_bool;
+//            }
+//        case BuiltinFnIdMemcpy:
+//            {
+//                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
+//                AstNode *src_node = node->data.fn_call_expr.params.at(1);
+//                AstNode *len_node = node->data.fn_call_expr.params.at(2);
+//                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
+//                TypeTableEntry *src_type = analyze_expression(g, import, context, nullptr, src_node);
+//                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
+//
+//                if (dest_type->id != TypeTableEntryIdInvalid &&
+//                    dest_type->id != TypeTableEntryIdPointer)
+//                {
+//                    add_node_error(g, dest_node,
+//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
+//                }
+//
+//                if (src_type->id != TypeTableEntryIdInvalid &&
+//                    src_type->id != TypeTableEntryIdPointer)
+//                {
+//                    add_node_error(g, src_node,
+//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&src_type->name)));
+//                }
+//
+//                if (dest_type->id == TypeTableEntryIdPointer &&
+//                    src_type->id == TypeTableEntryIdPointer)
+//                {
+//                    uint64_t dest_align = get_memcpy_align(g, dest_type->data.pointer.child_type);
+//                    uint64_t src_align = get_memcpy_align(g, src_type->data.pointer.child_type);
+//                    if (dest_align != src_align) {
+//                        add_node_error(g, dest_node, buf_sprintf(
+//                            "misaligned memcpy, '%s' has alignment '%" PRIu64 ", '%s' has alignment %" PRIu64,
+//                                    buf_ptr(&dest_type->name), dest_align,
+//                                    buf_ptr(&src_type->name), src_align));
+//                    }
+//                }
+//
+//                return builtin_fn->return_type;
+//            }
+//        case BuiltinFnIdMemset:
+//            {
+//                AstNode *dest_node = node->data.fn_call_expr.params.at(0);
+//                AstNode *char_node = node->data.fn_call_expr.params.at(1);
+//                AstNode *len_node = node->data.fn_call_expr.params.at(2);
+//                TypeTableEntry *dest_type = analyze_expression(g, import, context, nullptr, dest_node);
+//                analyze_expression(g, import, context, builtin_fn->param_types[1], char_node);
+//                analyze_expression(g, import, context, builtin_fn->param_types[2], len_node);
+//
+//                if (dest_type->id != TypeTableEntryIdInvalid &&
+//                    dest_type->id != TypeTableEntryIdPointer)
+//                {
+//                    add_node_error(g, dest_node,
+//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
+//                }
+//
+//                return builtin_fn->return_type;
+//            }
+//        case BuiltinFnIdSizeof:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//                if (type_entry->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_invalid;
+//                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
+//                    add_node_error(g, first_executing_node(type_node),
+//                            buf_sprintf("no size available for type '%s'", buf_ptr(&type_entry->name)));
+//                    return g->builtin_types.entry_invalid;
+//                } else {
+//                    uint64_t size_in_bytes = type_size(g, type_entry);
+//                    bool depends_on_compile_var = (type_entry == g->builtin_types.entry_usize ||
+//                            type_entry == g->builtin_types.entry_isize);
+//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                            size_in_bytes, depends_on_compile_var);
+//                }
+//            }
+//        case BuiltinFnIdAlignof:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//                if (type_entry->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_invalid;
+//                } else if (type_entry->id == TypeTableEntryIdUnreachable) {
+//                    add_node_error(g, first_executing_node(type_node),
+//                            buf_sprintf("no align available for type '%s'", buf_ptr(&type_entry->name)));
+//                    return g->builtin_types.entry_invalid;
+//                } else {
+//                    uint64_t align_in_bytes = LLVMABISizeOfType(g->target_data_ref, type_entry->type_ref);
+//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                            align_in_bytes, false);
+//                }
+//            }
+//        case BuiltinFnIdMaxValue:
+//            return analyze_min_max_value(g, import, context, node,
+//                    "no max value available for type '%s'", true);
+//        case BuiltinFnIdMinValue:
+//            return analyze_min_max_value(g, import, context, node,
+//                    "no min value available for type '%s'", false);
+//        case BuiltinFnIdMemberCount:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *type_entry = analyze_type_expr(g, import, context, type_node);
+//
+//                if (type_entry->id == TypeTableEntryIdInvalid) {
+//                    return type_entry;
+//                } else if (type_entry->id == TypeTableEntryIdEnum) {
+//                    uint64_t value_count = type_entry->data.enumeration.src_field_count;
+//                    return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                            value_count, false);
+//                } else {
+//                    add_node_error(g, node,
+//                            buf_sprintf("no value count available for type '%s'", buf_ptr(&type_entry->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case BuiltinFnIdCInclude:
+//            {
+//                if (!context->c_import_buf) {
+//                    add_node_error(g, node, buf_sprintf("@c_include valid only in c_import blocks"));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
+//                TypeTableEntry *str_type = get_slice_type(g, g->builtin_types.entry_u8, true);
+//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, str_type, *str_node);
+//
+//                if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                    return resolved_type;
+//                }
+//
+//                ConstExprValue *const_str_val = &get_resolved_expr(*str_node)->const_val;
+//
+//                if (!const_str_val->ok) {
+//                    add_node_error(g, *str_node, buf_sprintf("@c_include requires constant expression"));
+//                    return g->builtin_types.entry_void;
+//                }
+//
+//                buf_appendf(context->c_import_buf, "#include <");
+//                ConstExprValue *ptr_field = const_str_val->data.x_struct.fields[0];
+//                uint64_t len = ptr_field->data.x_ptr.len;
+//                for (uint64_t i = 0; i < len; i += 1) {
+//                    ConstExprValue *char_val = ptr_field->data.x_ptr.ptr[i];
+//                    uint64_t big_c = char_val->data.x_bignum.data.x_uint;
+//                    assert(big_c <= UINT8_MAX);
+//                    uint8_t c = big_c;
+//                    buf_append_char(context->c_import_buf, c);
+//                }
+//                buf_appendf(context->c_import_buf, ">\n");
+//
+//                return g->builtin_types.entry_void;
+//            }
+//        case BuiltinFnIdCDefine:
+//            zig_panic("TODO");
+//        case BuiltinFnIdCUndef:
+//            zig_panic("TODO");
+//
+//        case BuiltinFnIdCompileVar:
+//            {
+//                AstNode **str_node = node->data.fn_call_expr.params.at(0)->parent_field;
+//
+//                Buf *var_name = resolve_const_expr_str(g, import, context, str_node);
+//                if (!var_name) {
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//                const_val->ok = true;
+//                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, true);
+//                } else if (buf_eql_str(var_name, "is_release")) {
+//                    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, true);
+//                } else if (buf_eql_str(var_name, "os")) {
+//                    const_val->data.x_enum.tag = g->target_os_index;
+//                    return g->builtin_types.entry_os_enum;
+//                } else if (buf_eql_str(var_name, "arch")) {
+//                    const_val->data.x_enum.tag = g->target_arch_index;
+//                    return g->builtin_types.entry_arch_enum;
+//                } else if (buf_eql_str(var_name, "environ")) {
+//                    const_val->data.x_enum.tag = g->target_environ_index;
+//                    return g->builtin_types.entry_environ_enum;
+//                } else if (buf_eql_str(var_name, "object_format")) {
+//                    const_val->data.x_enum.tag = g->target_oformat_index;
+//                    return g->builtin_types.entry_oformat_enum;
+//                } else {
+//                    add_node_error(g, *str_node,
+//                        buf_sprintf("unrecognized compile variable: '%s'", buf_ptr(var_name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case BuiltinFnIdConstEval:
+//            {
+//                AstNode **expr_node = node->data.fn_call_expr.params.at(0)->parent_field;
+//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_type, *expr_node);
+//                if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                    return resolved_type;
+//                }
+//
+//                ConstExprValue *const_expr_val = &get_resolved_expr(*expr_node)->const_val;
+//
+//                if (!const_expr_val->ok) {
+//                    add_node_error(g, *expr_node, buf_sprintf("unable to evaluate constant expression"));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//                *const_val = *const_expr_val;
+//
+//                return resolved_type;
+//            }
+//        case BuiltinFnIdCtz:
+//        case BuiltinFnIdClz:
+//            {
+//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
+//                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
+//                if (int_type->id == TypeTableEntryIdInvalid) {
+//                    return int_type;
+//                } else if (int_type->id == TypeTableEntryIdInt) {
+//                    AstNode **expr_node = node->data.fn_call_expr.params.at(1)->parent_field;
+//                    TypeTableEntry *resolved_type = analyze_expression(g, import, context, int_type, *expr_node);
+//                    if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                        return resolved_type;
+//                    }
+//
+//                    // TODO const expr eval
+//
+//                    return resolved_type;
+//                } else {
+//                    add_node_error(g, type_node,
+//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case BuiltinFnIdImport:
+//            return analyze_import(g, import, context, node);
+//        case BuiltinFnIdCImport:
+//            return analyze_c_import(g, import, context, node);
+//        case BuiltinFnIdErrName:
+//            return analyze_err_name(g, import, context, node);
+//        case BuiltinFnIdBreakpoint:
+//            mark_impure_fn(g, context, node);
+//            return g->builtin_types.entry_void;
+//        case BuiltinFnIdReturnAddress:
+//        case BuiltinFnIdFrameAddress:
+//            mark_impure_fn(g, context, node);
+//            return builtin_fn->return_type;
+//        case BuiltinFnIdEmbedFile:
+//            return analyze_embed_file(g, import, context, node);
+//        case BuiltinFnIdCmpExchange:
+//            return analyze_cmpxchg(g, import, context, node);
+//        case BuiltinFnIdFence:
+//            return analyze_fence(g, import, context, node);
+//        case BuiltinFnIdDivExact:
+//            return analyze_div_exact(g, import, context, node);
+//        case BuiltinFnIdTruncate:
+//            return analyze_truncate(g, import, context, node);
+//        case BuiltinFnIdCompileErr:
+//            return analyze_compile_err(g, import, context, node);
+//        case BuiltinFnIdIntType:
+//            return analyze_int_type(g, import, context, node);
+//        case BuiltinFnIdSetFnTest:
+//            return analyze_set_fn_test(g, import, context, node);
+//        case BuiltinFnIdSetFnNoInline:
+//            return analyze_set_fn_no_inline(g, import, context, node);
+//        case BuiltinFnIdSetFnStaticEval:
+//            return analyze_set_fn_static_eval(g, import, context, node);
+//        case BuiltinFnIdSetFnVisible:
+//            return analyze_set_fn_visible(g, import, context, node);
+//        case BuiltinFnIdSetDebugSafety:
+//            return analyze_set_debug_safety(g, import, context, node);
+//    }
+//    zig_unreachable();
+//}
 
-        // unreachable instructions do their own control flow.
-        if (return_type->id == TypeTableEntryIdUnreachable)
-            continue;
+//static TypeTableEntry *analyze_container_init_expr(CodeGen *g, ImportTableEntry *import,
+//    BlockContext *context, AstNode *node)
+//{
+//    assert(node->type == NodeTypeContainerInitExpr);
+//
+//    AstNodeContainerInitExpr *container_init_expr = &node->data.container_init_expr;
+//
+//    ContainerInitKind kind = container_init_expr->kind;
+//
+//    if (container_init_expr->type->type == NodeTypeFieldAccessExpr) {
+//        container_init_expr->type->data.field_access_expr.container_init_expr_node = node;
+//    }
+//
+//    TypeTableEntry *container_meta_type = analyze_expression(g, import, context, nullptr,
+//            container_init_expr->type);
+//
+//    if (container_meta_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (node->data.container_init_expr.enum_type) {
+//        get_resolved_expr(node)->const_val = get_resolved_expr(container_init_expr->type)->const_val;
+//        return node->data.container_init_expr.enum_type;
+//    }
+//
+//    TypeTableEntry *container_type = resolve_type(g, container_init_expr->type);
+//
+//    if (container_type->id == TypeTableEntryIdInvalid) {
+//        return container_type;
+//    } else if (container_type->id == TypeTableEntryIdStruct &&
+//               !container_type->data.structure.is_slice &&
+//               (kind == ContainerInitKindStruct || (kind == ContainerInitKindArray &&
+//                                                    container_init_expr->entries.length == 0)))
+//    {
+//        StructValExprCodeGen *codegen = &container_init_expr->resolved_struct_val_expr;
+//        codegen->type_entry = container_type;
+//        codegen->source_node = node;
+//
+//
+//        size_t expr_field_count = container_init_expr->entries.length;
+//        size_t actual_field_count = container_type->data.structure.src_field_count;
+//
+//        AstNode *non_const_expr_culprit = nullptr;
+//
+//        size_t *field_use_counts = allocate<size_t>(actual_field_count);
+//        ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//        const_val->ok = true;
+//        const_val->data.x_struct.fields = allocate<ConstExprValue*>(actual_field_count);
+//        for (size_t i = 0; i < expr_field_count; i += 1) {
+//            AstNode *val_field_node = container_init_expr->entries.at(i);
+//            assert(val_field_node->type == NodeTypeStructValueField);
+//
+//            val_field_node->block_context = context;
+//
+//            TypeStructField *type_field = find_struct_type_field(container_type,
+//                    val_field_node->data.struct_val_field.name);
+//
+//            if (!type_field) {
+//                add_node_error(g, val_field_node,
+//                    buf_sprintf("no member named '%s' in '%s'",
+//                        buf_ptr(val_field_node->data.struct_val_field.name), buf_ptr(&container_type->name)));
+//                continue;
+//            }
+//
+//            if (type_field->type_entry->id == TypeTableEntryIdInvalid) {
+//                return g->builtin_types.entry_invalid;
+//            }
+//
+//            size_t field_index = type_field->src_index;
+//            field_use_counts[field_index] += 1;
+//            if (field_use_counts[field_index] > 1) {
+//                add_node_error(g, val_field_node, buf_sprintf("duplicate field"));
+//                continue;
+//            }
+//
+//            val_field_node->data.struct_val_field.type_struct_field = type_field;
+//
+//            analyze_expression(g, import, context, type_field->type_entry,
+//                    val_field_node->data.struct_val_field.expr);
+//
+//            if (const_val->ok) {
+//                ConstExprValue *field_val =
+//                    &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;
+//                    non_const_expr_culprit = val_field_node->data.struct_val_field.expr;
+//                }
+//            }
+//        }
+//        if (!const_val->ok) {
+//            assert(non_const_expr_culprit);
+//            if (context->fn_entry) {
+//                context->fn_entry->struct_val_expr_alloca_list.append(codegen);
+//            } else {
+//                add_node_error(g, non_const_expr_culprit, buf_sprintf("unable to evaluate constant expression"));
+//            }
+//        }
+//
+//        for (size_t i = 0; i < actual_field_count; i += 1) {
+//            if (field_use_counts[i] == 0) {
+//                add_node_error(g, node,
+//                    buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i].name)));
+//            }
+//        }
+//        return container_type;
+//    } else if (container_type->id == TypeTableEntryIdStruct &&
+//               container_type->data.structure.is_slice &&
+//               kind == ContainerInitKindArray)
+//    {
+//        size_t elem_count = container_init_expr->entries.length;
+//
+//        TypeTableEntry *pointer_type = container_type->data.structure.fields[0].type_entry;
+//        assert(pointer_type->id == TypeTableEntryIdPointer);
+//        TypeTableEntry *child_type = pointer_type->data.pointer.child_type;
+//
+//        ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//        const_val->ok = true;
+//        const_val->data.x_array.fields = allocate<ConstExprValue*>(elem_count);
+//
+//        for (size_t i = 0; i < elem_count; i += 1) {
+//            AstNode **elem_node = &container_init_expr->entries.at(i);
+//            analyze_expression(g, import, context, child_type, *elem_node);
+//
+//            if (const_val->ok) {
+//                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;
+//                }
+//            }
+//        }
+//
+//        TypeTableEntry *fixed_size_array_type = get_array_type(g, child_type, elem_count);
+//
+//        StructValExprCodeGen *codegen = &container_init_expr->resolved_struct_val_expr;
+//        codegen->type_entry = fixed_size_array_type;
+//        codegen->source_node = node;
+//        if (!const_val->ok) {
+//            if (!context->fn_entry) {
+//                add_node_error(g, node,
+//                    buf_sprintf("unable to evaluate constant expression"));
+//            } else {
+//                context->fn_entry->struct_val_expr_alloca_list.append(codegen);
+//            }
+//        }
+//
+//        return fixed_size_array_type;
+//    } else if (container_type->id == TypeTableEntryIdArray) {
+//        zig_panic("TODO array container init");
+//        return container_type;
+//    } else if (container_type->id == TypeTableEntryIdVoid) {
+//        if (container_init_expr->entries.length != 0) {
+//            add_node_error(g, node, buf_sprintf("void expression expects no arguments"));
+//            return g->builtin_types.entry_invalid;
+//        } else {
+//            return resolve_expr_const_val_as_void(g, node);
+//        }
+//    } else {
+//        add_node_error(g, node,
+//            buf_sprintf("type '%s' does not support %s initialization syntax",
+//                buf_ptr(&container_type->name), err_container_init_syntax_name(kind)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
 
-        ira->instruction_index += 1;
-    }
 
-    return ir_resolve_peer_types(ira, expected_type_source_node, ira->implicit_return_type_list.items,
-            ira->implicit_return_type_list.length);
-}
 
-static bool ir_builtin_call_has_side_effects(IrInstructionBuiltinCall *call_instruction) {
-    switch (call_instruction->fn->id) {
-        case BuiltinFnIdInvalid:
-            zig_unreachable();
-        case BuiltinFnIdMemcpy:
-        case BuiltinFnIdMemset:
-        case BuiltinFnIdAddWithOverflow:
-        case BuiltinFnIdSubWithOverflow:
-        case BuiltinFnIdMulWithOverflow:
-        case BuiltinFnIdShlWithOverflow:
-        case BuiltinFnIdCInclude:
-        case BuiltinFnIdCDefine:
-        case BuiltinFnIdCUndef:
-        case BuiltinFnIdImport:
-        case BuiltinFnIdCImport:
-        case BuiltinFnIdBreakpoint:
-        case BuiltinFnIdEmbedFile:
-        case BuiltinFnIdCmpExchange:
-        case BuiltinFnIdFence:
-        case BuiltinFnIdUnreachable:
-        case BuiltinFnIdSetFnTest:
-        case BuiltinFnIdSetFnVisible:
-        case BuiltinFnIdSetFnStaticEval:
-        case BuiltinFnIdSetFnNoInline:
-        case BuiltinFnIdSetDebugSafety:
-            return true;
-        case BuiltinFnIdSizeof:
-        case BuiltinFnIdAlignof:
-        case BuiltinFnIdMaxValue:
-        case BuiltinFnIdMinValue:
-        case BuiltinFnIdMemberCount:
-        case BuiltinFnIdTypeof:
-        case BuiltinFnIdCompileVar:
-        case BuiltinFnIdCompileErr:
-        case BuiltinFnIdConstEval:
-        case BuiltinFnIdCtz:
-        case BuiltinFnIdClz:
-        case BuiltinFnIdErrName:
-        case BuiltinFnIdReturnAddress:
-        case BuiltinFnIdFrameAddress:
-        case BuiltinFnIdDivExact:
-        case BuiltinFnIdTruncate:
-        case BuiltinFnIdIntType:
-            return false;
-    }
-    zig_unreachable();
-}
+//static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeFieldAccessExpr);
+//
+//    AstNode **struct_expr_node = &node->data.field_access_expr.struct_expr;
+//    TypeTableEntry *struct_type = analyze_expression(g, import, context, nullptr, *struct_expr_node);
+//    Buf *field_name = node->data.field_access_expr.field_name;
+//
+//    if (struct_type->id == TypeTableEntryIdInvalid) {
+//        return struct_type;
+//    } else if (is_container_ref(struct_type)) {
+//        return analyze_container_member_access(g, field_name, node, struct_type);
+//    } else if (struct_type->id == TypeTableEntryIdArray) {
+//        if (buf_eql_str(field_name, "len")) {
+//            return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                    struct_type->data.array.len, false);
+//        } else {
+//            add_node_error(g, node,
+//                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
+//                    buf_ptr(&struct_type->name)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    } else if (struct_type->id == TypeTableEntryIdMetaType) {
+//        TypeTableEntry *child_type = resolve_type(g, *struct_expr_node);
+//
+//        if (child_type->id == TypeTableEntryIdInvalid) {
+//            return g->builtin_types.entry_invalid;
+//        } else if (child_type->id == TypeTableEntryIdEnum) {
+//            AstNode *container_init_node = node->data.field_access_expr.container_init_expr_node;
+//            AstNode *value_node;
+//            if (container_init_node) {
+//                assert(container_init_node->type == NodeTypeContainerInitExpr);
+//                size_t param_count = container_init_node->data.container_init_expr.entries.length;
+//                if (param_count > 1) {
+//                    AstNode *first_invalid_node = container_init_node->data.container_init_expr.entries.at(1);
+//                    add_node_error(g, first_executing_node(first_invalid_node),
+//                            buf_sprintf("enum values accept only one parameter"));
+//                    return child_type;
+//                } else {
+//                    if (param_count == 1) {
+//                        value_node = container_init_node->data.container_init_expr.entries.at(0);
+//                    } else {
+//                        value_node = nullptr;
+//                    }
+//                    container_init_node->data.container_init_expr.enum_type = child_type;
+//                }
+//            } else {
+//                value_node = nullptr;
+//            }
+//            return analyze_enum_value_expr(g, import, context, node, value_node, child_type, field_name, node);
+//        } else if (child_type->id == TypeTableEntryIdStruct) {
+//            BlockContext *container_block_context = get_container_block_context(child_type);
+//            auto entry = container_block_context->decl_table.maybe_get(field_name);
+//            AstNode *decl_node = entry ? entry->value : nullptr;
+//            if (decl_node) {
+//                bool pointer_only = false;
+//                return analyze_decl_ref(g, node, decl_node, pointer_only, context, false);
+//            } else {
+//                add_node_error(g, node,
+//                    buf_sprintf("container '%s' has no member called '%s'",
+//                        buf_ptr(&child_type->name), buf_ptr(field_name)));
+//                return g->builtin_types.entry_invalid;
+//            }
+//        } else if (child_type->id == TypeTableEntryIdPureError) {
+//            return analyze_error_literal_expr(g, import, context, node, field_name);
+//        } else if (child_type->id == TypeTableEntryIdInt) {
+//            bool depends_on_compile_var =
+//                get_resolved_expr(*struct_expr_node)->const_val.depends_on_compile_var;
+//            if (buf_eql_str(field_name, "bit_count")) {
+//                return resolve_expr_const_val_as_unsigned_num_lit(g, node, expected_type,
+//                        child_type->data.integral.bit_count, depends_on_compile_var);
+//            } else if (buf_eql_str(field_name, "is_signed")) {
+//                return resolve_expr_const_val_as_bool(g, node, child_type->data.integral.is_signed,
+//                        depends_on_compile_var);
+//            } else {
+//                add_node_error(g, node,
+//                    buf_sprintf("type '%s' has no member called '%s'",
+//                        buf_ptr(&child_type->name), buf_ptr(field_name)));
+//                return g->builtin_types.entry_invalid;
+//            }
+//        } else {
+//            add_node_error(g, node,
+//                buf_sprintf("type '%s' does not support field access", buf_ptr(&struct_type->name)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    } else if (struct_type->id == TypeTableEntryIdNamespace) {
+//        ConstExprValue *const_val = &get_resolved_expr(*struct_expr_node)->const_val;
+//        assert(const_val->ok);
+//        ImportTableEntry *namespace_import = const_val->data.x_import;
+//        AstNode *decl_node = find_decl(namespace_import->block_context, field_name);
+//        if (!decl_node) {
+//            // we must now resolve all the use decls
+//            for (size_t i = 0; i < namespace_import->use_decls.length; i += 1) {
+//                AstNode *use_decl_node = namespace_import->use_decls.at(i);
+//                if (!get_resolved_expr(use_decl_node->data.use.expr)->type_entry) {
+//                    preview_use_decl(g, use_decl_node);
+//                }
+//                resolve_use_decl(g, use_decl_node);
+//            }
+//            decl_node = find_decl(namespace_import->block_context, field_name);
+//        }
+//        if (decl_node) {
+//            TopLevelDecl *tld = get_as_top_level_decl(decl_node);
+//            if (tld->visib_mod == VisibModPrivate && decl_node->owner != import) {
+//                ErrorMsg *msg = add_node_error(g, node,
+//                    buf_sprintf("'%s' is private", buf_ptr(field_name)));
+//                add_error_note(g, msg, decl_node, buf_sprintf("declared here"));
+//            }
+//            bool pointer_only = false;
+//            return analyze_decl_ref(g, node, decl_node, pointer_only, context,
+//                    const_val->depends_on_compile_var);
+//        } else {
+//            const char *import_name = namespace_import->path ? buf_ptr(namespace_import->path) : "(C import)";
+//            add_node_error(g, node,
+//                buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), import_name));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    } else {
+//        add_node_error(g, node,
+//            buf_sprintf("type '%s' does not support field access", buf_ptr(&struct_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
+//
 
-bool ir_has_side_effects(IrInstruction *instruction) {
-    switch (instruction->id) {
-        case IrInstructionIdInvalid:
-            zig_unreachable();
-        case IrInstructionIdBr:
-        case IrInstructionIdCondBr:
-        case IrInstructionIdSwitchBr:
-        case IrInstructionIdDeclVar:
-        case IrInstructionIdStorePtr:
-        case IrInstructionIdCall:
-        case IrInstructionIdReturn:
-        case IrInstructionIdUnreachable:
-            return true;
-        case IrInstructionIdPhi:
-        case IrInstructionIdUnOp:
-        case IrInstructionIdBinOp:
-        case IrInstructionIdLoadPtr:
-        case IrInstructionIdConst:
-        case IrInstructionIdCast:
-        case IrInstructionIdContainerInitList:
-        case IrInstructionIdContainerInitFields:
-        case IrInstructionIdFieldPtr:
-        case IrInstructionIdElemPtr:
-        case IrInstructionIdVarPtr:
-        case IrInstructionIdTypeOf:
-        case IrInstructionIdToPtrType:
-        case IrInstructionIdPtrTypeChild:
-        case IrInstructionIdReadField:
-        case IrInstructionIdStructFieldPtr:
-            return false;
-        case IrInstructionIdBuiltinCall:
-            return ir_builtin_call_has_side_effects((IrInstructionBuiltinCall *)instruction);
-    }
-    zig_unreachable();
-}
+//static TypeTableEntry *analyze_lvalue(CodeGen *g, ImportTableEntry *import, BlockContext *block_context,
+//        AstNode *lhs_node, LValPurpose purpose, bool is_ptr_const)
+//{
+//    TypeTableEntry *expected_rhs_type = nullptr;
+//    lhs_node->block_context = block_context;
+//    if (lhs_node->type == NodeTypeSymbol) {
+//        bool pointer_only = purpose == LValPurposeAddressOf;
+//        expected_rhs_type = analyze_symbol_expr(g, import, block_context, nullptr, lhs_node, pointer_only);
+//        if (expected_rhs_type->id == TypeTableEntryIdInvalid) {
+//            return g->builtin_types.entry_invalid;
+//        }
+//        if (purpose != LValPurposeAddressOf) {
+//            Buf *name = lhs_node->data.symbol_expr.symbol;
+//            VariableTableEntry *var = find_variable(g, block_context, name);
+//            if (var) {
+//                if (var->src_is_const) {
+//                    add_node_error(g, lhs_node, buf_sprintf("cannot assign to constant"));
+//                    expected_rhs_type = g->builtin_types.entry_invalid;
+//                } else {
+//                    expected_rhs_type = var->type;
+//                    get_resolved_expr(lhs_node)->variable = var;
+//                }
+//            } else {
+//                add_node_error(g, lhs_node,
+//                        buf_sprintf("use of undeclared identifier '%s'", buf_ptr(name)));
+//                expected_rhs_type = g->builtin_types.entry_invalid;
+//            }
+//        }
+//    } else if (lhs_node->type == NodeTypeArrayAccessExpr) {
+//        expected_rhs_type = analyze_array_access_expr(g, import, block_context, lhs_node, purpose);
+//    } else if (lhs_node->type == NodeTypeFieldAccessExpr) {
+//        expected_rhs_type = analyze_field_access_expr(g, import, block_context, nullptr, lhs_node);
+//    } else if (lhs_node->type == NodeTypePrefixOpExpr &&
+//            lhs_node->data.prefix_op_expr.prefix_op == PrefixOpDereference)
+//    {
+//        assert(purpose == LValPurposeAssign);
+//        AstNode *target_node = lhs_node->data.prefix_op_expr.primary_expr;
+//        TypeTableEntry *type_entry = analyze_expression(g, import, block_context, nullptr, target_node);
+//        if (type_entry->id == TypeTableEntryIdInvalid) {
+//            expected_rhs_type = type_entry;
+//        } else if (type_entry->id == TypeTableEntryIdPointer) {
+//            expected_rhs_type = type_entry->data.pointer.child_type;
+//        } else {
+//            add_node_error(g, target_node,
+//                buf_sprintf("indirection requires pointer operand ('%s' invalid)",
+//                    buf_ptr(&type_entry->name)));
+//            expected_rhs_type = g->builtin_types.entry_invalid;
+//        }
+//    } else {
+//        if (purpose == LValPurposeAssign) {
+//            add_node_error(g, lhs_node, buf_sprintf("invalid assignment target"));
+//            expected_rhs_type = g->builtin_types.entry_invalid;
+//        } else if (purpose == LValPurposeAddressOf) {
+//            TypeTableEntry *type_entry = analyze_expression(g, import, block_context, nullptr, lhs_node);
+//            if (type_entry->id == TypeTableEntryIdInvalid) {
+//                expected_rhs_type = g->builtin_types.entry_invalid;
+//            } else if (type_entry->id == TypeTableEntryIdMetaType) {
+//                expected_rhs_type = type_entry;
+//            } else {
+//                add_node_error(g, lhs_node, buf_sprintf("invalid addressof target"));
+//                expected_rhs_type = g->builtin_types.entry_invalid;
+//            }
+//        }
+//    }
+//    assert(expected_rhs_type);
+//    return expected_rhs_type;
+//}
 
-IrInstruction *ir_exec_const_result(IrExecutable *exec) {
-    if (exec->basic_block_list.length != 1)
-        return nullptr;
 
-    IrBasicBlock *bb = exec->basic_block_list.at(0);
-    if (bb->instruction_list.length != 1)
-        return nullptr;
 
-    IrInstruction *only_inst = bb->instruction_list.at(0);
-    if (only_inst->id != IrInstructionIdReturn)
-        return nullptr;
+//static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeBinOpExpr);
+//    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
+//    switch (bin_op_type) {
+//        case BinOpTypeAssign:
+//        case BinOpTypeAssignTimes:
+//        case BinOpTypeAssignTimesWrap:
+//        case BinOpTypeAssignDiv:
+//        case BinOpTypeAssignMod:
+//        case BinOpTypeAssignPlus:
+//        case BinOpTypeAssignPlusWrap:
+//        case BinOpTypeAssignMinus:
+//        case BinOpTypeAssignMinusWrap:
+//        case BinOpTypeAssignBitShiftLeft:
+//        case BinOpTypeAssignBitShiftLeftWrap:
+//        case BinOpTypeAssignBitShiftRight:
+//        case BinOpTypeAssignBitAnd:
+//        case BinOpTypeAssignBitXor:
+//        case BinOpTypeAssignBitOr:
+//        case BinOpTypeAssignBoolAnd:
+//        case BinOpTypeAssignBoolOr:
+//            {
+//                AstNode *lhs_node = node->data.bin_op_expr.op1;
+//
+//                TypeTableEntry *expected_rhs_type = analyze_lvalue(g, import, context, lhs_node,
+//                        LValPurposeAssign, false);
+//                if (expected_rhs_type->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_invalid;
+//                } else if (!is_op_allowed(expected_rhs_type, node->data.bin_op_expr.bin_op)) {
+//                    if (expected_rhs_type->id != TypeTableEntryIdInvalid) {
+//                        add_node_error(g, lhs_node,
+//                            buf_sprintf("operator not allowed for type '%s'",
+//                                buf_ptr(&expected_rhs_type->name)));
+//                    }
+//                }
+//
+//                analyze_expression(g, import, context, expected_rhs_type, node->data.bin_op_expr.op2);
+//                // not const ok because expression has side effects
+//                return g->builtin_types.entry_void;
+//            }
+//        case BinOpTypeBoolOr:
+//        case BinOpTypeBoolAnd:
+//            return analyze_logic_bin_op_expr(g, import, context, node);
+//        case BinOpTypeCmpEq:
+//        case BinOpTypeCmpNotEq:
+//        case BinOpTypeCmpLessThan:
+//        case BinOpTypeCmpGreaterThan:
+//        case BinOpTypeCmpLessOrEq:
+//        case BinOpTypeCmpGreaterOrEq:
+//            return analyze_bool_bin_op_expr(g, import, context, node);
+//        case BinOpTypeBinOr:
+//        case BinOpTypeBinXor:
+//        case BinOpTypeBinAnd:
+//        case BinOpTypeBitShiftLeft:
+//        case BinOpTypeBitShiftLeftWrap:
+//        case BinOpTypeBitShiftRight:
+//        case BinOpTypeAdd:
+//        case BinOpTypeAddWrap:
+//        case BinOpTypeSub:
+//        case BinOpTypeSubWrap:
+//        case BinOpTypeMult:
+//        case BinOpTypeMultWrap:
+//        case BinOpTypeDiv:
+//        case BinOpTypeMod:
+//            {
+//                AstNode **op1 = node->data.bin_op_expr.op1->parent_field;
+//                AstNode **op2 = node->data.bin_op_expr.op2->parent_field;
+//                TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, *op1);
+//                TypeTableEntry *rhs_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//                AstNode *op_nodes[] = {*op1, *op2};
+//                TypeTableEntry *op_types[] = {lhs_type, rhs_type};
+//
+//                TypeTableEntry *resolved_type = resolve_peer_type_compatibility(g, import, context, node,
+//                        op_nodes, op_types, 2);
+//
+//                if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                    return resolved_type;
+//                }
+//
+//                if (resolved_type->id == TypeTableEntryIdInt ||
+//                    resolved_type->id == TypeTableEntryIdNumLitInt)
+//                {
+//                    // int
+//                } else if ((resolved_type->id == TypeTableEntryIdFloat ||
+//                           resolved_type->id == TypeTableEntryIdNumLitFloat) &&
+//                    (bin_op_type == BinOpTypeAdd ||
+//                     bin_op_type == BinOpTypeSub ||
+//                     bin_op_type == BinOpTypeMult ||
+//                     bin_op_type == BinOpTypeDiv ||
+//                     bin_op_type == BinOpTypeMod))
+//                {
+//                    // float
+//                } else {
+//                    add_node_error(g, node, buf_sprintf("invalid operands to binary expression: '%s' and '%s'",
+//                            buf_ptr(&lhs_type->name), buf_ptr(&rhs_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
+//                ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
+//                if (!op1_val->ok || !op2_val->ok) {
+//                    return resolved_type;
+//                }
+//
+//                ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
+//                int err;
+//                if ((err = eval_const_expr_bin_op(op1_val, resolved_type, bin_op_type,
+//                                op2_val, resolved_type, out_val)))
+//                {
+//                    if (err == ErrorDivByZero) {
+//                        add_node_error(g, node, buf_sprintf("division by zero is undefined"));
+//                        return g->builtin_types.entry_invalid;
+//                    } else if (err == ErrorOverflow) {
+//                        add_node_error(g, node, buf_sprintf("value cannot be represented in any integer type"));
+//                        return g->builtin_types.entry_invalid;
+//                    }
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                num_lit_fits_in_other_type(g, node, resolved_type);
+//                return resolved_type;
+//            }
+//        case BinOpTypeUnwrapMaybe:
+//            {
+//                AstNode *op1 = node->data.bin_op_expr.op1;
+//                AstNode *op2 = node->data.bin_op_expr.op2;
+//                TypeTableEntry *lhs_type = analyze_expression(g, import, context, nullptr, op1);
+//
+//                if (lhs_type->id == TypeTableEntryIdInvalid) {
+//                    return lhs_type;
+//                } else if (lhs_type->id == TypeTableEntryIdMaybe) {
+//                    TypeTableEntry *child_type = lhs_type->data.maybe.child_type;
+//                    analyze_expression(g, import, context, child_type, op2);
+//                    return child_type;
+//                } else {
+//                    add_node_error(g, op1,
+//                        buf_sprintf("expected maybe type, got '%s'",
+//                            buf_ptr(&lhs_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case BinOpTypeArrayCat:
+//            {
+//                AstNode **op1 = node->data.bin_op_expr.op1->parent_field;
+//                AstNode **op2 = node->data.bin_op_expr.op2->parent_field;
+//
+//                TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
+//                TypeTableEntry *child_type;
+//                if (op1_type->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_invalid;
+//                } else if (op1_type->id == TypeTableEntryIdArray) {
+//                    child_type = op1_type->data.array.child_type;
+//                } else if (op1_type->id == TypeTableEntryIdPointer &&
+//                           op1_type->data.pointer.child_type == g->builtin_types.entry_u8) {
+//                    child_type = op1_type->data.pointer.child_type;
+//                } else {
+//                    add_node_error(g, *op1, buf_sprintf("expected array or C string literal, got '%s'",
+//                                buf_ptr(&op1_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//                if (op2_type->id == TypeTableEntryIdInvalid) {
+//                    return g->builtin_types.entry_invalid;
+//                } else if (op2_type->id == TypeTableEntryIdArray) {
+//                    if (op2_type->data.array.child_type != child_type) {
+//                        add_node_error(g, *op2, buf_sprintf("expected array of type '%s', got '%s'",
+//                                    buf_ptr(&child_type->name),
+//                                    buf_ptr(&op2_type->name)));
+//                        return g->builtin_types.entry_invalid;
+//                    }
+//                } else if (op2_type->id == TypeTableEntryIdPointer &&
+//                        op2_type->data.pointer.child_type == g->builtin_types.entry_u8) {
+//                } else {
+//                    add_node_error(g, *op2, buf_sprintf("expected array or C string literal, got '%s'",
+//                                buf_ptr(&op2_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
+//                ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
+//
+//                AstNode *bad_node;
+//                if (!op1_val->ok) {
+//                    bad_node = *op1;
+//                } else if (!op2_val->ok) {
+//                    bad_node = *op2;
+//                } else {
+//                    bad_node = nullptr;
+//                }
+//                if (bad_node) {
+//                    add_node_error(g, bad_node, buf_sprintf("array concatenation requires constant expression"));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//
+//                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;
+//
+//                if (op1_type->id == TypeTableEntryIdArray) {
+//                    uint64_t new_len = op1_type->data.array.len + op2_type->data.array.len;
+//                    const_val->data.x_array.fields = allocate<ConstExprValue*>(new_len);
+//                    uint64_t next_index = 0;
+//                    for (uint64_t i = 0; i < op1_type->data.array.len; i += 1, next_index += 1) {
+//                        const_val->data.x_array.fields[next_index] = op1_val->data.x_array.fields[i];
+//                    }
+//                    for (uint64_t i = 0; i < op2_type->data.array.len; i += 1, next_index += 1) {
+//                        const_val->data.x_array.fields[next_index] = op2_val->data.x_array.fields[i];
+//                    }
+//                    return get_array_type(g, child_type, new_len);
+//                } else if (op1_type->id == TypeTableEntryIdPointer) {
+//                    if (!op1_val->data.x_ptr.is_c_str) {
+//                        add_node_error(g, *op1,
+//                                buf_sprintf("expected array or C string literal, got '%s'",
+//                                    buf_ptr(&op1_type->name)));
+//                        return g->builtin_types.entry_invalid;
+//                    } else if (!op2_val->data.x_ptr.is_c_str) {
+//                        add_node_error(g, *op2,
+//                                buf_sprintf("expected array or C string literal, got '%s'",
+//                                    buf_ptr(&op2_type->name)));
+//                        return g->builtin_types.entry_invalid;
+//                    }
+//                    const_val->data.x_ptr.is_c_str = true;
+//                    const_val->data.x_ptr.len = op1_val->data.x_ptr.len + op2_val->data.x_ptr.len - 1;
+//                    const_val->data.x_ptr.ptr = allocate<ConstExprValue*>(const_val->data.x_ptr.len);
+//                    uint64_t next_index = 0;
+//                    for (uint64_t i = 0; i < op1_val->data.x_ptr.len - 1; i += 1, next_index += 1) {
+//                        const_val->data.x_ptr.ptr[next_index] = op1_val->data.x_ptr.ptr[i];
+//                    }
+//                    for (uint64_t i = 0; i < op2_val->data.x_ptr.len; i += 1, next_index += 1) {
+//                        const_val->data.x_ptr.ptr[next_index] = op2_val->data.x_ptr.ptr[i];
+//                    }
+//                    return op1_type;
+//                } else {
+//                    zig_unreachable();
+//                }
+//            }
+//        case BinOpTypeArrayMult:
+//            return analyze_array_mult(g, import, context, expected_type, node);
+//        case BinOpTypeInvalid:
+//            zig_unreachable();
+//    }
+//    zig_unreachable();
+//}
 
-    IrInstructionReturn *ret_inst = (IrInstructionReturn *)only_inst;
-    IrInstruction *value = ret_inst->value;
-    assert(value->static_value.ok);
-    return value;
-}
+
+//static TypeTableEntry *analyze_bool_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node)
+//{
+//    assert(node->type == NodeTypeBinOpExpr);
+//    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
+//
+//    AstNode **op1 = &node->data.bin_op_expr.op1;
+//    AstNode **op2 = &node->data.bin_op_expr.op2;
+//    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
+//    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//    AstNode *op_nodes[] = {*op1, *op2};
+//    TypeTableEntry *op_types[] = {op1_type, op2_type};
+//
+//    TypeTableEntry *resolved_type = resolve_peer_type_compatibility(g, import, context, node,
+//            op_nodes, op_types, 2);
+//
+//    bool is_equality_cmp = (bin_op_type == BinOpTypeCmpEq || bin_op_type == BinOpTypeCmpNotEq);
+//
+//    switch (resolved_type->id) {
+//        case TypeTableEntryIdInvalid:
+//            return g->builtin_types.entry_invalid;
+//
+//        case TypeTableEntryIdNumLitFloat:
+//        case TypeTableEntryIdNumLitInt:
+//        case TypeTableEntryIdInt:
+//        case TypeTableEntryIdFloat:
+//            break;
+//
+//        case TypeTableEntryIdBool:
+//        case TypeTableEntryIdMetaType:
+//        case TypeTableEntryIdVoid:
+//        case TypeTableEntryIdPointer:
+//        case TypeTableEntryIdPureError:
+//        case TypeTableEntryIdFn:
+//        case TypeTableEntryIdTypeDecl:
+//        case TypeTableEntryIdNamespace:
+//        case TypeTableEntryIdBlock:
+//        case TypeTableEntryIdGenericFn:
+//            if (!is_equality_cmp) {
+//                add_node_error(g, node,
+//                    buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
+//                return g->builtin_types.entry_invalid;
+//            }
+//            break;
+//
+//        case TypeTableEntryIdEnum:
+//            if (!is_equality_cmp || resolved_type->data.enumeration.gen_field_count != 0) {
+//                add_node_error(g, node,
+//                    buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
+//                return g->builtin_types.entry_invalid;
+//            }
+//            break;
+//
+//        case TypeTableEntryIdUnreachable:
+//        case TypeTableEntryIdArray:
+//        case TypeTableEntryIdStruct:
+//        case TypeTableEntryIdUndefLit:
+//        case TypeTableEntryIdNullLit:
+//        case TypeTableEntryIdMaybe:
+//        case TypeTableEntryIdErrorUnion:
+//        case TypeTableEntryIdUnion:
+//            add_node_error(g, node,
+//                buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name)));
+//            return g->builtin_types.entry_invalid;
+//
+//        case TypeTableEntryIdVar:
+//            zig_unreachable();
+//    }
+//
+//    ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
+//    ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
+//    if (!op1_val->ok || !op2_val->ok) {
+//        return g->builtin_types.entry_bool;
+//    }
+//
+//
+//    ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
+//    eval_const_expr_bin_op(op1_val, op1_type, bin_op_type, op2_val, op2_type, out_val);
+//    return g->builtin_types.entry_bool;
+//
+//}
+//
+////
+//static TypeTableEntry *analyze_if(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
+//        TypeTableEntry *expected_type, AstNode *node,
+//        AstNode **then_node, AstNode **else_node, bool cond_is_const, bool cond_bool_val)
+//{
+//    if (!*else_node) {
+//        *else_node = create_ast_void_node(g, import, node);
+//        normalize_parent_ptrs(node);
+//    }
+//
+//    BlockContext *then_context;
+//    BlockContext *else_context;
+//    if (cond_is_const) {
+//        if (cond_bool_val) {
+//            then_context = parent_context;
+//            else_context = new_block_context(node, parent_context);
+//
+//            else_context->codegen_excluded = true;
+//        } else {
+//            then_context = new_block_context(node, parent_context);
+//            else_context = parent_context;
+//
+//            then_context->codegen_excluded = true;
+//        }
+//    } else {
+//        then_context = parent_context;
+//        else_context = parent_context;
+//    }
+//
+//    TypeTableEntry *then_type = nullptr;
+//    TypeTableEntry *else_type = nullptr;
+//
+//    if (!then_context->codegen_excluded) {
+//        then_type = analyze_expression(g, import, then_context, expected_type, *then_node);
+//        if (then_type->id == TypeTableEntryIdInvalid) {
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//    if (!else_context->codegen_excluded) {
+//        else_type = analyze_expression(g, import, else_context, expected_type, *else_node);
+//        if (else_type->id == TypeTableEntryIdInvalid) {
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    TypeTableEntry *result_type;
+//    if (then_context->codegen_excluded) {
+//        result_type = else_type;
+//    } else if (else_context->codegen_excluded) {
+//        result_type = then_type;
+//    } else if (expected_type) {
+//        result_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type;
+//    } else {
+//        AstNode *op_nodes[] = {*then_node, *else_node};
+//        TypeTableEntry *op_types[] = {then_type, else_type};
+//        result_type = resolve_peer_type_compatibility(g, import, parent_context, node, op_nodes, op_types, 2);
+//    }
+//
+//    if (!cond_is_const) {
+//        return add_error_if_type_is_num_lit(g, result_type, node);
+//    }
+//
+//    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 add_error_if_type_is_num_lit(g, result_type, node);
+//    }
+//
+//    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//    *const_val = *other_const_val;
+//    // the condition depends on a compile var, so the entire if statement does too
+//    const_val->depends_on_compile_var = true;
+//    return result_type;
+//}
+//
+//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, parent_context);
+//
+//    analyze_variable_declaration_raw(g, import, child_context, node, &node->data.if_var_expr.var_decl, true,
+//        nullptr, node->data.if_var_expr.var_is_ptr);
+//    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(g, import, child_context, expected_type,
+//            node, then_node, else_node, cond_is_const, cond_bool_val);
+//}
+//
+//static TypeTableEntry *bad_method_call(CodeGen *g, AstNode *node, TypeTableEntry *container_type,
+//        TypeTableEntry *expected_param_type, FnTableEntry *fn_table_entry)
+//{
+//    ErrorMsg *msg = add_node_error(g, node,
+//        buf_sprintf("function called as method of '%s', but first parameter is of type '%s'",
+//            buf_ptr(&container_type->name),
+//            buf_ptr(&expected_param_type->name)));
+//    if (fn_table_entry) {
+//        add_error_note(g, msg, fn_table_entry->proto_node, buf_sprintf("function declared here"));
+//    }
+//    return g->builtin_types.entry_invalid;
+//}
+//
+//// Before calling this function, set node->data.fn_call_expr.fn_table_entry if the function is known
+//// at compile time. Otherwise this is a function pointer call.
+//static TypeTableEntry *analyze_fn_call_ptr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *fn_type,
+//        AstNode *struct_node)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//
+//    if (fn_type->id == TypeTableEntryIdInvalid) {
+//        return fn_type;
+//    }
+//
+//    // The function call might include inline parameters which we need to ignore according to the
+//    // fn_type.
+//    FnTableEntry *fn_table_entry = node->data.fn_call_expr.fn_entry;
+//    AstNode *generic_proto_node = fn_table_entry ?
+//        fn_table_entry->proto_node->data.fn_proto.generic_proto_node : nullptr;
+//
+//    // count parameters
+//    size_t struct_node_1_or_0 = struct_node ? 1 : 0;
+//    size_t src_param_count = fn_type->data.fn.fn_type_id.param_count +
+//        (generic_proto_node ? generic_proto_node->data.fn_proto.inline_arg_count : 0);
+//    size_t call_param_count = node->data.fn_call_expr.params.length;
+//    size_t expect_arg_count = src_param_count - struct_node_1_or_0;
+//
+//    bool ok_invocation = true;
+//
+//    if (fn_type->data.fn.fn_type_id.is_var_args) {
+//        if (call_param_count < expect_arg_count) {
+//            ok_invocation = false;
+//            add_node_error(g, node,
+//                buf_sprintf("expected at least %zu arguments, got %zu", src_param_count, call_param_count));
+//        }
+//    } else if (expect_arg_count != call_param_count) {
+//        ok_invocation = false;
+//        add_node_error(g, node,
+//                buf_sprintf("expected %zu arguments, got %zu", expect_arg_count, call_param_count));
+//    }
+//
+//    bool all_args_const_expr = true;
+//
+//    if (struct_node) {
+//        Expr *struct_expr = get_resolved_expr(struct_node);
+//        ConstExprValue *struct_const_val = &struct_expr->const_val;
+//        if (!struct_const_val->ok) {
+//            all_args_const_expr = false;
+//        }
+//
+//        FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[0];
+//        TypeTableEntry *expected_param_type = param_info->type;
+//        TypeTableEntry *container_bare_type = container_ref_type(struct_expr->type_entry);
+//        if (is_container_ref(expected_param_type)) {
+//            TypeTableEntry *param_bare_type = container_ref_type(expected_param_type);
+//            if (param_bare_type != container_bare_type) {
+//                return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
+//            }
+//        } else {
+//            return bad_method_call(g, node, container_bare_type, expected_param_type, fn_table_entry);
+//        }
+//    }
+//
+//    // analyze each parameter. in the case of a method, we already analyzed the
+//    // first parameter in order to figure out which struct we were calling a method on.
+//    size_t next_type_i = struct_node_1_or_0;
+//    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
+//        size_t proto_i = call_i + struct_node_1_or_0;
+//        AstNode **param_node = &node->data.fn_call_expr.params.at(call_i);
+//        // determine the expected type for each parameter
+//        TypeTableEntry *expected_param_type = nullptr;
+//        if (proto_i < src_param_count) {
+//            if (generic_proto_node &&
+//                generic_proto_node->data.fn_proto.params.at(proto_i)->data.param_decl.is_inline)
+//            {
+//                continue;
+//            }
+//
+//            FnTypeParamInfo *param_info = &fn_type->data.fn.fn_type_id.param_info[next_type_i];
+//            next_type_i += 1;
+//
+//            expected_param_type = param_info->type;
+//        }
+//        TypeTableEntry *param_type = analyze_expression(g, import, context, expected_param_type, *param_node);
+//        if (param_type->id == TypeTableEntryIdInvalid) {
+//            return param_type;
+//        }
+//
+//        ConstExprValue *const_arg_val = &get_resolved_expr(*param_node)->const_val;
+//        if (!const_arg_val->ok) {
+//            all_args_const_expr = false;
+//        }
+//    }
+//
+//    TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type;
+//
+//    if (return_type->id == TypeTableEntryIdInvalid) {
+//        return return_type;
+//    }
+//
+//    ConstExprValue *result_val = &get_resolved_expr(node)->const_val;
+//    if (ok_invocation && fn_table_entry && fn_table_entry->is_pure && fn_table_entry->want_pure != WantPureFalse) {
+//        if (fn_table_entry->anal_state == FnAnalStateReady) {
+//            analyze_fn_body(g, fn_table_entry);
+//            if (fn_table_entry->proto_node->data.fn_proto.skip) {
+//                return g->builtin_types.entry_invalid;
+//            }
+//        }
+//        if (all_args_const_expr) {
+//            if (fn_table_entry->is_pure && fn_table_entry->anal_state == FnAnalStateComplete) {
+//                if (eval_fn(g, node, fn_table_entry, result_val, 1000, struct_node)) {
+//                    // function evaluation generated an error
+//                    return g->builtin_types.entry_invalid;
+//                }
+//                return return_type;
+//            }
+//        }
+//    }
+//    if (!ok_invocation || !fn_table_entry || !fn_table_entry->is_pure || fn_table_entry->want_pure == WantPureFalse) {
+//        // calling an impure fn is impure
+//        mark_impure_fn(g, context, node);
+//        if (fn_table_entry && fn_table_entry->want_pure == WantPureTrue) {
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    // TODO
+//    //if (handle_is_ptr(return_type)) {
+//    //    if (context->fn_entry) {
+//    //        context->fn_entry->cast_alloca_list.append(node);
+//    //    } else if (!result_val->ok) {
+//    //        add_node_error(g, node, buf_sprintf("unable to evaluate constant expression"));
+//    //    }
+//    //}
+//
+//    return return_type;
+//}
+//
+//static TypeTableEntry *analyze_fn_call_with_inline_args(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *parent_context, TypeTableEntry *expected_type, AstNode *call_node,
+//        FnTableEntry *fn_table_entry, AstNode *struct_node)
+//{
+//    assert(call_node->type == NodeTypeFnCallExpr);
+//    assert(fn_table_entry);
+//
+//    AstNode *decl_node = fn_table_entry->proto_node;
+//
+//    // count parameters
+//    size_t struct_node_1_or_0 = (struct_node ? 1 : 0);
+//    size_t src_param_count = decl_node->data.fn_proto.params.length;
+//    size_t call_param_count = call_node->data.fn_call_expr.params.length;
+//
+//    if (src_param_count != call_param_count + struct_node_1_or_0) {
+//        add_node_error(g, call_node,
+//            buf_sprintf("expected %zu arguments, got %zu", src_param_count - struct_node_1_or_0, call_param_count));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    size_t inline_or_var_type_arg_count = decl_node->data.fn_proto.inline_or_var_type_arg_count;
+//    assert(inline_or_var_type_arg_count > 0);
+//
+//    BlockContext *child_context = decl_node->owner->block_context;
+//    size_t next_generic_param_index = 0;
+//
+//    GenericFnTypeId *generic_fn_type_id = allocate<GenericFnTypeId>(1);
+//    generic_fn_type_id->decl_node = decl_node;
+//    generic_fn_type_id->generic_param_count = inline_or_var_type_arg_count;
+//    generic_fn_type_id->generic_params = allocate<GenericParamValue>(inline_or_var_type_arg_count);
+//
+//    size_t next_impl_i = 0;
+//    for (size_t call_i = 0; call_i < call_param_count; call_i += 1) {
+//        size_t proto_i = call_i + struct_node_1_or_0;
+//        AstNode *generic_param_decl_node = decl_node->data.fn_proto.params.at(proto_i);
+//        assert(generic_param_decl_node->type == NodeTypeParamDecl);
+//
+//        AstNode **generic_param_type_node = &generic_param_decl_node->data.param_decl.type;
+//        TypeTableEntry *expected_param_type = analyze_type_expr(g, decl_node->owner, child_context,
+//                *generic_param_type_node);
+//        if (expected_param_type->id == TypeTableEntryIdInvalid) {
+//            return expected_param_type;
+//        }
+//
+//        bool is_var_type = (expected_param_type->id == TypeTableEntryIdVar);
+//        bool is_inline = generic_param_decl_node->data.param_decl.is_inline;
+//        if (!is_inline && !is_var_type) {
+//            next_impl_i += 1;
+//            continue;
+//        }
+//
+//
+//        AstNode **param_node = &call_node->data.fn_call_expr.params.at(call_i);
+//        TypeTableEntry *param_type = analyze_expression(g, import, parent_context,
+//                is_var_type ? nullptr : expected_param_type, *param_node);
+//        if (param_type->id == TypeTableEntryIdInvalid) {
+//            return param_type;
+//        }
+//
+//        // set child_context so that the previous param is in scope
+//        child_context = new_block_context(generic_param_decl_node, child_context);
+//
+//        ConstExprValue *const_val = &get_resolved_expr(*param_node)->const_val;
+//        if (is_inline && !const_val->ok) {
+//            add_node_error(g, *param_node,
+//                    buf_sprintf("unable to evaluate constant expression for inline parameter"));
+//
+//            return g->builtin_types.entry_invalid;
+//        }
+//
+//        VariableTableEntry *var = add_local_var_shadowable(g, generic_param_decl_node, decl_node->owner, child_context,
+//                generic_param_decl_node->data.param_decl.name, param_type, true, *param_node, true);
+//        // This generic function instance could be called with anything, so when this variable is read it
+//        // needs to know that it depends on compile time variable data.
+//        var->force_depends_on_compile_var = true;
+//
+//        GenericParamValue *generic_param_value =
+//            &generic_fn_type_id->generic_params[next_generic_param_index];
+//        generic_param_value->type = param_type;
+//        generic_param_value->node = is_inline ? *param_node : nullptr;
+//        generic_param_value->impl_index = next_impl_i;
+//        next_generic_param_index += 1;
+//
+//        if (!is_inline) {
+//            next_impl_i += 1;
+//        }
+//    }
+//
+//    assert(next_generic_param_index == inline_or_var_type_arg_count);
+//
+//    auto entry = g->generic_table.maybe_get(generic_fn_type_id);
+//    FnTableEntry *impl_fn;
+//    if (entry) {
+//        AstNode *impl_decl_node = entry->value;
+//        assert(impl_decl_node->type == NodeTypeFnProto);
+//        impl_fn = impl_decl_node->data.fn_proto.fn_table_entry;
+//    } else {
+//        AstNode *decl_node = generic_fn_type_id->decl_node;
+//        AstNode *impl_fn_def_node = ast_clone_subtree_special(decl_node->data.fn_proto.fn_def_node,
+//                &g->next_node_index, AstCloneSpecialOmitInlineParams);
+//        AstNode *impl_decl_node = impl_fn_def_node->data.fn_def.fn_proto;
+//        impl_decl_node->data.fn_proto.inline_arg_count = 0;
+//        impl_decl_node->data.fn_proto.inline_or_var_type_arg_count = 0;
+//        impl_decl_node->data.fn_proto.generic_proto_node = decl_node;
+//
+//        // replace var arg types with actual types
+//        for (size_t generic_arg_i = 0; generic_arg_i < inline_or_var_type_arg_count; generic_arg_i += 1) {
+//            GenericParamValue *generic_param_value = &generic_fn_type_id->generic_params[generic_arg_i];
+//            if (!generic_param_value->node) {
+//                size_t impl_i = generic_param_value->impl_index;
+//                AstNode *impl_param_decl_node = impl_decl_node->data.fn_proto.params.at(impl_i);
+//                assert(impl_param_decl_node->type == NodeTypeParamDecl);
+//
+//                impl_param_decl_node->data.param_decl.type = create_ast_type_node(g, import,
+//                        generic_param_value->type, impl_param_decl_node);
+//                normalize_parent_ptrs(impl_param_decl_node);
+//            }
+//        }
+//
+//        preview_fn_proto_instance(g, import, impl_decl_node, child_context);
+//        g->generic_table.put(generic_fn_type_id, impl_decl_node);
+//        impl_fn = impl_decl_node->data.fn_proto.fn_table_entry;
+//    }
+//
+//    call_node->data.fn_call_expr.fn_entry = impl_fn;
+//    return analyze_fn_call_ptr(g, import, parent_context, expected_type, call_node,
+//            impl_fn->type_entry, struct_node);
+//}
+//
+//static TypeTableEntry *analyze_generic_fn_call(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
+//        TypeTableEntry *expected_type, AstNode *node, TypeTableEntry *generic_fn_type)
+//{
+//    assert(node->type == NodeTypeFnCallExpr);
+//    assert(generic_fn_type->id == TypeTableEntryIdGenericFn);
+//
+//    AstNode *decl_node = generic_fn_type->data.generic_fn.decl_node;
+//    assert(decl_node->type == NodeTypeContainerDecl);
+//    ZigList<AstNode *> *generic_params = &decl_node->data.struct_decl.generic_params;
+//
+//    size_t expected_param_count = generic_params->length;
+//    size_t actual_param_count = node->data.fn_call_expr.params.length;
+//
+//    if (actual_param_count != expected_param_count) {
+//        add_node_error(g, first_executing_node(node),
+//                buf_sprintf("expected %zu arguments, got %zu", expected_param_count, actual_param_count));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    GenericFnTypeId *generic_fn_type_id = allocate<GenericFnTypeId>(1);
+//    generic_fn_type_id->decl_node = decl_node;
+//    generic_fn_type_id->generic_param_count = actual_param_count;
+//    generic_fn_type_id->generic_params = allocate<GenericParamValue>(actual_param_count);
+//
+//    BlockContext *child_context = decl_node->owner->block_context;
+//    for (size_t i = 0; i < actual_param_count; i += 1) {
+//        AstNode *generic_param_decl_node = generic_params->at(i);
+//        assert(generic_param_decl_node->type == NodeTypeParamDecl);
+//
+//        AstNode **generic_param_type_node = &generic_param_decl_node->data.param_decl.type;
+//
+//        TypeTableEntry *expected_param_type = analyze_type_expr(g, decl_node->owner,
+//                child_context, *generic_param_type_node);
+//        if (expected_param_type->id == TypeTableEntryIdInvalid) {
+//            return expected_param_type;
+//        }
+//
+//
+//
+//        AstNode **param_node = &node->data.fn_call_expr.params.at(i);
+//
+//        TypeTableEntry *param_type = analyze_expression(g, import, parent_context, expected_param_type,
+//                *param_node);
+//        if (param_type->id == TypeTableEntryIdInvalid) {
+//            return param_type;
+//        }
+//
+//        // set child_context so that the previous param is in scope
+//        child_context = new_block_context(generic_param_decl_node, child_context);
+//
+//        ConstExprValue *const_val = &get_resolved_expr(*param_node)->const_val;
+//        if (const_val->ok) {
+//            VariableTableEntry *var = add_local_var(g, generic_param_decl_node, decl_node->owner, child_context,
+//                    generic_param_decl_node->data.param_decl.name, param_type, true, *param_node);
+//            var->force_depends_on_compile_var = true;
+//        } else {
+//            add_node_error(g, *param_node, buf_sprintf("unable to evaluate constant expression"));
+//
+//            return g->builtin_types.entry_invalid;
+//        }
+//
+//        GenericParamValue *generic_param_value = &generic_fn_type_id->generic_params[i];
+//        generic_param_value->type = param_type;
+//        generic_param_value->node = *param_node;
+//    }
+//
+//    auto entry = g->generic_table.maybe_get(generic_fn_type_id);
+//    if (entry) {
+//        AstNode *impl_decl_node = entry->value;
+//        assert(impl_decl_node->type == NodeTypeContainerDecl);
+//        TypeTableEntry *type_entry = impl_decl_node->data.struct_decl.type_entry;
+//        return resolve_expr_const_val_as_type(g, node, type_entry, false);
+//    }
+//
+//    // make a type from the generic parameters supplied
+//    assert(decl_node->type == NodeTypeContainerDecl);
+//    AstNode *impl_decl_node = ast_clone_subtree(decl_node, &g->next_node_index);
+//    g->generic_table.put(generic_fn_type_id, impl_decl_node);
+//    scan_struct_decl(g, import, child_context, impl_decl_node);
+//    TypeTableEntry *type_entry = impl_decl_node->data.struct_decl.type_entry;
+//    resolve_struct_type(g, import, type_entry);
+//    return resolve_expr_const_val_as_type(g, node, type_entry, false);
+//}
+//
+//static TypeTableEntry *analyze_fn_call_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    AstNode *fn_ref_expr = node->data.fn_call_expr.fn_ref_expr;
+//
+//    if (node->data.fn_call_expr.is_builtin) {
+//        zig_panic("moved builtin fn call code to ir.cpp");
+//    }
+//
+//    TypeTableEntry *invoke_type_entry = analyze_expression(g, import, context, nullptr, fn_ref_expr);
+//    if (invoke_type_entry->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    // use constant expression evaluator to figure out the function at compile time.
+//    // otherwise we treat this as a function pointer.
+//    ConstExprValue *const_val = &get_resolved_expr(fn_ref_expr)->const_val;
+//
+//    if (const_val->ok) {
+//        if (invoke_type_entry->id == TypeTableEntryIdMetaType) {
+//            zig_unreachable();
+//        } else if (invoke_type_entry->id == TypeTableEntryIdFn) {
+//            AstNode *struct_node;
+//            if (fn_ref_expr->type == NodeTypeFieldAccessExpr &&
+//                fn_ref_expr->data.field_access_expr.is_member_fn)
+//            {
+//                struct_node = fn_ref_expr->data.field_access_expr.struct_expr;
+//            } else {
+//                struct_node = nullptr;
+//            }
+//
+//            FnTableEntry *fn_table_entry = const_val->data.x_fn;
+//            node->data.fn_call_expr.fn_entry = fn_table_entry;
+//            return analyze_fn_call_ptr(g, import, context, expected_type, node,
+//                    fn_table_entry->type_entry, struct_node);
+//        } else if (invoke_type_entry->id == TypeTableEntryIdGenericFn) {
+//            TypeTableEntry *generic_fn_type = const_val->data.x_type;
+//            AstNode *decl_node = generic_fn_type->data.generic_fn.decl_node;
+//            if (decl_node->type == NodeTypeFnProto) {
+//                AstNode *struct_node;
+//                if (fn_ref_expr->type == NodeTypeFieldAccessExpr &&
+//                    fn_ref_expr->data.field_access_expr.is_member_fn)
+//                {
+//                    struct_node = fn_ref_expr->data.field_access_expr.struct_expr;
+//                } else {
+//                    struct_node = nullptr;
+//                }
+//
+//                FnTableEntry *fn_table_entry = decl_node->data.fn_proto.fn_table_entry;
+//                if (fn_table_entry->proto_node->data.fn_proto.skip) {
+//                    return g->builtin_types.entry_invalid;
+//                }
+//                return analyze_fn_call_with_inline_args(g, import, context, expected_type, node,
+//                        fn_table_entry, struct_node);
+//            } else {
+//                return analyze_generic_fn_call(g, import, context, expected_type, node, const_val->data.x_type);
+//            }
+//        } else {
+//            add_node_error(g, fn_ref_expr,
+//                buf_sprintf("type '%s' not a function", buf_ptr(&invoke_type_entry->name)));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    // function pointer
+//    if (invoke_type_entry->id == TypeTableEntryIdFn) {
+//        return analyze_fn_call_ptr(g, import, context, expected_type, node, invoke_type_entry, nullptr);
+//    } else {
+//        add_node_error(g, fn_ref_expr,
+//            buf_sprintf("type '%s' not a function", buf_ptr(&invoke_type_entry->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
+//static TypeTableEntry *analyze_switch_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    AstNode **expr_node = &node->data.switch_expr.expr;
+//    TypeTableEntry *expr_type = analyze_expression(g, import, context, nullptr, *expr_node);
+//    ConstExprValue *expr_val = &get_resolved_expr(*expr_node)->const_val;
+//    if (expr_val->ok && !expr_val->depends_on_compile_var) {
+//        add_node_error(g, first_executing_node(*expr_node),
+//                buf_sprintf("value is constant; unnecessary switch statement"));
+//    }
+//    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//
+//
+//    size_t prong_count = node->data.switch_expr.prongs.length;
+//    AstNode **peer_nodes = allocate<AstNode*>(prong_count);
+//    TypeTableEntry **peer_types = allocate<TypeTableEntry*>(prong_count);
+//
+//    bool any_errors = false;
+//    if (expr_type->id == TypeTableEntryIdInvalid) {
+//        return expr_type;
+//    } else if (expr_type->id == TypeTableEntryIdUnreachable) {
+//        add_node_error(g, first_executing_node(*expr_node),
+//                buf_sprintf("switch on unreachable expression not allowed"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//
+//    size_t *field_use_counts = nullptr;
+//    HashMap<int, AstNode *, int_hash, int_eq> err_use_nodes = {};
+//    if (expr_type->id == TypeTableEntryIdEnum) {
+//        field_use_counts = allocate<size_t>(expr_type->data.enumeration.src_field_count);
+//    } else if (expr_type->id == TypeTableEntryIdErrorUnion) {
+//        err_use_nodes.init(10);
+//    }
+//
+//    size_t *const_chosen_prong_index = &node->data.switch_expr.const_chosen_prong_index;
+//    *const_chosen_prong_index = SIZE_MAX;
+//    AstNode *else_prong = nullptr;
+//    for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
+//        AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i);
+//
+//        TypeTableEntry *var_type;
+//        bool var_is_target_expr;
+//        if (prong_node->data.switch_prong.items.length == 0) {
+//            if (else_prong) {
+//                add_node_error(g, prong_node, buf_sprintf("multiple else prongs in switch expression"));
+//                any_errors = true;
+//            } else {
+//                else_prong = prong_node;
+//            }
+//            var_type = expr_type;
+//            var_is_target_expr = true;
+//            if (*const_chosen_prong_index == SIZE_MAX && expr_val->ok) {
+//                *const_chosen_prong_index = prong_i;
+//            }
+//        } else {
+//            bool all_agree_on_var_type = true;
+//            var_type = nullptr;
+//
+//            for (size_t item_i = 0; item_i < prong_node->data.switch_prong.items.length; item_i += 1) {
+//                AstNode *item_node = prong_node->data.switch_prong.items.at(item_i);
+//                if (item_node->type == NodeTypeSwitchRange) {
+//                    zig_panic("TODO range in switch statement");
+//                }
+//
+//                if (expr_type->id == TypeTableEntryIdEnum) {
+//                    if (item_node->type == NodeTypeSymbol) {
+//                        Buf *field_name = item_node->data.symbol_expr.symbol;
+//                        TypeEnumField *type_enum_field = find_enum_type_field(expr_type, field_name);
+//                        if (type_enum_field) {
+//                            item_node->data.symbol_expr.enum_field = type_enum_field;
+//                            if (!var_type) {
+//                                var_type = type_enum_field->type_entry;
+//                            }
+//                            if (type_enum_field->type_entry != var_type) {
+//                                all_agree_on_var_type = false;
+//                            }
+//                            uint32_t field_index = type_enum_field->value;
+//                            assert(field_use_counts);
+//                            field_use_counts[field_index] += 1;
+//                            if (field_use_counts[field_index] > 1) {
+//                                add_node_error(g, item_node,
+//                                    buf_sprintf("duplicate switch value: '%s'",
+//                                        buf_ptr(type_enum_field->name)));
+//                                any_errors = true;
+//                            }
+//                            if (!any_errors && expr_val->ok) {
+//                                if (expr_val->data.x_enum.tag == type_enum_field->value) {
+//                                    *const_chosen_prong_index = prong_i;
+//                                }
+//                            }
+//                        } else {
+//                            add_node_error(g, item_node,
+//                                    buf_sprintf("enum '%s' has no field '%s'",
+//                                        buf_ptr(&expr_type->name), buf_ptr(field_name)));
+//                            any_errors = true;
+//                        }
+//                    } else {
+//                        add_node_error(g, item_node, buf_sprintf("expected enum tag name"));
+//                        any_errors = true;
+//                    }
+//                } else if (expr_type->id == TypeTableEntryIdErrorUnion) {
+//                    if (item_node->type == NodeTypeSymbol) {
+//                        Buf *err_name = item_node->data.symbol_expr.symbol;
+//                        bool is_ok_case = buf_eql_str(err_name, "Ok");
+//                        auto err_table_entry = is_ok_case ? nullptr: g->error_table.maybe_get(err_name);
+//                        if (is_ok_case || err_table_entry) {
+//                            uint32_t err_value = is_ok_case ? 0 : err_table_entry->value->value;
+//                            item_node->data.symbol_expr.err_value = err_value;
+//                            TypeTableEntry *this_var_type;
+//                            if (is_ok_case) {
+//                                this_var_type = expr_type->data.error.child_type;
+//                            } else {
+//                                this_var_type = g->builtin_types.entry_pure_error;
+//                            }
+//                            if (!var_type) {
+//                                var_type = this_var_type;
+//                            }
+//                            if (this_var_type != var_type) {
+//                                all_agree_on_var_type = false;
+//                            }
+//
+//                            // detect duplicate switch values
+//                            auto existing_entry = err_use_nodes.maybe_get(err_value);
+//                            if (existing_entry) {
+//                                add_node_error(g, existing_entry->value,
+//                                        buf_sprintf("duplicate switch value: '%s'", buf_ptr(err_name)));
+//                                any_errors = true;
+//                            } else {
+//                                err_use_nodes.put(err_value, item_node);
+//                            }
+//
+//                            if (!any_errors && expr_val->ok) {
+//                                if (expr_val->data.x_err.err->value == err_value) {
+//                                    *const_chosen_prong_index = prong_i;
+//                                }
+//                            }
+//                        } else {
+//                            add_node_error(g, item_node,
+//                                    buf_sprintf("use of undeclared error value '%s'", buf_ptr(err_name)));
+//                            any_errors = true;
+//                        }
+//                    } else {
+//                        add_node_error(g, item_node, buf_sprintf("expected error value name"));
+//                        any_errors = true;
+//                    }
+//                } else {
+//                    if (!any_errors && expr_val->ok) {
+//                        // note: there is now a function in eval.cpp for doing const expr comparison
+//                        zig_panic("TODO determine if const exprs are equal");
+//                    }
+//                    TypeTableEntry *item_type = analyze_expression(g, import, context, expr_type, item_node);
+//                    if (item_type->id != TypeTableEntryIdInvalid) {
+//                        ConstExprValue *const_val = &get_resolved_expr(item_node)->const_val;
+//                        if (!const_val->ok) {
+//                            add_node_error(g, item_node,
+//                                buf_sprintf("unable to evaluate constant expression"));
+//                            any_errors = true;
+//                        }
+//                    }
+//                }
+//            }
+//            if (!var_type || !all_agree_on_var_type) {
+//                var_type = expr_type;
+//                var_is_target_expr = true;
+//            } else {
+//                var_is_target_expr = false;
+//            }
+//        }
+//
+//        BlockContext *child_context = new_block_context(node, context);
+//        prong_node->data.switch_prong.block_context = child_context;
+//        AstNode *var_node = prong_node->data.switch_prong.var_symbol;
+//        if (var_node) {
+//            assert(var_node->type == NodeTypeSymbol);
+//            Buf *var_name = var_node->data.symbol_expr.symbol;
+//            var_node->block_context = child_context;
+//            prong_node->data.switch_prong.var = add_local_var(g, var_node, import,
+//                    child_context, var_name, var_type, true, nullptr);
+//            prong_node->data.switch_prong.var_is_target_expr = var_is_target_expr;
+//        }
+//    }
+//
+//    for (size_t prong_i = 0; prong_i < prong_count; prong_i += 1) {
+//        AstNode *prong_node = node->data.switch_expr.prongs.at(prong_i);
+//        BlockContext *child_context = prong_node->data.switch_prong.block_context;
+//        child_context->codegen_excluded = expr_val->ok && (*const_chosen_prong_index != prong_i);
+//
+//        if (child_context->codegen_excluded) {
+//            peer_types[prong_i] = g->builtin_types.entry_unreachable;
+//        } else {
+//            peer_types[prong_i] = analyze_expression(g, import, child_context, expected_type,
+//                    prong_node->data.switch_prong.expr);
+//        }
+//        // This must go after the analyze_expression for
+//        // prong_node->data.switch_prong.expr because of AST rewriting.
+//        peer_nodes[prong_i] = prong_node->data.switch_prong.expr;
+//    }
+//
+//    if (expr_type->id == TypeTableEntryIdEnum && !else_prong) {
+//        for (uint32_t i = 0; i < expr_type->data.enumeration.src_field_count; i += 1) {
+//            if (field_use_counts[i] == 0) {
+//                add_node_error(g, node,
+//                    buf_sprintf("enumeration value '%s' not handled in switch",
+//                        buf_ptr(expr_type->data.enumeration.fields[i].name)));
+//                any_errors = true;
+//            }
+//        }
+//    }
+//
+//    if (any_errors) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (prong_count == 0) {
+//        add_node_error(g, node, buf_sprintf("switch statement has no prongs"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    TypeTableEntry *result_type = resolve_peer_type_compatibility(g, import, context, node,
+//            peer_nodes, peer_types, prong_count);
+//
+//    if (expr_val->ok) {
+//        assert(*const_chosen_prong_index != SIZE_MAX);
+//
+//        *const_val = get_resolved_expr(peer_nodes[*const_chosen_prong_index])->const_val;
+//        // the target expr depends on a compile var because we have an error on unnecessary
+//        // switch statement, so the entire switch statement does too
+//        const_val->depends_on_compile_var = true;
+//
+//        if (!const_val->ok) {
+//            return add_error_if_type_is_num_lit(g, result_type, node);
+//        }
+//    } else {
+//        return add_error_if_type_is_num_lit(g, result_type, node);
+//    }
+//
+//    return result_type;
+//}
+//
+//static TypeTableEntry *analyze_return_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    if (!node->data.return_expr.expr) {
+//        node->data.return_expr.expr = create_ast_void_node(g, import, node);
+//        normalize_parent_ptrs(node);
+//    }
+//
+//    TypeTableEntry *expected_return_type = get_return_type(context);
+//
+//    switch (node->data.return_expr.kind) {
+//        case ReturnKindUnconditional:
+//            zig_panic("TODO moved to ir.cpp");
+//        case ReturnKindError:
+//            {
+//                TypeTableEntry *expected_err_type;
+//                if (expected_type) {
+//                    expected_err_type = get_error_type(g, expected_type);
+//                } else {
+//                    expected_err_type = nullptr;
+//                }
+//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_err_type,
+//                        node->data.return_expr.expr);
+//                if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                    return resolved_type;
+//                } else if (resolved_type->id == TypeTableEntryIdErrorUnion) {
+//                    if (expected_return_type->id != TypeTableEntryIdErrorUnion &&
+//                        expected_return_type->id != TypeTableEntryIdPureError)
+//                    {
+//                        ErrorMsg *msg = add_node_error(g, node,
+//                            buf_sprintf("%%return statement in function with return type '%s'",
+//                                buf_ptr(&expected_return_type->name)));
+//                        AstNode *return_type_node = context->fn_entry->fn_def_node->data.fn_def.fn_proto->data.fn_proto.return_type;
+//                        add_error_note(g, msg, return_type_node, buf_sprintf("function return type here"));
+//                    }
+//
+//                    return resolved_type->data.error.child_type;
+//                } else {
+//                    add_node_error(g, node->data.return_expr.expr,
+//                        buf_sprintf("expected error type, got '%s'", buf_ptr(&resolved_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        case ReturnKindMaybe:
+//            {
+//                TypeTableEntry *expected_maybe_type;
+//                if (expected_type) {
+//                    expected_maybe_type = get_maybe_type(g, expected_type);
+//                } else {
+//                    expected_maybe_type = nullptr;
+//                }
+//                TypeTableEntry *resolved_type = analyze_expression(g, import, context, expected_maybe_type,
+//                        node->data.return_expr.expr);
+//                if (resolved_type->id == TypeTableEntryIdInvalid) {
+//                    return resolved_type;
+//                } else if (resolved_type->id == TypeTableEntryIdMaybe) {
+//                    if (expected_return_type->id != TypeTableEntryIdMaybe) {
+//                        ErrorMsg *msg = add_node_error(g, node,
+//                            buf_sprintf("?return statement in function with return type '%s'",
+//                                buf_ptr(&expected_return_type->name)));
+//                        AstNode *return_type_node = context->fn_entry->fn_def_node->data.fn_def.fn_proto->data.fn_proto.return_type;
+//                        add_error_note(g, msg, return_type_node, buf_sprintf("function return type here"));
+//                    }
+//
+//                    return resolved_type->data.maybe.child_type;
+//                } else {
+//                    add_node_error(g, node->data.return_expr.expr,
+//                        buf_sprintf("expected maybe type, got '%s'", buf_ptr(&resolved_type->name)));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//    }
+//    zig_unreachable();
+//}
+//static TypeTableEntry *analyze_goto_pass1(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeGoto);
+//
+//    FnTableEntry *fn_table_entry = context->fn_entry;
+//    assert(fn_table_entry);
+//
+//    fn_table_entry->goto_list.append(node);
+//
+//    return g->builtin_types.entry_unreachable;
+//}
+//
+//static TypeTableEntry *analyze_enum_value_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *field_access_node, AstNode *value_node, TypeTableEntry *enum_type, Buf *field_name,
+//        AstNode *out_node)
+//{
+//    assert(field_access_node->type == NodeTypeFieldAccessExpr);
+//
+//    TypeEnumField *type_enum_field = find_enum_type_field(enum_type, field_name);
+//    if (type_enum_field->type_entry->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    field_access_node->data.field_access_expr.type_enum_field = type_enum_field;
+//
+//    if (type_enum_field) {
+//        if (value_node) {
+//            AstNode **value_node_ptr = value_node->parent_field;
+//            TypeTableEntry *value_type = analyze_expression(g, import, context,
+//                    type_enum_field->type_entry, value_node);
+//
+//            if (value_type->id == TypeTableEntryIdInvalid) {
+//                return g->builtin_types.entry_invalid;
+//            }
+//
+//            StructValExprCodeGen *codegen = &field_access_node->data.field_access_expr.resolved_struct_val_expr;
+//            codegen->type_entry = enum_type;
+//            codegen->source_node = field_access_node;
+//
+//            ConstExprValue *value_const_val = &get_resolved_expr(*value_node_ptr)->const_val;
+//            if (value_const_val->ok) {
+//                ConstExprValue *const_val = &get_resolved_expr(out_node)->const_val;
+//                const_val->ok = true;
+//                const_val->data.x_enum.tag = type_enum_field->value;
+//                const_val->data.x_enum.payload = value_const_val;
+//            } else {
+//                if (context->fn_entry) {
+//                    context->fn_entry->struct_val_expr_alloca_list.append(codegen);
+//                } else {
+//                    add_node_error(g, *value_node_ptr, buf_sprintf("unable to evaluate constant expression"));
+//                    return g->builtin_types.entry_invalid;
+//                }
+//            }
+//        } else if (type_enum_field->type_entry->id != TypeTableEntryIdVoid) {
+//            add_node_error(g, field_access_node,
+//                buf_sprintf("enum value '%s.%s' requires parameter of type '%s'",
+//                    buf_ptr(&enum_type->name),
+//                    buf_ptr(field_name),
+//                    buf_ptr(&type_enum_field->type_entry->name)));
+//        } else {
+//            Expr *expr = get_resolved_expr(out_node);
+//            expr->const_val.ok = true;
+//            expr->const_val.data.x_enum.tag = type_enum_field->value;
+//            expr->const_val.data.x_enum.payload = nullptr;
+//        }
+//    } else {
+//        add_node_error(g, field_access_node,
+//            buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
+//                buf_ptr(&enum_type->name)));
+//    }
+//    return enum_type;
+//}
+//
+//static TypeTableEntry *analyze_container_member_access_inner(CodeGen *g,
+//    TypeTableEntry *bare_struct_type, Buf *field_name, AstNode *node, TypeTableEntry *struct_type)
+//{
+//    assert(node->type == NodeTypeFieldAccessExpr);
+//    if (!is_slice(bare_struct_type)) {
+//        BlockContext *container_block_context = get_container_block_context(bare_struct_type);
+//        assert(container_block_context);
+//        auto entry = container_block_context->decl_table.maybe_get(field_name);
+//        AstNode *fn_decl_node = entry ? entry->value : nullptr;
+//        if (fn_decl_node && fn_decl_node->type == NodeTypeFnProto) {
+//            resolve_top_level_decl(g, fn_decl_node, false);
+//            TopLevelDecl *tld = get_as_top_level_decl(fn_decl_node);
+//            if (tld->resolution == TldResolutionInvalid) {
+//                return g->builtin_types.entry_invalid;
+//            }
+//
+//            node->data.field_access_expr.is_member_fn = true;
+//            FnTableEntry *fn_entry = fn_decl_node->data.fn_proto.fn_table_entry;
+//            if (fn_entry->type_entry->id == TypeTableEntryIdGenericFn) {
+//                return resolve_expr_const_val_as_generic_fn(g, node, fn_entry->type_entry, false);
+//            } else {
+//                return resolve_expr_const_val_as_fn(g, node, fn_entry, false);
+//            }
+//        }
+//    }
+//    add_node_error(g, node,
+//        buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), buf_ptr(&bare_struct_type->name)));
+//    return g->builtin_types.entry_invalid;
+//}
+//
+//static TypeTableEntry *analyze_container_member_access(CodeGen *g,
+//        Buf *field_name, AstNode *node, TypeTableEntry *struct_type)
+//{
+//    TypeTableEntry *bare_type = container_ref_type(struct_type);
+//    if (!type_is_complete(bare_type)) {
+//        resolve_container_type(g, bare_type);
+//    }
+//
+//    node->data.field_access_expr.bare_container_type = bare_type;
+//
+//    if (bare_type->id == TypeTableEntryIdStruct) {
+//        node->data.field_access_expr.type_struct_field = find_struct_type_field(bare_type, field_name);
+//        if (node->data.field_access_expr.type_struct_field) {
+//            return node->data.field_access_expr.type_struct_field->type_entry;
+//        } else {
+//            return analyze_container_member_access_inner(g, bare_type, field_name,
+//                node, struct_type);
+//        }
+//    } else if (bare_type->id == TypeTableEntryIdEnum) {
+//        node->data.field_access_expr.type_enum_field = find_enum_type_field(bare_type, field_name);
+//        if (node->data.field_access_expr.type_enum_field) {
+//            return node->data.field_access_expr.type_enum_field->type_entry;
+//        } else {
+//            return analyze_container_member_access_inner(g, bare_type, field_name,
+//                node, struct_type);
+//        }
+//    } else if (bare_type->id == TypeTableEntryIdUnion) {
+//        zig_panic("TODO");
+//    } else {
+//        zig_unreachable();
+//    }
+//}
+//
+//static TypeTableEntry *analyze_slice_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node)
+//{
+//    assert(node->type == NodeTypeSliceExpr);
+//
+//    TypeTableEntry *array_type = analyze_expression(g, import, context, nullptr,
+//            node->data.slice_expr.array_ref_expr);
+//
+//    TypeTableEntry *return_type;
+//
+//    if (array_type->id == TypeTableEntryIdInvalid) {
+//        return_type = g->builtin_types.entry_invalid;
+//    } else if (array_type->id == TypeTableEntryIdArray) {
+//        return_type = get_slice_type(g, array_type->data.array.child_type,
+//                node->data.slice_expr.is_const);
+//    } else if (array_type->id == TypeTableEntryIdPointer) {
+//        return_type = get_slice_type(g, array_type->data.pointer.child_type,
+//                node->data.slice_expr.is_const);
+//    } else if (array_type->id == TypeTableEntryIdStruct &&
+//               array_type->data.structure.is_slice)
+//    {
+//        return_type = get_slice_type(g,
+//                array_type->data.structure.fields[0].type_entry->data.pointer.child_type,
+//                node->data.slice_expr.is_const);
+//    } else {
+//        add_node_error(g, node,
+//            buf_sprintf("slice of non-array type '%s'", buf_ptr(&array_type->name)));
+//        return_type = g->builtin_types.entry_invalid;
+//    }
+//
+//    if (return_type->id != TypeTableEntryIdInvalid) {
+//        node->data.slice_expr.resolved_struct_val_expr.type_entry = return_type;
+//        node->data.slice_expr.resolved_struct_val_expr.source_node = node;
+//        context->fn_entry->struct_val_expr_alloca_list.append(&node->data.slice_expr.resolved_struct_val_expr);
+//    }
+//
+//    analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.slice_expr.start);
+//
+//    if (node->data.slice_expr.end) {
+//        analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.slice_expr.end);
+//    }
+//
+//    return return_type;
+//}
+//
+//static TypeTableEntry *analyze_array_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node, LValPurpose purpose)
+//{
+//    TypeTableEntry *array_type = analyze_expression(g, import, context, nullptr,
+//            node->data.array_access_expr.array_ref_expr);
+//
+//    TypeTableEntry *return_type;
+//
+//    if (array_type->id == TypeTableEntryIdInvalid) {
+//        return_type = g->builtin_types.entry_invalid;
+//    } else if (array_type->id == TypeTableEntryIdArray) {
+//        if (array_type->data.array.len == 0) {
+//            add_node_error(g, node, buf_sprintf("out of bounds array access"));
+//        }
+//        return_type = array_type->data.array.child_type;
+//    } else if (array_type->id == TypeTableEntryIdPointer) {
+//        if (array_type->data.pointer.is_const && purpose == LValPurposeAssign) {
+//            add_node_error(g, node, buf_sprintf("cannot assign to constant"));
+//            return g->builtin_types.entry_invalid;
+//        }
+//        return_type = array_type->data.pointer.child_type;
+//    } else if (array_type->id == TypeTableEntryIdStruct &&
+//               array_type->data.structure.is_slice)
+//    {
+//        TypeTableEntry *pointer_type = array_type->data.structure.fields[0].type_entry;
+//        if (pointer_type->data.pointer.is_const && purpose == LValPurposeAssign) {
+//            add_node_error(g, node, buf_sprintf("cannot assign to constant"));
+//            return g->builtin_types.entry_invalid;
+//        }
+//        return_type = pointer_type->data.pointer.child_type;
+//    } else {
+//        add_node_error(g, node,
+//                buf_sprintf("array access of non-array type '%s'", buf_ptr(&array_type->name)));
+//        return_type = g->builtin_types.entry_invalid;
+//    }
+//
+//    analyze_expression(g, import, context, g->builtin_types.entry_usize, node->data.array_access_expr.subscript);
+//
+//    return return_type;
+//}
+//
+//static TypeTableEntry *analyze_logic_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        AstNode *node)
+//{
+//    assert(node->type == NodeTypeBinOpExpr);
+//    BinOpType bin_op_type = node->data.bin_op_expr.bin_op;
+//
+//    AstNode *op1 = node->data.bin_op_expr.op1;
+//    AstNode *op2 = node->data.bin_op_expr.op2;
+//    TypeTableEntry *op1_type = analyze_expression(g, import, context, g->builtin_types.entry_bool, op1);
+//    TypeTableEntry *op2_type = analyze_expression(g, import, context, g->builtin_types.entry_bool, op2);
+//
+//    if (op1_type->id == TypeTableEntryIdInvalid ||
+//        op2_type->id == TypeTableEntryIdInvalid)
+//    {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *op1_val = &get_resolved_expr(op1)->const_val;
+//    ConstExprValue *op2_val = &get_resolved_expr(op2)->const_val;
+//    if (!op1_val->ok || !op2_val->ok) {
+//        return g->builtin_types.entry_bool;
+//    }
+//
+//    ConstExprValue *out_val = &get_resolved_expr(node)->const_val;
+//    eval_const_expr_bin_op(op1_val, op1_type, bin_op_type, op2_val, op2_type, out_val);
+//    return g->builtin_types.entry_bool;
+//}
+//
+//static TypeTableEntry *analyze_array_mult(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeBinOpExpr);
+//    assert(node->data.bin_op_expr.bin_op == BinOpTypeArrayMult);
+//
+//    AstNode **op1 = node->data.bin_op_expr.op1->parent_field;
+//    AstNode **op2 = node->data.bin_op_expr.op2->parent_field;
+//
+//    TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1);
+//    TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2);
+//
+//    if (op1_type->id == TypeTableEntryIdInvalid ||
+//        op2_type->id == TypeTableEntryIdInvalid)
+//    {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val;
+//    ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val;
+//
+//    AstNode *bad_node;
+//    if (!op1_val->ok) {
+//        bad_node = *op1;
+//    } else if (!op2_val->ok) {
+//        bad_node = *op2;
+//    } else {
+//        bad_node = nullptr;
+//    }
+//    if (bad_node) {
+//        add_node_error(g, bad_node, buf_sprintf("array multiplication requires constant expression"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (op1_type->id != TypeTableEntryIdArray) {
+//        add_node_error(g, *op1,
+//            buf_sprintf("expected array type, got '%s'", buf_ptr(&op1_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (op2_type->id != TypeTableEntryIdNumLitInt &&
+//        op2_type->id != TypeTableEntryIdInt)
+//    {
+//        add_node_error(g, *op2, buf_sprintf("expected integer type, got '%s'", buf_ptr(&op2_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (op2_val->data.x_bignum.is_negative) {
+//        add_node_error(g, *op2, buf_sprintf("expected positive number"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    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;
+//
+//    TypeTableEntry *child_type = op1_type->data.array.child_type;
+//    BigNum old_array_len;
+//    bignum_init_unsigned(&old_array_len, op1_type->data.array.len);
+//
+//    BigNum new_array_len;
+//    if (bignum_mul(&new_array_len, &old_array_len, &op2_val->data.x_bignum)) {
+//        add_node_error(g, node, buf_sprintf("operation results in overflow"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    uint64_t old_array_len_bare = op1_type->data.array.len;
+//    uint64_t operand_amt = op2_val->data.x_bignum.data.x_uint;
+//
+//    uint64_t new_array_len_bare = new_array_len.data.x_uint;
+//    const_val->data.x_array.fields = allocate<ConstExprValue*>(new_array_len_bare);
+//
+//    uint64_t i = 0;
+//    for (uint64_t x = 0; x < operand_amt; x += 1) {
+//        for (uint64_t y = 0; y < old_array_len_bare; y += 1) {
+//            const_val->data.x_array.fields[i] = op1_val->data.x_array.fields[y];
+//            i += 1;
+//        }
+//    }
+//
+//    return get_array_type(g, child_type, new_array_len_bare);
+//}
+//
+//static TypeTableEntry *analyze_unwrap_error_expr(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *parent_context, TypeTableEntry *expected_type, AstNode *node)
+//{
+//    AstNode *op1 = node->data.unwrap_err_expr.op1;
+//    AstNode *op2 = node->data.unwrap_err_expr.op2;
+//    AstNode *var_node = node->data.unwrap_err_expr.symbol;
+//
+//    TypeTableEntry *lhs_type = analyze_expression(g, import, parent_context, nullptr, op1);
+//    if (lhs_type->id == TypeTableEntryIdInvalid) {
+//        return lhs_type;
+//    } else if (lhs_type->id == TypeTableEntryIdErrorUnion) {
+//        TypeTableEntry *child_type = lhs_type->data.error.child_type;
+//        BlockContext *child_context;
+//        if (var_node) {
+//            child_context = new_block_context(node, parent_context);
+//            var_node->block_context = child_context;
+//            Buf *var_name = var_node->data.symbol_expr.symbol;
+//            node->data.unwrap_err_expr.var = add_local_var(g, var_node, import, child_context, var_name,
+//                    g->builtin_types.entry_pure_error, true, nullptr);
+//        } else {
+//            child_context = parent_context;
+//        }
+//
+//        analyze_expression(g, import, child_context, child_type, op2);
+//        return child_type;
+//    } else {
+//        add_node_error(g, op1,
+//            buf_sprintf("expected error type, got '%s'", buf_ptr(&lhs_type->name)));
+//        return g->builtin_types.entry_invalid;
+//    }
+//}
+//
+//
+//static VariableTableEntry *analyze_variable_declaration_raw(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *source_node,
+//        AstNodeVariableDeclaration *variable_declaration,
+//        bool expr_is_maybe, AstNode *decl_node, bool var_is_ptr)
+//{
+//    bool is_const = variable_declaration->is_const;
+//    bool is_export = (variable_declaration->top_level_decl.visib_mod == VisibModExport);
+//    bool is_extern = variable_declaration->is_extern;
+//
+//    TypeTableEntry *explicit_type = nullptr;
+//    if (variable_declaration->type != nullptr) {
+//        explicit_type = analyze_type_expr(g, import, context, variable_declaration->type);
+//        if (explicit_type->id == TypeTableEntryIdUnreachable) {
+//            add_node_error(g, variable_declaration->type,
+//                buf_sprintf("variable of type 'unreachable' not allowed"));
+//            explicit_type = g->builtin_types.entry_invalid;
+//        }
+//    }
+//
+//    TypeTableEntry *implicit_type = nullptr;
+//    if (explicit_type && explicit_type->id == TypeTableEntryIdInvalid) {
+//        implicit_type = explicit_type;
+//    } else if (variable_declaration->expr) {
+//        implicit_type = analyze_expression(g, import, context, explicit_type, variable_declaration->expr);
+//        if (implicit_type->id == TypeTableEntryIdInvalid) {
+//            // ignore the poison value
+//        } else if (expr_is_maybe) {
+//            if (implicit_type->id == TypeTableEntryIdMaybe) {
+//                if (var_is_ptr) {
+//                    // TODO if the expression is constant, can't get pointer to it
+//                    implicit_type = get_pointer_to_type(g, implicit_type->data.maybe.child_type, false);
+//                } else {
+//                    implicit_type = implicit_type->data.maybe.child_type;
+//                }
+//            } else {
+//                add_node_error(g, variable_declaration->expr, buf_sprintf("expected maybe type"));
+//                implicit_type = g->builtin_types.entry_invalid;
+//            }
+//        } else if (implicit_type->id == TypeTableEntryIdUnreachable) {
+//            add_node_error(g, source_node,
+//                buf_sprintf("variable initialization is unreachable"));
+//            implicit_type = g->builtin_types.entry_invalid;
+//        } else if ((!is_const || is_export) &&
+//                (implicit_type->id == TypeTableEntryIdNumLitFloat ||
+//                 implicit_type->id == TypeTableEntryIdNumLitInt))
+//        {
+//            add_node_error(g, source_node, buf_sprintf("unable to infer variable type"));
+//            implicit_type = g->builtin_types.entry_invalid;
+//        } else if (implicit_type->id == TypeTableEntryIdMetaType && !is_const) {
+//            add_node_error(g, source_node, buf_sprintf("variable of type 'type' must be constant"));
+//            implicit_type = g->builtin_types.entry_invalid;
+//        }
+//        if (implicit_type->id != TypeTableEntryIdInvalid && !context->fn_entry) {
+//            ConstExprValue *const_val = &get_resolved_expr(variable_declaration->expr)->const_val;
+//            if (!const_val->ok) {
+//                add_node_error(g, first_executing_node(variable_declaration->expr),
+//                        buf_sprintf("global variable initializer requires constant expression"));
+//            }
+//        }
+//    } else if (!is_extern) {
+//        add_node_error(g, source_node, buf_sprintf("variables must be initialized"));
+//        implicit_type = g->builtin_types.entry_invalid;
+//    }
+//
+//    TypeTableEntry *type = explicit_type != nullptr ? explicit_type : implicit_type;
+//    assert(type != nullptr); // should have been caught by the parser
+//
+//    VariableTableEntry *var = add_local_var(g, source_node, import, context,
+//            variable_declaration->symbol, type, is_const,
+//            expr_is_maybe ? nullptr : variable_declaration->expr);
+//
+//    variable_declaration->variable = var;
+//
+//    return var;
+//}
+//
+//static VariableTableEntry *analyze_variable_declaration(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, TypeTableEntry *expected_type, AstNode *node)
+//{
+//    AstNodeVariableDeclaration *variable_declaration = &node->data.variable_declaration;
+//    return analyze_variable_declaration_raw(g, import, context, node, variable_declaration,
+//            false, nullptr, false);
+//}
+//
+//static TypeTableEntry *analyze_array_type(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    AstNode *size_node = node->data.array_type.size;
+//
+//    TypeTableEntry *child_type = analyze_type_expr_pointer_only(g, import, context,
+//            node->data.array_type.child_type, true);
+//
+//    if (child_type->id == TypeTableEntryIdUnreachable) {
+//        add_node_error(g, node, buf_create_from_str("array of unreachable not allowed"));
+//        return g->builtin_types.entry_invalid;
+//    } else if (child_type->id == TypeTableEntryIdInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (size_node) {
+//        child_type = analyze_type_expr(g, import, context, node->data.array_type.child_type);
+//        TypeTableEntry *size_type = analyze_expression(g, import, context,
+//                g->builtin_types.entry_usize, size_node);
+//        if (size_type->id == TypeTableEntryIdInvalid) {
+//            return g->builtin_types.entry_invalid;
+//        }
+//
+//        ConstExprValue *const_val = &get_resolved_expr(size_node)->const_val;
+//        if (const_val->ok) {
+//            if (const_val->data.x_bignum.is_negative) {
+//                add_node_error(g, size_node,
+//                    buf_sprintf("array size %s is negative",
+//                        buf_ptr(bignum_to_buf(&const_val->data.x_bignum))));
+//                return g->builtin_types.entry_invalid;
+//            } else {
+//                return resolve_expr_const_val_as_type(g, node,
+//                        get_array_type(g, child_type, const_val->data.x_bignum.data.x_uint), false);
+//            }
+//        } else if (context->fn_entry) {
+//            return resolve_expr_const_val_as_type(g, node,
+//                    get_slice_type(g, child_type, node->data.array_type.is_const), false);
+//        } else {
+//            add_node_error(g, first_executing_node(size_node),
+//                    buf_sprintf("unable to evaluate constant expression"));
+//            return g->builtin_types.entry_invalid;
+//        }
+//    } else {
+//        TypeTableEntry *slice_type = get_slice_type(g, child_type, node->data.array_type.is_const);
+//        return resolve_expr_const_val_as_type(g, node, slice_type, false);
+//    }
+//}
+//
+//static TypeTableEntry *analyze_while_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeWhileExpr);
+//
+//    AstNode **condition_node = &node->data.while_expr.condition;
+//    AstNode *while_body_node = node->data.while_expr.body;
+//    AstNode **continue_expr_node = &node->data.while_expr.continue_expr;
+//
+//    TypeTableEntry *condition_type = analyze_expression(g, import, context,
+//            g->builtin_types.entry_bool, *condition_node);
+//
+//    if (*continue_expr_node) {
+//        analyze_expression(g, import, context, g->builtin_types.entry_void, *continue_expr_node);
+//    }
+//
+//    BlockContext *child_context = new_block_context(node, context);
+//    child_context->parent_loop_node = node;
+//
+//    analyze_expression(g, import, child_context, g->builtin_types.entry_void, while_body_node);
+//
+//
+//    TypeTableEntry *expr_return_type = g->builtin_types.entry_void;
+//
+//    if (condition_type->id == TypeTableEntryIdInvalid) {
+//        expr_return_type = g->builtin_types.entry_invalid;
+//    } else {
+//        // if the condition is a simple constant expression and there are no break statements
+//        // then the return type is unreachable
+//        ConstExprValue *const_val = &get_resolved_expr(*condition_node)->const_val;
+//        if (const_val->ok) {
+//            if (const_val->data.x_bool) {
+//                node->data.while_expr.condition_always_true = true;
+//                if (!node->data.while_expr.contains_break) {
+//                    expr_return_type = g->builtin_types.entry_unreachable;
+//                }
+//            }
+//        }
+//    }
+//
+//    return expr_return_type;
+//}
+//
+//static TypeTableEntry *analyze_break_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeBreak);
+//
+//    AstNode *loop_node = context->parent_loop_node;
+//    if (loop_node) {
+//        if (loop_node->type == NodeTypeWhileExpr) {
+//            loop_node->data.while_expr.contains_break = true;
+//        } else if (loop_node->type == NodeTypeForExpr) {
+//            loop_node->data.for_expr.contains_break = true;
+//        } else {
+//            zig_unreachable();
+//        }
+//    } else {
+//        add_node_error(g, node, buf_sprintf("'break' expression outside loop"));
+//    }
+//    return g->builtin_types.entry_unreachable;
+//}
+//
+//static TypeTableEntry *analyze_continue_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    AstNode *loop_node = context->parent_loop_node;
+//    if (loop_node) {
+//        if (loop_node->type == NodeTypeWhileExpr) {
+//            loop_node->data.while_expr.contains_continue = true;
+//        } else if (loop_node->type == NodeTypeForExpr) {
+//            loop_node->data.for_expr.contains_continue = true;
+//        } else {
+//            zig_unreachable();
+//        }
+//    } else {
+//        add_node_error(g, node, buf_sprintf("'continue' expression outside loop"));
+//    }
+//    return g->builtin_types.entry_unreachable;
+//}
+//
+//static TypeTableEntry *analyze_defer(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    if (!parent_context->fn_entry) {
+//        add_node_error(g, node, buf_sprintf("defer expression outside function definition"));
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (!node->data.defer.expr) {
+//        add_node_error(g, node, buf_sprintf("defer expects an expression"));
+//        return g->builtin_types.entry_void;
+//    }
+//
+//    node->data.defer.child_block = new_block_context(node, parent_context);
+//
+//    TypeTableEntry *resolved_type = analyze_expression(g, import, parent_context, nullptr,
+//            node->data.defer.expr);
+//    validate_voided_expr(g, node->data.defer.expr, resolved_type);
+//
+//    return g->builtin_types.entry_void;
+//}
+//
+//static TypeTableEntry *analyze_string_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    if (node->data.string_literal.c) {
+//        return resolve_expr_const_val_as_c_string_lit(g, node, node->data.string_literal.buf);
+//    } else {
+//        return resolve_expr_const_val_as_string_lit(g, node, node->data.string_literal.buf);
+//    }
+//}
+//
+//static TypeTableEntry *analyze_block_expr(CodeGen *g, ImportTableEntry *import, BlockContext *parent_context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    BlockContext *child_context = new_block_context(node, parent_context);
+//    node->data.block.child_block = child_context;
+//    TypeTableEntry *return_type = g->builtin_types.entry_void;
+//
+//    for (size_t i = 0; i < node->data.block.statements.length; i += 1) {
+//        AstNode *child = node->data.block.statements.at(i);
+//        if (child->type == NodeTypeLabel) {
+//            FnTableEntry *fn_table_entry = child_context->fn_entry;
+//            assert(fn_table_entry);
+//
+//            LabelTableEntry *label = allocate<LabelTableEntry>(1);
+//            label->decl_node = child;
+//            label->entered_from_fallthrough = (return_type->id != TypeTableEntryIdUnreachable);
+//
+//            child->block_context = child_context;
+//            child->data.label.label_entry = label;
+//            fn_table_entry->all_labels.append(label);
+//
+//            child_context->label_table.put(child->data.label.name, label);
+//
+//            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, child_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 (child->type == NodeTypeDefer && return_type->id != TypeTableEntryIdInvalid) {
+//            // defer starts a new block context
+//            child_context = child->data.defer.child_block;
+//            assert(child_context);
+//        }
+//        if (!is_last) {
+//            validate_voided_expr(g, child, return_type);
+//        }
+//    }
+//    node->data.block.nested_block = child_context;
+//
+//    ConstExprValue *const_val = &node->data.block.resolved_expr.const_val;
+//    if (node->data.block.statements.length == 0) {
+//        const_val->ok = true;
+//    } else if (node->data.block.statements.length == 1) {
+//        AstNode *only_node = node->data.block.statements.at(0);
+//        ConstExprValue *other_const_val = &get_resolved_expr(only_node)->const_val;
+//        if (other_const_val->ok) {
+//            *const_val = *other_const_val;
+//        }
+//    }
+//
+//    return return_type;
+//}
+//
+//static TypeTableEntry *analyze_asm_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    mark_impure_fn(g, context, node);
+//
+//    node->data.asm_expr.return_count = 0;
+//    TypeTableEntry *return_type = g->builtin_types.entry_void;
+//    for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1) {
+//        AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
+//        if (asm_output->return_type) {
+//            node->data.asm_expr.return_count += 1;
+//            return_type = analyze_type_expr(g, import, context, asm_output->return_type);
+//            if (node->data.asm_expr.return_count > 1) {
+//                add_node_error(g, node,
+//                    buf_sprintf("inline assembly allows up to one output value"));
+//                break;
+//            }
+//        } else {
+//            Buf *variable_name = asm_output->variable_name;
+//            VariableTableEntry *var = find_variable(g, context, variable_name);
+//            if (var) {
+//                asm_output->variable = var;
+//                return var->type;
+//            } else {
+//                add_node_error(g, node,
+//                        buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
+//                return g->builtin_types.entry_invalid;
+//            }
+//        }
+//    }
+//    for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1) {
+//        AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
+//        analyze_expression(g, import, context, nullptr, asm_input->expr);
+//    }
+//
+//    return return_type;
+//}
+//
+//static TypeTableEntry *analyze_error_literal_expr(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *context, AstNode *node, Buf *err_name)
+//{
+//    auto err_table_entry = g->error_table.maybe_get(err_name);
+//
+//    if (err_table_entry) {
+//        return resolve_expr_const_val_as_err(g, node, err_table_entry->value);
+//    }
+//
+//    add_node_error(g, node,
+//            buf_sprintf("use of undeclared error value '%s'", buf_ptr(err_name)));
+//
+//    return g->builtin_types.entry_invalid;
+//}
+//
+//static TypeTableEntry *analyze_symbol_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node, bool pointer_only)
+//{
+//    Buf *variable_name = node->data.symbol_expr.symbol;
+//
+//    auto primitive_table_entry = g->primitive_type_table.maybe_get(variable_name);
+//    if (primitive_table_entry) {
+//        return resolve_expr_const_val_as_type(g, node, primitive_table_entry->value, false);
+//    }
+//
+//    VariableTableEntry *var = find_variable(g, context, variable_name);
+//    if (var) {
+//        TypeTableEntry *var_type = analyze_var_ref(g, node, var, context, false);
+//        return var_type;
+//    }
+//
+//    AstNode *decl_node = find_decl(context, variable_name);
+//    if (decl_node) {
+//        return analyze_decl_ref(g, node, decl_node, pointer_only, context, false);
+//    }
+//
+//    if (import->any_imports_failed) {
+//        // skip the error message since we had a failing import in this file
+//        // if an import breaks we don't need 9999 undeclared identifier errors
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    mark_impure_fn(g, context, node);
+//    add_node_error(g, node, buf_sprintf("use of undeclared identifier '%s'", buf_ptr(variable_name)));
+//    return g->builtin_types.entry_invalid;
+//}
+//
+//static TypeTableEntry *analyze_decl_ref(CodeGen *g, AstNode *source_node, AstNode *decl_node,
+//        bool pointer_only, BlockContext *block_context, bool depends_on_compile_var)
+//{
+//    resolve_top_level_decl(g, decl_node, pointer_only);
+//    TopLevelDecl *tld = get_as_top_level_decl(decl_node);
+//    if (tld->resolution == TldResolutionInvalid) {
+//        return g->builtin_types.entry_invalid;
+//    }
+//
+//    if (decl_node->type == NodeTypeVariableDeclaration) {
+//        VariableTableEntry *var = decl_node->data.variable_declaration.variable;
+//        return analyze_var_ref(g, source_node, var, block_context, depends_on_compile_var);
+//    } else if (decl_node->type == NodeTypeFnProto) {
+//        FnTableEntry *fn_entry = decl_node->data.fn_proto.fn_table_entry;
+//        assert(fn_entry->type_entry);
+//        if (fn_entry->type_entry->id == TypeTableEntryIdGenericFn) {
+//            return resolve_expr_const_val_as_generic_fn(g, source_node, fn_entry->type_entry, depends_on_compile_var);
+//        } else {
+//            return resolve_expr_const_val_as_fn(g, source_node, fn_entry, depends_on_compile_var);
+//        }
+//    } else if (decl_node->type == NodeTypeContainerDecl) {
+//        if (decl_node->data.struct_decl.generic_params.length > 0) {
+//            TypeTableEntry *type_entry = decl_node->data.struct_decl.generic_fn_type;
+//            assert(type_entry);
+//            return resolve_expr_const_val_as_generic_fn(g, source_node, type_entry, depends_on_compile_var);
+//        } else {
+//            return resolve_expr_const_val_as_type(g, source_node, decl_node->data.struct_decl.type_entry,
+//                    depends_on_compile_var);
+//        }
+//    } else if (decl_node->type == NodeTypeTypeDecl) {
+//        return resolve_expr_const_val_as_type(g, source_node, decl_node->data.type_decl.child_type_entry,
+//                depends_on_compile_var);
+//    } else {
+//        zig_unreachable();
+//    }
+//}
+//
+//static TypeTableEntry *analyze_var_ref(CodeGen *g, AstNode *source_node, VariableTableEntry *var,
+//        BlockContext *context, bool depends_on_compile_var)
+//{
+//    get_resolved_expr(source_node)->variable = var;
+//    if (!var_is_pure(var, context)) {
+//        mark_impure_fn(g, context, source_node);
+//    }
+//    if (var->src_is_const && var->val_node) {
+//        ConstExprValue *other_const_val = &get_resolved_expr(var->val_node)->const_val;
+//        if (other_const_val->ok) {
+//            return resolve_expr_const_val_as_other_expr(g, source_node, var->val_node,
+//                    depends_on_compile_var || var->force_depends_on_compile_var);
+//        }
+//    }
+//    return var->type;
+//}
+//
+//static TypeTableEntry *analyze_null_literal_expr(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeNullLiteral);
+//
+//    ConstExprValue *const_val = &get_resolved_expr(node)->const_val;
+//    const_val->ok = true;
+//
+//    return g->builtin_types.entry_null;
+//}
+//
+//static TypeTableEntry *analyze_undefined_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    assert(node->type == NodeTypeUndefinedLiteral);
+//
+//    Expr *expr = get_resolved_expr(node);
+//    ConstExprValue *const_val = &expr->const_val;
+//
+//    const_val->ok = true;
+//    const_val->special = ConstValSpecialUndef;
+//
+//    return expected_type ? expected_type : g->builtin_types.entry_undef;
+//}
+//
+//static TypeTableEntry *analyze_zeroes_literal_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    Expr *expr = get_resolved_expr(node);
+//    ConstExprValue *const_val = &expr->const_val;
+//
+//    const_val->ok = true;
+//    const_val->special = ConstValSpecialZeroes;
+//
+//    return expected_type ? expected_type : g->builtin_types.entry_undef;
+//}
+//
+//static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry *import,
+//        BlockContext *block_context, TypeTableEntry *expected_type, AstNode *node)
+//{
+//    return resolve_expr_const_val_as_bignum(g, node, expected_type, node->data.number_literal.bignum, false);
+//}
+//
+//static TypeTableEntry *analyze_fn_proto_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+//        TypeTableEntry *expected_type, AstNode *node)
+//{
+//    TypeTableEntry *type_entry = analyze_fn_proto_type(g, import, context, expected_type, node,
+//            false, false, nullptr);
+//
+//    if (type_entry->id == TypeTableEntryIdInvalid) {
+//        return type_entry;
+//    }
+//
+//    return resolve_expr_const_val_as_type(g, node, type_entry, false);
+//}
+//
+//static bool var_is_pure(VariableTableEntry *var, BlockContext *context) {
+//    if (var->block_context->fn_entry == context->fn_entry) {
+//        // variable was declared in the current function, so it's OK.
+//        return true;
+//    }
+//    return var->src_is_const && var->type->deep_const;
+//}
+//
+//static void validate_voided_expr(CodeGen *g, AstNode *source_node, TypeTableEntry *type_entry) {
+//    if (type_entry->id == TypeTableEntryIdMetaType) {
+//        add_node_error(g, first_executing_node(source_node), buf_sprintf("expected expression, found type"));
+//    } else if (type_entry->id == TypeTableEntryIdErrorUnion) {
+//        add_node_error(g, first_executing_node(source_node), buf_sprintf("statement ignores error value"));
+//    }
+//}
+//
src/ir_print.cpp
@@ -78,6 +78,12 @@ static void ir_print_const_value(IrPrint *irp, TypeTableEntry *type_entry, Const
                 fprintf(irp->f, "%s", buf_ptr(&fn_entry->symbol_name));
                 break;
             }
+        case TypeTableEntryIdBlock:
+            {
+                AstNode *node = const_val->data.x_block->node;
+                fprintf(irp->f, "(scope:%zu:%zu)", node->line + 1, node->column + 1);
+                break;
+            }
         case TypeTableEntryIdVar:
         case TypeTableEntryIdFloat:
         case TypeTableEntryIdArray:
@@ -91,7 +97,6 @@ static void ir_print_const_value(IrPrint *irp, TypeTableEntry *type_entry, Const
         case TypeTableEntryIdUnion:
         case TypeTableEntryIdTypeDecl:
         case TypeTableEntryIdNamespace:
-        case TypeTableEntryIdBlock:
         case TypeTableEntryIdGenericFn:
             zig_panic("TODO render more constant types in IR printer");
     }
@@ -263,18 +268,6 @@ static void ir_print_call(IrPrint *irp, IrInstructionCall *call_instruction) {
     fprintf(irp->f, ")");
 }
 
-static void ir_print_builtin_call(IrPrint *irp, IrInstructionBuiltinCall *call_instruction) {
-    fprintf(irp->f, "@%s(", buf_ptr(&call_instruction->fn->name));
-    for (size_t i = 0; i < call_instruction->fn->param_count; i += 1) {
-        IrInstruction *arg = call_instruction->args[i];
-        if (i != 0)
-            fprintf(irp->f, ", ");
-        ir_print_other_instruction(irp, arg);
-    }
-    fprintf(irp->f, ")");
-}
-
-
 static void ir_print_cond_br(IrPrint *irp, IrInstructionCondBr *cond_br_instruction) {
     const char *inline_kw = cond_br_instruction->is_inline ? "inline " : "";
     fprintf(irp->f, "%sif (", inline_kw);
@@ -393,6 +386,14 @@ static void ir_print_struct_field_ptr(IrPrint *irp, IrInstructionStructFieldPtr
     fprintf(irp->f, ")");
 }
 
+static void ir_print_set_fn_test(IrPrint *irp, IrInstructionSetFnTest *instruction) {
+    fprintf(irp->f, "@setFnTest(");
+    ir_print_other_instruction(irp, instruction->fn_value);
+    fprintf(irp->f, ", ");
+    ir_print_other_instruction(irp, instruction->is_test);
+    fprintf(irp->f, ")");
+}
+
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     ir_print_prefix(irp, instruction);
     switch (instruction->id) {
@@ -425,9 +426,6 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdBr:
             ir_print_br(irp, (IrInstructionBr *)instruction);
             break;
-        case IrInstructionIdBuiltinCall:
-            ir_print_builtin_call(irp, (IrInstructionBuiltinCall *)instruction);
-            break;
         case IrInstructionIdPhi:
             ir_print_phi(irp, (IrInstructionPhi *)instruction);
             break;
@@ -470,6 +468,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdStructFieldPtr:
             ir_print_struct_field_ptr(irp, (IrInstructionStructFieldPtr *)instruction);
             break;
+        case IrInstructionIdSetFnTest:
+            ir_print_set_fn_test(irp, (IrInstructionSetFnTest *)instruction);
+            break;
         case IrInstructionIdSwitchBr:
             zig_panic("TODO print more IR instructions");
     }
src/parseh.cpp
@@ -230,9 +230,7 @@ static AstNode *create_type_decl_node(Context *c, const char *name, AstNode *chi
 }
 
 static AstNode *make_type_node(Context *c, TypeTableEntry *type_entry) {
-    AstNode *node = create_node(c, NodeTypeSymbol);
-    node->data.symbol_expr.override_type_entry = type_entry;
-    return node;
+    zig_panic("TODO bypass AST in parseh");
 }
 
 static AstNode *create_fn_proto_node(Context *c, Buf *name, TypeTableEntry *fn_type) {