diff --git a/doc/langref.html.in b/doc/langref.html.in
index c1fe08ddb..44b225681 100644
--- a/doc/langref.html.in
+++ b/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.
+ {#syntax#}@typeOf{#endsyntax#} guarantees no run-time side-effects within the expression:
+ {#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#}
diff --git a/src/all_types.hpp b/src/all_types.hpp
index 87756d338..aee6d3994 100644
--- a/src/all_types.hpp
+++ b/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,
diff --git a/src/analyze.cpp b/src/analyze.cpp
index 43c8d499d..df5b27784 100644
--- a/src/analyze.cpp
+++ b/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(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(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)
{
diff --git a/src/analyze.hpp b/src/analyze.hpp
index 6e8897bf8..dc702167e 100644
--- a/src/analyze.hpp
+++ b/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);
diff --git a/src/codegen.cpp b/src/codegen.cpp
index d87b5d0ae..33713a9b3 100644
--- a/src/codegen.cpp
+++ b/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);
diff --git a/src/ir.cpp b/src/ir.cpp
index 02a134c62..ad81b27a9 100644
--- a/src/ir.cpp
+++ b/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;
diff --git a/test/stage1/behavior/sizeof_and_typeof.zig b/test/stage1/behavior/sizeof_and_typeof.zig
index da79c3a27..6f57bfedd 100644
--- a/test/stage1/behavior/sizeof_and_typeof.zig
+++ b/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);
+}