Merge branch 'python-structural-types'

This commit is contained in:
Andrey Vlasovskikh
2014-12-16 17:26:11 +03:00
30 changed files with 554 additions and 93 deletions
@@ -472,7 +472,7 @@ public class PyNames {
}
public static boolean isRightOperatorName(@Nullable String name) {
return name != null && name.matches("__r[a-z]+__");
return name != null && (name.matches("__r[a-z]+__") || CONTAINS.equals(name));
}
/**
@@ -30,14 +30,17 @@ import org.jetbrains.annotations.Nullable;
class TypeEvalConstraints {
final boolean myAllowDataFlow;
final boolean myAllowStubToAST;
final boolean myAllowCallContext;
@Nullable final PsiFile myOrigin;
/**
* @see com.jetbrains.python.psi.types.TypeEvalContext
*/
TypeEvalConstraints(final boolean allowDataFlow, final boolean allowStubToAST, @Nullable final PsiFile origin) {
TypeEvalConstraints(final boolean allowDataFlow, final boolean allowStubToAST, final boolean allowCallContext,
@Nullable final PsiFile origin) {
myAllowDataFlow = allowDataFlow;
myAllowStubToAST = allowStubToAST;
myAllowCallContext = allowCallContext;
myOrigin = origin;
}
@@ -50,6 +53,7 @@ class TypeEvalConstraints {
if (myAllowDataFlow != that.myAllowDataFlow) return false;
if (myAllowStubToAST != that.myAllowStubToAST) return false;
if (myAllowCallContext != that.myAllowCallContext) return false;
if (myOrigin != null ? !myOrigin.equals(that.myOrigin) : that.myOrigin != null) return false;
return true;
@@ -60,11 +64,12 @@ class TypeEvalConstraints {
int result = (myAllowDataFlow ? 1 : 0);
result = 31 * result + (myAllowStubToAST ? 1 : 0);
result = 31 * result + (myOrigin != null ? myOrigin.hashCode() : 0);
result = 31 * result + (myAllowCallContext ? 1 : 0);
return result;
}
@Override
public String toString() {
return String.format("TypeEvalConstraints(%b, %b, %s)", myAllowDataFlow, myAllowStubToAST, myOrigin);
return String.format("TypeEvalConstraints(%b, %b, %b, %s)", myAllowDataFlow, myAllowStubToAST, myAllowCallContext, myOrigin);
}
}
@@ -65,8 +65,8 @@ public class TypeEvalContext {
}
};
private TypeEvalContext(boolean allowDataFlow, boolean allowStubToAST, @Nullable PsiFile origin) {
myConstraints = new TypeEvalConstraints(allowDataFlow, allowStubToAST, origin);
private TypeEvalContext(boolean allowDataFlow, boolean allowStubToAST, boolean allowCallContext, @Nullable PsiFile origin) {
myConstraints = new TypeEvalConstraints(allowDataFlow, allowStubToAST, allowCallContext, origin);
}
@Override
@@ -83,17 +83,29 @@ public class TypeEvalContext {
return myConstraints.myAllowDataFlow || element.getContainingFile() == myConstraints.myOrigin;
}
public boolean allowLocalUsages(@NotNull PsiElement element) {
return myConstraints.myAllowStubToAST && myConstraints.myAllowDataFlow && element.getContainingFile() == myConstraints.myOrigin;
public boolean allowCallContext(@NotNull PsiElement element) {
return myConstraints.myAllowCallContext && element.getContainingFile() == myConstraints.myOrigin;
}
/**
* Create a context for code completion.
* <p/>
* It is as detailed as {@link TypeEvalContext#userInitiated(Project, PsiFile)}, but allows inferring types based on the context in which
* the analyzed code was called or may be called. Since this is basically guesswork, the results should be used only for code completion.
*/
public static TypeEvalContext codeCompletion(@NotNull final Project project, @Nullable final PsiFile origin) {
return CACHE.getContext(project, new TypeEvalContext(true, true, true, origin));
}
/**
* Create the most detailed type evaluation context for user-initiated actions.
* <p/>
* Should be used for code completion, go to definition, find usages, refactorings, documentation.
* Should be used go to definition, find usages, refactorings, documentation.
* <p/>
* For code completion see {@link TypeEvalContext#codeCompletion(Project, PsiFile)}.
*/
public static TypeEvalContext userInitiated(@NotNull final Project project, @Nullable final PsiFile origin) {
return CACHE.getContext(project, new TypeEvalContext(true, true, origin));
return CACHE.getContext(project, new TypeEvalContext(true, true, false, origin));
}
/**
@@ -103,20 +115,18 @@ public class TypeEvalContext {
* Inspections should not create a new type evaluation context. They should re-use the context of the inspection session.
*/
public static TypeEvalContext codeAnalysis(@NotNull final Project project, @Nullable final PsiFile origin) {
return CACHE.getContext(project, new TypeEvalContext(false, false, origin));
return CACHE.getContext(project, new TypeEvalContext(false, false, false, origin));
}
/**
* Create the most shallow type evaluation context for code insight purposes when other more detailed contexts are not available.
* It's use should be minimized.
* <p/>
* <p/>
*
* @param project pass project here to enable cache. Pass null if you do not have any project.
* <strong>Always</strong> do your best to pass project here: it increases performance!
*/
public static TypeEvalContext codeInsightFallback(@Nullable final Project project) {
final TypeEvalContext anchor = new TypeEvalContext(false, false, null);
final TypeEvalContext anchor = new TypeEvalContext(false, false, false, null);
if (project != null) {
return CACHE.getContext(project, anchor);
}
@@ -129,7 +139,7 @@ public class TypeEvalContext {
* Should be used only when normal code insight context is not enough for getting good results.
*/
public static TypeEvalContext deepCodeInsight(@NotNull final Project project) {
return CACHE.getContext(project, new TypeEvalContext(false, true, null));
return CACHE.getContext(project, new TypeEvalContext(false, true, false, null));
}
public TypeEvalContext withTracing() {
@@ -129,7 +129,7 @@ public class PyDictKeyNamesCompletionContributor extends CompletionContributor {
if (callee == null) return;
final String name = callee.getText();
if ("dict".equals(name)) {
final TypeEvalContext context = TypeEvalContext.userInitiated(callee.getProject(), callee.getContainingFile());
final TypeEvalContext context = TypeEvalContext.codeCompletion(callee.getProject(), callee.getContainingFile());
final PyType type = context.getType(dictConstructor);
if (type != null && type.isBuiltin()) {
final PyArgumentList list = dictConstructor.getArgumentList();
@@ -15,13 +15,16 @@
*/
package com.jetbrains.python.inspections;
import com.google.common.collect.Sets;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.util.Function;
import com.intellij.util.containers.hash.LinkedHashMap;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.documentation.PythonDocumentationProvider;
@@ -32,6 +35,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Set;
/**
* @author vlan
@@ -108,26 +112,45 @@ public class PyTypeCheckerInspection extends PyInspection {
}
@Nullable
private String checkTypes(@Nullable PyType superType, @Nullable PyType subType, @Nullable PsiElement node,
private String checkTypes(@Nullable PyType expected, @Nullable PyType actual, @Nullable PsiElement node,
@NotNull TypeEvalContext context, @NotNull Map<PyGenericType, PyType> substitutions) {
if (subType != null && superType != null) {
if (!PyTypeChecker.match(superType, subType, context, substitutions)) {
final String superName = PythonDocumentationProvider.getTypeName(superType, context);
String expected = String.format("'%s'", superName);
final boolean hasGenerics = PyTypeChecker.hasGenerics(superType, context);
if (actual != null && expected != null) {
if (!PyTypeChecker.match(expected, actual, context, substitutions)) {
final String expectedName = PythonDocumentationProvider.getTypeName(expected, context);
String quotedExpectedName = String.format("'%s'", expectedName);
final boolean hasGenerics = PyTypeChecker.hasGenerics(expected, context);
ProblemHighlightType highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
if (hasGenerics) {
final PyType subst = PyTypeChecker.substitute(superType, substitutions, context);
if (subst != null) {
expected = String.format("'%s' (matched generic type '%s')",
PythonDocumentationProvider.getTypeName(subst, context),
superName);
final PyType substitute = PyTypeChecker.substitute(expected, substitutions, context);
if (substitute != null) {
quotedExpectedName = String.format("'%s' (matched generic type '%s')",
PythonDocumentationProvider.getTypeName(substitute, context),
expectedName);
highlightType = ProblemHighlightType.WEAK_WARNING;
}
}
final String msg = String.format("Expected type %s, got '%s' instead",
expected,
PythonDocumentationProvider.getTypeName(subType, context));
final String actualName = PythonDocumentationProvider.getTypeName(actual, context);
String msg= String.format("Expected type %s, got '%s' instead", quotedExpectedName, actualName);
if (expected instanceof PyStructuralType) {
final Set<String> expectedAttributes = ((PyStructuralType)expected).getAttributeNames();
final Set<String> actualAttributes = getAttributes(actual);
if (actualAttributes != null) {
final Sets.SetView<String> missingAttributes = Sets.difference(expectedAttributes, actualAttributes);
if (missingAttributes.size() == 1) {
msg = String.format("Type '%s' doesn't have expected attribute '%s'", actualName, missingAttributes.iterator().next());
}
else {
msg = String.format("Type '%s' doesn't have expected attributes %s",
actualName,
StringUtil.join(missingAttributes, new Function<String, String>() {
@Override
public String fun(String s) {
return String.format("'%s'", s);
}
}, ", "));
}
}
}
registerProblem(node, msg, highlightType);
return msg;
}
@@ -136,6 +159,17 @@ public class PyTypeCheckerInspection extends PyInspection {
}
}
@Nullable
private static Set<String> getAttributes(@NotNull PyType type) {
if (type instanceof PyStructuralType) {
return ((PyStructuralType)type).getAttributeNames();
}
else if (type instanceof PyClassType) {
return PyTypeChecker.getClassAttributes(((PyClassType)type).getPyClass(), true);
}
return null;
}
@Override
public void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder problemsHolder) {
if (LOG.isDebugEnabled()) {
@@ -64,7 +64,6 @@ import com.jetbrains.python.psi.impl.references.PyOperatorReference;
import com.jetbrains.python.psi.resolve.ImportedResolveResult;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher;
@@ -705,6 +704,9 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
// this almost always means that we don't know the type, so don't show an error in this case
return true;
}
if (type instanceof PyStructuralType && ((PyStructuralType)type).isInferredFromUsages()) {
return true;
}
if (type instanceof PyImportedModuleType) {
PyImportedModule module = ((PyImportedModuleType)type).getImportedModule();
if (module.resolve() == null) {
@@ -720,7 +722,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
}
if (type instanceof PyClassTypeImpl) {
PyClass cls = ((PyClassType)type).getPyClass();
if (overridesGetAttr(cls, myTypeEvalContext)) {
if (PyTypeChecker.overridesGetAttr(cls, myTypeEvalContext)) {
return true;
}
if (cls.findProperty(name, true) != null) {
@@ -910,31 +912,6 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
return callExpression != null && node == callExpression.getCallee();
}
@Nullable
private static PsiElement resolveClassMember(@NotNull PyClass cls, @NotNull String name, @NotNull TypeEvalContext context) {
final PyType type = context.getType(cls);
if (type != null) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final List<? extends RatedResolveResult> results = type.resolveMember(name, null, AccessDirection.READ, resolveContext);
if (results != null && !results.isEmpty()) {
return results.get(0).getElement();
}
}
return null;
}
private static boolean overridesGetAttr(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
PsiElement method = resolveClassMember(cls, PyNames.GETATTR, context);
if (method != null) {
return true;
}
method = resolveClassMember(cls, PyNames.GETATTRIBUTE, context);
if (method != null && !PyBuiltinCache.getInstance(cls).isBuiltin(method)) {
return true;
}
return false;
}
private static void addPluginQuickFixes(PsiReference reference, final List<LocalQuickFix> actions) {
for (PyUnresolvedReferenceQuickFixProvider provider : Extensions.getExtensions(PyUnresolvedReferenceQuickFixProvider.EP_NAME)) {
provider.registerQuickFixes(reference, new Consumer<LocalQuickFix>() {
@@ -42,9 +42,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* @author yole
@@ -246,8 +244,8 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
}
}
}
// Guess the type from file-local usages
if (context.allowLocalUsages(this)) {
// Guess the type from file-local calls
if (context.allowCallContext(this)) {
final List<PyType> types = new ArrayList<PyType>();
processLocalCalls(func, new Processor<PyCallExpression>() {
@Override
@@ -276,6 +274,12 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
return PyUnionType.createWeakType(PyUnionType.union(types));
}
}
if (context.maySwitchToAST(this)) {
final Set<String> attributes = collectUsedAttributes(context);
if (!attributes.isEmpty()) {
return new PyStructuralType(attributes, true);
}
}
}
}
return null;
@@ -286,6 +290,86 @@ public class PyNamedParameterImpl extends PyBaseElementImpl<PyNamedParameterStub
return new PyElementPresentation(this);
}
@NotNull
private Set<String> collectUsedAttributes(@NotNull final TypeEvalContext context) {
final Set<String> result = new LinkedHashSet<String>();
final ScopeOwner owner = ScopeUtil.getScopeOwner(this);
final String name = getName();
if (owner != null && name != null) {
owner.accept(new PyRecursiveElementVisitor() {
@Override
public void visitPyElement(PyElement node) {
if (node instanceof ScopeOwner && node != owner) {
return;
}
if (node instanceof PyQualifiedExpression) {
final PyQualifiedExpression expr = (PyQualifiedExpression)node;
final PyExpression qualifier = expr.getQualifier();
if (qualifier != null) {
final String attributeName = expr.getReferencedName();
final PyExpression referencedExpr = node instanceof PyBinaryExpression && PyNames.isRightOperatorName(attributeName) ?
((PyBinaryExpression)node).getRightExpression() : qualifier;
if (referencedExpr != null) {
final PsiReference ref = referencedExpr.getReference();
if (ref != null && ref.isReferenceTo(PyNamedParameterImpl.this)) {
if (attributeName != null && !result.contains(attributeName)) {
result.add(attributeName);
}
}
}
}
else {
final PsiReference ref = expr.getReference();
if (ref != null && ref.isReferenceTo(PyNamedParameterImpl.this)) {
final PyNamedParameter parameter = getParameterByCallArgument(expr, context);
if (parameter != null) {
final PyType type = context.getType(parameter);
if (type instanceof PyStructuralType) {
result.addAll(((PyStructuralType)type).getAttributeNames());
}
}
}
}
}
super.visitPyElement(node);
}
@Override
public void visitPyIfStatement(PyIfStatement node) {
final PyExpression ifCondition = node.getIfPart().getCondition();
if (ifCondition != null) {
ifCondition.accept(this);
}
for (PyIfPart part : node.getElifParts()) {
final PyExpression elseIfCondition = part.getCondition();
if (elseIfCondition != null) {
elseIfCondition.accept(this);
}
}
}
});
}
return result;
}
@Nullable
private static PyNamedParameter getParameterByCallArgument(@NotNull PsiElement element, @NotNull TypeEvalContext context) {
final PyCallExpression call = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
if (call != null) {
final PyArgumentList argumentList = call.getArgumentList();
if (argumentList != null) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final CallArgumentsMapping mapping = argumentList.analyzeCall(resolveContext);
for (Map.Entry<PyExpression, PyNamedParameter> entry : mapping.getPlainMappedParams().entrySet()) {
if (entry.getKey() == element) {
return entry.getValue();
}
}
}
}
return null;
}
private static void processLocalCalls(@NotNull PyFunction function, @NotNull Processor<PyCallExpression> processor) {
final PsiFile file = function.getContainingFile();
final String name = function.getName();
@@ -66,7 +66,7 @@ public class KeywordArgumentCompletionUtil {
visited.add(def);
boolean needSelf = def.getContainingClass() != null && def.getModifier() != PyFunction.Modifier.STATICMETHOD;
final KwArgParameterCollector collector = new KwArgParameterCollector(needSelf, ret);
final TypeEvalContext context = TypeEvalContext.userInitiated(def.getProject(), def.getContainingFile());
final TypeEvalContext context = TypeEvalContext.codeCompletion(def.getProject(), def.getContainingFile());
final List<PyParameter> parameters = PyUtil.getParameters(def, context);
for (PyParameter parameter : parameters) {
parameter.accept(collector);
@@ -97,7 +97,8 @@ public class PyImportReference extends PyReferenceImpl {
}
PyExpression qualifier = myElement.getQualifier();
final TypeEvalContext context = TypeEvalContext.userInitiated(myElement.getProject(), CompletionUtil.getOriginalOrSelf(myElement).getContainingFile());
final TypeEvalContext context = TypeEvalContext.codeCompletion(myElement.getProject(),
CompletionUtil.getOriginalOrSelf(myElement).getContainingFile());
if (qualifier != null) {
// qualifier's type must be module, it should know how to complete
PyType type = context.getType(qualifier);
@@ -43,7 +43,10 @@ import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyImportedModule;
import com.jetbrains.python.psi.impl.ResolveResultList;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.resolve.ImplicitResolveResult;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import com.jetbrains.python.psi.search.PyProjectScopeBuilder;
import com.jetbrains.python.psi.stubs.PyClassNameIndexInsensitive;
import com.jetbrains.python.psi.stubs.PyFunctionNameIndex;
@@ -102,7 +105,8 @@ public class PyQualifiedReference extends PyReferenceImpl {
}
}
if (PyTypeChecker.isUnknown(qualifierType) &&
if ((PyTypeChecker.isUnknown(qualifierType) ||
(qualifierType instanceof PyStructuralType && ((PyStructuralType)qualifierType).isInferredFromUsages())) &&
myContext.allowImplicits() && canQualifyAnImplicitName(qualifier, qualifierType)) {
addImplicitResolveResults(referencedName, ret);
}
@@ -260,13 +264,19 @@ public class PyQualifiedReference extends PyReferenceImpl {
}
final PyQualifiedExpression element = CompletionUtil.getOriginalOrSelf(myElement);
PyType qualifierType = TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).getType(qualifier);
PyType qualifierType = TypeEvalContext.codeCompletion(element.getProject(), element.getContainingFile()).getType(qualifier);
ProcessingContext ctx = new ProcessingContext();
final Set<String> namesAlready = new HashSet<String>();
ctx.put(PyType.CTX_NAMES, namesAlready);
final Collection<Object> variants = new ArrayList<Object>();
if (qualifierType != null) {
Collection<Object> variants = new ArrayList<Object>();
Collections.addAll(variants, getVariantFromHasAttr(qualifier));
if (qualifierType instanceof PyStructuralType && ((PyStructuralType)qualifierType).isInferredFromUsages()) {
final PyClassType guessedType = guessClassTypeByName();
if (guessedType != null) {
Collections.addAll(variants, getTypeCompletionVariants(myElement, guessedType));
}
}
if (qualifier instanceof PyQualifiedExpression) {
Collection<PyExpression> attrs = collectAssignedAttributes((PyQualifiedExpression)qualifier);
for (PyExpression ex : attrs) {
@@ -295,7 +305,16 @@ public class PyQualifiedReference extends PyReferenceImpl {
return qualifierType.getCompletionVariants(element.getName(), element, ctx);
}
}
return getUntypedVariants();
else {
final PyClassType guessedType = guessClassTypeByName();
if (guessedType != null) {
Collections.addAll(variants, getTypeCompletionVariants(myElement, guessedType));
}
if (qualifier instanceof PyReferenceExpression) {
Collections.addAll(variants, collectSeenMembers(qualifier.getText()));
}
return variants.toArray();
}
}
private Object[] getVariantFromHasAttr(PyExpression qualifier) {
@@ -315,7 +334,8 @@ public class PyQualifiedReference extends PyReferenceImpl {
return variants.toArray();
}
private Object[] getUntypedVariants() {
@Nullable
private PyClassType guessClassTypeByName() {
final PyExpression qualifierElement = myElement.getQualifier();
if (qualifierElement instanceof PyReferenceExpression) {
PyReferenceExpression qualifier = (PyReferenceExpression)qualifierElement;
@@ -324,13 +344,11 @@ public class PyQualifiedReference extends PyReferenceImpl {
Collection<PyClass> classes = PyClassNameIndexInsensitive.find(className, getElement().getProject());
classes = filterByImports(classes, myElement.getContainingFile());
if (classes.size() == 1) {
final PyClassType classType = new PyClassTypeImpl(classes.iterator().next(), false);
return getTypeCompletionVariants(myElement, classType);
return new PyClassTypeImpl(classes.iterator().next(), false);
}
}
return collectSeenMembers(qualifier.getText());
}
return ArrayUtil.EMPTY_OBJECT_ARRAY;
return null;
}
private static Collection<PyClass> filterByImports(Collection<PyClass> classes, PsiFile containingFile) {
@@ -358,6 +376,16 @@ public class PyQualifiedReference extends PyReferenceImpl {
@Override
public void visitPyReferenceExpression(PyReferenceExpression node) {
super.visitPyReferenceExpression(node);
visitPyQualifiedExpression(node);
}
@Override
public void visitPyTargetExpression(PyTargetExpression node) {
super.visitPyTargetExpression(node);
visitPyQualifiedExpression(node);
}
private void visitPyQualifiedExpression(PyQualifiedExpression node) {
if (node != myElement) {
final PyExpression qualifier = node.getQualifier();
if (qualifier != null && qualifier.getText().equals(text)) {
@@ -373,7 +401,7 @@ public class PyQualifiedReference extends PyReferenceImpl {
for (String member : members) {
results.add(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(LookupElementBuilder.create(member)));
}
return results.toArray(new Object[results.size()]);
return ArrayUtil.toObjectArray(results);
}
private static Collection<PyExpression> collectAssignedAttributes(PyQualifiedExpression qualifier) {
@@ -423,7 +451,8 @@ public class PyQualifiedReference extends PyReferenceImpl {
final PyExpression qualifier = myElement.getQualifier();
if (qualifier != null) {
final PyType qualifierType = resolveContext.getTypeEvalContext().getType(qualifier);
if (qualifierType == null) {
if (qualifierType == null ||
(qualifierType instanceof PyStructuralType && ((PyStructuralType)qualifierType).isInferredFromUsages())) {
return true;
}
}
@@ -68,7 +68,7 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
!isSingleArgDecoratorCall(myContext, (PyFunction)object)) {
final Project project = ((PyFunction)object).getProject();
item = item.withInsertHandler(PyFunctionInsertHandler.INSTANCE);
final TypeEvalContext context = TypeEvalContext.userInitiated(project, myContext != null ? myContext.getContainingFile() : null);
final TypeEvalContext context = TypeEvalContext.codeCompletion(project, myContext != null ? myContext.getContainingFile() : null);
final List<PyParameter> parameters = PyUtil.getParameters((PyFunction)object, context);
final String params = StringUtil.join(parameters, new Function<PyParameter, String>() {
@Override
@@ -84,6 +84,10 @@ public class PyABCUtil {
}
public static boolean isSubtype(@NotNull PyType type, @NotNull String superClassName, @NotNull TypeEvalContext context) {
if (type instanceof PyStructuralType) {
// TODO: Convert abc types to structural types and check them properly
return true;
}
if (type instanceof PyClassType) {
final PyClassType classType = (PyClassType)type;
final PyClass pyClass = classType.getPyClass();
@@ -418,7 +418,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
CompletionUtil.getOriginalOrSelf(location)
.getContainingFile() :
null;
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myClass.getProject(), origin);
final TypeEvalContext typeEvalContext = TypeEvalContext.codeCompletion(myClass.getProject(), origin);
addInheritedMembers(prefix, location, namesAlready, context, ret, typeEvalContext);
// from providers
@@ -88,7 +88,7 @@ public class PyFunctionType implements PyCallableType {
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(location.getProject(), location.getContainingFile());
final TypeEvalContext typeEvalContext = TypeEvalContext.codeCompletion(location.getProject(), location.getContainingFile());
final PyClassType delegate;
if (location instanceof PyReferenceExpression) {
delegate = selectFakeType(((PyReferenceExpression)location).getQualifier(), typeEvalContext);
@@ -0,0 +1,91 @@
/*
* Copyright 2000-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.psi.types;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author vlan
*/
public class PyStructuralType implements PyType {
@NotNull private final Set<String> myAttributes;
private final boolean myInferredFromUsages;
public PyStructuralType(@NotNull Set<String> attributes, boolean inferredFromUsages) {
myAttributes = attributes;
myInferredFromUsages = inferredFromUsages;
}
@Nullable
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
return Collections.emptyList();
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
final List<Object> variants = new ArrayList<Object>();
for (String attribute : myAttributes) {
if (!attribute.equals(completionPrefix)) {
variants.add(LookupElementBuilder.create(attribute).withIcon(PlatformIcons.FIELD_ICON));
}
}
return variants.toArray();
}
@Nullable
@Override
public String getName() {
return "{" + StringUtil.join(myAttributes, ", ") + "}";
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public void assertValid(String message) {
}
@Override
public String toString() {
return "PyStructuralType(" + StringUtil.join(myAttributes, ", ") + ")";
}
public boolean isInferredFromUsages() {
return myInferredFromUsages;
}
public Set<String> getAttributeNames() {
return myAttributes;
}
}
@@ -21,6 +21,7 @@ import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
@@ -154,6 +155,29 @@ public class PyTypeChecker {
return true;
}
}
if (actual instanceof PyStructuralType && ((PyStructuralType)actual).isInferredFromUsages()) {
return true;
}
if (expected instanceof PyStructuralType && actual instanceof PyStructuralType) {
final PyStructuralType expectedStructural = (PyStructuralType)expected;
final PyStructuralType actualStructural = (PyStructuralType)actual;
if (expectedStructural.isInferredFromUsages()) {
return true;
}
return expectedStructural.getAttributeNames().containsAll(actualStructural.getAttributeNames());
}
if (expected instanceof PyStructuralType && actual instanceof PyClassType) {
final PyClass cls = ((PyClassType)actual).getPyClass();
if (overridesGetAttr(cls, context)) {
return true;
}
final Set<String> actualAttributes = getClassAttributes(cls, true);
return actualAttributes.containsAll(((PyStructuralType)expected).getAttributeNames());
}
if (actual instanceof PyStructuralType && expected instanceof PyClassType) {
final Set<String> expectedAttributes = getClassAttributes(((PyClassType)expected).getPyClass(), true);
return expectedAttributes.containsAll(((PyStructuralType)actual).getAttributeNames());
}
if (actual instanceof PyCallableType && expected instanceof PyCallableType) {
final PyCallableType expectedCallable = (PyCallableType)expected;
final PyCallableType actualCallable = (PyCallableType)actual;
@@ -180,6 +204,26 @@ public class PyTypeChecker {
return matchNumericTypes(expected, actual);
}
@NotNull
public static Set<String> getClassAttributes(@NotNull PyClass cls, boolean inherited) {
final Set<String> attributes = new HashSet<String>();
for (PyFunction function : cls.getMethods(false)) {
attributes.add(function.getName());
}
for (PyTargetExpression instanceAttribute : cls.getInstanceAttributes()) {
attributes.add(instanceAttribute.getName());
}
for (PyTargetExpression classAttribute : cls.getClassAttributes()) {
attributes.add(classAttribute.getName());
}
if (inherited) {
for (PyClass ancestor : cls.getAncestorClasses()) {
attributes.addAll(getClassAttributes(ancestor, false));
}
}
return attributes;
}
private static boolean matchNumericTypes(PyType expected, PyType actual) {
final String superName = expected.getName();
final String subName = actual.getName();
@@ -434,7 +478,7 @@ public class PyTypeChecker {
}
final Callable callable = ((PyFunctionType)type).getCallable();
final String operatorName = typedElement.getName();
final boolean isRight = PyNames.isRightOperatorName(operatorName) || PyNames.CONTAINS.equals(operatorName);
final boolean isRight = PyNames.isRightOperatorName(operatorName);
final PyExpression arg = isRight ? expr.getLeftExpression() : expr.getRightExpression();
final PyExpression receiver = isRight ? expr.getRightExpression() : expr.getLeftExpression();
final PyParameter[] parameters = callable.getParameterList().getParameters();
@@ -521,9 +565,37 @@ public class PyTypeChecker {
else if (type instanceof PyCallableType) {
return ((PyCallableType) type).isCallable();
}
else if (type instanceof PyStructuralType && ((PyStructuralType)type).isInferredFromUsages()) {
return true;
}
return false;
}
public static boolean overridesGetAttr(@NotNull PyClass cls, @NotNull TypeEvalContext context) {
PsiElement method = resolveClassMember(cls, PyNames.GETATTR, context);
if (method != null) {
return true;
}
method = resolveClassMember(cls, PyNames.GETATTRIBUTE, context);
if (method != null && !PyBuiltinCache.getInstance(cls).isBuiltin(method)) {
return true;
}
return false;
}
@Nullable
private static PsiElement resolveClassMember(@NotNull PyClass cls, @NotNull String name, @NotNull TypeEvalContext context) {
final PyType type = context.getType(cls);
if (type != null) {
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
final List<? extends RatedResolveResult> results = type.resolveMember(name, null, AccessDirection.READ, resolveContext);
if (results != null && !results.isEmpty()) {
return results.get(0).getElement();
}
}
return null;
}
public static class AnalyzeCallResults {
@NotNull private final Callable myCallable;
@Nullable private final PyExpression myReceiver;
+3 -5
View File
@@ -1,5 +1,3 @@
def foo(element):
print(element.children)
def bar(element):
element.ch<caret>
for region in regions:
region.children = []
region.ch<caret>
@@ -0,0 +1,4 @@
def f(x):
x.foo
x.bar
x.bar
@@ -0,0 +1,4 @@
def f(x):
x.foo
x.bar
x.b<caret>
@@ -0,0 +1,3 @@
def f(x):
x.foo + x.bar
x(0)
@@ -0,0 +1,19 @@
def f(x):
"""
:type x: str
"""
pass
def g(x):
return x.lower()
def test(x, y):
x.upper()
f(x)
g(x)
y.foo()
f(y)
g(y)
@@ -0,0 +1,16 @@
def f(x):
return x.foo + x.bar()
def g(x):
return x.lower()
def test(x):
x.foo
f(x)
g(x)
z = 'foo'
f(<warning descr="Type 'str' doesn't have expected attributes 'foo', 'bar'">z</warning>)
g(z)
@@ -0,0 +1,21 @@
def f(x):
return x.foo
class C(object):
def __getattribute__(self, item):
pass
class D(object):
def __getattr__(self, item):
pass
class E(object):
pass
f(C())
f(D())
f(<warning descr="Type 'E' doesn't have expected attribute 'foo'">E()</warning>)
@@ -0,0 +1,8 @@
def f(xs):
ys = 'string'
for x in xs:
g(ys)
def g(x):
return x.lower()
@@ -0,0 +1,10 @@
def f(x):
return x.foo + g(x)
def g(x):
return x.bar
def test():
f(<warning descr="Type 'str' doesn't have expected attributes 'foo', 'bar'">'string'</warning>)
@@ -1,4 +1,4 @@
def test():
def test(c):
def f1(c):
if c < 0:
return []
@@ -15,7 +15,7 @@ def test():
"""
:type x: int
"""
x1 = f1(42)
x1 = f1(c)
f2(x1) # Weaker union types
f3(<warning descr="Expected type 'int', got 'list | str | None' instead">x1</warning>)
@@ -589,13 +589,14 @@ public class PyTypeTest extends PyTestCase {
}
public void testParameterFromUsages() {
doTest("int | str | unknown",
"def foo(bar):\n" +
" expr = bar\n" +
"def use_foo(x):\n" +
" foo(x)\n" +
" foo(3)\n" +
" foo('bar')\n");
final String text = "def foo(bar):\n" +
" expr = bar\n" +
"def use_foo(x):\n" +
" foo(x)\n" +
" foo(3)\n" +
" foo('bar')\n";
final PyExpression expr = parseExpr(text);
doTest("int | str | unknown", expr, TypeEvalContext.codeCompletion(expr.getProject(), expr.getContainingFile()));
}
public void testUpperBoundGeneric() {
@@ -960,6 +961,42 @@ public class PyTypeTest extends PyTestCase {
" expr = x\n");
}
public void testStructuralType() {
doTest("{foo, bar}",
"def f(x):\n" +
" x.foo + x.bar()\n" +
" expr = x\n");
}
public void testOnlyRelatedNestedAttributes() {
doTest("{foo}",
"def g(x):\n" +
" x.bar\n" +
"\n" +
"def f(x, y):\n" +
" x.foo + g(y)\n" +
" expr = x\n");
}
public void testNoContainsInContainsArgumentForStructuralType() {
doTest("{foo, __getitem__}",
"def f(x):\n" +
" x in []\n" +
" x.foo\n" +
" x[0]" +
" expr = x\n");
}
public void testStructuralTypeAndIsInstanceChecks() {
doTest("(x: {foo}) -> None",
"def f(x):\n" +
" if isinstance(x, str):\n" +
" x.lower()\n" +
" x.foo\n" +
"\n" +
"expr = f\n");
}
private static TypeEvalContext getTypeEvalContext(@NotNull PyExpression element) {
return TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing();
}
@@ -969,6 +1006,12 @@ public class PyTypeTest extends PyTestCase {
return myFixture.findElementByText("expr", PyExpression.class);
}
private static void doTest(final String expectedType, final PyExpression expr, final TypeEvalContext context) {
PyType actual = context.getType(expr);
final String actualType = PythonDocumentationProvider.getTypeName(actual, context);
assertEquals(expectedType, actualType);
}
private void doTest(final String expectedType, final String text) {
PyExpression expr = parseExpr(text);
TypeEvalContext context = getTypeEvalContext(expr);
@@ -753,4 +753,8 @@ public class PythonCompletionTest extends PyTestCase {
assertNotNull(variants);
assertEmpty(variants);
}
public void testStructuralType() {
doTest();
}
}
@@ -99,6 +99,10 @@ public class PyCallingNonCallableInspectionTest extends PyTestCase {
doTest();
}
public void testStructuralType() {
doTest();
}
private void doTest() {
setLanguageLevel(LanguageLevel.PYTHON27);
try {
@@ -247,4 +247,24 @@ public class PyTypeCheckerInspectionTest extends PyTestCase {
public void testContainsArguments() {
doTest();
}
public void testExpectedStructuralType() {
doTest();
}
public void testActualStructuralType() {
doTest();
}
public void testStructuralTypesForNestedCalls() {
doTest();
}
public void testIterateOverParamWithNoAttributes() {
doTest();
}
public void testGetAttributeAgainstStructuralType() {
doTest();
}
}