From 2efa7df328ceb9c5a262670dd4b89ef7f26f796b Mon Sep 17 00:00:00 2001 From: Daniil Kalinin Date: Thu, 21 Aug 2025 16:07:39 +0200 Subject: [PATCH] PY-76832 Add missing inspection reports for `Self` type support Remove `generics_self_usage.py` from the ignored tests as it passes now GitOrigin-RevId: 0761d2c161ca00768be967b65639d806dfa4bac4 --- .../resources/messages/PyPsiBundle.properties | 2 + .../inspections/PyTypeHintsInspection.kt | 27 +++++++++++--- python/testData/typing/ignored.txt | 1 - .../PyTypeHintsInspectionTest.java | 37 +++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) diff --git a/python/python-psi-impl/resources/messages/PyPsiBundle.properties b/python/python-psi-impl/resources/messages/PyPsiBundle.properties index d7fa68071183..124daeb941f2 100644 --- a/python/python-psi-impl/resources/messages/PyPsiBundle.properties +++ b/python/python-psi-impl/resources/messages/PyPsiBundle.properties @@ -1239,6 +1239,8 @@ INSP.type.hints.typeIs.has.zero.parameters=User-defined TypeGuard or TypeIs func INSP.type.hints.typeIs.does.not.match=Return type of TypeIs ''{0}'' is not consistent with the type of the first parameter ''{1}'' INSP.type.hints.self.use.in.staticmethod=Cannot use 'Self' in staticmethod INSP.type.hints.self.use.outside.class=Cannot use 'Self' outside class +INSP.type.hints.self.cannot.use.self.in.this.context=Cannot use 'Self' in this context +INSP.type.hints.self.cannot.use.self.in.metaclass=Type 'Self' cannot be used in a metaclass INSP.type.hints.self.use.for.self.parameter.with.self.annotation=Cannot use 'Self' if 'self' parameter is not 'Self' annotated INSP.type.hints.self.use.for.cls.parameter.with.self.annotation=Cannot use 'Self' if 'cls' parameter is not 'Self' annotated INSP.type.hints.type.var.tuple.must.always.be.unpacked=TypeVarTuple must always be unpacked diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt index 3ce8d73abb7d..f134cef17af4 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeHintsInspection.kt @@ -201,6 +201,7 @@ class PyTypeHintsInspection : PyInspection() { if (type is PyTypeParameterType && type.scopeOwner == null && !isInsideTypeParameterDefault(node)) { registerProblem(node, PyPsiBundle.message("INSP.type.hints.unbound.type.variable")) } + checkSelfType(node) } if (!insideTypeHint) { @@ -231,6 +232,27 @@ class PyTypeHintsInspection : PyInspection() { } } + private fun checkSelfType(node: PyReferenceExpression) { + if (resolvesToAnyOfQualifiedNames(node, PyTypingTypeProvider.SELF, PyTypingTypeProvider.SELF_EXT)) { + val selfType = Ref.deref(PyTypingTypeProvider.getType(node, myTypeEvalContext)) as? PySelfType + if (selfType == null) { // we don't infer Self type outside a class + registerProblem(node, PyPsiBundle.message("INSP.type.hints.self.use.outside.class")) + return + } + else { + val argList = PsiTreeUtil.getParentOfType(node, PyArgumentList::class.java) + if (argList != null && argList.parent is PyClass) { + registerProblem(node, PyPsiBundle.message("INSP.type.hints.self.cannot.use.self.in.this.context")) + return + } + if (selfType.scopeClassType.getAncestorTypes(myTypeEvalContext) + .contains(PyBuiltinCache.getInstance(node).typeType?.toClass())) { + registerProblem(node, PyPsiBundle.message("INSP.type.hints.self.cannot.use.self.in.metaclass")) + } + } + } + } + private fun isGenericTypeArgument(node: PyReferenceExpression): Boolean { var element: PyElement = node var parentElement = element.parent @@ -326,11 +348,6 @@ class PyTypeHintsInspection : PyInspection() { } } - val classParent = PsiTreeUtil.getParentOfType(node, PyClass::class.java) - if (classParent == null) { - registerProblemForSelves(PyPsiBundle.message("INSP.type.hints.self.use.outside.class")) - } - val functionParent = PsiTreeUtil.getParentOfType(node, PyFunction::class.java) if (functionParent != null) { if (PyAstFunction.Modifier.STATICMETHOD == functionParent.modifier && PyNames.NEW != functionParent.name) { diff --git a/python/testData/typing/ignored.txt b/python/testData/typing/ignored.txt index e173df7d1ade..012b120d1e47 100644 --- a/python/testData/typing/ignored.txt +++ b/python/testData/typing/ignored.txt @@ -26,7 +26,6 @@ generics_base_class.py generics_paramspec_components.py generics_paramspec_semantics.py generics_self_attributes.py -generics_self_usage.py generics_syntax_infer_variance.py generics_typevartuple_basic.py generics_typevartuple_overloads.py diff --git a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java index 5783b26ad9b4..4e61a12a7ff9 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyTypeHintsInspectionTest.java @@ -3156,6 +3156,43 @@ public class PyTypeHintsInspectionTest extends PyInspectionTestCase { doMultiFileTest(); } + // PY-76832 + public void testTypeSelfAsTypeArg() { + doTestByText(""" + from typing import TypeAlias, Self + TupleSelf: TypeAlias = tuple[Self] # E + class A[T]: ... + a = A[Self]() # E + class B: + def __init__(self): + self.l: List[Self] = [] # OK + """); + } + + // PY-76832 + public void testTypeSelfInBaseClassTypeArgs() { + doTestByText(""" + from typing import Self + + class Bar[T]: ... + class Baz(Bar[Self]): ... # E + """); + } + + // PY-76832 + public void testTypeSelfInMetaclass() { + doTestByText(""" + from typing import Self, Any + + class MyMetaclass(type): + def __new__(cls, *args: Any) -> Self: # E + ... + + def __mul__(cls, count: int) -> list[Self]: # E + ... + """); + } + // PY-84289 public void testExponentialAnalysisTimeWhenMapLookupKeyEqualsVariableName() {