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() {}
+}
+