Skip to content

Basic implementation of typedef member lookups #2768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/src/model/model_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,10 @@ abstract class ModelElement extends Canonicalization
if (e.aliasedType is FunctionType) {
return FunctionTypedef(e, library, packageGraph);
}
return Typedef(e, library, packageGraph);
if (e.aliasedType.element is ClassElement) {
return ClassTypedef(e, library, packageGraph);
}
return GeneralizedTypedef(e, library, packageGraph);
}
if (e is ConstructorElement) {
return Constructor(e, library, packageGraph);
Expand Down
61 changes: 51 additions & 10 deletions lib/src/model/typedef.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:dartdoc/src/model/comment_referable.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/render/typedef_renderer.dart';

class Typedef extends ModelElement
abstract class Typedef extends ModelElement
with TypeParameters, Categorization
implements EnclosedElement {
Typedef(TypeAliasElement element, Library library, PackageGraph packageGraph)
Expand Down Expand Up @@ -40,6 +40,8 @@ class Typedef extends ModelElement
@override
String get filePath => '${library.dirName}/$fileName';

/// Helper for mustache templates, which can't do casting themselves
/// without this.
FunctionTypedef get asCallable => this as FunctionTypedef;

@override
Expand All @@ -65,34 +67,73 @@ class Typedef extends ModelElement

TypedefRenderer get _renderer => packageGraph.rendererFactory.typedefRenderer;

Map<String, CommentReferable> _referenceChildren;
@override
Iterable<CommentReferable> get referenceParents => [definingLibrary];

Map<String, CommentReferable> _referenceChildren;
@override
Map<String, CommentReferable> get referenceChildren {
if (_referenceChildren == null) {
_referenceChildren = {};

// Only consider parameters if this is a function typedef.
if (isCallable) {
_referenceChildren
.addEntriesIfAbsent(parameters.explicitOnCollisionWith(this));
}
_referenceChildren
.addEntriesIfAbsent(typeParameters.explicitOnCollisionWith(this));
}
return _referenceChildren;
}
}

/// A typedef referring to a non-function typedef that is nevertheless not
/// referring to a defined class. An example is a typedef alias for `void` or
/// for `Function` itself.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeesh, bizarre cases.

class GeneralizedTypedef extends Typedef {
GeneralizedTypedef(
TypeAliasElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph) {
assert(!isCallable);
}
}

/// A typedef referring to a non-function, defined type.
class ClassTypedef extends Typedef {
ClassTypedef(
TypeAliasElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph) {
assert(!isCallable);
assert(modelType.modelElement is Class);
}

@override
Iterable<CommentReferable> get referenceParents => [definingLibrary];
DefinedElementType get modelType => super.modelType;

@override
Map<String, CommentReferable> get referenceChildren {
if (_referenceChildren == null) {
_referenceChildren = super.referenceChildren;
_referenceChildren
.addEntriesIfAbsent(modelType.modelElement.referenceChildren.entries);
}
return _referenceChildren;
}
}

/// A typedef referring to a function type.
class FunctionTypedef extends Typedef {
FunctionTypedef(
TypeAliasElement element, Library library, PackageGraph packageGraph)
: super(element, library, packageGraph);
: super(element, library, packageGraph) {
assert(isCallable);
}

@override
Callable get modelType => super.modelType;

@override
Map<String, CommentReferable> get referenceChildren {
if (_referenceChildren == null) {
_referenceChildren = super.referenceChildren;
_referenceChildren
.addEntriesIfAbsent(parameters.explicitOnCollisionWith(this));
}
return _referenceChildren;
}
}
17 changes: 17 additions & 0 deletions test/end2end/model_special_cases_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ void main() {
expect(referenceLookup(constructorTearoffs, 'F.new'),
equals(MatchingLinkResult(Fnew)));
});

test('.new works on typedefs', () {
expect(referenceLookup(constructorTearoffs, 'At.new'),
equals(MatchingLinkResult(Anew)));
expect(referenceLookup(constructorTearoffs, 'Bt.new'),
equals(MatchingLinkResult(Bnew)));
expect(referenceLookup(constructorTearoffs, 'Ct.new'),
equals(MatchingLinkResult(Cnew)));
expect(referenceLookup(constructorTearoffs, 'Dt.new'),
equals(MatchingLinkResult(Dnew)));
expect(referenceLookup(constructorTearoffs, 'Et.new'),
equals(MatchingLinkResult(Enew)));
expect(referenceLookup(constructorTearoffs, 'Fstring.new'),
equals(MatchingLinkResult(Fnew)));
expect(referenceLookup(constructorTearoffs, 'Ft.new'),
equals(MatchingLinkResult(Fnew)));
});
}, skip: !_constructorTearoffsAllowed.allows(utils.platformVersion));
});

Expand Down
11 changes: 10 additions & 1 deletion test/end2end/model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2236,7 +2236,8 @@ void main() {
group('Linking for generalized typedef cases works', () {
Library generalizedTypedefs;
Typedef T0, T2, T5, T8;
Class C2;
Class C1, C2;
Field C1a;

setUpAll(() {
generalizedTypedefs = packageGraph.libraries
Expand All @@ -2245,7 +2246,9 @@ void main() {
T2 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T2');
T5 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T5');
T8 = generalizedTypedefs.typedefs.firstWhere((a) => a.name == 'T8');
C1 = generalizedTypedefs.classes.firstWhere((c) => c.name == 'C1');
C2 = generalizedTypedefs.classes.firstWhere((c) => c.name == 'C2');
C1a = C1.allFields.firstWhere((f) => f.name == 'a');
});

test('Verify basic ability to link anything', () {
Expand All @@ -2266,6 +2269,12 @@ void main() {
var T5name = T5.parameters.firstWhere((t) => t.name == 'name');
expect(referenceLookup(T5, 'name'), equals(MatchingLinkResult(T5name)));
});

test('Verify ability to link to class members of aliased classes', () {
expect(referenceLookup(generalizedTypedefs, 'T8.a'),
equals(MatchingLinkResult(C1a)));
expect(referenceLookup(T8, 'a'), equals(MatchingLinkResult(C1a)));
});
});

group('Linking for complex inheritance and reexport cases', () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ typedef NotAClass = Function;

/// Mixins don't have constructors either, so disallow `M.new`.
mixin M<T> on C {
}
}