| 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,7 +8114,23 @@ pub const TypeInfo = union(TypeId) { |
| 8114 | This function returns a compile-time constant, which is the type of the | 8114 | This function returns a compile-time constant, which is the type of the |
| 8115 | expression passed as an argument. The expression is evaluated. | 8115 | expression passed as an argument. The expression is evaluated. |
| 8116 | </p> | 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 | {#header_close#} | 8134 | {#header_close#} |
| 8119 | 8135 | ||
| 8120 | {#header_open|@unionInit#} | 8136 | {#header_open|@unionInit#} |
src/all_types.hpp+8| ... | @@ -2104,6 +2104,7 @@ enum ScopeId { | ... | @@ -2104,6 +2104,7 @@ enum ScopeId { |
| 2104 | ScopeIdFnDef, | 2104 | ScopeIdFnDef, |
| 2105 | ScopeIdCompTime, | 2105 | ScopeIdCompTime, |
| 2106 | ScopeIdRuntime, | 2106 | ScopeIdRuntime, |
| 2107 | ScopeIdTypeOf, | ||
| 2107 | }; | 2108 | }; |
| 2108 | 2109 | ||
| 2109 | struct Scope { | 2110 | struct Scope { |
| ... | @@ -2244,6 +2245,13 @@ struct ScopeFnDef { | ... | @@ -2244,6 +2245,13 @@ struct ScopeFnDef { |
| 2244 | ZigFn *fn_entry; | 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 | // synchronized with code in define_builtin_compile_vars | 2255 | // synchronized with code in define_builtin_compile_vars |
| 2248 | enum AtomicOrder { | 2256 | enum AtomicOrder { |
| 2249 | AtomicOrderUnordered, | 2257 | AtomicOrderUnordered, |
src/analyze.cpp+22| ... | @@ -197,6 +197,12 @@ Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent) { | ... | @@ -197,6 +197,12 @@ Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent) { |
| 197 | return &scope->base; | 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 | ZigType *get_scope_import(Scope *scope) { | 206 | ZigType *get_scope_import(Scope *scope) { |
| 201 | while (scope) { | 207 | while (scope) { |
| 202 | if (scope->id == ScopeIdDecls) { | 208 | if (scope->id == ScopeIdDecls) { |
| ... | @@ -209,6 +215,22 @@ ZigType *get_scope_import(Scope *scope) { | ... | @@ -209,6 +215,22 @@ ZigType *get_scope_import(Scope *scope) { |
| 209 | zig_unreachable(); | 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 | static ZigType *new_container_type_entry(CodeGen *g, ZigTypeId id, AstNode *source_node, Scope *parent_scope, | 234 | static ZigType *new_container_type_entry(CodeGen *g, ZigTypeId id, AstNode *source_node, Scope *parent_scope, |
| 213 | Buf *bare_name) | 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,6 +85,7 @@ void scan_decls(CodeGen *g, ScopeDecls *decls_scope, AstNode *node); |
| 85 | ZigFn *scope_fn_entry(Scope *scope); | 85 | ZigFn *scope_fn_entry(Scope *scope); |
| 86 | ZigPackage *scope_package(Scope *scope); | 86 | ZigPackage *scope_package(Scope *scope); |
| 87 | ZigType *get_scope_import(Scope *scope); | 87 | ZigType *get_scope_import(Scope *scope); |
| 88 | ScopeTypeOf *get_scope_typeof(Scope *scope); | ||
| 88 | void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node, Scope *parent_scope); | 89 | void init_tld(Tld *tld, TldId id, Buf *name, VisibMod visib_mod, AstNode *source_node, Scope *parent_scope); |
| 89 | ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf *name, | 90 | ZigVar *add_variable(CodeGen *g, AstNode *source_node, Scope *parent_scope, Buf *name, |
| 90 | bool is_const, ConstExprValue *init_value, Tld *src_tld, ZigType *var_type); | 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,6 +113,7 @@ ScopeSuspend *create_suspend_scope(CodeGen *g, AstNode *node, Scope *parent); |
| 112 | ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry); | 113 | ScopeFnDef *create_fndef_scope(CodeGen *g, AstNode *node, Scope *parent, ZigFn *fn_entry); |
| 113 | Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent); | 114 | Scope *create_comptime_scope(CodeGen *g, AstNode *node, Scope *parent); |
| 114 | Scope *create_runtime_scope(CodeGen *g, AstNode *node, Scope *parent, IrInstruction *is_comptime); | 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 | void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str); | 118 | void init_const_str_lit(CodeGen *g, ConstExprValue *const_val, Buf *str); |
| 117 | ConstExprValue *create_const_str_lit(CodeGen *g, Buf *str); | 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,6 +645,7 @@ static ZigLLVMDIScope *get_di_scope(CodeGen *g, Scope *scope) { |
| 645 | case ScopeIdSuspend: | 645 | case ScopeIdSuspend: |
| 646 | case ScopeIdCompTime: | 646 | case ScopeIdCompTime: |
| 647 | case ScopeIdRuntime: | 647 | case ScopeIdRuntime: |
| 648 | case ScopeIdTypeOf: | ||
| 648 | return get_di_scope(g, scope->parent); | 649 | return get_di_scope(g, scope->parent); |
| 649 | } | 650 | } |
| 650 | zig_unreachable(); | 651 | zig_unreachable(); |
| ... | @@ -3757,6 +3758,7 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) { | ... | @@ -3757,6 +3758,7 @@ static void render_async_var_decls(CodeGen *g, Scope *scope) { |
| 3757 | case ScopeIdSuspend: | 3758 | case ScopeIdSuspend: |
| 3758 | case ScopeIdCompTime: | 3759 | case ScopeIdCompTime: |
| 3759 | case ScopeIdRuntime: | 3760 | case ScopeIdRuntime: |
| 3761 | case ScopeIdTypeOf: | ||
| 3760 | scope = scope->parent; | 3762 | scope = scope->parent; |
| 3761 | continue; | 3763 | continue; |
| 3762 | } | 3764 | } |
| ... | @@ -5942,12 +5944,17 @@ static void ir_render(CodeGen *g, ZigFn *fn_entry) { | ... | @@ -5942,12 +5944,17 @@ static void ir_render(CodeGen *g, ZigFn *fn_entry) { |
| 5942 | 5944 | ||
| 5943 | for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) { | 5945 | for (size_t block_i = 0; block_i < executable->basic_block_list.length; block_i += 1) { |
| 5944 | IrBasicBlock *current_block = executable->basic_block_list.at(block_i); | 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 | assert(current_block->llvm_block); | 5950 | assert(current_block->llvm_block); |
| 5946 | LLVMPositionBuilderAtEnd(g->builder, current_block->llvm_block); | 5951 | LLVMPositionBuilderAtEnd(g->builder, current_block->llvm_block); |
| 5947 | for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) { | 5952 | for (size_t instr_i = 0; instr_i < current_block->instruction_list.length; instr_i += 1) { |
| 5948 | IrInstruction *instruction = current_block->instruction_list.at(instr_i); | 5953 | IrInstruction *instruction = current_block->instruction_list.at(instr_i); |
| 5949 | if (instruction->ref_count == 0 && !ir_has_side_effects(instruction)) | 5954 | if (instruction->ref_count == 0 && !ir_has_side_effects(instruction)) |
| 5950 | continue; | 5955 | continue; |
| 5956 | if (get_scope_typeof(instruction->scope) != nullptr) | ||
| 5957 | continue; | ||
| 5951 | 5958 | ||
| 5952 | if (!g->strip_debug_symbols) { | 5959 | if (!g->strip_debug_symbols) { |
| 5953 | set_debug_location(g, instruction); | 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,6 +3344,7 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco |
| 3344 | case ScopeIdSuspend: | 3344 | case ScopeIdSuspend: |
| 3345 | case ScopeIdCompTime: | 3345 | case ScopeIdCompTime: |
| 3346 | case ScopeIdRuntime: | 3346 | case ScopeIdRuntime: |
| 3347 | case ScopeIdTypeOf: | ||
| 3347 | scope = scope->parent; | 3348 | scope = scope->parent; |
| 3348 | continue; | 3349 | continue; |
| 3349 | case ScopeIdDeferExpr: | 3350 | case ScopeIdDeferExpr: |
| ... | @@ -3399,6 +3400,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o | ... | @@ -3399,6 +3400,7 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o |
| 3399 | case ScopeIdSuspend: | 3400 | case ScopeIdSuspend: |
| 3400 | case ScopeIdCompTime: | 3401 | case ScopeIdCompTime: |
| 3401 | case ScopeIdRuntime: | 3402 | case ScopeIdRuntime: |
| 3403 | case ScopeIdTypeOf: | ||
| 3402 | scope = scope->parent; | 3404 | scope = scope->parent; |
| 3403 | continue; | 3405 | continue; |
| 3404 | case ScopeIdDeferExpr: | 3406 | case ScopeIdDeferExpr: |
| ... | @@ -4379,8 +4381,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo | ... | @@ -4379,8 +4381,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo |
| 4379 | zig_unreachable(); | 4381 | zig_unreachable(); |
| 4380 | case BuiltinFnIdTypeof: | 4382 | case BuiltinFnIdTypeof: |
| 4381 | { | 4383 | { |
| 4384 | Scope *sub_scope = create_typeof_scope(irb->codegen, node, scope); | ||
| 4385 | |||
| 4382 | AstNode *arg_node = node->data.fn_call_expr.params.at(0); | 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 | if (arg == irb->codegen->invalid_instruction) | 4388 | if (arg == irb->codegen->invalid_instruction) |
| 4385 | return arg; | 4389 | return arg; |
| 4386 | 4390 | ||
| ... | @@ -8269,6 +8273,10 @@ static ConstExprValue *ir_exec_const_result(CodeGen *codegen, IrExecutable *exec | ... | @@ -8269,6 +8273,10 @@ static ConstExprValue *ir_exec_const_result(CodeGen *codegen, IrExecutable *exec |
| 8269 | break; | 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 | exec_add_error_node(codegen, exec, instruction->source_node, | 8280 | exec_add_error_node(codegen, exec, instruction->source_node, |
| 8273 | buf_sprintf("unable to evaluate constant expression")); | 8281 | buf_sprintf("unable to evaluate constant expression")); |
| 8274 | return &codegen->invalid_instruction->value; | 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,3 +89,29 @@ test "@sizeOf(T) == 0 doesn't force resolving struct size" { |
| 89 | expect(@sizeOf(S.Foo) == 4); | 89 | expect(@sizeOf(S.Foo) == 4); |
| 90 | expect(@sizeOf(S.Bar) == 8); | 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 | } |