Implement basic handling for extensions on special types (#2112)

* Fix FunctionTypeImpl crash

* Add bug number to comment

* Remove unnecessary comment

* Add TODO for bad cast

* First attempt to fix crash

* Clean up for always-applying extensions

* Double timeout factor on generator test
diff --git a/lib/src/element_type.dart b/lib/src/element_type.dart
index 86df0c7..9d9bb3c 100644
--- a/lib/src/element_type.dart
+++ b/lib/src/element_type.dart
@@ -10,6 +10,7 @@
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/element/element.dart' show ClassElementImpl;
 import 'package:analyzer/src/generated/type_system.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/render/element_type_renderer.dart';
@@ -289,6 +290,36 @@
     }
     return _instantiatedType;
   }
+
+  /// The instantiated to bounds type of this type is a subtype of
+  /// [t].
+  bool isSubtypeOf(DefinedElementType t) =>
+      library.typeSystem.isSubtypeOf(instantiatedType, t.instantiatedType);
+
+  /// Returns true if at least one supertype (including via mixins and
+  /// interfaces) is equivalent to or a subtype of [this] when
+  /// instantiated to bounds.
+  bool isBoundSupertypeTo(DefinedElementType t) =>
+      _isBoundSupertypeTo(t.instantiatedType, HashSet());
+
+  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);
+    if (superClass == type.element &&
+        (superType == instantiatedType ||
+            library.typeSystem.isSubtypeOf(superType, instantiatedType))) {
+      return true;
+    }
+    List<InterfaceType> supertypes = [];
+    ClassElementImpl.collectAllSupertypes(supertypes, superType, null);
+    for (InterfaceType toVisit in supertypes) {
+      if (_isBoundSupertypeTo(toVisit, visited)) return true;
+    }
+    return false;
+  }
 }
 
 /// Any callable ElementType will mix-in this class, whether anonymous or not.
diff --git a/lib/src/model/extension.dart b/lib/src/model/extension.dart
index 546eb08..457a208 100644
--- a/lib/src/model/extension.dart
+++ b/lib/src/model/extension.dart
@@ -2,11 +2,7 @@
 // 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 'dart:collection';
-
 import 'package:analyzer/dart/element/element.dart';
-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';
@@ -16,7 +12,7 @@
 class Extension extends Container
     with TypeParameters, Categorization
     implements EnclosedElement {
-  DefinedElementType extendedType;
+  ElementType extendedType;
 
   Extension(
       ExtensionElement element, Library library, PackageGraph packageGraph)
@@ -25,46 +21,29 @@
         ElementType.from(_extension.extendedType, library, packageGraph);
   }
 
+  /// Detect if this extension applies to every object.
+  bool get alwaysApplies =>
+      extendedType.type.isDynamic ||
+      extendedType.type.isVoid ||
+      extendedType.type.isObject;
+
   bool couldApplyTo<T extends ExtensionTarget>(T c) =>
       _couldApplyTo(c.modelType);
 
   /// Return true if this extension could apply to [t].
   bool _couldApplyTo(DefinedElementType t) {
-    return t.instantiatedType == extendedType.instantiatedType ||
-        (t.instantiatedType.element == extendedType.instantiatedType.element &&
-            isSubtypeOf(t)) ||
-        isBoundSupertypeTo(t);
-  }
-
-  /// The instantiated to bounds [extendedType] of this extension is a subtype of
-  /// [t].
-  bool isSubtypeOf(DefinedElementType t) => library.typeSystem
-      .isSubtypeOf(extendedType.instantiatedType, t.instantiatedType);
-
-  bool isBoundSupertypeTo(DefinedElementType t) =>
-      _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(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);
-    if (superClass == extendedType.type.element &&
-        (superType == extendedType.instantiatedType ||
-            library.typeSystem
-                .isSubtypeOf(superType, extendedType.instantiatedType))) {
+    if (extendedType is UndefinedElementType) {
+      assert(extendedType.type.isDynamic || extendedType.type.isVoid);
       return true;
     }
-    List<InterfaceType> supertypes = [];
-    ClassElementImpl.collectAllSupertypes(supertypes, superType, null);
-    for (InterfaceType toVisit in supertypes) {
-      if (_isBoundSupertypeTo(toVisit, visited)) return true;
+    {
+      DefinedElementType extendedType = this.extendedType;
+      return t.instantiatedType == extendedType.instantiatedType ||
+          (t.instantiatedType.element ==
+                  extendedType.instantiatedType.element &&
+              extendedType.isSubtypeOf(t)) ||
+          extendedType.isBoundSupertypeTo(t);
     }
-    return false;
   }
 
   @override
diff --git a/lib/src/model/extension_target.dart b/lib/src/model/extension_target.dart
index a88c0e5..a6fa138 100644
--- a/lib/src/model/extension_target.dart
+++ b/lib/src/model/extension_target.dart
@@ -14,9 +14,15 @@
 
   List<Extension> _potentiallyApplicableExtensions;
 
+  /// The set of potentiallyApplicableExtensions, for display in templates.
+  ///
+  /// This is defined as those extensions where an instantiation of the type
+  /// defined by [element] can exist where this extension applies, not including
+  /// any extension that applies to every type.
   Iterable<Extension> get potentiallyApplicableExtensions {
     if (_potentiallyApplicableExtensions == null) {
       _potentiallyApplicableExtensions = packageGraph.documentedExtensions
+          .where((e) => !e.alwaysApplies)
           .where((e) => e.couldApplyTo(this))
           .toList(growable: false)
             ..sort(byName);
diff --git a/test/html_generator_test.dart b/test/html_generator_test.dart
index 6cfdee0..460c5dc 100644
--- a/test/html_generator_test.dart
+++ b/test/html_generator_test.dart
@@ -123,7 +123,7 @@
             packageGraph.localPublicLibraries,
             anyElement((l) => packageGraph.packageWarningCounter
                 .hasWarning(l, PackageWarning.duplicateFile, expectedPath)));
-      }, timeout: Timeout.factor(2));
+      }, timeout: Timeout.factor(4));
     });
   });
 }
diff --git a/test/model_test.dart b/test/model_test.dart
index c044f03..c1da301 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -13,6 +13,7 @@
 import 'package:dartdoc/src/render/model_element_renderer.dart';
 import 'package:dartdoc/src/render/parameter_renderer.dart';
 import 'package:dartdoc/src/render/typedef_renderer.dart';
+import 'package:dartdoc/src/special_elements.dart';
 import 'package:dartdoc/src/warnings.dart';
 import 'package:test/test.dart';
 
@@ -1865,6 +1866,30 @@
           orderedEquals([uphill]));
     });
 
+    test('extensions on special types work', () {
+      Extension extensionOnDynamic, extensionOnVoid, extensionOnNull;
+      Class object = packageGraph.specialClasses[SpecialClass.object];
+      Extension getExtension(String name) =>
+          fakeLibrary.extensions.firstWhere((e) => e.name == name);
+
+      extensionOnDynamic = getExtension('ExtensionOnDynamic');
+      extensionOnNull = getExtension('ExtensionOnNull');
+      extensionOnVoid = getExtension('ExtensionOnVoid');
+
+      expect(extensionOnDynamic.couldApplyTo(object), isTrue);
+      expect(extensionOnVoid.couldApplyTo(object), isTrue);
+      expect(extensionOnNull.couldApplyTo(object), isFalse);
+
+      expect(extensionOnDynamic.alwaysApplies, isTrue);
+      expect(extensionOnVoid.alwaysApplies, isTrue);
+      expect(extensionOnNull.alwaysApplies, isFalse);
+
+      // Even though it does have extensions that could apply to it,
+      // extensions that apply to [Object] should always be hidden from
+      // documentation.
+      expect(object.hasPotentiallyApplicableExtensions, isFalse);
+    });
+
     test('applicableExtensions include those from implements & mixins', () {
       Extension extensionCheckLeft,
           extensionCheckRight,
diff --git a/testing/test_package/lib/fake.dart b/testing/test_package/lib/fake.dart
index 97a4795..9f1586d 100644
--- a/testing/test_package/lib/fake.dart
+++ b/testing/test_package/lib/fake.dart
@@ -1123,7 +1123,7 @@
 }
 
 /*
- * Complex extension methods case.
+ * Complex extension methods + typedefs 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
@@ -1135,4 +1135,19 @@
 
 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
+}
+
+
+/// Extensions might exist on types defined by the language.
+extension ExtensionOnDynamic on dynamic {
+  void youCanAlwaysCallMe() {}
+}
+
+extension ExtensionOnVoid on void {
+  void youCanStillAlwaysCallMe() {}
+}
+
+extension ExtensionOnNull on Null {
+  void youCanOnlyCallMeOnNulls() {}
+}
+