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); + } }