mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
PY-33843 Fix problem with extracting constant from comprehension
(cherry picked from commit 72bb8e523471c9922ae3d8f95f7fa9ce1fe0009d) IJ-MR-18836 GitOrigin-RevId: dadb7cf489a0501b834cff7e10979fab2c8ae8cd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d667021b15
commit
07ebb19aee
@@ -41,6 +41,7 @@ import com.intellij.refactoring.listeners.RefactoringEventData;
|
||||
import com.intellij.refactoring.listeners.RefactoringEventListener;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
@@ -48,6 +49,7 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.types.PyCallableParameter;
|
||||
import com.jetbrains.python.psi.types.PyNoneType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
@@ -295,8 +297,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
}
|
||||
|
||||
element1 = PyRefactoringUtil.getSelectedExpression(project, file, element1, element2);
|
||||
final PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(element1, PyComprehensionElement.class, true);
|
||||
if (element1 == null || comprehension != null) {
|
||||
if (element1 == null || referencesComprehensionIteratorValue(element1)) {
|
||||
showCannotPerformError(project, editor);
|
||||
return;
|
||||
}
|
||||
@@ -333,6 +334,45 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
performActionOnElement(operation);
|
||||
}
|
||||
|
||||
private static boolean referencesComprehensionIteratorValue(@NotNull PsiElement element) {
|
||||
PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(element, PyComprehensionElement.class, true);
|
||||
if (comprehension != null) {
|
||||
List<PyExpression> iteratorVariables = ContainerUtil.map(comprehension.getForComponents(), it -> it.getIteratorVariable());
|
||||
List<PsiElement> referencedInSelection = collectReferencedDefinitionsInSameFile(element, element.getContainingFile());
|
||||
if (iteratorVariables.contains(element) || ContainerUtil.intersects(referencedInSelection, iteratorVariables)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static @NotNull List<PsiElement> collectReferencedDefinitionsInSameFile(@NotNull PsiElement element, @NotNull PsiFile file) {
|
||||
PsiElement selectionElement = getOriginalSelectionCoveringElement(element);
|
||||
TextRange textRange = getTextRangeForOperationElement(element);
|
||||
|
||||
return StreamEx.of(PsiTreeUtil.collectElementsOfType(selectionElement, PyReferenceExpression.class))
|
||||
.filter(it -> textRange.contains(it.getTextRange()))
|
||||
.filter(ref -> !ref.isQualified())
|
||||
.flatMap(expr -> PyResolveUtil.resolveLocally(expr).stream())
|
||||
.filter(it -> it != null && it.getContainingFile() == file)
|
||||
.toList();
|
||||
}
|
||||
|
||||
protected static @NotNull TextRange getTextRangeForOperationElement(@NotNull PsiElement operationElement) {
|
||||
var userData = operationElement.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
|
||||
if (userData == null || userData.first == null || userData.second == null) {
|
||||
return operationElement.getTextRange();
|
||||
}
|
||||
else {
|
||||
return userData.second.shiftRight(userData.first.getTextOffset());
|
||||
}
|
||||
}
|
||||
|
||||
protected static @NotNull PsiElement getOriginalSelectionCoveringElement(@NotNull PsiElement operationElement) {
|
||||
var userData = operationElement.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
|
||||
return userData == null ? operationElement : userData.first;
|
||||
}
|
||||
|
||||
private static boolean breaksStringFormatting(@NotNull String s, @NotNull TextRange range) {
|
||||
return breaksRanges(substitutionsToRanges(filterSubstitutions(parsePercentFormat(s))), range);
|
||||
}
|
||||
@@ -421,8 +461,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
|
||||
if (call != null && call.getCallee() == element) {
|
||||
return false;
|
||||
}
|
||||
final PyComprehensionElement comprehension = PsiTreeUtil.getParentOfType(element, PyComprehensionElement.class, true);
|
||||
if (comprehension != null) {
|
||||
if (referencesComprehensionIteratorValue(element)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
+2
-29
@@ -67,7 +67,7 @@ public class PyIntroduceConstantHandler extends IntroduceHandler {
|
||||
assert containingFile instanceof PyFile;
|
||||
PsiElement initialPosition = AddImportHelper.getFileInsertPosition((PyFile)containingFile);
|
||||
|
||||
List<PsiElement> sameFileRefs = collectReferencedDefinitionsInSameFile(operation);
|
||||
List<PsiElement> sameFileRefs = collectReferencedDefinitionsInSameFile(operation.getElement(), operation.getFile());
|
||||
PsiElement maxPosition = getLowermostTopLevelStatement(sameFileRefs);
|
||||
|
||||
if (maxPosition == null) {
|
||||
@@ -102,7 +102,7 @@ public class PyIntroduceConstantHandler extends IntroduceHandler {
|
||||
Editor editor = operation.getEditor();
|
||||
if (editor == null) return false;
|
||||
|
||||
List<PsiElement> sameFileRefs = collectReferencedDefinitionsInSameFile(operation);
|
||||
List<PsiElement> sameFileRefs = collectReferencedDefinitionsInSameFile(operation.getElement(), operation.getFile());
|
||||
if (!ContainerUtil.all(sameFileRefs, it -> PyUtil.isTopLevel(it))) return false;
|
||||
PsiElement maxPosition = getLowermostTopLevelStatement(sameFileRefs);
|
||||
if (maxPosition == null) return true;
|
||||
@@ -110,33 +110,6 @@ public class PyIntroduceConstantHandler extends IntroduceHandler {
|
||||
!PsiTreeUtil.isAncestor(maxPosition, selectionElement, false);
|
||||
}
|
||||
|
||||
private static @NotNull List<PsiElement> collectReferencedDefinitionsInSameFile(@NotNull IntroduceOperation operation) {
|
||||
PsiElement selectionElement = getOriginalSelectionCoveringElement(operation.getElement());
|
||||
TextRange textRange = getTextRangeForOperationElement(operation.getElement());
|
||||
|
||||
return StreamEx.of(PsiTreeUtil.collectElementsOfType(selectionElement, PyReferenceExpression.class))
|
||||
.filter(it -> textRange.contains(it.getTextRange()))
|
||||
.filter(ref -> !ref.isQualified())
|
||||
.flatMap(expr -> PyResolveUtil.resolveLocally(expr).stream())
|
||||
.filter(it -> it != null && it.getContainingFile() == operation.getFile())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static @NotNull TextRange getTextRangeForOperationElement(@NotNull PsiElement operationElement) {
|
||||
var userData = operationElement.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
|
||||
if (userData == null || userData.first == null || userData.second == null) {
|
||||
return operationElement.getTextRange();
|
||||
}
|
||||
else {
|
||||
return userData.second.shiftRight(userData.first.getTextOffset());
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull PsiElement getOriginalSelectionCoveringElement(@NotNull PsiElement operationElement) {
|
||||
var userData = operationElement.getUserData(PyReplaceExpressionUtil.SELECTION_BREAKS_AST_NODE);
|
||||
return userData == null ? operationElement : userData.first;
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement getLowermostTopLevelStatement(@NotNull List<PsiElement> elements) {
|
||||
return StreamEx.of(elements)
|
||||
.map(it -> PyPsiUtils.getParentRightBefore(it, it.getContainingFile()))
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
a = 'constant'
|
||||
|
||||
|
||||
def func(xs):
|
||||
return [x for x in xs if x.startswith(a)]
|
||||
@@ -0,0 +1,2 @@
|
||||
def func(xs):
|
||||
return [x for x in xs if x.startswith(<selection>'constant'</selection>)]
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
def foo():
|
||||
a = range(0, 42)<caret>
|
||||
return [x + 1 for x in a]
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
return [x + 1 for x in <selection>range(0, 42)</selection>]
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
return [x + 1 for x in range(0, 42) if <selection>x > 3</selection>]
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
return [<selection>x + 1</selection> for x in range(0, 42)]
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
y = 42
|
||||
|
||||
def foo():
|
||||
a = y + 3<caret>
|
||||
return [x + 1 for x in range(0, 42) if x > a]
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
y = 42
|
||||
|
||||
def foo():
|
||||
return [x + 1 for x in range(0, 42) if x > <selection>y + 3</selection>]
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
y = 42
|
||||
|
||||
def foo():
|
||||
a = y + 1<caret>
|
||||
return [x + a for x in range(0, 42)]
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
y = 42
|
||||
|
||||
def foo():
|
||||
return [x + <selection>y + 1</selection> for x in range(0, 42)]
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
return [x + y for x in range(42) for y in <selection>range(x)</selection>]
|
||||
@@ -0,0 +1,2 @@
|
||||
def foo():
|
||||
return [x + 1 for <selection>x</selection> in range(0, 42)]
|
||||
@@ -119,6 +119,11 @@ public class PyIntroduceConstantTest extends PyIntroduceTestCase {
|
||||
doTestThrowsRefactoringErrorHintException();
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testStringLiteralArgumentInComprehension() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
private void doTestThrowsRefactoringErrorHintException() {
|
||||
assertThrows(CommonRefactoringUtil.RefactoringErrorHintException.class, () -> doTest());
|
||||
}
|
||||
|
||||
@@ -262,6 +262,41 @@ public class PyIntroduceVariableTest extends PyIntroduceTestCase {
|
||||
runWithLanguageLevel(LanguageLevel.PYTHON27, this::doTest);
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testIteratorVariableInComprehension() {
|
||||
doTestCannotPerform();
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testIteratorDependentExpressionInComprehensionIfComponent() {
|
||||
doTestCannotPerform();
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testIteratorDependentExpressionInComprehensionResult() {
|
||||
doTestCannotPerform();
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testIteratorFreeExpressionInComprehensionIfComponent() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testIteratorFreeExpressionInComprehensionResult() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testExpressionInComprehensionIteratedList() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
// PY-33843
|
||||
public void testIteratorVariableDependentExpressionInComprehensionIteratedList() {
|
||||
doTestCannotPerform();
|
||||
}
|
||||
|
||||
private void doTestCannotPerform() {
|
||||
boolean thrownExpectedException = false;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user