@@ -836,13 +836,19 @@ class _MockTargetGatherer {
836
836
}
837
837
}
838
838
839
+ String get _tryUnsupportedMembersMessage => 'Try generating this mock with '
840
+ "a MockSpec with 'unsupportedMembers' or a dummy generator (see "
841
+ 'https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).' ;
842
+
839
843
/// Checks [function] for properties that would make it un-stubbable.
840
844
///
841
845
/// Types are checked in the following positions:
842
846
/// - return type
843
847
/// - parameter types
844
848
/// - bounds of type parameters
845
- /// - type arguments on types in the above three positions
849
+ /// - recursively, written types on types in the above three positions
850
+ /// (namely, type arguments, return types of function types, and parameter
851
+ /// types of function types)
846
852
///
847
853
/// If any type in the above positions is private, [function] is un-stubbable.
848
854
/// If the return type is potentially non-nullable, [function] is
@@ -861,14 +867,19 @@ class _MockTargetGatherer {
861
867
final errorMessages = < String > [];
862
868
final returnType = function.returnType;
863
869
if (returnType is analyzer.InterfaceType ) {
864
- if (returnType.element2.isPrivate) {
865
- errorMessages.add (
866
- '${enclosingElement .fullName } features a private return type, and '
867
- 'cannot be stubbed.' );
870
+ if (returnType.containsPrivateName) {
871
+ if (! allowUnsupportedMember && ! hasDummyGenerator) {
872
+ errorMessages.add (
873
+ '${enclosingElement .fullName } features a private return type, '
874
+ 'and cannot be stubbed. $_tryUnsupportedMembersMessage ' );
875
+ }
868
876
}
869
877
errorMessages.addAll (_checkTypeArguments (
870
- returnType.typeArguments, enclosingElement,
871
- isParameter: isParameter));
878
+ returnType.typeArguments,
879
+ enclosingElement,
880
+ isParameter: isParameter,
881
+ allowUnsupportedMember: allowUnsupportedMember,
882
+ ));
872
883
} else if (returnType is analyzer.FunctionType ) {
873
884
errorMessages.addAll (_checkFunction (returnType, enclosingElement,
874
885
allowUnsupportedMember: allowUnsupportedMember,
@@ -878,11 +889,10 @@ class _MockTargetGatherer {
878
889
! allowUnsupportedMember &&
879
890
! hasDummyGenerator &&
880
891
_entryLib.typeSystem.isPotentiallyNonNullable (returnType)) {
881
- errorMessages.add (
882
- '${enclosingElement .fullName } features a non-nullable unknown '
883
- 'return type, and cannot be stubbed. Try generating this mock with '
884
- "a MockSpec with 'unsupportedMembers' or a dummy generator (see "
885
- 'https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).' );
892
+ errorMessages
893
+ .add ('${enclosingElement .fullName } features a non-nullable unknown '
894
+ 'return type, and cannot be stubbed. '
895
+ '$_tryUnsupportedMembersMessage ' );
886
896
}
887
897
}
888
898
@@ -894,13 +904,19 @@ class _MockTargetGatherer {
894
904
// Technically, we can expand the type in the mock to something like
895
905
// `Object?`. However, until there is a decent use case, we will not
896
906
// generate such a mock.
897
- errorMessages.add (
898
- '${enclosingElement .fullName } features a private parameter type, '
899
- "'${parameterTypeElement .name }', and cannot be stubbed." );
907
+ if (! allowUnsupportedMember) {
908
+ errorMessages.add (
909
+ '${enclosingElement .fullName } features a private parameter '
910
+ "type, '${parameterTypeElement .name }', and cannot be stubbed. "
911
+ '$_tryUnsupportedMembersMessage ' );
912
+ }
900
913
}
901
914
errorMessages.addAll (_checkTypeArguments (
902
- parameterType.typeArguments, enclosingElement,
903
- isParameter: true ));
915
+ parameterType.typeArguments,
916
+ enclosingElement,
917
+ isParameter: true ,
918
+ allowUnsupportedMember: allowUnsupportedMember,
919
+ ));
904
920
} else if (parameterType is analyzer.FunctionType ) {
905
921
errorMessages.addAll (
906
922
_checkFunction (parameterType, enclosingElement, isParameter: true ));
@@ -928,6 +944,8 @@ class _MockTargetGatherer {
928
944
var typeParameter = element.bound;
929
945
if (typeParameter == null ) continue ;
930
946
if (typeParameter is analyzer.InterfaceType ) {
947
+ // TODO(srawlins): Check for private names in bound; could be
948
+ // `List<_Bar>`.
931
949
if (typeParameter.element2.isPrivate) {
932
950
errorMessages.add (
933
951
'${enclosingElement .fullName } features a private type parameter '
@@ -947,18 +965,23 @@ class _MockTargetGatherer {
947
965
List <analyzer.DartType > typeArguments,
948
966
Element enclosingElement, {
949
967
bool isParameter = false ,
968
+ bool allowUnsupportedMember = false ,
950
969
}) {
951
970
var errorMessages = < String > [];
952
971
for (var typeArgument in typeArguments) {
953
972
if (typeArgument is analyzer.InterfaceType ) {
954
- if (typeArgument.element2.isPrivate) {
973
+ if (typeArgument.element2.isPrivate && ! allowUnsupportedMember ) {
955
974
errorMessages.add (
956
975
'${enclosingElement .fullName } features a private type argument, '
957
- 'and cannot be stubbed.' );
976
+ 'and cannot be stubbed. $ _tryUnsupportedMembersMessage ' );
958
977
}
959
978
} else if (typeArgument is analyzer.FunctionType ) {
960
- errorMessages.addAll (_checkFunction (typeArgument, enclosingElement,
961
- isParameter: isParameter));
979
+ errorMessages.addAll (_checkFunction (
980
+ typeArgument,
981
+ enclosingElement,
982
+ isParameter: isParameter,
983
+ allowUnsupportedMember: allowUnsupportedMember,
984
+ ));
962
985
}
963
986
}
964
987
return errorMessages;
@@ -1233,11 +1256,16 @@ class _MockClassInfo {
1233
1256
void _buildOverridingMethod (MethodBuilder builder, MethodElement method) {
1234
1257
var name = method.displayName;
1235
1258
if (method.isOperator) name = 'operator$name ' ;
1259
+ final returnType = method.returnType;
1236
1260
builder
1237
1261
..name = name
1238
1262
..annotations.add (referImported ('override' , 'dart:core' ))
1239
- ..returns = _typeReference (method.returnType)
1240
1263
..types.addAll (method.typeParameters.map (_typeParameterReference));
1264
+ // We allow overriding a method with a private return type by omitting the
1265
+ // return type (which is then inherited).
1266
+ if (! returnType.containsPrivateName) {
1267
+ builder.returns = _typeReference (returnType);
1268
+ }
1241
1269
1242
1270
// These two variables store the arguments that will be passed to the
1243
1271
// [Invocation] built for `noSuchMethod`.
@@ -1246,20 +1274,16 @@ class _MockClassInfo {
1246
1274
1247
1275
var position = 0 ;
1248
1276
for (final parameter in method.parameters) {
1249
- if (parameter.isRequiredPositional) {
1250
- final superParameterType =
1251
- _escapeCovariance (parameter, position: position);
1252
- final matchingParameter = _matchingParameter (parameter,
1253
- superParameterType: superParameterType, forceNullable: true );
1254
- builder.requiredParameters.add (matchingParameter);
1255
- invocationPositionalArgs.add (refer (parameter.displayName));
1256
- position++ ;
1257
- } else if (parameter.isOptionalPositional) {
1277
+ if (parameter.isRequiredPositional || parameter.isOptionalPositional) {
1258
1278
final superParameterType =
1259
1279
_escapeCovariance (parameter, position: position);
1260
1280
final matchingParameter = _matchingParameter (parameter,
1261
1281
superParameterType: superParameterType, forceNullable: true );
1262
- builder.optionalParameters.add (matchingParameter);
1282
+ if (parameter.isRequiredPositional) {
1283
+ builder.requiredParameters.add (matchingParameter);
1284
+ } else {
1285
+ builder.optionalParameters.add (matchingParameter);
1286
+ }
1263
1287
invocationPositionalArgs.add (refer (parameter.displayName));
1264
1288
position++ ;
1265
1289
} else if (parameter.isNamed) {
@@ -1282,11 +1306,18 @@ class _MockClassInfo {
1282
1306
return ;
1283
1307
}
1284
1308
1285
- final returnType = method.returnType;
1309
+ final returnTypeIsTypeVariable =
1310
+ typeSystem.isPotentiallyNonNullable (returnType) &&
1311
+ returnType is analyzer.TypeParameterType ;
1286
1312
final fallbackGenerator = fallbackGenerators[method.name];
1287
- if (typeSystem.isPotentiallyNonNullable (returnType) &&
1288
- returnType is analyzer.TypeParameterType &&
1289
- fallbackGenerator == null ) {
1313
+ final parametersContainPrivateName =
1314
+ method.parameters.any ((p) => p.type.containsPrivateName);
1315
+ final throwsUnsupported = fallbackGenerator == null &&
1316
+ (returnTypeIsTypeVariable ||
1317
+ returnType.containsPrivateName ||
1318
+ parametersContainPrivateName);
1319
+
1320
+ if (throwsUnsupported) {
1290
1321
if (! mockTarget.unsupportedMembers.contains (name)) {
1291
1322
// We shouldn't get here as this is guarded against in
1292
1323
// [_MockTargetGatherer._checkFunction].
@@ -1557,10 +1588,11 @@ class _MockClassInfo {
1557
1588
'$defaultName ' );
1558
1589
final name = parameter.name.isEmpty ? defaultName! : parameter.name;
1559
1590
return Parameter ((pBuilder) {
1560
- pBuilder
1561
- ..name = name
1562
- . .type =
1591
+ pBuilder.name = name;
1592
+ if ( ! superParameterType.containsPrivateName) {
1593
+ pBuilder .type =
1563
1594
_typeReference (superParameterType, forceNullable: forceNullable);
1595
+ }
1564
1596
if (parameter.isNamed) pBuilder.named = true ;
1565
1597
if (parameter.defaultValueCode != null ) {
1566
1598
try {
@@ -2025,6 +2057,30 @@ extension on Element {
2025
2057
}
2026
2058
2027
2059
extension on analyzer.DartType {
2060
+ /// Whether this type contains a private name, perhaps in a type argument or a
2061
+ /// function type's parameters, etc.
2062
+ bool get containsPrivateName {
2063
+ final self = this ;
2064
+ if (self is analyzer.DynamicType ) {
2065
+ return false ;
2066
+ } else if (self is analyzer.InterfaceType ) {
2067
+ return self.element2.isPrivate ||
2068
+ self.typeArguments.any ((t) => t.containsPrivateName);
2069
+ } else if (self is analyzer.FunctionType ) {
2070
+ return self.returnType.containsPrivateName ||
2071
+ self.parameters.any ((p) => p.type.containsPrivateName);
2072
+ } else if (self is analyzer.NeverType ) {
2073
+ return false ;
2074
+ } else if (self is analyzer.TypeParameterType ) {
2075
+ return false ;
2076
+ } else if (self is analyzer.VoidType ) {
2077
+ return false ;
2078
+ } else {
2079
+ assert (false , 'Unexpected subtype of DartType: ${self .runtimeType }' );
2080
+ return false ;
2081
+ }
2082
+ }
2083
+
2028
2084
/// Returns whether this type is `Future<void>` or `Future<void>?` .
2029
2085
bool get isFutureOfVoid =>
2030
2086
isDartAsyncFuture &&
0 commit comments