[java-dfa] Fix nullability of nested generic patterns (IDEA-339860)

GitOrigin-RevId: 3c035ab282515ad39b0fdff167d8105297c48515
This commit is contained in:
Tagir Valeev
2024-01-30 13:12:18 +00:00
committed by intellij-monorepo-bot
parent 7c438ed138
commit 04ab76833e
4 changed files with 68 additions and 15 deletions
@@ -128,8 +128,9 @@ public final class NullabilityUtil {
}
if (expression instanceof PsiReferenceExpression ref) {
PsiElement target = ref.resolve();
if (target instanceof PsiPatternVariable) {
return Nullability.NOT_NULL; // currently all pattern variables are not-null
if (target instanceof PsiPatternVariable patternVariable &&
!(patternVariable.getPattern().getParent() instanceof PsiDeconstructionList)) {
return Nullability.NOT_NULL; // currently top-level pattern variables are not-null
}
if (dumb) return Nullability.UNKNOWN;
if (target instanceof PsiLocalVariable || target instanceof PsiParameter) {
@@ -5,6 +5,7 @@ import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInsight.Nullability;
import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.java.anchor.JavaExpressionAnchor;
import com.intellij.codeInspection.dataFlow.java.anchor.JavaPolyadicPartAnchor;
@@ -1142,19 +1143,24 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
addInstruction(new PopInstruction());
PsiPattern[] components = deconstructionPattern.getDeconstructionList().getDeconstructionComponents();
PsiClass recordClass = PsiUtil.resolveClassInClassTypeOnly(patternType);
if (recordClass != null && recordClass.isRecord()) {
PsiRecordComponent[] recordComponents = recordClass.getRecordComponents();
if (components.length == recordComponents.length) {
for (int i = 0; i < components.length; i++) {
PsiRecordComponent recordComponent = recordComponents[i];
PsiPattern patternComponent = components[i];
PsiMethod accessor = JavaPsiRecordUtil.getAccessorForRecordComponent(recordComponent);
if (accessor == null) continue;
DfaVariableValue accessorDfaVar =
getFactory().getVarFactory().createVariableValue(new GetterDescriptor(accessor), patternDfaVar);
addInstruction(new JvmPushInstruction(accessorDfaVar, null));
processPattern(sourcePattern, patternComponent, recordComponent.getType(), null, endPatternOffset);
if (patternType instanceof PsiClassType patternClassType) {
PsiClassType.ClassResolveResult resolveResult = patternClassType.resolveGenerics();
PsiClass recordClass = resolveResult.getElement();
boolean unchecked = JavaGenericsUtil.isUncheckedCast(patternClassType, checkType);
PsiSubstitutor substitutor = unchecked ? PsiSubstitutor.EMPTY : resolveResult.getSubstitutor();
if (recordClass != null && recordClass.isRecord()) {
PsiRecordComponent[] recordComponents = recordClass.getRecordComponents();
if (components.length == recordComponents.length) {
for (int i = 0; i < components.length; i++) {
PsiRecordComponent recordComponent = recordComponents[i];
PsiPattern patternComponent = components[i];
PsiMethod accessor = JavaPsiRecordUtil.getAccessorForRecordComponent(recordComponent);
if (accessor == null) continue;
DfaVariableValue accessorDfaVar =
getFactory().getVarFactory().createVariableValue(new GetterDescriptor(accessor), patternDfaVar);
addInstruction(new JvmPushInstruction(accessorDfaVar, null));
processPattern(sourcePattern, patternComponent, substitutor.substitute(recordComponent.getType()), null, endPatternOffset);
}
}
}
}
@@ -0,0 +1,42 @@
class Test {
static void check(Result<CharSequence> result) {
switch (result) {
case Result.Ok(String string) -> {
if (<warning descr="Condition 'string == null' is always 'false'">string == null</warning>) {}
}
case Result.Ok(CharSequence cs) -> {
if (cs == null) {
System.out.println("Null");
}
}
case Result.Err(RuntimeException throwable) -> {
if (throwable == null) {
}
}
}
}
public static void main(String[] args) {
check(Result.ok(<warning descr="Passing 'null' argument to non-annotated parameter">null</warning>));
}
}
sealed interface Result<T> {
record Ok<R>(R value) implements Result<R> {
public R get() {
return value;
}
}
record Err<E>(RuntimeException throwable) implements Result<E> {
public E get() {
throw throwable;
}
}
static <OK> Result<OK> ok(OK value) {
return new Ok<>(value);
}
static <ERR> Result<ERR> err(RuntimeException throwable) {
return new Err<>(throwable);
}
}
@@ -54,6 +54,10 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
doTest();
}
public void testDeconstructionNullability() {
doTest();
}
public void testUnnamedPatterns() {
doTest();
}