blob: fe3633449b7427afeeb77c418f6f77dc673578a8 [file] [log] [blame]
/*
* Copyright 2000-2013 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.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;
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.
*
* @author vlan
*/
public class PyFunctionType implements PyCallableType {
@NotNull private final Callable myCallable;
public PyFunctionType(@NotNull Callable callable) {
myCallable = callable;
}
@Override
public boolean isCallable() {
return true;
}
@Nullable
@Override
public PyType getReturnType(@NotNull TypeEvalContext context) {
return context.getReturnType(myCallable);
}
@Nullable
@Override
public PyType getCallType(@NotNull TypeEvalContext context, @NotNull PyCallSiteExpression callSite) {
return myCallable.getCallType(context, callSite);
}
@Nullable
@Override
public List<PyCallableParameter> getParameters(@NotNull TypeEvalContext context) {
final List<PyCallableParameter> result = new ArrayList<PyCallableParameter>();
for (PyParameter parameter : myCallable.getParameterList().getParameters()) {
result.add(new PyCallableParameterImpl(parameter));
}
return result;
}
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
final PyClassType delegate = selectFakeType(location, resolveContext.getTypeEvalContext());
if (delegate == null) {
return Collections.emptyList();
}
return delegate.resolveMember(name, location, direction, resolveContext);
}
@Override
public Object[] getCompletionVariants(String completionPrefix, PsiElement location, ProcessingContext context) {
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 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) {
final String fakeClassName;
if (location instanceof PyReferenceExpression && isBoundMethodReference((PyReferenceExpression)location, context)) {
fakeClassName = PyNames.FAKE_METHOD;
}
else {
fakeClassName = PyNames.FAKE_FUNCTION;
}
return PyBuiltinCache.getInstance(getCallable()).getObjectType(fakeClassName);
}
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
public String getName() {
return "function";
}
@Override
public boolean isBuiltin() {
return false;
}
@Override
public void assertValid(String message) {
}
@NotNull
public Callable getCallable() {
return myCallable;
}
@Nullable
public static String getParameterName(@NotNull PyNamedParameter namedParameter) {
String name = namedParameter.getName();
if (namedParameter.isPositionalContainer()) {
name = "*" + name;
}
else if (namedParameter.isKeywordContainer()) {
name = "**" + name;
}
return name;
}
}