diff --git a/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt b/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt index fe0c9f54d89d..d00fdd2f111f 100644 --- a/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt @@ -124,6 +124,12 @@ class PyTypeHintsInspection : PyInspection() { checkTypeCommentAndParameters(node) } + override fun visitPyTargetExpression(node: PyTargetExpression) { + super.visitPyTargetExpression(node) + + checkAnnotatedNonSelfAttribute(node) + } + private fun checkTypeVarPlacement(call: PyCallExpression, target: PyExpression?) { if (target == null) { registerProblem(call, "A 'TypeVar()' expression must always directly be assigned to a variable") @@ -597,6 +603,23 @@ class PyTypeHintsInspection : PyInspection() { } } + private fun checkAnnotatedNonSelfAttribute(node: PyTargetExpression) { + val qualifier = node.qualifier ?: return + if (node.annotation == null && node.typeComment == null) return + + val scopeOwner = ScopeUtil.getScopeOwner(node) + if (scopeOwner !is PyFunction) { + registerProblem(node, "Non-self attribute could not be type hinted") + return + } + + val self = scopeOwner.parameterList.parameters.firstOrNull()?.takeIf { it.isSelf } + if (self == null || + PyUtil.multiResolveTopPriority(qualifier, resolveContext).let { it.isNotEmpty() && it.all { e -> e != self }}) { + registerProblem(node, "Non-self attribute could not be type hinted") + } + } + private fun followNotTypingOpaque(target: PyTargetExpression): Boolean { return !PyTypingTypeProvider.OPAQUE_NAMES.contains(target.qualifiedName) } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java index ee768f9a83a2..a2e0ae9226d5 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java @@ -848,6 +848,23 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase { ); } + public void testAnnotatingNonSelfAttribute() { + runWithLanguageLevel( + LanguageLevel.PYTHON36, + () -> doTestByText("class A:\n" + + " def method(self, b):\n" + + " b.a: int = 1\n" + + "\n" + + "class B:\n" + + " pass\n" + + "\n" + + "B.a: str = \"2\"\n" + + "\n" + + "def func(a):\n" + + " a.xxx: str = \"2\"") + ); + } + @NotNull @Override protected Class getInspectionClass() {