mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-21175 Add write instructions for assignments to attributes in CFG
to handle, in particular, lazy initialization scenarios For instance, the statement "foo.bar = 42" should add a node "WRITE ACCESS foo.bar" containing the corresponding target expression. Additionally, "Unused local" inspection doesn't consider writes to attributes, since, strictly speaking, these are not local names.
This commit is contained in:
@@ -23,6 +23,7 @@ import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.ParamHelper;
|
||||
@@ -218,19 +219,16 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor {
|
||||
|
||||
@Override
|
||||
public void visitPyTargetExpression(final PyTargetExpression node) {
|
||||
final PsiElement[] children = node.getChildren();
|
||||
// Case of non qualified reference
|
||||
if (children.length == 0) {
|
||||
final ReadWriteInstruction.ACCESS access = node.getParent() instanceof PySliceExpression
|
||||
? ReadWriteInstruction.ACCESS.READ : ReadWriteInstruction.ACCESS.WRITE;
|
||||
final ReadWriteInstruction instruction = ReadWriteInstruction.newInstruction(myBuilder, node, node.getName(), access);
|
||||
myBuilder.addNode(instruction);
|
||||
myBuilder.checkPending(instruction);
|
||||
}
|
||||
else {
|
||||
for (PsiElement child : children) {
|
||||
child.accept(this);
|
||||
}
|
||||
final ReadWriteInstruction.ACCESS access = ReadWriteInstruction.ACCESS.WRITE;
|
||||
final QualifiedName qName = node.asQualifiedName();
|
||||
final String targetName = qName == null ? node.getName() : qName.toString();
|
||||
final ReadWriteInstruction instruction = ReadWriteInstruction.newInstruction(myBuilder, node, targetName, access);
|
||||
myBuilder.addNode(instruction);
|
||||
myBuilder.checkPending(instruction);
|
||||
|
||||
final PyExpression qualifier = node.getQualifier();
|
||||
if (qualifier != null) {
|
||||
qualifier.accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,9 @@ public class PyUnusedLocalInspectionVisitor extends PyInspectionVisitor {
|
||||
if (name == null || PyNames.UNDERSCORE.equals(name) || scope.isGlobal(name) || scope.isNonlocal(name)) {
|
||||
continue;
|
||||
}
|
||||
if (element instanceof PyTargetExpression && ((PyTargetExpression)element).isQualified()) {
|
||||
continue;
|
||||
}
|
||||
// Ignore underscore-prefixed parameters
|
||||
if (name.startsWith(PyNames.UNDERSCORE) && element instanceof PyParameter) {
|
||||
continue;
|
||||
|
||||
@@ -96,8 +96,8 @@ public class PyDefUseUtil {
|
||||
if (element instanceof PyImportElement) {
|
||||
return ((PyImportElement) element).getVisibleName();
|
||||
}
|
||||
if (element instanceof PyReferenceExpression) {
|
||||
final QualifiedName qname = ((PyReferenceExpression)element).asQualifiedName();
|
||||
if (element instanceof PyReferenceExpression || element instanceof PyTargetExpression) {
|
||||
final QualifiedName qname = ((PyQualifiedExpression)element).asQualifiedName();
|
||||
if (qname != null) {
|
||||
return qname.toString();
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
1(2) WRITE ACCESS: self
|
||||
2(3) WRITE ACCESS: args
|
||||
3(4) element: PyAssignmentStatement
|
||||
4(5) READ ACCESS: self
|
||||
5() element: null
|
||||
4(5) WRITE ACCESS: self.args
|
||||
5(6) READ ACCESS: self
|
||||
6() element: null
|
||||
@@ -1,7 +1,8 @@
|
||||
0(1) element: null
|
||||
1(2) WRITE ACCESS: self
|
||||
2(3) element: PyAssignmentStatement
|
||||
3(4) READ ACCESS: self
|
||||
4(5) element: PyPrintStatement
|
||||
5(6) READ ACCESS: self
|
||||
6() element: null
|
||||
3(4) WRITE ACCESS: self.foo
|
||||
4(5) READ ACCESS: self
|
||||
5(6) element: PyPrintStatement
|
||||
6(7) READ ACCESS: self
|
||||
7() element: null
|
||||
@@ -2032,6 +2032,19 @@ public class PyTypeTest extends PyTestCase {
|
||||
"expr = x");
|
||||
}
|
||||
|
||||
// PY-21175
|
||||
public void testLazyAttributeInitialization() {
|
||||
doTest("int",
|
||||
"class C:\n" +
|
||||
" def __init__(self):\n" +
|
||||
" self.attr = None\n" +
|
||||
" \n" +
|
||||
" def m(self):\n" +
|
||||
" if self.attr is None:\n" +
|
||||
" self.attr = 42\n" +
|
||||
" expr = self.attr");
|
||||
}
|
||||
|
||||
private static List<TypeEvalContext> getTypeEvalContexts(@NotNull PyExpression element) {
|
||||
return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(),
|
||||
TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());
|
||||
|
||||
Reference in New Issue
Block a user