26
26
import com .sun .tools .javac .code .TargetType ;
27
27
import com .sun .tools .javac .code .Type ;
28
28
import com .sun .tools .javac .tree .JCTree ;
29
+ import com .sun .tools .javac .util .ListBuffer ;
29
30
import com .uber .nullaway .CodeAnnotationInfo ;
30
31
import com .uber .nullaway .Config ;
31
32
import com .uber .nullaway .ErrorBuilder ;
35
36
import com .uber .nullaway .handlers .Handler ;
36
37
import java .util .ArrayList ;
37
38
import java .util .HashMap ;
39
+ import java .util .LinkedHashMap ;
38
40
import java .util .List ;
39
41
import java .util .Map ;
40
42
import java .util .Objects ;
46
48
/** Methods for performing checks related to generic types and nullability. */
47
49
public final class GenericsChecks {
48
50
49
- /** Do not instantiate; all methods should be static */
50
- private GenericsChecks () {}
51
+ /**
52
+ * Maps a MethodInvocationTree representing a call to a generic method to a substitution for its
53
+ * type arguments. The call must not have any explicit type arguments. The substitution is a map
54
+ * from type variables for the method to their inferred type arguments (most importantly with
55
+ * inferred nullability information).
56
+ */
57
+ private final Map <MethodInvocationTree , Map <TypeVariable , Type >>
58
+ inferredSubstitutionsForGenericMethodCalls = new LinkedHashMap <>();
51
59
52
60
/**
53
61
* Checks that for an instantiated generic type, {@code @Nullable} types are only used for type
@@ -413,13 +421,16 @@ private static void reportInvalidOverridingMethodParamTypeError(
413
421
* @param analysis the analysis object
414
422
* @param state the visitor state
415
423
*/
416
- public static void checkTypeParameterNullnessForAssignability (
424
+ public void checkTypeParameterNullnessForAssignability (
417
425
Tree tree , NullAway analysis , VisitorState state ) {
418
426
Config config = analysis .getConfig ();
419
427
if (!config .isJSpecifyMode ()) {
420
428
return ;
421
429
}
422
430
Type lhsType = getTreeType (tree , config );
431
+ if (lhsType == null ) {
432
+ return ;
433
+ }
423
434
Tree rhsTree ;
424
435
if (tree instanceof VariableTree ) {
425
436
VariableTree varTree = (VariableTree ) tree ;
@@ -435,14 +446,58 @@ public static void checkTypeParameterNullnessForAssignability(
435
446
}
436
447
Type rhsType = getTreeType (rhsTree , config );
437
448
438
- if (lhsType != null && rhsType != null ) {
449
+ if (rhsTree instanceof MethodInvocationTree ) {
450
+ MethodInvocationTree methodInvocationTree = (MethodInvocationTree ) rhsTree ;
451
+ Symbol .MethodSymbol methodSymbol = ASTHelpers .getSymbol (methodInvocationTree );
452
+ if (methodSymbol .type instanceof Type .ForAll
453
+ && methodInvocationTree .getTypeArguments ().isEmpty ()) {
454
+ // generic method call with no explicit generic arguments
455
+ // update inferred type arguments based on the assignment context
456
+ InferSubstitutionViaAssignmentContextVisitor inferVisitor =
457
+ new InferSubstitutionViaAssignmentContextVisitor (config );
458
+ Type returnType = methodSymbol .getReturnType ();
459
+ returnType .accept (inferVisitor , lhsType );
460
+
461
+ Map <TypeVariable , Type > substitution = inferVisitor .getInferredSubstitution ();
462
+ inferredSubstitutionsForGenericMethodCalls .put (methodInvocationTree , substitution );
463
+ if (rhsType != null ) {
464
+ // update rhsType with inferred substitution
465
+ rhsType =
466
+ substituteInferredTypesForTypeVariables (
467
+ state , methodSymbol .getReturnType (), substitution , config );
468
+ }
469
+ }
470
+ }
471
+
472
+ if (rhsType != null ) {
439
473
boolean isAssignmentValid = subtypeParameterNullability (lhsType , rhsType , state , config );
440
474
if (!isAssignmentValid ) {
441
475
reportInvalidAssignmentInstantiationError (tree , lhsType , rhsType , state , analysis );
442
476
}
443
477
}
444
478
}
445
479
480
+ /**
481
+ * Substitutes inferred types for type variables within a type.
482
+ *
483
+ * @param state The visitor state
484
+ * @param targetType The type with type variables on which substitutions will be applied
485
+ * @param substitution The cache that maps type variables to its inferred types
486
+ * @param config Configuration for the analysis
487
+ * @return {@code targetType} with the substitutions applied
488
+ */
489
+ private Type substituteInferredTypesForTypeVariables (
490
+ VisitorState state , Type targetType , Map <TypeVariable , Type > substitution , Config config ) {
491
+ ListBuffer <Type > typeVars = new ListBuffer <>();
492
+ ListBuffer <Type > inferredTypes = new ListBuffer <>();
493
+ for (Map .Entry <TypeVariable , Type > entry : substitution .entrySet ()) {
494
+ typeVars .append ((Type ) entry .getKey ());
495
+ inferredTypes .append (entry .getValue ());
496
+ }
497
+ return TypeSubstitutionUtils .subst (
498
+ state .getTypes (), targetType , typeVars .toList (), inferredTypes .toList (), config );
499
+ }
500
+
446
501
/**
447
502
* Checks that the nullability of type parameters for a returned expression matches that of the
448
503
* type parameters of the enclosing method's return type.
@@ -613,7 +668,7 @@ public static void checkTypeParameterNullnessForConditionalExpression(
613
668
* @param analysis the analysis object
614
669
* @param state the visitor state
615
670
*/
616
- public static void compareGenericTypeParameterNullabilityForCall (
671
+ public void compareGenericTypeParameterNullabilityForCall (
617
672
Symbol .MethodSymbol methodSymbol ,
618
673
Tree tree ,
619
674
List <? extends ExpressionTree > actualParams ,
@@ -640,7 +695,7 @@ public static void compareGenericTypeParameterNullabilityForCall(
640
695
TypeSubstitutionUtils .memberType (state .getTypes (), enclosingType , methodSymbol , config );
641
696
}
642
697
}
643
- // substitute type arguments for generic methods
698
+ // substitute type arguments for generic methods with explicit type arguments
644
699
if (tree instanceof MethodInvocationTree && methodSymbol .type instanceof Type .ForAll ) {
645
700
invokedMethodType =
646
701
substituteTypeArgsInGenericMethodType (
@@ -830,7 +885,7 @@ public static Nullness getGenericMethodReturnTypeNullness(
830
885
* @return Nullness of invocation's return type, or {@code NONNULL} if the call does not invoke an
831
886
* instance method
832
887
*/
833
- public static Nullness getGenericReturnNullnessAtInvocation (
888
+ public Nullness getGenericReturnNullnessAtInvocation (
834
889
Symbol .MethodSymbol invokedMethodSymbol ,
835
890
MethodInvocationTree tree ,
836
891
VisitorState state ,
@@ -883,7 +938,7 @@ private static com.sun.tools.javac.util.List<Type> convertTreesToTypes(
883
938
* @param config the NullAway config
884
939
* @return the substituted method type for the generic method
885
940
*/
886
- private static Type substituteTypeArgsInGenericMethodType (
941
+ private Type substituteTypeArgsInGenericMethodType (
887
942
MethodInvocationTree methodInvocationTree ,
888
943
Symbol .MethodSymbol methodSymbol ,
889
944
VisitorState state ,
@@ -894,6 +949,17 @@ private static Type substituteTypeArgsInGenericMethodType(
894
949
895
950
Type .ForAll forAllType = (Type .ForAll ) methodSymbol .type ;
896
951
Type .MethodType underlyingMethodType = (Type .MethodType ) forAllType .qtype ;
952
+
953
+ // There are no explicit type arguments, so use the inferred types
954
+ if (explicitTypeArgs .isEmpty ()) {
955
+ if (inferredSubstitutionsForGenericMethodCalls .containsKey (methodInvocationTree )) {
956
+ return substituteInferredTypesForTypeVariables (
957
+ state ,
958
+ underlyingMethodType ,
959
+ inferredSubstitutionsForGenericMethodCalls .get (methodInvocationTree ),
960
+ config );
961
+ }
962
+ }
897
963
return TypeSubstitutionUtils .subst (
898
964
state .getTypes (), underlyingMethodType , forAllType .tvars , explicitTypeArgs , config );
899
965
}
@@ -932,7 +998,7 @@ private static Type substituteTypeArgsInGenericMethodType(
932
998
* @return Nullness of parameter at {@code paramIndex}, or {@code NONNULL} if the call does not
933
999
* invoke an instance method
934
1000
*/
935
- public static Nullness getGenericParameterNullnessAtInvocation (
1001
+ public Nullness getGenericParameterNullnessAtInvocation (
936
1002
int paramIndex ,
937
1003
Symbol .MethodSymbol invokedMethodSymbol ,
938
1004
MethodInvocationTree tree ,
@@ -941,7 +1007,6 @@ public static Nullness getGenericParameterNullnessAtInvocation(
941
1007
// If generic method invocation
942
1008
if (!invokedMethodSymbol .getTypeParameters ().isEmpty ()) {
943
1009
// Substitute the argument types within the MethodType
944
- // NOTE: if explicitTypeArgs is empty, this is a noop
945
1010
List <Type > substitutedParamTypes =
946
1011
substituteTypeArgsInGenericMethodType (tree , invokedMethodSymbol , state , config )
947
1012
.getParameterTypes ();
@@ -1160,6 +1225,14 @@ public static boolean passingLambdaOrMethodRefWithGenericReturnToUnmarkedCode(
1160
1225
return callingUnannotated ;
1161
1226
}
1162
1227
1228
+ /**
1229
+ * Clears the cache of inferred substitutions for generic method calls. This should be invoked
1230
+ * after each CompilationUnit to avoid memory leaks.
1231
+ */
1232
+ public void clearCache () {
1233
+ inferredSubstitutionsForGenericMethodCalls .clear ();
1234
+ }
1235
+
1163
1236
public static boolean isNullableAnnotated (Type type , Config config ) {
1164
1237
return Nullness .hasNullableAnnotation (type .getAnnotationMirrors ().stream (), config );
1165
1238
}
0 commit comments