You’re misunderstanding things:
julia> list_bad = [Bad([i]) for i=1:1000];
julia> eltype(list_bad) == Good
true
The last parameter is calculated correctly, even for just Bad:
julia> Bad([1]) |> typeof
SMatrix{1, 1, Int64, 1} (alias for SArray{Tuple{1, 1}, Int64, 2, 1})
It’s just that in Container{Bad}, you force the type of its only field to be non-concrete, even if the element saved there is completely specified:
julia> ex1 = Container{Bad}(Bad([1]))
Container{SMatrix{1, 1, Int64}}([1])
julia> fieldtypes(typeof(ex1))
(SMatrix{1, 1, Int64},)
julia> typeof(ex1.x)
SMatrix{1, 1, Int64, 1} (alias for SArray{Tuple{1, 1}, Int64, 2, 1})
Since inference relies on the fieldtype to check which type to propagate, it fails and just gives you Vector. After all, at compile time it can’t know that there will be an element in there with a tighter specification than what you explicitly request with Container{Bad}. Additionally, inference is not aware of the special invariant imposed by the first two type parameters on the last one, so it can’t assume that it will be concretely typed anyway.
E.g. with Container{Bad} it’s the same as if you had written it like this:
struct BadContainer
x::SMatrix{1,1,Int64} # non-concrete type.
end
All this is generally documented, in the form of “avoid abstract/non-concrete fields”.