diff --git a/CHANGELOG.md b/CHANGELOG.md index 51c8ada3..da6ae6fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ * Require dart_style >= 2.3.7, so that the current Dart language version can be passed to `DartFormatter`. * Add topics to `pubspec.yaml`. +* Fix a bug where type aliases in type arguments were not correctly + resolved. +* Fix a bug where record types were not correctly resolved. ## 5.4.4 diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 7fd81d7f..534343be 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -167,25 +167,36 @@ $rawOutput // `Future`, which is needed when overriding some methods which return // `FutureOr`. final typeVisitor = _TypeVisitor(entryLib.typeProvider.futureDynamicType); - final seenTypes = {}; + final seenTypes = {}; final librariesWithTypes = {}; - void addTypesFrom(analyzer.InterfaceType type) { + void addTypesFrom(analyzer.DartType type) { // Prevent infinite recursion. if (seenTypes.contains(type)) { + if (type.alias != null) { + // To check for duplicate typdefs that have different names + type.alias!.element.accept(typeVisitor); + } return; } seenTypes.add(type); - librariesWithTypes.add(type.element.library); - type.element.accept(typeVisitor); - if (type.alias != null) type.alias!.element.accept(typeVisitor); - // For a type like `Foo`, add the `Bar`. - type.typeArguments - .whereType() - .forEach(addTypesFrom); - // For a type like `Foo extends Bar`, add the `Baz`. - for (final supertype in type.allSupertypes) { - addTypesFrom(supertype); + + if (type.element?.library case var library?) { + librariesWithTypes.add(library); + } + if (type.alias?.element.library case var library?) { + librariesWithTypes.add(library); + } + + type.element?.accept(typeVisitor); + type.alias?.element.accept(typeVisitor); + switch (type) { + case analyzer.InterfaceType interface: + interface.typeArguments.forEach(addTypesFrom); + interface.allSupertypes.forEach(addTypesFrom); + case analyzer.RecordType record: + record.positionalTypes.forEach(addTypesFrom); + record.namedTypes.map((e) => e.type).forEach(addTypesFrom); } } @@ -314,6 +325,7 @@ class _TypeVisitor extends RecursiveElementVisitor { @override void visitTypeAliasElement(TypeAliasElement element) { + _addType(element.aliasedType); _elements.add(element); super.visitTypeAliasElement(element); } diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index 4c2d0d46..c6d66512 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -3582,6 +3582,66 @@ void main() { expect(mocksContent, contains('class MockBaz extends _i1.Mock')); expect(mocksContent, contains('implements _i2.Baz')); }); + + test('when it\'s a function which returns any type', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + typedef CreateBar = Bar Function(); + + class BaseFoo { + BaseFoo(this.t); + final T t; + } + + class Foo extends BaseFoo { + Foo() : super(() => 1); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Foo]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); + test('when the underlying type is identical to another type alias', + () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + typedef BarDef = int Function(); + typedef BarDef2 = int Function(); + class BaseFoo { + BaseFoo(this.t1, this.t2); + final T t1; + final P t2; + } + class Foo extends BaseFoo { + Foo() : super(() => 1, () => 2); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([Foo]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + expect(mocksContent, contains('_i2.BarDef get t1')); + expect(mocksContent, contains('_i2.BarDef2 get t2')); + }); }); test('generation throws when the aliased type is nullable', () { @@ -3649,6 +3709,54 @@ void main() { contains('returnValue: _i3.Future<(int, {_i2.Bar bar})>.value('), contains('bar: _FakeBar_0(')))); }); + test('are supported as type arguments', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + class BaseFoo { + BaseFoo(this.t); + final T t; + } + class Foo extends BaseFoo<(Bar, Bar)> { + Foo() : super((Bar(), Bar())); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); + test('are supported as nested type arguments', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Bar {} + class BaseFoo { + BaseFoo(this.t); + final T t; + } + class Foo extends BaseFoo<(int, (Bar, Bar))> { + Foo() : super(((1, (Bar(), Bar())))); + } + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + @GenerateMocks([Foo]) + void main() {} + ''' + }); + + expect(mocksContent, contains('class MockFoo extends _i1.Mock')); + expect(mocksContent, contains('implements _i2.Foo')); + }); }); group('Extension types', () {