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...@@ -5013,7 +5013,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
50135013
5014 {#header_open|@hasDecl#}5014 {#header_open|@hasDecl#}
5015 <pre>{#syntax#}@hasDecl(comptime Namespace: type, comptime name: []const u8) bool{#endsyntax#}</pre>5015 <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>
5017 {#code|test_hasDecl_builtin.zig#}5017 {#code|test_hasDecl_builtin.zig#}
50185018
5019 <p>Caution: using {#syntax#}@hasDecl{#endsyntax#} to implement5019 <p>Caution: using {#syntax#}@hasDecl{#endsyntax#} to implement
doc/langref/test_hasDecl_builtin.zig+2-4
...@@ -11,10 +11,8 @@ const Foo = struct {...@@ -11,10 +11,8 @@ const Foo = struct {
11test "@hasDecl" {11test "@hasDecl" {
12 try expect(@hasDecl(Foo, "blah"));12 try expect(@hasDecl(Foo, "blah"));
1313
14 // Even though `hi` is private, @hasDecl returns true because this test is14 // @hasDecl returns false for private declarations.
15 // in the same file scope as Foo. It would return false if Foo was declared15 try expect(!@hasDecl(Foo, "hi"));
16 // in a different file.
17 try expect(@hasDecl(Foo, "hi"));
1816
19 // @hasDecl is for declarations; not fields.17 // @hasDecl is for declarations; not fields.
20 try expect(!@hasDecl(Foo, "nope"));18 try expect(!@hasDecl(Foo, "nope"));
src/Sema.zig+6-8
...@@ -5977,7 +5977,7 @@ fn lookupIdentifier(sema: *Sema, block: *Block, name: InternPool.NullTerminatedS...@@ -5977,7 +5977,7 @@ fn lookupIdentifier(sema: *Sema, block: *Block, name: InternPool.NullTerminatedS
5977 var namespace = block.namespace;5977 var namespace = block.namespace;
5978 while (true) {5978 while (true) {
5979 if (try sema.lookupInNamespace(block, namespace, name)) |lookup| {5979 if (try sema.lookupInNamespace(block, namespace, name)) |lookup| {
5980 assert(lookup.accessible);5980 assert(lookup.accessible == .public or lookup.accessible == .private_same_file);
5981 return lookup.nav;5981 return lookup.nav;
5982 }5982 }
5983 namespace = zcu.namespacePtr(namespace).parent.unwrap() orelse break;5983 namespace = zcu.namespacePtr(namespace).parent.unwrap() orelse break;
...@@ -5993,9 +5993,7 @@ fn lookupInNamespace(...@@ -5993,9 +5993,7 @@ fn lookupInNamespace(
5993 ident_name: InternPool.NullTerminatedString,5993 ident_name: InternPool.NullTerminatedString,
5994) CompileError!?struct {5994) CompileError!?struct {
5995 nav: InternPool.Nav.Index,5995 nav: InternPool.Nav.Index,
5996 /// If `false`, the declaration is in a different file and is not `pub`.5996 accessible: enum { public, private_same_file, private },
5997 /// We still return the declaration for better error reporting.
5998 accessible: bool,
5999} {5997} {
6000 const pt = sema.pt;5998 const pt = sema.pt;
6001 const zcu = pt.zcu;5999 const zcu = pt.zcu;
...@@ -6025,12 +6023,12 @@ fn lookupInNamespace(...@@ -6025,12 +6023,12 @@ fn lookupInNamespace(
6025 if (namespace.pub_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {6023 if (namespace.pub_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {
6026 return .{6024 return .{
6027 .nav = nav_index,6025 .nav = nav_index,
6028 .accessible = true,6026 .accessible = .public,
6029 };6027 };
6030 } else if (namespace.priv_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {6028 } else if (namespace.priv_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {
6031 return .{6029 return .{
6032 .nav = nav_index,6030 .nav = nav_index,
6033 .accessible = src_file == namespace.file_scope,6031 .accessible = if (src_file == namespace.file_scope) .private_same_file else .private,
6034 };6032 };
6035 }6033 }
60366034
...@@ -12877,7 +12875,7 @@ fn zirHasDecl(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air...@@ -12877,7 +12875,7 @@ fn zirHasDecl(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
1287712875
12878 const namespace = container_type.getNamespace(zcu).unwrap() orelse return .bool_false;12876 const namespace = container_type.getNamespace(zcu).unwrap() orelse return .bool_false;
12879 if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {12877 if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {
12880 if (lookup.accessible) {12878 if (lookup.accessible == .public) {
12881 return .bool_true;12879 return .bool_true;
12882 }12880 }
12883 }12881 }
...@@ -26925,7 +26923,7 @@ fn namespaceLookup(...@@ -26925,7 +26923,7 @@ fn namespaceLookup(
26925 const zcu = pt.zcu;26923 const zcu = pt.zcu;
26926 const gpa = sema.gpa;26924 const gpa = sema.gpa;
26927 if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {26925 if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {
26928 if (!lookup.accessible) {26926 if (lookup.accessible == .private) {
26929 return sema.failWithOwnedErrorMsg(block, msg: {26927 return sema.failWithOwnedErrorMsg(block, msg: {
26930 const msg = try sema.errMsg(src, "'{f}' is not marked 'pub'", .{26928 const msg = try sema.errMsg(src, "'{f}' is not marked 'pub'", .{
26931 decl_name.fmt(&zcu.intern_pool),26929 decl_name.fmt(&zcu.intern_pool),
test/behavior/hasdecl.zig+8-8
...@@ -4,7 +4,7 @@ const expect = std.testing.expect;...@@ -4,7 +4,7 @@ const expect = std.testing.expect;
44
5const Foo = @import("hasdecl/foo.zig");5const Foo = @import("hasdecl/foo.zig");
66
7const Bar = struct {7pub const Bar = struct {
8 nope: i32,8 nope: i32,
99
10 const hi = 1;10 const hi = 1;
...@@ -18,7 +18,7 @@ test "@hasDecl" {...@@ -18,7 +18,7 @@ test "@hasDecl" {
18 try expect(!@hasDecl(Foo, "private_thing"));18 try expect(!@hasDecl(Foo, "private_thing"));
19 try expect(!@hasDecl(Foo, "no_thing"));19 try expect(!@hasDecl(Foo, "no_thing"));
2020
21 try expect(@hasDecl(Bar, "hi"));21 try expect(!@hasDecl(Bar, "hi"));
22 try expect(@hasDecl(Bar, "blah"));22 try expect(@hasDecl(Bar, "blah"));
23 try expect(!@hasDecl(Bar, "nope"));23 try expect(!@hasDecl(Bar, "nope"));
24}24}
...@@ -26,10 +26,10 @@ test "@hasDecl" {...@@ -26,10 +26,10 @@ test "@hasDecl" {
26test "@hasDecl using a sliced string literal" {26test "@hasDecl using a sliced string literal" {
27 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;27 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
2828
29 try expect(@hasDecl(@This(), "std") == true);29 try expect(@hasDecl(@This(), "Bar") == true);
30 try expect(@hasDecl(@This(), "std"[0..0]) == false);30 try expect(@hasDecl(@This(), "Bar"[0..0]) == false);
31 try expect(@hasDecl(@This(), "std"[0..1]) == false);31 try expect(@hasDecl(@This(), "Bar"[0..1]) == false);
32 try expect(@hasDecl(@This(), "std"[0..2]) == false);32 try expect(@hasDecl(@This(), "Bar"[0..2]) == false);
33 try expect(@hasDecl(@This(), "std"[0..3]) == true);33 try expect(@hasDecl(@This(), "Bar"[0..3]) == true);
34 try expect(@hasDecl(@This(), "std"[0..]) == true);34 try expect(@hasDecl(@This(), "Bar"[0..]) == true);
35}35}