From 179423ec2712956e04f188bf8b904e4fb9b48656 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 27 Apr 2020 02:33:21 +0300 Subject: [PATCH 1/6] Extern functions can now be evaluated to undefined values in TypeOfs --- src/ir.cpp | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 78d5ae2ff9..8ed22b12cb 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -19706,18 +19706,26 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return ira->codegen->invalid_inst_gen; } + bool extern_fn_in_typeof = false; + if (modifier == CallModifierCompileTime) { // No special handling is needed for compile time evaluation of generic functions. if (!fn_entry || fn_entry->body_node == nullptr) { - ir_add_error(ira, &fn_ref->base, buf_sprintf("unable to evaluate constant expression")); - return ira->codegen->invalid_inst_gen; + // We keep evaluating extern functions in TypeOfs + if (get_scope_typeof(source_instr->scope) != nullptr && fn_entry) { + extern_fn_in_typeof = true; + } else { + ir_add_error(ira, &fn_ref->base, buf_sprintf("unable to evaluate constant expression")); + return ira->codegen->invalid_inst_gen; + } } if (!ir_emit_backward_branch(ira, source_instr)) return ira->codegen->invalid_inst_gen; // Fork a scope of the function with known values for the parameters. - Scope *exec_scope = &fn_entry->fndef_scope->base; + // If we are evaluating an extern function in a TypeOf, we use the TypeOf's scope instead. + Scope *exec_scope = extern_fn_in_typeof ? source_instr->scope : &fn_entry->fndef_scope->base; size_t next_proto_i = 0; if (first_arg_ptr) { @@ -19766,7 +19774,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return_type = specified_return_type; } - bool cacheable = fn_eval_cacheable(exec_scope, return_type); + bool cacheable = !extern_fn_in_typeof && fn_eval_cacheable(exec_scope, return_type); ZigValue *result = nullptr; if (cacheable) { auto entry = ira->codegen->memoized_fn_eval_table.maybe_get(exec_scope); @@ -19779,12 +19787,16 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, AstNode *body_node = fn_entry->body_node; ZigValue *result_ptr; create_result_ptr(ira->codegen, return_type, &result, &result_ptr); - if ((err = ir_eval_const_value(ira->codegen, exec_scope, body_node, result_ptr, - ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, - fn_entry, nullptr, source_instr->source_node, nullptr, ira->new_irb.exec, return_type_node, - UndefOk))) - { - return ira->codegen->invalid_inst_gen; + + // If we are evaluating an extern function in a TypeOf, create a value and keep it undefined. + if (!extern_fn_in_typeof) { + if ((err = ir_eval_const_value(ira->codegen, exec_scope, body_node, result_ptr, + ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, + fn_entry, nullptr, source_instr->source_node, nullptr, ira->new_irb.exec, return_type_node, + UndefOk))) + { + return ira->codegen->invalid_inst_gen; + } } if (inferred_err_set_type != nullptr) { From 908b908481260322b60f479ccf4cabff72fcc5d5 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 27 Apr 2020 15:22:15 +0300 Subject: [PATCH 2/6] Added tests. --- src/ir.cpp | 12 +++++----- test/stage1/behavior.zig | 1 + test/stage1/behavior/bugs/4328.zig | 36 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 test/stage1/behavior/bugs/4328.zig diff --git a/src/ir.cpp b/src/ir.cpp index 8ed22b12cb..64f39bf6f5 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -19706,13 +19706,13 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return ira->codegen->invalid_inst_gen; } - bool extern_fn_in_typeof = false; - if (modifier == CallModifierCompileTime) { + bool extern_fn_in_typeof = false; + // No special handling is needed for compile time evaluation of generic functions. if (!fn_entry || fn_entry->body_node == nullptr) { - // We keep evaluating extern functions in TypeOfs - if (get_scope_typeof(source_instr->scope) != nullptr && fn_entry) { + // We keep evaluating extern functions directly in TypeOfs + if (fn_entry && source_instr->scope->id == ScopeIdTypeOf) { extern_fn_in_typeof = true; } else { ir_add_error(ira, &fn_ref->base, buf_sprintf("unable to evaluate constant expression")); @@ -19724,7 +19724,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return ira->codegen->invalid_inst_gen; // Fork a scope of the function with known values for the parameters. - // If we are evaluating an extern function in a TypeOf, we use the TypeOf's scope instead. + // If we are evaluating an extern function in a TypeOf, we use the current scope instead. Scope *exec_scope = extern_fn_in_typeof ? source_instr->scope : &fn_entry->fndef_scope->base; size_t next_proto_i = 0; @@ -19752,7 +19752,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return ira->codegen->invalid_inst_gen; } - for (size_t call_i = 0; call_i < args_len; call_i += 1) { + if (!extern_fn_in_typeof) for (size_t call_i = 0; call_i < args_len; call_i += 1) { IrInstGen *old_arg = args_ptr[call_i]; if (!ir_analyze_fn_call_inline_arg(ira, fn_proto_node, old_arg, &exec_scope, &next_proto_i)) diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 61b0c1aa56..a420fd6886 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -40,6 +40,7 @@ comptime { _ = @import("behavior/bugs/3384.zig"); _ = @import("behavior/bugs/3586.zig"); _ = @import("behavior/bugs/3742.zig"); + _ = @import("behavior/bugs/4328.zig"); _ = @import("behavior/bugs/4560.zig"); _ = @import("behavior/bugs/4769_a.zig"); _ = @import("behavior/bugs/4769_b.zig"); diff --git a/test/stage1/behavior/bugs/4328.zig b/test/stage1/behavior/bugs/4328.zig new file mode 100644 index 0000000000..631c213df1 --- /dev/null +++ b/test/stage1/behavior/bugs/4328.zig @@ -0,0 +1,36 @@ +const expectEqual = @import("std").testing.expectEqual; + +const FILE = extern struct { dummy_field: u8, }; +extern fn printf([*c]const u8, ...) c_int; +extern fn fputs([*c]const u8, noalias [*c]FILE) c_int; +extern fn ftell([*c]FILE) c_long; + +test "Extern function call in @TypeOf" { + const Test = struct { + fn test_fn(a: var, b: var) @TypeOf(printf("%d %s\n", a, b)) { + return 0; + } + + fn doTheTest() void { + expectEqual(c_int, @TypeOf(test_fn(0, 42))); + } + }; + + Test.doTheTest(); + comptime Test.doTheTest(); +} + +test "Peer resolution of extern function calls in @TypeOf" { + const Test = struct { + fn test_fn() @TypeOf(ftell(null), fputs(null, null)) { + return 0; + } + + fn doTheTest() void { + expectEqual(c_long, @TypeOf(test_fn())); + } + }; + + Test.doTheTest(); + comptime Test.doTheTest(); +} From 37fa418a94fc5da695f8799b6b021b632bb1d500 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 27 Apr 2020 18:07:18 +0300 Subject: [PATCH 3/6] Cleaned up code, added a testcase for an extern member function call --- src/ir.cpp | 45 ++++++++++++++++-------------- test/stage1/behavior/bugs/4328.zig | 21 +++++++++++--- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 64f39bf6f5..6f44f6c899 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -19707,25 +19707,31 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, } if (modifier == CallModifierCompileTime) { - bool extern_fn_in_typeof = false; + // If we are evaluating an extern function in a TypeOf call, we can return an undefined value + // of its return type. + if (fn_entry != nullptr && source_instr->scope->id == ScopeIdTypeOf && + fn_proto_node->data.fn_proto.is_extern) { + + assert(fn_entry->body_node == nullptr); + AstNode *return_type_node = fn_proto_node->data.fn_proto.return_type; + ZigType *return_type = ir_analyze_type_expr(ira, source_instr->scope, return_type_node); + if (type_is_invalid(return_type)) + return ira->codegen->invalid_inst_gen; + + return ir_const_undef(ira, source_instr, return_type); + } // No special handling is needed for compile time evaluation of generic functions. if (!fn_entry || fn_entry->body_node == nullptr) { - // We keep evaluating extern functions directly in TypeOfs - if (fn_entry && source_instr->scope->id == ScopeIdTypeOf) { - extern_fn_in_typeof = true; - } else { - ir_add_error(ira, &fn_ref->base, buf_sprintf("unable to evaluate constant expression")); - return ira->codegen->invalid_inst_gen; - } + ir_add_error(ira, &fn_ref->base, buf_sprintf("unable to evaluate constant expression")); + return ira->codegen->invalid_inst_gen; } if (!ir_emit_backward_branch(ira, source_instr)) return ira->codegen->invalid_inst_gen; // Fork a scope of the function with known values for the parameters. - // If we are evaluating an extern function in a TypeOf, we use the current scope instead. - Scope *exec_scope = extern_fn_in_typeof ? source_instr->scope : &fn_entry->fndef_scope->base; + Scope *exec_scope = &fn_entry->fndef_scope->base; size_t next_proto_i = 0; if (first_arg_ptr) { @@ -19752,7 +19758,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return ira->codegen->invalid_inst_gen; } - if (!extern_fn_in_typeof) for (size_t call_i = 0; call_i < args_len; call_i += 1) { + for (size_t call_i = 0; call_i < args_len; call_i += 1) { IrInstGen *old_arg = args_ptr[call_i]; if (!ir_analyze_fn_call_inline_arg(ira, fn_proto_node, old_arg, &exec_scope, &next_proto_i)) @@ -19774,7 +19780,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, return_type = specified_return_type; } - bool cacheable = !extern_fn_in_typeof && fn_eval_cacheable(exec_scope, return_type); + bool cacheable = fn_eval_cacheable(exec_scope, return_type); ZigValue *result = nullptr; if (cacheable) { auto entry = ira->codegen->memoized_fn_eval_table.maybe_get(exec_scope); @@ -19788,15 +19794,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, ZigValue *result_ptr; create_result_ptr(ira->codegen, return_type, &result, &result_ptr); - // If we are evaluating an extern function in a TypeOf, create a value and keep it undefined. - if (!extern_fn_in_typeof) { - if ((err = ir_eval_const_value(ira->codegen, exec_scope, body_node, result_ptr, - ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, - fn_entry, nullptr, source_instr->source_node, nullptr, ira->new_irb.exec, return_type_node, - UndefOk))) - { - return ira->codegen->invalid_inst_gen; - } + if ((err = ir_eval_const_value(ira->codegen, exec_scope, body_node, result_ptr, + ira->new_irb.exec->backward_branch_count, ira->new_irb.exec->backward_branch_quota, + fn_entry, nullptr, source_instr->source_node, nullptr, ira->new_irb.exec, return_type_node, + UndefOk))) + { + return ira->codegen->invalid_inst_gen; } if (inferred_err_set_type != nullptr) { diff --git a/test/stage1/behavior/bugs/4328.zig b/test/stage1/behavior/bugs/4328.zig index 631c213df1..bbad666c41 100644 --- a/test/stage1/behavior/bugs/4328.zig +++ b/test/stage1/behavior/bugs/4328.zig @@ -1,18 +1,31 @@ const expectEqual = @import("std").testing.expectEqual; -const FILE = extern struct { dummy_field: u8, }; +const FILE = extern struct { + dummy_field: u8, +}; extern fn printf([*c]const u8, ...) c_int; extern fn fputs([*c]const u8, noalias [*c]FILE) c_int; extern fn ftell([*c]FILE) c_long; -test "Extern function call in @TypeOf" { +const S = extern struct { + state: c_short, + + extern fn s_do_thing([*c]S, b: c_int) c_short; +}; + +test "Extern function calls in @TypeOf" { const Test = struct { - fn test_fn(a: var, b: var) @TypeOf(printf("%d %s\n", a, b)) { + fn test_fn_1(a: var, b: var) @TypeOf(printf("%d %s\n", a, b)) { return 0; } + fn test_fn_2(a: var) @TypeOf((S{ .state = 0 }).s_do_thing(a)) { + return 1; + } + fn doTheTest() void { - expectEqual(c_int, @TypeOf(test_fn(0, 42))); + expectEqual(c_int, @TypeOf(test_fn_1(0, 42))); + expectEqual(c_short, @TypeOf(test_fn_2(0))); } }; From 400a91e1c644228a66aa1702b9d800f018712105 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 18 May 2020 19:15:44 +0300 Subject: [PATCH 4/6] Add TypeOf resolution of dereferences and struct fields of undefined values --- src/ir.cpp | 19 ++++++++++++++++++- .../behavior/bugs/{4328.zig => 4328_5305.zig} | 0 2 files changed, 18 insertions(+), 1 deletion(-) rename test/stage1/behavior/bugs/{4328.zig => 4328_5305.zig} (100%) diff --git a/src/ir.cpp b/src/ir.cpp index 6f44f6c899..0cc35d365a 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -15406,6 +15406,12 @@ static IrInstGen *ir_get_deref(IrAnalyze *ira, IrInst* source_instruction, IrIns } if (instr_is_comptime(ptr)) { if (ptr->value->special == ConstValSpecialUndef) { + // If we are in a TypeOf call, we return an undefined value instead of erroring + // since we know the type. + if (get_scope_typeof(source_instruction->scope)) { + return ir_const_undef(ira, source_instruction, child_type); + } + ir_add_error(ira, &ptr->base, buf_sprintf("attempt to dereference undefined value")); return ira->codegen->invalid_inst_gen; } @@ -19709,7 +19715,7 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, if (modifier == CallModifierCompileTime) { // If we are evaluating an extern function in a TypeOf call, we can return an undefined value // of its return type. - if (fn_entry != nullptr && source_instr->scope->id == ScopeIdTypeOf && + if (fn_entry != nullptr && get_scope_typeof(source_instr->scope) != nullptr && fn_proto_node->data.fn_proto.is_extern) { assert(fn_entry->body_node == nullptr); @@ -21842,6 +21848,11 @@ static IrInstGen *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_name return ir_analyze_inferred_field_ptr(ira, field_name, source_instr, container_ptr, bare_type); } + // Tracks wether we should return an undefined value of the correct type. + // We do this if the container pointer is undefined and we are in a TypeOf call. + bool return_undef = container_ptr->value->special == ConstValSpecialUndef && \ + get_scope_typeof(source_instr->scope) != nullptr; + if ((err = type_resolve(ira->codegen, bare_type, ResolveStatusZeroBitsKnown))) return ira->codegen->invalid_inst_gen; @@ -21849,6 +21860,12 @@ static IrInstGen *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field_name if (bare_type->id == ZigTypeIdStruct) { TypeStructField *field = find_struct_type_field(bare_type, field_name); if (field != nullptr) { + if (return_undef) { + ZigType *field_ptr_type = get_pointer_to_type(ira->codegen, resolve_struct_field_type(ira->codegen, field), + container_ptr->value->type->data.pointer.is_const); + return ir_const_undef(ira, source_instr, field_ptr_type); + } + return ir_analyze_struct_field_ptr(ira, source_instr, field, container_ptr, bare_type, initializing); } else { return ir_analyze_container_member_access_inner(ira, bare_type, field_name, diff --git a/test/stage1/behavior/bugs/4328.zig b/test/stage1/behavior/bugs/4328_5305.zig similarity index 100% rename from test/stage1/behavior/bugs/4328.zig rename to test/stage1/behavior/bugs/4328_5305.zig From 75a4c02880393c08e245c80ed6114e97e2c47a25 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Mon, 18 May 2020 19:28:26 +0300 Subject: [PATCH 5/6] Updated test --- test/stage1/behavior.zig | 2 +- test/stage1/behavior/bugs/4328_5305.zig | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index a420fd6886..d4dc641ad6 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -40,7 +40,7 @@ comptime { _ = @import("behavior/bugs/3384.zig"); _ = @import("behavior/bugs/3586.zig"); _ = @import("behavior/bugs/3742.zig"); - _ = @import("behavior/bugs/4328.zig"); + _ = @import("behavior/bugs/4328_5305.zig"); _ = @import("behavior/bugs/4560.zig"); _ = @import("behavior/bugs/4769_a.zig"); _ = @import("behavior/bugs/4769_b.zig"); diff --git a/test/stage1/behavior/bugs/4328_5305.zig b/test/stage1/behavior/bugs/4328_5305.zig index bbad666c41..0196af1748 100644 --- a/test/stage1/behavior/bugs/4328_5305.zig +++ b/test/stage1/behavior/bugs/4328_5305.zig @@ -3,9 +3,11 @@ const expectEqual = @import("std").testing.expectEqual; const FILE = extern struct { dummy_field: u8, }; + extern fn printf([*c]const u8, ...) c_int; extern fn fputs([*c]const u8, noalias [*c]FILE) c_int; extern fn ftell([*c]FILE) c_long; +extern fn fopen([*c]const u8, [*c]const u8) [*c]FILE; const S = extern struct { state: c_short, @@ -47,3 +49,23 @@ test "Peer resolution of extern function calls in @TypeOf" { Test.doTheTest(); comptime Test.doTheTest(); } + +test "Extern function calls, dereferences and field access in @TypeOf" { + const Test = struct { + fn test_fn_1(a: c_long) @TypeOf(fopen("test", "r").*) { + return .{ .dummy_field = 0 }; + } + + fn test_fn_2(a: var) @TypeOf(fopen("test", "r").*.dummy_field) { + return 255; + } + + fn doTheTest() void { + expectEqual(FILE, @TypeOf(test_fn_1(0))); + expectEqual(u8, @TypeOf(test_fn_2(0))); + } + }; + + Test.doTheTest(); + comptime Test.doTheTest(); +} \ No newline at end of file From d88db4d34bad1f4e140217f425300b417b448b64 Mon Sep 17 00:00:00 2001 From: Alexandros Naskos Date: Tue, 26 May 2020 12:36:02 +0300 Subject: [PATCH 6/6] Changed test name to reflect it only fixes #4328 --- test/stage1/behavior.zig | 2 +- test/stage1/behavior/bugs/{4328_5305.zig => 4328.zig} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/stage1/behavior/bugs/{4328_5305.zig => 4328.zig} (100%) diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index d4dc641ad6..a420fd6886 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -40,7 +40,7 @@ comptime { _ = @import("behavior/bugs/3384.zig"); _ = @import("behavior/bugs/3586.zig"); _ = @import("behavior/bugs/3742.zig"); - _ = @import("behavior/bugs/4328_5305.zig"); + _ = @import("behavior/bugs/4328.zig"); _ = @import("behavior/bugs/4560.zig"); _ = @import("behavior/bugs/4769_a.zig"); _ = @import("behavior/bugs/4769_b.zig"); diff --git a/test/stage1/behavior/bugs/4328_5305.zig b/test/stage1/behavior/bugs/4328.zig similarity index 100% rename from test/stage1/behavior/bugs/4328_5305.zig rename to test/stage1/behavior/bugs/4328.zig