diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java index 8981c69d3a86..37c7161f6437 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java @@ -45,6 +45,8 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { private final CachedValue> myImportTargetsTransitive; private volatile Boolean myAbsoluteImportEnabled; + private List myDunderAll; + private boolean myDunderAllCalculated; public PyFileImpl(FileViewProvider viewProvider) { super(viewProvider, PythonLanguage.getInstance()); @@ -352,36 +354,76 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { if (stubElement instanceof PyFileStub) { return ((PyFileStub) stubElement).getDunderAll(); } - return calculateDunderAll(); + if (!myDunderAllCalculated) { + myDunderAll = calculateDunderAll(); + myDunderAllCalculated = true; + } + return myDunderAll; } @Nullable public List calculateDunderAll() { - final List attrs = getTopLevelAttributes(); - return getStringListFromTargetExpression(PyNames.ALL, attrs); + final DunderAllBuilder builder = new DunderAllBuilder(); + accept(builder); + return builder.result(); + } + + private static class DunderAllBuilder extends PyRecursiveElementVisitor { + private List myResult = null; + private boolean myDynamic = false; + + @Override + public void visitPyTargetExpression(PyTargetExpression node) { + if (PyNames.ALL.equals(node.getName())) { + myResult = getStringListFromTargetExpression(node); + } + } + + @Override + public void visitPyCallExpression(PyCallExpression node) { + final PyExpression callee = node.getCallee(); + if (callee instanceof PyQualifiedExpression) { + final PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier(); + if (qualifier != null && PyNames.ALL.equals(qualifier.getText())) { + // TODO handle append and extend with constant arguments here + myDynamic = true; + } + } + } + + @Nullable + List result() { + return myDynamic ? null : myResult; + } } @Nullable public static List getStringListFromTargetExpression(final String name, List attrs) { for (PyTargetExpression attr : attrs) { if (name.equals(attr.getName())) { - final PyExpression value = attr.findAssignedValue(); - if (value instanceof PySequenceExpression) { - final PyExpression[] elements = ((PySequenceExpression)value).getElements(); - List result = new ArrayList(elements.length); - for (PyExpression element : elements) { - if (!(element instanceof PyStringLiteralExpression)) { - return null; - } - result.add(((PyStringLiteralExpression) element).getStringValue()); - } - return result; - } + return getStringListFromTargetExpression(attr); } } return null; } + @Nullable + private static List getStringListFromTargetExpression(PyTargetExpression attr) { + final PyExpression value = attr.findAssignedValue(); + if (value instanceof PySequenceExpression) { + final PyExpression[] elements = ((PySequenceExpression)value).getElements(); + List result = new ArrayList(elements.length); + for (PyExpression element : elements) { + if (!(element instanceof PyStringLiteralExpression)) { + return null; + } + result.add(((PyStringLiteralExpression) element).getStringValue()); + } + return result; + } + return null; + } + @Override public boolean isAbsoluteImportEnabled() { final StubElement stub = getStub(); @@ -430,6 +472,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { myScopeRef.clear(); } myAbsoluteImportEnabled = null; + myDunderAllCalculated = false; } private SoftReference myControlFlowRef; diff --git a/python/testData/stubs/DynamicDunderAll.py b/python/testData/stubs/DynamicDunderAll.py new file mode 100644 index 000000000000..0fd313702133 --- /dev/null +++ b/python/testData/stubs/DynamicDunderAll.py @@ -0,0 +1,4 @@ +__all__ = ['foo', 'bar'] + +for i in range(5): + __all__.append('f' + str(i)) diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java index e81939443f4e..c9b42f0e8bd0 100644 --- a/python/testSrc/com/jetbrains/python/PyStubsTest.java +++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java @@ -234,6 +234,13 @@ public class PyStubsTest extends PyLightFixtureTestCase { assertNotParsed(file); } + public void testDynamicDunderAll() { + final PyFileImpl file = (PyFileImpl) getTestFile(); + final List all = file.getDunderAll(); + assertNull(all); + assertNotParsed(file); + } + public void testSlots() { final PyFileImpl file = (PyFileImpl) getTestFile(); final PyClass pyClass = file.getTopLevelClasses().get(0);