From 3220e2bacd0acddf9f99bcd1e0c12ab590022e7a Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Tue, 30 Dec 2014 21:00:29 +0300 Subject: [PATCH] PY-14216 Code completion for Model.objects does not work with Django 1.7.x PY-14714 Unresolved attribute reference 'objects' for class 'Foo' --- .../python/codeInsight/PyCustomMember.java | 19 +- .../psi/types/PyClassMembersProvider.java | 2 +- .../python/psi/types/PyTypeProviderBase.java | 1 + .../python/psi/types/TypeEvalContext.java | 2 +- .../com/jetbrains/python/PyBundle.properties | 1 - .../jetbrains/python/PyCustomMembersType.java | 204 --------------- .../com/jetbrains/python/PyCustomType.java | 245 ++++++++++++++++++ .../PyUnresolvedReferencesInspection.java | 20 +- .../psi/impl/PyCallExpressionHelper.java | 7 + .../python/psi/impl/PyClassImpl.java | 1 + .../psi/impl/PyReferenceExpressionImpl.java | 4 +- .../python/psi/types/PyClassTypeImpl.java | 108 ++++++-- 12 files changed, 371 insertions(+), 243 deletions(-) delete mode 100644 python/src/com/jetbrains/python/PyCustomMembersType.java create mode 100644 python/src/com/jetbrains/python/PyCustomType.java diff --git a/python/psi-api/src/com/jetbrains/python/codeInsight/PyCustomMember.java b/python/psi-api/src/com/jetbrains/python/codeInsight/PyCustomMember.java index 1c3406b211d9..4b533d813c08 100644 --- a/python/psi-api/src/com/jetbrains/python/codeInsight/PyCustomMember.java +++ b/python/psi-api/src/com/jetbrains/python/codeInsight/PyCustomMember.java @@ -45,6 +45,11 @@ public class PyCustomMember { boolean myFunction = false; + /** + * Force resolving to {@link MyInstanceElement} even if element is function + */ + private boolean myAlwaysResolveToCustomElement; + public PyCustomMember(@NotNull final String name, @Nullable final String type, final boolean resolveToInstance) { myName = name; myResolveToInstance = resolveToInstance; @@ -82,6 +87,7 @@ public class PyCustomMember { myTypeName = typeName; myTypeCallback = null; } + public PyCustomMember(@NotNull final String name, @Nullable final PsiElement target) { this(name, target, null); } @@ -96,6 +102,15 @@ public class PyCustomMember { return this; } + /** + * Force resolving to {@link MyInstanceElement} even if element is function + */ + @NotNull + public final PyCustomMember alwaysResolveToCustomElement() { + myAlwaysResolveToCustomElement = true; + return this; + } + public PyCustomMember toClass(String name) { myPsiPath = new PyPsiPath.ToClass(myPsiPath, name); return this; @@ -156,7 +171,7 @@ public class PyCustomMember { PyClass targetClass = myTypeName != null && myTypeName.indexOf('.') > 0 ? PyPsiFacade.getInstance(context.getProject()).findClass(myTypeName) : null; final PsiElement resolveTarget = findResolveTarget(context); - if (resolveTarget instanceof PyFunction) { + if (resolveTarget instanceof PyFunction && !myAlwaysResolveToCustomElement) { return resolveTarget; } if (resolveTarget != null || targetClass != null) { @@ -211,4 +226,4 @@ public class PyCustomMember { return null; } } -} +} \ No newline at end of file diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassMembersProvider.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassMembersProvider.java index 6b2dcc2e3610..99f59355f969 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyClassMembersProvider.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyClassMembersProvider.java @@ -30,7 +30,7 @@ public interface PyClassMembersProvider { ExtensionPointName EP_NAME = ExtensionPointName.create("Pythonid.pyClassMembersProvider"); @NotNull - Collection getMembers(PyClassType clazz, @Nullable PsiElement location); + Collection getMembers(final PyClassType clazz, PsiElement location); @Nullable PsiElement resolveMember(PyClassType clazz, String name, @Nullable PsiElement location); diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java b/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java index 21653178781d..368059138d9f 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/PyTypeProviderBase.java @@ -102,6 +102,7 @@ public class PyTypeProviderBase implements PyTypeProvider { return null; } + @Nullable @Override public PyType getCallType(@NotNull PyFunction function, @Nullable PyCallSiteExpression callSite, @NotNull TypeEvalContext context) { ReturnTypeDescriptor descriptor; diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java index 54af2a23e3c4..41a2a16c82cb 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java @@ -191,7 +191,7 @@ public class TypeEvalContext { } } final PyType type = element.getType(this, Key.INSTANCE); - assertValid(type, element); + assertValid(type, element); synchronized (myEvaluated) { myEvaluated.put(element, type); } diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 89608bf54456..574bb835de66 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -850,5 +850,4 @@ remote.interpreter.configure.path.label=Python interpreter path: remote.interpreter.configure.temp.files.path.label=PyCharm helpers path: # Message we display for inspection if user uses custom class type members that do not exist -custom.type.name=Dynamic class custom.type.mimic.name=Dynamic class based on {0} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/PyCustomMembersType.java b/python/src/com/jetbrains/python/PyCustomMembersType.java deleted file mode 100644 index ca6bc5368eb5..000000000000 --- a/python/src/com/jetbrains/python/PyCustomMembersType.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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; - -import com.intellij.codeInsight.lookup.LookupElement; -import com.intellij.psi.PsiElement; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ProcessingContext; -import com.jetbrains.python.codeInsight.PyCustomMember; -import com.jetbrains.python.codeInsight.PyCustomMemberUtils; -import com.jetbrains.python.psi.AccessDirection; -import com.jetbrains.python.psi.PyCallSiteExpression; -import com.jetbrains.python.psi.PyExpression; -import com.jetbrains.python.psi.resolve.PyResolveContext; -import com.jetbrains.python.psi.resolve.RatedResolveResult; -import com.jetbrains.python.psi.types.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -/** - * Custom (aka dynamic) type that has some members you pass to it. To be used for cases like "type()" - * This class can also mimic any other class (optionally). When mimics, it has all methods from this class and its own. - * @author Ilya.Kazakevich - */ -public class PyCustomMembersType implements PyClassLikeType { - @NotNull - private final Map myMembers; - @Nullable - private final PyClassType myTypeToMimic; - - /** - * @param typeToMimic this type may mimic some other class-based type. Pass it to have all members from this class + custom. - * Check class manual for more info. - * @param members custom members - */ - public PyCustomMembersType(@Nullable final PyClassType typeToMimic, @NotNull final PyCustomMember... members) { - myTypeToMimic = typeToMimic; - - myMembers = new HashMap(members.length); - for (final PyCustomMember member : members) { - myMembers.put(member.getName(), member); - } - } - - /** - * @return class we mimic (if any). Check class manual for more info. - */ - @Nullable - public PyClassType getTypeToMimic() { - return myTypeToMimic; - } - - @Override - public boolean isDefinition() { - return false; - } - - @Override - public PyClassLikeType toInstance() { - return this; - } - - @Nullable - @Override - public String getClassQName() { - return null; - } - - @NotNull - @Override - public List getSuperClassTypes(@NotNull TypeEvalContext context) { - return Collections.emptyList(); - } - - @Nullable - @Override - public List resolveMember(@NotNull final String name, - @Nullable final PyExpression location, - @NotNull final AccessDirection direction, - @NotNull final PyResolveContext resolveContext, - final boolean inherited) { - if (myMembers.containsKey(name)) { - PsiElement context = null; - if (location != null) { - context = location; - } - if (context == null) { - context = resolveContext.getTypeEvalContext().getOrigin(); - } - if (context != null) { - final PsiElement resolveResult = myMembers.get(name).resolve(context); - if (resolveResult != null) { - return Collections.singletonList(new RatedResolveResult(0, resolveResult)); - } - } - } - if (myTypeToMimic != null) { - return myTypeToMimic.resolveMember(name, location, direction, resolveContext, inherited); - } - return null; - } - - @Override - public boolean isValid() { - return true; - } - - @Nullable - @Override - public PyClassLikeType getMetaClassType(@NotNull TypeEvalContext context, boolean inherited) { - return null; - } - - @Override - public boolean isCallable() { - return true; - } - - @Nullable - @Override - public PyType getReturnType(@NotNull TypeEvalContext context) { - return null; - } - - @Nullable - @Override - public PyType getCallType(@NotNull TypeEvalContext context, @NotNull PyCallSiteExpression callSite) { - return this; - } - - @Nullable - @Override - public List getParameters(@NotNull TypeEvalContext context) { - return null; - } - - @Nullable - @Override - public List resolveMember(@NotNull final String name, - @Nullable final PyExpression location, - @NotNull final AccessDirection direction, - @NotNull final PyResolveContext resolveContext) { - return resolveMember(name, location, direction, resolveContext, true); - } - - @Override - public Object[] getCompletionVariants(final String completionPrefix, final PsiElement location, final ProcessingContext context) { - final Collection lookupElements = new ArrayList(myMembers.size()); - for (final PyCustomMember member : myMembers.values()) { - lookupElements.add(PyCustomMemberUtils.toLookUpElement(member, member.getShortType())); - } - return ArrayUtil.mergeArrays(ArrayUtil.toObjectArray(lookupElements), - ((myTypeToMimic != null) - ? myTypeToMimic.getCompletionVariants(completionPrefix, location, context) - : PsiElement.EMPTY_ARRAY)); - } - - - @Nullable - @Override - public String getName() { - String mimicName = null; - if (myTypeToMimic != null) { - mimicName = myTypeToMimic.getName(); - } - if (mimicName != null) { - return PyBundle.message("custom.type.mimic.name", mimicName); - } - return PyBundle.message("custom.type.name"); - } - - @Override - public boolean isBuiltin() { - return false; - } - - @Override - public void assertValid(final String message) { - - } - - /** - * @param name name to check - * @return True if this class (not the one it mimics!) has member with passed name - */ - public boolean hasMember(@NotNull final String name) { - return myMembers.containsKey(name); - } -} diff --git a/python/src/com/jetbrains/python/PyCustomType.java b/python/src/com/jetbrains/python/PyCustomType.java new file mode 100644 index 000000000000..09d9bee8bc56 --- /dev/null +++ b/python/src/com/jetbrains/python/PyCustomType.java @@ -0,0 +1,245 @@ +/* + * 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; + +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.resolve.PyResolveContext; +import com.jetbrains.python.psi.resolve.RatedResolveResult; +import com.jetbrains.python.psi.types.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * Custom (aka dynamic) type that delegates calls to some classes you pass to it. + * We say this this class mimics such classes. + * To be used for cases like "type()". + * It optionally filters methods using {@link ParentsMemberFilter} + * + * @author Ilya.Kazakevich + */ +public class PyCustomType implements PyClassLikeType, Predicate { + + @NotNull + private final List myTypesToMimic = new ArrayList(); + + @Nullable + private final ParentsMemberFilter myFilter; + + private final boolean myInstanceType; + + + /** + * @param filter filter to filter methods from classes (may be null to do no filtering) + * @param instanceType if true, then this class implements instance (it reports it is not definition and returns "this + * for {@link #toInstance()} call). + * @param typesToMimic types to "mimic": delegate calls to (must be one at least!) + */ + public PyCustomType(@Nullable final ParentsMemberFilter filter, + final boolean instanceType, + @NotNull final PyClassLikeType... typesToMimic) { + Preconditions.checkArgument(typesToMimic.length > 0, "Provide at least one class"); + myFilter = filter; + myTypesToMimic.addAll(Arrays.asList(typesToMimic)); + myInstanceType = instanceType; + } + + /** + * @return class we mimic (if any). Check class manual for more info. + */ + @NotNull + public final List getTypesToMimic() { + return Collections.unmodifiableList(myTypesToMimic); + } + + @Override + public final boolean isDefinition() { + return !myInstanceType; + } + + @Override + public final PyClassLikeType toInstance() { + return myInstanceType ? this : new PyCustomType(myFilter, true, myTypesToMimic.toArray(new PyClassLikeType[myTypesToMimic.size()])); + } + + + @Nullable + @Override + public final String getClassQName() { + return null; + } + + @NotNull + @Override + public final List getSuperClassTypes(@NotNull final TypeEvalContext context) { + return Collections.emptyList(); + } + + @Nullable + @Override + public final List resolveMember(@NotNull final String name, + @Nullable final PyExpression location, + @NotNull final AccessDirection direction, + @NotNull final PyResolveContext resolveContext, + final boolean inherited) { + final List globalResult = new ArrayList(); + + // Delegate calls to classes, we mimic but filter if filter is set. + for (final PyClassLikeType parentType : myTypesToMimic) { + final List results = parentType.resolveMember(name, location, direction, resolveContext, inherited); + if (results != null) { + globalResult.addAll(Collections2.filter(results, this)); + } + } + return globalResult; + } + + @Override + public final boolean isValid() { + return true; + } + + @Nullable + @Override + public final PyClassLikeType getMetaClassType(@NotNull final TypeEvalContext context, final boolean inherited) { + return null; + } + + @Override + public final boolean isCallable() { + return true; // We do not know, actually + } + + @Nullable + @Override + public final PyType getReturnType(@NotNull final TypeEvalContext context) { + return (myInstanceType ? null : toInstance()); + } + + @Nullable + @Override + public final PyType getCallType(@NotNull final TypeEvalContext context, @NotNull final PyCallSiteExpression callSite) { + return getReturnType(context); + } + + @Nullable + @Override + public final List getParameters(@NotNull final TypeEvalContext context) { + return null; + } + + @Nullable + @Override + public final List resolveMember(@NotNull final String name, + @Nullable final PyExpression location, + @NotNull final AccessDirection direction, + @NotNull final PyResolveContext resolveContext) { + return resolveMember(name, location, direction, resolveContext, true); + } + + @Override + public final Object[] getCompletionVariants(final String completionPrefix, final PsiElement location, final ProcessingContext context) { + final Collection lookupElements = new ArrayList(); + + for (final PyClassLikeType parentType : myTypesToMimic) { + lookupElements.addAll(Collections2.filter(Arrays.asList(parentType.getCompletionVariants(completionPrefix, location, context)), + new CompletionFilter())); + } + return lookupElements.toArray(new Object[lookupElements.size()]); + } + + + @Nullable + @Override + public final String getName() { + final Collection classNames = new ArrayList(myTypesToMimic.size()); + for (final PyClassLikeType type : myTypesToMimic) { + String name = type.getName(); + if (name == null && (type instanceof PyClassType)) { + name = ((PyClassType)type).getPyClass().getName(); + } + if (name != null) { + classNames.add(name); + } + } + + + return PyBundle.message("custom.type.mimic.name", StringUtil.join(classNames, ",")); + } + + @Override + public final boolean isBuiltin() { + return false; + } + + @Override + public final void assertValid(final String message) { + for (final PyClassLikeType type : myTypesToMimic) { + type.assertValid(message); + } + } + + + @Override + public final boolean apply(@Nullable final RatedResolveResult input) { + if (input == null) { + return false; + } + if (myFilter == null) { + return true; // No need to check + } + final PyElement pyElement = PyUtil.as(input.getElement(), PyElement.class); + if (pyElement == null) { + return false; + } + return myFilter.acceptMember(pyElement); + } + + public interface ParentsMemberFilter { + boolean acceptMember(@NotNull PyElement element); + } + + /** + * Predicate that filters completion using {@link #myFilter} + */ + private class CompletionFilter implements Predicate { + @Override + public final boolean apply(@Nullable final Object input) { + if (input == null) { + return false; + } + if (myFilter == null) { + return true; // No need to check + } + if (!(input instanceof LookupElement)) { + return true; // Do not know how to check + } + final PyElement pyElement = PyUtil.as(((LookupElement)input).getPsiElement(), PyElement.class); + if (pyElement == null) { + return false; + } + return myFilter.acceptMember(pyElement); + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index 781ad21bb88b..693b8dce7a47 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -38,7 +38,7 @@ import com.intellij.util.PlatformUtils; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyCustomMembersType; +import com.jetbrains.python.PyCustomType; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.codeInsight.PyCustomMember; @@ -607,13 +607,15 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } /** - * Checks if type is custom-member based and has custom member with certain name + * Checks if type is custom type and has custom member with certain name * @param refName name to check * @param type type * @return true if has one */ private static boolean isHasCustomMember(@NotNull final String refName, @NotNull final PyType type) { - return (type instanceof PyCustomMembersType) && ((PyCustomMembersType)type).hasMember(refName); + // TODO: check + return false; + /*return (type instanceof PyCustomType) && ((PyCustomType)type).hasMember(refName);*/ } /** @@ -713,11 +715,15 @@ public class PyUnresolvedReferencesInspection extends PyInspection { return true; } } - if (type instanceof PyCustomMembersType) { + if (type instanceof PyCustomType) { // Skip custom member types that mimics another class with fuzzy parents - PyClassType mimic = ((PyCustomMembersType)type).getTypeToMimic(); - if (mimic != null && PyUtil.hasUnresolvedAncestors(mimic.getPyClass(), myTypeEvalContext)) { - return true; + for (final PyClassLikeType mimic : ((PyCustomType)type).getTypesToMimic()) { + if (!(mimic instanceof PyClassType)) { + continue; + } + if (PyUtil.hasUnresolvedAncestors(((PyClassType)mimic).getPyClass(), myTypeEvalContext)) { + return true; + } } } if (type instanceof PyClassTypeImpl) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index c0ac149f527f..13c8a417c8ca 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -360,6 +360,9 @@ public class PyCallExpressionHelper { } } } + else if (qtype instanceof PyClassLikeType) { + return ((PyClassLikeType)qtype).isDefinition(); //Any definition means callable is classmethod + } return false; } @@ -522,6 +525,10 @@ public class PyCallExpressionHelper { final Callable callable = (Callable)target; return Ref.create(callable.getCallType(context, call)); } + /*PyCallExpression.PyMarkedCallee markedCallee = call.resolveCallee(PyResolveContext.defaultContext().withTypeEvalContext(context)); + if (markedCallee != null) { + return Ref.create(markedCallee.getCallable().getCallType(context, call)); + }*/ return null; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index c03fe346b6e3..29e590bac898 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -1219,6 +1219,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla } else { for (PyExpression expression : getSuperClassExpressions()) { + context.getType(expression); expression = unfoldClass(expression); if (expression instanceof PyKeywordArgument) { continue; diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 37e2d63f9f99..fc60a5f056ba 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -420,7 +420,9 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere @Nullable PsiElement anchor) { for (PyTypeProvider provider : Extensions.getExtensions(PyTypeProvider.EP_NAME)) { final PyType result = provider.getReferenceType(target, context, anchor); - if (result != null) return result; + if (result != null) { + return result; + } } return null; diff --git a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java index a7b0d548ea23..79fdf3c51c3d 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassTypeImpl.java @@ -32,6 +32,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.hash.HashMap; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCustomMember; import com.jetbrains.python.codeInsight.PyCustomMemberUtils; @@ -138,7 +139,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } resolving.add(key); try { - return doResolveMember(name, location, direction, resolveContext, inherited); + return doResolveMember(name, location, direction, resolveContext, inherited, null); } finally { resolving.remove(key); @@ -146,14 +147,16 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } @Nullable - private List doResolveMember(@NotNull String name, - @Nullable PyExpression location, - @NotNull AccessDirection direction, - @NotNull PyResolveContext resolveContext, - boolean inherited) { + public List doResolveMember(@NotNull String name, + @Nullable PyExpression location, + @NotNull AccessDirection direction, + @NotNull PyResolveContext resolveContext, + boolean inherited, + @Nullable PyClassMembersProvider providerToSkip) { final TypeEvalContext context = resolveContext.getTypeEvalContext(); PsiElement classMember = - resolveByOverridingMembersProviders(this, name, location); //overriding members provers have priority to normal resolve + resolveByOverridingMembersProviders(this, name, location, + providerToSkip); //overriding members provers have priority to normal resolve if (classMember != null) { return ResolveResultList.to(classMember); } @@ -188,7 +191,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { return ResolveResultList.to(classMember); } - classMember = resolveByOverridingAncestorsMembersProviders(this, name, location); + classMember = resolveByOverridingAncestorsMembersProviders(this, name, location, providerToSkip); if (classMember != null) { return ResolveResultList.to(classMember); } @@ -225,7 +228,8 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { if (inherited) { classMember = - resolveByMembersProviders(this, name, location); //ask providers after real class introspection as providers have less priority + resolveByMembersProviders(this, name, location, + providerToSkip); //ask providers after real class introspection as providers have less priority } if (classMember != null) { @@ -236,7 +240,7 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { 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); + PsiElement superMember = resolveByMembersProviders(new PyClassTypeImpl(pyClass, isDefinition()), name, location, providerToSkip); if (superMember != null) { return ResolveResultList.to(superMember); @@ -345,8 +349,13 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } @Nullable - private static PsiElement resolveByMembersProviders(PyClassType aClass, String name, @Nullable PsiElement location) { + private static PsiElement resolveByMembersProviders(PyClassType aClass, String name, + @Nullable PsiElement location, + @Nullable PyClassMembersProvider providerToSkip) { for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { + if (provider == providerToSkip) { + continue; + } final PsiElement resolveResult = provider.resolveMember(aClass, name, location); if (resolveResult != null) return resolveResult; } @@ -355,9 +364,10 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } @Nullable - private static PsiElement resolveByOverridingMembersProviders(PyClassType aClass, String name, @Nullable PsiElement location) { + private static PsiElement resolveByOverridingMembersProviders(PyClassType aClass, String name, @Nullable PsiElement location, + @Nullable PyClassMembersProvider providerToSkip) { for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { - if (provider instanceof PyOverridingClassMembersProvider) { + if (provider instanceof PyOverridingClassMembersProvider && provider != providerToSkip) { final PsiElement resolveResult = provider.resolveMember(aClass, name, location); if (resolveResult != null) return resolveResult; } @@ -367,9 +377,10 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } @Nullable - private static PsiElement resolveByOverridingAncestorsMembersProviders(PyClassType type, String name, @Nullable PyExpression location) { + private static PsiElement resolveByOverridingAncestorsMembersProviders(PyClassType type, String name, @Nullable PyExpression location, + @Nullable PyClassMembersProvider providerToSkip) { for (PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { - if (provider instanceof PyOverridingAncestorsClassMembersProvider) { + if (provider instanceof PyOverridingAncestorsClassMembersProvider && !(provider == providerToSkip)) { final PsiElement resolveResult = provider.resolveMember(type, name, location); if (resolveResult != null) return resolveResult; } @@ -409,28 +420,50 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { if (namesAlready == null) { namesAlready = new HashSet(); } - List ret = new ArrayList(); + + // TODO: Doc + final Map> usedNames = new HashMap>(); + boolean suppressParentheses = context.get(CTX_SUPPRESS_PARENTHESES) != null; - addOwnClassMembers(location, namesAlready, suppressParentheses, ret); + addOwnClassMembers(location, namesAlready, suppressParentheses, usedNames); + namesAlready.addAll(usedNames.keySet()); PsiFile origin = (location != null) ? CompletionUtil.getOriginalOrSelf(location) .getContainingFile() : null; final TypeEvalContext typeEvalContext = TypeEvalContext.codeCompletion(myClass.getProject(), origin); - addInheritedMembers(prefix, location, namesAlready, context, ret, typeEvalContext); + final List inheritedMembers = addInheritedMembers(prefix, location, namesAlready, context, usedNames, typeEvalContext); + namesAlready.addAll(usedNames.keySet()); + + // Move to func + // TODO: Override only if Overriding // from providers for (final PyClassMembersProvider provider : Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { for (final PyCustomMember member : provider.getMembers(this, location)) { final String name = member.getName(); - if (!namesAlready.contains(name)) { - ret.add(PyCustomMemberUtils.toLookUpElement(member, getName())); + final LookupElementBuilder element = PyCustomMemberUtils.toLookUpElement(member, getName()); + + final Pair usedNameInfo = usedNames.get(name); + // TODO: Doc + if (usedNameInfo == null || + (usedNameInfo.first == ElementType.INHERITED && provider instanceof PyOverridingAncestorsClassMembersProvider) || + (usedNameInfo.first == ElementType.OWN && provider instanceof PyOverridingClassMembersProvider)) { + usedNames.put(name, Pair.create(ElementType.BY_PROVIDER, element)); + } else { + int i = 1; } } } + + final List ret = new ArrayList(getElementsFromUsedNames(usedNames)); + namesAlready.addAll(usedNames.keySet()); + ret.addAll(inheritedMembers); + + if (!myClass.isNewStyleClass()) { final PyBuiltinCache cache = PyBuiltinCache.getInstance(myClass); final PyClassType classobjType = cache.getOldstyleClassobjType(); @@ -449,7 +482,11 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { return ret.toArray(); } - private void addOwnClassMembers(PsiElement expressionHook, Set namesAlready, boolean suppressParentheses, List ret) { + + private void addOwnClassMembers(PsiElement expressionHook, + Set namesAlready, + boolean suppressParentheses, + @NotNull final Map> usedNames) { PyClass containingClass = PsiTreeUtil.getParentOfType(expressionHook, PyClass.class); if (containingClass != null) { containingClass = CompletionUtil.getOriginalElement(containingClass); @@ -475,12 +512,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { if (namesAlready.contains(name)) continue; if (!withinOurClass && isClassPrivate(name)) continue; namesAlready.add(name); - ret.add(le); + usedNames.put(name, Pair.create(ElementType.OWN, le)); } if (slots != null) { for (String name : slots) { if (!namesAlready.contains(name)) { - ret.add(LookupElementBuilder.create(name)); + usedNames.put(name, Pair.create(ElementType.OWN, LookupElementBuilder.create(name))); } } } @@ -494,12 +531,14 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { return false; } - private void addInheritedMembers(String name, + @NotNull + private List addInheritedMembers(String name, PsiElement expressionHook, Set namesAlready, ProcessingContext context, - List ret, + @NotNull final Map> usedNames, @NotNull TypeEvalContext typeEvalContext) { + final List ret = new ArrayList(); for (PyExpression expression : myClass.getSuperClassExpressions()) { final PsiReference reference = expression.getReference(); PsiElement element = null; @@ -521,13 +560,14 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { for (Object ob : ancestry) { String inheritedName = ob.toString(); if (!namesAlready.contains(inheritedName) && !isClassPrivate(inheritedName)) { - ret.add(ob); + usedNames.put(inheritedName, Pair.create(ElementType.INHERITED, ob)); namesAlready.add(inheritedName); } } ContainerUtil.addAll(ret, ancestry); } } + return ret; } private static boolean isClassPrivate(String lookup_string) { @@ -607,4 +647,20 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType { } return new PyClassTypeImpl(pyClass, isDefinition); } + + // TOOD: Doc + private static List getElementsFromUsedNames(@NotNull final Map> usedNames) { + final List ret = new ArrayList(); + for (final Pair objectPair : usedNames.values()) { + ret.add(objectPair.second); + } + return ret; + } + + // TODO: Doc + private enum ElementType { + OWN, + INHERITED, + BY_PROVIDER + } }