blob: d44c848049ba379308dd936cadfd9c03238b4c7d [file] [log] [blame]
/*
* 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.impl;
import com.intellij.codeInsight.completion.CompletionUtil;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.FunctionParameter;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.nameResolver.FQNamesProvider;
import com.jetbrains.python.nameResolver.NameResolverTools;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.toolbox.Maybe;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Functions common to different implementors of PyCallExpression, with different base classes.
* User: dcheryasov
* Date: Dec 23, 2008 10:31:38 AM
*/
public class PyCallExpressionHelper {
private PyCallExpressionHelper() {
// none
}
/**
* TODO: Copy/Paste with {@link com.jetbrains.python.psi.PyArgumentList#addArgument(com.jetbrains.python.psi.PyExpression)}
* Adds an argument to the end of argument list.
*
* @param us the arg list
* @param expression what to add
*/
public static void addArgument(PyCallExpression us, PyExpression expression) {
PyExpression[] arguments = us.getArgumentList().getArguments();
final PyExpression last_arg = arguments.length == 0 ? null : arguments[arguments.length - 1];
PyElementGenerator.getInstance(us.getProject()).insertItemIntoList(us, last_arg, expression);
}
/**
* Tries to interpret a call as a call to built-in {@code classmethod} or {@code staticmethod}.
*
* @param redefiningCall the possible call, generally a result of chasing a chain of assignments
* @param us any in-project PSI element, used to determine SDK and ultimately builtins module used to check the wrapping functions
* @return a pair of wrapper name and wrapped function; for {@code staticmethod(foo)} it would be ("staticmethod", foo).
*/
@Nullable
public static Pair<String, PyFunction> interpretAsModifierWrappingCall(PyCallExpression redefiningCall, PsiElement us) {
PyExpression redefining_callee = redefiningCall.getCallee();
if (redefiningCall.isCalleeText(PyNames.CLASSMETHOD, PyNames.STATICMETHOD)) {
final PyReferenceExpression refex = (PyReferenceExpression)redefining_callee;
final String refname = refex.getReferencedName();
if ((PyNames.CLASSMETHOD.equals(refname) || PyNames.STATICMETHOD.equals(refname))) {
PsiElement redefining_func = refex.getReference().resolve();
if (redefining_func != null) {
PsiElement true_func = PyBuiltinCache.getInstance(us).getByName(refname);
if (true_func instanceof PyClass) true_func = ((PyClass)true_func).findInitOrNew(true);
if (true_func == redefining_func) {
// yes, really a case of "foo = classmethod(foo)"
PyArgumentList arglist = redefiningCall.getArgumentList();
if (arglist != null) { // really can't be any other way
PyExpression[] args = arglist.getArguments();
if (args.length == 1) {
PyExpression possible_original_ref = args[0];
if (possible_original_ref instanceof PyReferenceExpression) {
PsiElement original = ((PyReferenceExpression)possible_original_ref).getReference().resolve();
if (original instanceof PyFunction) {
// pinned down the original; replace our resolved callee with it and add flags.
return Pair.create(refname, (PyFunction)original);
}
}
}
}
}
}
}
}
return null;
}
@Nullable
public static PyClass resolveCalleeClass(PyCallExpression us) {
PyExpression callee = us.getCallee();
PsiElement resolved;
QualifiedResolveResult resolveResult = null;
if (callee instanceof PyReferenceExpression) {
// dereference
PyReferenceExpression ref = (PyReferenceExpression)callee;
resolveResult = ref.followAssignmentsChain(PyResolveContext.noImplicits());
resolved = resolveResult.getElement();
}
else {
resolved = callee;
}
// analyze
if (resolved instanceof PyClass) {
return (PyClass)resolved;
}
else if (resolved instanceof PyFunction) {
final PyFunction pyFunction = (PyFunction)resolved;
return pyFunction.getContainingClass();
}
return null;
}
@Nullable
public static Callable resolveCalleeFunction(PyCallExpression call, PyResolveContext resolveContext) {
PsiElement resolved;
PyExpression callee = call.getCallee();
if (callee instanceof PyReferenceExpression) {
// dereference
PyReferenceExpression ref = (PyReferenceExpression)callee;
QualifiedResolveResult resolveResult = ref.followAssignmentsChain(resolveContext);
resolved = resolveResult.getElement();
}
else {
resolved = callee;
}
if (resolved instanceof PyClass) {
resolved = ((PyClass)resolved).findInitOrNew(true); // class to constructor call
}
else if (resolved instanceof PyCallExpression) {
PyCallExpression redefiningCall = (PyCallExpression)resolved;
Pair<String, PyFunction> wrapperInfo = interpretAsModifierWrappingCall(redefiningCall, call);
if (wrapperInfo != null) {
resolved = wrapperInfo.getSecond();
}
}
if (resolved instanceof Callable) {
return (Callable)resolved;
}
return null;
}
@Nullable
public static PyCallExpression.PyMarkedCallee resolveCallee(PyCallExpression us, PyResolveContext resolveContext) {
return resolveCallee(us, resolveContext, 0);
}
@Nullable
public static PyCallExpression.PyMarkedCallee resolveCallee(PyCallExpression us, PyResolveContext resolveContext, int implicitOffset) {
PyFunction.Modifier wrappedModifier = null;
boolean isConstructorCall = false;
PyExpression callee = us.getCallee();
PsiElement resolved;
QualifiedResolveResult resolveResult = null;
if (callee instanceof PyReferenceExpression) {
// dereference
PyReferenceExpression ref = (PyReferenceExpression)callee;
resolveResult = ref.followAssignmentsChain(resolveContext);
resolved = resolveResult.getElement();
}
else {
resolved = callee;
}
// analyze
if (resolved instanceof PyClass) {
resolved = ((PyClass)resolved).findInitOrNew(true); // class to constructor call
isConstructorCall = true;
}
else if (resolved instanceof PyCallExpression) {
// is it a case of "foo = classmethod(foo)"?
PyCallExpression redefiningCall = (PyCallExpression)resolved;
Pair<String, PyFunction> wrapperInfo = interpretAsModifierWrappingCall(redefiningCall, us);
if (wrapperInfo != null) {
resolved = wrapperInfo.getSecond();
String wrapper_name = wrapperInfo.getFirst();
if (PyNames.CLASSMETHOD.equals(wrapper_name)) {
wrappedModifier = PyFunction.Modifier.CLASSMETHOD;
}
else if (PyNames.STATICMETHOD.equals(wrapper_name)) wrappedModifier = PyFunction.Modifier.STATICMETHOD;
}
}
final List<PyExpression> qualifiers = resolveResult != null ? resolveResult.getQualifiers() : Collections.<PyExpression>emptyList();
final TypeEvalContext context = resolveContext.getTypeEvalContext();
if (resolved instanceof PyFunction) {
final PyFunction function = (PyFunction)resolved;
final Property property = function.getProperty();
if (property != null && isQualifiedByInstance(function, qualifiers, context)) {
final PyType type = context.getReturnType(function);
if (type instanceof PyFunctionType) {
resolved = ((PyFunctionType)type).getCallable();
}
else {
resolved = null;
}
}
}
if (resolved instanceof Callable) {
PyFunction.Modifier modifier = resolved instanceof PyFunction
? ((PyFunction)resolved).getModifier()
: null;
if (modifier == null && wrappedModifier != null) {
modifier = wrappedModifier;
}
boolean isByInstance = isConstructorCall || isQualifiedByInstance((Callable)resolved, qualifiers, context)
|| resolved instanceof PyBoundFunction;
PyExpression lastQualifier = qualifiers != null && qualifiers.isEmpty() ? null : qualifiers.get(qualifiers.size() - 1);
boolean isByClass = lastQualifier == null ? false : isQualifiedByClass((Callable)resolved, lastQualifier, context);
final Callable callable = (Callable)resolved;
implicitOffset += getImplicitArgumentCount(callable, modifier, isConstructorCall, isByInstance, isByClass);
implicitOffset = implicitOffset < 0 ? 0 : implicitOffset; // wrong source can trigger strange behaviour
return new PyCallExpression.PyMarkedCallee(callable, modifier, implicitOffset,
resolveResult != null ? resolveResult.isImplicit() : false);
}
return null;
}
/**
* Calls the {@link #getImplicitArgumentCount(PyExpression, Callable, com.jetbrains.python.psi.PyFunction.Modifier, EnumSet< com.jetbrains.python.psi.PyFunction.Modifier >, boolean) full version}
* with null flags and with isByInstance inferred directly from call site (won't work with reassigned bound methods).
*
* @param callReference the call site, where arguments are given.
* @param functionBeingCalled resolved method which is being called; plain functions are OK but make little sense.
* @return a non-negative number of parameters that are implicit to this call.
*/
public static int getImplicitArgumentCount(
@NotNull final PyReferenceExpression callReference,
@NotNull PyFunction functionBeingCalled) {
//return getImplicitArgumentCount(functionBeingCalled, null, null, qualifierIsAnInstance(callReference, TypeEvalContext.fast()));
final PyDecorator decorator = PsiTreeUtil.getParentOfType(callReference, PyDecorator.class);
if (decorator != null && PsiTreeUtil.isAncestor(decorator.getCallee(), callReference, false)) {
return 1;
}
final PyResolveContext resolveContext = PyResolveContext.noImplicits();
QualifiedResolveResult followed = callReference.followAssignmentsChain(resolveContext);
boolean isByInstance = isQualifiedByInstance(functionBeingCalled, followed.getQualifiers(), resolveContext.getTypeEvalContext());
boolean isByClass = isQualifiedByInstance(functionBeingCalled, followed.getQualifiers(), resolveContext.getTypeEvalContext());
return getImplicitArgumentCount(functionBeingCalled, functionBeingCalled.getModifier(), false, isByInstance, isByClass);
}
/**
* Finds how many arguments are implicit in a given call.
*
* @param callable resolved method which is being called; non-methods immediately return 0.
* @param flags set of flags for the call
* @param isByInstance true if the call is known to be by instance (not by class).
* @return a non-negative number of parameters that are implicit to this call. E.g. for a typical method call 1 is returned
* because one parameter ('self') is implicit.
*/
private static int getImplicitArgumentCount(
Callable callable,
PyFunction.Modifier modifier,
boolean isConstructorCall,
boolean isByInstance,
boolean isByClass
) {
int implicit_offset = 0;
boolean firstIsArgsOrKwargs = false;
final PyParameter[] parameters = callable.getParameterList().getParameters();
if (parameters.length > 0) {
final PyParameter first = parameters[0];
final PyNamedParameter named = first.getAsNamed();
if (named != null && (named.isPositionalContainer() || named.isKeywordContainer())) {
firstIsArgsOrKwargs = true;
}
}
if (!firstIsArgsOrKwargs && (isByInstance || isConstructorCall)) {
implicit_offset += 1;
}
PyFunction method = callable.asMethod();
if (method == null) return implicit_offset;
if (PyNames.NEW.equals(method.getName())) {
return isConstructorCall ? 1 : 0;
}
if (!isByInstance && !isByClass && PyNames.INIT.equals(method.getName())) {
return 1;
}
// decorators?
if (modifier == PyFunction.Modifier.STATICMETHOD) {
if (isByInstance && implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self'
}
else if (modifier == PyFunction.Modifier.CLASSMETHOD) {
if (!isByInstance) implicit_offset += 1; // Both Foo.method() and foo.method() have implicit the first arg
}
return implicit_offset;
}
private static boolean isQualifiedByInstance(Callable resolved, List<PyExpression> qualifiers, TypeEvalContext context) {
PyDocStringOwner owner = PsiTreeUtil.getStubOrPsiParentOfType(resolved, PyDocStringOwner.class);
if (!(owner instanceof PyClass)) {
return false;
}
// true = call by instance
if (qualifiers.isEmpty()) {
return true; // unqualified + method = implicit constructor call
}
for (PyExpression qualifier : qualifiers) {
if (isQualifiedByInstance(resolved, qualifier, context)) {
return true;
}
}
return false;
}
private static boolean isQualifiedByInstance(Callable resolved, PyExpression qualifier, TypeEvalContext context) {
if (isQualifiedByClass(resolved, qualifier, context)) {
return false;
}
PyType qtype = context.getType(qualifier);
if (qtype != null) {
// TODO: handle UnionType
if (qtype instanceof PyModuleType) return false; // qualified by module, not instance.
}
return true; // NOTE. best guess: unknown qualifier is more probably an instance.
}
private static boolean isQualifiedByClass(Callable resolved, PyExpression qualifier, TypeEvalContext context) {
PyType qtype = context.getType(qualifier);
if (qtype instanceof PyClassType) {
if (((PyClassType)qtype).isDefinition()) {
PyClass resolvedParent = PsiTreeUtil.getStubOrPsiParentOfType(resolved, PyClass.class);
if (resolvedParent != null) {
final PyClass qualifierClass = ((PyClassType)qtype).getPyClass();
if ((qualifierClass.isSubclass(resolvedParent) || resolvedParent.isSubclass(qualifierClass))) {
return true;
}
}
}
}
return false;
}
static boolean isCalleeText(PyCallExpression pyCallExpression, String[] nameCandidates) {
final PyExpression callee = pyCallExpression.getCallee();
if (!(callee instanceof PyReferenceExpression)) {
return false;
}
for (String name : nameCandidates) {
if (name.equals(((PyReferenceExpression)callee).getReferencedName())) {
return true;
}
}
return false;
}
/**
* Returns argument if it exists and has appropriate type
* @param parameter argument
* @param argClass expected class
* @param expression call expression
* @param <T> expected class
* @return argument expression or null if has wrong type of does not exist
*/
@Nullable
public static <T extends PsiElement> T getArgument(
@NotNull final FunctionParameter parameter,
@NotNull final Class<T> argClass,
@NotNull final PyCallExpression expression) {
final PyArgumentList list = expression.getArgumentList();
if (list == null) {
return null;
}
return PyUtil.as(list.getValueExpressionForParam(parameter), argClass);
}
@Nullable
public static PyExpression getKeywordArgument(PyCallExpression expr, String keyword) {
for (PyExpression arg : expr.getArguments()) {
if (arg instanceof PyKeywordArgument) {
PyKeywordArgument kwarg = (PyKeywordArgument)arg;
if (keyword.equals(kwarg.getKeyword())) {
return kwarg.getValueExpression();
}
}
}
return null;
}
public static PyType getCallType(@NotNull PyCallExpression call, @NotNull TypeEvalContext context) {
if (!TypeEvalStack.mayEvaluate(call)) {
return null;
}
try {
PyExpression callee = call.getCallee();
if (callee instanceof PyReferenceExpression) {
// hardwired special cases
if (PyNames.SUPER.equals(callee.getText())) {
final Maybe<PyType> superCallType = getSuperCallType(call, context);
if (superCallType.isDefined()) {
return superCallType.value();
}
}
if ("type".equals(callee.getText())) {
final PyExpression[] args = call.getArguments();
if (args.length == 1) {
final PyExpression arg = args[0];
final PyType argType = context.getType(arg);
if (argType instanceof PyClassType) {
final PyClassType classType = (PyClassType)argType;
if (!classType.isDefinition()) {
final PyClass cls = classType.getPyClass();
return context.getType(cls);
}
}
else {
return null;
}
}
}
// normal cases
final PyResolveContext resolveContext = PyResolveContext.noImplicits().withTypeEvalContext(context);
ResolveResult[] targets = ((PyReferenceExpression)callee).getReference(resolveContext).multiResolve(false);
if (targets.length > 0) {
PsiElement target = targets[0].getElement();
if (target == null) {
return null;
}
PyClass cls = null;
PyFunction init = null;
if (target instanceof PyClass) {
cls = (PyClass)target;
init = cls.findInitOrNew(true);
}
else if (target instanceof PyFunction) {
final PyFunction f = (PyFunction)target;
if (PyNames.INIT.equals(f.getName())) {
init = f;
cls = f.getContainingClass();
}
}
if (init != null) {
final PyType t = init.getCallType(context, call);
if (cls != null) {
if (init.getContainingClass() != cls) {
if (t instanceof PyCollectionType) {
final PyType elementType = ((PyCollectionType)t).getElementType(context);
return new PyCollectionTypeImpl(cls, false, elementType);
}
return new PyClassTypeImpl(cls, false);
}
}
if (t != null && !(t instanceof PyNoneType)) {
return t;
}
if (cls != null && t == null) {
final PyFunction newMethod = cls.findMethodByName(PyNames.NEW, true);
if (newMethod != null && !PyBuiltinCache.getInstance(call).isBuiltin(newMethod)) {
return PyUnionType.createWeakType(new PyClassTypeImpl(cls, false));
}
}
}
if (cls != null) {
return new PyClassTypeImpl(cls, false);
}
final PyType providedType = PyReferenceExpressionImpl.getReferenceTypeFromProviders(target, context, call);
if (providedType instanceof PyCallableType) {
return ((PyCallableType)providedType).getCallType(context, call);
}
if (target instanceof Callable) {
final Callable callable = (Callable)target;
return callable.getCallType(context, call);
}
}
}
if (callee == null) {
return null;
}
else {
final PyType type = context.getType(callee);
if (type instanceof PyCallableType) {
final PyCallableType callableType = (PyCallableType)type;
return callableType.getCallType(context, call);
}
return null;
}
}
finally {
TypeEvalStack.evaluated(call);
}
}
@NotNull
private static Maybe<PyType> getSuperCallType(@NotNull PyCallExpression call, TypeEvalContext context) {
final PyExpression callee = call.getCallee();
if (callee instanceof PyReferenceExpression) {
PsiElement must_be_super_init = ((PyReferenceExpression)callee).getReference().resolve();
if (must_be_super_init instanceof PyFunction) {
PyClass must_be_super = ((PyFunction)must_be_super_init).getContainingClass();
if (must_be_super == PyBuiltinCache.getInstance(call).getClass(PyNames.SUPER)) {
PyArgumentList arglist = call.getArgumentList();
if (arglist != null) {
final PyClass containingClass = PsiTreeUtil.getParentOfType(call, PyClass.class);
PyExpression[] args = arglist.getArguments();
if (args.length > 1) {
PyExpression first_arg = args[0];
if (first_arg instanceof PyReferenceExpression) {
final PyReferenceExpression firstArgRef = (PyReferenceExpression)first_arg;
final PyExpression qualifier = firstArgRef.getQualifier();
if (qualifier != null && PyNames.__CLASS__.equals(firstArgRef.getReferencedName())) {
final PsiReference qRef = qualifier.getReference();
final PsiElement element = qRef == null ? null : qRef.resolve();
if (element instanceof PyParameter) {
final PyParameterList parameterList = PsiTreeUtil.getParentOfType(element, PyParameterList.class);
if (parameterList != null && element == parameterList.getParameters()[0]) {
return new Maybe<PyType>(getSuperCallTypeForArguments(context, containingClass, args[1]));
}
}
}
PsiElement possible_class = firstArgRef.getReference().resolve();
if (possible_class instanceof PyClass && ((PyClass)possible_class).isNewStyleClass()) {
final PyClass first_class = (PyClass)possible_class;
return new Maybe<PyType>(getSuperCallTypeForArguments(context, first_class, args[1]));
}
}
}
else if ((call.getContainingFile() instanceof PyFile) &&
((PyFile)call.getContainingFile()).getLanguageLevel().isPy3K() &&
(containingClass != null)) {
return new Maybe<PyType>(getSuperClassUnionType(containingClass));
}
}
}
}
}
return new Maybe<PyType>();
}
@Nullable
private static PyType getSuperCallTypeForArguments(TypeEvalContext context, PyClass firstClass, PyExpression second_arg) {
// check 2nd argument, too; it should be an instance
if (second_arg != null) {
PyType second_type = context.getType(second_arg);
if (second_type instanceof PyClassType) {
// imitate isinstance(second_arg, possible_class)
PyClass secondClass = ((PyClassType)second_type).getPyClass();
if (CompletionUtil.getOriginalOrSelf(firstClass) == secondClass) {
return getSuperClassUnionType(firstClass);
}
if (secondClass.isSubclass(firstClass)) {
final Iterator<PyClass> iterator = firstClass.getAncestorClasses(context).iterator();
if (iterator.hasNext()) {
return new PyClassTypeImpl(iterator.next(), false); // super(Foo, self) has type of Foo, modulo __get__()
}
}
}
}
return null;
}
@Nullable
private static PyType getSuperClassUnionType(@NotNull PyClass pyClass) {
// TODO: this is closer to being correct than simply taking first superclass type but still not entirely correct;
// super can also delegate to sibling types
// TODO handle __mro__ here
final PyClass[] supers = pyClass.getSuperClasses();
if (supers.length > 0) {
if (supers.length == 1) {
return new PyClassTypeImpl(supers[0], false);
}
List<PyType> superTypes = new ArrayList<PyType>();
for (PyClass aSuper : supers) {
superTypes.add(new PyClassTypeImpl(aSuper, false));
}
return PyUnionType.union(superTypes);
}
return null;
}
/**
* Checks if expression callee's name matches one of names, provided by appropriate {@link com.jetbrains.python.nameResolver.FQNamesProvider}
*
* @param expression call expression
* @param namesProviders name providers to check name against
* @return true if matches
* @see com.jetbrains.python.nameResolver
*/
public static boolean isCallee(@NotNull final PyCallExpression expression, @NotNull final FQNamesProvider... namesProviders) {
final PyExpression callee = expression.getCallee();
return (callee != null) && NameResolverTools.isName(callee, namesProviders);
}
}