/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
JuliaLowering/test/decls.jl
1 222 строки
45 KB
Em Chu
[JuliaLowering] Bugfix batch (#62558)
29 июл 2026, 23:45
Не верифицирован
29 июл 2026, 23:45
0e259a7
Код
Авторство
О чём код?
test_mod = Module() @test JuliaLowering.include_string(test_mod, """ begin local x::Int = 1.0 x end """) === 1 # In value position, yield the right hand side, not `x` @test JuliaLowering.include_string(test_mod, """ begin local x::Int = 1.0 end """) === 1.0 @testset "global decl in value position" begin # Global decl in value position without assignment returns nothing @test JuliaLowering.include_string(test_mod, "global x_no_assign") === nothing # In tail position in a function is OK; returns nothing @test JuliaLowering.include_string(test_mod, """ function f_tail_global_decl() global x_tail_decl end f_tail_global_decl() """) === nothing @test Base.binding_kind(test_mod, :x_tail_decl) == Base.PARTITION_KIND_DECLARED @test JuliaLowering.include_string(test_mod, """ function f_tail_global_decl_2(b) if b global x_tail_decl2, x_tail_decl3 end end (f_tail_global_decl_2(true), f_tail_global_decl_2(false)) """) === (nothing, nothing) @test JuliaLowering.include_string(test_mod, "(() -> (global x_tail_decl4))()") === nothing # non-simple is fine to read from for some reason @test JuliaLowering.include_string( test_mod, "_ = global tail_decl_typed::Int") === nothing @test Base.binding_kind(test_mod, :tail_decl_typed) == Base.PARTITION_KIND_GLOBAL @test JuliaLowering.include_string( test_mod, "_ = global _______________::Int") === nothing # disallowed in value position otherwise @test_throws LoweringError JuliaLowering.include_string(test_mod, """ function f_value_global_decl() y = (global x_value_decl) end """) end # Unadorned declarations @test JuliaLowering.include_string(test_mod, """ let a = 0.0 x::Int = a x end """) === 0 @test JuliaLowering.include_string(test_mod, """ let local x::Int = 1 x1 = x x = 20.0 x2 = x (x1,x2) end """) === (1, 20) @testset "Global const mixes" for (mod, run) in [(Module(), fl_eval), (Module(), jl_eval)] @gensym sym @test run(mod, :(const $sym = 1)) === 1 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 @gensym sym @test_broken run(mod, :(const $sym::Int = 1.0)) === 1.0 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 @testset for wrap in [identity, x->Expr(:let, Expr(:block), Expr(:block, x))] @gensym sym @test run(mod, wrap(:(global $sym = 1))) === 1 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test !Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 @gensym sym @test run(mod, wrap(:(global $sym::Int = 1.0))) === 1.0 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test !Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 @gensym sym @test run(mod, wrap(:(global const $sym = 1))) === 1 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 @gensym sym @test_broken run(mod, wrap(:(global const $sym::Int = 1.0))) === 1.0 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 @gensym sym @test run(mod, wrap(:(const global $sym = 1))) === 1 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 @gensym sym @test_broken run(mod, wrap(:(const global $sym::Int = 1.0))) === 1.0 Core.@latestworld @test Base.isdefinedglobal(mod, sym) @test Base.isconst(mod, sym) @test getproperty(mod, sym) === 1 end end # lowering is strict about the nesting order where parsing is not @test_throws LoweringError jl_eval(test_mod, Expr(:global, Expr(:const, Expr(:(=), :a, 1)))) @test_throws ErrorException fl_eval(test_mod, Expr(:global, Expr(:const, Expr(:(=), :a, 1)))) # Possibly worth testing excessive global/const keywords or invalid combinations # (local + global/const) once we decide whether that's a parse error or a # lowering error # Global decls with types @test JuliaLowering.include_string(test_mod, """ global a_typed_global::Int = 10.0 """) === 10.0 @test Core.get_binding_type(test_mod, :a_typed_global) === Int @test test_mod.a_typed_global === 10 @test JuliaLowering.include_string(test_mod, """ global a_curly_typed_global::Union{Int, Float64} = 10.0 """) === 10.0 @test Core.get_binding_type(test_mod, :a_curly_typed_global) === Union{Int, Float64} @test test_mod.a_curly_typed_global === 10.0 @test JuliaLowering.include_string(test_mod, """ begin global opassign_global = 1 global opassign_global += 1 end """) === 2 @test test_mod.opassign_global === 2 @test JuliaLowering.include_string(test_mod, """ begin global dotopassign_global = [1,2,3] global dotopassign_global .+= 1 end """) == [2,3,4] @test test_mod.dotopassign_global == [2,3,4] # Also allowed in nontrivial scopes in a top level thunk @test JuliaLowering.include_string(test_mod, """ let global a_typed_global_2::Int = 10.0 end """) === 10.0 @test Core.get_binding_type(test_mod, :a_typed_global_2) === Int @test test_mod.a_typed_global_2 === 10 @test JuliaLowering.include_string(test_mod, """ let global a_curly_typed_global_2::Union{Int, Float64} = 10.0 end """) === 10.0 @test Core.get_binding_type(test_mod, :a_curly_typed_global_2) === Union{Int, Float64} @test test_mod.a_curly_typed_global_2 === 10.0 @test JuliaLowering.include_string(test_mod, """ begin global opassign_global_t::Int = 1 global opassign_global_t::Int += 1.0 end """) === 2.0 @test Core.get_binding_type(test_mod, :opassign_global_t) === Int @test test_mod.opassign_global_t === 2 @test JuliaLowering.include_string(test_mod, """ begin global dotopassign_global_t::Vector{Int} = [1,2,3] global dotopassign_global_t::Vector{Int} .+= [1.0,2.0,3.0] end """) == [2.0,4.0,6.0] @test Core.get_binding_type(test_mod, :dotopassign_global_t) === Vector{Int} @test test_mod.dotopassign_global_t == [2,4,6] @test JuliaLowering.include_string(test_mod, "const x_c_T::Int = 9") === 9 @test Base.isdefinedglobal(test_mod, :x_c_T) @test Base.isconst(test_mod, :x_c_T) @testset "typed const redeclaration" begin # redeclaration of the same value used to be allowed @test_throws ErrorException JuliaLowering.include_string(test_mod, "x_c_T = 9") @test_throws ErrorException JuliaLowering.include_string(test_mod, "x_c_T = 10") # redeclaration with const should be OK @test JuliaLowering.include_string(test_mod, "const x_c_T::Int = 0") === 0 end # Tuple/destructuring assignments @test JuliaLowering.include_string(test_mod, "(a0, a1, a2) = [1,2,3]") == [1,2,3] @test JuliaLowering.include_string(test_mod, "const a,b,c = 1,2,3") === (1, 2, 3) @testset "Placeholder decls" begin @test JuliaLowering.include_string(test_mod, "global _ = 1") === 1 @test JuliaLowering.include_string(test_mod, "global _::Int = 1") === 1 @test JuliaLowering.include_string(test_mod, "let; local _; _ = 1; end") === 1 @test JuliaLowering.include_string(test_mod, "let; local _::Int = 1; end") === 1 @test JuliaLowering.include_string(test_mod, "let; local (a0, _, a2) = [1,2,3]; end") == [1,2,3] @test JuliaLowering.include_string(test_mod, "let; local (a0, _::Int, a2) = [1,2,3]; end") == [1,2,3] @test JuliaLowering.include_string(test_mod, "_{x} = Vector{x}") == Vector @test !Base.isdefinedglobal(test_mod, :_) end test_mod_2 = Module() @testset "toplevel-preserving syntax" begin JuliaLowering.include_string(test_mod_2, "if true; global v1::Bool; else const v1 = 1; end") @test !isdefined(test_mod_2, :v1) @test Base.binding_kind(test_mod_2, :v1) == Base.PARTITION_KIND_GLOBAL @test Core.get_binding_type(test_mod_2, :v1) == Bool JuliaLowering.include_string(test_mod_2, "if false; global v2::Bool; else const v2 = 2; end") @test test_mod_2.v2 === 2 @test Base.binding_kind(test_mod_2, :v2) == Base.PARTITION_KIND_CONST JuliaLowering.include_string(test_mod_2, "v3 = if true; global v4::Bool; 4 else const v4 = 5; 6; end") @test test_mod_2.v3 == 4 @test !isdefined(test_mod_2, :v4) @test Base.binding_kind(test_mod_2, :v4) == Base.PARTITION_KIND_GLOBAL @test Core.get_binding_type(test_mod_2, :v4) == Bool JuliaLowering.include_string(test_mod_2, "v5 = if false; global v6::Bool; 4 else const v6 = 5; 6; end") @test test_mod_2.v5 === 6 @test test_mod_2.v6 === 5 @test Base.binding_kind(test_mod_2, :v6) == Base.PARTITION_KIND_CONST end @testset "decls on functions" begin # local @gensym func func2 @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(:local, Expr(:(=), Expr(:call, func, :x), :x), Expr(:(=), Expr(:call, func2, :y), :(y+1))), Expr(:tuple, Expr(:call, func, 1), Expr(:call, func2, 1)))) @test jl_eval(test_mod, ex) == (1, 2) @test !isdefined(test_mod, func) @test !isdefined(test_mod, func2) end # const @gensym func func2 @testset let ex = Expr(:const, Expr(:(=), Expr(:call, func, :x), :x)) @test jl_eval(test_mod, ex) isa Function Core.@latestworld @test getproperty(test_mod, func)(1) == 1 end # global @gensym func func2 @testset let ex = Expr(:global, Expr(:(=), Expr(:call, func, :x), :x), Expr(:(=), Expr(:call, func2, :y), :(y+1))) @test jl_eval(test_mod, ex) isa Function Core.@latestworld @test getproperty(test_mod, func)(1) == 1 @test getproperty(test_mod, func2)(1) == 2 end # const global 1-arg @gensym func func2 @testset let ex = Expr(:const, Expr(:global, Expr(:(=), Expr(:call, func, :x), :x))) @test jl_eval(test_mod, ex) isa Function Core.@latestworld @test getproperty(test_mod, func)(1) == 1 end # const global 2-arg (should probably disallow) @gensym func func2 @testset let ex = Expr(:const, Expr(:global, Expr(:(=), Expr(:call, func, :x), :x), Expr(:(=), Expr(:call, func2, :y), :(y+1)))) @test_broken jl_eval(test_mod, ex) isa Function Core.@latestworld @test_broken getproperty(test_mod, func)(1) == 1 # also broken in flisp (func2 doesn't get defined) @test_broken getproperty(test_mod, func2)(1) == 2 end # global in local scope @gensym func func2 @testset let ex = Expr(:let, Expr(:block), Expr(:global, Expr(:(=), Expr(:call, func, :x), :x), Expr(:(=), Expr(:call, func2, :y), :(y+1)))) @test jl_eval(test_mod, ex) isa Function Core.@latestworld @test getproperty(test_mod, func)(1) == 1 @test getproperty(test_mod, func2)(1) == 2 end @gensym func func2 @testset let ex = Expr(:let, Expr(:block), Expr(:const, Expr(:global, Expr(:(=), Expr(:call, func, :x), :x)))) @test jl_eval(test_mod, ex) isa Function Core.@latestworld @test getproperty(test_mod, func)(1) == 1 end # const global in local scope 2-arg (should probably disallow) @gensym func func2 @testset let ex = Expr(:let, Expr(:block), Expr(:const, Expr(:global, Expr(:(=), Expr(:call, func, :x), :x), Expr(:(=), Expr(:call, func2, :y), :(y+1))))) @test_broken jl_eval(test_mod, ex) isa Function Core.@latestworld @test_broken getproperty(test_mod, func)(1) == 1 # also broken in flisp (func2 doesn't get defined) @test_broken getproperty(test_mod, func2)(1) == 2 end end @testset "all non-call assignment forms within global, local" for declkind in (:local, :global) # basic form @gensym sym @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(declkind, Expr(:(=), sym, 1)), Expr(:tuple, sym))) @test jl_eval(test_mod, ex) == (1,) Core.@latestworld if declkind === :global @test getproperty(test_mod, sym) == 1 else @test !isdefined(test_mod, sym) end end # setproperty form: decl is ignored (this is misleading, syntax TODO) @gensym sym @eval test_mod mutable struct with_mutable_a; a; end @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(:(=), sym, :(with_mutable_a(1))), Expr(declkind, Expr(:(=), Expr(:., sym, QuoteNode(:a)), 2)), sym)) @test jl_eval(test_mod, ex).a == 2 Core.@latestworld @test !isdefined(test_mod, sym) end # ref form: decl is ignored, but assignment works (syntax TODO) @gensym sym @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(declkind, Expr(:(=), Expr(:ref, sym), 0)))) @test_throws UndefVarError jl_eval(test_mod, ex) Core.@latestworld @test !isdefined(test_mod, sym) end @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(:(=), sym, [1,2,3]), Expr(declkind, Expr(:(=), Expr(:ref, sym, 2), 0)), Expr(:tuple, sym))) @test jl_eval(test_mod, ex) == ([1,0,3],) Core.@latestworld @test !isdefined(test_mod, sym) end # chained, decl on first @gensym sym1 sym2 sym3 @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(declkind, Expr(:(=), sym1, Expr(:(=), sym2, Expr(:(=), sym3, :(gensym()))))), Expr(:tuple, sym1, sym2, sym3))) res = jl_eval(test_mod, ex) Core.@latestworld @test res isa Tuple @test res[1] == res[2] == res[3] if declkind === :global @test isdefined(test_mod, sym1) else @test !isdefined(test_mod, sym1) end @test !isdefined(test_mod, sym2) @test !isdefined(test_mod, sym3) end # decl sym += val @gensym sym @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(declkind, Expr(:(=), sym, 1)), Expr(declkind, Expr(:(+=), sym, 2)), Expr(:tuple, sym))) @test jl_eval(test_mod, ex) == (3,) Core.@latestworld if declkind === :global @test getproperty(test_mod, sym) == 3 else @test !isdefined(test_mod, sym) end end # decl sym .= val @gensym sym @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(declkind, Expr(:(=), sym, [1,2,3])), Expr(declkind, Expr(:(.=), sym, 0)), Expr(:tuple, sym))) @test jl_eval(test_mod, ex) == ([0,0,0],) Core.@latestworld if declkind === :global @test getproperty(test_mod, sym) == [0,0,0] else @test !isdefined(test_mod, sym) end end # decl sym .+= val @gensym sym @testset let ex = Expr(:let, Expr(:block), Expr(:block, Expr(declkind, Expr(:(=), sym, [1,2,3])), Expr(declkind, Expr(:(.+=), sym, [4,5,6])), Expr(:tuple, sym))) @test jl_eval(test_mod, ex) == ([5,7,9],) Core.@latestworld if declkind === :global @test getproperty(test_mod, sym) == [5,7,9] else @test !isdefined(test_mod, sym) end end end @testset "all non-call non-globalref assignment forms within `const`" begin # prohibited by parsing as of writing this, so hard to make into an IR test ex = Expr(:const, Expr(:(.=), :x, 1)) @test_throws LoweringError jl_lower(test_mod, ex) ex = Expr(:const, Expr(:(+=), :x, 1)) @test_throws LoweringError jl_lower(test_mod, ex) ex = Expr(:const, Expr(:(.+=), :x, 1)) @test_throws LoweringError jl_lower(test_mod, ex) # placeholder @test jl_eval(test_mod, :(const _ = 1)) === 1 @test jl_eval(test_mod, :(const _ = _ = __ = ___ = 1)) === 1 @test jl_eval(test_mod, :(const _::Int = 1.0)) === 1.0 @test jl_eval(test_mod, :(const _{x} = Vector{x})) == Vector @test jl_eval(test_mod, :(const (_, _::Int, _{x}) = 1, 2, Vector)) == (1, 2, Vector) Core.@latestworld @test !Base.isdefinedglobal(Main, :_) @test !Base.isdefinedglobal(Main, :__) @test !Base.isdefinedglobal(Main, :___) # pre-desugared const @gensym sym ex = Expr(:const, sym, 1) @test jl_eval(test_mod, ex) == 1 @test Base.binding_kind(test_mod, sym) == Base.PARTITION_KIND_CONST # chained, const first @gensym sym1 sym2 sym3 @testset let ex = Expr(:const, Expr(:(=), sym1, Expr(:(=), sym2, Expr(:(=), sym3, :(gensym()))))) @test jl_eval(test_mod, ex) isa Symbol Core.@latestworld @test Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym2) == Base.PARTITION_KIND_GLOBAL @test Base.binding_kind(test_mod, sym3) == Base.PARTITION_KIND_GLOBAL @test getproperty(test_mod, sym1) == getproperty(test_mod, sym2) == getproperty(test_mod, sym3) end # chained, const first, with types @gensym sym1 sym2 sym3 @testset let ex = Expr(:const, Expr(:(=), Expr(:(::), sym1, :Symbol), Expr(:(=), Expr(:(::), sym2, :Symbol), Expr(:(=), Expr(:(::), sym3, :Symbol), :(gensym()))))) @test_broken jl_eval(test_mod, ex) isa Symbol Core.@latestworld @test_broken Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test_broken Base.binding_kind(test_mod, sym2) == Base.PARTITION_KIND_GLOBAL @test_broken Base.binding_kind(test_mod, sym3) == Base.PARTITION_KIND_GLOBAL # also broken in flisp (sym1 has type Any, others are Symbol) @test_broken Core.get_binding_type(test_mod, sym1) == Symbol @test_broken Core.get_binding_type(test_mod, sym2) == Symbol @test_broken Core.get_binding_type(test_mod, sym3) == Symbol @test_broken getproperty(test_mod, sym1) == getproperty(test_mod, sym2) == getproperty(test_mod, sym3) end # chained, const all @gensym sym1 sym2 sym3 @testset let ex = Expr(:const, Expr(:(=), sym1, Expr(:const, Expr(:(=), sym2, Expr(:const, Expr(:(=), sym3, :(gensym()))))))) @test jl_eval(test_mod, ex) isa Symbol Core.@latestworld @test Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym2) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym3) == Base.PARTITION_KIND_CONST @test getproperty(test_mod, sym1) == getproperty(test_mod, sym2) == getproperty(test_mod, sym3) end # destructured @gensym sym1 sym2 sym3 @testset let ex = :(const ($sym1, ($sym2, $sym3)) = (1, (2, 3))) @test jl_eval(test_mod, ex) == (1, (2, 3)) Core.@latestworld @test Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym2) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym3) == Base.PARTITION_KIND_CONST @test getproperty(test_mod, sym1) == 1 @test getproperty(test_mod, sym2) == 2 @test getproperty(test_mod, sym3) == 3 end # destructured, with types @gensym sym1 sym2 sym3 @testset let ex = :(const ($sym1::Int, ($sym2::Int, $sym3::Int)) = (1, (2, 3))) @test jl_eval(test_mod, ex) == (1, (2, 3)) Core.@latestworld @test Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym2) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym3) == Base.PARTITION_KIND_CONST @test getproperty(test_mod, sym1) == 1 @test getproperty(test_mod, sym2) == 2 @test getproperty(test_mod, sym3) == 3 # note flisp also doesn't set binding types, though it does for globals @test Core.get_binding_type(test_mod, sym1) == Any @test Core.get_binding_type(test_mod, sym2) == Any @test Core.get_binding_type(test_mod, sym3) == Any end # destructured, nested NamedTuple @gensym sym1 sym2 sym3 @testset let ex = :(const ($sym1, (;$sym2, $sym3)) = (1, (;$sym2=2, $sym3=3))) @test jl_eval(test_mod, ex) == (1, (;sym2=>2, sym3=>3)) Core.@latestworld @test Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test_broken Base.binding_kind(test_mod, sym2) == Base.PARTITION_KIND_CONST @test_broken Base.binding_kind(test_mod, sym3) == Base.PARTITION_KIND_CONST @test getproperty(test_mod, sym1) == 1 @test getproperty(test_mod, sym2) == 2 @test getproperty(test_mod, sym3) == 3 end # destructured, slurp @gensym sym1 sym2 sym3 @testset let ex = :(const ($sym1, $sym2..., $sym3) = (1, 2, 22, 222, 3)) @test jl_eval(test_mod, ex) == (1, 2, 22, 222, 3) Core.@latestworld @test Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym2) == Base.PARTITION_KIND_CONST @test Base.binding_kind(test_mod, sym3) == Base.PARTITION_KIND_CONST @test getproperty(test_mod, sym1) == 1 @test getproperty(test_mod, sym2) == (2, 22, 222) @test getproperty(test_mod, sym3) == 3 end # curly @gensym sym1 sym2 @testset let ex = Expr(:const, Expr(:(=), Expr(:curly, sym1, sym2), Expr(:curly, Vector, sym2))) @test jl_eval(test_mod, ex) == Vector Core.@latestworld @test Base.binding_kind(test_mod, sym1) == Base.PARTITION_KIND_CONST @test !Base.isdefinedglobal(test_mod, sym2) @test getproperty(test_mod, sym1){Int} == Vector{Int} end end gr_mod = Module() @testset "GlobalRef as an identifier" begin # gr = 1 @gensym sym @test 1 == jl_eval(test_mod, Expr(:(=), GlobalRef(gr_mod, sym), 1)) @test Base.isdefinedglobal(gr_mod, sym) @test getproperty(gr_mod, sym) == 1 @test !Base.isdefinedglobal(test_mod, sym) # test gr as a value @test 1 == jl_eval(test_mod, Expr(:block, GlobalRef(gr_mod, sym))) # gr resolves when a similar local is in scope @gensym sym Base.eval(gr_mod, Expr(:(=), sym, "gr")) @test ("let-local", "gr") == jl_eval( test_mod, Expr(:let, Expr(:block, Expr(:(=), sym, "let-local")), Expr(:tuple, sym, GlobalRef(gr_mod, sym)))) @test !Base.isdefinedglobal(test_mod, sym) @test ("let-local", "gr reassigned") == jl_eval( test_mod, Expr(:let, Expr(:block, Expr(:(=), sym, "let-local")), Expr(:block, Expr(:(=), GlobalRef(gr_mod, sym), "gr reassigned"), Expr(:tuple, sym, GlobalRef(gr_mod, sym))))) @test !Base.isdefinedglobal(test_mod, sym) @test getproperty(gr_mod, sym) == "gr reassigned" @test ("let-local", "gr reassigned twice") == jl_eval( test_mod, Expr(:let, Expr(:block, Expr(:(=), sym, "let-local")), Expr(:block, Expr(:(*=), GlobalRef(gr_mod, sym), " twice"), Expr(:tuple, sym, GlobalRef(gr_mod, sym))))) @test !Base.isdefinedglobal(test_mod, sym) @test getproperty(gr_mod, sym) == "gr reassigned twice" @test ("lambda-local", "gr reassigned twice") == jl_eval( test_mod, Expr(:let, Expr(:block, Expr(:(=), sym, "let-local")), Expr(:call, Expr(:->, Expr(:tuple, Expr(:kw, sym, "lambda-local")), Expr(:block, Expr(:tuple, sym, GlobalRef(gr_mod, sym))))))) @test !Base.isdefinedglobal(test_mod, sym) # gr1 = gr2 = gr3 = gr4 = 1 @gensym sym1 sym2 sym3 sym4 @test 1 == jl_eval( test_mod, Expr(:(=), GlobalRef(gr_mod, sym1), Expr(:(=), GlobalRef(gr_mod, sym2), Expr(:(=), GlobalRef(gr_mod, sym3), Expr(:(=), GlobalRef(gr_mod, sym4), 1))))) @test Base.isdefinedglobal(gr_mod, sym1) @test Base.isdefinedglobal(gr_mod, sym2) @test Base.isdefinedglobal(gr_mod, sym3) @test Base.isdefinedglobal(gr_mod, sym4) @test getproperty(gr_mod, sym1) == 1 @test getproperty(gr_mod, sym2) == 1 @test getproperty(gr_mod, sym3) == 1 @test getproperty(gr_mod, sym4) == 1 @test !Base.isdefinedglobal(test_mod, sym1) @test !Base.isdefinedglobal(test_mod, sym2) @test !Base.isdefinedglobal(test_mod, sym3) @test !Base.isdefinedglobal(test_mod, sym4) # gr += 5 @gensym sym jl_eval(test_mod, Expr(:(=), GlobalRef(gr_mod, sym), 10)) @test 15 == jl_eval( test_mod, Expr(:(+=), GlobalRef(gr_mod, sym), 5)) @test getproperty(gr_mod, sym) == 15 # (gr1, gr2) = (1, 2) @gensym sym1 sym2 @test (1, 2) == jl_eval( test_mod, Expr(:(=), Expr(:tuple, GlobalRef(gr_mod, sym1), GlobalRef(gr_mod, sym2)), Expr(:call, :tuple, 1, 2))) @test getproperty(gr_mod, sym1) == 1 @test getproperty(gr_mod, sym2) == 2 @test !Base.isdefinedglobal(test_mod, sym1) # global gr::Int = 1 @gensym sym @test 1 == jl_eval( test_mod, Expr(:global, Expr(:(=), Expr(:(::), GlobalRef(gr_mod, sym), Int), 1))) @test Base.isdefinedglobal(gr_mod, sym) @test Core.get_binding_type(gr_mod, sym) == Int @test getproperty(gr_mod, sym) == 1 @test !Base.isdefinedglobal(test_mod, sym) # global gr::Int @gensym sym @test nothing == jl_eval( test_mod, Expr(:global, Expr(:(::), GlobalRef(gr_mod, sym), Int))) @test Core.get_binding_type(gr_mod, sym) == Int # const gr = 1 @gensym sym @test 1 == jl_eval( test_mod, Expr(:const, Expr(:(=), GlobalRef(gr_mod, sym), 1))) @test Base.isdefinedglobal(gr_mod, sym) @test getproperty(gr_mod, sym) == 1 @test Base.binding_kind(gr_mod, sym) == Base.PARTITION_KIND_CONST @test !Base.isdefinedglobal(test_mod, sym) # const gr::Int = 42 @gensym sym @test 42 == jl_eval( test_mod, Expr(:const, Expr(:(=), Expr(:(::), GlobalRef(gr_mod, sym), Int), 42))) @test Base.isdefinedglobal(gr_mod, sym) @test getproperty(gr_mod, sym) == 42 @test Base.binding_kind(gr_mod, sym) == Base.PARTITION_KIND_CONST @test !Base.isdefinedglobal(test_mod, sym) # local gr (error) @gensym sym @test_throws LoweringError jl_eval( test_mod, Expr(:local, GlobalRef(gr_mod, sym))) @test_throws LoweringError jl_eval( test_mod, Expr(:let, Expr(:block, Expr(:(=), GlobalRef(gr_mod, sym), 1)))) @test !Base.isdefinedglobal(test_mod, sym) # function gr end @gensym sym @test jl_eval(test_mod, Expr(:function, GlobalRef(gr_mod, sym))) isa Function @test Base.isdefinedglobal(gr_mod, sym) @test getproperty(gr_mod, sym) isa Function @test !Base.isdefinedglobal(test_mod, sym) # function gr(x); x; end @gensym sym @test jl_eval(test_mod, Expr(:function, Expr(:call, GlobalRef(gr_mod, sym), :x), Expr(:block, :x))) isa Function @test Base.isdefinedglobal(gr_mod, sym) @test getproperty(gr_mod, sym)(1) == 1 @test !Base.isdefinedglobal(test_mod, sym) # function gr(x;kw1,kw2=2); x; end @gensym sym @test jl_eval(test_mod, Expr(:function, Expr(:call, GlobalRef(gr_mod, sym), Expr(:parameters, :kw1, Expr(:kw, :kw2, 2)), :x), Expr(:block, Expr(:tuple, :x, :kw1, :kw2)))) isa Function @test Base.isdefinedglobal(gr_mod, sym) @test getproperty(gr_mod, sym)(0;kw1=1) == (0,1,2) @test getproperty(gr_mod, sym)(0;kw1=1,kw2=20) == (0,1,20) @test !Base.isdefinedglobal(test_mod, sym) # gr inner function (let) should act like global inner function @gensym sym @test jl_eval( test_mod, Expr(:let, Expr(:block, Expr(:(=), :a, 1), Expr(:(=), :b, 2)), Expr(:block, Expr(:function, Expr(:call, GlobalRef(gr_mod, sym), :c), Expr(:block, Expr(:tuple, :a, :b, :c)))))) isa Function @test Base.isdefinedglobal(gr_mod, sym) @test !Base.isdefinedglobal(test_mod, sym) @test getproperty(gr_mod, sym)(3) == (1,2,3) # error: gr inner function (function) should act like global inner function @gensym sym outer_f @test_throws LoweringError jl_eval( test_mod, Expr(:function, Expr(:call, outer_f), Expr(:block, Expr(:function, Expr(:call, GlobalRef(gr_mod, sym)), Expr(:block))))) # macro gr end @gensym sym mac_sym = Symbol("@"*string(sym)) @test jl_eval(test_mod, Expr(:macro, GlobalRef(gr_mod, sym))) isa Function @test Base.isdefinedglobal(gr_mod, mac_sym) @test !Base.isdefinedglobal(test_mod, mac_sym) # macro gr(x); (x, @__MODULE__); end # # should define the symbol in gr_mod, but the method (and expansion) are # attributed to test_mod, where the macro expression was evaluated. @gensym sym mac_sym = Symbol("@"*string(sym)) @test jl_eval(test_mod, Expr(:macro, Expr(:call, GlobalRef(gr_mod, sym), :x), Expr(:block, Expr(:tuple, :x, :(@__MODULE__())))); expr_compat_mode=true) isa Function @test Base.isdefinedglobal(gr_mod, mac_sym) @test !Base.isdefinedglobal(test_mod, mac_sym) @test jl_eval(gr_mod, :(@($mac_sym)(1))) == (1, test_mod) @testset "globalref as macrocall name" begin @test (1, test_mod) == jl_eval( test_mod, Expr(:macrocall, GlobalRef(gr_mod, mac_sym), LineNumberNode(1, :none), 1)) @test (1, test_mod) == jl_eval( gr_mod, Expr(:macrocall, GlobalRef(gr_mod, mac_sym), LineNumberNode(1, :none), 1)) # globalref(test_mod, mac_sym) should fail @test_throws MacroExpansionError jl_eval( test_mod, Expr(:macrocall, GlobalRef(test_mod, mac_sym), LineNumberNode(1, :none), 1)) @test_throws MacroExpansionError jl_eval( gr_mod, Expr(:macrocall, GlobalRef(test_mod, mac_sym), LineNumberNode(1, :none), 1)) end # error: begin; local gr = 1; end # (note: flisp allows this) @gensym sym @test_throws "cannot use GlobalRef as local identifier" jl_eval( test_mod, Expr(:block, Expr(:local, Expr(:(=), GlobalRef(gr_mod, sym), 1)))) @test !Base.isdefinedglobal(test_mod, sym) @test !Base.isdefinedglobal(gr_mod, sym) # error: let gr = 1; end # (note: flisp allows this) @gensym sym @test_throws "cannot use GlobalRef as local identifier" jl_eval( test_mod, Expr(:let, Expr(:block, Expr(:(=), GlobalRef(gr_mod, sym), 1)), Expr(:block))) # error: for gr = 1:3 # (note: flisp allows this) @gensym sym @test_throws "cannot use GlobalRef as local identifier" jl_eval( test_mod, Expr(:for, Expr(:(=), GlobalRef(gr_mod, sym), Expr(:call, :(:), 1, 3)), Expr(:block))) # error: function f(gr); end @gensym sym @test_throws "cannot use GlobalRef as local identifier" jl_eval( test_mod, Expr(:function, Expr(:call, :fname, GlobalRef(gr_mod, sym)), Expr(:block))) # error: try/catch with GlobalRef catch var @gensym sym @test_throws ErrorException fl_eval( test_mod, Expr(:try, Expr(:block, Expr(:call, :error, "oops")), GlobalRef(gr_mod, sym), Expr(:block, 1))) broken=true end @testset "All possible `let` forms" for run in [fl_eval, jl_eval], maybe_int in [identity, x->Expr(:(::), x, :Int)] # no-assignment forms @test run(test_mod, Expr(:let, maybe_int(:a), Expr(:tuple, Expr(:islocal, :a), Expr(:isdefined, :a)))) == (true, false) @test run(test_mod, Expr(:let, Expr(:block, maybe_int(:a)), Expr(:tuple, Expr(:islocal, :a), Expr(:isdefined, :a)))) == (true, false) @test run(test_mod, Expr(:let, Expr(:block, maybe_int(:a), maybe_int(:b), maybe_int(:c)), Expr(:tuple, Expr(:islocal, :a), Expr(:isdefined, :a), Expr(:islocal, :b), Expr(:isdefined, :b), Expr(:islocal, :c), Expr(:isdefined, :c)))) == (true, false, true, false, true, false) # placeholder should at least pass lowering # flisp bug: isdefined throws because `_` is assumed global @testset "placeholder" for p_inner in [maybe_int(:_), Expr(:(=), maybe_int(:_), 1)], p_block in [p_inner, Expr(:block, p_inner)] @test run(test_mod, Expr(:let, p_block, Expr(:block, Expr(:islocal, :_)))) == false end # assignment forms @test run(test_mod, Expr(:let, Expr(:(=), maybe_int(:a), 1), Expr(:tuple, Expr(:islocal, :a), :a))) == (true, 1) @test run(test_mod, Expr(:let, Expr(:block, Expr(:(=), maybe_int(:a), 1)), Expr(:tuple, Expr(:islocal, :a), :a))) == (true, 1) @test run(test_mod, Expr(:let, Expr(:block, Expr(:(=), maybe_int(:a), 10), Expr(:(=), maybe_int(:b), 20), Expr(:(=), maybe_int(:c), 30)), Expr(:tuple, Expr(:islocal, :a), :a, Expr(:islocal, :b), :b, Expr(:islocal, :c), :c))) == (true, 10, true, 20, true, 30) @test run(test_mod, Expr(:let, Expr(:block, Expr(:(=), Expr(:tuple, :a1, maybe_int(:a2), :a3), Expr(:tuple, 11, 12, 13)), Expr(:(=), Expr(:tuple, :b1, :b2, :b3, :_), Expr(:tuple, 21, 22, 23, 0)), Expr(:(=), Expr(:tuple, Expr(:parameters, :c1, maybe_int(:c2), :c3)), :((;c1=31, c2=32, c3=33)))), Expr(:tuple, Expr(:islocal, :a1), :a1, Expr(:islocal, :a2), :a2, Expr(:islocal, :a3), :a3, Expr(:islocal, :b1), :b1, Expr(:islocal, :b2), :b2, Expr(:islocal, :b3), :b3, Expr(:islocal, :c1), :c1, Expr(:islocal, :c2), :c2, Expr(:islocal, :c3), :c3, ))) == (true, 11, true, 12, true, 13, true, 21, true, 22, true, 23, true, 31, true, 32, true, 33) @test run(test_mod, Expr(:let, Expr(:block, Expr(:(=), Expr(:tuple, :a1, maybe_int(:a2), Expr(:..., :a3)), Expr(:tuple, 11, 12, 13, 14, 15)), Expr(:(=), Expr(:tuple, :b1, :b2, :b3, Expr(:..., :_)), Expr(:tuple, 21, 22, 23, 0, 0, 0))), Expr(:tuple, Expr(:islocal, :a1), :a1, Expr(:islocal, :a2), :a2, Expr(:islocal, :a3), :a3, Expr(:islocal, :b1), :b1, Expr(:islocal, :b2), :b2, Expr(:islocal, :b3), :b3, ))) == (true, 11, true, 12, true, (13, 14, 15), true, 21, true, 22, true, 23) # functions @test run(test_mod, Expr(:let, Expr(:(=), maybe_int(Expr(:call, :f)), 1), Expr(:tuple, Expr(:call, :f), Expr(:islocal, :f)))) == (1, true) @test run(test_mod, Expr(:let, Expr(:block, Expr(:(=), maybe_int(Expr(:call, :f)), 1), Expr(:(=), maybe_int(Expr(:call, :g)), 2)), Expr(:tuple, Expr(:call, :f), Expr(:islocal, :f), Expr(:call, :g), Expr(:islocal, :g)))) == (1, true, 2, true) @test run(test_mod, Expr(:let, Expr(:(=), Expr(:where, maybe_int(Expr(:call, :f, :(x::Int))), :Int), :x), Expr(:tuple, Expr(:call, :f, "foo"), Expr(:islocal, :f)))) == ("foo", true) @test run(test_mod, Expr(:let, Expr(:(=), Expr(:where, Expr(:where, maybe_int(Expr(:call, :f, :(x::Int), :(y::T))), :T), :Int), :(x*y)), Expr(:tuple, Expr(:call, :f, "x", "y"), Expr(:islocal, :f)))) == ("xy", true) end @testset "(AI) reassigned local with `<:`-bounded declared type" begin # A declared type containing a `<:` bound desugars to a `TypeVar(...)` # construction bound to an SSA value. The declared type is re-evaluated at # each assignment (like flisp), so each emission needs fresh SSA values. @test JuliaLowering.include_string(test_mod, """ function f() local x::Type{<:Real} = Int x = Float64 x end f() """) === Float64 # Bare declaration followed by two assignments @test JuliaLowering.include_string(test_mod, """ function f() local x::Type{<:Real} x = Int x = Float64 x end f() """) === Float64 # `Vector{<:Real}` variant @test JuliaLowering.include_string(test_mod, """ function f() local x::Vector{<:Real} = [1, 2, 3] x = [1.0] x end f() """) == [1.0] # Declaration + reassignment inside a `let` @test JuliaLowering.include_string(test_mod, """ let local y::Type{<:Real} = Int y = Float64 y end """) === Float64 # Captured typed local: the closure sees the reassigned value @test JuliaLowering.include_string(test_mod, """ function f() local x::Type{<:Real} = Int g = () -> x x = Float64 g() end f() """) === Float64 # Three-plus assignments @test JuliaLowering.include_string(test_mod, """ function f() local x::Type{<:Real} = Int x = Float32 x = Float64 x = Int8 x end f() """) === Int8 # The convert/typeassert is still enforced on every assignment @test_throws Exception JuliaLowering.include_string(test_mod, """ function f() local x::Type{<:Real} = Int x = String x end f() """) # A side-effecting declared-type expression is re-evaluated once per # assignment, exactly as flisp does @test JuliaLowering.include_string(test_mod, """ let side = Ref(0) sidetype() = (side[] += 1; Type{<:Real}) function f() local x::(sidetype()) = Int x = Float64 x end (f(), side[]) end """) === (Float64, 2) end @testset "Bodyless `function Name end` declares a fresh generic function" begin # Shadowing a type visible via `using Mod` (Globtim / Optim.Sphere): the # following method builds a real Function, not the imported constructor. m = @newmod() @test JuliaLowering.include_string(m, """ module Provider export Sphere struct Sphere end end module Consumer using ..Provider function Sphere end Sphere(x::AbstractVector) = sum(x) end (Consumer.Sphere isa Function, parentmodule(Consumer.Sphere) === Consumer, Consumer.Sphere([1,2,3])) """) == (true, true, 6) # Shadowing a type visible only via the implicit `using Core, Base` # (CImGui / Base.Docs.Text): `Base.Text`'s own constructor is untouched. m = @newmod() @test JuliaLowering.include_string(m, """ module Consumer function Text end Text(fmt) = "text: \$fmt" end (Consumer.Text isa Function, Consumer.Text !== Base.Text, Base.Text("x") isa Base.Docs.Text, Consumer.Text("hi")) """) == (true, true, true, "text: hi") # A fresh (unimported) name works unchanged. m = @newmod() @test JuliaLowering.include_string(m, """ module Consumer function Foo end Foo(x::Int) = x + 1 end (Consumer.Foo isa Function, parentmodule(Consumer.Foo) === Consumer, Consumer.Foo(3)) """) == (true, true, 4) # A function visible via `using Mod` is likewise shadowed by a fresh one. m = @newmod() @test JuliaLowering.include_string(m, """ module Provider export myfun function myfun end myfun(x::Int) = x * 10 end module Consumer using ..Provider function myfun end myfun(x::AbstractVector) = length(x) end (Consumer.myfun !== Provider.myfun, Consumer.myfun([1,2,3])) """) == (true, 3) # An *explicit* selective `using Mod: Name` binding is extended, not # shadowed -- flisp defers to the existing binding (the runtime `global` # refers to it), so the bodyless decl leaves `Name` bound to the imported # type. The bare `(method Name)` on that type prints the same "extended # without qualification" warning flisp does, so we silence it here. m = @newmod() @test redirect_stderr(devnull) do JuliaLowering.include_string(m, """ module Provider export Sphere struct Sphere end end module Consumer using ..Provider: Sphere function Sphere end end (Consumer.Sphere === Provider.Sphere, Consumer.Sphere isa Function) """) end == (true, false) # Likewise for an explicit `import Mod: Name`. m = @newmod() @test JuliaLowering.include_string(m, """ module Provider export Sphere struct Sphere end end module Consumer import ..Provider: Sphere function Sphere end end (Consumer.Sphere === Provider.Sphere, Consumer.Sphere isa Function) """) == (true, false) # A bodyless decl whose name is a local stays a local closure and leaks no # module global (flisp's `global-if-global` local exemption). m = @newmod() @test JuliaLowering.include_string(m, """ begin local no_method_f function no_method_f end no_method_f end """) isa Function @test !isdefined(m, :no_method_f) end