ExplicitImports.jl
ExplicitImports.jl copied to clipboard
Detect usage of non-public names?
Would it be possible to detect uses of non-public names? E.g. using Foo: bar
where Foo.bar
is not public.
Example
If I have:
module MyPackage
export f
public g
function f end
function g end
function h end
end # module
And then another package has:
module SomeOtherPackage
using MyPackage: f, g, h
end
Would it be possible to detect that SomeOtherPackage
is using MyPackage.h
, which is not public?
Of course we'd need a way to provide a list of exceptions, for the cases where you knowingly and intentionally want to use a non-public name.
Case 1: explicit import of non-public names like using Foo: private
yep, that should be possible! IIRC Base has an api for querying if a given name is public or not, so that part should be easy. Then the slightly tricky part is figuring out what names are actually being explicitly imported into a module.
I think Base should have an API for that too, but it currently doesn't (edit: this would be fixed by https://github.com/JuliaLang/julia/pull/42092 I think). One would expect names
to do this, but names
does not detect names which have been using
'd into the namespace, only ones that have been import
'd into the namespace. For example:
julia> module A
export a
a = 1
end
Main.A
julia> module B
using ..A: a
end
Main.B
julia> names(B)
1-element Vector{Symbol}:
:B
julia> names(B; imported=true)
1-element Vector{Symbol}:
:B
julia> names(B; imported=true, all=true)
5-element Vector{Symbol}:
Symbol("#eval")
Symbol("#include")
:B
:eval
:include
If we change B
to use import
, then:
julia> module B
import ..A: a
end
WARNING: replacing module B.
Main.B
julia> names(B; imported=true)
2-element Vector{Symbol}:
:B
:a
So we can't detect that a
has been added to B's namespace.
If we had that, we could probably simplify some (but not most) of the code here, and also solve the issue of detecting non-public names very easily: we would just iterate over names
, and for each name, check if it is public or not.
For now, given that names
does not support this, the parsing technique we use in ExplicitImports definitely could be used. We already come up with a list of explicitly imported names (to check which ones are stale); we could also query which of those are non-public. We can have an ignore
system like we do with the check_*
function as well.
Case 2: qualified usage of non-public names, like y = Foo.private()
For this case, I don't think Base is necessarily missing an API that should solve it, and the parsing approach used in ExplicitImports.jl would be necessary. We already look for qualified names (bc if all uses of a name is qualified, you don't need an explicit import), so what we would need to do is also look at the module being used to qualify the name, and then check if the name is public in that module or not. This one would be a bit more work since we need to modify the hairy parsing code, but should be doable.
#50 adds the technology needed for the second case, but it isn't exposed to the user yet.