From 0c310f0fbf5ccbfff6c8235aa1a315589f9d8cf9 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 4 Mar 2020 10:55:34 +0100 Subject: [PATCH 1/5] ir: Implement @TypeOf with multiple arguments Closes #439 --- src/all_types.hpp | 3 +- src/codegen.cpp | 2 +- src/ir.cpp | 59 ++++++++++++++++++---- src/ir_print.cpp | 2 +- test/compile_errors.zig | 18 +++++++ test/stage1/behavior/sizeof_and_typeof.zig | 23 +++++++++ 6 files changed, 93 insertions(+), 14 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index 9f6bfd80a5..d1fc03f8a1 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -3286,7 +3286,8 @@ struct IrInstGenUnreachable { struct IrInstSrcTypeOf { IrInstSrc base; - IrInstSrc *value; + IrInstSrc **values; + size_t value_count; }; struct IrInstSrcSetCold { diff --git a/src/codegen.cpp b/src/codegen.cpp index 51e2f4625e..ef567c27aa 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -8142,7 +8142,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdTypeInfo, "typeInfo", 1); create_builtin_fn(g, BuiltinFnIdType, "Type", 1); create_builtin_fn(g, BuiltinFnIdHasField, "hasField", 2); - create_builtin_fn(g, BuiltinFnIdTypeof, "TypeOf", 1); + create_builtin_fn(g, BuiltinFnIdTypeof, "TypeOf", SIZE_MAX); create_builtin_fn(g, BuiltinFnIdAddWithOverflow, "addWithOverflow", 4); create_builtin_fn(g, BuiltinFnIdSubWithOverflow, "subWithOverflow", 4); create_builtin_fn(g, BuiltinFnIdMulWithOverflow, "mulWithOverflow", 4); diff --git a/src/ir.cpp b/src/ir.cpp index d203e0aa91..3f36838bfc 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -2766,11 +2766,13 @@ static IrInstGen *ir_build_load_ptr_gen(IrAnalyze *ira, IrInst *source_instructi return &instruction->base; } -static IrInstSrc *ir_build_typeof(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *value) { +static IrInstSrc *ir_build_typeof(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc **values, size_t value_count) { IrInstSrcTypeOf *instruction = ir_build_instruction(irb, scope, source_node); - instruction->value = value; + instruction->values = values; + instruction->value_count = value_count; - ir_ref_instruction(value, irb->current_basic_block); + for (size_t i = 0; i < value_count; i++) + ir_ref_instruction(values[i], irb->current_basic_block); return &instruction->base; } @@ -6043,7 +6045,9 @@ static IrInstSrc *ir_gen_fn_call_with_args(IrBuilderSrc *irb, Scope *scope, AstN if (fn_ref == irb->codegen->invalid_inst_src) return fn_ref; - IrInstSrc *fn_type = ir_build_typeof(irb, scope, source_node, fn_ref); + IrInstSrc **typeof_args = heap::c_allocator.allocate(1); + typeof_args[0] = fn_ref; + IrInstSrc *fn_type = ir_build_typeof(irb, scope, source_node, typeof_args, 1); IrInstSrc **args = heap::c_allocator.allocate(args_len); for (size_t i = 0; i < args_len; i += 1) { @@ -6104,12 +6108,24 @@ static IrInstSrc *ir_gen_builtin_fn_call(IrBuilderSrc *irb, Scope *scope, AstNod { Scope *sub_scope = create_typeof_scope(irb->codegen, node, scope); - AstNode *arg_node = node->data.fn_call_expr.params.at(0); - IrInstSrc *arg = ir_gen_node(irb, arg_node, sub_scope); - if (arg == irb->codegen->invalid_inst_src) - return arg; + size_t arg_count = node->data.fn_call_expr.params.length; - IrInstSrc *type_of = ir_build_typeof(irb, scope, node, arg); + if (arg_count < 1) { + add_node_error(irb->codegen, node, + buf_sprintf("expected at least 1 argument, found 0")); + return irb->codegen->invalid_inst_src; + } + + IrInstSrc **args = heap::c_allocator.allocate(arg_count); + for (size_t i = 0; i < arg_count; i += 1) { + AstNode *arg_node = node->data.fn_call_expr.params.at(i); + IrInstSrc *arg = ir_gen_node(irb, arg_node, sub_scope); + if (arg == irb->codegen->invalid_inst_src) + return irb->codegen->invalid_inst_src; + args[i] = arg; + } + + IrInstSrc *type_of = ir_build_typeof(irb, scope, node, args, arg_count); return ir_lval_wrap(irb, scope, type_of, lval, result_loc); } case BuiltinFnIdSetCold: @@ -21662,10 +21678,31 @@ static IrInstGen *ir_analyze_instruction_load_ptr(IrAnalyze *ira, IrInstSrcLoadP } static IrInstGen *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstSrcTypeOf *typeof_instruction) { - IrInstGen *expr_value = typeof_instruction->value->child; - ZigType *type_entry = expr_value->value->type; + ZigType *type_entry; + + const size_t value_count = typeof_instruction->value_count; + + // Fast path for the common case of TypeOf with a single argument + if (value_count < 2) { + type_entry = typeof_instruction->values[0]->child->value->type; + } else { + IrInstGen **args = heap::c_allocator.allocate(value_count); + for (size_t i = 0; i < value_count; i += 1) { + IrInstGen *value = typeof_instruction->values[i]->child; + if (type_is_invalid(value->value->type)) + return ira->codegen->invalid_inst_gen; + args[i] = value; + } + + type_entry = ir_resolve_peer_types(ira, typeof_instruction->base.base.source_node, + nullptr, args, value_count); + + heap::c_allocator.deallocate(args, value_count); + } + if (type_is_invalid(type_entry)) return ira->codegen->invalid_inst_gen; + return ir_const_type(ira, &typeof_instruction->base.base, type_entry); } diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 1cfe904660..57469c4767 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -1118,7 +1118,7 @@ static void ir_print_vector_store_elem(IrPrintGen *irp, IrInstGenVectorStoreElem static void ir_print_typeof(IrPrintSrc *irp, IrInstSrcTypeOf *instruction) { fprintf(irp->f, "@TypeOf("); - ir_print_other_inst_src(irp, instruction->value); + // ir_print_other_inst_src(irp, instruction->value); fprintf(irp->f, ")"); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 979bf45bbe..f559311547 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,24 @@ const tests = @import("tests.zig"); const std = @import("std"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.addTest("@TypeOf with no arguments", + \\export fn entry() void { + \\ _ = @TypeOf(); + \\} + , &[_][]const u8{ + "tmp.zig:2:9: error: expected at least 1 argument, found 0", + }); + + cases.addTest("@TypeOf with incompatible arguments", + \\export fn entry() void { + \\ var var_1: f32 = undefined; + \\ var var_2: u32 = undefined; + \\ _ = @TypeOf(var_1, var_2); + \\} + , &[_][]const u8{ + "tmp.zig:4:9: error: incompatible types: 'f32' and 'u32'", + }); + cases.addTest("type mismatch with tuple concatenation", \\export fn entry() void { \\ var x = .{}; diff --git a/test/stage1/behavior/sizeof_and_typeof.zig b/test/stage1/behavior/sizeof_and_typeof.zig index 322c5fbbad..5f9dc8ba5e 100644 --- a/test/stage1/behavior/sizeof_and_typeof.zig +++ b/test/stage1/behavior/sizeof_and_typeof.zig @@ -105,6 +105,29 @@ test "@TypeOf() has no runtime side effects" { expect(data == 0); } +test "@TypeOf() with multiple arguments" { + { + var var_1: u32 = undefined; + var var_2: u8 = undefined; + var var_3: u64 = undefined; + comptime expect(@TypeOf(var_1, var_2, var_3) == u64); + } + { + var var_1: f16 = undefined; + var var_2: f32 = undefined; + var var_3: f64 = undefined; + comptime expect(@TypeOf(var_1, var_2, var_3) == f64); + } + { + var var_1: u16 = undefined; + comptime expect(@TypeOf(var_1, 0xffff) == u16); + } + { + var var_1: f32 = undefined; + comptime expect(@TypeOf(var_1, 3.1415) == f32); + } +} + test "branching logic inside @TypeOf" { const S = struct { var data: i32 = 0; From e0290322514827ef8f0bff19f58ceb14b3c3ad62 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 4 Mar 2020 10:56:01 +0100 Subject: [PATCH 2/5] std: Use @TypeOf(x,y) as return value for max --- lib/std/math.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/math.zig b/lib/std/math.zig index c99de8f9dc..9a1143a17d 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -293,7 +293,7 @@ test "math.min" { } } -pub fn max(x: var, y: var) @TypeOf(x + y) { +pub fn max(x: var, y: var) @TypeOf(x, y) { return if (x > y) x else y; } From 2e3e8d0c74d7b5aac3d1996bc8ce9735f2d3f8c6 Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Wed, 4 Mar 2020 11:00:09 +0100 Subject: [PATCH 3/5] ir: Adapt ir_print for the new @TypeOf format --- src/ir_print.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 57469c4767..744dcf673d 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -1118,7 +1118,9 @@ static void ir_print_vector_store_elem(IrPrintGen *irp, IrInstGenVectorStoreElem static void ir_print_typeof(IrPrintSrc *irp, IrInstSrcTypeOf *instruction) { fprintf(irp->f, "@TypeOf("); - // ir_print_other_inst_src(irp, instruction->value); + for (size_t i = 0; i < instruction->value_count; i += 1) { + ir_print_other_inst_src(irp, instruction->values[i]); + } fprintf(irp->f, ")"); } From 3e3d46488419445c1b3a4b1da384f084c71cddb0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Mar 2020 17:43:23 -0500 Subject: [PATCH 4/5] `@TypeOf` avoids heap allocation for only 1 parameter --- src/all_types.hpp | 5 ++++- src/ir.cpp | 56 ++++++++++++++++++++++++++++++++--------------- src/ir_print.cpp | 8 +++++-- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index d1fc03f8a1..2a3ab2041a 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -3286,7 +3286,10 @@ struct IrInstGenUnreachable { struct IrInstSrcTypeOf { IrInstSrc base; - IrInstSrc **values; + union { + IrInstSrc *scalar; // value_count == 1 + IrInstSrc **list; // value_count > 1 + } value; size_t value_count; }; diff --git a/src/ir.cpp b/src/ir.cpp index 3f36838bfc..56ba634189 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -2766,9 +2766,13 @@ static IrInstGen *ir_build_load_ptr_gen(IrAnalyze *ira, IrInst *source_instructi return &instruction->base; } -static IrInstSrc *ir_build_typeof(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc **values, size_t value_count) { +static IrInstSrc *ir_build_typeof_n(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, + IrInstSrc **values, size_t value_count) +{ + assert(value_count >= 2); + IrInstSrcTypeOf *instruction = ir_build_instruction(irb, scope, source_node); - instruction->values = values; + instruction->value.list = values; instruction->value_count = value_count; for (size_t i = 0; i < value_count; i++) @@ -2777,6 +2781,15 @@ static IrInstSrc *ir_build_typeof(IrBuilderSrc *irb, Scope *scope, AstNode *sour return &instruction->base; } +static IrInstSrc *ir_build_typeof_1(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *value) { + IrInstSrcTypeOf *instruction = ir_build_instruction(irb, scope, source_node); + instruction->value.scalar = value; + + ir_ref_instruction(value, irb->current_basic_block); + + return &instruction->base; +} + static IrInstSrc *ir_build_set_cold(IrBuilderSrc *irb, Scope *scope, AstNode *source_node, IrInstSrc *is_cold) { IrInstSrcSetCold *instruction = ir_build_instruction(irb, scope, source_node); instruction->is_cold = is_cold; @@ -6045,9 +6058,7 @@ static IrInstSrc *ir_gen_fn_call_with_args(IrBuilderSrc *irb, Scope *scope, AstN if (fn_ref == irb->codegen->invalid_inst_src) return fn_ref; - IrInstSrc **typeof_args = heap::c_allocator.allocate(1); - typeof_args[0] = fn_ref; - IrInstSrc *fn_type = ir_build_typeof(irb, scope, source_node, typeof_args, 1); + IrInstSrc *fn_type = ir_build_typeof_1(irb, scope, source_node, fn_ref); IrInstSrc **args = heap::c_allocator.allocate(args_len); for (size_t i = 0; i < args_len; i += 1) { @@ -6110,22 +6121,31 @@ static IrInstSrc *ir_gen_builtin_fn_call(IrBuilderSrc *irb, Scope *scope, AstNod size_t arg_count = node->data.fn_call_expr.params.length; - if (arg_count < 1) { + IrInstSrc *type_of; + + if (arg_count == 0) { add_node_error(irb->codegen, node, buf_sprintf("expected at least 1 argument, found 0")); return irb->codegen->invalid_inst_src; - } + } else if (arg_count == 1) { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstSrc *arg0_value = ir_gen_node(irb, arg0_node, sub_scope); + if (arg0_value == irb->codegen->invalid_inst_src) + return arg0_value; - IrInstSrc **args = heap::c_allocator.allocate(arg_count); - for (size_t i = 0; i < arg_count; i += 1) { - AstNode *arg_node = node->data.fn_call_expr.params.at(i); - IrInstSrc *arg = ir_gen_node(irb, arg_node, sub_scope); - if (arg == irb->codegen->invalid_inst_src) - return irb->codegen->invalid_inst_src; - args[i] = arg; - } + type_of = ir_build_typeof_1(irb, scope, node, arg0_value); + } else { + IrInstSrc **args = heap::c_allocator.allocate(arg_count); + for (size_t i = 0; i < arg_count; i += 1) { + AstNode *arg_node = node->data.fn_call_expr.params.at(i); + IrInstSrc *arg = ir_gen_node(irb, arg_node, sub_scope); + if (arg == irb->codegen->invalid_inst_src) + return irb->codegen->invalid_inst_src; + args[i] = arg; + } - IrInstSrc *type_of = ir_build_typeof(irb, scope, node, args, arg_count); + type_of = ir_build_typeof_n(irb, scope, node, args, arg_count); + } return ir_lval_wrap(irb, scope, type_of, lval, result_loc); } case BuiltinFnIdSetCold: @@ -21684,11 +21704,11 @@ static IrInstGen *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstSrcTypeOf // Fast path for the common case of TypeOf with a single argument if (value_count < 2) { - type_entry = typeof_instruction->values[0]->child->value->type; + type_entry = typeof_instruction->value.scalar->child->value->type; } else { IrInstGen **args = heap::c_allocator.allocate(value_count); for (size_t i = 0; i < value_count; i += 1) { - IrInstGen *value = typeof_instruction->values[i]->child; + IrInstGen *value = typeof_instruction->value.list[i]->child; if (type_is_invalid(value->value->type)) return ira->codegen->invalid_inst_gen; args[i] = value; diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 744dcf673d..e66b4a3cdf 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -1118,8 +1118,12 @@ static void ir_print_vector_store_elem(IrPrintGen *irp, IrInstGenVectorStoreElem static void ir_print_typeof(IrPrintSrc *irp, IrInstSrcTypeOf *instruction) { fprintf(irp->f, "@TypeOf("); - for (size_t i = 0; i < instruction->value_count; i += 1) { - ir_print_other_inst_src(irp, instruction->values[i]); + if (instruction->value_count == 1) { + ir_print_other_inst_src(irp, instruction->value.scalar); + } else { + for (size_t i = 0; i < instruction->value_count; i += 1) { + ir_print_other_inst_src(irp, instruction->value.list[i]); + } } fprintf(irp->f, ")"); } From 116e2a93f1ab068ee46f4b7cce6b3306334cdec6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Mar 2020 18:05:14 -0500 Subject: [PATCH 5/5] update docs for `@TypeOf` --- doc/langref.html.in | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 16d16718a9..50058dbd46 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8371,12 +8371,14 @@ test "integer truncation" { {#header_close#} {#header_open|@TypeOf#} -
{#syntax#}@TypeOf(expression) type{#endsyntax#}
+
{#syntax#}@TypeOf(...) type{#endsyntax#}

- 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#} is a special builtin function that takes any (nonzero) number of expressions + as parameters and returns the type of the result, using {#link|Peer Type Resolution#}. +

+

+ The expressions are evaluated, however they are guaranteed to have no runtime side-effects:

-

{#syntax#}@TypeOf{#endsyntax#} guarantees no run-time side-effects within the expression:

{#code_begin|test#} const std = @import("std"); const assert = std.debug.assert;