fix #28526, await FutureOr<T>
fix #28527, FutureOr<T> subtyping with type parameters

For subtyping: we've had a long standing issue in how inference reuses the <: operator code. Inference needs to make sure constraints are recorded on the type parameters we're inferring, but it also needs to correctly implement the future or union. This change addresses that & is a step towards the bigger inference change upcoming that tracks top/bottom constraints explicitly.

R=leafp@google.com

Review-Url: https://codereview.chromium.org/2655273008 .
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;
+  }
 }
 
 /**