diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 10afbf0e..b4786d6a 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -1436,7 +1436,7 @@ class _MockClassInfo { } builder.body = superNoSuchMethod.code; - }); + }, typeFormalsHack: method.type.typeFormals); } Expression _fallbackGeneratorCode( @@ -2058,16 +2058,33 @@ class _MockClassInfo { } T _withTypeParameters(Iterable typeParameters, - T Function(Iterable, Iterable) body) { - final typeVars = {for (final t in typeParameters) t: _newTypeVar(t)}; - _typeVariableScopes.add(typeVars); - final typeRefsWithBounds = typeParameters.map(_typeParameterReference); + T Function(Iterable, Iterable) body, + {Iterable? typeFormalsHack}) { + final typeVars = [for (final t in typeParameters) _newTypeVar(t)]; + final scope = Map.fromIterables(typeParameters, typeVars); + if (typeFormalsHack != null) { + // add a scope based on [type.typeFormals] just to make + // type parameters references, as [typeParameters] could contain + // an inconsistency if a parameter refers itself in its own bound, + // see https://github.com/dart-lang/mockito/issues/658 + _typeVariableScopes.add(Map.fromIterables(typeFormalsHack, typeVars)); + // use typeFormals instead of typeParameters to create refs. + typeParameters = typeFormalsHack; + } else { + _typeVariableScopes.add(scope); + } + final typeRefsWithBounds = + typeParameters.map(_typeParameterReference).toList(); final typeRefs = typeParameters.map((t) => _typeParameterReference(t, withBound: false)); + if (typeFormalsHack != null) { + _typeVariableScopes.removeLast(); + _typeVariableScopes.add(scope); + } final result = body(typeRefsWithBounds, typeRefs); _typeVariableScopes.removeLast(); - _usedTypeVariables.removeAll(typeVars.values); + _usedTypeVariables.removeAll(typeVars); return result; } diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index 836a699e..dfdf2325 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -1540,6 +1540,21 @@ void main() { ); }); + test('widens the type of covariant generic parameters to be nullable', + () async { + await expectSingleNonNullableOutput( + dedent(''' + abstract class FooBase { + void m(Object a); + } + abstract class Foo extends FooBase { + void m(covariant T a); + } + '''), + _containsAllOf('void m(Object? a) => super.noSuchMethod('), + ); + }); + test('matches nullability of type arguments of a parameter', () async { await expectSingleNonNullableOutput( dedent(r''' diff --git a/test/builder/custom_mocks_test.dart b/test/builder/custom_mocks_test.dart index 4a673e9a..d0de4b80 100644 --- a/test/builder/custom_mocks_test.dart +++ b/test/builder/custom_mocks_test.dart @@ -1843,6 +1843,29 @@ void main() { expect(mocksContent, contains('Iterable m1(X1 Function(X)? f)')); expect(mocksContent, contains('Iterable m2(X1 Function(X)? f)')); }); + test('We preserve nested generic bounded type arguments', () async { + final mocksContent = await buildWithNonNullable({ + ...annotationsAsset, + 'foo|lib/foo.dart': dedent(r''' + class Foo {} + abstract class Bar { + X m1, X>>(X Function(T)? f); + } + abstract class FooBar extends Bar {} + '''), + 'foo|test/foo_test.dart': ''' + import 'package:foo/foo.dart'; + import 'package:mockito/annotations.dart'; + + @GenerateMocks([FooBar]) + void main() {} + ''' + }); + expect( + mocksContent, + contains( + 'X1 m1, X1>>(X1 Function(X)? f)')); + }); } TypeMatcher> _containsAllOf(a, [b]) => decodedMatches(