Internal details

Implementation strategy

  1. [DONE hackily] Figure out what names used in the module are being used to refer to bindings in global scope (as opposed to e.g. shadowing globals).
    • We do this by parsing the code (thanks to JuliaSyntax), then reimplementing scoping rules on top of the parse tree
    • This is finicky, but assuming scoping doesn't change, should be robust enough (once the long tail of edge cases are dealt with...)
      • Currently, I don't handle the global keyword, so those may look like local variables and confuse things
    • This means we need access to the raw source code; pathof works well for packages, but for local modules one has to pass the path themselves. Also doesn't seem to work well for stdlibs in the sysimage
  2. [DONE] Figure out what implicit imports are available in the module, and which module they come from
    • done, via a magic ccall from Discourse, and Base.which.
  3. [DONE] Figure out which names have been explicitly imported already
    • Done via parsing

Then we can put this information together to figure out what names are actually being used from other modules, and whose usage could be made explicit, and also which existing explicit imports are not being used.

Internals

ExplicitImports.find_implicit_importsFunction
find_implicit_imports(mod::Module; skip=(mod, Base, Core))

Given a module mod, returns a Dict{Symbol, @NamedTuple{source::Module,exporters::Vector{Module}}} showing names exist in mod's namespace which are available due to implicit exports by other modules. The dict's keys are those names, and the values are the source module that the name comes from, along with the modules which export the same binding that are available in mod due to implicit imports.

In the case of ambiguities (two modules exporting the same name), the name is unavailable in the module, and hence the name will not be present in the dict.

This is powered by Base.which.

source
ExplicitImports.get_names_usedFunction
get_names_used(file) -> FileAnalysis

Figures out which global names are used in file, and what modules they are used within.

Traverses static include statements.

Returns two Set{@NamedTuple{name::Symbol,module_path::Vector{Symbol}}}, namely

  • needs_explicit_import
  • unnecessary_explicit_import

and a Set{Vector{Symbol}} of "untainted module paths", i.e. those which were analyzed and do not contain an unanalyzable include:

  • untainted_modules
source
ExplicitImports.analyze_all_namesFunction
analyze_all_names(file)

Returns a tuple of two items:

  • per_usage_info: a table containing information about each name each time it was used
  • untainted_modules: a set containing modules found and analyzed successfully
source
ExplicitImports.inspect_sessionFunction
ExplicitImports.inspect_session([io::IO=stdout,]; skip=(Base, Core), inner=print_explicit_imports)

Experimental functionality to call inner (defaulting to print_explicit_imports) on each loaded package in the Julia session.

source