PY-16035 Warning about not implemented abstract methods is hidden for abstract subclasses

This commit is contained in:
Mikhail Golubev
2015-06-01 15:46:22 +03:00
parent fb0c462097
commit 412c9ff86c
8 changed files with 105 additions and 20 deletions
@@ -141,6 +141,7 @@ public class PyNames {
public static final String ABSTRACTMETHOD = "abstractmethod";
public static final String ABSTRACTPROPERTY = "abstractproperty";
public static final String ABC_META_CLASS = "ABCMeta";
public static final String TUPLE = "tuple";
public static final String SET = "set";
@@ -307,13 +307,11 @@ public class PyOverrideImplementUtil {
}
@NotNull
public static Collection<PyFunction> getAllSuperFunctions(@NotNull final PyClass pyClass) {
public static Collection<PyFunction> getAllSuperFunctions(@NotNull PyClass pyClass) {
final Map<String, PyFunction> superFunctions = new HashMap<String, PyFunction>();
for (PyClass aClass : pyClass.getAncestorClasses()) {
for (PyFunction function : aClass.getMethods(false)) {
if (!superFunctions.containsKey(function.getName())) {
superFunctions.put(function.getName(), function);
}
for (PyFunction function : pyClass.getMethods(true)) {
if (!superFunctions.containsKey(function.getName())) {
superFunctions.put(function.getName(), function);
}
}
return superFunctions.values();
@@ -20,11 +20,13 @@ import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.override.PyOverrideImplementUtil;
import com.jetbrains.python.inspections.quickfix.PyImplementMethodsQuickFix;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.PyClassLikeType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -33,6 +35,8 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import static com.jetbrains.python.psi.PyUtil.as;
/**
* User: ktisha
*/
@@ -58,25 +62,49 @@ public class PyAbstractClassInspection extends PyInspection {
}
@Override
public void visitPyClass(PyClass node) {
public void visitPyClass(PyClass pyClass) {
if (isAbstract(pyClass)) {
return;
}
final Set<PyFunction> toBeImplemented = new HashSet<PyFunction>();
final Collection<PyFunction> functions = PyOverrideImplementUtil.getAllSuperFunctions(node);
final Collection<PyFunction> functions = PyOverrideImplementUtil.getAllSuperFunctions(pyClass);
for (PyFunction method : functions) {
if (isAbstractMethodForClass(method, node)) {
if (isAbstractMethodForClass(method, pyClass)) {
toBeImplemented.add(method);
}
}
final ASTNode nameNode = node.getNameNode();
final ASTNode nameNode = pyClass.getNameNode();
if (!toBeImplemented.isEmpty() && nameNode != null) {
registerProblem(nameNode.getPsi(),
PyBundle.message("INSP.NAME.abstract.class.$0.must.implement", node.getName()),
new PyImplementMethodsQuickFix(node, toBeImplemented));
PyBundle.message("INSP.NAME.abstract.class.$0.must.implement", pyClass.getName()),
new PyImplementMethodsQuickFix(pyClass, toBeImplemented));
}
}
private static boolean isAbstract(@NotNull PyClass pyClass) {
final PyType metaClass = pyClass.getMetaClassType(TypeEvalContext.userInitiated(pyClass.getProject(), pyClass.getContainingFile()));
if (metaClass instanceof PyClassLikeType && PyNames.ABC_META_CLASS.equals(metaClass.getName())) {
return true;
}
if (metaClass == null) {
final PyExpression metaClassExpr = as(pyClass.getMetaClassExpression(), PyReferenceExpression.class);
if (metaClassExpr != null && PyNames.ABC_META_CLASS.equals(metaClassExpr.getName())) {
return true;
}
}
for (PyFunction method : pyClass.getMethods(false)) {
if (PyUtil.isDecoratedAsAbstract(method)) {
return true;
}
}
return false;
}
private static boolean isAbstractMethodForClass(@NotNull PyFunction method, @NotNull PyClass cls) {
final String methodName = method.getName();
if (methodName == null || cls.findMethodByName(methodName, false) != null || cls.findClassAttribute(methodName, false) != null) {
if (methodName == null ||
cls.findMethodByName(methodName, false) != null ||
cls.findClassAttribute(methodName, false) != null) {
return false;
}
return PyUtil.isDecoratedAsAbstract(method) || PyOverrideImplementUtil.raisesNotImplementedError(method);
@@ -42,7 +42,6 @@ import java.util.*;
* @author Ilya.Kazakevich
*/
class MethodsManager extends MembersManager<PyFunction> {
private static final String ABC_META_CLASS = "ABCMeta";
/**
* Some decorators should be copied with methods if method is marked abstract. Here is list.
@@ -123,7 +122,7 @@ class MethodsManager extends MembersManager<PyFunction> {
// Add imports for ABC if needed
for (final PsiFile file : filesToCheckImport) {
addImportFromAbc(file, PyNames.ABSTRACTMETHOD);
addImportFromAbc(file, ABC_META_CLASS);
addImportFromAbc(file, PyNames.ABC_META_CLASS);
PyClassRefactoringUtil.optimizeImports(file); //To remove redundant imports
}
}
@@ -146,11 +145,11 @@ class MethodsManager extends MembersManager<PyFunction> {
// Add (metaclass= for Py3K
PyClassRefactoringUtil
.addSuperClassExpressions(aClass.getProject(), aClass, null, Collections.singletonList(Pair.create(PyNames.METACLASS,
ABC_META_CLASS)));
PyNames.ABC_META_CLASS)));
}
else {
// Add __metaclass__ for Py2
PyClassRefactoringUtil.addClassAttributeIfNotExist(aClass, PyNames.DUNDER_METACLASS, ABC_META_CLASS);
PyClassRefactoringUtil.addClassAttributeIfNotExist(aClass, PyNames.DUNDER_METACLASS, PyNames.ABC_META_CLASS);
}
return true;
}
@@ -0,0 +1,15 @@
import abc
class A1(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def m1(self):
pass
class A2(A1):
@abc.abstractmethod
def m2(self):
pass
@@ -0,0 +1,13 @@
import abc
class A1(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def m1(self):
pass
class A2(A1):
__metaclass__ = abc.ABCMeta
@@ -0,0 +1,11 @@
import abc
class A1(metaclass=abc.ABCMeta):
@abc.abstractmethod
def m1(self):
pass
class A2(A1, metaclass=abc.ABCMeta):
pass
@@ -16,6 +16,7 @@
package com.jetbrains.python.inspections;
import com.jetbrains.python.fixtures.PyInspectionTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import org.jetbrains.annotations.NotNull;
public class PyAbstractClassInspectionTest extends PyInspectionTestCase {
@@ -32,6 +33,25 @@ public class PyAbstractClassInspectionTest extends PyInspectionTestCase {
doTest();
}
// PY-16035
public void testHiddenForAbstractSubclassWithExplicitMetaclass() {
doTest();
}
// PY-16035
public void testHiddenForAbstractSubclassWithExplicitMetaclassPy3() {
runWithLanguageLevel(LanguageLevel.PYTHON30, new Runnable() {
public void run() {
doTest();
}
});
}
// PY-16035
public void testHiddenForAbstractSubclassWithAbstractMethod() {
doTest();
}
@NotNull
@Override
protected Class<? extends PyInspection> getInspectionClass() {