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

at-kwdef support for parametric types and subtypes #29316

Merged
merged 4 commits into from
Oct 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 67 additions & 32 deletions base/util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,10 @@ expression. The default argument is supplied by declaring fields of the form `fi
default` or `field = default`. If no default is provided then the keyword argument becomes
a required keyword argument in the resulting type constructor.

Inner constructors can still be defined, but at least one should accept arguments in the
same form as the default inner constructor (i.e. one positional argument per field) in
order to function correctly with the keyword outer constructor.

# Examples
```jldoctest
julia> Base.@kwdef struct Foo
Expand All @@ -652,51 +656,82 @@ 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)
elseif !isa(ei, Expr)
continue
elseif ei.head == :(=)
# var::Typ = defexpr
dec = ei.args[1] # var::Typ
if isa(dec, Expr) && dec.head == :(::)
var = dec.args[1]
else
var = dec
if ei isa Symbol
# var
push!(params_args, ei)
push!(call_args, ei)
elseif ei isa Expr
if ei.head == :(=)
lhs = ei.args[1]
if lhs isa Symbol
# var = defexpr
var = lhs
elseif lhs isa Expr && lhs.head == :(::) && lhs.args[1] isa Symbol
# var::T = defexpr
var = lhs.args[1]
else
# something else, e.g. inline inner constructor
# F(...) = ...
continue
end
defexpr = ei.args[2] # defexpr
push!(params_args, Expr(:kw, var, esc(defexpr)))
push!(call_args, var)
blk.args[i] = lhs
elseif ei.head == :(::) && ei.args[1] isa Symbol
# var::Typ
var = ei.args[1]
push!(params_args, var)
push!(call_args, var)
elseif ei.head == :block
# can arise with use of @static inside type decl
_kwdef!(ei, params_args, call_args)
end
def = ei.args[2] # defexpr
push!(params_ex.args, Expr(:kw, var, def))
push!(call_ex.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)
elseif ei.head == :block
# can arise with use of @static inside type decl
_kwdef!(ei, params_ex, call_ex)
end
end
blk
Expand Down
1 change: 1 addition & 0 deletions contrib/generate_precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ function generate_precompile_statements()
# println(statement)
# Work around #28808
occursin("\"YYYY-mm-dd\\THH:MM:SS\"", statement) && continue
statement == "precompile(Tuple{typeof(Base.show), Base.IOContext{Base.TTY}, Type{Vararg{Any, N} where N}})" && continue
try
Base.include_string(PrecompileStagingArea, statement)
catch ex
Expand Down
36 changes: 36 additions & 0 deletions test/misc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,42 @@ 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

@kwdef struct TestInnerConstructor
a = 1
TestInnerConstructor(a::Int) = (@assert a>0; new(a))
function TestInnerConstructor(a::String)
@assert length(a) > 0
new(a)
end
end

@testset "@kwdef inner constructor" begin
@test TestInnerConstructor() == TestInnerConstructor(1)
@test TestInnerConstructor(a=2) == TestInnerConstructor(2)
@test_throws AssertionError TestInnerConstructor(a=0)
@test TestInnerConstructor(a="2") == TestInnerConstructor("2")
@test_throws AssertionError TestInnerConstructor(a="")
end

const outsidevar = 7
@kwdef struct TestOutsideVar
a::Int=outsidevar
end
@test TestOutsideVar() == TestOutsideVar(7)


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