[ANN] ExplicitImports.jl: tooling to help use and maintain explicit imports in your package

Background: what are implicit and explicit imports?

An explicit import is a statement like using SomePkg: foo or import SomePkg: foo which explicitly brings the name foo into your module (often another package). In contrast, an implicit one is a statement like using SomePkg, which brings all names exported by SomePkg into your module.

Implicit imports can be convenient, but they occasionally have issues in which another package OtherPkg starts exporting the same name foo (pointing to a different function, say), which makes it ambiguous which function should be associated to the name foo in your module.

Some also criticize that with implicit imports, if you only have access to the source code, you can’t see where each name is coming from. (If you are in a REPL, use @which to easily do so).

Explicit imports, on the other hand, can be quite inconvenient. You need to manually specify each name that you want to import. You also need to maintain this list as you use more names or stop using some names.

The choice of implicit vs explicit exports can be somewhat contentious, and I think in general the right choice depends on the specific usage (and your preference). I would prefer if folks don’t use this thread to debate which to use, and focus on the functionality ExplicitImports.jl provides (or fails to provide, as the case may be).

What does ExplicitImports.jl do?

ExplicitImports.jl attempts to make it easier to use and maintain lists of explicit imports. The main interactive entrypoint is:

using ExplicitImports, MyPackage
print_explicit_imports(MyPackage)

For example:

julia> using ExplicitImports

julia> print_explicit_imports(ExplicitImports)
Module ExplicitImports is relying on implicit imports for 6 names. These could be explicitly imported as follows:

```julia
using AbstractTrees: AbstractTrees
using AbstractTrees: Leaves
using AbstractTrees: TreeCursor
using AbstractTrees: children
using AbstractTrees: nodevalue
using JuliaSyntax: JuliaSyntax
```

Additionally, ExplicitImports has stale explicit imports for these unused names:
- GreenNode

This allows one to develop some code using implicit imports, then call print_explicit_imports to get a list of explicit import statements to copy into their code.

Additionally, if one would like to only rely on explicit imports, you can add ExplicitImports as a test dependency, and add check_no_implicit_imports(ExplicitImports) to your tests. ExplicitImports is registered in General to make it convenient to add as a test dependency.

There’s a bit more discussion and a few more features in the docs, so please check those out for more!

And if you run into any bugs or problems, please file an issue. ExplicitImports works by parsing your code and trying to re-implement Julia’s scoping rules to figure out whether any given name refers to a global that may be implicitly imported, or just a local variable. There’s a ton of edge cases and it’s very possible there are common failure modes. I am hoping we can squash those as they arise, and rely on the ignore kwarg in check_no_implicit_imports in the meantime to manually suppress false positives. (And if you know about parsing and have ideas about how I can do it better, please file an issue or PR!).

41 Likes

This would be a nice addition to the language server code actions. The language server should already know where bindings come from so you wouldn’t have to traverse the source code in this case (I think).

4 Likes

Awesome idea!!

Is the plan to (eventually) add this to Aqua.jl?

3 Likes

I think LanguageServer integration would be great. I have no idea how that works though.

I am not sure if Aqua would want it, since it adds two dependencies (AbstractTrees and JuliaSyntax - though we could use Base.JuliaSyntax to get rid of the latter). It also might be too immature at this phase. Lastly I think automatically erroring on stuff like implicit imports seems to opinionated/aggressive to me. But I think it’s really up to the Aqua maintainers in terms of what they want to do and what the vision for the package is.

1 Like

If you, or anyone else, wants to give it a try perhaps the PR where I implemented import sorting could be useful to get an idea about what you need to add.

1 Like

Thanks for the nice package! Right now, it requires Julia v1.10. Thus, we cannot use it for packages running CI also with older versions of Julia. Do you have a suggestion for a workaround for such a case?

Would just putting a conditional on the test set work:

if VERSION >= v"1.10"
    @testset ...
end

Not in a nice way, since we need to add ExplicitImports.jl to test/Project.toml and the package cannot be installed on older Julia versions.
The only way I see would be to depend on Pkg for tests and install ExplicitImports.jl after a check for the Julia version, but that appears to be more cumbersome than I would like.

I think we should be able to drop the julia compat bound to at least 1.7 (I’m using the destructuring syntax there so dropping it further would require not using that syntax). But when I tried some tests failed, so I need to debug further.

That would be very nice, thanks a lot.

1 Like

Just to say v1.0.1 is out and supports Julia 1.7+.

5 Likes

Nice, thanks!