From bfd71ad33099e2e2e7165ab0abc4f070d59e3229 Mon Sep 17 00:00:00 2001 From: Stephen Adams Date: Fri, 14 Apr 2023 22:04:28 +0000 Subject: [PATCH] Test for yield* of Iterable with throwing get:iterator DDC passes, dart2js and VM fail. ---- The dart2js and VM fringe-following scheme could be modified to call `.iterator` at the `yield*` site and use the Iterator instead of the Iterable. Calling `.iterator` at the `yield*` site would move the exception to the right place. It might also present an optimization opportunity where the call might be inlined, or the entry into the fringe-following algorithm could be made more efficient based on the type of the iterator. Change-Id: Icfb6f7ca0b92cbeea1349ce138e469cfa707f571 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/295200 Commit-Queue: Stephen Adams Reviewed-by: Lasse Nielsen --- .../sync_star_exception_iterator_test.dart | 122 +++++++++++++++++ .../sync_star_exception_iterator_test.dart | 124 ++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 tests/language/sync_star/sync_star_exception_iterator_test.dart create mode 100644 tests/language_2/sync_star/sync_star_exception_iterator_test.dart diff --git a/tests/language/sync_star/sync_star_exception_iterator_test.dart b/tests/language/sync_star/sync_star_exception_iterator_test.dart new file mode 100644 index 00000000000..22d36d107d4 --- /dev/null +++ b/tests/language/sync_star/sync_star_exception_iterator_test.dart @@ -0,0 +1,122 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:expect/expect.dart'; + +String log = 'uninitialized'; + +class Throwing extends Iterable { + final String path; + Throwing(this.path) { + log += '[$path]'; + } + Iterator get iterator => throw 'iterator@$path'; + + // The following ensure these methods are not used to implement `yield*`. + int get length => throw 'length'; + int elementAt(int i) => throw 'elementAt'; + void forEach(void Function(int) action) => throw 'forEach'; +} + +Iterable f1(String path) sync* { + final here = '$path.f1'; + yield* Throwing('$here.a'); + yield* Throwing('$here.b'); + log += '[f1.done]'; +} + +Iterable f2(String path) sync* { + final here = '$path.f2'; + try { + final p = f1('$here.p'); + log += '[$here.p.y*1]'; + yield* p; + log += '[$here.p.y*2]'; + + yield* f1('$here.q'); + } catch (e) { + log += '[$here.catch:$e]'; + } + yield 100; + log += '[$here.done]'; +} + +Iterable f3(String path) sync* { + final here = '$path.f3'; + try { + yield* Throwing('$here.f'); + yield* Throwing('$here.g'); + } catch (e) { + log += '[$here.catch:$e]'; + } + log += '[$here.done]'; +} + +Iterable f4(String path) sync* { + final here = '$path.f4'; + try { + final s = f3('$here.s'); + log += '[$here.s.y*1]'; + yield* s; + log += '[$here.s.y*2]'; + + yield* f3('$here.t'); + } catch (e) { + log += '[$here.catch:$e]'; + } + yield 200; + log += '[$here.done]'; +} + +main() { + // The spec dictates that `yield*` calls `iterator` on the operand. This + // implies that any exception thrown by accessing the iterator should happen + // as if at the yield* statement. + + { + log = ''; + final iterator = f1('main').iterator; + Expect.throws(() => iterator.moveNext()); + Expect.equals('[main.f1.a]', log); + Expect.isFalse(iterator.moveNext()); + Expect.equals('[main.f1.a]', log); + } + + { + log = ''; + final iterator = f2('main').iterator; + Expect.isTrue(iterator.moveNext()); + Expect.equals(100, iterator.current); + Expect.equals( + '[main.f2.p.y*1][main.f2.p.f1.a][main.f2.catch:iterator@main.f2.p.f1.a]', + log); + log = ''; + Expect.isFalse(iterator.moveNext()); + Expect.equals('[main.f2.done]', log); + } + + { + log = ''; + final iterator = f3('main').iterator; + Expect.isFalse(iterator.moveNext()); + Expect.equals( + '[main.f3.f][main.f3.catch:iterator@main.f3.f][main.f3.done]', log); + } + + { + log = ''; + final iterator = f4('M').iterator; + Expect.isTrue(iterator.moveNext()); + Expect.equals(200, iterator.current); + Expect.equals( + '[M.f4.s.y*1]' + '[M.f4.s.f3.f][M.f4.s.f3.catch:iterator@M.f4.s.f3.f][M.f4.s.f3.done]' + '[M.f4.s.y*2]' + '[M.f4.t.f3.f][M.f4.t.f3.catch:iterator@M.f4.t.f3.f][M.f4.t.f3.done]', + log); + log = ''; + Expect.isFalse(iterator.moveNext()); + Expect.equals('[M.f4.done]', log); + } +} diff --git a/tests/language_2/sync_star/sync_star_exception_iterator_test.dart b/tests/language_2/sync_star/sync_star_exception_iterator_test.dart new file mode 100644 index 00000000000..e4759f94c2b --- /dev/null +++ b/tests/language_2/sync_star/sync_star_exception_iterator_test.dart @@ -0,0 +1,124 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// @dart = 2.9 + +import 'package:expect/expect.dart'; + +String log = 'uninitialized'; + +class Throwing extends Iterable { + final String path; + Throwing(this.path) { + log += '[$path]'; + } + Iterator get iterator => throw 'iterator@$path'; + + // The following ensure these methods are not used to implement `yield*`. + int get length => throw 'length'; + int elementAt(int i) => throw 'elementAt'; + void forEach(void Function(int) action) => throw 'forEach'; +} + +Iterable f1(String path) sync* { + final here = '$path.f1'; + yield* Throwing('$here.a'); + yield* Throwing('$here.b'); + log += '[f1.done]'; +} + +Iterable f2(String path) sync* { + final here = '$path.f2'; + try { + final p = f1('$here.p'); + log += '[$here.p.y*1]'; + yield* p; + log += '[$here.p.y*2]'; + + yield* f1('$here.q'); + } catch (e) { + log += '[$here.catch:$e]'; + } + yield 100; + log += '[$here.done]'; +} + +Iterable f3(String path) sync* { + final here = '$path.f3'; + try { + yield* Throwing('$here.f'); + yield* Throwing('$here.g'); + } catch (e) { + log += '[$here.catch:$e]'; + } + log += '[$here.done]'; +} + +Iterable f4(String path) sync* { + final here = '$path.f4'; + try { + final s = f3('$here.s'); + log += '[$here.s.y*1]'; + yield* s; + log += '[$here.s.y*2]'; + + yield* f3('$here.t'); + } catch (e) { + log += '[$here.catch:$e]'; + } + yield 200; + log += '[$here.done]'; +} + +main() { + // The spec dictates that `yield*` calls `iterator` on the operand. This + // implies that any exception thrown by accessing the iterator should happen + // as if at the yield* statement. + + { + log = ''; + final iterator = f1('main').iterator; + Expect.throws(() => iterator.moveNext()); + Expect.equals('[main.f1.a]', log); + Expect.isFalse(iterator.moveNext()); + Expect.equals('[main.f1.a]', log); + } + + { + log = ''; + final iterator = f2('main').iterator; + Expect.isTrue(iterator.moveNext()); + Expect.equals(100, iterator.current); + Expect.equals( + '[main.f2.p.y*1][main.f2.p.f1.a][main.f2.catch:iterator@main.f2.p.f1.a]', + log); + log = ''; + Expect.isFalse(iterator.moveNext()); + Expect.equals('[main.f2.done]', log); + } + + { + log = ''; + final iterator = f3('main').iterator; + Expect.isFalse(iterator.moveNext()); + Expect.equals( + '[main.f3.f][main.f3.catch:iterator@main.f3.f][main.f3.done]', log); + } + + { + log = ''; + final iterator = f4('M').iterator; + Expect.isTrue(iterator.moveNext()); + Expect.equals(200, iterator.current); + Expect.equals( + '[M.f4.s.y*1]' + '[M.f4.s.f3.f][M.f4.s.f3.catch:iterator@M.f4.s.f3.f][M.f4.s.f3.done]' + '[M.f4.s.y*2]' + '[M.f4.t.f3.f][M.f4.t.f3.catch:iterator@M.f4.t.f3.f][M.f4.t.f3.done]', + log); + log = ''; + Expect.isFalse(iterator.moveNext()); + Expect.equals('[M.f4.done]', log); + } +}