diff --git a/doc/langref.html.in b/doc/langref.html.in index d578be4ba8..69b06796c5 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6734,6 +6734,38 @@ export fn @"A function name that is a complete sentence."() void {}

{#header_close#} + {#header_open|@hasDecl#} +
{#syntax#}@hasDecl(comptime container: type, comptime name: []const u8) bool{#endsyntax#}
+

+ Returns whether or not a {#link|struct#}, {#link|enum#}, or {#link|union#} has a declaration + matching {#syntax#}name{#endsyntax#}. +

+ {#code_begin|test#} +const std = @import("std"); +const assert = std.debug.assert; + +const Foo = struct { + nope: i32, + + pub var blah = "xxx"; + const hi = 1; +}; + +test "@hasDecl" { + assert(@hasDecl(Foo, "blah")); + + // Even though `hi` is private, @hasDecl returns true because this test is + // in the same file scope as Foo. It would return false if Foo was declared + // in a different file. + assert(@hasDecl(Foo, "hi")); + + // @hasDecl is for declarations; not fields. + assert(!@hasDecl(Foo, "nope")); + assert(!@hasDecl(Foo, "nope1234")); +} + {#code_end#} + {#header_close#} + {#header_open|@import#}
{#syntax#}@import(comptime path: []u8) type{#endsyntax#}

diff --git a/src/all_types.hpp b/src/all_types.hpp index 3b6ac32ea5..2cd4a5f958 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1471,6 +1471,7 @@ enum BuiltinFnId { BuiltinFnIdErrorReturnTrace, BuiltinFnIdAtomicRmw, BuiltinFnIdAtomicLoad, + BuiltinFnIdHasDecl, }; struct BuiltinFnEntry { @@ -2297,6 +2298,7 @@ enum IrInstructionId { IrInstructionIdArrayToVector, IrInstructionIdAssertZero, IrInstructionIdAssertNonNull, + IrInstructionIdHasDecl, }; struct IrInstruction { @@ -3503,6 +3505,13 @@ struct IrInstructionAssertNonNull { IrInstruction *target; }; +struct IrInstructionHasDecl { + IrInstruction base; + + IrInstruction *container; + IrInstruction *name; +}; + static const size_t slice_ptr_index = 0; static const size_t slice_len_index = 1; diff --git a/src/codegen.cpp b/src/codegen.cpp index 3a34894bb3..00a4338810 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -5616,6 +5616,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, case IrInstructionIdLoadPtr: case IrInstructionIdBitCast: case IrInstructionIdGlobalAsm: + case IrInstructionIdHasDecl: zig_unreachable(); case IrInstructionIdDeclVarGen: @@ -7409,6 +7410,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdToBytes, "sliceToBytes", 1); create_builtin_fn(g, BuiltinFnIdFromBytes, "bytesToSlice", 2); create_builtin_fn(g, BuiltinFnIdThis, "This", 0); + create_builtin_fn(g, BuiltinFnIdHasDecl, "hasDecl", 2); } static const char *bool_to_str(bool b) { diff --git a/src/ir.cpp b/src/ir.cpp index f24b2b6a7b..3ab936d14c 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -1011,6 +1011,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionAssertNonNull *) return IrInstructionIdAssertNonNull; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionHasDecl *) { + return IrInstructionIdHasDecl; +} + template static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) { T *special_instruction = allocate(1); @@ -3014,6 +3018,19 @@ static IrInstruction *ir_build_sqrt(IrBuilder *irb, Scope *scope, AstNode *sourc return &instruction->base; } +static IrInstruction *ir_build_has_decl(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *container, IrInstruction *name) +{ + IrInstructionHasDecl *instruction = ir_build_instruction(irb, scope, source_node); + instruction->container = container; + instruction->name = name; + + ir_ref_instruction(container, irb->current_basic_block); + ir_ref_instruction(name, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_check_runtime_scope(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *scope_is_comptime, IrInstruction *is_comptime) { IrInstructionCheckRuntimeScope *instruction = ir_build_instruction(irb, scope, source_node); instruction->scope_is_comptime = scope_is_comptime; @@ -5098,6 +5115,21 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo } return ir_lval_wrap(irb, scope, result, lval); } + case BuiltinFnIdHasDecl: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + AstNode *arg1_node = node->data.fn_call_expr.params.at(1); + IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope); + if (arg1_value == irb->codegen->invalid_instruction) + return arg1_value; + + IrInstruction *has_decl = ir_build_has_decl(irb, scope, node, arg0_value, arg1_value); + return ir_lval_wrap(irb, scope, has_decl, lval); + } } zig_unreachable(); } @@ -23173,6 +23205,33 @@ static IrInstruction *ir_analyze_instruction_check_runtime_scope(IrAnalyze *ira, return ir_const_void(ira, &instruction->base); } +static IrInstruction *ir_analyze_instruction_has_decl(IrAnalyze *ira, IrInstructionHasDecl *instruction) { + ZigType *container_type = ir_resolve_type(ira, instruction->container->child); + if (type_is_invalid(container_type)) + return ira->codegen->invalid_instruction; + + Buf *name = ir_resolve_str(ira, instruction->name->child); + if (name == nullptr) + return ira->codegen->invalid_instruction; + + if (!is_container(container_type)) { + ir_add_error(ira, instruction->container, + buf_sprintf("expected struct, enum, or union; found '%s'", buf_ptr(&container_type->name))); + return ira->codegen->invalid_instruction; + } + + ScopeDecls *container_scope = get_container_scope(container_type); + Tld *tld = find_container_decl(ira->codegen, container_scope, name); + if (tld == nullptr) + return ir_const_bool(ira, &instruction->base, false); + + if (tld->visib_mod == VisibModPrivate && tld->import != get_scope_import(instruction->base.scope)) { + return ir_const_bool(ira, &instruction->base, false); + } + + return ir_const_bool(ira, &instruction->base, true); +} + static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) { switch (instruction->id) { case IrInstructionIdInvalid: @@ -23467,6 +23526,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio return ir_analyze_instruction_enum_to_int(ira, (IrInstructionEnumToInt *)instruction); case IrInstructionIdCheckRuntimeScope: return ir_analyze_instruction_check_runtime_scope(ira, (IrInstructionCheckRuntimeScope *)instruction); + case IrInstructionIdHasDecl: + return ir_analyze_instruction_has_decl(ira, (IrInstructionHasDecl *)instruction); } zig_unreachable(); } @@ -23703,6 +23764,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdEnumToInt: case IrInstructionIdVectorToArray: case IrInstructionIdArrayToVector: + case IrInstructionIdHasDecl: return false; case IrInstructionIdAsm: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index d38b552749..dd671231c0 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -1453,6 +1453,14 @@ static void ir_print_decl_var_gen(IrPrint *irp, IrInstructionDeclVarGen *decl_va } } +static void ir_print_has_decl(IrPrint *irp, IrInstructionHasDecl *instruction) { + fprintf(irp->f, "@hasDecl("); + ir_print_other_instruction(irp, instruction->container); + fprintf(irp->f, ","); + ir_print_other_instruction(irp, instruction->name); + fprintf(irp->f, ")"); +} + static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); switch (instruction->id) { @@ -1920,6 +1928,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdResizeSlice: ir_print_resize_slice(irp, (IrInstructionResizeSlice *)instruction); break; + case IrInstructionIdHasDecl: + ir_print_has_decl(irp, (IrInstructionHasDecl *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 932a550eab..d151b31343 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,15 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "@hasDecl with non-container", + \\export fn entry() void { + \\ _ = @hasDecl(i32, "hi"); + \\} + , + "tmp.zig:2:18: error: expected struct, enum, or union; found 'i32'", + ); + cases.add( "field access of slices", \\export fn entry() void { diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 8aa885d337..e9ff84c035 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -53,6 +53,7 @@ comptime { _ = @import("behavior/fn_in_struct_in_comptime.zig"); _ = @import("behavior/for.zig"); _ = @import("behavior/generics.zig"); + _ = @import("behavior/hasdecl.zig"); _ = @import("behavior/if.zig"); _ = @import("behavior/import.zig"); _ = @import("behavior/incomplete_struct_param_tld.zig"); diff --git a/test/stage1/behavior/hasdecl.zig b/test/stage1/behavior/hasdecl.zig new file mode 100644 index 0000000000..f3bb9887fe --- /dev/null +++ b/test/stage1/behavior/hasdecl.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const expect = std.testing.expect; + +const Foo = @import("hasdecl/foo.zig"); + +const Bar = struct { + nope: i32, + + const hi = 1; + pub var blah = "xxx"; +}; + +test "@hasDecl" { + expect(@hasDecl(Foo, "public_thing")); + expect(!@hasDecl(Foo, "private_thing")); + expect(!@hasDecl(Foo, "no_thing")); + + expect(@hasDecl(Bar, "hi")); + expect(@hasDecl(Bar, "blah")); + expect(!@hasDecl(Bar, "nope")); +} diff --git a/test/stage1/behavior/hasdecl/foo.zig b/test/stage1/behavior/hasdecl/foo.zig new file mode 100644 index 0000000000..c42faaa890 --- /dev/null +++ b/test/stage1/behavior/hasdecl/foo.zig @@ -0,0 +1,2 @@ +pub const public_thing = 42; +const private_thing = 666;