in nullaway/src/main/java/com/uber/nullaway/dataflow/CoreNullnessStoreInitializer.java [93:180]
private NullnessStore lambdaInitialStore(
UnderlyingAST.CFGLambda underlyingAST,
List<LocalVariableNode> parameters,
Handler handler,
Context context,
Types types,
Config config,
CodeAnnotationInfo codeAnnotationInfo) {
// include nullness info for locals from enclosing environment
EnclosingEnvironmentNullness environmentNullness =
EnclosingEnvironmentNullness.instance(context);
NullnessStore environmentMapping =
Objects.requireNonNull(
environmentNullness.getEnvironmentMapping(underlyingAST.getLambdaTree()),
"no environment stored for lambda");
NullnessStore.Builder result = environmentMapping.toBuilder();
LambdaExpressionTree code = underlyingAST.getLambdaTree();
// need to check annotation for i'th parameter of functional interface declaration
Symbol.MethodSymbol fiMethodSymbol = NullabilityUtil.getFunctionalInterfaceMethod(code, types);
com.sun.tools.javac.util.List<Symbol.VarSymbol> fiMethodParameters =
fiMethodSymbol.getParameters();
/*
* A potential concern here is that this method might return null because we haven't inferred the type
* of the lambda yet.
* However, this is not an issue due to the standard AST traversal order used by Error Prone.
* We currently only infer lambda types when they are passed as a parameter to a generic method.
* The checker is guaranteed to visit the enclosing generic method call (e.g., {@code wrap(s -> ...)})
* and perform type inference for it *before* it descends into the lambda's body to begin dataflow
* analysis. By the time this method is called during the setup for the lambda's analysis, the
* inferred type for the lambda argument will have already been computed and stored.
*/
Type lambdaType = castToNonNull(ASTHelpers.getType(code));
Type inferredType = genericsChecks.getInferredPolyExpressionType(code);
if (inferredType != null) {
lambdaType = inferredType;
}
// This obtains the types of the functional interface method parameters with preserved
// annotations in case of generic type arguments. Only used in JSpecify mode.
List<Type> overridenMethodParamTypeList =
TypeSubstitutionUtils.memberType(types, lambdaType, fiMethodSymbol, config)
.getParameterTypes();
MethodParameterNullness fiArgumentNullness = MethodParameterNullness.create(fiMethodSymbol);
boolean isFIAnnotated =
!codeAnnotationInfo.isSymbolUnannotated(fiMethodSymbol, config, handler);
if (isFIAnnotated) {
for (int i = 0; i < fiMethodParameters.size(); i++) {
if (Nullness.hasNullableAnnotation(fiMethodParameters.get(i), config)) {
// Get the Nullness if the Annotation is directly written with the parameter
fiArgumentNullness.setParameterNullness(i, NULLABLE);
} else if (config.isJSpecifyMode()
&& Nullness.hasNullableAnnotation(
overridenMethodParamTypeList.get(i).getAnnotationMirrors().stream(), config)) {
// Get the Nullness if the Annotation is indirectly applied through a generic type if we
// are in JSpecify mode
fiArgumentNullness.setParameterNullness(i, NULLABLE);
} else {
fiArgumentNullness.setParameterNullness(i, NONNULL);
}
}
}
fiArgumentNullness =
handler.onOverrideMethodInvocationParametersNullability(
context, fiMethodSymbol, isFIAnnotated, fiArgumentNullness);
for (int i = 0; i < parameters.size(); i++) {
LocalVariableNode param = parameters.get(i);
VariableTree variableTree = code.getParameters().get(i);
Element element = param.getElement();
Nullness assumed;
// we treat lambda parameters differently; they "inherit" the nullability of the
// corresponding functional interface parameter, unless they are explicitly annotated
if (Nullness.hasNullableAnnotation((Symbol) element, config)) {
assumed = NULLABLE;
} else if (!NullabilityUtil.lambdaParamIsImplicitlyTyped(variableTree)) {
// the parameter has a declared type with no @Nullable annotation
// treat as non-null
assumed = NONNULL;
} else {
Nullness fiParameterNullness = fiArgumentNullness.getParameterNullness(i);
assumed = fiParameterNullness == null ? NONNULL : fiParameterNullness;
}
result.setInformation(AccessPath.fromLocal(param), assumed);
}
result = handler.onDataflowInitialStore(underlyingAST, parameters, result);
return result.build();
}