ExplicitImports.jl icon indicating copy to clipboard operation
ExplicitImports.jl copied to clipboard

Detect usage of non-public names?

Open DilumAluthge opened this issue 11 months ago • 2 comments

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.

DilumAluthge avatar Feb 26 '24 14:02 DilumAluthge

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.

ericphanson avatar Feb 26 '24 15:02 ericphanson

#50 adds the technology needed for the second case, but it isn't exposed to the user yet.

ericphanson avatar May 25 '24 15:05 ericphanson