Skip to content

fix: three jsval-representation gaps in checked-dynamic lowering - #203

Open
techfreaque wants to merge 92 commits into
vercel-labs:mainfrom
techfreaque:fix-jsval-gaps
Open

fix: three jsval-representation gaps in checked-dynamic lowering#203
techfreaque wants to merge 92 commits into
vercel-labs:mainfrom
techfreaque:fix-jsval-gaps

Conversation

@techfreaque

Copy link
Copy Markdown

Problem

Found while pushing a large real-world program (~600 TypeScript files) through scriptc build --dynamic. Three separate gaps, all sharing the same shape: a value already in the jsval (checked-dynamic island handle) representation hits a code path that only recognized dyn or a handful of other kinds, not jsval.

1. Union property reads on a jsval-lowered receiver crash the compiler outright. lowerUnionProperty handles a checker-union receiver whose value lowered to dyn/record/object, but not jsval — that case fell through to throw new Error("lowerer bug: union-typed receiver lowered to a non-union"), an unrecoverable process crash (not a diagnostic) that stopped the whole compile from producing any output at all.

2. A checked cast to a jsval-shaped target type incorrectly requires JSON-boundary validation. x as T where T maps to jsval (an npm/ambient-declared type with no static shape — e.g. many ORM query-builder return types) fell through the targetTs.flags & TypeFlags.Any fast path (that's for the literal any keyword, not an alias that happens to map to the same representation) into JSON-boundary validation, which a jsval target can never satisfy — producing the confusing error SC1090: a checked cast of 'any' to 'any' is not supported yet (formatIrType prints jsval as "any", hence the doubled wording).

3. A computed object-literal key that folds to a compile-time constant string was rejected outright. The 'any'-typed object-literal lowering only accepted Identifier/StringLiteral keys, rejecting the common { [Methods.POST]: {...} } / { [SomeEnum.Member]: value } shape even though the file already has literalComputedKey/foldedStringKeyOf utilities for exactly this fold, used by many other call sites in the same file.

Fix

  1. Added the missing jsval branch to lowerUnionProperty — the same generic island property read (jsOp/getProp) isIslandExpr's own jsval handling already uses elsewhere in the file. Also improved the crash message to include the offending kind/file/line, since it previously gave no way to find the triggering code.
  2. Added an early if (target.kind === "jsval") return inner; check, matching the existing Any-keyword fast path's semantics.
  3. pushProp's signature changed from ts.Identifier | ts.StringLiteral to { text: string; node: ts.Node } so a folded computed key (which has no single text-bearing AST node of its own) can flow through it; a small foldedKeyTextOf helper resolves identifier/string-literal/computed keys uniformly using the existing fold utilities.

Verification

Compiled the full next-vibe program through scriptc build --dynamic before and after each fix:

  • Fix 1 eliminated an unconditional process crash that previously prevented the compiler from producing any diagnostic output for this program at all.
  • Fix 2 eliminated 256 instances of the doubled "any to any" error.
  • Fix 3 eliminated 62 instances of the object-literal property-form error (142 computed-key call sites now resolve; some of those unlock further downstream diagnostics rather than a 1:1 error-count reduction).

packages/compiler builds clean (tsc -p tsconfig.json, 0 errors).

phocks and others added 30 commits July 28, 2026 11:26
Update README to reflect changes in build output path.
The runtime assumed mingw-w64 on Windows, which provides POSIX headers
and functions (ssize_t, clock_gettime, nanosleep, dirent.h, unistd.h)
that MSVC's CRT does not ship. Users opening VS2022 Developer Command
Prompt get MSVC's bundled clang (i686-pc-windows-msvc) instead of
mingw-w64, and every compilation fails with missing type/function errors.

Add _MSC_VER-guarded shims in scr_win.c:
- clock_gettime() over QueryPerformanceCounter (monotonic) and
  GetSystemTimeAsFileTime (realtime)
- nanosleep() over Sleep()
- opendir/readdir/closedir over FindFirstFileW/FindNextFileW
- CLOCK_REALTIME, CLOCK_MONOTONIC, struct timespec declarations

Guard POSIX header includes in scr_lib.c, scr_path.c, scr_url.c
with _MSC_VER checks, providing CRT equivalents (_getcwd, _access,
_isatty) where needed.

Fixes vercel-labs#25
Run on push/PR to fix/msvc-posix-shims only. Tests the exact
scenario from vercel-labs#25: compiling the runtime with MSVC's bundled clang
(no mingw, no zigcc) on windows-latest, including the Map + sort
pattern that bare.ts uses. Also runs a Linux corpus smoke test
to verify no regressions.
- Remove #include <windows.h> from scr_runtime.h's _MSC_VER block:
  it pulled winsock.h (via windows.h) into every TU, conflicting
  with winsock2.h included later in scr_lib.c
- Guard struct timespec with #ifndef _TIMESPEC_DEFINED: modern UCRT
  already defines it, so redefinition caused C1104 errors on CI
The _TIMESPEC_DEFINED guard didn't work because UCRT's time.h
defines struct timespec (line 45) but doesn't set _TIMESPEC_DEFINED
in the clang/MSVC mode we're compiling in. Remove the definition
entirely — UCRT 10.0.26100.0 provides it, and scr_win.c's
clock_gettime/nanosleep use it without redefining.
MSVC CRT lacks several POSIX constants/types used throughout the runtime:
- PATH_MAX (use _MAX_PATH from stdlib.h)
- mode_t (typedef unsigned int)
- F_OK (value 0)
- S_ISDIR/S_ISREG macros (use _S_IFMT/_S_IFDIR/_S_IFREG from sys/stat.h)
- S_ISLNK/S_ISFIFO/S_ISSOCK/S_ISBLK/S_ISCHR (stub as 0 on Windows)

These are guarded by _MSC_VER so mingw-w64 and Linux are unaffected.
WideCharToMultiByte silently drops the null terminator when the
UTF-8 output fills all 260 bytes of d_name. Force-terminate after
the conversion to prevent out-of-bounds reads by callers.
- Mark node:crypto, node:zlib, and node:fs(+fs/promises) as partial: their island shims cover only a slice of Node's surface and the rest throw at the call.
- Report partial shims as 'partial' in the --dynamic builtins table, with a note, so they are not indistinguishable from fully implemented shims.
- Add a coverage snapshot for the crypto-shims fixture; full shims stay 'shimmed' (the esbuild-require snapshot pins that side).
refine() derived clearNaN from the negated operator, treating the
failed edge of a < b as a >= b having held — the two differ exactly
when a side is NaN, so guard-clause spellings let NaN be "proven"
whole and cross a declared i64/u64 slot as an unchecked (int64_t) /
fptosi conversion. Judge NaN exclusion by the relation that actually
held on the edge; the numeric interval refinement is unchanged.

Co-authored-by: Cursor <[email protected]>
… kinds, C-emission mapping

Adds declare module midi/node:midi, midiInput/midiOutput IR handle kinds,
moduleUsesMidi predicate, type mapping, module registry entries, and the
C-representation/retain/release mapping in the emission layer.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
Refcounted midi Input/Output handles over the event-loop poller seam,
off-thread callback bridging via self-pipe, number[] message delivery with
deltaTime, virtual-port loopback on POSIX, header decls and scr_async.c
loop hook. Falls back to a stub backend where no MIDI stack is present.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EYLKF6JBn2Fozts9CGr9W6
# Conflicts:
#	packages/runtime/src/scr_runtime.h
#	packages/runtime/src/scr_url.c
… the pinned OPAQUE-global destructuring fence test and left the mechanism dead); keep only the well-scoped globalHints crypto message improvement
…e PR's own now-merged branch name, would never fire again)
Found while pushing a large real-world program (next-vibe) through
--dynamic. All three are gaps where a value already in the `jsval`
(checked-dynamic island handle) representation hits a code path that
only recognized `dyn` or a handful of other kinds, not `jsval`.

1. **Union property reads on a jsval-lowered receiver crash the
   compiler.** `lowerUnionProperty` (lower-exprs.ts) handles a
   checker-union receiver whose VALUE lowered to `dyn`/`record`/`object`,
   but not `jsval` (an optional-chained receiver whose narrowed arms all
   trace back to one dynamic/npm value) — that case fell through to
   `throw new Error("lowerer bug: union-typed receiver lowered to a
   non-union")`, an unrecoverable process crash rather than a
   diagnostic. Fixed by adding the missing `jsval` branch (the same
   generic island property read `isIslandExpr`'s own jsval handling
   already uses elsewhere in this file). Also improved the crash
   message itself to include the offending kind, file, and line — it
   previously gave no way to find the triggering code at all.

2. **A checked cast to a jsval-shaped target type incorrectly requires
   JSON-boundary validation.** In the `as T` cast lowering, a jsval
   receiver cast to a target type that ITSELF maps to `jsval` (an
   npm/ambient-declared type with no static shape — e.g. many Drizzle
   ORM query-builder return types) fell through the
   `targetTs.flags & TypeFlags.Any` fast path (that check is for the
   literal `any` keyword, not an alias that merely happens to map to
   the same representation) into `boundarySafe`'s JSON-representable-
   type validation, which a jsval target can never satisfy — producing
   `error SC1090: a checked cast of 'any' to 'any' is not supported
   yet` (formatIrType prints `jsval` as `"any"`, hence the confusing
   doubled wording). Fixed by returning the receiver unchanged when the
   target itself is jsval-shaped, matching the existing `Any`-keyword
   fast path's semantics.

3. **A computed object-literal key that folds to a compile-time
   constant string was rejected outright.** The 'any'-typed
   object-literal lowering only accepted `Identifier`/`StringLiteral`
   keys, rejecting the extremely common `{ [Methods.POST]: {...} }` /
   `{ [SomeEnum.Member]: value }` shape (a computed key referencing an
   enum member or other compile-time-constant string) even though this
   file already has `literalComputedKey`/`foldedStringKeyOf` utilities
   for exactly this fold, used by many other call sites in the same
   file. Fixed by using the same fold here; `pushProp`'s signature
   changed from `ts.Identifier | ts.StringLiteral` to a plain
   `{ text: string; node: ts.Node }` pair so a folded computed key (no
   single text-bearing AST node) can flow through it too.

## Verification

Compiled the full next-vibe program (~600 TypeScript files) through
`scriptc build --dynamic` before and after:
- Fix 1 eliminated an unconditional process crash (`lowerer bug:
  union-typed receiver lowered to a non-union`) that previously
  prevented the compiler from producing ANY diagnostic output at all
  for this program.
- Fix 2 eliminated 256 instances of the doubled "any to any" error.
- Fix 3 eliminated 62 instances of the object-literal property-form
  error (142 computed-key sites now resolve; the remaining error-count
  delta reflects some of those unlocking further, unrelated downstream
  diagnostics rather than a 1:1 elimination).

`packages/compiler` builds clean (`tsc -p tsconfig.json`, 0 errors).
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

@techfreaque is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​julusian/​midi@​3.8.19010010090100

View full report

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.