Commit a223063923

Andrew Kelley <andrew@ziglang.org>
2019-08-31 16:38:18
`@typeOf` now guarantees no runtime side effects
related: #1627
1 parent 6ab8b2a
doc/langref.html.in
@@ -8114,7 +8114,23 @@ pub const TypeInfo = union(TypeId) {
       This function returns a compile-time constant, which is the type of the
       expression passed as an argument. The expression is evaluated.
       </p>
+      <p>{#syntax#}@typeOf{#endsyntax#} guarantees no run-time side-effects within the expression:</p>
+      {#code_begin|test#}
+const std = @import("std");
+const assert = std.debug.assert;
+
+test "no runtime side effects" {
+    var data: i32 = 0;
+    const T = @typeOf(foo(i32, &data));
+    comptime assert(T == i32);
+    assert(data == 0);
+}
 
+fn foo(comptime T: type, ptr: *T) T {
+    ptr.* += 1;
+    return ptr.*;
+}
+      {#code_end#}
       {#header_close#}
 
       {#header_open|@unionInit#}
src/all_types.hpp
@@ -2104,6 +2104,7 @@ enum ScopeId {
     ScopeIdFnDef,
     ScopeIdCompTime,
     ScopeIdRuntime,
+    ScopeIdTypeOf,
 };
 
 struct Scope {
@@ -2244,6 +2245,13 @@ struct ScopeFnDef {
     ZigFn *fn_entry;
 };
 
+// This scope is created for a @typeOf.
+// All runtime side-effects are elided within it.
+// NodeTypeFnCallExpr
+struct ScopeTypeOf {
+    Scope base;
+};
+
 // synchronized with code in define_builtin_compile_vars
 enum AtomicOrder {
     AtomicOrderUnordered,
src/analyze.cpp
@@ -197,6 +197,12 @@ Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent) {
     return &scope->base;
 }
 
+Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent) {
+    ScopeTypeOf *scope = allocate<ScopeTypeOf>(1);
+    init_scope(g, &scope->base, ScopeIdTypeOf, node, parent);
+    return &scope->base;
+}
+
 ZigType *get_scope_import(Scope *scope) {
     while (scope) {
         if (scope->id == ScopeIdDecls) {
@@ -209,6 +215,22 @@ ZigType *get_scope_import(Scope *scope) {
     zig_unreachable();
 }
 
+ScopeTypeOf *get_scope_typeof(Scope *scope) {
+    while (scope) {
+        switch (scope->id) {
+            case ScopeIdTypeOf:
+                return reinterpret_cast<ScopeTypeOf *>(scope);
+            case ScopeIdFnDef:
+            case ScopeIdDecls:
+                return nullptr;
+            default:
+                scope = scope->parent;
+                continue;
+        }
+    }
+    zig_unreachable();
+}
+
 static ZigType *new_container_type_entry(CodeGen *g, ZigTypeId id, AstNode *source_node, Scope *parent_scope,
         Buf *bare_name)
 {
src/analyze.hpp
@@ -85,6 +85,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node);
 ZigFn *scope_fn_entry(Scope *scope);
 ZigPackage *scope_package(Scope *scope);
 ZigType *get_scope_import(Scope *scope);
+ScopeTypeOf *get_scope_typeof(Scope *scope);
 void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node, Scope *parent_scope);
 ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf *name,
     bool is_const, ConstExprValue *init_value, Tld *src_tld, ZigType *var_type);
@@ -112,6 +113,7 @@ ScopeSuspend *create_suspend_scope(CodeGen *g, AstNode *node, Scope *parent);
 ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry);
 Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent);
 Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime);
+Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent);
 
 void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str);
 ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str);
src/codegen.cpp
@@ -645,6 +645,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) {
         case ScopeIdSuspend:
         case ScopeIdCompTime:
         case ScopeIdRuntime:
+        case ScopeIdTypeOf:
             return get_di_scope(g, scope->parent);
     }
     zig_unreachable();
@@ -3757,6 +3758,7 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) {
             case ScopeIdSuspend:
             case ScopeIdCompTime:
             case ScopeIdRuntime:
+            case ScopeIdTypeOf:
                 scope = scope->parent;
                 continue;
         }
@@ -5942,12 +5944,17 @@ static void ir_render(CodeGen *g, ZigFn *fn_entry) {
 
     for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) {
         IrBasicBlock *current_block = executable->basic_block_list.at(block_i);
+        if (get_scope_typeof(current_block->scope) != nullptr) {
+            LLVMBuildBr(g->builder, current_block->llvm_block);
+        }
         assert(current_block->llvm_block);
         LLVMPositionBuilderAtEnd(g->builder, current_block->llvm_block);
         for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) {
             IrInstruction *instruction = current_block->instruction_list.at(instr_i);
             if (instruction->ref_count == 0 && !ir_has_side_effects(instruction))
                 continue;
+            if (get_scope_typeof(instruction->scope) != nullptr)
+                continue;
 
             if (!g->strip_debug_symbols) {
                 set_debug_location(g, instruction);
src/ir.cpp
@@ -3344,6 +3344,7 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco
             case ScopeIdSuspend:
             case ScopeIdCompTime:
             case ScopeIdRuntime:
+            case ScopeIdTypeOf:
                 scope = scope->parent;
                 continue;
             case ScopeIdDeferExpr:
@@ -3399,6 +3400,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o
             case ScopeIdSuspend:
             case ScopeIdCompTime:
             case ScopeIdRuntime:
+            case ScopeIdTypeOf:
                 scope = scope->parent;
                 continue;
             case ScopeIdDeferExpr:
@@ -4379,8 +4381,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
             zig_unreachable();
         case BuiltinFnIdTypeof:
             {
+                Scope *sub_scope = create_typeof_scope(irb->codegen, node, scope);
+
                 AstNode *arg_node = node->data.fn_call_expr.params.at(0);
-                IrInstruction *arg = ir_gen_node(irb, arg_node, scope);
+                IrInstruction *arg = ir_gen_node(irb, arg_node, sub_scope);
                 if (arg == irb->codegen->invalid_instruction)
                     return arg;
 
@@ -8269,6 +8273,10 @@ static ConstExprValue *ir_exec_const_result(CodeGen *codegen, IrExecutable *exec
                         break;
                 }
             }
+            if (get_scope_typeof(instruction->scope) != nullptr) {
+                // doesn't count, it's inside a @typeOf()
+                continue;
+            }
             exec_add_error_node(codegen, exec, instruction->source_node,
                     buf_sprintf("unable to evaluate constant expression"));
             return &codegen->invalid_instruction->value;
test/stage1/behavior/sizeof_and_typeof.zig
@@ -89,3 +89,29 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" {
     expect(@sizeOf(S.Foo) == 4);
     expect(@sizeOf(S.Bar) == 8);
 }
+
+test "@typeOf() has no runtime side effects" {
+    const S = struct {
+        fn foo(comptime T: type, ptr: *T) T {
+            ptr.* += 1;
+            return ptr.*;
+        }
+    };
+    var data: i32 = 0;
+    const T = @typeOf(S.foo(i32, &data));
+    comptime expect(T == i32);
+    expect(data == 0);
+}
+
+test "branching logic inside @typeOf" {
+    const S = struct {
+        var data: i32 = 0;
+        fn foo() anyerror!i32 {
+            data += 1;
+            return undefined;
+        }
+    };
+    const T = @typeOf(S.foo() catch undefined);
+    comptime expect(T == i32);
+    expect(S.data == 0);
+}