Introduce tests specific to behaviors of FunctionType.

I am about to start embarking on bug fixes and refactors to
FunctionType, so I want a set of tests that I can use to make sure I
don't break any important behaviors.  These tests use a mock element
model, so they don't rely on any behaviors outside of FunctionTypeImpl
itself.

Change-Id: I037f6a8cd2ee2a94fba13bb9b6be9e7090e254e8
Reviewed-on: https://dart-review.googlesource.com/57708
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/test/src/dart/element/function_type_test.dart b/pkg/analyzer/test/src/dart/element/function_type_test.dart
new file mode 100644
index 0000000..047473b
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/element/function_type_test.dart
@@ -0,0 +1,1002 @@
+// 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();
+
+  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);
+    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);
+    expect(f.normalParameterNames, normalParameterNames,
+        reason: 'normalParameterNames');
+    expect(f.normalParameterTypes, normalParameterTypes);
+    expect(f.optionalParameterNames, optionalParameterNames);
+    expect(f.optionalParameterTypes, optionalParameterTypes);
+    expect(f.parameters, 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_elementWithNameAndArgs_nonTypedef_noTypeArguments() {
+    var e = new MockFunctionTypedElement();
+    FunctionType f =
+        new FunctionTypeImpl.elementWithNameAndArgs(e, null, [], false);
+    basicChecks(f, element: same(e));
+  }
+
+  test_elementWithNameAndArgs_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.elementWithNameAndArgs(e, null, [t.type], false);
+    basicChecks(f,
+        element: same(e),
+        typeArguments: [same(t.type)],
+        typeParameters: [same(t)],
+        displayName: '() → T',
+        returnType: same(t.type));
+  }
+
+  test_elementWithNameAndArgs_typedef_bothTypeParameters() {
+    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.elementWithNameAndArgs(e, 'F', [objectType], true);
+    basicChecks(f,
+        element: same(e),
+        displayName: 'F<Object>',
+        name: 'F',
+        typeArguments: [same(objectType)],
+        typeParameters: [same(t)],
+        returnType: mapOf(objectType, u.type));
+  }
+
+  test_elementWithNameAndArgs_typedef_innerTypeParameter() {
+    var t = new MockTypeParameterElement('T');
+    var e = new MockGenericTypeAliasElement('F',
+        innerTypeParameters: [t], returnType: t.type);
+    FunctionType f =
+        new FunctionTypeImpl.elementWithNameAndArgs(e, 'F', [], true);
+    basicChecks(f,
+        element: same(e),
+        displayName: 'F',
+        name: 'F',
+        returnType: same(t.type));
+  }
+
+  test_elementWithNameAndArgs_typedef_noTypeParameters() {
+    var e = new MockGenericTypeAliasElement('F');
+    FunctionType f =
+        new FunctionTypeImpl.elementWithNameAndArgs(e, 'F', [], true);
+    basicChecks(f, element: same(e), displayName: 'F', name: 'F');
+  }
+
+  test_elementWithNameAndArgs_typedef_outerTypeParameters() {
+    var t = new MockTypeParameterElement('T');
+    var e = new MockGenericTypeAliasElement('F',
+        typeParameters: [t], returnType: t.type);
+    FunctionType f =
+        new FunctionTypeImpl.elementWithNameAndArgs(e, 'F', [objectType], true);
+    basicChecks(f,
+        element: same(e),
+        displayName: 'F<Object>',
+        name: 'F',
+        typeArguments: [same(objectType)],
+        typeParameters: [same(t)],
+        returnType: same(objectType));
+  }
+
+  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 isInstanceOf<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 isInstanceOf<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 isInstanceOf<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 isInstanceOf<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 isInstanceOf<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 isInstanceOf<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_unnamedConstructor() {
+    var e = new MockFunctionTypedElement();
+    FunctionType f = new FunctionTypeImpl(e);
+    basicChecks(f, element: same(e));
+  }
+
+  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));
+    // TODO(paulberry): test instantiate length mismatch
+  }
+
+  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_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_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_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 ClassElement {
+  @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.elementWithNameAndArgs(this, null,
+      enclosingElement.typeParameters.map((e) => e.type).toList(), false);
+
+  @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
+  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 ParameterElement {
+  @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);
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/element/test_all.dart b/pkg/analyzer/test/src/dart/element/test_all.dart
index 2577db8..b2a1b2e 100644
--- a/pkg/analyzer/test/src/dart/element/test_all.dart
+++ b/pkg/analyzer/test/src/dart/element/test_all.dart
@@ -7,10 +7,12 @@
 import 'package:test_reflective_loader/test_reflective_loader.dart';
 
 import 'element_test.dart' as element;
+import 'function_type_test.dart' as function_type;
 
 /// Utility for manually running all tests.
 main() {
   defineReflectiveSuite(() {
     element.main();
+    function_type.main();
   }, name: 'element');
 }