summaryrefslogtreecommitdiff
path: root/python/src/com/jetbrains/python/psi/types/PyFunctionType.java
diff options
context:
space:
mode:
Diffstat (limited to 'python/src/com/jetbrains/python/psi/types/PyFunctionType.java')
-rw-r--r--python/src/com/jetbrains/python/psi/types/PyFunctionType.java76
1 files changed, 70 insertions, 6 deletions
diff --git a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java
index 4b3adfd112f8..4bdbae69d83d 100644
--- a/python/src/com/jetbrains/python/psi/types/PyFunctionType.java
+++ b/python/src/com/jetbrains/python/psi/types/PyFunctionType.java
@@ -18,10 +18,12 @@ package com.jetbrains.python.psi.types;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
+import com.intellij.util.containers.ContainerUtil;
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.QualifiedResolveResult;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,6 +32,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
+import static com.jetbrains.python.psi.PyUtil.as;
+
/**
* Type of a particular function that is represented as a {@link Callable} in the PSI tree.
*
@@ -74,20 +79,79 @@ public class PyFunctionType implements PyCallableType {
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
- final PyClassTypeImpl functionType = PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
- if (functionType == null) {
+ final PyClassType delegate = selectFakeType(location, resolveContext.getTypeEvalContext());
+ if (delegate == null) {
return Collections.emptyList();
}
- return functionType.resolveMember(name, location, direction, resolveContext);
+ return delegate.resolveMember(name, location, direction, resolveContext);
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
- final PyClassTypeImpl functionType = PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
- if (functionType == null) {
+ final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(location.getContainingFile());
+ final PyClassType delegate;
+ if (location instanceof PyReferenceExpression) {
+ delegate = selectFakeType(((PyReferenceExpression)location).getQualifier(), typeEvalContext);
+ }
+ else {
+ delegate = PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
+ }
+ if (delegate == null) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
- return functionType.getCompletionVariants(completionPrefix, location, context);
+ return delegate.getCompletionVariants(completionPrefix, location, context);
+ }
+
+ /**
+ * Select either {@link PyNames#FAKE_FUNCTION} or {@link PyNames#FAKE_METHOD} fake class depending on concrete reference used and
+ * language level. Will fallback to fake function type.
+ */
+ @Nullable
+ private PyClassTypeImpl selectFakeType(@Nullable PyExpression location, @NotNull TypeEvalContext context) {
+ if (location instanceof PyReferenceExpression && isBoundMethodReference(((PyReferenceExpression)location), context)) {
+ return PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_METHOD);
+ }
+ return PyBuiltinCache.getInstance(getCallable()).getObjectType(PyNames.FAKE_FUNCTION);
+ }
+
+ private boolean isBoundMethodReference(@NotNull PyReferenceExpression location, @NotNull TypeEvalContext context) {
+ final PyFunction function = as(getCallable(), PyFunction.class);
+ final boolean isNonStaticMethod = function != null && function.getContainingClass() != null && function.getModifier() != STATICMETHOD;
+ if (isNonStaticMethod) {
+ // In Python 2 unbound methods have __method fake type
+ if (LanguageLevel.forElement(location).isOlderThan(LanguageLevel.PYTHON30)) {
+ return true;
+ }
+ final PyExpression qualifier;
+ if (location.isQualified()) {
+ qualifier = location.getQualifier();
+ }
+ else {
+ final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
+ final QualifiedResolveResult resolveResult = location.followAssignmentsChain(resolveContext);
+ final List<PyExpression> qualifiers = resolveResult.getQualifiers();
+ qualifier = ContainerUtil.isEmpty(qualifiers) ? null : qualifiers.get(qualifiers.size() - 1);
+ }
+ if (qualifier != null) {
+ //noinspection ConstantConditions
+ final PyType qualifierType = PyTypeChecker.toNonWeakType(context.getType(qualifier), context);
+ if (isInstanceType(qualifierType)) {
+ return true;
+ }
+ else if (qualifierType instanceof PyUnionType) {
+ for (PyType type : ((PyUnionType)qualifierType).getMembers()) {
+ if (isInstanceType(type)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private static boolean isInstanceType(@Nullable PyType type) {
+ return type instanceof PyClassType && !((PyClassType)type).isDefinition();
}
@Override