diff --git a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt index 6acff46fcdc4..bbf2b02d53aa 100644 --- a/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt +++ b/python/src/com/jetbrains/python/inspections/PyFinalInspection.kt @@ -10,6 +10,7 @@ import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL import com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.TYPING_FINAL_EXT import com.jetbrains.python.psi.search.PySuperMethodsSearch import com.jetbrains.python.psi.types.PyClassType +import com.jetbrains.python.pyi.PyiUtil class PyFinalInspection : PyInspection() { @@ -28,6 +29,21 @@ class PyFinalInspection : PyInspection() { registerProblem(it, "'${cls.name}' is marked as '@final' and should not be subclassed") } } + + if (PyiUtil.isInsideStub(node)) { + val visitedNames = mutableSetOf() + + node.visitMethods( + { m -> + if (!visitedNames.add(m.name) && isFinal(m)) { + registerProblem(m.nameIdentifier, "'@final' should be placed on the first overload") + } + true + }, + false, + myTypeEvalContext + ) + } } override fun visitPyFunction(node: PyFunction) { @@ -40,6 +56,10 @@ class PyFinalInspection : PyInspection() { ?.let { registerProblem(node.nameIdentifier, "'${(it as PyFunction).qualifiedName}' is marked as '@final' and should not be overridden") } + + if (!PyiUtil.isInsideStub(node) && isFinal(node) && PyiUtil.isOverload(node, myTypeEvalContext)) { + registerProblem(node.nameIdentifier, "'@final' should be placed on the implementation") + } } else if (isFinal(node)) { registerProblem(node.nameIdentifier, "Non-method function could not be marked as '@final'") diff --git a/python/testData/inspections/PyFinalInspection/overloadedFinalMethodInStub.pyi b/python/testData/inspections/PyFinalInspection/overloadedFinalMethodInStub.pyi new file mode 100644 index 000000000000..a6cbeefd192d --- /dev/null +++ b/python/testData/inspections/PyFinalInspection/overloadedFinalMethodInStub.pyi @@ -0,0 +1,18 @@ +from typing import overload +from typing_extensions import final + +class A: + @final + @overload + def foo(self, a: int) -> int: ... + + @overload + def foo(self, a: str) -> str: ... + +class B: + @overload + def foo(self, a: int) -> int: ... + + @final + @overload + def foo(self, a: str) -> str: ... diff --git a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java index 3cfd2f86d54b..bf13a25a2ea6 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyFinalInspectionTest.java @@ -1,6 +1,7 @@ // 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.psi.PsiFile; import com.jetbrains.python.fixtures.PyInspectionTestCase; import com.jetbrains.python.psi.LanguageLevel; import org.jetbrains.annotations.NotNull; @@ -84,6 +85,44 @@ public class PyFinalInspectionTest extends PyInspectionTestCase { ); } + // PY-34945 + public void testOverloadedFinalMethod() { + runWithLanguageLevel( + LanguageLevel.PYTHON35, + () -> doTestByText("from typing import overload\n" + + "from typing_extensions import final\n" + + "\n" + + "class A:\n" + + " @overload\n" + + " def foo(self, a: int) -> int: ...\n" + + "\n" + + " @overload\n" + + " def foo(self, a: str) -> str: ...\n" + + "\n" + + " @final\n" + + " def foo(self, a):\n" + + " pass\n" + + "\n" + + "class B:\n" + + " @final\n" + + " @overload\n" + + " def foo(self, a: int) -> int: ...\n" + + "\n" + + " @overload\n" + + " def foo(self, a: str) -> str: ...\n" + + "\n" + + " def foo(self, a):\n" + + " pass\n") + ); + } + + // PY-34945 + public void testOverloadedFinalMethodInStub() { + final PsiFile currentFile = myFixture.configureByFile(getTestFilePath() + "i"); + configureInspection(); + assertSdkRootsNotParsed(currentFile); + } + @NotNull @Override protected Class getInspectionClass() {