mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[java-dfa] Support for-each patterns in Java DFA
Fixes IDEA-312232 Incorrect "Condition is always true" in presence of pattern foreach GitOrigin-RevId: f25124ba8c0db15fc8c614aca7afec98cf78a29c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
880d2ef474
commit
0484c455d1
@@ -507,9 +507,18 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
return expressions == null ? getFactory().getUnknown() : JavaDfaValueFactory.createCommonValue(getFactory(), expressions, type);
|
||||
}
|
||||
|
||||
@Override public void visitForeachStatement(@NotNull PsiForeachStatement statement) {
|
||||
@Override
|
||||
public void visitForeachPatternStatement(@NotNull PsiForeachPatternStatement statement) {
|
||||
processForeach(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForeachStatement(@NotNull PsiForeachStatement statement) {
|
||||
processForeach(statement);
|
||||
}
|
||||
|
||||
private void processForeach(@NotNull PsiForeachStatementBase statement) {
|
||||
startElement(statement);
|
||||
final PsiParameter parameter = statement.getIterationParameter();
|
||||
final PsiExpression iteratedValue = PsiUtil.skipParenthesizedExprDown(statement.getIteratedValue());
|
||||
|
||||
ControlFlowOffset loopEndOffset = getEndOffset(statement);
|
||||
@@ -536,12 +545,29 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
}
|
||||
|
||||
ControlFlowOffset offset = myCurrentFlow.getNextOffset();
|
||||
DfaVariableValue dfaVariable = PlainDescriptor.createVariableValue(myFactory, parameter);
|
||||
DfaValue commonValue = getIteratedElement(parameter.getType(), iteratedValue);
|
||||
if (DfaTypeValue.isUnknown(commonValue)) {
|
||||
addInstruction(new FlushVariableInstruction(dfaVariable));
|
||||
} else {
|
||||
new CFGBuilder(this).pushForWrite(dfaVariable).push(commonValue).assign().pop();
|
||||
if (statement instanceof PsiForeachStatement normalForeach) {
|
||||
PsiParameter parameter = normalForeach.getIterationParameter();
|
||||
DfaVariableValue dfaVariable = PlainDescriptor.createVariableValue(myFactory, parameter);
|
||||
DfaValue commonValue = getIteratedElement(parameter.getType(), iteratedValue);
|
||||
if (DfaTypeValue.isUnknown(commonValue)) {
|
||||
addInstruction(new FlushVariableInstruction(dfaVariable));
|
||||
} else {
|
||||
new CFGBuilder(this).pushForWrite(dfaVariable).push(commonValue).assign().pop();
|
||||
}
|
||||
}
|
||||
else if (statement instanceof PsiForeachPatternStatement patternForeach &&
|
||||
patternForeach.getIterationPattern() instanceof PsiDeconstructionPattern pattern) {
|
||||
PsiType contextType = JavaPsiPatternUtil.getContextType(pattern);
|
||||
if (contextType == null) {
|
||||
contextType = TypeUtils.getObjectType(statement);
|
||||
}
|
||||
DfaValue commonValue = getIteratedElement(contextType, iteratedValue);
|
||||
addInstruction(new PushInstruction(commonValue, null));
|
||||
DeferredOffset endPattern = new DeferredOffset();
|
||||
processPattern(pattern, pattern, contextType,null, endPattern);
|
||||
endPattern.setOffset(getInstructionCount());
|
||||
addInstruction(new EnsureInstruction(null, RelationType.EQ, DfTypes.TRUE, null));
|
||||
addInstruction(new PopInstruction());
|
||||
}
|
||||
|
||||
if (!hasSizeCheck) {
|
||||
@@ -562,7 +588,6 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
addInstruction(new GotoInstruction(offset));
|
||||
|
||||
finishElement(statement);
|
||||
removeVariable(parameter);
|
||||
}
|
||||
|
||||
@Override public void visitForStatement(@NotNull PsiForStatement statement) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl.source.resolve;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion;
|
||||
import com.intellij.openapi.projectRoots.JavaVersionService;
|
||||
@@ -16,11 +15,7 @@ import com.intellij.psi.impl.source.tree.java.PsiExpressionListImpl;
|
||||
import com.intellij.psi.infos.CandidateInfo;
|
||||
import com.intellij.psi.javadoc.PsiDocComment;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -273,7 +268,7 @@ public final class JavaResolveUtil {
|
||||
if (resultClass.hasTypeParameters()) {
|
||||
PsiSubstitutor substitutor = resolveResult.getSubstitutor();
|
||||
result[i] = pattern != null && ref.getTypeParameterCount() == 0
|
||||
? PatternInference.inferPatternGenerics(resolveResult, pattern, resultClass, getContextType(pattern))
|
||||
? PatternInference.inferPatternGenerics(resolveResult, pattern, resultClass, JavaPsiPatternUtil.getContextType(pattern))
|
||||
: new CandidateInfo(resolveResult, substitutor) {
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -326,54 +321,4 @@ public final class JavaResolveUtil {
|
||||
|
||||
return JavaDirectoryService.getInstance().getPackage(directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pattern deconstruction pattern to find a context type for
|
||||
* @return a context type for the pattern; null, if cannot be determined. This method can perform
|
||||
* the inference for outer patterns if necessary.
|
||||
*/
|
||||
private static @Nullable PsiType getContextType(@NotNull PsiDeconstructionPattern pattern) {
|
||||
PsiElement parent = pattern.getParent();
|
||||
while (parent instanceof PsiParenthesizedPattern) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
if (parent instanceof PsiInstanceOfExpression) {
|
||||
return ((PsiInstanceOfExpression)parent).getOperand().getType();
|
||||
}
|
||||
if (parent instanceof PsiForeachPatternStatement) {
|
||||
PsiExpression iteratedValue = ((PsiForeachPatternStatement)parent).getIteratedValue();
|
||||
if (iteratedValue == null) {
|
||||
return null;
|
||||
}
|
||||
return JavaGenericsUtil.getCollectionItemType(iteratedValue);
|
||||
}
|
||||
if (parent instanceof PsiCaseLabelElementList) {
|
||||
PsiSwitchLabelStatementBase label = ObjectUtils.tryCast(parent.getParent(), PsiSwitchLabelStatementBase.class);
|
||||
if (label != null) {
|
||||
PsiSwitchBlock block = label.getEnclosingSwitchBlock();
|
||||
if (block != null) {
|
||||
PsiExpression expression = block.getExpression();
|
||||
if (expression != null) {
|
||||
return expression.getType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiDeconstructionList) {
|
||||
PsiDeconstructionPattern parentPattern = ObjectUtils.tryCast(parent.getParent(), PsiDeconstructionPattern.class);
|
||||
if (parentPattern != null) {
|
||||
int index = ArrayUtil.indexOf(((PsiDeconstructionList)parent).getDeconstructionComponents(), pattern);
|
||||
if (index < 0) return null;
|
||||
PsiType patternType = parentPattern.getTypeElement().getType();
|
||||
if (!(patternType instanceof PsiClassType)) return null;
|
||||
PsiSubstitutor parentSubstitutor = ((PsiClassType)patternType).resolveGenerics().getSubstitutor();
|
||||
PsiClass parentRecord = PsiUtil.resolveClassInClassTypeOnly(parentPattern.getTypeElement().getType());
|
||||
if (parentRecord == null) return null;
|
||||
PsiRecordComponent[] components = parentRecord.getRecordComponents();
|
||||
if (index >= components.length) return null;
|
||||
return parentSubstitutor.substitute(components[index].getType());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.*;
|
||||
@@ -485,6 +486,56 @@ public final class JavaPsiPatternUtil {
|
||||
return variable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pattern deconstruction pattern to find a context type for
|
||||
* @return a context type for the pattern; null, if it cannot be determined. This method can perform
|
||||
* the inference for outer patterns if necessary.
|
||||
*/
|
||||
public static @Nullable PsiType getContextType(@NotNull PsiDeconstructionPattern pattern) {
|
||||
PsiElement parent = pattern.getParent();
|
||||
while (parent instanceof PsiParenthesizedPattern) {
|
||||
parent = parent.getParent();
|
||||
}
|
||||
if (parent instanceof PsiInstanceOfExpression) {
|
||||
return ((PsiInstanceOfExpression)parent).getOperand().getType();
|
||||
}
|
||||
if (parent instanceof PsiForeachPatternStatement) {
|
||||
PsiExpression iteratedValue = ((PsiForeachPatternStatement)parent).getIteratedValue();
|
||||
if (iteratedValue == null) {
|
||||
return null;
|
||||
}
|
||||
return JavaGenericsUtil.getCollectionItemType(iteratedValue);
|
||||
}
|
||||
if (parent instanceof PsiCaseLabelElementList) {
|
||||
PsiSwitchLabelStatementBase label = ObjectUtils.tryCast(parent.getParent(), PsiSwitchLabelStatementBase.class);
|
||||
if (label != null) {
|
||||
PsiSwitchBlock block = label.getEnclosingSwitchBlock();
|
||||
if (block != null) {
|
||||
PsiExpression expression = block.getExpression();
|
||||
if (expression != null) {
|
||||
return expression.getType();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiDeconstructionList) {
|
||||
PsiDeconstructionPattern parentPattern = ObjectUtils.tryCast(parent.getParent(), PsiDeconstructionPattern.class);
|
||||
if (parentPattern != null) {
|
||||
int index = ArrayUtil.indexOf(((PsiDeconstructionList)parent).getDeconstructionComponents(), pattern);
|
||||
if (index < 0) return null;
|
||||
PsiType patternType = parentPattern.getTypeElement().getType();
|
||||
if (!(patternType instanceof PsiClassType)) return null;
|
||||
PsiSubstitutor parentSubstitutor = ((PsiClassType)patternType).resolveGenerics().getSubstitutor();
|
||||
PsiClass parentRecord = PsiUtil.resolveClassInClassTypeOnly(parentPattern.getTypeElement().getType());
|
||||
if (parentRecord == null) return null;
|
||||
PsiRecordComponent[] components = parentRecord.getRecordComponents();
|
||||
if (index >= components.length) return null;
|
||||
return parentSubstitutor.substitute(components[index].getType());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class PatternVariableWrapper {
|
||||
private final @NotNull PsiPatternVariable myVariable;
|
||||
private final boolean myIsFake;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import java.util.*;
|
||||
import org.jetbrains.annotations.Range;
|
||||
|
||||
public class ForEachPattern {
|
||||
record IntBox(int i) {}
|
||||
void bar1(Iterable<IntBox> i) {
|
||||
int a = 1;
|
||||
for (IntBox(int d) : i) {
|
||||
a = 2;
|
||||
}
|
||||
System.out.println(a == 1);
|
||||
}
|
||||
|
||||
record Point(int x, @Range(from = 1, to = 10) int y) {}
|
||||
|
||||
public static void main(String[] args) {
|
||||
List<Point> points = new ArrayList<>();
|
||||
use(points);
|
||||
}
|
||||
|
||||
private static void use(List<Point> points) {
|
||||
int a = 0, b = 0;
|
||||
for (Point(int x, int y) : points) {
|
||||
if (x == 1) {
|
||||
a = 1;
|
||||
}
|
||||
if (x == 2) {
|
||||
b = 1;
|
||||
}
|
||||
if (x == 2 && <warning descr="Condition 'b == 1' is always 'true' when reached">b == 1</warning>) {
|
||||
b = y;
|
||||
}
|
||||
if (<warning descr="Condition 'y == 12' is always 'false'">y == 12</warning>) {}
|
||||
}
|
||||
if (a == 1 && b == 1) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,4 +38,13 @@ public class DataFlowInspection20Test extends DataFlowInspectionTestCase {
|
||||
public void testSuspiciousLabelElementsJava19() {
|
||||
doTest();
|
||||
}
|
||||
public void testForEachPattern() {
|
||||
myFixture.addClass("""
|
||||
package org.jetbrains.annotations;
|
||||
public @interface Range {
|
||||
long from();
|
||||
long to();
|
||||
}""");
|
||||
doTest();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user