Version 2.14.0-90.0.dev
Merge commit '484c999f129aacedc6c568d865450c98e4967fbd' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/convert_to_for_loop.dart b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_for_loop.dart
new file mode 100644
index 0000000..0481730
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/convert_to_for_loop.dart
@@ -0,0 +1,83 @@
+// 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:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:analyzer_plugin/utilities/range_factory.dart';
+
+class ConvertForEachToForLoop extends CorrectionProducer {
+ @override
+ FixKind get fixKind => DartFixKind.CONVERT_FOR_EACH_TO_FOR_LOOP;
+
+ @override
+ FixKind get multiFixKind => DartFixKind.CONVERT_FOR_EACH_TO_FOR_LOOP_MULTI;
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ var invocation = node.parent;
+ if (invocation is! MethodInvocation) {
+ return;
+ }
+ var statement = invocation.parent;
+ if (statement is! ExpressionStatement) {
+ return;
+ }
+ var argument = invocation.argumentList.arguments[0];
+ if (argument is! FunctionExpression) {
+ return;
+ }
+ var parameters = argument.parameters?.parameters;
+ if (parameters == null || parameters.length != 1) {
+ return;
+ }
+ var parameter = parameters[0];
+ if (parameter is! NormalFormalParameter) {
+ return;
+ }
+ var loopVariableName = parameter.identifier?.name;
+ if (loopVariableName == null) {
+ return;
+ }
+ var target = utils.getNodeText(invocation.target!);
+ var body = argument.body;
+ if (body is BlockFunctionBody) {
+ await builder.addDartFileEdit(file, (builder) {
+ builder.addReplacement(range.startStart(invocation, body), (builder) {
+ builder.write('for (var ');
+ builder.write(loopVariableName);
+ builder.write(' in ');
+ builder.write(target);
+ builder.write(') ');
+ });
+ builder.addDeletion(range.endEnd(body, statement));
+ });
+ } else if (body is ExpressionFunctionBody) {
+ await builder.addDartFileEdit(file, (builder) {
+ var expression = body.expression;
+ var prefix = utils.getPrefix(statement.offset);
+ builder.addReplacement(range.startStart(invocation, expression),
+ (builder) {
+ builder.write('for (var ');
+ builder.write(loopVariableName);
+ builder.write(' in ');
+ builder.write(target);
+ builder.writeln(') {');
+ builder.write(prefix);
+ builder.write(' ');
+ });
+ builder.addReplacement(range.endEnd(expression, statement), (builder) {
+ builder.writeln(';');
+ builder.write(prefix);
+ builder.write('}');
+ });
+ });
+ }
+ }
+
+ /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+ static ConvertForEachToForLoop newInstance() => ConvertForEachToForLoop();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 041bda6..7bb4b16 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -257,6 +257,14 @@
'dart.fix.flutter.convert.childrenToChild',
DartFixKindPriority.DEFAULT,
'Convert to child:');
+ static const CONVERT_FOR_EACH_TO_FOR_LOOP = FixKind(
+ 'dart.fix.convert.toForLoop',
+ DartFixKindPriority.DEFAULT,
+ "Convert 'forEach' to a 'for' loop");
+ static const CONVERT_FOR_EACH_TO_FOR_LOOP_MULTI = FixKind(
+ 'dart.fix.convert.toForLoop.multi',
+ DartFixKindPriority.IN_FILE,
+ "Convert 'forEach' to a 'for' loop everywhere in file");
static const CONVERT_INTO_EXPRESSION_BODY = FixKind(
'dart.fix.convert.toExpressionBody',
DartFixKindPriority.DEFAULT,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 3b6bbe8..23e1d41 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -43,6 +43,7 @@
import 'package:analysis_server/src/services/correction/dart/convert_quotes.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_contains.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_expression_function_body.dart';
+import 'package:analysis_server/src/services/correction/dart/convert_to_for_loop.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_generic_function_syntax.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_if_null.dart';
import 'package:analysis_server/src/services/correction/dart/convert_to_int_literal.dart';
@@ -452,6 +453,15 @@
],
)
],
+ LintNames.avoid_function_literals_in_foreach_calls: [
+ FixInfo(
+ canBeAppliedToFile: true,
+ canBeBulkApplied: true,
+ generators: [
+ ConvertForEachToForLoop.newInstance,
+ ],
+ )
+ ],
LintNames.avoid_init_to_null: [
FixInfo(
canBeAppliedToFile: true,
diff --git a/pkg/analysis_server/lib/src/services/linter/lint_names.dart b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
index cc4823f..4f9fd95 100644
--- a/pkg/analysis_server/lib/src/services/linter/lint_names.dart
+++ b/pkg/analysis_server/lib/src/services/linter/lint_names.dart
@@ -13,6 +13,8 @@
static const String avoid_annotating_with_dynamic =
'avoid_annotating_with_dynamic';
static const String avoid_empty_else = 'avoid_empty_else';
+ static const String avoid_function_literals_in_foreach_calls =
+ 'avoid_function_literals_in_foreach_calls';
static const String avoid_init_to_null = 'avoid_init_to_null';
static const String avoid_private_typedef_functions =
'avoid_private_typedef_functions';
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/convert_for_each_to_for_loop_test.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/convert_for_each_to_for_loop_test.dart
new file mode 100644
index 0000000..2514ae0
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/convert_for_each_to_for_loop_test.dart
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, 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:analysis_server/src/services/linter/lint_names.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'bulk_fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ConvertForEachToForLoop);
+ });
+}
+
+@reflectiveTest
+class ConvertForEachToForLoop extends BulkFixProcessorTest {
+ @override
+ String get lintCode => LintNames.avoid_function_literals_in_foreach_calls;
+
+ Future<void> test_blockBody_blockBody() async {
+ await resolveTestCode(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ a.forEach((ea) {
+ b.forEach((eb) {
+ result.add('$ea $eb');
+ });
+ });
+}
+''');
+ await assertHasFix(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ for (var ea in a) {
+ for (var eb in b) {
+ result.add('$ea $eb');
+ }
+ }
+}
+''');
+ }
+
+ Future<void> test_blockBody_expressionBody() async {
+ await resolveTestCode(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ a.forEach((ea) {
+ b.forEach((eb) => result.add('$ea $eb'));
+ });
+}
+''');
+ await assertHasFix(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ for (var ea in a) {
+ for (var eb in b) {
+ result.add('$ea $eb');
+ }
+ }
+}
+''');
+ }
+
+ Future<void> test_expressionBody_blockBody() async {
+ await resolveTestCode(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ a.forEach((ea) => b.forEach((eb) {
+ result.add('$ea $eb');
+ }));
+}
+''');
+ await assertHasFix(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ for (var ea in a) {
+ b.forEach((eb) {
+ result.add('$ea $eb');
+ });
+ }
+}
+''');
+ }
+
+ Future<void> test_expressionBody_expressionBody() async {
+ await resolveTestCode(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ a.forEach((ea) => b.forEach((eb) => result.add('$ea $eb')));
+}
+''');
+ await assertHasFix(r'''
+void f(List<String> a, List<String> b) {
+ var result = <String>[];
+ for (var ea in a) {
+ b.forEach((eb) => result.add('$ea $eb'));
+ }
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
index 6b8bb68..a90e17f 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/bulk/test_all.dart
@@ -13,6 +13,7 @@
import 'bulk_fix_processor_test.dart' as bulk_fix_processor;
import 'convert_documentation_into_line_test.dart'
as convert_documentation_into_line;
+import 'convert_for_each_to_for_loop_test.dart' as convert_for_each_to_for_loop;
import 'convert_map_from_iterable_to_for_literal_test.dart'
as convert_map_from_iterable_to_for_literal;
import 'convert_to_contains_test.dart' as convert_to_contains;
@@ -82,6 +83,7 @@
add_required.main();
bulk_fix_processor.main();
convert_documentation_into_line.main();
+ convert_for_each_to_for_loop.main();
convert_map_from_iterable_to_for_literal.main();
convert_to_contains.main();
convert_to_generic_function_syntax.main();
diff --git a/pkg/analysis_server/test/src/services/correction/fix/convert_for_each_to_for_loop_test.dart b/pkg/analysis_server/test/src/services/correction/fix/convert_for_each_to_for_loop_test.dart
new file mode 100644
index 0000000..b8d2fbd
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/convert_for_each_to_for_loop_test.dart
@@ -0,0 +1,57 @@
+// 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:analysis_server/src/services/correction/fix.dart';
+import 'package:analysis_server/src/services/linter/lint_names.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+ defineReflectiveSuite(() {
+ defineReflectiveTests(ConvertForEachToForLoopTest);
+ });
+}
+
+@reflectiveTest
+class ConvertForEachToForLoopTest extends FixProcessorLintTest {
+ @override
+ FixKind get kind => DartFixKind.CONVERT_FOR_EACH_TO_FOR_LOOP;
+
+ @override
+ String get lintCode => LintNames.avoid_function_literals_in_foreach_calls;
+
+ Future<void> test_blockBody() async {
+ await resolveTestCode('''
+void f(List<String> list) {
+ list.forEach((e) {
+ e.length / 2;
+ });
+}
+''');
+ await assertHasFix('''
+void f(List<String> list) {
+ for (var e in list) {
+ e.length / 2;
+ }
+}
+''');
+ }
+
+ Future<void> test_expressionBody() async {
+ await resolveTestCode('''
+void f(List<String> list) {
+ list.forEach((e) => e.substring(3, 7));
+}
+''');
+ await assertHasFix('''
+void f(List<String> list) {
+ for (var e in list) {
+ e.substring(3, 7);
+ }
+}
+''');
+ }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index 570d7a8..e520510 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -43,6 +43,7 @@
as convert_documentation_into_line;
import 'convert_flutter_child_test.dart' as convert_flutter_child;
import 'convert_flutter_children_test.dart' as convert_flutter_children;
+import 'convert_for_each_to_for_loop_test.dart' as convert_for_each_to_for_loop;
import 'convert_into_expression_body_test.dart' as convert_into_expression_body;
import 'convert_to_contains_test.dart' as convert_to_contains;
import 'convert_to_for_element_test.dart' as convert_to_for_element;
@@ -211,6 +212,7 @@
convert_documentation_into_line.main();
convert_flutter_child.main();
convert_flutter_children.main();
+ convert_for_each_to_for_loop.main();
convert_into_expression_body.main();
convert_to_contains.main();
convert_to_for_element.main();
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 996f87c..037ae47 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -2642,14 +2642,37 @@
}
var nameExpr = _emitTopLevelName(p);
+ var jsName = _safeFunctionNameForSafari(p.name.text, fn);
body.add(js.statement('# = #',
- [nameExpr, js_ast.NamedFunction(_emitTemporaryId(p.name.text), fn)]));
+ [nameExpr, js_ast.NamedFunction(_emitTemporaryId(jsName), fn)]));
_currentUri = savedUri;
_staticTypeContext.leaveMember(p);
return js_ast.Statement.from(body);
}
+ /// Choose a safe name for [fn].
+ ///
+ /// Most of the time we use [candidateName], except if the name collides
+ /// with a parameter name and the function contains default parameter values.
+ ///
+ /// In ES6, functions containing default parameter values, which DDC
+ /// generates when Dart uses positional optional parameters, cannot have
+ /// two parameters with the same name. Because we have a similar restriction
+ /// in Dart, this is not normally an issue we need to pay attention to.
+ /// However, a bug in Safari makes it a syntax error to have the function
+ /// name overlap with the parameter names as well. This rename works around
+ /// such bug (dartbug.com/43520).
+ static String _safeFunctionNameForSafari(
+ String candidateName, js_ast.Fun fn) {
+ if (fn.params.any((p) => p is js_ast.DestructuredVariable)) {
+ while (fn.params.any((a) => a.parameterName == candidateName)) {
+ candidateName = '$candidateName\$';
+ }
+ }
+ return candidateName;
+ }
+
js_ast.Expression _emitFunctionTagged(js_ast.Expression fn, FunctionType type,
{bool topLevel = false}) {
var lazy = topLevel && !_canEmitTypeAtTopLevel(type);
diff --git a/runtime/observatory/tests/service/notify_debugger_on_exception_yielding_test.dart b/runtime/observatory/tests/service/notify_debugger_on_exception_yielding_test.dart
new file mode 100644
index 0000000..1cab6a7
--- /dev/null
+++ b/runtime/observatory/tests/service/notify_debugger_on_exception_yielding_test.dart
@@ -0,0 +1,64 @@
+// 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.
+//
+// VMOptions=--verbose_debug
+
+// See: https://github.com/dart-lang/sdk/issues/45673
+
+import 'test_helper.dart';
+import 'service_test_common.dart';
+
+const int LINE_A = 19;
+const int LINE_B = 29;
+const int LINE_C = 39;
+
+@pragma('vm:notify-debugger-on-exception')
+Future<void> throwFromAsync() async {
+ try {
+ throw 'Throw from throwFromAsync'; // LINE_A
+ } catch (e) {
+ // Ignore. Internals will notify debugger.
+ }
+ return Future.value();
+}
+
+@pragma('vm:notify-debugger-on-exception')
+Stream<int> throwFromAsyncStar() async* {
+ try {
+ throw 'Throw from throwFromAsyncStar'; // LINE_B
+ } catch (e) {
+ // Ignore. Internals will notify debugger.
+ }
+ yield 13;
+}
+
+@pragma('vm:notify-debugger-on-exception')
+Iterable<int> throwFromSyncStar() sync* {
+ try {
+ throw 'Throw from throwFromSyncStar'; // LINE_C
+ } catch (e) {
+ // Ignore. Internals will notify debugger.
+ }
+ yield 7;
+}
+
+testMain() async {
+ throwFromAsync();
+ await for (var e in throwFromAsyncStar()) {/*ignore*/}
+ for (var e in throwFromSyncStar()) {/*ignore*/}
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_C),
+];
+
+main([args = const <String>[]]) => runIsolateTests(args, tests,
+ testeeConcurrent: testMain, pause_on_unhandled_exceptions: true);
diff --git a/runtime/observatory_2/tests/service_2/notify_debugger_on_exception_yielding_test.dart b/runtime/observatory_2/tests/service_2/notify_debugger_on_exception_yielding_test.dart
new file mode 100644
index 0000000..1cab6a7
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/notify_debugger_on_exception_yielding_test.dart
@@ -0,0 +1,64 @@
+// 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.
+//
+// VMOptions=--verbose_debug
+
+// See: https://github.com/dart-lang/sdk/issues/45673
+
+import 'test_helper.dart';
+import 'service_test_common.dart';
+
+const int LINE_A = 19;
+const int LINE_B = 29;
+const int LINE_C = 39;
+
+@pragma('vm:notify-debugger-on-exception')
+Future<void> throwFromAsync() async {
+ try {
+ throw 'Throw from throwFromAsync'; // LINE_A
+ } catch (e) {
+ // Ignore. Internals will notify debugger.
+ }
+ return Future.value();
+}
+
+@pragma('vm:notify-debugger-on-exception')
+Stream<int> throwFromAsyncStar() async* {
+ try {
+ throw 'Throw from throwFromAsyncStar'; // LINE_B
+ } catch (e) {
+ // Ignore. Internals will notify debugger.
+ }
+ yield 13;
+}
+
+@pragma('vm:notify-debugger-on-exception')
+Iterable<int> throwFromSyncStar() sync* {
+ try {
+ throw 'Throw from throwFromSyncStar'; // LINE_C
+ } catch (e) {
+ // Ignore. Internals will notify debugger.
+ }
+ yield 7;
+}
+
+testMain() async {
+ throwFromAsync();
+ await for (var e in throwFromAsyncStar()) {/*ignore*/}
+ for (var e in throwFromSyncStar()) {/*ignore*/}
+}
+
+final tests = <IsolateTest>[
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_A),
+ resumeIsolate,
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_B),
+ resumeIsolate,
+ hasStoppedWithUnhandledException,
+ stoppedAtLine(LINE_C),
+];
+
+main([args = const <String>[]]) => runIsolateTests(args, tests,
+ testeeConcurrent: testMain, pause_on_unhandled_exceptions: true);
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc
index dc9d048..0f18a47 100644
--- a/runtime/vm/debugger.cc
+++ b/runtime/vm/debugger.cc
@@ -2248,11 +2248,27 @@
// it will be caught once we unwind the stack.
return true;
}
+
+ auto& handler_function = Function::Handle(handler_frame->function().ptr());
+ // If the handler function is an synthetic inner function, we need to look for
+ // the annotations on the outer function.
+ if (handler_function.IsAsyncClosure()) {
+ // async :async_op
+ handler_function = handler_function.parent_function();
+ } else if (handler_frame->function().IsAsyncGenClosure()) {
+ // async* :async_op
+ handler_function = handler_function.parent_function();
+ } else if (handler_frame->function().IsSyncGenClosure()) {
+ // sync* :sync_op + :sync_op_gen
+ handler_function = handler_function.parent_function();
+ handler_function = handler_function.parent_function();
+ }
+
// If handler_frame's function is annotated with
// @pragma('vm:notify-debugger-on-exception'), we specifically want to notify
// the debugger of this otherwise ignored exception.
if (Library::FindPragma(Thread::Current(), /*only_core=*/false,
- handler_frame->function(),
+ handler_function,
Symbols::vm_notify_debugger_on_exception())) {
return true;
}
diff --git a/tests/web/regress/43520_safari_test.dart b/tests/web/regress/43520_safari_test.dart
new file mode 100644
index 0000000..2dee2bb
--- /dev/null
+++ b/tests/web/regress/43520_safari_test.dart
@@ -0,0 +1,20 @@
+// 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 dartbug.com/43520.
+///
+/// Safari has a bug that makes it a syntax error for a function name to overlap
+/// with names of parameters in functions with default parameter values.
+///
+/// DDC now generates code to circumvent this issue.
+
+import 'package:expect/expect.dart';
+
+String a(Object a, [String f = '3']) {
+ return "$a$f";
+}
+
+main() async {
+ Expect.equals('13', a(1));
+}
diff --git a/tests/web_2/regress/43520_safari_test.dart b/tests/web_2/regress/43520_safari_test.dart
new file mode 100644
index 0000000..2dee2bb
--- /dev/null
+++ b/tests/web_2/regress/43520_safari_test.dart
@@ -0,0 +1,20 @@
+// 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 dartbug.com/43520.
+///
+/// Safari has a bug that makes it a syntax error for a function name to overlap
+/// with names of parameters in functions with default parameter values.
+///
+/// DDC now generates code to circumvent this issue.
+
+import 'package:expect/expect.dart';
+
+String a(Object a, [String f = '3']) {
+ return "$a$f";
+}
+
+main() async {
+ Expect.equals('13', a(1));
+}
diff --git a/tools/VERSION b/tools/VERSION
index 6ae49c3..ed6e679 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 14
PATCH 0
-PRERELEASE 89
+PRERELEASE 90
PRERELEASE_PATCH 0
\ No newline at end of file