PY-80627 @override usage with overloaded methods

GitOrigin-RevId: d864bf51ce48a662d4aed696802c321561d27877
This commit is contained in:
Petr
2025-05-17 01:57:54 +02:00
committed by intellij-monorepo-bot
parent 57c773ccb3
commit f8ed1ff69b
7 changed files with 67 additions and 10 deletions

View File

@@ -1051,6 +1051,8 @@ INSP.overloads.at.least.two.overload.decorated.methods.must.be.present=At least
INSP.overloads.at.least.two.overload.decorated.functions.must.be.present=At least two @overload-decorated functions must be present
INSP.overloads.use.staticmethod.inconsistently=Overloads use @staticmethod inconsistently
INSP.overloads.use.classmethod.inconsistently=Overloads use @classmethod inconsistently
INSP.overloads.override.should.be.placed.on.the.implementation='@override' should be placed on the implementation
INSP.overloads.override.should.be.placed.only.on.the.first.overload='@override' should be placed only on the first overload
# PyOverridesInspection
INSP.NAME.invalid.usages.of.override.decorator=Invalid usages of @override decorator

View File

@@ -7,6 +7,7 @@ import com.intellij.psi.PsiElementVisitor
import com.intellij.util.Processor
import com.intellij.util.containers.SortedList
import com.intellij.util.containers.sequenceOfNotNull
import com.intellij.util.containers.tail
import com.jetbrains.python.PyPsiBundle
import com.jetbrains.python.ast.PyAstFunction
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner
@@ -69,6 +70,8 @@ class PyOverloadsInspection : PyInspection() {
checkClassMethodAndStaticMethodConsistency(overloads, implementation)
checkOverride(overloads, implementation)
var requiresImplementation = true
if (owner is PyClass) {
if (isProtocol(owner, myTypeEvalContext)) {
@@ -121,6 +124,34 @@ class PyOverloadsInspection : PyInspection() {
}
}
private fun checkOverride(overloads: List<PyFunction>, implementation: PyFunction?) {
if (implementation == null) {
for (overload in overloads.tail()) {
if (isOverride(overload, myTypeEvalContext)) {
registerProblem(overload.nameIdentifier,
PyPsiBundle.message("INSP.overloads.override.should.be.placed.only.on.the.first.overload"))
}
}
}
else {
for (overload in overloads) {
if (isOverride(overload, myTypeEvalContext)) {
registerProblem(overload.nameIdentifier,
PyPsiBundle.message("INSP.overloads.override.should.be.placed.on.the.implementation"))
}
}
}
}
private fun isOverride(function: PyFunction, context: TypeEvalContext): Boolean {
val decoratorList = function.decoratorList ?: return false
return decoratorList.decorators.any { decorator ->
PyKnownDecoratorUtil.asKnownDecorators(decorator, myTypeEvalContext).any {
it == PyKnownDecorator.TYPING_OVERRIDE || it == PyKnownDecorator.TYPING_EXTENSIONS_OVERRIDE
}
}
}
private fun isIncompatibleOverload(implementation: PyFunction, overload: PyFunction): Boolean {
return implementation != overload &&
PyiUtil.isOverload(overload, myTypeEvalContext) &&

View File

@@ -23,20 +23,15 @@ class PyOverridesInspection : PyInspection() {
override fun visitPyFunction(node: PyFunction) {
super.visitPyFunction(node)
if (!PyKnownDecoratorUtil.getKnownDecorators(node, myTypeEvalContext)
.any { it == PyKnownDecorator.TYPING_OVERRIDE ||
it == PyKnownDecorator.TYPING_EXTENSIONS_OVERRIDE }) {
return
}
val overrideDecorator = node.decoratorList?.decorators?.firstOrNull {
"override" == it.qualifiedName?.lastComponent
val overrideDecorator = node.decoratorList?.decorators?.firstOrNull { decorator ->
PyKnownDecoratorUtil.asKnownDecorators(decorator, myTypeEvalContext).any {
it == PyKnownDecorator.TYPING_OVERRIDE || it == PyKnownDecorator.TYPING_EXTENSIONS_OVERRIDE
}
} ?: return
val superMethods = PySuperMethodsSearch.search(node, myTypeEvalContext).findAll()
if (superMethods.isEmpty()) {
registerProblem(overrideDecorator, PyPsiBundle.message("INSP.override.missing.super.method"))
return
}
}
}

View File

@@ -0,0 +1,24 @@
from typing import overload, override
class A:
@overload
def foo(self, x: int) -> None: ...
@overload
def foo(self, x: str) -> None: ...
@override
def foo(self, x: int | str) -> None:
pass
@overload
def bar(self, x: int) -> None: ...
@override
@overload
def <warning descr="'@override' should be placed on the implementation">bar</warning>(self, x: str) -> None: ...
@override
def bar(self, x: int | str) -> None:
pass

View File

@@ -51,7 +51,6 @@ namedtuples_type_compat.py
namedtuples_usage.py
narrowing_typeis.py
overloads_consistency.py
overloads_definitions.py
overloads_definitions_stub.pyi
overloads_evaluation.py
protocols_class_objects.py

View File

@@ -48,6 +48,10 @@ public class PyOverloadsInspectionTest extends PyInspectionTestCase {
doTest();
}
public void testOverridenMethods() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {

View File

@@ -35,6 +35,8 @@ private val inspections
//PyInitNewSignatureInspection(), // False negative constructors_consistency.py
PyNewStyleGenericSyntaxInspection(),
PyNewTypeInspection(),
PyOverloadsInspection(),
PyOverridesInspection(),
PyProtocolInspection(),
PyTypedDictInspection(),
PyTypeCheckerInspection(),