Skip to content

Commit

Permalink
Add mixins with superclass constraints, to the 'implementers' list.
Browse files Browse the repository at this point in the history
"Implementers" is loose. The list of "implementors" of a class (etc.), _C_,
includes classes (etc.) that extend, implement, mix in, or
use-as-a-superclass-constraint, _C_.

Fixes dart-lang#3406
  • Loading branch information
srawlins committed Aug 24, 2024
1 parent ab8d74c commit 1e49183
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 93 deletions.
12 changes: 6 additions & 6 deletions lib/src/generator/templates.runtime_renderers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9568,17 +9568,17 @@ class _Renderer_MixedInTypes extends RendererBase<MixedInTypes> {
self.renderSimpleVariable(c, remainingNames, 'bool'),
getBool: (CT_ c) => c.hasPublicMixedInTypes,
),
'mixedInElements': Property(
getValue: (CT_ c) => c.mixedInElements,
'mixedInTypes': Property(
getValue: (CT_ c) => c.mixedInTypes,
renderVariable: (CT_ c, Property<CT_> self,
List<String> remainingNames) =>
self.renderSimpleVariable(
c, remainingNames, 'List<InheritingContainer>'),
c, remainingNames, 'List<DefinedElementType>'),
renderIterable: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
return c.mixedInElements.map((e) =>
_render_InheritingContainer(e, ast, r.template, sink,
parent: r));
return c.mixedInTypes.map((e) => _render_DefinedElementType(
e, ast, r.template, sink,
parent: r));
},
),
'publicMixedInTypes': Property(
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/class.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Class extends InheritingContainer with Constructable, MixedInTypes {
this,

// Caching should make this recursion a little less painful.
for (var container in mixedInElements.reversed)
for (var container in mixedInTypes.modelElements.reversed)
...container.inheritanceChain,

for (var container in superChain.modelElements)
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Enum extends InheritingContainer with Constructable, MixedInTypes {
@override
late final List<InheritingContainer> inheritanceChain = [
this,
for (var container in mixedInElements.reversed)
for (var container in mixedInTypes.modelElements.reversed)
...container.inheritanceChain,
for (var container in superChain.modelElements)
...container.inheritanceChain,
Expand Down
12 changes: 4 additions & 8 deletions lib/src/model/inheriting_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ abstract class InheritingContainer extends Container {

/// All the "immediate" public implementers of this container.
///
/// For a [Mixin], this is actually the mixin applications using the [Mixin].
/// For a [Mixin], this is actually the mixin applications that use the
/// [Mixin].
///
/// If this container has a private implementer, then that is counted as a
/// proxy for any public implementers of that private container.
Expand Down Expand Up @@ -580,15 +581,10 @@ abstract class InheritingContainer extends Container {

/// Add the ability to support mixed-in types to an [InheritingContainer].
mixin MixedInTypes on InheritingContainer {
@visibleForTesting
late final List<DefinedElementType> mixedInTypes = element.mixins
.map((f) => getTypeFor(f, library) as DefinedElementType)
.toList(growable: false);

List<InheritingContainer> get mixedInElements => [
for (var t in mixedInTypes) t.modelElement as InheritingContainer,
];

@override
bool get hasModifiers => super.hasModifiers || hasPublicMixedInTypes;

Expand All @@ -604,8 +600,8 @@ extension on InterfaceElement {

extension DefinedElementTypeIterableExtension on Iterable<DefinedElementType> {
/// The [ModelElement] for each element.
Iterable<InheritingContainer> get modelElements =>
map((e) => e.modelElement as InheritingContainer);
List<InheritingContainer> get modelElements =>
map((e) => e.modelElement as InheritingContainer).toList();
}

extension InheritingContainerIterableExtension
Expand Down
9 changes: 3 additions & 6 deletions lib/src/model/package_graph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -643,14 +643,11 @@ class PackageGraph with CommentReferable, Nameable {
supertype.modelElement as InheritingContainer, container);
}
if (container is Class) {
for (var element in container.mixedInElements) {
for (var element in container.mixedInTypes.modelElements) {
checkAndAddContainer(element, container);
}
for (var element in container.interfaceElements) {
checkAndAddContainer(element, container);
}
} else if (container is ExtensionType) {
for (var element in container.interfaceElements) {
} else if (container is Mixin) {
for (var element in container.superclassConstraints.modelElements) {
checkAndAddContainer(element, container);
}
}
Expand Down
56 changes: 4 additions & 52 deletions test/end2end/model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1763,7 +1763,7 @@ void main() async {

test('Verify inheritance/mixin structure and type inference', () {
expect(
TypeInferenceMixedIn.mixedInElements
TypeInferenceMixedIn.mixedInTypes
.map<String>((element) => element.name),
orderedEquals(['GenericMixin']));
expect(
Expand Down Expand Up @@ -1928,11 +1928,11 @@ void main() async {
});

test('mixins', () {
expect(Apple.mixedInElements, hasLength(0));
expect(Apple.mixedInTypes, hasLength(0));
});

test('mixins private', () {
expect(F.mixedInElements, hasLength(1));
expect(F.mixedInTypes, hasLength(1));
});

test('interfaces', () {
Expand Down Expand Up @@ -4487,30 +4487,6 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
});

group('Implementors', () {
late final Class apple;
late final Class b;
late final List<InheritingContainer> implA, implC;

setUpAll(() {
apple = exLibrary.classes.named('Apple');
b = exLibrary.classes.named('B');
implA = apple.publicImplementersSorted;
implC = exLibrary.classes.named('Cat').publicImplementersSorted;
});

test('private classes do not break the implementor chain', () {
var Super1 = fakeLibrary.classes.named('Super1');
var publicImplementors =
Super1.publicImplementersSorted.map((i) => i.name);
expect(publicImplementors, hasLength(3));
// A direct implementor.
expect(publicImplementors, contains('Super4'));
// An implementor through _Super2.
expect(publicImplementors, contains('Super3'));
// An implementor through _Super5 and _Super2.
expect(publicImplementors, contains('Super6'));
});

test(
'private classes in internal libraries do not break the implementor chain',
() {
Expand All @@ -4535,31 +4511,6 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
expect(publicImplementors, hasLength(1));
expect(publicImplementors, contains('GenericSuperInt'));
});

test('the first class is Apple', () {
expect(apple.name, equals('Apple'));
});

test('apple has some implementors', () {
expect(apple.hasPublicImplementers, isTrue);
expect(implA, isNotNull);
expect(implA, hasLength(1));
expect(implA[0].name, equals('B'));
});

test('Cat has implementors', () {
expect(implC, hasLength(3));
var implementors = <String>['B', 'Dog', 'ConstantCat'];
expect(implementors, contains(implC[0].name));
expect(implementors, contains(implC[1].name));
expect(implementors, contains(implC[2].name));
});

test('B does not have implementors', () {
expect(b, isNotNull);
expect(b.name, equals('B'));
expect(b.publicImplementersSorted, hasLength(0));
});
});

group('Errors and exceptions', () {
Expand All @@ -4569,6 +4520,7 @@ String? topLevelFunction(int param1, bool param2, Cool coolBeans,
'MyErrorImplements',
'MyExceptionImplements'
];

test('library has the exact errors/exceptions we expect', () {
expect(exLibrary.exceptions.map((e) => e.name),
unorderedEquals(expectedNames));
Expand Down
7 changes: 5 additions & 2 deletions test/enums_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,11 @@ enum E<T> with M<T>, N { one, two, three; }
''');
var eEnum = library.enums.named('E');

expect(eEnum.mixedInElements, hasLength(2));
expect(eEnum.mixedInElements.map((e) => e.name), equals(['M', 'N']));
expect(eEnum.mixedInTypes.modelElements, hasLength(2));
expect(
eEnum.mixedInTypes.modelElements.map((e) => e.name),
equals(['M', 'N']),
);
}

void test_operatorsAreDocumented() async {
Expand Down
29 changes: 24 additions & 5 deletions test/templates/class_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class ClassTest extends TemplateTestBase {
void test_implementers_class_extends() async {
await createPackageWithLibrary('''
class Base {}
class Foo extends Base {}
class _Foo extends Base {}
class Foo extends _Foo {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

Expand All @@ -40,7 +41,8 @@ class Foo extends Base {}
void test_implementers_class_implements() async {
await createPackageWithLibrary('''
class Base {}
class Foo implements Base {}
class _Foo implements Base {}
class Foo implements _Foo {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

Expand Down Expand Up @@ -82,6 +84,22 @@ class Foo implements Base<int> {}
]);
}

void test_implementers_class_mixesIn() async {
await createPackageWithLibrary('''
class Base {}
class _Foo with Base {}
class Foo with _Foo {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

baseLines.expectMainContentContainsAllInOrder([
matches('<dt>Implementers</dt>'),
matches('<dd><ul class="comma-separated clazz-relationships">'),
matches('<li><a href="../lib/Foo-class.html">Foo</a></li>'),
matches('</ul></dd>'),
]);
}

void test_implementers_extensionType_implements() async {
await createPackageWithLibrary('''
class Base1 {}
Expand All @@ -102,7 +120,8 @@ extension type ET(Base2 base) implements Base1 {}
void test_implementers_mixin_implements() async {
await createPackageWithLibrary('''
class Base {}
mixin M implements Base {}
mixin _M implements Base {}
mixin M implements _M {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

Expand All @@ -114,11 +133,11 @@ mixin M implements Base {}
]);
}

@FailingTest(reason: 'Not implemented yet; should be?')
void test_implementers_mixin_superclassConstraint() async {
await createPackageWithLibrary('''
class Base {}
mixin M on Base {}
mixin _M on Base {}
mixin M on _M {}
''');
var baseLines = readLines(['lib', 'Base-class.html']);

Expand Down
12 changes: 0 additions & 12 deletions testing/test_package/lib/fake.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1214,18 +1214,6 @@ extension ExtensionOnTypeParameter<T> on T {
T aFunctionReturningT(T other) => other;
}

class Super1 {}

class _Super2 implements Super1 {}

class Super3 implements _Super2 {}

class Super4 implements Super1 {}

class _Super5 implements _Super2 {}

class Super6 implements _Super5 {}

abstract class IntermediateAbstract extends Object {
/// This is an override.
@override
Expand Down

0 comments on commit 1e49183

Please sign in to comment.