MockBuilder: Fix a few nullability bugs.
Methods should not be overridden unless they have a non-nullable return type, or at least one non-nullable parameter type. Previously, there was a complicated, pre-non-nullability logic to determine this for methods. This has been fixed to simply ask the type system.
Type variables are now correctly suffixed with a `?` in order to match the source type annotation; the _typeReference logic now handles TypeParameterTypes.
Stop using NullabilitySuffix; the TypeSystem is just a better way to handle question marks. dynamic can be handled separately.
PiperOrigin-RevId: 314792190
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index ff878f3..99a2a3f 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -14,7 +14,6 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
-import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart' as analyzer;
import 'package:analyzer/dart/element/type_system.dart';
import 'package:build/build.dart';
@@ -167,6 +166,12 @@
..url = _typeImport(dartType)
..types.addAll(typeArguments);
}));
+
+ // Only override members of a class declared in a library which uses the
+ // non-nullable type system.
+ if (!sourceLibIsNonNullable) {
+ return;
+ }
for (final field in classToMock.fields) {
if (field.isPrivate || field.isStatic) {
continue;
@@ -191,22 +196,8 @@
});
}
- // TODO(srawlins): Update this logic to correctly handle non-nullable return
- // types. Right now this logic does not seem to be available on DartType.
- bool _returnTypeIsNonNullable(MethodElement method) {
- var type = method.returnType;
- if (type.isDynamic || type.isVoid) return false;
- if (method.isAsynchronous && type.isDartAsyncFuture ||
- type.isDartAsyncFutureOr) {
- var typeArgument = (type as analyzer.InterfaceType).typeArguments.first;
- if (typeArgument.isDynamic || typeArgument.isVoid) {
- // An asynchronous method which returns `Future<void>`, for example,
- // does not need a dummy return value.
- return false;
- }
- }
- return true;
- }
+ bool _returnTypeIsNonNullable(MethodElement method) =>
+ typeSystem.isPotentiallyNonNullable(method.returnType);
// Returns whether [method] has at least one parameter whose type is
// potentially non-nullable.
@@ -220,7 +211,6 @@
// final c1 = C<int?>(); // m's parameter's type is nullable.
// final c2 = C<int>(); // m's parameter's type is non-nullable.
bool _hasNonNullableParameter(MethodElement method) =>
- sourceLibIsNonNullable &&
method.parameters.any((p) => typeSystem.isPotentiallyNonNullable(p.type));
/// Build a method which overrides [method], with all non-nullable
@@ -274,8 +264,7 @@
// TODO(srawlins): Optionally pass a non-null return value to `noSuchMethod`
// which `Mock.noSuchMethod` will simply return, in order to satisfy runtime
// type checks.
- // TODO(srawlins): Handle getter invocations with `Invocation.getter`,
- // and operators???
+ // TODO(srawlins): Handle getter invocations with `Invocation.getter`.
final invocation = refer('Invocation').property('method').call([
refer('#${method.displayName}'),
literalList(invocationPositionalArgs),
@@ -453,17 +442,16 @@
/// * InterfaceTypes (classes, generic classes),
/// * FunctionType parameters (like `void callback(int i)`),
/// * type aliases (typedefs), both new- and old-style,
- /// * enums.
+ /// * enums,
+ /// * type variables.
// TODO(srawlins): Contribute this back to a common location, like
// package:source_gen?
Reference _typeReference(analyzer.DartType type) {
if (type is analyzer.InterfaceType) {
- return TypeReference((TypeReferenceBuilder b) {
+ return TypeReference((b) {
b
..symbol = type.name
- // Using the `nullabilitySuffix` rather than `TypeSystem.isNullable`
- // is more correct for types like `dynamic`.
- ..isNullable = type.nullabilitySuffix == NullabilitySuffix.question
+ ..isNullable = typeSystem.isPotentiallyNullable(type)
..url = _typeImport(type)
..types.addAll(type.typeArguments.map(_typeReference));
});
@@ -483,15 +471,21 @@
}
});
}
- return TypeReference((TypeReferenceBuilder trBuilder) {
+ return TypeReference((b) {
var typedef = element.enclosingElement;
- trBuilder
+ b
..symbol = typedef.name
..url = _typeImport(type);
for (var typeArgument in type.typeArguments) {
- trBuilder.types.add(_typeReference(typeArgument));
+ b.types.add(_typeReference(typeArgument));
}
});
+ } else if (type is analyzer.TypeParameterType) {
+ return TypeReference((b) {
+ b
+ ..symbol = type.name
+ ..isNullable = typeSystem.isNullable(type);
+ });
} else {
return refer(type.displayName, _typeImport(type));
}
diff --git a/test/builder_test.dart b/test/builder_test.dart
index a5d5894..aa79a52 100644
--- a/test/builder_test.dart
+++ b/test/builder_test.dart
@@ -133,7 +133,7 @@
);
});
- test('generates a mock class and overrides methods parameters', () async {
+ test('generates a mock class and overrides method parameters', () async {
await _testWithNonNullable(
{
...annotationsAsset,
@@ -171,7 +171,7 @@
void d(String one, {String two, String three = ""}) => super
.noSuchMethod(Invocation.method(#d, [one], {#two: two, #three: three}));
_i3.Future<void> e(String s) async =>
- super.noSuchMethod(Invocation.method(#e, [s]));
+ super.noSuchMethod(Invocation.method(#e, [s]), Future.value(null));
}
'''),
},
@@ -450,11 +450,11 @@
void j(int? a(), int b());
void k(void a(int? x), void b(int x));
void l<T>(T? a, T b);
+ void m(dynamic a, int b);
}
'''),
},
outputs: {
- // TODO(srawlins): The type of l's first parameter should be `T?`.
'foo|test/foo_test.mocks.dart': dedent(r'''
import 'package:mockito/mockito.dart' as _i1;
import 'package:foo/foo.dart' as _i2;
@@ -474,7 +474,8 @@
super.noSuchMethod(Invocation.method(#j, [a, b]));
void k(void Function(int?) a, void Function(int) b) =>
super.noSuchMethod(Invocation.method(#k, [a, b]));
- void l<T>(T a, T b) => super.noSuchMethod(Invocation.method(#l, [a, b]));
+ void l<T>(T? a, T b) => super.noSuchMethod(Invocation.method(#l, [a, b]));
+ void m(dynamic a, int b) => super.noSuchMethod(Invocation.method(#m, [a, b]));
}
'''),
},
@@ -488,10 +489,12 @@
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
abstract class Foo {
- int f();
- int? g();
- List<int?> h();
- List<int> i();
+ int f(int a);
+ int? g(int a);
+ List<int?> h(int a);
+ List<int> i(int a);
+ j(int a);
+ T? k<T extends int>(int a);
}
'''),
},
@@ -504,10 +507,12 @@
///
/// See the documentation for Mockito's code generation for more information.
class MockFoo extends _i1.Mock implements _i2.Foo {
- int f() => super.noSuchMethod(Invocation.method(#f, []), 0);
- int? g() => super.noSuchMethod(Invocation.method(#g, []), 0);
- List<int?> h() => super.noSuchMethod(Invocation.method(#h, []), []);
- List<int> i() => super.noSuchMethod(Invocation.method(#i, []), []);
+ int f(int a) => super.noSuchMethod(Invocation.method(#f, [a]), 0);
+ int? g(int a) => super.noSuchMethod(Invocation.method(#g, [a]));
+ List<int?> h(int a) => super.noSuchMethod(Invocation.method(#h, [a]), []);
+ List<int> i(int a) => super.noSuchMethod(Invocation.method(#i, [a]), []);
+ dynamic j(int a) => super.noSuchMethod(Invocation.method(#j, [a]));
+ T? k<T extends int>(int a) => super.noSuchMethod(Invocation.method(#k, [a]));
}
'''),
},
@@ -549,9 +554,40 @@
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
- void a(int? m) {}
- void b(dynamic n) {}
- void c(int Function()? o) {}
+ void a(int? p) {}
+ void b(dynamic p) {}
+ void c(var p) {}
+ void d(final p) {}
+ void e(int Function()? p) {}
+ }
+ '''),
+ },
+ outputs: {
+ 'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+ import 'package:foo/foo.dart' as _i2;
+
+ /// A class which mocks [Foo].
+ ///
+ /// See the documentation for Mockito's code generation for more information.
+ class MockFoo extends _i1.Mock implements _i2.Foo {}
+ '''),
+ },
+ );
+ });
+
+ test('does not override methods with a nullable return type', () async {
+ await _testWithNonNullable(
+ {
+ ...annotationsAsset,
+ ...simpleTestAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ abstract class Foo {
+ void a();
+ b();
+ dynamic c();
+ void d();
+ int? f();
}
'''),
},
@@ -965,7 +1001,7 @@
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
abstract class Foo {
- dynamic f(int a) {}
+ int f(int a);
}
'''),
},