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 extends PyInspection> getInspectionClass() {
+ return PyFinalInspection.class;
+ }
+}