Map branded primitives to their carrier type - #283
Open
paoloanzn wants to merge 1 commit into
Open
Conversation
Contributor
|
@paoloanzn is attempting to deploy a commit to the Vercel Labs Team on Vercel. A member of the Team first needs to authorize it. |
A branded type is a primitive joined to a marker object, for example
`string & { readonly __brand: "UserId" }`. The marker exists only in the
type system; at runtime the value is an ordinary string. The compiler had
no rule for this shape, so it failed in two different ways: brands written
as an object literal were treated as records and reported SC2006, and
brands written with a `unique symbol` key reached the unresolved-
intersection fence and reported SC2008.
Map these types to the primitive they wrap. When an intersection has
exactly one primitive part, and every other part is a plain object type
that adds nothing at runtime, the whole type now maps to that primitive.
A branded value is stored, passed and computed exactly like an unbranded
one. Reading a brand member is still refused at the point of use, like any
other type-only member.
Two shapes stay refused because they describe no runtime value: a
primitive joined to a class instance, since nothing can be both, and an
intersection of two different primitives. The test for a type-only object
part already existed inline in the handle-intersection path and is now a
shared helper, so both paths use one definition. The primitive lookup is
shared with the scalar mapping for the same reason.
A new differential program covers string and number brands, `unique
symbol` tags, string methods and arithmetic on branded values, branded
fields in interfaces, arrays, Maps and Sets, and generic passthrough. The
intersection diagnostic fixture now uses a primitive joined to a class,
which is the case that still cannot compile.
Closes vercel-labs#282
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011KE8gQ92pFxakTj5obV1oB
paoloanzn
force-pushed
the
branded-primitives
branch
from
September 2, 2026 07:39
42a5322 to
a1cd695
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A branded type is a primitive joined to a marker object, for example
string & { readonly __brand: "UserId" }. The marker exists only in the type system; at runtime the value is an ordinary string. The compiler had no rule for this shape, so it failed in two different ways: brands written as an object literal were treated as records and reported SC2006, and brands written with aunique symbolkey reached the unresolved-intersection fence and reported SC2008.Map these types to the primitive they wrap. When an intersection has exactly one primitive part, and every other part is a plain object type that adds nothing at runtime, the whole type now maps to that primitive. A branded value is stored, passed and computed exactly like an unbranded one. Reading a brand member is still refused at the point of use, like any other type-only member.
Two shapes stay refused because they describe no runtime value: a primitive joined to a class instance, since nothing can be both, and an intersection of two different primitives. The test for a type-only object part already existed inline in the handle-intersection path and is now a shared helper, so both paths use one definition. The primitive lookup is shared with the scalar mapping for the same reason.
A new differential program covers string and number brands,
unique symboltags, string methods and arithmetic on branded values, branded fields in interfaces, arrays, Maps and Sets, and generic passthrough. The intersection diagnostic fixture now uses a primitive joined to a class, which is the case that still cannot compile.Closes #282
Written with AI assistance and reviewed by a human.