From 5565a2e065f95d8dcaa6e6a0a0367b83fa2502bf Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 12 Jul 2010 20:10:04 +0400 Subject: [PATCH 1/4] 'Override method' inserts super method call instead of 'pass' (PY-302) --- python/src/com/jetbrains/python/PyNames.java | 3 + .../override/PyOverrideImplementUtil.java | 86 ++++++++++--------- .../PySuperArgumentsInspection.java | 4 +- .../jetbrains/python/psi/PyDecoratorList.java | 9 +- .../python/psi/impl/PyCallExpressionImpl.java | 4 +- .../python/psi/impl/PyClassImpl.java | 5 +- .../python/psi/impl/PyDecoratorListImpl.java | 11 +++ .../python/psi/impl/PyFunctionBuilder.java | 16 +++- .../validation/UnsupportedFeatures.java | 3 +- python/testData/override/classmethod_after.py | 2 +- python/testData/override/newStyle.py | 6 ++ python/testData/override/newStyle_after.py | 8 ++ python/testData/override/py3k.py | 6 ++ python/testData/override/py3k_after.py | 8 ++ python/testData/override/simple_after.py | 2 +- .../com/jetbrains/python/PyOverrideTest.java | 24 +++++- 16 files changed, 139 insertions(+), 58 deletions(-) create mode 100644 python/testData/override/newStyle.py create mode 100644 python/testData/override/newStyle_after.py create mode 100644 python/testData/override/py3k.py create mode 100644 python/testData/override/py3k_after.py diff --git a/python/src/com/jetbrains/python/PyNames.java b/python/src/com/jetbrains/python/PyNames.java index 4a167dd077a8..e66542c2ba48 100644 --- a/python/src/com/jetbrains/python/PyNames.java +++ b/python/src/com/jetbrains/python/PyNames.java @@ -20,6 +20,9 @@ public class PyNames { @NonNls public static final String GETATTR = "__getattr__"; @NonNls public static final String GETATTRIBUTE = "__getattribute__"; @NonNls public static final String CLASS = "__class__"; + @NonNls public static final String METACLASS = "__metaclass__"; + + @NonNls public static final String SUPER = "super"; @NonNls public static final String OBJECT = "object"; @NonNls public static final String NONE = "None"; diff --git a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java index 9858d9585c07..792c17a52edd 100644 --- a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java @@ -16,8 +16,9 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.IncorrectOperationException; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyFunctionBuilder; import com.jetbrains.python.psi.impl.PyPsiUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -30,6 +31,9 @@ import java.util.*; public class PyOverrideImplementUtil { private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.codeInsight.override.PyOverrideImplementUtil"); + private PyOverrideImplementUtil() { + } + @Nullable public static PyClass getContextClass(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile file) { PsiDocumentManager.getInstance(project).commitAllDocuments(); @@ -90,22 +94,19 @@ public class PyOverrideImplementUtil { overrideMethods(editor, pyClass, membersToOverride); } - public static void overrideMethods(final Editor editor, final PyClass pyClass, List membersToOverride) { - final List newMembers = generateCode(membersToOverride); - if (newMembers.isEmpty()) { + public static void overrideMethods(final Editor editor, final PyClass pyClass, final List membersToOverride) { + if (membersToOverride == null) { return; } - new WriteCommandAction(pyClass.getProject(), pyClass.getContainingFile()) { protected void run(final Result result) throws Throwable { - write(pyClass, newMembers, pyClass.getProject(), editor); + write(pyClass, membersToOverride, editor); } }.execute(); } private static void write(@NotNull final PyClass pyClass, - @NotNull final List newMembers, - @NotNull final Project project, + @NotNull final List newMembers, @NotNull final Editor editor) { final PyStatementList statementList = pyClass.getStatementList(); final int offset = editor.getCaretModel().getOffset(); @@ -117,15 +118,11 @@ public class PyOverrideImplementUtil { } PyFunction element = null; - for (String newMember : newMembers) { - element = PyElementGenerator.getInstance(project).createFromText(PyFunction.class, newMember + "\n pass"); - try { - element = (PyFunction)statementList.addAfter(element, anchor); - element = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(element); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } + for (PyMethodMember newMember : newMembers) { + PyFunction baseFunction = (PyFunction) newMember.getPsiElement(); + final PyFunctionBuilder builder = buildOverriddenFunction(pyClass, baseFunction); + PyFunction function = builder.addFunctionAfter(statementList, anchor); + element = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(function); } PyPsiUtils.removeRedundantPass(statementList); @@ -135,31 +132,43 @@ public class PyOverrideImplementUtil { editor.getSelectionModel().setSelection(start, element.getTextRange().getEndOffset()); } - private static List generateCode(final List members) { - if (members == null) { - return Collections.emptyList(); + private static PyFunctionBuilder buildOverriddenFunction(PyClass pyClass, PyFunction baseFunction) { + PyFunctionBuilder pyFunctionBuilder = new PyFunctionBuilder(baseFunction.getName()); + final PyDecoratorList decorators = baseFunction.getDecoratorList(); + if (decorators != null && decorators.findDecorator(PyNames.CLASSMETHOD) != null) { + pyFunctionBuilder.decorate(PyNames.CLASSMETHOD); } - List newMembers = new ArrayList(); - for (PyMethodMember member : members) { - newMembers.add(generateNewMethod(member.getPsiElement())); + final PyParameter[] baseParams = baseFunction.getParameterList().getParameters(); + for (PyParameter parameter : baseParams) { + pyFunctionBuilder.parameter(parameter.getText()); } - return newMembers; - } - @NotNull - private static String generateNewMethod(@NotNull final PsiElement element) { - assert (element instanceof PyFunction); - final PyFunction function = (PyFunction)element; - final StringBuilder newMethodText = new StringBuilder(); - final PyDecoratorList decoratorList = function.getDecoratorList(); - if (decoratorList != null) { - for (PyDecorator decorator: decoratorList.getDecorators()) { - if ("classmethod".equals(decorator.getCallee().getText())) { - newMethodText.append("@classmethod\n"); - } + PyClass baseClass = baseFunction.getContainingClass(); + assert baseClass != null; + StringBuilder statementBody = new StringBuilder(); + if (baseClass.isNewStyleClass()) { + statementBody.append(PyNames.SUPER); + statementBody.append("("); + final LanguageLevel langLevel = ((PyFile)pyClass.getContainingFile()).getLanguageLevel(); + if (!langLevel.isPy3K()) { + statementBody.append(pyClass.getName()).append(", self"); } + statementBody.append(").").append(baseFunction.getName()).append("("); + for (int i = 1; i < baseParams.length; i++) { + statementBody.append(baseParams [i].getText()); + } + statementBody.append(")"); } - return newMethodText.append("def ").append(function.getName()).append(function.getParameterList().getText()).append(":").toString(); + else { + statementBody.append(baseClass.getName()).append(".").append(baseFunction.getName()).append("("); + for (PyParameter param : baseParams) { + statementBody.append(param.getText()); + } + statementBody.append(")"); + } + + pyFunctionBuilder.statement(statementBody.toString()); + return pyFunctionBuilder; } @NotNull @@ -173,7 +182,4 @@ public class PyOverrideImplementUtil { } return superFunctions.values(); } - - private PyOverrideImplementUtil() { - } } diff --git a/python/src/com/jetbrains/python/inspections/PySuperArgumentsInspection.java b/python/src/com/jetbrains/python/inspections/PySuperArgumentsInspection.java index 5138ae12500a..e26090cf09b6 100644 --- a/python/src/com/jetbrains/python/inspections/PySuperArgumentsInspection.java +++ b/python/src/com/jetbrains/python/inspections/PySuperArgumentsInspection.java @@ -1,10 +1,10 @@ package com.jetbrains.python.inspections; -import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyCallExpression; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyExpression; @@ -37,7 +37,7 @@ public class PySuperArgumentsInspection extends PyInspection { @Override public void visitPyCallExpression(PyCallExpression node) { - if ("super".equals(node.getCallee().getName())) { + if (PyNames.SUPER.equals(node.getCallee().getName())) { PyExpression[] arguments = node.getArguments(); if (arguments.length == 2) { if (arguments[0] instanceof PyReferenceExpression && arguments[1] instanceof PyReferenceExpression) { diff --git a/python/src/com/jetbrains/python/psi/PyDecoratorList.java b/python/src/com/jetbrains/python/psi/PyDecoratorList.java index 5c5a1eb571c0..acac2208f263 100644 --- a/python/src/com/jetbrains/python/psi/PyDecoratorList.java +++ b/python/src/com/jetbrains/python/psi/PyDecoratorList.java @@ -2,19 +2,20 @@ package com.jetbrains.python.psi; import com.intellij.psi.StubBasedPsiElement; import com.jetbrains.python.psi.stubs.PyDecoratorListStub; -import com.jetbrains.python.psi.PyDecorator; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * A list of function decorators. - * User: dcheryasov - * Date: Sep 28, 2008 + * @author dcheryasov */ public interface PyDecoratorList extends PyElement, StubBasedPsiElement { - /** * @return decorators of function, in order of declaration (outermost first). */ @NotNull PyDecorator[] getDecorators(); + + @Nullable + PyDecorator findDecorator(String name); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java index 76e7168dec52..7ccb337eec7f 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java @@ -72,7 +72,7 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress PyExpression callee = getCallee(); if (callee instanceof PyReferenceExpression) { // hardwired special cases - if ("super".equals(callee.getText())) { + if (PyNames.SUPER.equals(callee.getText())) { final PyType superCallType = getSuperCallType(callee, context); if (superCallType != null) { return superCallType; @@ -135,7 +135,7 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress 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(this).getClass("super")) { + if (must_be_super == PyBuiltinCache.getInstance(this).getClass(PyNames.SUPER)) { PyArgumentList arglist = getArgumentList(); if (arglist != null) { PyExpression[] args = arglist.getArguments(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 93703894282f..b998eece14eb 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -708,6 +708,9 @@ public class PyClassImpl extends PyPresentableElementImpl implement } private boolean calculateNewStyleClass() { + if (((PyFile) getContainingFile()).getLanguageLevel().isPy3K()) { + return true; + } PyClass objclass = PyBuiltinCache.getInstance(this).getClass("object"); if (this == objclass) return true; // a rare but possible case if (hasNewStyleMetaClass(this)) return true; @@ -723,7 +726,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement private static boolean hasNewStyleMetaClass(PyClass pyClass) { final PsiFile containingFile = pyClass.getContainingFile(); if (containingFile instanceof PyFile) { - final PsiElement element = ((PyFile)containingFile).findExportedName("__metaclass__"); + final PsiElement element = ((PyFile)containingFile).findExportedName(PyNames.METACLASS); if (element instanceof PyTargetExpression) { final PyExpression assignedValue = ((PyTargetExpression)element).findAssignedValue(); if (assignedValue != null && assignedValue.getText().equals("type")) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java index 90dae79c973f..ac186cab4740 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorListImpl.java @@ -32,4 +32,15 @@ public class PyDecoratorListImpl extends PyBaseElementImpl return getStubOrPsiChildren(PyElementTypes.DECORATOR_CALL, decoarray); //return decoarray; } + + @Override + public PyDecorator findDecorator(String name) { + final PyDecorator[] decorators = getDecorators(); + for (PyDecorator decorator : decorators) { + if (name.equals(decorator.getCallee().getText())) { + return decorator; + } + } + return null; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java index c0357429eb19..ace6f388d866 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java @@ -6,7 +6,6 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.jetbrains.python.PythonFileType; -import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.psi.PyElementGenerator; import com.jetbrains.python.psi.PyFunction; @@ -21,6 +20,7 @@ public class PyFunctionBuilder { private final String myName; private final List myParameters = new ArrayList(); private final List myStatements = new ArrayList(); + private final List myDecorators = new ArrayList(); public PyFunctionBuilder(String name) { myName = name; @@ -46,6 +46,10 @@ public class PyFunctionBuilder { return (PyFunction) target.add(buildFunction(target.getProject())); } + public PyFunction addFunctionAfter(PsiElement target, PsiElement anchor) { + return (PyFunction) target.addAfter(buildFunction(target.getProject()), anchor); + } + public PyFunction buildFunction(Project project) { String text = buildText(project); PyElementGenerator generator = PyElementGenerator.getInstance(project); @@ -53,7 +57,11 @@ public class PyFunctionBuilder { } private String buildText(Project project) { - StringBuilder builder = new StringBuilder("def "); + StringBuilder builder = new StringBuilder(); + for (String decorator : myDecorators) { + builder.append(decorator).append("\n"); + } + builder.append("def "); builder.append(myName).append("("); builder.append(StringUtil.join(myParameters, ", ")); builder.append("):"); @@ -66,4 +74,8 @@ public class PyFunctionBuilder { } return builder.toString(); } + + public void decorate(String decoratorName) { + myDecorators.add("@" + decoratorName); + } } diff --git a/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java b/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java index 50a905b86bdc..4e2be57e9329 100644 --- a/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java +++ b/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java @@ -5,6 +5,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiWhiteSpace; import com.intellij.util.containers.HashSet; import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.intentions.*; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyQualifiedName; @@ -133,7 +134,7 @@ public class UnsupportedFeatures extends PyAnnotator { final PsiElement firstChild = node.getFirstChild(); if (firstChild != null) { final String name = firstChild.getText(); - if ("super".equals(name)) { + if (PyNames.SUPER.equals(name)) { final PyArgumentList argumentList = node.getArgumentList(); if (argumentList != null && argumentList.getArguments().length == 0) { getHolder().createWarningAnnotation(node, "super() should have arguments in Python 2"); diff --git a/python/testData/override/classmethod_after.py b/python/testData/override/classmethod_after.py index 3e72c7c12bbd..dd85fe6b083e 100644 --- a/python/testData/override/classmethod_after.py +++ b/python/testData/override/classmethod_after.py @@ -6,4 +6,4 @@ class A: class B(A): @classmethod def foo(cls): - pass + A.foo(cls) diff --git a/python/testData/override/newStyle.py b/python/testData/override/newStyle.py new file mode 100644 index 000000000000..24296d1825f8 --- /dev/null +++ b/python/testData/override/newStyle.py @@ -0,0 +1,6 @@ +class A(object): + def m(self): + pass + +class B(A): + pass \ No newline at end of file diff --git a/python/testData/override/newStyle_after.py b/python/testData/override/newStyle_after.py new file mode 100644 index 000000000000..12bf56e375ce --- /dev/null +++ b/python/testData/override/newStyle_after.py @@ -0,0 +1,8 @@ +class A(object): + def m(self): + pass + +class B(A): + def m(self): + super(B, self).m() + diff --git a/python/testData/override/py3k.py b/python/testData/override/py3k.py new file mode 100644 index 000000000000..d68058a7e0b7 --- /dev/null +++ b/python/testData/override/py3k.py @@ -0,0 +1,6 @@ +class A: + def m(self): + pass + +class B(A): + pass \ No newline at end of file diff --git a/python/testData/override/py3k_after.py b/python/testData/override/py3k_after.py new file mode 100644 index 000000000000..70a4635e681c --- /dev/null +++ b/python/testData/override/py3k_after.py @@ -0,0 +1,8 @@ +class A: + def m(self): + pass + +class B(A): + def m(self): + super().m() + diff --git a/python/testData/override/simple_after.py b/python/testData/override/simple_after.py index 82fdc7c86029..90e017899c8d 100644 --- a/python/testData/override/simple_after.py +++ b/python/testData/override/simple_after.py @@ -3,7 +3,7 @@ class A: class B(A): def doStuff(self): - pass + A.doStuff(self) def otherMethod(self, foo, bar): print foo, bar diff --git a/python/testSrc/com/jetbrains/python/PyOverrideTest.java b/python/testSrc/com/jetbrains/python/PyOverrideTest.java index c7417c1e6889..713d64db4cdc 100644 --- a/python/testSrc/com/jetbrains/python/PyOverrideTest.java +++ b/python/testSrc/com/jetbrains/python/PyOverrideTest.java @@ -3,9 +3,11 @@ package com.jetbrains.python; import com.jetbrains.python.codeInsight.override.PyMethodMember; import com.jetbrains.python.codeInsight.override.PyOverrideImplementUtil; import com.jetbrains.python.fixtures.PyLightFixtureTestCase; +import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; import java.util.Collections; import java.util.List; @@ -14,21 +16,35 @@ import java.util.List; * @author yole */ public class PyOverrideTest extends PyLightFixtureTestCase { - private void doTest() throws Exception { + private void doTest() { myFixture.configureByFile("override/" + getTestName(true) + ".py"); PyFile file = (PyFile) myFixture.getFile(); List classes = file.getTopLevelClasses(); PyFunction toOverride = classes.get(0).getMethods() [0]; PyOverrideImplementUtil.overrideMethods(myFixture.getEditor(), classes.get(1), Collections.singletonList(new PyMethodMember(toOverride))); - myFixture.checkResultByFile("override/" + getTestName(true) + "_after.py"); + myFixture.checkResultByFile("override/" + getTestName(true) + "_after.py", true); } - public void testSimple() throws Exception { + public void testSimple() { doTest(); } - public void testClassmethod() throws Exception { + public void testClassmethod() { doTest(); } + + public void testNewStyle() { + doTest(); + } + + public void testPy3k() { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), LanguageLevel.PYTHON31); + try { + doTest(); + } + finally { + PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), null); + } + } } From 8b3b17cb26340a498e3db93d1014550a59160cc2 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 12 Jul 2010 21:09:15 +0400 Subject: [PATCH 2/4] class name completion also works for top-level functions (PY-539) --- .../PyClassNameCompletionContributor.java | 110 ++++++++++++------ .../className/function/function.after.py | 3 + .../completion/className/function/function.py | 1 + .../className/function/mypackage/__init__.py | 6 + .../python/PyClassNameCompletionTest.java | 22 ++-- 5 files changed, 95 insertions(+), 47 deletions(-) create mode 100644 python/testData/completion/className/function/function.after.py create mode 100644 python/testData/completion/className/function/function.py create mode 100644 python/testData/completion/className/function/mypackage/__init__.py diff --git a/python/src/com/jetbrains/python/codeInsight/PyClassNameCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/PyClassNameCompletionContributor.java index 3a5aecdc7821..f5573cdb5907 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyClassNameCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/PyClassNameCompletionContributor.java @@ -3,23 +3,29 @@ package com.jetbrains.python.codeInsight; import com.intellij.codeInsight.completion.*; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.navigation.NavigationItem; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Iconable; +import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.PsiReference; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; +import com.intellij.psi.stubs.StubIndex; +import com.intellij.psi.stubs.StubIndexKey; import com.intellij.util.ProcessingContext; import com.jetbrains.python.actions.AddImportHelper; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyQualifiedName; import com.jetbrains.python.psi.resolve.ResolveImportUtil; import com.jetbrains.python.psi.stubs.PyClassNameIndex; +import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -37,46 +43,79 @@ public class PyClassNameCompletionContributor extends CompletionContributor { protected void addCompletions(@NotNull final CompletionParameters parameters, ProcessingContext context, @NotNull final CompletionResultSet resultSet) { - final Project project = parameters.getOriginalFile().getProject(); - final GlobalSearchScope scope = ProjectScope.getAllScope(project); - final Collection allKeys = ApplicationManager.getApplication().runReadAction(new Computable>() { - public Collection compute() { - return PyClassNameIndex.allKeys(project); - } - }); - for (final String className : allKeys) { - if (resultSet.getPrefixMatcher().prefixMatches(className)) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final Collection classes = PyClassNameIndex.find(className, project, scope); - for (PyClass aClass : classes) { - resultSet.addElement(LookupElementBuilder.create(aClass) - .setIcon(aClass.getIcon(Iconable.ICON_FLAG_CLOSED)) - .setTailText(" " + aClass.getPresentation().getLocationString(), true) - .setInsertHandler(INSERT_HANDLER)); - } - } - }); - } - } + addVariantsFromIndex(resultSet, parameters.getOriginalFile(), PyClassNameIndex.KEY, CLASS_INSERT_HANDLER, Condition.TRUE); + addVariantsFromIndex(resultSet, parameters.getOriginalFile(), PyFunctionNameIndex.KEY, FUNCTION_INSERT_HANDLER, TOPLEVEL_FUNCTION); } }); } - private static class PyClassNameInsertHandler implements InsertHandler { - public void handleInsert(final InsertionContext context, final LookupElement item) { - final PsiReference ref = context.getFile().findReferenceAt(context.getTailOffset() - 1); - if (ref == null || ref.resolve() == item.getObject()) { - // no import statement needed - return; - } - new WriteCommandAction(context.getProject(), context.getFile()) { - @Override - protected void run(Result result) throws Throwable { - addImport((PsiNamedElement) item.getObject(), context.getFile(), (PyElement) ref.getElement()); - } - }.execute(); + private static Condition TOPLEVEL_FUNCTION = new Condition() { + @Override + public boolean value(PyFunction pyFunction) { + return pyFunction.isTopLevel(); } + }; + + private static void addVariantsFromIndex(final CompletionResultSet resultSet, + final PsiFile targetFile, + final StubIndexKey key, + final InsertHandler insertHandler, + final Condition condition) { + final Project project = targetFile.getProject(); + final GlobalSearchScope scope = ProjectScope.getAllScope(project); + final Collection allNames = ApplicationManager.getApplication().runReadAction(new Computable>() { + public Collection compute() { + return StubIndex.getInstance().getAllKeys(key, project); + } + }); + for (final String elementName : allNames) { + if (resultSet.getPrefixMatcher().prefixMatches(elementName)) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final Collection elements = StubIndex.getInstance().get(key, elementName, project, scope); + for (T element : elements) { + if (condition.value(element)) { + resultSet.addElement(LookupElementBuilder.create(element) + .setIcon(element.getIcon(Iconable.ICON_FLAG_CLOSED)) + .setTailText(" " + ((NavigationItem)element).getPresentation().getLocationString(), true) + .setInsertHandler(insertHandler)); + } + } + } + }); + } + } + } + + private static final InsertHandler CLASS_INSERT_HANDLER = new InsertHandler() { + public void handleInsert(final InsertionContext context, final LookupElement item) { + addImportForLookupElement(context, item, context.getTailOffset() - 1); + } + }; + + + private static final InsertHandler FUNCTION_INSERT_HANDLER = new PyFunctionInsertHandler() { + public void handleInsert(final InsertionContext context, final LookupElement item) { + int tailOffset = context.getTailOffset()-1; + super.handleInsert(context, item); // adds parentheses, modifies tail offset + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(context.getProject()); + documentManager.commitDocument(documentManager.getDocument(context.getFile())); + addImportForLookupElement(context, item, tailOffset); + } + }; + + private static void addImportForLookupElement(final InsertionContext context, final LookupElement item, final int tailOffset) { + final PsiReference ref = context.getFile().findReferenceAt(tailOffset); + if (ref == null || ref.resolve() == item.getObject()) { + // no import statement needed + return; + } + new WriteCommandAction(context.getProject(), context.getFile()) { + @Override + protected void run(Result result) throws Throwable { + addImport((PsiNamedElement) item.getObject(), context.getFile(), (PyElement) ref.getElement()); + } + }.execute(); } private static void addImport(final PsiNamedElement target, final PsiFile file, final PyElement element) { @@ -101,5 +140,4 @@ public class PyClassNameCompletionContributor extends CompletionContributor { } } - private static final PyClassNameInsertHandler INSERT_HANDLER = new PyClassNameInsertHandler(); } diff --git a/python/testData/completion/className/function/function.after.py b/python/testData/completion/className/function/function.after.py new file mode 100644 index 000000000000..2635ca6d32c9 --- /dev/null +++ b/python/testData/completion/className/function/function.after.py @@ -0,0 +1,3 @@ +from mypackage import shazam + +shazam() \ No newline at end of file diff --git a/python/testData/completion/className/function/function.py b/python/testData/completion/className/function/function.py new file mode 100644 index 000000000000..73b815896ae5 --- /dev/null +++ b/python/testData/completion/className/function/function.py @@ -0,0 +1 @@ +shaz \ No newline at end of file diff --git a/python/testData/completion/className/function/mypackage/__init__.py b/python/testData/completion/className/function/mypackage/__init__.py new file mode 100644 index 000000000000..3f9f1aa84468 --- /dev/null +++ b/python/testData/completion/className/function/mypackage/__init__.py @@ -0,0 +1,6 @@ +def shazam(): + pass + +class MyClass: + def shazzy(self): # should not be offered as completion variant + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java b/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java index 357196902512..054b04d2b0ca 100644 --- a/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java @@ -11,11 +11,6 @@ import com.jetbrains.python.fixtures.PyLightFixtureTestCase; public class PyClassNameCompletionTest extends PyLightFixtureTestCase { private boolean myOldAutocompleteValue; - @Override - protected String getTestDataPath() { - return PythonTestUtil.getTestDataPath() + "/completion/className/"; - } - @Override protected void setUp() throws Exception { super.setUp(); @@ -30,15 +25,15 @@ public class PyClassNameCompletionTest extends PyLightFixtureTestCase { super.tearDown(); } - public void testSimple() throws Exception { + public void testSimple() { doTest(); } - public void testReuseExisting() throws Exception { + public void testReuseExisting() { doTest(); } - public void testQualified() throws Exception { + public void testQualified() { final PyCodeInsightSettings settings = PyCodeInsightSettings.getInstance(); boolean oldValue = settings.PREFER_FROM_IMPORT; settings.PREFER_FROM_IMPORT = false; @@ -50,10 +45,15 @@ public class PyClassNameCompletionTest extends PyLightFixtureTestCase { } } - private void doTest() throws Exception { - myFixture.copyDirectoryToProject(getTestName(true), ""); + public void testFunction() { + doTest(); + } + + private void doTest() { + final String path = "/completion/className/" + getTestName(true); + myFixture.copyDirectoryToProject(path, ""); myFixture.configureFromTempProjectFile(getTestName(true) + ".py"); myFixture.complete(CompletionType.CLASS_NAME); - myFixture.checkResultByFile(getTestName(true) + "/" + getTestName(true) + ".after.py"); + myFixture.checkResultByFile(path + "/" + getTestName(true) + ".after.py"); } } From 4d69d8d6c743bfe1e96010b6a066d9e4db0d697c Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 12 Jul 2010 21:33:26 +0400 Subject: [PATCH 3/4] parameter is not unused if function has either super methods or inherited methods (PY-1234) --- ...yUnusedLocalVariableInspectionVisitor.java | 57 +++++++++++-------- .../search/PySuperMethodsSearchExecutor.java | 23 ++++++-- 2 files changed, 51 insertions(+), 29 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyUnusedLocalVariableInspectionVisitor.java b/python/src/com/jetbrains/python/inspections/PyUnusedLocalVariableInspectionVisitor.java index 3597cbda8c6f..ff9986d73581 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnusedLocalVariableInspectionVisitor.java +++ b/python/src/com/jetbrains/python/inspections/PyUnusedLocalVariableInspectionVisitor.java @@ -26,6 +26,7 @@ import com.jetbrains.python.psi.impl.PyAugAssignmentStatementNavigator; import com.jetbrains.python.psi.impl.PyForStatementNavigator; import com.jetbrains.python.psi.impl.PyImportStatementNavigator; import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; import com.jetbrains.python.psi.search.PySuperMethodsSearch; import org.jetbrains.annotations.NotNull; @@ -55,36 +56,20 @@ class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor { processScope(PsiTreeUtil.getParentOfType(node, ScopeOwner.class), node); } - class DontPerformException extends RuntimeException {} + static class DontPerformException extends RuntimeException {} private void processScope(final ScopeOwner owner, final PyElement node) { if (owner.getContainingFile() instanceof PyExpressionCodeFragment || PydevConsoleRunner.isInPydevConsole(owner)){ return; } - // Check for locals() call - try { - owner.acceptChildren(new PyRecursiveElementVisitor(){ - @Override - public void visitPyCallExpression(final PyCallExpression node) { - if ("locals".equals(node.getCallee().getText())){ - throw new DontPerformException(); - } - } - @Override - public void visitPyFunction(final PyFunction node) { - // stop here - } - }); - } - catch (DontPerformException e) { - return; - } + if (callsLocals(owner)) return; - // If method overrides others do not mark parameters as unused if they are - boolean parametersCanBeUnused = false; + // If method overrides others or is overridden, do not mark parameters as unused if they are + boolean parametersCanBeUnused = true; if (owner instanceof PyFunction) { - parametersCanBeUnused = PySuperMethodsSearch.search(((PyFunction)owner)).findFirst() != null; + parametersCanBeUnused = PySuperMethodsSearch.search(((PyFunction)owner)).findFirst() == null && + PyOverridingMethodsSearch.search((PyFunction) owner, true).findFirst() == null; } final Scope scope = owner.getScope(); @@ -115,7 +100,7 @@ class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor { } final ReadWriteInstruction.ACCESS access = ((ReadWriteInstruction)instruction).getAccess(); // WriteAccess - if (access.isWriteAccess() && (parametersCanBeUnused || !(element != null && element.getParent() instanceof PyNamedParameter))) { + if (access.isWriteAccess() && (parametersCanBeUnused || !isParameter(element))) { if (!myUsedElements.contains(element)){ myUnusedElements.add(element); } @@ -185,6 +170,32 @@ class PyUnusedLocalVariableInspectionVisitor extends PyInspectionVisitor { } } + private static boolean callsLocals(ScopeOwner owner) { + try { + owner.acceptChildren(new PyRecursiveElementVisitor(){ + @Override + public void visitPyCallExpression(final PyCallExpression node) { + if ("locals".equals(node.getCallee().getText())){ + throw new DontPerformException(); + } + } + + @Override + public void visitPyFunction(final PyFunction node) { + // stop here + } + }); + } + catch (DontPerformException e) { + return true; + } + return false; + } + + private static boolean isParameter(PsiElement element) { + return element != null && (element instanceof PyNamedParameter || element.getParent() instanceof PyNamedParameter); + } + void registerProblems() { // Register problems for (PsiElement element : myUnusedElements) { diff --git a/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java b/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java index 0bdc19179cb7..53da9d1eeb92 100644 --- a/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java +++ b/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java @@ -6,6 +6,9 @@ import com.intellij.util.QueryExecutor; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; +import java.util.HashSet; +import java.util.Set; + /** * @author yole */ @@ -14,15 +17,23 @@ public class PySuperMethodsSearchExecutor implements QueryExecutor foundMethodContainingClasses = new HashSet(); if (name != null && containingClass != null) { - PyClass[] superClasses = containingClass.getSuperClasses(); - if (superClasses != null) { - for(PyClass superClass: superClasses) { - PyFunction superMethod = superClass.findMethodByName(name, false); - if (superMethod != null) { - if (!consumer.process(superMethod)) return false; + for (PyClass superClass : containingClass.iterateAncestors()) { + boolean isAlreadyFound = false; + for (PyClass alreadyFound : foundMethodContainingClasses) { + if (alreadyFound.isSubclass(superClass)) { + isAlreadyFound = true; } } + if (isAlreadyFound) { + continue; + } + PyFunction superMethod = superClass.findMethodByName(name, false); + if (superMethod != null) { + foundMethodContainingClasses.add(superClass); + if (!consumer.process(superMethod)) return false; + } } } return true; From d3bbdc97ef5fee026b59eb2e7da5e58c0f2e9513 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Mon, 12 Jul 2010 22:42:12 +0400 Subject: [PATCH 4/4] rename refactoring handles class inheritance (PY-1236) --- .../psi/search/PySuperMethodsSearch.java | 15 +++++- .../search/PySuperMethodsSearchExecutor.java | 16 ++++--- .../rename/RenamePyFunctionProcessor.java | 47 +++++++++++++++++++ .../refactoring/rename/renameInheritors.py | 5 ++ .../rename/renameInheritors_after.py | 5 ++ .../python/refactoring/PyRenameTest.java | 26 +++++----- 6 files changed, 94 insertions(+), 20 deletions(-) create mode 100644 python/testData/refactoring/rename/renameInheritors.py create mode 100644 python/testData/refactoring/rename/renameInheritors_after.py diff --git a/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearch.java b/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearch.java index 6c1d81fc208a..8c58ac650eb9 100644 --- a/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearch.java +++ b/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearch.java @@ -13,14 +13,20 @@ public class PySuperMethodsSearch extends ExtensibleQueryFactory search(final PyFunction derivedMethod) { - final SearchParameters parameters = new SearchParameters(derivedMethod); + final SearchParameters parameters = new SearchParameters(derivedMethod, false); + return INSTANCE.createUniqueResultsQuery(parameters); + } + + public static Query search(final PyFunction derivedMethod, boolean deepSearch) { + final SearchParameters parameters = new SearchParameters(derivedMethod, deepSearch); return INSTANCE.createUniqueResultsQuery(parameters); } } diff --git a/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java b/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java index 53da9d1eeb92..7f7f48fbc2f5 100644 --- a/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java +++ b/python/src/com/jetbrains/python/psi/search/PySuperMethodsSearchExecutor.java @@ -20,14 +20,16 @@ public class PySuperMethodsSearchExecutor implements QueryExecutor foundMethodContainingClasses = new HashSet(); if (name != null && containingClass != null) { for (PyClass superClass : containingClass.iterateAncestors()) { - boolean isAlreadyFound = false; - for (PyClass alreadyFound : foundMethodContainingClasses) { - if (alreadyFound.isSubclass(superClass)) { - isAlreadyFound = true; + if (!queryParameters.isDeepSearch()) { + boolean isAlreadyFound = false; + for (PyClass alreadyFound : foundMethodContainingClasses) { + if (alreadyFound.isSubclass(superClass)) { + isAlreadyFound = true; + } + } + if (isAlreadyFound) { + continue; } - } - if (isAlreadyFound) { - continue; } PyFunction superMethod = superClass.findMethodByName(name, false); if (superMethod != null) { diff --git a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java index 6e52bbbabeea..820220c6ce53 100644 --- a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java @@ -1,9 +1,19 @@ package com.jetbrains.python.refactoring.rename; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.ui.Messages; import com.intellij.psi.PsiElement; import com.intellij.refactoring.rename.RenamePsiElementProcessor; +import com.intellij.util.Processor; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; +import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; +import com.jetbrains.python.psi.search.PySuperMethodsSearch; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; /** * @author yole @@ -38,4 +48,41 @@ public class RenamePyFunctionProcessor extends RenamePsiElementProcessor { public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) { PyCodeInsightSettings.getInstance().RENAME_SEARCH_NON_CODE_FOR_FUNCTION = enabled; } + + @Override + public PsiElement substituteElementToRename(PsiElement element, Editor editor) { + PyFunction function = (PyFunction) element; + final PyClass containingClass = function.getContainingClass(); + if (containingClass == null) { + return function; + } + final List superMethods = new ArrayList(PySuperMethodsSearch.search(function, true).findAll()); + if (superMethods.size() > 0) { + // TODO this is not exactly right for multiple inheritance + final PyFunction deepestSuperMethod = (PyFunction) superMethods.get(superMethods.size()-1); + String message = "Method " + function.getName() + " of class " + containingClass.getQualifiedName() + "\noverrides method of class " + + deepestSuperMethod.getContainingClass().getQualifiedName() + ".\nDo you want to rename the base method?"; + int rc = Messages.showYesNoCancelDialog(element.getProject(), message, "Rename", Messages.getQuestionIcon()); + if (rc == 0) { + return deepestSuperMethod; + } + if (rc == 1) { + return function; + } + return null; + } + return function; + } + + @Override + public void prepareRenaming(PsiElement element, final String newName, final Map allRenames) { + PyFunction function = (PyFunction) element; + PyOverridingMethodsSearch.search(function, true).forEach(new Processor() { + @Override + public boolean process(PyFunction pyFunction) { + allRenames.put(pyFunction, newName); + return true; + } + }); + } } diff --git a/python/testData/refactoring/rename/renameInheritors.py b/python/testData/refactoring/rename/renameInheritors.py new file mode 100644 index 000000000000..b6d7d3700cf9 --- /dev/null +++ b/python/testData/refactoring/rename/renameInheritors.py @@ -0,0 +1,5 @@ +class A: + def foo(self): pass + +class B(A): + def foo(self): pass \ No newline at end of file diff --git a/python/testData/refactoring/rename/renameInheritors_after.py b/python/testData/refactoring/rename/renameInheritors_after.py new file mode 100644 index 000000000000..4c43885db799 --- /dev/null +++ b/python/testData/refactoring/rename/renameInheritors_after.py @@ -0,0 +1,5 @@ +class A: + def qu(self): pass + +class B(A): + def qu(self): pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java index 4d606044672f..ada298dca901 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java @@ -8,13 +8,11 @@ import com.jetbrains.python.fixtures.PyLightFixtureTestCase; * @author yole */ public class PyRenameTest extends PyLightFixtureTestCase { - public void testRenameField() throws Exception { // PY-457 - myFixture.configureByFile("refactoring/rename/" + getTestName(true) + ".py"); - myFixture.renameElementAtCaret("qu"); - myFixture.checkResultByFile("refactoring/rename/" + getTestName(true) + "_after.py"); + public void testRenameField() { // PY-457 + doTest("qu"); } - public void testSearchInStrings() throws Exception { // PY-670 + public void testSearchInStrings() { // PY-670 myFixture.configureByFile("refactoring/rename/" + getTestName(true) + ".py"); final PsiElement element = TargetElementUtilBase.findTargetElement(myFixture.getEditor(), TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED | TargetElementUtilBase.ELEMENT_NAME_ACCEPTED); @@ -23,15 +21,21 @@ public class PyRenameTest extends PyLightFixtureTestCase { myFixture.checkResultByFile("refactoring/rename/" + getTestName(true) + "_after.py"); } - public void testRenameParameter() throws Exception { // PY-385 - myFixture.configureByFile("refactoring/rename/" + getTestName(true) + ".py"); - myFixture.renameElementAtCaret("qu"); - myFixture.checkResultByFile("refactoring/rename/" + getTestName(true) + "_after.py"); + public void testRenameParameter() { // PY-385 + doTest("qu"); } - public void testRenameMultipleDefinitionsLocal() throws Exception { // PY-727 + public void testRenameMultipleDefinitionsLocal() { // PY-727 + doTest("qu"); + } + + public void testRenameInheritors() { + doTest("qu"); + } + + private void doTest(final String newName) { myFixture.configureByFile("refactoring/rename/" + getTestName(true) + ".py"); - myFixture.renameElementAtCaret("qu"); + myFixture.renameElementAtCaret(newName); myFixture.checkResultByFile("refactoring/rename/" + getTestName(true) + "_after.py"); } }