Remove unused PyTypeFromUsedAttributesHelper and its tests

This commit is contained in:
Mikhail Golubev
2015-01-14 16:37:18 +03:00
parent a1a23af2cf
commit b0881f93f7
8 changed files with 56 additions and 657 deletions
@@ -25,9 +25,15 @@ import java.util.Set;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.jetbrains.python.psi.PyUtil.as;
import static com.jetbrains.python.psi.types.PyTypeFromUsedAttributesHelper.getAllDeclaredAttributeNames;
/**
* This completion contributor tries to map structural type (if any) of qualifier under caret to set of concrete
* classes (nominal types) that have (declare and/or inherit) all necessary attributes and thus complete other
* attributes that may be accessed on this expression.
* <p/>
* Because it's somewhat computationally heavy operation that requires extensive resolution of superclasses and their
* attributes, this contributor is activated only on smart completion.
*
* @author Mikhail Golubev
*/
public class PyStructuralTypeAttributesCompletionContributor extends CompletionContributor {
@@ -125,9 +131,9 @@ public class PyStructuralTypeAttributesCompletionContributor extends CompletionC
@NotNull
private Set<String> getAllInheritedAttributeNames(@NotNull PyClass candidate) {
final Set<String> availableAttrs = Sets.newHashSet(getAllDeclaredAttributeNames(candidate));
final Set<String> availableAttrs = Sets.newHashSet(PyClassAttributesIndex.getAllDeclaredAttributeNames(candidate));
for (PyClass parent : getAncestorClassesFast(candidate)) {
availableAttrs.addAll(getAllDeclaredAttributeNames(parent));
availableAttrs.addAll(PyClassAttributesIndex.getAllDeclaredAttributeNames(parent));
}
return availableAttrs;
}
@@ -25,7 +25,6 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyClassImpl;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.stubs.*;
import com.jetbrains.python.psi.types.PyTypeFromUsedAttributesHelper;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -107,7 +106,7 @@ public class PyClassElementType extends PyStubElementType<PyClassStub, PyClass>
sink.occurrence(PyClassNameIndexInsensitive.KEY, name.toLowerCase());
}
final PyClass pyClass = createPsi(stub);
for (String attribute: PyTypeFromUsedAttributesHelper.getAllDeclaredAttributeNames(pyClass)) {
for (String attribute: PyClassAttributesIndex.getAllDeclaredAttributeNames(pyClass)) {
sink.occurrence(PyClassAttributesIndex.KEY, attribute);
}
for (QualifiedName s : stub.getSuperClasses()) {
@@ -1,14 +1,19 @@
package com.jetbrains.python.psi.stubs;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StringStubIndexExtension;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.stubs.StubIndexKey;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.PyClass;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author Mikhail Golubev
@@ -25,4 +30,25 @@ public class PyClassAttributesIndex extends StringStubIndexExtension<PyClass> {
public static Collection<PyClass> find(@NotNull String name, @NotNull Project project) {
return StubIndex.getElements(KEY, name, project, GlobalSearchScope.allScope(project), PyClass.class);
}
/**
* Returns all attributes: methods, class and instance fields that are declared directly in the specified class
* (not taking inheritance into account).
* <p/>
* This method <b>must not</b> access the AST because it is being called during stub indexing.
*/
@NotNull
public static List<String> getAllDeclaredAttributeNames(@NotNull PyClass pyClass) {
final List<PsiNamedElement> members = ContainerUtil.<PsiNamedElement>concat(pyClass.getInstanceAttributes(),
pyClass.getClassAttributes(),
Arrays.asList(pyClass.getMethods(false)));
return ContainerUtil.mapNotNull(members, new Function<PsiNamedElement, String>() {
@Override
public String fun(PsiNamedElement expression) {
final String attrName = expression.getName();
return attrName != null ? attrName : null;
}
});
}
}
@@ -1,361 +0,0 @@
package com.jetbrains.python.psi.types;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.stubs.PyClassAttributesIndex;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.jetbrains.python.psi.resolve.QualifiedNameFinder.findShortestImportableQName;
/**
* @author Mikhail Golubev
*/
public class PyTypeFromUsedAttributesHelper {
public static final int MAX_CANDIDATES = 5;
// Not final so it can be changed in debugger
private static boolean ENABLED = Boolean.parseBoolean(System.getProperty("py.infer.types.from.used.attributes", "true"));
private static final Set<String> COMMON_OBJECT_ATTRIBUTES = ImmutableSet.of(
"__init__",
"__new__",
"__str__",
"__repr__", // TODO __module__ actually belongs to object's metaclass, it's not available to instances
"__doc__",
"__class__",
"__module__",
"__dict__"
);
private static final Logger LOG = Logger.getInstance(PyTypeFromUsedAttributesHelper.class);
private final TypeEvalContext myContext;
private final Map<PyClass, Set<PyClass>> myAncestorsCache = Maps.newHashMap();
/**
* Attempts to guess the type of a given expression based on what attributes (including special names) were accessed on it. If several
* types fit then their union is returned. Suggested classes are ordered according to their
* {@link PyTypeFromUsedAttributesHelper.Priority}. Currently at most {@link #MAX_CANDIDATES} can be returned in a union.
*/
@Nullable
public static PyType getType(@NotNull PyExpression expression, @NotNull TypeEvalContext context) {
return new PyTypeFromUsedAttributesHelper(context).getType(expression);
}
private PyTypeFromUsedAttributesHelper(@NotNull TypeEvalContext context) {
myContext = context;
}
@Nullable
private PyType getType(@NotNull PyExpression expression) {
if (!ENABLED || !myContext.allowCallContext(expression)) {
return null;
}
final long startInference = System.currentTimeMillis();
final Set<String> seenAttrs = collectUsedAttributes(expression);
LOG.debug(String.format("Attempting to infer type for expression: %s. Used attributes: %s", expression.getText(), seenAttrs));
final Set<PyClass> allCandidates = suggestCandidateClasses(expression, seenAttrs);
final long startPrioritization = System.currentTimeMillis();
final List<CandidateClass> bestCandidates = prepareCandidates(allCandidates, expression);
LOG.debug("Total " + (System.currentTimeMillis() - startPrioritization) + " ms to prioritize candidate classes");
LOG.debug("Total " + (System.currentTimeMillis() - startInference) + " ms to infer candidate classes");
return PyUnionType.createWeakType(PyUnionType.union(ContainerUtil.map(bestCandidates, new Function<CandidateClass, PyType>() {
@Override
public PyType fun(CandidateClass cls) {
return new PyClassTypeImpl(cls.myClass, false);
}
})));
}
@NotNull
private Set<PyClass> suggestCandidateClasses(@NotNull final PyExpression expression, @NotNull Set<String> seenAttrs) {
final Set<PyClass> candidates = Sets.newHashSet();
for (String attribute : seenAttrs) {
// Search for some of these attributes like __init__ may produce thousands of candidates in average SDK
// and we probably don't want to confuse user with PyNames.FAKE_OLD_BASE anyway
if (COMMON_OBJECT_ATTRIBUTES.contains(attribute)) {
candidates.add(PyBuiltinCache.getInstance(expression).getClass(PyNames.OBJECT));
}
else {
final Collection<PyClass> declaringClasses = PyClassAttributesIndex.find(attribute, expression.getProject());
LOG.debug("Attribute " + attribute + " is declared in " + declaringClasses.size() + " classes");
candidates.addAll(declaringClasses);
}
}
final Set<PyClass> suitableClasses = Sets.newHashSet();
for (PyClass candidate : candidates) {
if (PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(candidate.getContainingFile())) {
continue;
}
if (getAllInheritedAttributeNames(candidate).containsAll(seenAttrs)) {
suitableClasses.add(candidate);
}
}
for (PyClass candidate : Lists.newArrayList(suitableClasses)) {
for (PyClass ancestor : getAncestorClassesFast(candidate)) {
if (suitableClasses.contains(ancestor)) {
suitableClasses.remove(candidate);
}
}
}
return Collections.unmodifiableSet(suitableClasses);
}
@NotNull
private static List<CandidateClass> prepareCandidates(@NotNull Set<PyClass> candidates, @NotNull final PyExpression expression) {
final Set<QualifiedName> importQualifiers = collectImportQualifiers(expression.getContainingFile());
final List<CandidateClass> prioritizedCandidates = ContainerUtil.map(candidates, new Function<PyClass, CandidateClass>() {
@Override
public CandidateClass fun(PyClass candidate) {
return new CandidateClass(candidate, findPriority(candidate, expression, importQualifiers));
}
});
Collections.sort(prioritizedCandidates);
final List<CandidateClass> result = Lists.newArrayList();
for (CandidateClass candidate : prioritizedCandidates) {
if (result.size() == MAX_CANDIDATES || candidate.myPriority.compareTo(Priority.PROJECT) >= 0) {
break;
}
result.add(candidate);
}
return Collections.unmodifiableList(result);
}
@NotNull
private static Priority findPriority(@NotNull PyClass candidate, @NotNull PyExpression expression,
@NotNull Set<QualifiedName> qualifiers) {
if (PyBuiltinCache.getInstance(expression).isBuiltin(candidate)) {
return Priority.BUILTIN;
}
if (PyUtil.inSameFile(candidate, expression)) {
return Priority.SAME_FILE;
}
final String qualifiedName = candidate.getQualifiedName();
if (qualifiedName != null) {
for (QualifiedName qualifier : qualifiers) {
if (QualifiedName.fromDottedString(qualifiedName).matchesPrefix(qualifier)) {
return Priority.IMPORTED;
}
}
}
if (ProjectScope.getProjectScope(candidate.getProject()).contains(candidate.getContainingFile().getVirtualFile())) {
return Priority.PROJECT;
}
return Priority.OTHER;
}
@VisibleForTesting
@NotNull
public static Set<QualifiedName> collectImportQualifiers(@NotNull PsiFile file) {
final Set<QualifiedName> result = Sets.newHashSet();
if (file instanceof PyFile) {
final PyFile originalModule = (PyFile)file;
for (PyFromImportStatement fromImport : originalModule.getFromImports()) {
if (fromImport.isFromFuture()) {
continue;
}
final PsiFileSystemItem importedModule = PyUtil.as(fromImport.resolveImportSource(), PsiFileSystemItem.class);
if (importedModule == null) {
continue;
}
final QualifiedName qName = findShortestImportableQName(file.getFirstChild(), importedModule.getVirtualFile());
if (qName == null) {
continue;
}
final PyImportElement[] importElements = fromImport.getImportElements();
if (fromImport.isStarImport() || importElements.length == 0) {
result.add(qName);
}
else {
result.addAll(ContainerUtil.map(importElements, new Function<PyImportElement, QualifiedName>() {
@Override
public QualifiedName fun(PyImportElement element) {
final QualifiedName name = element.getImportedQName();
return name != null ? qName.append(name) : qName;
}
}));
}
}
for (PyImportElement normalImport : originalModule.getImportTargets()) {
ContainerUtil.addIfNotNull(result, normalImport.getImportedQName());
}
}
return Collections.unmodifiableSet(result);
}
@NotNull
private Set<String> getAllInheritedAttributeNames(@NotNull PyClass candidate) {
final Set<String> availableAttrs = Sets.newHashSet(getAllDeclaredAttributeNames(candidate));
for (PyClass parent : getAncestorClassesFast(candidate)) {
availableAttrs.addAll(getAllDeclaredAttributeNames(parent));
}
return availableAttrs;
}
@VisibleForTesting
@NotNull
public static Set<String> collectUsedAttributes(@NotNull PyExpression element) {
final QualifiedName qualifiedName;
if (element instanceof PyQualifiedExpression) {
qualifiedName = ((PyQualifiedExpression)element).asQualifiedName();
}
else {
final String elementName = element.getName();
qualifiedName = elementName == null ? null : QualifiedName.fromDottedString(elementName);
}
if (qualifiedName == null) {
return Collections.emptySet();
}
return collectUsedAttributes(element, qualifiedName);
}
@NotNull
private static Set<String> collectUsedAttributes(@NotNull PyExpression anchor, @NotNull final QualifiedName qualifiedName) {
final Set<String> result = Sets.newHashSet();
final PsiReference reference = anchor.getReference();
final ScopeOwner definitionScope = ScopeUtil.getScopeOwner(reference != null ? reference.resolve() : anchor);
for (ScopeOwner scope = ScopeUtil.getScopeOwner(anchor); scope != null; scope = ScopeUtil.getScopeOwner(scope)) {
final ScopeOwner inspectedScope = scope;
scope.accept(new PyRecursiveElementVisitor() {
@Override
public void visitPyElement(PyElement node) {
if (node instanceof ScopeOwner && node != inspectedScope) {
return;
}
if (node instanceof PyQualifiedExpression) {
ContainerUtil.addIfNotNull(result, getAttributeOfQualifier(((PyQualifiedExpression)node), qualifiedName));
}
super.visitPyElement(node);
}
});
if (scope == definitionScope) {
break;
}
}
return result;
}
@Nullable
private static String getAttributeOfQualifier(@NotNull PyQualifiedExpression expression, @NotNull QualifiedName expectedQualifier) {
if (!expression.isQualified()) {
return null;
}
final QualifiedName qualifiedName = expression.asQualifiedName();
if (qualifiedName != null && qualifiedName.removeTail(1).equals(expectedQualifier)) {
return qualifiedName.getLastComponent();
}
return null;
}
/**
* Returns all attributes: methods, class and instance fields that are declared directly in the specified class
* (not taking inheritance into account).
* <p/>
* This method <b>must not</b> access the AST because it is being called during stub indexing.
*/
@NotNull
public static List<String> getAllDeclaredAttributeNames(@NotNull PyClass pyClass) {
final List<PsiNamedElement> members = ContainerUtil.<PsiNamedElement>concat(pyClass.getInstanceAttributes(),
pyClass.getClassAttributes(),
Arrays.asList(pyClass.getMethods(false)));
return ContainerUtil.mapNotNull(members, new Function<PsiNamedElement, String>() {
@Override
public String fun(PsiNamedElement expression) {
final String attrName = expression.getName();
return attrName != null ? attrName : null;
}
});
}
/**
* A simpler and faster alternative to {@link com.jetbrains.python.psi.impl.PyClassImpl#getAncestorClasses()}.
* The approach used here does not require proper MRO order of ancestors and its performance is greatly improved by reusing
* intermediate results in case of a large class hierarchy.
*/
@NotNull
private Set<PyClass> getAncestorClassesFast(@NotNull PyClass pyClass) {
final Set<PyClass> ancestors = myAncestorsCache.get(pyClass);
if (ancestors != null) {
return ancestors;
}
// Sentinel value to prevent infinite recursion
myAncestorsCache.put(pyClass, Collections.<PyClass>emptySet());
final Set<PyClass> result = Sets.newHashSet();
try {
for (final PyClassLikeType baseType : pyClass.getSuperClassTypes(myContext)) {
if (!(baseType instanceof PyClassType)) {
continue;
}
final PyClass baseClass = ((PyClassType)baseType).getPyClass();
result.add(baseClass);
result.addAll(getAncestorClassesFast(baseClass));
}
}
finally {
// May happen in case of cyclic inheritance
result.remove(pyClass);
myAncestorsCache.put(pyClass, Collections.unmodifiableSet(result));
}
return result;
}
private static class CandidateClass implements Comparable<CandidateClass> {
final PyClass myClass;
final Priority myPriority;
public CandidateClass(@NotNull PyClass cls, @NotNull Priority priority) {
myClass = cls;
myPriority = priority;
}
@Override
public int compareTo(@NotNull CandidateClass o) {
// Alphabetical tie-breaker for consistent results
//noinspection ConstantConditions
return ComparisonChain.start()
.compare(myPriority, o.myPriority)
.compare(myClass.getName(), o.myClass.getName())
.result();
}
@Override
public String toString() {
return String.format("ClassCandidate(name='%s' priority=%s)", myClass.getName(), myPriority);
}
}
enum Priority {
BUILTIN,
SAME_FILE,
IMPORTED,
PROJECT,
// TODO How to implement it?
DEPENDENCY,
OTHER
}
}
@@ -8,8 +8,8 @@ class MyClass(Base):
def other_method(self):
pass
x = undefined()
x.__init__()
print(x.name)
x.unique_method()
x.<caret>
def func(x):
x.__init__()
print(x.name)
x.unique_method()
x.<caret>
@@ -1,280 +0,0 @@
package com.jetbrains.python;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiElementFilter;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.documentation.PythonDocumentationProvider;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.psi.PyReferenceExpression;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeFromUsedAttributesHelper;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
/**
* @author Mikhail Golubev
*/
public class PyTypeFromUsedAttributesTest extends PyTestCase {
public void testCollectAttributeOfParameter() {
doTestUsedAttributes("def func(x):\n" +
" if x.baz():\n" +
" x.foo = x.bar",
"foo", "bar", "baz");
}
public void testCollectAttributesInOuterScopes() {
doTestUsedAttributes("x = undefined()" +
"x.quux\n" +
"def func2():\n" +
" x.bar\n" +
" def nested():\n" +
" x.baz",
"quux", "baz", "bar");
}
public void testIgnoreAttributesOfParameterInOtherFunctions() {
doTestUsedAttributes("def func1(x):\n" +
" x.foo\n" +
" \n" +
"def func2(x):\n" +
" x.bar",
"bar");
}
public void testCollectSpecialMethodNames() {
doTestUsedAttributes("x = undefined()\n" +
"x[0] = x[...]\n" +
"x + 42",
"__getitem__", "__setitem__", "__add__");
}
public void testOnlyBaseClassesRetained() {
doTestType("class Base(object):\n" +
" attr_a = None\n" +
" attr_b = None\n" +
"\n" +
"class C2(Base):\n" +
" pass\n" +
"\n" +
"class C3(Base):\n" +
" attr_a = None\n" +
"\n" +
"class C4(Base):\n" +
" attr_a = None\n" +
" attr_b = None\n" +
"\n" +
"x = undefined()\n" +
"x.attr_a\n" +
"x.attr_b",
"Base | unknown");
}
public void testDiamondHierarchyBottom() {
doTestType("class D(object):\n" +
" pass\n" +
"class B(D):\n" +
" pass\n" +
"class C(D):\n" +
" pass\n" +
"class A(B, C):\n" +
" foo = None\n" +
" bar = None\n" +
"\n" +
"def func(x):\n" +
" x.foo\n" +
" x.bar",
"A | unknown");
}
public void testDiamondHierarchySiblings() {
doTestType("class D(object):\n" +
" bar = None\n" +
"class B(D):\n" +
" foo = None\n" +
"class C(D):\n" +
" foo = None\n" +
" bar = None\n" +
"class A(B, C):\n" +
" foo = None\n" +
" bar = None\n" +
"\n" +
"def func(x):\n" +
" x.foo()\n" +
" x.bar()\n",
"B | C | unknown");
}
public void testDiamondHierarchyTop() {
doTestType("class D(object):\n" +
" foo = None\n" +
" bar = None\n" +
"\n" +
"class B(D):\n" +
" foo = None\n" +
" bar = None\n" +
"\n" +
"class C(D):\n" +
" foo = None\n" +
"\n" +
"class A(B, C):\n" +
" foo = None\n" +
" bar = None\n" +
"\n" +
"def func(x):\n" +
" x.foo()\n" +
" x.bar()",
"D | unknown");
}
public void testDiamondHierarchyLeft() {
doTestType("class D(object):\n" +
" foo = None\n" +
"class B(D):\n" +
" bar = None\n" +
"class C(D):\n" +
" pass\n" +
"class A(B, C):\n" +
" foo = None\n" +
" bar = None\n" +
"def func(x):\n" +
" x.foo()\n" +
" x.bar()",
"B | unknown");
}
public void testBuiltinTypes() {
doTestType("def func(x):\n" +
" x.upper()\n" +
" x.decode()",
"bytearray | str | unicode | unknown");
doTestType("def func(x):\n" +
" x.pop() and x.update()",
"dict | set | unknown");
}
public void testFunctionType() {
doTestType("class A:\n" +
" def method_a(self):\n" +
" pass\n" +
"class B:\n" +
" def method_b(self):\n" +
" pass\n" +
"def func(a, b):\n" +
" a.method_a\n" +
" b.method_b\n" +
" return a\n" +
"x = func\n" +
"x",
"(a: A | unknown, b: B | unknown) -> A | unknown");
}
public void testFastInferenceForObjectAttributes() {
doTestType("x = undefined()\n" +
"x.__init__(1)\n" +
"x",
"object | unknown");
}
public void testResultsOrdering() {
myFixture.copyDirectoryToProject(getTestName(true), "");
doTestType("import module\n" +
"class MySortable(object):\n" +
" def sort(self):\n" +
" pass\n" +
"def f(x):\n" +
" x.sort()",
"list | MySortable | OtherClassA | OtherClassB | unknown");
}
public void testCyclicInheritance() {
myFixture.copyDirectoryToProject(getTestName(true), "");
myFixture.configureByFile("main.py");
final PyReferenceExpression referenceExpression = findLastReferenceByText("x");
assertNotNull(referenceExpression);
final TypeEvalContext context =
TypeEvalContext.userInitiated(myFixture.getProject(), referenceExpression.getContainingFile()).withTracing();
final PyType actual = context.getType(referenceExpression);
final String actualType = PythonDocumentationProvider.getTypeName(actual, context);
assertEquals("B | unknown", actualType);
}
public void testLongInheritanceChain() {
// This obvious test is needed because of custom method for resolution of class ancestors
doTestType("class C1(object):\n" +
" attr = 'top'\n" +
"class C2(C1):\n" +
" pass\n" +
"class C3(C2):\n" +
" pass\n" +
"class C4(C3):\n" +
" pass\n" +
"class C5(C4):\n" +
" attr = 'bottom'\n" +
"def f(x):\n" +
" x.attr\n",
"C1 | unknown");
}
public void testImportQualifiers() {
myFixture.copyDirectoryToProject(getTestName(true), "");
myFixture.configureByFile("pkg1/pkg2/main.py");
final Set<QualifiedName> qualifiers = PyTypeFromUsedAttributesHelper.collectImportQualifiers(myFixture.getFile());
final Set<String> qualifiedNames = ContainerUtil.map2Set(qualifiers, new Function<QualifiedName, String>() {
@Override
public String fun(QualifiedName name) {
return name.toString();
}
});
assertSameElements(qualifiedNames,
"root",
"pkg1.pkg2.module2a",
"pkg1.module1a",
"pkg1.pkg2.module2b.C1",
"pkg1.module1b.B1",
"pkg1.pkg2.module2b",
"pkg1.pkg2");
}
private void doTestType(@NotNull String text, @NotNull String expectedType) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final PyReferenceExpression referenceExpression = findLastReferenceByText("x");
assertNotNull(referenceExpression);
final TypeEvalContext context = TypeEvalContext.userInitiated(myFixture.getProject(), referenceExpression.getContainingFile());
final PyType actual = context.getType(referenceExpression);
final String actualType = PythonDocumentationProvider.getTypeName(actual, context);
assertEquals(expectedType, actualType);
}
private void doTestUsedAttributes(@NotNull String text, @NotNull String... attributesExpected) {
myFixture.configureByText(PythonFileType.INSTANCE, text);
final PyReferenceExpression referenceExpression = findLastReferenceByText("x");
assertNotNull(referenceExpression);
assertSameElements(PyTypeFromUsedAttributesHelper.collectUsedAttributes(referenceExpression), attributesExpected);
}
@Nullable
private PyReferenceExpression findLastReferenceByText(@NotNull final String text) {
final PsiElement[] elements = PsiTreeUtil.collectElements(myFixture.getFile(), new PsiElementFilter() {
@Override
public boolean isAccepted(PsiElement element) {
return element instanceof PyReferenceExpression && element.getText().equals(text);
}
});
return (PyReferenceExpression)ArrayUtil.getLastElement(elements);
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/typesFromAttributes/";
}
}
@@ -16,6 +16,7 @@
package com.jetbrains.python;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
import com.intellij.codeInsight.lookup.Lookup;
@@ -29,6 +30,7 @@ import com.jetbrains.python.psi.LanguageLevel;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
public class PythonCompletionTest extends PyTestCase {
@@ -61,6 +63,13 @@ public class PythonCompletionTest extends PyTestCase {
return myFixture.getLookupElementStrings();
}
@Nullable
private List<String> doTestSmartByFile() {
myFixture.configureByFile("completion/" + getTestName(true) + ".py");
myFixture.complete(CompletionType.SMART);
return myFixture.getLookupElementStrings();
}
public void testLocalVar() {
doTest();
}
@@ -702,15 +711,15 @@ public class PythonCompletionTest extends PyTestCase {
assertDoesntContain(suggested, PyNames.METHOD_SPECIAL_ATTRIBUTES);
}
public void testFromUsedMethodsOfString() {
final List<String> suggested = doTestByFile();
public void testSmartFromUsedMethodsOfString() {
final List<String> suggested = doTestSmartByFile();
assertNotNull(suggested);
// append comes from bytearray
assertContainsElements(suggested, "lower", "capitalize", "join", "append");
// Remove duplicates for assertContainsElements(), "append" comes from bytearray
assertContainsElements(new HashSet<String>(suggested), "lower", "capitalize", "join", "append");
}
public void testFromUsedAttributesOfClass() {
final List<String> suggested = doTestByFile();
public void testSmartFromUsedAttributesOfClass() {
final List<String> suggested = doTestSmartByFile();
assertNotNull(suggested);
assertContainsElements(suggested, "other_method", "name", "unique_method");
}