I’m working on a raytracer where I need a Point type, which is just a wrapper over StaticArray that has different properties when transformed than a vector. My current implementation is as follows:

Where I defined all the methods in the iterator and indexing interfaces to call down to p.
However, whenever I use broadcasting on a Point, the wrapper is lost, such that Point(SVector(1., 2.)) .+ 1 isa Vector{Float64}.

My two questions are

Why is the return type of the broadcast a Vector, and not SVector?

How do I correctly write the wrapper such that I can broadcast over a Point and get a new Point out?

Here’s my working solution for anyone stumbling here. Based on this other discourse post. The main issue I had is the documentation doesn’t describe how to implement broadcasting for immutable types, such as when the backing vector is a SVector.

struct Point{N, T<:Number} <: AbstractArray{T, 1}
p::SVector{N, T}
end; export Point
# Broadcasting
import Base.broadcastable
Base.broadcastable(point::Point) = point
struct PointStyle{N} <: Broadcast.BroadcastStyle end
import Base.Broadcast.BroadcastStyle
Base.Broadcast.BroadcastStyle(::Type{Point{N,T}}) where {N,T} = PointStyle{N}()
Base.Broadcast.BroadcastStyle(::PointStyle{N}, ::Broadcast.DefaultArrayStyle{0}) where N = PointStyle{N}()
import Base.Broadcast.materialize
function Base.Broadcast.materialize(B::Broadcast.Broadcasted{PointStyle{N}}) where N
flat = Broadcast.flatten(B)
args = flat.args
f = flat.f
ps = map(x -> x isa Point ? x.p : Ref(x), args)
Point(f.(ps...))
end

I have since run into an issue with this. Doing p .+ 1 works, however p .^ 2 did not. Changing the line ps = map(x -> x isa Point ? x.p : Ref(x), args) to just ps = map(x -> x isa Point ? x.p : x, args) (no Ref) fixes this. Is this the normal way to do this, or would this introduce further bugs?