3333import com .uber .nullaway .ErrorBuilder ;
3434import com .uber .nullaway .ErrorMessage ;
3535import com .uber .nullaway .NullAway ;
36+ import com .uber .nullaway .NullabilityUtil ;
3637import com .uber .nullaway .Nullness ;
3738import com .uber .nullaway .handlers .Handler ;
3839import java .util .ArrayList ;
@@ -446,42 +447,92 @@ public void checkTypeParameterNullnessForAssignability(
446447 return ;
447448 }
448449 Type rhsType = getTreeType (rhsTree , config );
449-
450- if (rhsTree instanceof MethodInvocationTree ) {
451- MethodInvocationTree methodInvocationTree = (MethodInvocationTree ) rhsTree ;
452- Symbol .MethodSymbol methodSymbol = ASTHelpers .getSymbol (methodInvocationTree );
453- if (methodSymbol .type instanceof Type .ForAll
454- && methodInvocationTree .getTypeArguments ().isEmpty ()) {
455- // generic method call with no explicit generic arguments
456- // update inferred type arguments based on the assignment context
457- boolean invokedMethodIsNullUnmarked =
458- CodeAnnotationInfo .instance (state .context )
459- .isSymbolUnannotated (methodSymbol , config , analysis .getHandler ());
460- InferGenericMethodSubstitutionViaAssignmentContextVisitor inferVisitor =
461- new InferGenericMethodSubstitutionViaAssignmentContextVisitor (
462- state , config , invokedMethodIsNullUnmarked );
463- Type returnType = methodSymbol .getReturnType ();
464- returnType .accept (inferVisitor , lhsType );
465-
466- Map <TypeVariable , Type > substitution = inferVisitor .getInferredSubstitution ();
467- inferredSubstitutionsForGenericMethodCalls .put (methodInvocationTree , substitution );
468- if (rhsType != null ) {
469- // update rhsType with inferred substitution
470- rhsType =
471- substituteInferredTypesForTypeVariables (
472- state , methodSymbol .getReturnType (), substitution , config );
473- }
474- }
475- }
476-
477450 if (rhsType != null ) {
451+ if (rhsTree instanceof MethodInvocationTree ) {
452+ rhsType =
453+ inferGenericMethodCallType (
454+ analysis , state , (MethodInvocationTree ) rhsTree , config , lhsType , rhsType );
455+ }
478456 boolean isAssignmentValid = subtypeParameterNullability (lhsType , rhsType , state , config );
479457 if (!isAssignmentValid ) {
480458 reportInvalidAssignmentInstantiationError (tree , lhsType , rhsType , state , analysis );
481459 }
482460 }
483461 }
484462
463+ /**
464+ * Infers the type of a generic method call based on the assignment context. Side-effects the
465+ * #inferredSubstitutionsForGenericMethodCalls map with the inferred type.
466+ *
467+ * @param analysis the analysis
468+ * @param state the visitor state
469+ * @param invocationTree the method invocation tree representing the call to a generic method
470+ * @param config the analysis config
471+ * @param typeFromAssignmentContext the type being "assigned to" in the assignment context
472+ * @param exprType the type of the right-hand side of the pseudo-assignment, which may be null
473+ * @return the type of the method call after inference
474+ */
475+ private Type inferGenericMethodCallType (
476+ NullAway analysis ,
477+ VisitorState state ,
478+ MethodInvocationTree invocationTree ,
479+ Config config ,
480+ Type typeFromAssignmentContext ,
481+ Type exprType ) {
482+ Type result = exprType ;
483+ MethodInvocationTree methodInvocationTree = invocationTree ;
484+ Symbol .MethodSymbol methodSymbol = ASTHelpers .getSymbol (methodInvocationTree );
485+ if (methodSymbol .type instanceof Type .ForAll
486+ && methodInvocationTree .getTypeArguments ().isEmpty ()) {
487+ // generic method call with no explicit generic arguments
488+ // update inferred type arguments based on the assignment context
489+ boolean invokedMethodIsNullUnmarked =
490+ CodeAnnotationInfo .instance (state .context )
491+ .isSymbolUnannotated (methodSymbol , config , analysis .getHandler ());
492+ Map <TypeVariable , Type > substitution ;
493+ Type returnType = methodSymbol .getReturnType ();
494+ if (returnType instanceof Type .TypeVar ) {
495+ // we need different logic if the return type is a type variable
496+ // if the assignment context type is @Nullable, we shouldn't infer anything, since that
497+ // accommodates the type argument being either @Nullable or @NonNull
498+ Type .TypeVar typeVar = (Type .TypeVar ) returnType ;
499+ substitution = new LinkedHashMap <>();
500+ boolean nonNullAssignmentContextType =
501+ !Nullness .hasNullableAnnotation (
502+ typeFromAssignmentContext .getAnnotationMirrors ().stream (), config );
503+ if (nonNullAssignmentContextType ) {
504+ // if the assignment context type is @NonNull, we can just use it
505+ substitution .put (typeVar , typeFromAssignmentContext );
506+ } else {
507+ Type upperBound = typeVar .getUpperBound ();
508+ boolean typeVarHasNullableUpperBound =
509+ Nullness .hasNullableAnnotation (upperBound .getAnnotationMirrors ().stream (), config );
510+ // if the type variable cannot be @Nullable, we can use the lhsType with any @Nullable
511+ // annotation stripped
512+ if (!typeVarHasNullableUpperBound && !invokedMethodIsNullUnmarked ) {
513+ // we can use the lhsType with any @Nullable annotation stripped
514+ // TODO we should just strip out the top-level @Nullable annotation;
515+ // stripMetadata() also removes nested @Nullable annotations
516+ substitution .put (typeVar , typeFromAssignmentContext .stripMetadata ());
517+ }
518+ }
519+
520+ } else {
521+ InferGenericMethodSubstitutionViaAssignmentContextVisitor inferVisitor =
522+ new InferGenericMethodSubstitutionViaAssignmentContextVisitor (
523+ state , config , invokedMethodIsNullUnmarked );
524+ returnType .accept (inferVisitor , typeFromAssignmentContext );
525+ substitution = inferVisitor .getInferredSubstitution ();
526+ }
527+ inferredSubstitutionsForGenericMethodCalls .put (methodInvocationTree , substitution );
528+ // update with inferred substitution
529+ result =
530+ substituteInferredTypesForTypeVariables (
531+ state , methodSymbol .getReturnType (), substitution , config );
532+ }
533+ return result ;
534+ }
535+
485536 /**
486537 * Substitutes inferred types for type variables within a type.
487538 *
@@ -512,7 +563,7 @@ private Type substituteInferredTypesForTypeVariables(
512563 * @param analysis the analysis object
513564 * @param state the visitor state
514565 */
515- public static void checkTypeParameterNullnessForFunctionReturnType (
566+ public void checkTypeParameterNullnessForFunctionReturnType (
516567 ExpressionTree retExpr ,
517568 Symbol .MethodSymbol methodSymbol ,
518569 NullAway analysis ,
@@ -529,6 +580,16 @@ public static void checkTypeParameterNullnessForFunctionReturnType(
529580 }
530581 Type returnExpressionType = getTreeType (retExpr , config );
531582 if (formalReturnType != null && returnExpressionType != null ) {
583+ if (retExpr instanceof MethodInvocationTree ) {
584+ returnExpressionType =
585+ inferGenericMethodCallType (
586+ analysis ,
587+ state ,
588+ (MethodInvocationTree ) retExpr ,
589+ config ,
590+ formalReturnType ,
591+ returnExpressionType );
592+ }
532593 boolean isReturnTypeValid =
533594 subtypeParameterNullability (formalReturnType , returnExpressionType , state , config );
534595 if (!isReturnTypeValid ) {
@@ -730,28 +791,48 @@ public void compareGenericTypeParameterNullabilityForCall(
730791 // bail out of any checking involving raw types for now
731792 return ;
732793 }
733- Type actualParameter = getTreeType (actualParams .get (i ), config );
734- if (actualParameter != null ) {
735- if (!subtypeParameterNullability (formalParameter , actualParameter , state , config )) {
794+ ExpressionTree currentActualParam = actualParams .get (i );
795+ Type actualParameterType = getTreeType (currentActualParam , config );
796+ if (actualParameterType != null ) {
797+ if (currentActualParam instanceof MethodInvocationTree ) {
798+ // infer the type of the method call based on the assignment context
799+ // and the formal parameter type
800+ actualParameterType =
801+ inferGenericMethodCallType (
802+ analysis ,
803+ state ,
804+ (MethodInvocationTree ) currentActualParam ,
805+ config ,
806+ formalParameter ,
807+ actualParameterType );
808+ }
809+ if (!subtypeParameterNullability (formalParameter , actualParameterType , state , config )) {
736810 reportInvalidParametersNullabilityError (
737- formalParameter , actualParameter , actualParams . get ( i ) , state , analysis );
811+ formalParameter , actualParameterType , currentActualParam , state , analysis );
738812 }
739813 }
740814 }
741- if (isVarArgs && !formalParamTypes .isEmpty ()) {
742- Type .ArrayType varargsArrayType =
743- (Type .ArrayType ) formalParamTypes .get (formalParamTypes .size () - 1 );
744- Type varargsElementType = varargsArrayType .elemtype ;
815+ if (isVarArgs && !formalParamTypes .isEmpty () && NullabilityUtil .isVarArgsCall (tree )) {
816+ Type varargsElementType =
817+ ((Type .ArrayType ) formalParamTypes .get (formalParamTypes .size () - 1 )).elemtype ;
745818 for (int i = formalParamTypes .size () - 1 ; i < actualParams .size (); i ++) {
746- Type actualParameterType = getTreeType (actualParams .get (i ), config );
747- // If the actual parameter type is assignable to the varargs array type, then the call site
748- // is passing the varargs directly in an array, and we should skip our check.
749- if (actualParameterType != null
750- && !state .getTypes ().isAssignable (actualParameterType , varargsArrayType )) {
819+ ExpressionTree actualParamExpr = actualParams .get (i );
820+ Type actualParameterType = getTreeType (actualParamExpr , config );
821+ if (actualParameterType != null ) {
822+ if (actualParamExpr instanceof MethodInvocationTree ) {
823+ actualParameterType =
824+ inferGenericMethodCallType (
825+ analysis ,
826+ state ,
827+ (MethodInvocationTree ) actualParamExpr ,
828+ config ,
829+ varargsElementType ,
830+ actualParameterType );
831+ }
751832 if (!subtypeParameterNullability (
752833 varargsElementType , actualParameterType , state , config )) {
753834 reportInvalidParametersNullabilityError (
754- varargsElementType , actualParameterType , actualParams . get ( i ) , state , analysis );
835+ varargsElementType , actualParameterType , actualParamExpr , state , analysis );
755836 }
756837 }
757838 }
0 commit comments