Revise.jl icon indicating copy to clipboard operation
Revise.jl copied to clipboard

World age error when revising in Plots

Open yha opened this issue 4 years ago • 3 comments

With Plots checked out for development:

julia> using Revise

julia> using Plots
[ Info: Precompiling Plots [91a5bcdd-55d7-5caf-9e0b-520d859cae80]

julia> # edit Plots/src/utils.jl

julia> plot(rand(4))
┌ Warning: likely failure to return to toplevel, try `ExprSplitter`
└ @ JuliaInterpreter C:\Users\sternlab\.julia\packages\JuliaInterpreter\QbBZX\src\interpret.jl:622
┌ Error: Failed to revise C:\Users\sternlab\.julia\dev\Plots\src\utils.jl
│   exception =
│    MethodError: no method matching (::Plots.var"#486#487")(::Int64)
│    The applicable method may be too new: running in world age 27894, while current world is 27901.
│    Closest candidates are:
│      #486(::Any) at none:0 (method too new to be called from this world context.)
│    top-level scope at C:\Users\sternlab\.julia\dev\Plots\src\utils.jl:154
│    Revise evaluation error at C:\Users\sternlab\.julia\dev\Plots\src\utils.jl:154
│
└ @ Revise C:\Users\sternlab\.julia\packages\Revise\mvD4N\src\packagedef.jl:707
┌ Warning: The running code does not match the saved version for the following files:
│
│   C:\Users\sternlab\.julia\dev\Plots\src\utils.jl
│
│ If the error was due to evaluation order, it can sometimes be resolved by calling `Revise.retry()`.
│ Use Revise.errors() to report errors again. Only the first error in each file is shown.
│ Your prompt color may be yellow until the errors are resolved.
└ @ Revise C:\Users\sternlab\.julia\packages\Revise\mvD4N\src\packagedef.jl:805

yha avatar Feb 23 '21 14:02 yha

Oof, this is a really tough one. It comes from here:

for i in 2:4
    @eval begin
        RecipesPipeline.unzip(
            v::Union{AVec{<:Tuple{Vararg{T,$i} where T}}, AVec{<:GeometryBasics.Point{$i}}},
        ) = $(Expr(:tuple, (:([t[$j] for t in v]) for j=1:i)...))
    end
end

The problem is that the method body it built via comprehension, which defines an anonymous function, and this is where the world age issue comes from. The ExprSplitter framework of JuliaInterpreter is designed to allow everything to happen in successive evals and thus avoid world age problems, but this is so deeply tangled inside its own @eval that it defeats all the logic we've created to handle this.

I'm not immediately sure how to fix this. Julia itself can play nice tricks on the C side with the world age, but none of those capabilities are available to packages.

timholy avatar Feb 24 '21 09:02 timholy

An easy way to circumvent this problem (make it rarer) is to split this definition out into a separate file. As long as you never change that file, then it won't rear its head. Meanwhile I'll think about how to handle this.

timholy avatar Feb 24 '21 10:02 timholy

For my own future debugging reference, here's the lowered framecode (the t/f shows which statements Revise will evaluate in its quest to spot method signatures...which there are, so it's not wrong to be trying to do this):

 1 t 1 ─       $(Expr(:thunk, CodeInfo(
    @ none within `top-level scope'
1 ─      global var"#474#475"
│        const var"#474#475"
│   %3 = Core._structtype(Plots, Symbol("#474#475"), Core.svec(), Core.svec(), false, 0)
│        var"#474#475" = %3
│        Core._setsuper!(var"#474#475", Core.Function)
│        Core._typebody!(var"#474#475", Core.svec())
└──      return nothing
)))
 2 t │   %2  = Core.svec(var"#474#475", $(QuoteNode(Any)))
 3 t │   %3  = Core.svec()
 4 t │   %4  = Core.svec(%2, %3, $(QuoteNode(:(#= none:0 =#))))
 5 t │         $(Expr(:method, false, :(%4), CodeInfo(
    @ none within `none'
1 ─ %1 = Core._expr(:ref, :t, j)
│   %2 = $(Expr(:copyast, :($(QuoteNode(:(t = v))))))
│   %3 = Core._expr(:generator, %1, %2)
│   %4 = Core._expr(:comprehension, %3)
└──      return %4
)))
 6 t │   %6  = 2:4
 7 t │         _1 = Base.iterate(%6)
 8 t │   %8  = _1 === nothing
 9 t │   %9  = ($(QuoteNode(Core.Intrinsics.not_int)))(%8)
10 t └──       goto #4 if not %9
11 t 2 ┄ %11 = _1
12 t │         _3 = Core.getfield(%11, 1)
13 t │   %13 = Core.getfield(%11, 2)
14 t │   %14 = $(Expr(:copyast, :($(QuoteNode(:(RecipesPipeline.unzip))))))
15 t │   %15 = Core._expr(:curly, :Vararg, :T, _3)
16 t │   %16 = Core._expr(:where, %15, :T)
17 t │   %17 = Core._expr(:curly, :Tuple, %16)
18 t │   %18 = Core._expr(:<:, %17)
19 t │   %19 = Core._expr(:curly, :AVec, %18)
20 t │   %20 = $(Expr(:copyast, :($(QuoteNode(:(GeometryBasics.Point))))))
21 t │   %21 = Core._expr(:curly, %20, _3)
22 t │   %22 = Core._expr(:<:, %21)
23 t │   %23 = Core._expr(:curly, :AVec, %22)
24 t │   %24 = Core._expr(:curly, :Union, %19, %23)
25 t │   %25 = Core._expr(:(::), :v, %24)
26 t │   %26 = Core._expr(:call, %14, %25)
27 t │   %27 = Core.tuple(:tuple)
28 t │         _2 = %new(var"#474#475")
29 t │   %29 = _2
30 t │   %30 = 1:_3
31 t │   %31 = ($(QuoteNode(Base.Generator)))(%29, %30)
32 t │   %32 = Core._apply_iterate($(QuoteNode(iterate)), Expr, %27, %31)
33 t │   %33 = Core._expr(:block, $(QuoteNode(:(#= /home/tim/.julia/dev/Plots/src/utils_eval.jl:5 =#))), %32)
34 t │   %34 = Core._expr(:(=), %26, %33)
35 t │   %35 = Core._expr(:block, $(QuoteNode(:(#= /home/tim/.julia/dev/Plots/src/utils_eval.jl:3 =#))), %34)
36 t │         Core.eval(Plots, %35)
37 t │         _1 = Base.iterate(%6, %13)
38 t │   %38 = _1 === nothing
39 t │   %39 = ($(QuoteNode(Core.Intrinsics.not_int)))(%38)
40 t └──       goto #4 if not %39
41 t 3 ─       goto #2
42 f 4 ┄       return nothing

It fails at %32, because this line tries to apply the anonymous function (it never even makes it to the Core.eval). If we could interpret statements 1-5 in one world age, and the rest in another, we'd be fine, but how to do that without absolutely littering JuliaInterpreter with invokelatests (likely hurting performance drastically, as we'd have to abandon our local method tables) is not obvious.

Any ideas @KristofferC, @aviatesk?

timholy avatar Feb 24 '21 10:02 timholy