The last example will also become type-unstable if you increase d (and the length of the tuple).
julia> @code_warntype foo3(bar{11}(),(1,2,3,4,5,6,7,8,9,0,1,2))
Variables
#self#::Core.Const(foo3)
a::Core.Const(bar{11}())
x::NTuple{12, Int64}
#1::var"#1#2"{NTuple{12, Int64}}
Body::Tuple{Vararg{Int64, N} where N}
1 ─ %1 = Main.:(var"#1#2")::Core.Const(var"#1#2")
│ %2 = Core.typeof(x)::Core.Const(NTuple{12, Int64})
│ %3 = Core.apply_type(%1, %2)::Core.Const(var"#1#2"{NTuple{12, Int64}})
│ (#1 = %new(%3, x))
│ %5 = #1::var"#1#2"{NTuple{12, Int64}}
│ %6 = Main.ntuple(%5, $(Expr(:static_parameter, 1)))::Tuple{Vararg{Int64, N} where N}
└── return %6
The first example is type-stable while the other one is not since it’s handled differently in getindex, see https://github.com/JuliaLang/julia/blob/399f8ba175b1add5f8b8286097afd2deaada0a41/base/range.jl#L301-L314. Whenever the first and/or last index of the tuple are included in the range, there is a special case. The general case is just more demanding for the compiler. As @bramtayl wrote above, it might be possible to write another version based on recursion (similar to Base.front and Base.tail).