Skip to content

Make Reexport composable with other import macros #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 21, 2021
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,29 @@ using X
# all of Y's exported symbols and Z's x and y also available here
```

`@reexport import <module>.<name>` or `@reexport import <module>: <name>` exports `<name>` from `<module>` after importing it.

```julia
module Y
...
end

module Z
...
end

module X
using Reexport
@reexport import Y
# Only `Y` itself is available here
@reexport import Z: x, y
# Z's x and y symbols available here
end

using X
# Y (but not it's exported names) and Z's x and y are available here.
```

`@reexport module <modulename> ... end` defines `module <modulename>` and also re-exports its symbols:

```julia
Expand All @@ -45,3 +68,7 @@ end
using A
# all of B's exported symbols available here
```

`@reexport @another_macro <import or using expression>` first expands `@another_macro` on the expression, making `@reexport` with other macros.

`@reexport begin ... end` will apply the reexport macro to every expression in the block.
44 changes: 30 additions & 14 deletions src/Reexport.jl
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
module Reexport

macro reexport(ex)
isa(ex, Expr) && (ex.head == :module ||
ex.head == :using ||
(ex.head == :toplevel &&
all(e->isa(e, Expr) && e.head == :using, ex.args))) ||
macro reexport(ex::Expr)
esc(reexport(__module__, ex))
end

reexport(m::Module, l::LineNumberNode) = l

function reexport(m::Module, ex::Expr)
# unpack any macros
ex = macroexpand(m, ex)
# recursively unpack any blocks
if ex.head == :block
return Expr(:block, map(e -> reexport(m, e), ex.args)...)
end

ex.head in (:module, :using, :import) ||
ex.head === :toplevel && all(e -> isa(e, Expr) && e.head == :using, ex.args) ||
error("@reexport: syntax error")

if ex.head == :module
if ex.head === :module
# @reexport {using, import} module Foo ... end
modules = Any[ex.args[2]]
ex = Expr(:toplevel, ex, :(using .$(ex.args[2])))
elseif ex.head == :using && all(e->isa(e, Symbol), ex.args)
modules = Any[ex.args[end]]
elseif ex.head == :using && ex.args[1].head == :(:)
elseif ex.head in (:using, :import) && ex.args[1].head == :(:)
# @reexport {using, import} Foo: bar, baz
symbols = [e.args[end] for e in ex.args[1].args[2:end]]
return esc(Expr(:toplevel, ex, :(eval(Expr(:export, $symbols...)))))
return Expr(:toplevel, ex, :(eval(Expr(:export, $symbols...))))
elseif ex.head === :import && all(e -> e.head == :(.), ex.args)
# @reexport import Foo.bar, Baz.qux
symbols = Any[e.args[end] for e in ex.args]
return Expr(:toplevel, ex, :(eval(Expr(:export, $symbols...))))
else
# @reexport using Foo, Bar, Baz
modules = Any[e.args[end] for e in ex.args]
end

esc(Expr(:toplevel, ex,
[:(eval(Expr(:export, filter!(x -> Base.isexported($mod, x),
names($mod; all=true, imported=true))...)))
for mod in modules]...))
Expr(:toplevel, ex,
[:(eval(Expr(:export, filter!(x -> Base.isexported($mod, x),
names($mod; all=true, imported=true))...)))
for mod in modules]...)
end

export @reexport
Expand Down
127 changes: 127 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,130 @@ module X7
end
using .X7
@test Base.isexported(X7, :S7)

#== Imports ==#
module X8
using Reexport

module InnerX8
const a = 1
export a
end
@reexport import .InnerX8: a
end

module X9
using Reexport

module InnerX9
const a = 1
export a
end
@reexport import .InnerX9.a
end

module X10
using Reexport

module InnerX10_1
const a = 1
export a
end

module InnerX10_2
const b = 1
export b
end

@reexport import .InnerX10_1.a, .InnerX10_2.b
end

module X11
using Reexport

module InnerX11
const b = 1
export b
end
@reexport import .InnerX11
end

@testset "import" begin
@testset "Reexported colon-qualified single import" begin
@test Set(names(X8)) == Set([:X8, :a])
end

@testset "Reexported dot-qualified single import" begin
@test Set(names(X9)) == Set([:X9, :a])
end

@testset "Reexported qualified multiple import" begin
@test Set(names(X10)) == Set([:X10, :a, :b])
end

@testset "Reexported module import" begin
@test Set(names(X11)) == Set([:X11, :InnerX11])
end
end

#== block ==#
module X12
using Reexport
@reexport begin
using Main.X9
using Main.X10
end
end

module X13
using Reexport
@reexport begin
import Main.X9
import Main.X10
end
end

module X14
using Reexport
module InnerX14
const a = 1
export a
end
@reexport begin
import Main.X9
using Main.X10
using .InnerX14: a
end
end

@testset "block" begin
@testset "block of using" begin
@test Set(names(X12)) == union(Set(names(X9)), Set(names(X10)), Set([:X12]))
end
@testset "block of import" begin
@test Set(names(X13)) == Set([:X13, :X9, :X10])
end
@testset "mixed using and import" begin
@test Set(names(X14)) == union(Set([:X14, :X9, :a]), Set(names(X10)))
end
end

#== macroexpand ==#
module X15
using Reexport

macro identity_macro(ex::Expr)
ex
end

module InnerX15
const a = 1
export a
end

@reexport @identity_macro using .InnerX15: a
end
@testset "macroexpand" begin
@test Set(names(X15)) == Set([:X15, :a])
end