Skip to content

v1.7.4 — Unions carry what they accept

Back to all updates
v1.7.4August 28, 2026

A union refuses members it cannot tell apart, and hostile payloads no longer cost memory.

Unions refuse members they cannot tell apart

Section titled “Unions refuse members they cannot tell apart”

A Union works out which member a value belongs to from the value itself. Where two members could both answer to one value, it used to pick the first it found.

local Payload = Net.Types.Union(
Net.Types.Map(Net.Types.String, Net.Types.NumberU8),
Net.Types.Array(Net.Types.NumberU8)
)

Both members are a table when sent. The array was tried first, so every table was written as an array, and a map arrived empty, with no error or warning. The same fault had two other forms:

  • Union(NumberVarU, NumberF32) sent 1.5 and delivered 1. Its signed twin delivered -2.
  • Union(Color3, Color3F32) quantised a colour to eight bits.

Members that share a runtime shape are now told apart by the widest of them, which carries everything the others could:

Union(NumberU8, NumberF64) -- keeps its double
Union(Vector3F32, Vector3F16) -- keeps its precision
Union(CFrame, CFrameRot) -- keeps its skew

A union with no such member is refused where it is declared, not where it is sent:

BuyItem argument 1 names 2 members that all look like table when sent,
and nothing says which one a value meant

UDim, UDim2, Rect, Region3, NumberRange, TweenInfo, DateTime, BrickColor, NumberSequence and ColorSequence could be declared in a union but never carried by one. They travel now.

A member of a kind that still cannot be recognised, such as Any, Nil or Static, is refused at the declaration instead of at the first send.

  • Memory exhaustion in Compress. A run of values carries its own count, and a table was built for that count before the values were read. Five bytes could ask for about 90 MB. A count is now weighed against the bytes that arrived with it, so a malformed payload returns nil.
  • A replication guard that covered half its key. SetValidator refused Set and Mutate, but never saw SetPath, SetPathFor, Increment or IncrementFor. Every write now answers to the guard, and a field is tried on a copy first, so a refusal leaves nothing behind.
  • Unmetered instances. Metering weighed a message’s payload but not the instances travelling beside it. Both are charged now.
  • A colour crossing through a channel no screen can show returns a valid colour instead of nan.
  • Tween applies the same finite-number rule to a rate and a repeat count as to a time and a delay.
  • The descriptions of Navigation and Net.List now match what they do.

Every change in this release is listed in the changelog.