Skip to content

Consider allowing local imports #55680

Open
@Keno

Description

@Keno

Summary

The import Foo syntax does two things:

  1. Resolves the name Foo using the containing Project's name=>uuid mapping and if necessary loads the package.
  2. Creates a global binding Foo to assign the result to

In some instances it would be useful to be able to do the former without the latter. I propose the following new syntax:

let
    local import Foo # Foo resolved in calling module's project
end
# No global identifier `Foo`

This only applies to import (with all of its subforms), but not to using.

Motivating use case

My motivating use case comes from Cedar, which has a string macro for defining SPICE circuits like so:

ckt = sp"""
* Inverter
Xneg VSS D Q VSS sg13_lv_nmos W=1.48u L=130n
Xpos VDD D Q VDD sg13_lv_pmos W=2.24u L=130n
.LIB "jlpkg://IHP_SG13G2/models/cornerMOSlv.lib" mos_tt
"""

In particular, the semantics of this string macro reference the IHP_SG13G2 package in the caller's project (so
the package resolution can't happen where the string macro is defined). Currently this lowers to import IHP_SG13G2 as $(gensym()), which works, but pollutes the global namespace with an unnecessary binding.

I considered having the macro expand to an explicit call to Base.require instead, but we have special support in the REPL that recognizes expanded imports and offers to install the package, which is an important feature for this use case.

Frequently asked questions

Why is the local keyword required?

We currently allow import at global-but-local scope with global semantics:

julia> let
       import Dates
       end

julia> Dates
Dates

This is inconsistent with our treatment in fully local scope, where it is an error:

julia> foo() = import Dates
ERROR: syntax: "import" expression not at top level
Stacktrace:
 [1] top-level scope
   @ REPL[1]:1

but was added for convenience of expressions like @time using Foo.

We could consider changing the scoping semantics in global-but-local scope in 2.0 and making the keyword optional.

What are the semantics with respect to expression ordering?

The import effect is hoisted to top-level, i.e.
foo() = local import Dates
lowers to something like:

let #gensym# = require(:Dates)
foo() = local Dates = #gensym#
end

this is generally unobservable, but e.g. in a case where no Dates package exists, you get an error even if you never call foo.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions