diff --git a/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java b/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java index 91e161dd625c..3bc10190b4ea 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnboundLocalVariableInspection.java @@ -1,17 +1,23 @@ package com.jetbrains.python.inspections; +import com.intellij.codeInsight.controlflow.ControlFlow; +import com.intellij.codeInsight.controlflow.ControlFlowUtil; +import com.intellij.codeInsight.controlflow.Instruction; import com.intellij.codeInsight.dataflow.DFALimitExceededException; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.Function; import com.intellij.util.containers.HashSet; import com.jetbrains.python.PyBundle; import com.jetbrains.python.actions.AddGlobalQuickFix; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; +import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; @@ -102,6 +108,9 @@ public class PyUnboundLocalVariableInspection extends PyInspection { return; } if (variable == null) { + if (!isFirstUnboundRead(node, owner)) { + return; + } final PsiElement resolved = node.getReference(resolveWithoutImplicits()).resolve(); final boolean isBuiltin = PyBuiltinCache.getInstance(node).hasInBuiltins(resolved); if (owner instanceof PyClass) { @@ -127,6 +136,45 @@ public class PyUnboundLocalVariableInspection extends PyInspection { } } + private static 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); + final Instruction[] instructions = flow.getInstructions(); + final int num = ControlFlowUtil.findInstructionNumberByElement(instructions, node); + if (num < 0) { + return true; + } + final Ref first = Ref.create(true); + ControlFlowUtil.iteratePrev(num, instructions, new Function() { + @Override + public ControlFlowUtil.Operation fun(Instruction instruction) { + if (instruction instanceof ReadWriteInstruction) { + final ReadWriteInstruction rwInstruction = (ReadWriteInstruction)instruction; + final String name = rwInstruction.getName(); + final PsiElement element = rwInstruction.getElement(); + if (element != null && name != null && name.equals(nodeName) && instruction.num() != num) { + try { + if (scope.getDeclaredVariable(element, name) == null) { + final ReadWriteInstruction.ACCESS access = rwInstruction.getAccess(); + if (access.isReadAccess()) { + first.set(false); + return ControlFlowUtil.Operation.BREAK; + } + } + } + catch (DFALimitExceededException e) { + first.set(false); + } + return ControlFlowUtil.Operation.CONTINUE; + } + } + return ControlFlowUtil.Operation.NEXT; + } + }); + return first.get(); + } + @Override public void visitPyNonlocalStatement(final PyNonlocalStatement node) { for (PyTargetExpression var : node.getVariables()) { diff --git a/python/testData/inspections/PyUnboundLocalVariableInspection/expected.xml b/python/testData/inspections/PyUnboundLocalVariableInspection/expected.xml index c0fb1e997796..5aaae21724f3 100644 --- a/python/testData/inspections/PyUnboundLocalVariableInspection/expected.xml +++ b/python/testData/inspections/PyUnboundLocalVariableInspection/expected.xml @@ -10,11 +10,6 @@ 14 "Name 'b' can be not defined" - - test.py - 15 - "Name 'b' can be not defined" - test.py 51