Skip to content

Commit 2d4817d

Browse files
Ilya Yanokcopybara-github
Ilya Yanok
authored andcommitted
Second attempt to fix "not found" error for type vars in bounds
First attempt was #671 and I had to roll it back, since it caused breakages in two ways: 1. Sometimes `ParameterElement` from `type.parameters` had `enclosingElement` set to `null` and we use that in one helper function. That was easy to fix, we could just pass `methodElement` to that function directly. It's probably correct that `ParameterElement` of a `FunctionType` doesn't link back to a `MethodElement`, but it's weird that sometimes it does, so it wasn't caught in the tests. I had to get rid of using `type.parameters` anyway because of the second problem. 2. `type.parameters` don't contain parameters' default values (totally correct, since default values belong to methods, not to types), but that means we can't use them, since we do need default values. So I ended up with a more hacky solution, that uses `type.typeFormals` just to get correct references for method's type parameters, and then uses `typeParameters`, `returnType` and `parameters` for the rest as before. Original commit description: Use `FunctionTypedElement.type` while generating method overrides Turns out `FunctionTypedElement.typeParameters` could be inconsistent for `MethodMember`s returned by `InheritanceManager3.getMember2`. `FunctionTypedElement.type.typeFormals` seem to be always good, but we have to also use `type.parameters` and `type.returnType` instead of just `parameters` and `returnType` in this case. Fixes #658 PiperOrigin-RevId: 545934516
1 parent 451f756 commit 2d4817d

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

lib/src/builder.dart

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1316,7 +1316,8 @@ class _MockClassInfo {
13161316
var name = method.displayName;
13171317
if (method.isOperator) name = 'operator$name';
13181318
final returnType = method.returnType;
1319-
_withTypeParameters(method.typeParameters, (typeParamsWithBounds, _) {
1319+
_withTypeParameters(method.typeParameters,
1320+
typeFormalsHack: method.type.typeFormals, (typeParamsWithBounds, _) {
13201321
builder
13211322
..name = name
13221323
..annotations.add(referImported('override', 'dart:core'))
@@ -2057,17 +2058,43 @@ class _MockClassInfo {
20572058
}
20582059
}
20592060

2061+
/// Creates fresh type parameter names for [typeParameters] and runs [body]
2062+
/// in the extended type parameter scope, passing type references for
2063+
/// [typeParameters] (both with and without bound) as arguments.
2064+
/// If [typeFormalsHack] is not `null`, it will be used to build the
2065+
/// type references instead of [typeParameters]. This is needed while
2066+
/// building method overrides, since sometimes
2067+
/// [ExecutableMember.typeParameters] can contain inconsistency if a type
2068+
/// parameter refers to itself in its bound. See
2069+
/// https://github.com/dart-lang/mockito/issues/658. So we have to
2070+
/// pass `ExecutableMember.type.typeFormals` instead, that seem to be
2071+
/// always correct. Unfortunately we can't just use the latter everywhere,
2072+
/// since `type.typeFormals` don't contain default arguments' values
2073+
/// and we need that for code generation.
20602074
T _withTypeParameters<T>(Iterable<TypeParameterElement> typeParameters,
2061-
T Function(Iterable<TypeReference>, Iterable<TypeReference>) body) {
2062-
final typeVars = {for (final t in typeParameters) t: _newTypeVar(t)};
2063-
_typeVariableScopes.add(typeVars);
2075+
T Function(Iterable<TypeReference>, Iterable<TypeReference>) body,
2076+
{Iterable<TypeParameterElement>? typeFormalsHack}) {
2077+
final typeVars = [for (final t in typeParameters) _newTypeVar(t)];
2078+
final scope = Map.fromIterables(typeParameters, typeVars);
2079+
_typeVariableScopes.add(scope);
2080+
if (typeFormalsHack != null) {
2081+
// add an additional scope based on [type.typeFormals] just to make
2082+
// type parameters references.
2083+
_typeVariableScopes.add(Map.fromIterables(typeFormalsHack, typeVars));
2084+
// use typeFormals instead of typeParameters to create refs.
2085+
typeParameters = typeFormalsHack;
2086+
}
20642087
final typeRefsWithBounds = typeParameters.map(_typeParameterReference);
20652088
final typeRefs =
20662089
typeParameters.map((t) => _typeParameterReference(t, withBound: false));
20672090

20682091
final result = body(typeRefsWithBounds, typeRefs);
20692092
_typeVariableScopes.removeLast();
2070-
_usedTypeVariables.removeAll(typeVars.values);
2093+
if (typeFormalsHack != null) {
2094+
// remove the additional scope too.
2095+
_typeVariableScopes.removeLast();
2096+
}
2097+
_usedTypeVariables.removeAll(typeVars);
20712098
return result;
20722099
}
20732100

test/builder/auto_mocks_test.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,21 @@ void main() {
15401540
);
15411541
});
15421542

1543+
test('widens the type of covariant generic parameters to be nullable',
1544+
() async {
1545+
await expectSingleNonNullableOutput(
1546+
dedent('''
1547+
abstract class FooBase<T extends Object> {
1548+
void m(Object a);
1549+
}
1550+
abstract class Foo<T extends Object> extends FooBase<T> {
1551+
void m(covariant T a);
1552+
}
1553+
'''),
1554+
_containsAllOf('void m(Object? a) => super.noSuchMethod('),
1555+
);
1556+
});
1557+
15431558
test('matches nullability of type arguments of a parameter', () async {
15441559
await expectSingleNonNullableOutput(
15451560
dedent(r'''

test/builder/custom_mocks_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,6 +1843,29 @@ void main() {
18431843
expect(mocksContent, contains('Iterable<X1> m1<X1>(X1 Function(X)? f)'));
18441844
expect(mocksContent, contains('Iterable<X1?> m2<X1>(X1 Function(X)? f)'));
18451845
});
1846+
test('We preserve nested generic bounded type arguments', () async {
1847+
final mocksContent = await buildWithNonNullable({
1848+
...annotationsAsset,
1849+
'foo|lib/foo.dart': dedent(r'''
1850+
class Foo<A, B> {}
1851+
abstract class Bar<T> {
1852+
X m1<X extends Foo<Foo<X, T>, X>>(X Function(T)? f);
1853+
}
1854+
abstract class FooBar<X> extends Bar<X> {}
1855+
'''),
1856+
'foo|test/foo_test.dart': '''
1857+
import 'package:foo/foo.dart';
1858+
import 'package:mockito/annotations.dart';
1859+
1860+
@GenerateMocks([FooBar])
1861+
void main() {}
1862+
'''
1863+
});
1864+
expect(
1865+
mocksContent,
1866+
contains(
1867+
'X1 m1<X1 extends _i2.Foo<_i2.Foo<X1, X>, X1>>(X1 Function(X)? f)'));
1868+
});
18461869
}
18471870

18481871
TypeMatcher<List<int>> _containsAllOf(a, [b]) => decodedMatches(

0 commit comments

Comments
 (0)