blob: 34a92e16bf014430c54d6ba4f468577d3be9cef5 [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.codeInsight.completion.CompletionUtil;
import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiInvalidElementAccessException;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
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.codeInsight.PyDynamicMember;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.ResolveResultList;
import com.jetbrains.python.psi.resolve.*;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import com.jetbrains.python.toolbox.Maybe;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
@NotNull protected final PyClass myClass;
protected final boolean myIsDefinition;
private static ThreadLocal<Set<Pair<PyClass, String>>> ourResolveMemberStack = new ThreadLocal<Set<Pair<PyClass, String>>>() {
@Override
protected Set<Pair<PyClass, String>> initialValue() {
return new HashSet<Pair<PyClass, String>>();
}
};
/**
* Describes a class-based type. Since everything in Python is an instance of some class, this type pretty much completes
* the type system :)
* Note that classes' and instances' member list can change during execution, so it is important to construct an instance of PyClassType
* right in the place of reference, so that such changes could possibly be accounted for.
*
* @param source PyClass which defines this type. For builtin or external classes, skeleton files contain the definitions.
* @param isDefinition whether this type describes an instance or a definition of the class.
*/
public PyClassTypeImpl(@NotNull PyClass source, boolean isDefinition) {
PyClass originalElement = CompletionUtil.getOriginalElement(source);
myClass = originalElement != null ? originalElement : source;
myIsDefinition = isDefinition;
}
public <T> PyClassTypeImpl withUserData(Key<T> key, T value) {
putUserData(key, value);
return this;
}
/**
* @return a PyClass which defined this type.
*/
@Override
@NotNull
public PyClass getPyClass() {
return myClass;
}
/**
* @return whether this type refers to an instance or a definition of the class.
*/
@Override
public boolean isDefinition() {
return myIsDefinition;
}
@Override
public PyClassType toInstance() {
return myIsDefinition ? new PyClassTypeImpl(myClass, false) : this;
}
@Override
@Nullable
public String getClassQName() {
return myClass.getQualifiedName();
}
@NotNull
@Override
public List<PyClassLikeType> getSuperClassTypes(@NotNull TypeEvalContext context) {
return myClass.getSuperClassTypes(context);
}
@Nullable
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull final String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext) {
return resolveMember(name, location, direction, resolveContext, true);
}
@Nullable
@Override
public List<? extends RatedResolveResult> resolveMember(@NotNull final String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext,
boolean inherited) {
final Set<Pair<PyClass, String>> resolving = ourResolveMemberStack.get();
final Pair<PyClass, String> key = Pair.create(myClass, name);
if (resolving.contains(key)) {
return Collections.emptyList();
}
resolving.add(key);
try {
return doResolveMember(name, location, direction, resolveContext, inherited);
}
finally {
resolving.remove(key);
}
}
@Nullable
private List<? extends RatedResolveResult> doResolveMember(@NotNull String name,
@Nullable PyExpression location,
@NotNull AccessDirection direction,
@NotNull PyResolveContext resolveContext,
boolean inherited) {
final TypeEvalContext context = resolveContext.getTypeEvalContext();
PsiElement classMember = resolveByOverridingMembersProviders(this, name, location); //overriding members provers have priority to normal resolve
if (classMember != null) {
return ResolveResultList.to(classMember);
}
if (resolveContext.allowProperties()) {
final Ref<ResolveResultList> resultRef = findProperty(name, direction, true);
if (resultRef != null) {
return resultRef.get();
}
}
if ("super".equals(getClassQName()) && isBuiltin() && location instanceof PyCallExpression) {
// methods of super() call are not of class super!
PyExpression first_arg = ((PyCallExpression)location).getArgument(0, PyExpression.class);
if (first_arg != null) { // the usual case: first arg is the derived class that super() is proxying for
PyType first_arg_type = context.getType(first_arg);
if (first_arg_type instanceof PyClassType) {
PyClass derived_class = ((PyClassType)first_arg_type).getPyClass();
final Iterator<PyClass> base_it = derived_class.getAncestorClasses(context).iterator();
if (base_it.hasNext()) {
return new PyClassTypeImpl(base_it.next(), true).resolveMember(name, location, direction, resolveContext);
}
else {
return null; // no base classes = super() cannot proxy anything meaningful from a base class
}
}
}
}
classMember = resolveClassMember(myClass, myIsDefinition, name, location);
if (classMember != null) {
return ResolveResultList.to(classMember);
}
classMember = resolveByOverridingAncestorsMembersProviders(this, name, location);
if (classMember != null) {
return ResolveResultList.to(classMember);
}
if (inherited) {
for (PyClassLikeType type : myClass.getAncestorTypes(context)) {
if (type instanceof PyClassType) {
PsiElement superMember = resolveClassMember(((PyClassType)type).getPyClass(), myIsDefinition, name, null);
if (superMember != null) {
return ResolveResultList.to(superMember);
}
}
if (type != null) {
final List<? extends RatedResolveResult> results = type.resolveMember(name, location, direction, resolveContext, false);
if (results != null && !results.isEmpty()) {
return results;
}
}
}
}
if (isDefinition() && myClass.isNewStyleClass()) {
final PyClassLikeType typeType = getMetaClassType(context, inherited);
if (typeType != null) {
List<? extends RatedResolveResult> typeMembers = typeType.resolveMember(name, location, direction, resolveContext);
if (typeMembers != null && !typeMembers.isEmpty()) {
return typeMembers;
}
}
}
if (inherited) {
classMember = resolveByMembersProviders(this, name, location); //ask providers after real class introspection as providers have less priority
}
if (classMember != null) {
return ResolveResultList.to(classMember);
}
if (inherited) {
for (PyClassLikeType type : myClass.getAncestorTypes(context)) {
if (type instanceof PyClassType) {
final PyClass pyClass = ((PyClassType)type).getPyClass();
PsiElement superMember = resolveByMembersProviders(new PyClassTypeImpl(pyClass, isDefinition()), name, location);
if (superMember != null) {
return ResolveResultList.to(superMember);
}
}
}
}
return Collections.emptyList();
}
private Ref<ResolveResultList> findProperty(String name, AccessDirection direction, boolean inherited) {
Ref<ResolveResultList> resultRef = null;
Property property = myClass.findProperty(name, inherited);
if (property != null) {
Maybe<Callable> accessor = property.getByDirection(direction);
if (accessor.isDefined()) {
Callable accessor_code = accessor.value();
ResolveResultList ret = new ResolveResultList();
if (accessor_code != null) ret.poke(accessor_code, RatedResolveResult.RATE_NORMAL);
PyTargetExpression site = property.getDefinitionSite();
if (site != null) ret.poke(site, RatedResolveResult.RATE_LOW);
if (ret.size() > 0) {
resultRef = Ref.create(ret);
}
else {
resultRef = Ref.create();
} // property is found, but the required accessor is explicitly absent
}
}
return resultRef;
}
@Nullable
@Override
public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context, boolean inherited) {
final PyType ownMeta = myClass.getMetaClassType(context);
if (ownMeta != null) {
return (ownMeta instanceof PyClassLikeType) ? (PyClassLikeType)ownMeta : null;
}
if (inherited) {
for (PyClassLikeType ancestor : myClass.getAncestorTypes(context)) {
if (ancestor != null) {
final PyClassLikeType ancestorMeta = ancestor.getMetaClassType(context, false);
if (ancestorMeta != null) {
return ancestorMeta;
}
}
}
return PyBuiltinCache.getInstance(myClass).getObjectType("type");
}
return null;
}
@Override
public boolean isCallable() {
if (isDefinition()) {
return true;
}
if (isMethodType(this)) {
return true;
}
final PyClass cls = getPyClass();
if (PyABCUtil.isSubclass(cls, PyNames.CALLABLE)) {
return true;
}
return false;
}
private static boolean isMethodType(@NotNull PyClassType type) {
final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(type.getPyClass());
return type.equals(builtinCache.getClassMethodType()) || type.equals(builtinCache.getStaticMethodType());
}
@Nullable
@Override
public PyType getReturnType(@NotNull TypeEvalContext context) {
if (isDefinition()) {
return new PyClassTypeImpl(getPyClass(), false);
}
return null;
}
@Nullable
@Override
public PyType getCallType(@NotNull TypeEvalContext context, @NotNull PyCallSiteExpression callSite) {
return getReturnType(context);
}
@Nullable
@Override
public List<PyCallableParameter> getParameters(@NotNull TypeEvalContext context) {
return null;
}
@Nullable
private static PsiElement resolveClassMember(@NotNull PyClass cls,
boolean isDefinition,
@NotNull String name,
@Nullable PyExpression location) {
PsiElement result = resolveInner(cls, isDefinition, name, location);
if (result != null) {
return result;
}
return null;
}
@Nullable
private static PsiElement resolveByMembersProviders(PyClassType aClass, String name, @Nullable PsiElement location) {
for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) {
final PsiElement resolveResult = provider.resolveMember(aClass, name, location);
if (resolveResult != null) return resolveResult;
}
return null;
}
@Nullable
private static PsiElement resolveByOverridingMembersProviders(PyClassType aClass, String name, @Nullable PsiElement location) {
for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) {
if (provider instanceof PyOverridingClassMembersProvider) {
final PsiElement resolveResult = provider.resolveMember(aClass, name, location);
if (resolveResult != null) return resolveResult;
}
}
return null;
}
@Nullable
private static PsiElement resolveByOverridingAncestorsMembersProviders(PyClassType type, String name, @Nullable PyExpression location) {
for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) {
if (provider instanceof PyOverridingAncestorsClassMembersProvider) {
final PsiElement resolveResult = provider.resolveMember(type, name, location);
if (resolveResult != null) return resolveResult;
}
}
return null;
}
@Nullable
private static PsiElement resolveInner(@NotNull PyClass cls,
boolean isDefinition,
@NotNull String name,
@Nullable PyExpression location) {
final ResolveProcessor processor = new ResolveProcessor(name);
if (!isDefinition) {
if (!cls.processInstanceLevelDeclarations(processor, location)) {
return processor.getResult();
}
}
cls.processClassLevelDeclarations(processor);
return processor.getResult();
}
private static Key<Set<PyClassType>> CTX_VISITED = Key.create("PyClassType.Visited");
public static Key<Boolean> CTX_SUPPRESS_PARENTHESES = Key.create("PyFunction.SuppressParentheses");
public Object[] getCompletionVariants(String prefix, PsiElement location, ProcessingContext context) {
Set<PyClassType> visited = context.get(CTX_VISITED);
if (visited == null) {
visited = new HashSet<PyClassType>();
context.put(CTX_VISITED, visited);
}
if (visited.contains(this)) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
visited.add(this);
Set<String> namesAlready = context.get(CTX_NAMES);
if (namesAlready == null) {
namesAlready = new HashSet<String>();
}
List<Object> ret = new ArrayList<Object>();
boolean suppressParentheses = context.get(CTX_SUPPRESS_PARENTHESES) != null;
addOwnClassMembers(location, namesAlready, suppressParentheses, ret);
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(location != null ?
CompletionUtil.getOriginalOrSelf(location).getContainingFile() :
null);
addInheritedMembers(prefix, location, namesAlready, context, ret, typeEvalContext);
// from providers
for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) {
for (PyDynamicMember member : provider.getMembers(this, location)) {
final String name = member.getName();
if (!namesAlready.contains(name)) {
LookupElementBuilder lookupElementBuilder = LookupElementBuilder.create(name).withIcon(member.getIcon()).withTypeText(getName());
if (member.isFunction()) {
lookupElementBuilder = lookupElementBuilder.withInsertHandler(ParenthesesInsertHandler.NO_PARAMETERS);
lookupElementBuilder.withTailText("()");
}
ret.add(lookupElementBuilder);
}
}
}
if (!myClass.isNewStyleClass()) {
final PyBuiltinCache cache = PyBuiltinCache.getInstance(myClass);
final PyClassType classobjType = cache.getOldstyleClassobjType();
if (classobjType != null) {
ret.addAll(Arrays.asList(classobjType.getCompletionVariants(prefix, location, context)));
}
}
if (isDefinition() && myClass.isNewStyleClass()) {
final PyClassLikeType typeType = getMetaClassType(typeEvalContext, true);
if (typeType != null) {
Collections.addAll(ret, typeType.getCompletionVariants(prefix, location, context));
}
}
return ret.toArray();
}
private void addOwnClassMembers(PsiElement expressionHook, Set<String> namesAlready, boolean suppressParentheses, List<Object> ret) {
PyClass containingClass = PsiTreeUtil.getParentOfType(expressionHook, PyClass.class);
if (containingClass != null) {
containingClass = CompletionUtil.getOriginalElement(containingClass);
}
boolean withinOurClass = containingClass == getPyClass() || isInSuperCall(expressionHook);
final CompletionVariantsProcessor processor = new CompletionVariantsProcessor(
expressionHook, new PyResolveUtil.FilterNotInstance(myClass), null
);
if (suppressParentheses) {
processor.suppressParentheses();
}
myClass.processClassLevelDeclarations(processor);
List<String> slots = myClass.isNewStyleClass() ? myClass.getSlots() : null;
if (slots != null) {
processor.setAllowedNames(slots);
}
myClass.processInstanceLevelDeclarations(processor, expressionHook);
for (LookupElement le : processor.getResultList()) {
String name = le.getLookupString();
if (namesAlready.contains(name)) continue;
if (!withinOurClass && isClassPrivate(name)) continue;
namesAlready.add(name);
ret.add(le);
}
if (slots != null) {
for (String name : slots) {
if (!namesAlready.contains(name)) {
ret.add(LookupElementBuilder.create(name));
}
}
}
}
private static boolean isInSuperCall(PsiElement hook) {
if (hook instanceof PyReferenceExpression) {
final PyExpression qualifier = ((PyReferenceExpression)hook).getQualifier();
return qualifier instanceof PyCallExpression && ((PyCallExpression)qualifier).isCalleeText(PyNames.SUPER);
}
return false;
}
private void addInheritedMembers(String name,
PsiElement expressionHook,
Set<String> namesAlready,
ProcessingContext context,
List<Object> ret,
@NotNull TypeEvalContext typeEvalContext) {
for (PyExpression expression : myClass.getSuperClassExpressions()) {
final PsiReference reference = expression.getReference();
PsiElement element = null;
if (reference != null) {
element = reference.resolve();
}
PyType type;
if (element instanceof PyClass) {
type = new PyClassTypeImpl((PyClass)element, myIsDefinition);
}
else {
type = typeEvalContext.getType(expression);
if (type instanceof PyClassType && !myIsDefinition) {
type = ((PyClassType)type).toInstance();
}
}
if (type != null) {
Object[] ancestry = type.getCompletionVariants(name, expressionHook, context);
for (Object ob : ancestry) {
String inheritedName = ob.toString();
if (!namesAlready.contains(inheritedName) && !isClassPrivate(inheritedName)) {
ret.add(ob);
namesAlready.add(inheritedName);
}
}
ContainerUtil.addAll(ret, ancestry);
}
}
}
private static boolean isClassPrivate(String lookup_string) {
return lookup_string.startsWith("__") && !lookup_string.endsWith("__");
}
public String getName() {
return getPyClass().getName();
}
@Override
public boolean isBuiltin() {
return PyBuiltinCache.getInstance(myClass).isBuiltin(myClass);
}
@Override
public void assertValid(String message) {
if (!myClass.isValid()) {
throw new PsiInvalidElementAccessException(myClass, myClass.getClass().toString() + ": " + message);
}
}
@NotNull
public Set<String> getPossibleInstanceMembers() {
Set<String> ret = new HashSet<String>();
/*
if (myClass != null) {
PyClassType otype = PyBuiltinCache.getInstance(myClass.getProject()).getObjectType();
ret.addAll(otype.getPossibleInstanceMembers());
}
*/
// TODO: add our own ideas here, e.g. from methods other than constructor
return Collections.unmodifiableSet(ret);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PyClassTypeImpl classType = (PyClassTypeImpl)o;
if (myIsDefinition != classType.myIsDefinition) return false;
if (!myClass.equals(classType.myClass)) return false;
return true;
}
@Override
public int hashCode() {
int result = myClass.hashCode();
result = 31 * result + (myIsDefinition ? 1 : 0);
return result;
}
public static boolean is(@NotNull String qName, PyType type) {
if (type instanceof PyClassType) {
return qName.equals(((PyClassType)type).getClassQName());
}
return false;
}
@Override
public String toString() {
return (isValid() ? "" : "[INVALID] ") + "PyClassType: " + getClassQName();
}
public boolean isValid() {
return myClass.isValid();
}
@Nullable
public static PyClassTypeImpl createTypeByQName(@NotNull Project project, String classQualifiedName, boolean isDefinition) {
PyClass pyClass = PyClassNameIndex.findClass(classQualifiedName, project);
if (pyClass == null) {
return null;
}
return new PyClassTypeImpl(pyClass, isDefinition);
}
}