PY-21088 Understand __all__.append() and __all__.extend()

PyCharm used to fail to statically evaluate __all__.append() calls in
typing.py and concluded that since __all__ was too dynamic to analyse
it was better to assume that __all__ isn't present.

It affected resolving to internal module attributes not mentioned in
__all__ and even led to false positives in unused locals when these
internal names were imported from another module right next to
star-import from the module with __all__.
This commit is contained in:
Andrey Vlasovskikh
2016-10-26 20:06:07 +03:00
parent 9e4071cd68
commit 60451cb448
4 changed files with 41 additions and 2 deletions
@@ -23,7 +23,6 @@ import com.intellij.lang.Language;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.io.FileUtil;
@@ -575,7 +574,24 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
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
final String calleeName = callee.getName();
if ("append".equals(calleeName)) {
final PyStringLiteralExpression argument = node.getArgument(0, PyStringLiteralExpression.class);
if (argument != null) {
myResult.add(argument.getStringValue());
return;
}
}
else if ("extend".equals(calleeName)) {
final PyExpression argument = node.getArgument(0, PyExpression.class);
if (argument != null) {
final List<String> results = PyUtil.strListValue(argument);
if (results != null) {
myResult.addAll(results);
return;
}
}
}
myDynamic = true;
}
}
@@ -0,0 +1,5 @@
from m1 import *
print(internal_name)
# <ref>
@@ -0,0 +1,13 @@
__all__ = [
'foo',
'bar'
]
foo = 0
bar = 1
__all__.append('quux')
quux = 3
internal_name = 4
@@ -473,6 +473,11 @@ public class PyMultiFileResolveTest extends PyMultiFileResolveTestCase {
assertSameElements(doMultiResolveAndGetFileUrls("pkg/__init__.py"), "pkg/mod.py");
}
// PY-21088
public void testDontResolveToMissingNameInDynamicDunderAll() {
assertNull(doResolve());
}
@NotNull
private List<String> doMultiResolveAndGetFileUrls(@NotNull String currentFilePath) {
myFixture.configureByFile(currentFilePath);