mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-18 20:41:22 +07:00
[pycharm] add un-reachability support to PyUnboundLocalVariableInspection
GitOrigin-RevId: ea1125977ea8b4de370973d606cc5ba7b258dd82
This commit is contained in:
committed by
intellij-monorepo-bot
parent
582edda2e8
commit
d649ae4e61
@@ -6,12 +6,14 @@ import com.intellij.codeInsight.dataflow.map.DFAMap;
|
||||
import com.intellij.codeInsight.dataflow.map.DfaMapInstance;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeVariable;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeVariableImpl;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyExceptPartNavigator;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -19,13 +21,26 @@ import java.util.Map;
|
||||
public class PyReachingDefsDfaInstance implements DfaMapInstance<ScopeVariable> {
|
||||
// Use this its own map, because check in PyReachingDefsDfaSemilattice is important
|
||||
public static final DFAMap<ScopeVariable> INITIAL_MAP = new DFAMap<>();
|
||||
public static final DFAMap<ScopeVariable> UNREACHABLE_MARKER = new DFAMap<>();
|
||||
|
||||
private final TypeEvalContext myContext;
|
||||
|
||||
public PyReachingDefsDfaInstance(@NotNull TypeEvalContext typeEvalContext) {
|
||||
myContext = typeEvalContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DFAMap<ScopeVariable> fun(final DFAMap<ScopeVariable> map, final Instruction instruction) {
|
||||
if (map == UNREACHABLE_MARKER) return map;
|
||||
final PsiElement element = instruction.getElement();
|
||||
if (element == null || ((PyFile) element.getContainingFile()).getLanguageLevel().isPython2()){
|
||||
return processReducedMap(map, instruction, element);
|
||||
}
|
||||
if (instruction instanceof CallInstruction callInstruction) {
|
||||
if (callInstruction.isNoReturnCall(myContext)) {
|
||||
return UNREACHABLE_MARKER;
|
||||
}
|
||||
}
|
||||
// Scope reduction
|
||||
final DFAMap<ScopeVariable> reducedMap = new DFAMap<>();
|
||||
for (Map.Entry<String, ScopeVariable> entry : map.entrySet()) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Set;
|
||||
public class PyReachingDefsSemilattice implements MapSemilattice<ScopeVariable> {
|
||||
@Override
|
||||
public boolean eq(@NotNull DFAMap<ScopeVariable> e1, @NotNull DFAMap<ScopeVariable> e2) {
|
||||
if (e1 == PyReachingDefsDfaInstance.UNREACHABLE_MARKER || e2 == PyReachingDefsDfaInstance.UNREACHABLE_MARKER) return e1 == e2;
|
||||
if (e1 == PyReachingDefsDfaInstance.INITIAL_MAP && e2 != PyReachingDefsDfaInstance.INITIAL_MAP ||
|
||||
e2 == PyReachingDefsDfaInstance.INITIAL_MAP && e1 != PyReachingDefsDfaInstance.INITIAL_MAP) {
|
||||
return false;
|
||||
@@ -32,6 +33,8 @@ public class PyReachingDefsSemilattice implements MapSemilattice<ScopeVariable>
|
||||
return ins.get(0);
|
||||
}
|
||||
|
||||
ins = ins.stream().filter( e -> e != PyReachingDefsDfaInstance.UNREACHABLE_MARKER).toList();
|
||||
|
||||
final Set<String> resultNames = getResultNames(ins);
|
||||
if (resultNames == null || resultNames.isEmpty()) {
|
||||
return new DFAMap<>();
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.jetbrains.python.psi.PyImportedNameDefiner;
|
||||
import com.jetbrains.python.psi.PyTargetExpression;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -29,9 +31,15 @@ import java.util.List;
|
||||
public interface Scope {
|
||||
/*
|
||||
* @return defined scope local/instance/class variables and parameters, using reaching defs
|
||||
*
|
||||
* it uses obsolete dfa analysis under the hood for pretty unclear reasons
|
||||
*/
|
||||
@Nullable
|
||||
ScopeVariable getDeclaredVariable(@NotNull PsiElement anchorElement, @NotNull String name) throws DFALimitExceededException;
|
||||
@Deprecated
|
||||
@ApiStatus.Internal
|
||||
ScopeVariable getDeclaredVariable(@NotNull PsiElement anchorElement,
|
||||
@NotNull String name,
|
||||
@NotNull TypeEvalContext typeEvalContext) throws DFALimitExceededException;
|
||||
|
||||
boolean hasGlobals();
|
||||
boolean isGlobal(String name);
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeVariable;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -47,8 +48,9 @@ public class ScopeImpl implements Scope {
|
||||
|
||||
@Override
|
||||
public ScopeVariable getDeclaredVariable(@NotNull final PsiElement anchorElement,
|
||||
@NotNull final String name) throws DFALimitExceededException {
|
||||
computeScopeVariables();
|
||||
@NotNull final String name,
|
||||
@NotNull final TypeEvalContext typeEvalContext) throws DFALimitExceededException {
|
||||
computeScopeVariables(typeEvalContext);
|
||||
for (int i = 0; i < myFlow.length; i++) {
|
||||
Instruction instruction = myFlow[i];
|
||||
final PsiElement element = instruction.getElement();
|
||||
@@ -59,10 +61,10 @@ public class ScopeImpl implements Scope {
|
||||
return null;
|
||||
}
|
||||
|
||||
private synchronized void computeScopeVariables() throws DFALimitExceededException {
|
||||
private synchronized void computeScopeVariables(@NotNull TypeEvalContext typeEvalContext) throws DFALimitExceededException {
|
||||
computeFlow();
|
||||
if (myCachedScopeVariables == null) {
|
||||
final PyReachingDefsDfaInstance dfaInstance = new PyReachingDefsDfaInstance();
|
||||
final PyReachingDefsDfaInstance dfaInstance = new PyReachingDefsDfaInstance(typeEvalContext);
|
||||
final PyReachingDefsSemilattice semilattice = new PyReachingDefsSemilattice();
|
||||
final DFAMapEngine<ScopeVariable> engine = new DFAMapEngine<>(myFlow, dfaInstance, semilattice);
|
||||
myCachedScopeVariables = engine.performDFA();
|
||||
|
||||
@@ -6,13 +6,17 @@ import com.intellij.codeInsight.controlflow.ControlFlowUtil;
|
||||
import com.intellij.codeInsight.controlflow.Instruction;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class PyInspectionsUtil {
|
||||
public static boolean hasAnyInterruptedControlFlowPaths(@NotNull PsiElement element) {
|
||||
@ApiStatus.Internal
|
||||
public static boolean hasAnyInterruptedControlFlowPaths(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
|
||||
final ScopeOwner owner = ScopeUtil.getScopeOwner(element);
|
||||
if (owner != null) {
|
||||
final ControlFlow flow = ControlFlowCache.getControlFlow(owner);
|
||||
@@ -21,7 +25,7 @@ public final class PyInspectionsUtil {
|
||||
if (start >= 0) {
|
||||
final Ref<Boolean> resultRef = Ref.create(false);
|
||||
ControlFlowUtil.iteratePrev(start, instructions, instruction -> {
|
||||
if (instruction.allPred().isEmpty() && !isFirstInstruction(instruction)) {
|
||||
if (CallInstruction.Companion.allPredWithoutNoReturn(instruction, context).isEmpty() && !isFirstInstruction(instruction)) {
|
||||
resultRef.set(true);
|
||||
return ControlFlowUtil.Operation.BREAK;
|
||||
}
|
||||
@@ -33,6 +37,7 @@ public final class PyInspectionsUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
static boolean isFirstInstruction(Instruction instruction) {
|
||||
return instruction.num() == 0;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public final class PyUnboundLocalVariableInspection extends PyInspection {
|
||||
}
|
||||
final ScopeVariable variable;
|
||||
try {
|
||||
variable = scope.getDeclaredVariable(anchor, name);
|
||||
variable = scope.getDeclaredVariable(anchor, name, myTypeEvalContext);
|
||||
}
|
||||
catch (DFALimitExceededException e) {
|
||||
largeFunctions.add(owner);
|
||||
@@ -123,7 +123,7 @@ public final class PyUnboundLocalVariableInspection extends PyInspection {
|
||||
if (resolvedUnderWithStatement(node, resolved) || resolvedUnderAssignmentExpressionAndCondition(node, resolved)) {
|
||||
return;
|
||||
}
|
||||
if (PyInspectionsUtil.hasAnyInterruptedControlFlowPaths(node)) {
|
||||
if (PyInspectionsUtil.hasAnyInterruptedControlFlowPaths(node, myTypeEvalContext)) {
|
||||
return;
|
||||
}
|
||||
if (owner instanceof PyFile) {
|
||||
@@ -167,7 +167,7 @@ public final class PyUnboundLocalVariableInspection extends PyInspection {
|
||||
) != null;
|
||||
}
|
||||
|
||||
private static boolean isFirstUnboundRead(@NotNull PyReferenceExpression node, @NotNull ScopeOwner owner) {
|
||||
private boolean isFirstUnboundRead(@NotNull PyReferenceExpression node, @NotNull ScopeOwner owner) {
|
||||
final String nodeName = node.getReferencedName();
|
||||
final Scope scope = ControlFlowCache.getScope(owner);
|
||||
final ControlFlow flow = ControlFlowCache.getControlFlow(owner);
|
||||
@@ -183,7 +183,7 @@ public final class PyUnboundLocalVariableInspection extends PyInspection {
|
||||
final PsiElement element = rwInstruction.getElement();
|
||||
if (element != null && name != null && name.equals(nodeName) && instruction.num() < num) {
|
||||
try {
|
||||
if (scope.getDeclaredVariable(element, name) == null) {
|
||||
if (scope.getDeclaredVariable(element, name, myTypeEvalContext) == null) {
|
||||
final ReadWriteInstruction.ACCESS access = rwInstruction.getAccess();
|
||||
if (access.isReadAccess()) {
|
||||
first.set(false);
|
||||
|
||||
@@ -295,7 +295,7 @@ public abstract class PyUnresolvedReferencesVisitor extends PyInspectionVisitor
|
||||
return;
|
||||
}
|
||||
if (!expr.isQualified()) {
|
||||
if (PyInspectionsUtil.hasAnyInterruptedControlFlowPaths(expr)) {
|
||||
if (PyInspectionsUtil.hasAnyInterruptedControlFlowPaths(expr, myTypeEvalContext)) {
|
||||
return;
|
||||
}
|
||||
ContainerUtil.addIfNotNull(fixes, getTrueFalseQuickFix(refText));
|
||||
|
||||
Reference in New Issue
Block a user