Remove unused PyTypeFromUsedAttributesHelper and its tests
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 14 Jan 2015 11:46:33 +0000 (14:46 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 14 Jan 2015 13:37:18 +0000 (16:37 +0300)
python/src/com/jetbrains/python/codeInsight/completion/PyStructuralTypeAttributesCompletionContributor.java
python/src/com/jetbrains/python/psi/impl/stubs/PyClassElementType.java
python/src/com/jetbrains/python/psi/stubs/PyClassAttributesIndex.java
python/src/com/jetbrains/python/psi/types/PyTypeFromUsedAttributesHelper.java [deleted file]
python/testData/completion/smartFromUsedAttributesOfClass.py [moved from python/testData/completion/fromUsedAttributesOfClass.py with 64% similarity]
python/testData/completion/smartFromUsedMethodsOfString.py [moved from python/testData/completion/fromUsedMethodsOfString.py with 100% similarity]
python/testSrc/com/jetbrains/python/PyTypeFromUsedAttributesTest.java [deleted file]
python/testSrc/com/jetbrains/python/PythonCompletionTest.java

index dc7f90f1330319d12c39594f93e40f40affa5318..19baad2fc6b50b8e9b5350a7ff5e1feeb4723a69 100644 (file)
@@ -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;
     }
index 850c22310372e933420247d377e293f428052271..54b2f8ecfce4238909e67dbc7bad6d89404c36b3 100644 (file)
@@ -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()) {
index b6831d91c973ddd16d9074b0f5cedddc4ead97c5..d371ecc7b4427f34077d1c5ab8124176672ab073 100644 (file)
@@ -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;
+      }
+    });
+  }
 }
diff --git a/python/src/com/jetbrains/python/psi/types/PyTypeFromUsedAttributesHelper.java b/python/src/com/jetbrains/python/psi/types/PyTypeFromUsedAttributesHelper.java
deleted file mode 100644 (file)
index 97fb75b..0000000
+++ /dev/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
-  }
-}
similarity index 64%
rename from python/testData/completion/fromUsedAttributesOfClass.py
rename to python/testData/completion/smartFromUsedAttributesOfClass.py
index 3fc0d1036e78754b0fa6c27029a75e6d15c479f4..7918948ab87f5e654e6df8c68ed4bf5db179991a 100644 (file)
@@ -8,8 +8,8 @@ class MyClass(Base):
       def other_method(self):
           pass
 
-x = undefined()
-x.__init__()
-print(x.name)
-x.unique_method()
-x.<caret>
\ No newline at end of file
+def func(x):
+    x.__init__()
+    print(x.name)
+    x.unique_method()
+    x.<caret>
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/PyTypeFromUsedAttributesTest.java b/python/testSrc/com/jetbrains/python/PyTypeFromUsedAttributesTest.java
deleted file mode 100644 (file)
index cdfffec..0000000
+++ /dev/null
@@ -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/";
-  }
-}
index f5cf1ef01d7636bc5d42cae92cc70ad19cf5827b..1d7181fa9a1bb964449f6cd4aba5379daee91c44 100644 (file)
@@ -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");
   }