diff --git a/README.md b/README.md index 32be7e6..4b916a8 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,29 @@ using X # all of Y's exported symbols and Z's x and y also available here ``` +`@reexport import .` or `@reexport import : ` exports `` from `` 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 ... end` defines `module ` and also re-exports its symbols: ```julia @@ -45,3 +68,7 @@ end using A # all of B's exported symbols available here ``` + +`@reexport @another_macro ` 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. diff --git a/src/Reexport.jl b/src/Reexport.jl index 5cde14b..ef2694c 100644 --- a/src/Reexport.jl +++ b/src/Reexport.jl @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index abfbc4d..47349f9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 +