// Copyright (c) 2018, 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/analyzer.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/member.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(FunctionTypeTest);
  });
}

DynamicTypeImpl get dynamicType => DynamicTypeImpl.instance;

VoidTypeImpl get voidType => VoidTypeImpl.instance;

Element getBaseElement(Element e) {
  if (e is Member) {
    return e.baseElement;
  } else {
    return e;
  }
}

@reflectiveTest
class FunctionTypeTest {
  static const bug_33294_fixed = false;
  static const bug_33300_fixed = false;
  static const bug_33301_fixed = false;
  static const bug_33302_fixed = false;

  final objectType = new InterfaceTypeImpl(new MockClassElement('Object'));

  final mapType = _makeMapType();

  final listType = _makeListType();

  final intType = new InterfaceTypeImpl(new MockClassElement('int'));

  void basicChecks(FunctionType f,
      {element,
      displayName: '() → dynamic',
      returnType,
      namedParameterTypes: isEmpty,
      normalParameterNames: isEmpty,
      normalParameterTypes: isEmpty,
      optionalParameterNames: isEmpty,
      optionalParameterTypes: isEmpty,
      parameters: isEmpty,
      typeFormals: isEmpty,
      typeArguments: isEmpty,
      typeParameters: isEmpty,
      name: isNull}) {
    // DartType properties
    expect(f.displayName, displayName, reason: 'displayName');
    expect(f.element, element, reason: 'element');
    expect(f.name, name, reason: 'name');
    // ParameterizedType properties
    expect(f.typeArguments, typeArguments, reason: 'typeArguments');
    expect(f.typeParameters, typeParameters, reason: 'typeParameters');
    // FunctionType properties
    expect(f.namedParameterTypes, namedParameterTypes,
        reason: 'namedParameterTypes');
    expect(f.normalParameterNames, normalParameterNames,
        reason: 'normalParameterNames');
    expect(f.normalParameterTypes, normalParameterTypes,
        reason: 'normalParameterTypes');
    expect(f.optionalParameterNames, optionalParameterNames,
        reason: 'optionalParameterNames');
    expect(f.optionalParameterTypes, optionalParameterTypes,
        reason: 'optionalParameterTypes');
    expect(f.parameters, parameters, reason: 'parameters');
    expect(f.returnType, returnType ?? same(dynamicType), reason: 'returnType');
    expect(f.typeFormals, typeFormals, reason: 'typeFormals');
  }

  DartType listOf(DartType elementType) => listType.instantiate([elementType]);

  DartType mapOf(DartType keyType, DartType valueType) =>
      mapType.instantiate([keyType, valueType]);

  test_forInstantiatedTypedef_bothTypeParameters() {
    // typedef F<T> = Map<T, U> Function<U>();
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var e = new MockGenericTypeAliasElement('F',
        typeParameters: [t],
        innerTypeParameters: [u],
        returnType: mapOf(t.type, u.type));
    FunctionType f =
        new FunctionTypeImpl.forTypedef(e, typeArguments: [objectType]);
    // Note: forTypedef returns the type `() -> Map<Object, U>`.
    // See https://github.com/dart-lang/sdk/issues/34657.
    basicChecks(f,
        element: same(e),
        displayName: 'F<Object>',
        name: 'F',
        typeArguments: [same(objectType)],
        typeParameters: [same(t)],
        returnType: mapOf(objectType, u.type));
  }

  test_forInstantiatedTypedef_bothTypeParameters_noTypeArgs() {
    // typedef F<T> = Map<T, U> Function<U>();
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var e = new MockGenericTypeAliasElement('F',
        typeParameters: [t],
        innerTypeParameters: [u],
        returnType: mapOf(t.type, u.type));
    FunctionType f = new FunctionTypeImpl.forTypedef(e);
    // Note: forTypedef returns the type `<T>() -> Map<T, U>`.
    // See https://github.com/dart-lang/sdk/issues/34657.
    basicChecks(f,
        element: same(e),
        displayName: 'F',
        name: 'F',
        typeFormals: [same(t)],
        returnType: mapOf(t.type, u.type));
  }

  test_forInstantiatedTypedef_innerTypeParameter() {
    // typedef F = T F<T>();
    var t = new MockTypeParameterElement('T');
    var e = new MockGenericTypeAliasElement('F',
        innerTypeParameters: [t], returnType: t.type);
    FunctionType f = new FunctionTypeImpl.forTypedef(e, typeArguments: []);
    // Note: forTypedef returns the type `() -> T`.
    // See https://github.com/dart-lang/sdk/issues/34657.
    basicChecks(f,
        element: same(e),
        displayName: 'F',
        name: 'F',
        returnType: same(t.type));
  }

  test_forInstantiatedTypedef_innerTypeParameter_noTypeArgs() {
    // typedef F = T F<T>();
    var t = new MockTypeParameterElement('T');
    var e = new MockGenericTypeAliasElement('F',
        innerTypeParameters: [t], returnType: t.type);
    FunctionType f = new FunctionTypeImpl.forTypedef(e);
    // Note: forTypedef returns the type `() -> T`.
    // See https://github.com/dart-lang/sdk/issues/34657.
    basicChecks(f,
        element: same(e),
        displayName: 'F',
        name: 'F',
        returnType: same(t.type));
  }

  test_forInstantiatedTypedef_noTypeParameters() {
    // typedef F = void Function();
    var e = new MockGenericTypeAliasElement('F');
    FunctionType f = new FunctionTypeImpl.forTypedef(e, typeArguments: []);
    // Note: forTypedef returns the type `() -> void`.
    basicChecks(f, element: same(e), displayName: 'F', name: 'F');
  }

  test_forInstantiatedTypedef_noTypeParameters_noTypeArgs() {
    // typedef F = void Function();
    var e = new MockGenericTypeAliasElement('F');
    FunctionType f = new FunctionTypeImpl.forTypedef(e);
    // Note: forTypedef returns the type `() -> void`.
    basicChecks(f, element: same(e), displayName: 'F', name: 'F');
  }

  test_forInstantiatedTypedef_outerTypeParameters() {
    // typedef F<T> = T Function();
    var t = new MockTypeParameterElement('T');
    var e = new MockGenericTypeAliasElement('F',
        typeParameters: [t], returnType: t.type);
    FunctionType f =
        new FunctionTypeImpl.forTypedef(e, typeArguments: [objectType]);
    // Note: forTypedef returns the type `() -> Object`.
    basicChecks(f,
        element: same(e),
        displayName: 'F<Object>',
        name: 'F',
        typeArguments: [same(objectType)],
        typeParameters: [same(t)],
        returnType: same(objectType));
  }

  test_forInstantiatedTypedef_outerTypeParameters_noTypeArgs() {
    // typedef F<T> = T Function();
    var t = new MockTypeParameterElement('T');
    var e = new MockGenericTypeAliasElement('F',
        typeParameters: [t], returnType: t.type);
    FunctionType f = new FunctionTypeImpl.forTypedef(e);
    // Note: forTypedef returns the type `<T>() -> T`.
    // See https://github.com/dart-lang/sdk/issues/34657.
    basicChecks(f,
        element: same(e),
        displayName: 'F',
        name: 'F',
        typeFormals: [same(t)],
        returnType: same(t.type));
  }

  test_forTypedef() {
    var e = new MockGenericTypeAliasElement('F');
    basicChecks(e.type, element: same(e), displayName: 'F', name: 'F');
    basicChecks(e.function.type,
        element: same(e.function), displayName: '() → dynamic');
  }

  test_forTypedef_innerAndOuterTypeParameter() {
    // typedef F<T> = T Function<U>(U p);
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var p = new MockParameterElement('p', type: u.type);
    var e = new MockGenericTypeAliasElement('F',
        typeParameters: [t],
        innerTypeParameters: [u],
        returnType: t.type,
        parameters: [p]);
    basicChecks(e.type,
        element: same(e),
        displayName: 'F',
        name: 'F',
        returnType: same(t.type),
        normalParameterTypes: [same(u.type)],
        normalParameterNames: ['p'],
        parameters: [same(p)],
        typeFormals: [same(t)]);
    basicChecks(e.function.type,
        element: same(e.function),
        displayName: '<U>(U) → T',
        returnType: same(t.type),
        typeArguments: [same(t.type)],
        typeParameters: [same(t)],
        typeFormals: [same(u)],
        normalParameterTypes: [same(u.type)],
        normalParameterNames: ['p'],
        parameters: [same(p)]);
  }

  test_forTypedef_innerAndOuterTypeParameter_instantiate() {
    // typedef F<T> = T Function<U>(U p);
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var p = new MockParameterElement('p', type: u.type);
    var e = new MockGenericTypeAliasElement('F',
        typeParameters: [t],
        innerTypeParameters: [u],
        returnType: t.type,
        parameters: [p]);
    var instantiated = e.type.instantiate([objectType]);
    basicChecks(instantiated,
        element: same(e),
        displayName: 'F<Object>',
        name: 'F',
        returnType: same(objectType),
        normalParameterTypes: [same(u.type)],
        normalParameterNames: ['p'],
        parameters: [same(p)],
        typeFormals: isNotNull,
        typeArguments: [same(objectType)],
        typeParameters: [same(t)]);
    if (bug_33294_fixed) {
      expect(instantiated.typeFormals, [same(u)]);
    } else {
      expect(instantiated.typeFormals, isEmpty);
    }
  }

  test_forTypedef_innerTypeParameter() {
    // typedef F = T Function<T>();
    var t = new MockTypeParameterElement('T');
    var e = new MockGenericTypeAliasElement('F',
        innerTypeParameters: [t], returnType: t.type);
    basicChecks(e.type,
        element: same(e),
        displayName: 'F',
        name: 'F',
        returnType: same(t.type));
    basicChecks(e.function.type,
        element: same(e.function),
        displayName: '<T>() → T',
        returnType: same(t.type),
        typeFormals: [same(t)]);
  }

  test_forTypedef_normalParameter() {
    var p = new MockParameterElement('p');
    var e = new MockGenericTypeAliasElement('F', parameters: [p]);
    basicChecks(e.type,
        element: same(e),
        displayName: 'F',
        name: 'F',
        normalParameterNames: ['p'],
        normalParameterTypes: [same(dynamicType)],
        parameters: [same(p)]);
    basicChecks(e.function.type,
        element: same(e.function),
        displayName: '(dynamic) → dynamic',
        normalParameterNames: ['p'],
        normalParameterTypes: [same(dynamicType)],
        parameters: [same(p)]);
  }

  test_forTypedef_recursive_via_interfaceTypes() {
    // typedef F = List<G> Function();
    // typedef G = List<F> Function();
    var f = new MockGenericTypeAliasElement('F');
    var g = new MockGenericTypeAliasElement('G');
    f.returnType = listOf(g.function.type);
    g.returnType = listOf(f.function.type);
    basicChecks(f.type,
        element: same(f), displayName: 'F', name: 'F', returnType: isNotNull);
    var fReturn = f.type.returnType;
    expect(fReturn.element, same(listType.element));
    if (bug_33302_fixed) {
      expect(fReturn.displayName, 'List<G>');
    } else {
      expect(fReturn.displayName, 'List<() → List<...>>');
    }
    var fReturnArg = (fReturn as InterfaceType).typeArguments[0];
    expect(fReturnArg.element, same(g.function));
    var fReturnArgReturn = (fReturnArg as FunctionType).returnType;
    expect(fReturnArgReturn.element, same(listType.element));
    expect((fReturnArgReturn as InterfaceType).typeArguments[0],
        new TypeMatcher<CircularFunctionTypeImpl>());
    basicChecks(f.function.type,
        element: same(f.function), displayName: isNotNull, returnType: fReturn);
    if (bug_33302_fixed) {
      expect(f.function.type.displayName, '() → List<G>');
    } else {
      expect(f.function.type.displayName, '() → List<() → List<...>>');
    }
    basicChecks(g.type,
        element: same(g), displayName: 'G', name: 'G', returnType: isNotNull);
    var gReturn = g.type.returnType;
    expect(gReturn.element, same(listType.element));
    if (bug_33302_fixed) {
      expect(gReturn.displayName, 'List<F>');
    } else {
      expect(gReturn.displayName, 'List<() → List<...>>');
    }
    var gReturnArg = (gReturn as InterfaceType).typeArguments[0];
    expect(gReturnArg.element, same(f.function));
    var gReturnArgReturn = (gReturnArg as FunctionType).returnType;
    expect(gReturnArgReturn.element, same(listType.element));
    expect((gReturnArgReturn as InterfaceType).typeArguments[0],
        new TypeMatcher<CircularFunctionTypeImpl>());
    basicChecks(g.function.type,
        element: same(g.function), displayName: isNotNull, returnType: gReturn);
    if (bug_33302_fixed) {
      expect(g.function.type.displayName, '() → F');
    } else {
      expect(g.function.type.displayName, '() → List<() → List<...>>');
    }
  }

  test_forTypedef_recursive_via_parameterTypes() {
    // typedef F = void Function(G g);
    // typedef G = void Function(F f);
    var f = new MockGenericTypeAliasElement('F', returnType: voidType);
    var g = new MockGenericTypeAliasElement('G', returnType: voidType);
    f.parameters = [new MockParameterElement('g', type: g.function.type)];
    g.parameters = [new MockParameterElement('f', type: f.function.type)];
    basicChecks(f.type,
        element: same(f),
        displayName: 'F',
        name: 'F',
        parameters: hasLength(1),
        normalParameterTypes: hasLength(1),
        normalParameterNames: ['g'],
        returnType: same(voidType));
    var fParamType = f.type.normalParameterTypes[0];
    expect(fParamType.element, same(g.function));
    expect((fParamType as FunctionType).normalParameterTypes[0],
        new TypeMatcher<CircularFunctionTypeImpl>());
    basicChecks(f.function.type,
        element: same(f.function),
        displayName: isNotNull,
        parameters: hasLength(1),
        normalParameterTypes: [fParamType],
        normalParameterNames: ['g'],
        returnType: same(voidType));
    if (bug_33302_fixed) {
      expect(f.function.type.displayName, '(G) → void');
    } else {
      expect(f.function.type.displayName, '((...) → void) → void');
    }
    basicChecks(g.type,
        element: same(g),
        displayName: 'G',
        name: 'G',
        parameters: hasLength(1),
        normalParameterTypes: hasLength(1),
        normalParameterNames: ['f'],
        returnType: same(voidType));
    var gParamType = g.type.normalParameterTypes[0];
    expect(gParamType.element, same(f.function));
    expect((gParamType as FunctionType).normalParameterTypes[0],
        new TypeMatcher<CircularFunctionTypeImpl>());
    basicChecks(g.function.type,
        element: same(g.function),
        displayName: isNotNull,
        parameters: hasLength(1),
        normalParameterTypes: [gParamType],
        normalParameterNames: ['f'],
        returnType: same(voidType));
    if (bug_33302_fixed) {
      expect(g.function.type.displayName, '(F) → void');
    } else {
      expect(g.function.type.displayName, '((...) → void) → void');
    }
  }

  test_forTypedef_recursive_via_returnTypes() {
    // typedef F = G Function();
    // typedef G = F Function();
    var f = new MockGenericTypeAliasElement('F');
    var g = new MockGenericTypeAliasElement('G');
    f.returnType = g.function.type;
    g.returnType = f.function.type;
    basicChecks(f.type,
        element: same(f), displayName: 'F', name: 'F', returnType: isNotNull);
    var fReturn = f.type.returnType;
    expect(fReturn.element, same(g.function));
    expect((fReturn as FunctionType).returnType,
        new TypeMatcher<CircularFunctionTypeImpl>());
    basicChecks(f.function.type,
        element: same(f.function), displayName: isNotNull, returnType: fReturn);
    if (bug_33302_fixed) {
      expect(f.function.type.displayName, '() → G');
    } else {
      expect(f.function.type.displayName, '() → () → ...');
    }
    basicChecks(g.type,
        element: same(g), displayName: 'G', name: 'G', returnType: isNotNull);
    var gReturn = g.type.returnType;
    expect(gReturn.element, same(f.function));
    expect((gReturn as FunctionType).returnType,
        new TypeMatcher<CircularFunctionTypeImpl>());
    basicChecks(g.function.type,
        element: same(g.function), displayName: isNotNull, returnType: gReturn);
    if (bug_33302_fixed) {
      expect(g.function.type.displayName, '() → F');
    } else {
      expect(g.function.type.displayName, '() → () → ...');
    }
  }

  test_forTypedef_returnType() {
    var e = new MockGenericTypeAliasElement('F', returnType: objectType);
    basicChecks(e.type,
        element: same(e), displayName: 'F', name: 'F', returnType: objectType);
    basicChecks(e.function.type,
        element: same(e.function),
        displayName: '() → Object',
        returnType: objectType);
  }

  test_forTypedef_returnType_null() {
    var e = new MockGenericTypeAliasElement.withNullReturn('F');
    basicChecks(e.type, element: same(e), displayName: 'F', name: 'F');
    basicChecks(e.function.type,
        element: same(e.function), displayName: '() → dynamic');
  }

  test_forTypedef_typeParameter() {
    // typedef F<T> = T Function();
    var t = new MockTypeParameterElement('T');
    var e = new MockGenericTypeAliasElement('F',
        typeParameters: [t], returnType: t.type);
    basicChecks(e.type,
        element: same(e),
        displayName: 'F',
        name: 'F',
        returnType: same(t.type),
        typeFormals: [same(t)]);
    basicChecks(e.function.type,
        element: same(e.function),
        displayName: '() → T',
        returnType: same(t.type),
        typeArguments: [same(t.type)],
        typeParameters: [same(t)]);
  }

  test_synthetic() {
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [], []);
    basicChecks(f, element: isNull);
  }

  test_synthetic_instantiate() {
    // T Function<T>(T x)
    var t = new MockTypeParameterElement('T');
    var x = new MockParameterElement('x', type: t.type);
    FunctionType f = new FunctionTypeImpl.synthetic(t.type, [t], [x]);
    FunctionType instantiated = f.instantiate([objectType]);
    basicChecks(instantiated,
        element: isNull,
        displayName: '(Object) → Object',
        returnType: same(objectType),
        normalParameterNames: ['x'],
        normalParameterTypes: [same(objectType)],
        parameters: hasLength(1));
  }

  test_synthetic_instantiate_argument_length_mismatch() {
    // dynamic Function<T>()
    var t = new MockTypeParameterElement('T');
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [t], []);
    expect(() => f.instantiate([]), throwsA(new TypeMatcher<ArgumentError>()));
  }

  test_synthetic_instantiate_no_type_formals() {
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [], []);
    expect(f.instantiate([]), same(f));
  }

  test_synthetic_instantiate_share_parameters() {
    // T Function<T>(int x)
    var t = new MockTypeParameterElement('T');
    var x = new MockParameterElement('x', type: intType);
    FunctionType f = new FunctionTypeImpl.synthetic(t.type, [t], [x]);
    FunctionType instantiated = f.instantiate([objectType]);
    basicChecks(instantiated,
        element: isNull,
        displayName: '(int) → Object',
        returnType: same(objectType),
        normalParameterNames: ['x'],
        normalParameterTypes: [same(intType)],
        parameters: same(f.parameters));
  }

  test_synthetic_namedParameter() {
    var p = new MockParameterElement('x',
        type: objectType, parameterKind: ParameterKind.NAMED);
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [], [p]);
    basicChecks(f,
        element: isNull,
        displayName: '({x: Object}) → dynamic',
        namedParameterTypes: {'x': same(objectType)},
        parameters: hasLength(1));
    expect(f.parameters[0].isNamed, isTrue);
    expect(f.parameters[0].name, 'x');
    expect(f.parameters[0].type, same(objectType));
  }

  test_synthetic_normalParameter() {
    var p = new MockParameterElement('x', type: objectType);
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [], [p]);
    basicChecks(f,
        element: isNull,
        displayName: '(Object) → dynamic',
        normalParameterNames: ['x'],
        normalParameterTypes: [same(objectType)],
        parameters: hasLength(1));
    expect(f.parameters[0].isNotOptional, isTrue);
    expect(f.parameters[0].name, 'x');
    expect(f.parameters[0].type, same(objectType));
  }

  test_synthetic_optionalParameter() {
    var p = new MockParameterElement('x',
        type: objectType, parameterKind: ParameterKind.POSITIONAL);
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [], [p]);
    basicChecks(f,
        element: isNull,
        displayName: '([Object]) → dynamic',
        optionalParameterNames: ['x'],
        optionalParameterTypes: [same(objectType)],
        parameters: hasLength(1));
    expect(f.parameters[0].isOptionalPositional, isTrue);
    expect(f.parameters[0].name, 'x');
    expect(f.parameters[0].type, same(objectType));
  }

  test_synthetic_returnType() {
    FunctionType f = new FunctionTypeImpl.synthetic(objectType, [], []);
    basicChecks(f,
        element: isNull,
        displayName: '() → Object',
        returnType: same(objectType));
  }

  test_synthetic_substitute() {
    // Map<T, U> Function<U extends T>(T x, U y)
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U', bound: t.type);
    var x = new MockParameterElement('x', type: t.type);
    var y = new MockParameterElement('y', type: u.type);
    FunctionType f =
        new FunctionTypeImpl.synthetic(mapOf(t.type, u.type), [u], [x, y]);
    FunctionType substituted = f.substitute2([objectType], [t.type]);
    var uSubstituted = substituted.typeFormals[0];
    basicChecks(substituted,
        element: isNull,
        displayName: '<U extends Object>(Object, U) → Map<Object, U>',
        returnType: mapOf(objectType, uSubstituted.type),
        typeFormals: [uSubstituted],
        normalParameterNames: ['x', 'y'],
        normalParameterTypes: [same(objectType), same(uSubstituted.type)],
        parameters: hasLength(2));
  }

  test_synthetic_substitute_argument_length_mismatch() {
    // dynamic Function()
    var t = new MockTypeParameterElement('T');
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [], []);
    expect(() => f.substitute2([], [t.type]),
        throwsA(new TypeMatcher<ArgumentError>()));
  }

  test_synthetic_substitute_share_returnType_and_parameters() {
    // int Function<U extends T>(int x)
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U', bound: t.type);
    var x = new MockParameterElement('x', type: intType);
    FunctionType f = new FunctionTypeImpl.synthetic(intType, [u], [x]);
    FunctionType substituted = f.substitute2([objectType], [t.type]);
    basicChecks(substituted,
        element: isNull,
        displayName: '<U extends Object>(int) → int',
        returnType: same(f.returnType),
        typeFormals: hasLength(1),
        normalParameterNames: ['x'],
        normalParameterTypes: [same(intType)],
        parameters: same(f.parameters));
    expect(substituted.typeFormals[0].name, 'U');
    expect(substituted.typeFormals[0].bound, same(objectType));
  }

  test_synthetic_substitute_share_returnType_and_typeFormals() {
    // int Function<U>(T x, U y)
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var x = new MockParameterElement('x', type: t.type);
    var y = new MockParameterElement('y', type: u.type);
    FunctionType f = new FunctionTypeImpl.synthetic(intType, [u], [x, y]);
    FunctionType substituted = f.substitute2([objectType], [t.type]);
    basicChecks(substituted,
        element: isNull,
        displayName: '<U>(Object, U) → int',
        returnType: same(f.returnType),
        typeFormals: same(f.typeFormals),
        normalParameterNames: ['x', 'y'],
        normalParameterTypes: [same(objectType), same(u.type)],
        parameters: hasLength(2));
  }

  test_synthetic_substitute_share_typeFormals_and_parameters() {
    // T Function<U>(U x)
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var x = new MockParameterElement('x', type: u.type);
    FunctionType f = new FunctionTypeImpl.synthetic(t.type, [u], [x]);
    FunctionType substituted = f.substitute2([objectType], [t.type]);
    basicChecks(substituted,
        element: isNull,
        displayName: '<U>(U) → Object',
        returnType: same(objectType),
        typeFormals: same(f.typeFormals),
        normalParameterNames: ['x'],
        normalParameterTypes: [same(u.type)],
        parameters: same(f.parameters));
  }

  test_synthetic_substitute_unchanged() {
    // dynamic Function<U>(U x)
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var x = new MockParameterElement('x', type: u.type);
    FunctionType f = new FunctionTypeImpl.synthetic(dynamicType, [u], [x]);
    FunctionType substituted = f.substitute2([objectType], [t.type]);
    expect(substituted, same(f));
  }

  test_synthetic_typeFormals() {
    var t = new MockTypeParameterElement('T');
    FunctionType f = new FunctionTypeImpl.synthetic(t.type, [t], []);
    basicChecks(f,
        element: isNull,
        displayName: '<T>() → T',
        returnType: same(t.type),
        typeFormals: [same(t)]);
  }

  test_unnamedConstructor() {
    var e = new MockFunctionTypedElement();
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f, element: same(e));
  }

  test_unnamedConstructor_instantiate_argument_length_mismatch() {
    var t = new MockTypeParameterElement('T');
    var e = new MockFunctionTypedElement(typeParameters: [t]);
    FunctionType f = new FunctionTypeImpl(e);
    expect(() => f.instantiate([]), throwsA(new TypeMatcher<ArgumentError>()));
  }

  test_unnamedConstructor_instantiate_noop() {
    var t = new MockTypeParameterElement('T');
    var p = new MockParameterElement('x', type: t.type);
    var e = new MockFunctionTypedElement(typeParameters: [t], parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    var instantiated = f.instantiate([t.type]);
    basicChecks(instantiated,
        element: same(e),
        displayName: '(T) → dynamic',
        typeArguments: hasLength(1),
        typeParameters: [same(t)],
        normalParameterNames: ['x'],
        normalParameterTypes: [same(t.type)],
        parameters: [same(p)]);
    expect(instantiated.typeArguments[0], same(t.type));
  }

  test_unnamedConstructor_instantiate_noTypeParameters() {
    var e = new MockFunctionTypedElement();
    FunctionType f = new FunctionTypeImpl(e);
    expect(f.instantiate([]), same(f));
  }

  test_unnamedConstructor_instantiate_parameterType_simple() {
    var t = new MockTypeParameterElement('T');
    var p = new MockParameterElement('x', type: t.type);
    var e = new MockFunctionTypedElement(typeParameters: [t], parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    var instantiated = f.instantiate([objectType]);
    basicChecks(instantiated,
        element: same(e),
        displayName: '(Object) → dynamic',
        typeArguments: hasLength(1),
        typeParameters: [same(t)],
        normalParameterNames: ['x'],
        normalParameterTypes: [same(objectType)],
        parameters: hasLength(1));
    expect(instantiated.typeArguments[0], same(objectType));
    expect(instantiated.parameters[0].name, 'x');
    expect(instantiated.parameters[0].type, same(objectType));
  }

  test_unnamedConstructor_instantiate_returnType_simple() {
    var t = new MockTypeParameterElement('T');
    var e =
        new MockFunctionTypedElement(typeParameters: [t], returnType: t.type);
    FunctionType f = new FunctionTypeImpl(e);
    var instantiated = f.instantiate([objectType]);
    basicChecks(instantiated,
        element: same(e),
        displayName: '() → Object',
        typeArguments: hasLength(1),
        typeParameters: [same(t)],
        returnType: same(objectType));
    expect(instantiated.typeArguments[0], same(objectType));
  }

  test_unnamedConstructor_namedParameter() {
    var p = new MockParameterElement('x', parameterKind: ParameterKind.NAMED);
    var e = new MockFunctionTypedElement(parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '({x: dynamic}) → dynamic',
        namedParameterTypes: {'x': same(dynamicType)},
        parameters: [same(p)]);
  }

  test_unnamedConstructor_namedParameter_object() {
    var p = new MockParameterElement('x',
        parameterKind: ParameterKind.NAMED, type: objectType);
    var e = new MockFunctionTypedElement(parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '({x: Object}) → dynamic',
        namedParameterTypes: {'x': same(objectType)},
        parameters: [same(p)]);
  }

  test_unnamedConstructor_nonTypedef_noTypeArguments() {
    var e = new MockFunctionTypedElement();
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f, element: same(e));
  }

  test_unnamedConstructor_nonTypedef_withTypeArguments() {
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var e = new MockMethodElement(c, returnType: t.type);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        typeArguments: [same(t.type)],
        typeParameters: [same(t)],
        displayName: '() → T',
        returnType: same(t.type));
  }

  test_unnamedConstructor_normalParameter() {
    var p = new MockParameterElement('x');
    var e = new MockFunctionTypedElement(parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '(dynamic) → dynamic',
        normalParameterNames: ['x'],
        normalParameterTypes: [same(dynamicType)],
        parameters: [same(p)]);
  }

  test_unnamedConstructor_normalParameter_object() {
    var p = new MockParameterElement('x', type: objectType);
    var e = new MockFunctionTypedElement(parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '(Object) → dynamic',
        normalParameterNames: ['x'],
        normalParameterTypes: [same(objectType)],
        parameters: [same(p)]);
  }

  test_unnamedConstructor_optionalParameter() {
    var p =
        new MockParameterElement('x', parameterKind: ParameterKind.POSITIONAL);
    var e = new MockFunctionTypedElement(parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '([dynamic]) → dynamic',
        optionalParameterNames: ['x'],
        optionalParameterTypes: [same(dynamicType)],
        parameters: [same(p)]);
  }

  test_unnamedConstructor_optionalParameter_object() {
    var p = new MockParameterElement('x',
        parameterKind: ParameterKind.POSITIONAL, type: objectType);
    var e = new MockFunctionTypedElement(parameters: [p]);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '([Object]) → dynamic',
        optionalParameterNames: ['x'],
        optionalParameterTypes: [same(objectType)],
        parameters: [same(p)]);
  }

  test_unnamedConstructor_returnType() {
    var e = new MockFunctionTypedElement(returnType: objectType);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        returnType: same(objectType),
        displayName: '() → Object');
  }

  test_unnamedConstructor_returnType_null() {
    var e = new MockFunctionTypedElement.withNullReturn();
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        returnType: same(dynamicType),
        displayName: '() → dynamic');
  }

  test_unnamedConstructor_staticMethod_ignores_enclosing_type_params() {
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var e = new MockMethodElement(c, isStatic: true);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f, element: same(e));
  }

  test_unnamedConstructor_substitute_argument_length_mismatch() {
    // abstract class C<T> {
    //   dynamic f();
    // }
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var e = new MockFunctionTypedElement(enclosingElement: c);
    FunctionType f = new FunctionTypeImpl(e);
    expect(() => f.substitute2([], [t.type]),
        throwsA(new TypeMatcher<ArgumentError>()));
  }

  test_unnamedConstructor_substitute_bound_recursive() {
    // abstract class C<T> {
    //   Map<S, V> f<S extends T, T extends U, V extends T>();
    // }
    var s = new MockTypeParameterElement('S');
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var v = new MockTypeParameterElement('V');
    s.bound = t.type;
    t.bound = u.type;
    v.bound = t.type;
    var c = new MockClassElement('C', typeParameters: [u]);
    var e = new MockFunctionTypedElement(
        returnType: mapOf(s.type, v.type),
        typeParameters: [s, t, v],
        enclosingElement: c);
    FunctionType f = new FunctionTypeImpl(e);
    var substituted = f.substitute2([objectType], [u.type]);
    basicChecks(substituted,
        element: same(e),
        displayName: isNotNull,
        returnType: isNotNull,
        typeFormals: hasLength(3),
        typeParameters: [same(u)],
        typeArguments: [same(objectType)]);
    if (bug_33300_fixed) {
      expect(substituted.displayName,
          '<S extends T,T extends Object,V extends T>() → Map<S, V>');
    } else {
      expect(substituted.displayName,
          '<S extends T extends Object,T extends Object,V extends T>() → Map<S, V>');
    }
    var s2 = substituted.typeFormals[0];
    var t2 = substituted.typeFormals[1];
    var v2 = substituted.typeFormals[2];
    expect(s2.name, 'S');
    expect(t2.name, 'T');
    expect(v2.name, 'V');
    expect(s2.bound, t2.type);
    expect(t2.bound, same(objectType));
    expect(v2.bound, t2.type);
    if (bug_33301_fixed) {
      expect(substituted.returnType, mapOf(s2.type, v2.type));
    } else {
      expect(substituted.returnType, mapOf(s.type, v.type));
    }
  }

  test_unnamedConstructor_substitute_bound_recursive_parameter() {
    // abstract class C<T> {
    //   void f<S extends T, T extends U, V extends T>(S x, V y);
    // }
    var s = new MockTypeParameterElement('S');
    var t = new MockTypeParameterElement('T');
    var u = new MockTypeParameterElement('U');
    var v = new MockTypeParameterElement('V');
    s.bound = t.type;
    t.bound = u.type;
    v.bound = t.type;
    var c = new MockClassElement('C', typeParameters: [u]);
    var x = new MockParameterElement('x', type: s.type);
    var y = new MockParameterElement('y', type: v.type);
    var e = new MockFunctionTypedElement(
        returnType: voidType,
        typeParameters: [s, t, v],
        enclosingElement: c,
        parameters: [x, y]);
    FunctionType f = new FunctionTypeImpl(e);
    var substituted = f.substitute2([objectType], [u.type]);
    basicChecks(substituted,
        element: same(e),
        displayName: isNotNull,
        returnType: same(voidType),
        normalParameterNames: ['x', 'y'],
        normalParameterTypes: hasLength(2),
        parameters: hasLength(2),
        typeFormals: hasLength(3),
        typeParameters: [same(u)],
        typeArguments: [same(objectType)]);
    if (bug_33300_fixed) {
      expect(substituted.displayName,
          '<S extends T,T extends Object,V extends T>(S, V) → void');
    } else {
      expect(
          substituted.displayName,
          '<S extends T extends Object,T extends Object,V extends T>(S, V) '
          '→ void');
    }
    var s2 = substituted.typeFormals[0];
    var t2 = substituted.typeFormals[1];
    var v2 = substituted.typeFormals[2];
    expect(s2.name, 'S');
    expect(t2.name, 'T');
    expect(v2.name, 'V');
    expect(s2.bound, t2.type);
    expect(t2.bound, same(objectType));
    expect(v2.bound, t2.type);
    if (bug_33301_fixed) {
      expect(substituted.normalParameterTypes, [same(s2.type), same(v2.type)]);
    } else {
      expect(substituted.normalParameterTypes, [same(s.type), same(v.type)]);
    }
  }

  test_unnamedConstructor_substitute_bound_simple() {
    // abstract class C<T> {
    //   U f<U extends T>();
    // }
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var u = new MockTypeParameterElement('U', bound: t.type);
    var e = new MockFunctionTypedElement(
        typeParameters: [u], returnType: u.type, enclosingElement: c);
    FunctionType f = new FunctionTypeImpl(e);
    var substituted = f.substitute2([objectType], [t.type]);
    basicChecks(substituted,
        element: same(e),
        displayName: '<U extends Object>() → U',
        typeArguments: [same(objectType)],
        typeParameters: [same(t)],
        returnType: isNotNull,
        typeFormals: hasLength(1));
    expect(substituted.typeFormals[0].name, 'U');
    expect(substituted.typeFormals[0].bound, same(objectType));
    expect((substituted.returnType as TypeParameterTypeImpl).element,
        same(getBaseElement(substituted.typeFormals[0])));
  }

  test_unnamedConstructor_substitute_noop() {
    var t = new MockTypeParameterElement('T');
    var e = new MockFunctionTypedElement(returnType: t.type);
    FunctionType f = new FunctionTypeImpl(e);
    var substituted = f.substitute2([t.type], [t.type]);
    basicChecks(substituted,
        element: same(e), displayName: '() → T', returnType: same(t.type));
    // TODO(paulberry): test substitute length mismatch
  }

  test_unnamedConstructor_substitute_parameterType_simple() {
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var p = new MockParameterElement('x', type: t.type);
    var e = new MockFunctionTypedElement(parameters: [p], enclosingElement: c);
    FunctionType f = new FunctionTypeImpl(e);
    var substituted = f.substitute2([objectType], [t.type]);
    basicChecks(substituted,
        element: same(e),
        displayName: '(Object) → dynamic',
        normalParameterNames: ['x'],
        normalParameterTypes: [same(objectType)],
        parameters: hasLength(1),
        typeArguments: [same(objectType)],
        typeParameters: [same(t)]);
    expect(substituted.parameters[0].name, 'x');
    expect(substituted.parameters[0].type, same(objectType));
  }

  test_unnamedConstructor_substitute_returnType_simple() {
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var e =
        new MockFunctionTypedElement(returnType: t.type, enclosingElement: c);
    FunctionType f = new FunctionTypeImpl(e);
    var substituted = f.substitute2([objectType], [t.type]);
    basicChecks(substituted,
        element: same(e),
        displayName: '() → Object',
        returnType: same(objectType),
        typeArguments: [same(objectType)],
        typeParameters: [same(t)]);
  }

  test_unnamedConstructor_typeParameter() {
    var t = new MockTypeParameterElement('T');
    var e = new MockFunctionTypedElement(typeParameters: [t]);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '<T>() → dynamic',
        typeFormals: [same(t)]);
    // TODO(paulberry): test pruning of bounds
  }

  test_unnamedConstructor_typeParameter_with_bound() {
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var u = new MockTypeParameterElement('U', bound: t.type);
    var e = new MockFunctionTypedElement(
        typeParameters: [u], returnType: u.type, enclosingElement: c);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '<U extends T>() → U',
        typeArguments: [same(t.type)],
        typeParameters: [same(t)],
        returnType: same(u.type),
        typeFormals: hasLength(1));
    expect(f.typeFormals[0].name, 'U');
    expect(f.typeFormals[0].bound, same(t.type));
  }

  test_unnamedConstructor_with_enclosing_type_parameters() {
    // Test a weird behavior: substitutions are recorded in typeArguments and
    // typeParameters.
    var t = new MockTypeParameterElement('T');
    var c = new MockClassElement('C', typeParameters: [t]);
    var e =
        new MockFunctionTypedElement(returnType: t.type, enclosingElement: c);
    FunctionType f = new FunctionTypeImpl(e);
    basicChecks(f,
        element: same(e),
        displayName: '() → T',
        returnType: same(t.type),
        typeArguments: [same(t.type)],
        typeParameters: [same(t)]);
  }

  static InterfaceTypeImpl _makeListType() {
    var e = new MockTypeParameterElement('E');
    return new InterfaceTypeImpl.elementWithNameAndArgs(
        new MockClassElement('List', typeParameters: [e]),
        'List',
        () => [e.type]);
  }

  static InterfaceTypeImpl _makeMapType() {
    var k = new MockTypeParameterElement('K');
    var v = new MockTypeParameterElement('V');
    return new InterfaceTypeImpl.elementWithNameAndArgs(
        new MockClassElement('Map', typeParameters: [k, v]),
        'Map',
        () => [k.type, v.type]);
  }
}

class MockClassElement implements ClassElementImpl {
  @override
  final List<TypeParameterElement> typeParameters;

  @override
  final String displayName;

  MockClassElement(this.displayName, {this.typeParameters: const []});

  @override
  get enclosingElement => const MockCompilationUnitElement();

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockCompilationUnitElement implements CompilationUnitElement {
  const MockCompilationUnitElement();

  @override
  get enclosingElement => const MockLibraryElement();

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockElementLocation implements ElementLocation {
  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockFunctionTypedElement implements FunctionTypedElement {
  @override
  final List<ParameterElement> parameters;

  @override
  final DartType returnType;

  @override
  final List<TypeParameterElement> typeParameters;

  @override
  final Element enclosingElement;

  MockFunctionTypedElement(
      {this.parameters: const [],
      DartType returnType,
      this.typeParameters: const [],
      this.enclosingElement: const MockCompilationUnitElement()})
      : returnType = returnType ?? dynamicType;

  MockFunctionTypedElement.withNullReturn(
      {this.parameters: const [],
      this.typeParameters: const [],
      this.enclosingElement: const MockCompilationUnitElement()})
      : returnType = null;

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockGenericFunctionTypeElementImpl
    implements GenericFunctionTypeElementImpl {
  @override
  final MockGenericTypeAliasElement enclosingElement;

  FunctionTypeImpl _type;

  MockGenericFunctionTypeElementImpl(this.enclosingElement);

  @override
  get parameters => enclosingElement.parameters;

  @override
  get returnType => enclosingElement.returnType;

  @override
  get type => _type ??= new FunctionTypeImpl(this);

  @override
  get typeParameters => enclosingElement.innerTypeParameters;

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockGenericTypeAliasElement implements GenericTypeAliasElement {
  @override
  final String name;

  @override
  List<ParameterElement> parameters;

  @override
  final List<TypeParameterElement> typeParameters;

  @override
  DartType returnType;

  FunctionType _type;

  MockGenericFunctionTypeElementImpl _function;

  final List<TypeParameterElement> innerTypeParameters;

  MockGenericTypeAliasElement(this.name,
      {this.parameters: const [],
      DartType returnType,
      this.typeParameters: const [],
      this.innerTypeParameters: const []})
      : returnType = returnType ?? dynamicType;

  MockGenericTypeAliasElement.withNullReturn(this.name,
      {this.parameters: const [],
      this.typeParameters: const [],
      this.innerTypeParameters: const []})
      : returnType = null;

  @override
  get enclosingElement => const MockCompilationUnitElement();

  @override
  get function => _function ??= new MockGenericFunctionTypeElementImpl(this);

  @override
  get isSynthetic => false;

  @override
  FunctionType get type => _type ??= new FunctionTypeImpl.forTypedef(this);

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockLibraryElement implements LibraryElement {
  const MockLibraryElement();

  @override
  get enclosingElement => null;

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockMethodElement extends MockFunctionTypedElement
    implements MethodElement {
  @override
  final bool isStatic;

  MockMethodElement(MockClassElement enclosingElement,
      {this.isStatic: false, DartType returnType})
      : super(enclosingElement: enclosingElement, returnType: returnType);

  @override
  ClassElement get enclosingElement => super.enclosingElement;
}

class MockParameterElement implements ParameterElementImpl {
  @override
  Element enclosingElement;

  @override
  final String name;

  @override
  final ParameterKind parameterKind;

  @override
  final DartType type;

  MockParameterElement(this.name,
      {this.parameterKind: ParameterKind.REQUIRED, this.type});

  @override
  get displayName => name;

  @override
  bool get isNamed => parameterKind == ParameterKind.NAMED;

  @override
  bool get isNotOptional => parameterKind == ParameterKind.REQUIRED;

  @override
  bool get isOptionalPositional => parameterKind == ParameterKind.POSITIONAL;

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }
}

class MockTypeParameterElement implements TypeParameterElement {
  @override
  final String name;

  TypeParameterTypeImpl _type;

  MockElementLocation _location;

  @override
  DartType bound;

  MockTypeParameterElement(this.name, {this.bound});

  @override
  get kind => ElementKind.TYPE_PARAMETER;

  @override
  get location => _location ??= new MockElementLocation();

  @override
  get type => _type ??= new TypeParameterTypeImpl(this);

  noSuchMethod(Invocation invocation) {
    return super.noSuchMethod(invocation);
  }

  @override
  toString() => name;
}
