Skip to content

Map branded primitives to their carrier - #1

Closed
paoloanzn wants to merge 2 commits into
mainfrom
branded-primitives
Closed

Map branded primitives to their carrier#1
paoloanzn wants to merge 2 commits into
mainfrom
branded-primitives

Conversation

@paoloanzn

@paoloanzn paoloanzn commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Problem

scriptc run refused any program using the nominal-typing idiom:

type UserId = string & { readonly __brand: "UserId" };

mapType had no rule for an intersection whose carrier is a primitive, so a branded slot fell through to the index-signature path (SC2006) or the unresolved-intersection fence (SC2008).

Fix

Every value of a branded type is the primitive — the brand member exists only in the type world — so the mapping is the primitive's own. A branded slot holds, passes, and computes exactly like its unbranded twin.

This is the carrier-plus-refinement rule the builtin-handle intersections (netServer, httpReq, …) already follow, applied to a primitive carrier. Reads of a brand member still fence per site, like any other refinement member.

  • Two different primitive constituents make the intersection uninhabited, so it stays unmapped.
  • The refinement predicate the handle path used inline is now shared as isPlainRefinement, keeping one definition of "decorates but carries no runtime shape".
  • mapPrimitive is now the single table both the scalar dispatch and the brand rule read, so they cannot disagree.

Tests

tests/corpus/2694-branded-primitives.ts covers string/number brands, Brand<T,B> via unique symbol, methods and arithmetic on branded values, typeof pins, arrays/Set/Map, branded interface fields, JSON.stringify, unions with null, and generic passthrough.

tests/diagnostics/intersection-values.ts kept its SC2008 coverage by moving to a primitive-against-class intersection, which no value can inhabit.

Check Result
differential (C backend)
llvm-differential
diagnostics corpus 114/114
coverage on new program 32/32 statements, 100% static
lint / tsc --noEmit 0 errors

Note on the sandbox gate

pnpm test:sandbox could not be run to completion locally — the Vercel Hobby quota on the linked project is exhausted until 2026-10-01 (HTTP 402 at sandbox creation, before any test runs). The remote plain lane did pass EXIT=0 across 6 shards before the quota ran out. Please let CI run the full gate.

* Expand LLVM native output targets

- Add owned helper and runtime-pack contracts for macOS x64, Linux glibc/musl, Windows x64, and WASI.
- Generalize LLVM target selection, package validation, linker plans, and release/CI matrices.
- Document linker boundaries and add focused native-output and runtime-pack coverage.

* Pin Windows LLVM helper CI to VS 2022

The official LLVM archive needs the Visual Studio 2022 CMake generator, which is not guaranteed by windows-latest.

* Align bootstrap cache with LLVM runtime-pack builds

Use the same target-specific helper and linker identity before and after compiler loading so routed executable cache hits remain lightweight.

* Install Windows LLVM CI tools with Chocolatey

windows-2022 includes Visual Studio but not winget; use its available Chocolatey bootstrap for CMake and Ninja.

* Prevent Zig version probes from leaving runtime-pack objects

Run runtime-pack compiler version probes in a private temporary directory and remove the accidental tracked WASI a.o file.

* Use 7-Zip for Windows LLVM setup

Extract the official LLVM development archive with two-stage 7-Zip instead of Windows tar.exe, which timed out while materializing the toolchain tree.

* Define ssize_t for the MSVC runtime pack

Clang's MSVC target does not expose POSIX ssize_t through sys/types.h; define the pointer-sized runtime type without affecting MinGW.

* Build the Windows runtime pack with Zig

Use the MinGW-compatible Windows sysroot required by the runtime while retaining COFF helper output and installing Zig in the Windows native CI lane.

* Give macOS LLVM differential shard time to finish

Shard 1 passed its setup and focused contracts but was cancelled during its cold LLVM differential slice at the 20-minute job limit.
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview Sep 2, 2026 7:27am UTC

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: c964427c-7668-4b44-aca9-914ca6863c32


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

An intersection whose only runtime-carrying constituent is a primitive had
no mapping. Object-literal brands such as `string & { readonly __brand:
"UserId" }` fell through to the record path and reported SC2006, while
`unique symbol` brands reached the unresolved-intersection fence and
reported SC2008. Both forms describe a value that is the primitive itself
at runtime.

Map an intersection of one primitive constituent and any number of plain
object refinements to the primitive's own IR type. This is the
carrier-plus-refinement rule the builtin handle intersections already use,
applied to a primitive carrier. A branded slot now holds, passes and
computes exactly like its unbranded counterpart, and reads of a brand
member continue to fence per site like any other refinement member.

Keep the remaining cases fenced. A primitive against a class instance stays
unbuildable, and two different primitive constituents make the intersection
uninhabited, so neither maps. Share the refinement predicate the handle path
applied inline so both paths keep one definition of an object type that
carries no runtime shape, and route the scalar dispatch through the same
primitive table the new rule reads.

Add differential coverage for string and number brands, unique symbol tags,
methods and arithmetic on branded values, branded fields in interfaces,
arrays, Maps and Sets, and generic passthrough. Repoint the intersection
diagnostic fixture at a primitive against a class instance, which is the
case that still has no runtime shape.

Closes vercel-labs#282

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011KE8gQ92pFxakTj5obV1oB
@paoloanzn

Copy link
Copy Markdown
Owner Author

Superseded by the upstream pull request vercel-labs#283, which targets vercel-labs/scriptc and closes vercel-labs#282. This fork-local pull request was only used to run CI.

@paoloanzn paoloanzn closed this Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants