2626import com .sun .tools .javac .code .TargetType ;
2727import com .sun .tools .javac .code .Type ;
2828import com .sun .tools .javac .tree .JCTree ;
29+ import com .sun .tools .javac .util .ListBuffer ;
2930import com .uber .nullaway .CodeAnnotationInfo ;
3031import com .uber .nullaway .Config ;
3132import com .uber .nullaway .ErrorBuilder ;
3536import com .uber .nullaway .handlers .Handler ;
3637import java .util .ArrayList ;
3738import java .util .HashMap ;
39+ import java .util .LinkedHashMap ;
3840import java .util .List ;
3941import java .util .Map ;
4042import java .util .Objects ;
4648/** Methods for performing checks related to generic types and nullability. */
4749public final class GenericsChecks {
4850
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 <>();
5159
5260 /**
5361 * Checks that for an instantiated generic type, {@code @Nullable} types are only used for type
@@ -413,13 +421,16 @@ private static void reportInvalidOverridingMethodParamTypeError(
413421 * @param analysis the analysis object
414422 * @param state the visitor state
415423 */
416- public static void checkTypeParameterNullnessForAssignability (
424+ public void checkTypeParameterNullnessForAssignability (
417425 Tree tree , NullAway analysis , VisitorState state ) {
418426 Config config = analysis .getConfig ();
419427 if (!config .isJSpecifyMode ()) {
420428 return ;
421429 }
422430 Type lhsType = getTreeType (tree , config );
431+ if (lhsType == null ) {
432+ return ;
433+ }
423434 Tree rhsTree ;
424435 if (tree instanceof VariableTree ) {
425436 VariableTree varTree = (VariableTree ) tree ;
@@ -435,14 +446,58 @@ public static void checkTypeParameterNullnessForAssignability(
435446 }
436447 Type rhsType = getTreeType (rhsTree , config );
437448
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 ) {
439473 boolean isAssignmentValid = subtypeParameterNullability (lhsType , rhsType , state , config );
440474 if (!isAssignmentValid ) {
441475 reportInvalidAssignmentInstantiationError (tree , lhsType , rhsType , state , analysis );
442476 }
443477 }
444478 }
445479
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+
446501 /**
447502 * Checks that the nullability of type parameters for a returned expression matches that of the
448503 * type parameters of the enclosing method's return type.
@@ -613,7 +668,7 @@ public static void checkTypeParameterNullnessForConditionalExpression(
613668 * @param analysis the analysis object
614669 * @param state the visitor state
615670 */
616- public static void compareGenericTypeParameterNullabilityForCall (
671+ public void compareGenericTypeParameterNullabilityForCall (
617672 Symbol .MethodSymbol methodSymbol ,
618673 Tree tree ,
619674 List <? extends ExpressionTree > actualParams ,
@@ -640,7 +695,7 @@ public static void compareGenericTypeParameterNullabilityForCall(
640695 TypeSubstitutionUtils .memberType (state .getTypes (), enclosingType , methodSymbol , config );
641696 }
642697 }
643- // substitute type arguments for generic methods
698+ // substitute type arguments for generic methods with explicit type arguments
644699 if (tree instanceof MethodInvocationTree && methodSymbol .type instanceof Type .ForAll ) {
645700 invokedMethodType =
646701 substituteTypeArgsInGenericMethodType (
@@ -830,7 +885,7 @@ public static Nullness getGenericMethodReturnTypeNullness(
830885 * @return Nullness of invocation's return type, or {@code NONNULL} if the call does not invoke an
831886 * instance method
832887 */
833- public static Nullness getGenericReturnNullnessAtInvocation (
888+ public Nullness getGenericReturnNullnessAtInvocation (
834889 Symbol .MethodSymbol invokedMethodSymbol ,
835890 MethodInvocationTree tree ,
836891 VisitorState state ,
@@ -883,7 +938,7 @@ private static com.sun.tools.javac.util.List<Type> convertTreesToTypes(
883938 * @param config the NullAway config
884939 * @return the substituted method type for the generic method
885940 */
886- private static Type substituteTypeArgsInGenericMethodType (
941+ private Type substituteTypeArgsInGenericMethodType (
887942 MethodInvocationTree methodInvocationTree ,
888943 Symbol .MethodSymbol methodSymbol ,
889944 VisitorState state ,
@@ -894,6 +949,17 @@ private static Type substituteTypeArgsInGenericMethodType(
894949
895950 Type .ForAll forAllType = (Type .ForAll ) methodSymbol .type ;
896951 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+ }
897963 return TypeSubstitutionUtils .subst (
898964 state .getTypes (), underlyingMethodType , forAllType .tvars , explicitTypeArgs , config );
899965 }
@@ -932,7 +998,7 @@ private static Type substituteTypeArgsInGenericMethodType(
932998 * @return Nullness of parameter at {@code paramIndex}, or {@code NONNULL} if the call does not
933999 * invoke an instance method
9341000 */
935- public static Nullness getGenericParameterNullnessAtInvocation (
1001+ public Nullness getGenericParameterNullnessAtInvocation (
9361002 int paramIndex ,
9371003 Symbol .MethodSymbol invokedMethodSymbol ,
9381004 MethodInvocationTree tree ,
@@ -941,7 +1007,6 @@ public static Nullness getGenericParameterNullnessAtInvocation(
9411007 // If generic method invocation
9421008 if (!invokedMethodSymbol .getTypeParameters ().isEmpty ()) {
9431009 // Substitute the argument types within the MethodType
944- // NOTE: if explicitTypeArgs is empty, this is a noop
9451010 List <Type > substitutedParamTypes =
9461011 substituteTypeArgsInGenericMethodType (tree , invokedMethodSymbol , state , config )
9471012 .getParameterTypes ();
@@ -1160,6 +1225,14 @@ public static boolean passingLambdaOrMethodRefWithGenericReturnToUnmarkedCode(
11601225 return callingUnannotated ;
11611226 }
11621227
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+
11631236 public static boolean isNullableAnnotated (Type type , Config config ) {
11641237 return Nullness .hasNullableAnnotation (type .getAnnotationMirrors ().stream (), config );
11651238 }
0 commit comments