Warn incorrect usages of @overload and @final (PEP 591) (PY-34945)

GitOrigin-RevId: fd9c3acc6b67d9ac1c46512573df3e936c683e29
This commit is contained in:
Semyon Proshev
2019-07-02 06:52:16 +03:00
committed by intellij-monorepo-bot
parent c61d1c4e84
commit b5fe834814
3 changed files with 77 additions and 0 deletions
@@ -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<String?>()
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'")
@@ -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 <warning descr="'@final' should be placed on the first overload">foo</warning>(self, a: str) -> str: ...
@@ -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 <warning descr=\"'@final' should be placed on the implementation\">foo</warning>(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<? extends PyInspection> getInspectionClass() {