Version 1.22.0-dev.10.1
Cherry-pick 39977ac6ec58516f5db5ac4df9fa2426fc19dac5 to dev
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index a86bcde..a2aeae1 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -278,16 +278,19 @@
*/
@override
Object visitAwaitExpression(AwaitExpression node) {
- DartType staticExpressionType = _getStaticType(node.expression);
- if (staticExpressionType == null) {
- // TODO(brianwilkerson) Determine whether this can still happen.
- staticExpressionType = _dynamicType;
+ // Await the Future. This results in whatever type is (ultimately) returned.
+ DartType awaitType(DartType awaitedType) {
+ if (awaitedType == null) {
+ return null;
+ }
+ if (awaitedType.isDartAsyncFutureOr) {
+ return awaitType((awaitedType as InterfaceType).typeArguments[0]);
+ }
+ return awaitedType.flattenFutures(_typeSystem);
}
- DartType staticType = staticExpressionType.flattenFutures(_typeSystem);
- _recordStaticType(node, staticType);
- DartType propagatedExpressionType = node.expression.propagatedType;
- DartType propagatedType =
- propagatedExpressionType?.flattenFutures(_typeSystem);
+
+ _recordStaticType(node, awaitType(_getStaticType(node.expression)));
+ DartType propagatedType = awaitType(node.expression.propagatedType);
_resolver.recordPropagatedTypeIfBetter(node, propagatedType);
return null;
}
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index 2b6012a..b951f4f 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -696,13 +696,6 @@
};
}
- /// If [t1] or [t2] is a type parameter we are inferring, update its bound.
- /// Returns `true` if we could possibly find a compatible type,
- /// otherwise `false`.
- bool _inferTypeParameterSubtypeOf(DartType t1, DartType t2) {
- return false;
- }
-
/**
* This currently does not implement a very complete least upper bound
* algorithm, but handles a couple of the very common cases that are
@@ -840,28 +833,7 @@
// Trivially false.
if (_isTop(t1, dynamicIsBottom: dynamicIsBottom) ||
_isBottom(t2, dynamicIsBottom: dynamicIsBottom)) {
- return _inferTypeParameterSubtypeOf(t1, t2);
- }
-
- // S <: T where S is a type variable
- // T is not dynamic or object (handled above)
- // True if T == S
- // Or true if bound of S is S' and S' <: T
- if (t1 is TypeParameterType) {
- if (t2 is TypeParameterType &&
- t1.definition == t2.definition &&
- guardedSubtype(t1.bound, t2.bound, visited)) {
- return true;
- }
- if (_inferTypeParameterSubtypeOf(t1, t2)) {
- return true;
- }
- DartType bound = t1.element.bound;
- return bound == null ? false : guardedSubtype(bound, t2, visited);
- }
-
- if (t2 is TypeParameterType) {
- return _inferTypeParameterSubtypeOf(t1, t2);
+ return false;
}
// Handle FutureOr<T> union type.
@@ -887,6 +859,23 @@
return isSubtypeOf(t1, t2Future) || isSubtypeOf(t1, t2TypeArg);
}
+ // S <: T where S is a type variable
+ // T is not dynamic or object (handled above)
+ // True if T == S
+ // Or true if bound of S is S' and S' <: T
+ if (t1 is TypeParameterType) {
+ if (t2 is TypeParameterType &&
+ t1.definition == t2.definition &&
+ guardedSubtype(t1.bound, t2.bound, visited)) {
+ return true;
+ }
+ DartType bound = t1.element.bound;
+ return bound == null ? false : guardedSubtype(bound, t2, visited);
+ }
+ if (t2 is TypeParameterType) {
+ return false;
+ }
+
// Void only appears as the return type of a function, and we handle it
// directly in the function subtype rules. We should not get to a point
// where we're doing a subtype test on a "bare" void, but just in case we
@@ -1482,6 +1471,7 @@
for (int i = 0; i < fnTypeParams.length; i++) {
TypeParameterType typeParam = fnTypeParams[i];
+ _TypeParameterBound bound = _bounds[typeParam];
// Apply the `extends` clause for the type parameter, if any.
//
@@ -1503,7 +1493,9 @@
DartType declaredUpperBound = typeParam.element.bound;
if (declaredUpperBound != null) {
// Assert that the type parameter is a subtype of its bound.
- _inferTypeParameterSubtypeOf(typeParam,
+ // TODO(jmesserly): the order of calling GLB here matters, because of
+ // https://github.com/dart-lang/sdk/issues/28513
+ bound.upper = _typeSystem.getGreatestLowerBound(bound.upper,
declaredUpperBound.substitute2(inferredTypes, fnTypeParams));
}
@@ -1519,7 +1511,6 @@
_TypeParameterVariance variance =
new _TypeParameterVariance.from(typeParam, declaredReturnType);
- _TypeParameterBound bound = _bounds[typeParam];
DartType lowerBound = bound.lower;
DartType upperBound = bound.upper;
@@ -1562,8 +1553,19 @@
}
@override
- bool _inferTypeParameterSubtypeOf(DartType t1, DartType t2) {
+ bool _isSubtypeOf(DartType t1, DartType t2, Set<Element> visited,
+ {bool dynamicIsBottom: false}) {
+ // TODO(jmesserly): the trivial constraints are not treated as part of
+ // the constraint set here. This seems incorrect once we are able to pin the
+ // inferred type of a type parameter based on the downwards information.
+ if (identical(t1, t2) ||
+ _isTop(t2, dynamicIsBottom: dynamicIsBottom) ||
+ _isBottom(t1, dynamicIsBottom: dynamicIsBottom)) {
+ return true;
+ }
+
if (t1 is TypeParameterType) {
+ // TODO(jmesserly): we ignore `dynamicIsBottom` here, is that correct?
_TypeParameterBound bound = _bounds[t1];
if (bound != null) {
// Ensure T1 <: T2, where T1 is a type parameter we are inferring.
@@ -1591,7 +1593,8 @@
return true;
}
}
- return false;
+ return super
+ ._isSubtypeOf(t1, t2, visited, dynamicIsBottom: dynamicIsBottom);
}
}
diff --git a/pkg/analyzer/test/generated/strong_mode_test.dart b/pkg/analyzer/test/generated/strong_mode_test.dart
index d0042cd..a90fe14 100644
--- a/pkg/analyzer/test/generated/strong_mode_test.dart
+++ b/pkg/analyzer/test/generated/strong_mode_test.dart
@@ -14,6 +14,7 @@
import 'package:analyzer/src/error/codes.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/source_io.dart';
+import 'package:front_end/src/base/errors.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -38,6 +39,7 @@
Asserter<DartType> _isDynamic;
Asserter<InterfaceType> _isFutureOfDynamic;
Asserter<InterfaceType> _isFutureOfInt;
+ Asserter<InterfaceType> _isFutureOrOfInt;
Asserter<DartType> _isInt;
Asserter<DartType> _isNum;
Asserter<DartType> _isObject;
@@ -46,6 +48,7 @@
AsserterBuilder2<Asserter<DartType>, Asserter<DartType>, DartType>
_isFunction2Of;
AsserterBuilder<List<Asserter<DartType>>, InterfaceType> _isFutureOf;
+ AsserterBuilder<List<Asserter<DartType>>, InterfaceType> _isFutureOrOf;
AsserterBuilderBuilder<Asserter<DartType>, List<Asserter<DartType>>, DartType>
_isInstantiationOf;
AsserterBuilder<Asserter<DartType>, InterfaceType> _isListOf;
@@ -75,8 +78,11 @@
_isFunction2Of = _assertions.isFunction2Of;
_hasElementOf = _assertions.hasElementOf;
_isFutureOf = _isInstantiationOf(_hasElementOf(typeProvider.futureType));
+ _isFutureOrOf =
+ _isInstantiationOf(_hasElementOf(typeProvider.futureOrType));
_isFutureOfDynamic = _isFutureOf([_isDynamic]);
_isFutureOfInt = _isFutureOf([_isInt]);
+ _isFutureOrOfInt = _isFutureOrOf([_isInt]);
_isStreamOf = _isInstantiationOf(_hasElementOf(typeProvider.streamType));
}
return result;
@@ -135,19 +141,10 @@
// when instantiating type variables.
// TODO(leafp): I think this should pass once the inference changes
// that jmesserly is adding are landed.
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
T mk<T extends int>(T x) => null;
FutureOr<int> test() => mk(new Future.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
_isFutureOfInt(invoke.argumentList.arguments[0].staticType);
}
@@ -157,19 +154,10 @@
// when instantiating type variables.
// TODO(leafp): I think this should pass once the inference changes
// that jmesserly is adding are landed.
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
T mk<T extends Future<Object>>(T x) => null;
FutureOr<int> test() => mk(new Future.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
_isFutureOfInt(invoke.argumentList.arguments[0].staticType);
}
@@ -970,57 +958,30 @@
test_futureOr_downwards1() async {
// Test that downwards inference interacts correctly with FutureOr
// parameters.
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
Future<T> mk<T>(FutureOr<T> x) => null;
Future<int> test() => mk(new Future<int>.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
}
test_futureOr_downwards2() async {
// Test that downwards inference interacts correctly with FutureOr
// parameters when the downwards context is FutureOr
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
Future<T> mk<T>(FutureOr<T> x) => null;
FutureOr<int> test() => mk(new Future<int>.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
}
test_futureOr_downwards3() async {
// Test that downwards inference correctly propogates into
// arguments.
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
Future<T> mk<T>(FutureOr<T> x) => null;
Future<int> test() => mk(new Future.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
_isFutureOfInt(invoke.argumentList.arguments[0].staticType);
}
@@ -1028,19 +989,10 @@
test_futureOr_downwards4() async {
// Test that downwards inference interacts correctly with FutureOr
// parameters when the downwards context is FutureOr
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
Future<T> mk<T>(FutureOr<T> x) => null;
FutureOr<int> test() => mk(new Future.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
_isFutureOfInt(invoke.argumentList.arguments[0].staticType);
}
@@ -1048,19 +1000,10 @@
test_futureOr_downwards5() async {
// Test that downwards inference correctly pins the type when it
// comes from a FutureOr
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
Future<T> mk<T>(FutureOr<T> x) => null;
FutureOr<num> test() => mk(new Future.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOf([_isNum])(invoke.staticType);
_isFutureOf([_isNum])(invoke.argumentList.arguments[0].staticType);
}
@@ -1068,19 +1011,10 @@
test_futureOr_downwards6() async {
// Test that downwards inference doesn't decompose FutureOr
// when instantiating type variables.
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
T mk<T>(T x) => null;
FutureOr<int> test() => mk(new Future.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
_isFutureOfInt(invoke.argumentList.arguments[0].staticType);
}
@@ -1088,129 +1022,119 @@
test_futureOr_downwards9() async {
// Test that downwards inference decomposes correctly with
// other composite types
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
List<T> mk<T>(T x) => null;
FutureOr<List<int>> test() => mk(3);
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isListOf(_isInt)(invoke.staticType);
_isInt(invoke.argumentList.arguments[0].staticType);
}
test_futureOr_methods1() async {
// Test that FutureOr has the Object methods
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
dynamic test(FutureOr<int> x) => x.toString();
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isString(invoke.staticType);
}
test_futureOr_methods2() async {
// Test that FutureOr does not have the constituent type methods
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(
+ r'''
dynamic test(FutureOr<int> x) => x.abs();
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertErrors(source, [StaticTypeWarningCode.UNDEFINED_METHOD]);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''',
+ errors: [StaticTypeWarningCode.UNDEFINED_METHOD]);
_isDynamic(invoke.staticType);
}
test_futureOr_methods3() async {
// Test that FutureOr does not have the Future type methods
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(
+ r'''
dynamic test(FutureOr<int> x) => x.then((x) => x);
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertErrors(source, [StaticTypeWarningCode.UNDEFINED_METHOD]);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''',
+ errors: [StaticTypeWarningCode.UNDEFINED_METHOD]);
_isDynamic(invoke.staticType);
}
test_futureOr_methods4() async {
// Test that FutureOr<dynamic> does not have all methods
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(
+ r'''
dynamic test(FutureOr<dynamic> x) => x.abs();
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertErrors(source, [StaticTypeWarningCode.UNDEFINED_METHOD]);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''',
+ errors: [StaticTypeWarningCode.UNDEFINED_METHOD]);
_isDynamic(invoke.staticType);
}
test_futureOr_upwards1() async {
// Test that upwards inference correctly prefers to instantiate type
// variables with the "smaller" solution when both are possible.
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(r'''
Future<T> mk<T>(FutureOr<T> x) => null;
dynamic test() => mk(new Future<int>.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertNoErrors(source);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''');
_isFutureOfInt(invoke.staticType);
}
test_futureOr_upwards2() async {
// Test that upwards inference fails when the solution doesn't
// match the bound.
- String code = r'''
- import "dart:async";
+ MethodInvocation invoke = await _testFutureOr(
+ r'''
Future<T> mk<T extends Future<Object>>(FutureOr<T> x) => null;
dynamic test() => mk(new Future<int>.value(42));
- ''';
- Source source = addSource(code);
- TestAnalysisResult analysisResult = await computeAnalysisResult(source);
- assertErrors(source, [StrongModeCode.COULD_NOT_INFER]);
- verify([source]);
- CompilationUnit unit = analysisResult.unit;
- FunctionDeclaration test = AstFinder.getTopLevelFunction(unit, "test");
- ExpressionFunctionBody body = test.functionExpression.body;
- MethodInvocation invoke = body.expression;
+ ''',
+ errors: [StrongModeCode.COULD_NOT_INFER]);
_isFutureOf([_isObject])(invoke.staticType);
}
+ test_futureOr_assignFromValue() async {
+ // Test a T can be assigned to FutureOr<T>.
+ MethodInvocation invoke = await _testFutureOr(r'''
+ FutureOr<T> mk<T>(T x) => x;
+ test() => mk(42);
+ ''');
+ _isFutureOrOfInt(invoke.staticType);
+ }
+
+ test_futureOr_assignFromFuture() async {
+ // Test a Future<T> can be assigned to FutureOr<T>.
+ MethodInvocation invoke = await _testFutureOr(r'''
+ FutureOr<T> mk<T>(Future<T> x) => x;
+ test() => mk(new Future<int>.value(42));
+ ''');
+ _isFutureOrOfInt(invoke.staticType);
+ }
+
+ test_futureOr_await() async {
+ // Test a FutureOr<T> can be awaited.
+ MethodInvocation invoke = await _testFutureOr(r'''
+ Future<T> mk<T>(FutureOr<T> x) async => await x;
+ test() => mk(42);
+ ''');
+ _isFutureOfInt(invoke.staticType);
+ }
+
+ test_futureOr_asyncExpressionBody() async {
+ // A FutureOr<T> can be used as the expression body for an async function
+ MethodInvocation invoke = await _testFutureOr(r'''
+ Future<T> mk<T>(FutureOr<T> x) async => x;
+ test() => mk(42);
+ ''');
+ _isFutureOfInt(invoke.staticType);
+ }
+
+ test_futureOr_asyncReturn() async {
+ // A FutureOr<T> can be used as the return value for an async function
+ MethodInvocation invoke = await _testFutureOr(r'''
+ Future<T> mk<T>(FutureOr<T> x) async { return x; }
+ test() => mk(42);
+ ''');
+ _isFutureOfInt(invoke.staticType);
+ }
+
test_inference_hints() async {
Source source = addSource(r'''
void main () {
@@ -2073,6 +1997,30 @@
check("f2", _isListOf(_isInt));
check("f3", _isListOf((DartType type) => _isListOf(_isInt)(type)));
}
+
+ /// Helper method for testing `FutureOr<T>`.
+ ///
+ /// Validates that [code] produces [errors]. It should define a function
+ /// "test", whose body is an expression that invokes a method. Returns that
+ /// invocation.
+ Future<MethodInvocation> _testFutureOr(String code,
+ {List<ErrorCode> errors}) async {
+ Source source = addSource("""
+ import "dart:async";
+ $code""");
+ TestAnalysisResult analysisResult = await computeAnalysisResult(source);
+
+ if (errors == null) {
+ assertNoErrors(source);
+ } else {
+ assertErrors(source, errors);
+ }
+ verify([source]);
+ FunctionDeclaration test =
+ AstFinder.getTopLevelFunction(analysisResult.unit, "test");
+ ExpressionFunctionBody body = test.functionExpression.body;
+ return body.expression;
+ }
}
/**
diff --git a/tools/VERSION b/tools/VERSION
index 977d95b..4e9ac1f 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
MINOR 22
PATCH 0
PRERELEASE 10
-PRERELEASE_PATCH 0
+PRERELEASE_PATCH 1