Splatting an iterable can reduce retyping when there are more cases or the name needs to change: Union{(ArbitraryMesh{i} for i in 1:3)...}. I would prefer something like struct Mesh{D} end; const MeshTrio = Union{(Mesh{i} for i in 1:3)...} because writing Mesh{2} is easier than ArbitraryMesh{2}, and you can’t tack a parameter onto a Union to retrieve one of its members.

AFAIK, what densmojd is thinking of seems impossible now. Inner constructors generally check constraints because redefinable methods and mutable instances can change what constraint checks do, yet type annotations and previous instances of the type can’t feasibly change with them and must use the “obsolete” types. Subtype checking is a built-in function <:, and types can’t be changed, so those are fine to incorporate into the type. isa is also a built-in function, so that seems feasible as a future parameter constraint. On the other hand, membership checking is a generic function in. in(::Any, ::Tuple) falls back to an iteration-based in(x, itr) method, so there’s no underlying built-in function to use instead. We’d also need something semantically different for parameter membership because missing in (1, missing) is missing, not true. If parameter constraints are ever expanded beyond subtyping checks, such a built-in function will probably show up.

If code duplication is an issue, you could do this:

struct Mesh{D<:Union{map(i -> Val{i}, 1:3)...,}} end

Hardcoded singleton types, each representing an integer

struct N1 end
struct N2 end
struct N3 end
struct Mesh{D<:Union{N1,N2,N3}} end

Set-theoretic construction of the natural numbers

This is like the above, but avoids hardcoding the values of the natural numbers. Inspired by a construction of the natural numbers, Zermelo ordinals.

module Naturals
# Arbitrarily choose to start counting from zero
struct Zero end
struct Natural{Previous} end
successor(::M) where {M<:Union{Zero,Natural}} = Natural{M}()
natural(::Val{0}) = Zero()
function natural(::Val{N}) where {N}
N::Int
signbit(N) && throw(ArgumentError("negative"))
successor(natural(Val(N - 1)))::Natural
end
natural(n::Int) = natural(Val(n))::Union{Zero,Natural}
natural_type(n::Union{Int,Val}) = typeof(natural(n))
end
struct Mesh{D<:Union{map(i -> Naturals.natural_type(i), 1:3)...,}} end

julia> struct Mesh{D<:Union{Val{1},Val{2},Val{3}}} end
julia> numdim(mesh::Mesh{Val{D}}) where {D} = D
numdim (generic function with 1 method)
julia> numdim(Mesh{Val{2}}())
2