authorgravatar for der.teufel.mail@gmail.comKrzysztof Wolicki <der.teufel.mail@gmail.com> 2026-08-24 14:41:34+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-08-27 23:47:13+02:00
logba74ad552592108ccc3e0ad6fb94eff639db2e45
treee9d6ed06a1b396cc8654dc1c0dbdd53d2a9e0578
parente2c60cf76778cc049d41a9149df8a5fbb172d3c1

Make `@hasDecl` return `true` only for public declarations


4 files changed, 17 insertions(+), 21 deletions(-)

doc/langref.html.in+1-1
......@@ -5013,7 +5013,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
50135013
50145014 {#header_open|@hasDecl#}
50155015 <pre>{#syntax#}@hasDecl(comptime Namespace: type, comptime name: []const u8) bool{#endsyntax#}</pre>
5016 <p>Returns whether or not a {#link|Namespace#} has a declaration matching {#syntax#}name{#endsyntax#}.</p>
5016 <p>Returns whether or not a {#link|Namespace#} has a public declaration matching {#syntax#}name{#endsyntax#}.</p>
50175017 {#code|test_hasDecl_builtin.zig#}
50185018
50195019 <p>Caution: using {#syntax#}@hasDecl{#endsyntax#} to implement
doc/langref/test_hasDecl_builtin.zig+2-4
......@@ -11,10 +11,8 @@ const Foo = struct {
1111test "@hasDecl" {
1212 try expect(@hasDecl(Foo, "blah"));
1313
14 // Even though `hi` is private, @hasDecl returns true because this test is
15 // in the same file scope as Foo. It would return false if Foo was declared
16 // in a different file.
17 try expect(@hasDecl(Foo, "hi"));
14 // @hasDecl returns false for private declarations.
15 try expect(!@hasDecl(Foo, "hi"));
1816
1917 // @hasDecl is for declarations; not fields.
2018 try expect(!@hasDecl(Foo, "nope"));
src/Sema.zig+6-8
......@@ -5977,7 +5977,7 @@ fn lookupIdentifier(sema: *Sema, block: *Block, name: InternPool.NullTerminatedS
59775977 var namespace = block.namespace;
59785978 while (true) {
59795979 if (try sema.lookupInNamespace(block, namespace, name)) |lookup| {
5980 assert(lookup.accessible);
5980 assert(lookup.accessible == .public or lookup.accessible == .private_same_file);
59815981 return lookup.nav;
59825982 }
59835983 namespace = zcu.namespacePtr(namespace).parent.unwrap() orelse break;
......@@ -5993,9 +5993,7 @@ fn lookupInNamespace(
59935993 ident_name: InternPool.NullTerminatedString,
59945994) CompileError!?struct {
59955995 nav: InternPool.Nav.Index,
5996 /// If `false`, the declaration is in a different file and is not `pub`.
5997 /// We still return the declaration for better error reporting.
5998 accessible: bool,
5996 accessible: enum { public, private_same_file, private },
59995997} {
60005998 const pt = sema.pt;
60015999 const zcu = pt.zcu;
......@@ -6025,12 +6023,12 @@ fn lookupInNamespace(
60256023 if (namespace.pub_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {
60266024 return .{
60276025 .nav = nav_index,
6028 .accessible = true,
6026 .accessible = .public,
60296027 };
60306028 } else if (namespace.priv_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {
60316029 return .{
60326030 .nav = nav_index,
6033 .accessible = src_file == namespace.file_scope,
6031 .accessible = if (src_file == namespace.file_scope) .private_same_file else .private,
60346032 };
60356033 }
60366034
......@@ -12877,7 +12875,7 @@ fn zirHasDecl(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
1287712875
1287812876 const namespace = container_type.getNamespace(zcu).unwrap() orelse return .bool_false;
1287912877 if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {
12880 if (lookup.accessible) {
12878 if (lookup.accessible == .public) {
1288112879 return .bool_true;
1288212880 }
1288312881 }
......@@ -26925,7 +26923,7 @@ fn namespaceLookup(
2692526923 const zcu = pt.zcu;
2692626924 const gpa = sema.gpa;
2692726925 if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {
26928 if (!lookup.accessible) {
26926 if (lookup.accessible == .private) {
2692926927 return sema.failWithOwnedErrorMsg(block, msg: {
2693026928 const msg = try sema.errMsg(src, "'{f}' is not marked 'pub'", .{
2693126929 decl_name.fmt(&zcu.intern_pool),
test/behavior/hasdecl.zig+8-8
......@@ -4,7 +4,7 @@ const expect = std.testing.expect;
44
55const Foo = @import("hasdecl/foo.zig");
66
7const Bar = struct {
7pub const Bar = struct {
88 nope: i32,
99
1010 const hi = 1;
......@@ -18,7 +18,7 @@ test "@hasDecl" {
1818 try expect(!@hasDecl(Foo, "private_thing"));
1919 try expect(!@hasDecl(Foo, "no_thing"));
2020
21 try expect(@hasDecl(Bar, "hi"));
21 try expect(!@hasDecl(Bar, "hi"));
2222 try expect(@hasDecl(Bar, "blah"));
2323 try expect(!@hasDecl(Bar, "nope"));
2424}
......@@ -26,10 +26,10 @@ test "@hasDecl" {
2626test "@hasDecl using a sliced string literal" {
2727 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2828
29 try expect(@hasDecl(@This(), "std") == true);
30 try expect(@hasDecl(@This(), "std"[0..0]) == false);
31 try expect(@hasDecl(@This(), "std"[0..1]) == false);
32 try expect(@hasDecl(@This(), "std"[0..2]) == false);
33 try expect(@hasDecl(@This(), "std"[0..3]) == true);
34 try expect(@hasDecl(@This(), "std"[0..]) == true);
29 try expect(@hasDecl(@This(), "Bar") == true);
30 try expect(@hasDecl(@This(), "Bar"[0..0]) == false);
31 try expect(@hasDecl(@This(), "Bar"[0..1]) == false);
32 try expect(@hasDecl(@This(), "Bar"[0..2]) == false);
33 try expect(@hasDecl(@This(), "Bar"[0..3]) == true);
34 try expect(@hasDecl(@This(), "Bar"[0..]) == true);
3535}