PY-16063 Fixes according to review:

* Using ancestor types to prevent SO
* Contexts are passed
This commit is contained in:
Ilya.Kazakevich
2015-09-03 22:21:42 +03:00
parent f3b86e22c0
commit 8eed6b44ca
14 changed files with 107 additions and 22 deletions

View File

@@ -32,6 +32,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -153,8 +154,13 @@ public class PyJavaClassType implements PyClassLikeType {
@Override
public void visitMembers(@NotNull final Processor<PsiElement> processor, final boolean inherited, @NotNull TypeEvalContext context) {
// TODO: Implement
// We do not have enough time to this method for Java and looks like there is no need to do that since
// jython is not very popular
}
@NotNull
@Override
public List<PyClassLikeType> getAncestorTypes(@NotNull final TypeEvalContext context) {
// TODO: Implement
return Collections.emptyList();
}
@Override

View File

@@ -74,7 +74,7 @@ public class PyJavaTypeProvider extends PyTypeProviderBase {
final int index = params.indexOf(param);
if (index < 0) return null;
final List<PyType> superMethodParameterTypes = new ArrayList<PyType>();
PySuperMethodsSearch.search(func, null).forEach(new Processor<PsiElement>() {
PySuperMethodsSearch.search(func, context).forEach(new Processor<PsiElement>() {
public boolean process(final PsiElement psiElement) {
if (psiElement instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)psiElement;

View File

@@ -38,7 +38,7 @@ import java.util.Map;
* Represents a class declaration in source.
*/
public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefiner, PyDocStringOwner, StubBasedPsiElement<PyClassStub>,
ScopeOwner, PyDecoratable, PyTypedElement, PyQualifiedNameOwner, PyStatementListContainer {
ScopeOwner, PyDecoratable, PyTypedElement, PyQualifiedNameOwner, PyStatementListContainer, PyWithAncestors {
ArrayFactory<PyClass> ARRAY_FACTORY = new ArrayFactory<PyClass>() {
@NotNull
@Override
@@ -51,12 +51,6 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine
ASTNode getNameNode();
/**
* Returns types of all ancestors from the hierarchy.
*/
@NotNull
List<PyClassLikeType> getAncestorTypes(@NotNull TypeEvalContext context);
/**
* Returns only those ancestors from the hierarchy, that are resolved to PyClass PSI elements.
*

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2000-2015 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;
import com.jetbrains.python.psi.types.PyClassLikeType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* Class or class-like entity with ancestors
*
* @author Ilya.Kazakevich
*/
public interface PyWithAncestors {
/**
* Returns types of all ancestors from the hierarchy.
*/
@NotNull
List<PyClassLikeType> getAncestorTypes(@NotNull TypeEvalContext context);
}

View File

@@ -19,6 +19,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.util.Processor;
import com.jetbrains.python.psi.AccessDirection;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyWithAncestors;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.RatedResolveResult;
import org.jetbrains.annotations.NotNull;
@@ -29,7 +30,7 @@ import java.util.List;
/**
* @author vlan
*/
public interface PyClassLikeType extends PyCallableType {
public interface PyClassLikeType extends PyCallableType, PyWithAncestors {
boolean isDefinition();
PyClassLikeType toInstance();
@@ -45,6 +46,7 @@ public interface PyClassLikeType extends PyCallableType {
@NotNull AccessDirection direction, @NotNull PyResolveContext resolveContext,
boolean inherited);
// TODO: Pull to PyType at next iteration
/**
* Visits all class members. This method is better then bare class since it uses type info and supports not only classes but
* class-like structures as well. Consider using user-friendly wrapper {@link PyClassLikeTypeUtil#getMembersOfType(PyClassLikeType, Class, TypeEvalContext)}

View File

@@ -178,6 +178,17 @@ public class PyCustomType implements PyClassLikeType {
return resolveMember(name, location, direction, resolveContext, true);
}
@NotNull
@Override
public final List<PyClassLikeType> getAncestorTypes(@NotNull final TypeEvalContext context) {
final Collection<PyClassLikeType> result = new LinkedHashSet<PyClassLikeType>();
for (final PyClassLikeType type : myTypesToMimic) {
result.addAll(type.getAncestorTypes(context));
}
return new ArrayList<PyClassLikeType>(result);
}
@Override
public final Object[] getCompletionVariants(final String completionPrefix, final PsiElement location, final ProcessingContext context) {
final Collection<Object> lookupElements = new ArrayList<Object>();

View File

@@ -28,6 +28,7 @@ import com.jetbrains.python.findUsages.PyFunctionFindUsagesHandler;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -128,7 +129,8 @@ public class PyStaticCallHierarchyUtil {
@Nullable
private static FindUsagesHandler createFindUsageHandler(@NotNull final PsiElement element) {
if (element instanceof PyFunction) {
final Collection<PsiElement> superMethods = PySuperMethodsSearch.search((PyFunction)element, true, null).findAll();
final TypeEvalContext context = TypeEvalContext.userInitiated(element.getProject(), null);
final Collection<PsiElement> superMethods = PySuperMethodsSearch.search((PyFunction)element, true, context).findAll();
if (superMethods.size() > 0) {
final PsiElement next = superMethods.iterator().next();
if (next instanceof PyFunction && !isInObject((PyFunction)next)) {

View File

@@ -24,6 +24,7 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureDialog;
import com.jetbrains.python.refactoring.changeSignature.PyMethodDescriptor;
import com.jetbrains.python.refactoring.changeSignature.PyParameterInfo;
@@ -58,7 +59,8 @@ public class PyChangeSignatureQuickFix implements LocalQuickFix {
assert cls != null;
final String functionName = function.getName();
final String complementaryName = PyNames.NEW.equals(functionName) ? PyNames.INIT : PyNames.NEW;
final PyFunction complementaryMethod = myOverridenMethod ? (PyFunction)PySuperMethodsSearch.search(function, null).findFirst()
final TypeEvalContext context = TypeEvalContext.userInitiated(project, descriptor.getEndElement().getContainingFile());
final PyFunction complementaryMethod = myOverridenMethod ? (PyFunction)PySuperMethodsSearch.search(function, context).findFirst()
: cls.findMethodByName(complementaryName, true);
assert complementaryMethod != null;

View File

@@ -88,7 +88,7 @@ public class KeywordArgumentCompletionUtil {
// nothing interesting besides self and **kwargs, let's look at superclass (PY-778)
if (fromStatementCallCollector.isKwArgsTransit()) {
final PsiElement superMethod = PySuperMethodsSearch.search(def, null).findFirst();
final PsiElement superMethod = PySuperMethodsSearch.search(def, context).findFirst();
if (superMethod instanceof PyFunction) {
addKeywordArgumentVariants((PyFunction)superMethod, callExpr, ret, visited);
}

View File

@@ -64,6 +64,7 @@ public class PySuperMethodsSearch extends ExtensibleQueryFactory<PsiElement, PyS
myContext = context;
}
@Nullable
public TypeEvalContext getContext() {
return myContext;
}
@@ -81,12 +82,12 @@ public class PySuperMethodsSearch extends ExtensibleQueryFactory<PsiElement, PyS
super("Pythonid");
}
public static Query<PsiElement> search(final PyFunction derivedMethod, TypeEvalContext context) {
public static Query<PsiElement> search(final PyFunction derivedMethod, @Nullable final TypeEvalContext context) {
final SearchParameters parameters = new SearchParameters(derivedMethod, false, context);
return INSTANCE.createUniqueResultsQuery(parameters);
}
public static Query<PsiElement> search(final PyFunction derivedMethod, boolean deepSearch, TypeEvalContext context) {
public static Query<PsiElement> search(final PyFunction derivedMethod, final boolean deepSearch, @Nullable final TypeEvalContext context) {
final SearchParameters parameters = new SearchParameters(derivedMethod, deepSearch, context);
return INSTANCE.createUniqueResultsQuery(parameters);
}

View File

@@ -346,6 +346,12 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
return null;
}
@NotNull
@Override
public final List<PyClassLikeType> getAncestorTypes(@NotNull final TypeEvalContext context) {
return myClass.getAncestorTypes(context);
}
@Nullable
private static PsiElement resolveClassMember(@NotNull PyClass cls,
boolean isDefinition,
@@ -476,12 +482,14 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
// TODO: accept instance attributes as well
if (inherited) {
// TODO: Add guard to prevent stack overflow
for (final PyClassLikeType type : getSuperClassTypes(context)) {
if (type != null) {
type.visitMembers(processor, true, context);
}
if (!inherited) {
return;
}
for (final PyClassLikeType type : getAncestorTypes(context)) {
if (type != null) {
// "false" because getAncestorTypes returns ALL ancestors, not only direct parents
type.visitMembers(processor, false, context);
}
}
}

View File

@@ -0,0 +1,7 @@
class Spam(Eggs):
def spam_methods(self):
pass
class Eggs(Spam):
def my_methods(self):
pass

View File

@@ -0,0 +1,10 @@
class Spam(Eggs):
def spam_methods(self):
pass
class Eggs(Spam):
def spam_methods(self):
super(Eggs, self).spam_methods()
def my_methods(self):
pass

View File

@@ -54,6 +54,13 @@ public class PyOverrideTest extends PyTestCase {
return file.getTopLevelClasses().get(index);
}
/**
* Ensures loops in class hierarchy does not lead to SO
*/
public final void testCircle() throws Exception {
doTest();
}
public void testSimple() {
doTest();
}