Stripping parameter from parametric types

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.