From 84d0677d4e342106939285fa164335222d88195e Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 13:23:40 +0200 Subject: [PATCH 01/15] correct formatting of relative imports (PY-7367) --- .../python/formatter/PythonFormattingModelBuilder.java | 1 + python/testData/formatter/fromImportRelative.py | 1 + python/testData/formatter/fromImportRelative_after.py | 1 + python/testSrc/com/jetbrains/python/PyFormatterTest.java | 4 ++++ 4 files changed, 7 insertions(+) create mode 100644 python/testData/formatter/fromImportRelative.py create mode 100644 python/testData/formatter/fromImportRelative_after.py diff --git a/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java b/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java index 24a2156dd216..ccc1c132a79c 100644 --- a/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java +++ b/python/src/com/jetbrains/python/formatter/PythonFormattingModelBuilder.java @@ -77,6 +77,7 @@ public class PythonFormattingModelBuilder implements FormattingModelBuilderEx, C .before(COLON).spaceIf(pySettings.SPACE_BEFORE_PY_COLON) .after(COMMA).spaceIf(commonSettings.SPACE_AFTER_COMMA) .before(COMMA).spaceIf(commonSettings.SPACE_BEFORE_COMMA) + .between(FROM_KEYWORD, DOT).spaces(1) .around(DOT).spaces(0) .before(SEMICOLON).spaceIf(commonSettings.SPACE_BEFORE_SEMICOLON) .withinPairInside(LPAR, RPAR, ARGUMENT_LIST).spaceIf(commonSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES) diff --git a/python/testData/formatter/fromImportRelative.py b/python/testData/formatter/fromImportRelative.py new file mode 100644 index 000000000000..1c634a0c0f0c --- /dev/null +++ b/python/testData/formatter/fromImportRelative.py @@ -0,0 +1 @@ +from..foo import foo2 \ No newline at end of file diff --git a/python/testData/formatter/fromImportRelative_after.py b/python/testData/formatter/fromImportRelative_after.py new file mode 100644 index 000000000000..d35a86678d71 --- /dev/null +++ b/python/testData/formatter/fromImportRelative_after.py @@ -0,0 +1 @@ +from ..foo import foo2 \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java index 324b7d666b82..55e6f9299ef6 100644 --- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java +++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java @@ -171,6 +171,10 @@ public class PyFormatterTest extends PyTestCase { doTest(); } + public void testFromImportRelative() { + doTest(); + } + public void testPsiFormatting() { // IDEA-69724 String initial = "def method_name(\n" + From b58eb735b3cada92a96b4956f04a3e2caee207df Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 13:42:49 +0200 Subject: [PATCH 02/15] consistent UI between creating virtualenv and specifying options for added virtualenv (PY-7365) --- .../python/sdk/CreateVirtualEnvDialog.form | 21 +++++++++++++------ .../python/sdk/CreateVirtualEnvDialog.java | 17 +++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.form b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.form index 848e1d846df8..bd84b37f8011 100644 --- a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.form +++ b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.form @@ -1,6 +1,6 @@
- + @@ -16,7 +16,7 @@ - + @@ -58,13 +58,13 @@ - + - + - - + + @@ -75,6 +75,15 @@ + + + + + + + + + diff --git a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java index f502453bcfbf..1891c7bd602b 100644 --- a/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java +++ b/python/src/com/jetbrains/python/sdk/CreateVirtualEnvDialog.java @@ -34,7 +34,8 @@ public class CreateVirtualEnvDialog extends IdeaDialog { private TextFieldWithBrowseButton myDestination; private JTextField myName; private JBCheckBox mySitePackagesCheckBox; - private JBCheckBox myAssociateCheckbox; + private JBCheckBox myMakeAvailableToAllProjectsCheckbox; + private JBCheckBox mySetAsProjectInterpreterCheckbox; private Project myProject; private String myInitialPath; @@ -45,13 +46,13 @@ public class CreateVirtualEnvDialog extends IdeaDialog { setTitle("Create Virtual Environment"); updateSdkList(sdk, allSdks); - myAssociateCheckbox.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0)); + myMakeAvailableToAllProjectsCheckbox.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0)); if (project.isDefault()) { - myAssociateCheckbox.setSelected(false); - myAssociateCheckbox.setVisible(false); + myMakeAvailableToAllProjectsCheckbox.setSelected(true); + myMakeAvailableToAllProjectsCheckbox.setVisible(false); } else if (isNewProject) { - myAssociateCheckbox.setText("Associate this virtual environment with the project being created"); + mySetAsProjectInterpreterCheckbox.setText("Set as project interpreter for the project being created"); } setOKActionEnabled(false); @@ -206,7 +207,11 @@ public class CreateVirtualEnvDialog extends IdeaDialog { } public boolean associateWithProject() { - return myAssociateCheckbox.isSelected(); + return !myMakeAvailableToAllProjectsCheckbox.isSelected(); + } + + public boolean setAsProjectInterpreter() { + return mySetAsProjectInterpreterCheckbox.isSelected(); } @Override From 5a0d7cdfe4cba92d9ffb79a784e3f1c0e98d96b5 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 30 Aug 2012 18:52:47 +0400 Subject: [PATCH 03/15] Fixed multiple compatibility messages for 'yield from' syntax (PY-7374) --- .../com/jetbrains/python/validation/CompatibilityVisitor.java | 1 + 1 file changed, 1 insertion(+) diff --git a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java index 6af4d9a1821e..1214f3995f30 100644 --- a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java +++ b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java @@ -484,6 +484,7 @@ public abstract class CompatibilityVisitor extends PyAnnotator { if (level.isOlderThan(LanguageLevel.PYTHON33)) { registerProblem(node, "Python versions < 3.3 do not support this syntax. Delegating to a subgenerator is available since " + "Python 3.3; use explicit iteration over subgenerator instead."); + break; } } } From a7e111a8b05ce59f60227f75fb69d038aa177ab1 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 17:15:19 +0200 Subject: [PATCH 04/15] resolve of target references doesn't go to functions or classes shadowed by the variable (PY-7342) --- .../python/psi/impl/references/PyTargetReference.java | 11 +++++++---- python/testData/resolve/ShadowingTargetExpression.py | 4 ++++ .../testSrc/com/jetbrains/python/PyResolveTest.java | 4 ++++ 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 python/testData/resolve/ShadowingTargetExpression.py diff --git a/python/src/com/jetbrains/python/psi/impl/references/PyTargetReference.java b/python/src/com/jetbrains/python/psi/impl/references/PyTargetReference.java index fddfb33533ed..aa54cb96c2ab 100644 --- a/python/src/com/jetbrains/python/psi/impl/references/PyTargetReference.java +++ b/python/src/com/jetbrains/python/psi/impl/references/PyTargetReference.java @@ -5,6 +5,8 @@ import com.intellij.psi.PsiElementResolveResult; import com.intellij.psi.ResolveResult; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFunction; import com.jetbrains.python.psi.PyImportElement; import com.jetbrains.python.psi.PyQualifiedExpression; import com.jetbrains.python.psi.resolve.PyResolveContext; @@ -22,15 +24,16 @@ public class PyTargetReference extends PyReferenceImpl { @Override public ResolveResult[] multiResolve(boolean incompleteCode) { final ResolveResult[] results = super.multiResolve(incompleteCode); - boolean resolvedToAnotherFile = false; + boolean shadowed = false; for (ResolveResult result : results) { final PsiElement element = result.getElement(); - if (element != null && element.getContainingFile() != myElement.getContainingFile()) { - resolvedToAnotherFile = true; + if (element != null && (element.getContainingFile() != myElement.getContainingFile() || + element instanceof PyFunction || element instanceof PyClass)) { + shadowed = true; break; } } - if (results.length > 0 && !resolvedToAnotherFile) { + if (results.length > 0 && !shadowed) { return results; } // resolve to self if no other target found diff --git a/python/testData/resolve/ShadowingTargetExpression.py b/python/testData/resolve/ShadowingTargetExpression.py new file mode 100644 index 000000000000..fb7b9ea3ac34 --- /dev/null +++ b/python/testData/resolve/ShadowingTargetExpression.py @@ -0,0 +1,4 @@ +def lab(): pass +lab = 1 +# +print(lab) diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index 3991c0d911c0..5b03696c98c0 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -476,4 +476,8 @@ public class PyResolveTest extends PyResolveTestCase { public void testKWArg() { assertResolvesTo(PyClass.class, "timedelta"); } + + public void testShadowingTargetExpression() { + assertResolvesTo(PyTargetExpression.class, "lab"); + } } From 5d5deeebfb0506384e963be5c2317777dfde4fba Mon Sep 17 00:00:00 2001 From: Dmitry Trofimov Date: Thu, 30 Aug 2012 17:29:23 +0200 Subject: [PATCH 05/15] Fixed NFE (EA-38726). --- .../python/remote/PythonRemoteSdkAdditionalData.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/remote/PythonRemoteSdkAdditionalData.java b/python/src/com/jetbrains/python/remote/PythonRemoteSdkAdditionalData.java index 825a19ccdc5e..8710e902b5d3 100644 --- a/python/src/com/jetbrains/python/remote/PythonRemoteSdkAdditionalData.java +++ b/python/src/com/jetbrains/python/remote/PythonRemoteSdkAdditionalData.java @@ -295,14 +295,14 @@ public class PythonRemoteSdkAdditionalData extends PythonSdkAdditionalData imple if (element != null) { data.setHost(element.getAttributeValue(HOST)); - data.setPort(Integer.parseInt(element.getAttributeValue(PORT))); - data.setAnonymous(Boolean.parseBoolean(element.getAttributeValue(ANONYMOUS))); + data.setPort(StringUtil.parseInt(element.getAttributeValue(PORT), 22)); + data.setAnonymous(StringUtil.parseBoolean(element.getAttributeValue(ANONYMOUS), false)); data.setSerializedUserName(element.getAttributeValue(USERNAME)); data.setSerializedPassword(element.getAttributeValue(PASSWORD)); data.setPrivateKeyFile(StringUtil.nullize(element.getAttributeValue(PRIVATE_KEY_FILE))); data.setKnownHostsFile(StringUtil.nullize(element.getAttributeValue(KNOWN_HOSTS_FILE))); data.setSerializedPassphrase(element.getAttributeValue(PASSPHRASE)); - data.setUseKeyPair(Boolean.parseBoolean(element.getAttributeValue(USE_KEY_PAIR))); + data.setUseKeyPair(StringUtil.parseBoolean(element.getAttributeValue(USE_KEY_PAIR), false)); data.setInterpreterPath(StringUtil.nullize(element.getAttributeValue(INTERPRETER_PATH))); data.setPyCharmTempFilesPath(StringUtil.nullize(element.getAttributeValue(PYCHARM_HELPERS_PATH))); From eb3d95629736d4367a2c6242f8d06f668bc973ef Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 17:47:51 +0200 Subject: [PATCH 06/15] a binary expression without a right operand is an incomplete block (PY-7360) --- .../jetbrains/python/formatter/PyBlock.java | 18 +++++++++++++----- .../com/jetbrains/python/PyIndentTest.java | 8 ++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java index 89dbfb394f9e..54c87f401d3e 100644 --- a/python/src/com/jetbrains/python/formatter/PyBlock.java +++ b/python/src/com/jetbrains/python/formatter/PyBlock.java @@ -549,11 +549,19 @@ public class PyBlock implements ASTBlock { } ASTNode lastChild = getLastNonSpaceChild(_node, false); - if (lastChild != null && lastChild.getElementType() == PyElementTypes.STATEMENT_LIST) { - // only multiline statement lists are considered incomplete - ASTNode statementListPrev = lastChild.getTreePrev(); - if (statementListPrev != null && statementListPrev.getText().indexOf('\n') >= 0) { - return true; + if (lastChild != null) { + if (lastChild.getElementType() == PyElementTypes.STATEMENT_LIST) { + // only multiline statement lists are considered incomplete + ASTNode statementListPrev = lastChild.getTreePrev(); + if (statementListPrev != null && statementListPrev.getText().indexOf('\n') >= 0) { + return true; + } + } + if (lastChild.getElementType() == PyElementTypes.BINARY_EXPRESSION) { + PyBinaryExpression binaryExpression = (PyBinaryExpression) lastChild.getPsi(); + if (binaryExpression.getRightExpression() == null) { + return true; + } } } diff --git a/python/testSrc/com/jetbrains/python/PyIndentTest.java b/python/testSrc/com/jetbrains/python/PyIndentTest.java index 25d343bc5b8d..6e5ef280655d 100644 --- a/python/testSrc/com/jetbrains/python/PyIndentTest.java +++ b/python/testSrc/com/jetbrains/python/PyIndentTest.java @@ -313,6 +313,14 @@ public class PyIndentTest extends PyTestCase { ""); } + public void testIndentOnBackslash() { // PY-7360 + doTest("def index():\n" + + " return 'some string' + \\", + "def index():\n" + + " return 'some string' + \\\n" + + " "); + } + /* TODO: formatter core problem? public void testAlignListBeforeEquals() throws Exception { From db169f46063f6c399cca850a583709ee878978c6 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 17:59:07 +0200 Subject: [PATCH 07/15] tune 'in function header' condition (PY-7370) --- .../codeInsight/completion/PyKeywordCompletionContributor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java index ac303b484ffb..f6155bf84737 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java @@ -216,7 +216,7 @@ public class PyKeywordCompletionContributor extends CompletionContributor { psiElement().inside(PyStringLiteralExpression.class); private static final PsiElementPattern.Capture IN_FUNCTION_HEADER = - psiElement().inside(PyFunction.class).andNot(psiElement().inside(PyStatementList.class)); + psiElement().inside(PyFunction.class).andNot(psiElement().inside(false, psiElement(PyStatementList.class), psiElement(PyFunction.class))); public static final PsiElementPattern.Capture AFTER_QUALIFIER = psiElement().afterLeaf(psiElement().withText(".").inside(PyReferenceExpression.class)); From 67be1e45e0b1a079a247ac46cb1f6524e6531be2 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 30 Aug 2012 21:06:58 +0400 Subject: [PATCH 08/15] Added intention for replacing explicit iteration with delegation to sub-generator for Python 3.3 (PY-7383) --- python/src/META-INF/python-plugin-common.xml | 5 + .../com/jetbrains/python/PyBundle.properties | 3 + .../intentions/PyYieldFromIntention.java | 108 ++++++++++++++++++ python/testData/intentions/afterYieldFrom.py | 4 + python/testData/intentions/beforeYieldFrom.py | 5 + .../com/jetbrains/python/PyIntentionTest.java | 5 + 6 files changed, 130 insertions(+) create mode 100644 python/src/com/jetbrains/python/codeInsight/intentions/PyYieldFromIntention.java create mode 100644 python/testData/intentions/afterYieldFrom.py create mode 100644 python/testData/intentions/beforeYieldFrom.py diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 3920ee02cd5c..de48e57c2e00 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -240,6 +240,11 @@ Python + + com.jetbrains.python.codeInsight.intentions.PyYieldFromIntention + Python + + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index a747d89160b7..4f6458af2e8e 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -191,6 +191,9 @@ INTN.specify.type.in.annotation=Specify type for reference using annotation #TypeAssertionIntention INTN.insert.assertion=Insert type assertion +#PyYieldFromIntention +INTN.yield.from=Transform explicit iteration with 'yield' into 'yield from' expression + # Conflict checker CONFLICT.name.$0.obscured=Name ''{0}'' obscured by local definitions CONFLICT.name.$0.obscured.cannot.convert=Name ''{0}'' obscured. Cannot convert. diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyYieldFromIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyYieldFromIntention.java new file mode 100644 index 000000000000..52c7356a8dc0 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyYieldFromIntention.java @@ -0,0 +1,108 @@ +package com.jetbrains.python.codeInsight.intentions; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author vlan + */ +public class PyYieldFromIntention extends BaseIntentionAction { + @NotNull + @Override + public String getFamilyName() { + return PyBundle.message("INTN.yield.from"); + } + + @NotNull + @Override + public String getText() { + return PyBundle.message("INTN.yield.from"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + if (LanguageLevel.forElement(file).isAtLeast(LanguageLevel.PYTHON33)) { + final PyForStatement forLoop = findForStatementAtCaret(editor, file); + if (forLoop != null) { + final PyTargetExpression forTarget = findSingleForLoopTarget(forLoop); + final PyReferenceExpression yieldValue = findSingleYieldValue(forLoop); + if (forTarget != null && yieldValue != null) { + final String targetName = forTarget.getName(); + if (targetName != null && targetName.equals(yieldValue.getName())) { + return true; + } + } + } + } + return false; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + final PyForStatement forLoop = findForStatementAtCaret(editor, file); + if (forLoop != null) { + final PyExpression source = forLoop.getForPart().getSource(); + if (source != null) { + final PyElementGenerator generator = PyElementGenerator.getInstance(project); + final String text = "yield from foo"; + final PyExpressionStatement exprStmt = generator.createFromText(LanguageLevel.forElement(file), PyExpressionStatement.class, text); + final PyExpression expr = exprStmt.getExpression(); + if (expr instanceof PyYieldExpression) { + final PyExpression yieldValue = ((PyYieldExpression)expr).getExpression(); + if (yieldValue != null) { + yieldValue.replace(source); + forLoop.replace(exprStmt); + } + } + } + } + } + + @Nullable + private static PyForStatement findForStatementAtCaret(@NotNull Editor editor, @NotNull PsiFile file) { + final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); + return PsiTreeUtil.getParentOfType(elementAtCaret, PyForStatement.class); + } + + @Nullable + private static PyTargetExpression findSingleForLoopTarget(@NotNull PyForStatement forLoop) { + final PyForPart forPart = forLoop.getForPart(); + final PyExpression forTarget = forPart.getTarget(); + if (forTarget instanceof PyTargetExpression) { + return (PyTargetExpression)forTarget; + } + return null; + } + + @Nullable + private static PyReferenceExpression findSingleYieldValue(@NotNull PyForStatement forLoop) { + final PyForPart forPart = forLoop.getForPart(); + final PyStatementList stmtList = forPart.getStatementList(); + if (stmtList != null && forLoop.getElsePart() == null) { + final PyStatement[] statements = stmtList.getStatements(); + if (statements.length == 1) { + final PyStatement firstStmt = statements[0]; + if (firstStmt instanceof PyExpressionStatement) { + final PyExpression firstExpr = ((PyExpressionStatement)firstStmt).getExpression(); + if (firstExpr instanceof PyYieldExpression) { + final PyYieldExpression yieldExpr = (PyYieldExpression)firstExpr; + final PyExpression yieldValue = yieldExpr.getExpression(); + if (yieldValue instanceof PyReferenceExpression) { + return (PyReferenceExpression)yieldValue; + } + } + } + } + } + return null; + } +} diff --git a/python/testData/intentions/afterYieldFrom.py b/python/testData/intentions/afterYieldFrom.py new file mode 100644 index 000000000000..7c246bec7a41 --- /dev/null +++ b/python/testData/intentions/afterYieldFrom.py @@ -0,0 +1,4 @@ +def f(g): + yield 'begin' + yield from g() + print('end') diff --git a/python/testData/intentions/beforeYieldFrom.py b/python/testData/intentions/beforeYieldFrom.py new file mode 100644 index 000000000000..b93f4b5550d7 --- /dev/null +++ b/python/testData/intentions/beforeYieldFrom.py @@ -0,0 +1,5 @@ +def f(g): + yield 'begin' + for x in g(): + yield x + print('end') diff --git a/python/testSrc/com/jetbrains/python/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/PyIntentionTest.java index cb69abafe97f..9ef848193884 100644 --- a/python/testSrc/com/jetbrains/python/PyIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/PyIntentionTest.java @@ -261,6 +261,11 @@ public class PyIntentionTest extends PyTestCase { doDocStubTest(); } + // PY-7383 + public void testYieldFrom() { + doTest(PyBundle.message("INTN.yield.from"), LanguageLevel.PYTHON33); + } + private void doDocStubTest() { CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance(); codeInsightSettings.JAVADOC_STUB_ON_ENTER = true; From fb233cb90ca724997dcca209aa85dee38b7b167a Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 30 Aug 2012 21:09:49 +0400 Subject: [PATCH 09/15] Disabled extract method refactoring for code fragments that contain 'yield' (PY-7381) --- .../codeFragment/PyCodeFragment.java | 9 ++++++++- .../codeFragment/PyCodeFragmentUtil.java | 18 +++++++++++++++--- .../controlflow/PyControlFlowBuilder.java | 9 +++++++++ .../extractmethod/PyExtractMethodHandler.java | 3 +-- .../refactoring/extractmethod/Yield.before.py | 6 ++++++ .../refactoring/PyExtractMethodTest.java | 5 +++++ 6 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 python/testData/refactoring/extractmethod/Yield.before.py diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragment.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragment.java index dc01ddba6036..1c8af2692966 100644 --- a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragment.java +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragment.java @@ -10,15 +10,18 @@ import java.util.Set; public class PyCodeFragment extends CodeFragment { private final Set myGlobalWrites; private final Set myNonlocalWrites; + private final boolean myYieldInside; public PyCodeFragment(final Set input, final Set output, final Set globalWrites, final Set nonlocalWrites, - final boolean returnInside) { + final boolean returnInside, + final boolean yieldInside) { super(input, output, returnInside); myGlobalWrites = globalWrites; myNonlocalWrites = nonlocalWrites; + myYieldInside = yieldInside; } public Set getGlobalWrites() { @@ -28,4 +31,8 @@ public class PyCodeFragment extends CodeFragment { public Set getNonlocalWrites() { return myNonlocalWrites; } + + public boolean isYieldInside() { + return myYieldInside; + } } diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java index 6dd64b2d2d37..6e532215f50e 100644 --- a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java @@ -82,7 +82,13 @@ public class PyCodeFragmentUtil { } } - return new PyCodeFragment(inputNames, outputNames, globalWrites, nonlocalWrites, subGraphAnalysis.returns > 0); + + final boolean yieldsFound = subGraphAnalysis.yieldExpressions > 0; + if (yieldsFound && LanguageLevel.forElement(owner).isOlderThan(LanguageLevel.PYTHON33)) { + throw new CannotCreateCodeFragmentException("Cannot perform refactoring with 'yield' statement inside code block"); + } + + return new PyCodeFragment(inputNames, outputNames, globalWrites, nonlocalWrites, subGraphAnalysis.returns > 0, yieldsFound); } private static boolean resolvesToBoundMethodParameter(@NotNull PsiElement element) { @@ -164,13 +170,15 @@ public class PyCodeFragmentUtil { private final int regularExits; private final int returns; private final int outerLoopBreaks; + private final int yieldExpressions; - public AnalysisResult(int starImports, int targetInstructions, int returns, int regularExits, int outerLoopBreaks) { + public AnalysisResult(int starImports, int targetInstructions, int returns, int regularExits, int outerLoopBreaks, int yieldExpressions) { this.starImports = starImports; this.targetInstructions = targetInstructions; this.regularExits = regularExits; this.returns = returns; this.outerLoopBreaks = outerLoopBreaks; + this.yieldExpressions = yieldExpressions; } } @@ -181,6 +189,7 @@ public class PyCodeFragmentUtil { final Set targetInstructions = new HashSet(); int starImports = 0; int outerLoopBreaks = 0; + int yieldExpressions = 0; for (Pair edge : getOutgoingEdges(subGraph)) { final Instruction sourceInstruction = edge.getFirst(); @@ -218,9 +227,12 @@ public class PyCodeFragmentUtil { outerLoopBreaks++; } } + if (element instanceof PyYieldExpression) { + yieldExpressions++; + } } - return new AnalysisResult(starImports, targetInstructions.size(), returnSources, regularSources, outerLoopBreaks); + return new AnalysisResult(starImports, targetInstructions.size(), returnSources, regularSources, outerLoopBreaks, yieldExpressions); } @NotNull diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index 1673b8f26977..3727f9936db8 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -425,6 +425,15 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { myBuilder.flowAbrupted(); } + @Override + public void visitPyYieldExpression(PyYieldExpression node) { + myBuilder.startNode(node); + final PyExpression expression = node.getExpression(); + if (expression != null) { + expression.accept(this); + } + } + @Override public void visitPyRaiseStatement(final PyRaiseStatement node) { myBuilder.startNode(node); diff --git a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodHandler.java b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodHandler.java index 5d94c22c6004..13851055b779 100644 --- a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodHandler.java +++ b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodHandler.java @@ -1,7 +1,6 @@ package com.jetbrains.python.refactoring.extractmethod; import com.intellij.codeInsight.codeFragment.CannotCreateCodeFragmentException; -import com.intellij.codeInsight.codeFragment.CodeFragment; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.*; import com.intellij.openapi.project.Project; @@ -93,7 +92,7 @@ public class PyExtractMethodHandler implements RefactoringActionHandler { if (owner == null) { return; } - final CodeFragment fragment; + final PyCodeFragment fragment; try { fragment = PyCodeFragmentUtil.createCodeFragment(owner, element1, element2); } diff --git a/python/testData/refactoring/extractmethod/Yield.before.py b/python/testData/refactoring/extractmethod/Yield.before.py new file mode 100644 index 000000000000..38ff1855adad --- /dev/null +++ b/python/testData/refactoring/extractmethod/Yield.before.py @@ -0,0 +1,6 @@ +def f(xs): + found = False + for x in xs: + yield x + found = True + print(found) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java index fd71ca35dfbe..4223c9a4fcd0 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java @@ -235,4 +235,9 @@ public class PyExtractMethodTest extends LightMarkedTestCase { public void testNonlocal() { doTest("baz", LanguageLevel.PYTHON30); } + + // PY-7381 + public void testYield() { + doFail("bar", "Cannot perform refactoring with 'yield' statement inside code block"); + } } From 0e7bad6ede667dd9a361cb1c0cdfe50d064e5304 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 30 Aug 2012 21:13:28 +0400 Subject: [PATCH 10/15] Added ability to extract sub-generator in Python 3.3 for extract method refactoring (PY-7382) --- .../extractmethod/PyExtractMethodUtil.java | 11 ++++++++++- .../refactoring/extractmethod/Yield33.after.py | 11 +++++++++++ .../refactoring/extractmethod/Yield33.before.py | 6 ++++++ .../python/refactoring/PyExtractMethodTest.java | 5 +++++ 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 python/testData/refactoring/extractmethod/Yield33.after.py create mode 100644 python/testData/refactoring/extractmethod/Yield33.before.py diff --git a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java index ca9a2e987751..d40240a60415 100644 --- a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java +++ b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java @@ -110,6 +110,9 @@ public class PyExtractMethodUtil { if (fragment.isReturnInstructionInside()) { builder.append("return "); } + if (fragment.isYieldInside()) { + builder.append("yield from "); + } if (isMethod) { appendSelf(firstElement, builder, isStaticMethod); } @@ -158,6 +161,9 @@ public class PyExtractMethodUtil { // Generate call element builder.append(" = "); + if (fragment.isYieldInside()) { + builder.append("yield from "); + } if (isMethod){ appendSelf(elementsRange.get(0), builder, isStaticMethod); } @@ -234,7 +240,7 @@ public class PyExtractMethodUtil { public static void extractFromExpression(final Project project, final Editor editor, - final CodeFragment fragment, + final PyCodeFragment fragment, final PsiElement expression) { if (!fragment.getOutputVariables().isEmpty()){ CommonRefactoringUtil.showErrorHint(project, editor, @@ -281,6 +287,9 @@ public class PyExtractMethodUtil { // Generating call element final StringBuilder builder = new StringBuilder(); builder.append("return "); + if (fragment.isYieldInside()) { + builder.append("yield from "); + } if (isMethod){ appendSelf(expression, builder, isStaticMethod); } diff --git a/python/testData/refactoring/extractmethod/Yield33.after.py b/python/testData/refactoring/extractmethod/Yield33.after.py new file mode 100644 index 000000000000..edec94a609ea --- /dev/null +++ b/python/testData/refactoring/extractmethod/Yield33.after.py @@ -0,0 +1,11 @@ +def bar(found_new, xs_new): + for x in xs_new: + yield x + found_new = True + return found_new + + +def f(xs): + found = False + found = yield from bar(found, xs) + print(found) \ No newline at end of file diff --git a/python/testData/refactoring/extractmethod/Yield33.before.py b/python/testData/refactoring/extractmethod/Yield33.before.py new file mode 100644 index 000000000000..38ff1855adad --- /dev/null +++ b/python/testData/refactoring/extractmethod/Yield33.before.py @@ -0,0 +1,6 @@ +def f(xs): + found = False + for x in xs: + yield x + found = True + print(found) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java index 4223c9a4fcd0..e9be5974a0e9 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java @@ -240,4 +240,9 @@ public class PyExtractMethodTest extends LightMarkedTestCase { public void testYield() { doFail("bar", "Cannot perform refactoring with 'yield' statement inside code block"); } + + // PY-7382 + public void testYield33() { + doTest("bar", LanguageLevel.PYTHON33); + } } From 36ebf9ef46f5e69f047ebb07ea6ec89f1b9e1f2e Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 19:22:17 +0200 Subject: [PATCH 11/15] findReferencesToHighlight() correctly handles dir/__init__.py duality (PY-1514) --- .../findUsages/PyModuleFindUsagesHandler.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java index 9441948095bb..689c77aa644d 100644 --- a/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java @@ -6,11 +6,20 @@ import com.intellij.find.findUsages.FindUsagesHandler; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFileSystemItem; +import com.intellij.psi.PsiReference; +import com.intellij.psi.search.SearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.ui.SimpleColoredComponent; import com.intellij.ui.SimpleTextAttributes; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.PyUtil; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + /** * @author yole */ @@ -46,4 +55,15 @@ public class PyModuleFindUsagesHandler extends FindUsagesHandler { } }; } + + @Override + public Collection findReferencesToHighlight(PsiElement target, SearchScope searchScope) { + if (target instanceof PyFile && PyNames.INIT_DOT_PY.equals(((PyFile)target).getName())) { + List result = new ArrayList(); + result.addAll(super.findReferencesToHighlight(target, searchScope)); + result.addAll(ReferencesSearch.search(PyUtil.turnInitIntoDir(target), searchScope, false).findAll()); + return result; + } + return super.findReferencesToHighlight(target, searchScope); + } } From 0134a6cd36e80c53b345cf702a689a7546e85c4a Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 19:48:27 +0200 Subject: [PATCH 12/15] performance: don't ask next TemplateContextProvider for references if previous one gave a satisfactory result (PY-7384) --- .../python/templateLanguages/TemplateContextProvider.java | 4 +++- .../testSrc/com/jetbrains/python/PythonCompletionTest.java | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/python/openapi/src/com/jetbrains/python/templateLanguages/TemplateContextProvider.java b/python/openapi/src/com/jetbrains/python/templateLanguages/TemplateContextProvider.java index 22ddb72e4cae..2b249bc7fd74 100644 --- a/python/openapi/src/com/jetbrains/python/templateLanguages/TemplateContextProvider.java +++ b/python/openapi/src/com/jetbrains/python/templateLanguages/TemplateContextProvider.java @@ -18,6 +18,7 @@ package com.jetbrains.python.templateLanguages; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.Nullable; import java.util.Collection; @@ -34,7 +35,8 @@ public interface TemplateContextProvider { * name of the variable; the object is the PsiElement declaring the variable. * * @param template the template file - * @return the list of variables + * @return the list of variables, or null if the template is not used in the context handled by this processor. */ + @Nullable Collection getTemplateContext(PsiFile template); } diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index d19648feefbf..dc59f43a9887 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -606,4 +606,10 @@ public class PythonCompletionTest extends PyTestCase { " pass\n" + "except IOError ").contains("as")); } + + public void testElseInFor() { // PY-6755 + assertTrue(doTestByText("for item in range(10):\n" + + " pass\n" + + "el").contains("else")); + } } From 961ab0943044696206e2bd4294b1466d0dda275d Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 20:01:50 +0200 Subject: [PATCH 13/15] complete 'else' after loop (PY-6755) --- .../completion/PyKeywordCompletionContributor.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java index f6155bf84737..a4a566300d4a 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java @@ -289,13 +289,8 @@ public class PyKeywordCompletionContributor extends CompletionContributor { private static final PsiElementPattern.Capture AFTER_IF = afterStatement(psiElement(PyIfStatement.class)); private static final PsiElementPattern.Capture AFTER_TRY = afterStatement(psiElement(PyTryExceptStatement.class)); - /* - private static final FilterPattern AFTER_LOOP_NO_ELSE = new FilterPattern(new PrecededByFilter( - psiElement() - .withChild(StandardPatterns.or(psiElement(PyWhileStatement.class), psiElement(PyForStatement.class))) - .withLastChild(StandardPatterns.not(psiElement(PyElsePart.class))) - )); - */ + private static final PsiElementPattern.Capture AFTER_LOOP_NO_ELSE = + afterStatement(psiElement(PyLoopStatement.class).withLastChild(StandardPatterns.not(psiElement(PyElsePart.class)))); private static final PsiElementPattern.Capture AFTER_COND_STMT_NO_ELSE = afterStatement(psiElement().withChild(psiElement(PyConditionalStatementPart.class)) @@ -485,7 +480,7 @@ public class PyKeywordCompletionContributor extends CompletionContributor { CompletionType.BASIC, psiElement() .withLanguage(PythonLanguage.getInstance()) .and(FIRST_ON_LINE) - .andOr(IN_COND_STMT, IN_TRY_BODY, IN_EXCEPT_BODY, AFTER_COND_STMT_NO_ELSE, AFTER_TRY_NO_ELSE) + .andOr(IN_COND_STMT, IN_TRY_BODY, IN_EXCEPT_BODY, AFTER_COND_STMT_NO_ELSE, AFTER_LOOP_NO_ELSE, AFTER_TRY_NO_ELSE) //.andNot(RIGHT_AFTER_COLON) .andNot(AFTER_QUALIFIER).andNot(IN_STRING_LITERAL) , From 7d868ff7f5c1d3da3404d424c63dbc769e69d5e5 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Thu, 30 Aug 2012 20:08:34 +0200 Subject: [PATCH 14/15] never generate return statement when overriding __init__ method (PY-2171) --- .../python/codeInsight/override/PyOverrideImplementUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java index 7143c5b9d762..ccba3e2c2efd 100644 --- a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java @@ -183,7 +183,7 @@ public class PyOverrideImplementUtil { statementBody.append(PyNames.PASS); } else { - if (baseFunction.getReturnType(TypeEvalContext.slow(), null) != PyNoneType.INSTANCE) { + if (!PyNames.INIT.equals(baseFunction.getName()) && baseFunction.getReturnType(TypeEvalContext.slow(), null) != PyNoneType.INSTANCE) { statementBody.append("return "); } if (baseClass.isNewStyleClass()) { From bd9c80cffdf48c294a70278d7a77b0489286059c Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Fri, 31 Aug 2012 14:12:06 +0400 Subject: [PATCH 15/15] cleanup, notnull --- .../jetbrains/python/findUsages/PyClassFindUsagesHandler.java | 2 +- .../python/findUsages/PyFunctionFindUsagesHandler.java | 2 +- .../jetbrains/python/findUsages/PyModuleFindUsagesHandler.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java index 72d52d3a9e49..2e618cd1b4b7 100644 --- a/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyClassFindUsagesHandler.java @@ -32,7 +32,7 @@ public class PyClassFindUsagesHandler extends FindUsagesHandler { } @Override - protected boolean isSearchForTextOccurencesAvailable(PsiElement psiElement, boolean isSingleFile) { + protected boolean isSearchForTextOccurencesAvailable(@NotNull PsiElement psiElement, boolean isSingleFile) { return true; } diff --git a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java index 0ab8c2c823f1..62bd457b594c 100644 --- a/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyFunctionFindUsagesHandler.java @@ -23,7 +23,7 @@ public class PyFunctionFindUsagesHandler extends FindUsagesHandler { } @Override - protected boolean isSearchForTextOccurencesAvailable(PsiElement psiElement, boolean isSingleFile) { + protected boolean isSearchForTextOccurencesAvailable(@NotNull PsiElement psiElement, boolean isSingleFile) { return true; } diff --git a/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java b/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java index 689c77aa644d..50641b85f76b 100644 --- a/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java +++ b/python/src/com/jetbrains/python/findUsages/PyModuleFindUsagesHandler.java @@ -49,7 +49,7 @@ public class PyModuleFindUsagesHandler extends FindUsagesHandler { isSingleFile, this) { @Override - public void configureLabelComponent(final SimpleColoredComponent coloredComponent) { + public void configureLabelComponent(@NotNull final SimpleColoredComponent coloredComponent) { coloredComponent.append(myElement instanceof PsiDirectory ? "Package " : "Module "); coloredComponent.append(myElement.getName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); }