/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
JuliaLowering/test/misc.jl
966 строк
33 KB
Em Chu
[JuliaLowering] Quote local AST-like stuff captured in global lambda
17 июл 2026, 19:19
17 июл 2026, 19:19
a00b598
Код
Авторство
О чём код?
test_mod = @newmod(misc) # Blocks @test JuliaLowering.include_string(test_mod, """ begin end """) == nothing # Placeholders @test JuliaLowering.include_string(test_mod, """_ = 10""") == 10 # GC.@preserve @test JuliaLowering.include_string(test_mod, """ let x = [1,2] GC.@preserve x begin x end end """) == [1,2] @test JuliaLowering.include_string(test_mod, raw""" let x = 10 @eval $x + 2 end """) == 12 @test JuliaLowering.include_string(test_mod, raw""" module EvalTest _some_var = 1 end let x = 10 @eval EvalTest $x + _some_var end """) == 11 @test JuliaLowering.include_string(test_mod, raw""" module EvalTest2 _some_var = 2 end let x = 10 @eval EvalTest2 $x + _some_var end """; expr_compat_mode=true) == 12 @test JuliaLowering.include_string(test_mod, """ let x=11 20x end """) == 220 @testset "syntactic --> <: >:" begin @test jl_eval(test_mod, Expr(:<:, Int, Number)) @test jl_eval(test_mod, Expr(:<:, Expr(:..., Expr(:tuple, Int, Number)))) @test jl_eval(test_mod, Expr(:>:, Number, Int)) @test jl_eval(test_mod, Expr(:>:, Expr(:..., Expr(:tuple, Number, Int)))) JuliaLowering.include_string(test_mod, """ function var"-->"(args...; kws...) (args, values(kws)) end """) @test jl_eval(test_mod, Expr(:-->, 1, 2)) == ((1,2),(;)) @test jl_eval(test_mod, Expr(:-->, 1, Expr(:kw, :foo, 2))) == ((1,),(;foo=2)) @test jl_eval(test_mod, Expr(:-->, Expr(:..., (1,)))) == ((1,),(;)) end @testset "empty symbol" begin @test JuliaLowering.include_string(test_mod, """ let var\"\"=1; 1; end """) == 1 # Note function name fails in flisp @test jl_eval(test_mod, Expr(:let, Expr(:block), Expr(:block, Expr(:function, Expr(:call, Symbol(""), :x), Expr(:block, :x)), Expr(:call, Symbol(""), 1)))) == 1 # function arg @test jl_eval(test_mod, Expr(:let, Expr(:block), Expr(:block, Expr(:function, Expr(:call, :func, Symbol("")), Expr(:block, Symbol(""))), Expr(:call, :func, 2)))) == 2 # kwarg @test jl_eval(test_mod, Expr(:let, Expr(:block), Expr(:block, Expr(:function, Expr(:call, :func, Expr(:parameters, Symbol(""))), Expr(:block, Symbol(""))), Expr(:call, :func, Expr(:kw, Symbol(""), 2))))) == 2 # empty label @test jl_eval(test_mod, Expr(:symbolicblock, Symbol(""), Expr(:break, Symbol(""), 1))) == 1 # read empty local @test jl_eval(test_mod, Expr(:let, Expr(:block), Expr(:block, Expr(:local, Symbol("")), Expr(:(=), Symbol(""), 17), Symbol("")))) == 17 # read empty global @test jl_eval(test_mod, Expr(:block, Expr(:global, Symbol("")), Expr(:(=), Symbol(""), 7), Symbol(""))) == 7 # typed read of empty @test jl_eval(test_mod, Expr(:let, Expr(:block, Expr(:(=), Symbol(""), 5)), Expr(:block, Expr(:(::), Symbol(""), :Int)))) == 5 # empty in curly (read position) @test jl_eval(test_mod, Expr(:let, Expr(:block, Expr(:(=), Symbol(""), Int)), Expr(:block, Expr(:curly, :Vector, Symbol(""))))) == Vector{Int} # empty in tuple (read position) @test jl_eval(test_mod, Expr(:let, Expr(:block, Expr(:(=), Symbol(""), 99)), Expr(:block, Expr(:tuple, Symbol(""))))) == (99,) # isdefined on empty @test jl_eval(test_mod, Expr(:let, Expr(:block, Expr(:(=), Symbol(""), 1)), Expr(:block, Expr(:isdefined, Symbol(""))))) # for-loop empty iter var referenced in body @test jl_eval(test_mod, Expr(:let, Expr(:block, Expr(:(=), :s, 0)), Expr(:block, Expr(:for, Expr(:(=), Symbol(""), Expr(:tuple, 10, 20, 30)), Expr(:block, Expr(:(=), :s, Expr(:call, :+, :s, Symbol(""))))), :s))) == 60 # tuple destructure with empty lhs @test jl_eval(test_mod, Expr(:let, Expr(:block), Expr(:block, Expr(:local, Symbol("")), Expr(:local, :a), Expr(:(=), Expr(:tuple, Symbol(""), :a), Expr(:tuple, 1, 2)), Expr(:tuple, Symbol(""), :a)))) == (1, 2) # quote of empty @test jl_eval(test_mod, Expr(:quote, Symbol("")); expr_compat_mode=true) === Symbol("") end @eval test_mod libccalltest_var = "libccalltest" @testset "cglobal" begin cg = JuliaLowering.include_string(test_mod, """ cglobal(:jl_, Any) """) @test cg isa Ptr{Any} @test cg !== C_NULL cg = JuliaLowering.include_string(test_mod, """ cglobal((:global_var, libccalltest_var), Cint) """) @test cg isa Ptr{Cint} @test cg !== C_NULL @test unsafe_load(cg) == 1 # the pointer-vs-name choice is syntactic, not value-based: a runtime # variable holding a tuple takes the pointer form, which errors @eval test_mod global cglobal_tuple = (:global_var, libccalltest_var) @test_throws TypeError JuliaLowering.include_string(test_mod, """ cglobal(cglobal_tuple, Cint) """) @test_throws TypeError JuliaLowering.include_string(test_mod, """ let local_tuple = (:global_var, libccalltest_var) cglobal(local_tuple, Cint) end """) # unlike the argtypes / rettype of a ccall, cglobal(name, T) should allow # rettype T to be any runtime expression cg = JuliaLowering.include_string(test_mod, """ function cglobal_runtime_type(T) cglobal((:global_var, libccalltest_var), T) end cglobal_runtime_type(Cint) """) @test cg isa Ptr{Cint} @test unsafe_load(cg) == 1 # invalid foreignsymbol (tuple) forms should error for cglobal @test_throws ErrorException JuliaLowering.include_string(test_mod, "cglobal((:a, :b, :c))") @test_throws ErrorException JuliaLowering.include_string(test_mod, "cglobal(())") @test_throws TypeError JuliaLowering.include_string(test_mod, "cglobal((1,))") # cglobal(name) with a non-static name errors, just like ccall @test_throws TypeError JuliaLowering.include_string(test_mod, """ function cglobal_non_static1() sym = (:global_var, libccalltest_var) cglobal(sym) end cglobal_non_static1() """) @eval test_mod global the_sym = (:global_var, libccalltest_var) @test_throws TypeError JuliaLowering.include_string(test_mod, """ cglobal_non_static2() = cglobal(the_sym) cglobal_non_static2() """) end # ccall @test JuliaLowering.include_string(test_mod, """ ccall(:strlen, Csize_t, (Cstring,), "asdfg") """) == 5 @test JuliaLowering.include_string(test_mod, """ function cvarargs_0() strp = Ref{Ptr{Cchar}}(0) fmt = "hi" len = ccall(:asprintf, Cint, (Ptr{Ptr{Cchar}}, Cstring, Cfloat...), strp, fmt) str = unsafe_string(strp[], len) Libc.free(strp[]) return str end """) isa Function @test test_mod.cvarargs_0() == "hi" @test JuliaLowering.include_string(test_mod, """ function cvarargs_2(arg1::Float64, arg2::Float64) strp = Ref{Ptr{Cchar}}(0) fmt = "%3.1f %3.1f" len = ccall(:asprintf, Cint, (Ptr{Ptr{Cchar}}, Cstring, Cfloat...), strp, fmt, arg1, arg2) str = unsafe_string(strp[], len) Libc.free(strp[]) return str end """) isa Function @test test_mod.cvarargs_2(1.1, 2.2) == "1.1 2.2" # (function, library) syntax @test JuliaLowering.include_string(test_mod, """ ccall((:ctest, :libccalltest), Complex{Int}, (Complex{Int},), 10 + 20im) """) === 11 + 18im # (function, library): library is a global @test JuliaLowering.include_string(test_mod, """ ccall((:ctest, libccalltest_var), Complex{Int}, (Complex{Int},), 10 + 20im) """) === 11 + 18im @testset "(robot-generated) ccall (sym, lib) tuple: globals and hygiene" for expr_compat_mode in [true, false] # library is a module-qualified global JuliaLowering.include_string(test_mod, """ module CCallLibMod const the_lib = "libccalltest" end """; expr_compat_mode) Core.@latestworld @test JuliaLowering.include_string(test_mod, """ ccall((:ctest, CCallLibMod.the_lib), Complex{Int}, (Complex{Int},), 10 + 20im) """) === 11 + 18im # macro in a nested module produces ccall with lib from that module (hygiene) JuliaLowering.include_string(test_mod, raw""" module CCallHygieneMod const mylib = "libccalltest" import ..JuliaLowering.@legacy_quote_to_syntax macro do_ccall() @legacy_quote_to_syntax( :(ccall((:ctest, mylib), Complex{Int}, (Complex{Int},), 10 + 20im))) end end """; expr_compat_mode) Core.@latestworld @test JuliaLowering.include_string(test_mod, """ CCallHygieneMod.@do_ccall() """) === 11 + 18im # hygiene: `mylib` in the macro body should resolve in CCallHygieneMod, not # the caller, even when the caller defines a different `mylib` @test JuliaLowering.include_string(test_mod, """ mylib = "this_lib_does_not_exist" CCallHygieneMod.@do_ccall() """; expr_compat_mode) === 11 + 18im # macro that interpolates the lib value at expansion time JuliaLowering.include_string(test_mod, raw""" module CCallHygieneMod2 import ..JuliaLowering.@legacy_quote_to_syntax const mylib2 = "libccalltest" macro do_ccall_interp() lib = mylib2 @legacy_quote_to_syntax( :(ccall((:ctest, $lib), Complex{Int}, (Complex{Int},), 10 + 20im))) end end """; expr_compat_mode) Core.@latestworld @test JuliaLowering.include_string(test_mod, """ CCallHygieneMod2.@do_ccall_interp() """; expr_compat_mode) === 11 + 18im # ccall with plain symbol name still works inside a function @test JuliaLowering.include_string(test_mod, """ function ccall_plain_sym() ccall(:strlen, Csize_t, (Cstring,), "abc") end """; expr_compat_mode) isa Function Core.@latestworld @test test_mod.ccall_plain_sym() == 3 # ccall with (sym, lib) tuple where lib is a global, inside a function @test JuliaLowering.include_string(test_mod, """ function ccall_global_lib() ccall((:ctest, libccalltest_var), Complex{Int}, (Complex{Int},), 10 + 20im) end """; expr_compat_mode) isa Function Core.@latestworld @test test_mod.ccall_global_lib() === 11 + 18im # ccall with module-qualified lib inside a function @test JuliaLowering.include_string(test_mod, """ function ccall_qualified_lib() ccall((:ctest, CCallLibMod.the_lib), Complex{Int}, (Complex{Int},), 10 + 20im) end """; expr_compat_mode) isa Function Core.@latestworld @test test_mod.ccall_qualified_lib() === 11 + 18im end # cfunction JuliaLowering.include_string(test_mod, """ function f_ccallable(x, y) x + y * 10 end """) cf_int = JuliaLowering.include_string(test_mod, """ @cfunction(f_ccallable, Int, (Int,Int)) """) @test @ccall($cf_int(2::Int, 3::Int)::Int) == 32 cf_float = JuliaLowering.include_string(test_mod, """ @cfunction(f_ccallable, Float64, (Float64,Float64)) """) @test @ccall($cf_float(2::Float64, 3::Float64)::Float64) == 32.0 # Test that hygiene works with @ccallable function names JuliaLowering.include_string(test_mod, raw""" f_ccallable_hygiene() = 1 module Nested import ..JuliaLowering.@legacy_quote_to_syntax f_ccallable_hygiene() = 2 macro cfunction_hygiene() @legacy_quote_to_syntax :(@cfunction($f_ccallable_hygiene, Int, ())) end end """) cf_hygiene = JuliaLowering.include_string(test_mod, """ Nested.@cfunction_hygiene """) @test @ccall($cf_hygiene()::Int) == 2 # Same as above, but non-interpolated symbol. Arguably this could return 20, # but if it should, this is a bug in the macro implementation, not lowering. # Match Base for now. JuliaLowering.include_string(test_mod, raw""" f_ccallable_hygiene() = 10 module Nested import ..JuliaLowering.@legacy_quote_to_syntax f_ccallable_hygiene() = 20 macro cfunction_hygiene() @legacy_quote_to_syntax :(@cfunction(f_ccallable_hygiene, Int, ())) end end """) cf_hygiene = JuliaLowering.include_string(test_mod, """ Nested.@cfunction_hygiene """) @test @ccall($cf_hygiene()::Int) == 10 @test JuliaLowering.include_string(test_mod, """ cfunction_ignores_locals() = Int32(1) function getptr() cfunction_ignores_locals = @cfunction(cfunction_ignores_locals, Int32, ()) cfunction_ignores_locals end getptr() isa Ptr{Cvoid} """) # quoted function in cfunction quoted_cfn_anon = JuliaLowering.include_string(test_mod, raw""" @cfunction((function(x); x; end), Int, (Int,)) """) @test ccall(quoted_cfn_anon, Int, (Int,), 1) == 1 quoted_cfn_named = JuliaLowering.include_string(test_mod, raw""" @cfunction((function fname_unused(x); x; end), Int, (Int,)) """) @test ccall(quoted_cfn_named, Int, (Int,), 1) == 1 # flisp-expanded ccall, cfunction should be lowerable by us let fl_ex = macroexpand( test_mod, :(cfun_flisp_thunk() = @cfunction(+, Cint, (Cint, Cint)))) fl_st = JuliaLowering.expr_to_est(fl_ex) fl_fn = JuliaLowering.eval(test_mod, fl_st) fl_cfn = @invokelatest fl_fn() @test fl_fn isa Function @test fl_cfn isa Ptr @test ccall(fl_cfn, Int, (Int,Int), 1, 2) == 3 end let fl_ex = macroexpand( test_mod, :(cfun_flisp_thunk() = @cfunction(function some_cfunc_add(x,y); x+y; end, Cint, (Cint, Cint)))) fl_st = JuliaLowering.expr_to_est(fl_ex) fl_fn = JuliaLowering.eval(test_mod, fl_st) fl_cfn = @invokelatest fl_fn() @test fl_fn isa Function @test fl_cfn isa Ptr @test ccall(fl_cfn, Int, (Int,Int), 1, 2) == 3 end let fl_ex = macroexpand( test_mod, :(@ccall(libccalltest_var.ctest((10+20im)::Complex{Int})::Complex{Int}))) fl_st = JuliaLowering.expr_to_est(fl_ex) @test JuliaLowering.eval(test_mod, fl_st) == 11 + 18im end # raw :foreigncall should also be lowerable let raw_foreigncall_ex = Expr( :foreigncall, Expr( :tuple, QuoteNode(:ctest), "libccalltest"), :(Complex{Int}), :(Core.svec(Complex{Int})), 0, QuoteNode((:ccall, 0x0000, false)), 10+20im, Complex{Int}) # test flisp does this: it's unclear how much desugaring the user is # responsible for here @test fl_eval(test_mod, raw_foreigncall_ex) == 11 + 18im @test JuliaLowering.eval(test_mod, JuliaLowering.expr_to_est(raw_foreigncall_ex)) == 11 + 18im end # Test that ccall can be passed static parameters in type signatures. # # Note that the cases where this works are extremely limited and tend to look # like `Ptr{T}` or `Ref{T}` (`T` doesn't work!?) because of the compilation # order in which the runtime inspects the arguments to ccall (`Ptr{T}` has a # well defined C ABI even when `T` is not yet determined). See also # https://github.com/JuliaLang/julia/issues/29400 # https://github.com/JuliaLang/julia/pull/40947 JuliaLowering.include_string(test_mod, raw""" function sparam_ccallable(x::Ptr{T}) where {T} unsafe_store!(x, one(T)) nothing end function ccall_with_sparams(::Type{T}) where {T} x = T[zero(T)] cf = @cfunction(sparam_ccallable, Cvoid, (Ptr{T},)) @ccall $cf(x::Ptr{T})::Cvoid x[1] end """) @test test_mod.ccall_with_sparams(Int) === 1 @test test_mod.ccall_with_sparams(Float64) === 1.0 # (AI) has_fcall: sparam-dependent @cfunction must not inline into a caller # knowing T only abstractly. The abstract field keeps T non-constant there. @test JuliaLowering.include_string(test_mod, """ cfunc_has_fcall(buf::Ptr{UInt8}, len::UInt32) = Int32(0) get_cf(::Type{T}) where {T} = @cfunction cfunc_has_fcall Int32 (Ref{T}, Ptr{UInt8}, UInt32) do_cf(stream::T) where {T <: IO} = get_cf(T) mutable struct Box3 x::IO end call_it(b::Box3) = do_cf(b.x) call_it(Box3(IOBuffer())) """) isa Ptr{Cvoid} # Test that ccall can be passed static parameters in the function name # Note that this only works with `@generated` functions from 1.13 onwards, # where the function name can be evaluated at code generation time. JuliaLowering.include_string(test_mod, raw""" # In principle, may add other strlen-like functions here for different string # types ccallable_sptest_name(::Type{String}) = :strlen @generated function ccall_with_sparams_in_name(s::T) where {T} name = QuoteNode(ccallable_sptest_name(T)) @legacy_quote_to_syntax :(ccall($name, Csize_t, (Cstring,), s)) end """) @test test_mod.ccall_with_sparams_in_name("hii") == 3 # Where local variables may appear in ccall / cfunction type and name positions @testset "(AI) local variables in ccall/cfunction type/name positions" begin # fresh module per case: many of these define global methods cc(code) = JuliaLowering.include_string(Module(:cc_locals), code) # ---- ALLOWED: a top-level local interpolated into a global method ---- # return type @test cc(""" begin local t = Csize_t g() = ccall(:strlen, t, (Cstring,), "asdfg") g() end""") == 5 # argument type @test cc(""" begin local a = Cstring g(s) = ccall(:strlen, Csize_t, (a,), s) g("asdfg") end""") == 5 # library (2nd element of the name tuple): a constant string is fine @test cc(""" begin local lib = "libccalltest" g() = ccall((:ctest, lib), Complex{Int}, (Complex{Int},), 10+20im) g() end""") == 11 + 18im # still a global method (its capture still top-level) when nested in a `let` @test cc(""" begin local t = Csize_t let global g g() = ccall(:strlen, t, (Cstring,), "asdfg") end g() end""") == 5 # cfunction mirrors ccall for the return and argument types @test cc("""fcb(x) = x + 1; @cfunction(fcb, Int, (Int,)) isa Ptr""") @test cc(""" begin local RT = Int fcb(x) = x + 1 g() = @cfunction(fcb, RT, (Int,)) g() isa Ptr end""") @test cc(""" begin local AT = Int fcb(x) = x + 1 g() = @cfunction(fcb, Int, (AT,)) g() isa Ptr end""") # ---- REJECTED (`static_eval_disallowed_binding`) ---- @test_broken false == """ Note these should ideally all be LoweringError, but that's just so the error prints nicely. flisp doesn't do much validation, and leaves it to the ccall machinery to reject everything it can't handle. """ # A same-frame local (declared in the method that runs the ccall) is a slot. @test_throws LoweringError cc(""" function g() local rt = Csize_t ccall(:strlen, rt, (Cstring,), "asdfg") end g()""") # A genuine (local-name / escaping) closure captures the type as a *field*. @test_throws ErrorException cc(""" let t = Csize_t g() = ccall(:strlen, t, (Cstring,), "asdfg") g() end""") @test_throws ErrorException cc(""" let a = Cstring g(s) = ccall(:strlen, Csize_t, (a,), s) g("asdfg") end""") # A *reassigned* (boxed) top-level local can't be baked in as a constant. @test_throws LoweringError cc(""" begin local t = Csize_t t = Csize_t g() = ccall(:strlen, t, (Cstring,), "asdfg") g() end""") # cfunction rejects the same shapes. @test_throws LoweringError cc(""" function g() fcb(x) = x + 1 local RT = Int @cfunction(fcb, RT, (Int,)) end g()""") @test_throws UndefVarError cc(""" let RT = Int fcb(x) = x + 1 g() = @cfunction(fcb, RT, (Int,)) g() end""") # ...and so does the library position. @test_throws LoweringError cc(""" function g() local lib = "libccalltest" ccall((:ctest, lib), Complex{Int}, (Complex{Int},), 10+20im) end g()""") @test_throws TypeError cc(""" let lib = "libccalltest" g() = ccall((:ctest, lib), Complex{Int}, (Complex{Int},), 10+20im) g() end""") # A top-level ccall (no enclosing method) referencing a same-frame local: the # static_eval can't be evaluated with no locals available. @test_throws LoweringError cc(""" let rt = Csize_t ccall(:strlen, rt, (Cstring,), "asdfg") end""") # The function *name* must be a literal symbol/string; a local holding a # Symbol is only rejected later, at codegen. @test_throws TypeError cc(""" function g() local nm = :strlen ccall(nm, Csize_t, (Cstring,), "asdfg") end g()""") # A top-level local *library* likewise slips past lowering and fails later. @test_throws LoweringError cc(""" let lib = "libccalltest" ccall((:ctest, lib), Complex{Int}, (Complex{Int},), 10+20im) end""") @test_throws TypeError cc(""" begin local nm = :strlen g() = ccall(nm, Csize_t, (Cstring,), "asdfg") g() end""") end @testset "CodeInfo: has_image_globalref" begin @test lower_str(test_mod, "x + y").args[1].has_image_globalref === false @test lower_str(Main, "x + y").args[1].has_image_globalref === true end baremodule baremod macro int128_str(x); error("baremod macro; expected call to Core macro"); end macro uint128_str(x); error("baremod macro; expected call to Core macro"); end macro big_str(x); error("baremod macro; expected call to Core macro"); end macro cmd(x); error("baremod macro; expected call to Core macro"); end macro doc(x, y); error("baremod macro; expected call to Core macro"); end global nothing = "baremod.nothing; expected core nothing" end @testset "globalrefs inserted by parsing" begin local jl_s_eval = x->JuliaLowering.include_string(baremod, x; expr_compat_mode=true) local fl_s_eval = x->fl_eval(baremod, JuliaSyntax.parsestmt(Expr, x; filename="file")) let s = "100000000000000000000000000000" @test jl_s_eval(s) == fl_s_eval(s) end let s = "0x100000000000000000000000000000" @test jl_s_eval(s) == fl_s_eval(s) end let s = "10000000000000000000000000000000000000000000000000000000000000000" @test jl_s_eval(s) == fl_s_eval(s) end let s = "`ls`" @test jl_s_eval(s) == fl_s_eval(s) end let s = """ "foo" function fl_documented_function(); fl_documented_function; end """ @test fl_s_eval(s) isa Function end @test baremod.fl_documented_function() == baremod.fl_documented_function let s = """ "foo" function jl_documented_function(); jl_documented_function; end """ @test jl_s_eval(s) isa Function end @test baremod.jl_documented_function() == baremod.jl_documented_function let s = """ function fl_ret_nothing(); return; end """ @test fl_s_eval(s) isa Function end @test baremod.fl_ret_nothing() == Core.nothing let s = """ function jl_ret_nothing(); return; end """ @test fl_s_eval(s) isa Function end @test baremod.jl_ret_nothing() == Core.nothing end @testset "docstrings: doc-only expressions" begin local jeval(mod, str) = JuliaLowering.include_string(mod, str; expr_compat_mode=true) jeval(test_mod, "function fun_exists(x); x; end") jeval(test_mod, "module M end; module M2 end") # TODO: return values are to be determined, currently Base.Docs.Binding for # both lowering implementations. We can't return the value of the # expression in these special cases. jeval(test_mod, "\"docstr1\" sym_noexist") jeval(test_mod, "\"docstr2\" fun_noexist()") jeval(test_mod, "\"docstr3\" fun_exists(sym_noexist)") jeval(test_mod, "\"docstr4\" M.sym_noexist") jeval(test_mod, "\"docstr5\" M.fun_noexist()") jeval(test_mod, "\"docstr6\" M.fun_exists(sym_noexist)") @test jeval(test_mod, "@doc sym_noexist") |> string === "docstr1\n" @test jeval(test_mod, "@doc fun_noexist()") |> string === "docstr2\n" @test jeval(test_mod, "@doc fun_exists(sym_noexist)") |> string === "docstr3\n" @test jeval(test_mod, "@doc M.sym_noexist") |> string === "docstr4\n" @test jeval(test_mod, "@doc M.fun_noexist()") |> string === "docstr5\n" @test jeval(test_mod, "@doc M.fun_exists(sym_noexist)") |> string === "docstr6\n" @test jeval(test_mod.M, "@doc M.sym_noexist") |> string === "docstr4\n" @test jeval(test_mod.M, "@doc M.fun_noexist()") |> string === "docstr5\n" @test jeval(test_mod.M, "@doc M.fun_exists(sym_noexist)") |> string === "docstr6\n" jeval(test_mod.M2, "\"docstr7\" M2.M2.sym_noexist") jeval(test_mod.M2, "\"docstr8\" M2.M2.fun_noexist()") jeval(test_mod.M2, "\"docstr9\" M2.M2.fun_exists(sym_noexist)") @test jeval(test_mod, "@doc M2.M2.sym_noexist") |> string === "docstr7\n" @test jeval(test_mod, "@doc M2.M2.fun_noexist()") |> string === "docstr8\n" @test jeval(test_mod, "@doc M2.M2.fun_exists(sym_noexist)") |> string === "docstr9\n" @test jeval(test_mod.M2, "@doc M2.M2.sym_noexist") |> string === "docstr7\n" @test jeval(test_mod.M2, "@doc M2.M2.fun_noexist()") |> string === "docstr8\n" @test jeval(test_mod.M2, "@doc M2.M2.fun_exists(sym_noexist)") |> string === "docstr9\n" # Try with signatures and type variables jeval(test_mod, "abstract type T_exists end") jeval(test_mod, "\"docstr10\" f10(x::Int, y, z::T_exists)") d = jeval(test_mod, "@doc f10") @test d |> string === "docstr10\n" jeval(test_mod, "\"docstr11\" f11(x::T_exists, y::U, z::T) where {T, U<:Number}") d = jeval(test_mod, "@doc f11") @test d |> string === "docstr11\n" jeval(test_mod, "\"docstr12\" f12(x::Int, y::U, z::T=1) where {T, U<:Number}") d = jeval(test_mod, "@doc f12") @test d |> string === "docstr12\n" # doc-strings on macrocalls (punned on quoted macrocall) # TODO: implement and test `doc!` support for this @test_broken jeval(test_mod, """ "doc string" :@test """) isa Expr end # SyntaxTree @eval should pass along expr_compat_mode @test JuliaLowering.include_string(test_mod, raw""" let T = gensym("documented_sym_no_logspam") @eval @doc $"This is a $T" $T = 1 end """; expr_compat_mode=true) === 1 @test JuliaLowering.include_string(test_mod, raw""" let T = gensym("documented_sym_no_logspam") @eval @doc $"This is a $T" $T = 1 end """; expr_compat_mode=false) === 1 @testset "tryfinally with scopedvalues" begin @eval test_mod scopedval = Base.ScopedValues.ScopedValue(1) @eval test_mod val_history = [] ex = Expr(:tryfinally, :(push!(val_history, scopedval[])), :(push!(val_history, scopedval[])), :(Base.ScopedValues.Scope(Core.current_scope(), $test_mod.scopedval => 2))) JuliaLowering.eval(test_mod, JuliaLowering.expr_to_est(ex); expr_compat_mode=true) # try block uses "inner" dynamic scope, finally does not @test test_mod.val_history == [2, 1] JuliaLowering.eval(test_mod, JuliaLowering.expr_to_est(ex)) @test test_mod.val_history == [2, 1, 2, 1] end # JuliaLowering/issues/144 emptyblock_result = JuliaLowering.eval(test_mod, Expr(:(=), :emptyblock_144, Expr(:block))) @test emptyblock_result == nothing @testset "string forms" begin @test JuliaLowering.include_string(test_mod, raw""" "str" """) == "str" @test JuliaLowering.include_string(test_mod, raw""" let x = 1 "str$x" end """) == "str1" @test JuliaLowering.include_string(test_mod, raw""" let x = 1 "str$(x)" end """) == "str1" @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] "str$(x...)" end """) == "str123" @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] "str$(x)" end """) == "str[1, 2, 3]" @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] "str$("innerstr$(x...)")" end """) == "strinnerstr123" @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] "str$(["innerstr$(x...)"]...)" end """) == "strinnerstr123" # cmds @test JuliaLowering.include_string(test_mod, raw""" `cmdstr` """) == `cmdstr` @test JuliaLowering.include_string(test_mod, raw""" let x = 1 `cmdstr$x` end """) == `cmdstr1` @test JuliaLowering.include_string(test_mod, raw""" let x = 1 `cmdstr$(x)` end """) == `cmdstr1` @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] `cmdstr$(x...)` end """) == `cmdstr123` @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] `cmdstr$(x)` end """) == `cmdstr1 cmdstr2 cmdstr3` @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] `cmdstr$("innerstr$(x...)")` end """) == `cmdstrinnerstr123` @test JuliaLowering.include_string(test_mod, raw""" let x = [1,2,3] `cmdstr$(["innerstr$(x...)"]...)` end """) == `cmdstrinnerstr123` end let op_mod = Module(:opmod, false) @testset "operators" for run in [ s->fl_eval(op_mod, JuliaSyntax.parseall(Expr, s)), s->JuliaLowering.include_string(op_mod, s; expr_compat_mode=true), s->JuliaLowering.include_string(op_mod, s; expr_compat_mode=false)] @testset "unary prefix (no parens needed)" for op in String["⋆", "±", "∓", "~", "!", "¬", "√", "∛", "∜"] @test run("$(op)x = (x,)") isa Function Core.@latestworld @test run(op) isa Function @test run("$(op)1") == (1,) @test run(""" let $(op)x = (x,x) $(op)1 end """) == (1,1) end @testset "prefix" for op in String["..", "+", "-", "⋆", "±", "∓", "~", "!", "¬", "√", "∛", "∜"] @test run("$op(a,b,c) = 3") isa Function Core.@latestworld @test run(op) isa Function @test run("$op(1,2,3)") == 3 @test run(""" let $op(a,b,c) = (c,b,a) $op(1,2,3) end """) == (3,2,1) end @testset "infix" for op in String["&", "|", "+", "-", ":", ".."] @test run("a$(op)b = (a,b)") isa Function Core.@latestworld @test run(op) isa Function @test run("var\"$op\"(1,2)") == (1,2) @test run("1$(op)2") == (1,2) @test run(""" let a$(op)b = (b,a) 1$(op)2 end """) == (2,1) end end end @testset "jl_assert" begin st = @ast_ [K"function" "foo"::K"Identifier"] if JL.DEBUG err = try JuliaLowering.@jl_assert(1 == 2, (st, "error message 1"), (st, "error message 2")) nothing catch err err end @test err isa LoweringError @test err.internal === true @test length(err.sts) == 2 @test length(err.msgs) == 2 shown = sprint(show, err) @test contains(shown, "error message 1") @test contains(shown, "error message 2") err = try new_st_name = st JuliaLowering.@jl_assert(1 == 2, (st, "error message 1"), new_st_name, (st, "error message 2")) nothing catch err err end @test err isa LoweringError @test err.internal === true shown = sprint(show, err) @test contains(shown, "new_st_name") else @test nothing !== try JuliaLowering.@jl_assert false st catch err err else nothing end end end @testset "(static_parameter n) form as lowering input" begin # function (::Type{T},) where T; return (sp 1); end ex = Expr(:function, Expr(:where, Expr(:tuple, Expr(:(::), Expr(:curly, :Type, :T))), :T), Expr(:block, Expr(:return, Expr(:static_parameter, 1)))) local f @test (f = fl_eval(test_mod, ex)) isa Function @test f(String) == String @test (f = jl_eval(test_mod, ex; expr_compat_mode=true)) isa Function @test f(String) == String @test (f = jl_eval(test_mod, ex; expr_compat_mode=false)) isa Function @test f(String) == String # function (x::T, y::U) where {T, U}; (x, y, (sp 1), (sp 2)); end ex = Expr(:function, Expr(:where, Expr(:tuple, Expr(:(::), :x, :T), Expr(:(::), :y, :U)), :T, :U), Expr(:block, Expr(:return, Expr(:tuple, :x, :y, Expr(:static_parameter, 1), Expr(:static_parameter, 2))))) @test (f = fl_eval(test_mod, ex)) isa Function @test f(1, 'a') == (1, 'a', Int, Char) @test (f = jl_eval(test_mod, ex; expr_compat_mode=true)) isa Function @test f(1, 'a') == (1, 'a', Int, Char) @test (f = jl_eval(test_mod, ex; expr_compat_mode=false)) isa Function @test f(1, 'a') == (1, 'a', Int, Char) end