Skip to content

Commit

Permalink
redirect line info of @kwdef constructor to the definition site (#4…
Browse files Browse the repository at this point in the history
…7887)

This should improve the debuggability of constructors defined by `@kwdef`.

```julia
julia> @kwdef struct Test_kwdef_lineinfo
           a::String
       end

julia> Test_kwdef_lineinfo(; a=42)
[...]
```

Before:
```
Stacktrace:
 [1] Test_kwdef_lineinfo(a::Int64)
   @ Main ./none:2
 [2] Test_kwdef_lineinfo(; a::Int64)
   @ Main ~/julia/julia/base/util.jl:549
 [3] kwcall(::NamedTuple{(:a,), Tuple{Int64}}, ::Type{Test_kwdef_lineinfo})
   @ Main ~/julia/julia/base/util.jl:549
 [4] top-level scope
   @ none:1
```

After:
```
Stacktrace:
 [1] Test_kwdef_lineinfo(a::Int64)
   @ Main ./none:2
 [2] Test_kwdef_lineinfo(; a::Int64)
   @ Main ./none:1
 [3] kwcall(::NamedTuple{(:a,), Tuple{Int64}}, ::Type{Test_kwdef_lineinfo})
   @ Main ./none:1
 [4] top-level scope
   @ none:1
```
  • Loading branch information
aviatesk authored Dec 14, 2022
1 parent 9fd3136 commit 0fbe56e
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 13 deletions.
29 changes: 16 additions & 13 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -531,8 +531,7 @@ Stacktrace:
"""
macro kwdef(expr)
expr = macroexpand(__module__, expr) # to expand @static
expr isa Expr && expr.head === :struct || error("Invalid usage of @kwdef")
expr = expr::Expr
isexpr(expr, :struct) || error("Invalid usage of @kwdef")
T = expr.args[2]
if T isa Expr && T.head === :<:
T = T.args[1]
Expand All @@ -546,29 +545,33 @@ macro kwdef(expr)
# overflow on construction
if !isempty(params_ex.args)
if T isa Symbol
kwdefs = :(($(esc(T)))($params_ex) = ($(esc(T)))($(call_args...)))
elseif T isa Expr && T.head === :curly
T = T::Expr
sig = :(($(esc(T)))($params_ex))
call = :(($(esc(T)))($(call_args...)))
body = Expr(:block, __source__, call)
kwdefs = Expr(:function, sig, body)
elseif isexpr(T, :curly)
# if T == S{A<:AA,B<:BB}, define two methods
# S(...) = ...
# S{A,B}(...) where {A<:AA,B<:BB} = ...
S = T.args[1]
P = T.args[2:end]
Q = Any[U isa Expr && U.head === :<: ? U.args[1] : U for U in P]
Q = Any[isexpr(U, :<:) ? U.args[1] : U for U in P]
SQ = :($S{$(Q...)})
kwdefs = quote
($(esc(S)))($params_ex) =($(esc(S)))($(call_args...))
($(esc(SQ)))($params_ex) where {$(esc.(P)...)} =
($(esc(SQ)))($(call_args...))
end
body1 = Expr(:block, __source__, :(($(esc(S)))($(call_args...))))
sig1 = :(($(esc(S)))($params_ex))
def1 = Expr(:function, sig1, body1)
body2 = Expr(:block, __source__, :(($(esc(SQ)))($(call_args...))))
sig2 = :(($(esc(SQ)))($params_ex) where {$(esc.(P)...)})
def2 = Expr(:function, sig2, body2)
kwdefs = Expr(:block, def1, def2)
else
error("Invalid usage of @kwdef")
end
else
kwdefs = nothing
end
quote
Base.@__doc__($(esc(expr)))
return quote
Base.@__doc__ $(esc(expr))
$kwdefs
end
end
Expand Down
19 changes: 19 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,25 @@ end
end
end

@kwdef struct Test_kwdef_lineinfo
a::String
end
@testset "@kwdef constructor line info" begin
for method in methods(Test_kwdef_lineinfo)
@test method.file === Symbol(@__FILE__)
@test ((@__LINE__)-6) method.line ((@__LINE__)-5)
end
end
@kwdef struct Test_kwdef_lineinfo_sparam{S<:AbstractString}
a::S
end
@testset "@kwdef constructor line info with static parameter" begin
for method in methods(Test_kwdef_lineinfo_sparam)
@test method.file === Symbol(@__FILE__)
@test ((@__LINE__)-6) method.line ((@__LINE__)-5)
end
end

@testset "exports of modules" begin
for (_, mod) in Base.loaded_modules
mod === Main && continue # Main exports everything
Expand Down

0 comments on commit 0fbe56e

Please sign in to comment.