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:
Mikhail Golubev
2017-07-24 14:38:41 +03:00
parent 58f32e7a70
commit 247e0aeeb1
6 changed files with 37 additions and 21 deletions
@@ -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());