diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index d9e7f8aea8a7..75291f095f28 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1269,8 +1269,8 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla @Override public boolean processInstanceLevelDeclarations(@NotNull PsiScopeProcessor processor, @Nullable PsiElement location) { - Map declarationsInMethod = new HashMap<>(); - PyFunction instanceMethod = PsiTreeUtil.getParentOfType(location, PyFunction.class); + final Map declarationsInMethod = new HashMap<>(); + final PyFunction instanceMethod = PsiTreeUtil.getStubOrPsiParentOfType(location, PyFunction.class); final PyClass containingClass = instanceMethod != null ? instanceMethod.getContainingClass() : null; if (instanceMethod != null && containingClass != null && CompletionUtil.getOriginalElement(containingClass) == this) { collectInstanceAttributes(instanceMethod, declarationsInMethod); diff --git a/python/testData/resolve/AttributeClassLevelAnnotation.py b/python/testData/resolve/AttributeClassLevelAnnotation.py new file mode 100644 index 000000000000..88d7400ed598 --- /dev/null +++ b/python/testData/resolve/AttributeClassLevelAnnotation.py @@ -0,0 +1,3 @@ +import b +b.B().some_attr +# \ No newline at end of file diff --git a/python/testData/resolve/AttributeClassLevelAnnotation/b.py b/python/testData/resolve/AttributeClassLevelAnnotation/b.py new file mode 100644 index 000000000000..da61cad4c9ea --- /dev/null +++ b/python/testData/resolve/AttributeClassLevelAnnotation/b.py @@ -0,0 +1,3 @@ +class B(object): + def __init__(self) -> None: + self.some_attr = unresolved diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index c25704d5ef14..1747df9ccf73 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -1,10 +1,7 @@ // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiPolyVariantReference; -import com.intellij.psi.PsiReference; -import com.intellij.psi.ResolveResult; +import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.UsefulTestCase; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; @@ -14,6 +11,8 @@ import com.jetbrains.python.fixtures.PyResolveTestCase; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.resolve.ImportedResolveResult; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.types.PyClassTypeImpl; import com.jetbrains.python.psi.types.TypeEvalContext; public class PyResolveTest extends PyResolveTestCase { @@ -1247,4 +1246,22 @@ public class PyResolveTest extends PyResolveTestCase { myFixture.copyDirectoryToProject("resolve/" + getTestName(false), ""); assertResolvesTo(PyFunction.class, "append"); } + + // PY-27863 + public void testAttributeClassLevelAnnotation() { + myFixture.copyDirectoryToProject("resolve/" + getTestName(false), ""); + + final PyTargetExpression target = assertResolvesTo(PyTargetExpression.class, "some_attr"); + + final PsiFile file = myFixture.getFile(); + final TypeEvalContext context = TypeEvalContext.codeAnalysis(myFixture.getProject(), file); + final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context); + + // It's like an attempt to find type annotation for attribute on the class level. + final PyClassTypeImpl classType = new PyClassTypeImpl(target.getContainingClass(), true); + assertEmpty(classType.resolveMember(target.getReferencedName(), target, AccessDirection.READ, resolveContext, true)); + + assertProjectFilesNotParsed(file); + assertSdkRootsNotParsed(file); + } }