From 3399546577d6de7e7b79c6cf2e0737380151915c Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Fri, 24 May 2019 20:18:02 +0300 Subject: [PATCH] Warn about subclassing classes marked with `@final` (PEP 591) (PY-34945) GitOrigin-RevId: 40e2faffc4215359c5c18341b512b04da59b318a --- .../PyFinalInspection.html | 5 +++ python/src/META-INF/python-core-common.xml | 1 + .../python/inspections/PyFinalInspection.kt | 37 +++++++++++++++++++ .../python/psi/PyKnownDecoratorUtil.java | 2 + .../SubclassingFinalClass/a.py | 3 ++ .../SubclassingFinalClass/b.py | 4 ++ .../inspections/PyFinalInspectionTest.java | 26 +++++++++++++ 7 files changed, 78 insertions(+) create mode 100644 python/resources/inspectionDescriptions/PyFinalInspection.html create mode 100644 python/src/com/jetbrains/python/inspections/PyFinalInspection.kt create mode 100644 python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py create mode 100644 python/testData/inspections/PyFinalInspection/SubclassingFinalClass/b.py create mode 100644 python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java diff --git a/python/resources/inspectionDescriptions/PyFinalInspection.html b/python/resources/inspectionDescriptions/PyFinalInspection.html new file mode 100644 index 000000000000..6c7fcc28b4e6 --- /dev/null +++ b/python/resources/inspectionDescriptions/PyFinalInspection.html @@ -0,0 +1,5 @@ + + +This inspection detects invalid usages of final classes, methods and variables. + + \ No newline at end of file diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index 2f581ad55f49..46e3118d89ca 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -430,6 +430,7 @@ + diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt new file mode 100644 index 000000000000..2a9e05779ee8 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -0,0 +1,37 @@ +// Copyright 2000-2019 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.inspections + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElementVisitor +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyDecoratable +import com.jetbrains.python.psi.PyKnownDecoratorUtil +import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL +import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL_EXT +import com.jetbrains.python.psi.types.PyClassType + +class PyFinalInspection : PyInspection() { + + override fun buildVisitor(holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession): PsiElementVisitor = Visitor(holder, session) + + private class Visitor(holder: ProblemsHolder, session: LocalInspectionToolSession) : PyInspectionVisitor(holder, session) { + + 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") + } + } + } + + private fun isFinal(decoratable: PyDecoratable): Boolean { + return PyKnownDecoratorUtil.getKnownDecorators(decoratable, myTypeEvalContext).any { it == TYPING_FINAL || it == TYPING_FINAL_EXT } + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index 3b9873ac9047..9918a5f4d209 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -64,6 +64,8 @@ public class PyKnownDecoratorUtil { TYPING_OVERLOAD("typing." + PyNames.OVERLOAD), TYPING_RUNTIME("typing.runtime"), TYPING_RUNTIME_EXT("typing_extensions.runtime"), + TYPING_FINAL("typing.final"), + TYPING_FINAL_EXT("typing_extensions.final"), REPRLIB_RECURSIVE_REPR("reprlib.recursive_repr"), diff --git a/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py b/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py new file mode 100644 index 000000000000..2b484bc609c8 --- /dev/null +++ b/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/a.py @@ -0,0 +1,3 @@ +from b import A +class B(A): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/b.py b/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/b.py new file mode 100644 index 000000000000..462e51006d8a --- /dev/null +++ b/python/testData/inspections/PyFinalInspection/SubclassingFinalClass/b.py @@ -0,0 +1,4 @@ +from typing_extensions import final +@final +class 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 new file mode 100644 index 000000000000..c1cf287e3a56 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -0,0 +1,26 @@ +// Copyright 2000-2019 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.inspections; + +import com.jetbrains.python.fixtures.PyInspectionTestCase; +import org.jetbrains.annotations.NotNull; + +public class PyFinalInspectionTest extends PyInspectionTestCase { + + // PY-34945 + public void testSubclassingFinalClass() { + doMultiFileTest(); + + doTestByText("from typing_extensions import final\n" + + "@final\n" + + "class A:\n" + + " pass\n" + + "class B(A):\n" + + " pass"); + } + + @NotNull + @Override + protected Class getInspectionClass() { + return PyFinalInspection.class; + } +}