Skip to content

Commit

Permalink
Second attempt to fix "not found" error for type vars in bounds
Browse files Browse the repository at this point in the history
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: 547427000
  • Loading branch information
Ilya Yanok authored and copybara-github committed Jul 12, 2023
1 parent 451f756 commit ffbbb4c
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 5 deletions.
37 changes: 32 additions & 5 deletions lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Expand Down Expand Up @@ -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<T>(Iterable<TypeParameterElement> typeParameters,
T Function(Iterable<TypeReference>, Iterable<TypeReference>) body) {
final typeVars = {for (final t in typeParameters) t: _newTypeVar(t)};
_typeVariableScopes.add(typeVars);
T Function(Iterable<TypeReference>, Iterable<TypeReference>) body,
{Iterable<TypeParameterElement>? 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;
}

Expand Down
15 changes: 15 additions & 0 deletions test/builder/auto_mocks_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,21 @@ void main() {
);
});

test('widens the type of covariant generic parameters to be nullable',
() async {
await expectSingleNonNullableOutput(
dedent('''
abstract class FooBase<T extends Object> {
void m(Object a);
}
abstract class Foo<T extends Object> extends FooBase<T> {
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'''
Expand Down
23 changes: 23 additions & 0 deletions test/builder/custom_mocks_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1843,6 +1843,29 @@ void main() {
expect(mocksContent, contains('Iterable<X1> m1<X1>(X1 Function(X)? f)'));
expect(mocksContent, contains('Iterable<X1?> m2<X1>(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<A, B> {}
abstract class Bar<T> {
X m1<X extends Foo<Foo<X, T>, X>>(X Function(T)? f);
}
abstract class FooBar<X> extends Bar<X> {}
'''),
'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 extends _i2.Foo<_i2.Foo<X1, X>, X1>>(X1 Function(X)? f)'));
});
}

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

0 comments on commit ffbbb4c

Please sign in to comment.