Skip to content

Commit 269a53b

Browse files
committed
introduce @hasDecl builtin function
closes #1439
1 parent 21ed939 commit 269a53b

File tree

9 files changed

+149
-0
lines changed

9 files changed

+149
-0
lines changed

doc/langref.html.in

+32
Original file line numberDiff line numberDiff line change
@@ -6734,6 +6734,38 @@ export fn @"A function name that is a complete sentence."() void {}
67346734
</p>
67356735
{#header_close#}
67366736

6737+
{#header_open|@hasDecl#}
6738+
<pre>{#syntax#}@hasDecl(comptime container: type, comptime name: []const u8) bool{#endsyntax#}</pre>
6739+
<p>
6740+
Returns whether or not a {#link|struct#}, {#link|enum#}, or {#link|union#} has a declaration
6741+
matching {#syntax#}name{#endsyntax#}.
6742+
</p>
6743+
{#code_begin|test#}
6744+
const std = @import("std");
6745+
const assert = std.debug.assert;
6746+
6747+
const Foo = struct {
6748+
nope: i32,
6749+
6750+
pub var blah = "xxx";
6751+
const hi = 1;
6752+
};
6753+
6754+
test "@hasDecl" {
6755+
assert(@hasDecl(Foo, "blah"));
6756+
6757+
// Even though `hi` is private, @hasDecl returns true because this test is
6758+
// in the same file scope as Foo. It would return false if Foo was declared
6759+
// in a different file.
6760+
assert(@hasDecl(Foo, "hi"));
6761+
6762+
// @hasDecl is for declarations; not fields.
6763+
assert(!@hasDecl(Foo, "nope"));
6764+
assert(!@hasDecl(Foo, "nope1234"));
6765+
}
6766+
{#code_end#}
6767+
{#header_close#}
6768+
67376769
{#header_open|@import#}
67386770
<pre>{#syntax#}@import(comptime path: []u8) type{#endsyntax#}</pre>
67396771
<p>

src/all_types.hpp

+9
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,7 @@ enum BuiltinFnId {
14711471
BuiltinFnIdErrorReturnTrace,
14721472
BuiltinFnIdAtomicRmw,
14731473
BuiltinFnIdAtomicLoad,
1474+
BuiltinFnIdHasDecl,
14741475
};
14751476

14761477
struct BuiltinFnEntry {
@@ -2297,6 +2298,7 @@ enum IrInstructionId {
22972298
IrInstructionIdArrayToVector,
22982299
IrInstructionIdAssertZero,
22992300
IrInstructionIdAssertNonNull,
2301+
IrInstructionIdHasDecl,
23002302
};
23012303

23022304
struct IrInstruction {
@@ -3503,6 +3505,13 @@ struct IrInstructionAssertNonNull {
35033505
IrInstruction *target;
35043506
};
35053507

3508+
struct IrInstructionHasDecl {
3509+
IrInstruction base;
3510+
3511+
IrInstruction *container;
3512+
IrInstruction *name;
3513+
};
3514+
35063515
static const size_t slice_ptr_index = 0;
35073516
static const size_t slice_len_index = 1;
35083517

src/codegen.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -5616,6 +5616,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
56165616
case IrInstructionIdLoadPtr:
56175617
case IrInstructionIdBitCast:
56185618
case IrInstructionIdGlobalAsm:
5619+
case IrInstructionIdHasDecl:
56195620
zig_unreachable();
56205621

56215622
case IrInstructionIdDeclVarGen:
@@ -7409,6 +7410,7 @@ static void define_builtin_fns(CodeGen *g) {
74097410
create_builtin_fn(g, BuiltinFnIdToBytes, "sliceToBytes", 1);
74107411
create_builtin_fn(g, BuiltinFnIdFromBytes, "bytesToSlice", 2);
74117412
create_builtin_fn(g, BuiltinFnIdThis, "This", 0);
7413+
create_builtin_fn(g, BuiltinFnIdHasDecl, "hasDecl", 2);
74127414
}
74137415

74147416
static const char *bool_to_str(bool b) {

src/ir.cpp

+62
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionAssertNonNull *)
10111011
return IrInstructionIdAssertNonNull;
10121012
}
10131013

1014+
static constexpr IrInstructionId ir_instruction_id(IrInstructionHasDecl *) {
1015+
return IrInstructionIdHasDecl;
1016+
}
1017+
10141018
template<typename T>
10151019
static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) {
10161020
T *special_instruction = allocate<T>(1);
@@ -3014,6 +3018,19 @@ static IrInstruction *ir_build_sqrt(IrBuilder *irb, Scope *scope, AstNode *sourc
30143018
return &instruction->base;
30153019
}
30163020

3021+
static IrInstruction *ir_build_has_decl(IrBuilder *irb, Scope *scope, AstNode *source_node,
3022+
IrInstruction *container, IrInstruction *name)
3023+
{
3024+
IrInstructionHasDecl *instruction = ir_build_instruction<IrInstructionHasDecl>(irb, scope, source_node);
3025+
instruction->container = container;
3026+
instruction->name = name;
3027+
3028+
ir_ref_instruction(container, irb->current_basic_block);
3029+
ir_ref_instruction(name, irb->current_basic_block);
3030+
3031+
return &instruction->base;
3032+
}
3033+
30173034
static IrInstruction *ir_build_check_runtime_scope(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *scope_is_comptime, IrInstruction *is_comptime) {
30183035
IrInstructionCheckRuntimeScope *instruction = ir_build_instruction<IrInstructionCheckRuntimeScope>(irb, scope, source_node);
30193036
instruction->scope_is_comptime = scope_is_comptime;
@@ -5098,6 +5115,21 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
50985115
}
50995116
return ir_lval_wrap(irb, scope, result, lval);
51005117
}
5118+
case BuiltinFnIdHasDecl:
5119+
{
5120+
AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
5121+
IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
5122+
if (arg0_value == irb->codegen->invalid_instruction)
5123+
return arg0_value;
5124+
5125+
AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
5126+
IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
5127+
if (arg1_value == irb->codegen->invalid_instruction)
5128+
return arg1_value;
5129+
5130+
IrInstruction *has_decl = ir_build_has_decl(irb, scope, node, arg0_value, arg1_value);
5131+
return ir_lval_wrap(irb, scope, has_decl, lval);
5132+
}
51015133
}
51025134
zig_unreachable();
51035135
}
@@ -23173,6 +23205,33 @@ static IrInstruction *ir_analyze_instruction_check_runtime_scope(IrAnalyze *ira,
2317323205
return ir_const_void(ira, &instruction->base);
2317423206
}
2317523207

23208+
static IrInstruction *ir_analyze_instruction_has_decl(IrAnalyze *ira, IrInstructionHasDecl *instruction) {
23209+
ZigType *container_type = ir_resolve_type(ira, instruction->container->child);
23210+
if (type_is_invalid(container_type))
23211+
return ira->codegen->invalid_instruction;
23212+
23213+
Buf *name = ir_resolve_str(ira, instruction->name->child);
23214+
if (name == nullptr)
23215+
return ira->codegen->invalid_instruction;
23216+
23217+
if (!is_container(container_type)) {
23218+
ir_add_error(ira, instruction->container,
23219+
buf_sprintf("expected struct, enum, or union; found '%s'", buf_ptr(&container_type->name)));
23220+
return ira->codegen->invalid_instruction;
23221+
}
23222+
23223+
ScopeDecls *container_scope = get_container_scope(container_type);
23224+
Tld *tld = find_container_decl(ira->codegen, container_scope, name);
23225+
if (tld == nullptr)
23226+
return ir_const_bool(ira, &instruction->base, false);
23227+
23228+
if (tld->visib_mod == VisibModPrivate && tld->import != get_scope_import(instruction->base.scope)) {
23229+
return ir_const_bool(ira, &instruction->base, false);
23230+
}
23231+
23232+
return ir_const_bool(ira, &instruction->base, true);
23233+
}
23234+
2317623235
static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
2317723236
switch (instruction->id) {
2317823237
case IrInstructionIdInvalid:
@@ -23467,6 +23526,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio
2346723526
return ir_analyze_instruction_enum_to_int(ira, (IrInstructionEnumToInt *)instruction);
2346823527
case IrInstructionIdCheckRuntimeScope:
2346923528
return ir_analyze_instruction_check_runtime_scope(ira, (IrInstructionCheckRuntimeScope *)instruction);
23529+
case IrInstructionIdHasDecl:
23530+
return ir_analyze_instruction_has_decl(ira, (IrInstructionHasDecl *)instruction);
2347023531
}
2347123532
zig_unreachable();
2347223533
}
@@ -23703,6 +23764,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
2370323764
case IrInstructionIdEnumToInt:
2370423765
case IrInstructionIdVectorToArray:
2370523766
case IrInstructionIdArrayToVector:
23767+
case IrInstructionIdHasDecl:
2370623768
return false;
2370723769

2370823770
case IrInstructionIdAsm:

src/ir_print.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,14 @@ static void ir_print_decl_var_gen(IrPrint *irp, IrInstructionDeclVarGen *decl_va
14531453
}
14541454
}
14551455

1456+
static void ir_print_has_decl(IrPrint *irp, IrInstructionHasDecl *instruction) {
1457+
fprintf(irp->f, "@hasDecl(");
1458+
ir_print_other_instruction(irp, instruction->container);
1459+
fprintf(irp->f, ",");
1460+
ir_print_other_instruction(irp, instruction->name);
1461+
fprintf(irp->f, ")");
1462+
}
1463+
14561464
static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
14571465
ir_print_prefix(irp, instruction);
14581466
switch (instruction->id) {
@@ -1920,6 +1928,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
19201928
case IrInstructionIdResizeSlice:
19211929
ir_print_resize_slice(irp, (IrInstructionResizeSlice *)instruction);
19221930
break;
1931+
case IrInstructionIdHasDecl:
1932+
ir_print_has_decl(irp, (IrInstructionHasDecl *)instruction);
1933+
break;
19231934
}
19241935
fprintf(irp->f, "\n");
19251936
}

test/compile_errors.zig

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ const tests = @import("tests.zig");
22
const builtin = @import("builtin");
33

44
pub fn addCases(cases: *tests.CompileErrorContext) void {
5+
cases.add(
6+
"@hasDecl with non-container",
7+
\\export fn entry() void {
8+
\\ _ = @hasDecl(i32, "hi");
9+
\\}
10+
,
11+
"tmp.zig:2:18: error: expected struct, enum, or union; found 'i32'",
12+
);
13+
514
cases.add(
615
"field access of slices",
716
\\export fn entry() void {

test/stage1/behavior.zig

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ comptime {
5353
_ = @import("behavior/fn_in_struct_in_comptime.zig");
5454
_ = @import("behavior/for.zig");
5555
_ = @import("behavior/generics.zig");
56+
_ = @import("behavior/hasdecl.zig");
5657
_ = @import("behavior/if.zig");
5758
_ = @import("behavior/import.zig");
5859
_ = @import("behavior/incomplete_struct_param_tld.zig");

test/stage1/behavior/hasdecl.zig

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const std = @import("std");
2+
const expect = std.testing.expect;
3+
4+
const Foo = @import("hasdecl/foo.zig");
5+
6+
const Bar = struct {
7+
nope: i32,
8+
9+
const hi = 1;
10+
pub var blah = "xxx";
11+
};
12+
13+
test "@hasDecl" {
14+
expect(@hasDecl(Foo, "public_thing"));
15+
expect(!@hasDecl(Foo, "private_thing"));
16+
expect(!@hasDecl(Foo, "no_thing"));
17+
18+
expect(@hasDecl(Bar, "hi"));
19+
expect(@hasDecl(Bar, "blah"));
20+
expect(!@hasDecl(Bar, "nope"));
21+
}

test/stage1/behavior/hasdecl/foo.zig

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub const public_thing = 42;
2+
const private_thing = 666;

0 commit comments

Comments
 (0)