mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Remove unused PyTypeFromUsedAttributesHelper and its tests
This commit is contained in:
+9
-3
@@ -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
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user