/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
JuliaLowering/test/functions.jl
2 339 строк
84 KB
Em Chu
[JuliaLowering] Bugfix batch (#62558)
29 июл 2026, 23:45
Не верифицирован
29 июл 2026, 23:45
0e259a7
Код
Авторство
О чём код?
test_mod = Module() # Function calls # Splatting @test JuliaLowering.include_string(test_mod, """ let x = 1 y = 2 zs = (3,4) w = 5 (tuple(zs...), tuple(zs..., w), tuple(y, zs...), tuple(x, y, zs..., w)) end """) == ((3,4), (3,4,5), (2,3,4), (1,2,3,4,5)) # Nested splatting @test JuliaLowering.include_string(test_mod, """ let xs = [[1, 2], [3, 4]] tuple((xs...)...) end """) == (1, 2, 3, 4) @test JuliaLowering.include_string(test_mod, """ let xs = [[1, 2]] ys = [[3, 4]] tuple((xs...)..., (ys...)...) end """) == (1, 2, 3, 4) # Multiple (>2) nested splat @test JuliaLowering.include_string(test_mod, """ let xs = [[[1, 2]]] tuple(((xs...)...)...) end """) == (1, 2) @test JuliaLowering.include_string(test_mod, """ let xs = [[[1, 2]]] ys = [[[3, 4]]] tuple(((xs...)...)..., ((ys...)...)...) end """) == (1, 2, 3, 4) @test JuliaLowering.include_string(test_mod, """ let xs = [[[1, 2]]] ys = [[[3, 4]]] tuple(((xs...)...)..., ((ys...)...)) end """) == (1, 2, [3, 4]) # Trailing comma case should still work (different semantics) @test JuliaLowering.include_string(test_mod, """ let xs = [[1, 2], [3, 4]] tuple((xs...,)...) end """) == ([1, 2], [3, 4]) # Keyword calls Base.eval(test_mod, :( begin function kwtest(; kws...) values(kws) end # Note this definition generates an arguably-spurious warning when run via # `Pkg.test()` due to the use of `--warn-override=true` in the test # harness. function kwtest() "non-kw version of kwtest" end end )) @test JuliaLowering.include_string(test_mod, """ let kws = (c=3,d=4) kwtest(; kws..., a=1, d=0, e=5) end """) == (c=3, d=0, a=1, e=5) @test JuliaLowering.include_string(test_mod, """ let kws = (;) kwtest(; kws..., kws...) end """) == "non-kw version of kwtest" # literal_pow @test JuliaLowering.include_string(test_mod, """ 2^4 """) == 16 #------------------------------------------------------------------------------- # Arrow syntax @test JuliaLowering.include_string(test_mod, """ let f = ((x::T, y::T) where T) -> x + y f(1, 2) end """) === 3 @test JuliaLowering.include_string(test_mod, """ let f = ((x::T; y=2) where T) -> x + y f(1) end """) === 3 # Passes desugaring, but T is detected as unused and throws an error. # Is it clear whether this should be `f(x::T) where T` or `f(x::T where T)`? @test JuliaLowering.include_string(test_mod, """ let f = ((x::T) where T) -> x f(1) end """) === 1 #------------------------------------------------------------------------------- # Function definitions @test JuliaLowering.include_string(test_mod, """ begin function f(x) y = x + 1 "hi", x, y end f(1) end """) == ("hi", 1, 2) @test JuliaLowering.include_string(test_mod, """ begin function unused_arg(x, _, y) x + y end unused_arg(1,2,3) end """) == 4 @test JuliaLowering.include_string(test_mod, """ begin function g(x)::Int if x == 1 return 42.0 end 0xff end (g(1), g(2)) end """) === (42, 255) # static parameters @test JuliaLowering.include_string(test_mod, """ begin function h(x, y) "fallback" end function h(::Vector{T}, ::S) where {T, S <: T} T, S end (h(1, 2), h(Number[0xff], 1.0), h(Int[1], 1), h(Int[1], 1.0)) end """) === ("fallback", (Number, Float64), (Int, Int), "fallback") # Static parameter may be undefined @test JuliaLowering.include_string(test_mod, """ begin func_undef_static_param(x::Union{T,Nothing}) where T = @isdefined(T) (func_undef_static_param(nothing), func_undef_static_param(42)) end """) === (false, true) Base.eval(test_mod, :(struct X1{T} end) ) # `where` params used in function obj type @test JuliaLowering.include_string(test_mod, """ begin function (x::X1{T})() where T T end X1{Int}()() end """) === Int # or anywhere @test JuliaLowering.include_string(test_mod, """ let f = function foo(y::X1{T})::X1{T} where T y end f(X1{Int}()) end """) == test_mod.X1{Int}() @test JuliaLowering.include_string(test_mod, """ let f = function foo(y::X1{<:T})::X1{<:T} where T y end f(X1{Int}()) end """) == test_mod.X1{Int}() Base.include_string(test_mod, """ struct X end # Erroneous `convert` to test type assert in function return values Base.convert(::Type{X}, y) = y """) @test_throws TypeError JuliaLowering.include_string(test_mod, """ begin function h()::X return nothing end h() end """) @test JuliaLowering.include_string(test_mod, """ x = 0 function f_return_in_value_pos() global x x = return 42 end (f_return_in_value_pos(), x) """) === (42, 0) @test JuliaLowering.include_string(test_mod, """ function f_return_in_call() f_return_in_call(return 123) end f_return_in_call() """) === 123 @test JuliaLowering.include_string(test_mod, raw""" function f_return_in_interpolation() :(1 + $(return 123)) end f_return_in_interpolation() """) === 123 @test JuliaLowering.include_string(test_mod, raw""" function f_return_in_pparam_default(x, y=(return x), z=10) (x, y, z) end (f_return_in_pparam_default(1), f_return_in_pparam_default(1,2), f_return_in_pparam_default(1,2,3)) """) === (1, (1,2,10), (1,2,3)) @test JuliaLowering.include_string(test_mod, raw""" function f_return_in_pparam_default2(x, y=(return x), z=(return y); kw=1) (x, y, z, kw) end (f_return_in_pparam_default2(1), f_return_in_pparam_default2(1,2), f_return_in_pparam_default2(1,2,3), f_return_in_pparam_default2(1;kw=0), f_return_in_pparam_default2(1,2;kw=0), f_return_in_pparam_default2(1,2,3;kw=0)) """) === (1, 2, (1,2,3,1), 1, 2, (1,2,3,0)) @testset "Optional positional arguments" begin @test JuliaLowering.include_string(test_mod, """ begin function f_def_simple(x=1, y=2, z=x) (x,y,z) end (f_def_simple(), f_def_simple(10), f_def_simple(10,20), f_def_simple(10,20,30)) end """) == ((1,2,1), (10,2,10), (10,20,10), (10,20,30)) # anon forms @test JL.include_string(test_mod, "((x=1,y=2,z=3,va...)->(x,y,z,va))()") == (1,2,3,()) @test JL.include_string(test_mod, "((x=1,y=2,z=3,va...)->(x,y,z,va))(0)") == (0,2,3,()) @test JL.include_string(test_mod, "((x=1,y=2,z=3,va...)->(x,y,z,va))(0,0)") == (0,0,3,()) @test JL.include_string(test_mod, "((x=1,y=2,z=3,va...)->(x,y,z,va))(0,0,0)") == (0,0,0,()) @test JL.include_string(test_mod, "((x=1,y=2,z=3,va...)->(x,y,z,va))(0,0,0,0)") == (0,0,0,(0,)) @test JL.include_string(test_mod, "(function (x=1,y=2,z=3,va...); (x,y,z,va); end)()") == (1,2,3,()) @test JL.include_string(test_mod, "(function (x=1,y=2,z=3,va...); (x,y,z,va); end)(0)") == (0,2,3,()) @test JL.include_string(test_mod, "(function (x=1,y=2,z=3,va...); (x,y,z,va); end)(0,0)") == (0,0,3,()) @test JL.include_string(test_mod, "(function (x=1,y=2,z=3,va...); (x,y,z,va); end)(0,0,0)") == (0,0,0,()) @test JL.include_string(test_mod, "(function (x=1,y=2,z=3,va...); (x,y,z,va); end)(0,0,0,0)") == (0,0,0,(0,)) # defaults containing previous args @test JL.include_string(test_mod, "((x=1,y=x,z=x,va...=x)->(x,y,z,va))()") == (1,1,1,(1,)) @test JL.include_string(test_mod, "((x=1,y=x,z=x,va...=x)->(x,y,z,va))(2)") == (2,2,2,(2,)) @test JL.include_string(test_mod, "((x=1,y=x,z=y+x,va...=z+y+x)->(x,y,z,va))()") == (1,1,2,(4,)) @test JL.include_string(test_mod, "((x=1,y=x,z=y+x,va...=z+y+x)->(x,y,z,va))(2)") == (2,2,4,(8,)) # defaults shadowed by later args (resolution should not pick the arg) @test JL.include_string(test_mod, "let x = 1; ((x=x)->(x,))(); end") == (1,) @test JL.include_string(test_mod, "let x = 1; ((x=x)->(x,))(0); end") == (0,) @test JL.include_string(test_mod, "let x = 1; ((x...=x)->(x,))(); end") == ((1,),) @test JL.include_string(test_mod, "let x = 1; ((x...=x)->(x,))(0); end") == ((0,),) @test JL.include_string(test_mod, "let y = 2; ((x=y,y=y)->(x,y))(); end") == (2,2,) @test JL.include_string(test_mod, "let y = 2; ((x=y,y=y)->(x,y))(0); end") == (0,2,) @test JL.include_string(test_mod, "let y = 2; ((x=y,y=y)->(x,y))(0,0); end") == (0,0,) @test JL.include_string(test_mod, "let y = 2; ((x=y,y...=y)->(x,y))(); end") == (2,(2,),) @test JL.include_string(test_mod, "let y = 2; ((x=y,y...=y)->(x,y))(0); end") == (0,(2,),) @test JL.include_string(test_mod, "let y = 2; ((x=y,y...=y)->(x,y))(0,0); end") == (0,(0,),) @test JL.include_string(test_mod, "let y = 2; ((x=y,y=y,z=y)->(x,y,z))(); end") == (2,2,2) @test JL.include_string(test_mod, "let y = 2; ((x=y,y=y,z=y)->(x,y,z))(0); end") == (0,2,2) @test JL.include_string(test_mod, "let y = 2; ((x=y,y=y,z=y)->(x,y,z))(0,0); end") == (0,0,0) # defaults containing previous sparams @test JL.include_string(test_mod, "(((x::T=1,y=T) where T)->(x,y,T))()") == (1, Int, Int) @test JL.include_string(test_mod, "(((x::T=1,y=T) where T)->(x,y,T))(true)") == (true, Bool, Bool) @test JL.include_string(test_mod, "(((x::Type{T}=Vector{Int},y=T) where T)->(x,y,T))()") == (Vector{Int}, Vector{Int}, Vector{Int}) @test JL.include_string(test_mod, "(((x::Type{T}=Vector{Int},y=T) where T)->(x,y,T))(Bool)") == (Bool, Bool, Bool) # https://github.com/JuliaLang/JuliaLowering.jl/issues/158 @test JL.include_string( test_mod, "(((::Type{T}=Vector{UInt8}, sz=Base.aligned_sizeof(eltype(T))) where T)->sz)()") == 1 @test JL.include_string( test_mod, "(((::Type{T}=Vector{UInt8}, sz=Base.aligned_sizeof(eltype(T))) where T)->sz)(Int32)") == 4 @test JuliaLowering.include_string(test_mod, """ begin function f_def_placeholders(::T=1, _::S=1.0) where {T,S} (T,S) end (f_def_placeholders(), f_def_placeholders(1.0), f_def_placeholders(1.0, 1)) end """) == ((Int,Float64), (Float64,Float64), (Float64,Int)) @test JuliaLowering.include_string(test_mod, """ begin function f_def_typevars(x, y::S=[1], z::U=2) where {T, S<:AbstractVector{T}, U} (x, y, z, T, S, U) end (f_def_typevars(1), f_def_typevars(1,[1.0]), f_def_typevars(1,[1.0],-1.0)) end """) == ((1, [1], 2, Int, Vector{Int}, Int), (1, [1.0], 2, Float64, Vector{Float64}, Int), (1, [1.0], -1.0, Float64, Vector{Float64}, Float64)) @test JuliaLowering.include_string(test_mod, """ begin function f_def_typevar_vararg_undef(x::T, y::Vararg{S}) where {T,S} (x, y, @isdefined S) end (f_def_typevar_vararg_undef(1), f_def_typevar_vararg_undef(1,2), f_def_typevar_vararg_undef(1,2,3)) end """) === ((1, (), false), (1, (2,), true), (1, (2, 3), true)) @test JuliaLowering.include_string(test_mod, """ begin f_def_typevar_with_lowerbound(x::T) where {T>:Int} = (x, @isdefined(T)) (f_def_typevar_with_lowerbound(1), f_def_typevar_with_lowerbound(1.0)) end """) == ((1, true), (1.0, false)) @test JuliaLowering.include_string(test_mod, """ begin function f_def_slurp(x=1, ys...) (x, ys) end (f_def_slurp(), f_def_slurp(2), f_def_slurp(2,3)) end """) == ((1, ()), (2, ()), (2, (3,))) @test JuliaLowering.include_string(test_mod, """ begin function f_def_ret_type(x=1.0)::Int x end (f_def_ret_type(), f_def_ret_type(10.0)) end """) === (1,10) @test JuliaLowering.include_string(test_mod, """ begin function f_def_slurp_splat(ys...=(1,2)...) ys end (f_def_slurp_splat(), f_def_slurp_splat(10,20)) end """) == ((1,2), (10,20)) @test JuliaLowering.include_string(test_mod, """ begin function f_def_destructure(x, (y,z)::Tuple{Int,Int}, (w,)...=(4,)...) (x,y,z,w) end f_def_destructure(1, (2,3)) end """) == (1,2,3,4) @test JuliaLowering.include_string(test_mod, """ begin function f_optarg_complex_spbounds(p::T, o=1) where {T<:Complex{<:Real}} T, p, o end f_optarg_complex_spbounds(Complex(1)), f_optarg_complex_spbounds(Complex(2), Complex(3)) end """) == ((Complex{Int},Complex(1),1), (Complex{Int},Complex(2),Complex(3))) @test JuliaLowering.include_string(test_mod, """ begin function f_optarg_complex_spbounds2(p::T, o::T=Complex(0)) where {T<:Complex{<:Real}} T, p, o end f_optarg_complex_spbounds2(Complex(1)), f_optarg_complex_spbounds2(Complex(2)) end """) == ((Complex{Int},Complex(1),Complex(0)), (Complex{Int},Complex(2),Complex(0))) @test JuliaLowering.include_string(test_mod, """ begin function f_optarg_complex_spbounds_rett(p::T, o=1)::T where {T<:Complex{<:Real}} p end f_optarg_complex_spbounds_rett(Complex(1)), f_optarg_complex_spbounds_rett(Complex(2)) end """) == (Complex(1), Complex(2)) # flisp will evaluate the sparam bound multiple times let res = JuliaLowering.include_string(test_mod, """ let eval_spbounds_counter = 0 global function f_optarg_eval_spbounds_counter( p::T, o=1,_=2,_=3) where { T<:Complex{<:(eval_spbounds_counter += 1; Real)}} (p, eval_spbounds_counter) end f_optarg_eval_spbounds_counter(Complex(1)) end """) @test res == (Complex(1), 1) end end @testset "slotflags" begin JuliaLowering.include_string(test_mod, """ function f_slotflags(x, y, f, z) f() + x + y end """) @test only(methods(test_mod.f_slotflags)).called == 0b0100 end @testset "nospecialize" begin # note f(a,b,c) means a is arg 1, not f function test_arg_unspecialized(f::Function, arg_i::Int) for m in methods(f) arg_i > m.nargs-1 && return nothing @test m.nospecialize & (1 << (arg_i-1)) != 0 end end function test_arg_specialized(f::Function, arg_i::Int) for m in methods(f) arg_i > m.nargs-1 && return nothing @test m.nospecialize & (1 << (arg_i-1)) == 0 end end @test JuliaLowering.include_string(test_mod, """ begin function f_nospecialize(u, v, @nospecialize(x), y, @nospecialize(z)) (u, v, x, y, z) end f_nospecialize(1,2,3,4,5) end """) == (1,2,3,4,5) # We dig into the internal of `Method` here to check which slots have been # flagged as nospecialize. @test only(methods(test_mod.f_nospecialize)).nospecialize == 0b10100 # Branching combined with nospecialize meta in CodeInfo @test JuliaLowering.include_string(test_mod, """ begin function f_branch_meta(@nospecialize(x), cond) if cond x + 1 else x + 2 end end (f_branch_meta(10, false), f_branch_meta(20, true)) end """) == (12, 21) # @nospecialize with multiple args in function body @test JuliaLowering.include_string(test_mod, """ begin function f_nospecialize_multi_body(a, b, c, d) @nospecialize a c d (a, b, c, d) end f_nospecialize_multi_body(1, 2, 3, 4) end """) == (1, 2, 3, 4) @test only(methods(test_mod.f_nospecialize_multi_body)).nospecialize == 0b1101 # @nospecialize with single arg in function body @test JuliaLowering.include_string(test_mod, """ begin function f_nospecialize_single_body(a, b) @nospecialize b (a, b) end f_nospecialize_single_body(1, 2) end """) == (1, 2) @test only(methods(test_mod.f_nospecialize_single_body)).nospecialize == 0b10 # @nospecialize with zero args in function body (blanket nospecialize) @test JuliaLowering.include_string(test_mod, """ begin function f_nospecialize_zero_body(a, b, c) @nospecialize (a, b, c) end f_nospecialize_zero_body(1, 2, 3) end """) == (1, 2, 3) # 0-arg @nospecialize sets all bits (-1 == typemax(Int32) for nospecialize) @test only(methods(test_mod.f_nospecialize_zero_body)).nospecialize == -1 # @nospecialize with default value in signature @test JuliaLowering.include_string(test_mod, """ begin function f_nospecialize_default(x, @nospecialize(y=1)) (x, y) end (f_nospecialize_default(10, 20), f_nospecialize_default(30)) end """) == ((10, 20), (30, 1)) # The 2-arg method has nospecialize on y (bit 2), the 1-arg forwarding method has no y ms = collect(methods(test_mod.f_nospecialize_default)) @test any(m -> m.nargs == 3 && m.nospecialize == 0b10, ms) @test any(m -> m.nargs == 2 && m.nospecialize == 0b00, ms) # Body-level @nospecialize with default value in signature @test JuliaLowering.include_string(test_mod, """ begin function f_body_nospecialize_default(x, y=1) @nospecialize (x, y) end (f_body_nospecialize_default(10, 20), f_body_nospecialize_default(30)) end """) == ((10, 20), (30, 1)) # The 2-arg method has nospecialize on y (bit 2), the 1-arg forwarding method has no y ms = collect(methods(test_mod.f_body_nospecialize_default)) @test count(m -> m.nargs == 3 && m.nospecialize == -1, ms) == 1 @test count(m -> m.nargs == 2 && m.nospecialize == -1, ms) == 1 # body nospecialize into complex sig: all @test JuliaLowering.include_string(test_mod, """ begin function f_body_nospecialize_nontrivial_sig(x::T, y::Vector{<:U}=[])::Any where T where U @nospecialize (x, y) end (f_body_nospecialize_nontrivial_sig(10, [20]), f_body_nospecialize_nontrivial_sig(30)) end """) == ((10, [20]), (30, [])) test_arg_unspecialized(test_mod.f_body_nospecialize_nontrivial_sig, 1) test_arg_unspecialized(test_mod.f_body_nospecialize_nontrivial_sig, 2) # should be blanket-nospecialized ms = collect(methods(test_mod.f_body_nospecialize_nontrivial_sig)) @test count(m -> m.nargs == 3 && m.nospecialize == -1, ms) == 1 @test count(m -> m.nargs == 2 && m.nospecialize == -1, ms) == 1 # body nospecialize into complex sig: by name @test JuliaLowering.include_string(test_mod, """ begin function f_body_nospecialize_nontrivial_sig2(x::T, y::Vector{<:U}=[])::Any where T where U @nospecialize x (x, y) end (f_body_nospecialize_nontrivial_sig2(10, [20]), f_body_nospecialize_nontrivial_sig2(30)) end """) == ((10, [20]), (30, [])) test_arg_unspecialized(test_mod.f_body_nospecialize_nontrivial_sig2, 1) test_arg_specialized(test_mod.f_body_nospecialize_nontrivial_sig2, 2) # callable type: should compile, but nospecialize doesn't do anything @test JuliaLowering.include_string(test_mod, """ struct nospecialize_callable_type; field; end (@nospecialize(x::nospecialize_callable_type))() = (x.field,) nospecialize_callable_type(0)() """) == (0,) @test JuliaLowering.include_string(test_mod, """ (@nospecialize(::nospecialize_callable_type))(x::Int) = (x,) nospecialize_callable_type(0)(1) """) == (1,) @test JuliaLowering.include_string(test_mod, """ (@nospecialize(_::nospecialize_callable_type))(x::Int, y::Int) = (x,y) nospecialize_callable_type(0)(1,2) """) == (1,2) @test JuliaLowering.include_string(test_mod, """ function (self::nospecialize_callable_type)(x::Int, y::Int, z::Int) @nospecialize self (self.field,x,y,z) end nospecialize_callable_type(0)(1,2,3) """) == (0,1,2,3) @test JuliaLowering.include_string(test_mod, """ (@nospecialize((;field)::nospecialize_callable_type))(x::Int, y::Int, z::Int, a::Int) = (field,x,y,z,a) nospecialize_callable_type(0)(1,2,3,4) """) == (0,1,2,3,4) @test_throws LoweringError JuliaLowering.include_string(test_mod, """ (@nospecialize(x)::nospecialize_callable_type)() = 1 """) # function name: should compile, but nospecialize doesn't do anything @test_broken JuliaLowering.include_string(test_mod, """ (@nospecialize(_))(x::Int) = () func_nospecialize_self(1) """) == () @test JuliaLowering.include_string(test_mod, """ (@nospecialize(func_nospecialize_self))(x::Int) = (x,) func_nospecialize_self(1) """) == (1,) # all positional arg forms @testset for arg0 in [:x, :(x::Type), :(::Type), :(_), :(_::Type)], arg1 in [arg0, Expr(:..., arg0)], arg2 in [arg1, Expr(:kw, arg1, :Int)], expander in [fl_macroexpand, jl_macroexpand] @testset let expanded = expander( test_mod, :(function (specialized, @nospecialize($arg2)) specialized end)) f = jl_eval(test_mod, expanded) test_arg_specialized(f, 1) test_arg_unspecialized(f, 2) end @testset let expanded = expander( test_mod, :(function ($arg2,) @nospecialize end)) f = jl_eval(test_mod, expanded) test_arg_unspecialized(f, 1) end end # nospecialize should still compile where flisp drops it @test jl_eval( test_mod, :(let bad(@nospecialize(x) = 1) = x (bad(0), bad()) end)) == (0, 1) @test jl_eval( test_mod, :(let bad(@nospecialize(x::Int) = 1) = x (bad(0), bad()) end)) == (0, 1) @testset "kwargs" for expander in [fl_macroexpand, (_,x)->x] local f @test (f = jl_eval(test_mod, expander( test_mod, quote function (@nospecialize(a); kw=1) (a, kw) end end))) isa Function Core.@latestworld @test f(1, kw=2) == (1,2) && f(3) == (3,1) test_arg_unspecialized(f, 1) @test only(methods(Core.kwcall, (NamedTuple,typeof(f),Any))).nospecialize == 1 << 2 # Body-level @nospecialize @test (f = jl_eval(test_mod, expander( test_mod, quote function (a; kw=1) @nospecialize a (a, kw) end end))) isa Function Core.@latestworld @test f(1, kw=2) == (1,2) && f(3) == (3,1) test_arg_unspecialized(f, 1) @test only(methods(Core.kwcall, (NamedTuple,typeof(f),Any))).nospecialize == 1 << 2 # kw nospecialize. TODO: The body method is local; how do we get it out # for testing? @test (f = jl_eval(test_mod, expander( test_mod, quote function (a; @nospecialize(kw=1)) (a, kw) end end))) isa Function Core.@latestworld @test f(1, kw=2) == (1,2) && f(3) == (3,1) test_arg_specialized(f, 1) # kw... nospecialize (same TODO) @test (f = jl_eval(test_mod, expander( test_mod, quote function (a; @nospecialize(kw...)) (a, kw...) end end))) isa Function Core.@latestworld @test f(1, kw=2, a=3) == (1,:kw=>2,:a=>3) test_arg_specialized(f, 1) end # macros already mark all non-internal args nospecialize @testset "macro definitions" begin @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(x)); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(x::Int)); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(x=1)); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(x::Int=1)); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(x); @nospecialize(); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(x); @nospecialize(x); x; end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(x::Int); @nospecialize(x); x; end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(x=1); @nospecialize(x); x; end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(x::Int=1); @nospecialize(x); x; end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(_)); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(_::Int)); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(_=1)); end)) isa Function @gensym sym @test jl_eval(test_mod, :(macro $sym(@nospecialize(_::Int=1)); end)) isa Function end end @testset "Keyword functions" begin JuliaLowering.include_string(test_mod, """ function f_kw_simple(a::Int=1, b::Float64=1.0; x::Char='a', y::Bool=true) (a, b, x, y) end """) @test test_mod.f_kw_simple() === (1, 1.0, 'a', true) @test test_mod.f_kw_simple(x='b') === (1, 1.0, 'b', true) @test test_mod.f_kw_simple(y=false) === (1, 1.0, 'a', false) @test test_mod.f_kw_simple(x='b', y=false) === (1, 1.0, 'b', false) @test test_mod.f_kw_simple(20) === (20, 1.0, 'a', true) @test test_mod.f_kw_simple(20; x='b') === (20, 1.0, 'b', true) @test test_mod.f_kw_simple(20; y=false) === (20, 1.0, 'a', false) @test test_mod.f_kw_simple(20; x='b', y=false) === (20, 1.0, 'b', false) @test test_mod.f_kw_simple(20, 2.0) === (20, 2.0, 'a', true) @test test_mod.f_kw_simple(20, 2.0; x='b') === (20, 2.0, 'b', true) @test test_mod.f_kw_simple(20, 2.0; y=false) === (20, 2.0, 'a', false) @test test_mod.f_kw_simple(20, 2.0; x='b', y=false) === (20, 2.0, 'b', false) # Bad types for keyword args throw a type error @test_throws(TypeError(Symbol("keyword argument"), :x, Char, 100), test_mod.f_kw_simple(x=100)) @test_throws(TypeError(Symbol("keyword argument"), :y, Bool, 100), test_mod.f_kw_simple(y=100)) # Keywords which aren't present throw an error try test_mod.f_kw_simple(20; not_present=100) @test false catch exc @test exc isa MethodError @test exc.f == Core.kwcall @test exc.args == ((; not_present=100), test_mod.f_kw_simple, 20, 1.0) end @testset "anonymous forms" begin f = JL.include_string(test_mod, "function (;kw); kw; end") @test f(;kw=1) == 1 @test_throws UndefKeywordError f(;) @test_throws MethodError f(1) f = JL.include_string(test_mod, "function (;kw::Int); kw; end") @test f(;kw=1) == 1 @test_throws TypeError f(;kw=1.1) @test_throws UndefKeywordError f(;) @test_throws MethodError f(1) f = JL.include_string(test_mod, "function (;kw=2); kw; end") @test f(;kw=1) == 1 @test f(;) == 2 @test_throws MethodError f(1) f = JL.include_string(test_mod, "function (;kw::Int=2); kw; end") @test f(;kw=1) == 1 @test f(;) == 2 @test_throws TypeError f(;kw=1.1) @test_throws MethodError f(1) f = JL.include_string(test_mod, "function (;kw...); kw; end") @test f(;kw=1) isa Base.Pairs @test (f(;kw=1))[:kw] == 1 exkw = (;k3=3) @test f(;k1=1, k2=2, exkw...) isa Base.Pairs @test (f(;k1=1, k2=2, exkw...))[:k1] == 1 @test (f(;k1=1, k2=2, exkw...))[:k2] == 2 @test (f(;k1=1, k2=2, exkw...))[:k3] == 3 # -> f = JL.include_string(test_mod, "(;kw)->kw") @test f(;kw=1) == 1 @test_throws UndefKeywordError f(;) @test_throws MethodError f(1) f = JL.include_string(test_mod, "(;kw::Int)->kw") @test f(;kw=1) == 1 @test_throws TypeError f(;kw=1.1) @test_throws UndefKeywordError f(;) @test_throws MethodError f(1) f = JL.include_string(test_mod, "(;kw=2)->kw") @test f(;kw=1) == 1 @test f(;) == 2 @test_throws MethodError f(1) f = JL.include_string(test_mod, "(;kw::Int=2)->kw") @test f(;kw=1) == 1 @test f(;) == 2 @test_throws TypeError f(;kw=1.1) @test_throws MethodError f(1) f = JL.include_string(test_mod, "(;kw...)->kw") @test f(;kw=1) isa Base.Pairs @test (f(;kw=1))[:kw] == 1 exkw = (;k3=3) @test f(;k1=1, k2=2, exkw...) isa Base.Pairs @test (f(;k1=1, k2=2, exkw...))[:k1] == 1 @test (f(;k1=1, k2=2, exkw...))[:k2] == 2 @test (f(;k1=1, k2=2, exkw...))[:k3] == 3 end # Slurping of positional args with keywords JuliaLowering.include_string(test_mod, """ function f_pos_slurp_with_kws(z, args...; x=1,y=2) args end """) @test test_mod.f_pos_slurp_with_kws(3, 2, 1; x = 100) === (2,1) @test test_mod.f_pos_slurp_with_kws(3, 2, 1) === (2,1) # Slurping of keyword args JuliaLowering.include_string(test_mod, """ function f_kw_slurp_all(; kws...) kws end """) @test values(test_mod.f_kw_slurp_all(x = 1, y = 2)) === (x=1, y=2) @test values(test_mod.f_kw_slurp_all()) === (;) # Slurping of keyword args JuliaLowering.include_string(test_mod, """ function f_kw_slurp_some(; x=1, y=2, kws...) kws end """) @test values(test_mod.f_kw_slurp_some(z=3, x = 1, y = 2, w=4)) === (z=3, w=4) @test values(test_mod.f_kw_slurp_some(x = 1)) === (;) @test values(test_mod.f_kw_slurp_some()) === (;) # Slurping with defaults depending on keyword names JuliaLowering.include_string(test_mod, """ function f_kw_slurp_dep(; a=1, b=a, kws...) (a, b, length(kws)) end """) @test test_mod.f_kw_slurp_dep(; a=1) == (1, 1, 0) @test test_mod.f_kw_slurp_dep(; a=2, c=3) == (2, 2, 1) # Keyword defaults which depend on other keywords. JuliaLowering.include_string(test_mod, """ begin aaa = :outer function f_kw_default_dependencies(; x=1, y=x, bbb=aaa, aaa=:aaa_kw, ccc=aaa) (x, y, bbb, aaa, ccc) end end """) @test values(test_mod.f_kw_default_dependencies()) === (1, 1, :outer, :aaa_kw, :aaa_kw) @test values(test_mod.f_kw_default_dependencies(x = 10)) === (10, 10, :outer, :aaa_kw, :aaa_kw) @test values(test_mod.f_kw_default_dependencies(x = 10, aaa=:blah)) === (10, 10, :outer, :blah, :blah) # depend on positional args @test JuliaLowering.include_string(test_mod, """ function f_kw_pos_dependencies(p1, o1=1, va...; kw1=p1, kw2=o1, kw3=va) (p1, o1, va..., kw1, kw2, kw3...) end """) isa Function @test test_mod.f_kw_pos_dependencies('p', 'o', 'v', 'v') == ('p', 'o', 'v', 'v', 'p', 'o', 'v', 'v') @test test_mod.f_kw_pos_dependencies('p', 'o', 'v') == ('p', 'o', 'v', 'p', 'o', 'v') @test test_mod.f_kw_pos_dependencies('p', 'o') == ('p', 'o', 'p', 'o') @test test_mod.f_kw_pos_dependencies('p') == ('p', 1, 'p', 1) # Keywords with static parameters JuliaLowering.include_string(test_mod, """ function f_kw_sparams(x::X, y::Y; a::A, b::B) where {X,Y,A,B} (X,Y,A,B) end """) @test values(test_mod.f_kw_sparams(1, 1.0; a="a", b='b')) === (Int, Float64, String, Char) # Keywords with static parameters, where some keyword types can be inferred # based on the positional parameters and others cannot. JuliaLowering.include_string(test_mod, """ function f_kw_type_errors(x::X; a::F, b::X) where {X<:Integer,F<:AbstractFloat} (X,F) end """) @test values(test_mod.f_kw_type_errors(1; a=1.0, b=10)) === (Int, Float64) # The following is a keyword TypeError because we can infer `X` based on # the positional parameters and use that to check the type of `b`. @test_throws TypeError values(test_mod.f_kw_type_errors(1; a=1.0, b="str")) # The following is only a method error as we can't infer `F` prior to # dispatching to the body function. @test_throws MethodError values(test_mod.f_kw_type_errors(1; a="str", b=10)) # Return type annotation using default argument names # The return type must be evaluated in a scope where keyword args are bound. JuliaLowering.include_string(test_mod, """ function f_default_rett(T::Type=Int)::Vector{T} T[1,2,3] end """) @test test_mod.f_default_rett() isa Vector{Int} @test test_mod.f_default_rett(Float64) isa Vector{Float64} # Return type annotation using keyword argument names # The return type must be evaluated in a scope where keyword args are bound. JuliaLowering.include_string(test_mod, """ function f_kw_rett(; T::Type=Int)::Vector{T} T[1,2,3] end """) @test test_mod.f_kw_rett() isa Vector{Int} @test test_mod.f_kw_rett(T=Float64) isa Vector{Float64} JuliaLowering.include_string(test_mod, """ function f_kw_rett2(; T::Type=Int)::Union{Vector{<:T}, Vector{<:AbstractVector{<:T}}} false && return T[] T == Int ? T[1,2,3] : [T[1,2],T[3,4]] end """) @test test_mod.f_kw_rett2() isa Vector{Int} @test test_mod.f_kw_rett2() == Int[1,2,3] @test test_mod.f_kw_rett2(T=Float64) isa Vector{Vector{Float64}} @test test_mod.f_kw_rett2(T=Float64) == [Float64[1.0,2.0],Float64[3.0,4.0]] # Throwing of UndefKeywordError JuliaLowering.include_string(test_mod, """ function f_kw_no_default(; x) x end """) @test test_mod.f_kw_no_default(x = 10) == 10 @test_throws UndefKeywordError(:x) test_mod.f_kw_no_default() == 10 # Closure with keywords cl = JuliaLowering.include_string(test_mod, """ let y = 1 function f_kw_closure(; x=10) x + y end end """) @test cl() == 11 @test cl(x = 20) == 21 f = JuliaLowering.include_string(test_mod, """ function f_kw_closure_outer(; x=1) function f_kw_closure(; y=2) (x, y) end end """) @test f() isa Function @test f()() == (1, 2) @test f()(y = 3) == (1, 3) @test f(x = 10) isa Function @test f(x = 10)(y = 10) == (10, 10) f = JuliaLowering.include_string(test_mod, """ function f_kw_closure_capt_default(; x=1) function f_kw_closure(; y=x) (x, y) end end """) @test f() isa Function @test f()() == (1, 1) @test f(x=2)(y=3) == (2, 3) f = JuliaLowering.include_string(test_mod, """ let outer_capt = 0 function f_kw_closure_capt_default(; x=1) function f_kw_closure(; y=x) (outer_capt, x, y) end end end """) @test f() isa Function @test f()() == (0, 1, 1) @test f(x=2)(y=3) == (0, 2, 3) f = JuliaLowering.include_string(test_mod, """ function f_kw_anon(outervar) (a,;kw=1)->a+kw+outervar end """) @test f(100) isa Function @test f(100)(2) == 103 @test f(100)(2;kw=2) == 104 @testset "complex arg types requiring temporaries" begin @test JL.include_string( test_mod, """ let f = function (x::Vector{<:Number};kw=[2]) (x,kw) end f([1]), f([1], kw=[0]) end """) == (([1], [2]), ([1], [0])) @test JL.include_string( test_mod, """ let f = function (x::Vector{<:Number}, o1=10, o2=20;kw=[2]) (x,kw,o1,o2) end f([1]), f([1], kw=[0]) end """) == (([1], [2], 10, 20), ([1], [0], 10, 20)) @test JL.include_string( test_mod, """ let f = function (x;kw::Vector{<:Number}=x) (x,kw) end f([1]), f([1], kw=[0]) end """) == (([1], [1]), ([1], [0])) @test JL.include_string( test_mod, """ let f = function (x, o1=10, o2=20;kw::Vector{<:Number}=x) (x,kw,o1,o2) end f([1]), f([1], kw=[0]) end """) == (([1], [1], 10, 20), ([1], [0], 10, 20)) @test JL.include_string( test_mod, """ let f = function (o1::Vector{<:Number}=[10];kw=1) (kw,o1) end f(), f([1]), f(;kw=2), f([1]; kw=2) end """) == ((1, [10]), (1, [1]), (2, [10]), (2, [1])) end @testset "complex sparam bounds requiring temporaries" begin @test JL.include_string( test_mod, """ let f = function (x::T;kw=[2]) where {T<:Vector{<:Number}} (T,x,kw) end f([1]), f([1], kw=[0]) end """) == ((Vector{Int}, [1], [2]), (Vector{Int}, [1], [0])) @test JL.include_string( test_mod, """ let f = function (x::T, o1=10, o2=20;kw=[2]) where {T<:Vector{<:Number}} (T,x,kw,o1,o2) end f([1]), f([1], kw=[0]) end """) == ((Vector{Int}, [1], [2], 10, 20), (Vector{Int}, [1], [0], 10, 20)) @test JL.include_string( test_mod, """ let f = function (x;kw::T=x) where {T<:Vector{<:Number}} (T,x,kw) end f([1]), f([1], kw=[0]) end """) == ((Vector{Int}, [1], [1]), (Vector{Int}, [1], [0])) @test JL.include_string( test_mod, """ let f = function (x, o1=10, o2=20;kw::T=x) where {T<:Vector{<:Number}} (T,x,kw,o1,o2) end f([1]), f([1], kw=[0]) end """) == ((Vector{Int}, [1], [1], 10, 20), (Vector{Int}, [1], [0], 10, 20)) @test JL.include_string( test_mod, """ let f = function (o1::T=[10];kw=1) where {T<:Vector{<:Number}} (T,kw,o1) end f(), f([1]), f(;kw=2), f([1]; kw=2) end """) == ((Vector{Int}, 1, [10]), (Vector{Int}, 1, [1]), (Vector{Int}, 2, [10]), (Vector{Int}, 2, [1])) end @testset "destructured args" begin @test JL.include_string( test_mod, "(function ((d1,d2);kw); [d1,d2,kw]; end)((1,2);kw=3)") == [1,2,3] # with kw default @test JL.include_string( test_mod, "(function ((d1,d2);kw=4); [d1,d2,kw]; end)((1,2);kw=3)") == [1,2,3] @test JL.include_string( test_mod, "(function ((d1,d2);kw=4); [d1,d2,kw]; end)((1,2))") == [1,2,4] # flisp doesn't do this either @test_broken JL.include_string( test_mod, "(function ((d1,d2);kw1=d1); [d1,d2,kw1]; end)((1,2))") == [1,2,1] # with kw deps @test JL.include_string( test_mod, "(function ((d1,d2);kw1=1,kw2=kw1); [d1,d2,kw1,kw2]; end)((1,2);kw1=9)") == [1,2,9,9] @test JL.include_string( test_mod, "(function ((d1,d2);kw1=1,kw2=kw1); [d1,d2,kw1,kw2]; end)((1,2);kw1=9,kw2=10)") == [1,2,9,10] end end # Brittle test, needs fixing if kw_body naming or kwarg implementation changes @testset "(AI) kw function helper is declared in the correct module" begin # Extending another module's keyword function with a new keyword method must # reserve the hidden `#kw_body#...` global in the *extending* module (the # call/eval site), never in the extended function's home module -- reserving # it in a foreign (possibly precompiled/closed) module breaks incremental # compilation. This is most easily broken when the method name arrives as an # interpolated `GlobalRef` *value* (the StatsBase/TracedSample shape), whose # `:mod` attribute would otherwise steer the reservation to the owner module. kwbodies(m) = filter(s -> occursin("kw_body", String(s)), names(m; all=true)) # (a) interpolated `GlobalRef` value as the method name (the regressing case) OwnerA = Module() JL.include_string(OwnerA, "sample(x; y=1) = x + y") a_before = Set(kwbodies(OwnerA)) ExtA = Module() @eval ExtA const OwnerA = $OwnerA JL.include_string(ExtA, """ let fn = GlobalRef(OwnerA, :sample) @eval \$fn(x::Symbol; y=1) = y end """) @test isempty(setdiff(Set(kwbodies(OwnerA)), a_before)) # no new global in owner @test !isempty(kwbodies(ExtA)) # reserved in extender @test OwnerA.sample(3; y=10) == 13 # original method intact @test OwnerA.sample(:s; y=7) == 7 # new method dispatches @test OwnerA.sample(:s) == 1 # ...with its own default # (b) syntactic dotted name reaches the same conclusion (guards the common path) OwnerB = Module() JL.include_string(OwnerB, "sample(x; y=1) = x + y") b_before = Set(kwbodies(OwnerB)) ExtB = Module() @eval ExtB const OwnerB = $OwnerB JL.include_string(ExtB, "function OwnerB.sample(x::Symbol; y=1); y; end") @test isempty(setdiff(Set(kwbodies(OwnerB)), b_before)) @test !isempty(kwbodies(ExtB)) @test OwnerB.sample(:s; y=7) == 7 end @testset "pre-desugared arg::Vararg" begin @test JuliaLowering.include_string(test_mod, """ let f_vararg_nosplat = function (x::Vararg{Int}) x end f_vararg_nosplat(1,2,3) end """) == (1, 2, 3) @test JuliaLowering.include_string(test_mod, """ let f_vararg_nosplat = function ((a,b,c)::Vararg{Int}) (a,b,c) end f_vararg_nosplat(1,2,3) end """) == (1, 2, 3) @test JuliaLowering.include_string(test_mod, """ let f_vararg_nosplat = function (((a,b)...,c)::Vararg{Int}) (a,b,c) end f_vararg_nosplat(1,2,3) end """) == (1, 2, 3) @test JuliaLowering.include_string(test_mod, """ let f_vararg_nosplat = function (((a,b)...,c)::Vararg{Tuple{Vararg{Int}}}) (a,b,c) end f_vararg_nosplat((1,2),(3,),(4,)) end """) == ((1, 2), (3,), (4,)) @testset "(AI) in keyword functions" begin # A trailing positional `Vararg{T,N}` written with an explicit `::Vararg` # annotation (rather than `...`) must still be splatted when the keyword # wrappers forward it to the body method. The zero-keyword path (defaulting # sorter), the explicit-keyword path, and splatted keywords must all work, # and `N` may be referenced in the signature and body. JuliaLowering.include_string(test_mod, """ function f_vararg_N_kws(y::Integer, args::Vararg{Integer,N}; kwargs...) where {N} (y, args, N, kwargs) end """) @test test_mod.f_vararg_N_kws(1, 2, 3) === (1, (2, 3), 2, Base.pairs(NamedTuple())) let r = test_mod.f_vararg_N_kws(1, 2, 3; foo=1) @test (r[1], r[2], r[3]) === (1, (2, 3), 2) @test r[4][:foo] == 1 end let ekw = (a=1, b=2), r = test_mod.f_vararg_N_kws(1, 2; ekw...) @test (r[1], r[2], r[3]) === (1, (2,), 1) @test (r[4][:a], r[4][:b]) == (1, 2) end # `N` used as a keyword default (exercises the sorter/body kw forwarding too). JuliaLowering.include_string(test_mod, """ function f_vararg_N_kwdefault(y::Integer, args::Vararg{Integer,N}; scale::Int=N) where {N} (y, args, scale) end """) @test test_mod.f_vararg_N_kwdefault(1, 2, 3) === (1, (2, 3), 2) @test test_mod.f_vararg_N_kwdefault(1, 2, 3; scale=10) === (1, (2, 3), 10) # `Vararg{T}` with no count, and a bare `Vararg`, plus `Vararg` on an # anonymous (unnamed) positional argument. JuliaLowering.include_string(test_mod, """ function f_vararg_T_kws(y, args::Vararg{Integer}; kwargs...) (y, args, kwargs) end """) @test test_mod.f_vararg_T_kws(1, 2, 3) === (1, (2, 3), Base.pairs(NamedTuple())) @test test_mod.f_vararg_T_kws(1, 2, 3; foo=1)[3][:foo] == 1 JuliaLowering.include_string(test_mod, """ function f_vararg_bare_kws(y, args::Vararg; kwargs...) (y, args, kwargs) end """) @test test_mod.f_vararg_bare_kws(1, 2, 3) === (1, (2, 3), Base.pairs(NamedTuple())) @test test_mod.f_vararg_bare_kws(1; z=9)[3][:z] == 9 JuliaLowering.include_string(test_mod, """ function f_vararg_anon_kws(y, ::Vararg{Integer,N}; kwargs...) where {N} (y, N, kwargs) end """) @test test_mod.f_vararg_anon_kws(1, 2, 3) === (1, 2, Base.pairs(NamedTuple())) @test test_mod.f_vararg_anon_kws(1, 2, 3; k=1)[3][:k] == 1 # Equivalent `args::T...` and plain `args...` forms with kwargs (already # handled, covered here for parity). JuliaLowering.include_string(test_mod, """ function f_vararg_dots_typed_kws(y, args::Integer...; kwargs...) (y, args, kwargs) end """) @test test_mod.f_vararg_dots_typed_kws(1, 2, 3) === (1, (2, 3), Base.pairs(NamedTuple())) @test test_mod.f_vararg_dots_typed_kws(1, 2, 3; foo=1)[3][:foo] == 1 JuliaLowering.include_string(test_mod, """ function f_vararg_dots_kws(y, args...; kwargs...) (y, args, kwargs) end """) @test test_mod.f_vararg_dots_kws(1, 2, 3) === (1, (2, 3), Base.pairs(NamedTuple())) @test test_mod.f_vararg_dots_kws(1, 2, 3; foo=1)[3][:foo] == 1 # Vararg-annotated positional args carrying a default value (`K"kw"`-wrapped # in the AST), both named and anonymous. JuliaLowering.include_string(test_mod, """ function f_vararg_default_kws(y, args::Vararg{Int,N}=1; k=1) where {N} (y, args, N, k) end """) @test test_mod.f_vararg_default_kws(1, 2, 3) === (1, (2, 3), 2, 1) @test test_mod.f_vararg_default_kws(1, 2, 3; k=9) === (1, (2, 3), 2, 9) JuliaLowering.include_string(test_mod, """ function f_vararg_anon_default_kws(y, ::Vararg{Int,N}=1; k=1) where {N} (y, N, k) end """) @test test_mod.f_vararg_anon_default_kws(1, 2, 3) === (1, 2, 1) @test test_mod.f_vararg_anon_default_kws(1, 2, 3; k=9) === (1, 2, 9) # Callable-type method with a trailing Vararg and keywords (shape from # SerializedElementArrays.jl). JuliaLowering.include_string(test_mod, """ struct VKS{T,N} dims::NTuple{N,Int} end function (A::Type{VKS{<:Any,N}})(::UndefInitializer, dims::Vararg{Integer,N}; kw=1) where {N} (N, dims, kw) end """) @test test_mod.VKS{<:Any,2}(undef, 3, 4) === (2, (3, 4), 1) @test test_mod.VKS{<:Any,1}(undef, 7; kw=9) === (1, (7,), 9) end end @testset "all known valid positional argument forms" begin make_defaults(x) = let (ps, vals) = x # (p1,p2,p3) => (v1,v2,v3) to # ((kw p1 v1),(kw p2 v2),(kw p3 v3)) => (v1,v2,v3) map(zip(ps, vals)) do pv Expr(:kw, pv[1], pv[2]) end => vals end make_typed(pv) = let (ps, vals) = pv new_ps = map(ps) do p # types go under `...` if Meta.isexpr(p, :...) Expr(:..., Expr(:(::), p.args[1], Any)) else Expr(:(::), p, Any) end end new_ps => vals end pparams_req = let # tuple of params => tuple of acceptable values pparams_untyped = [ # x,y,z must be defined for testing (:x, :y, :z) => (1,2,3), (:x, Expr(:tuple, :y, :z)) => (1,(2,3)), (:x, Expr(:tuple, Expr(:parameters, :y, :z))) => (1,(;y=2,z=3)), (:x, Expr(:tuple, Expr(:..., :y), :z)) => (1,(2,3,4)), (Expr(:tuple, Expr(:tuple, :x, :y), :z),) => (((1,2),3),), (Expr(:tuple, Expr(:..., Expr(:tuple, :x, :y)), :z),) => ((1,2,3),), (Expr(:tuple, Expr(:..., Expr(:tuple, :x, :y)), :z),) => ((1,2,3,4,5),), (:x, :y, Expr(:..., :z)) => (1,2,3), ] pparams_typed = map(make_typed, pparams_untyped) vcat(pparams_untyped, pparams_typed) end @testset "required args" for (params_i, args_i) in pparams_req @testset let f_expr = Expr(:function, Expr(:call, gensym(), params_i...), Expr(:tuple, :x, :y, :z)), f_st = JuliaLowering.expr_to_est(f_expr) local func_ref, func_test @test ((func_ref = fl_eval(test_mod, f_expr)) isa Function) @test ((func_test = jl_eval(test_mod, f_st)) isa Function) Core.@latestworld @test func_ref(args_i...) == func_test(args_i...) end end pparams_default = map(make_defaults, pparams_req) @testset "default args" for (params_i, args_i) in pparams_default @testset let f_expr = Expr(:function, Expr(:call, gensym(), params_i...), Expr(:tuple, :x, :y, :z)), f_st = JuliaLowering.expr_to_est(f_expr) local func_ref, func_test @test ((func_ref = fl_eval(test_mod, f_expr)) isa Function) @test ((func_test = jl_eval(test_mod, f_st)) isa Function) Core.@latestworld @test func_ref(args_i...) == func_test(args_i...) @test func_ref() == func_test() end end # test vararg-tuples and splatted defaults separately, as providing defaults # must be done with a syntactic splat, and some variants are valid syntax # but not callable (may later be disallowed) @test JuliaLowering.include_string(test_mod, """ let f_vararg_tuple = function ((x,y,z)...) (x,y,z) end f_vararg_tuple(1,2,3), f_vararg_tuple(1,2,3,4,5) end """) === ((1,2,3), (1,2,3)) @test JuliaLowering.include_string(test_mod, """ let f_vararg_tuple = function ((x,y,z)...=(1,2,3)...) (x,y,z) end f_vararg_tuple(4,5,6,7), f_vararg_tuple() end """) === ((4,5,6), (1,2,3)) @test JuliaLowering.include_string(test_mod, """ let f_vararg_tuple = function ((x,(y,z))...=(1,(2,3))...) (x,y,z) end f_vararg_tuple(4,(5,6),7), f_vararg_tuple() end """) === ((4,5,6), (1,2,3)) @test JuliaLowering.include_string(test_mod, """ let f_vararg_tuple = function ((x,(y,z)...)...=(1,(2,3)...)...) (x,y,z) end f_vararg_tuple(4,5,6,7), f_vararg_tuple() end """) === ((4,5,6), (1,2,3)) # uncallable(?) @test JuliaLowering.include_string(test_mod, """ let f_vararg_tuple = function ((x,y,z)::Tuple...) (x,y,z) end end """) isa Function @test JuliaLowering.include_string(test_mod, """ let f_vararg_tuple = function ((;x,y,z)...) (x,y,z) end end """) isa Function @test JuliaLowering.include_string(test_mod, """ let f_vararg_tuple = function ((;x,y,z)::NamedTuple...) (x,y,z) end end """) isa Function # final default arg may always be splatted, even if no-op or followed by va @test JuliaLowering.include_string(test_mod, """ let f = function (x=1...) x end f(), f(2), try; f(9,9); catch e; "fail"; end end """) === (1, 2, "fail") @test JuliaLowering.include_string(test_mod, """ let f = function (x=1..., args...) x, args end f(), f(2), f(3,4,5) end """) === ((1, ()), (2, ()), (3, (4,5))) end @testset "first-arg destructuring" begin @eval test_mod struct XY; x; y; end Core.@latestworld xy = test_mod.XY(1,2) @test JL.include_string(test_mod, "((;x,y)::XY)(arg) = (x,y,arg)") === nothing Core.@latestworld @test xy(3) == (1,2,3) @eval test_mod begin struct XYVec; val::Core.SimpleVector; end Base.iterate(x::XYVec) = Base.iterate(x.val) Base.iterate(x::XYVec, i) = Base.iterate(x.val, i) end xy = test_mod.XYVec(Core.svec(1,2,999)) @test JL.include_string(test_mod, "((x,y)::XYVec)(arg) = (x,y,arg)") === nothing Core.@latestworld @test xy(3) == (1,2,3) # `...` xy = test_mod.XYVec(Core.svec(1,2,9,9,9)) @test JL.include_string(test_mod, "((x,y,rest...)::XYVec)() = (x,y,rest...,)") === nothing Core.@latestworld @test xy() == (1,2,9,9,9) xy = test_mod.XYVec(Core.svec(1,9,9,9,2)) @test JL.include_string(test_mod, "((x,rest...,y)::XYVec)(a1,a2) = (x,rest...,y,a1,a2)") === nothing Core.@latestworld @test xy(0,0) == (1,9,9,9,2,0,0) end @testset "sparam in keyword default" begin # The keyword default is evaluated in the body method, which carries all # of the function's static parameters @test JL.include_string(test_mod, "f_kwdef_sp(y::T; k=T) where T = (y, k); f_kwdef_sp(1)") == (1, Int) # ... but an sparam unused in the signature is undetermined at dispatch JL.include_string(test_mod, "f_kwdef_sp_undet(y; k=T) where T = (y, k)") @test_throws UndefVarError test_mod.f_kwdef_sp_undet(1) end @testset "anonymous static parameters" begin # `where _` declares a static parameter which can never be referenced @test JL.include_string(test_mod, "f_anon_sp(x) where _ = x; f_anon_sp(42)") == 42 @test JL.include_string( test_mod, "f_anon_sp2(x::T) where {T, _} = (x, T); f_anon_sp2(1.5)") == (1.5, Float64) @test_throws LoweringError JL.include_string( test_mod, "f_anon_sp3(x) where {_, _} = x"; expr_compat_mode=true) # Currently allowed (like arguments). Could error like flisp. @test_throws LoweringError JL.include_string( test_mod, "f_anon_sp3(x) where {_, _} = x") broken=true end @testset "first arg `where`" begin @eval test_mod struct A12238{T} end Core.@latestworld @test JL.include_string(test_mod, "(A12238{T} where T<:Real)(x) = 0") === nothing @test test_mod.A12238{<:Real}(0) == 0 @test_throws MethodError test_mod.A12238{<:Integer}(0) # Nested where @eval test_mod struct A12238_2{T, U}; x::T; y::U; end Core.@latestworld @test JL.include_string( test_mod, "(A12238_2{T, U} where T<:U where U<:Real)(x) = A12238_2(x,x)") === nothing @test (test_mod.A12238_2{T, U} where {U<:Real, T<:U})(0) === test_mod.A12238_2{Int, Int}(0, 0) # Implicit whereparams @eval test_mod struct A12238_3{T, U}; x::T; y::U; end Core.@latestworld @test JL.include_string( test_mod, "(A12238_3{<:Real, <:AbstractVector{<:Real}})() = A12238_3(1,Int[1])") === nothing @test (test_mod.A12238_3{<:Real, <:AbstractVector{<:Real}})() isa test_mod.A12238_3{Int, Vector{Int}} @test (test_mod.A12238_3{<:Real, <:AbstractVector{<:Real}})().x == 1 @test (test_mod.A12238_3{<:Real, <:AbstractVector{<:Real}})().y == [1] # >: @eval test_mod struct A12238_4{T} end Core.@latestworld @test JL.include_string( test_mod, "(A12238_4{T} where T>:Int)(x) = x") === nothing @test test_mod.A12238_4{>:Int}(1) == 1 @test_throws MethodError test_mod.A12238_4{<:Int}(1) end @testset "Write-only placeholder function arguments" begin # positional arguments may be duplicate placeholders. keyword arguments can # contain placeholders, but they must be unique params_req = ["" "_" "::Int" "_, _" "(_, _)"] params_opt = ["" "::Int=2" "_=2"] params_va = ["", "_..."] params_kw = ["" "; _" "; _::Int" "; _::Int=1" "; _=1, __=2" "; _..." "; _=1, __..."] for req in params_req, opt in params_opt, va in params_va, kw in params_kw arg_str = join(filter(!isempty, (req, opt, va, kw)), ", ") f_str = "function ($arg_str); end" @testset "$f_str" begin @test JuliaLowering.include_string(test_mod, f_str) isa Function end f_lam_str = "($arg_str)->nothing" @testset "$f_lam_str" begin @test JuliaLowering.include_string(test_mod, f_lam_str) isa Function end end end @testset "Badly-parsed anonymous forms (fix_arglist)" begin @test JL.include_string(test_mod, "(()->nothing)()") == nothing @test JL.include_string(test_mod, "((a...)->(a...,))(1,2,3)") == (1,2,3) @test JL.include_string(test_mod, "((a::Int)->(a))(1)") == 1 @test JL.include_string(test_mod, "((a::Int...)->(a...,))(1,2,3)") == (1,2,3) @test JL.include_string(test_mod, "((;)->nothing)()") == nothing @test JL.include_string(test_mod, "((a;)->a)(1)") == 1 @test JL.include_string(test_mod, "((a;b=2)->(a,b))(1)") == (1,2) @test JL.include_string(test_mod, "((a;b=2)->(a,b))(1;b=3)") == (1,3) @test JL.include_string(test_mod, "((a=0;b=2)->(a,b))()") == (0,2) @test JL.include_string(test_mod, "((a=0;b=2)->(a,b))(1)") == (1,2) @test JL.include_string(test_mod, "((a=0;b=2)->(a,b))(;b=3)") == (0,3) @test JL.include_string(test_mod, "((a=0;b=2)->(a,b))(1;b=3)") == (1,3) @test_throws LoweringError JL.include_string(test_mod, "(a=0;b=2;c=3)->nothing") # try again with `where` @test JL.include_string(test_mod, "(((a::T...) where T<:U where U<:Any) ->(a...,))(1,2,3)") == (1,2,3) @test JL.include_string(test_mod, "(((a::T;) where T<:U where U<:Any) ->a)(1)") == 1 @test JL.include_string(test_mod, "(((a::T;b=2) where T<:U where U<:Any) ->(a,b))(1)") == (1,2) @test JL.include_string(test_mod, "(((a::T;b=2) where T<:U where U<:Any) ->(a,b))(1;b=3)") == (1,3) @test JL.include_string(test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) ->(a,b))()") == (0,2) @test JL.include_string(test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) ->(a,b))(1)") == (1,2) @test JL.include_string(test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) ->(a,b))(;b=3)") == (0,3) @test JL.include_string(test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) ->(a,b))(1;b=3)") == (1,3) @test_throws LoweringError JL.include_string(test_mod, "(a=0;b=2;c=3)->nothing") # `...` is the only parser-reachable bad form with (function notcall _) forms @test JL.include_string(test_mod, "(function (a...); (a...,); end)(1,2,3)") == (1,2,3) @test JL.include_string(test_mod, "(function (a::Int...); (a...,); end)(1,2,3)") == (1,2,3) # test with where: need empty tv list to avoid unused sparam warning @test jl_eval(test_mod, Expr(:call, Expr(:function, Expr(:where, Expr(:where, Expr(:..., :a))), Expr(:block, Expr(:tuple, Expr(:..., :a)))), 1,2,3)) == (1,2,3) @test JL.include_string(test_mod, "(function (a::T) where T<:U where U<:Any; a; end)(1)") == 1 @test JL.include_string(test_mod, "(function (a::T...) where T<:U where U<:Any; a; end)(1,2,3)") == (1,2,3) end @testset "Consequences of accepting badly-parsed anonymous forms" begin # kw @test jl_eval(test_mod, Expr(:call, Expr(:function, Expr(:kw, :a, 1), Expr(:block, Expr(:tuple, :a))), )) == (1,) # empty block @test jl_eval(test_mod, Expr(:call, Expr(:function, Expr(:block), Expr(:block, Expr(:tuple))), )) == () # unwrapped or block-wrapped arg @testset for a1 in [:a, Expr(:(::), :a, :Int)], a2 in [a1, Expr(:(=), :a, 0), Expr(:kw, :a, 0)], wrap_where in [identity, x->Expr(:where, x), x->Expr(:where, Expr(:where, x))] @test jl_eval(test_mod, Expr(:call, Expr(:function, wrap_where(a2), Expr(:block, Expr(:tuple, :a))), 1)) == (1,) @test jl_eval(test_mod, Expr(:call, Expr(:function, wrap_where(Expr(:block, a2)), Expr(:block, Expr(:tuple, :a))), 1)) == (1,) end # two-arg block @test jl_eval(test_mod, Expr(:call, Expr(:function, Expr(:block, :a, :b), Expr(:block, Expr(:tuple, :a, :b))), 1, Expr(:kw, :b, 2))) == (1,2) end @testset "assignment to where-wrapped-tuple" begin @test_throws LoweringError JL.include_string( test_mod, "(((a,b,c::T) where T<:U where U<:Any) = (a,b,c))(1,2,3)") == (1,2,3) @test_throws LoweringError JL.include_string( test_mod, "(((a,b=0,c::T=0) where T<:U where U<:Any) = (a,b,c))(1)") == (1,0,0) @test_throws LoweringError JL.include_string( test_mod, "(((a,b=0,c::T=0) where T<:U where U<:Any) = (a,b,c))(1,2)") == (1,2,0) @test_throws LoweringError JL.include_string( test_mod, "(((a,b=0,c::T=0) where T<:U where U<:Any) = (a,b,c))(1,2,3)") == (1,2,3) @test_throws LoweringError JL.include_string( test_mod, "(((a::T...) where T<:U where U<:Any) = (a...,))(1,2,3)") == (1,2,3) @test_throws LoweringError JL.include_string( test_mod, "(((a::T;) where T<:U where U<:Any) = a)(1)") == 1 @test_throws LoweringError JL.include_string( test_mod, "(((a::T;b=2) where T<:U where U<:Any) = (a,b))(1)") == (1,2) @test_throws LoweringError JL.include_string( test_mod, "(((a::T;b=2) where T<:U where U<:Any) = (a,b))(1;b=3)") == (1,3) @test_throws LoweringError JL.include_string( test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) = (a,b))()") == (0,2) @test_throws LoweringError JL.include_string( test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) = (a,b))(1)") == (1,2) @test_throws LoweringError JL.include_string( test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) = (a,b))(;b=3)") == (0,3) @test_throws LoweringError JL.include_string( test_mod, "(((a::T=0;b=2) where T<:U where U<:Any) = (a,b))(1;b=3)") == (1,3) @test_throws LoweringError JL.include_string( test_mod, "(a=0;b=2;c=3) where T = nothing") @test_throws LoweringError jl_eval( test_mod, Expr(:call, Expr(:(=), Expr(:where, Expr(:where, Expr(:..., :a))), Expr(:block, Expr(:tuple, Expr(:..., :a)))), 1,2,3)) == (1,2,3) end @testset "Assigned-to arguments" begin # These examples are all macros, since they have specialized de-optimization # behavior that sends un-optimized code straight to codegen. Normal compiled # functions essentially always pass through SSA conversion on the way to the # optimizer, erasing these slots (potentially hiding bugs in slot handling) @test JuliaLowering.include_string(test_mod, raw""" macro m_assigned_args_1(x) x = x + 1 return x end var"@m_assigned_args_1"(LineNumberNode(0, nothing), Main, 2) """; expr_compat_mode=true) == 3 @test JuliaLowering.include_string(test_mod, raw""" macro m_assigned_args_2(x, y = 1) (y, x) = (x + 1, y + 1) return y - x end ( var"@m_assigned_args_2"(LineNumberNode(0, nothing), Main, 2), var"@m_assigned_args_2"(LineNumberNode(0, nothing), Main, 1, 2), ) """; expr_compat_mode=true) == (1, -1) for expr_compat_mode in (false, true) @test JuliaLowering.include_string(test_mod, raw""" macro m_assigned_args(ex) ex = Base.remove_linenums!(ex) return ex end ((@m_assigned_args 1 + 1), @m_assigned_args 1) """; expr_compat_mode) == (2, 1) end end @testset "Generated functions" begin; for expr_compat_mode in (false, true) local genfunc_s, genfunc_f @eval test_mod import JuliaLowering.@legacy_quote_to_syntax @testset "returning special syntax forms" begin @test JuliaLowering.include_string(test_mod, raw""" begin @generated f_gen_nothing() = nothing f_gen_nothing() end """; expr_compat_mode) == nothing @test JuliaLowering.include_string(test_mod, raw""" begin @generated f_gen_quotenothing() = :(nothing) f_gen_quotenothing() end """; expr_compat_mode) == nothing @test JuliaLowering.include_string(test_mod, raw""" begin @generated f_gen_quotenodenothing() = QuoteNode(nothing) f_gen_quotenodenothing() end """; expr_compat_mode) == nothing @test JuliaLowering.include_string(test_mod, raw""" begin @generated f_gen_quotenodeexpr() = QuoteNode(Expr(:begin, nothing)) f_gen_quotenodeexpr() end """; expr_compat_mode) == Expr(:begin, nothing) @test JuliaLowering.include_string(test_mod, raw""" begin @generated f_gen_gr_nothing() = GlobalRef(Core, :nothing) f_gen_gr_nothing() end """; expr_compat_mode) == nothing @test JuliaLowering.include_string(test_mod, raw""" begin @generated f_gen_quotegr_nothing() = :(GlobalRef(Core, :nothing)) f_gen_quotegr_nothing() end """; expr_compat_mode) == GlobalRef(Core, :nothing) @test JuliaLowering.include_string(test_mod, raw""" begin @generated f_gen_quotenodegr_nothing() = QuoteNode(GlobalRef(Core, :nothing)) f_gen_quotenodegr_nothing() end """; expr_compat_mode) == GlobalRef(Core, :nothing) end @test JuliaLowering.include_string(test_mod, raw""" begin @generated function f_gen_trivial(x) x end f_gen_trivial(1), f_gen_trivial(Int[1]) end """; expr_compat_mode) == (Int, Vector{Int}) @test JuliaLowering.include_string(test_mod, raw""" begin function f_gen_trivial_if(x) if @generated x else :($x, "nongen") end end f_gen_trivial_if(1), f_gen_trivial_if(Int[1]) end """; expr_compat_mode) == (Int, Vector{Int}) @testset "anonymous forms" begin @test JuliaLowering.include_string(test_mod, """ let f = @generated function (x); x; end f(1), f(Int[1]) end """; expr_compat_mode) == (Int, Vector{Int}) @test JuliaLowering.include_string(test_mod, """ let f = (x)->(if @generated(); x; else; "nongen"; end) f(1), f(Int[1]) end """; expr_compat_mode) == (Int, Vector{Int}) end @testset "destructured args" begin genfunc_s = raw""" function ((d1,d2)::T) where {T} if @generated :($T, "gen") else :($T, "nongen") end end """ @test (genfunc_f = JL.include_string(test_mod, genfunc_s; expr_compat_mode)) isa Function @test genfunc_f((1,2)) == (Tuple{Int, Int}, "gen") end @testset "destructured args: values" begin genfunc_s = raw""" function ((d1,d2)::T) where {T} if @generated :(d1, d2, $T, "gen") else :($T, "nongen") end end """ @test (genfunc_f = JL.include_string(test_mod, genfunc_s; expr_compat_mode)) isa Function @test genfunc_f((1,2)) == (1, 2, Tuple{Int, Int}, "gen") end @testset "(AI) destructured args: shapes" begin # A destructured-tuple argument in a fully-`@generated` function whose # body is a `quote`/`Expr(:block)` (not a bare single expression): the # implicit `(names...) = <arg>` prologue must reach the generated code. @test JL.include_string(test_mod, raw""" @generated function fds_named(x, (a, b)); quote a + b end; end fds_named(1, (2, 3)) """; expr_compat_mode) == 5 # Same, with the generated body built as an explicit `Expr(:block, ...)`. @test JL.include_string(test_mod, raw""" @generated function fds_exprblock(x, (a, b)); Expr(:block, :(a + b)); end fds_exprblock(1, (2, 3)) """; expr_compat_mode) == 5 # Nested destructuring. @test JL.include_string(test_mod, raw""" @generated function fds_nested(x, (a, (b, c))); quote a + b + c end; end fds_nested(1, (2, (3, 4))) """; expr_compat_mode) == 9 # Destructured first argument. @test JL.include_string(test_mod, raw""" @generated function fds_first((a, b)); quote a + b end; end fds_first((2, 3)) """; expr_compat_mode) == 5 # Positional vararg after a destructured argument. @test JL.include_string(test_mod, raw""" @generated function fds_va((a, b), xs...); quote a + b + length(xs) end; end fds_va((2, 3), 10, 20) """; expr_compat_mode) == 7 # Destructured argument alongside keyword arguments. @test JL.include_string(test_mod, raw""" @generated function fds_kw((a, b); k=0); quote a + b + k end; end fds_kw((2, 3); k=10) """; expr_compat_mode) == 15 # Multiple destructured args @test JL.include_string(test_mod, raw""" @generated function fds_multi((a, b), (c, d)); quote a + b + c + d end; end fds_multi((1, 2), (3, 4)) """; expr_compat_mode) == 10 end @testset "keyword args" begin genfunc_f = JL.include_string(test_mod, raw""" function (parg::Tuple{T}; kw) where {T} if @generated :($parg, $T, $kw, "gen") else :($parg, $T, $kw, "nongen") end end """; expr_compat_mode) @test genfunc_f((1,); kw=1) == (Tuple{Int}, Int, Int, "gen") @test_throws UndefKeywordError genfunc_f((1,)) genfunc_f = JL.include_string(test_mod, raw""" function (parg::Tuple{T}; kw::Vector{T}) where {T} if @generated :($parg, $T, $kw, "gen") else :($parg, $T, $kw, "nongen") end end """; expr_compat_mode) @test genfunc_f((1,); kw=[1]) == (Tuple{Int}, Int, Vector{Int}, "gen") @test_throws UndefKeywordError genfunc_f((1,)) @test_throws TypeError genfunc_f((1,); kw=1) genfunc_f = JL.include_string(test_mod, raw""" function (; kw::T, rkw...) where {T} if @generated :($T, $kw, $rkw, "gen") else :($T, $kw, $rkw, "nongen") end end """; expr_compat_mode) @test genfunc_f(; kw=1) == (Int, Int, Base.Pairs{Symbol, Union{}, Nothing, @NamedTuple{}}, "gen") @test genfunc_f(; kw=1, kw2=2) == (Int, Int, Base.Pairs{Symbol, Int, Nothing, @NamedTuple{kw2::Int}}, "gen") @test_throws UndefKeywordError genfunc_f() end @test JuliaLowering.include_string(test_mod, raw""" begin @generated function f_gen(x::NTuple{N,T}) where {N,T} quote ($x, $N, $T) end end f_gen((1,2,3,4,5)) end """; expr_compat_mode) == (NTuple{5,Int}, 5, Int) @test JuliaLowering.include_string(test_mod, """ begin @generated function f_gen_unnamed_args(::Type{T}, y, ::Type{U}) where {T, U} return (T, y, U) end f_gen_unnamed_args(Int, UInt8(3), Float64) end """; expr_compat_mode) == (Int, UInt8, Float64) @test JuliaLowering.include_string(test_mod, raw""" begin function f_partially_gen(x::NTuple{N,T}) where {N,T} shared = :shared_stuff if @generated if N == 2 error("intentionally broken codegen (will trigger nongen branch)") end quote unshared = (:gen, ($x, $N, $T)) end else unshared = (:nongen, (typeof(x), N, T)) end (shared, unshared) end (f_partially_gen((1,2)), f_partially_gen((1,2,3,4,5))) end """; expr_compat_mode) == ((:shared_stuff, (:nongen, (NTuple{2,Int}, 2, Int))), (:shared_stuff, (:gen, (NTuple{5,Int}, 5, Int)))) @test JuliaLowering.include_string(test_mod, raw""" begin @generated function f_gen_calls_macros(x::T) where {T} s = @raw_str "foo" :(@raw_str $s) end f_gen_calls_macros(1) end """; expr_compat_mode) === "foo" @test JuliaLowering.include_string(test_mod, raw"""begin @generated function calls_versioned_macro(::Type{T}, ::Val{i}) where {T, i} i isa Integer || @goto err return i @label err return 0 end calls_versioned_macro(Tuple{Int}, Val(1)) end """; expr_compat_mode) == 1 # (AI) pkgeval reduction: `(. value macroname)` should work @test (let m = Module() jl_eval(m, :(module RM5b; macro mm(ex); esc(ex); end; end); expr_compat_mode=true) rm = Core.eval(m, :RM5b) jl_eval(m, quote @generated function fr5b(x) Expr(:macrocall, Expr(:., $rm, QuoteNode(Symbol("@mm"))), LineNumberNode(1), :(x[1])) end end; expr_compat_mode=true) jl_eval(m, :(fr5b([7, 8])); expr_compat_mode=true) end) == 7 @testset "(AI) anonymous args promoted by optional/keyword args" begin # A `@generated` method with >=2 anonymous args (`::T` or `_`) whose # placeholder slots get promoted to `#arg#` identifiers because the # method also has an optional positional or keyword arg used to fail at # first call with "function argument name not unique". @test JuliaLowering.include_string(test_mod, raw""" begin @generated function g_anon_opt(x, ::Val{A}=Val(false), ::Val{B}=Val(false)) where {A,B} :( (x, A, B) ) end g_anon_opt(1) end """; expr_compat_mode) === (1, false, false) # calling the same function at two different type instantiations @test JuliaLowering.include_string(test_mod, raw""" (g_anon_opt(1, Val(:a), Val(:b)), g_anon_opt(2.0, Val(3))) """; expr_compat_mode) === ((1, :a, :b), (2.0, 3, false)) # 2 anonymous required args forced by an unrelated keyword arg @test JuliaLowering.include_string(test_mod, raw""" begin @generated function g_anon_kw(::Val{A}, ::Val{B}; kw=1) where {A,B} :( (A, B, kw) ) end g_anon_kw(Val(1), Val(2)) end """; expr_compat_mode) === (1, 2, 1) @test JuliaLowering.include_string(test_mod, "g_anon_kw(Val(1), Val(2); kw=5)"; expr_compat_mode) === (1, 2, 5) # underscore args (also anonymous), forced by a default @test JuliaLowering.include_string(test_mod, raw""" begin @generated function g_anon_underscore(_, _, z=10) :( z ) end g_anon_underscore(:a, :b) end """; expr_compat_mode) === 10 # named + anonymous mix, with the body reading the named arg while the # generator also uses the where-params @test JuliaLowering.include_string(test_mod, raw""" begin @generated function g_named_anon(a, ::Val{A}, ::Val{B}=Val(0); k=7) where {A,B} :( (a, A, B, k) ) end g_named_anon("hi", Val(1)) end """; expr_compat_mode) === ("hi", 1, 0, 7) # single anonymous arg (no collision possible) still works with a kwarg @test JuliaLowering.include_string(test_mod, raw""" begin @generated function g_one_anon(x, ::Val{A}; k=3) where {A} :( (x, A, k) ) end g_one_anon(1, Val(2)) end """; expr_compat_mode) === (1, 2, 3) # Pathological: a user arg literally named `#arg#` (the promotion name) # must remain a real, body-referenceable slot -- the discriminator is a # metadata tag on promoted anonymous args, not a name match. @test JuliaLowering.include_string(test_mod, raw""" begin @generated function g_user_hasharg(var"#arg#", ::Val{A}=Val(0)) where {A} :( (var"#arg#", A) ) end g_user_hasharg(5) end """; expr_compat_mode) === (5, 0) end @testset "hygiene in generated functions" begin # (AI) A generator whose returned body is a bare macrocall to an # old-style macro that re-wraps `esc`'d fragments in a freshly-built, # unescaped `Expr` (e.g. `Base.Cartesian.@nif`), referencing the # generated function's own arguments and static parameters. Those # escaped references unwind to the generator's base layer, so the # synthesized argument/sparam names of the staged method must live in # that same layer -- otherwise they resolve as bogus module globals # (`UndefVarError`). @test JuliaLowering.include_string(test_mod, raw""" begin @generated function find_first_eq(x, itr::I) where { N, I <: Tuple{Vararg{Any, N}} } return :(Base.Cartesian.@nif $(N + 1) d -> (x == getfield(itr, d)) d -> (d) d -> (nothing)) end (find_first_eq(20, (10, 20, 30)), find_first_eq(99, (10, 20, 30))) end"""; expr_compat_mode) === (2, nothing) @test JuliaLowering.include_string(test_mod, raw"""begin @generated function nif_uses_sparam(x, ::Type{T}) where {T} return :(Base.Cartesian.@nif 2 d -> (x isa T) d -> (T) d -> (nothing)) end nif_uses_sparam(1, Int) end"""; expr_compat_mode) === Int end end genfunc_quote_s = """ begin function f_gen_quote_1(::Tuple{T}) where {T} out = @legacy_quote_to_syntax :(:x1,first) if @generated else end return out end f_gen_quote_1((1,)) end """ @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=true) == :(:x1,first) @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=false) ≈ @ast_ [K"tuple" [K"inert" "x1"::K"Identifier"] "first"::K"Identifier"] genfunc_quote_s = """ begin function f_gen_quote_2(::Tuple{T}) where {T} out = nothing if @generated @legacy_quote_to_syntax :(out = @legacy_quote_to_syntax :(:x2,generated)) else out = (:x2,nongen) end return out end f_gen_quote_2((1,)) end """ @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=true) == :(:x2,generated) @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=false) ≈ @ast_ [K"tuple" [K"inert" "x2"::K"Identifier"] "generated"::K"Identifier"] genfunc_quote_s = """ begin function f_gen_quote_3(::Tuple{T}) where {T} if @generated else end return @legacy_quote_to_syntax :(:x4,after) end f_gen_quote_3((1,)) end """ @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=true) == :(:x4,after) @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=false) ≈ @ast_ [K"tuple" [K"inert" "x4"::K"Identifier"] "after"::K"Identifier"] genfunc_quote_s = raw""" begin function f_gen_interpolate(::Tuple{T}) where {T} out = :(:x1,first) if @generated out = @legacy_quote_to_syntax :($out, generated) else out = @legacy_quote_to_syntax :($out, nongen) end return out end f_gen_interpolate((1,)) end """ @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=true) == :((:x1,first),nongen) @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=false) ≈ @ast_ [K"tuple" [K"tuple" [K"inert" "x1"::K"Identifier"] "first"::K"Identifier"] "nongen"::K"Identifier"] genfunc_quote_s = raw""" begin @eval function f_gen_eval_quote_1(::Tuple{T}) where {T} out = $(Expr(:quote, Expr(:call, :+, 1, Expr(:if, Expr(:generated), 1, 2)))) if @generated else end return out end f_gen_eval_quote_1((1,)) end """ @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=true) == :(1 + $(Expr(:if, Expr(:generated), 1, 2))) @test JuliaLowering.include_string( test_mod, genfunc_quote_s; expr_compat_mode=false) == :(1 + $(Expr(:if, Expr(:generated), 1, 2))) # Test generated function edges to bindings # (see also https://github.com/JuliaLang/julia/pull/57230) JuliaLowering.include_string(test_mod, raw""" const delete_me = 4 @generated f_generated_return_delete_me() = return quote; delete_me; end """) @test test_mod.f_generated_return_delete_me() == 4 Base.delete_binding(test_mod, :delete_me) @test_throws UndefVarError test_mod.f_generated_return_delete_me() end @testset "Broadcast" begin @test JuliaLowering.include_string(test_mod, """ let x = [1,2], y = [3,4], z = [5,6] x .* y .+ z end """) == [8, 14] @test JuliaLowering.include_string(test_mod, """ let nums = [1, 2, 3] string.(nums, base=2; pad=2) end """) == ["01", "10", "11"] @test JuliaLowering.include_string(test_mod, """ let lhs = [0,0], x = [1,2], y = [3,4], z = [5,6] lhs .= x .* y .+ z lhs end """) == [8, 14] @test JuliaLowering.include_string(test_mod, """ [1,2] .+ ([3,4] .< [5,6] .< [7,1]) """) == [2, 2] @test JuliaLowering.include_string(test_mod, """ let x = [0,0,0,0] x[begin+1:end-1] .= [1,2] .+ [3,4] x end """) == [0,4,6,0] end @testset "method table overlays" begin OverlayModule = @newmod() @eval OverlayModule Base.Experimental.@MethodTable mt @test JL.include_string(OverlayModule, """ Base.Experimental.@overlay mt function sin(x::Float64); 1; end """) isa Method @test JL.include_string(OverlayModule, """ Base.Experimental.@overlay mt cos(x::Float64) = 2 """) isa Method @test JL.include_string(OverlayModule, """ Base.Experimental.@overlay mt tan(x::T) where {T} = 3 """) isa Method let ms = Base._methods_by_ftype( Tuple{typeof(sin), Float64}, nothing, 1, Base.get_world_counter()) @test only(ms).method.module === Base.Math end let ms = Base._methods_by_ftype( Tuple{typeof(sin), Float64}, OverlayModule.mt, 1, Base.get_world_counter()) @test only(ms).method.module === OverlayModule end let ms = Base._methods_by_ftype( Tuple{typeof(sin), Int}, OverlayModule.mt, 1, Base.get_world_counter()) @test isempty(ms) end # anything may go in first arg @test JL.include_string(OverlayModule, """ Base.Experimental.@overlay @__MODULE__().mt identity(x::Int) = x """) isa Method let ms = Base._methods_by_ftype( Tuple{typeof(identity), Int}, nothing, 1, Base.get_world_counter()) @test only(ms).method.module === Base end end