Version 2.15.0-291.0.dev
Merge commit '9b7caf3b93856838dd51053effec3ff37d2c69a4' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
index a09c3b7..2a8a2ae 100644
--- a/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
+++ b/pkg/analysis_server/lib/src/services/correction/bulk_fix_processor.dart
@@ -19,6 +19,7 @@
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/instrumentation/service.dart';
import 'package:analyzer/source/error_processor.dart';
+import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer_plugin/src/utilities/change_builder/change_builder_core.dart';
@@ -208,6 +209,7 @@
final analysisOptions = unit.session.analysisContext.analysisOptions;
var overrideSet = _readOverrideSet(unit);
+ var lockRanges = <String, List<SourceRange>>{};
for (var error in errors) {
final processor = ErrorProcessor.getProcessor(analysisOptions, error);
// Only fix errors not filtered out in analysis options.
@@ -219,7 +221,7 @@
error,
(name) => [],
);
- await _fixSingleError(fixContext, unit, error, overrideSet);
+ await _fixSingleError(fixContext, unit, error, overrideSet, lockRanges);
}
}
@@ -236,6 +238,7 @@
var errors = List.from(unitResult.errors, growable: false);
errors.sort((a, b) => a.offset.compareTo(b.offset));
+ var lockRanges = <String, List<SourceRange>>{};
for (var error in errors) {
var processor = ErrorProcessor.getProcessor(analysisOptions, error);
// Only fix errors not filtered out in analysis options.
@@ -247,7 +250,8 @@
error,
(name) => [],
);
- await _fixSingleError(fixContext, unitResult, error, overrideSet);
+ await _fixSingleError(
+ fixContext, unitResult, error, overrideSet, lockRanges);
}
}
}
@@ -260,11 +264,13 @@
DartFixContext fixContext,
ResolvedUnitResult result,
AnalysisError diagnostic,
- TransformOverrideSet? overrideSet) async {
+ TransformOverrideSet? overrideSet,
+ Map<String, List<SourceRange>> lockRanges) async {
var context = CorrectionProducerContext.create(
applyingBulkFixes: true,
dartFixContext: fixContext,
diagnostic: diagnostic,
+ lockRanges: lockRanges,
overrideSet: overrideSet,
resolvedResult: result,
selectionOffset: diagnostic.offset,
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/abstract_producer.dart b/pkg/analysis_server/lib/src/services/correction/dart/abstract_producer.dart
index 96389a0..c1d4638 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/abstract_producer.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/abstract_producer.dart
@@ -241,7 +241,7 @@
/// A map keyed by lock names whose value is a list of the ranges for which a
/// lock has already been acquired.
- final Map<String, List<SourceRange>> _lockRanges = {};
+ final Map<String, List<SourceRange>> lockRanges;
CorrectionProducerContext._({
required this.resolvedResult,
@@ -253,6 +253,7 @@
this.overrideSet,
this.selectionOffset = -1,
this.selectionLength = 0,
+ required this.lockRanges,
}) : file = resolvedResult.path,
session = resolvedResult.session,
sessionHelper = AnalysisSessionHelper(resolvedResult.session),
@@ -276,6 +277,7 @@
TransformOverrideSet? overrideSet,
int selectionOffset = -1,
int selectionLength = 0,
+ Map<String, List<SourceRange>>? lockRanges,
}) {
var selectionEnd = selectionOffset + selectionLength;
var locator = NodeLocator(selectionOffset, selectionEnd);
@@ -292,6 +294,7 @@
overrideSet: overrideSet,
selectionOffset: selectionOffset,
selectionLength: selectionLength,
+ lockRanges: lockRanges ?? {},
);
}
}
@@ -470,7 +473,7 @@
/// ensure this behavior by attempting to acquire a lock prior to creating any
/// edits, and only create the edits if a lock could be acquired.
bool acquireLockOnRange(String lockName, SourceRange range) {
- var ranges = _context._lockRanges.putIfAbsent(lockName, () => []);
+ var ranges = _context.lockRanges.putIfAbsent(lockName, () => []);
if (ranges.contains(range)) {
return false;
}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart
index e26e0061..c464828 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/data_driven_test_support.dart
@@ -2,6 +2,8 @@
// 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:analysis_server/src/services/correction/bulk_fix_processor.dart';
+import 'package:analysis_server/src/services/correction/change_workspace.dart';
import 'package:analysis_server/src/services/correction/dart/data_driven.dart';
import 'package:analysis_server/src/services/correction/fix.dart';
import 'package:analysis_server/src/services/correction/fix/data_driven/code_template.dart';
@@ -9,13 +11,72 @@
import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set.dart';
import 'package:analysis_server/src/services/correction/fix/data_driven/transform_set_manager.dart';
import 'package:analyzer/error/error.dart';
+import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/error/hint_codes.dart';
+import 'package:analyzer/src/services/available_declarations.dart';
+import 'package:analyzer/src/test_utilities/platform.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' show SourceEdit;
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test/test.dart';
+import '../../../../../utils/test_instrumentation_service.dart';
import '../fix_processor.dart';
/// A base class defining support for writing fix processor tests for
/// data-driven fixes.
+abstract class DataDrivenBulkFixProcessorTest
+ extends DataDrivenFixProcessorTest {
+ /// Return `true` if this test uses config files.
+ bool get useConfigFiles => false;
+
+ /// The workspace in which fixes contributor operates.
+ @override
+ DartChangeWorkspace get workspace {
+ return DartChangeWorkspace([session]);
+ }
+
+ @override
+ Future<void> assertHasFix(String expected,
+ {bool Function(AnalysisError)? errorFilter,
+ int? length,
+ String? target,
+ int? expectedNumberOfFixesForKind,
+ String? matchFixMessage,
+ bool allowFixAllFixes = false}) async {
+ if (useLineEndingsForPlatform) {
+ expected = normalizeNewlinesForPlatform(expected);
+ }
+ var processor = await computeFixes();
+ change = processor.builder.sourceChange;
+
+ // apply to "file"
+ var fileEdits = change.edits;
+ expect(fileEdits, hasLength(1));
+
+ var fileContent = testCode;
+ if (target != null) {
+ expect(fileEdits.first.file, convertPath(target));
+ fileContent = getFile(target).readAsStringSync();
+ }
+
+ resultCode = SourceEdit.applySequence(fileContent, change.edits[0].edits);
+ expect(resultCode, expected);
+ }
+
+ /// Computes fixes for the specified [testUnit].
+ Future<BulkFixProcessor> computeFixes() async {
+ var tracker = DeclarationsTracker(MemoryByteStore(), resourceProvider);
+ var analysisContext = contextFor(testFile);
+ tracker.addContext(analysisContext);
+ var processor = BulkFixProcessor(TestInstrumentationService(), workspace,
+ useConfigFiles: useConfigFiles);
+ await processor.fixErrors([analysisContext]);
+ return processor;
+ }
+}
+
+/// A base class defining support for writing fix processor tests for
+/// data-driven fixes.
abstract class DataDrivenFixProcessorTest extends FixProcessorTest {
/// Return the URI used to import the library created by [setPackageContent].
String get importUri => 'package:p/lib.dart';
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_bulk_test.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_bulk_test.dart
new file mode 100644
index 0000000..6e30841
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/flutter_bulk_test.dart
@@ -0,0 +1,178 @@
+// Copyright (c) 2021, 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'data_driven_test_support.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(FlutterBulkTest);
+ });
+}
+
+@reflectiveTest
+class FlutterBulkTest extends DataDrivenBulkFixProcessorTest {
+ Future<void>
+ test_material_ThemeData_textSelectionHandleColor_deprecated() async {
+ setPackageContent('''
+class ThemeData {
+ ThemeData({
+ @deprecated Color? textSelectionHandleColor,
+ @deprecated bool useTextSelectionTheme = false,
+ TextSelectionThemeData? textSelectionTheme}) {}
+}
+class TextSelectionThemeData {
+ TextSelectionThemeData({Color selectionHandleColor}) {}
+}
+class Color {}
+class Colors {
+ static Color yellow = Color();
+}
+''');
+ addPackageDataFile('''
+version: 1
+transforms:
+ - title: "Migrate to 'TextSelectionThemeData'"
+ date: 2020-09-24
+ element:
+ uris: ['$importUri']
+ constructor: ''
+ inClass: 'ThemeData'
+ oneOf:
+ - if: "textSelectionColor != '' && cursorColor != '' && textSelectionHandleColor != ''"
+ changes:
+ - kind: 'addParameter'
+ index: 73
+ name: 'textSelectionTheme'
+ style: optional_named
+ argumentValue:
+ expression: 'TextSelectionThemeData(cursorColor: {% cursorColor %}, selectionColor: {% textSelectionColor %}, selectionHandleColor: {% textSelectionHandleColor %},)'
+ requiredIf: "textSelectionColor != '' && cursorColor != '' && textSelectionHandleColor != ''"
+ - kind: 'removeParameter'
+ name: 'textSelectionColor'
+ - kind: 'removeParameter'
+ name: 'cursorColor'
+ - kind: 'removeParameter'
+ name: 'textSelectionHandleColor'
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ - if: "textSelectionColor == '' && cursorColor != '' && textSelectionHandleColor != ''"
+ changes:
+ - kind: 'addParameter'
+ index: 73
+ name: 'textSelectionTheme'
+ style: optional_named
+ argumentValue:
+ expression: 'TextSelectionThemeData(cursorColor: {% cursorColor %}, selectionHandleColor: {% textSelectionHandleColor %},)'
+ requiredIf: "textSelectionColor == '' && cursorColor != '' && textSelectionHandleColor != ''"
+ - kind: 'removeParameter'
+ name: 'cursorColor'
+ - kind: 'removeParameter'
+ name: 'textSelectionHandleColor'
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ - if: "textSelectionColor != '' && cursorColor != '' && textSelectionHandleColor == ''"
+ changes:
+ - kind: 'addParameter'
+ index: 73
+ name: 'textSelectionTheme'
+ style: optional_named
+ argumentValue:
+ expression: 'TextSelectionThemeData(cursorColor: {% cursorColor %}, selectionColor: {% textSelectionColor %},)'
+ requiredIf: "textSelectionColor != '' && cursorColor != '' && textSelectionHandleColor == ''"
+ - kind: 'removeParameter'
+ name: 'textSelectionColor'
+ - kind: 'removeParameter'
+ name: 'cursorColor'
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ - if: "textSelectionColor != '' && cursorColor == '' && textSelectionHandleColor != ''"
+ changes:
+ - kind: 'addParameter'
+ index: 73
+ name: 'textSelectionTheme'
+ style: optional_named
+ argumentValue:
+ expression: 'TextSelectionThemeData(selectionColor: {% textSelectionColor %}, selectionHandleColor: {% textSelectionHandleColor %},)'
+ requiredIf: "textSelectionColor != '' && cursorColor == '' && textSelectionHandleColor != ''"
+ - kind: 'removeParameter'
+ name: 'textSelectionColor'
+ - kind: 'removeParameter'
+ name: 'textSelectionHandleColor'
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ - if: "textSelectionColor == '' && cursorColor != '' && textSelectionHandleColor == ''"
+ changes:
+ - kind: 'addParameter'
+ index: 73
+ name: 'textSelectionTheme'
+ style: optional_named
+ argumentValue:
+ expression: 'TextSelectionThemeData(cursorColor: {% cursorColor %})'
+ requiredIf: "textSelectionColor == '' && cursorColor != '' && textSelectionHandleColor == ''"
+ - kind: 'removeParameter'
+ name: 'cursorColor'
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ - if: "textSelectionColor != '' && cursorColor == '' && textSelectionHandleColor == ''"
+ changes:
+ - kind: 'addParameter'
+ index: 73
+ name: 'textSelectionTheme'
+ style: optional_named
+ argumentValue:
+ expression: 'TextSelectionThemeData(selectionColor: {% textSelectionColor %})'
+ requiredIf: "textSelectionColor != '' && cursorColor == '' && textSelectionHandleColor == ''"
+ - kind: 'removeParameter'
+ name: 'textSelectionColor'
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ - if: "textSelectionColor == '' && cursorColor == '' && textSelectionHandleColor != ''"
+ changes:
+ - kind: 'addParameter'
+ index: 73
+ name: 'textSelectionTheme'
+ style: optional_named
+ argumentValue:
+ expression: 'TextSelectionThemeData(selectionHandleColor: {% textSelectionHandleColor %})'
+ requiredIf: "textSelectionColor == '' && cursorColor == '' && textSelectionHandleColor != ''"
+ - kind: 'removeParameter'
+ name: 'textSelectionHandleColor'
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ - if: "useTextSelectionTheme != ''"
+ changes:
+ - kind: 'removeParameter'
+ name: 'useTextSelectionTheme'
+ variables:
+ textSelectionColor:
+ kind: 'fragment'
+ value: 'arguments[textSelectionColor]'
+ cursorColor:
+ kind: 'fragment'
+ value: 'arguments[cursorColor]'
+ textSelectionHandleColor:
+ kind: 'fragment'
+ value: 'arguments[textSelectionHandleColor]'
+ useTextSelectionTheme:
+ kind: 'fragment'
+ value: 'arguments[useTextSelectionTheme]'
+''');
+ await resolveTestCode('''
+import '$importUri';
+
+void f() {
+ ThemeData(textSelectionHandleColor: Colors.yellow, useTextSelectionTheme: false);
+}
+''');
+ await assertHasFix('''
+import '$importUri';
+
+void f() {
+ ThemeData(textSelectionTheme: TextSelectionThemeData(selectionHandleColor: Colors.yellow));
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
index 64cffa2..b2ced8a 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/data_driven/test_all.dart
@@ -11,6 +11,7 @@
import 'diagnostics/test_all.dart' as diagnostics;
import 'element_matcher_test.dart' as element_matcher;
import 'end_to_end_test.dart' as end_to_end;
+import 'flutter_bulk_test.dart' as flutter_bulk;
import 'flutter_use_case_test.dart' as flutter_use_case;
import 'modify_parameters_test.dart' as modify_parameters;
import 'rename_parameter_test.dart' as rename_parameter;
@@ -30,6 +31,7 @@
diagnostics.main();
element_matcher.main();
end_to_end.main();
+ flutter_bulk.main();
flutter_use_case.main();
modify_parameters.main();
rename_parameter.main();
diff --git a/pkg/analysis_server/test/src/services/correction/fix/remove_argument_test.dart b/pkg/analysis_server/test/src/services/correction/fix/remove_argument_test.dart
index 736da8c..87759cd 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/remove_argument_test.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/remove_argument_test.dart
@@ -21,7 +21,7 @@
@override
String get lintCode => LintNames.avoid_redundant_argument_values;
- Future<void> test_singleFile() async {
+ Future<void> test_independentInvocations() async {
await resolveTestCode('''
void f({bool valWithDefault = true, bool val}) {}
void f2({bool valWithDefault = true, bool val}) {}
@@ -41,6 +41,23 @@
}
''');
}
+
+ Future<void> test_multipleInSingleInvocation() async {
+ await resolveTestCode('''
+void f() {
+ g(a: 0, b: 1, c: 2);
+}
+
+void g({int a = 0, int b = 1, int c = 2}) {}
+''');
+ await assertHasFix('''
+void f() {
+ g();
+}
+
+void g({int a = 0, int b = 1, int c = 2}) {}
+''');
+ }
}
@reflectiveTest
diff --git a/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart b/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
index 777ccda..459974c 100644
--- a/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
+++ b/pkg/analyzer_plugin/lib/src/protocol/protocol_internal.dart
@@ -4,6 +4,7 @@
import 'dart:collection';
import 'dart:convert' hide JsonDecoder;
+import 'dart:math' as math;
import 'package:analyzer_plugin/protocol/protocol.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
@@ -28,6 +29,17 @@
/// If the invariants can't be preserved, then a [ConflictingEditException] is
/// thrown.
void addEditForSource(SourceFileEdit sourceFileEdit, SourceEdit sourceEdit) {
+ /// If the [leftEdit] and the [rightEdit] can be merged, then merge them.
+ SourceEdit? _merge(SourceEdit leftEdit, SourceEdit rightEdit) {
+ assert(leftEdit.offset <= rightEdit.offset);
+ if (leftEdit.isDeletion && rightEdit.isDeletion) {
+ var offset = leftEdit.offset;
+ var end = math.max(leftEdit.end, rightEdit.end);
+ return SourceEdit(offset, end - offset, '');
+ }
+ return null;
+ }
+
var edits = sourceFileEdit.edits;
var length = edits.length;
var index = 0;
@@ -39,7 +51,12 @@
// The [previousEdit] has an offset that is strictly greater than the offset
// of the [sourceEdit] so we only need to look at the end of the
// [sourceEdit] to know whether they overlap.
- if (sourceEdit.offset + sourceEdit.length > previousEdit.offset) {
+ if (sourceEdit.end > previousEdit.offset) {
+ var mergedEdit = _merge(sourceEdit, previousEdit);
+ if (mergedEdit != null) {
+ edits[index - 1] = mergedEdit;
+ return;
+ }
throw ConflictingEditException(
newEdit: sourceEdit, existingEdit: previousEdit);
}
@@ -54,7 +71,12 @@
if ((sourceEdit.offset == nextEdit.offset &&
sourceEdit.length > 0 &&
nextEdit.length > 0) ||
- nextEdit.offset + nextEdit.length > sourceEdit.offset) {
+ nextEdit.end > sourceEdit.offset) {
+ var mergedEdit = _merge(nextEdit, sourceEdit);
+ if (mergedEdit != null) {
+ edits[index] = mergedEdit;
+ return;
+ }
throw ConflictingEditException(
newEdit: sourceEdit, existingEdit: nextEdit);
}
@@ -468,3 +490,11 @@
/// the given [id], where the request was received at the given [requestTime].
Response toResponse(String id, int requestTime);
}
+
+extension SourceEditExtensions on SourceEdit {
+ /// Return `true` if this source edit represents a deletion.
+ bool get isDeletion => replacement.isEmpty;
+
+ /// Return `true` if this source edit represents an insertion.
+ bool get isInsertion => length == 0;
+}
diff --git a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
index a571f04..6ed22f9 100644
--- a/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
+++ b/pkg/analyzer_plugin/test/src/utilities/change_builder/change_builder_core_test.dart
@@ -17,6 +17,7 @@
defineReflectiveSuite(() {
defineReflectiveTests(ChangeBuilderImplTest);
defineReflectiveTests(EditBuilderImplTest);
+ defineReflectiveTests(FileEditBuilderImpl_ConflictingTest);
defineReflectiveTests(FileEditBuilderImplTest);
defineReflectiveTests(LinkedEditBuilderImplTest);
});
@@ -297,6 +298,229 @@
}
}
+/// Tests that are specifically targeted at the handling of conflicting edits.
+@reflectiveTest
+class FileEditBuilderImpl_ConflictingTest extends AbstractChangeBuilderTest {
+ String path = '/test.dart';
+
+ Matcher get hasConflict => throwsA(isA<ConflictingEditException>());
+
+ Future<void> test_deletion_deletion_adjacent_left() async {
+ var firstOffset = 30;
+ var firstLength = 5;
+ var secondOffset = 23;
+ var secondLength = 7;
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addDeletion(SourceRange(firstOffset, firstLength));
+ builder.addDeletion(SourceRange(secondOffset, secondLength));
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(2));
+ expect(edits[0].offset, firstOffset);
+ expect(edits[0].length, firstLength);
+ expect(edits[0].replacement, isEmpty);
+ expect(edits[1].offset, secondOffset);
+ expect(edits[1].length, secondLength);
+ expect(edits[1].replacement, isEmpty);
+ }
+
+ Future<void> test_deletion_deletion_adjacent_right() async {
+ var firstOffset = 23;
+ var firstLength = 7;
+ var secondOffset = 30;
+ var secondLength = 5;
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addDeletion(SourceRange(firstOffset, firstLength));
+ builder.addDeletion(SourceRange(secondOffset, secondLength));
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(2));
+ expect(edits[0].offset, secondOffset);
+ expect(edits[0].length, secondLength);
+ expect(edits[0].replacement, isEmpty);
+ expect(edits[1].offset, firstOffset);
+ expect(edits[1].length, firstLength);
+ expect(edits[1].replacement, isEmpty);
+ }
+
+ Future<void> test_deletion_deletion_overlap_left() async {
+ var firstOffset = 27;
+ var firstLength = 8;
+ var secondOffset = 23;
+ var secondLength = 7;
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addDeletion(SourceRange(firstOffset, firstLength));
+ builder.addDeletion(SourceRange(secondOffset, secondLength));
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(1));
+ expect(edits[0].offset, secondOffset);
+ expect(edits[0].length, firstOffset + firstLength - secondOffset);
+ expect(edits[0].replacement, isEmpty);
+ }
+
+ Future<void> test_deletion_deletion_overlap_right() async {
+ var firstOffset = 23;
+ var firstLength = 7;
+ var secondOffset = 27;
+ var secondLength = 8;
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addDeletion(SourceRange(firstOffset, firstLength));
+ builder.addDeletion(SourceRange(secondOffset, secondLength));
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(1));
+ expect(edits[0].offset, firstOffset);
+ expect(edits[0].length, secondOffset + secondLength - firstOffset);
+ expect(edits[0].replacement, isEmpty);
+ }
+
+ Future<void> test_deletion_insertion_adjacent_left() async {
+ var deletionOffset = 23;
+ var deletionLength = 7;
+ var insertionOffset = 23;
+ var insertionText = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addDeletion(SourceRange(deletionOffset, deletionLength));
+ expect(() {
+ builder.addSimpleInsertion(insertionOffset, insertionText);
+ }, hasConflict);
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(1));
+ expect(edits[0].offset, deletionOffset);
+ expect(edits[0].length, deletionLength);
+ expect(edits[0].replacement, '');
+ }
+
+ Future<void> test_deletion_insertion_adjacent_right() async {
+ var deletionOffset = 23;
+ var deletionLength = 7;
+ var insertionOffset = 30;
+ var insertionText = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addDeletion(SourceRange(deletionOffset, deletionLength));
+ builder.addSimpleInsertion(insertionOffset, insertionText);
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(2));
+ expect(edits[0].offset, insertionOffset);
+ expect(edits[0].length, 0);
+ expect(edits[0].replacement, insertionText);
+ expect(edits[1].offset, deletionOffset);
+ expect(edits[1].length, deletionLength);
+ expect(edits[1].replacement, isEmpty);
+ }
+
+ Future<void> test_deletion_insertion_overlap() async {
+ var deletionOffset = 23;
+ var deletionLength = 7;
+ var insertionOffset = 26;
+ var insertionText = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addDeletion(SourceRange(deletionOffset, deletionLength));
+ expect(() {
+ builder.addSimpleInsertion(insertionOffset, insertionText);
+ }, hasConflict);
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(1));
+ expect(edits[0].offset, deletionOffset);
+ expect(edits[0].length, deletionLength);
+ expect(edits[0].replacement, '');
+ }
+
+ Future<void> test_insertion_deletion_adjacent_left() async {
+ var deletionOffset = 23;
+ var deletionLength = 7;
+ var insertionOffset = 23;
+ var insertionText = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addSimpleInsertion(insertionOffset, insertionText);
+ builder.addDeletion(SourceRange(deletionOffset, deletionLength));
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(2));
+ expect(edits[0].offset, deletionOffset);
+ expect(edits[0].length, deletionLength);
+ expect(edits[0].replacement, isEmpty);
+ expect(edits[1].offset, insertionOffset);
+ expect(edits[1].length, 0);
+ expect(edits[1].replacement, insertionText);
+ }
+
+ Future<void> test_insertion_deletion_adjacent_right() async {
+ var deletionOffset = 23;
+ var deletionLength = 7;
+ var insertionOffset = 30;
+ var insertionText = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addSimpleInsertion(insertionOffset, insertionText);
+ builder.addDeletion(SourceRange(deletionOffset, deletionLength));
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(2));
+ expect(edits[0].offset, insertionOffset);
+ expect(edits[0].length, 0);
+ expect(edits[0].replacement, insertionText);
+ expect(edits[1].offset, deletionOffset);
+ expect(edits[1].length, deletionLength);
+ expect(edits[1].replacement, isEmpty);
+ }
+
+ Future<void> test_insertion_deletion_overlap() async {
+ var deletionOffset = 23;
+ var deletionLength = 7;
+ var insertionOffset = 26;
+ var insertionText = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addSimpleInsertion(insertionOffset, insertionText);
+ expect(() {
+ builder.addDeletion(SourceRange(deletionOffset, deletionLength));
+ }, hasConflict);
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(1));
+ expect(edits[0].offset, insertionOffset);
+ expect(edits[0].length, 0);
+ expect(edits[0].replacement, insertionText);
+ }
+
+ Future<void> test_replacement_replacement_overlap_left() async {
+ var offset = 23;
+ var length = 7;
+ var text = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addSimpleReplacement(SourceRange(offset, length), text);
+ expect(() {
+ builder.addSimpleReplacement(SourceRange(offset - 2, length), text);
+ }, hasConflict);
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(1));
+ expect(edits[0].offset, offset);
+ expect(edits[0].length, length);
+ expect(edits[0].replacement, text);
+ }
+
+ Future<void> test_replacement_replacement_overlap_right() async {
+ var offset = 23;
+ var length = 7;
+ var text = 'x';
+ await builder.addGenericFileEdit(path, (builder) {
+ builder.addSimpleReplacement(SourceRange(offset, length), text);
+ expect(() {
+ builder.addSimpleReplacement(SourceRange(offset + 2, length), text);
+ }, hasConflict);
+ });
+ var edits = builder.sourceChange.edits[0].edits;
+ expect(edits, hasLength(1));
+ expect(edits[0].offset, offset);
+ expect(edits[0].length, length);
+ expect(edits[0].replacement, text);
+ }
+}
+
@reflectiveTest
class FileEditBuilderImplTest extends AbstractChangeBuilderTest {
String path = '/test.dart';
@@ -314,68 +538,6 @@
expect(edits[0].replacement, isEmpty);
}
- Future<void> test_addDeletion_adjacent_lowerOffsetFirst() async {
- // TODO(brianwilkerson) This should also merge the deletions, but is written
- // to ensure that existing uses of FileEditBuilder continue to work even
- // without that change.
- var firstOffset = 23;
- var firstLength = 7;
- var secondOffset = 30;
- var secondLength = 5;
- await builder.addGenericFileEdit(path, (builder) {
- builder.addDeletion(SourceRange(firstOffset, firstLength));
- builder.addDeletion(SourceRange(secondOffset, secondLength));
- });
- var edits = builder.sourceChange.edits[0].edits;
- expect(edits, hasLength(2));
- expect(edits[0].offset, secondOffset);
- expect(edits[0].length, secondLength);
- expect(edits[0].replacement, isEmpty);
- expect(edits[1].offset, firstOffset);
- expect(edits[1].length, firstLength);
- expect(edits[1].replacement, isEmpty);
- }
-
- Future<void> test_addDeletion_adjacent_lowerOffsetSecond() async {
- // TODO(brianwilkerson) This should also merge the deletions, but is written
- // to ensure that existing uses of FileEditBuilder continue to work even
- // without that change.
- var firstOffset = 23;
- var firstLength = 7;
- var secondOffset = 30;
- var secondLength = 5;
- await builder.addGenericFileEdit(path, (builder) {
- builder.addDeletion(SourceRange(secondOffset, secondLength));
- builder.addDeletion(SourceRange(firstOffset, firstLength));
- });
- var edits = builder.sourceChange.edits[0].edits;
- expect(edits, hasLength(2));
- expect(edits[0].offset, secondOffset);
- expect(edits[0].length, secondLength);
- expect(edits[0].replacement, isEmpty);
- expect(edits[1].offset, firstOffset);
- expect(edits[1].length, firstLength);
- expect(edits[1].replacement, isEmpty);
- }
-
- @failingTest
- Future<void> test_addDeletion_overlapping() async {
- // This support is not yet implemented.
- var firstOffset = 23;
- var firstLength = 7;
- var secondOffset = 27;
- var secondLength = 8;
- await builder.addGenericFileEdit(path, (builder) {
- builder.addDeletion(SourceRange(firstOffset, firstLength));
- builder.addDeletion(SourceRange(secondOffset, secondLength));
- });
- var edits = builder.sourceChange.edits[0].edits;
- expect(edits, hasLength(1));
- expect(edits[0].offset, firstOffset);
- expect(edits[0].length, secondOffset + secondLength - firstOffset);
- expect(edits[0].replacement, isEmpty);
- }
-
Future<void> test_addInsertion() async {
await builder.addGenericFileEdit(path, (builder) {
builder.addInsertion(10, (builder) {
@@ -472,40 +634,6 @@
expect(edits[1].replacement, text);
}
- Future<void> test_addSimpleReplacement_overlapsHead() async {
- var offset = 23;
- var length = 7;
- var text = 'xyz';
- await builder.addGenericFileEdit(path, (builder) {
- builder.addSimpleReplacement(SourceRange(offset, length), text);
- expect(() {
- builder.addSimpleReplacement(SourceRange(offset - 2, length), text);
- }, throwsA(isA<ConflictingEditException>()));
- });
- var edits = builder.sourceChange.edits[0].edits;
- expect(edits, hasLength(1));
- expect(edits[0].offset, offset);
- expect(edits[0].length, length);
- expect(edits[0].replacement, text);
- }
-
- Future<void> test_addSimpleReplacement_overlapsTail() async {
- var offset = 23;
- var length = 7;
- var text = 'xyz';
- await builder.addGenericFileEdit(path, (builder) {
- builder.addSimpleReplacement(SourceRange(offset, length), text);
- expect(() {
- builder.addSimpleReplacement(SourceRange(offset + 2, length), text);
- }, throwsA(isA<ConflictingEditException>()));
- });
- var edits = builder.sourceChange.edits[0].edits;
- expect(edits, hasLength(1));
- expect(edits[0].offset, offset);
- expect(edits[0].length, length);
- expect(edits[0].replacement, text);
- }
-
Future<void> test_createEditBuilder() async {
await builder.addGenericFileEdit(path, (builder) {
var offset = 4;
diff --git a/runtime/lib/isolate.cc b/runtime/lib/isolate.cc
index 3569eb9..f3bb2bd 100644
--- a/runtime/lib/isolate.cc
+++ b/runtime/lib/isolate.cc
@@ -111,9 +111,8 @@
// We have to check whether the reciever has the same isolate group (e.g.
// native message handlers such as an IOService handler does not but does
// share the same origin port).
- const bool same_group =
- FLAG_enable_isolate_groups && PortMap::IsReceiverInThisIsolateGroup(
- destination_port_id, isolate->group());
+ const bool same_group = PortMap::IsReceiverInThisIsolateGroup(
+ destination_port_id, isolate->group());
// TODO(turnidge): Throw an exception when the return value is false?
PortMap::PostMessage(WriteMessage(can_send_any_object, same_group, obj,
destination_port_id,
@@ -639,7 +638,7 @@
ASSERT(name != nullptr);
auto group = state_->isolate_group();
- if (!FLAG_enable_isolate_groups || group == nullptr) {
+ if (group == nullptr) {
RunHeavyweight(name);
} else {
RunLightweight(name);
@@ -676,8 +675,7 @@
}
void RunLightweight(const char* name) {
- // The create isolate initialize callback is mandatory if
- // --enable-isolate-groups was passed.
+ // The create isolate initialize callback is mandatory.
auto initialize_callback = Isolate::InitializeCallback();
if (initialize_callback == nullptr) {
FailedSpawn(
@@ -934,22 +932,16 @@
const auto& func = Function::Handle(zone, GetTopLevelFunction(zone, closure));
PersistentHandle* closure_tuple_handle = nullptr;
if (func.IsNull()) {
- if (!FLAG_enable_isolate_groups) {
- const String& msg = String::Handle(String::New(
- "Isolate.spawn expects to be passed a static or top-level function"));
- Exceptions::ThrowArgumentError(msg);
- } else {
- // We have a non-toplevel closure that we might need to copy.
- // Result will be [<closure-copy>, <objects-in-msg-to-rehash>]
- const auto& closure_copy_tuple = Object::Handle(
- zone, CopyMutableObjectGraph(closure)); // Throws if it fails.
- ASSERT(closure_copy_tuple.IsArray());
- ASSERT(Object::Handle(zone, Array::Cast(closure_copy_tuple).At(0))
- .IsClosure());
- closure_tuple_handle =
- isolate->group()->api_state()->AllocatePersistentHandle();
- closure_tuple_handle->set_ptr(closure_copy_tuple.ptr());
- }
+ // We have a non-toplevel closure that we might need to copy.
+ // Result will be [<closure-copy>, <objects-in-msg-to-rehash>]
+ const auto& closure_copy_tuple = Object::Handle(
+ zone, CopyMutableObjectGraph(closure)); // Throws if it fails.
+ ASSERT(closure_copy_tuple.IsArray());
+ ASSERT(Object::Handle(zone, Array::Cast(closure_copy_tuple).At(0))
+ .IsClosure());
+ closure_tuple_handle =
+ isolate->group()->api_state()->AllocatePersistentHandle();
+ closure_tuple_handle->set_ptr(closure_copy_tuple.ptr());
}
bool fatal_errors = fatalErrors.IsNull() ? true : fatalErrors.value();
@@ -960,9 +952,8 @@
// serializable this will throw an exception.
SerializedObjectBuffer message_buffer;
message_buffer.set_message(WriteMessage(
- /* can_send_any_object */ true,
- /* same_group */ FLAG_enable_isolate_groups, message, ILLEGAL_PORT,
- Message::kNormalPriority));
+ /*can_send_any_object=*/true,
+ /*same_group=*/true, message, ILLEGAL_PORT, Message::kNormalPriority));
const char* utf8_package_config =
packageConfig.IsNull() ? NULL : String2UTF8(packageConfig);
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 798174d..6bf2d87 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -4869,17 +4869,28 @@
if (instructions.is_open()) {
if (inside_try_finally) {
- ASSERT(scopes()->finally_return_variable != NULL);
+ LocalVariable* const finally_return_variable =
+ scopes()->finally_return_variable;
+ ASSERT(finally_return_variable != nullptr);
const Function& function = parsed_function()->function();
if (NeedsDebugStepCheck(function, position)) {
instructions += DebugStepCheck(position);
}
- instructions += StoreLocal(position, scopes()->finally_return_variable);
+ instructions += StoreLocal(position, finally_return_variable);
instructions += Drop();
- instructions += TranslateFinallyFinalizers(NULL, -1);
+ const intptr_t target_context_depth =
+ finally_return_variable->is_captured()
+ ? finally_return_variable->owner()->context_level()
+ : -1;
+ instructions += TranslateFinallyFinalizers(nullptr, target_context_depth);
if (instructions.is_open()) {
- instructions += LoadLocal(scopes()->finally_return_variable);
+ const intptr_t saved_context_depth = B->context_depth_;
+ if (finally_return_variable->is_captured()) {
+ B->context_depth_ = target_context_depth;
+ }
+ instructions += LoadLocal(finally_return_variable);
instructions += Return(TokenPosition::kNoSource);
+ B->context_depth_ = saved_context_depth;
}
} else {
instructions += Return(position);
diff --git a/runtime/vm/compiler/jit/compiler.cc b/runtime/vm/compiler/jit/compiler.cc
index 1ec2adc..5b08899 100644
--- a/runtime/vm/compiler/jit/compiler.cc
+++ b/runtime/vm/compiler/jit/compiler.cc
@@ -211,7 +211,7 @@
ASSERT(thread->IsMutatorThread());
const Function& function = Function::CheckedHandle(zone, arguments.ArgAt(0));
- if (FLAG_enable_isolate_groups) {
+ {
// Another isolate's mutator thread may have created [function] and
// published it via an ICData, MegamorphicCache etc. Entering the lock below
// is an acquire operation that pairs with the release operation when the
@@ -220,12 +220,6 @@
SafepointReadRwLocker ml(thread, thread->isolate_group()->program_lock());
}
- // In single-isolate scenarios the lazy compile stub is only invoked if
- // there's no existing code. In multi-isolate scenarios with shared JITed code
- // we can end up in the lazy compile runtime entry here with code being
- // installed.
- ASSERT(!function.HasCode() || FLAG_enable_isolate_groups);
-
// Will throw if compilation failed (e.g. with compile-time error).
function.EnsureHasCode();
}
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 1c7026a..88dc441 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -1463,13 +1463,6 @@
*error = nullptr;
- if (!FLAG_enable_isolate_groups) {
- *error = Utils::StrDup(
- "Lightweight isolates need to be explicitly enabled by passing "
- "--enable-isolate-groups.");
- return nullptr;
- }
-
Isolate* isolate;
isolate = CreateWithinExistingIsolateGroup(member->group(), name, error);
if (isolate != nullptr) {
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index 224a6b1..eccc2ac 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -194,7 +194,6 @@
P(retain_code_objects, bool, true, \
"Serialize all code objects even if not otherwise " \
"needed in the precompiled runtime.") \
- P(enable_isolate_groups, bool, true, "Enable isolate group support.") \
P(show_invisible_frames, bool, false, \
"Show invisible frames in stack traces.") \
D(trace_cha, bool, false, "Trace CHA operations") \
diff --git a/runtime/vm/heap/heap_test.cc b/runtime/vm/heap/heap_test.cc
index b30bf66..c45765c 100644
--- a/runtime/vm/heap/heap_test.cc
+++ b/runtime/vm/heap/heap_test.cc
@@ -574,9 +574,6 @@
};
VM_UNIT_TEST_CASE(CleanupBequestNeverReceived) {
- // This test uses features from isolate groups
- FLAG_enable_isolate_groups = true;
-
const char* TEST_MESSAGE = "hello, world";
Dart_Isolate parent = TestCase::CreateTestIsolate("parent");
EXPECT_EQ(parent, Dart_CurrentIsolate());
@@ -608,9 +605,6 @@
}
VM_UNIT_TEST_CASE(ReceivesSendAndExitMessage) {
- // This test uses features from isolate groups
- FLAG_enable_isolate_groups = true;
-
const char* TEST_MESSAGE = "hello, world";
Dart_Isolate parent = TestCase::CreateTestIsolate("parent");
EXPECT_EQ(parent, Dart_CurrentIsolate());
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index c1e6cbe..3f425e4 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2638,11 +2638,9 @@
Dart::thread_pool()->Run<ShutdownGroupTask>(isolate_group);
}
} else {
- if (FLAG_enable_isolate_groups) {
- // TODO(dartbug.com/36097): An isolate just died. A significant amount of
- // memory might have become unreachable. We should evaluate how to best
- // inform the GC about this situation.
- }
+ // TODO(dartbug.com/36097): An isolate just died. A significant amount of
+ // memory might have become unreachable. We should evaluate how to best
+ // inform the GC about this situation.
}
} // namespace dart
@@ -2807,11 +2805,6 @@
auto thread = Thread::Current();
StoppedMutatorsScope stopped_mutators_scope(thread);
- if (thread->IsMutatorThread() && !FLAG_enable_isolate_groups) {
- single_current_mutator->Call();
- return;
- }
-
if (thread->IsAtSafepoint()) {
RELEASE_ASSERT(safepoint_handler()->IsOwnedByTheThread(thread));
single_current_mutator->Call();
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 549dd9b..c693c02 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -869,9 +869,6 @@
new_cache.WriteEntryToBuffer(zone, &buffer, colliding_index, " ");
THR_Print("%s\n", buffer.buffer());
}
- if (!FLAG_enable_isolate_groups) {
- FATAL("Duplicate subtype test cache entry");
- }
if (old_result.ptr() != result.ptr()) {
FATAL("Existing subtype test cache entry has result %s, not %s",
old_result.ToCString(), result.ToCString());
@@ -1246,9 +1243,6 @@
const Code& target_code = Code::Handle(zone, target_function.EnsureHasCode());
// Before patching verify that we are not repeatedly patching to the same
// target.
- ASSERT(FLAG_enable_isolate_groups ||
- target_code.ptr() != CodePatcher::GetStaticCallTargetAt(
- caller_frame->pc(), caller_code));
if (target_code.ptr() !=
CodePatcher::GetStaticCallTargetAt(caller_frame->pc(), caller_code)) {
GcSafepointOperationScope safepoint(thread);
@@ -3018,10 +3012,6 @@
current_target_code.EntryPoint(),
current_target_code.is_optimized() ? "optimized" : "unoptimized");
}
- // With isolate groups enabled, it is possible that the target code
- // has been deactivated just now(as a result of re-optimizatin for example),
- // which will result in another run through FixCallersTarget.
- ASSERT(!current_target_code.IsDisabled() || FLAG_enable_isolate_groups);
arguments.SetReturn(current_target_code);
#else
UNREACHABLE();
diff --git a/runtime/vm/symbols.cc b/runtime/vm/symbols.cc
index 673f26e..a60edfa 100644
--- a/runtime/vm/symbols.cc
+++ b/runtime/vm/symbols.cc
@@ -401,11 +401,6 @@
// cases.
if (thread->IsAtSafepoint()) {
RELEASE_ASSERT(group->safepoint_handler()->IsOwnedByTheThread(thread));
- // In DEBUG mode the snapshot writer also calls this method inside a
- // safepoint.
-#if !defined(DEBUG)
- RELEASE_ASSERT(FLAG_enable_isolate_groups || !USING_PRODUCT);
-#endif
data = object_store->symbol_table();
CanonicalStringSet table(&key, &value, &data);
symbol ^= table.GetOrNull(str);
diff --git a/tests/language/async_star/regression_47610_test.dart b/tests/language/async_star/regression_47610_test.dart
new file mode 100644
index 0000000..9bb4ce2
--- /dev/null
+++ b/tests/language/async_star/regression_47610_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, 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.
+
+// Regression test for https://github.com/dart-lang/sdk/issues/47610.
+// Tests returning value from a deep context depth along with
+// breaking from 'await for'.
+
+import "dart:async";
+import "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+
+Stream<int> foo() async* {
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < 2; ++j) {
+ for (int k = 0; k < 2; ++k) {
+ yield i + j + k;
+ }
+ }
+ }
+}
+
+void test() async {
+ await for (var x in foo()) {
+ Expect.equals(0, x);
+ break;
+ }
+}
+
+void main() {
+ asyncTest(test);
+}
diff --git a/tests/language_2/async_star/regression_47610_test.dart b/tests/language_2/async_star/regression_47610_test.dart
new file mode 100644
index 0000000..4fd7815
--- /dev/null
+++ b/tests/language_2/async_star/regression_47610_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, 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
+
+// Regression test for https://github.com/dart-lang/sdk/issues/47610.
+// Tests returning value from a deep context depth along with
+// breaking from 'await for'.
+
+import "dart:async";
+import "package:expect/expect.dart";
+import "package:async_helper/async_helper.dart";
+
+Stream<int> foo() async* {
+ for (int i = 0; i < 2; ++i) {
+ for (int j = 0; j < 2; ++j) {
+ for (int k = 0; k < 2; ++k) {
+ yield i + j + k;
+ }
+ }
+ }
+}
+
+void test() async {
+ await for (var x in foo()) {
+ Expect.equals(0, x);
+ break;
+ }
+}
+
+void main() {
+ asyncTest(test);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 41c3212..0754943 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 290
+PRERELEASE 291
PRERELEASE_PATCH 0
\ No newline at end of file