diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java index 5d76d38369c4..38df298c991c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/NullabilityUtil.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java index d7dcc0d244bf..4c96fa7ab90a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/java/ControlFlowAnalyzer.java @@ -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); + } } } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/DeconstructionNullability.java b/java/java-tests/testData/inspection/dataFlow/fixture/DeconstructionNullability.java new file mode 100644 index 000000000000..5dc7a0c43f65 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/DeconstructionNullability.java @@ -0,0 +1,42 @@ +class Test { + static void check(Result result) { + switch (result) { + case Result.Ok(String string) -> { + if (string == null) {} + } + 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(null)); + } +} +sealed interface Result { + record Ok(R value) implements Result { + public R get() { + return value; + } + } + + record Err(RuntimeException throwable) implements Result { + public E get() { + throw throwable; + } + } + + static Result ok(OK value) { + return new Ok<>(value); + } + + static Result err(RuntimeException throwable) { + return new Err<>(throwable); + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java index 79bac07ee7df..d73121854b71 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/DataFlowInspection21Test.java @@ -54,6 +54,10 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase { doTest(); } + public void testDeconstructionNullability() { + doTest(); + } + public void testUnnamedPatterns() { doTest(); }