Skip to content

Commit fc46f99

Browse files
authored
Improve couldApplyTo and add tests (#2062)
* intermediate -- tests pass * First version of extension tree complete * it works * It actually works, with tests that are debugged * Cleanups * Add test case for implements * Remove extension tree and correct couldApplyTo once more. * Remove debugging code
1 parent 5983455 commit fc46f99

File tree

5 files changed

+165
-42
lines changed

5 files changed

+165
-42
lines changed

lib/src/element_type.dart

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ class TypeParameterElementType extends DefinedElementType {
254254
}
255255
return _nameWithGenerics;
256256
}
257+
258+
@override
259+
ClassElement get _boundClassElement => interfaceType.element;
260+
@override
261+
InterfaceType get interfaceType => (type as TypeParameterType).bound;
257262
}
258263

259264
/// An [ElementType] associated with an [Element].
@@ -311,6 +316,26 @@ abstract class DefinedElementType extends ElementType {
311316
}
312317
return _typeArguments;
313318
}
319+
320+
/// By default, the bound is the type of the declared class.
321+
ClassElement get _boundClassElement => (element.element as ClassElement);
322+
Class get boundClass =>
323+
ModelElement.fromElement(_boundClassElement, packageGraph);
324+
InterfaceType get interfaceType => type;
325+
326+
InterfaceType _instantiatedType;
327+
328+
/// Return this type, instantiated to bounds if it isn't already.
329+
DartType get instantiatedType {
330+
if (_instantiatedType == null) {
331+
if (!interfaceType.typeArguments.every((t) => t is InterfaceType)) {
332+
_instantiatedType = packageGraph.typeSystem.instantiateToBounds(interfaceType);
333+
} else {
334+
_instantiatedType = interfaceType;
335+
}
336+
}
337+
return _instantiatedType;
338+
}
314339
}
315340

316341
/// Any callable ElementType will mix-in this class, whether anonymous or not.
@@ -446,8 +471,8 @@ class CallableGenericTypeAliasElementType extends ParameterizedElementType
446471
@override
447472
ElementType get returnType {
448473
if (_returnType == null) {
449-
_returnType = ElementType.from(
450-
type.returnType, library, packageGraph, this);
474+
_returnType =
475+
ElementType.from(type.returnType, library, packageGraph, this);
451476
}
452477
return _returnType;
453478
}

lib/src/markdown_processor.dart

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,13 @@ MatchingLinkResult _getMatchingLinkElement(
206206
if (refModelElement == null && element is ModelElement) {
207207
Container preferredClass = _getPreferredClass(element);
208208
if (preferredClass is Extension) {
209-
element.warn(PackageWarning.notImplemented, message: 'Comment reference resolution inside extension methods is not yet implemented');
209+
element.warn(PackageWarning.notImplemented,
210+
message:
211+
'Comment reference resolution inside extension methods is not yet implemented');
210212
} else {
211-
refModelElement =
212-
_MarkdownCommentReference(codeRef, element, commentRefs, preferredClass)
213-
.computeReferredElement();
213+
refModelElement = _MarkdownCommentReference(
214+
codeRef, element, commentRefs, preferredClass)
215+
.computeReferredElement();
214216
}
215217
}
216218

@@ -649,8 +651,8 @@ class _MarkdownCommentReference {
649651
Inheritable overriddenElement =
650652
(element as Inheritable).overriddenElement;
651653
while (overriddenElement != null) {
652-
tryClasses.add(
653-
(element as Inheritable).overriddenElement.enclosingElement);
654+
tryClasses
655+
.add((element as Inheritable).overriddenElement.enclosingElement);
654656
overriddenElement = overriddenElement.overriddenElement;
655657
}
656658
}
@@ -669,7 +671,7 @@ class _MarkdownCommentReference {
669671

670672
if (results.isEmpty && realClass != null) {
671673
for (Class superClass
672-
in realClass.publicSuperChain.map((et) => et.element as Class)) {
674+
in realClass.publicSuperChain.map((et) => et.element)) {
673675
if (!tryClasses.contains(superClass)) {
674676
_getResultsForClass(superClass);
675677
}
@@ -719,12 +721,12 @@ class _MarkdownCommentReference {
719721
// TODO(jcollins-g): get rid of reimplementation of identifier resolution
720722
// or integrate into ModelElement in a simpler way.
721723
List<Class> superChain = [tryClass];
722-
superChain.addAll(tryClass.interfaces.map((t) => t.element as Class));
724+
superChain.addAll(tryClass.interfaces.map((t) => t.element));
723725
// This seems duplicitous with our caller, but the preferredClass
724726
// hint matters with findCanonicalModelElementFor.
725727
// TODO(jcollins-g): This makes our caller ~O(n^2) vs length of superChain.
726728
// Fortunately superChains are short, but optimize this if it matters.
727-
superChain.addAll(tryClass.superChain.map((t) => t.element as Class));
729+
superChain.addAll(tryClass.superChain.map((t) => t.element));
728730
for (final c in superChain) {
729731
_getResultsForSuperChainElement(c, tryClass);
730732
if (results.isNotEmpty) break;
@@ -778,7 +780,9 @@ String _linkDocReference(String codeRef, Warnable warnable,
778780
// current element.
779781
warnable.warn(PackageWarning.unresolvedDocReference,
780782
message: codeRef,
781-
referredFrom: warnable.documentationIsLocal ? null : warnable.documentationFrom);
783+
referredFrom: warnable.documentationIsLocal
784+
? null
785+
: warnable.documentationFrom);
782786
}
783787
return '<code>${htmlEscape.convert(codeRef)}</code>';
784788
}

lib/src/model.dart

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
library dartdoc.models;
77

88
import 'dart:async';
9-
import 'dart:collection' show UnmodifiableListView;
9+
import 'dart:collection' show HashSet, UnmodifiableListView;
1010
import 'dart:convert';
1111
import 'dart:io';
1212

@@ -22,7 +22,6 @@ import 'package:analyzer/dart/ast/ast.dart'
2222
InstanceCreationExpression;
2323
import 'package:analyzer/dart/element/element.dart';
2424
import 'package:analyzer/dart/element/type.dart';
25-
import 'package:analyzer/dart/element/type_system.dart';
2625
import 'package:analyzer/dart/element/visitor.dart';
2726
import 'package:analyzer/file_system/file_system.dart' as file_system;
2827
import 'package:analyzer/file_system/physical_file_system.dart';
@@ -36,7 +35,6 @@ import 'package:analyzer/src/dart/element/element.dart';
3635
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
3736
import 'package:analyzer/src/dart/element/member.dart'
3837
show ExecutableMember, Member, ParameterMember;
39-
import 'package:analyzer/src/dart/element/type.dart' show InterfaceTypeImpl;
4038
import 'package:analyzer/src/dart/sdk/sdk.dart';
4139
import 'package:analyzer/src/generated/engine.dart';
4240
import 'package:analyzer/src/generated/java_io.dart';
@@ -805,7 +803,8 @@ class Class extends Container
805803
_potentiallyApplicableExtensions = utils
806804
.filterNonDocumented(packageGraph.extensions)
807805
.where((e) => e.couldApplyTo(this))
808-
.toList(growable: false);
806+
.toList(growable: false)
807+
..sort(byName);
809808
}
810809
return _potentiallyApplicableExtensions;
811810
}
@@ -1344,26 +1343,44 @@ class Extension extends Container
13441343
ElementType.from(_extension.extendedType, library, packageGraph);
13451344
}
13461345

1347-
/// Returns [true] if there is an instantiation of [c] to which this extension
1348-
/// could be applied.
1349-
bool couldApplyTo(Class c) =>
1350-
_couldApplyTo(extendedType.type, c.element, packageGraph.typeSystem);
1351-
1352-
static bool _couldApplyTo(
1353-
DartType extendedType, ClassElement element, Dart2TypeSystem typeSystem) {
1354-
InterfaceTypeImpl classInstantiated =
1355-
typeSystem.instantiateToBounds(element.thisType);
1356-
classInstantiated = element.instantiate(
1357-
typeArguments: classInstantiated.typeArguments.map((a) {
1358-
if (a.isDynamic) {
1359-
return typeSystem.typeProvider.neverType;
1360-
}
1361-
return a;
1362-
}).toList(),
1363-
nullabilitySuffix: classInstantiated.nullabilitySuffix);
1364-
1365-
return (classInstantiated.element == extendedType.element) ||
1366-
typeSystem.isSubtypeOf(classInstantiated, extendedType);
1346+
bool couldApplyTo(Class c) => _couldApplyTo(c.modelType);
1347+
1348+
/// Return true if this extension could apply to [t].
1349+
bool _couldApplyTo(DefinedElementType t) {
1350+
return t.instantiatedType == extendedType.instantiatedType ||
1351+
(t.instantiatedType.element == extendedType.instantiatedType.element &&
1352+
isSubtypeOf(t)) ||
1353+
isBoundSupertypeTo(t);
1354+
}
1355+
1356+
/// The instantiated to bounds [extendedType] of this extension is a subtype of
1357+
/// [t].
1358+
bool isSubtypeOf(DefinedElementType t) => packageGraph.typeSystem
1359+
.isSubtypeOf(extendedType.instantiatedType, t.instantiatedType);
1360+
1361+
bool isBoundSupertypeTo(DefinedElementType t) =>
1362+
_isBoundSupertypeTo(t.type, HashSet());
1363+
1364+
/// Returns true if at least one supertype (including via mixins and
1365+
/// interfaces) is equivalent to or a subtype of [extendedType] when
1366+
/// instantiated to bounds.
1367+
bool _isBoundSupertypeTo(
1368+
InterfaceType superType, HashSet<InterfaceType> visited) {
1369+
ClassElement superClass = superType?.element;
1370+
if (visited.contains(superType)) return false;
1371+
visited.add(superType);
1372+
if (superClass == extendedType.type.element &&
1373+
(superType == extendedType.instantiatedType ||
1374+
packageGraph.typeSystem
1375+
.isSubtypeOf(superType, extendedType.instantiatedType))) {
1376+
return true;
1377+
}
1378+
List<InterfaceType> supertypes = [];
1379+
ClassElementImpl.collectAllSupertypes(supertypes, superType, null);
1380+
for (InterfaceType toVisit in supertypes) {
1381+
if (_isBoundSupertypeTo(toVisit, visited)) return true;
1382+
}
1383+
return false;
13671384
}
13681385

13691386
@override
@@ -1418,7 +1435,7 @@ class Extension extends Container
14181435
}
14191436

14201437
@override
1421-
DefinedElementType get modelType => super.modelType;
1438+
ParameterizedElementType get modelType => super.modelType;
14221439

14231440
List<ModelElement> _allModelElements;
14241441

@@ -5062,13 +5079,11 @@ class PackageGraph {
50625079
package._libraries.sort((a, b) => compareNatural(a.name, b.name));
50635080
package._libraries.forEach((library) {
50645081
library.allClasses.forEach(_addToImplementors);
5065-
// TODO(jcollins-g): Use a better data structure.
50665082
_extensions.addAll(library.extensions);
50675083
});
50685084
});
50695085
_implementors.values.forEach((l) => l.sort());
50705086
allImplementorsAdded = true;
5071-
_extensions.sort(byName);
50725087
allExtensionsAdded = true;
50735088

50745089
// We should have found all special classes by now.
@@ -5179,8 +5194,7 @@ class PackageGraph {
51795194
final Map<String, List<Class>> _implementors = Map();
51805195

51815196
/// A list of extensions that exist in the package graph.
5182-
// TODO(jcollins-g): Consider implementing a smarter structure for this.
5183-
final List<Extension> _extensions = List();
5197+
final List<Extension> _extensions = [];
51845198

51855199
/// PackageMeta for the default package.
51865200
final PackageMeta packageMeta;
@@ -5212,7 +5226,7 @@ class PackageGraph {
52125226
/// TODO(brianwilkerson) Replace the driver with the session.
52135227
final AnalysisDriver driver;
52145228
final AnalysisSession session;
5215-
final TypeSystem typeSystem;
5229+
final Dart2TypeSystem typeSystem;
52165230
final DartSdk sdk;
52175231

52185232
Map<Source, SdkLibrary> _sdkLibrarySources;

test/model_test.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,43 @@ void main() {
21992199
orderedEquals([uphill]));
22002200
});
22012201

2202+
test('applicableExtensions include those from implements & mixins', () {
2203+
Extension extensionCheckLeft,
2204+
extensionCheckRight,
2205+
extensionCheckCenter,
2206+
extensionCheckImplementor2,
2207+
onNewSchool,
2208+
onOldSchool;
2209+
Class implementor, implementor2, school;
2210+
Extension getExtension(String name) =>
2211+
fakeLibrary.extensions.firstWhere((e) => e.name == name);
2212+
Class getClass(String name) =>
2213+
fakeLibrary.classes.firstWhere((e) => e.name == name);
2214+
extensionCheckLeft = getExtension('ExtensionCheckLeft');
2215+
extensionCheckRight = getExtension('ExtensionCheckRight');
2216+
extensionCheckCenter = getExtension('ExtensionCheckCenter');
2217+
extensionCheckImplementor2 = getExtension('ExtensionCheckImplementor2');
2218+
onNewSchool = getExtension('OnNewSchool');
2219+
onOldSchool = getExtension('OnOldSchool');
2220+
2221+
implementor = getClass('Implementor');
2222+
implementor2 = getClass('Implementor2');
2223+
school = getClass('School');
2224+
2225+
expect(
2226+
implementor.potentiallyApplicableExtensions,
2227+
orderedEquals([
2228+
extensionCheckCenter,
2229+
extensionCheckImplementor2,
2230+
extensionCheckLeft,
2231+
extensionCheckRight
2232+
]));
2233+
expect(implementor2.potentiallyApplicableExtensions,
2234+
orderedEquals([extensionCheckImplementor2, extensionCheckLeft]));
2235+
expect(school.potentiallyApplicableExtensions,
2236+
orderedEquals([onNewSchool, onOldSchool]));
2237+
});
2238+
22022239
test('type parameters and bounds work with applicableExtensions', () {
22032240
expect(
22042241
superMegaTron.potentiallyApplicableExtensions, orderedEquals([leg]));

testing/test_package/lib/fake.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,49 @@ class AnotherExtended<T extends BaseTest> extends BaseTest {}
10181018

10191019
class BigAnotherExtended extends AnotherExtended<SubclassBaseTest> {}
10201020

1021+
extension ExtensionCheckLeft on Implemented1 {
1022+
int get left => 3;
1023+
}
1024+
1025+
extension ExtensionCheckRight on Implemented2 {
1026+
int get right => 4;
1027+
}
1028+
1029+
extension ExtensionCheckCenter on BaseExtended {
1030+
bool get center => true;
1031+
}
1032+
1033+
class BaseExtended {}
1034+
1035+
class Implemented1 {}
1036+
1037+
class Implemented2 {}
1038+
1039+
class Implementor extends BaseExtended
1040+
implements Implemented1, Implemented2, Implementor2 {}
1041+
1042+
extension ExtensionCheckImplementor2 on Implementor2 {
1043+
int get test => 1;
1044+
}
1045+
1046+
class Implementor2 implements Implemented1 {}
1047+
1048+
class OldSchoolMixin {
1049+
aThing() {}
1050+
}
1051+
1052+
mixin NewSchoolMixin {}
1053+
1054+
extension OnNewSchool on NewSchoolMixin {
1055+
String get bar => 'hello';
1056+
}
1057+
1058+
extension OnOldSchool on OldSchoolMixin {
1059+
int get foo => 4;
1060+
}
1061+
1062+
class School with OldSchoolMixin, NewSchoolMixin {}
1063+
10211064
//
10221065
//
10231066
//

0 commit comments

Comments
 (0)