Skip to content

Commit

Permalink
at-kwdef support for parametric types and subtypes
Browse files Browse the repository at this point in the history
Fixes #29307.
  • Loading branch information
simonbyrne committed Sep 21, 2018
1 parent 2fd386c commit 1a6565d
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 15 deletions.
55 changes: 40 additions & 15 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -652,29 +652,54 @@ Stacktrace:
"""
macro kwdef(expr)
expr = macroexpand(__module__, expr) # to expand @static
expr isa Expr && expr.head == :struct || error("Invalid usage of @kwdef")
T = expr.args[2]
params_ex = Expr(:parameters)
call_ex = Expr(:call, T)
_kwdef!(expr.args[3], params_ex, call_ex)
ret = quote
Base.@__doc__($(esc(expr)))
if T isa Expr && T.head == :<:
T = T.args[1]
end

params_ex = Expr(:parameters)
call_args = Any[]

_kwdef!(expr.args[3], params_ex.args, call_args)
# Only define a constructor if the type has fields, otherwise we'll get a stack
# overflow on construction
if !isempty(params_ex.args)
push!(ret.args, :($(esc(Expr(:call, T, params_ex))) = $(esc(call_ex))))
if T isa Symbol
kwdefs = :(($(esc(T)))($params_ex) = ($(esc(T)))($(call_args...)))
elseif T isa Expr && T.head == :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 = [U isa Expr && U.head == :<: ? 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
else
error("Invalid usage of @kwdef")
end
else
kwdefs = nothing
end
quote
Base.@__doc__($(esc(expr)))
$kwdefs
end
ret
end

# @kwdef helper function
# mutates arguments inplace
function _kwdef!(blk, params_ex, call_ex)
function _kwdef!(blk, params_args, call_args)
for i in eachindex(blk.args)
ei = blk.args[i]
if isa(ei, Symbol)
push!(params_ex.args, ei)
push!(call_ex.args, ei)
push!(params_args, ei)
push!(call_args, ei)
elseif !isa(ei, Expr)
continue
elseif ei.head == :(=)
Expand All @@ -686,17 +711,17 @@ function _kwdef!(blk, params_ex, call_ex)
var = dec
end
def = ei.args[2] # defexpr
push!(params_ex.args, Expr(:kw, var, def))
push!(call_ex.args, var)
push!(params_args, Expr(:kw, var, def))
push!(call_args, var)
blk.args[i] = dec
elseif ei.head == :(::)
dec = ei # var::Typ
var = dec.args[1] # var
push!(params_ex.args, var)
push!(call_ex.args, var)
push!(params_args, var)
push!(call_args, var)
elseif ei.head == :block
# can arise with use of @static inside type decl
_kwdef!(ei, params_ex, call_ex)
_kwdef!(ei, params_args, call_args)
end
end
blk
Expand Down
12 changes: 12 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,18 @@ end
@test Test27970Empty() == Test27970Empty()
end

abstract type AbstractTest29307 end
@kwdef struct Test29307{T<:Integer} <: AbstractTest29307
a::T=2
end

@testset "subtyped @kwdef" begin
@test Test29307() == Test29307{Int}(2)
@test Test29307(a=0x03) == Test29307{UInt8}(0x03)
@test Test29307{UInt32}() == Test29307{UInt32}(2)
@test Test29307{UInt32}(a=0x03) == Test29307{UInt32}(0x03)
end

@testset "exports of modules" begin
for (_, mod) in Base.loaded_modules
for v in names(mod)
Expand Down

0 comments on commit 1a6565d

Please sign in to comment.