diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index 2fabb81f95e6..315152aee4b6 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -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"; diff --git a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java index 58b2428ba3c7..1ed88d45623c 100644 --- a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java @@ -307,13 +307,11 @@ public class PyOverrideImplementUtil { } @NotNull - public static Collection getAllSuperFunctions(@NotNull final PyClass pyClass) { + public static Collection getAllSuperFunctions(@NotNull PyClass pyClass) { final Map superFunctions = new HashMap(); - 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(); diff --git a/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java b/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java index dcf0fcd15bdd..6d78b10e2821 100644 --- a/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java @@ -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 toBeImplemented = new HashSet(); - final Collection functions = PyOverrideImplementUtil.getAllSuperFunctions(node); + final Collection 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); diff --git a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java index 8bbac27145f3..b7327c89e77a 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java +++ b/python/src/com/jetbrains/python/refactoring/classes/membersManager/MethodsManager.java @@ -42,7 +42,6 @@ import java.util.*; * @author Ilya.Kazakevich */ class MethodsManager extends MembersManager { - 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 { // 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 { // 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; } diff --git a/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithAbstractMethod.py b/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithAbstractMethod.py new file mode 100644 index 000000000000..e4bd6bde61e4 --- /dev/null +++ b/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithAbstractMethod.py @@ -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 \ No newline at end of file diff --git a/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithExplicitMetaclass.py b/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithExplicitMetaclass.py new file mode 100644 index 000000000000..b1a6720fdcf9 --- /dev/null +++ b/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithExplicitMetaclass.py @@ -0,0 +1,13 @@ +import abc + + +class A1(object): + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def m1(self): + pass + + +class A2(A1): + __metaclass__ = abc.ABCMeta \ No newline at end of file diff --git a/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithExplicitMetaclassPy3.py b/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithExplicitMetaclassPy3.py new file mode 100644 index 000000000000..37a62b7c53e7 --- /dev/null +++ b/python/testData/inspections/PyAbstractClassInspection/hiddenForAbstractSubclassWithExplicitMetaclassPy3.py @@ -0,0 +1,11 @@ +import abc + + +class A1(metaclass=abc.ABCMeta): + @abc.abstractmethod + def m1(self): + pass + + +class A2(A1, metaclass=abc.ABCMeta): + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/inspections/PyAbstractClassInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyAbstractClassInspectionTest.java index cf1bba172199..78c2b5f8e4bd 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyAbstractClassInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyAbstractClassInspectionTest.java @@ -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 getInspectionClass() {