From b1326d769d297334b62a9ba91c5615256b6fe597 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 4 Jul 2016 16:27:43 +0300 Subject: [PATCH] PY-19461 Fixed: Inspection does not recognize all subclasses PY-19530 Fixed: from MODULE import SUBMODULE as NAME not find inheritance If superclass is imported via 'as', save its imported and original names in stubs. Use original name in PySuperClassIndex building and imported name in superclasses calculating --- .../python/psi/stubs/PyClassStub.java | 8 ++- .../python/psi/PyFileElementType.java | 2 +- .../python/psi/impl/PyClassImpl.java | 15 ++-- .../psi/impl/stubs/PyClassElementType.java | 72 +++++++++++++------ .../psi/impl/stubs/PyClassStubImpl.java | 13 ++-- .../a.py | 2 + .../b.py | 5 ++ .../a.py | 2 + .../b.py | 4 ++ .../a.py | 2 + .../b.py | 5 ++ .../python/PyInheritorsSearchTest.java | 10 ++- .../com/jetbrains/python/PyStubsTest.java | 56 +++++++++++++-- 13 files changed, 151 insertions(+), 45 deletions(-) create mode 100644 python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/a.py create mode 100644 python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/b.py create mode 100644 python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/a.py create mode 100644 python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/b.py create mode 100644 python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/a.py create mode 100644 python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/b.py diff --git a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java index 7d00e5988bf6..b14307af1cd1 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java +++ b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyClassStub.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,9 +25,13 @@ import com.jetbrains.python.psi.PyClass; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Map; public interface PyClassStub extends NamedStub { - QualifiedName[] getSuperClasses(); + /** + * @return a {@code Map} which contains imported class names as keys and their original names as values + */ + Map getSuperClasses(); @Nullable QualifiedName getMetaClass(); List getSlots(); String getDocString(); diff --git a/python/src/com/jetbrains/python/psi/PyFileElementType.java b/python/src/com/jetbrains/python/psi/PyFileElementType.java index 3fc046be1632..c62a709c74a2 100644 --- a/python/src/com/jetbrains/python/psi/PyFileElementType.java +++ b/python/src/com/jetbrains/python/psi/PyFileElementType.java @@ -62,7 +62,7 @@ public class PyFileElementType extends IStubFileElementType { @Override public int getStubVersion() { // Don't forget to update versions of indexes that use the updated stub-based elements - return 57; + return 58; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 928d60736ab9..7f193e9843f0 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1294,18 +1294,13 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla private void fillSuperClassesNoSwitchToAst(@NotNull final TypeEvalContext context, @Nullable final PyClassStub stub, @NotNull final List result) { - final List qualifiedNames; - if (stub != null) { - qualifiedNames = Arrays.asList(stub.getSuperClasses()); - } - else { - qualifiedNames = PyClassElementType.getSuperClassQNames(this); - } - + final Map superClasses = stub != null + ? stub.getSuperClasses() + : PyClassElementType.getSuperClassQNames(this); final PsiFile file = getContainingFile(); if (file instanceof PyFile) { - for (QualifiedName name : qualifiedNames) { + for (QualifiedName name : superClasses.keySet()) { result.add(name != null ? classTypeFromQName(name, (PyFile)file, context) : null); } } diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java index a62cae8583f4..dc2f29cd4018 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,12 +24,14 @@ import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyClassImpl; import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.psi.resolve.PyResolveProcessor; +import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.stubs.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * @author max @@ -53,10 +55,10 @@ public class PyClassElementType extends PyStubElementType } public PyClassStub createStub(@NotNull final PyClass psi, final StubElement parentStub) { - final List superClasses = getSuperClassQNames(psi); + final Map superClasses = getSuperClassQNames(psi); final PyStringLiteralExpression docStringExpression = psi.getDocStringExpression(); return new PyClassStubImpl(psi.getName(), parentStub, - superClasses.toArray(new QualifiedName[superClasses.size()]), + superClasses, PyPsiUtils.asQualifiedName(psi.getMetaClassExpression()), psi.getOwnSlots(), PyPsiUtils.strValue(docStringExpression), @@ -64,25 +66,53 @@ public class PyClassElementType extends PyStubElementType } @NotNull - public static List getSuperClassQNames(@NotNull final PyClass pyClass) { - final PyExpression[] exprs = pyClass.getSuperClassExpressions(); - List superClasses = new ArrayList(); - for (PyExpression expression : exprs) { - if (expression instanceof PyKeywordArgument) { - continue; + public static Map getSuperClassQNames(@NotNull final PyClass pyClass) { + final Map result = new LinkedHashMap<>(); + + Arrays + .stream(pyClass.getSuperClassExpressions()) + .filter(expression -> !PyKeywordArgument.class.isInstance(expression)) + .map(PyClassImpl::unfoldClass) + .forEach(expression -> result.put(PyPsiUtils.asQualifiedName(expression), resolveOriginalSuperClassQName(expression))); + + return result; + } + + @Nullable + private static QualifiedName resolveOriginalSuperClassQName(@NotNull PyExpression superClassExpression) { + if (superClassExpression instanceof PyReferenceExpression) { + final PyReferenceExpression reference = (PyReferenceExpression)superClassExpression; + final String referenceName = reference.getName(); + + if (referenceName == null) { + return PyPsiUtils.asQualifiedName(superClassExpression); + } + + final PyResolveProcessor processor = new PyResolveProcessor(referenceName, true); + PyResolveUtil.scopeCrawlUp(processor, reference, referenceName, null); + + final Optional qualifiedName = processor.getElements().stream() + .filter(PyImportElement.class::isInstance) + .map(PyImportElement.class::cast) + .filter(element -> element.getAsName() != null) + .map(PyImportElement::getImportedQName) + .findAny(); + + if (qualifiedName.isPresent()) { + return qualifiedName.get(); } - expression = PyClassImpl.unfoldClass(expression); - superClasses.add(PyPsiUtils.asQualifiedName(expression)); } - return superClasses; + + return PyPsiUtils.asQualifiedName(superClassExpression); } public void serialize(@NotNull final PyClassStub pyClassStub, @NotNull final StubOutputStream dataStream) throws IOException { dataStream.writeName(pyClassStub.getName()); - final QualifiedName[] classes = pyClassStub.getSuperClasses(); - dataStream.writeByte(classes.length); - for (QualifiedName s : classes) { - QualifiedName.serialize(s, dataStream); + final Map superClasses = pyClassStub.getSuperClasses(); + dataStream.writeByte(superClasses.size()); + for (Map.Entry entry : superClasses.entrySet()) { + QualifiedName.serialize(entry.getKey(), dataStream); + QualifiedName.serialize(entry.getValue(), dataStream); } QualifiedName.serialize(pyClassStub.getMetaClass(), dataStream); PyFileElementType.writeNullableList(dataStream, pyClassStub.getSlots()); @@ -94,9 +124,9 @@ public class PyClassElementType extends PyStubElementType public PyClassStub deserialize(@NotNull final StubInputStream dataStream, final StubElement parentStub) throws IOException { String name = StringRef.toString(dataStream.readName()); int superClassCount = dataStream.readByte(); - QualifiedName[] superClasses = new QualifiedName[superClassCount]; + Map superClasses = new LinkedHashMap<>(); for (int i = 0; i < superClassCount; i++) { - superClasses[i] = QualifiedName.deserialize(dataStream); + superClasses.put(QualifiedName.deserialize(dataStream), QualifiedName.deserialize(dataStream)); } final QualifiedName metaClass = QualifiedName.deserialize(dataStream); List slots = PyFileElementType.readNullableList(dataStream); @@ -115,7 +145,7 @@ public class PyClassElementType extends PyStubElementType for (String attribute : PyClassAttributesIndex.getAllDeclaredAttributeNames(pyClass)) { sink.occurrence(PyClassAttributesIndex.KEY, attribute); } - for (QualifiedName s : stub.getSuperClasses()) { + for (QualifiedName s : stub.getSuperClasses().values()) { if (s != null) { String className = s.getLastComponent(); if (className != null) { diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java index 294885838bfc..934db36edb90 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyClassStubImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,25 +18,26 @@ package com.jetbrains.python.psi.impl.stubs; import com.intellij.psi.stubs.IStubElementType; import com.intellij.psi.stubs.StubBase; import com.intellij.psi.stubs.StubElement; -import com.jetbrains.python.psi.PyClass; import com.intellij.psi.util.QualifiedName; +import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.stubs.PyClassStub; import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Map; /** * @author max */ public class PyClassStubImpl extends StubBase implements PyClassStub { private final String myName; - private final QualifiedName[] mySuperClasses; + private final Map mySuperClasses; @Nullable private final QualifiedName myMetaClass; private final List mySlots; private final String myDocString; - public PyClassStubImpl(final String name, StubElement parentStub, final QualifiedName[] superClasses, @Nullable QualifiedName metaClass, - final List slots, String docString, IStubElementType stubElementType) { + public PyClassStubImpl(final String name, StubElement parentStub, final Map superClasses, + @Nullable QualifiedName metaClass, final List slots, String docString, IStubElementType stubElementType) { super(parentStub, stubElementType); myName = name; mySuperClasses = superClasses; @@ -49,7 +50,7 @@ public class PyClassStubImpl extends StubBase implements PyClassStub { return myName; } - public QualifiedName[] getSuperClasses() { + public Map getSuperClasses() { return mySuperClasses; } diff --git a/python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/a.py b/python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/a.py new file mode 100644 index 000000000000..67b24cac9509 --- /dev/null +++ b/python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/a.py @@ -0,0 +1,2 @@ +class C: + pass \ No newline at end of file diff --git a/python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/b.py b/python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/b.py new file mode 100644 index 000000000000..475d9ea8466c --- /dev/null +++ b/python/testData/inheritors/inheritorsWhenSuperClassImportedWithAs/b.py @@ -0,0 +1,5 @@ +from a import C as C2 + + +class D(C2): + pass \ No newline at end of file diff --git a/python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/a.py b/python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/a.py new file mode 100644 index 000000000000..696b8ce26dab --- /dev/null +++ b/python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/a.py @@ -0,0 +1,2 @@ +class A(object): + pass \ No newline at end of file diff --git a/python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/b.py b/python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/b.py new file mode 100644 index 000000000000..849937b67b39 --- /dev/null +++ b/python/testData/stubs/ancestorsWhenSuperClassImportedWithQName/b.py @@ -0,0 +1,4 @@ +import A + +class B(A.A): + pass \ No newline at end of file diff --git a/python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/a.py b/python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/a.py new file mode 100644 index 000000000000..67b24cac9509 --- /dev/null +++ b/python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/a.py @@ -0,0 +1,2 @@ +class C: + pass \ No newline at end of file diff --git a/python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/b.py b/python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/b.py new file mode 100644 index 000000000000..475d9ea8466c --- /dev/null +++ b/python/testData/stubs/inheritorsWhenSuperClassImportedWithAs/b.py @@ -0,0 +1,5 @@ +from a import C as C2 + + +class D(C2): + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyInheritorsSearchTest.java b/python/testSrc/com/jetbrains/python/PyInheritorsSearchTest.java index 11c02f14abdc..c0ebd0d929da 100644 --- a/python/testSrc/com/jetbrains/python/PyInheritorsSearchTest.java +++ b/python/testSrc/com/jetbrains/python/PyInheritorsSearchTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,6 +49,14 @@ public class PyInheritorsSearchTest extends PyTestCase { assertEquals(1, inheritors.size()); } + // PY-19461 + public void testInheritorsWhenSuperClassImportedWithAs() throws Exception { + setupProject(); + final PyClass pyClass = findClass("C"); + final Collection inheritors = PyClassInheritorsSearch.search(pyClass, false).findAll(); + assertSameElements(inheritors, findClass("D")); + } + private void setupProject() throws Exception { String testName = getTestName(true); myFixture.copyDirectoryToProject(testName, ""); diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index 60eaac935e80..c4bddf13d69a 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -25,7 +25,9 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.ProjectScope; import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.util.QualifiedName; import com.intellij.testFramework.TestDataPath; import com.jetbrains.python.codeInsight.stdlib.PyNamedTupleType; @@ -35,6 +37,7 @@ import com.jetbrains.python.psi.impl.PyFileImpl; import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import com.jetbrains.python.psi.stubs.PyNamedTupleStub; +import com.jetbrains.python.psi.stubs.PySuperClassIndex; import com.jetbrains.python.psi.stubs.PyVariableNameIndex; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; @@ -42,10 +45,7 @@ import com.jetbrains.python.toolbox.Maybe; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; /** * @author max @@ -548,4 +548,52 @@ public class PyStubsTest extends PyTestCase { assertEquals(expectedName, namedTupleType.getName()); assertEquals(expectedFields, namedTupleType.getElementNames()); } + + // PY-19461 + public void testInheritorsWhenSuperClassImportedWithAs() { + final PyFile file1 = getTestFile("inheritorsWhenSuperClassImportedWithAs/a.py"); + final PyFile file2 = getTestFile("inheritorsWhenSuperClassImportedWithAs/b.py"); + + final Project project = myFixture.getProject(); + + final Collection classes = + StubIndex.getElements(PySuperClassIndex.KEY, "C", project, ProjectScope.getAllScope(project), PyClass.class); + + assertEquals(1, classes.size()); + assertEquals("D", classes.iterator().next().getName()); + + assertNotParsed(file1); + assertNotParsed(file2); + } + + // PY-19461 + public void testAncestorsWhenSuperClassImportedWithAs() { + final PyFile file1 = getTestFile("inheritorsWhenSuperClassImportedWithAs/a.py"); + final PyFile file2 = getTestFile("inheritorsWhenSuperClassImportedWithAs/b.py"); + + final PyClass pyClass = file2.findTopLevelClass("D"); + assertNotNull(pyClass); + + final Map superClasses = pyClass.getStub().getSuperClasses(); + assertEquals(1, superClasses.size()); + assertEquals(QualifiedName.fromComponents("C"), superClasses.get(QualifiedName.fromComponents("C2"))); + + assertNotParsed(file1); + assertNotParsed(file2); + } + + public void testAncestorsWhenSuperClassImportedWithQName() { + final PyFile file1 = getTestFile("ancestorsWhenSuperClassImportedWithQName/a.py"); + final PyFile file2 = getTestFile("ancestorsWhenSuperClassImportedWithQName/b.py"); + + final PyClass pyClass = file2.findTopLevelClass("B"); + assertNotNull(pyClass); + + final Map superClasses = pyClass.getStub().getSuperClasses(); + assertEquals(1, superClasses.size()); + assertEquals(QualifiedName.fromDottedString("A.A"), superClasses.get(QualifiedName.fromDottedString("A.A"))); + + assertNotParsed(file1); + assertNotParsed(file2); + } }