From 451e92581da5c715444b538cadab4be4346542af Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 25 Jan 2013 16:59:55 +0400 Subject: [PATCH 1/4] Fixed false positive in unbound vars inspection for assert False with argument (PY-7784) --- .../codeInsight/controlflow/PyControlFlowBuilder.java | 2 +- .../codeInsight/controlflow/assertfalseargument.py | 4 ++++ .../codeInsight/controlflow/assertfalseargument.txt | 7 +++++++ .../com/jetbrains/python/PyControlFlowBuilderTest.java | 5 +++++ 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 python/testData/codeInsight/controlflow/assertfalseargument.py create mode 100644 python/testData/codeInsight/controlflow/assertfalseargument.txt diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index f000baff0d1c..ab4b19508a98 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -688,7 +688,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { super.visitPyAssertStatement(node); final PyExpression[] args = node.getArguments(); // assert False - if (args.length == 1 && PyConstantExpressionEvaluator.evaluate(args[0]) == Boolean.FALSE) { + if (args.length >= 1 && PyConstantExpressionEvaluator.evaluate(args[0]) == Boolean.FALSE) { abruptFlow(node); return; } diff --git a/python/testData/codeInsight/controlflow/assertfalseargument.py b/python/testData/codeInsight/controlflow/assertfalseargument.py new file mode 100644 index 000000000000..297988954a04 --- /dev/null +++ b/python/testData/codeInsight/controlflow/assertfalseargument.py @@ -0,0 +1,4 @@ +assert False, 'foo' +print('unreachable 1') +assert False, f() +print('unreachable 2') diff --git a/python/testData/codeInsight/controlflow/assertfalseargument.txt b/python/testData/codeInsight/controlflow/assertfalseargument.txt new file mode 100644 index 000000000000..c6cc78ddd935 --- /dev/null +++ b/python/testData/codeInsight/controlflow/assertfalseargument.txt @@ -0,0 +1,7 @@ +0(1) element: null +1(6) READ ACCESS: False +2(3) element: PyPrintStatement +3(4) READ ACCESS: False +4(6) READ ACCESS: f +5(6) element: PyPrintStatement +6() element: null diff --git a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java index 6ae12dbcf71a..da0bb6533ef8 100644 --- a/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java +++ b/python/testSrc/com/jetbrains/python/PyControlFlowBuilderTest.java @@ -215,6 +215,11 @@ public class PyControlFlowBuilderTest extends LightMarkedTestCase { doTestFirstStatement(); } + // PY-7784 + public void testAssertFalseArgument() { + doTest(); + } + private void doTestFirstStatement() { final String testName = getTestName(false).toLowerCase(); configureByFile(testName + ".py"); From 20aacf1c247583bdfdd61c1a9087c18b0241c416 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Fri, 25 Jan 2013 18:18:29 +0400 Subject: [PATCH 2/4] Fixed skeleton signatures for format() methods of string and bytes classes (PY-8328) --- python/helpers/StdlibTypes2.properties | 7 +++++++ python/helpers/generator3.py | 8 +++++++- python/helpers/required_gen_version | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/python/helpers/StdlibTypes2.properties b/python/helpers/StdlibTypes2.properties index a6a9ed1b9873..633c8d0cfb9a 100644 --- a/python/helpers/StdlibTypes2.properties +++ b/python/helpers/StdlibTypes2.properties @@ -677,6 +677,9 @@ __builtin__.str.find = \ :type end: int or long or None \n\ :rtype: int \n\ +__builtin__.str.format = \ + :rtype: bytes \n\ + __builtin__.str.index = \ :type sub: string \n\ :type start: int or long or None \n\ @@ -817,10 +820,14 @@ __builtin__.unicode.__rmul__ = \ __builtin__.unicode.__rmod__ = \ :type y: object \n\ :rtype: unicode \n\ + __builtin__.unicode.__getitem__ = \ :type y: int \n\ :rtype: unicode \n\ +__builtin__.unicode.format = \ + :rtype: unicode \n\ + __builtin__.unicode.capitalize = \ :rtype: unicode \n\ diff --git a/python/helpers/generator3.py b/python/helpers/generator3.py index 5ffc1099a77a..02cdb91f4f8c 100644 --- a/python/helpers/generator3.py +++ b/python/helpers/generator3.py @@ -24,7 +24,7 @@ but seemingly no one uses them in C extensions yet anyway. # * re.search-bound, ~30% time, in likes of builtins and _gtk with complex docstrings. # None of this can seemingly be easily helped. Maybe there's a simpler and faster parser library? -VERSION = "1.121" # Must be a number-dot-number string, updated with each change that affects generated skeletons +VERSION = "1.122" # Must be a number-dot-number string, updated with each change that affects generated skeletons # Note: DON'T FORGET TO UPDATE! import sys @@ -817,9 +817,15 @@ class ModuleRedeclarator(object): PREDEFINED_BUILTIN_SIGS[(None, "max")] = "(*args, key=None)" PREDEFINED_BUILTIN_SIGS[(None, "open")] = "(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True)" PREDEFINED_BUILTIN_SIGS[("str", "__init__")] = "(self, value='', encoding=None, errors='strict')" # overrides a fake + PREDEFINED_BUILTIN_SIGS[("str", "format")] = "(*args, **kwargs)" PREDEFINED_BUILTIN_SIGS[("bytes", "__init__")] = "(self, value=b'', encoding=None, errors='strict')" # overrides a fake + PREDEFINED_BUILTIN_SIGS[("bytes", "format")] = "(*args, **kwargs)" PREDEFINED_BUILTIN_SIGS[(None, "print")] = "(*args, sep=' ', end='\\n', file=None)" # proper signature + if version >= (2, 6) and version < (3, 0): + PREDEFINED_BUILTIN_SIGS[("unicode", "format")] = "(*args, **kwargs)" + PREDEFINED_BUILTIN_SIGS[("str", "format")] = "(*args, **kwargs)" + if version == (2, 5): PREDEFINED_BUILTIN_SIGS[("unicode", "splitlines")] = "(keepends=None)" # a typo in docstring there diff --git a/python/helpers/required_gen_version b/python/helpers/required_gen_version index 81a2f2cbb8f3..f974ed5ae1a3 100644 --- a/python/helpers/required_gen_version +++ b/python/helpers/required_gen_version @@ -6,7 +6,7 @@ (default) 1.92 # anything not explicitly marked -(built-in) 1.119 # skeletons of all built-in modules are built together +(built-in) 1.122 # skeletons of all built-in modules are built together # Note: modules like itertools, etc are "(built-in)" and are ignored if given separately _fileio 1.101 From a9a26725575f4955b6671118f00a40843305cd27 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 28 Jan 2013 15:40:00 +0400 Subject: [PATCH 3/4] Fixed rename for old-style properties and properties with lambdas (PY-8315) --- .../src/com/jetbrains/python/psi/PyClass.java | 4 +- .../python/psi/impl/PyClassImpl.java | 8 ++-- .../python/psi/impl/PyFunctionImpl.java | 2 +- .../impl/references/PyQualifiedReference.java | 41 +++++++++++-------- .../rename/RenamePyFunctionProcessor.java | 15 ++++--- .../rename/RenamePyVariableProcessor.java | 28 ++++++++++++- .../rename/renameOldStyleProperty.py | 18 ++++++++ .../rename/renameOldStyleProperty_after.py | 18 ++++++++ .../rename/renamePropertyWithLambda.py | 5 +++ .../rename/renamePropertyWithLambda_after.py | 5 +++ .../python/refactoring/PyRenameTest.java | 10 +++++ 11 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 python/testData/refactoring/rename/renameOldStyleProperty.py create mode 100644 python/testData/refactoring/rename/renameOldStyleProperty_after.py create mode 100644 python/testData/refactoring/rename/renamePropertyWithLambda.py create mode 100644 python/testData/refactoring/rename/renamePropertyWithLambda_after.py diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java index b70a4604d3f8..62e9f3735bc8 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyClass.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyClass.java @@ -136,11 +136,11 @@ public interface PyClass extends PsiNameIdentifierOwner, PyStatement, NameDefine /** * Non-recursively searches for a property for which the given function is a getter, setter or deleter. * - * @param function the function which may be an accessor + * @param callable the function which may be an accessor * @return the property, or null */ @Nullable - Property findPropertyByFunction(PyFunction function); + Property findPropertyByCallable(Callable callable); /** * @param parent diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 4643f69118dc..1c9e818fe85e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -662,14 +662,14 @@ public class PyClassImpl extends PyPresentableElementImpl implement } @Override - public Property findPropertyByFunction(PyFunction function) { + public Property findPropertyByCallable(Callable callable) { if (myPropertyCache == null) { myPropertyCache = initializePropertyCache(); } for (Property property : myPropertyCache.values()) { - if (property.getGetter().valueOrNull() == function || - property.getSetter().valueOrNull() == function || - property.getDeleter().valueOrNull() == function) { + if (property.getGetter().valueOrNull() == callable || + property.getSetter().valueOrNull() == callable || + property.getDeleter().valueOrNull() == callable) { return property; } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index 40cd988060cd..e15e43208c03 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -493,7 +493,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl imp public Property getProperty() { final PyClass containingClass = getContainingClass(); if (containingClass != null) { - return containingClass.findPropertyByFunction(this); + return containingClass.findPropertyByCallable(this); } return null; } diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java index a626ffbf1fab..de36cbafed9b 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyQualifiedReference.java @@ -8,10 +8,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.ProjectScope; import com.intellij.psi.stubs.StubUpdatingIndex; @@ -376,17 +373,6 @@ public class PyQualifiedReference extends PyReferenceImpl { if (isLocalScope(element)) { return false; } - PsiElement resolveResult = resolve(); - if (resolveResult instanceof PyImportedModule) { - resolveResult = resolveResult.getNavigationElement(); - } - if (element instanceof PsiDirectory && resolveResult instanceof PyFile && - PyNames.INIT_DOT_PY.equals(((PyFile)resolveResult).getName()) && ((PyFile)resolveResult).getContainingDirectory() == element) { - return true; - } - if (resolveResult == element) { - return true; - } final String referencedName = myElement.getReferencedName(); if (element instanceof PyFunction && Comparing.equal(referencedName, ((PyFunction)element).getName()) && ((PyFunction)element).getContainingClass() != null && !PyNames.INIT.equals(referencedName)) { @@ -399,6 +385,30 @@ public class PyQualifiedReference extends PyReferenceImpl { } } } + for (ResolveResult result : multiResolve(false)) { + if (result instanceof ImplicitResolveResult) { + continue; + } + PsiElement resolveResult = result.getElement(); + if (isResolvedToResult(element, resolveResult)) { + return true; + } + } + + return false; + } + + private boolean isResolvedToResult(PsiElement element, PsiElement resolveResult) { + if (resolveResult instanceof PyImportedModule) { + resolveResult = resolveResult.getNavigationElement(); + } + if (element instanceof PsiDirectory && resolveResult instanceof PyFile && + PyNames.INIT_DOT_PY.equals(((PyFile)resolveResult).getName()) && ((PyFile)resolveResult).getContainingDirectory() == element) { + return true; + } + if (resolveResult == element) { + return true; + } if (resolveResult instanceof PyTargetExpression && isInstanceOrClassAttribute((PyTargetExpression)resolveResult) && element instanceof PyTargetExpression && isInstanceOrClassAttribute((PyTargetExpression)element) && Comparing.equal( ((PyTargetExpression)resolveResult).getReferencedName(), @@ -415,7 +425,6 @@ public class PyQualifiedReference extends PyReferenceImpl { if (resolvesToWrapper(element, resolveResult)) { return true; } - return false; } diff --git a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java index 1ffe17db7623..692f5ce44650 100644 --- a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java @@ -6,10 +6,7 @@ import com.intellij.psi.PsiElement; import com.intellij.util.Processor; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; -import com.jetbrains.python.psi.Callable; -import com.jetbrains.python.psi.Property; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.search.PyOverridingMethodsSearch; import com.jetbrains.python.psi.search.PySuperMethodsSearch; import com.jetbrains.python.toolbox.Maybe; @@ -78,6 +75,14 @@ public class RenamePyFunctionProcessor extends RenamePyElementProcessor { } return null; } + final Property property = containingClass.findPropertyByCallable(function); + final PyTargetExpression site; + if (property != null) { + site = property.getDefinitionSite(); + if (site != null) { + return site; + } + } return function; } @@ -93,7 +98,7 @@ public class RenamePyFunctionProcessor extends RenamePyElementProcessor { }); final PyClass containingClass = function.getContainingClass(); if (containingClass != null) { - final Property property = containingClass.findPropertyByFunction(function); + final Property property = containingClass.findPropertyByCallable(function); if (property != null) { addRename(allRenames, newName, property.getGetter()); addRename(allRenames, newName, property.getSetter()); diff --git a/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java b/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java index fb377fb8ca19..c1bd1af6ec20 100644 --- a/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/rename/RenamePyVariableProcessor.java @@ -1,10 +1,13 @@ package com.jetbrains.python.refactoring.rename; +import com.intellij.openapi.editor.Editor; import com.intellij.psi.PsiElement; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; -import com.jetbrains.python.psi.PyElement; -import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; +import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author yole @@ -35,4 +38,25 @@ public class RenamePyVariableProcessor extends RenamePyElementProcessor { public void setToSearchForTextOccurrences(PsiElement element, boolean enabled) { PyCodeInsightSettings.getInstance().RENAME_SEARCH_NON_CODE_FOR_VARIABLE = enabled; } + + @Nullable + @Override + public PsiElement substituteElementToRename(PsiElement element, @Nullable Editor editor) { + if (element instanceof PyLambdaExpression) { + final PyLambdaExpression lambdaExpression = (PyLambdaExpression)element; + final ScopeOwner owner = ScopeUtil.getScopeOwner(lambdaExpression); + if (owner instanceof PyClass) { + final PyClass cls = (PyClass)owner; + final Property property = cls.findPropertyByCallable(lambdaExpression); + if (property != null) { + final PyTargetExpression site = property.getDefinitionSite(); + if (site != null) { + return site; + } + } + } + return null; + } + return element; + } } diff --git a/python/testData/refactoring/rename/renameOldStyleProperty.py b/python/testData/refactoring/rename/renameOldStyleProperty.py new file mode 100644 index 000000000000..57b9326c7e07 --- /dev/null +++ b/python/testData/refactoring/rename/renameOldStyleProperty.py @@ -0,0 +1,18 @@ +class C(object): + def __init__(self): + self._foo = 'foo' + + def get_foo(self): + return self._foo + + def set_foo(self, value): + self._foo = value + + def __str__(self): + return self.foo + + foo = property(get_foo, set_foo) + + +c = C() +print(c.foo) diff --git a/python/testData/refactoring/rename/renameOldStyleProperty_after.py b/python/testData/refactoring/rename/renameOldStyleProperty_after.py new file mode 100644 index 000000000000..e477cf88024f --- /dev/null +++ b/python/testData/refactoring/rename/renameOldStyleProperty_after.py @@ -0,0 +1,18 @@ +class C(object): + def __init__(self): + self._foo = 'foo' + + def get_foo(self): + return self._foo + + def set_foo(self, value): + self._foo = value + + def __str__(self): + return self.bar + + bar = property(get_foo, set_foo) + + +c = C() +print(c.bar) diff --git a/python/testData/refactoring/rename/renamePropertyWithLambda.py b/python/testData/refactoring/rename/renamePropertyWithLambda.py new file mode 100644 index 000000000000..504045ba1e87 --- /dev/null +++ b/python/testData/refactoring/rename/renamePropertyWithLambda.py @@ -0,0 +1,5 @@ +class C(object): + foo = property(lambda self: 'bar') + +c = C() +print(c.foo, c.foo) diff --git a/python/testData/refactoring/rename/renamePropertyWithLambda_after.py b/python/testData/refactoring/rename/renamePropertyWithLambda_after.py new file mode 100644 index 000000000000..f3e69d292d93 --- /dev/null +++ b/python/testData/refactoring/rename/renamePropertyWithLambda_after.py @@ -0,0 +1,5 @@ +class C(object): + bar = property(lambda self: 'bar') + +c = C() +print(c.bar, c.bar) diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java index da1c07175ce3..efa699c65442 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java @@ -137,6 +137,16 @@ public class PyRenameTest extends PyTestCase { doRenameConflictTest("foo", "A variable named 'foo' is already defined in variableNameConflict.py"); } + // PY-8315 + public void testRenamePropertyWithLambda() { + doTest("bar"); + } + + // PY-8315 + public void testRenameOldStyleProperty() { + doTest("bar"); + } + private void doRenameConflictTest(String newName, String expectedConflict) { myFixture.configureByFile("refactoring/rename/" + getTestName(true) + ".py"); try { From 43d78ce40f9433db21bfbcd7659bfe6d11095b9e Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 28 Jan 2013 15:52:19 +0400 Subject: [PATCH 4/4] Ask the user if he wants to rename the old-style property itself or its getter/setter function --- .../rename/RenamePyFunctionProcessor.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java index 692f5ce44650..b2178b03179f 100644 --- a/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/rename/RenamePyFunctionProcessor.java @@ -1,5 +1,6 @@ package com.jetbrains.python.refactoring.rename; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.ui.Messages; import com.intellij.psi.PsiElement; @@ -80,7 +81,17 @@ public class RenamePyFunctionProcessor extends RenamePyElementProcessor { if (property != null) { site = property.getDefinitionSite(); if (site != null) { - return site; + if (ApplicationManager.getApplication().isUnitTestMode()) { + return site; + } + final String message = String.format("Do you want to rename the property '%s' instead of its accessor function '%s'?", + property.getName(), function.getName()); + final int rc = Messages.showYesNoCancelDialog(element.getProject(), message, "Rename", Messages.getQuestionIcon()); + switch (rc) { + case 0: return site; + case 1: return function; + default: return null; + } } } return function;