Resolve the pipeline promise to undefined for an untyped iterable destination

When an untyped destination function returned an async iterable, the
promise form settled its result without a payload, and code that read the
awaited value as an untyped value dereferenced NULL. The promise form for
an untyped destination now resolves to undefined when the destination
returned no promise, or its promise carries no value, as Node does.
This commit is contained in:
Chris Tate
2026-10-25 18:44:18 -05:00
parent 30cf205e90
commit a848a7b349
2 changed files with 27 additions and 2 deletions
+22 -2
View File
@@ -5312,13 +5312,31 @@ static void scr_sp_value_inv(ScrClosure *cb, ScrError *err, ScrDyn *value, ScrPr
scr_promise_release(p);
}
/* scr_sp_value_inv for an untyped destination (SCR_PIPE_DEST_DYN), whose
* result promise carries a dyn value: when the destination returned an
* async iterable (no promise), or its promise carries no payload, the
* result is undefined, as in Node. */
static void scr_sp_dyn_value_inv(ScrClosure *cb, ScrError *err, ScrDyn *value,
ScrPromise *value_p) {
if (err != NULL || (value_p != NULL && scr_promise_payload_kind(value_p) != SCR_EXC_NONE)) {
scr_sp_value_inv(cb, err, value, value_p);
return;
}
ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
scr_promise_fulfill_ref(p, scr_dyn_retain(scr_dyn_undefined()), scr_dyn_retain_v,
scr_dyn_release_v, NULL);
scr_promise_release(p);
}
ScrPromise *scr_sp_pipeline_stages(double n, ScrStream **streams, double count,
ScrClosure **stages, double positions, double flags,
double mask, ScrClosure **helpers, ScrClosure *box,
ScrDyn *signal, ScrClosure *abort) {
ScrPromise *p = scr_promise_new();
ScrPipeStages fs = scr_pipe_stages_of(count, stages, positions, flags, mask, helpers, box, signal);
ScrPipeValInv vi = fs.dest != SCR_PIPE_DEST_STREAM ? &scr_sp_value_inv : NULL;
ScrPipeValInv vi = fs.dest == SCR_PIPE_DEST_STREAM ? NULL
: fs.dest == SCR_PIPE_DEST_DYN ? &scr_sp_dyn_value_inv
: &scr_sp_value_inv;
ScrStream *dst = scr_stream_pipeline_ex((size_t)n, streams, scr_sp_watcher(p), &scr_sp_settle_inv,
true, NULL, NULL, &fs, abort, vi);
if (dst) scr_stream_release(dst);
@@ -5370,7 +5388,9 @@ ScrPromise *scr_sp_pipeline_stages_opts(double n, ScrStream **streams, double co
ScrPromise *p = scr_promise_new();
if (!scr_sp_opts_signal(p, &outer)) return p;
ScrPipeStages fs = scr_pipe_stages_of(count, stages, positions, flags, mask, helpers, box, signal);
ScrPipeValInv vi = fs.dest != SCR_PIPE_DEST_STREAM ? &scr_sp_value_inv : NULL;
ScrPipeValInv vi = fs.dest == SCR_PIPE_DEST_STREAM ? NULL
: fs.dest == SCR_PIPE_DEST_DYN ? &scr_sp_dyn_value_inv
: &scr_sp_value_inv;
ScrStream *dst = scr_stream_pipeline_ex((size_t)n, streams, scr_sp_watcher(p), &scr_sp_settle_inv,
end, outer, add, &fs, abort, vi);
if (dst) scr_stream_release(dst);
@@ -53,6 +53,11 @@ const out = pipeline(Readable.from(["p1", "p2"]), wrap, (err, value) => {
console.log("returns a PassThrough", out instanceof PassThrough);
for await (const chunk of out) console.log("passthrough", chunk);
// an async generator destination in the promise form: the PassThrough
// takes its values and the promise resolves to undefined
const promised = await pipelineP(Readable.from(["q1", "q2"]), wrap);
console.log("generator destination promise", promised, typeof promised);
// a destination whose promise rejects
try {
await pipelineP(