From 5555f2ba2309b4fd6a11112498abd5122ccdfd90 Mon Sep 17 00:00:00 2001 From: Sam Rawlins Date: Fri, 10 May 2024 09:05:52 -0700 Subject: [PATCH] Fix bug around ref resolution of type mentioned in type arguments --- lib/src/markdown_processor.dart | 33 +++------- lib/src/model/comment_referable.dart | 45 ++++++-------- lib/src/model/method.dart | 18 +++++- .../comment_referable_test.dart | 24 ++------ test/enum_test.dart | 60 +++++++++++++++++++ 5 files changed, 108 insertions(+), 72 deletions(-) diff --git a/lib/src/markdown_processor.dart b/lib/src/markdown_processor.dart index ee574c7d5a..e6405aafeb 100644 --- a/lib/src/markdown_processor.dart +++ b/lib/src/markdown_processor.dart @@ -165,30 +165,15 @@ MatchingLinkResult _getMatchingLinkElement( String referenceText, Warnable element) { var commentReference = ModelCommentReference(referenceText); - // A filter to be used by [CommentReferable.referenceBy]. - bool Function(CommentReferable?) filter; - - // An "allow tree" filter to be used by [CommentReferable.referenceBy]. - bool Function(CommentReferable?) allowTree; - - if (commentReference.hasCallableHint) { - allowTree = (_) => true; - // Trailing parens indicate we are looking for a callable. - filter = _requireCallable; - } else { - allowTree = (_) => true; - // Neither reject, nor require, an unnamed constructor in the event the - // comment reference structure implies one. (We cannot require it in case a - // library name is the same as a member class name and the class is the - // intended lookup). - filter = commentReference.hasCallableHint - ? _requireCallable - // Without hints, reject unnamed constructors and their parameters to - // force resolution to the class. - : filter = _rejectUnnamedAndShadowingConstructors; - } - var lookupResult = element.referenceBy(commentReference.referenceBy, - allowTree: allowTree, filter: filter); + var filter = commentReference.hasCallableHint + // Trailing parens indicate we are looking for a callable. + ? _requireCallable + // Without hints, reject unnamed constructors and their parameters to + // force resolution to the class. + : _rejectUnnamedAndShadowingConstructors; + + var lookupResult = + element.referenceBy(commentReference.referenceBy, filter: filter); // TODO(jcollins-g): Consider prioritizing analyzer resolution before custom. return MatchingLinkResult(lookupResult); diff --git a/lib/src/model/comment_referable.dart b/lib/src/model/comment_referable.dart index 1a5c276e07..ba8fac49d5 100644 --- a/lib/src/model/comment_referable.dart +++ b/lib/src/model/comment_referable.dart @@ -31,7 +31,6 @@ class _ReferenceChildrenLookup { /// Support comment reference lookups on a Nameable object. mixin CommentReferable implements Nameable { - //, ModelBuilderInterface { /// For any [CommentReferable] where an analyzer [Scope] exists (or can /// be constructed), implement this. This will take priority over /// lookups via [referenceChildren]. Can be cached. @@ -39,17 +38,15 @@ mixin CommentReferable implements Nameable { String? get href => null; - /// Look up a comment reference by its component parts. + /// Looks up a comment reference by its component parts. /// /// If [tryParents] is true, try looking up the same reference in any parents /// of `this`. Will skip over results that do not pass a given [filter] and - /// keep searching. Will skip over entire subtrees whose parent node does not - /// pass [allowTree]. + /// keep searching. @nonVirtual CommentReferable? referenceBy( List reference, { required bool Function(CommentReferable?) filter, - required bool Function(CommentReferable?) allowTree, bool tryParents = true, Iterable? parentOverrides, }) { @@ -57,11 +54,9 @@ mixin CommentReferable implements Nameable { if (reference.isEmpty) { return tryParents ? null : this; } - for (var referenceLookup in _childLookups(reference)) { if (scope != null) { - var result = _lookupViaScope(referenceLookup, - filter: filter, allowTree: allowTree); + var result = _lookupViaScope(referenceLookup, filter: filter); if (result != null) { return result; } @@ -69,8 +64,11 @@ mixin CommentReferable implements Nameable { final referenceChildren = this.referenceChildren; final childrenResult = referenceChildren[referenceLookup.lookup]; if (childrenResult != null) { - var result = _recurseChildrenAndFilter(referenceLookup, childrenResult, - allowTree: allowTree, filter: filter); + var result = _recurseChildrenAndFilter( + referenceLookup, + childrenResult, + filter: filter, + ); if (result != null) { return result; } @@ -79,11 +77,12 @@ mixin CommentReferable implements Nameable { // If we can't find it in children, try searching parents if allowed. if (tryParents) { for (var parent in parentOverrides) { - var result = parent.referenceBy(reference, - tryParents: true, - parentOverrides: referenceGrandparentOverrides, - allowTree: allowTree, - filter: filter); + var result = parent.referenceBy( + reference, + tryParents: true, + parentOverrides: referenceGrandparentOverrides, + filter: filter, + ); if (result != null) return result; } } @@ -99,7 +98,6 @@ mixin CommentReferable implements Nameable { CommentReferable? _lookupViaScope( _ReferenceChildrenLookup referenceLookup, { required bool Function(CommentReferable?) filter, - required bool Function(CommentReferable?) allowTree, }) { final resultElement = scope!.lookupPreferGetter(referenceLookup.lookup); if (resultElement == null) return null; @@ -122,9 +120,7 @@ mixin CommentReferable implements Nameable { ); return null; } - if (!allowTree(result)) return null; - return _recurseChildrenAndFilter(referenceLookup, result, - allowTree: allowTree, filter: filter); + return _recurseChildrenAndFilter(referenceLookup, result, filter: filter); } /// Given a [result] found in an implementation of [_lookupViaScope] or @@ -134,19 +130,14 @@ mixin CommentReferable implements Nameable { _ReferenceChildrenLookup referenceLookup, CommentReferable result, { required bool Function(CommentReferable?) filter, - required bool Function(CommentReferable?) allowTree, }) { CommentReferable? returnValue = result; if (referenceLookup.remaining.isNotEmpty) { - if (allowTree(result)) { - returnValue = result.referenceBy(referenceLookup.remaining, - tryParents: false, allowTree: allowTree, filter: filter); - } else { - returnValue = null; - } + returnValue = result.referenceBy(referenceLookup.remaining, + tryParents: false, filter: filter); } else if (!filter(result)) { returnValue = result.referenceBy([referenceLookup.lookup], - tryParents: false, allowTree: allowTree, filter: filter); + tryParents: false, filter: filter); } if (!filter(returnValue)) { returnValue = null; diff --git a/lib/src/model/method.dart b/lib/src/model/method.dart index fd1c530f13..1b5ce53119 100644 --- a/lib/src/model/method.dart +++ b/lib/src/model/method.dart @@ -138,10 +138,24 @@ class Method extends ModelElement return from.referenceChildren; } return _referenceChildren ??= { - ...modelType.returnType.typeArguments.explicitOnCollisionWith(this), - ...modelType.typeArguments.explicitOnCollisionWith(this), + // If we want to include all types referred to in the signature of this + // method, this is woefully incomplete. Notice we don't currently include + // the element of the returned type itself, nor nested type arguments, + // nor other nested types e.g. in the case of function types or record + // types. But this is all being replaced with analyzer's resolution soon. + ...modelType.returnType.typeArguments.modelElements + .explicitOnCollisionWith(this), ...parameters.explicitOnCollisionWith(this), ...typeParameters.explicitOnCollisionWith(this), }; } } + +extension on Iterable { + /// The [ModelElement] associated with each type, for each type that is a + /// [DefinedElementType]. + List get modelElements => [ + for (var type in this) + if (type is DefinedElementType) type.modelElement, + ]; +} diff --git a/test/comment_referable/comment_referable_test.dart b/test/comment_referable/comment_referable_test.dart index e10bbfa7f8..3ebf1efae0 100644 --- a/test/comment_referable/comment_referable_test.dart +++ b/test/comment_referable/comment_referable_test.dart @@ -23,11 +23,11 @@ abstract class Base with Nameable, CommentReferable { /// Returns the added (or already existing) [Base]. Base add(String newName); - CommentReferable? lookup(String value, - {bool Function(CommentReferable?)? allowTree, - bool Function(CommentReferable?)? filter}) { - return referenceBy(value.split(_separator), - allowTree: allowTree ?? (_) => true, filter: filter ?? (_) => true); + CommentReferable? lookup( + String value, { + bool Function(CommentReferable?)? filter, + }) { + return referenceBy(value.split(_separator), filter: filter ?? (_) => true); } @override @@ -165,20 +165,6 @@ void main() { isA()); }); - test('Check that allowTree works', () { - referable.add('lib4'); - var lib4lib4 = referable.add('lib4.lib4'); - var tooDeepSub1 = referable.add('lib4.lib4.sub1'); - var sub1 = referable.add('lib4.sub1'); - var sub2 = referable.add('lib4.sub2'); - expect(sub2.lookup('lib4.lib4'), equals(lib4lib4)); - expect(sub2.lookup('lib4.sub1'), equals(tooDeepSub1)); - expect( - sub2.lookup('lib4.sub1', - allowTree: (r) => r is Base && (r.parent is Top)), - equals(sub1)); - }); - test('Check that grandparent overrides work', () { referable.add('lib4'); var i1 = referable.add('lib4.intermediate1'); diff --git a/test/enum_test.dart b/test/enum_test.dart index 4347cb9ca5..71ca8af6ed 100644 --- a/test/enum_test.dart +++ b/test/enum_test.dart @@ -641,4 +641,64 @@ class C {} 'E.values.

', ); } + + void test_canBeReferenced_whenFoundAsReturnType() async { + var library = await bootPackageWithLibrary(''' +enum E { + one, two +} + +class C { + /// [E] can be referenced. + E m() => E.one; +} +'''); + var m = library.classes.named('C').instanceMethods.named('m'); + + expect(m.hasDocumentationComment, true); + expect( + m.documentationAsHtml, + '

E can be referenced.

', + ); + } + + void test_canBeReferenced_whenFoundAsReturnType_typeArgument() async { + var library = await bootPackageWithLibrary(''' +enum E { + one, two +} + +class C { + /// [E] can be referenced. + Future m() async => E.one; +} +'''); + var m = library.classes.named('C').instanceMethods.named('m'); + + expect(m.hasDocumentationComment, true); + expect( + m.documentationAsHtml, + '

E can be referenced.

', + ); + } + + void test_canBeReferenced_whenFoundAsParameterType() async { + var library = await bootPackageWithLibrary(''' +enum E { + one, two +} + +class C { + /// [E] can be referenced. + void m(E p) {} +} +'''); + var m = library.classes.named('C').instanceMethods.named('m'); + + expect(m.hasDocumentationComment, true); + expect( + m.documentationAsHtml, + '

E can be referenced.

', + ); + } }