/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
JuliaLowering/test/import.jl
340 строк
13 KB
Em Chu
[JuliaLowering] underscores: allow in using/import, lambda
17 июл 2026, 19:19
17 июл 2026, 19:19
56e469a
Код
Авторство
О чём код?
test_mod = Module() # Test attributes are correctly set for export/public JuliaLowering.include_string(test_mod, """ x = 1 y = 2 export x public y """) @test Base.isexported(test_mod, :x) @test Base.ispublic(test_mod, :x) @test Base.ispublic(test_mod, :y) @test !Base.isexported(test_mod, :y) # Test various forms of `using` C = JuliaLowering.include_string(test_mod, """ module C module D export x public y, f x = [101] y = [202] function f() "hi" end end module E using ..D: f using ..D using .D: y as D_y using .D: x as D_x_2, y as D_y_2 import .D.y as D_y_3 end end """) @test C.D.f === C.E.f @test C.D.x === C.E.x @test C.D.y === C.E.D_y @test C.D.x === C.E.D_x_2 @test C.D.y === C.E.D_y_2 @test C.D.y === C.E.D_y_3 # Test that using F brings in the exported symbol G immediately and that it can # be used next in the import list. F = JuliaLowering.include_string(test_mod, """ module F export G module G export G_global G_global = "exported from G" end end """) JuliaLowering.include_string(test_mod, """ using .F, .G """) @test test_mod.F === F @test test_mod.G === F.G @test test_mod.G_global === "exported from G" # Similarly, that import makes symbols available immediately H = JuliaLowering.include_string(test_mod, """ module H module I module J end end end """) JuliaLowering.include_string(test_mod, """ import .H.I, .I.J """) @test test_mod.I === H.I @test test_mod.J === H.I.J @test test_mod.G_global === "exported from G" @testset "(AI) from macro expansion" for expr_compat_mode in (true, false) macrocall_mod = Module() @eval macrocall_mod import JuliaLowering, JuliaLowering.@legacy_quote_to_syntax JuliaLowering.include_string(macrocall_mod, raw""" module Exporter export val val = [123] other = [456] end macro imp_names() @legacy_quote_to_syntax :(import .Exporter: val, other as o) end macro use_target() @legacy_quote_to_syntax :(using .Exporter) end """; expr_compat_mode) Core.@latestworld JuliaLowering.include_string(macrocall_mod, "@imp_names"; expr_compat_mode) JuliaLowering.include_string(macrocall_mod, "@use_target"; expr_compat_mode) Core.@latestworld @test macrocall_mod.val === macrocall_mod.Exporter.val @test macrocall_mod.o === macrocall_mod.Exporter.other @test !isdefined(macrocall_mod, :other) # imported only under the name `o` end @testset "Imported macrocalls" for expr_compat_mode in (true, false) # Test importing macros by their @-name macname_mod = Module() JuliaLowering.include_string(macname_mod, raw""" module Macros macro mac1(); "mac1"; end macro mac2(); "mac2"; end module Inner macro mac3(); "mac3"; end macro mac4(); "mac4"; end end end """; expr_compat_mode) JuliaLowering.include_string(macname_mod, raw""" module UseMacros import ..Macros: @mac1, @mac2 as @mac2_renamed, Inner.@mac3, Inner.@mac4 as @mac4_renamed end """; expr_compat_mode) Core.@latestworld @test JuliaLowering.include_string(macname_mod.UseMacros, "@mac1()"; expr_compat_mode) == "mac1" @test JuliaLowering.include_string(macname_mod.UseMacros, "@mac2_renamed()"; expr_compat_mode) == "mac2" @test JuliaLowering.include_string(macname_mod.UseMacros, "@mac3()"; expr_compat_mode) == "mac3" @test JuliaLowering.include_string(macname_mod.UseMacros, "@mac4_renamed()"; expr_compat_mode) == "mac4" end fl_eval(test_mod, :( module mod_p_e_n public_var = 1 public p exported_var = 2 export e neither_var = 3 end)) @testset "colon followed by only from-path" begin jl_eval(test_mod, Expr(:import, Expr(:(:), Expr(:., :., :mod_p_e_n)))) @test !isdefined(test_mod, :public_var) @test !isdefined(test_mod, :exported_var) @test !isdefined(test_mod, :neither_var) @test test_mod.mod_p_e_n isa Module jl_eval(test_mod, Expr(:using, Expr(:(:), Expr(:., :., :mod_p_e_n)))) @test !isdefined(test_mod, :public_var) @test !isdefined(test_mod, :exported_var) @test !isdefined(test_mod, :neither_var) @test test_mod.mod_p_e_n isa Module end @testset "(AI) public/export module resolution from macros" for (is_new, run) in [ (false, (mod, x)->fl_eval(mod,JuliaSyntax.parsestmt(Expr, x))), (true, (mod, x)->JuliaLowering.include_string(mod, x; expr_compat_mode=true)), (true, (mod, x)->JuliaLowering.include_string(mod, x; expr_compat_mode=false)) ] defs_mod = Module(:Defs) call_mod = Module(:CallSite) Core.eval(call_mod, :(const Defs = $defs_mod)) Core.eval(defs_mod, :(import JuliaLowering, JuliaLowering.@legacy_quote_to_syntax)) Core.eval(defs_mod, :(const var"@ast" = $(JuliaLowering.var"@ast"))) Core.eval(defs_mod, :(const var"@K_str" = $(JuliaSyntax.var"@K_str"))) # old-style macros: hygienic plain name vs escaped argument fl_eval(defs_mod, :(macro old_pub_plain(); Expr(:public, :op_hyg); end)) fl_eval(defs_mod, :(macro old_exp_plain(); Expr(:export, :oe_hyg); end)) fl_eval(defs_mod, :(macro old_pub_arg(name); Expr(:public, esc(name)); end)) fl_eval(defs_mod, :(macro old_exp_arg(name); Expr(:export, esc(name)); end)) # new-style macros: hygienic name (from syntaxquote) vs argument JuliaLowering.include_string(defs_mod, raw""" macro new_exp_plain(); @legacy_quote_to_syntax quote export ne_hyg end; end macro new_pub_arg(name); @ast __context__ __context__.macrocall [K"public" name]; end macro new_exp_arg(name); @ast __context__ __context__.macrocall [K"export" name]; end """) Core.@latestworld # Define the names being marked so `isexported`/`ispublic` are meaningful. # Hygienic names live in defs_mod; argument/escaped names live in call_mod. Core.eval(defs_mod, :(global op_hyg=1; global oe_hyg=1; global ne_hyg=1)) Core.eval(call_mod, :(global cp=1; global ce=1; global np=1; global ne=1)) # old macros, hygienic plain name -> macro-definition module (defs_mod) # flisp error: globalref means malformed public # @test run(call_mod, "Defs.@old_pub_plain()") # @test Base.ispublic(defs_mod, :op_hyg) # @test !Base.ispublic(call_mod, :op_hyg) run(call_mod, "Defs.@old_exp_plain()"); Core.@latestworld @test !Base.isexported(defs_mod, :oe_hyg) @test Base.isexported(call_mod, :oe_hyg) # old macros, escaped argument -> call-site module (call_mod) run(call_mod, "Defs.@old_pub_arg(cp)"); Core.@latestworld @test Base.ispublic(call_mod, :cp) @test !Base.ispublic(defs_mod, :cp) run(call_mod, "Defs.@old_exp_arg(ce)"); Core.@latestworld @test Base.isexported(call_mod, :ce) @test !Base.isexported(defs_mod, :ce) if is_new # new macros, hygienic name -> macro-definition module (defs_mod) run(call_mod, "Defs.@new_exp_plain()"); Core.@latestworld @test Base.isexported(defs_mod, :ne_hyg) @test !Base.isexported(call_mod, :ne_hyg) # new macros, argument -> call-site module (call_mod) run(call_mod, "Defs.@new_pub_arg(np)"); Core.@latestworld @test Base.ispublic(call_mod, :np) @test !Base.ispublic(defs_mod, :np) run(call_mod, "Defs.@new_exp_arg(ne)"); Core.@latestworld @test Base.isexported(call_mod, :ne) @test !Base.isexported(defs_mod, :ne) end end @testset "(AI) using/import module resolution from macros" for (is_new, run) in [ (false, (mod, x)->fl_eval(mod, JuliaSyntax.parsestmt(Expr, x))), (true, (mod, x)->JuliaLowering.include_string(mod, x; expr_compat_mode=true)), (true, (mod, x)->JuliaLowering.include_string(mod, x; expr_compat_mode=false)) ] # Unlike `public`/`export`, `using`/`import` are never hygienic defs_mod = Module(:Defs) call_mod = Module(:CallSite) Core.eval(call_mod, :(const Defs = $defs_mod)) Core.eval(defs_mod, :(import JuliaLowering, JuliaLowering.@legacy_quote_to_syntax)) # Distinguishable `Exporter` submodules in *both* modules. The imported # values reveal which `.Exporter` a relative path resolved to. Core.eval(defs_mod, :( module Exporter export val val = [:defs_val] other = [:defs_other] end)) Core.eval(call_mod, :( module Exporter export val val = [:call_val] other = [:call_other] end)) # A submodule reachable only from defs_mod: if a path were resolved against # the macro-definition module, `.OnlyInDefs` would be found there. Core.eval(defs_mod, :( module OnlyInDefs secret = [:secret] end)) fl_eval(defs_mod, :(macro old_use(); :(using .Exporter); end)) fl_eval(defs_mod, :(macro old_imp(); :(import .Exporter: other as o); end)) fl_eval(defs_mod, :(macro old_priv(); :(using .OnlyInDefs); end)) JuliaLowering.include_string(defs_mod, raw""" macro new_use(); @legacy_quote_to_syntax quote using .Exporter end; end macro new_imp(); @legacy_quote_to_syntax quote import .Exporter: other as o end; end macro new_priv(); @legacy_quote_to_syntax quote using .OnlyInDefs end; end """) Core.@latestworld # `using .Exporter` -> exported `val` becomes visible in call_mod, resolved # against call_mod.Exporter, not defs_mod.Exporter; defs_mod is untouched. run(call_mod, "Defs.@old_use()"); Core.@latestworld @test call_mod.val === call_mod.Exporter.val @test call_mod.val !== defs_mod.Exporter.val @test !isdefined(defs_mod, :val) # `import .Exporter: other as o` -> the (plain, non-hygienic) rename target # `o` is bound in call_mod, again resolved against call_mod.Exporter. run(call_mod, "Defs.@old_imp()"); Core.@latestworld @test call_mod.o === call_mod.Exporter.other @test call_mod.o !== defs_mod.Exporter.other @test !isdefined(defs_mod, :o) # The path is resolved in call_mod, where `.OnlyInDefs` does not exist, so # the statement errors rather than reaching defs_mod.OnlyInDefs. @test_throws UndefVarError run(call_mod, "Defs.@old_priv()") @test !isdefined(call_mod, :OnlyInDefs) if is_new run(call_mod, "Defs.@new_use()"); Core.@latestworld @test call_mod.val === call_mod.Exporter.val @test call_mod.val !== defs_mod.Exporter.val @test !isdefined(defs_mod, :val) run(call_mod, "Defs.@new_imp()"); Core.@latestworld @test call_mod.o === call_mod.Exporter.other @test call_mod.o !== defs_mod.Exporter.other @test !isdefined(defs_mod, :o) @test_throws UndefVarError run(call_mod, "Defs.@new_priv()") @test !isdefined(call_mod, :OnlyInDefs) end end @testset "(AI) all-underscore (`_`) import names" begin # Names in an import/using path are symbolic references, not value reads, so # an all-underscore name is a genuine (write-only) binding here rather than a # discard -- matching flisp, which binds `_` for `import X as _`, # `using X: a as _` and `using X: _`. Previously the desugaring assert # `kind(spec[2]) == K"Identifier"` crashed on the `K"Placeholder"` rename # target (e.g. SymbolicRegression's `using ConstructionBase: ... as _`). U = JuliaLowering.include_string(test_mod, """ module Uroot module Src export sx sx = [1] sy = [2] end module AsTarget; using ..Src: sx as _; end # rename target `_` module ImportColon; import ..Src: sy as _; end # `import X: name as _` module ImportAs; import ..Src as _; end # whole-module rename module VarSpelling; using ..Src: sx as var"_"; end # var"_" is the same name module DoubleUnder; import ..Src as __; end # `__` is also write-only end """) for m in (U.AsTarget, U.ImportColon, U.ImportAs, U.VarSpelling) @test isdefined(m, :_) end @test isdefined(U.DoubleUnder, :__) # The created `_` stays write-only: reading it is a parser-level error, not a # usable value (so `as _` really is an import-for-side-effect idiom). @test_throws JuliaLowering.LoweringError JuliaLowering.include_string(U.AsTarget, "_") # Two `as _` targets and a bare `_` source name are accepted (flisp only # warns -- conflicting import / undeclared binding); they must not error. redirect_stderr(devnull) do @test (JuliaLowering.include_string(test_mod, """ module TwoUnders; using Base: sum as _, prod as _; end """); true) @test (JuliaLowering.include_string(test_mod, """ module SrcUnder; module Inner; end; using .Inner: _; end """); true) end end