Commit b581da41f8

Andrew Kelley <superjoe30@gmail.com>
2016-09-28 08:33:32
remove compiler directives
* add `setFnTest`, `setFnVisible`, `setFnStaticEval`, `setFnNoInline` builtin functions to replace previous directive functionality * add `coldcc` and `nakedcc` as keywords which can be used as part of a function prototype. * `setDebugSafety` builtin can be used to set debug safety features at a per block scope level. * closes #169
1 parent e5fd8ef
doc/vim/syntax/zig.vim
@@ -8,7 +8,7 @@ if exists("b:current_syntax")
 endif
 let b:current_syntax = "zig"
 
-syn keyword zigStorage const var extern export pub noalias inline noinline
+syn keyword zigStorage const var extern export pub noalias inline nakedcc coldcc
 syn keyword zigStructure struct enum union
 syn keyword zigStatement goto break return continue asm defer
 syn keyword zigConditional if else switch
doc/langref.md
@@ -3,9 +3,11 @@
 ## Grammar
 
 ```
-Root = many(TopLevelDecl) "EOF"
+Root = many(TopLevelItem) "EOF"
 
-TopLevelDecl = many(Directive) option(VisibleMod) (FnDef | ExternDecl | ContainerDecl | GlobalVarDecl | ErrorValueDecl | TypeDecl | UseDecl)
+TopLevelItem = ErrorValueDecl | Block | TopLevelDecl
+
+TopLevelDecl = option(VisibleMod) (FnDef | ExternDecl | ContainerDecl | GlobalVarDecl | TypeDecl | UseDecl)
 
 TypeDecl = "type" Symbol "=" TypeExpr ";"
 
@@ -17,7 +19,7 @@ VariableDeclaration = ("var" | "const") Symbol option(":" TypeExpr) "=" Expressi
 
 ContainerDecl = ("struct" | "enum" | "union") Symbol option(ParamDeclList) "{" many(StructMember) "}"
 
-StructMember = many(Directive) option(VisibleMod) (StructField | FnDef | GlobalVarDecl | ContainerDecl)
+StructMember = (StructField | FnDef | GlobalVarDecl | ContainerDecl)
 
 StructField = Symbol option(":" Expression) ",")
 
@@ -25,9 +27,7 @@ UseDecl = "use" Expression ";"
 
 ExternDecl = "extern" (FnProto | VariableDeclaration) ";"
 
-FnProto = "fn" option(Symbol) ParamDeclList option("->" TypeExpr)
-
-Directive = "#" Symbol "(" Expression ")"
+FnProto = option("coldcc" | "nakedcc") "fn" option(Symbol) ParamDeclList option("->" TypeExpr)
 
 VisibleMod = "pub" | "export"
 
src/all_types.hpp
@@ -129,7 +129,6 @@ enum TldResolution {
 struct TopLevelDecl {
     // populated by parser
     Buf *name;
-    ZigList<AstNode *> *directives;
     VisibMod visib_mod;
 
     // populated by semantic analyzer
@@ -153,7 +152,6 @@ enum NodeType {
     NodeTypeFnDecl,
     NodeTypeParamDecl,
     NodeTypeBlock,
-    NodeTypeDirective,
     NodeTypeReturnExpr,
     NodeTypeDefer,
     NodeTypeVariableDeclaration,
@@ -210,6 +208,8 @@ struct AstNodeFnProto {
     bool is_var_args;
     bool is_extern;
     bool is_inline;
+    bool is_coldcc;
+    bool is_nakedcc;
 
     // populated by semantic analyzer:
 
@@ -459,11 +459,6 @@ struct AstNodeFieldAccessExpr {
     AstNode *container_init_expr_node;
 };
 
-struct AstNodeDirective {
-    Buf *name;
-    AstNode *expr;
-};
-
 enum PrefixOp {
     PrefixOpInvalid,
     PrefixOpBoolNot,
@@ -802,7 +797,6 @@ struct AstNode {
         AstNodeErrorValueDecl error_value_decl;
         AstNodeBinOpExpr bin_op_expr;
         AstNodeUnwrapErrorExpr unwrap_err_expr;
-        AstNodeDirective directive;
         AstNodePrefixOpExpr prefix_op_expr;
         AstNodeFnCallExpr fn_call_expr;
         AstNodeArrayAccessExpr array_access_expr;
@@ -1109,10 +1103,14 @@ struct FnTableEntry {
     WantPure want_pure;
     AstNode *want_pure_attr_node;
     AstNode *want_pure_return_type;
-    bool safety_off;
     FnInline fn_inline;
     FnAnalState anal_state;
 
+    AstNode *fn_no_inline_set_node;
+    AstNode *fn_export_set_node;
+    AstNode *fn_test_set_node;
+    AstNode *fn_static_eval_set_node;
+
     ZigList<AstNode *> cast_alloca_list;
     ZigList<StructValExprCodeGen *> struct_val_expr_alloca_list;
     ZigList<VariableTableEntry *> variable_list;
@@ -1154,6 +1152,11 @@ enum BuiltinFnId {
     BuiltinFnIdTruncate,
     BuiltinFnIdIntType,
     BuiltinFnIdUnreachable,
+    BuiltinFnIdSetFnTest,
+    BuiltinFnIdSetFnVisible,
+    BuiltinFnIdSetFnStaticEval,
+    BuiltinFnIdSetFnNoInline,
+    BuiltinFnIdSetDebugSafety,
 };
 
 struct BuiltinFnEntry {
@@ -1373,6 +1376,7 @@ struct BlockContext {
     bool codegen_excluded;
 
     bool safety_off;
+    AstNode *safety_set_node;
 };
 
 enum AtomicOrder {
src/analyze.cpp
@@ -75,7 +75,6 @@ static AstNode *first_executing_node(AstNode *node) {
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeBlock:
-        case NodeTypeDirective:
         case NodeTypeReturnExpr:
         case NodeTypeDefer:
         case NodeTypeVariableDeclaration:
@@ -1123,6 +1122,28 @@ static bool resolve_const_expr_bool(CodeGen *g, ImportTableEntry *import, BlockC
     return true;
 }
 
+static FnTableEntry *resolve_const_expr_fn(CodeGen *g, ImportTableEntry *import, BlockContext *context,
+        AstNode **node)
+{
+    TypeTableEntry *resolved_type = analyze_expression(g, import, context, nullptr, *node);
+
+    if (resolved_type->id == TypeTableEntryIdInvalid) {
+        return nullptr;
+    } else if (resolved_type->id == TypeTableEntryIdFn) {
+        ConstExprValue *const_val = &get_resolved_expr(*node)->const_val;
+
+        if (!const_val->ok) {
+            add_node_error(g, *node, buf_sprintf("unable to evaluate constant expression"));
+            return nullptr;
+        }
+
+        return const_val->data.x_fn;
+    } else {
+        add_node_error(g, *node, buf_sprintf("expected function, got '%s'", buf_ptr(&resolved_type->name)));
+        return nullptr;
+    }
+}
+
 static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_table_entry,
         ImportTableEntry *import, BlockContext *containing_context)
 {
@@ -1133,85 +1154,6 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
         return;
     }
 
-    bool is_cold = false;
-    bool is_naked = false;
-    bool is_test = false;
-    bool is_noinline = false;
-
-    if (fn_proto->top_level_decl.directives) {
-        for (size_t i = 0; i < fn_proto->top_level_decl.directives->length; i += 1) {
-            AstNode *directive_node = fn_proto->top_level_decl.directives->at(i);
-            Buf *name = directive_node->data.directive.name;
-
-            if (buf_eql_str(name, "attribute")) {
-                if (fn_table_entry->fn_def_node) {
-                    Buf *attr_name = resolve_const_expr_str(g, import, import->block_context,
-                            &directive_node->data.directive.expr);
-                    if (attr_name) {
-                        if (buf_eql_str(attr_name, "naked")) {
-                            is_naked = true;
-                        } else if (buf_eql_str(attr_name, "noinline")) {
-                            is_noinline = true;
-                        } else if (buf_eql_str(attr_name, "cold")) {
-                            is_cold = true;
-                        } else if (buf_eql_str(attr_name, "test")) {
-                            is_test = true;
-                            g->test_fn_count += 1;
-                        } else {
-                            add_node_error(g, directive_node,
-                                    buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
-                        }
-                    }
-                } else {
-                    add_node_error(g, directive_node,
-                            buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
-                }
-            } else if (buf_eql_str(name, "debug_safety")) {
-                if (!fn_table_entry->fn_def_node) {
-                    add_node_error(g, directive_node,
-                        buf_sprintf("#debug_safety valid only on function definitions"));
-                } else {
-                    bool enable;
-                    bool ok = resolve_const_expr_bool(g, import, import->block_context,
-                            &directive_node->data.directive.expr, &enable);
-                    if (ok && !enable) {
-                        fn_table_entry->safety_off = true;
-                    }
-                }
-            } else if (buf_eql_str(name, "condition")) {
-                if (fn_proto->top_level_decl.visib_mod == VisibModExport) {
-                    bool include;
-                    bool ok = resolve_const_expr_bool(g, import, import->block_context,
-                            &directive_node->data.directive.expr, &include);
-                    if (ok && !include) {
-                        fn_proto->top_level_decl.visib_mod = VisibModPub;
-                    }
-                } else {
-                    add_node_error(g, directive_node,
-                        buf_sprintf("#condition valid only on exported symbols"));
-                }
-            } else if (buf_eql_str(name, "static_eval_enable")) {
-                if (!fn_table_entry->fn_def_node) {
-                    add_node_error(g, directive_node,
-                        buf_sprintf("#static_val_enable valid only on function definitions"));
-                } else {
-                    bool enable;
-                    bool ok = resolve_const_expr_bool(g, import, import->block_context,
-                            &directive_node->data.directive.expr, &enable);
-                    if (!ok || !enable) {
-                        fn_table_entry->want_pure = WantPureFalse;
-                    } else if (ok && enable) {
-                        fn_table_entry->want_pure = WantPureTrue;
-                        fn_table_entry->want_pure_attr_node = directive_node->data.directive.expr;
-                    }
-                }
-            } else {
-                add_node_error(g, directive_node,
-                        buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
-            }
-        }
-    }
-
     bool is_internal = (fn_proto->top_level_decl.visib_mod != VisibModExport);
     bool is_c_compat = !is_internal || fn_proto->is_extern;
     fn_table_entry->internal_linkage = !is_c_compat;
@@ -1219,24 +1161,17 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
 
 
     TypeTableEntry *fn_type = analyze_fn_proto_type(g, import, containing_context, nullptr, node,
-            is_naked, is_cold, fn_table_entry);
+            fn_proto->is_nakedcc, fn_proto->is_coldcc, fn_table_entry);
 
     fn_table_entry->type_entry = fn_type;
-    fn_table_entry->is_test = is_test;
 
     if (fn_type->id == TypeTableEntryIdInvalid) {
         fn_proto->skip = true;
         return;
     }
 
-    if (fn_proto->is_inline && is_noinline) {
-        add_node_error(g, node, buf_sprintf("function is both inline and noinline"));
-        fn_proto->skip = true;
-        return;
-    } else if (fn_proto->is_inline) {
+    if (fn_proto->is_inline) {
         fn_table_entry->fn_inline = FnInlineAlways;
-    } else if (is_noinline) {
-        fn_table_entry->fn_inline = FnInlineNever;
     }
 
 
@@ -1881,7 +1816,6 @@ static void resolve_top_level_decl(CodeGen *g, AstNode *node, bool pointer_only)
             zig_panic("TODO resolve_top_level_decl NodeTypeUse");
             break;
         case NodeTypeFnDef:
-        case NodeTypeDirective:
         case NodeTypeParamDecl:
         case NodeTypeFnDecl:
         case NodeTypeReturnExpr:
@@ -2406,13 +2340,11 @@ BlockContext *new_block_context(AstNode *node, BlockContext *parent) {
         context->parent_loop_node = parent->parent_loop_node;
         context->c_import_buf = parent->c_import_buf;
         context->codegen_excluded = parent->codegen_excluded;
-        context->safety_off = parent->safety_off;
     }
 
     if (node && node->type == NodeTypeFnDef) {
         AstNode *fn_proto_node = node->data.fn_def.fn_proto;
         context->fn_entry = fn_proto_node->data.fn_proto.fn_table_entry;
-        context->safety_off = context->fn_entry->safety_off;
     } else if (parent) {
         context->fn_entry = parent->fn_entry;
     }
@@ -5246,6 +5178,203 @@ static TypeTableEntry *analyze_int_type(CodeGen *g, ImportTableEntry *import,
 
 }
 
+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)
 {
@@ -5607,6 +5736,16 @@ static TypeTableEntry *analyze_builtin_fn_call_expr(CodeGen *g, ImportTableEntry
             return analyze_int_type(g, import, context, node);
         case BuiltinFnIdUnreachable:
             return g->builtin_types.entry_unreachable;
+        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();
 }
@@ -6876,7 +7015,6 @@ static TypeTableEntry *analyze_expression_pointer_only(CodeGen *g, ImportTableEn
             break;
         case NodeTypeSwitchProng:
         case NodeTypeSwitchRange:
-        case NodeTypeDirective:
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeRoot:
@@ -7109,7 +7247,6 @@ static void scan_decls(CodeGen *g, ImportTableEntry *import, BlockContext *conte
             // error value declarations do not depend on other top level decls
             preview_error_value_decl(g, node);
             break;
-        case NodeTypeDirective:
         case NodeTypeParamDecl:
         case NodeTypeFnDecl:
         case NodeTypeReturnExpr:
@@ -7418,7 +7555,6 @@ Expr *get_resolved_expr(AstNode *node) {
         case NodeTypeFnDef:
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
-        case NodeTypeDirective:
         case NodeTypeUse:
         case NodeTypeContainerDecl:
         case NodeTypeStructField:
@@ -7469,7 +7605,6 @@ static TopLevelDecl *get_as_top_level_decl(AstNode *node) {
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
         case NodeTypeBlock:
-        case NodeTypeDirective:
         case NodeTypeStringLiteral:
         case NodeTypeCharLiteral:
         case NodeTypeSymbol:
src/ast_render.cpp
@@ -141,8 +141,6 @@ static const char *node_type_str(NodeType node_type) {
             return "ArrayAccessExpr";
         case NodeTypeSliceExpr:
             return "SliceExpr";
-        case NodeTypeDirective:
-            return "Directive";
         case NodeTypeReturnExpr:
             return "ReturnExpr";
         case NodeTypeDefer:
@@ -416,13 +414,6 @@ static void render_node(AstRender *ar, AstNode *node) {
             }
         case NodeTypeFnDef:
             {
-                ZigList<AstNode *> *directives =
-                    node->data.fn_def.fn_proto->data.fn_proto.top_level_decl.directives;
-                if (directives) {
-                    for (size_t i = 0; i < directives->length; i += 1) {
-                        render_node(ar, directives->at(i));
-                    }
-                }
                 render_node(ar, node->data.fn_def.fn_proto);
                 fprintf(ar->f, " ");
                 render_node(ar, node->data.fn_def.body);
@@ -445,11 +436,6 @@ static void render_node(AstRender *ar, AstNode *node) {
             print_indent(ar);
             fprintf(ar->f, "}");
             break;
-        case NodeTypeDirective:
-            fprintf(ar->f, "#%s(",  buf_ptr(node->data.directive.name));
-            render_node(ar, node->data.directive.expr);
-            fprintf(ar->f, ")\n");
-            break;
         case NodeTypeReturnExpr:
             {
                 const char *return_str = return_string(node->data.return_expr.kind);
src/codegen.cpp
@@ -352,8 +352,20 @@ static LLVMValueRef get_handle_value(CodeGen *g, AstNode *source_node, LLVMValue
     }
 }
 
+static bool want_debug_safety_recursive(CodeGen *g, BlockContext *context) {
+    if (context->safety_set_node || !context->parent) {
+        return !context->safety_off;
+    }
+    context->safety_off = want_debug_safety_recursive(g, context->parent);
+    context->safety_set_node = context->parent->safety_set_node;
+    return !context->safety_off;
+}
+
 static bool want_debug_safety(CodeGen *g, AstNode *node) {
-    return !g->is_release_build && !node->block_context->safety_off;
+    if (g->is_release_build) {
+        return false;
+    }
+    return want_debug_safety_recursive(g, node->block_context);
 }
 
 static void gen_debug_safety_crash(CodeGen *g) {
@@ -709,6 +721,13 @@ static LLVMValueRef gen_builtin_fn_call_expr(CodeGen *g, AstNode *node) {
             return gen_truncate(g, node);
         case BuiltinFnIdUnreachable:
             return gen_unreachable(g, node);
+        case BuiltinFnIdSetFnTest:
+        case BuiltinFnIdSetFnVisible:
+        case BuiltinFnIdSetFnStaticEval:
+        case BuiltinFnIdSetFnNoInline:
+        case BuiltinFnIdSetDebugSafety:
+            // do nothing
+            return nullptr;
     }
     zig_unreachable();
 }
@@ -3617,7 +3636,6 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) {
         case NodeTypeFnDef:
         case NodeTypeFnDecl:
         case NodeTypeParamDecl:
-        case NodeTypeDirective:
         case NodeTypeUse:
         case NodeTypeContainerDecl:
         case NodeTypeStructField:
@@ -4880,6 +4898,11 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn_with_arg_count(g, BuiltinFnIdCompileErr, "compileError", 1);
     create_builtin_fn_with_arg_count(g, BuiltinFnIdIntType, "intType", 2);
     create_builtin_fn_with_arg_count(g, BuiltinFnIdUnreachable, "unreachable", 0);
+    create_builtin_fn_with_arg_count(g, BuiltinFnIdSetFnTest, "setFnTest", 2);
+    create_builtin_fn_with_arg_count(g, BuiltinFnIdSetFnVisible, "setFnVisible", 2);
+    create_builtin_fn_with_arg_count(g, BuiltinFnIdSetFnStaticEval, "setFnStaticEval", 2);
+    create_builtin_fn_with_arg_count(g, BuiltinFnIdSetFnNoInline, "setFnNoInline", 2);
+    create_builtin_fn_with_arg_count(g, BuiltinFnIdSetDebugSafety, "setDebugSafety", 2);
 }
 
 static void init(CodeGen *g, Buf *source_path) {
src/eval.cpp
@@ -963,6 +963,12 @@ static bool eval_fn_call_builtin(EvalFn *ef, AstNode *node, ConstExprValue *out_
         case BuiltinFnIdCompileErr:
         case BuiltinFnIdIntType:
             zig_unreachable();
+        case BuiltinFnIdSetFnTest:
+        case BuiltinFnIdSetFnVisible:
+        case BuiltinFnIdSetFnStaticEval:
+        case BuiltinFnIdSetFnNoInline:
+        case BuiltinFnIdSetDebugSafety:
+            return false;
     }
 
     return false;
@@ -1398,7 +1404,6 @@ static bool eval_expr(EvalFn *ef, AstNode *node, ConstExprValue *out) {
         case NodeTypeUse:
         case NodeTypeAsmExpr:
         case NodeTypeParamDecl:
-        case NodeTypeDirective:
         case NodeTypeTypeDecl:
             zig_unreachable();
     }
src/parseh.cpp
@@ -124,7 +124,6 @@ static AstNode *create_typed_var_decl_node(Context *c, bool is_const, const char
     node->data.variable_declaration.is_const = is_const;
     node->data.variable_declaration.top_level_decl.visib_mod = c->visib_mod;
     node->data.variable_declaration.expr = init_node;
-    node->data.variable_declaration.top_level_decl.directives = nullptr;
     node->data.variable_declaration.type = type_node;
     normalize_parent_ptrs(node);
     return node;
src/parser.cpp
@@ -211,8 +211,7 @@ static AstNode *ast_parse_if_expr(ParseContext *pc, size_t *token_index, bool ma
 static AstNode *ast_parse_block_expr(ParseContext *pc, size_t *token_index, bool mandatory);
 static AstNode *ast_parse_unwrap_expr(ParseContext *pc, size_t *token_index, bool mandatory);
 static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, size_t *token_index, bool mandatory);
-static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory,
-        ZigList<AstNode*> *directives, VisibMod visib_mod);
+static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod);
 static AstNode *ast_parse_return_expr(ParseContext *pc, size_t *token_index);
 static AstNode *ast_parse_grouped_expr(ParseContext *pc, size_t *token_index, bool mandatory);
 
@@ -233,39 +232,6 @@ static Token *ast_eat_token(ParseContext *pc, size_t *token_index, TokenId token
     return token;
 }
 
-/*
-Directive = "#" "Symbol" "(" Expression ")"
-*/
-static AstNode *ast_parse_directive(ParseContext *pc, size_t *token_index) {
-    Token *number_sign = ast_eat_token(pc, token_index, TokenIdNumberSign);
-
-    AstNode *node = ast_create_node(pc, NodeTypeDirective, number_sign);
-
-    Token *name_symbol = ast_eat_token(pc, token_index, TokenIdSymbol);
-
-    node->data.directive.name = token_buf(name_symbol);
-
-    node->data.directive.expr = ast_parse_grouped_expr(pc, token_index, true);
-
-    normalize_parent_ptrs(node);
-    return node;
-}
-
-static void ast_parse_directives(ParseContext *pc, size_t *token_index,
-        ZigList<AstNode *> *directives)
-{
-    for (;;) {
-        Token *token = &pc->tokens->at(*token_index);
-        if (token->id == TokenIdNumberSign) {
-            AstNode *directive_node = ast_parse_directive(pc, token_index);
-            directives->append(directive_node);
-        } else {
-            return;
-        }
-    }
-    zig_unreachable();
-}
-
 /*
 TypeExpr = PrefixOpExpression | "var"
 */
@@ -686,7 +652,7 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo
         return node;
     } else if (token->id == TokenIdKeywordExtern) {
         *token_index += 1;
-        AstNode *node = ast_parse_fn_proto(pc, token_index, true, nullptr, VisibModPrivate);
+        AstNode *node = ast_parse_fn_proto(pc, token_index, true, VisibModPrivate);
         node->data.fn_proto.is_extern = true;
         return node;
     } else if (token->id == TokenIdAtSign) {
@@ -735,7 +701,7 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, size_t *token_index, bo
         return array_type_node;
     }
 
-    AstNode *fn_proto_node = ast_parse_fn_proto(pc, token_index, false, nullptr, VisibModPrivate);
+    AstNode *fn_proto_node = ast_parse_fn_proto(pc, token_index, false, VisibModPrivate);
     if (fn_proto_node) {
         return fn_proto_node;
     }
@@ -1487,7 +1453,7 @@ static AstNode *ast_parse_defer_expr(ParseContext *pc, size_t *token_index) {
 VariableDeclaration : ("var" | "const") "Symbol" ("=" Expression | ":" PrefixOpExpression option("=" Expression))
 */
 static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, size_t *token_index, bool mandatory,
-        ZigList<AstNode*> *directives, VisibMod visib_mod)
+        VisibMod visib_mod)
 {
     Token *first_token = &pc->tokens->at(*token_index);
 
@@ -1509,7 +1475,6 @@ static AstNode *ast_parse_variable_declaration_expr(ParseContext *pc, size_t *to
 
     node->data.variable_declaration.is_const = is_const;
     node->data.variable_declaration.top_level_decl.visib_mod = visib_mod;
-    node->data.variable_declaration.top_level_decl.directives = directives;
 
     Token *name_token = ast_eat_token(pc, token_index, TokenIdSymbol);
     node->data.variable_declaration.symbol = token_buf(name_token);
@@ -1985,8 +1950,7 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand
         if (statement_node) {
             semicolon_expected = false;
         } else {
-            statement_node = ast_parse_variable_declaration_expr(pc, token_index, false,
-                    nullptr, VisibModPrivate);
+            statement_node = ast_parse_variable_declaration_expr(pc, token_index, false, VisibModPrivate);
             if (!statement_node) {
                 statement_node = ast_parse_defer_expr(pc, token_index);
             }
@@ -2023,25 +1987,35 @@ static AstNode *ast_parse_block(ParseContext *pc, size_t *token_index, bool mand
 }
 
 /*
-FnProto = "fn" option("Symbol") ParamDeclList option("->" TypeExpr)
+FnProto = option("coldcc" | "nakedcc") "fn" option(Symbol) ParamDeclList option("->" TypeExpr)
 */
-static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory,
-        ZigList<AstNode*> *directives, VisibMod visib_mod)
-{
+static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) {
     Token *first_token = &pc->tokens->at(*token_index);
+    Token *fn_token;
 
-    if (first_token->id != TokenIdKeywordFn) {
-        if (mandatory) {
-            ast_expect_token(pc, first_token, TokenIdKeywordFn);
-        } else {
-            return nullptr;
-        }
+    bool is_coldcc = false;
+    bool is_nakedcc = false;
+    if (first_token->id == TokenIdKeywordColdCC) {
+        *token_index += 1;
+        fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn);
+        is_coldcc = true;
+    } else if (first_token->id == TokenIdKeywordNakedCC) {
+        *token_index += 1;
+        fn_token = ast_eat_token(pc, token_index, TokenIdKeywordFn);
+        is_nakedcc = true;
+    } else if (first_token->id == TokenIdKeywordFn) {
+        fn_token = first_token;
+        *token_index += 1;
+    } else if (mandatory) {
+        ast_expect_token(pc, first_token, TokenIdKeywordFn);
+    } else {
+        return nullptr;
     }
-    *token_index += 1;
 
-    AstNode *node = ast_create_node(pc, NodeTypeFnProto, first_token);
+    AstNode *node = ast_create_node(pc, NodeTypeFnProto, fn_token);
     node->data.fn_proto.top_level_decl.visib_mod = visib_mod;
-    node->data.fn_proto.top_level_decl.directives = directives;
+    node->data.fn_proto.is_coldcc = is_coldcc;
+    node->data.fn_proto.is_nakedcc = is_nakedcc;
 
     Token *fn_name = &pc->tokens->at(*token_index);
     if (fn_name->id == TokenIdSymbol) {
@@ -2068,9 +2042,7 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc, size_t *token_index, bool m
 /*
 FnDef = option("inline" | "extern") FnProto Block
 */
-static AstNode *ast_parse_fn_def(ParseContext *pc, size_t *token_index, bool mandatory,
-        ZigList<AstNode*> *directives, VisibMod visib_mod)
-{
+static AstNode *ast_parse_fn_def(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) {
     Token *first_token = &pc->tokens->at(*token_index);
     bool is_inline;
     bool is_extern;
@@ -2087,7 +2059,7 @@ static AstNode *ast_parse_fn_def(ParseContext *pc, size_t *token_index, bool man
         is_extern = false;
     }
 
-    AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, mandatory, directives, visib_mod);
+    AstNode *fn_proto = ast_parse_fn_proto(pc, token_index, mandatory, visib_mod);
     if (!fn_proto) {
         if (is_inline || is_extern) {
             *token_index -= 1;
@@ -2115,9 +2087,7 @@ static AstNode *ast_parse_fn_def(ParseContext *pc, size_t *token_index, bool man
 /*
 ExternDecl = "extern" (FnProto | VariableDeclaration) ";"
 */
-static AstNode *ast_parse_extern_decl(ParseContext *pc, size_t *token_index, bool mandatory,
-        ZigList<AstNode *> *directives, VisibMod visib_mod)
-{
+static AstNode *ast_parse_extern_decl(ParseContext *pc, size_t *token_index, bool mandatory, VisibMod visib_mod) {
     Token *extern_kw = &pc->tokens->at(*token_index);
     if (extern_kw->id != TokenIdKeywordExtern) {
         if (mandatory) {
@@ -2128,7 +2098,7 @@ static AstNode *ast_parse_extern_decl(ParseContext *pc, size_t *token_index, boo
     }
     *token_index += 1;
 
-    AstNode *fn_proto_node = ast_parse_fn_proto(pc, token_index, false, directives, visib_mod);
+    AstNode *fn_proto_node = ast_parse_fn_proto(pc, token_index, false, visib_mod);
     if (fn_proto_node) {
         ast_eat_token(pc, token_index, TokenIdSemicolon);
 
@@ -2138,7 +2108,7 @@ static AstNode *ast_parse_extern_decl(ParseContext *pc, size_t *token_index, boo
         return fn_proto_node;
     }
 
-    AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, directives, visib_mod);
+    AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod);
     if (var_decl_node) {
         ast_eat_token(pc, token_index, TokenIdSemicolon);
 
@@ -2155,9 +2125,7 @@ static AstNode *ast_parse_extern_decl(ParseContext *pc, size_t *token_index, boo
 /*
 UseDecl = "use" Expression ";"
 */
-static AstNode *ast_parse_use(ParseContext *pc, size_t *token_index,
-        ZigList<AstNode*> *directives, VisibMod visib_mod)
-{
+static AstNode *ast_parse_use(ParseContext *pc, size_t *token_index, VisibMod visib_mod) {
     Token *use_kw = &pc->tokens->at(*token_index);
     if (use_kw->id != TokenIdKeywordUse)
         return nullptr;
@@ -2165,7 +2133,6 @@ static AstNode *ast_parse_use(ParseContext *pc, size_t *token_index,
 
     AstNode *node = ast_create_node(pc, NodeTypeUse, use_kw);
     node->data.use.top_level_decl.visib_mod = visib_mod;
-    node->data.use.top_level_decl.directives = directives;
     node->data.use.expr = ast_parse_expression(pc, token_index, true);
 
     ast_eat_token(pc, token_index, TokenIdSemicolon);
@@ -2175,13 +2142,11 @@ static AstNode *ast_parse_use(ParseContext *pc, size_t *token_index,
 }
 
 /*
-ContainerDecl = ("struct" | "enum" | "union") "Symbol" option(ParamDeclList) "{" many(StructMember) "}"
-StructMember = many(Directive) option(VisibleMod) (StructField | FnDef | GlobalVarDecl | ContainerDecl)
-StructField : "Symbol" option(":" Expression) ",")
+ContainerDecl = ("struct" | "enum" | "union") Symbol option(ParamDeclList) "{" many(StructMember) "}"
+StructMember = (StructField | FnDef | GlobalVarDecl | ContainerDecl)
+StructField = Symbol option(":" Expression) ",")
 */
-static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index,
-        ZigList<AstNode*> *directives, VisibMod visib_mod)
-{
+static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index, VisibMod visib_mod) {
     Token *first_token = &pc->tokens->at(*token_index);
 
     ContainerKind kind;
@@ -2203,7 +2168,6 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index,
     node->data.struct_decl.kind = kind;
     node->data.struct_decl.name = token_buf(struct_name);
     node->data.struct_decl.top_level_decl.visib_mod = visib_mod;
-    node->data.struct_decl.top_level_decl.directives = directives;
 
     Token *paren_or_brace = &pc->tokens->at(*token_index);
     if (paren_or_brace->id == TokenIdLParen) {
@@ -2217,10 +2181,6 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index,
     }
 
     for (;;) {
-        Token *directive_token = &pc->tokens->at(*token_index);
-        ZigList<AstNode *> *directive_list = allocate<ZigList<AstNode*>>(1);
-        ast_parse_directives(pc, token_index, directive_list);
-
         Token *visib_tok = &pc->tokens->at(*token_index);
         VisibMod visib_mod;
         if (visib_tok->id == TokenIdKeywordPub) {
@@ -2233,20 +2193,20 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index,
             visib_mod = VisibModPrivate;
         }
 
-        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false, directive_list, visib_mod);
+        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false, visib_mod);
         if (fn_def_node) {
             node->data.struct_decl.decls.append(fn_def_node);
             continue;
         }
 
-        AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, directive_list, visib_mod);
+        AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod);
         if (var_decl_node) {
             ast_eat_token(pc, token_index, TokenIdSemicolon);
             node->data.struct_decl.decls.append(var_decl_node);
             continue;
         }
 
-        AstNode *container_decl_node = ast_parse_container_decl(pc, token_index, directive_list, visib_mod);
+        AstNode *container_decl_node = ast_parse_container_decl(pc, token_index, visib_mod);
         if (container_decl_node) {
             node->data.struct_decl.decls.append(container_decl_node);
             continue;
@@ -2255,10 +2215,6 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index,
         Token *token = &pc->tokens->at(*token_index);
 
         if (token->id == TokenIdRBrace) {
-            if (directive_list->length > 0) {
-                ast_error(pc, directive_token, "invalid directive");
-            }
-
             *token_index += 1;
             break;
         } else if (token->id == TokenIdSymbol) {
@@ -2266,7 +2222,6 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index,
             *token_index += 1;
 
             field_node->data.struct_field.top_level_decl.visib_mod = visib_mod;
-            field_node->data.struct_field.top_level_decl.directives = directive_list;
             field_node->data.struct_field.name = token_buf(token);
 
             Token *expr_or_comma = &pc->tokens->at(*token_index);
@@ -2293,9 +2248,7 @@ static AstNode *ast_parse_container_decl(ParseContext *pc, size_t *token_index,
 /*
 ErrorValueDecl : "error" "Symbol" ";"
 */
-static AstNode *ast_parse_error_value_decl(ParseContext *pc, size_t *token_index,
-        ZigList<AstNode*> *directives, VisibMod visib_mod)
-{
+static AstNode *ast_parse_error_value_decl(ParseContext *pc, size_t *token_index, VisibMod visib_mod) {
     Token *first_token = &pc->tokens->at(*token_index);
 
     if (first_token->id != TokenIdKeywordError) {
@@ -2308,7 +2261,6 @@ static AstNode *ast_parse_error_value_decl(ParseContext *pc, size_t *token_index
 
     AstNode *node = ast_create_node(pc, NodeTypeErrorValueDecl, first_token);
     node->data.error_value_decl.top_level_decl.visib_mod = visib_mod;
-    node->data.error_value_decl.top_level_decl.directives = directives;
     node->data.error_value_decl.name = token_buf(name_tok);
 
     normalize_parent_ptrs(node);
@@ -2318,9 +2270,7 @@ static AstNode *ast_parse_error_value_decl(ParseContext *pc, size_t *token_index
 /*
 TypeDecl = "type" "Symbol" "=" TypeExpr ";"
 */
-static AstNode *ast_parse_type_decl(ParseContext *pc, size_t *token_index,
-        ZigList<AstNode*> *directives, VisibMod visib_mod)
-{
+static AstNode *ast_parse_type_decl(ParseContext *pc, size_t *token_index, VisibMod visib_mod) {
     Token *first_token = &pc->tokens->at(*token_index);
 
     if (first_token->id != TokenIdKeywordType) {
@@ -2338,21 +2288,17 @@ static AstNode *ast_parse_type_decl(ParseContext *pc, size_t *token_index,
     ast_eat_token(pc, token_index, TokenIdSemicolon);
 
     node->data.type_decl.top_level_decl.visib_mod = visib_mod;
-    node->data.type_decl.top_level_decl.directives = directives;
 
     normalize_parent_ptrs(node);
     return node;
 }
 
 /*
-TopLevelDecl = many(Directive) option(VisibleMod) (FnDef | ExternDecl | Import | ContainerDecl | GlobalVarDecl | ErrorValueDecl | CImportDecl | TypeDecl)
+TopLevelItem = ErrorValueDecl | Block | TopLevelDecl
+TopLevelDecl = option(VisibleMod) (FnDef | ExternDecl | ContainerDecl | GlobalVarDecl | TypeDecl | UseDecl)
 */
 static void ast_parse_top_level_decls(ParseContext *pc, size_t *token_index, ZigList<AstNode *> *top_level_decls) {
     for (;;) {
-        Token *directive_token = &pc->tokens->at(*token_index);
-        ZigList<AstNode *> *directives = allocate<ZigList<AstNode*>>(1);
-        ast_parse_directives(pc, token_index, directives);
-
         Token *visib_tok = &pc->tokens->at(*token_index);
         VisibMod visib_mod;
         if (visib_tok->id == TokenIdKeywordPub) {
@@ -2365,61 +2311,56 @@ static void ast_parse_top_level_decls(ParseContext *pc, size_t *token_index, Zig
             visib_mod = VisibModPrivate;
         }
 
-        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false, directives, visib_mod);
+        AstNode *fn_def_node = ast_parse_fn_def(pc, token_index, false, visib_mod);
         if (fn_def_node) {
             top_level_decls->append(fn_def_node);
             continue;
         }
 
-        AstNode *fn_proto_node = ast_parse_extern_decl(pc, token_index, false, directives, visib_mod);
+        AstNode *fn_proto_node = ast_parse_extern_decl(pc, token_index, false, visib_mod);
         if (fn_proto_node) {
             top_level_decls->append(fn_proto_node);
             continue;
         }
 
-        AstNode *use_node = ast_parse_use(pc, token_index, directives, visib_mod);
+        AstNode *use_node = ast_parse_use(pc, token_index, visib_mod);
         if (use_node) {
             top_level_decls->append(use_node);
             continue;
         }
 
-        AstNode *struct_node = ast_parse_container_decl(pc, token_index, directives, visib_mod);
+        AstNode *struct_node = ast_parse_container_decl(pc, token_index, visib_mod);
         if (struct_node) {
             top_level_decls->append(struct_node);
             continue;
         }
 
-        AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false,
-                directives, visib_mod);
+        AstNode *var_decl_node = ast_parse_variable_declaration_expr(pc, token_index, false, visib_mod);
         if (var_decl_node) {
             ast_eat_token(pc, token_index, TokenIdSemicolon);
             top_level_decls->append(var_decl_node);
             continue;
         }
 
-        AstNode *error_value_node = ast_parse_error_value_decl(pc, token_index, directives, visib_mod);
+        AstNode *error_value_node = ast_parse_error_value_decl(pc, token_index, visib_mod);
         if (error_value_node) {
             top_level_decls->append(error_value_node);
             continue;
         }
 
-        AstNode *type_decl_node = ast_parse_type_decl(pc, token_index, directives, visib_mod);
+        AstNode *type_decl_node = ast_parse_type_decl(pc, token_index, visib_mod);
         if (type_decl_node) {
             top_level_decls->append(type_decl_node);
             continue;
         }
 
-        if (directives->length > 0) {
-            ast_error(pc, directive_token, "invalid directive");
-        }
-
         return;
     }
     zig_unreachable();
 }
 
 /*
-Root : many(TopLevelDecl) token(EOF)
+Root = many(TopLevelItem) "EOF"
  */
 static AstNode *ast_parse_root(ParseContext *pc, size_t *token_index) {
     AstNode *node = ast_create_node(pc, NodeTypeRoot, &pc->tokens->at(*token_index));
@@ -2471,7 +2412,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
             break;
         case NodeTypeFnProto:
             visit_field(&node->data.fn_proto.return_type, visit, context);
-            visit_node_list(node->data.fn_proto.top_level_decl.directives, visit, context);
             visit_node_list(&node->data.fn_proto.params, visit, context);
             break;
         case NodeTypeFnDef:
@@ -2487,9 +2427,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
         case NodeTypeBlock:
             visit_node_list(&node->data.block.statements, visit, context);
             break;
-        case NodeTypeDirective:
-            visit_field(&node->data.directive.expr, visit, context);
-            break;
         case NodeTypeReturnExpr:
             visit_field(&node->data.return_expr.expr, visit, context);
             break;
@@ -2497,12 +2434,10 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
             visit_field(&node->data.defer.expr, visit, context);
             break;
         case NodeTypeVariableDeclaration:
-            visit_node_list(node->data.variable_declaration.top_level_decl.directives, visit, context);
             visit_field(&node->data.variable_declaration.type, visit, context);
             visit_field(&node->data.variable_declaration.expr, visit, context);
             break;
         case NodeTypeTypeDecl:
-            visit_node_list(node->data.type_decl.top_level_decl.directives, visit, context);
             visit_field(&node->data.type_decl.child_type, visit, context);
             break;
         case NodeTypeErrorValueDecl:
@@ -2550,7 +2485,6 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
             break;
         case NodeTypeUse:
             visit_field(&node->data.use.expr, visit, context);
-            visit_node_list(node->data.use.top_level_decl.directives, visit, context);
             break;
         case NodeTypeBoolLiteral:
             // none
@@ -2626,11 +2560,9 @@ void ast_visit_node_children(AstNode *node, void (*visit)(AstNode **, void *cont
         case NodeTypeContainerDecl:
             visit_node_list(&node->data.struct_decl.fields, visit, context);
             visit_node_list(&node->data.struct_decl.decls, visit, context);
-            visit_node_list(node->data.struct_decl.top_level_decl.directives, visit, context);
             break;
         case NodeTypeStructField:
             visit_field(&node->data.struct_field.type, visit, context);
-            visit_node_list(node->data.struct_field.top_level_decl.directives, visit, context);
             break;
         case NodeTypeContainerInitExpr:
             visit_field(&node->data.container_init_expr.type, visit, context);
@@ -2688,16 +2620,6 @@ static void clone_subtree_list_omit_inline_params(ZigList<AstNode *> *dest, ZigL
     }
 }
 
-static void clone_subtree_list_ptr(ZigList<AstNode *> **dest_ptr, ZigList<AstNode *> *src,
-        uint32_t *next_node_index)
-{
-    if (src) {
-        ZigList<AstNode *> *dest = allocate<ZigList<AstNode *>>(1);
-        *dest_ptr = dest;
-        clone_subtree_list(dest, src, next_node_index);
-    }
-}
-
 static void clone_subtree_field_special(AstNode **dest, AstNode *src, uint32_t *next_node_index,
         enum AstCloneSpecial special)
 {
@@ -2713,10 +2635,6 @@ static void clone_subtree_field(AstNode **dest, AstNode *src, uint32_t *next_nod
     return clone_subtree_field_special(dest, src, next_node_index, AstCloneSpecialNone);
 }
 
-static void clone_subtree_tld(TopLevelDecl *dest, TopLevelDecl *src, uint32_t *next_node_index) {
-    clone_subtree_list_ptr(&dest->directives, src->directives, next_node_index);
-}
-
 AstNode *ast_clone_subtree_special(AstNode *old_node, uint32_t *next_node_index, enum AstCloneSpecial special) {
     AstNode *new_node = allocate_nonzero<AstNode>(1);
     safe_memcpy(new_node, old_node, 1);
@@ -2730,8 +2648,6 @@ AstNode *ast_clone_subtree_special(AstNode *old_node, uint32_t *next_node_index,
                                &old_node->data.root.top_level_decls, next_node_index);
             break;
         case NodeTypeFnProto:
-            clone_subtree_tld(&new_node->data.fn_proto.top_level_decl, &old_node->data.fn_proto.top_level_decl,
-                    next_node_index);
             clone_subtree_field(&new_node->data.fn_proto.return_type, old_node->data.fn_proto.return_type,
                     next_node_index);
 
@@ -2761,9 +2677,6 @@ AstNode *ast_clone_subtree_special(AstNode *old_node, uint32_t *next_node_index,
             clone_subtree_list(&new_node->data.block.statements, &old_node->data.block.statements,
                     next_node_index);
             break;
-        case NodeTypeDirective:
-            clone_subtree_field(&new_node->data.directive.expr, old_node->data.directive.expr, next_node_index);
-            break;
         case NodeTypeReturnExpr:
             clone_subtree_field(&new_node->data.return_expr.expr, old_node->data.return_expr.expr, next_node_index);
             break;
@@ -2771,14 +2684,10 @@ AstNode *ast_clone_subtree_special(AstNode *old_node, uint32_t *next_node_index,
             clone_subtree_field(&new_node->data.defer.expr, old_node->data.defer.expr, next_node_index);
             break;
         case NodeTypeVariableDeclaration:
-            clone_subtree_list_ptr(&new_node->data.variable_declaration.top_level_decl.directives,
-                    old_node->data.variable_declaration.top_level_decl.directives, next_node_index);
             clone_subtree_field(&new_node->data.variable_declaration.type, old_node->data.variable_declaration.type, next_node_index);
             clone_subtree_field(&new_node->data.variable_declaration.expr, old_node->data.variable_declaration.expr, next_node_index);
             break;
         case NodeTypeTypeDecl:
-            clone_subtree_list_ptr(&new_node->data.type_decl.top_level_decl.directives,
-                    old_node->data.type_decl.top_level_decl.directives, next_node_index);
             clone_subtree_field(&new_node->data.type_decl.child_type, old_node->data.type_decl.child_type, next_node_index);
             break;
         case NodeTypeErrorValueDecl:
@@ -2832,8 +2741,6 @@ AstNode *ast_clone_subtree_special(AstNode *old_node, uint32_t *next_node_index,
             break;
         case NodeTypeUse:
             clone_subtree_field(&new_node->data.use.expr, old_node->data.use.expr, next_node_index);
-            clone_subtree_list_ptr(&new_node->data.use.top_level_decl.directives,
-                    old_node->data.use.top_level_decl.directives, next_node_index);
             break;
         case NodeTypeBoolLiteral:
             // none
@@ -2908,13 +2815,9 @@ AstNode *ast_clone_subtree_special(AstNode *old_node, uint32_t *next_node_index,
                     next_node_index);
             clone_subtree_list(&new_node->data.struct_decl.decls, &old_node->data.struct_decl.decls,
                     next_node_index);
-            clone_subtree_list_ptr(&new_node->data.struct_decl.top_level_decl.directives,
-                    old_node->data.struct_decl.top_level_decl.directives, next_node_index);
             break;
         case NodeTypeStructField:
             clone_subtree_field(&new_node->data.struct_field.type, old_node->data.struct_field.type, next_node_index);
-            clone_subtree_list_ptr(&new_node->data.struct_field.top_level_decl.directives,
-                    old_node->data.struct_field.top_level_decl.directives, next_node_index);
             break;
         case NodeTypeContainerInitExpr:
             clone_subtree_field(&new_node->data.container_init_expr.type, old_node->data.container_init_expr.type, next_node_index);
src/tokenizer.cpp
@@ -109,6 +109,7 @@ struct ZigKeyword {
 static const struct ZigKeyword zig_keywords[] = {
     {"asm", TokenIdKeywordAsm},
     {"break", TokenIdKeywordBreak},
+    {"coldcc", TokenIdKeywordColdCC},
     {"const", TokenIdKeywordConst},
     {"continue", TokenIdKeywordContinue},
     {"defer", TokenIdKeywordDefer},
@@ -123,6 +124,7 @@ static const struct ZigKeyword zig_keywords[] = {
     {"goto", TokenIdKeywordGoto},
     {"if", TokenIdKeywordIf},
     {"inline", TokenIdKeywordInline},
+    {"nakedcc", TokenIdKeywordNakedCC},
     {"noalias", TokenIdKeywordNoAlias},
     {"null", TokenIdKeywordNull},
     {"pub", TokenIdKeywordPub},
@@ -1476,6 +1478,8 @@ const char * token_name(TokenId id) {
         case TokenIdKeywordType: return "type";
         case TokenIdKeywordInline: return "inline";
         case TokenIdKeywordDefer: return "defer";
+        case TokenIdKeywordColdCC: return "coldcc";
+        case TokenIdKeywordNakedCC: return "nakedcc";
         case TokenIdLParen: return "(";
         case TokenIdRParen: return ")";
         case TokenIdComma: return ",";
src/tokenizer.hpp
@@ -46,6 +46,8 @@ enum TokenId {
     TokenIdKeywordInline,
     TokenIdKeywordDefer,
     TokenIdKeywordThis,
+    TokenIdKeywordColdCC,
+    TokenIdKeywordNakedCC,
     TokenIdLParen,
     TokenIdRParen,
     TokenIdComma,
std/bootstrap.zig
@@ -13,9 +13,9 @@ const want_main_symbol = !want_start_symbol;
 var argc: usize = undefined;
 var argv: &&u8 = undefined;
 
-#attribute("naked")
-#condition(want_start_symbol)
-export fn _start() -> unreachable {
+export nakedcc fn _start() -> unreachable {
+    @setFnVisible(this, want_start_symbol);
+
     switch (@compileVar("arch")) {
         x86_64 => {
             argc = asm("mov (%%rsp), %[argc]": [argc] "=r" (-> usize));
@@ -44,8 +44,9 @@ fn callMainAndExit() -> unreachable {
     linux.exit(0);
 }
 
-#condition(want_main_symbol)
 export fn main(c_argc: i32, c_argv: &&u8) -> i32 {
+    @setFnVisible(this, want_main_symbol);
+
     argc = usize(c_argc);
     argv = c_argv;
     callMain() %% return 1;
std/builtin.zig
@@ -1,8 +1,9 @@
 // These functions are provided when not linking against libc because LLVM
 // sometimes generates code that calls them.
 
-#debug_safety(false)
 export fn memset(dest: &u8, c: u8, n: usize) -> &u8 {
+    @setDebugSafety(this, false);
+
     var index: usize = 0;
     while (index != n) {
         dest[index] = c;
@@ -11,8 +12,9 @@ export fn memset(dest: &u8, c: u8, n: usize) -> &u8 {
     return dest;
 }
 
-#debug_safety(false)
 export fn memcpy(noalias dest: &u8, noalias src: &const u8, n: usize) -> &u8 {
+    @setDebugSafety(this, false);
+
     var index: usize = 0;
     while (index != n) {
         dest[index] = src[index];
std/compiler_rt.zig
@@ -8,18 +8,19 @@ const udwords = [2]su_int;
 const low = if (@compileVar("is_big_endian")) 1 else 0;
 const high = 1 - low;
 
-#debug_safety(false)
 export fn __udivdi3(a: du_int, b: du_int) -> du_int {
+    @setDebugSafety(this, false);
     return __udivmoddi4(a, b, null);
 }
 
-#debug_safety(false)
 fn du_int_to_udwords(x: du_int) -> udwords {
+    @setDebugSafety(this, false);
     return *(&udwords)(&x);
 }
 
-#debug_safety(false)
 export fn __udivmoddi4(a: du_int, b: du_int, maybe_rem: ?&du_int) -> du_int {
+    @setDebugSafety(this, false);
+
     const n_uword_bits = @sizeOf(su_int) * CHAR_BIT;
     const n_udword_bits = @sizeOf(du_int) * CHAR_BIT;
     var n = du_int_to_udwords(a);
@@ -203,15 +204,17 @@ export fn __udivmoddi4(a: du_int, b: du_int, maybe_rem: ?&du_int) -> du_int {
     return *(&du_int)(&q[0]);
 }
 
-#debug_safety(false)
 export fn __umoddi3(a: du_int, b: du_int) -> du_int {
+    @setDebugSafety(this, false);
+
     var r: du_int = undefined;
     __udivmoddi4(a, b, &r);
     return r;
 }
 
-#attribute("test")
 fn test_umoddi3() {
+    @setFnTest(this, true);
+
     test_one_umoddi3(0, 1, 0);
     test_one_umoddi3(2, 1, 0);
     test_one_umoddi3(0x8000000000000000, 1, 0x0);
@@ -224,8 +227,9 @@ fn test_one_umoddi3(a: du_int, b: du_int, expected_r: du_int) {
     assert(r == expected_r);
 }
 
-#attribute("test")
 fn test_udivmoddi4() {
+    @setFnTest(this, true);
+
     const cases = [][4]du_int {
         []du_int{0x0000000000000000, 0x0000000000000001, 0x0000000000000000, 0x0000000000000000},
         []du_int{0x0000000080000000, 0x0000000100000001, 0x0000000000000000, 0x0000000080000000},
std/cstr.zig
@@ -126,8 +126,9 @@ pub struct CBuf {
     }
 }
 
-#attribute("test")
 fn testSimpleCBuf() {
+    @setFnTest(this, true);
+
     var buf = %%CBuf.initEmpty(&debug.global_allocator);
     assert(buf.len() == 0);
     %%buf.appendCStr(c"hello");
@@ -146,12 +147,14 @@ fn testSimpleCBuf() {
     assert(buf.startsWithCBuf(&buf2));
 }
 
-#attribute("test")
 fn testCompileTimeStrCmp() {
+    @setFnTest(this, true);
+
     assert(@constEval(cmp(c"aoeu", c"aoez") == -1));
 }
 
-#attribute("test")
 fn testCompileTimeStrLen() {
+    @setFnTest(this, true);
+
     assert(@constEval(len(c"123456789") == 9));
 }
std/hash_map.zig
@@ -230,8 +230,9 @@ pub struct SmallHashMap(K: type, V: type, hash: fn(key: K)->u32, eql: fn(a: K, b
     }
 }
 
-#attribute("test")
 fn basicHashMapTest() {
+    @setFnTest(this, true);
+
     var map: HashMap(i32, i32, hash_i32, eql_i32) = undefined;
     map.init(&debug.global_allocator);
     defer map.deinit();
std/io.zig
@@ -423,8 +423,9 @@ fn bufPrintUnsigned(inline T: type, out_buf: []u8, x: T) -> usize {
     return len;
 }
 
-#attribute("test")
 fn parseU64DigitTooBig() {
+    @setFnTest(this, true);
+
     parseUnsigned(u64, "123a", 10) %% |err| {
         if (err == error.InvalidChar) return;
         @unreachable();
std/list.zig
@@ -49,8 +49,9 @@ pub struct List(T: type) {
     }
 }
 
-#attribute("test")
 fn basicListTest() {
+    @setFnTest(this, true);
+
     var list = List(i32).init(&debug.global_allocator);
     defer list.deinit();
 
std/mem.zig
@@ -77,8 +77,8 @@ pub fn sliceAsInt(buf: []u8, is_be: bool, inline T: type) -> T {
     return result;
 }
 
-#attribute("test")
 fn testSliceAsInt() {
+    @setFnTest(this, true);
     {
         const buf = []u8{0x00, 0x00, 0x12, 0x34};
         const answer = sliceAsInt(buf[0...], true, u64);
std/net.zig
@@ -180,8 +180,9 @@ error Overflow;
 error JunkAtEnd;
 error Incomplete;
 
-#static_eval_enable(false)
 fn parseIp6(buf: []const u8) -> %Address {
+    @setFnStaticEval(this, false);
+
     var result: Address = undefined;
     result.family = linux.AF_INET6;
     result.scope_id = 0;
@@ -318,8 +319,9 @@ fn parseIp4(buf: []const u8) -> %u32 {
 }
 
 
-#attribute("test")
 fn testParseIp4() {
+    @setFnTest(this, true);
+
     assert(%%parseIp4("127.0.0.1") == endian.swapIfLe(u32, 0x7f000001));
     switch (parseIp4("256.0.0.1")) { Overflow => {}, else => @unreachable(), }
     switch (parseIp4("x.0.0.1")) { InvalidChar => {}, else => @unreachable(), }
@@ -328,8 +330,9 @@ fn testParseIp4() {
     switch (parseIp4("100..0.1")) { InvalidChar => {}, else => @unreachable(), }
 }
 
-#attribute("test")
 fn testParseIp6() {
+    @setFnTest(this, true);
+
     {
         const addr = %%parseIp6("FF01:0:0:0:0:0:0:FB");
         assert(addr.addr[0] == 0xff);
@@ -338,8 +341,9 @@ fn testParseIp6() {
     }
 }
 
-#attribute("test")
 fn testLookupSimpleIp() {
+    @setFnTest(this, true);
+
     {
         var addrs_buf: [5]Address = undefined;
         const addrs = %%lookup("192.168.1.1", addrs_buf);
std/os.zig
@@ -27,8 +27,7 @@ pub fn getRandomBytes(buf: []u8) -> %void {
     }
 }
 
-#attribute("cold")
-pub fn abort() -> unreachable {
+pub coldcc fn abort() -> unreachable {
     switch (@compileVar("os")) {
         linux, darwin => {
             system.raise(system.SIGABRT);
std/rand.zig
@@ -153,8 +153,9 @@ struct MersenneTwister(
     }
 }
 
-#attribute("test")
 fn testFloat32() {
+    @setFnTest(this, true);
+
     var r: Rand = undefined;
     r.init(42);
 
@@ -165,8 +166,9 @@ fn testFloat32() {
     }}
 }
 
-#attribute("test")
 fn testMT19937_64() {
+    @setFnTest(this, true);
+
     var rng: MT19937_64 = undefined;
     rng.init(rand_test.mt64_seed);
     for (rand_test.mt64_data) |value| {
@@ -174,8 +176,9 @@ fn testMT19937_64() {
     }
 }
 
-#attribute("test")
 fn testMT19937_32() {
+    @setFnTest(this, true);
+
     var rng: MT19937_32 = undefined;
     rng.init(rand_test.mt32_seed);
     for (rand_test.mt32_data) |value| {
std/str.zig
@@ -12,8 +12,9 @@ pub fn sliceEql(inline T: type, a: []const T, b: []const T) -> bool {
     return true;
 }
 
-#attribute("test")
-fn stringEquality() {
+fn testStringEquality() {
+    @setFnTest(this, true);
+
     assert(eql("abcd", "abcd"));
     assert(!eql("abcdef", "abZdef"));
     assert(!eql("abcdefg", "abcdef"));
test/cases/namespace_depends_on_compile_var/index.zig
@@ -1,7 +1,8 @@
 const assert = @import("std").debug.assert;
 
-#attribute("test")
 fn namespaceDependsOnCompileVar() {
+    @setFnTest(this, true);
+
     if (some_namespace.a_bool) {
         assert(some_namespace.a_bool);
     } else {
test/cases/pub_enum/index.zig
@@ -1,16 +1,18 @@
 const assert = @import("std").debug.assert;
 const other = @import("other.zig");
 
-#attribute("test")
 fn pubEnum() {
+    @setFnTest(this, true);
+
     pubEnumTest(other.APubEnum.Two);
 }
 fn pubEnumTest(foo: other.APubEnum) {
     assert(foo == other.APubEnum.Two);
 }
 
-#attribute("test")
 fn castWithImportedSymbol() {
+    @setFnTest(this, true);
+
     assert(other.size_t(42) == 42);
 }
 
test/cases/const_slice_child.zig
@@ -2,8 +2,9 @@ const assert = @import("std").debug.assert;
 
 var argv: &&const u8 = undefined;
 
-#attribute("test")
 fn constSliceChild() {
+    @setFnTest(this, true);
+
     const strs = ([]&const u8) {
         c"one",
         c"two",
@@ -13,16 +14,18 @@ fn constSliceChild() {
     bar(strs.len);
 }
 
-#static_eval_enable(false)
 fn foo(args: [][]const u8) {
+    @setFnStaticEval(this, false);
+
     assert(args.len == 3);
     assert(streql(args[0], "one"));
     assert(streql(args[1], "two"));
     assert(streql(args[2], "three"));
 }
 
-#static_eval_enable(false)
 fn bar(argc: usize) {
+    @setFnStaticEval(this, false);
+
     var args: [argc][]u8 = undefined;
     for (args) |_, i| {
         const ptr = argv[i];
@@ -31,15 +34,17 @@ fn bar(argc: usize) {
     foo(args);
 }
 
-#static_eval_enable(false)
 fn strlen(ptr: &const u8) -> usize {
+    @setFnStaticEval(this, false);
+
     var count: usize = 0;
     while (ptr[count] != 0; count += 1) {}
     return count;
 }
 
-#static_eval_enable(false)
 fn streql(a: []const u8, b: []const u8) -> bool {
+    @setFnStaticEval(this, false);
+
     if (a.len != b.len) return false;
     for (a) |item, index| {
         if (b[index] != item) return false;
test/cases/enum_to_int.zig
@@ -8,17 +8,24 @@ enum Number {
     Four,
 }
 
-#attribute("test")
 fn enumToInt() {
-    shouldEqual(Number.Zero, 0);
-    shouldEqual(Number.One, 1);
-    shouldEqual(Number.Two, 2);
-    shouldEqual(Number.Three, 3);
-    shouldEqual(Number.Four, 4);
+    @setFnTest(this, true);
+
+    shouldEqual(false, Number.Zero, 0);
+    shouldEqual(false, Number.One, 1);
+    shouldEqual(false, Number.Two, 2);
+    shouldEqual(false, Number.Three, 3);
+    shouldEqual(false, Number.Four, 4);
+
+    shouldEqual(true, Number.Zero, 0);
+    shouldEqual(true, Number.One, 1);
+    shouldEqual(true, Number.Two, 2);
+    shouldEqual(true, Number.Three, 3);
+    shouldEqual(true, Number.Four, 4);
 }
 
-// TODO add test with this disabled
-#static_eval_enable(false)
-fn shouldEqual(n: Number, expected: usize) {
+fn shouldEqual(inline static_eval: bool, n: Number, expected: usize) {
+    @setFnStaticEval(this, static_eval);
+
     assert(usize(n) == expected);
 }
test/cases/enum_with_members.zig
@@ -15,8 +15,9 @@ enum ET {
     }
 }
 
-#attribute("test")
 fn enumWithMembers() {
+    @setFnTest(this, true);
+
     const a = ET.SINT { -42 };
     const b = ET.UINT { 42 };
     var buf: [20]u8 = undefined;
test/cases/max_value_type.zig
@@ -1,7 +1,8 @@
 const assert = @import("std").debug.assert;
 
-#attribute("test")
 fn maxValueType() {
+    @setFnTest(this, true);
+
     // If the type of @maxValue(i32) was i32 then this implicit cast to
     // u32 would not work. But since the value is a number literal,
     // it works fine.
test/cases/maybe_return.zig
@@ -1,15 +1,17 @@
 const assert = @import("std").debug.assert;
 
-#attribute("test")
 fn maybeReturn() {
+    @setFnTest(this, true);
+
     assert(??foo(1235));
     assert(if (const _ ?= foo(null)) false else true);
     assert(!??foo(1234));
 }
 
-// TODO add another function with static_eval_enable(true)
-#static_eval_enable(false)
+// TODO test static eval maybe return
 fn foo(x: ?i32) -> ?bool {
+    @setFnStaticEval(this, false);
+
     const value = ?return x;
     return value > 1234;
 }
test/cases/return_type_type.zig
@@ -10,8 +10,9 @@ pub struct SmallList(inline T: type, inline STATIC_SIZE: usize) {
     prealloc_items: [STATIC_SIZE]T,
 }
 
-#attribute("test")
 fn functionWithReturnTypeType() {
+    @setFnTest(this, true);
+
     var list: List(i32) = undefined;
     var list2: List(i32) = undefined;
     list.length = 10;
test/cases/sizeof_and_typeof.zig
@@ -1,7 +1,8 @@
 const assert = @import("std").debug.assert;
 
-#attribute("test")
 fn sizeofAndTypeOf() {
+    @setFnTest(this, true);
+
     const y: @typeOf(x) = 120;
     assert(@sizeOf(@typeOf(y)) == 2);
 }
test/cases/struct_contains_slice_of_itself.zig
@@ -5,8 +5,9 @@ struct Node {
     children: []Node,
 }
 
-#attribute("test")
 fn structContainsSliceOfItself() {
+    @setFnTest(this, true);
+
     var nodes = []Node {
         Node {
             .payload = 1,
test/cases/switch_prong_err_enum.zig
@@ -14,16 +14,18 @@ enum FormValue {
     Other: bool,
 }
 
-#static_eval_enable(false)
 fn doThing(form_id: u64) -> %FormValue {
+    @setFnStaticEval(this, false);
+
     return switch (form_id) {
         17 => FormValue.Address { %return readOnce() },
         else => error.InvalidDebugInfo,
     }
 }
 
-#attribute("test")
 fn switchProngReturnsErrorEnum() {
+    @setFnTest(this, true);
+
     %%doThing(17);
     assert(read_count == 1);
 }
test/cases/switch_prong_implicit_cast.zig
@@ -7,8 +7,9 @@ enum FormValue {
 
 error Whatever;
 
-#static_eval_enable(false)
 fn foo(id: u64) -> %FormValue {
+    @setFnStaticEval(this, false);
+
     switch (id) {
         2 => FormValue.Two { true },
         1 => FormValue.One,
@@ -16,8 +17,9 @@ fn foo(id: u64) -> %FormValue {
     }
 }
 
-#attribute("test")
 fn switchProngImplicitCast() {
+    @setFnTest(this, true);
+
     const result = switch (%%foo(2)) {
         One => false,
         Two => |x| x,
test/cases/this.zig
@@ -25,13 +25,15 @@ fn factorial(x: i32) -> i32 {
     }
 }
 
-#attribute("test")
 fn thisReferToModuleCallPrivateFn() {
+    @setFnTest(this, true);
+
     assert(module.add(1, 2) == 3);
 }
 
-#attribute("test")
 fn thisReferToContainer() {
+    @setFnTest(this, true);
+
     var pt = Point(i32) {
         .x = 12,
         .y = 34,
@@ -41,7 +43,8 @@ fn thisReferToContainer() {
     assert(pt.y == 35);
 }
 
-#attribute("test")
 fn thisReferToFn() {
+    @setFnTest(this, true);
+
     assert(factorial(5) == 120);
 }
test/cases/var_params.zig
@@ -1,7 +1,8 @@
 const assert = @import("std").debug.assert;
 
-#attribute("test")
 fn varParams() {
+    @setFnTest(this, true);
+
     assert(max_i32(12, 34) == 34);
     assert(max_f64(1.2, 3.4) == 3.4);
 
@@ -21,12 +22,14 @@ fn max_f64(a: f64, b: f64) -> f64 {
     max(a, b)
 }
 
-#static_eval_enable(false)
 fn max_i32_noeval(a: i32, b: i32) -> i32 {
+    @setFnStaticEval(this, false);
+
     max(a, b)
 }
 
-#static_eval_enable(false)
 fn max_f64_noeval(a: f64, b: f64) -> f64 {
+    @setFnStaticEval(this, false);
+
     max(a, b)
 }
test/cases/zeroes.zig
@@ -7,8 +7,9 @@ struct Foo {
     d: ?i32,
 }
 
-#attribute("test")
 fn initializing_a_struct_with_zeroes() {
+    @setFnTest(this, true);
+
     const foo: Foo = zeroes;
     assert(foo.a == 0.0);
     assert(foo.b == 0);
test/run_tests.cpp
@@ -279,8 +279,9 @@ pub fn bar_function() {
         )SOURCE");
 
         add_source_file(tc, "other.zig", R"SOURCE(
-#static_eval_enable(false)
 pub fn foo_function() -> bool {
+    @setFnStaticEval(this, false);
+
     // this one conflicts with the one from foo
     return true;
 }
@@ -686,14 +687,6 @@ fn a() {}
 fn a() {}
     )SOURCE", 1, ".tmp_source.zig:3:1: error: redefinition of 'a'");
 
-    add_compile_fail_case("bad directive", R"SOURCE(
-#bogus1("")
-extern fn b();
-#bogus2("")
-fn a() {}
-    )SOURCE", 2, ".tmp_source.zig:2:1: error: invalid directive: 'bogus1'",
-                 ".tmp_source.zig:4:1: error: invalid directive: 'bogus2'");
-
     add_compile_fail_case("unreachable with return", R"SOURCE(
 fn a() -> unreachable {return;}
     )SOURCE", 1, ".tmp_source.zig:2:24: error: expected type 'unreachable', got 'void'");
@@ -1280,8 +1273,11 @@ struct Foo {
     x: i32,
 }
 const a = get_it();
-#static_eval_enable(false)
-fn get_it() -> Foo { Foo {.x = 13} }
+fn get_it() -> Foo {
+    @setFnStaticEval(this, false);
+    Foo {.x = 13}
+}
+
     )SOURCE", 1, ".tmp_source.zig:5:17: error: unable to evaluate constant expression");
 
     add_compile_fail_case("undeclared identifier error should mark fn as impure", R"SOURCE(
@@ -1316,8 +1312,11 @@ fn foo() {
         else => 3,
     };
 }
-#static_eval_enable(false)
-fn bar() -> i32 { 2 }
+fn bar() -> i32 {
+    @setFnStaticEval(this, false);
+    2
+}
+
     )SOURCE", 1, ".tmp_source.zig:3:15: error: unable to infer expression type");
 
     add_compile_fail_case("atomic orderings of cmpxchg", R"SOURCE(
@@ -1458,7 +1457,6 @@ pub struct SmallList(inline T: type, inline STATIC_SIZE: usize) {
     prealloc_items: [STATIC_SIZE]T,
 }
 
-#attribute("test")
 fn function_with_return_type_type() {
     var list: List(i32) = undefined;
     list.length = 10;
@@ -1623,12 +1621,15 @@ pub fn main(args: [][]u8) -> %void {
     const a = []i32{1, 2, 3, 4};
     baz(bar(a));
 }
-#static_eval_enable(false)
 fn bar(a: []i32) -> i32 {
+    @setFnStaticEval(this, false);
+
     a[4]
 }
-#static_eval_enable(false)
-fn baz(a: i32) {}
+fn baz(a: i32) {
+    @setFnStaticEval(this, false);
+}
+
     )SOURCE");
 
     add_debug_safety_case("integer addition overflow", R"SOURCE(
@@ -1637,8 +1638,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = add(65530, 10);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn add(a: u16, b: u16) -> u16 {
+    @setFnStaticEval(this, false);
+
     a + b
 }
     )SOURCE");
@@ -1649,8 +1651,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = sub(10, 20);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn sub(a: u16, b: u16) -> u16 {
+    @setFnStaticEval(this, false);
+
     a - b
 }
     )SOURCE");
@@ -1661,8 +1664,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = mul(300, 6000);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn mul(a: u16, b: u16) -> u16 {
+    @setFnStaticEval(this, false);
+
     a * b
 }
     )SOURCE");
@@ -1673,8 +1677,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = neg(-32768);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn neg(a: i16) -> i16 {
+    @setFnStaticEval(this, false);
+
     -a
 }
     )SOURCE");
@@ -1685,8 +1690,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = shl(-16385, 1);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn shl(a: i16, b: i16) -> i16 {
+    @setFnStaticEval(this, false);
+
     a << b
 }
     )SOURCE");
@@ -1697,8 +1703,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = shl(0b0010111111111111, 3);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn shl(a: u16, b: u16) -> u16 {
+    @setFnStaticEval(this, false);
+
     a << b
 }
     )SOURCE");
@@ -1708,8 +1715,9 @@ error Whatever;
 pub fn main(args: [][]u8) -> %void {
     const x = div0(999, 0);
 }
-#static_eval_enable(false)
 fn div0(a: i32, b: i32) -> i32 {
+    @setFnStaticEval(this, false);
+
     a / b
 }
     )SOURCE");
@@ -1720,8 +1728,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = divExact(10, 3);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn divExact(a: i32, b: i32) -> i32 {
+    @setFnStaticEval(this, false);
+
     @divExact(a, b)
 }
     )SOURCE");
@@ -1732,8 +1741,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = widenSlice([]u8{1, 2, 3, 4, 5});
     if (x.len == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn widenSlice(slice: []u8) -> []i32 {
+    @setFnStaticEval(this, false);
+
     ([]i32)(slice)
 }
     )SOURCE");
@@ -1744,8 +1754,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = shorten_cast(200);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn shorten_cast(x: i32) -> i8 {
+    @setFnStaticEval(this, false);
+
     i8(x)
 }
     )SOURCE");
@@ -1756,8 +1767,9 @@ pub fn main(args: [][]u8) -> %void {
     const x = unsigned_cast(-10);
     if (x == 0) return error.Whatever;
 }
-#static_eval_enable(false)
 fn unsigned_cast(x: i32) -> u32 {
+    @setFnStaticEval(this, false);
+
     u32(x)
 }
     )SOURCE");
test/self_hosted.zig
@@ -19,12 +19,15 @@ const test_this = @import("cases/this.zig");
 // normal comment
 /// this is a documentation comment
 /// doc comment line 2
-#attribute("test")
-fn emptyFunctionWithComments() {}
+fn emptyFunctionWithComments() {
+    @setFnTest(this, true);
+}
+
 
 
-#attribute("test")
 fn ifStatements() {
+    @setFnTest(this, true);
+
     shouldBeEqual(1, 1);
     firstEqlThird(2, 1, 2);
 }
@@ -48,8 +51,9 @@ fn firstEqlThird(a: i32, b: i32, c: i32) {
 }
 
 
-#attribute("test")
 fn params() {
+    @setFnTest(this, true);
+
     assert(testParamsAdd(22, 11) == 33);
 }
 fn testParamsAdd(a: i32, b: i32) -> i32 {
@@ -57,8 +61,9 @@ fn testParamsAdd(a: i32, b: i32) -> i32 {
 }
 
 
-#attribute("test")
 fn localVariables() {
+    @setFnTest(this, true);
+
     testLocVars(2);
 }
 fn testLocVars(b: i32) {
@@ -66,14 +71,16 @@ fn testLocVars(b: i32) {
     if (a + b != 3) @unreachable();
 }
 
-#attribute("test")
 fn boolLiterals() {
+    @setFnTest(this, true);
+
     assert(true);
     assert(!false);
 }
 
-#attribute("test")
 fn voidParameters() {
+    @setFnTest(this, true);
+
     voidFun(1, void{}, 2, {});
 }
 fn voidFun(a : i32, b : void, c : i32, d : void) {
@@ -83,8 +90,9 @@ fn voidFun(a : i32, b : void, c : i32, d : void) {
     return vv;
 }
 
-#attribute("test")
 fn mutableLocalVariables() {
+    @setFnTest(this, true);
+
     var zero : i32 = 0;
     assert(zero == 0);
 
@@ -95,8 +103,9 @@ fn mutableLocalVariables() {
     assert(i == 3);
 }
 
-#attribute("test")
 fn arrays() {
+    @setFnTest(this, true);
+
     var array : [5]u32 = undefined;
 
     var i : u32 = 0;
@@ -120,8 +129,9 @@ fn getArrayLen(a: []u32) -> usize {
     a.len
 }
 
-#attribute("test")
 fn shortCircuit() {
+    @setFnTest(this, true);
+
     var hit_1 = false;
     var hit_2 = false;
     var hit_3 = false;
@@ -148,13 +158,15 @@ fn shortCircuit() {
     assert(hit_4);
 }
 
-#static_eval_enable(false)
 fn assertRuntime(b: bool) {
+    @setFnStaticEval(this, false);
+
     if (!b) @unreachable()
 }
 
-#attribute("test")
 fn modifyOperators() {
+    @setFnTest(this, true);
+
     var i : i32 = 0;
     i += 5;  assert(i == 5);
     i -= 2;  assert(i == 3);
@@ -171,8 +183,9 @@ fn modifyOperators() {
 }
 
 
-#attribute("test")
 fn separateBlockScopes() {
+    @setFnTest(this, true);
+
     {
         const no_conflict : i32 = 5;
         assert(no_conflict == 5);
@@ -186,8 +199,9 @@ fn separateBlockScopes() {
 }
 
 
-#attribute("test")
 fn voidStructFields() {
+    @setFnTest(this, true);
+
     const foo = VoidStructFieldsFoo {
         .a = void{},
         .b = 1,
@@ -204,8 +218,9 @@ struct VoidStructFieldsFoo {
 
 
 
-#attribute("test")
 pub fn structs() {
+    @setFnTest(this, true);
+
     var foo : StructFoo = undefined;
     @memset(&foo, 0, @sizeOf(StructFoo));
     foo.a += 1;
@@ -234,8 +249,9 @@ struct Val {
     x: i32,
 }
 
-#attribute("test")
 fn structPointToSelf() {
+    @setFnTest(this, true);
+
     var root : Node = undefined;
     root.val.x = 1;
 
@@ -248,8 +264,9 @@ fn structPointToSelf() {
     assert(node.next.next.next.val.x == 1);
 }
 
-#attribute("test")
 fn structByvalAssign() {
+    @setFnTest(this, true);
+
     var foo1 : StructFoo = undefined;
     var foo2 : StructFoo = undefined;
 
@@ -269,16 +286,18 @@ fn structInitializer() {
 const g1 : i32 = 1233 + 1;
 var g2 : i32 = 0;
 
-#attribute("test")
 fn globalVariables() {
+    @setFnTest(this, true);
+
     assert(g2 == 0);
     g2 = g1;
     assert(g2 == 1234);
 }
 
 
-#attribute("test")
 fn whileLoop() {
+    @setFnTest(this, true);
+
     var i : i32 = 0;
     while (i < 4) {
         i += 1;
@@ -295,8 +314,9 @@ fn whileLoop2() -> i32 {
     }
 }
 
-#attribute("test")
 fn voidArrays() {
+    @setFnTest(this, true);
+
     var array: [4]void = undefined;
     array[0] = void{};
     array[1] = array[2];
@@ -305,8 +325,9 @@ fn voidArrays() {
 }
 
 
-#attribute("test")
 fn threeExprInARow() {
+    @setFnTest(this, true);
+
     assertFalse(false || false || false);
     assertFalse(true && true && false);
     assertFalse(1 | 2 | 4 != 7);
@@ -325,8 +346,9 @@ fn assertFalse(b: bool) {
 }
 
 
-#attribute("test")
 fn maybeType() {
+    @setFnTest(this, true);
+
     const x : ?bool = true;
 
     if (const y ?= x) {
@@ -353,8 +375,9 @@ fn maybeType() {
 }
 
 
-#attribute("test")
 fn enumType() {
+    @setFnTest(this, true);
+
     const foo1 = EnumTypeFoo.One {13};
     const foo2 = EnumTypeFoo.Two {EnumType { .x = 1234, .y = 5678, }};
     const bar = EnumTypeBar.B;
@@ -387,8 +410,9 @@ enum EnumTypeBar {
 }
 
 
-#attribute("test")
 fn arrayLiteral() {
+    @setFnTest(this, true);
+
     const hex_mult = []u16{4096, 256, 16, 1};
 
     assert(hex_mult.len == 4);
@@ -396,8 +420,9 @@ fn arrayLiteral() {
 }
 
 
-#attribute("test")
 fn constNumberLiteral() {
+    @setFnTest(this, true);
+
     const one = 1;
     const eleven = ten + one;
 
@@ -406,8 +431,9 @@ fn constNumberLiteral() {
 const ten = 10;
 
 
-#attribute("test")
 fn errorValues() {
+    @setFnTest(this, true);
+
     const a = i32(error.err1);
     const b = i32(error.err2);
     assert(a != b);
@@ -417,8 +443,9 @@ error err2;
 
 
 
-#attribute("test")
 fn fnCallOfStructField() {
+    @setFnTest(this, true);
+
     assert(callStructField(Foo {.ptr = aFunc,}) == 13);
 }
 
@@ -434,8 +461,9 @@ fn callStructField(foo: Foo) -> i32 {
 
 
 
-#attribute("test")
 fn redefinitionOfErrorValuesAllowed() {
+    @setFnTest(this, true);
+
     shouldBeNotEqual(error.AnError, error.SecondError);
 }
 error AnError;
@@ -448,8 +476,9 @@ fn shouldBeNotEqual(a: error, b: error) {
 
 
 
-#attribute("test")
 fn constantEnumWithPayload() {
+    @setFnTest(this, true);
+
     var empty = AnEnumWithPayload.Empty;
     var full = AnEnumWithPayload.Full {13};
     shouldBeEmpty(empty);
@@ -476,8 +505,9 @@ enum AnEnumWithPayload {
 }
 
 
-#attribute("test")
 fn continueInForLoop() {
+    @setFnTest(this, true);
+
     const array = []i32 {1, 2, 3, 4, 5};
     var sum : i32 = 0;
     for (array) |x| {
@@ -491,8 +521,9 @@ fn continueInForLoop() {
 }
 
 
-#attribute("test")
 fn castBoolToInt() {
+    @setFnTest(this, true);
+
     const t = true;
     const f = false;
     assert(i32(t) == i32(1));
@@ -506,8 +537,9 @@ fn nonConstCastBoolToInt(t: bool, f: bool) {
 }
 
 
-#attribute("test")
 fn switchOnEnum() {
+    @setFnTest(this, true);
+
     const fruit = Fruit.Orange;
     nonConstSwitchOnEnum(fruit);
 }
@@ -516,8 +548,9 @@ enum Fruit {
     Orange,
     Banana,
 }
-#static_eval_enable(false)
 fn nonConstSwitchOnEnum(fruit: Fruit) {
+    @setFnStaticEval(this, false);
+
     switch (fruit) {
         Apple => @unreachable(),
         Orange => {},
@@ -525,12 +558,14 @@ fn nonConstSwitchOnEnum(fruit: Fruit) {
     }
 }
 
-#attribute("test")
 fn switchStatement() {
+    @setFnTest(this, true);
+
     nonConstSwitch(SwitchStatmentFoo.C);
 }
-#static_eval_enable(false)
 fn nonConstSwitch(foo: SwitchStatmentFoo) {
+    @setFnStaticEval(this, false);
+
     const val: i32 = switch (foo) {
         A => 1,
         B => 2,
@@ -547,8 +582,9 @@ enum SwitchStatmentFoo {
 }
 
 
-#attribute("test")
 fn switchProngWithVar() {
+    @setFnTest(this, true);
+
     switchProngWithVarFn(SwitchProngWithVarEnum.One {13});
     switchProngWithVarFn(SwitchProngWithVarEnum.Two {13.0});
     switchProngWithVarFn(SwitchProngWithVarEnum.Meh);
@@ -558,8 +594,9 @@ enum SwitchProngWithVarEnum {
     Two: f32,
     Meh,
 }
-#static_eval_enable(false)
 fn switchProngWithVarFn(a: SwitchProngWithVarEnum) {
+    @setFnStaticEval(this, false);
+
     switch(a) {
         One => |x| {
             if (x != 13) @unreachable();
@@ -574,13 +611,15 @@ fn switchProngWithVarFn(a: SwitchProngWithVarEnum) {
 }
 
 
-#attribute("test")
 fn errReturnInAssignment() {
+    @setFnTest(this, true);
+
     %%doErrReturnInAssignment();
 }
 
-#static_eval_enable(false)
 fn doErrReturnInAssignment() -> %void {
+    @setFnStaticEval(this, false);
+
     var x : i32 = undefined;
     x = %return makeANonErr();
 }
@@ -591,15 +630,17 @@ fn makeANonErr() -> %i32 {
 
 
 
-#attribute("test")
 fn rhsMaybeUnwrapReturn() {
+    @setFnTest(this, true);
+
     const x = ?true;
     const y = x ?? return;
 }
 
 
-#attribute("test")
 fn implicitCastFnUnreachableReturn() {
+    @setFnTest(this, true);
+
     wantsFnWithVoid(fnWithUnreachable);
 }
 
@@ -610,15 +651,17 @@ fn fnWithUnreachable() -> unreachable {
 }
 
 
-#attribute("test")
 fn explicitCastMaybePointers() {
+    @setFnTest(this, true);
+
     const a: ?&i32 = undefined;
     const b: ?&f32 = (?&f32)(a);
 }
 
 
-#attribute("test")
 fn constExprEvalOnSingleExprBlocks() {
+    @setFnTest(this, true);
+
     assert(constExprEvalOnSingleExprBlocksFn(1, true) == 3);
 }
 
@@ -635,14 +678,16 @@ fn constExprEvalOnSingleExprBlocksFn(x: i32, b: bool) -> i32 {
 }
 
 
-#attribute("test")
 fn builtinConstEval() {
+    @setFnTest(this, true);
+
     const x : i32 = @constEval(1 + 2 + 3);
     assert(x == @constEval(6));
 }
 
-#attribute("test")
 fn slicing() {
+    @setFnTest(this, true);
+
     var array : [20]i32 = undefined;
 
     array[5] = 1234;
@@ -659,8 +704,9 @@ fn slicing() {
 }
 
 
-#attribute("test")
 fn memcpyAndMemsetIntrinsics() {
+    @setFnTest(this, true);
+
     var foo : [20]u8 = undefined;
     var bar : [20]u8 = undefined;
 
@@ -671,31 +717,36 @@ fn memcpyAndMemsetIntrinsics() {
 }
 
 
-#attribute("test")
-fn arrayDotLenConstExpr() { }
+fn arrayDotLenConstExpr() {
+    @setFnTest(this, true);
+}
+
 struct ArrayDotLenConstExpr {
     y: [@constEval(some_array.len)]u8,
 }
 const some_array = []u8 {0, 1, 2, 3};
 
 
-#attribute("test")
 fn countLeadingZeroes() {
+    @setFnTest(this, true);
+
     assert(@clz(u8, 0b00001010) == 4);
     assert(@clz(u8, 0b10001010) == 0);
     assert(@clz(u8, 0b00000000) == 8);
 }
 
-#attribute("test")
 fn countTrailingZeroes() {
+    @setFnTest(this, true);
+
     assert(@ctz(u8, 0b10100000) == 5);
     assert(@ctz(u8, 0b10001010) == 1);
     assert(@ctz(u8, 0b00000000) == 8);
 }
 
 
-#attribute("test")
 fn multilineString() {
+    @setFnTest(this, true);
+
     const s1 =
         \\one
         \\two)
@@ -705,8 +756,9 @@ fn multilineString() {
     assert(str.eql(s1, s2));
 }
 
-#attribute("test")
 fn multilineCString() {
+    @setFnTest(this, true);
+
     const s1 =
         c\\one
         c\\two)
@@ -718,8 +770,9 @@ fn multilineCString() {
 
 
 
-#attribute("test")
 fn simpleGenericFn() {
+    @setFnTest(this, true);
+
     assert(max(i32, 3, -1) == 3);
     assert(max(f32, 0.123, 0.456) == 0.456);
     assert(add(2, 3) == 5);
@@ -734,8 +787,9 @@ fn add(inline a: i32, b: i32) -> i32 {
 }
 
 
-#attribute("test")
 fn constantEqualFunctionPointers() {
+    @setFnTest(this, true);
+
     const alias = emptyFn;
     assert(@constEval(emptyFn == alias));
 }
@@ -743,44 +797,50 @@ fn constantEqualFunctionPointers() {
 fn emptyFn() {}
 
 
-#attribute("test")
 fn genericMallocFree() {
+    @setFnTest(this, true);
+
     const a = %%memAlloc(u8, 10);
     memFree(u8, a);
 }
 const some_mem : [100]u8 = undefined;
-#static_eval_enable(false)
 fn memAlloc(inline T: type, n: usize) -> %[]T {
+    @setFnStaticEval(this, false);
+
     return (&T)(&some_mem[0])[0...n];
 }
 fn memFree(inline T: type, mem: []T) { }
 
 
-#attribute("test")
 fn callFnWithEmptyString() {
+    @setFnTest(this, true);
+
     acceptsString("");
 }
 
 fn acceptsString(foo: []u8) { }
 
 
-#attribute("test")
 fn hexEscape() {
+    @setFnTest(this, true);
+
     assert(str.eql("\x68\x65\x6c\x6c\x6f", "hello"));
 }
 
 
 error AnError;
 error ALongerErrorName;
-#attribute("test")
 fn errorNameString() {
+    @setFnTest(this, true);
+
     assert(str.eql(@errorName(error.AnError), "AnError"));
     assert(str.eql(@errorName(error.ALongerErrorName), "ALongerErrorName"));
 }
 
 
-#attribute("test")
 fn gotoAndLabels() {
+    @setFnTest(this, true);
+
     gotoLoop();
     assert(goto_counter == 10);
 }
@@ -799,12 +859,14 @@ var goto_counter: i32 = 0;
 
 
 
-#attribute("test")
 fn gotoLeaveDeferScope() {
+    @setFnTest(this, true);
+
     testGotoLeaveDeferScope(true);
 }
-#static_eval_enable(false)
 fn testGotoLeaveDeferScope(b: bool) {
+    @setFnStaticEval(this, false);
+
     var it_worked = false;
 
     goto entry;
@@ -819,8 +881,9 @@ entry:
 }
 
 
-#attribute("test")
 fn castUndefined() {
+    @setFnTest(this, true);
+
     const array: [100]u8 = undefined;
     const slice = ([]u8)(array);
     testCastUndefined(slice);
@@ -828,8 +891,9 @@ fn castUndefined() {
 fn testCastUndefined(x: []u8) {}
 
 
-#attribute("test")
 fn castSmallUnsignedToLargerSigned() {
+    @setFnTest(this, true);
+
     assert(castSmallUnsignedToLargerSigned1(200) == i16(200));
     assert(castSmallUnsignedToLargerSigned2(9999) == i64(9999));
 }
@@ -837,8 +901,9 @@ fn castSmallUnsignedToLargerSigned1(x: u8) -> i16 { x }
 fn castSmallUnsignedToLargerSigned2(x: u16) -> i64 { x }
 
 
-#attribute("test")
 fn implicitCastAfterUnreachable() {
+    @setFnTest(this, true);
+
     assert(outer() == 1234);
 }
 fn inner() -> i32 { 1234 }
@@ -847,8 +912,9 @@ fn outer() -> i64 {
 }
 
 
-#attribute("test")
 fn elseIfExpression() {
+    @setFnTest(this, true);
+
     assert(elseIfExpressionF(1) == 1);
 }
 fn elseIfExpressionF(c: u8) -> u8 {
@@ -861,8 +927,9 @@ fn elseIfExpressionF(c: u8) -> u8 {
     }
 }
 
-#attribute("test")
 fn errBinaryOperator() {
+    @setFnTest(this, true);
+
     const a = errBinaryOperatorG(true) %% 3;
     const b = errBinaryOperatorG(false) %% 3;
     assert(a == 3);
@@ -877,16 +944,18 @@ fn errBinaryOperatorG(x: bool) -> %isize {
     }
 }
 
-#attribute("test")
 fn unwrapSimpleValueFromError() {
+    @setFnTest(this, true);
+
     const i = %%unwrapSimpleValueFromErrorDo();
     assert(i == 13);
 }
 fn unwrapSimpleValueFromErrorDo() -> %isize { 13 }
 
 
-#attribute("test")
 fn storeMemberFunctionInVariable() {
+    @setFnTest(this, true);
+
     const instance = MemberFnTestFoo { .x = 1234, };
     const memberFn = MemberFnTestFoo.member;
     const result = memberFn(instance);
@@ -897,15 +966,17 @@ struct MemberFnTestFoo {
     fn member(foo: MemberFnTestFoo) -> i32 { foo.x }
 }
 
-#attribute("test")
 fn callMemberFunctionDirectly() {
+    @setFnTest(this, true);
+
     const instance = MemberFnTestFoo { .x = 1234, };
     const result = MemberFnTestFoo.member(instance);
     assert(result == 1234);
 }
 
-#attribute("test")
 fn memberFunctions() {
+    @setFnTest(this, true);
+
     const r = MemberFnRand {.seed = 1234};
     assert(r.getSeed() == 1234);
 }
@@ -916,16 +987,18 @@ struct MemberFnRand {
     }
 }
 
-#attribute("test")
 fn staticFunctionEvaluation() {
+    @setFnTest(this, true);
+
     assert(statically_added_number == 3);
 }
 const statically_added_number = staticAdd(1, 2);
 fn staticAdd(a: i32, b: i32) -> i32 { a + b }
 
 
-#attribute("test")
 fn staticallyInitalizedList() {
+    @setFnTest(this, true);
+
     assert(static_point_list[0].x == 1);
     assert(static_point_list[0].y == 2);
     assert(static_point_list[1].x == 3);
@@ -944,8 +1017,9 @@ fn makePoint(x: i32, y: i32) -> Point {
 }
 
 
-#attribute("test")
 fn staticEvalRecursive() {
+    @setFnTest(this, true);
+
     assert(some_data.len == 21);
 }
 var some_data: [usize(fibbonaci(7))]u8 = undefined;
@@ -954,8 +1028,9 @@ fn fibbonaci(x: i32) -> i32 {
     return fibbonaci(x - 1) + fibbonaci(x - 2);
 }
 
-#attribute("test")
 fn staticEvalWhile() {
+    @setFnTest(this, true);
+
     assert(static_eval_while_number == 1);
 }
 const static_eval_while_number = staticWhileLoop1();
@@ -968,8 +1043,9 @@ fn staticWhileLoop2() -> i32 {
     }
 }
 
-#attribute("test")
 fn staticEvalListInit() {
+    @setFnTest(this, true);
+
     assert(static_vec3.data[2] == 1.0);
 }
 const static_vec3 = vec3(0.0, 0.0, 1.0);
@@ -983,8 +1059,9 @@ pub fn vec3(x: f32, y: f32, z: f32) -> Vec3 {
 }
 
 
-#attribute("test")
 fn genericFnWithImplicitCast() {
+    @setFnTest(this, true);
+
     assert(getFirstByte(u8, []u8 {13}) == 13);
     assert(getFirstByte(u16, []u16 {0, 13}) == 0);
 }
@@ -993,8 +1070,9 @@ fn getFirstByte(inline T: type, mem: []T) -> u8 {
     getByte((&u8)(&mem[0]))
 }
 
-#attribute("test")
 fn continueAndBreak() {
+    @setFnTest(this, true);
+
     runContinueAndBreakTest();
     assert(continue_and_break_counter == 8);
 }
@@ -1013,8 +1091,9 @@ fn runContinueAndBreakTest() {
 }
 
 
-#attribute("test")
 fn pointerDereferencing() {
+    @setFnTest(this, true);
+
     var x = i32(3);
     const y = &x;
 
@@ -1024,16 +1103,18 @@ fn pointerDereferencing() {
     assert(*y == 4);
 }
 
-#attribute("test")
 fn constantExpressions() {
+    @setFnTest(this, true);
+
     var array : [array_size]u8 = undefined;
     assert(@sizeOf(@typeOf(array)) == 20);
 }
 const array_size : u8 = 20;
 
 
-#attribute("test")
 fn minValueAndMaxValue() {
+    @setFnTest(this, true);
+
     assert(@maxValue(u8) == 255);
     assert(@maxValue(u16) == 65535);
     assert(@maxValue(u32) == 4294967295);
@@ -1055,8 +1136,9 @@ fn minValueAndMaxValue() {
     assert(@minValue(i64) == -9223372036854775808);
 }
 
-#attribute("test")
 fn overflowIntrinsics() {
+    @setFnTest(this, true);
+
     var result: u8 = undefined;
     assert(@addWithOverflow(u8, 250, 100, &result));
     assert(!@addWithOverflow(u8, 100, 150, &result));
@@ -1064,8 +1146,9 @@ fn overflowIntrinsics() {
 }
 
 
-#attribute("test")
 fn nestedArrays() {
+    @setFnTest(this, true);
+
     const array_of_strings = [][]u8 {"hello", "this", "is", "my", "thing"};
     for (array_of_strings) |s, i| {
         if (i == 0) assert(str.eql(s, "hello"));
@@ -1076,21 +1159,24 @@ fn nestedArrays() {
     }
 }
 
-#attribute("test")
 fn intToPtrCast() {
+    @setFnTest(this, true);
+
     const x = isize(13);
     const y = (&u8)(x);
     const z = usize(y);
     assert(z == 13);
 }
 
-#attribute("test")
 fn stringConcatenation() {
+    @setFnTest(this, true);
+
     assert(str.eql("OK" ++ " IT " ++ "WORKED", "OK IT WORKED"));
 }
 
-#attribute("test")
 fn constantStructWithNegation() {
+    @setFnTest(this, true);
+
     assert(vertices[0].x == -0.6);
 }
 struct Vertex {
@@ -1107,8 +1193,9 @@ const vertices = []Vertex {
 };
 
 
-#attribute("test")
 fn returnWithImplicitCastFromWhileLoop() {
+    @setFnTest(this, true);
+
     %%returnWithImplicitCastFromWhileLoopTest();
 }
 fn returnWithImplicitCastFromWhileLoopTest() -> %void {
@@ -1117,8 +1204,9 @@ fn returnWithImplicitCastFromWhileLoopTest() -> %void {
     }
 }
 
-#attribute("test")
 fn returnStructByvalFromFunction() {
+    @setFnTest(this, true);
+
     const bar = makeBar(1234, 5678);
     assert(bar.y == 5678);
 }
@@ -1133,8 +1221,9 @@ fn makeBar(x: i32, y: i32) -> Bar {
     }
 }
 
-#attribute("test")
 fn functionPointers() {
+    @setFnTest(this, true);
+
     const fns = []@typeOf(fn1) { fn1, fn2, fn3, fn4, };
     for (fns) |f, i| {
         assert(f() == u32(i) + 5);
@@ -1147,8 +1236,9 @@ fn fn4() -> u32 {8}
 
 
 
-#attribute("test")
 fn staticallyInitalizedStruct() {
+    @setFnTest(this, true);
+
     st_init_str_foo.x += 1;
     assert(st_init_str_foo.x == 14);
 }
@@ -1158,8 +1248,9 @@ struct StInitStrFoo {
 }
 var st_init_str_foo = StInitStrFoo { .x = 13, .y = true, };
 
-#attribute("test")
 fn staticallyInitializedArrayLiteral() {
+    @setFnTest(this, true);
+
     const y : [4]u8 = st_init_arr_lit_x;
     assert(y[3] == 4);
 }
@@ -1167,8 +1258,9 @@ const st_init_arr_lit_x = []u8{1,2,3,4};
 
 
 
-#attribute("test")
 fn pointerToVoidReturnType() {
+    @setFnTest(this, true);
+
     %%testPointerToVoidReturnType();
 }
 fn testPointerToVoidReturnType() -> %void {
@@ -1181,8 +1273,9 @@ fn testPointerToVoidReturnType2() -> &void {
 }
 
 
-#attribute("test")
 fn callResultOfIfElseExpression() {
+    @setFnTest(this, true);
+
     assert(str.eql(f2(true), "a"));
     assert(str.eql(f2(false), "b"));
 }
@@ -1193,8 +1286,9 @@ fn fA() -> []u8 { "a" }
 fn fB() -> []u8 { "b" }
 
 
-#attribute("test")
 fn constExpressionEvalHandlingOfVariables() {
+    @setFnTest(this, true);
+
     var x = true;
     while (x) {
         x = false;
@@ -1203,8 +1297,9 @@ fn constExpressionEvalHandlingOfVariables() {
 
 
 
-#attribute("test")
 fn constantEnumInitializationWithDifferingSizes() {
+    @setFnTest(this, true);
+
     test3_1(test3_foo);
     test3_2(test3_bar);
 }
@@ -1219,8 +1314,9 @@ struct Test3Point {
 }
 const test3_foo = Test3Foo.Three{Test3Point {.x = 3, .y = 4}};
 const test3_bar = Test3Foo.Two{13};
-#static_eval_enable(false)
 fn test3_1(f: Test3Foo) {
+    @setFnStaticEval(this, false);
+
     switch (f) {
         Three => |pt| {
             assert(pt.x == 3);
@@ -1229,8 +1325,9 @@ fn test3_1(f: Test3Foo) {
         else => @unreachable(),
     }
 }
-#static_eval_enable(false)
 fn test3_2(f: Test3Foo) {
+    @setFnStaticEval(this, false);
+
     switch (f) {
         Two => |x| {
             assert(x == 13);
@@ -1241,8 +1338,9 @@ fn test3_2(f: Test3Foo) {
 
 
 
-#attribute("test")
 fn whileWithContinueExpr() {
+    @setFnTest(this, true);
+
     var sum: i32 = 0;
     {var i: i32 = 0; while (i < 10; i += 1) {
         if (i == 5) continue;
@@ -1252,38 +1350,47 @@ fn whileWithContinueExpr() {
 }
 
 
-#attribute("test")
 fn forLoopWithPointerElemVar() {
+    @setFnTest(this, true);
+
     const source = "abcdefg";
     var target: [source.len]u8 = undefined;
     @memcpy(&target[0], &source[0], source.len);
     mangleString(target);
     assert(str.eql(target, "bcdefgh"));
 }
-#static_eval_enable(false)
 fn mangleString(s: []u8) {
+    @setFnStaticEval(this, false);
+
     for (s) |*c| {
         *c += 1;
     }
 }
 
-#attribute("test")
 fn emptyStructMethodCall() {
+    @setFnTest(this, true);
+
     const es = EmptyStruct{};
     assert(es.method() == 1234);
 }
 struct EmptyStruct {
-    #static_eval_enable(false)
-    fn method(es: EmptyStruct) -> i32 { 1234 }
+    fn method(es: EmptyStruct) -> i32 {
+        @setFnStaticEval(this, false);
+        1234
+    }
+
 }
 
 
-#attribute("test")
-fn @"weird function name"() { }
+fn @"weird function name"() {
+    @setFnTest(this, true);
+}
+
 
 
-#attribute("test")
 fn returnEmptyStructFromFn() {
+    @setFnTest(this, true);
+
     testReturnEmptyStructFromFn();
     testReturnEmptyStructFromFnNoeval();
 }
@@ -1291,13 +1398,15 @@ struct EmptyStruct2 {}
 fn testReturnEmptyStructFromFn() -> EmptyStruct2 {
     EmptyStruct2 {}
 }
-#static_eval_enable(false)
 fn testReturnEmptyStructFromFnNoeval() -> EmptyStruct2 {
+    @setFnStaticEval(this, false);
+
     EmptyStruct2 {}
 }
 
-#attribute("test")
 fn passSliceOfEmptyStructToFn() {
+    @setFnTest(this, true);
+
     assert(testPassSliceOfEmptyStructToFn([]EmptyStruct2{ EmptyStruct2{} }) == 1);
 }
 fn testPassSliceOfEmptyStructToFn(slice: []EmptyStruct2) -> usize {
@@ -1305,8 +1414,9 @@ fn testPassSliceOfEmptyStructToFn(slice: []EmptyStruct2) -> usize {
 }
 
 
-#attribute("test")
 fn pointerComparison() {
+    @setFnTest(this, true);
+
     const a = ([]u8)("a");
     const b = &a;
     assert(ptrEql(b, b));
@@ -1315,15 +1425,17 @@ fn ptrEql(a: &[]u8, b: &[]u8) -> bool {
     a == b
 }
 
-#attribute("test")
 fn characterLiterals() {
+    @setFnTest(this, true);
+
     assert('\'' == single_quote);
 }
 const single_quote = '\'';
 
 
-#attribute("test")
 fn switchWithMultipleExpressions() {
+    @setFnTest(this, true);
+
     const x: i32 = switch (returnsFive()) {
         1, 2, 3 => 1,
         4, 5, 6 => 2,
@@ -1331,12 +1443,16 @@ fn switchWithMultipleExpressions() {
     };
     assert(x == 2);
 }
-#static_eval_enable(false)
-fn returnsFive() -> i32 { 5 }
+fn returnsFive() -> i32 {
+    @setFnStaticEval(this, false);
+    5
+}
+
 
 
-#attribute("test")
 fn switchOnErrorUnion() {
+    @setFnTest(this, true);
+
     const x = switch (returnsTen()) {
         Ok => |val| val + 1,
         ItBroke, NoMem => 1,
@@ -1347,20 +1463,28 @@ fn switchOnErrorUnion() {
 error ItBroke;
 error NoMem;
 error CrappedOut;
-#static_eval_enable(false)
-fn returnsTen() -> %i32 { 10 }
+fn returnsTen() -> %i32 {
+    @setFnStaticEval(this, false);
+    10
+}
+
 
 
-#attribute("test")
 fn boolCmp() {
+    @setFnTest(this, true);
+
     assert(testBoolCmp(true, false) == false);
 }
-#static_eval_enable(false)
-fn testBoolCmp(a: bool, b: bool) -> bool { a == b }
+fn testBoolCmp(a: bool, b: bool) -> bool {
+    @setFnStaticEval(this, false);
+    a == b
+}
+
 
 
-#attribute("test")
 fn takeAddressOfParameter() {
+    @setFnTest(this, true);
+
     testTakeAddressOfParameter(12.34);
     testTakeAddressOfParameterNoeval(12.34);
 }
@@ -1368,20 +1492,23 @@ fn testTakeAddressOfParameter(f: f32) {
     const f_ptr = &f;
     assert(*f_ptr == 12.34);
 }
-#static_eval_enable(false)
 fn testTakeAddressOfParameterNoeval(f: f32) {
+    @setFnStaticEval(this, false);
+
     const f_ptr = &f;
     assert(*f_ptr == 12.34);
 }
 
 
-#attribute("test")
 fn arrayMultOperator() {
+    @setFnTest(this, true);
+
     assert(str.eql("ab" ** 5, "ababababab"));
 }
 
-#attribute("test")
 fn stringEscapes() {
+    @setFnTest(this, true);
+
     assert(str.eql("\"", "\x22"));
     assert(str.eql("\'", "\x27"));
     assert(str.eql("\n", "\x0a"));
@@ -1391,12 +1518,14 @@ fn stringEscapes() {
     assert(str.eql("\u1234\u0069", "\xe1\x88\xb4\x69"));
 }
 
-#attribute("test")
 fn ifVarMaybePointer() {
+    @setFnTest(this, true);
+
     assert(shouldBeAPlus1(Particle {.a = 14, .b = 1, .c = 1, .d = 1}) == 15);
 }
-#static_eval_enable(false)
 fn shouldBeAPlus1(p: Particle) -> u64 {
+    @setFnStaticEval(this, false);
+
     var maybe_particle: ?Particle = p;
     if (const *particle ?= maybe_particle) {
         particle.a += 1;
@@ -1413,8 +1542,9 @@ struct Particle {
     d: u64,
 }
 
-#attribute("test")
 fn assignToIfVarPtr() {
+    @setFnTest(this, true);
+
     var maybe_bool: ?bool = true;
 
     if (const *b ?= maybe_bool) {
@@ -1424,22 +1554,25 @@ fn assignToIfVarPtr() {
     assert(??maybe_bool == false);
 }
 
-#attribute("test")
 fn cmpxchg() {
+    @setFnTest(this, true);
+
     var x: i32 = 1234;
     while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.SeqCst, AtomicOrder.SeqCst)) {}
     assert(x == 5678);
 }
 
-#attribute("test")
 fn fence() {
+    @setFnTest(this, true);
+
     var x: i32 = 1234;
     @fence(AtomicOrder.SeqCst);
     x = 5678;
 }
 
-#attribute("test")
 fn unsignedWrapping() {
+    @setFnTest(this, true);
+
     testUnsignedWrappingEval(@maxValue(u32));
     testUnsignedWrappingNoeval(@maxValue(u32));
 }
@@ -1449,16 +1582,18 @@ fn testUnsignedWrappingEval(x: u32) {
     const orig = zero -% 1;
     assert(orig == @maxValue(u32));
 }
-#static_eval_enable(false)
 fn testUnsignedWrappingNoeval(x: u32) {
+    @setFnStaticEval(this, false);
+
     const zero = x +% 1;
     assert(zero == 0);
     const orig = zero -% 1;
     assert(orig == @maxValue(u32));
 }
 
-#attribute("test")
 fn signedWrapping() {
+    @setFnTest(this, true);
+
     testSignedWrappingEval(@maxValue(i32));
     testSignedWrappingNoeval(@maxValue(i32));
 }
@@ -1468,16 +1603,18 @@ fn testSignedWrappingEval(x: i32) {
     const max_val = min_val -% 1;
     assert(max_val == @maxValue(i32));
 }
-#static_eval_enable(false)
 fn testSignedWrappingNoeval(x: i32) {
+    @setFnStaticEval(this, false);
+
     const min_val = x +% 1;
     assert(min_val == @minValue(i32));
     const max_val = min_val -% 1;
     assert(max_val == @maxValue(i32));
 }
 
-#attribute("test")
 fn negationWrapping() {
+    @setFnTest(this, true);
+
     testNegationWrappingEval(@minValue(i16));
     testNegationWrappingNoeval(@minValue(i16));
 }
@@ -1486,15 +1623,17 @@ fn testNegationWrappingEval(x: i16) {
     const neg = -%x;
     assert(neg == -32768);
 }
-#static_eval_enable(false)
 fn testNegationWrappingNoeval(x: i16) {
+    @setFnStaticEval(this, false);
+
     assert(x == -32768);
     const neg = -%x;
     assert(neg == -32768);
 }
 
-#attribute("test")
 fn shlWrapping() {
+    @setFnTest(this, true);
+
     testShlWrappingEval(@maxValue(u16));
     testShlWrappingNoeval(@maxValue(u16));
 }
@@ -1502,22 +1641,25 @@ fn testShlWrappingEval(x: u16) {
     const shifted = x <<% 1;
     assert(shifted == 65534);
 }
-#static_eval_enable(false)
 fn testShlWrappingNoeval(x: u16) {
+    @setFnStaticEval(this, false);
+
     const shifted = x <<% 1;
     assert(shifted == 65534);
 }
 
-#attribute("test")
 fn shlWithOverflow() {
+    @setFnTest(this, true);
+
     var result: u16 = undefined;
     assert(@shlWithOverflow(u16, 0b0010111111111111, 3, &result));
     assert(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result));
     assert(result == 0b1011111111111100);
 }
 
-#attribute("test")
 fn cStringConcatenation() {
+    @setFnTest(this, true);
+
     const a = c"OK" ++ c" IT " ++ c"WORKED";
     const b = c"OK IT WORKED";
 
@@ -1530,8 +1672,9 @@ fn cStringConcatenation() {
     assert(b[len] == 0);
 }
 
-#attribute("test")
 fn genericStruct() {
+    @setFnTest(this, true);
+
     var a1 = GenNode(i32) {.value = 13, .next = null,};
     var b1 = GenNode(bool) {.value = true, .next = null,};
     assert(a1.value == 13);
@@ -1544,8 +1687,9 @@ struct GenNode(T: type) {
     fn getVal(n: &const GenNode(T)) -> T { n.value }
 }
 
-#attribute("test")
 fn castSliceToU8Slice() {
+    @setFnTest(this, true);
+
     assert(@sizeOf(i32) == 4);
     var big_thing_array = []i32{1, 2, 3, 4};
     const big_thing_slice: []i32 = big_thing_array;
@@ -1565,26 +1709,31 @@ fn castSliceToU8Slice() {
     assert(bytes[11] == @maxValue(u8));
 }
 
-#attribute("test")
 fn floatDivision() {
+    @setFnTest(this, true);
+
     assert(fdiv32(12.0, 3.0) == 4.0);
 }
-#static_eval_enable(false)
 fn fdiv32(a: f32, b: f32) -> f32 {
+    @setFnStaticEval(this, false);
+
     a / b
 }
 
-#attribute("test")
 fn exactDivision() {
+    @setFnTest(this, true);
+
     assert(divExact(55, 11) == 5);
 }
-#static_eval_enable(false)
 fn divExact(a: u32, b: u32) -> u32 {
+    @setFnStaticEval(this, false);
+
     @divExact(a, b)
 }
 
-#attribute("test")
 fn nullLiteralOutsideFunction() {
+    @setFnTest(this, true);
+
     const is_null = if (const _ ?= here_is_a_null_literal.context) false else true;
     assert(is_null);
 }
@@ -1595,25 +1744,29 @@ const here_is_a_null_literal = SillyStruct {
     .context = null,
 };
 
-#attribute("test")
 fn truncate() {
+    @setFnTest(this, true);
+
     assert(testTruncate(0x10fd) == 0xfd);
 }
-#static_eval_enable(false)
 fn testTruncate(x: u32) -> u8 {
+    @setFnStaticEval(this, false);
+
     @truncate(u8, x)
 }
 
-#attribute("test")
 fn constDeclsInStruct() {
+    @setFnTest(this, true);
+
     assert(GenericDataThing(3).count_plus_one == 4);
 }
 struct GenericDataThing(count: isize) {
     const count_plus_one = count + 1;
 }
 
-#attribute("test")
 fn useGenericParamInGenericParam() {
+    @setFnTest(this, true);
+
     assert(aGenericFn(i32, 3, 4) == 7);
 }
 fn aGenericFn(inline T: type, inline a: T, b: T) -> T {
@@ -1621,14 +1774,16 @@ fn aGenericFn(inline T: type, inline a: T, b: T) -> T {
 }
 
 
-#attribute("test")
 fn unsigned64BitDivision() {
+    @setFnTest(this, true);
+
     const result = div(1152921504606846976, 34359738365);
     assert(result.quotient == 33554432);
     assert(result.remainder == 100663296);
 }
-#static_eval_enable(false)
 fn div(a: u64, b: u64) -> DivResult {
+    @setFnStaticEval(this, false);
+
     DivResult {
         .quotient = a / b,
         .remainder = a % b,
@@ -1639,8 +1794,9 @@ struct DivResult {
     remainder: u64,
 }
 
-#attribute("test")
 fn intTypeBuiltin() {
+    @setFnTest(this, true);
+
     assert(@intType(true, 8) == i8);
     assert(@intType(true, 16) == i16);
     assert(@intType(true, 32) == i32);
@@ -1670,16 +1826,18 @@ fn intTypeBuiltin() {
 
 }
 
-#attribute("test")
 fn intToEnum() {
+    @setFnTest(this, true);
+
     testIntToEnumEval(3);
     testIntToEnumNoeval(3);
 }
 fn testIntToEnumEval(x: i32) {
     assert(IntToEnumNumber(x) == IntToEnumNumber.Three);
 }
-#static_eval_enable(false)
 fn testIntToEnumNoeval(x: i32) {
+    @setFnStaticEval(this, false);
+
     assert(IntToEnumNumber(x) == IntToEnumNumber.Three);
 }
 enum IntToEnumNumber {