From 1a6565d3289a68e9f2c8a70c88ddd21e64aa3931 Mon Sep 17 00:00:00 2001 From: Simon Byrne Date: Fri, 21 Sep 2018 15:05:03 -0700 Subject: [PATCH] at-kwdef support for parametric types and subtypes Fixes #29307. --- base/util.jl | 55 ++++++++++++++++++++++++++++++++++++++-------------- test/misc.jl | 12 ++++++++++++ 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/base/util.jl b/base/util.jl index 80c790770af74..fb1859440d750 100644 --- a/base/util.jl +++ b/base/util.jl @@ -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 == :(=) @@ -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 diff --git a/test/misc.jl b/test/misc.jl index c280fc79d145e..2ef63c2d2ba84 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -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)