Commit Graph
504 Commits
Author SHA1 Message Date
Chris Tate 686c34bd25 Store tty.isatty and process.cwd as function values (#2494) 2026-10-28 22:46:19 -05:00
Chris Tate 3e7528ca86 pi wave 4: path values, union delete, NonNullable unions, BigInt stats, optional-param function values, live accessor literals, clearer stdlib-alias errors (#2497) 2026-10-28 22:33:16 -05:00
Malte Ubl db617bd594 Merge remote-tracking branch 'origin/main' into fix/pi-conversions-layouts 2026-10-28 18:30:14 -07:00
Malte UblandClaude Opus 5.5 caf098eff9 Refuse a stdlib-global snapshot read as a value
`const proc = globalThis.process` lowers to alias plumbing: the declaration
emits no storage and no code, because member and call reads resolve through
the global it names. A read that needs the VALUE therefore failed at the use
with the generic "binding form with no lowering", which names neither the
declaration nor the cause.

Register every stdlib-global snapshot declaration (TypeScript sources only,
matching the elision) and report the cause at the declaration (SC2020),
naming the global and, when it has a short spelling, the type the program
asked the compiler to hold. The use site keeps the inherited-blocker cascade
(SC2004) instead of the generic text.

An unread snapshot stays elided: reading a global has no side effects, so
dropping an unread `const p = process` is observationally identical to Node,
and refusing it would break programs that compile harmlessly today.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:58:04 -07:00
Malte UblandClaude Opus 5.5 7245ec9a04 Admit function values whose completed parameters the slot coercion adapts
A method declaring `ctx?: Ctx` completes to `(id, Ctx | undefined) => string`;
a slot that requires the narrower `(id, ToolCtx) => string` was refused with
SC1090 at the value's birth, because the gate only compared the two
signatures for equality. The slot coercion (funcCoerceAdapter) already
converts every parameter through coercibleValue, the record width conversion
(which also selects a width-liftable arm of a union destination — the
`ToolCtx` into `Ctx | undefined` shape), the partial checked-dynamic
extraction, and the union arm preservation. `funcSlotAdaptable` now asks the
same question through `cleanParamPair`, so the gate admits exactly the pairs
the adapter converts without a trap-only path, and both call sites (the
object-literal method slot and the mapped value target) use it.

The gate also learns the two slot shapes it could not name: an object
literal contextualized by an INDEX SIGNATURE (`Record<string, Handler>`)
takes its member slot from the index signature's value type, and a literal
with no contextual type takes the member's own completed type — the type
every later read of the member spells.

Nested function DECLARATIONS are no longer judged at birth: lowering a
declaration builds the body, not a value, and each value use re-checks the
completed signature against that use's own target. An overloaded nested
declaration with an optional parameter, returned as a value (`return write`
into a one-parameter `Writer`), is now accepted instead of refused.

Corpus: tests/corpus/optional-param-function-values.ts (a required slot over
an optional parameter, an index-signature member with an extra trailing
optional parameter, an annotated value with a defaulted parameter, the nested
overloaded declaration returned as a value, direct-call controls, and a
union-typed slot). Node-identical stdout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:58:02 -07:00
Malte UblandClaude Opus 5.5 cf7814d19c Lower fs statSync/lstatSync/fstatSync with { bigint: true }
Node answers a BigIntStats when the stat call is passed `bigint: true`: the
same snapshot, read through a bigint member table. Every number member of
Stats reads as a bigint — the integers, the `*Ms` millisecond members (the
truncated nanosecond quotient, as the binding computes them) and the
BigIntStats-only `*Ns` nanosecond members (sec * 1e9 + nsec, exactly) —
while only the four time members stay Dates. scriptc refused the option
outright.

The representation is static, chosen by the option literal: the lowerer
validates `bigint` against the call's result type (so
`{ bigint: true, throwIfNoEntry: false }` still narrows
`BigIntStats | undefined`), refuses a runtime-valued option and an option
that disagrees with the destination, and keeps every other option key
fenced. fstatSync joins statSync/lstatSync on the same path, with the
descriptor evaluated before the options.

The IR gains a `bigintStats` handle kind beside `stats`: the same RC
family, the same non-traced leaf and non-dynamic handle treatment, so no
shape, tracing or dyn-check behavior moves for any program that never asks
for one. Its member table is a `bigstats.*` lib-call family whose Date
members reuse the existing stat reads.

The runtime support is its own unit, scr_stats_bigint.c: the snapshot
layout moved into scr_stats_private.h so the bigint getters can read the
same fields without scr_lib.c carrying their code. The unit is gated on the
existing `bigint` feature in both the runtime-pack matrix and the
compiler's from-source unit table (the sanitized lane links from that one),
and `-ffunction-sections` keeps a bigint program that never reads a member
from linking any of the getters. No typed program's IR or LLVM changes.

tests/corpus/7018-bigint-stats.ts pins the member table, the Date/bigint
split, ms/ns agreement, arithmetic on a member, lstat over a symlink,
fstatSync over a descriptor, the throwIfNoEntry union, and the plain Stats
answers of the un-optioned and `bigint: false` forms against Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:58:01 -07:00
Malte UblandClaude Opus 5.5 4c4a53d538 Name the arms a union-typed spread merge cannot pick between
A spread merge like `{ ...base, ...override }` whose sources are union
types has one field set per combination of source arms, so the merged value
can carry fields from several arms at once. No single arm of the
destination union can hold it — naming one would drop the other arms'
fields, which Node keeps — and no runtime tag would be honest. The merge
was refused with the generic "cannot be compiled yet" (SC2001), which says
nothing about the shape the program is building.

The new SC2003 names the destination union and lists the candidate arms,
and its hint gives the two repairs: merge ONE arm per path (narrow every
spread source to the same arm first), or declare one open record type whose
fields are all optional instead of a union of all-optional arms. The
diagnostic fires only for a union-typed destination; other non-record
destinations keep badType.

`NonNullable<T>` over a union already maps to the union value itself, so
tests/corpus/7017-nonnullable-union.ts pins the mapped-type path
(assertions, generic instantiations, narrowing, serialization) against
Node. tests/diagnostics/union-spread-merge.ts re-pins the refusal, and the
union-clone-override-width snapshot moves from SC2001's generic text to
the arm-naming SC2003.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 17:58:00 -07:00
Chris Tate db30118a0e Store tty.isatty and process.cwd as function values
Fixes #2408
Co-authored-by: MathurAditya724 <57684218+MathurAditya724@users.noreply.github.com>
Fixes #2402
2026-10-28 19:18:01 -05:00
Malte UblandClaude Opus 5.5 ea23feeb21 Merge a union's promise arms into one promise of their payload union
`void | Promise<A> | Promise<void> | Decision` (the agent-loop `finishTurn`
hook, codemode's remote client) had no representation: a union arm's index is
its runtime tag and a promise arm's tag IS the `instanceof Promise` test, so a
second promise arm had no tag. The union was refused (SC2009) and every use
site inherited the blocker.

JavaScript cannot tell the arms apart: `instanceof Promise` answers true for
both and `await` settles to either payload. The union therefore maps to one
promise arm `Promise<A | undefined>`: payloads join like union arms (`void`
becomes the undefined unit, an already-union payload flattens, identical
payloads dedupe, a checked-dynamic payload absorbs the arms the dynamic
representation holds faithfully). A payload with no union arm home (a RegExp
payload, a promise-of-a-promise) keeps the whole union fenced, and the
refusal now says why.

A `Promise<A>` value flowing into the merged arm converts with the existing
checked promise-payload widening (promisePayloadWidens / promiseWiden): the
promise keeps its identity and its settlement turn, and the settled payload
is checked against the payload slot.

Cost: the merge only runs for a union that already has two promise arms,
which is refused today. Every other union interns the same arms in the same
order, so a fully typed program without such a union compiles to identical
IR.

Corpus 8414-promise-union-arms pins identity through the union slot,
`instanceof Promise` narrowing, `await`, `.then`, the void arm, the rejection
path and a non-promise data arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:17:05 -07:00
Chris Tate e80b5f1447 Compile .length of computed tuple expressions
`.length` of a tuple is its arity, and the lowering folded it to that
constant only when the receiver was rooted at a binding or `this`. Any
other receiver (a call result, a ternary, an `as` assertion, an `as
const` literal) was refused with SC1090.

The receiver is now evaluated once, for its side effects and exceptions,
and its value discarded; the expression is the arity. A binding, `this`,
or a chain of plain property reads from one still folds. A chain through
an accessor is evaluated, so an accessor that returns a tuple runs, as in
Node.
2026-10-28 14:57:21 -05:00
Malte UblandClaude Opus 5.5 ac2323fb2a Retire the spread diagnostics fixtures their corpus programs replace
`tests/diagnostics/spread-fences.ts` pinned the conditional-spread fence
(`...(c ? { x: 1, y: 2 } : { x: 3, y: 4 })`) and
`spread-runtime-optional.ts` pinned the `{ ...value }` spread over a
callback's runtime-optional record. Both shapes compile now, so neither
file can stay in a suite whose contract is that every program in it fails;
the differential corpus owns them instead (`8501-object-spread-optional-source`,
`8503-object-spread-conditional-shapes`), where the programs run under Node
and as a compiled binary rather than pinning a refusal.

The rest of `spread-fences.ts` already compiled before this branch, and the
fences it named that remain (an element-type mismatch, a non-tuple call
spread) are ordinary type errors, so the file goes with the fence it pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:37:13 -07:00
Malte UblandClaude Opus 5.5 91deb8b5a0 Lower a tuple read with a runtime index as a positional dispatch
`t[i]` on a fixed-shape tuple where `i` is not a literal refused with
SC1090. A tuple is a positional record, so the read now dispatches over
its arity: the element at an integer index in [0, arity), and undefined
for anything else — a fraction, a negative, NaN, Infinity, or one past
the end, exactly Node's array-like read.

The answer type is the join of the element types plus undefined, the
same policy the array index read uses, so a consumer observes a miss
like any other optional element read: `t[i]!` narrows through the
existing checked-narrow path and arithmetic on a miss is NaN. The
receiver and the index are hoisted into hidden locals when they cannot
be repeated (receiver first, since JavaScript evaluates it before the
key). The chain stays inline because the arity is a compile-time
constant, unlike the lifted array helper whose length is not.

Writes keep their fence: a stored position must be a literal index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:37:03 -07:00
Chris Tate 3a476d5f32 Fix the X509 recursive-type crash and lower the refused array species forms (#2467) 2026-10-28 09:45:10 -05:00
Chris Tate ec79f2aca0 Destructure narrowed and joined union sources, await promise-armed unions, support writeFileSync(fd) (#2466) 2026-10-28 08:57:38 -05:00
Chris Tate f041da4b10 Drop the computed Array.isArray refusal from the stdlib fence snapshot
`Array.isArray([1])` folds after evaluating its argument, so the stdlib
fence battery no longer reports SC1090 for it. The snapshot drops that
diagnostic and the statement notes that it lowers.
2026-10-28 08:56:20 -05:00
Malte UblandClaude Opus 5.5 a0a6910e4e Destructure union sources by narrowing first and joining arm reads
An object pattern over a union whose arms type a field differently, or do
not declare it at all, was refused with SC1031, even though the pattern
element's own type is the checker's answer: the join of the arms' declared
types, with undefined for an arm that omits the key, or the narrowed arm
when a discriminant test or an `in` guard refined the source at the
pattern's site.

Keep the unionDisc fast path wherever every arm declares the field with
one type, so programs that compile today emit identical IR. Otherwise read
through the existing keyed join (unionKeyGet): each arm answers its
declared type and a record arm that does not declare the key answers
undefined, which is exactly the desugar's own property read. Only
destructuring passes the new missingFieldAsUndefined flag, so dotted reads
keep their current refusals.

Narrow first when the un-narrowed pattern does not fit: bind the
checker-narrowed view of the source to a hidden local — a trusting
arm extraction for one arm, a sub-union re-tag whose excluded arms trap,
and the stored layout view for an intersection refinement — and lower the
pattern over that view.

A rest element over a union source packs each arm's unconsumed fields into
a fresh record of the checker's arm-wise rest type, selected by the
runtime tag; CopyDataProperties makes a fresh object too, so nothing
aliases the source. Shapes that do not spell the rest type still fence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:37:58 -07:00
Malte Ubl a1742e6377 Merge branch 'ni/i2-handles' into ni/i3-exports 2026-10-27 19:14:25 -07:00
Chris Tate 349feb5e47 Capture stack traces with Error.captureStackTrace as Node does (#1809) 2026-10-27 13:49:06 -05:00
Malte Ubl 0d054133ad Merge remote-tracking branch 'origin/ni/i2-handles' into HEAD
# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-classes.ts
2026-10-26 11:25:53 -07:00
Chris Tate de17158254 Merge main 2026-10-26 06:51:26 -05:00
Chris Tate 2ad699c98a Compile classes that extend Array (#2059) 2026-10-26 03:57:16 -05:00
Chris Tate 0ba138b57e Merge main 2026-10-26 03:25:54 -05:00
Chris Tate 055d1f9682 Fix eight compiler and type bug reports
Co-authored-by: dolphin278 <1130473+dolphin278@users.noreply.github.com>
Fixes #2139
Fixes #2131
Fixes #2132
Fixes #2133
Fixes #2140
Fixes #2141
Fixes #2159
2026-10-26 01:50:59 -05:00
Chris Tate 21bce9e4fa Merge main 2026-10-26 00:21:24 -05:00
Chris Tate d48e060076 Merge main 2026-10-25 22:34:40 -05:00
Chris Tate 8a71a20e32 Merge ni/i2-handles 2026-10-25 21:39:21 -05:00
Chris Tate 826519133f Merge main 2026-10-25 14:49:01 -05:00
Chris Tate 1b9a4e1361 Refuse SC9001 cases and merge main 2026-10-25 13:26:27 -05:00
Chris Tate eca815da02 Merge main 2026-10-25 10:55:32 -05:00
Chris Tate 466ae48980 Merge main 2026-10-25 09:42:07 -05:00
Chris Tate b7915b92a7 Merge main 2026-10-25 06:38:44 -05:00
Chris Tate 5da6d76085 Run lone array callbacks as loops with their bodies inlined (#1997) 2026-10-25 05:24:07 -05:00
Chris Tate 0eef6251de Merge main 2026-10-25 02:28:32 -05:00
Chris Tate 0b4301bc3e Merge base and fix 2026-10-25 02:21:39 -05:00
Chris Tate 2e052854c5 Merge main 2026-10-25 01:21:07 -05:00
Chris Tate 5d3e0c7a8b Support X509Certificate fields and methods, TLS certificates and tls.Server contexts (#2052) 2026-10-25 01:00:24 -05:00
Chris Tate 468ef7692b Drop the refused never-promise instanceof case from the maybe-promise fences
instanceof Promise on values that are never promises now compiles to
false after the operand is evaluated, so the diagnostics fixture no longer
lists it as refused. 8326-never-promise-instanceof covers records, arrays,
dates, collections, regular expressions, byte arrays and unions with null,
and a call operand that still runs.
2026-10-24 23:35:56 -05:00
Chris Tate d775fdee8c Merge main 2026-10-24 23:26:30 -05:00
Chris Tate fc076258e4 Merge main 2026-10-24 20:07:16 -05:00
Chris Tate 9e55835014 Compile more npm and CommonJS JavaScript dependencies statically (#2122) 2026-10-24 18:30:56 -05:00
Chris Tate 6c18c58708 Merge main 2026-10-24 18:21:25 -05:00
Chris Tate 9c670e8d93 Count the arguments a function receives when it is called through a value (#1880) 2026-10-24 17:58:41 -05:00
Chris Tate a711d74ecf Keep copied array callbacks exact with local classes, switches and contracts
- A binding that a local class captures counts as written: the class's
  methods may assign it while the loop runs, so the call keeps its helper.
- Copied bodies rename the labels of switch and for-of statements and the
  binding of for-of loops.
- The known-union and record-temporary cleanups rewrite switch cases and
  for-of bodies, where they left declarations of removed locals.
- Copied locals keep their source bindings. In programs that write
  contract types, a parameter with a source binding keeps its own local,
  so an Int32 parameter is checked against the elements the loop passes.
2026-10-24 17:35:51 -05:00
Chris Tate 8d9705e22d Run CommonJS top-level return, untyped assert forms, and untyped instanceof Object as Node does (#2101) 2026-10-24 17:01:39 -05:00
Chris Tate 6a8ba89d9f Make Dates mutable objects with Node's setters and conversions (#2072) 2026-10-24 16:21:06 -05:00
Chris Tate 4f0975cbbe Compile more npm and CommonJS JavaScript dependencies statically 2026-10-24 12:01:26 -05:00
Chris Tate d9f1457cd5 Run CommonJS top-level return, untyped assert forms, and untyped instanceof Object as Node does 2026-10-24 08:07:52 -05:00
Chris Tate f23447dc99 Merge main 2026-10-24 06:50:53 -05:00
Chris Tate f13ae4495d Merge main 2026-10-24 02:22:49 -05:00
Chris Tate 09e50bee50 Merge main 2026-10-24 01:58:41 -05:00