Downside is that it isn’t visually as obvious as [[1] [2]; [3] [4]] would be (if it worked), so probably needs a comment alongside it to explain what it’s doing.

I think you misunderstood the question; the point is not create the matrix in the MWE, but to construct a matrix using the [ ; ] syntax without concatenation, eg generally

[a b; c d]

where a::Union{AbstractVector,AbstractMatrix} etc.

The simplest solution may be a wrapper type, eg along the lines of

"""
A wrapper type that should be used on the element of a `hvcat` (`[...; ...]`) call to
prevent concatenation of the arguments.
"""
struct NoCat{T}
x::T
end
function Base.hvcat(rows::Tuple{Vararg{Int}}, x1::NoCat, xR...)
nrow = length(rows)
ncol = rows[1]
@assert allequal(rows)
xs = promote(x1.x, xR...)
T = typeof(xs[1])
m = Matrix{T}(undef, nrow, ncol)
j = 1
for row in 1:nrow
for col in 1:ncol
m[row, col] = xs[j]
j += 1
end
end
m
end

I agree with this, but I was just looking for a syntax that makes a matrix using the same visual arrangement without concatenating the elements. Julia does not have (built-in) syntax for a matrix constructor that does nothing else (cf the [] syntax for vectors). Fortunately it can easily be added.

I think it’d be a bit cleaner to instead abuse typed_hvcat like what StaticArrays.jl does:

struct var"typeof(NoCat)" end
const NoCat = var"typeof(NoCat)"()
Base.show(io::IO, ::var"typeof(NoCat)") = print(io, "NoCat")
function Base.typed_hvcat(::var"typeof(NoCat)", rows::Tuple{Vararg{Int}}, _xs...)
nrow = length(rows)
ncol = rows[1]
@assert allequal(rows)
xs = promote(_xs...)
T = typeof(xs[1])
m = Matrix{T}(undef, nrow, ncol)
j = 1
for row in 1:nrow
for col in 1:ncol
m[row, col] = xs[j]
j += 1
end
end
m
end