Commit Graph
133 Commits
Author SHA1 Message Date
Chris Tate 37df3c4dfd Support split CommonJS require bindings (#1945) 2026-10-23 19:50:13 -05:00
Chris Tate bf68b3860c Skip split require calls by identity and climb parentheses in early-read checks 2026-10-23 19:05:58 -05:00
Chris Tate b09d5c6862 Merge main 2026-10-23 17:43:25 -05:00
Chris Tate 02597fa3e8 Merge main and give split require cycles the const form's admission 2026-10-23 17:15:33 -05:00
Chris Tateandthekevinbrown 19d6736b01 Look up exports whose names start with "__" by their escaped key
TypeScript keys a symbol table entry whose name starts with `__` with an
extra `_` (`__x` is stored as `___x`; `symbolName` removes it). Native ESM
link checking looked an import up by its own spelling, so a program that
imports such an export compiled to Node's startup SyntaxError:

    // dep.ts
    export const __x = 1;
    // main.ts
    import { __x } from "./dep.ts"; // The requested module './dep.ts'
                                    // does not provide an export named '__x'

`escapedSymbolName`, the inverse of `symbolName`, gives a name's table key.
Link checking, external type lookups, CommonJS export lookups and the
`this` check of ES module exports now look names up through it. `Object.keys`
over a module namespace skipped every key starting with `__` to drop
TypeScript's internal names, so it also dropped these exports; it now
skips only internal names and lists exports by their own names.

Closes #1582.

Co-authored-by: thekevinbrown <1531750+thekevinbrown@users.noreply.github.com>
2026-10-23 15:04:56 -05:00
Chris Tate fd592d61df Merge main 2026-10-23 11:56:40 -05:00
Chris Tate e771de1cfb Check cycle bindings only where early code can reach the reading function (#1882) 2026-10-23 10:02:29 -05:00
Chris Tate 0f8dd276d9 Reach a class's methods from its class expression's inner name in static initializers
A named class expression's own name (const C = class Impl { static x =
Impl.m(); }) names the class inside its body as the binding it is stored
in names it outside. The reachability walk followed only stored binding
names, so a static initializer calling a method through the inner name
did not reach that method, and its early reads of the other cycle
member's bindings lost their TDZ checks: the program read the binding
before its declaration ran (a crash), where Node throws ReferenceError.
The inner name now maps to its class expression.
2026-10-23 09:24:28 -05:00
Malte Ubl be7a194bfd Merge remote-tracking branch 'origin/main' into HEAD 2026-10-23 06:33:24 -07:00
Chris Tate ba5d73e90b Recognize esbuild's __commonJS helper under a minified name
Refs #1777

An identifier-minified esbuild bundle renames the __commonJS helper
(var j=(u,t)=>()=>(t||u((t={exports:{}}).exports,t),t.exports)). The
recognizer already compared the helper's shape independently of its
parameter names, but only used a renamed match to refuse the package.
A stable top-level declaration with the helper's shape now splits the
bundle's chunks like the named helper, so lru-cache's CommonJS build is
no longer refused.
2026-10-23 06:20:02 -05:00
Chris Tate b9275c2693 buffer: fromArrayBuffer ranges, the deprecated constructor, Buffer as a value 2026-10-23 03:05:19 -05:00
Malte UblandClaude Opus 5.5 14966de563 Move adoption into its own runtime unit behind the declAdopt feature
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:35:40 -07:00
Malte UblandClaude Opus 5.5 17bef8c03b Type user code from npm package declarations at a checked, identity-preserving boundary
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:54:55 -07:00
Chris Tate 43917080c9 Reach a class's methods from 'this' in its static initializers
The cycle walk resolved only identifiers, so a static field initializer or static block that called a method through 'this' did not reach the method, and its reads of a cycle binding declared later lost their temporal-dead-zone checks. 'this' in static code now reaches the class's stored unit, as naming the class does.
2026-10-22 20:01:47 -05:00
Malte UblandClaude Opus 5.5 fd4f14936f Check cycle bindings only where early code can reach the reading function
The import-cycle analysis treated every deferred reference (a function
body, parameter default or instance field) to a cycle binding as early
once any user code could run before the declaration completed. With a
static evaluation order it now walks the cycle's top-level code in
evaluation order, following stored callables (function and class
declarations, functions and methods stored by initializers) across the
cycle's modules through import aliases, and counts a deferred reference
as early only when that walk reaches it before the declaration ran.
Top-level reads, unknown orders, top-level await and default-exported
declarations keep the previous rule.

This removes the SC1016 refusal on zod 4's `fn[CONSTANT_CATCH] = true`
(a computed key folded from a const's literal type inside a function
nothing calls during the cycle's evaluation), and drops initialization
flags and checks that can never fire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:39:40 -07:00
Chris Tate c47a0e90ae Compile JSX with the classic runtime
Fixes #889
Co-authored-by: papack <33105506+papack@users.noreply.github.com>
2026-10-22 04:27:35 -05:00
Chris Tate c8a524c368 Keep the .d.ts-only types of a package compiled from its JavaScript 2026-10-21 19:59:04 -05:00
Chris Tate 94aa7065f1 Compile the remaining corpus programs with the native compiler (#1418) 2026-10-20 20:29:41 -05:00
Chris Tate f7e8d7c490 Compile import cycles whose class is reached only after initialization (#1495) 2026-10-20 18:55:01 -05:00
Chris Tate 74633db92f Merge main 2026-10-20 18:21:35 -05:00
Chris Tate 14ee145399 Merge main and keep inferred method returns for object literals under satisfies 2026-10-20 18:07:48 -05:00
Chris Tate 5765d89227 Check the frontend's types against tsc-ts/api
The frontend calls tokenStartOfNode, getTextOfNode, getSourceFileOfNode and
forEachChild instead of AstNode's position and traversal methods. Node lists
answer hasTrailingComma(), and a declaration's modifiers are a modifier list
with their flags.

Symbol, Type, Signature, IndexInfo, TypePredicate, Program, TypeChecker and
Diagnostic have tsc-ts/api's members and types. Compiler code reads symbol
names with symbolName(symbol).

tsc-ts goes to 0.0.5. api-conformance.ts asserts that AstNode, AstNodeList,
SourceFile and each node interface are assignable to tsc-ts/api's interface of
the same name, and names the members where the wire format differs.
2026-10-20 13:41:39 -05:00
Chris Tate 8c40f92019 Merge main 2026-10-20 11:00:03 -05:00
Chris Tate da476df676 Merge main 2026-10-20 10:11:51 -05:00
Chris Tate 7d555c4cf3 Merge main 2026-10-20 09:04:02 -05:00
Chris Tate 55f4eaca5b Merge main 2026-10-20 09:02:52 -05:00
Chris Tate 924d5b96de Merge main 2026-10-20 08:39:29 -05:00
Malte UblandClaude Opus 5.5 18034065d6 Compile the packages a module loaded through import() requires
A workspace package loaded on demand had its npm dependency selected, but
the selection never scanned that dependency's own JavaScript: it is
outside the startup module order, so a compiled CommonJS dist requiring a
sibling package was refused with "the module is not supported yet". The
scan of the import() graph now follows opted-in package JavaScript through
its literal require() and import edges.

The lowering appended those modules by their import declarations only, so
a CommonJS file's requires never joined the compiled graph and their
bindings stayed fenced. Preflight's resolved module edges, require edges
included, now reach the walks that append import(), fork() and Worker
modules, with lazy requires ordered after their module and inline require
cycles answered as at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 03:22:43 -07:00
Malte Ubl 62aa9afd58 Merge remote-tracking branch 'origin/main' into opt/cycle-class-tdz 2026-10-20 03:08:42 -07:00
Chris Tate d45a542221 Compile esbuild's bundled CommonJS chunks and CommonJS cycles statically (#1411) 2026-10-20 02:00:58 -05:00
Chris Tate 2fbc08e36d Check every early reference to a cycle class
The references of a cycle class were found by their spelling, so a class
imported under another name (`import { Sink as Alias }`) was never seen as
reached early, and neither was a let or const imported under another
name. An `extends` clause only counted when its expression was the bare
class name. A static method read as a value (`const f = Sink.m`) built a
closure that no check covered.

The early-binding scan and the class reference scan now also match each
file's local import names that resolve to the binding, and record them:
Node's ReferenceError names the binding the access spells, so the check
names the alias. The heritage check walks the whole `extends` expression.
A closure of a flagged class's static method checks the class like a
static call does.
2026-10-20 01:55:18 -05:00
Malte UblandClaude Opus 5.5 1b282f9c80 Merge branch 'opt/npm-cjs-exports' into opt/npm-polyfill-shims
Renumbers this branch's npm cases 2569-2571 to 2585-2587: #1411 now uses
2571.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:44:08 -07:00
Malte UblandClaude Opus 5.5 6fc85e848d Merge branch 'opt/npm-esm-wrappers' into opt/npm-lazy-loaders
Renumbers this branch's npm cases 2564-2566 to 2580-2582: #1411 now uses
2564-2566.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:42:59 -07:00
Chris Tate 1af7a862ce Keep stored cycle functions and self-referencing helper copies refused
Two fixes for the CommonJS cycle check and the bundled-chunk splitter:
- During a cycle's initialization, a read of a partner's exported
  function is safe only where the value neither runs nor stays: an
  expression statement, typeof, void, !, an equality operand, or the test
  of if, while, do and ?:. A const alias (`const f = binding.fn`), an
  object or array literal, a return, or an argument can be called before
  the cycle settles, so these keep SC1016 (Node throws when the function
  reads a binding in its temporal dead zone). A top-level const that only
  publishes the function in an export statement, as the static rewrite
  spells an export-table re-export, stays a read.
- A copied helper whose initializer is a named function expression
  reaches itself through its own name. Each chunk gets its own copy, so
  that name must also be used only as a callee; otherwise the chunk keeps
  the package refused with the existing reason.
2026-10-19 22:05:25 -05:00
Chris Tate d07188d250 Merge base 2026-10-19 22:00:59 -05:00
Malte UblandClaude Opus 5.5 f176de2124 Construct CommonJS constructor functions imported through a member alias
A package that exports a constructor function whole and also as a named member (`exports = module.exports = Client; exports.StatsD = Client`, the statsd-client shape) could not be constructed from TypeScript: `new StatsD(...)` was refused as "constructing values other than classes declared in the program", and a bare reference had "no lowering".

- `exports = module.exports = value` is now the module's whole export, and it rebinds `exports` too, so members attached through `exports` afterwards are exported. The fresh-table reset `exports = module.exports = {}` keeps its own handling.
- An `import { name }` or a top-level `const { name } = require(...)` of a member written once as `exports.<name> = <binding or primitive literal>` on such a module reads that value, following `module.exports = require("./x")` re-exports. The checker gives these bindings a synthesized property symbol, so the module's statements answer.
- Construction then takes the existing checked path for JavaScript functions: prototype methods, `util.inherits` between two constructor functions, and `Parent.call(this, ...)` behave as in Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:58:56 -07:00
Malte UblandClaude Opus 5.5 2efa5d9411 Fold global detection typeof tests and follow var builtin aliases
A condition built from typeof tests whose answers are static (an
undeclared global is "undefined", the global object and process are
"object") folds, so the untaken arm of `typeof window < "u" ? window : …`
never lowers, and a detection chain used as a receiver resolves to the
global object it selects. JavaScript files no longer report a name the
file feature-tests with typeof as undeclared. `typeof global.setTimeout`
answers "function" instead of the identity token's "string".

`var pna = process` (process-nextick-args once it compiles as process)
and `var Transform = require("stream").Transform` alias like their const
spellings when nothing reassigns them; before, the first silently lost
process's members.

Also reads symbol declarations directly in the CommonJS accessor
snapshot path, after the checker helper's removal on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:52:05 -07:00
Malte UblandClaude Opus 5.5 f80e4c1a09 Compile polyfill packages that are Node builtins as the builtins
isarray, safe-buffer, process-nextick-args, inherits and util-deprecate
evaluate to a Node builtin on Node 24 at their checked major versions, so
a require of one compiles as that builtin expression at the require's
position: from an opted-in package's files (the installed version where
Node's resolution finds it), from the program's own CommonJS files, and
through an esbuild __commonJS chunk whose pnpm path key names the package
and its version. The package's files never join the program.

readable-stream 2.x and 4.x compile as node:stream, the package's own
READABLE_STREAM=disable behavior (a documented divergence). A use of the
entry's export picks the builtin module (member reads of the carried
classes) or stream.Readable (value uses); a mixture keeps the package.
3.x stays as is: its disable branch throws on Node 24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:52:05 -07:00
Malte Ubl ea14ae9bae Merge remote-tracking branch 'origin/opt/native-undici' into opt/npm-polyfill-shims
# Conflicts:
#	packages/compiler/src/frontend/lowering/lower-modules.ts
#	tests/harness/npm.test.ts
2026-10-19 19:20:10 -07:00
Malte Ubl f5f8c4821c Merge remote-tracking branch 'origin/main' into opt/native-undici
# Conflicts:
#	packages/compiler/src/frontend/lowering/builtins/timers.ts
#	packages/compiler/src/frontend/type-mapper.ts
2026-10-19 18:08:01 -07:00
Chris Tate 4770b3ffa7 Fix pi's small compile gaps (#1348) 2026-10-19 19:50:07 -05:00
Malte UblandClaude Opus 5.5 c59457b0dc Map npm packages to built-in implementations and mark optional dependencies unloaded
package-shims.ts registers implementation modules shipped with the
compiler per package and major version (chosen by the importer's
installed version) and reads the project's never-loaded packages from
"scriptc": { "packages": { "<name>": "unloaded" } }. Module edges go
through resolveProjectImport; the npm resolution, the npm-edge walk and
the --npm-static set skip such packages; the tsgo host reads a shimmed
package's index.d.ts as a re-export of its implementation; an unloaded
package's require() throws Node's not-installed MODULE_NOT_FOUND.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:31:38 -07:00
Chris Tate c18a4a790a Resolve chunk references by scope, keep shared functions out of chunk copies, and treat indirect calls as calls in cycles
Three fixes for the bundled-chunk splitter and the CommonJS cycle check:
- A reference counts as local only when a scope between it and the chunk
  factory (or the copied helper) declares its name. A nested function,
  block, loop or catch binding with the name of a top-level helper no
  longer hides the chunk's read of that helper. Before, the helper was not
  copied and the chunk failed at runtime on the unresolved reference.
- A top-level function is copied into a chunk only when the bundle calls
  it and uses it in no other way. Each chunk gets its own copy, so a
  function that is exported, compared, constructed, or given properties
  would lose its shared identity. Such a chunk now keeps the package
  refused.
- During a cycle's initialization, a read of a partner's exported
  function is safe only as a plain value read. A call through
  esbuild's `(0, binding.fn)()` spelling, `new`, a tagged template, a
  member of the function, or passing it to a call can run the function,
  so these uses keep SC1016 (Node throws when the function reads a binding
  in its temporal dead zone).
2026-10-19 19:07:43 -05:00
Malte UblandClaude Opus 5.5 f7f239ad53 Lower memoized lazy loaders of bundled ES-module namespaces
A loader `L = () => M ??= NS` over a lazily initialized module's
namespace (optionally through __toESM) is respelled at every `L().name`
as the module's initializer call followed by the export's live read;
loaders whose result escapes as a value stay refused. The canonical
export table keeps a getter it cannot chase as a live accessor, so a
lazy re-export still initializes its module at the first read. An
ES-module importer copies those accessors once when it links the
CommonJS module, in the lexer's order, exactly like Node's loader;
require() keeps reading them live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:55:37 -07:00
Chris Tate 37f54ff157 Merge main 2026-10-19 17:14:32 -05:00
Chris Tate 302f0f9fa9 Compile the remaining corpus programs with the native compiler
The native compiler failed on corpus programs that the Node-hosted
compiler compiles. Four compiler causes:

- A record that width-lifts into several members of a discriminated
  union had no conversion plan. As a field of a larger record, that
  removed the outer member it belonged to, and the outer record could
  then lift into the only member left: a `recordSet` statement built with
  a nested `recordGet` literal became a `break` statement, and a switch
  on `kind` read it with the `recordSet` layout. The native compiler
  stopped at the narrowing guard on 5585-record-results-inlined.ts and
  similar programs. Such a record now converts through the discriminant:
  each member it width-lifts into is a route, and the value's
  discriminant selects the member at run time, as same-layout members
  already do.
- Three compiler paths asserted a value that can be null, and the native
  compiler threw "null is not representable in the target union":
  stableCreateRequireBindingReason7 (`cache.get(decl)!` from a cache that
  stores null for stable createRequire bindings; 2631-create-require,
  2932-create-require-program), the Int32 contract check (`catchLocalId!`
  for a bindingless `catch {}`; 4970-int32-call-abi) and
  StringIndexBounds.binding (a memo that stores null). The caches are
  read without assertions and the catch test checks for null.
- scriptc adopts alwaysStrict, esModuleInterop and
  allowSyntheticDefaultImports from a project's tsconfig, but the TS 7
  SDK schema no longer declares them. A native compiler keeps only
  declared fields when it builds or parses a typed record, so these
  options were dropped, and the default-import check refused
  `import fs from "node:fs"` (SC1012, 2391-default-builtin-imports and the
  3055 to 3057 module-setting variants). The options a project reports
  are parsed with these fields declared, and Ts7CompilerOptions uses the
  same type.
- An object-literal method kept its inferred return type where an arrow
  function or function expression takes the return type of its
  contextual signature. For a method that returns an array of statement
  literals, a `varDecl` literal with `init: null` was converted to the
  member whose `init` holds only expressions, and the conversion threw
  (Array.fromAsync's array loop: 5854-array-from-async,
  5855-array-from-async-js). A synchronous object-literal method now
  takes its slot from the property of the same name on the literal's
  contextual type and adopts its return the same way.

Adds corpus 6022 and 6024. 2391, 2631, 3055, 4970, 5585 and 5854 run in
the native-frontend self-host check, which now also links the dyn-async
unit when the program uses it, as the production build does.
2026-10-19 17:02:31 -05:00
Malte UblandClaude Opus 5.5 40ca92fa13 Format
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:49 -07:00
Malte UblandClaude Opus 5.5 405f643742 Judge bundled packages by the files their imports can load
Interop the rewrite cannot finish now refuses its package only when the
compiled program can load that file: the module order closed over every
literal require() and import() and, in package JavaScript, every import
and export declaration (a computed specifier keeps every file loadable).
A package reached through a leaf subpath and types from its main entry
stays static. Auto mode also no longer refuses a whitespace/syntax
minified bundle whose interop helpers keep their names: the recognizers
judge it instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:31:48 -07:00
Malte UblandClaude Opus 5.5 17647d8bfd Add esbuild chunk and CommonJS cycle npm fixtures
Real esbuild output (0.25 plain and whitespace/syntax minified, 0.17)
of one bundle with lazily and eagerly required chunks, a chunk that is
itself an esbuild dist, an external through __toESM, a .cjs bundle in a
type-module package, and per-file esbuild CommonJS cycles entered from
either side. Split chunks are require-only modules: their whole export
table is their surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:17:13 -07:00
Malte UblandClaude Opus 5.5 6b1f1c5991 Admit require cycles through esbuild's live export tables
The interop rewrite certifies a file whose esbuild export object is
installed before anything can run another module's code; the CommonJS
cycle rule then treats its tail table as settled at that point. A
top-level plain read of a cycle member's function-declaration export is
exact as well: Node and the compiled read see the same function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:07:43 -07:00