using JuliaLowering: compress_sbt, uncompress_sbt, SourceByteTable,
    add_debuginfo!

test_mod = Module()

function test_byte_precise(di::Core.DebugInfo, nstmts::Int)
    for i in 1:nstmts
        sl = Base.Compiler.source_location(di, i)
        @test sl.byte > 0      context=di
        @test sl.byte_end > 0  context=di
        @test sl.col > 0       context=di
        @test sl.col_end > 0   context=di
        @test sl.line > 0      context=di
        @test sl.line_end > 0  context=di
    end
end

@testset "SourceByteTable roundtrip" begin
    st_thunk = jl_lower(test_mod, JuliaSyntax.parsestmt(SyntaxTree, "1 + 2 - \n 3"))
    st = st_thunk[1]
    JuliaLowering.@jl_assert kind(st_thunk) === K"thunk" &&
        kind(st) === K"code_info" (st_thunk, "fix this brittle test")
    add_debuginfo!(st)
    csbt = st.meta[:debuginfo]
    usbt = uncompress_sbt(csbt)
    cusbt = compress_sbt(usbt)
    @test csbt isa Core.DebugInfo
    @test usbt isa SourceByteTable
    @test cusbt isa String
    @test cusbt === csbt.linetable

    @test length(usbt.spans) <= numchildren(st[2]) # lhs is unique locs
    test_byte_precise(csbt, numchildren(st[2]))
end

@testset "Attaching DebugInfo to methods" begin
    local f = JuliaLowering.include_string(test_mod, """
    function f_with_debuginfo(x)
        show(x)
        map(a->a+1, x)
    end
    """)
    di = methods(f)[1].debuginfo
    @test di.linetable isa String
    let nstmts = length(Base._uncompressed_ir(methods(f)[1]).code)
        test_byte_precise(di, nstmts)
    end
end

@testset "Stack traces can read our debuginfo" begin
    f_errors = JuliaLowering.eval(
        test_mod,
        Expr(:function, Expr(:call, :func_throws_at_myfile_200),
             Expr(:block,
                  LineNumberNode(100, :myfile),
                  LineNumberNode(200, :myfile),
                  Expr(:call, :error, "foo"),
                  LineNumberNode(300, :myfile),
                  :(return y * 2))))
    frames = try
        f_errors()
        @test false=="expected error"
    catch err
        stacktrace(catch_backtrace())
    end
    myframe_i = findfirst(x->x.func==:func_throws_at_myfile_200, frames)
    @test myframe_i == 2
    # TODO: line field may disappear
    @test frames[myframe_i].line == 200
end
