MockBuilder: Include type arguments on dummy return values.
Without type arguments, runtime errors can take place. A stub method may try to use `[]` as a return value for type `<int>[]`, but inference does not occur, so `[]` is implicitly `<dynamic>[]`.
Additionally, include type arguments on function types.
Clean up tests which are affected by these two fixes.
PiperOrigin-RevId: 316966473
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index 7c82bdf..030d510 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -356,106 +356,132 @@
}
Expression _dummyValue(analyzer.DartType type) {
- if (type.isDartCoreBool) {
+ if (type is analyzer.FunctionType) {
+ return _dummyFunctionValue(type);
+ }
+
+ if (type is! analyzer.InterfaceType) {
+ // TODO(srawlins): This case is not known.
+ return literalNull;
+ }
+
+ var interfaceType = type as analyzer.InterfaceType;
+ var typeArguments = interfaceType.typeArguments;
+ if (interfaceType.isDartCoreBool) {
return literalFalse;
- } else if (type.isDartCoreDouble) {
+ } else if (interfaceType.isDartCoreDouble) {
return literalNum(0.0);
- } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) {
- var typeArgument = (type as analyzer.InterfaceType).typeArguments.first;
+ } else if (interfaceType.isDartAsyncFuture ||
+ interfaceType.isDartAsyncFutureOr) {
+ var typeArgument = typeArguments.first;
return refer('Future')
.property('value')
.call([_dummyValue(typeArgument)]);
- } else if (type.isDartCoreInt) {
+ } else if (interfaceType.isDartCoreInt) {
return literalNum(0);
- } else if (type.isDartCoreIterable) {
+ } else if (interfaceType.isDartCoreIterable) {
return literalList([]);
- } else if (type.isDartCoreList) {
- return literalList([]);
- } else if (type.isDartCoreMap) {
- return literalMap({});
- } else if (type.isDartCoreNum) {
+ } else if (interfaceType.isDartCoreList) {
+ assert(typeArguments.length == 1);
+ var elementType = _typeReference(typeArguments[0]);
+ return literalList([], elementType);
+ } else if (interfaceType.isDartCoreMap) {
+ assert(typeArguments.length == 2);
+ var keyType = _typeReference(typeArguments[0]);
+ var valueType = _typeReference(typeArguments[1]);
+ return literalMap({}, keyType, valueType);
+ } else if (interfaceType.isDartCoreNum) {
return literalNum(0);
- } else if (type.isDartCoreSet) {
- // This is perhaps a dangerous hack. The code, `{}`, is parsed as a Set
- // literal if it is used in a context which explicitly expects a Set.
- return literalMap({});
- } else if (type.element?.declaration == typeProvider.streamElement) {
- return refer('Stream').property('empty').call([]);
- } else if (type.isDartCoreString) {
+ } else if (interfaceType.isDartCoreSet) {
+ assert(typeArguments.length == 1);
+ var elementType = _typeReference(typeArguments[0]);
+ return literalSet({}, elementType);
+ } else if (interfaceType.element?.declaration ==
+ typeProvider.streamElement) {
+ assert(typeArguments.length == 1);
+ var elementType = _typeReference(typeArguments[0]);
+ return TypeReference((b) {
+ b
+ ..symbol = 'Stream'
+ ..types.add(elementType);
+ }).property('empty').call([]);
+ } else if (interfaceType.isDartCoreString) {
return literalString('');
- } else {
- // This class is unknown; we must likely generate a fake class, and return
- // an instance here.
- return _dummyValueImplementing(type);
}
+
+ // This class is unknown; we must likely generate a fake class, and return
+ // an instance here.
+ return _dummyValueImplementing(type);
}
- Expression _dummyValueImplementing(analyzer.DartType dartType) {
- // For each type parameter on [classToMock], the Mock class needs a type
- // parameter with same type variables, and a mirrored type argument for
- // the "implements" clause.
- var typeArguments = <Reference>[];
- var elementToFake = dartType.element;
- if (elementToFake is ClassElement) {
- if (elementToFake.isEnum) {
- return _typeReference(dartType).property(
- elementToFake.fields.firstWhere((f) => f.isEnumConstant).name);
- } else {
- // There is a potential for these names to collide. If one mock class
- // requires a fake for a certain Foo, and another mock class requires a
- // fake for a different Foo, they will collide.
- var fakeName = '_Fake${dartType.name}';
- // Only make one fake class for each class that needs to be faked.
- if (!fakedClassElements.contains(elementToFake)) {
- fakeClasses.add(Class((cBuilder) {
- cBuilder
- ..name = fakeName
- ..extend = refer('Fake', 'package:mockito/mockito.dart');
- if (elementToFake.typeParameters != null) {
- for (var typeParameter in elementToFake.typeParameters) {
- cBuilder.types.add(_typeParameterReference(typeParameter));
- typeArguments.add(refer(typeParameter.name));
- }
- }
- cBuilder.implements.add(TypeReference((b) {
- b
- ..symbol = dartType.name
- ..url = _typeImport(dartType)
- ..types.addAll(typeArguments);
- }));
- }));
- fakedClassElements.add(elementToFake);
+ Expression _dummyFunctionValue(analyzer.FunctionType type) {
+ return Method((b) {
+ // The positional parameters in a FunctionType have no names. This
+ // counter lets us create unique dummy names.
+ var counter = 0;
+ for (final parameter in type.parameters) {
+ if (parameter.isRequiredPositional) {
+ b.requiredParameters
+ .add(_matchingParameter(parameter, defaultName: '__p$counter'));
+ counter++;
+ } else if (parameter.isOptionalPositional) {
+ b.optionalParameters
+ .add(_matchingParameter(parameter, defaultName: '__p$counter'));
+ counter++;
+ } else if (parameter.isNamed) {
+ b.optionalParameters.add(_matchingParameter(parameter));
}
- return refer(fakeName).newInstance([]);
}
- } else if (dartType is analyzer.FunctionType) {
- return Method((b) {
- // The positional parameters in a FunctionType have no names. This
- // counter lets us create unique dummy names.
- var counter = 0;
- for (final parameter in dartType.parameters) {
- if (parameter.isRequiredPositional) {
- b.requiredParameters
- .add(_matchingParameter(parameter, defaultName: '__p$counter'));
- counter++;
- } else if (parameter.isOptionalPositional) {
- b.optionalParameters
- .add(_matchingParameter(parameter, defaultName: '__p$counter'));
- counter++;
- } else if (parameter.isNamed) {
- b.optionalParameters.add(_matchingParameter(parameter));
- }
- }
- if (dartType.returnType.isVoid) {
- b.body = Code('');
- } else {
- b.body = _dummyValue(dartType.returnType).code;
- }
- }).closure;
- }
+ if (type.returnType.isVoid) {
+ b.body = Code('');
+ } else {
+ b.body = _dummyValue(type.returnType).code;
+ }
+ }).closure;
+ }
- // We shouldn't get here.
- return literalNull;
+ Expression _dummyValueImplementing(analyzer.InterfaceType dartType) {
+ // For each type parameter on [dartType], the Mock class needs a type
+ // parameter with same type variables, and a mirrored type argument for the
+ // "implements" clause.
+ var typeParameters = <Reference>[];
+ var elementToFake = dartType.element;
+ if (elementToFake.isEnum) {
+ return _typeReference(dartType).property(
+ elementToFake.fields.firstWhere((f) => f.isEnumConstant).name);
+ } else {
+ // There is a potential for these names to collide. If one mock class
+ // requires a fake for a certain Foo, and another mock class requires a
+ // fake for a different Foo, they will collide.
+ var fakeName = '_Fake${dartType.name}';
+ // Only make one fake class for each class that needs to be faked.
+ if (!fakedClassElements.contains(elementToFake)) {
+ fakeClasses.add(Class((cBuilder) {
+ cBuilder
+ ..name = fakeName
+ ..extend = refer('Fake', 'package:mockito/mockito.dart');
+ if (elementToFake.typeParameters != null) {
+ for (var typeParameter in elementToFake.typeParameters) {
+ cBuilder.types.add(_typeParameterReference(typeParameter));
+ typeParameters.add(refer(typeParameter.name));
+ }
+ }
+ cBuilder.implements.add(TypeReference((b) {
+ b
+ ..symbol = dartType.name
+ ..url = _typeImport(dartType)
+ ..types.addAll(typeParameters);
+ }));
+ }));
+ fakedClassElements.add(elementToFake);
+ }
+ var typeArguments = dartType.typeArguments;
+ return TypeReference((b) {
+ b
+ ..symbol = fakeName
+ ..types.addAll(typeArguments.map(_typeReference));
+ }).newInstance([]);
+ }
}
/// Returns a [Parameter] which matches [parameter].
@@ -582,6 +608,9 @@
for (var parameter in type.namedParameterTypes.entries) {
b.namedParameters[parameter.key] = _typeReference(parameter.value);
}
+ if (type.typeFormals != null) {
+ b.types.addAll(type.typeFormals.map(_typeParameterReference));
+ }
});
}
return TypeReference((b) {
diff --git a/test/builder_test.dart b/test/builder_test.dart
index ba0e3b9..578bb45 100644
--- a/test/builder_test.dart
+++ b/test/builder_test.dart
@@ -145,7 +145,7 @@
}
'''),
_containsAllOf('_i3.Stream<int> m() =>',
- 'super.noSuchMethod(Invocation.method(#m, []), Stream.empty());'),
+ 'super.noSuchMethod(Invocation.method(#m, []), Stream<int>.empty());'),
);
});
@@ -534,40 +534,63 @@
);
});
- test('matches nullability of return types', () async {
- await _testWithNonNullable(
- {
- ...annotationsAsset,
- ...simpleTestAsset,
- 'foo|lib/foo.dart': dedent(r'''
+ test('matches nullability of non-nullable return type', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
abstract class Foo {
- 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);
+ int m(int a);
}
'''),
- },
- outputs: {
- 'foo|test/foo_test.mocks.dart': dedent(r'''
- import 'package:mockito/mockito.dart' as _i1;
- import 'package:foo/foo.dart' as _i2;
+ _containsAllOf(
+ 'int m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]), 0);'),
+ );
+ });
- /// A class which mocks [Foo].
- ///
- /// See the documentation for Mockito's code generation for more information.
- class MockFoo extends _i1.Mock implements _i2.Foo {
- 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]));
+ test('matches nullability of nullable return type', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ abstract class Foo {
+ int? m(int a);
}
'''),
- },
+ _containsAllOf(
+ 'int? m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'),
+ );
+ });
+
+ test('matches nullability of return type type arguments', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ abstract class Foo {
+ List<int?> m(int a);
+ }
+ '''),
+ _containsAllOf('List<int?> m(int? a) =>',
+ 'super.noSuchMethod(Invocation.method(#m, [a]), <int?>[]);'),
+ );
+ });
+
+ test('matches nullability of nullable type variable return type', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ abstract class Foo {
+ T? m<T>(int a);
+ }
+ '''),
+ _containsAllOf(
+ 'T? m<T>(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'),
+ );
+ });
+
+ test('overrides implicit return type with dynamic', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ abstract class Foo {
+ m(int a);
+ }
+ '''),
+ _containsAllOf(
+ 'dynamic m(int? a) => super.noSuchMethod(Invocation.method(#m, [a]));'),
);
});
@@ -923,8 +946,8 @@
List<Foo> m() => [Foo()];
}
'''),
- _containsAllOf(
- 'List<_i2.Foo> m() => super.noSuchMethod(Invocation.method(#m, []), []);'),
+ _containsAllOf('List<_i2.Foo> m() =>',
+ 'super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>[]);'),
);
});
@@ -935,8 +958,8 @@
Set<Foo> m() => {Foo()};
}
'''),
- _containsAllOf(
- 'Set<_i2.Foo> m() => super.noSuchMethod(Invocation.method(#m, []), {});'),
+ _containsAllOf('Set<_i2.Foo> m() =>',
+ 'super.noSuchMethod(Invocation.method(#m, []), <_i2.Foo>{});'),
);
});
@@ -947,8 +970,20 @@
Map<int, Foo> m() => {7: Foo()};
}
'''),
- _containsAllOf(
- 'Map<int, _i2.Foo> m() => super.noSuchMethod(Invocation.method(#m, []), {});'),
+ _containsAllOf('Map<int, _i2.Foo> m() =>',
+ 'super.noSuchMethod(Invocation.method(#m, []), <int, _i2.Foo>{});'),
+ );
+ });
+
+ test('creates dummy non-null raw-typed return value', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ abstract class Foo {
+ Map m();
+ }
+ '''),
+ _containsAllOf('Map<dynamic, dynamic> m() =>',
+ 'super.noSuchMethod(Invocation.method(#m, []), <dynamic, dynamic>{});'),
);
});
@@ -965,6 +1000,18 @@
);
});
+ test('creates dummy non-null Stream return value', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ abstract class Foo {
+ Stream<int> m();
+ }
+ '''),
+ _containsAllOf('Stream<int> m() =>',
+ 'super.noSuchMethod(Invocation.method(#m, []), Stream<int>.empty());'),
+ );
+ });
+
test('creates dummy non-null return values for unknown classes', () async {
await _expectSingleNonNullableOutput(
dedent(r'''
@@ -981,6 +1028,19 @@
);
});
+ test('creates dummy non-null return values for generic type', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ abstract class Foo {
+ Bar<int> m();
+ }
+ class Bar<T> {}
+ '''),
+ _containsAllOf('Bar<int> m() =>',
+ 'super.noSuchMethod(Invocation.method(#m, []), _FakeBar<int>());'),
+ );
+ });
+
test('creates dummy non-null return values for enums', () async {
await _expectSingleNonNullableOutput(
dedent(r'''
@@ -998,7 +1058,7 @@
});
test(
- 'creates a dummy non-null return function-typed value, with optional '
+ 'creates a dummy non-null function-typed return value, with optional '
'parameters', () async {
await _expectSingleNonNullableOutput(
dedent(r'''
@@ -1012,7 +1072,7 @@
});
test(
- 'creates a dummy non-null return function-typed value, with named '
+ 'creates a dummy non-null function-typed return value, with named '
'parameters', () async {
await _expectSingleNonNullableOutput(
dedent(r'''
@@ -1026,7 +1086,7 @@
});
test(
- 'creates a dummy non-null return function-typed value, with non-core '
+ 'creates a dummy non-null function-typed return value, with non-core '
'return type', () async {
await _expectSingleNonNullableOutput(
dedent(r'''
@@ -1039,6 +1099,44 @@
);
});
+ test('creates a dummy non-null generic function-typed return value',
+ () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ T Function<T>(T) m() => (int i, [String s]) {};
+ }
+ '''),
+ _containsAllOf('T Function<T>(T) m() =>',
+ 'super.noSuchMethod(Invocation.method(#m, []), (T __p0) => null);'),
+ );
+ });
+
+ test('generates a fake class used in return values', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ Bar m1() => Bar('name1');
+ }
+ class Bar {}
+ '''),
+ _containsAllOf('class _FakeBar extends _i1.Fake implements _i2.Bar {}'),
+ );
+ });
+
+ test('generates a fake generic class used in return values', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ Bar m1() => Bar('name1');
+ }
+ class Bar<T, U> {}
+ '''),
+ _containsAllOf(
+ 'class _FakeBar<T, U> extends _i1.Fake implements _i2.Bar<T, U> {}'),
+ );
+ });
+
test('deduplicates fake classes', () async {
await _expectSingleNonNullableOutput(
dedent(r'''