Skip to content

Display inherited elements for extension types #3556

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 4 commits into from
Oct 30, 2023
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
58 changes: 2 additions & 56 deletions lib/src/generator/templates.runtime_renderers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5413,18 +5413,7 @@ class _Renderer_ExtensionType extends RendererBase<ExtensionType> {
() => {
..._Renderer_InheritingContainer.propertyMap<CT_>(),
..._Renderer_Constructable.propertyMap<CT_>(),
'allFields': Property(
getValue: (CT_ c) => c.allFields,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'List<Field>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.allFields.map((e) =>
_render_Field(e, ast, r.template, sink, parent: r));
},
),
..._Renderer_TypeImplementing.propertyMap<CT_>(),
'allModelElements': Property(
getValue: (CT_ c) => c.allModelElements,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -5508,13 +5497,6 @@ class _Renderer_ExtensionType extends RendererBase<ExtensionType> {
parent: r);
},
),
'hasPublicInterfaces': Property(
getValue: (CT_ c) => c.hasPublicInterfaces,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(c, remainingNames, 'bool'),
getBool: (CT_ c) => c.hasPublicInterfaces == true,
),
'inheritanceChain': Property(
getValue: (CT_ c) => c.inheritanceChain,
renderVariable: (CT_ c, Property<CT_> self,
Expand All @@ -5528,30 +5510,6 @@ class _Renderer_ExtensionType extends RendererBase<ExtensionType> {
parent: r));
},
),
'inheritedMethods': Property(
getValue: (CT_ c) => c.inheritedMethods,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'Iterable<Method>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.inheritedMethods.map((e) =>
_render_Method(e, ast, r.template, sink, parent: r));
},
),
'inheritedOperators': Property(
getValue: (CT_ c) => c.inheritedOperators,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'List<Operator>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.inheritedOperators.map((e) =>
_render_Operator(e, ast, r.template, sink, parent: r));
},
),
'isAbstract': Property(
getValue: (CT_ c) => c.isAbstract,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -5599,19 +5557,6 @@ class _Renderer_ExtensionType extends RendererBase<ExtensionType> {
parent: r, getters: _invisibleGetters['Kind']!);
},
),
'publicInterfaces': Property(
getValue: (CT_ c) => c.publicInterfaces,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'List<DefinedElementType>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.publicInterfaces.map((e) =>
_render_DefinedElementType(e, ast, r.template, sink,
parent: r));
},
),
'referenceChildren': Property(
getValue: (CT_ c) => c.referenceChildren,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -17022,6 +16967,7 @@ const _invisibleGetters = {
'hashCode',
'implementors',
'inheritThrough',
'inheritanceManager',
'invisibleAnnotations',
'libraries',
'libraryCount',
Expand Down
14 changes: 7 additions & 7 deletions lib/src/model/container_member.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ mixin ContainerMember on ModelElement implements EnclosedElement {

Container? computeCanonicalEnclosingContainer() {
final enclosingElement = this.enclosingElement;
if (enclosingElement is! Extension) {
return packageGraph.findCanonicalModelElementFor(element.enclosingElement)
as Container?;
}
// TODO(jcollins-g): move Extension specific code to [Extendable]
return packageGraph.findCanonicalModelElementFor(enclosingElement.element)
as Container?;
return switch (enclosingElement) {
Extension() =>
packageGraph.findCanonicalModelElementFor(enclosingElement.element),
ExtensionType() =>
packageGraph.findCanonicalModelElementFor(enclosingElement.element),
_ => packageGraph.findCanonicalModelElementFor(element.enclosingElement),
} as Container?;
}

@override
Expand Down
40 changes: 6 additions & 34 deletions lib/src/model/extension_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import 'package:dartdoc/src/model/comment_referable.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:meta/meta.dart';

class ExtensionType extends InheritingContainer with Constructable {
class ExtensionType extends InheritingContainer
with Constructable, TypeImplementing {
@override
final ExtensionTypeElement element;

Expand Down Expand Up @@ -38,25 +39,6 @@ class ExtensionType extends InheritingContainer with Constructable {
@override
bool get isSealed => false;

bool get hasPublicInterfaces => publicInterfaces.isNotEmpty;

@override
List<DefinedElementType> get publicInterfaces {
var interfaces = <DefinedElementType>[];
for (var interface in element.interfaces) {
var elementType =
modelBuilder.typeFrom(interface, library) as DefinedElementType;

if (elementType.modelElement.canonicalModelElement != null) {
interfaces.add(elementType);
continue;
}

// TODO(srawlins): Work through intermediate, private, interfaces.
}
return interfaces;
}

@override
late final List<Field> declaredFields = element.fields.map((field) {
Accessor? getter, setter;
Expand All @@ -79,20 +61,10 @@ class ExtensionType extends InheritingContainer with Constructable {
];

@override
// TODO(srawlins): Implement.
List<Field> get allFields => [];

@override
// TODO(srawlins): Implement.
Iterable<Method> get inheritedMethods => [];

@override
// TODO(srawlins): Implement.
List<Operator> get inheritedOperators => [];

@override
// TODO(srawlins): Implement. It might just be empty...
List<InheritingContainer> get inheritanceChain => [];
late final List<InheritingContainer> inheritanceChain = [
this,
...interfaces.expandInheritanceChain,
];

@override
String get filePath => '${library.dirName}/${fileStructure.fileName}';
Expand Down
22 changes: 13 additions & 9 deletions lib/src/model/inheritable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import 'package:dartdoc/src/model/attribute.dart';
import 'package:dartdoc/src/model/model.dart';
import 'package:dartdoc/src/special_elements.dart';

/// Mixin for subclasses of ModelElement representing Elements that can be
/// Mixin for subclasses of [ModelElement] representing elements that can be
/// inherited from one class to another.
///
/// We can search the inheritance chain between this instance and
/// [definingEnclosingContainer] in [Inheritable.canonicalEnclosingContainer],
/// for the canonical [Class] closest to where this member was defined. We
/// can then know that when we find [Inheritable.modelElement] inside that [Class]'s
/// namespace, that's the one we should treat as canonical and implementors
/// of this class can use that knowledge to determine canonicalization.
/// can then know that when we find [Inheritable.modelElement] inside that
/// [Class]'s namespace, that's the one we should treat as canonical and
/// implementors of this class can use that knowledge to determine
/// canonicalization.
///
/// We pick the class closest to the [definingEnclosingContainer] so that all
/// children of that class inheriting the same member will point to the same
Expand Down Expand Up @@ -89,7 +90,10 @@ mixin Inheritable on ContainerMember {
// classes.
if (definingEnclosingContainer.isCanonical &&
definingEnclosingContainer.isPublic) {
assert(definingEnclosingContainer == found);
assert(
definingEnclosingContainer == found,
'For $element, expected $definingEnclosingContainer to be Object '
'or $found, but was neither.');
}
if (found != null) {
return found;
Expand All @@ -106,13 +110,13 @@ mixin Inheritable on ContainerMember {
var inheritance = [
...(enclosingElement as InheritingContainer).inheritanceChain,
];
var object = packageGraph.specialClasses[SpecialClass.object];
var object = packageGraph.specialClasses[SpecialClass.object]!;

assert(definingEnclosingContainer == object ||
inheritance.contains(definingEnclosingContainer));

// Unless the code explicitly extends dart-core's Object, we won't get
// Unless the code explicitly extends dart:core's Object, we won't get
// an entry here. So add it.
if (inheritance.last != object && object != null) {
if (inheritance.last != object) {
inheritance.add(object);
}
assert(inheritance.where((e) => e == object).length == 1);
Expand Down
10 changes: 3 additions & 7 deletions lib/src/model/inheriting_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@

import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/element/inheritance_manager3.dart'
show InheritanceManager3;
import 'package:collection/collection.dart' show IterableExtension;
import 'package:dartdoc/src/element_type.dart';
import 'package:dartdoc/src/model/comment_referable.dart';
Expand Down Expand Up @@ -154,8 +151,9 @@ abstract class InheritingContainer extends Container
}

final concreteInheritenceMap =
_inheritanceManager.getInheritedConcreteMap2(element);
final inheritenceMap = _inheritanceManager.getInheritedMap2(element);
packageGraph.inheritanceManager.getInheritedConcreteMap2(element);
final inheritenceMap =
packageGraph.inheritanceManager.getInheritedMap2(element);

List<InterfaceElement>? inheritanceChainElements;

Expand Down Expand Up @@ -195,8 +193,6 @@ abstract class InheritingContainer extends Container
return combinedMap.values.toList(growable: false);
}

static final InheritanceManager3 _inheritanceManager = InheritanceManager3();

/// All fields defined on this container, _including inherited fields_.
List<Field> get allFields {
var inheritedAccessorElements = {
Expand Down
5 changes: 5 additions & 0 deletions lib/src/model/package_graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
// ignore: implementation_imports
import 'package:analyzer/src/dart/element/inheritance_manager3.dart'
show InheritanceManager3;
// ignore: implementation_imports
import 'package:analyzer/src/generated/sdk.dart' show DartSdk, SdkLibrary;
// ignore: implementation_imports
import 'package:analyzer/src/generated/source.dart' show Source;
Expand Down Expand Up @@ -43,6 +46,8 @@ class PackageGraph with CommentReferable, Nameable, ModelBuilder {
Package.fromPackageMeta(packageMeta, this);
}

final InheritanceManager3 inheritanceManager = InheritanceManager3();

void dispose() {
// Clear out any cached tool snapshots and temporary directories.
// TODO(jcollins-g): Consider ownership change for these objects
Expand Down
72 changes: 68 additions & 4 deletions test/templates/extension_type_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,21 @@ analyzer:
''',
libFiles: [
d.file('lib.dart', '''
class Base1<E> {}
class Base1<E> {
// An inherited method.
void m2() {}

// An inherited field.
int get field1 => 1;
}

class Base2 {}

class Foo<E> extends Base1<E>, Base2 {}

class FooSub extends Foo<int> {}

extension type One<E>(Foo<E> e) {
extension type One<E>(Foo<E> it) {
/// A named constructor.
MyIterable.named(Foo<E> e);

Expand Down Expand Up @@ -109,7 +115,7 @@ extension type One<E>(Foo<E> e) {

extension type TwoWithBase<E>(Foo<E> it) implements Base1<E>, Base2 {}

extension type ThreeWithOne<E>(FooSub it) implements One<int> {}
extension type ThreeWithOne<E>(FooSub it3) implements One<int> {}
'''),
],
dartdocOptions: '''
Expand Down Expand Up @@ -216,8 +222,66 @@ dartdoc:
},
);

test('page contains instance operators', () async {
test('page contains representation field', () async {
oneLines.expectMainContentContainsAllInOrder([
matches('<h2>Properties</h2>'),
matches('<a href="../lib/One/it.html">it</a>'),
matches('An operator.'),
]);
});

test('page contains inherited representation field', () async {
threeLines.expectMainContentContainsAllInOrder([
matches('<h2>Properties</h2>'),
matches('<a href="../lib/One/it.html">it</a>'),
]);
});

test('page contains inherited getters', () async {
twoLines.expectMainContentContainsAllInOrder([
matches('<h2>Properties</h2>'),
matches('<a href="../lib/Base1/field1.html">field1</a>'),
]);
});

test('page contains instance methods', () async {
oneLines.expectMainContentContainsAllInOrder([
matches('<h2>Methods</h2>'),
matches('<dt id="m1" class="callable">'),
matches('<a href="../lib/One/m1.html">m1</a>'),
matches('An instance method.'),
]);
});

test('page contains methods inherited from a class super-interface',
() async {
twoLines.expectMainContentContainsAllInOrder([
matches('<h2>Methods</h2>'),
matches('<dt id="m2" class="callable inherited">'),
matches('<a href="../lib/Base1/m2.html">m2</a>'),
]);
});

test(
'page contains methods inherited from an extension type '
'super-interface', () async {
threeLines.expectMainContentContainsAllInOrder([
matches('<h2>Methods</h2>'),
matches('<dt id="m1" class="callable inherited">'),
matches('<a href="../lib/One/m1.html">m1</a>'),
]);
});

test('page contains operators', () async {
oneLines.expectMainContentContainsAllInOrder([
matches('<h2>Operators</h2>'),
matches('<a href="../lib/One/operator_greater.html">operator ></a>'),
matches('An operator.'),
]);
});

test('page contains inherited operators', () async {
threeLines.expectMainContentContainsAllInOrder([
matches('<h2>Operators</h2>'),
matches('<a href="../lib/One/operator_greater.html">operator ></a>'),
matches('An operator.'),
Expand Down