PY-21479 Properly resolve references in f-strings inside comprehensions

Namely, choose the correct anchor PSI element for CFG-sensitive local
resolve process: not the whole statement that contains the comprehension
but rather the closest ancestor result part of the comprehension because
if the referenced symbol is defined inside the comprehension, the
corresponding vertex will be below the statement in the graph not above
it.
This commit is contained in:
Mikhail Golubev
2016-12-26 17:13:21 +03:00
parent e6f4abb954
commit 7ca673715e
5 changed files with 39 additions and 4 deletions

View File

@@ -30,9 +30,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
import com.jetbrains.python.psi.PyQualifiedExpression;
import com.jetbrains.python.psi.PyStatement;
import com.jetbrains.python.psi.PyStringLiteralExpression;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.PyUtil.StringNodeInfo;
import com.jetbrains.python.psi.impl.references.PyReferenceImpl;
import com.jetbrains.python.psi.resolve.*;
@@ -43,6 +41,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static com.jetbrains.python.psi.PyUtil.as;
/**
* User : ktisha
*/
@@ -100,9 +100,22 @@ public class PyDocReference extends PyReferenceImpl {
@Nullable
private PsiElement getScopeControlFlowAnchor(@NotNull PsiLanguageInjectionHost host) {
return isInsideFormattedStringNode(host) ? PsiTreeUtil.getParentOfType(host, PyStatement.class) : null;
if (isInsideFormattedStringNode(host)) {
// Comprehension's result expression is preserved in CFG: this anchor is necessary for flow-sensitive getResultsFromProcessor()
final PsiElement parentComprehensionResult = PsiTreeUtil.findFirstParent(host, PyDocReference::isComprehensionResult);
if (parentComprehensionResult != null) {
return parentComprehensionResult;
}
return PsiTreeUtil.getParentOfType(host, PyStatement.class);
}
return null;
}
private static boolean isComprehensionResult(@NotNull PsiElement element) {
final PyComprehensionElement comprehension = as(element.getParent(), PyComprehensionElement.class);
return comprehension != null && comprehension.getResultExpression() == element;
}
private boolean isInsideFormattedStringNode(@NotNull PsiLanguageInjectionHost host) {
if (host instanceof PyStringLiteralExpression) {
final ASTNode node = findContainingStringNode(getElement(), (PyStringLiteralExpression)host);

View File

@@ -0,0 +1,3 @@
foo = 42
xs = [x for x in f'{foo}']
<ref>

View File

@@ -0,0 +1,2 @@
xs = [f'{foo}' + foo for foo in range(10)]
<ref>

View File

@@ -0,0 +1,2 @@
xs = [[x for x in f'{foo}'] for foo in range(10)]
<ref>

View File

@@ -64,7 +64,22 @@ public class PyInjectionResolveTest extends PyResolveTestCase {
public void testFStringNestedScopes() {
assertResolvesTo(LanguageLevel.PYTHON36, PyTargetExpression.class, "foo");
}
// PY-21479
public void testFStringComprehensionTarget() {
assertResolvesTo(LanguageLevel.PYTHON36, PyTargetExpression.class, "foo");
}
// PY-21479
public void testFStringComprehensionSourcePart() {
assertResolvesTo(LanguageLevel.PYTHON36, PyTargetExpression.class, "foo");
}
// PY-21479
public void testFStringNestedComprehensionSourcePart() {
assertResolvesTo(LanguageLevel.PYTHON36, PyTargetExpression.class, "foo");
}
public void testTypeCommentReference() {
assertResolvesTo(PyClass.class, "MyClass");
}