Skip to content

InteractiveUtils: support syntax found in stacktraces for introspection macros#58222

Merged
aviatesk merged 2 commits intomasterfrom
serenity4/interactiveutils-stacktrace-syntax
Apr 26, 2025
Merged

InteractiveUtils: support syntax found in stacktraces for introspection macros#58222
aviatesk merged 2 commits intomasterfrom
serenity4/interactiveutils-stacktrace-syntax

Conversation

@serenity4
Copy link
Copy Markdown
Member

@serenity4 serenity4 commented Apr 24, 2025

Continuing the work done at #57909, this PR adds support for the following syntax:

@code_typed f(some_undef_var::Int) # some_undef_var is ignored, only the annotation is used
@code_typed f(; x::Int) # same here, the name is used but not the value

This should allow us to copy and paste signatures found in stacktraces, such as

julia> f(x; y = 3) = error()
f (generic function with 1 method)

julia> f(1)
ERROR: 
Stacktrace:
 [1] error()
   @ Base ./error.jl:45
 [2] f(x::Int64; y::Int64)
   @ Main ./REPL[40]:1
 [3] top-level scope
   @ REPL[41]:1
   
julia> asin(-2)
ERROR: DomainError with -2.0:
asin(x) is not defined for |x| > 1.
Stacktrace:
 [1] asin_domain_error(x::Float64)
   @ Base.Math ./special/trig.jl:429
 [2] asin(x::Float64)
   @ Base.Math ./special/trig.jl:443

where any function call may be copied and pasted into @code_typed, @edit etc as is, provided that the function and argument types are defined in the active module (i.e. Main).

Thanks @topolarity for the idea.

Copy link
Copy Markdown
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

I have spent an embarrassing amount of time parsing a method signature to see which foo::Int64 was part of a NamedTuple and which needed to be deleted as part of the arguments

This looks very convenient 👍

@serenity4
Copy link
Copy Markdown
Member Author

serenity4 commented Apr 24, 2025

Good observation! I just tried with named tuples to make sure, it works well:

julia> f(x, y; z = 3) = x + y.a + z
f (generic function with 1 method)

julia> f((; k = current_task(), l = 3), (; a = 1, b = "hello"))
ERROR: MethodError: no method matching +(::@NamedTuple{k::Task, l::Int64}, ::Int64)
The function `+` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...)
   @ Base operators.jl:643
  +(::BigInt, ::Union{Int16, Int32, Int64, Int8})
   @ Base gmp.jl:554
  +(::Base.CoreLogging.LogLevel, ::Integer)
   @ Base logging/logging.jl:132
  ...

Stacktrace:
 [1] +
   @ ./operators.jl:643 [inlined]
 [2] f(x::@NamedTuple{k::Task, l::Int64}, y::@NamedTuple{a::Int64, b::String}; z::Int64)
   @ Main ./REPL[7]:1
 [3] top-level scope
   @ REPL[8]:1
 [4] top-level scope
   @ REPL:1

julia> @code_typed f(x::@NamedTuple{k::Task, l::Int64}, y::@NamedTuple{a::Int64, b::String}; z::Int64)
CodeInfo(
1nothing::Nothing%2 =   builtin Core.getfield(@_2, :z)::Int64nothing::Nothingnothing::Nothing
│           invoke Main.:(var"#f#3")(%2::Int64, @_3::typeof(f), x::@NamedTuple{k::Task, l::Int64}, y::@NamedTuple{a::Int64, b::String})::Union{}
└──      unreachable
) => Union{}

(now, if only we could step through that invoke...)

@aviatesk
Copy link
Copy Markdown
Member

This is a very nice enhancement, thanks!
I think we also want to update the documentation or the NEWS file?

BTW this will really improve JET's usability.
With this PR's feature, we can launch Cthulhu session for a specific method call copy-and-pasting its signature from JET's virtual stacktrace output, which will make debugging a lot easier:

julia> using JET, Cthulhu

julia> @report_call sum("julia")
═════ 2 possible errors found ═════
┌ sum(a::String) @ Base ./reduce.jl:553
│┌ sum(a::String; kw::@Kwargs{}) @ Base ./reduce.jl:553
││┌ sum(f::typeof(identity), a::String) @ Base ./reduce.jl:524
│││┌ sum(f::typeof(identity), a::String; kw::@Kwargs{}) @ Base ./reduce.jl:524
││││┌ mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::String) @ Base ./reduce.jl:299
│││││┌ mapreduce(f::typeof(identity), op::typeof(Base.add_sum), itr::String; kw::@Kwargs{}) @ Base ./reduce.jl:299
││││││┌ mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::String) @ Base ./reduce.jl:167
│││││││┌ mapfoldl(f::typeof(identity), op::typeof(Base.add_sum), itr::String; init::Base._InitialValue) @ Base ./reduce.jl:167
││││││││┌ mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::String) @ Base ./reduce.jl:36
│││││││││┌ foldl_impl(op::Base.BottomRF{typeof(Base.add_sum)}, nt::Base._InitialValue, itr::String) @ Base ./reduce.jl:40
││││││││││┌ _foldl_impl(op::Base.BottomRF{typeof(Base.add_sum)}, init::Base._InitialValue, itr::String) @ Base ./reduce.jl:54
│││││││││││┌ (::Base.BottomRF{typeof(Base.add_sum)})(acc::Char, x::Char) @ Base ./reduce.jl:78
││││││││││││┌ add_sum(x::Char, y::Char) @ Base ./reduce.jl:16
│││││││││││││ no matching method found `+(::Char, ::Char)`: (x::Char + y::Char)
││││││││││││└────────────────────
│││││││││┌ foldl_impl(op::Base.BottomRF{typeof(Base.add_sum)}, nt::Base._InitialValue, itr::String) @ Base ./reduce.jl:41
││││││││││┌ reduce_empty_iter(op::Base.BottomRF{typeof(Base.add_sum)}, itr::String) @ Base ./reduce.jl:372
│││││││││││┌ reduce_empty_iter(op::Base.BottomRF{typeof(Base.add_sum)}, itr::String, ::Base.HasEltype) @ Base ./reduce.jl:373
││││││││││││┌ reduce_empty(op::Base.BottomRF{typeof(Base.add_sum)}, ::Type{Char}) @ Base ./reduce.jl:349
│││││││││││││┌ reduce_empty(::typeof(Base.add_sum), ::Type{Char}) @ Base ./reduce.jl:342
││││││││││││││┌ reduce_empty(::typeof(+), ::Type{Char}) @ Base ./reduce.jl:335
│││││││││││││││ no matching method found `zero(::Type{Char})`: zero(T::Type{Char})
││││││││││││││└────────────────────


julia> @descend Base. mapfoldl_impl(f::typeof(identity), op::typeof(Base.add_sum), nt::Base._InitialValue, itr::String)
mapfoldl_impl(f::F, op::OP, nt, itr) where {F, OP} @ Base ~/julia/julia/base/reduce.jl:34
...

@aviatesk aviatesk merged commit 142f419 into master Apr 26, 2025
7 checks passed
@aviatesk aviatesk deleted the serenity4/interactiveutils-stacktrace-syntax branch April 26, 2025 19:33
LebedevRI pushed a commit to LebedevRI/julia that referenced this pull request May 2, 2025
…on macros (JuliaLang#58222)

Continuing the work done at JuliaLang#57909, this PR adds support for the
following syntax:
```julia
@code_typed f(some_undef_var::Int) # some_undef_var is ignored, only the annotation is used
@code_typed f(; x::Int) # same here, the name is used but not the value
```
This should allow us to copy and paste signatures found in stacktraces,
such as
```julia
julia> f(x; y = 3) = error()
f (generic function with 1 method)

julia> f(1)
ERROR: 
Stacktrace:
 [1] error()
   @ Base ./error.jl:45
 [2] f(x::Int64; y::Int64)
   @ Main ./REPL[40]:1
 [3] top-level scope
   @ REPL[41]:1
   
julia> asin(-2)
ERROR: DomainError with -2.0:
asin(x) is not defined for |x| > 1.
Stacktrace:
 [1] asin_domain_error(x::Float64)
   @ Base.Math ./special/trig.jl:429
 [2] asin(x::Float64)
   @ Base.Math ./special/trig.jl:443
```

where any function call may be copied and pasted into `@code_typed`,
`@edit` etc as is, provided that the function and argument types are
defined in the active module (i.e. `Main`).

Thanks @topolarity for the idea.

---------

Co-authored-by: Cédric Belmant <cedric.belmant@juliahub.com>
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.

3 participants