Skip to content

Commit 9f2e685

Browse files
aviateskNHDalypfitzseb
committed
reflection: allow names to return using-ed names
This commit makes it possible for `names` to return `using`-ed names as well: ```julia julia> using Base: @assume_effects julia> Symbol("@assume_effects") in names(@__MODULE__; usings=true) true ``` Currently, to find all names available in a module `A`, the following steps are needed: 1. Use `names(A; all=true, imported=true)` to get the names defined by `A` and the names explicitly `import`ed by `A`. 2. Use `jl_module_usings(A)` to get the list of modules `A` has `using`-ed and then use `names()` to get the names `export`ed by those modules. This method is implemented in e.g. REPL completions, but it has a problem: it could not get the names explicitly `using`-ed by `using B: ...` (#36529, #40356, JuliaDebug/Infiltrator.jl#106, etc.). This commit adds a new keyword argument `usings::Bool=false` to `names(A; ...)`, which, when `usings=true` is specified, returns all names introduced by `using` in `A`. In other words, `usings=true` not only returns explicitly `using`-ed names but also incorporates step 2 above into the implementation of `names`. By using this new option, we can now use `names(A; all=true, imported=true, usings=true)` to know all names available in `A`, without implementing the two-fold steps on application side. As example application, this new feature will be used to simplify and enhance the implementation of REPL completions. - fixes #36529 Co-authored-by: Nathan Daly <[email protected]> Co-authored-by: Sebastian Pfitzner <[email protected]>
1 parent 6a10d03 commit 9f2e685

File tree

4 files changed

+167
-31
lines changed

4 files changed

+167
-31
lines changed

NEWS.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Julia v1.12 Release Notes
44
New language features
55
---------------------
66

7+
- A new keyword argument `usings::Bool` has been added to `names`. By using this, we can now
8+
find all the names available in module `A` by `names(A; all=true, imported=true, usings=true)`. ([#54609])
9+
710
Language changes
811
----------------
912

@@ -17,7 +20,7 @@ Language changes
1720
may pave the way for inference to be able to intelligently re-use the old
1821
results, once the new method is deleted. ([#53415])
1922

20-
- Macro expansion will no longer eargerly recurse into into `Expr(:toplevel)`
23+
- Macro expansion will no longer eagerly recurse into into `Expr(:toplevel)`
2124
expressions returned from macros. Instead, macro expansion of `:toplevel`
2225
expressions will be delayed until evaluation time. This allows a later
2326
expression within a given `:toplevel` expression to make use of macros

base/reflection.jl

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,28 +77,33 @@ function fullname(m::Module)
7777
end
7878

7979
"""
80-
names(x::Module; all::Bool = false, imported::Bool = false)
80+
names(x::Module; all::Bool=false, imported::Bool=false, usings::Bool=false) -> Vector{Symbol}
8181
8282
Get a vector of the public names of a `Module`, excluding deprecated names.
8383
If `all` is true, then the list also includes non-public names defined in the module,
8484
deprecated names, and compiler-generated names.
8585
If `imported` is true, then names explicitly imported from other modules
86-
are also included. Names are returned in sorted order.
86+
are also included.
87+
If `usings` is true, then names explicitly imported via `using` are also included.
88+
Names are returned in sorted order.
8789
8890
As a special case, all names defined in `Main` are considered \"public\",
8991
since it is not idiomatic to explicitly mark names from `Main` as public.
9092
9193
!!! note
9294
`sym ∈ names(SomeModule)` does *not* imply `isdefined(SomeModule, sym)`.
93-
`names` will return symbols marked with `public` or `export`, even if
95+
`names` may return symbols marked with `public` or `export`, even if
9496
they are not defined in the module.
9597
98+
!!! warning
99+
`names` may return duplicate names. The duplication happens, e.g. if an `import`ed name
100+
conflicts with an already existing identifier.
101+
96102
See also: [`Base.isexported`](@ref), [`Base.ispublic`](@ref), [`Base.@locals`](@ref), [`@__MODULE__`](@ref).
97103
"""
98-
names(m::Module; all::Bool = false, imported::Bool = false) =
99-
sort!(unsorted_names(m; all, imported))
100-
unsorted_names(m::Module; all::Bool = false, imported::Bool = false) =
101-
ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint), m, all, imported)
104+
names(m::Module; kwargs...) = sort!(unsorted_names(m; kwargs...))
105+
unsorted_names(m::Module; all::Bool=false, imported::Bool=false, usings::Bool=false) =
106+
ccall(:jl_module_names, Array{Symbol,1}, (Any, Cint, Cint, Cint), m, all, imported, usings)
102107

103108
"""
104109
isexported(m::Module, s::Symbol) -> Bool

src/module.c

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -991,10 +991,19 @@ JL_DLLEXPORT jl_value_t *jl_module_usings(jl_module_t *m)
991991
return (jl_value_t*)a;
992992
}
993993

994-
JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported)
994+
uint8_t _binding_is_from_explicit_using(jl_binding_t *b) {
995+
jl_binding_t *owner = jl_atomic_load_relaxed(&b->owner);
996+
return (owner != NULL && owner != b && !b->imported);
997+
}
998+
999+
void _append_symbol_to_bindings_array(jl_array_t* a, jl_sym_t *name) {
1000+
jl_array_grow_end(a, 1);
1001+
//XXX: change to jl_arrayset if array storage allocation for Array{Symbols,1} changes:
1002+
jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)name);
1003+
}
1004+
1005+
void append_module_names(jl_array_t* a, jl_module_t *m, int all, int imported, int usings)
9951006
{
996-
jl_array_t *a = jl_alloc_array_1d(jl_array_symbol_type, 0);
997-
JL_GC_PUSH1(&a);
9981007
jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings);
9991008
for (size_t i = 0; i < jl_svec_len(table); i++) {
10001009
jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i);
@@ -1003,16 +1012,41 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported)
10031012
jl_sym_t *asname = b->globalref->name;
10041013
int hidden = jl_symbol_name(asname)[0]=='#';
10051014
int main_public = (m == jl_main_module && !(asname == jl_eval_sym || asname == jl_include_sym));
1006-
if ((b->publicp ||
1015+
if (((b->publicp) ||
10071016
(imported && b->imported) ||
1017+
(usings && _binding_is_from_explicit_using(b)) ||
10081018
(jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || main_public))) &&
1009-
(all || (!b->deprecated && !hidden))) {
1010-
jl_array_grow_end(a, 1);
1011-
// n.b. change to jl_arrayset if array storage allocation for Array{Symbols,1} changes:
1012-
jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)asname);
1013-
}
1019+
(all || (!b->deprecated && !hidden)))
1020+
_append_symbol_to_bindings_array(a, asname);
1021+
}
1022+
}
1023+
1024+
void append_exported_names(jl_array_t* a, jl_module_t *m, int all)
1025+
{
1026+
jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings);
1027+
for (size_t i = 0; i < jl_svec_len(table); i++) {
1028+
jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i);
1029+
if ((void*)b == jl_nothing)
1030+
break;
1031+
if (b->exportp && (all || !b->deprecated))
1032+
_append_symbol_to_bindings_array(a, b->globalref->name);
10141033
table = jl_atomic_load_relaxed(&m->bindings);
10151034
}
1035+
}
1036+
1037+
JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported, int usings)
1038+
{
1039+
jl_array_t *a = jl_alloc_array_1d(jl_array_symbol_type, 0);
1040+
JL_GC_PUSH1(&a);
1041+
append_module_names(a, m, all, imported, usings);
1042+
if (usings) {
1043+
// If `usings` is specified, traverse the list of `using`-ed modules and incorporate
1044+
// the names exported by those modules into the list.
1045+
for(int i=(int)m->usings.len-1; i >= 0; --i) {
1046+
jl_module_t *usinged = module_usings_getidx(m, i);
1047+
append_exported_names(a, usinged, all);
1048+
}
1049+
}
10161050
JL_GC_POP();
10171051
return (jl_value_t*)a;
10181052
}

test/reflection.jl

Lines changed: 108 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,18 @@ not_const = 1
125125
# For curmod_*
126126
include("testenv.jl")
127127

128+
module TestMod36529
129+
x36529 = 0
130+
y36529 = 1
131+
export y36529
132+
end
133+
128134
module TestMod7648
129135
using Test
130136
import Base.convert
131137
import ..curmod_name, ..curmod
132-
export a9475, foo9475, c7648, foo7648, foo7648_nomethods, Foo7648
138+
using ..TestMod36529: x36529 # doesn't import TestMod36529 or y36529, even though it's exported
139+
export a9475, c7648, f9475, foo7648, foo7648_nomethods, Foo7648
133140

134141
const c7648 = 8
135142
d7648 = 9
@@ -142,10 +149,11 @@ module TestModSub9475
142149
using Test
143150
using ..TestMod7648
144151
import ..curmod_name
145-
export a9475, foo9475
152+
export a9475, f9475, f54609
146153
a9475 = 5
147154
b9475 = 7
148-
foo9475(x) = x
155+
f9475(x) = x
156+
f54609(x) = x
149157
let
150158
@test Base.binding_module(@__MODULE__, :a9475) == @__MODULE__
151159
@test Base.binding_module(@__MODULE__, :c7648) == TestMod7648
@@ -169,18 +177,104 @@ let
169177
@test Base.binding_module(TestMod7648, :d7648) == TestMod7648
170178
@test Base.binding_module(TestMod7648, :a9475) == TestMod7648.TestModSub9475
171179
@test Base.binding_module(TestMod7648.TestModSub9475, :b9475) == TestMod7648.TestModSub9475
172-
@test Set(names(TestMod7648))==Set([:TestMod7648, :a9475, :foo9475, :c7648, :foo7648, :foo7648_nomethods, :Foo7648])
173-
@test Set(names(TestMod7648, all = true)) == Set([:TestMod7648, :TestModSub9475, :a9475, :foo9475, :c7648, :d7648, :f7648,
174-
:foo7648, Symbol("#foo7648"), :foo7648_nomethods, Symbol("#foo7648_nomethods"),
175-
:Foo7648, :eval, Symbol("#eval"), :include, Symbol("#include")])
176-
@test Set(names(TestMod7648, all = true, imported = true)) == Set([:TestMod7648, :TestModSub9475, :a9475, :foo9475, :c7648, :d7648, :f7648,
177-
:foo7648, Symbol("#foo7648"), :foo7648_nomethods, Symbol("#foo7648_nomethods"),
178-
:Foo7648, :eval, Symbol("#eval"), :include, Symbol("#include"),
179-
:convert, :curmod_name, :curmod])
180+
defaultset = Set(Symbol[:Foo7648, :TestMod7648, :a9475, :c7648, :f9475, :foo7648, :foo7648_nomethods])
181+
allset = defaultset Set(Symbol[
182+
Symbol("#eval"), Symbol("#foo7648"), Symbol("#foo7648_nomethods"), Symbol("#include"),
183+
:TestModSub9475, :d7648, :eval, :f7648, :include])
184+
imported = Set(Symbol[:convert, :curmod_name, :curmod])
185+
usings_from_Test = Set(Symbol[
186+
Symbol("@inferred"), Symbol("@test"), Symbol("@test_broken"), Symbol("@test_deprecated"),
187+
Symbol("@test_logs"), Symbol("@test_nowarn"), Symbol("@test_skip"), Symbol("@test_throws"),
188+
Symbol("@test_warn"), Symbol("@testset"), :GenericArray, :GenericDict, :GenericOrder,
189+
:GenericSet, :GenericString, :LogRecord, :Test, :TestLogger, :TestSetException,
190+
:detect_ambiguities, :detect_unbound_args])
191+
usings_from_Base = delete!(Set(names(Module(); usings=true)), :anonymous) # the name of the anonymous module itself
192+
usings = Set(Symbol[:x36529, :TestModSub9475, :f54609]) usings_from_Test usings_from_Base
193+
@test Set(names(TestMod7648)) == defaultset
194+
@test Set(names(TestMod7648, all=true)) == allset
195+
@test Set(names(TestMod7648, all=true, imported=true)) == allset imported
196+
@test Set(names(TestMod7648, usings=true)) == defaultset usings
197+
@test Set(names(TestMod7648, all=true, usings=true)) == allset usings
180198
@test isconst(TestMod7648, :c7648)
181199
@test !isconst(TestMod7648, :d7648)
182200
end
183201

202+
# tests for `names(...; usings=true)`
203+
204+
baremodule Test54609Simple
205+
module Inner
206+
export exported
207+
global exported::Int = 1
208+
global unexported::Int = 0
209+
end
210+
using Base: @assume_effects
211+
using .Inner
212+
end
213+
let usings = names(Test54609Simple; usings=true)
214+
@test Symbol("@assume_effects") usings
215+
@test :Base usings
216+
@test :exported usings
217+
@test :unexported usings
218+
end # baremodule Test54609Simple
219+
220+
baremodule _Test54609Complex
221+
export exported_new
222+
using Base: @deprecate_binding
223+
global exported_new = nothing
224+
@deprecate_binding exported_old exported_new
225+
end # baremodule _Test54609Complex
226+
baremodule Test54609Complex
227+
using .._Test54609Complex
228+
end # baremodule Test54609Complex
229+
let usings = names(Test54609Complex; usings=true)
230+
@test :exported_new usings
231+
@test :exported_old usings
232+
@test :_Test54609Complex usings # should include the `using`ed module itself
233+
usings_all = names(Test54609Complex; usings=true, all=true)
234+
@test :exported_new usings_all
235+
@test :exported_old usings_all # deprecated names should be included with `all=true`
236+
end
237+
238+
module TestMod54609
239+
module M1
240+
const m1_x = 1
241+
export m1_x
242+
end
243+
module M2
244+
const m2_x = 1
245+
export m2_x
246+
end
247+
module A
248+
module B
249+
f(x) = 1
250+
secret = 1
251+
module Inner2 end
252+
end
253+
module C
254+
x = 1
255+
y = 2
256+
export y
257+
end
258+
using .B: f
259+
using .C
260+
using ..M1
261+
import ..M2
262+
end
263+
end # module TestMod54609
264+
let defaultset = Set((:A,))
265+
imported = Set((:M2,))
266+
usings_from_Base = delete!(Set(names(Module(); usings=true)), :anonymous) # the name of the anonymous module itself
267+
usings = Set((:A, :f, :C, :y, :M1, :m1_x)) usings_from_Base
268+
allset = Set((:A, :B, :C, :eval, :include, Symbol("#eval"), Symbol("#include")))
269+
@test Set(names(TestMod54609.A)) == defaultset
270+
@test Set(names(TestMod54609.A, imported=true)) == defaultset imported
271+
@test Set(names(TestMod54609.A, usings=true)) == defaultset usings
272+
@test Set(names(TestMod54609.A, all=true)) == allset
273+
@test Set(names(TestMod54609.A, all=true, usings=true)) == allset usings
274+
@test Set(names(TestMod54609.A, imported=true, usings=true)) == defaultset imported usings
275+
@test Set(names(TestMod54609.A, all=true, imported=true, usings=true)) == allset imported usings
276+
end
277+
184278
let
185279
using .TestMod7648
186280
@test Base.binding_module(@__MODULE__, :a9475) == TestMod7648.TestModSub9475
@@ -189,10 +283,10 @@ let
189283
@test parentmodule(foo7648, (Any,)) == TestMod7648
190284
@test parentmodule(foo7648) == TestMod7648
191285
@test parentmodule(foo7648_nomethods) == TestMod7648
192-
@test parentmodule(foo9475, (Any,)) == TestMod7648.TestModSub9475
193-
@test parentmodule(foo9475) == TestMod7648.TestModSub9475
286+
@test parentmodule(f9475, (Any,)) == TestMod7648.TestModSub9475
287+
@test parentmodule(f9475) == TestMod7648.TestModSub9475
194288
@test parentmodule(Foo7648) == TestMod7648
195-
@test parentmodule(first(methods(foo9475))) == TestMod7648.TestModSub9475
289+
@test parentmodule(first(methods(f9475))) == TestMod7648.TestModSub9475
196290
@test parentmodule(first(methods(foo7648))) == TestMod7648
197291
@test nameof(Foo7648) === :Foo7648
198292
@test basename(functionloc(foo7648, (Any,))[1]) == "reflection.jl"

0 commit comments

Comments
 (0)