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/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 62b0f1414d56..5f7a16601a3e 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -193,6 +193,9 @@ INTN.specify.returt.type.in.annotation=Specify return type 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/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/completion/PyKeywordCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java index ac303b484ffb..a4a566300d4a 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)); @@ -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) , 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/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/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()) { 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 9441948095bb..50641b85f76b 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 */ @@ -40,10 +49,21 @@ 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); } }; } + + @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); + } } 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/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/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/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/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/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))); 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 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; } } } 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/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/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/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/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/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" + 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 { diff --git a/python/testSrc/com/jetbrains/python/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/PyIntentionTest.java index 2e0a3d5e7fbe..af4120b4deb1 100644 --- a/python/testSrc/com/jetbrains/python/PyIntentionTest.java +++ b/python/testSrc/com/jetbrains/python/PyIntentionTest.java @@ -269,6 +269,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; diff --git a/python/testSrc/com/jetbrains/python/PyResolveTest.java b/python/testSrc/com/jetbrains/python/PyResolveTest.java index ab3a496bf146..7cdfec822b1f 100644 --- a/python/testSrc/com/jetbrains/python/PyResolveTest.java +++ b/python/testSrc/com/jetbrains/python/PyResolveTest.java @@ -477,6 +477,10 @@ public class PyResolveTest extends PyResolveTestCase { assertResolvesTo(PyClass.class, "timedelta"); } + public void testShadowingTargetExpression() { + assertResolvesTo(PyTargetExpression.class, "lab"); + } + public void testReferenceInDocstring() { assertResolvesTo(PyClass.class, "datetime"); } 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")); + } } diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java index fd71ca35dfbe..e9be5974a0e9 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java @@ -235,4 +235,14 @@ 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"); + } + + // PY-7382 + public void testYield33() { + doTest("bar", LanguageLevel.PYTHON33); + } }