mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 23:31:05 +07:00
PY-12132 Support ABC classes (pep-3119)
A class containing at least one method declared with `abstractmethod` decorator that hasn’t been overridden yet cannot be instantiated. Also report instantiation of classes that directly inherit ABC or have metaclass set to ABCMeta. (cherry picked from commit 55cb4dc90c55ddc63991a4c3f6b50b4e34a3b4bd) GitOrigin-RevId: b37ea24490dc5ce7dcce87adf21aa6fe31e0e9dc
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8fdd497e22
commit
42dd3f39bf
@@ -1,7 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
<p>Reports cases when not all abstract properties or methods are defined in
|
||||
a subclass.</p>
|
||||
<p>Reports invalid definition and usages of abstract classes.</p>
|
||||
<p><b>Example:</b></p>
|
||||
<pre><code>
|
||||
from abc import abstractmethod, ABC
|
||||
@@ -14,9 +13,12 @@ class Figure(ABC):
|
||||
pass
|
||||
|
||||
|
||||
class Triangle(Figure):
|
||||
class Triangle(Figure): # Not all abstract methods are defined in 'Triangle' class
|
||||
def do_triangle(self):
|
||||
pass
|
||||
|
||||
|
||||
Triangle() # Cannot instantiate abstract class 'Triangle'
|
||||
</code></pre>
|
||||
<p>When the quick-fix is applied, the IDE implements an abstract method for the <code>Triangle</code> class:</p>
|
||||
<pre><code>
|
||||
|
||||
@@ -880,8 +880,9 @@ INSP.NAME.method.may.be.static=Method is not declared static
|
||||
INSP.method.may.be.static=Method <code>#ref</code> may be 'static'
|
||||
|
||||
# PyAbstractClassInspection
|
||||
INSP.NAME.abstract.class=Class must implement all abstract methods
|
||||
INSP.NAME.abstract.class=Invalid abstract class definition and usages
|
||||
INSP.abstract.class.class.must.implement.all.abstract.methods=Class {0} must implement all abstract methods
|
||||
INSP.abstract.class.cannot.instantiate.abstract.class=Cannot instantiate abstract class ''{0}''
|
||||
|
||||
#PyAssignmentToLoopOrWithParameterInspection
|
||||
INSP.NAME.assignment.to.loop.or.with.parameter=Assignments to 'for' loop or 'with' statement parameter
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.jetbrains.python.inspections;
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
@@ -16,14 +17,16 @@ 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.types.PyClassLikeType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import com.jetbrains.python.refactoring.PyPsiRefactoringUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class PyAbstractClassInspection extends PyInspection {
|
||||
|
||||
@@ -36,22 +39,39 @@ public final class PyAbstractClassInspection extends PyInspection {
|
||||
|
||||
private static final class Visitor extends PyInspectionVisitor {
|
||||
|
||||
private static @Nls @NotNull String canNotInstantiateAbstractClassMessage(@NotNull PyClass pyClass) {
|
||||
return PyPsiBundle.message("INSP.abstract.class.cannot.instantiate.abstract.class", pyClass.getName());
|
||||
}
|
||||
|
||||
private Visitor(@NotNull ProblemsHolder holder,
|
||||
@NotNull TypeEvalContext context) {
|
||||
super(holder, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyCallExpression(@NotNull PyCallExpression node) {
|
||||
if (node.getCallee() instanceof PyReferenceExpression calleeReferenceExpression) {
|
||||
QualifiedResolveResult resolveResult = calleeReferenceExpression.followAssignmentsChain(getResolveContext());
|
||||
if (resolveResult.getElement() instanceof PyClass pyClass) {
|
||||
if (canHaveAbstractMethods(pyClass)) {
|
||||
if (hasAbstractMethod(pyClass) || !getAllSuperAbstractMethods(pyClass).isEmpty()) {
|
||||
registerProblem(node, canNotInstantiateAbstractClassMessage(pyClass), ProblemHighlightType.WARNING);
|
||||
}
|
||||
else if (isAbstract(pyClass)) {
|
||||
registerProblem(node, canNotInstantiateAbstractClassMessage(pyClass));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyClass(@NotNull PyClass pyClass) {
|
||||
if (isAbstract(pyClass) || PyProtocolsKt.isProtocol(pyClass, myTypeEvalContext)) {
|
||||
if (isAbstract(pyClass) || hasAbstractMethod(pyClass) || PyProtocolsKt.isProtocol(pyClass, myTypeEvalContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Do not report problem if class contains only methods that raise NotImplementedError without any abc.* decorators
|
||||
but keep ability to implement them via quickfix (see PY-38680) */
|
||||
final List<PyFunction> toImplement = ContainerUtil
|
||||
.filter(PyPsiRefactoringUtil.getAllSuperAbstractMethods(pyClass, myTypeEvalContext),
|
||||
function -> PyKnownDecoratorUtil.hasAbstractDecorator(function, myTypeEvalContext));
|
||||
final List<PyFunction> toImplement = getAllSuperAbstractMethods(pyClass);
|
||||
|
||||
final ASTNode nameNode = pyClass.getNameNode();
|
||||
if (!toImplement.isEmpty() && nameNode != null) {
|
||||
@@ -73,6 +93,17 @@ 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())) {
|
||||
@@ -83,6 +114,10 @@ public final class PyAbstractClassInspection extends PyInspection {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasAbstractMethod(@NotNull PyClass pyClass) {
|
||||
for (PyFunction method : pyClass.getMethods()) {
|
||||
if (PyKnownDecoratorUtil.hasAbstractDecorator(method, myTypeEvalContext)) {
|
||||
return true;
|
||||
@@ -91,6 +126,13 @@ public final class PyAbstractClassInspection extends PyInspection {
|
||||
return false;
|
||||
}
|
||||
|
||||
private @NotNull List<PyFunction> getAllSuperAbstractMethods(@NotNull PyClass pyClass) {
|
||||
/* Do not report problem if class contains only methods that raise NotImplementedError without any abc.* decorators
|
||||
but keep ability to implement them via quickfix (see PY-38680) */
|
||||
return ContainerUtil.filter(PyPsiRefactoringUtil.getAllSuperAbstractMethods(pyClass, myTypeEvalContext),
|
||||
function -> PyKnownDecoratorUtil.hasAbstractDecorator(function, myTypeEvalContext));
|
||||
}
|
||||
|
||||
private static class AddABCToSuperclassesQuickFix extends PsiUpdateModCommandQuickFix {
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user