| author | |
| committer | |
| log | a2230639232c069e4052a2e994dd5c0bd4e2517f |
| tree | e8bdb0438078f740d1509ded7e4b67162e7ac8e8 |
| parent | 6ab8b2aab4b146a7d1d882686199eace19989011 |
| signature |
related: #16277 files changed, 90 insertions(+), 1 deletions(-)
doc/langref.html.in+16| ... | ... | @@ -8114,7 +8114,23 @@ pub const TypeInfo = union(TypeId) { |
| 8114 | 8114 | This function returns a compile-time constant, which is the type of the |
| 8115 | 8115 | expression passed as an argument. The expression is evaluated. |
| 8116 | 8116 | </p> |
| 8117 | <p>{#syntax#}@typeOf{#endsyntax#} guarantees no run-time side-effects within the expression:</p> | |
| 8118 | {#code_begin|test#} | |
| 8119 | const std = @import("std"); | |
| 8120 | const assert = std.debug.assert; | |
| 8121 | ||
| 8122 | test "no runtime side effects" { | |
| 8123 | var data: i32 = 0; | |
| 8124 | const T = @typeOf(foo(i32, &data)); | |
| 8125 | comptime assert(T == i32); | |
| 8126 | assert(data == 0); | |
| 8127 | } | |
| 8117 | 8128 | |
| 8129 | fn foo(comptime T: type, ptr: *T) T { | |
| 8130 | ptr.* += 1; | |
| 8131 | return ptr.*; | |
| 8132 | } | |
| 8133 | {#code_end#} | |
| 8118 | 8134 | {#header_close#} |
| 8119 | 8135 | |
| 8120 | 8136 | {#header_open|@unionInit#} |
src/all_types.hpp+8| ... | ... | @@ -2104,6 +2104,7 @@ enum ScopeId { |
| 2104 | 2104 | ScopeIdFnDef, |
| 2105 | 2105 | ScopeIdCompTime, |
| 2106 | 2106 | ScopeIdRuntime, |
| 2107 | ScopeIdTypeOf, | |
| 2107 | 2108 | }; |
| 2108 | 2109 | |
| 2109 | 2110 | struct Scope { |
| ... | ... | @@ -2244,6 +2245,13 @@ struct ScopeFnDef { |
| 2244 | 2245 | ZigFn *fn_entry; |
| 2245 | 2246 | }; |
| 2246 | 2247 | |
| 2248 | // This scope is created for a @typeOf. | |
| 2249 | // All runtime side-effects are elided within it. | |
| 2250 | // NodeTypeFnCallExpr | |
| 2251 | struct ScopeTypeOf { | |
| 2252 | Scope base; | |
| 2253 | }; | |
| 2254 | ||
| 2247 | 2255 | // synchronized with code in define_builtin_compile_vars |
| 2248 | 2256 | enum AtomicOrder { |
| 2249 | 2257 | AtomicOrderUnordered, |
src/analyze.cpp+22| ... | ... | @@ -197,6 +197,12 @@ Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent) { |
| 197 | 197 | return &scope->base; |
| 198 | 198 | } |
| 199 | 199 | |
| 200 | Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent) { | |
| 201 | ScopeTypeOf *scope = allocate<ScopeTypeOf>(1); | |
| 202 | init_scope(g, &scope->base, ScopeIdTypeOf, node, parent); | |
| 203 | return &scope->base; | |
| 204 | } | |
| 205 | ||
| 200 | 206 | ZigType *get_scope_import(Scope *scope) { |
| 201 | 207 | while (scope) { |
| 202 | 208 | if (scope->id == ScopeIdDecls) { |
| ... | ... | @@ -209,6 +215,22 @@ ZigType *get_scope_import(Scope *scope) { |
| 209 | 215 | zig_unreachable(); |
| 210 | 216 | } |
| 211 | 217 | |
| 218 | ScopeTypeOf *get_scope_typeof(Scope *scope) { | |
| 219 | while (scope) { | |
| 220 | switch (scope->id) { | |
| 221 | case ScopeIdTypeOf: | |
| 222 | return reinterpret_cast<ScopeTypeOf *>(scope); | |
| 223 | case ScopeIdFnDef: | |
| 224 | case ScopeIdDecls: | |
| 225 | return nullptr; | |
| 226 | default: | |
| 227 | scope = scope->parent; | |
| 228 | continue; | |
| 229 | } | |
| 230 | } | |
| 231 | zig_unreachable(); | |
| 232 | } | |
| 233 | ||
| 212 | 234 | static ZigType *new_container_type_entry(CodeGen *g, ZigTypeId id, AstNode *source_node, Scope *parent_scope, |
| 213 | 235 | Buf *bare_name) |
| 214 | 236 | { |
src/analyze.hpp+2| ... | ... | @@ -85,6 +85,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node); |
| 85 | 85 | ZigFn *scope_fn_entry(Scope *scope); |
| 86 | 86 | ZigPackage *scope_package(Scope *scope); |
| 87 | 87 | ZigType *get_scope_import(Scope *scope); |
| 88 | ScopeTypeOf *get_scope_typeof(Scope *scope); | |
| 88 | 89 | void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node, Scope *parent_scope); |
| 89 | 90 | ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf *name, |
| 90 | 91 | 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); |
| 112 | 113 | ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry); |
| 113 | 114 | Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent); |
| 114 | 115 | Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime); |
| 116 | Scope *create_typeof_scope(CodeGen *g, AstNode *node, Scope *parent); | |
| 115 | 117 | |
| 116 | 118 | void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str); |
| 117 | 119 | ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str); |
src/codegen.cpp+7| ... | ... | @@ -645,6 +645,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) { |
| 645 | 645 | case ScopeIdSuspend: |
| 646 | 646 | case ScopeIdCompTime: |
| 647 | 647 | case ScopeIdRuntime: |
| 648 | case ScopeIdTypeOf: | |
| 648 | 649 | return get_di_scope(g, scope->parent); |
| 649 | 650 | } |
| 650 | 651 | zig_unreachable(); |
| ... | ... | @@ -3757,6 +3758,7 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) { |
| 3757 | 3758 | case ScopeIdSuspend: |
| 3758 | 3759 | case ScopeIdCompTime: |
| 3759 | 3760 | case ScopeIdRuntime: |
| 3761 | case ScopeIdTypeOf: | |
| 3760 | 3762 | scope = scope->parent; |
| 3761 | 3763 | continue; |
| 3762 | 3764 | } |
| ... | ... | @@ -5942,12 +5944,17 @@ static void ir_render(CodeGen *g, ZigFn *fn_entry) { |
| 5942 | 5944 | |
| 5943 | 5945 | for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) { |
| 5944 | 5946 | IrBasicBlock *current_block = executable->basic_block_list.at(block_i); |
| 5947 | if (get_scope_typeof(current_block->scope) != nullptr) { | |
| 5948 | LLVMBuildBr(g->builder, current_block->llvm_block); | |
| 5949 | } | |
| 5945 | 5950 | assert(current_block->llvm_block); |
| 5946 | 5951 | LLVMPositionBuilderAtEnd(g->builder, current_block->llvm_block); |
| 5947 | 5952 | for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) { |
| 5948 | 5953 | IrInstruction *instruction = current_block->instruction_list.at(instr_i); |
| 5949 | 5954 | if (instruction->ref_count == 0 && !ir_has_side_effects(instruction)) |
| 5950 | 5955 | continue; |
| 5956 | if (get_scope_typeof(instruction->scope) != nullptr) | |
| 5957 | continue; | |
| 5951 | 5958 | |
| 5952 | 5959 | if (!g->strip_debug_symbols) { |
| 5953 | 5960 | set_debug_location(g, instruction); |
src/ir.cpp+9-1| ... | ... | @@ -3344,6 +3344,7 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco |
| 3344 | 3344 | case ScopeIdSuspend: |
| 3345 | 3345 | case ScopeIdCompTime: |
| 3346 | 3346 | case ScopeIdRuntime: |
| 3347 | case ScopeIdTypeOf: | |
| 3347 | 3348 | scope = scope->parent; |
| 3348 | 3349 | continue; |
| 3349 | 3350 | case ScopeIdDeferExpr: |
| ... | ... | @@ -3399,6 +3400,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o |
| 3399 | 3400 | case ScopeIdSuspend: |
| 3400 | 3401 | case ScopeIdCompTime: |
| 3401 | 3402 | case ScopeIdRuntime: |
| 3403 | case ScopeIdTypeOf: | |
| 3402 | 3404 | scope = scope->parent; |
| 3403 | 3405 | continue; |
| 3404 | 3406 | case ScopeIdDeferExpr: |
| ... | ... | @@ -4379,8 +4381,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo |
| 4379 | 4381 | zig_unreachable(); |
| 4380 | 4382 | case BuiltinFnIdTypeof: |
| 4381 | 4383 | { |
| 4384 | Scope *sub_scope = create_typeof_scope(irb->codegen, node, scope); | |
| 4385 | ||
| 4382 | 4386 | AstNode *arg_node = node->data.fn_call_expr.params.at(0); |
| 4383 | IrInstruction *arg = ir_gen_node(irb, arg_node, scope); | |
| 4387 | IrInstruction *arg = ir_gen_node(irb, arg_node, sub_scope); | |
| 4384 | 4388 | if (arg == irb->codegen->invalid_instruction) |
| 4385 | 4389 | return arg; |
| 4386 | 4390 | |
| ... | ... | @@ -8269,6 +8273,10 @@ static ConstExprValue *ir_exec_const_result(CodeGen *codegen, IrExecutable *exec |
| 8269 | 8273 | break; |
| 8270 | 8274 | } |
| 8271 | 8275 | } |
| 8276 | if (get_scope_typeof(instruction->scope) != nullptr) { | |
| 8277 | // doesn't count, it's inside a @typeOf() | |
| 8278 | continue; | |
| 8279 | } | |
| 8272 | 8280 | exec_add_error_node(codegen, exec, instruction->source_node, |
| 8273 | 8281 | buf_sprintf("unable to evaluate constant expression")); |
| 8274 | 8282 | return &codegen->invalid_instruction->value; |
test/stage1/behavior/sizeof_and_typeof.zig+26| ... | ... | @@ -89,3 +89,29 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" { |
| 89 | 89 | expect(@sizeOf(S.Foo) == 4); |
| 90 | 90 | expect(@sizeOf(S.Bar) == 8); |
| 91 | 91 | } |
| 92 | ||
| 93 | test "@typeOf() has no runtime side effects" { | |
| 94 | const S = struct { | |
| 95 | fn foo(comptime T: type, ptr: *T) T { | |
| 96 | ptr.* += 1; | |
| 97 | return ptr.*; | |
| 98 | } | |
| 99 | }; | |
| 100 | var data: i32 = 0; | |
| 101 | const T = @typeOf(S.foo(i32, &data)); | |
| 102 | comptime expect(T == i32); | |
| 103 | expect(data == 0); | |
| 104 | } | |
| 105 | ||
| 106 | test "branching logic inside @typeOf" { | |
| 107 | const S = struct { | |
| 108 | var data: i32 = 0; | |
| 109 | fn foo() anyerror!i32 { | |
| 110 | data += 1; | |
| 111 | return undefined; | |
| 112 | } | |
| 113 | }; | |
| 114 | const T = @typeOf(S.foo() catch undefined); | |
| 115 | comptime expect(T == i32); | |
| 116 | expect(S.data == 0); | |
| 117 | } |