Repository navigation
bun-types: take a URL where the runtime does, let a class extend ReadableStream again, declare WebAssembly.Tag, Exception and JSTag - #48359
Conversation
…ableStream again, declare WebAssembly.Tag, Exception and JSTag
Without lib.dom, bun-types declares the web globals itself. Three of
those declarations were narrower than the runtime, so a program that
runs on Bun was a type error in the project that `bun init` writes.
- `new Request(new URL(...))`, `new Request({ url: new URL(...) })`,
`Response.redirect(new URL(...))`, `URL.canParse()` and `URL.parse()`
take a URL object, as the runtime does.
- `class S extends ReadableStream<string> {}` and
`class S extends ReadableStream {}` compile again. Both compiled
through bun-types 1.4.2. The byte-source construct signature that
#44614 added took part in the `extends` clause. It is now two
signatures that an `extends` clause can read.
- `WebAssembly.Tag`, `WebAssembly.Exception`, `WebAssembly.JSTag` and the
`toResizableBuffer` and `toFixedLengthBuffer` methods of
`WebAssembly.Memory` are declared, with the types of lib.dom.
With lib.dom loaded the types stay as they are. TypeScript 5.7 to 5.9
now gets the WebAssembly members that its lib.dom does not have.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 49 seconds. View limit details
Pre-merge checks:
|
|
Updated 10:05 PM PT - Nov 10th, 2026
❌ @robobun, your commit 1f04f9b has some failures in 🧪 To try this PR locally: bunx bun-pr 48359That installs a local version of the PR into your bun-48359 --bun |
|
Status: the fix is pushed. CI runs on the new head. How I reproduced it (main at 89452d2, the project that console.log(new Request(new URL("http://h.example/a")).url);
class S extends ReadableStream<string> {}
console.log(typeof S, typeof WebAssembly.Tag, typeof WebAssembly.Exception, typeof WebAssembly.JSTag);
With the |
…odule import a tag
ReadableStream: the first version of the byte-source signatures changed
three things for a `new` expression: the expected type no longer set
the chunk type, `new ReadableStream<string>({ type: "bytes" })` was
accepted, and `InstanceType<typeof ReadableStream>` became
`ReadableStream<unknown>`. The signature that infers the chunk type now
has main's `R extends Uint8Array = Uint8Array<ArrayBuffer>` behind two
type parameters that an `extends` clause cannot fill, and it is the
last signature. The signature for an explicit `<R>` refuses a byte
source when `R` is not a `Uint8Array`. A `new` expression and the
derived types are as on main, and both `extends` forms compile.
WebAssembly: `Tag` has the `Symbol.toStringTag` member that the runtime
has, so a value that is not a tag is refused where a tag is expected.
`ExportValue` includes `Tag`, so a module can import and export one.
`toResizableBuffer()` and `toFixedLengthBuffer()` return
`ArrayBuffer | SharedArrayBuffer`, which is what a shared memory gives.
The `bun check` test reads the standard error of both commands through
the shared `run` helper.
… lib.dom's `WebAssembly.Tag`
`ReadableStream` has three construct signatures again, as on main, and
only the byte-source signature differs. Its chunk type `R` has no
constraint, so `extends ReadableStream<string>` and a bare
`extends ReadableStream` compile. A call without type arguments infers
the new type parameter `K` from `type: "bytes"`. Then the chunk type `B`
has the constraint `Uint8Array` and the default `Uint8Array<ArrayBuffer>`,
as on main, and an expected type sets it. An `extends` clause or an
explicit type argument leaves `K` as `never`, and the signature returns
`ReadableStream<R>`. The parameter is `UnderlyingByteSource` for each
`K`, so what a program reads off the constructor type stays as on main.
The second commit had two byte-source signatures. With four signatures
TypeScript 5.7 to 6.0 reports a rejected call through the last signature
only, a checked JavaScript file lost
`@extends {ReadableStream<Uint8Array>}`, and a union expected type did
not set the chunk type. Its conditional parameter refused
`new ReadableStream<T>(source)` with `T extends Uint8Array`.
A type argument on a byte source is not checked:
`new ReadableStream<string>({ type: "bytes" })` compiles, as it does with
lib.dom and with `@types/node`. Main refuses it.
`WebAssembly.Tag` keeps lib.dom's empty interface when lib.dom is loaded.
The `Symbol.toStringTag` member made a cast of an export to
`WebAssembly.Tag` an error there (TS2352). Without lib.dom the member
stays, and a tag stays among the values that a module imports.
`Memory.toFixedLengthBuffer` and `Memory.toResizableBuffer` are not
declared in this change any more. `traceStack` takes `undefined`.
…types-web-globals-follow-runtime
… types of Bun
Without a tsconfig and without installed types, a build reads the
declarations that Bun brings, and Bun's `ReadableStream` constructor
applies there. So `class Pipe extends ReadableStream {}` no longer stops
that build with TS2510. The class moves from the programs that stop to
the programs that build and run, beside a subclass with a type argument.
Problem
bun initproject does not compile code that runs:new Request(new URL(..))(TS2769),class S extends ReadableStream<string> {}(TS2344: Type 'string' does not satisfy the constraint 'Uint8Array<ArrayBufferLike>'),WebAssembly.Tag(TS2339).bun build --compile --aotstops there.packages/bun-types. TheReadableStreamcase regressed in Fix every type error intest/#44614 (bun-types 1.4.3).Fix
new Request(url),new Request({ url }),Response.redirect,URL.canParseandURL.parsetake aURL.WebAssembly.Tag,ExceptionandJSTagare declared.ReadableStreamhas no constraint on the chunk type, so bothextendsforms compile. Without a type argument, a call infers the chunk type as on main.bun-types.test.ts, the newtest/cli/check/web-globals-take-what-the-runtime-takes.test.ts, andtest/aot/what-rests-on-a-brought-declaration-stops-no-build.test.ts.Background
bun initsets"lib": ["ESNext"], so bun-types declares the web globals itself. With lib.dom, lib.dom's declarations apply.extendsclause checks its type arguments against each construct signature. The constraintR extends Uint8Arrayrefusedstringthere.Kis inferred only by a call, fromtype: "bytes". With it the chunk type has main's constraint and default.Downsides
new ReadableStream<string>({ type: "bytes" })compiles, as with lib.dom and@types/node. 1.4.3 and 1.4.4 refuse it.string | URL, for example a mock ofURL.canParse..d.ts, +342 in the binary. tsc allocations: +45 per byte stream, +17 perextends ReadableStream<T>.Notes
What takes a
URLnow, without lib.domnew Request(url, init?)and theurlmember ofnew Request({ url, ... }).Response.redirect(url, status)andResponse.redirect(url, init).URL.canParse(url, base?)andURL.parse(url, base?).bun checkreads it.The byte-source signature of
ReadableStream, without lib.domReadableStreamkeeps three construct signatures, in main's order: default source, direct source, byte source. Only the third changes.new <R extends Uint8Array = Uint8Array<ArrayBuffer>>(source: UnderlyingByteSource, strategy?): ReadableStream<R>. Anextendsclause reads every signature that takes its number of type arguments. Forextends ReadableStream<string>the constraint is TS2344. Forextends ReadableStreamthe default gives a return type that the other two signatures do not have (TS2510).R = any,K extends "bytes" = never, andB, and it returnsReadableStream<B>. A call without type arguments infersKas"bytes"from thetypemember of its source. ThenBhas the constraintUint8Arrayand the defaultUint8Array<ArrayBuffer>, asRhas on main, and an expected type sets it.extendsclause and a call with a type argument infer nothing, soKkeeps its defaultnever. ThenBisRwithout a constraint, and the signature returnsReadableStream<R>as the other two do.Kthat isUnderlyingByteSourcefor eachK. Its other branch only gives a call the place to inferKfrom. SoConstructorParameters<typeof ReadableStream>, a subclass, and a union of the class with a subclass see main's parameter type.test/#44614 (Fix every type error intest/#44614 (comment)): a source of typeanygivesReadableStream<any>, andnew ReadableStream<Uint8Array>({ type: "bytes" })compiles. The test "web globals without lib.dom > follow the runtime" now pins the first.What differs from main for
new ReadableStream, without lib.domUint8Array. Examples:<string>,<string | Uint8Array>,<Uint8Array | undefined>, a type parameter without a constraint, andsuper({ type: "bytes" })in a class that extendsReadableStream<string>. The program states the chunk type there, and the declarations do not check it against the source. lib.dom (TypeScript 5.7 to 7.1) and@types/node26 accept all of these.<string>, and now it is the last one tried. So the error of such a rejected call can move to another line, and a// @ts-expect-erroron the old line no longer takes it.new ReadableStream<T>({ type: "bytes" })andsuper({ type: "bytes" })withT extends Uint8Array,new ReadableStream<never>({ type: "bytes" }), andconst s: ReadableStream<string> = new ReadableStream({ type: "bytes" })is an error.const s: ReadableStream<Buffer> = new ReadableStream({ type: "bytes" })compiles. At run time a chunk of a byte stream is a plainUint8Array, so the tests do not pin this form.InstanceType<typeof ReadableStream>isReadableStream<Uint8Array>,ConstructorParameters<typeof ReadableStream>[0]isUnderlyingByteSource, a type that reads three construct signatures gets the same three sources, andnew (flag ? Sub : ReadableStream)(source)compiles.ReadableStreamwithout a type argument takes a byte source innewand insuper().@extends {ReadableStream<string>}and others). With this PR none fails.newon the declared uniontypeof ReadableStream | typeof Sub, whereSub<T extends Uint8Array = Uint8Array<ArrayBuffer>>is a generic subclass with main's constraint and default, is TS2351. Main's third signature was identical to the one thatSubinherits, and the new one is not.Earlier shapes of the byte-source signature in this PR
ReadableStream<Uint8Array<ArrayBuffer>>return type, hidden from anextendsclause by two more type parameters, and a second signature for a type argument. An expected type no longer set the chunk type.ReadableStream<R | A | B>, and the second one had the parameterR extends Uint8Array ? UnderlyingByteSource : never. TypeScript does not resolve that condition for a type parameter, sonew ReadableStream<T>(source)withT extends Uint8Arraydid not compile. TypeScript 6 and 7 did not inferRthrough the union in the return type (16 of 30 forms that main compiles failed). Checked JavaScript on TypeScript 5.7 to 6.0 lost@extends {ReadableStream<Uint8Array>}.InstanceType<typeof ReadableStream>isReadableStream<unknown>, and a type that reads three signatures loses the default source. One signature avoids all three. It costs +45 tsc allocations per byte stream, against +4 for the cheapest pair.UnderlyingByteSource & { type: .. K .. }. TypeScript putsanyfor the type parameters when it compares two constructor types, and that parameter then hasanyin it. Sonew (flag ? Sub : ReadableStream)(source)was TS2351.<string>through a lookup type ({ BYTES_PER_ELEMENT: "bytes" }[keyof R & "BYTES_PER_ELEMENT"]). It accepts every typed array, and it rests on how TypeScript assigns to an indexed access of a type parameter.WebAssembly
WebAssembly.Taghas the memberreadonly [Symbol.toStringTag]: "WebAssembly.Tag", which the prototype has at run time. So a number or a plain object is no tag.ExportValueincludesTag, so{ env: { tag: WebAssembly.JSTag } }is an import object. A string is still refused as an import.Tagstays an empty interface as in lib.dom, andExportValuehas no tag. The second commit put the member on lib.dom'sTagtoo, andinstance.exports.x as WebAssembly.Tagbecame TS2352 there. That is gone, and the fixture pins the cast and the refused string import in both configurations.WebAssembly.instantiatestays TS2769 there, as with lib.dom alone.Tag,ExceptionorJSTagin lib.dom. They get them from bun-types with lib.dom loaded too, with the emptyTag.Memory.toFixedLengthBufferandtoResizableBufferwere in the first two commits and are out. As required members they break a hand-writtenWebAssembly.Memoryand feature detection within, and their return type for a shared memory needs a decision. A separate change carries them.ExceptionOptions.traceStackisboolean | undefined, forexactOptionalPropertyTypes."lib": ["ESNext", "WebWorker"]and no DOM, bun-types applies its declarations for a project without lib.dom, as for every other global. lib.webworker's emptyTagthen gets the member, and a cast of an export toWebAssembly.Tagis TS2352.Effects of the fix on code that compiles on main (each comes with the declaration itself)
string | URL: a callback without a parameter type forspyOn(URL, "canParse").mockImplementation(url => url.startsWith(..)),URL.canParsepassed to a generic function that then infersstring | URLwhere it inferredstring, a static override withurl: string | null, and a class assigned totypeof Requestwhose constructor takes{ url: string }. lib.dom and@types/nodedeclarecanParseandparsewithstring | URLtoo.ConstructorParameters<typeof Request>[0]isstring | URL | Request(main:Request), andReflect.construct(Request, [url, init])isRequest(main:any). Of 41 sites that read such derived types and compile on main, 1 is an error now:ConstructorParameters<typeof Request>[0].url.// @ts-expect-erroron a line that now compiles is TS2578. A project that declaresWebAssembly.Tag,ExceptionorJSTagitself gets TS2300, TS2451 or TS2403. Afterif (!("Tag" in wasm))on an alias of the namespace the value isnever.Record<keyof typeof WebAssembly, ..>needs three more keys.Tag. Soconst e: Record<string, Function | WebAssembly.Global | WebAssembly.Memory | WebAssembly.Table> = instance.exportsis TS2322, and code that narrows an export by exclusion sees one more member.URL,RequestorResponsewith main's type gets TS2403.typeisnever, a constructor type that adds a signature after those ofReadableStream,newon a union oftypeof ReadableStreamwith an unrelated constructor type (TypeScript 5.7 to 6.0 then giveReadableStream<any>for a byte source),Symbol.toStringTag in valueon an export, and an object of typeURLthat is noURLinstance.Self-review
TextDecoderlabel of typestringand a null listener ofaddEventListener. Both are decisions for the code owner, so they are out.TextDecoderlabel case is bun-types: accept aTextDecoderlabel in any ASCII case #48360.URL.canParseandURL.parsecame in.WebAssembly.Tagwith lib.dom, the union expected type, the checked JavaScript forms, and pins that said a byte stream yieldsBuffer.Taginterface with lib.dom (1), theMemorymethods taken out (7), new pins (3), sentences of this description (5). The other 26 come with the fix itself. The sections above list their kinds.bun checktest. Onebun checkreads every declaration of bun-types and@types/node. On a debug build the test takes 4 s to 14 s locally, and the default limit is 5 s.Still rejected, on purpose or owned elsewhere
BlobPropertyBag.endings: the runtime ignores the option (Blob constructorendingsoption doen't work. #11796,docs/runtime/nodejs-compat.mdx). A@ts-expect-errorinbun-types.test.tspins that it is not declared.new TextDecoder("UTF-8"): bun-types: accept aTextDecoderlabel in any ASCII case #48360. A label of typestringand a null listener ofaddEventListenerneed a maintainer's decision.WebAssembly.promising,Suspending,SuspendError: Upgrade WebKit to 8156ba68efab #48297 changes whatnew WebAssembly.Suspending(f)returns, and no lib.dom declares the names yet.class X extends CustomEvent {}andextends MessageEvent(TS2508), with and without lib.dom.WebAssembly.Imports,Exports,ImportExportKind,ExportValue,ModuleImports,ImportValueandTableKind(TS2694) andValueType(TS2724) without lib.dom.Bun.WebAssembly.Importsand the others work.new Bun.RedisClient(url)with aURL(TS2345).WritableStreamDefaultController.signal(bun-types: fix four globals that do not match the runtime without lib.dom #41809), theDOMExceptionoptions object (bun-types: accept the { name, cause } options object in the DOMException constructor #39323), the globaldispatchEvent(bun-types: declare the global dispatchEvent() function #39074),ReadableStream.from(bun-types: declareSubtleCrypto.supports()andReadableStream.from()without lib.dom #46355).new Request({ url }),Response.redirect(url, init), a bareextends ReadableStream([Web API type definition issue] ExtendingReadableStreamthrows "Base constructors must all have the same return type" error microsoft/TypeScript-DOM-lib-generator#2456), a direct stream, and a tag import.Reach
bun initwrites"lib": ["ESNext"]for a blank project and"lib": ["ESNext", "DOM"]for its three React templates.ReadableStreamconstructor applies there. Soclass Pipe extends ReadableStream {}builds now, where main stops with TS2510.test/aot/what-rests-on-a-brought-declaration-stops-no-build.test.tspinned that stop (AOT: the next base #48120). The class moved to the programs that build and run.--aot(Compile TypeScript to machine code ahead of time:bun build --compile --aot#45631) is in no stable release yet.Measurements ("six compilers" are tsc 5.7.3, 5.8.3, 5.9.3, 6.0.3, 7.0.2 and 7.1.0-beta, other numbers are from tsc 7.0.2)
bun checkon it exits 1 on main's declarations and 0 with this PR.bun build --compile --aotof a program with these forms exits 0, and the executable prints whatbun index.tsprints.test/aot/what-rests-on-a-brought-declaration-stops-no-build.test.ts: 60 pass with this PR, and its new case stops with TS2510 on main's declarations.test/aot/missing-bun-types.test.ts: 91 of 92 pass. The one failure is a build that exits 1 without a diagnostic on the debug build, in a test that passed in the runs before.bun-types.test.ts: its 31 type tests pass with this PR. 12 of them fail on main's declarations, and the same 12 on the declarations of the second commit. The bindgen test of that file does not run here since the merge of main: it stops insrc/runtime/api/bun/subprocess.bind.tsunder the installed Bun 1.4.3, with or without these declarations. Fixture without lib.dom: 28 diagnostics on main, 0 with this PR. With lib.dom: the 40 pinned ones.bun checkof the debug build reports the same lines as tsc 7.0.2 on that set, in all four configurations.Memory allocsline oftsc --extendedDiagnostics --singleThreaded, per extra call site (slope between 1,001 and 2,001 sites, median of 9 runs).new ReadableStream({ type: "bytes", pull(c) { c.close(); } }): 105.6 on main, 150.6 with this PR.class X extends ReadableStream<Uint8Array> {}: 71.5 and 88.5. A default stream: 70.3 and 70.4. A direct stream: 90.6 and 90.6. 16 other forms differ by 0.02 at most.new Request("…")2 and 2.new Request(new URL(..)): 274 (an error on main) and 67. A byte stream: 275 and 289.globals.d.ts+487,wasm.d.ts+3,438. The packed copy in the binary (comments removed, zstd) grows by 342 bytes.tsc -p test/tsconfig.json): 1,441 diagnostics on main and with this PR, 0 new and 0 gone.lint.yml(build scripts, built-in modules,src, the dev server): 0 errors.