Version 1.19.0-dev.7.2
Cherry-pick 49020b819ca13695c4adfbcd6ce1c09229a99b6c to dev
Cherry-pick c98c9c304a13caf28ea7b9de45a690503954e452 to dev
diff --git a/pkg/analyzer/lib/src/generated/resolver.dart b/pkg/analyzer/lib/src/generated/resolver.dart
index 98e7ca7..db027ac 100644
--- a/pkg/analyzer/lib/src/generated/resolver.dart
+++ b/pkg/analyzer/lib/src/generated/resolver.dart
@@ -4726,12 +4726,10 @@
if (_returnStack.isEmpty) {
return;
}
- DartType context = _returnStack.last;
- if (context is! FutureUnionType) {
- DartType inferred = _inferredReturn.last;
- inferred = _typeSystem.getLeastUpperBound(_typeProvider, type, inferred);
- _inferredReturn[_inferredReturn.length - 1] = inferred;
- }
+
+ DartType inferred = _inferredReturn.last;
+ inferred = _typeSystem.getLeastUpperBound(_typeProvider, type, inferred);
+ _inferredReturn[_inferredReturn.length - 1] = inferred;
}
/**
@@ -4756,7 +4754,17 @@
if (_returnStack.isNotEmpty && _inferredReturn.isNotEmpty) {
DartType context = _returnStack.removeLast() ?? DynamicTypeImpl.instance;
DartType inferred = _inferredReturn.removeLast();
- if (!inferred.isBottom && _typeSystem.isSubtypeOf(inferred, context)) {
+ if (inferred.isBottom) {
+ return;
+ }
+
+ if (context is FutureUnionType) {
+ // Try and match the Future type first.
+ if (_typeSystem.isSubtypeOf(inferred, context.futureOfType) ||
+ _typeSystem.isSubtypeOf(inferred, context.type)) {
+ setType(node, inferred);
+ }
+ } else if (_typeSystem.isSubtypeOf(inferred, context)) {
setType(node, inferred);
}
} else {
@@ -5758,18 +5766,28 @@
}
/**
- * Returns true if this method is `Future.then`.
+ * Returns true if this method is `Future.then` or an override thereof.
*
* If so we will apply special typing rules in strong mode, to handle the
* implicit union of `S | Future<S>`
*/
bool isFutureThen(Element element) {
- return element is MethodElement &&
- element.name == 'then' &&
- element.enclosingElement.type.isDartAsyncFuture;
+ // If we are a method named then
+ if (element is MethodElement && element.name == 'then') {
+ DartType type = element.enclosingElement.type;
+ // On Future or a subtype, then we're good.
+ return (type.isDartAsyncFuture || isSubtypeOfFuture(type));
+ }
+ return false;
}
/**
+ * Returns true if this type is any subtype of the built in Future type.
+ */
+ bool isSubtypeOfFuture(DartType type) =>
+ typeSystem.isSubtypeOf(type, typeProvider.futureDynamicType);
+
+ /**
* Given a downward inference type [fnType], and the declared
* [typeParameterList] for a function expression, determines if we can enable
* downward inference and if so, returns the function type to use for
@@ -6725,6 +6743,12 @@
@override
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
TypeName classTypeName = node.constructorName.type;
+ // TODO(leafp): Currently, we may re-infer types here, since we
+ // sometimes resolve multiple times. We should really check that we
+ // have not already inferred something. However, the obvious ways to
+ // check this don't work, since we may have been instantiated
+ // to bounds in an earlier phase, and we *do* want to do inference
+ // in that case.
if (classTypeName.typeArguments == null) {
// Given a union of context types ` T0 | T1 | ... | Tn`, find the first
// valid instantiation `new C<Ti>`, if it exists.
diff --git a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
index b3484a9..628c340 100644
--- a/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
+++ b/pkg/analyzer/lib/src/generated/static_type_analyzer.dart
@@ -1947,7 +1947,7 @@
DartType returnContext = InferenceContext.getContext(node);
DartType returnType;
if (returnContext is FutureUnionType) {
- returnType = fnType.returnType.isDartAsyncFuture
+ returnType = _resolver.isSubtypeOfFuture(fnType.returnType)
? returnContext.futureOfType
: returnContext.type;
} else {
@@ -1974,7 +1974,7 @@
// S or Future<S> in a conditional.
if (!argReturnType.isObject && !argReturnType.isDynamic) {
DartType paramReturnType = fnType.typeFormals[0].type;
- if (argReturnType.isDartAsyncFuture) {
+ if (_resolver.isSubtypeOfFuture(argReturnType)) {
// Given an argument of (T) -> Future<S>, instantiate with <S>
paramReturnType =
_typeProvider.futureType.instantiate([paramReturnType]);
@@ -1986,13 +1986,11 @@
..shareParameters(firstParamType.parameters)
..returnType = paramReturnType;
function.type = new FunctionTypeImpl(function);
-
// Use this as the expected 1st parameter type.
paramTypes[0] = function.type;
}
}
}
-
return ts.inferGenericFunctionCall(
_typeProvider, fnType, paramTypes, argTypes, returnType);
}
@@ -2016,6 +2014,13 @@
return;
}
+ // TODO(leafp): Currently, we may re-infer types here, since we
+ // sometimes resolve multiple times. We should really check that we
+ // have not already inferred something. However, the obvious ways to
+ // check this don't work, since we may have been instantiated
+ // to bounds in an earlier phase, and we *do* want to do inference
+ // in that case.
+
// Get back to the uninstantiated generic constructor.
// TODO(jmesserly): should we store this earlier in resolution?
// Or look it up, instead of jumping backwards through the Member?
@@ -2076,7 +2081,6 @@
}
computedType = _computeReturnTypeOfFunction(body, computedType);
-
functionElement.returnType = computedType;
_recordPropagatedTypeOfFunction(functionElement, node.body);
_recordStaticType(node, functionElement.type);
diff --git a/pkg/analyzer/lib/src/generated/type_system.dart b/pkg/analyzer/lib/src/generated/type_system.dart
index c32e5c5..9a4a5cf 100644
--- a/pkg/analyzer/lib/src/generated/type_system.dart
+++ b/pkg/analyzer/lib/src/generated/type_system.dart
@@ -1666,8 +1666,8 @@
throw new UnsupportedError('Future unions are not used in typedefs');
/**
- * Creates a union of `T | Future<T>`, unless `T` is already a future-union,
- * in which case it simply returns `T`
+ * Creates a union of `flatten(T) | Future<flatten(T)>`, unless `T` is
+ * already a future-union, in which case it simply returns `T`
*/
static DartType from(
DartType type, TypeProvider provider, TypeSystem system) {
diff --git a/pkg/analyzer/test/src/context/mock_sdk.dart b/pkg/analyzer/test/src/context/mock_sdk.dart
index d7b0a35..91cc635 100644
--- a/pkg/analyzer/test/src/context/mock_sdk.dart
+++ b/pkg/analyzer/test/src/context/mock_sdk.dart
@@ -48,6 +48,8 @@
static Future<List/*<T>*/> wait/*<T>*/(
Iterable<Future/*<T>*/> futures) => null;
Future/*<R>*/ then/*<R>*/(onValue(T value)) => null;
+
+ Future<T> whenComplete(action());
}
abstract class Completer<T> {
diff --git a/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart b/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart
index bca5068..bd2c1fb 100644
--- a/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart
+++ b/pkg/analyzer/test/src/summary/resynthesize_ast_test.dart
@@ -209,6 +209,36 @@
@override
@failingTest
+ void test_futureThen() {
+ super.test_futureThen();
+ }
+
+ @override
+ @failingTest
+ void test_futureThen_conditional() {
+ super.test_futureThen_conditional();
+ }
+
+ @override
+ @failingTest
+ void test_futureThen_upwards() {
+ super.test_futureThen_upwards();
+ }
+
+ @override
+ @failingTest
+ void test_futureUnion_asyncConditional() {
+ super.test_futureUnion_asyncConditional();
+ }
+
+ @override
+ @failingTest
+ void test_futureUnion_downwards() {
+ super.test_futureUnion_downwards();
+ }
+
+ @override
+ @failingTest
void test_genericMethods_inferJSBuiltin() {
super.test_genericMethods_inferJSBuiltin();
}
diff --git a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart
index b52167d..5405293 100644
--- a/pkg/analyzer/test/src/task/strong/inferred_type_test.dart
+++ b/pkg/analyzer/test/src/task/strong/inferred_type_test.dart
@@ -1520,73 +1520,202 @@
}
void test_futureThen() {
- checkFile('''
+ String build({String declared, String downwards, String upwards}) => '''
import 'dart:async';
-Future f;
-Future<int> t1 = f.then((_) async => await new Future<int>.value(3));
-Future<int> t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return await new Future<int>.value(3);});
-Future<int> t3 = f.then((_) async => 3);
-Future<int> t4 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return 3;});
-Future<int> t5 = f.then((_) => new Future<int>.value(3));
-Future<int> t6 = f.then((_) {return new Future<int>.value(3);});
-Future<int> t7 = f.then((_) async => new Future<int>.value(3));
-Future<int> t8 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {return new Future<int>.value(3);});
-''');
+class MyFuture<T> implements Future<T> {
+ MyFuture() {}
+ MyFuture.value(T x) {}
+ dynamic noSuchMethod(invocation);
+ MyFuture/*<S>*/ then/*<S>*/(dynamic f(T x), {Function onError}) => null;
+}
+
+void main() {
+ $declared f;
+ $downwards<int> t1 = f.then((_) async => await new $upwards<int>.value(3));
+ $downwards<int> t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {
+ return await new $upwards<int>.value(3);});
+ $downwards<int> t3 = f.then((_) async => 3);
+ $downwards<int> t4 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {
+ return 3;});
+ $downwards<int> t5 = f.then((_) => new $upwards<int>.value(3));
+ $downwards<int> t6 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) {return new $upwards<int>.value(3);});
+ $downwards<int> t7 = f.then((_) async => new $upwards<int>.value(3));
+ $downwards<int> t8 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(_) async {
+ return new $upwards<int>.value(3);});
+}
+''';
+
+ checkFile(
+ build(declared: "MyFuture", downwards: "Future", upwards: "Future"));
+ checkFile(
+ build(declared: "MyFuture", downwards: "Future", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "MyFuture", downwards: "MyFuture", upwards: "Future"));
+ checkFile(build(
+ declared: "MyFuture", downwards: "MyFuture", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "Future", downwards: "Future", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "Future", downwards: "Future", upwards: "Future"));
}
void test_futureThen_conditional() {
- checkFile('''
+ String build({String declared, String downwards, String upwards}) => '''
import 'dart:async';
-Future<bool> f;
-Future<int> t1 = f.then((x) async => x ? 2 : await new Future<int>.value(3));
-Future<int> t2 = f.then(/*info:INFERRED_TYPE_CLOSURE*/(x) async {return await x ? 2 : new Future<int>.value(3);});
-Future<int> t5 = f.then((x) => x ? 2 : new Future<int>.value(3));
-Future<int> t6 = f.then((x) {return x ? 2 : new Future<int>.value(3);});
-''');
+class MyFuture<T> implements Future<T> {
+ MyFuture() {}
+ MyFuture.value(T x) {}
+ dynamic noSuchMethod(invocation);
+ MyFuture/*<S>*/ then/*<S>*/(dynamic f(T x), {Function onError}) => null;
+}
+
+void main() {
+ $declared<bool> f;
+ $downwards<int> t1 = f.then(/*info:INFERRED_TYPE_CLOSURE*/
+ (x) async => x ? 2 : await new $upwards<int>.value(3));
+ $downwards<int> t2 = f.then(/*info:INFERRED_TYPE_CLOSURE,info:INFERRED_TYPE_CLOSURE*/(x) async { // TODO(leafp): Why the duplicate here?
+ return await x ? 2 : new $upwards<int>.value(3);});
+ $downwards<int> t5 = f.then(/*info:INFERRED_TYPE_CLOSURE*/
+ (x) => x ? 2 : new $upwards<int>.value(3));
+ $downwards<int> t6 = f.then(/*info:INFERRED_TYPE_CLOSURE*/
+ (x) {return x ? 2 : new $upwards<int>.value(3);});
+}
+''';
+ checkFile(
+ build(declared: "MyFuture", downwards: "Future", upwards: "Future"));
+ checkFile(
+ build(declared: "MyFuture", downwards: "Future", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "MyFuture", downwards: "MyFuture", upwards: "Future"));
+ checkFile(build(
+ declared: "MyFuture", downwards: "MyFuture", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "Future", downwards: "Future", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "Future", downwards: "Future", upwards: "Future"));
+ }
+
+ void test_futureThen_downwardsMethodTarget() {
+ // Not working yet, see: https://github.com/dart-lang/sdk/issues/27114
+ checkFile(r'''
+import 'dart:async';
+main() {
+ Future<int> f;
+ Future<List<int>> b = /*info:ASSIGNMENT_CAST should be pass*/f
+ .then(/*info:INFERRED_TYPE_CLOSURE*/(x) => [])
+ .whenComplete(/*pass should be info:INFERRED_TYPE_LITERAL*/() {});
+ b = f.then(/*info:INFERRED_TYPE_CLOSURE*/(x) => /*info:INFERRED_TYPE_LITERAL*/[]);
+}
+ ''');
}
void test_futureThen_upwards() {
// Regression test for https://github.com/dart-lang/sdk/issues/27088.
- checkFile(r'''
+ String build({String declared, String downwards, String upwards}) => '''
import 'dart:async';
-main() {
+class MyFuture<T> implements Future<T> {
+ MyFuture() {}
+ MyFuture.value(T x) {}
+ dynamic noSuchMethod(invocation);
+ MyFuture/*<S>*/ then/*<S>*/(dynamic f(T x), {Function onError}) => null;
+}
+
+void main() {
var f = foo().then((_) => 2.3);
- Future<int> f2 = /*error:INVALID_ASSIGNMENT*/f;
+ $downwards<int> f2 = /*error:INVALID_ASSIGNMENT*/f;
// The unnecessary cast is to illustrate that we inferred <double> for
// the generic type args, even though we had a return type context.
- Future<num> f3 = /*info:UNNECESSARY_CAST*/foo().then((_) => 2.3) as Future<double>;
+ $downwards<num> f3 = /*info:UNNECESSARY_CAST*/foo().then(
+ (_) => 2.3) as $upwards<double>;
}
-Future foo() async => 1;
+$declared foo() => new $declared<int>.value(1);
+ ''';
+ checkFile(
+ build(declared: "MyFuture", downwards: "Future", upwards: "Future"));
+ checkFile(build(
+ declared: "MyFuture", downwards: "MyFuture", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "Future", downwards: "Future", upwards: "Future"));
+ }
+
+ void test_futureThen_upwardsFromBlock() {
+ // Regression test for https://github.com/dart-lang/sdk/issues/27113.
+ checkFile(r'''
+import 'dart:async';
+main() {
+ Future<int> base;
+ var f = base.then(/*info:INFERRED_TYPE_CLOSURE,info:INFERRED_TYPE_CLOSURE*/(x) { return x == 0; });
+ var g = base.then(/*info:INFERRED_TYPE_CLOSURE*/(x) => x == 0);
+ Future<bool> b = f;
+ b = g;
+}
''');
}
void test_futureUnion_asyncConditional() {
- checkFile('''
+ String build({String declared, String downwards, String upwards}) => '''
import 'dart:async';
+class MyFuture<T> implements Future<T> {
+ MyFuture() {}
+ MyFuture.value(T x) {}
+ dynamic noSuchMethod(invocation);
+ MyFuture/*<S>*/ then/*<S>*/(dynamic f(T x), {Function onError}) => null;
+}
-Future<int> g1(bool x) async { return x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42); }
-Future<int> g2(bool x) async => x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42);
-
-Future<int> g3(bool x) async {
- var y = x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(42);
+$downwards<int> g1(bool x) async {
+ return x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value(42); }
+$downwards<int> g2(bool x) async =>
+ x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value(42);
+$downwards<int> g3(bool x) async {
+ var y = x ? 42 : /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value(42);
return y;
}
- ''');
+ ''';
+ checkFile(build(downwards: "Future", upwards: "Future"));
+ checkFile(build(downwards: "Future", upwards: "MyFuture"));
}
void test_futureUnion_downwards() {
- checkFile('''
+ String build({String declared, String downwards, String upwards}) {
+ // TODO(leafp): The use of matchTypes in visitInstanceCreationExpression
+ // in the resolver visitor isn't powerful enough to catch this for the
+ // subclass. See the TODO there.
+ var allocInfo =
+ (upwards == "Future") ? "/*info:INFERRED_TYPE_ALLOCATION*/" : "";
+ return '''
import 'dart:async';
-Future f;
+class MyFuture<T> implements Future<T> {
+ MyFuture() {}
+ MyFuture.value([T x]) {}
+ dynamic noSuchMethod(invocation);
+ MyFuture/*<S>*/ then/*<S>*/(dynamic f(T x), {Function onError}) => null;
+}
+
+$declared f;
// Instantiates Future<int>
-Future<int> t1 = f.then((_) => /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(/*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/'hi'));
+$downwards<int> t1 = f.then((_) =>
+ ${allocInfo}new $upwards.value(
+ /*error:ARGUMENT_TYPE_NOT_ASSIGNABLE*/'hi'));
// Instantiates List<int>
-Future<List<int>> t2 = f.then((_) => /*info:INFERRED_TYPE_LITERAL*/[3]);
-Future<List<int>> g2() async { return /*info:INFERRED_TYPE_LITERAL*/[3]; }
-Future<List<int>> g3() async { return /*info:INFERRED_TYPE_ALLOCATION*/new Future.value(/*info:INFERRED_TYPE_LITERAL*/[3]); }
-''');
+$downwards<List<int>> t2 = f.then((_) => /*info:INFERRED_TYPE_LITERAL*/[3]);
+$downwards<List<int>> g2() async { return /*info:INFERRED_TYPE_LITERAL*/[3]; }
+$downwards<List<int>> g3() async {
+ return /*info:INFERRED_TYPE_ALLOCATION*/new $upwards.value(
+ /*info:INFERRED_TYPE_LITERAL*/[3]); }
+''';
+ }
+
+ ;
+ checkFile(
+ build(declared: "MyFuture", downwards: "Future", upwards: "Future"));
+ checkFile(
+ build(declared: "MyFuture", downwards: "Future", upwards: "MyFuture"));
+ checkFile(
+ build(declared: "Future", downwards: "Future", upwards: "Future"));
+ checkFile(
+ build(declared: "Future", downwards: "Future", upwards: "MyFuture"));
}
void test_genericMethods_basicDownwardInference() {
@@ -2616,18 +2745,6 @@
expect(fns[9].type.toString(), '() → Stream<int>');
}
- void test_inferReturnOfStatementLambda() {
- // Regression test for https://github.com/dart-lang/sdk/issues/26139
- checkFile(r'''
-List<String> strings() {
- var stuff = [].expand(/*info:INFERRED_TYPE_CLOSURE*/(i) {
- return <String>[];
- });
- return stuff.toList();
-}
- ''');
- }
-
void test_inferred_nonstatic_field_depends_on_static_field_complex() {
var mainUnit = checkFile('''
class C {
@@ -3010,6 +3127,18 @@
expect(f.type.toString(), '(bool) → int');
}
+ void test_inferReturnOfStatementLambda() {
+ // Regression test for https://github.com/dart-lang/sdk/issues/26139
+ checkFile(r'''
+List<String> strings() {
+ var stuff = [].expand(/*info:INFERRED_TYPE_CLOSURE*/(i) {
+ return <String>[];
+ });
+ return stuff.toList();
+}
+ ''');
+ }
+
void test_inferStaticsTransitively() {
addFile(
'''
diff --git a/tools/VERSION b/tools/VERSION
index c7cc232..2fecdd8 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
MINOR 19
PATCH 0
PRERELEASE 7
-PRERELEASE_PATCH 1
+PRERELEASE_PATCH 2