Allow FunctionTypes to work in place of InterfaceType in applicability check (#2109)

* Fix FunctionTypeImpl crash

* Add bug number to comment

* Remove unnecessary comment

* Add TODO for bad cast
diff --git a/lib/src/element_type.dart b/lib/src/element_type.dart
index 3e9e618..86df0c7 100644
--- a/lib/src/element_type.dart
+++ b/lib/src/element_type.dart
@@ -205,10 +205,11 @@
   String get nameWithGenerics => name;
 
   @override
-  ClassElement get _boundClassElement => interfaceType.element;
+  ClassElement get _boundClassElement => type.element;
 
   @override
-  InterfaceType get interfaceType => (type as TypeParameterType).bound;
+  // TODO(jcollins-g): This is wrong; bound is not always an InterfaceType.
+  InterfaceType get _interfaceType => (type as TypeParameterType).bound;
 }
 
 /// An [ElementType] associated with an [Element].
@@ -271,18 +272,19 @@
   ClassElement get _boundClassElement => (element.element as ClassElement);
   Class get boundClass =>
       ModelElement.fromElement(_boundClassElement, packageGraph);
-  InterfaceType get interfaceType => type;
+
+  InterfaceType get _interfaceType => type;
 
   InterfaceType _instantiatedType;
 
   /// Return this type, instantiated to bounds if it isn't already.
   DartType get instantiatedType {
     if (_instantiatedType == null) {
-      if (!interfaceType.typeArguments.every((t) => t is InterfaceType)) {
+      if (!_interfaceType.typeArguments.every((t) => t is InterfaceType)) {
         var typeSystem = library.element.typeSystem as TypeSystemImpl;
-        _instantiatedType = typeSystem.instantiateToBounds(interfaceType);
+        _instantiatedType = typeSystem.instantiateToBounds(_interfaceType);
       } else {
-        _instantiatedType = interfaceType;
+        _instantiatedType = _interfaceType;
       }
     }
     return _instantiatedType;
@@ -413,4 +415,7 @@
     }
     return _returnType;
   }
+
+  @override
+  DartType get instantiatedType => type;
 }
diff --git a/lib/src/model/class.dart b/lib/src/model/class.dart
index a647d29..cf9d1d5 100644
--- a/lib/src/model/class.dart
+++ b/lib/src/model/class.dart
@@ -5,12 +5,13 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
 import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/extension_target.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart' as model_utils;
 import 'package:quiver/iterables.dart' as quiver;
 
 class Class extends Container
-    with TypeParameters, Categorization
+    with TypeParameters, Categorization, ExtensionTarget
     implements EnclosedElement {
   List<DefinedElementType> mixins;
   DefinedElementType supertype;
@@ -51,22 +52,6 @@
     return _defaultConstructor;
   }
 
-  bool get hasPotentiallyApplicableExtensions =>
-      potentiallyApplicableExtensions.isNotEmpty;
-
-  List<Extension> _potentiallyApplicableExtensions;
-
-  Iterable<Extension> get potentiallyApplicableExtensions {
-    if (_potentiallyApplicableExtensions == null) {
-      _potentiallyApplicableExtensions = model_utils
-          .filterNonDocumented(packageGraph.extensions)
-          .where((e) => e.couldApplyTo(this))
-          .toList(growable: false)
-            ..sort(byName);
-    }
-    return _potentiallyApplicableExtensions;
-  }
-
   Iterable<Method> get allInstanceMethods =>
       quiver.concat([instanceMethods, inheritedMethods]);
 
@@ -223,6 +208,7 @@
 
   bool get hasPublicMixins => publicMixins.isNotEmpty;
 
+  @override
   bool get hasModifiers =>
       hasPublicMixins ||
       hasAnnotations ||
diff --git a/lib/src/model/extension.dart b/lib/src/model/extension.dart
index dd9bd06..546eb08 100644
--- a/lib/src/model/extension.dart
+++ b/lib/src/model/extension.dart
@@ -8,6 +8,7 @@
 import 'package:analyzer/dart/element/type.dart';
 import 'package:analyzer/src/dart/element/element.dart';
 import 'package:dartdoc/src/element_type.dart';
+import 'package:dartdoc/src/model/extension_target.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:quiver/iterables.dart' as quiver;
 
@@ -24,7 +25,8 @@
         ElementType.from(_extension.extendedType, library, packageGraph);
   }
 
-  bool couldApplyTo(Class c) => _couldApplyTo(c.modelType);
+  bool couldApplyTo<T extends ExtensionTarget>(T c) =>
+      _couldApplyTo(c.modelType);
 
   /// Return true if this extension could apply to [t].
   bool _couldApplyTo(DefinedElementType t) {
@@ -40,13 +42,14 @@
       .isSubtypeOf(extendedType.instantiatedType, t.instantiatedType);
 
   bool isBoundSupertypeTo(DefinedElementType t) =>
-      _isBoundSupertypeTo(t.type, HashSet());
+      _isBoundSupertypeTo(t.instantiatedType, HashSet());
 
   /// Returns true if at least one supertype (including via mixins and
   /// interfaces) is equivalent to or a subtype of [extendedType] when
   /// instantiated to bounds.
-  bool _isBoundSupertypeTo(
-      InterfaceType superType, HashSet<InterfaceType> visited) {
+  bool _isBoundSupertypeTo(DartType superType, HashSet<DartType> visited) {
+    // Only InterfaceTypes can have superTypes.
+    if (superType is! InterfaceType) return false;
     ClassElement superClass = superType?.element;
     if (visited.contains(superType)) return false;
     visited.add(superType);
diff --git a/lib/src/model/extension_target.dart b/lib/src/model/extension_target.dart
new file mode 100644
index 0000000..a88c0e5
--- /dev/null
+++ b/lib/src/model/extension_target.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2020, 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:dartdoc/src/model/model.dart';
+
+// TODO(jcollins-g): Mix-in ExtensionTarget on Method, ModelFunction, Typedef,
+// and other possible documented symbols that could be extended (#2701).
+mixin ExtensionTarget on ModelElement {
+  bool get hasModifiers;
+
+  bool get hasPotentiallyApplicableExtensions =>
+      potentiallyApplicableExtensions.isNotEmpty;
+
+  List<Extension> _potentiallyApplicableExtensions;
+
+  Iterable<Extension> get potentiallyApplicableExtensions {
+    if (_potentiallyApplicableExtensions == null) {
+      _potentiallyApplicableExtensions = packageGraph.documentedExtensions
+          .where((e) => e.couldApplyTo(this))
+          .toList(growable: false)
+            ..sort(byName);
+    }
+    return _potentiallyApplicableExtensions;
+  }
+}
diff --git a/lib/src/model/package_graph.dart b/lib/src/model/package_graph.dart
index 83043bb..cc8d51d 100644
--- a/lib/src/model/package_graph.dart
+++ b/lib/src/model/package_graph.dart
@@ -152,13 +152,21 @@
     return _implementors;
   }
 
+  List<Extension> _documentedExtensions;
+  Iterable<Extension> get documentedExtensions {
+    if (_documentedExtensions == null) {
+      _documentedExtensions =
+          utils.filterNonDocumented(extensions).toList(growable: false);
+    }
+    return _documentedExtensions;
+  }
+
   Iterable<Extension> get extensions {
     assert(allExtensionsAdded);
     return _extensions;
   }
 
   Map<String, Set<ModelElement>> _findRefElementCache;
-
   Map<String, Set<ModelElement>> get findRefElementCache {
     if (_findRefElementCache == null) {
       assert(packageGraph.allLibrariesAdded);
diff --git a/testing/test_package/lib/fake.dart b/testing/test_package/lib/fake.dart
index a195bfb..94ff87e 100644
--- a/testing/test_package/lib/fake.dart
+++ b/testing/test_package/lib/fake.dart
@@ -1117,3 +1117,18 @@
     print('hello, tool world');
   }
 }
+
+/*
+ * Complex extension methods case.
+ *
+ * TODO(jcollins-g): add unit tests around behavior when #2701 is implemented.
+ * Until #2701 is fixed we mostly are testing that we don't crash because
+ * DoSomething2X is declared.
+ */
+
+typedef R Function1<A, R>(A a);
+typedef R Function2<A, B, R>(A a, B b);
+
+extension DoSomething2X<A, B, R> on Function1<A, Function1<B, R>> {
+  Function2<A, B, R> something() => (A first, B second) => this(first)(second);
+}
\ No newline at end of file