mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-80627 Overloaded abstract methods and methods within Protocols do not require an implementation
GitOrigin-RevId: a53889a8d6f4ed9ad7fde1e7fa02025f88e292a3
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6f9f976018
commit
8abfc73b15
+3
-14
@@ -17,8 +17,8 @@ import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.PythonUiService;
|
||||
import com.jetbrains.python.codeInsight.typing.PyProtocolsKt;
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyClassImpl;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import com.jetbrains.python.refactoring.PyPsiRefactoringUtil;
|
||||
@@ -49,7 +49,7 @@ public final class PyAbstractClassInspection extends PyInspection {
|
||||
if (node.getCallee() instanceof PyReferenceExpression calleeReferenceExpression) {
|
||||
QualifiedResolveResult resolveResult = calleeReferenceExpression.followAssignmentsChain(getResolveContext());
|
||||
if (resolveResult.getElement() instanceof PyClass pyClass) {
|
||||
if (canHaveAbstractMethods(pyClass)) {
|
||||
if (PyClassImpl.canHaveAbstractMethods(pyClass, myTypeEvalContext)) {
|
||||
boolean hasAbstractMethod =
|
||||
ContainerUtil.exists(pyClass.getMethods(), method -> PyKnownDecoratorUtil.hasAbstractDecorator(method, myTypeEvalContext));
|
||||
if (hasAbstractMethod || !getAllSuperAbstractMethods(pyClass).isEmpty()) {
|
||||
@@ -84,7 +84,7 @@ public final class PyAbstractClassInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
|
||||
if (!canHaveAbstractMethods(pyClass)) {
|
||||
if (!PyClassImpl.canHaveAbstractMethods(pyClass, myTypeEvalContext)) {
|
||||
for (PyDecorator decorator : abstractDecorators) {
|
||||
final SmartList<LocalQuickFix> quickFixes = new SmartList<>();
|
||||
addMakeClassAbstractFixes(pyClass, quickFixes);
|
||||
@@ -114,17 +114,6 @@ public final class PyAbstractClassInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canHaveAbstractMethods(@NotNull PyClass pyClass) {
|
||||
PyClassLikeType metaClassType = pyClass.getMetaClassType(true, myTypeEvalContext);
|
||||
if (metaClassType != null && PyNames.ABC_META.equals(metaClassType.getClassQName())) {
|
||||
return true;
|
||||
}
|
||||
return pyClass.getAncestorTypes(myTypeEvalContext).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(PyClassLikeType::getClassQName)
|
||||
.anyMatch(qName -> PyTypingTypeProvider.PROTOCOL.equals(qName) || PyTypingTypeProvider.PROTOCOL_EXT.equals(qName));
|
||||
}
|
||||
|
||||
private boolean isAbstract(@NotNull PyClass pyClass) {
|
||||
final PyClassLikeType metaClass = pyClass.getMetaClassType(false, myTypeEvalContext);
|
||||
if (metaClass != null && PyNames.ABC_META_CLASS.equals(metaClass.getName())) {
|
||||
|
||||
+23
-10
@@ -8,10 +8,13 @@ import com.intellij.util.Processor
|
||||
import com.intellij.util.containers.SortedList
|
||||
import com.jetbrains.python.PyPsiBundle
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner
|
||||
import com.jetbrains.python.codeInsight.typing.isProtocol
|
||||
import com.jetbrains.python.psi.PyClass
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import com.jetbrains.python.psi.PyFunction
|
||||
import com.jetbrains.python.psi.PyKnownDecoratorUtil
|
||||
import com.jetbrains.python.psi.PyUtil
|
||||
import com.jetbrains.python.psi.impl.PyClassImpl
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
import com.jetbrains.python.pyi.PyiFile
|
||||
import com.jetbrains.python.pyi.PyiUtil
|
||||
@@ -48,28 +51,38 @@ class PyOverloadsInspection : PyInspection() {
|
||||
}
|
||||
|
||||
private fun processSameNameFunctions(owner: ScopeOwner, functions: List<PyFunction>) {
|
||||
if (functions.find { PyiUtil.isOverload(it, myTypeEvalContext) } == null) return
|
||||
val (overloads, implementations) = functions.partition { PyiUtil.isOverload(it, myTypeEvalContext) }
|
||||
|
||||
val implementation = functions.lastOrNull { !PyiUtil.isOverload(it, myTypeEvalContext) }
|
||||
if (overloads.isEmpty()) return
|
||||
|
||||
if (implementation == null) {
|
||||
registerProblem(functions.first().nameIdentifier, if (owner is PyClass) {
|
||||
PyPsiBundle.message("INSP.overloads.series.overload.decorated.methods.should.always.be.followed.by.implementation")
|
||||
var requiresImplementation = true
|
||||
if (owner is PyClass) {
|
||||
if (isProtocol(owner, myTypeEvalContext)) {
|
||||
requiresImplementation = false
|
||||
}
|
||||
else {
|
||||
PyPsiBundle.message("INSP.overloads.series.overload.decorated.functions.should.always.be.followed.by.implementation")
|
||||
})
|
||||
if (PyClassImpl.canHaveAbstractMethods(owner, myTypeEvalContext)) {
|
||||
if (overloads.all { PyKnownDecoratorUtil.hasAbstractDecorator(it, myTypeEvalContext) }) {
|
||||
requiresImplementation = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (implementation != functions.last()) {
|
||||
registerProblem(functions.last().nameIdentifier, if (owner is PyClass) {
|
||||
|
||||
val implementation = implementations.lastOrNull()
|
||||
if (requiresImplementation) {
|
||||
if (implementation !== functions.last()) {
|
||||
val problemElement = if (implementation == null) functions.first() else functions.last()
|
||||
registerProblem(problemElement.nameIdentifier, if (owner is PyClass) {
|
||||
PyPsiBundle.message("INSP.overloads.series.overload.decorated.methods.should.always.be.followed.by.implementation")
|
||||
}
|
||||
else {
|
||||
PyPsiBundle.message("INSP.overloads.series.overload.decorated.functions.should.always.be.followed.by.implementation")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (implementation != null) {
|
||||
functions
|
||||
.asSequence()
|
||||
.filter { isIncompatibleOverload(implementation, it) }
|
||||
|
||||
@@ -47,6 +47,7 @@ import com.jetbrains.python.psi.types.*;
|
||||
import com.jetbrains.python.pyi.PyiUtil;
|
||||
import com.jetbrains.python.toolbox.Maybe;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -1770,6 +1771,18 @@ public class PyClassImpl extends PyBaseElementImpl<PyClassStub> implements PyCla
|
||||
return getStubOrPsiChild(PyStubElementTypes.DECORATOR_LIST);
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public static boolean canHaveAbstractMethods(@NotNull PyClass pyClass, @NotNull TypeEvalContext context) {
|
||||
PyClassLikeType metaClassType = pyClass.getMetaClassType(true, context);
|
||||
if (metaClassType != null && PyNames.ABC_META.equals(metaClassType.getClassQName())) {
|
||||
return true;
|
||||
}
|
||||
return pyClass.getAncestorTypes(context).stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(PyClassLikeType::getClassQName)
|
||||
.anyMatch(qName -> PyTypingTypeProvider.PROTOCOL.equals(qName) || PyTypingTypeProvider.PROTOCOL_EXT.equals(qName));
|
||||
}
|
||||
|
||||
private @NotNull TypeEvalContext notNullizeContext(@Nullable TypeEvalContext context) {
|
||||
return context == null ? TypeEvalContext.codeInsightFallback(getProject()) : context;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import overload
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import overload, Protocol
|
||||
|
||||
|
||||
@overload
|
||||
@@ -27,4 +28,34 @@ class A:
|
||||
|
||||
@overload
|
||||
def foo(self, value: str) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class P(Protocol):
|
||||
@overload
|
||||
def foo(self, x: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def foo(self, x: str) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class Abstract(ABC):
|
||||
@overload
|
||||
@abstractmethod
|
||||
def foo(self, x: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
@abstractmethod
|
||||
def foo(self, x: str) -> str:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def <warning descr="A series of @overload-decorated methods should always be followed by an implementation that is not @overload-ed">not_abstract</warning>(self, x: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def not_abstract(self, x: str) -> str:
|
||||
pass
|
||||
Reference in New Issue
Block a user