Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions ext/webidl/00_webidl.js
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,7 @@ function createAsyncIterableConverter(converter) {
context = undefined,
opts = EMPTY_OPTS,
) {
if (type(V) !== "Object") {
if (V === undefined || V === null) {
throw makeException(
TypeError,
"can not be converted to async iterable.",
Expand All @@ -1007,12 +1007,16 @@ function createAsyncIterableConverter(converter) {
);
}

// Follows the GetIterator(obj, async) abstract operation: the @@asyncIterator
// and @@iterator methods are looked up with GetMethod semantics, which treats
// both `undefined` and `null` as an absent method, and which performs ToObject
// on `V` (so primitives such as strings are accepted).
let isAsync = true;
let method = V[SymbolAsyncIterator];
if (method === undefined) {
if (method === undefined || method === null) {
method = V[SymbolIterator];

if (method === undefined) {
if (method === undefined || method === null) {
throw makeException(
TypeError,
"is not iterable.",
Expand Down
24 changes: 11 additions & 13 deletions tests/unit/streams_test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
// Copyright 2018-2026 the Deno authors. MIT license.
import {
assertEquals,
assertRejects,
assertThrows,
fail,
} from "./test_util.ts";
import { assertEquals, assertRejects, fail } from "./test_util.ts";

// `resourceForReadableStream` is registered on the internals object only when
// `ext:deno_web/06_streams.js` first evaluates, which is now lazy. Touch the
Expand Down Expand Up @@ -578,13 +573,16 @@ Deno.test(async function decompressionStreamInvalidGzipStillReported() {
);
});

Deno.test(function readableStreamFromWithStringThrows() {
assertThrows(
// @ts-expect-error: primitives are not acceptable
() => ReadableStream.from("string"),
TypeError,
"Failed to execute 'ReadableStream.from': Argument 1 can not be converted to async iterable.",
);
Deno.test(async function readableStreamFromString() {
// Per the Streams spec, ReadableStream.from() accepts any iterable, including
// strings, which are iterated by code point. The TS type requires an object,
// so a string primitive is rejected statically even though it works at runtime.
// @ts-expect-error: string is iterable at runtime but the type requires an object
const stream = ReadableStream.from("ab");
const reader = stream.getReader();
assertEquals(await reader.read(), { value: "a", done: false });
assertEquals(await reader.read(), { value: "b", done: false });
assertEquals((await reader.read()).done, true);
});

Deno.test(async function readableStreamFromWithStringThrows() {
Expand Down
14 changes: 2 additions & 12 deletions tests/wpt/runner/expectations/streams.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,8 @@
"templated.any.worker.html": true,
"async-iterator.any.worker.html": true,
"cross-realm-crash.window.html": false,
"from.any.html": {
"expectedFailures": [
"ReadableStream.from ignores a null @@asyncIterator",
"ReadableStream.from accepts a string"
]
},
"from.any.worker.html": {
"expectedFailures": [
"ReadableStream.from ignores a null @@asyncIterator",
"ReadableStream.from accepts a string"
]
},
"from.any.html": true,
"from.any.worker.html": true,
"crashtests": {
"garbage-collection.any.html": true,
"garbage-collection.any.worker.html": true
Expand Down
Loading