Promote parametric type in repeat

Just for laziness I would like to implement a repeat function for my own parametric type. For example, consider the following example, where A1 might even be used recursively (i.e. Arrays of Arrays) and I would like to do a simple repeat (introducing one further dimension, here a second one)

import Base: size, repeat
abstract type A end

struct A1{C<:Array{<:A,N} where N} <: A
    value::C
end
struct A2{T} <: A
    value::T
end

size(a::A1) = size(a.value)
repeat(a::A1; inner=nothing, outer=nothing) = A1( repeat(a.value; inner=inner, outer=outer) )
repeat(a::A1, counts...) = A1( repeat(a.value, counts...) )

function runExample()
  a = A1([ A2(0.2); A2(0.2)])
  s = length(size(a))
  d = fill(1,s+1)
  d[s+1] = s
  return repeat(a; inner=d)
end
@code_warntype runExample()
runExample()

While the example runs perfectly (last line) the @code_warntype says that it can’t infer the type of the repeat return, i.e.

   11 ─ %25 = invoke Main.:(#repeat#8)(%7::Array{Int64,1}, %10::Nothing, %9::Function, %4::A1{Array{A2{Float64},1}})::A1{_1} where _1 

I started trying something like

repeat(a::A1{Ca}; inner=nothing, outer=nothing) where {Ca <: Array{C,N} where {C <: A, N}} = ...

however the new type will have a different N, since the array dimensions change, so I don’t know how to continue. Actually the problem is similar for cat, vcat and hcat.

How can I properly define repeat to not avoid the type instability?

I just noticed that it is enough to specify one of the parameters, i.e.

repeat(a::A1{C}; inner=nothing, outer=nothing) where {C <:Array{P,N}} where {P<:A,N} = A1{Array{P}}( repeat(a.value; inner=inner, outer=outer) )
repeat(a::A1{C}, counts...)  where {C <:Array{P,N}} where {P<:A,N} = A1{Array{P}}( repeat(a.value, counts...) )

note that for the resulting type A1{Array{P}} the N is not specified. This resolves the @code_warntype problem partly, but there still seems to be a problem determining the resulting number dimensions.

Interestingly, at least when I try to compute the dimensions of the resulting array (the lazy way), i.e. the script looks like

import Base: size, repeat
abstract type A end

struct A1{C<:Array{<:A,N} where N} <: A
    value::C
end
struct A2{T} <: A
    value::T
end

size(a::A1) = size(a.value)
function repeat(a::A1{Array{P,N}}; inner=nothing, outer=nothing) where {P<:A,N}
    b = repeat(a.value; inner=inner, outer=outer)
    return A1{Array{P,ndims(b)}}(b)
end
function repeat(a::A1{Array{P,N}}, counts...) where {P<:A,N}
    b = repeat(a.value, counts...)
    return A1(b)
end

Then comparing defining a function with the code running in REPL like

function runExample()
  a = A1([ A2(0.2); A2(0.2)])
  s = length(size(a))
  d = fill(1,s+1)
  d[s+1] = s
  return repeat(a, d...)
end
@code_warntype runExample()
runExample()

vs.

a = A1([ A2(0.2); A2(0.2)])
s = length(size(a))
d = fill(1,s+1)
d[s+1] = s
@code_warntype repeat(a, d...)

yields that the function can not infer its return type, i.e. I get (just the last part of code_warntype)

   │   %7  = invoke Base.fill!(%6::Array{Int64,1}, 1::Int64)::Array{Int64,1}                                   ││┃    fill
25 │         (Base.arrayset)(true, %7, 1, 2)                                                                   │╻    setindex!
26 │   %9  = (Core.tuple)(%4)::Tuple{A1{Array{A2{Float64},1}}}                                                 │
   │   %10 = (Core._apply)(Main.repeat, %9, %7)::A1{_1} where _1                                               │
   └──       return %10  

versus the last lines from the second code_warntype yield

   │   %4 = invoke Main.repeat(%1::Array{A2{Float64},1}, %2::Int64, %3::Int64)::Array{A2{Float64},2}           │
19 │   %5 = %new(A1{Array{A2{Float64},2}}, %4)::A1{Array{A2{Float64},2}}                                       │╻╷ Type
   └──      return %5   

So, interestingly, the first does know the types of %7 and %9 but does not use them in the apply, while in REPL in invokes the repeat and can pass down the types. Can somebody explain (and maybe solve) this difference?