That’s because it does not now how many iterators it will be taking the product of if you input them via a vector. If possible, use a tuple of iterators as input argument
julia> f(m)=collect(Iterators.product(m...))
f (generic function with 1 method)
julia> @code_warntype f([1:2,3:5])
Body::Any
1 1 ─ %1 = (Core._apply)(Base.Iterators.product, m)::Any │
│ %2 = (Main.collect)(%1)::Any │
└── return %2
julia> @code_warntype f((1:2,3:5))
Body::Array{Tuple{Int64,Int64},2}
1 1 ── %1 = (getfield)(m, 1)::UnitRange{Int64}
│ %2 = (getfield)(m, 2)::UnitRange{Int64}
│ %3 = (Core.tuple)(%1, %2)::Tuple{UnitRange{Int64},UnitRange{Int64}}
│ %4 = %new(Base.Iterators.ProductIterator{Tuple{UnitRange{Int64},UnitRange{Int64}}}, %3)::Base.Iterators.ProductIterator{Tuple{UnitRange{Int64},UnitRange{Int64}}}
│ (Base.ifelse)(true, 1, 0) Colon
│ %6 = (Base.getfield)(%1, :stop)::Int64 _similar_for
│ %7 = (Base.getfield)(%1, :start)::Int64 axes
│ %8 = (Base.Checked.checked_ssub_int)(%6, %7)::Tuple{Int64,Bool}
│ %9 = (Base.getfield)(%8, 1, true)::Int64 _prod_axes1
│ %10 = (Base.getfield)(%8, 2, true)::Bool axes
└─── goto #3 if not %10 ││││││││╻ size
2 ── invoke Base.Checked.throw_overflowerr_binaryop(:-::Symbol, %6::Int64, %7::Int64)
└─── $(Expr(:unreachable))│││││││││┃ checked_sub
3 ── goto #4 │││││││││││
4 ── %15 = (Base.Checked.checked_sadd_int)(%9, 1)::Tuple{Int64,Bool}erflow
│ %16 = (Base.getfield)(%15, 1, true)::Int64 indexed_iterate
│ %17 = (Base.getfield)(%15, 2, true)::Bool getindex
└─── goto #6 if not %17 ││││││││││╻ checked_add
5 ── invoke Base.Checked.throw_overflowerr_binaryop(:+::Symbol, %9::Int64, 1::Int64)
└─── $(Expr(:unreachable))││││││││││
6 ── goto #7 │││││││││││
7 ── goto #8 ││││││││││
8 ── goto #9 │││││││││
9 ── %24 = (Base.slt_int)(%16, 0)::Bool│╻╷╷╷ Type
│ %25 = (Base.ifelse)(%24, 0, %16)::Int64 Type
└─── goto #10 ││││││││
10 ─ goto #11 │││││││
11 ─ %28 = (Base.getfield)(%2, :stop)::Int64 _prod_axes1
│ %29 = (Base.getfield)(%2, :start)::Int64 axes
│ %30 = (Base.Checked.checked_ssub_int)(%28, %29)::Tuple{Int64,Bool}
│ %31 = (Base.getfield)(%30, 1, true)::Int64 length
│ %32 = (Base.getfield)(%30, 2, true)::Bool checked_sub
└─── goto #13 if not %32 │││││││││││╻ checked_sub
12 ─ invoke Base.Checked.throw_overflowerr_binaryop(:-::Symbol, %28::Int64, %29::Int64)
└─── $(Expr(:unreachable))│││││││││││
13 ─ goto #14 ││││││││││││
14 ─ %37 = (Base.Checked.checked_sadd_int)(%31, 1)::Tuple{Int64,Bool}erflow
│ %38 = (Base.getfield)(%37, 1, true)::Int64 indexed_iterate
│ %39 = (Base.getfield)(%37, 2, true)::Bool getindex
└─── goto #16 if not %39 │││││││││││╻ checked_add
15 ─ invoke Base.Checked.throw_overflowerr_binaryop(:+::Symbol, %31::Int64, 1::Int64)
└─── $(Expr(:unreachable))│││││││││││
16 ─ goto #17 ││││││││││││
17 ─ goto #18 │││││││││││
18 ─ goto #19 ││││││││││
19 ─ %46 = (Base.slt_int)(%38, 0)::Bool││╻╷╷╷ Type
│ %47 = (Base.ifelse)(%46, 0, %38)::Int64 Type
└─── goto #20 │││││││││
20 ─ goto #21 ││││││││
21 ─ goto #22 │││││││
22 ─ goto #23 ││││││
23 ─ goto #24 │││││
24 ─ %53 = $(Expr(:foreigncall, :(:jl_alloc_array_2d), Array{Tuple{Int64,Int64},2}, svec(Any, Int64, Int64), :(:ccall), 3, Array{Tuple{Int64,Int64},2}, :(%25), :(%47), :(%47), :(%25)))::Array{Tuple{Int64,Int64},2}
└─── goto #25 ││││
25 ─ %55 = invoke Base.copyto!(%53::Array{Tuple{Int64,Int64},2}, %4::Base.Iterators.ProductIterator{Tuple{UnitRange{Int64},UnitRange{Int64}}})::Array{Tuple{Int64,Int64},2}
└─── goto #26 │││
26 ─ goto #27 ││
27 ─ return %55 │