// Copyright (c) 2017, 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:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/ast/visitor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../../../generated/resolver_test_case.dart';
import '../../../generated/test_support.dart';

void main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(Dart2InferenceTest);
    defineReflectiveTests(Dart2InferenceTest_Task);
  });
}

/// Tests for Dart2 inference rules back-ported from FrontEnd.
///
/// https://github.com/dart-lang/sdk/issues/31638
@reflectiveTest
class Dart2InferenceTest extends ResolverTestCase {
  @override
  AnalysisOptions get defaultAnalysisOptions => new AnalysisOptionsImpl();

  @override
  bool get enableNewAnalysisDriver => true;

  test_bool_assert() async {
    var code = r'''
T f<T>() => null;

main() {
  assert(f()); // 1
  assert(f(), f()); // 2
}

class C {
  C() : assert(f()), // 3
        assert(f(), f()); // 4
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    String getType(String prefix) {
      var invocation = _findMethodInvocation(unit, code, prefix);
      return invocation.staticInvokeType.toString();
    }

    expect(getType('f()); // 1'), '() → bool');

    expect(getType('f(), '), '() → bool');
    expect(getType('f()); // 2'), '() → dynamic');

    expect(getType('f()), // 3'), '() → bool');

    expect(getType('f(), '), '() → bool');
    expect(getType('f()); // 4'), '() → dynamic');
  }

  test_bool_logical() async {
    var code = r'''
T f<T>() => null;

var v1 = f() || f(); // 1
var v2 = f() && f(); // 2

main() {
  var v1 = f() || f(); // 3
  var v2 = f() && f(); // 4
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    void assertType(String prefix) {
      var invocation = _findMethodInvocation(unit, code, prefix);
      expect(invocation.staticInvokeType.toString(), '() → bool');
    }

    assertType('f() || f(); // 1');
    assertType('f(); // 1');
    assertType('f() && f(); // 2');
    assertType('f(); // 2');

    assertType('f() || f(); // 3');
    assertType('f(); // 3');
    assertType('f() && f(); // 4');
    assertType('f(); // 4');
  }

  test_bool_statement() async {
    var code = r'''
T f<T>() => null;

main() {
  while (f()) {} // 1
  do {} while (f()); // 2
  if (f()) {} // 3
  for (; f(); ) {} // 4
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    void assertType(String prefix) {
      var invocation = _findMethodInvocation(unit, code, prefix);
      expect(invocation.staticInvokeType.toString(), '() → bool');
    }

    assertType('f()) {} // 1');
    assertType('f());');
    assertType('f()) {} // 3');
    assertType('f(); ) {} // 4');
  }

  test_closure_downwardReturnType_arrow() async {
    var code = r'''
void main() {
  List<int> Function() g;
  g = () => 42;
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    Expression closure = _findExpression(unit, code, '() => 42');
    expect(closure.staticType.toString(), '() → List<int>');
  }

  test_closure_downwardReturnType_block() async {
    var code = r'''
void main() {
  List<int> Function() g;
  g = () { // mark
    return 42;
  };
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    Expression closure = _findExpression(unit, code, '() { // mark');
    expect(closure.staticType.toString(), '() → List<int>');
  }

  test_compoundAssignment_index() async {
    var code = r'''
int getInt() => 0;
num getNum() => 0;
double getDouble() => 0.0;

abstract class Test<T, U> {
  T operator [](String s);
  void operator []=(String s, U v);
}

void test1(Test<int, int> t) {
  var /*@type=int*/ v1 = t['x'] = getInt();
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=int*/ v4 = t['x'] ??= getInt();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=int*/ v7 = t['x'] += getInt();
  var /*@type=num*/ v8 = t['x'] += getNum();
  var /*@type=int*/ v10 = ++t['x'];
  var /*@type=int*/ v11 = t['x']++;
}

void test2(Test<int, num> t) {
  var /*@type=int*/ v1 = t['x'] = getInt();
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=double*/ v3 = t['x'] = getDouble();
  var /*@type=int*/ v4 = t['x'] ??= getInt();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=num*/ v6 = t['x'] ??= getDouble();
  var /*@type=int*/ v7 = t['x'] += getInt();
  var /*@type=num*/ v8 = t['x'] += getNum();
  var /*@type=double*/ v9 = t['x'] += getDouble();
  var /*@type=int*/ v10 = ++t['x'];
  var /*@type=int*/ v11 = t['x']++;
}

void test3(Test<int, double> t) {
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=double*/ v3 = t['x'] = getDouble();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=num*/ v6 = t['x'] ??= getDouble();
  var /*@type=int*/ v7 = t['x'] += getInt();
  var /*@type=num*/ v8 = t['x'] += getNum();
  var /*@type=double*/ v9 = t['x'] += getDouble();
  var /*@type=int*/ v10 = ++t['x'];
  var /*@type=int*/ v11 = t['x']++;
}

void test4(Test<num, int> t) {
  var /*@type=int*/ v1 = t['x'] = getInt();
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=num*/ v4 = t['x'] ??= getInt();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=num*/ v7 = t['x'] += getInt();
  var /*@type=num*/ v8 = t['x'] += getNum();
  var /*@type=num*/ v10 = ++t['x'];
  var /*@type=num*/ v11 = t['x']++;
}

void test5(Test<num, num> t) {
  var /*@type=int*/ v1 = t['x'] = getInt();
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=double*/ v3 = t['x'] = getDouble();
  var /*@type=num*/ v4 = t['x'] ??= getInt();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=num*/ v6 = t['x'] ??= getDouble();
  var /*@type=num*/ v7 = t['x'] += getInt();
  var /*@type=num*/ v8 = t['x'] += getNum();
  var /*@type=num*/ v9 = t['x'] += getDouble();
  var /*@type=num*/ v10 = ++t['x'];
  var /*@type=num*/ v11 = t['x']++;
}

void test6(Test<num, double> t) {
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=double*/ v3 = t['x'] = getDouble();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=num*/ v6 = t['x'] ??= getDouble();
  var /*@type=num*/ v7 = t['x'] += getInt();
  var /*@type=num*/ v8 = t['x'] += getNum();
  var /*@type=num*/ v9 = t['x'] += getDouble();
  var /*@type=num*/ v10 = ++t['x'];
  var /*@type=num*/ v11 = t['x']++;
}

void test7(Test<double, int> t) {
  var /*@type=int*/ v1 = t['x'] = getInt();
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=num*/ v4 = t['x'] ??= getInt();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=double*/ v7 = t['x'] += getInt();
  var /*@type=double*/ v8 = t['x'] += getNum();
  var /*@type=double*/ v10 = ++t['x'];
  var /*@type=double*/ v11 = t['x']++;
}

void test8(Test<double, num> t) {
  var /*@type=int*/ v1 = t['x'] = getInt();
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=double*/ v3 = t['x'] = getDouble();
  var /*@type=num*/ v4 = t['x'] ??= getInt();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=double*/ v6 = t['x'] ??= getDouble();
  var /*@type=double*/ v7 = t['x'] += getInt();
  var /*@type=double*/ v8 = t['x'] += getNum();
  var /*@type=double*/ v9 = t['x'] += getDouble();
  var /*@type=double*/ v10 = ++t['x'];
  var /*@type=double*/ v11 = t['x']++;
}

void test9(Test<double, double> t) {
  var /*@type=num*/ v2 = t['x'] = getNum();
  var /*@type=double*/ v3 = t['x'] = getDouble();
  var /*@type=num*/ v5 = t['x'] ??= getNum();
  var /*@type=double*/ v6 = t['x'] ??= getDouble();
  var /*@type=double*/ v7 = t['x'] += getInt();
  var /*@type=double*/ v8 = t['x'] += getNum();
  var /*@type=double*/ v9 = t['x'] += getDouble();
  var /*@type=double*/ v10 = ++t['x'];
  var /*@type=double*/ v11 = t['x']++;
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;
    _assertTypeAnnotations(code, unit);
  }

  test_compoundAssignment_prefixedIdentifier() async {
    var code = r'''
int getInt() => 0;
num getNum() => 0;
double getDouble() => 0.0;

class Test<T extends U, U> {
  T get x => null;
  void set x(U _) {}
}

void test1(Test<int, int> t) {
  var /*@type=int*/ v1 = t.x = getInt();
  var /*@type=num*/ v2 = t.x = getNum();
  var /*@type=int*/ v4 = t.x ??= getInt();
  var /*@type=num*/ v5 = t.x ??= getNum();
  var /*@type=int*/ v7 = t.x += getInt();
  var /*@type=num*/ v8 = t.x += getNum();
  var /*@type=int*/ v10 = ++t.x;
  var /*@type=int*/ v11 = t.x++;
}

void test2(Test<int, num> t) {
  var /*@type=int*/ v1 = t.x = getInt();
  var /*@type=num*/ v2 = t.x = getNum();
  var /*@type=double*/ v3 = t.x = getDouble();
  var /*@type=int*/ v4 = t.x ??= getInt();
  var /*@type=num*/ v5 = t.x ??= getNum();
  var /*@type=num*/ v6 = t.x ??= getDouble();
  var /*@type=int*/ v7 = t.x += getInt();
  var /*@type=num*/ v8 = t.x += getNum();
  var /*@type=double*/ v9 = t.x += getDouble();
  var /*@type=int*/ v10 = ++t.x;
  var /*@type=int*/ v11 = t.x++;
}

void test5(Test<num, num> t) {
  var /*@type=int*/ v1 = t.x = getInt();
  var /*@type=num*/ v2 = t.x = getNum();
  var /*@type=double*/ v3 = t.x = getDouble();
  var /*@type=num*/ v4 = t.x ??= getInt();
  var /*@type=num*/ v5 = t.x ??= getNum();
  var /*@type=num*/ v6 = t.x ??= getDouble();
  var /*@type=num*/ v7 = t.x += getInt();
  var /*@type=num*/ v8 = t.x += getNum();
  var /*@type=num*/ v9 = t.x += getDouble();
  var /*@type=num*/ v10 = ++t.x;
  var /*@type=num*/ v11 = t.x++;
}

void test8(Test<double, num> t) {
  var /*@type=int*/ v1 = t.x = getInt();
  var /*@type=num*/ v2 = t.x = getNum();
  var /*@type=double*/ v3 = t.x = getDouble();
  var /*@type=num*/ v4 = t.x ??= getInt();
  var /*@type=num*/ v5 = t.x ??= getNum();
  var /*@type=double*/ v6 = t.x ??= getDouble();
  var /*@type=double*/ v7 = t.x += getInt();
  var /*@type=double*/ v8 = t.x += getNum();
  var /*@type=double*/ v9 = t.x += getDouble();
  var /*@type=double*/ v10 = ++t.x;
  var /*@type=double*/ v11 = t.x++;
}

void test9(Test<double, double> t) {
  var /*@type=num*/ v2 = t.x = getNum();
  var /*@type=double*/ v3 = t.x = getDouble();
  var /*@type=num*/ v5 = t.x ??= getNum();
  var /*@type=double*/ v6 = t.x ??= getDouble();
  var /*@type=double*/ v7 = t.x += getInt();
  var /*@type=double*/ v8 = t.x += getNum();
  var /*@type=double*/ v9 = t.x += getDouble();
  var /*@type=double*/ v10 = ++t.x;
  var /*@type=double*/ v11 = t.x++;
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    assertNoErrors(source);

    var unit = analysisResult.unit;
    _assertTypeAnnotations(code, unit);
  }

  test_compoundAssignment_propertyAccess() async {
    var t1 = 'new Test<int, int>()';
    var t2 = 'new Test<int, num>()';
    var t5 = 'new Test<num, num>()';
    var t8 = 'new Test<double, num>()';
    var t9 = 'new Test<double, double>()';
    var code = '''
int getInt() => 0;
num getNum() => 0;
double getDouble() => 0.0;

class Test<T extends U, U> {
  T get x => null;
  void set x(U _) {}
}

void test1() {
  var /*@type=int*/ v1 = $t1.x = getInt();
  var /*@type=num*/ v2 = $t1.x = getNum();
  var /*@type=int*/ v4 = $t1.x ??= getInt();
  var /*@type=num*/ v5 = $t1.x ??= getNum();
  var /*@type=int*/ v7 = $t1.x += getInt();
  var /*@type=num*/ v8 = $t1.x += getNum();
  var /*@type=int*/ v10 = ++$t1.x;
  var /*@type=int*/ v11 = $t1.x++;
}

void test2() {
  var /*@type=int*/ v1 = $t2.x = getInt();
  var /*@type=num*/ v2 = $t2.x = getNum();
  var /*@type=double*/ v3 = $t2.x = getDouble();
  var /*@type=int*/ v4 = $t2.x ??= getInt();
  var /*@type=num*/ v5 = $t2.x ??= getNum();
  var /*@type=num*/ v6 = $t2.x ??= getDouble();
  var /*@type=int*/ v7 = $t2.x += getInt();
  var /*@type=num*/ v8 = $t2.x += getNum();
  var /*@type=double*/ v9 = $t2.x += getDouble();
  var /*@type=int*/ v10 = ++$t2.x;
  var /*@type=int*/ v11 = $t2.x++;
}

void test5() {
  var /*@type=int*/ v1 = $t5.x = getInt();
  var /*@type=num*/ v2 = $t5.x = getNum();
  var /*@type=double*/ v3 = $t5.x = getDouble();
  var /*@type=num*/ v4 = $t5.x ??= getInt();
  var /*@type=num*/ v5 = $t5.x ??= getNum();
  var /*@type=num*/ v6 = $t5.x ??= getDouble();
  var /*@type=num*/ v7 = $t5.x += getInt();
  var /*@type=num*/ v8 = $t5.x += getNum();
  var /*@type=num*/ v9 = $t5.x += getDouble();
  var /*@type=num*/ v10 = ++$t5.x;
  var /*@type=num*/ v11 = $t5.x++;
}

void test8() {
  var /*@type=int*/ v1 = $t8.x = getInt();
  var /*@type=num*/ v2 = $t8.x = getNum();
  var /*@type=double*/ v3 = $t8.x = getDouble();
  var /*@type=num*/ v4 = $t8.x ??= getInt();
  var /*@type=num*/ v5 = $t8.x ??= getNum();
  var /*@type=double*/ v6 = $t8.x ??= getDouble();
  var /*@type=double*/ v7 = $t8.x += getInt();
  var /*@type=double*/ v8 = $t8.x += getNum();
  var /*@type=double*/ v9 = $t8.x += getDouble();
  var /*@type=double*/ v10 = ++$t8.x;
  var /*@type=double*/ v11 = $t8.x++;
}

void test9() {
  var /*@type=num*/ v2 = $t9.x = getNum();
  var /*@type=double*/ v3 = $t9.x = getDouble();
  var /*@type=num*/ v5 = $t9.x ??= getNum();
  var /*@type=double*/ v6 = $t9.x ??= getDouble();
  var /*@type=double*/ v7 = $t9.x += getInt();
  var /*@type=double*/ v8 = $t9.x += getNum();
  var /*@type=double*/ v9 = $t9.x += getDouble();
  var /*@type=double*/ v10 = ++$t9.x;
  var /*@type=double*/ v11 = $t9.x++;
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    assertNoErrors(source);

    var unit = analysisResult.unit;
    _assertTypeAnnotations(code, unit);
  }

  test_compoundAssignment_simpleIdentifier() async {
    var code = r'''
int getInt() => 0;
num getNum() => 0;
double getDouble() => 0.0;

class Test<T extends U, U> {
  T get x => null;
  void set x(U _) {}
}

class Test1 extends Test<int, int> {
  void test1() {
    var /*@type=int*/ v1 = x = getInt();
    var /*@type=num*/ v2 = x = getNum();
    var /*@type=int*/ v4 = x ??= getInt();
    var /*@type=num*/ v5 = x ??= getNum();
    var /*@type=int*/ v7 = x += getInt();
    var /*@type=num*/ v8 = x += getNum();
    var /*@type=int*/ v10 = ++x;
    var /*@type=int*/ v11 = x++;
  }
}

class Test2 extends Test<int, num> {
  void test2() {
    var /*@type=int*/ v1 = x = getInt();
    var /*@type=num*/ v2 = x = getNum();
    var /*@type=double*/ v3 = x = getDouble();
    var /*@type=int*/ v4 = x ??= getInt();
    var /*@type=num*/ v5 = x ??= getNum();
    var /*@type=num*/ v6 = x ??= getDouble();
    var /*@type=int*/ v7 = x += getInt();
    var /*@type=num*/ v8 = x += getNum();
    var /*@type=double*/ v9 = x += getDouble();
    var /*@type=int*/ v10 = ++x;
    var /*@type=int*/ v11 = x++;
  }
}

class Test5 extends Test<num, num> {
  void test5() {
    var /*@type=int*/ v1 = x = getInt();
    var /*@type=num*/ v2 = x = getNum();
    var /*@type=double*/ v3 = x = getDouble();
    var /*@type=num*/ v4 = x ??= getInt();
    var /*@type=num*/ v5 = x ??= getNum();
    var /*@type=num*/ v6 = x ??= getDouble();
    var /*@type=num*/ v7 = x += getInt();
    var /*@type=num*/ v8 = x += getNum();
    var /*@type=num*/ v9 = x += getDouble();
    var /*@type=num*/ v10 = ++x;
    var /*@type=num*/ v11 = x++;
  }
}

class Test8 extends Test<double, num> {
  void test8() {
    var /*@type=int*/ v1 = x = getInt();
    var /*@type=num*/ v2 = x = getNum();
    var /*@type=double*/ v3 = x = getDouble();
    var /*@type=num*/ v4 = x ??= getInt();
    var /*@type=num*/ v5 = x ??= getNum();
    var /*@type=double*/ v6 = x ??= getDouble();
    var /*@type=double*/ v7 = x += getInt();
    var /*@type=double*/ v8 = x += getNum();
    var /*@type=double*/ v9 = x += getDouble();
    var /*@type=double*/ v10 = ++x;
    var /*@type=double*/ v11 = x++;
  }
}

class Test9 extends Test<double, double> {
  void test9() {
    var /*@type=num*/ v2 = x = getNum();
    var /*@type=double*/ v3 = x = getDouble();
    var /*@type=num*/ v5 = x ??= getNum();
    var /*@type=double*/ v6 = x ??= getDouble();
    var /*@type=double*/ v7 = x += getInt();
    var /*@type=double*/ v8 = x += getNum();
    var /*@type=double*/ v9 = x += getDouble();
    var /*@type=double*/ v10 = ++x;
    var /*@type=double*/ v11 = x++;
  }
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    assertNoErrors(source);

    var unit = analysisResult.unit;
    _assertTypeAnnotations(code, unit);
  }

  test_compoundAssignment_simpleIdentifier_topLevel() async {
    var code = r'''
class A {}

class B extends A {
  B operator +(int i) => this;
}

B get topLevel => new B();

void set topLevel(A value) {}

main() {
  var /*@type=B*/ v = topLevel += 1;
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    assertNoErrors(source);

    var unit = analysisResult.unit;
    _assertTypeAnnotations(code, unit);
  }

  test_forIn_identifier() async {
    var code = r'''
T f<T>() => null;

class A {}

A aTopLevel;
void set aTopLevelSetter(A value) {}

class C {
  A aField;
  void set aSetter(A value) {}
  void test() {
    A aLocal;
    for (aLocal in f()) {} // local
    for (aField in f()) {} // field
    for (aSetter in f()) {} // setter
    for (aTopLevel in f()) {} // top variable
    for (aTopLevelSetter in f()) {} // top setter
  }
}''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    void assertType(String prefix) {
      var invocation = _findMethodInvocation(unit, code, prefix);
      expect(invocation.staticType.toString(), 'Iterable<A>');
    }

    assertType('f()) {} // local');
    assertType('f()) {} // field');
    assertType('f()) {} // setter');
    assertType('f()) {} // top variable');
    assertType('f()) {} // top setter');
  }

  test_forIn_variable() async {
    var code = r'''
T f<T>() => null;

void test(Iterable<num> iter) {
  for (var w in f()) {} // 1
  for (var x in iter) {} // 2
  for (num y in f()) {} // 3
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    {
      var node = EngineTestCase.findSimpleIdentifier(unit, code, 'w in');
      VariableElement element = node.staticElement;
      expect(node.staticType, typeProvider.dynamicType);
      expect(element.type, typeProvider.dynamicType);

      var invocation = _findMethodInvocation(unit, code, 'f()) {} // 1');
      expect(invocation.staticType.toString(), 'Iterable<dynamic>');
    }

    {
      var node = EngineTestCase.findSimpleIdentifier(unit, code, 'x in');
      VariableElement element = node.staticElement;
      expect(node.staticType, typeProvider.numType);
      expect(element.type, typeProvider.numType);
    }

    {
      var node = EngineTestCase.findSimpleIdentifier(unit, code, 'y in');
      VariableElement element = node.staticElement;

      expect(node.staticType, typeProvider.numType);
      expect(element.type, typeProvider.numType);

      var invocation = _findMethodInvocation(unit, code, 'f()) {} // 3');
      expect(invocation.staticType.toString(), 'Iterable<num>');
    }
  }

  test_forIn_variable_implicitlyTyped() async {
    var code = r'''
class A {}
class B extends A {}

List<T> f<T extends A>(List<T> items) => items;

void test(List<A> listA, List<B> listB) {
  for (var a1 in f(listA)) {} // 1
  for (A a2 in f(listA)) {} // 2
  for (var b1 in f(listB)) {} // 3
  for (A b2 in f(listB)) {} // 4
  for (B b3 in f(listB)) {} // 5
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    void assertTypes(
        String vSearch, String vType, String fSearch, String fType) {
      var node = EngineTestCase.findSimpleIdentifier(unit, code, vSearch);
      expect(node.staticType.toString(), vType);

      var invocation = _findMethodInvocation(unit, code, fSearch);
      expect(invocation.staticType.toString(), fType);
    }

    assertTypes('a1 in', 'A', 'f(listA)) {} // 1', 'List<A>');
    assertTypes('a2 in', 'A', 'f(listA)) {} // 2', 'List<A>');
    assertTypes('b1 in', 'B', 'f(listB)) {} // 3', 'List<B>');
    assertTypes('b2 in', 'A', 'f(listB)) {} // 4', 'List<A>');
    assertTypes('b3 in', 'B', 'f(listB)) {} // 5', 'List<B>');
  }

  test_implicitVoidReturnType_default() async {
    var code = r'''
class C {
  set x(_) {}
  operator []=(int index, double value) => null;
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    ClassElement c = unit.declaredElement.getType('C');

    PropertyAccessorElement x = c.accessors[0];
    expect(x.returnType, VoidTypeImpl.instance);

    MethodElement operator = c.methods[0];
    expect(operator.displayName, '[]=');
    expect(operator.returnType, VoidTypeImpl.instance);
  }

  test_implicitVoidReturnType_derived() async {
    var code = r'''
class Base {
  dynamic set x(_) {}
  dynamic operator[]=(int x, int y) => null;
}
class Derived extends Base {
  set x(_) {}
  operator[]=(int x, int y) {}
}''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    ClassElement c = unit.declaredElement.getType('Derived');

    PropertyAccessorElement x = c.accessors[0];
    expect(x.returnType, VoidTypeImpl.instance);

    MethodElement operator = c.methods[0];
    expect(operator.displayName, '[]=');
    expect(operator.returnType, VoidTypeImpl.instance);
  }

  test_inferObject_whenDownwardNull() async {
    var code = r'''
int f(void Function(Null) f2) {}
void main() {
  f((x) {});
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;
    var xNode = EngineTestCase.findSimpleIdentifier(unit, code, 'x) {}');
    VariableElement xElement = xNode.staticElement;
    expect(xNode.staticType, typeProvider.objectType);
    expect(xElement.type, typeProvider.objectType);
  }

  test_listMap_empty() async {
    var code = r'''
var x = [];
var y = {};
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    SimpleIdentifier x = _findExpression(unit, code, 'x = ');
    expect(x.staticType.toString(), 'List<dynamic>');

    SimpleIdentifier y = _findExpression(unit, code, 'y = ');
    expect(y.staticType.toString(), 'Map<dynamic, dynamic>');
  }

  test_listMap_null() async {
    var code = r'''
var x = [null];
var y = {null: null};
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    SimpleIdentifier x = _findExpression(unit, code, 'x = ');
    expect(x.staticType.toString(), 'List<Null>');

    SimpleIdentifier y = _findExpression(unit, code, 'y = ');
    expect(y.staticType.toString(), 'Map<Null, Null>');
  }

  test_switchExpression_asContext_forCases() async {
    var code = r'''
class C<T> {
  const C();
}

void test(C<int> x) {
  switch (x) {
    case const C():
      break;
    default:
      break;
  }
}''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    var node = _findInstanceCreation(unit, code, 'const C():');
    expect(node.staticType.toString(), 'C<int>');
  }

  test_voidType_method() async {
    var code = r'''
class C {
  void m() {}
}
var x = new C().m();
main() {
  var y = new C().m();
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    SimpleIdentifier x = _findExpression(unit, code, 'x = ');
    expect(x.staticType, VoidTypeImpl.instance);

    SimpleIdentifier y = _findExpression(unit, code, 'y = ');
    expect(y.staticType, VoidTypeImpl.instance);
  }

  test_voidType_topLevelFunction() async {
    var code = r'''
void f() {}
var x = f();
main() {
  var y = f();
}
''';
    var source = addSource(code);
    var analysisResult = await computeAnalysisResult(source);
    var unit = analysisResult.unit;

    SimpleIdentifier x = _findExpression(unit, code, 'x = ');
    expect(x.staticType, VoidTypeImpl.instance);

    SimpleIdentifier y = _findExpression(unit, code, 'y = ');
    expect(y.staticType, VoidTypeImpl.instance);
  }

  void _assertTypeAnnotations(String code, CompilationUnit unit) {
    var types = <int, String>{};
    {
      int lastIndex = 0;
      while (true) {
        const prefix = '/*@type=';
        int openIndex = code.indexOf(prefix, lastIndex);
        if (openIndex == -1) {
          break;
        }
        int closeIndex = code.indexOf('*/', openIndex + 1);
        expect(closeIndex, isPositive);
        types[openIndex] =
            code.substring(openIndex + prefix.length, closeIndex);
        lastIndex = closeIndex;
      }
    }
    unit.accept(new _TypeAnnotationsValidator(types));
  }

  Expression _findExpression(AstNode root, String code, String prefix) {
    return EngineTestCase.findNode(root, code, prefix, (n) {
      return n is Expression;
    });
  }

  InstanceCreationExpression _findInstanceCreation(
      AstNode root, String code, String prefix) {
    return EngineTestCase.findNode(root, code, prefix, (n) {
      return n is InstanceCreationExpression;
    });
  }

  MethodInvocation _findMethodInvocation(
      AstNode root, String code, String prefix) {
    return EngineTestCase.findNode(root, code, prefix, (n) {
      return n is MethodInvocation;
    });
  }
}

@reflectiveTest
class Dart2InferenceTest_Task extends Dart2InferenceTest {
  @override
  bool get enableNewAnalysisDriver => false;
}

class _TypeAnnotationsValidator extends RecursiveAstVisitor {
  final Map<int, String> types;

  _TypeAnnotationsValidator(this.types);

  void visitSimpleIdentifier(SimpleIdentifier node) {
    Token comment = node.token.precedingComments;
    if (comment != null) {
      String expectedType = types[comment.offset];
      if (expectedType != null) {
        String actualType = node.staticType.toString();
        expect(actualType, expectedType, reason: '@${comment.offset}');
      }
    }
  }
}
