diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 10afbf0e..3fc4c28a 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -1316,7 +1316,8 @@ class _MockClassInfo { var name = method.displayName; if (method.isOperator) name = 'operator$name'; final returnType = method.returnType; - _withTypeParameters(method.typeParameters, (typeParamsWithBounds, _) { + _withTypeParameters(method.typeParameters, + typeFormalsHack: method.type.typeFormals, (typeParamsWithBounds, _) { builder ..name = name ..annotations.add(referImported('override', 'dart:core')) @@ -2057,17 +2058,43 @@ class _MockClassInfo { } } + /// Creates fresh type parameter names for [typeParameters] and runs [body] + /// in the extended type parameter scope, passing type references for + /// [typeParameters] (both with and without bound) as arguments. + /// If [typeFormalsHack] is not `null`, it will be used to build the + /// type references instead of [typeParameters]. This is needed while + /// building method overrides, since sometimes + /// [ExecutableMember.typeParameters] can contain inconsistency if a type + /// parameter refers to itself in its bound. See + /// https://github.com/dart-lang/mockito/issues/658. So we have to + /// pass `ExecutableMember.type.typeFormals` instead, that seem to be + /// always correct. Unfortunately we can't just use the latter everywhere, + /// since `type.typeFormals` don't contain default arguments' values + /// and we need that for code generation. T _withTypeParameters(Iterable typeParameters, - T Function(Iterable, Iterable) body) { - final typeVars = {for (final t in typeParameters) t: _newTypeVar(t)}; - _typeVariableScopes.add(typeVars); + T Function(Iterable, Iterable) body, + {Iterable? typeFormalsHack}) { + final typeVars = [for (final t in typeParameters) _newTypeVar(t)]; + final scope = Map.fromIterables(typeParameters, typeVars); + _typeVariableScopes.add(scope); + if (typeFormalsHack != null) { + // add an additional scope based on [type.typeFormals] just to make + // type parameters references. + _typeVariableScopes.add(Map.fromIterables(typeFormalsHack, typeVars)); + // use typeFormals instead of typeParameters to create refs. + typeParameters = typeFormalsHack; + } final typeRefsWithBounds = typeParameters.map(_typeParameterReference); final typeRefs = typeParameters.map((t) => _typeParameterReference(t, withBound: false)); final result = body(typeRefsWithBounds, typeRefs); _typeVariableScopes.removeLast(); - _usedTypeVariables.removeAll(typeVars.values); + if (typeFormalsHack != null) { + // remove the additional scope too. + _typeVariableScopes.removeLast(); + } + _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(