diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index aab91ce8b..f532e11a2 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -4259,6 +4259,7 @@ export class Lowerer { "createRequireBindingTargets", "createRequireElementTargets", "computedGlobalReads", + "namesWebStreamClass", "timeValueDateDecisions", "timeValueDateWrites", "dynamicOverloadAnswers", diff --git a/packages/runtime/src/scr_async_dyn.c b/packages/runtime/src/scr_async_dyn.c index 6e5d9856e..1d8febbc3 100644 --- a/packages/runtime/src/scr_async_dyn.c +++ b/packages/runtime/src/scr_async_dyn.c @@ -1551,8 +1551,11 @@ static ScrDyn *scr_dyn_then_of(const ScrDyn *value, const ScrDyn *reader) { return NULL; } -/* Resolves `target` with `value` (borrowed). */ -static void scr_dyn_resolve_value(ScrPromise *target, ScrDyn *value, const ScrDyn *reader) { +/* Resolves `target` with `value` (borrowed). Inlined into both callers, so + * the code of scr_dyn_then_resolve_call does not depend on whether a + * program links scr_dyn_promise_resolve_with. */ +__attribute__((always_inline)) static inline void scr_dyn_resolve_value(ScrPromise *target, ScrDyn *value, + const ScrDyn *reader) { if (value->kind == SCR_DYN_PROMISE) { scr_promise_resolve_dyn(target, scr_dyn_retain(value)); return; @@ -1669,6 +1672,10 @@ ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader) { return scr_dyn_promise_entry(NULL, value, reader); } +void scr_dyn_promise_resolve_with(ScrPromise *target, ScrDyn *value) { + scr_dyn_resolve_value(target, value, NULL); +} + static void scr_dyn_settled_store(ScrArr *a, double i, ScrPromise *src) { ScrDyn *record = scr_dyn_new_obj(); bool fulfilled = !scr_promise_is_rejected(src); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index ab94282f1..6ac123e58 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -2061,6 +2061,12 @@ typedef struct ScrDynHandleClass { /* The prototype's methods and getters in definition order (the order * Object.keys reports); NULL = the methods, then the getters. */ const char *const *order; + /* A call of one of the class's method values + * (`WritableStream.prototype.close.call(stream)`): the class's method, + * which an own property of the same name on the handle does not + * replace. NULL = ScrDynHandleOps.invoke. */ + ScrDyn *(*method_invoke)(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, + const char *what); } ScrDynHandleClass; /* Installs a tag's ops with the class its handles belong to; both outlive @@ -2087,6 +2093,11 @@ ScrDyn *scr_dyn_handle_class_prototype_of(const ScrDyn *handle); * receiver (+1); NULL when the chain has none or with the exception * pending. */ ScrDyn *scr_dyn_handle_class_member(ScrDynHandleTag tag, void *h, const char *key, size_t len); +/* A method value's call on a handle of its tag (`self`, borrowed): the + * class's method_invoke, or the ops' invoke for a tag without one. +1, or + * NULL with the exception pending. */ +ScrDyn *scr_dyn_handle_class_method_call(const ScrDynHandleOps *ops, ScrDyn *self, const char *method, + ScrDyn *const *args, size_t argc); /* The operations a Transform handle gives the stream unit (scr_stream.c): * the slot where the handle keeps its stream (+1) with the stream's @@ -2163,6 +2174,11 @@ ScrPromise *scr_dyn_promise_any(const ScrDyn *entries, const ScrDyn *reader); * itself, a thenable adopts through its resolve job, a throwing `then` * read rejects, and any other value fulfills. +1. */ ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader); +/* Resolves `target` with a dyn value (borrowed), as a promise's resolve + * function does: a native promise adopts, a thenable adopts through its + * resolve job, a throwing `then` read rejects, and any other value + * fulfills. */ +void scr_dyn_promise_resolve_with(ScrPromise *target, ScrDyn *value); /* Native predicates for typed reference capsules and boxed errors. */ bool scr_dyn_ref_typeof_is(const ScrDyn *d, const char *name); diff --git a/packages/runtime/src/scr_dyn_handle_class.c b/packages/runtime/src/scr_dyn_handle_class.c index 42e2884c5..cf2a04f76 100644 --- a/packages/runtime/src/scr_dyn_handle_class.c +++ b/packages/runtime/src/scr_dyn_handle_class.c @@ -183,6 +183,14 @@ ScrDyn *scr_dyn_handle_class_member(ScrDynHandleTag tag, void *h, const char *ke return value; } +ScrDyn *scr_dyn_handle_class_method_call(const ScrDynHandleOps *ops, ScrDyn *self, const char *method, + ScrDyn *const *args, size_t argc) { + const ScrDynHandleClass *klass = hc_classes[self->v.handle.tag]; + if (klass && klass->method_invoke) + return klass->method_invoke(self->v.handle.ptr, self, method, args, argc, method); + return ops->invoke(self->v.handle.ptr, self, method, args, argc, method); +} + static void hc_define_method(ScrDyn *prototype, ScrDynHandleTag tag, const char *name) { ScrDyn *fn = scr_dyn_handle_class_method(tag, name); if (fn) hc_define(prototype, name, fn, true, true, true); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 0b574b1fb..7de149143 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -7004,7 +7004,14 @@ static ScrDyn *scr_dynh_method_call(ScrClosure *closure, ScrDyn *const *args, si ScrDyn *self = scr_dyn_this_get(); ScrDyn *result; if (self->kind == SCR_DYN_HANDLE && self->v.handle.tag == tag) { +#ifdef SCR_HANDLE_CLASSES + /* A class's method value reaches the class's method past the handle's + * own properties; this build is the one programs with such classes + * link. */ + result = scr_dyn_handle_class_method_call(ops, self, method->name, args, argc); +#else result = ops->invoke(self->v.handle.ptr, self, method->name, args, argc, method->name); +#endif } else { const char *message = method->invalid_this ? method->invalid_this : ops->invalid_this ? ops->invalid_this diff --git a/packages/runtime/src/scr_webstreams.c b/packages/runtime/src/scr_webstreams.c index 8ae438902..1ea9c754e 100644 --- a/packages/runtime/src/scr_webstreams.c +++ b/packages/runtime/src/scr_webstreams.c @@ -3520,7 +3520,10 @@ static ScrDyn *ws_transform_construct(ScrDyn *const *args, size_t argc) { ws_release(t); goto done; } - scr_promise_resolve_dyn(start_promise, value); + /* startPromise.resolve(result): a thenable result adopts through its + * resolve job, so both sides start when it settles. */ + scr_dyn_promise_resolve_with(start_promise, value); + scr_dyn_release(value); } else { ws_promise_fulfill_undefined(start_promise); } @@ -4128,6 +4131,18 @@ static ScrDyn *ws_arg(ScrDyn *const *args, size_t argc, size_t i) { return i < argc ? args[i] : scr_dyn_undefined(); } +/* A member call on a handle (ScrDynHandleOps.invoke): an own property of + * the name first, then the class's method (_method). The class's + * method values call _method directly (ScrDynHandleClass's + * method_invoke), so a wrapper an own property installs reaches it. */ +#define WS_MEMBER_INVOKE(NAME) \ + static ScrDyn *NAME##_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, \ + const char *what) { \ + ScrDyn *own_result; \ + if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; \ + return NAME##_method(ptr, self, method, args, argc, what); \ + } + static ScrDyn *ws_promise_box(ScrPromise *p) { ScrDyn *box = scr_dyn_new_promise(p); scr_promise_release(p); @@ -4168,10 +4183,8 @@ static ScrDyn *ws_readable_get_reader(WsReadable *s, ScrDyn *options) { return box; } -static ScrDyn *ws_readable_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, +static ScrDyn *ws_readable_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; (void)self; (void)what; WsReadable *s = ptr; @@ -4279,6 +4292,7 @@ static ScrDyn *ws_readable_invoke(void *ptr, ScrDyn *self, const char *method, S } return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_readable) static ScrDyn *ws_readable_get(void *ptr, const char *key, size_t len) { WsReadable *s = ptr; @@ -4310,7 +4324,7 @@ static const char *const ws_readable_order[] = {"locked", "cancel", "getReader", "tee", "values", NULL}; static const ScrDynHandleClass ws_readable_class = { - "ReadableStream", 0, ws_readable_construct, ws_readable_accessors, ws_readable_statics, ws_readable_order, + "ReadableStream", 0, ws_readable_construct, ws_readable_accessors, ws_readable_statics, ws_readable_order, ws_readable_method, }; /* ReadableStreamDefaultReader */ @@ -4354,10 +4368,8 @@ static ScrDyn *ws_reader_read(WsReader *r) { return ws_promise_box(result); } -static ScrDyn *ws_reader_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, +static ScrDyn *ws_reader_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; (void)self; (void)what; WsReader *r = ptr; @@ -4373,6 +4385,7 @@ static ScrDyn *ws_reader_invoke(void *ptr, ScrDyn *self, const char *method, Scr } return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_reader) static ScrDyn *ws_reader_get(void *ptr, const char *key, size_t len) { WsReader *r = ptr; @@ -4390,15 +4403,13 @@ static const ScrDynHandleMethod ws_reader_methods[] = { static const char *const ws_reader_order[] = {"read", "releaseLock", "closed", "cancel", NULL}; static const ScrDynHandleClass ws_reader_class = { - "ReadableStreamDefaultReader", 1, ws_reader_construct, ws_reader_accessors, NULL, ws_reader_order, + "ReadableStreamDefaultReader", 1, ws_reader_construct, ws_reader_accessors, NULL, ws_reader_order, ws_reader_method, }; /* ReadableStreamDefaultController */ -static ScrDyn *ws_rcontroller_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, +static ScrDyn *ws_rcontroller_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; (void)self; (void)what; WsRController *c = ptr; @@ -4425,6 +4436,7 @@ static ScrDyn *ws_rcontroller_invoke(void *ptr, ScrDyn *self, const char *method } return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_rcontroller) static ScrDyn *ws_rcontroller_get(void *ptr, const char *key, size_t len) { if (ws_name(key, len, "desiredSize")) return ws_rcontroller_desired_size(ptr); @@ -4441,14 +4453,12 @@ static const ScrDynHandleMethod ws_rcontroller_methods[] = { static const char *const ws_rcontroller_order[] = {"desiredSize", "close", "enqueue", "error", NULL}; static const ScrDynHandleClass ws_rcontroller_class = { - "ReadableStreamDefaultController", 0, NULL, ws_rcontroller_accessors, NULL, ws_rcontroller_order, + "ReadableStreamDefaultController", 0, NULL, ws_rcontroller_accessors, NULL, ws_rcontroller_order, ws_rcontroller_method, }; /* WritableStream */ -static ScrDyn *ws_writable_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, +static ScrDyn *ws_writable_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; WsWritable *s = ptr; size_t len = strlen(method); if (ws_name(method, len, "abort")) { @@ -4470,6 +4480,7 @@ static ScrDyn *ws_writable_invoke(void *ptr, ScrDyn *self, const char *method, S } return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_writable) static ScrDyn *ws_writable_get(void *ptr, const char *key, size_t len) { WsWritable *s = ptr; @@ -4487,7 +4498,7 @@ static const ScrDynHandleMethod ws_writable_methods[] = { static const char *const ws_writable_order[] = {"locked", "abort", "close", "getWriter", NULL}; static const ScrDynHandleClass ws_writable_class = { - "WritableStream", 0, ws_writable_construct, ws_writable_accessors, NULL, ws_writable_order, + "WritableStream", 0, ws_writable_construct, ws_writable_accessors, NULL, ws_writable_order, ws_writable_method, }; /* WritableStreamDefaultWriter */ @@ -4507,10 +4518,8 @@ static ScrDyn *ws_writer_construct(ScrDyn *const *args, size_t argc) { static const char ws_writer_unbound[] = "Writer is not bound to a WritableStream"; -static ScrDyn *ws_writer_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, +static ScrDyn *ws_writer_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; WsWriter *w = ptr; size_t len = strlen(method); if (ws_name(method, len, "abort")) { @@ -4533,6 +4542,7 @@ static ScrDyn *ws_writer_invoke(void *ptr, ScrDyn *self, const char *method, Scr } return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_writer) static ScrDyn *ws_writer_get(void *ptr, const char *key, size_t len) { WsWriter *w = ptr; @@ -4560,14 +4570,12 @@ static const char *const ws_writer_order[] = {"closed", "desiredSize", "ready", "close", "releaseLock", "write", NULL}; static const ScrDynHandleClass ws_writer_class = { - "WritableStreamDefaultWriter", 1, ws_writer_construct, ws_writer_accessors, NULL, ws_writer_order, + "WritableStreamDefaultWriter", 1, ws_writer_construct, ws_writer_accessors, NULL, ws_writer_order, ws_writer_method, }; /* WritableStreamDefaultController */ -static ScrDyn *ws_wcontroller_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, +static ScrDyn *ws_wcontroller_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; WsWController *c = ptr; size_t len = strlen(method); if (ws_name(method, len, "error")) { @@ -4576,6 +4584,7 @@ static ScrDyn *ws_wcontroller_invoke(void *ptr, ScrDyn *self, const char *method } return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_wcontroller) static ScrDyn *ws_wcontroller_get(void *ptr, const char *key, size_t len) { if (ws_name(key, len, "signal")) return ws_wcontroller_signal(ptr); @@ -4590,7 +4599,7 @@ static const ScrDynHandleMethod ws_wcontroller_methods[] = {{"error", 0, false, static const char *const ws_wcontroller_order[] = {"signal", "error", NULL}; static const ScrDynHandleClass ws_wcontroller_class = { - "WritableStreamDefaultController", 0, NULL, ws_wcontroller_accessors, NULL, ws_wcontroller_order, + "WritableStreamDefaultController", 0, NULL, ws_wcontroller_accessors, NULL, ws_wcontroller_order, ws_wcontroller_method, }; /* TransformStream */ @@ -4603,24 +4612,21 @@ static ScrDyn *ws_transform_get(void *ptr, const char *key, size_t len) { return scr_dyn_handle_class_member(SCR_DYNH_WS_TRANSFORM, ptr, key, len); } -static ScrDyn *ws_transform_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, +static ScrDyn *ws_transform_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; (void)ptr; return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_transform) static const ScrDynHandleClass ws_transform_class = { - "TransformStream", 0, ws_transform_construct, ws_transform_accessors, NULL, NULL, + "TransformStream", 0, ws_transform_construct, ws_transform_accessors, NULL, NULL, ws_transform_method, }; /* TransformStreamDefaultController */ -static ScrDyn *ws_tcontroller_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, +static ScrDyn *ws_tcontroller_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; WsTController *c = ptr; size_t len = strlen(method); if (ws_name(method, len, "enqueue")) { @@ -4637,6 +4643,7 @@ static ScrDyn *ws_tcontroller_invoke(void *ptr, ScrDyn *self, const char *method } return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_tcontroller) static ScrDyn *ws_tcontroller_get(void *ptr, const char *key, size_t len) { WsTController *c = ptr; @@ -4654,7 +4661,7 @@ static const ScrDynHandleMethod ws_tcontroller_methods[] = { static const char *const ws_tcontroller_order[] = {"desiredSize", "enqueue", "error", "terminate", NULL}; static const ScrDynHandleClass ws_tcontroller_class = { - "TransformStreamDefaultController", 0, NULL, ws_tcontroller_accessors, NULL, ws_tcontroller_order, + "TransformStreamDefaultController", 0, NULL, ws_tcontroller_accessors, NULL, ws_tcontroller_order, ws_tcontroller_method, }; /* ── TextEncoderStream and TextDecoderStream (webstreams/encoding.js) */ @@ -4930,19 +4937,18 @@ static ScrDyn *ws_text_get(void *ptr, const char *key, size_t len) { return scr_dyn_handle_class_member(tag, ptr, key, len); } -static ScrDyn *ws_text_invoke(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, +static ScrDyn *ws_text_method(void *ptr, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, const char *what) { - ScrDyn *own_result; - if (ws_own_invoke(ptr, self, method, args, argc, what, &own_result)) return own_result; return ws_invoke_member(self, method, args, argc, what); } +WS_MEMBER_INVOKE(ws_text) static const ScrDynHandleClass ws_text_encoder_class = { - "TextEncoderStream", 0, ws_text_encoder_construct, ws_text_encoder_accessors, NULL, NULL, + "TextEncoderStream", 0, ws_text_encoder_construct, ws_text_encoder_accessors, NULL, NULL, ws_text_method, }; static const ScrDynHandleClass ws_text_decoder_class = { - "TextDecoderStream", 0, ws_text_decoder_construct, ws_text_decoder_accessors, NULL, NULL, + "TextDecoderStream", 0, ws_text_decoder_construct, ws_text_decoder_accessors, NULL, NULL, ws_text_method, }; /* ── queuing strategies ────────────────────────────────────────────── */ diff --git a/tests/corpus/web-stream-transform-pipes-untyped.mjs b/tests/corpus/web-stream-transform-pipes-untyped.mjs index 3bf171fd6..b72fec111 100644 --- a/tests/corpus/web-stream-transform-pipes-untyped.mjs +++ b/tests/corpus/web-stream-transform-pipes-untyped.mjs @@ -1,7 +1,8 @@ // Web TransformStream and pipes from untyped JavaScript: transformers with // start, transform, flush and cancel, backpressure between the sides, // controller errors and terminate, pipeTo and pipeThrough with their -// options, abort signals, own properties on streams, and encoder and +// options, abort signals, own properties on streams and method wrappers +// over them, a transformer start that returns a thenable, and encoder and // decoder streams, in Node's promise order. import { TransformStream as WebTransformStream, TransformStreamDefaultController } from "node:stream/web"; @@ -127,6 +128,66 @@ out("prototype", Object.keys(TransformStream.prototype).join(","), Object.keys(T out("own-properties", /** @type {any} */ (sink).tag, typeof /** @type {any} */ (source).getReader, caught(() => { /** @type {any} */ (source).locked = true; })); } +{ + // An own property that wraps the saved prototype method reaches the + // class's method. + const calls = []; + const wrap = (target, name, method) => { + target[name] = function (...args) { + calls.push(name); + return method.apply(this, args); + }; + }; + const writable = new WritableStream({ write(chunk) { calls.push(`sink:${chunk}`); } }); + wrap(writable, "getWriter", WritableStream.prototype.getWriter); + const writer = writable.getWriter(); + wrap(writer, "write", WritableStreamDefaultWriter.prototype.write); + wrap(writer, "close", WritableStreamDefaultWriter.prototype.close); + await writer.write("a"); + await writer.close(); + const readable = ReadableStream.from(["b"]); + wrap(readable, "getReader", ReadableStream.prototype.getReader); + const reader = readable.getReader(); + wrap(reader, "read", ReadableStreamDefaultReader.prototype.read); + const first = await reader.read(); + const t = new TransformStream({ + transform(chunk, controller) { + wrap(controller, "enqueue", TransformStreamDefaultController.prototype.enqueue); + controller.enqueue(`${chunk}!`); + }, + }); + t.writable.getWriter().write("c"); + const transformed = await t.readable.getReader().read(); + out("wrappers", calls.join(","), first.value, transformed.value, writable.locked); +} + +{ + // A transformer start that returns a thenable: both sides start when it + // settles, and its rejection errors the stream. + const order = []; + const later = { then(resolve) { order.push("then"); setTimeout(() => { order.push("resolve"); resolve(); }, 0); } }; + const t = new TransformStream({ start() { return later; }, transform(chunk, c) { order.push(`transform:${chunk}`); c.enqueue(chunk); } }); + Promise.resolve().then(() => order.push("tick1")).then(() => order.push("tick2")).then(() => order.push("tick3")); + const written = t.writable.getWriter().write("x").then(() => order.push("written")); + const read = await t.readable.getReader().read(); + await written; + out("start-thenable", order.join(","), read.value); + const ticks = []; + const sync = new TransformStream({ start() { return { then(resolve) { ticks.push("then"); resolve(); } }; }, transform(chunk, c) { ticks.push("transform"); c.enqueue(chunk); } }); + Promise.resolve().then(() => ticks.push("tick1")).then(() => ticks.push("tick2")).then(() => ticks.push("tick3")).then(() => ticks.push("tick4")); + sync.writable.getWriter().write("z").then(() => ticks.push("written")); + sync.readable.getReader().read().then(() => ticks.push("read")); + await new Promise((r) => setTimeout(r, 0)); + out("start-thenable-ticks", ticks.join(",")); + const failing = new TransformStream({ start() { return { then(_, reject) { reject(new Error("start failed")); } }; } }); + out("start-thenable-reject", await settled(failing.writable.getWriter().write("y")), await settled(failing.readable.getReader().read())); + const throwing = new TransformStream({ start() { return { get then() { throw new Error("then getter"); } }; } }); + out("start-then-getter", await settled(throwing.writable.getWriter().write("y"))); + const plain = new TransformStream({ start() { return { then: 1 }; } }); + const pending = plain.readable.getReader().read(); + out("start-object", await settled(plain.writable.getWriter().write("p")), (await pending).value); +} + out( "invalid", await settled(ReadableStream.from([]).pipeTo(/** @type {any} */ ({}))),