diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 93555e207b7f..5bb9a401b4aa 100644 --- a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -30,11 +30,11 @@ class PyFinalInspection : PyInspection() { override fun visitPyClass(node: PyClass) { super.visitPyClass(node) - node.superClassExpressions.forEach { - val cls = (myTypeEvalContext.getType(it) as? PyClassType)?.pyClass - if (cls != null && isFinal(cls)) { - registerProblem(it, "'${cls.name}' is marked as '@final' and should not be subclassed") - } + node.getSuperClasses(myTypeEvalContext).filter { isFinal(it) }.let { finalSuperClasses -> + if (finalSuperClasses.isEmpty()) return@let + + val postfix = " ${if (finalSuperClasses.size == 1) "is" else "are"} marked as '@final' and should not be subclassed" + registerProblem(node.nameIdentifier, finalSuperClasses.joinToString(postfix = postfix) { "'${it.name}'" ?: "" }) } if (PyiUtil.isInsideStub(node)) { diff --git a/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py b/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py index 2b484bc609c8..a03a8b0198c3 100644 --- a/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py +++ b/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py @@ -1,3 +1,3 @@ from b import A -class B(A): +class B(A): pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java index 04e9d392f2a3..a19e927d426a 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -16,8 +16,33 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { "@final\n" + "class A:\n" + " pass\n" + - "class B(A):\n" + + "class B(A):\n" + " pass"); + + doTestByText("from typing_extensions import final\n" + + "@final\n" + + "class A:\n" + + " pass\n" + + "@final\n" + + "class B:\n" + + " pass\n" + + "class C(A, B):\n" + + " pass"); + } + + // PY-34945 + public void testFinalClassAsMetaclass() { + runWithLanguageLevel( + LanguageLevel.PYTHON30, + () -> doTestByText("from typing_extensions import final\n" + + "\n" + + "@final\n" + + "class MT(type):\n" + + " pass\n" + + "\n" + + "class A(metaclass=MT):\n" + + " pass") + ); } // PY-34945 @@ -416,7 +441,7 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { " a = 2") ); } - + // PY-34945 public void testNonLocalReassignment() { runWithLanguageLevel(