Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Depth-limited printing of types in stacktraces #49231

Closed
wants to merge 5 commits into from
Closed

Conversation

timholy
Copy link
Member

@timholy timholy commented Apr 3, 2023

Print parametric types in stacktraces up to a depth limited by screen size, printing deeper type-parameters as .... Complete info can be printed with a manual show(err[1].backtrace[idx]).

I'm posting this for feedback about the general approach, before investing more time in correctness, testing, etc. There are two ways you could go about this:

  • build the tree structure from the type itself
  • parse the printed output about the type to construct the tree

Perhaps surprisingly, this takes the latter approach. We do a lot of customization when we print types, like using aliases and interacting with the IOContext to signal whether we're deferring printing TypeVar constraints. Given these customizations, it seemed to make sense to use the text output to ensure uniformity. That said, I can be persuaded to go the other way.

If we like this proposal, then FoldingTrees.jl can be extended with specializations to allow interactive unfolding (but this plan would require an external package).

CC @ChrisRackauckas, @storopoli

Closes #48444

Demo (exhibiting some currently-buggy missing arguments):

julia> a = rand(UInt8, 80);

julia> b = view(a, 1:64);

julia> c = reshape(b, (8, 8));

julia> d = reinterpret(reshape, Float64, c)
8-element reinterpret(reshape, Float64, reshape(view(::Vector{UInt8}, 1:64), 8, 8)) with eltype Float64:
  1.80852721337015e220
  8.960367199887082e169
  2.2887057012581985e-127
 -5.017255012702684e-121
  8.891015307157821e288
  2.3276748109198913e-288
  8.683913364354522e-132
 -4.126925591937627e-189

julia> sqrteach(a) = [sqrt(x) for x in a]
sqrteach (generic function with 1 method)

julia> sqrteach(d)     # old stacktraces
ERROR: DomainError with -5.017255012702684e-121:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math ./math.jl:33
 [2] sqrt
   @ ./math.jl:681 [inlined]
 [3] #3
   @ ./none:0 [inlined]
 [4] iterate
   @ ./generator.jl:47 [inlined]
 [5] collect_to!(dest::Vector{Float64}, itr::Base.Generator{Base.ReinterpretArray{Float64, 1, UInt8, Base.ReshapedArray{UInt8, 2, SubArray{UInt8, 1, Vector{UInt8}, Tuple{UnitRange{Int64}}, true}, Tuple{}}, true}, var"#3#4"}, offs::Int64, st::Tuple{Base.OneTo{Int64}, Int64})
   @ Base ./array.jl:893
 [6] collect_to_with_first!
   @ ./array.jl:871 [inlined]
 [7] collect(itr::Base.Generator{Base.ReinterpretArray{Float64, 1, UInt8, Base.ReshapedArray{UInt8, 2, SubArray{UInt8, 1, Vector{UInt8}, Tuple{UnitRange{Int64}}, true}, Tuple{}}, true}, var"#3#4"})
   @ Base ./array.jl:845
 [8] sqrteach(a::Base.ReinterpretArray{Float64, 1, UInt8, Base.ReshapedArray{UInt8, 2, SubArray{UInt8, 1, Vector{UInt8}, Tuple{UnitRange{Int64}}, true}, Tuple{}}, true})
   @ Main ./REPL[5]:1
 [9] top-level scope
   @ REPL[6]:1

julia> Revise.track(Base)      # trigger new stacktraces

julia> sqrteach(d)
ERROR: DomainError with -5.017255012702684e-121:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math ./math.jl:33
 [2] sqrt
   @ ./math.jl:681 [inlined]
 [3] #3
   @ ./none:0 [inlined]
 [4] iterate
   @ ./generator.jl:47 [inlined]
 [5] collect_to!(dest::Vector{…})
   @ Base ./array.jl:893
 [6] collect_to_with_first!
   @ ./array.jl:871 [inlined]
 [7] collect(itr::Base.Generator{Base.ReinterpretArray{Float64, 1, UInt8, Base.ReshapedArray{…}}, true}, var"#3#4", )
   @ Base ./array.jl:845
 [8] sqrteach(a::Base.ReinterpretArray{Float64, 1, UInt8, Base.ReshapedArray{UInt8, 2, SubArray{…}, Tuple}}, true, )
   @ Main ./REPL[5]:1
 [9] top-level scope
   @ REPL[8]:1

julia> show(err[1].backtrace[5])    # we can still see all the info if we want
collect_to!(dest::Vector{Float64}, itr::Base.Generator{Base.ReinterpretArray{Float64, 1, UInt8, Base.ReshapedArray{UInt8, 2, SubArray{UInt8, 1, Vector{UInt8}, Tuple{UnitRange{Int64}}, true}, Tuple{}}, true}, var"#3#4"}, offs::Int64, st::Tuple{Base.OneTo{Int64}, Int64}) at array.jl:893

Print types in stacktraces up to a depth limited by screen size,
printing deeper type-parameters as `...`. Complete info can be
printed with a manual `show(err[1].backtrace[idx])`.
@timholy timholy added the needs tests Unit tests are required for this change label Apr 3, 2023
@ChrisRackauckas
Copy link
Member

This seems like a reasonable way to do it.

@storopoli
Copy link
Contributor

This is a good approach. Much better than the full stacktrace for newcomers.

@JeffBezanson
Copy link
Member

Chris, does this work for ODEIntegrator? I think there are cases where the problem is too many parameters, not too-deep parameters.

@timholy
Copy link
Member Author

timholy commented Apr 4, 2023

I should also comment that I'm playing with the alternative approach I outlined at the top. I'm unsure which one will seem best by the end, but it seems worth trying, and if I like it at least on par with this one I'll submit it as an alternative PR.

@timholy timholy changed the title RFC/WIP: depth-limited printing of types in stacktraces Depth-limited printing of types in stacktraces Apr 10, 2023
@timholy timholy removed the needs tests Unit tests are required for this change label Apr 10, 2023
@timholy
Copy link
Member Author

timholy commented Apr 10, 2023

Having spent a couple days trying the alternative, I think this remains the best approach. Assuming, of course, that I can fix the last bug (it shouldn't yet traverse up the tree on '}' if the next character is an ANSI escape).

@timholy
Copy link
Member Author

timholy commented Apr 28, 2023

As an update, this is getting closer but will need refactoring to handle printing of calls like (obj::MyObj{parameters...})(args...). And the color printing still isn't working to the level of being able to test equivalence with traditional printing.

@JeffBezanson
Copy link
Member

I'm trying this out and it's really nice! We should go with this approach. Two suggestions that jumped out at me:

  • Show Vector instead of Vector{...} at the top level
  • For functions with many arguments, never fully omit the argument info. For long argument lists this prints f(...), but I think we should always at least show the top-level type family of each argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants