diff --git a/docs/content/limitations/builtin-apis/iterator-helpers.mdx b/docs/content/limitations/builtin-apis/iterator-helpers.mdx index cb7cab1f4..b4c4d8200 100644 --- a/docs/content/limitations/builtin-apis/iterator-helpers.mdx +++ b/docs/content/limitations/builtin-apis/iterator-helpers.mdx @@ -1 +1 @@ -- **Iterator helpers:** `map`, `filter`, `take`, `drop`, `flatMap`, `toArray`, `forEach`, `reduce`, `some`, `every` and `find` compile on generators, on array, typed array, string, Map and Set iterators, and on the objects the helpers return. Helper objects support `.next()`, `.return()`, `for...of`, spreads, `Array.from` and `yield*`, pull their source lazily, and close it (run its `return()`) at the same points as Node: when `take` reaches its count, when a short-circuiting method finds its answer, when a callback throws, and when the loop consuming the helper exits early. In TypeScript, `h.return!()` and `h.return?.()` compile when `h` is a helper call or a `const` initialized with one. `flatMap` callbacks return arrays, generators, Sets or Maps; a string result throws Node's TypeError. `Iterator.from` accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators. In JavaScript, untyped array, string, Map and Set iterators answer the same methods. `Iterator.from` over an object with its own `next` method or over an untyped value, the `Iterator` global as a value, and printing typed helper objects are unsupported. +- **Iterator helpers:** `map`, `filter`, `take`, `drop`, `flatMap`, `toArray`, `forEach`, `reduce`, `some`, `every` and `find` compile on generators, on array, typed array, string, Map and Set iterators, and on the objects the helpers return. Helper objects support `.next()`, `.return()`, `for...of`, spreads, `Array.from` and `yield*`, pull their source lazily, and close it (run its `return()`) at the same points as Node: when `take` reaches its count, when a short-circuiting method finds its answer, when a callback throws, and when the loop consuming the helper exits early. In TypeScript, `h.return!()` and `h.return?.()` compile when `h` is a helper call or a `const` initialized with one. `flatMap` callbacks return arrays, generators, Sets or Maps; a string result throws Node's TypeError. `Iterator.from` accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators. In JavaScript, untyped array, string, Map and Set iterators answer the same methods. `console.log`, `util.inspect` and `Object.prototype.toString` print iterator objects as Node does: `Object [Iterator Helper] {}`, `Object [Generator] {}`, `Object [Array Iterator] {}`, and Map and Set iterators with the entries they have left. `Iterator.from` over an object with its own `next` method or over an untyped value, the `Iterator` global as a value, and `String()` and template literals of typed iterator objects are unsupported. diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 872153f40..48012704b 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -4644,6 +4644,9 @@ export class LlEmitter { // An iterator helper's return() before its first next() closes the // source generator in its first argument slot. const closesSource = fn.generator.closesSource === true; + if (fn.generator.tag !== undefined) this.declare(`declare void @scr_gen_set_tag(ptr, ptr)`); + if (fn.generator.inheritsTag === true) + this.declare(`declare void @scr_gen_copy_tag(ptr, ptr)`); if (closesSource) { const field = lifted ? 1 : 0; this.declare(`declare void @scr_gen_close(ptr)`); @@ -4676,6 +4679,12 @@ export class LlEmitter { settleAsync === null ? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})` : ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`, + ...(fn.generator.tag !== undefined + ? [` call void @scr_gen_set_tag(ptr %gg, ptr ${this.cstr(fn.generator.tag)})`] + : []), + ...(fn.generator.inheritsTag === true + ? [` call void @scr_gen_copy_tag(ptr %gg, ptr %a${lifted ? 1 : 0})`] + : []), ...(closesSource ? [ ` call void @scr_gen_set_unstarted_return(ptr %gg, ptr @${mangleGenSourceClose(fn.name)})`, diff --git a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts index 229999627..79b91af64 100644 --- a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts +++ b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts @@ -2259,6 +2259,7 @@ function buildHelperGenFn( yieldT: outT.yieldT, nextT: outT.nextT, resultType, + tag: "Iterator Helper", // A native Map or Set iterator has no return() to call. ...(source.kind === "generator" ? { closesSource: true as const } : {}), }, diff --git a/packages/compiler/src/frontend/lowering/iterator-adapters.ts b/packages/compiler/src/frontend/lowering/iterator-adapters.ts index f086417de..898c46f20 100644 --- a/packages/compiler/src/frontend/lowering/iterator-adapters.ts +++ b/packages/compiler/src/frontend/lowering/iterator-adapters.ts @@ -258,7 +258,7 @@ function dynIteratorAdapter( returnType: expected.retT, locals, body, - generator: { yieldT: expected.yieldT, nextT: expected.nextT, resultType }, + generator: { yieldT: expected.yieldT, nextT: expected.nextT, resultType, tag: "!" }, loc, }); } @@ -374,7 +374,12 @@ export function lowerCollectionIteratorValue( loc, }, ], - generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType }, + generator: { + yieldT: type.yieldT, + nextT: UNDEFINED_T, + resultType, + tag: "!", + }, loc, }); } @@ -484,7 +489,7 @@ export function lowerBytesIteratorValue( }, { kind: "return", value: null, loc }, ], - generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType }, + generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType, tag: "Array Iterator" }, loc, }); } @@ -590,7 +595,7 @@ export function lowerStringIteratorValue( }, { kind: "return", value: null, loc }, ], - generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType }, + generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType, tag: "String Iterator" }, loc, }); } @@ -751,7 +756,12 @@ function generatorRetypeAdapter( loc, }, ], - generator: { yieldT: expected.yieldT, nextT: expected.nextT, resultType: toResult }, + generator: { + yieldT: expected.yieldT, + nextT: expected.nextT, + resultType: toResult, + inheritsTag: true, + }, loc, }); } @@ -1022,7 +1032,7 @@ export function lowerArrayIteratorValue( }, { kind: "return", value: null, loc }, ], - generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType }, + generator: { yieldT: type.yieldT, nextT: UNDEFINED_T, resultType, tag: "Array Iterator" }, loc, }); } diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 58cbb9543..b3cd7e39b 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -2800,7 +2800,8 @@ function lowerObjectPrototypeCall( const immediate = immediateObjectTagReceiver(lowerer, receiverNode); const receiver = immediate?.effect ?? lowerer.lowerExpr(receiverNode); const tag = immediate?.tag ?? objectToStringTag(lowerer, receiver.type); - if (tag === null && receiver.type.kind !== "dyn") { + // A generator object answers its tag through its checked capsule. + if (tag === null && receiver.type.kind !== "dyn" && receiver.type.kind !== "generator") { lowerer.noLowering( "Object.prototype.toString.call over a " + lowerer.fmt(receiver.type) + " receiver", receiverNode, @@ -2820,7 +2821,13 @@ function lowerObjectPrototypeCall( } const result: IrExpr = tag === null - ? { kind: "libCall", fn: "dyn.objectTag", args: [ref], type: STRING, loc } + ? { + kind: "libCall", + fn: "dyn.objectTag", + args: [ref.type.kind === "generator" ? lowerer.coerceToExpected(ref, DYN) : ref], + type: STRING, + loc, + } : { kind: "strLit", value: "[object " + tag + "]", type: STRING, loc }; return { kind: "seqExpr", stmts, result, type: STRING, loc }; } diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 1d67cef92..2551031eb 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -253,6 +253,10 @@ function inspectSupport( visiting.delete(t.className); return null; } + // Generator objects print through their checked capsule: Node's + // `Object [Generator] {}`, or the tag of the iterator they implement. + case "generator": + return null; default: return `'${lowerer.fmt(t)}' values have no inspect lowering`; } @@ -343,6 +347,14 @@ export function inspectExpr( return { kind: "libCall", fn: "insp.buffer", args: [value], type: STRING, loc }; case "dyn": return { kind: "libCall", fn: "insp.dyn", args: [value, recurse, depth], type: STRING, loc }; + case "generator": + return { + kind: "libCall", + fn: "insp.dyn", + args: [lowerer.coerceToExpected(value, DYN), recurse, depth], + type: STRING, + loc, + }; case "object": if (eventTargetRootOf(lowerer.classes.get(t.className)) !== null) { return { diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index ab4b05c8b..117bba24a 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -143,6 +143,7 @@ const FUNCTION_FIELDS: Record>, Role> = { "captures[].localId": "other", "classCaptures[].name": "other", "classCaptures[].localId": "other", + "generator.tag": "other", }; const MODULE_FIELDS: Record>, Role> = { diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 0aa510b8d..751c6c021 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1712,6 +1712,15 @@ export interface IrFunction { * iterator helper's source): `.return()` before the first `.next()` * closes it. */ closesSource?: true; + /** The @@toStringTag the object prints with (util.inspect, + * Object.prototype.toString) when it is not a generator object: + * "Iterator Helper", "Array Iterator", "String Iterator". A tag that + * starts with '!' marks a generator standing in for an object of + * another class, which those refuse to print. */ + tag?: string; + /** The first parameter is a generator this one adapts: it prints with + * that generator's tag. */ + inheritsTag?: true; }; /** Source function name for native error stack frames. */ sourceName?: string; diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6075-print-iterators.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6075-print-iterators.ts.json new file mode 100644 index 000000000..04adc830d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6075-print-iterators.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6075-print-iterators.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6076-print-iterators-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6076-print-iterators-js.js.json new file mode 100644 index 000000000..255e32494 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6076-print-iterators-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6076-print-iterators-js.js" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index b1ebfc6e9..124304970 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -4986,6 +4986,7 @@ struct ScrGen { void (*drop_args)(void *); /* scr_gen_set_unstarted_return's hook; NULL for most generators. */ void (*unstarted_return)(void *); + const char *tag; /* scr_gen_set_tag's; NULL is the default */ bool is_async; void (*settle_async)(ScrGen *, ScrPromise *); ScrAsyncGenRequest *requests_head; @@ -5547,6 +5548,14 @@ void scr_gen_resume(ScrGen *g) { void scr_gen_set_unstarted_return(ScrGen *g, void (*hook)(void *)) { g->unstarted_return = hook; } +void scr_gen_set_tag(ScrGen *g, const char *tag) { g->tag = tag; } + +void scr_gen_copy_tag(ScrGen *g, const ScrGen *from) { g->tag = from->tag; } + +const char *scr_gen_tag(const ScrGen *g) { + return g->tag ? g->tag : g->is_async ? "AsyncGenerator" : "Generator"; +} + void scr_gen_close(ScrGen *g) { scr_gen_slot_reset(&g->ret); scr_gen_resume_return(g); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index e76563ab7..4938ee01d 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -706,6 +706,12 @@ bool scr_dyn_iterator_step_done(const ScrDyn *iterator); * next live entry index of its collection, or -1 once the collection is * exhausted (the iterator then ends its iteration). */ double scr_dyn_collection_iterator_index(const ScrDyn *iterator); +/* For util.inspect: a native iterator's kind (0 array or typed array, + * 1 string, 2 Map, 3 Set, 4 a handle's), its selection (0 values, 1 keys, + * 2 entries), its collection (NULL once exhausted, and for kinds other + * than 2 and 3) and the index its next step reads from. */ +void scr_dyn_native_iterator_state(const ScrDyn *iterator, unsigned *kind, unsigned *selection, + ScrMap **collection, double *index); /* The ordinary object snapshot plus a retained typed source. source_access * releases that source when materialize=false and returns a fresh snapshot * when true. */ diff --git a/packages/runtime/src/scr_generator.h b/packages/runtime/src/scr_generator.h index a92cd3222..76d152f8a 100644 --- a/packages/runtime/src/scr_generator.h +++ b/packages/runtime/src/scr_generator.h @@ -63,6 +63,16 @@ ScrGen *scr_async_gen_new(void (*entry)(ScrFiber *, void *), void *argpack, /* Runs when return() reaches the generator before its first resume, with * the packed arguments still alive: an iterator helper closes its source. */ void scr_gen_set_unstarted_return(ScrGen *g, void (*hook)(void *)); +/* The @@toStringTag that util.inspect and Object.prototype.toString print + * for the generator object: "Generator" (or "AsyncGenerator") unless set, + * as the compiler does for iterator helpers ("Iterator Helper") and for + * the array and string iterators it builds as generators. A tag that + * starts with '!' marks a generator standing in for an object of another + * class, which these refuse to print. */ +void scr_gen_set_tag(ScrGen *g, const char *tag); +/* An adapter generator prints with the tag of the generator it adapts. */ +void scr_gen_copy_tag(ScrGen *g, const ScrGen *from); +const char *scr_gen_tag(const ScrGen *g); ScrGen *scr_gen_retain(ScrGen *g); void scr_gen_release(ScrGen *g); /* NULL-tolerant */ void *scr_gen_retain_v(void *g); diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 4aa703711..019f36536 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -1058,6 +1058,99 @@ static ScrStr *scr_insp_weak_collection(const char *label, double recurse, doubl return ib_take(&out); } +/* A native iterator: the array and string iterators print Node's empty + * `Object [Array Iterator] {}`; the Map and Set iterators print the + * entries their next steps would produce, under Node's labels + * (`[Set Iterator] { 1, 2 }`, `[Map Entries] { [ 'a', 1 ] }`), without + * advancing. NULL for a handle's iterator (kind 4), which keeps the + * unsupported answer. */ +static ScrStr *scr_insp_native_iterator(ScrDyn *d, double recurse, double depth) { + unsigned kind, selection; + ScrMap *map; + double index; + scr_dyn_native_iterator_state(d, &kind, &selection, &map, &index); + if (kind == 0) return scr_str_new("Object [Array Iterator] {}", 26); + if (kind == 1) return scr_str_new("Object [String Iterator] {}", 27); + if (kind != 2 && kind != 3) return NULL; + bool pairs = selection == 2; + const char *label = kind == 3 ? (pairs ? "[Set Entries]" : "[Set Iterator]") + : (pairs ? "[Map Entries]" : "[Map Iterator]"); + if (recurse > depth) { + InspBuf out = {0}; + ib_cstr(&out, "[Object "); + ib_cstr(&out, label); + ib_char(&out, ']'); + return ib_take(&out); + } + size_t remaining = 0; + if (map) { + for (double i = scr_map_iter_skip(map, index); i < scr_map_iter_count(map); i++) + if (scr_map_iter_live(map, i)) remaining++; + } + if (remaining == 0) { + InspBuf out = {0}; + ib_cstr(&out, label); + ib_cstr(&out, " { }"); + return ib_take(&out); + } + ScrStr *base = scr_str_new(label, strlen(label)); + scr_insp_begin(recurse + 1); + scr_map_iter_enter(map); + size_t shown = 0; + for (double i = scr_map_iter_skip(map, index); i < scr_map_iter_count(map) && shown < 100; i++) { + if (!scr_map_iter_live(map, i)) continue; + shown++; + ScrDyn *item; + if (pairs) { + item = scr_dyn_new_arr(); + scr_dyn_arr_push(item, scr_map_dyn_key(map, i)); + scr_dyn_arr_push(item, kind == 3 ? scr_map_dyn_key(map, i) : scr_map_dyn_value(map, i)); + } else { + item = kind == 2 && selection == 0 ? scr_map_dyn_value(map, i) : scr_map_dyn_key(map, i); + } + ScrStr *text = scr_insp_dyn(item, recurse + 1, depth); + scr_dyn_release(item); + scr_insp_entry(text, false); + scr_str_release(text); + } + scr_map_iter_exit(map); + if (remaining > shown) { + ScrStr *more = scr_insp_more_items((double)(remaining - shown)); + scr_insp_entry(more, false); + scr_str_release(more); + } + ScrStr *b0 = scr_str_new("{", 1); + ScrStr *b1 = scr_str_new("}", 1); + ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, false, false); + scr_str_release(base); + scr_str_release(b0); + scr_str_release(b1); + return out; +} + +/* A compiled generator object: Node's empty `Object [] {}`, with the + * tag the compiler set (scr_gen_set_tag). A generator standing in for an + * object of another class (a tag starting with '!') throws the + * "not supported yet" Error instead. */ +static bool scr_insp_is_generator(const ScrDyn *d) { + const char *key = d->v.typed_ref.type_key; + return !strncmp(key, "generator<", 10) || !strncmp(key, "async-generator<", 16); +} + +static ScrStr *scr_insp_generator(const ScrDyn *d) { + const char *tag = scr_gen_tag(d->v.typed_ref.ptr); + if (tag[0] == '!') { + static const char message[] = "util.inspect of this iterator object is not supported yet"; + scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1); + return scr_str_new("", 0); + } + InspBuf out = {0}; + ib_cstr(&out, "Object ["); + ib_cstr(&out, tag); + ib_cstr(&out, "] {}"); + return ib_take(&out); +} + static ScrStr *scr_insp_dyn_collection(ScrDyn *d, double recurse, double depth, bool is_map) { ScrMap *map = d->v.handle.ptr; const char *label = is_map ? "Map" : "Set"; @@ -1309,6 +1402,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { return scr_insp_weak_collection(d->v.handle.tag == SCR_DYNH_WEAK_MAP ? "WeakMap" : "WeakSet", recurse, depth); if (d->v.handle.tag == SCR_DYNH_DATE) return scr_insp_date(scr_dyn_native_date_value(d)); + if (d->v.handle.tag == SCR_DYNH_ITERATOR) { + ScrStr *iterator = scr_insp_native_iterator(d, recurse, depth); + if (iterator) return iterator; + } const ScrDynHandleOps *ops = scr_dyn_handle_ops_of(d); if (ops->inspect_view) { /* Node's custom inspectors answer the bare class name beyond the @@ -1349,6 +1446,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } case SCR_DYN_TYPED_REF: { + if (scr_insp_is_generator(d)) return scr_insp_generator(d); const char *cls = NULL; ScrDyn *view = scr_event_ref_hooks ? scr_event_ref_hooks->inspect_view(d, &cls) : NULL; if (view) { diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index d18b3d910..206c01839 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -4673,6 +4673,16 @@ bool scr_dyn_iterator_step_done(const ScrDyn *value) { return ((ScrNativeIterator *)value->v.handle.ptr)->step_done; } +void scr_dyn_native_iterator_state(const ScrDyn *value, unsigned *kind, unsigned *selection, + ScrMap **collection, double *index) { + const ScrNativeIterator *iterator = value->v.handle.ptr; + *kind = iterator->kind; + *selection = iterator->selection; + *collection = (iterator->kind == 2 || iterator->kind == 3) && iterator->source + ? iterator->source->v.handle.ptr : NULL; + *index = iterator->index; +} + double scr_dyn_collection_iterator_index(const ScrDyn *value) { ScrNativeIterator *iterator = value->v.handle.ptr; ScrDyn *source = iterator->source; @@ -6217,6 +6227,20 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) { } if (d->kind == SCR_DYN_TYPED_REF) { if (scr_dyn_ref_is_error(d)) return scr_str_new("[object Error]", 14); + if (scr_dyn_generator(d)) { + const char *gen_tag = scr_gen_tag(d->v.typed_ref.ptr); + if (gen_tag[0] == '!') { + static const char message[] = "Object.prototype.toString of this iterator is not supported yet"; + scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1); + return NULL; + } + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "[object "); + scr_jb_puts(&b, gen_tag); + scr_jb_putc(&b, ']'); + return scr_jb_finish(&b); + } ScrDyn *materialized = scr_dyn_typed_ref_materialize(d); ScrStr *out = scr_dyn_object_tag(materialized); scr_dyn_release(materialized); @@ -6249,6 +6273,13 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) { scr_jb_putc(&b, ']'); return scr_jb_finish(&b); } + if (d->v.handle.tag == SCR_DYNH_ITERATOR && ((const ScrNativeIterator *)d->v.handle.ptr)->kind < 4) { + static const char *const tags[] = { + "[object Array Iterator]", "[object String Iterator]", "[object Map Iterator]", "[object Set Iterator]", + }; + const char *it_tag = tags[((const ScrNativeIterator *)d->v.handle.ptr)->kind]; + return scr_str_new(it_tag, strlen(it_tag)); + } if (scr_dyn_handle_ops_of(d)->to_string_tag) { ScrJsonBuf b; scr_jb_init(&b); diff --git a/tests/corpus/6075-print-iterators.ts b/tests/corpus/6075-print-iterators.ts new file mode 100644 index 000000000..8b4833264 --- /dev/null +++ b/tests/corpus/6075-print-iterators.ts @@ -0,0 +1,19 @@ +// util.inspect and Object.prototype.toString of iterator objects: generators, +// array, typed array and string iterators and iterator helpers print as +// Object [] {}, and Map and Set iterators print the entries they have +// left. +import { inspect } from "node:util"; +function* g(): Generator { yield 1; } +const s = new Set([1, 2, 3]); +const m = new Map([["a", 1], ["b", 2]]); +const si = s.values(); si.next(); +console.log(g(), [1, 2].values(), "ab"[Symbol.iterator](), new Uint8Array([1]).values()); +console.log(s.values(), s.entries(), m.keys(), m.values(), m.entries(), si); +console.log([1].values().map((x) => x), Iterator.from([1]), g().filter(() => true)); +console.log(inspect({ a: { b: { c: s.values(), d: g() } } })); +console.log(Object.prototype.toString.call(g()), Object.prototype.toString.call([1].values().map((x) => x)), Object.prototype.toString.call(m.keys())); +const done = s.values(); Array.from(done); +console.log(done, new Map().entries(), g()); +const keyed = new Map<{ x: number }, number[]>([[{ x: 1 }, [1, 2]]]); +console.log(keyed.entries(), keyed.keys()); +console.log(new Set(Array.from({ length: 120 }, (_, i) => i)).values()); diff --git a/tests/corpus/6076-print-iterators-js.js b/tests/corpus/6076-print-iterators-js.js new file mode 100644 index 000000000..ad4022b6b --- /dev/null +++ b/tests/corpus/6076-print-iterators-js.js @@ -0,0 +1,17 @@ +// The JavaScript form: util.inspect and Object.prototype.toString of +// iterator objects. +import { inspect } from "node:util"; +function* g() { yield 1; } +const s = new Set([1, 2, 3]); +const m = new Map ([["a", 1], ["b", 2]]); +const si = s.values(); si.next(); +console.log(g(), [1, 2].values(), "ab"[Symbol.iterator](), new Uint8Array([1]).values()); +console.log(s.values(), s.entries(), m.keys(), m.values(), m.entries(), si); +console.log([1].values().map((x) => x), Iterator.from([1]), g().filter(() => true)); +console.log(inspect({ a: { b: { c: s.values(), d: g() } } })); +console.log(Object.prototype.toString.call(g()), Object.prototype.toString.call([1].values().map((x) => x)), Object.prototype.toString.call(m.keys())); +const done = s.values(); Array.from(done); +console.log(done, new Map ().entries(), g()); +const keyed = new Map ([[{ x: 1 }, [1, 2]]]); +console.log(keyed.entries(), keyed.keys()); +console.log(new Set(Array.from({ length: 120 }, (_, i) => i)).values()); diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index c37b780ea..10d9c3aca 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -2242,6 +2242,8 @@ "6072-iterator-sources-js.js": 5350, "6073-stored-collection-helpers.ts": 4000, "6074-stored-collection-helpers-js.js": 4100, + "6075-print-iterators.ts": 3800, + "6076-print-iterators-js.js": 3500, "6100-record-get-or-create.ts": 3771, "6101-record-get-or-create-unchecked/main.ts": 2107, "6103-typeof-builtin-bindings.cjs": 2202, @@ -5320,6 +5322,8 @@ "6072-iterator-sources-js.js": 5350, "6073-stored-collection-helpers.ts": 4150, "6074-stored-collection-helpers-js.js": 3600, + "6075-print-iterators.ts": 3450, + "6076-print-iterators-js.js": 3300, "6100-record-get-or-create.ts": 3173, "6101-record-get-or-create-unchecked/main.ts": 3228, "6103-typeof-builtin-bindings.cjs": 1796,