Not sure if there is an easier way or not, but here is my attempt.
julia> name(T::DataType) = T.name
name (generic function with 1 method)
julia> name(T::UnionAll) = name(T.body)
name (generic function with 2 methods)
julia> datatype(T::UnionAll) = datatype(T.body)
datatype (generic function with 1 method)
julia> datatype(T::DataType) = T
datatype (generic function with 2 methods)
julia> nvars(T::UnionAll) = 1 + nvars(T.body)
nvars (generic function with 1 method)
julia> nvars(T::DataType) = 0
nvars (generic function with 2 methods)
julia> unionall(T) = eval(Symbol(name(T)))
unionall (generic function with 1 method)
julia> parametersless1(T) = datatype(T).parameters[1:end-nvars(T)-1]
parametersless1 (generic function with 1 method)
julia> @generated function f(::Type{T}) where {T}
any(isa.(T, (DataType, UnionAll))) || throw("Only supports DataType and UnionAll inputs.")
n = unionall(T)
n isa DataType && return :($n)
ps = parametersless1(T)
return :($n{$(ps...)})
end
f (generic function with 1 method)
julia> struct T{S1,S2,S3} end
julia> f(T{1,2,3})
T{1,2,S3} where S3
julia> f(T{1,2})
T{1,S2,S3} where S3 where S2
julia> f(T{1})
T
julia> f(T)
T
julia> f(Int)
Int64
You can also make a normal function not a generated one. And you can avoid using eval
altogether if you find a way to change a TypeName
to a UnionAll
in case of parametric types, or to a DataType
in case of non-parametric types, eval
was the lazy solution.