diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java index a44e539f7513..4c2fa489536a 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyReferenceImpl.java @@ -38,6 +38,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Supplier; /** * @author yole @@ -265,11 +266,11 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference @Nullable PsiElement realContext, @Nullable PsiElement resolveRoof) { boolean unreachableLocalDeclaration = false; - boolean resolveInParentScope = false; + Supplier resolveInParentScope = null; final ResolveResultList resultList = new ResolveResultList(); final ScopeOwner referenceOwner = ScopeUtil.getScopeOwner(realContext); final TypeEvalContext typeEvalContext = myContext.getTypeEvalContext(); - ScopeOwner resolvedOwner = processor.getOwner(); + final ScopeOwner resolvedOwner = processor.getOwner(); final Collection resolvedElements = processor.getElements(); if (resolvedOwner != null && !resolvedElements.isEmpty() && !ControlFlowCache.getScope(resolvedOwner).isGlobal(referencedName)) { @@ -290,8 +291,11 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference return latestDefs; } - else if (resolvedOwner instanceof PyClass || instructions.isEmpty() && allInOwnScopeComprehensions(resolvedElements)) { - resolveInParentScope = true; + else if (resolvedOwner instanceof PyClass) { + resolveInParentScope = () -> PyUtil.as(resolvedOwner.getContainingFile(), PyFile.class); + } + else if (instructions.isEmpty() && allInOwnScopeComprehensions(resolvedElements)) { + resolveInParentScope = () -> ScopeUtil.getScopeOwner(resolvedOwner); } else { unreachableLocalDeclaration = true; @@ -319,11 +323,11 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference // TODO: Try resolve to latest defs for outer scopes starting from the last element in CFG (=> no need for a special rate for globals) if (!unreachableLocalDeclaration) { - if (resolveInParentScope) { + if (resolveInParentScope != null) { processor = new PyResolveProcessor(referencedName); - resolvedOwner = ScopeUtil.getScopeOwner(resolvedOwner); - if (resolvedOwner != null) { - PyResolveUtil.scopeCrawlUp(processor, resolvedOwner, referencedName, resolveRoof); + final ScopeOwner parentScope = resolveInParentScope.get(); + if (parentScope != null) { + PyResolveUtil.scopeCrawlUp(processor, parentScope, referencedName, resolveRoof); } } diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java index 442a2114c8df..7d300cccbf1e 100644 --- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java @@ -22,7 +22,6 @@ import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.QualifiedName; -import com.jetbrains.extenstions.PsiElementExtKt; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; import com.jetbrains.python.codeInsight.dataflow.scope.Scope; @@ -111,7 +110,12 @@ public class PyResolveUtil { if (scopeOwner == roof) { return; } - scopeOwner = ScopeUtil.getScopeOwner(scopeOwner); + if (scopeOwner instanceof PyClass && scopeOwner == originalScopeOwner) { + scopeOwner = PyUtil.as(scopeOwner.getContainingFile(), PyFile.class); + } + else { + scopeOwner = ScopeUtil.getScopeOwner(scopeOwner); + } } } diff --git a/python/testData/resolve/UnboundVariableOnClassLevel.py b/python/testData/resolve/UnboundVariableOnClassLevel.py new file mode 100644 index 000000000000..1c073b275a40 --- /dev/null +++ b/python/testData/resolve/UnboundVariableOnClassLevel.py @@ -0,0 +1,8 @@ +foo = 'global' + +def method(foo): + class A: + print(foo) + # + foo = 'local' + print(foo) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 4a217efc30c9..204aa2daca83 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -1286,4 +1286,13 @@ public class PyResolveTest extends PyResolveTestCase { () -> assertResolvesTo(PyClass.class, "A") ); } + + // PY-19890 + public void testUnboundVariableOnClassLevel() { + final PyTargetExpression foo = assertResolvesTo(PyTargetExpression.class, "foo"); + + final PyExpression value = foo.findAssignedValue(); + assertInstanceOf(value, PyStringLiteralExpression.class); + assertEquals("global", ((PyStringLiteralExpression)value).getStringValue()); + } }