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 extends PyInspection> getInspectionClass() {