Skip to content

Commit 25985e9

Browse files
committed
Python: Remove a few false positives from py/unused-import.
1 parent 7a57a3c commit 25985e9

File tree

2 files changed

+35
-4
lines changed

2 files changed

+35
-4
lines changed

python/ql/src/Imports/UnusedImport.ql

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,28 @@ predicate imported_module_used_in_doctest(Import imp) {
5858
}
5959

6060
predicate imported_module_used_in_typehint(Import imp) {
61-
exists(string modname |
62-
imp.getAName().getAsname().(Name).getId() = modname
63-
and
61+
exists(string modname, Location loc |
62+
imp.getAName().getAsname().(Name).getId() = modname and
63+
loc.getFile() = imp.getScope().(Module).getFile()
64+
|
6465
/* Look for typehints containing the patterns:
6566
* # type: …name…
6667
*/
6768
exists(Comment typehint |
68-
typehint.getLocation().getFile() = imp.getScope().(Module).getFile() and
69+
loc = typehint.getLocation() and
6970
typehint.getText().regexpMatch("# type:.*" + modname + ".*")
7071
)
72+
or
73+
// Type hint is inside a string annotation, as needed for forward references
74+
exists(string typehint, Expr annotation |
75+
annotation = any(Arguments a).getAnAnnotation()
76+
or
77+
annotation = any(AnnAssign a).getAnnotation()
78+
|
79+
annotation.pointsTo(Value::forString(typehint)) and
80+
loc = annotation.getLocation() and
81+
typehint.regexpMatch(".*\\b" + modname + "\\b.*")
82+
)
7183
)
7284
}
7385

@@ -100,6 +112,11 @@ predicate unused_import(Import imp, Variable name) {
100112
not imported_module_used_in_doctest(imp)
101113
and
102114
not imported_module_used_in_typehint(imp)
115+
and
116+
/* Only consider import statements that actually point-to something (possibly an unknown module).
117+
* If this is not the case, it's likely that the import statement never gets executed.
118+
*/
119+
imp.getAName().getValue().pointsTo(_)
103120
}
104121

105122

python/ql/test/query-tests/Imports/unused/imports_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,17 @@ def f():
7676

7777
foo = None # type: typing.Optional[int]
7878

79+
80+
# OK since the import statement is never executed
81+
if False:
82+
import never_imported
83+
84+
# OK since the imported module is used in a forward-referenced type annotation.
85+
import only_used_in_parameter_annotation
86+
87+
def func(x: 'Optional[only_used_in_parameter_annotation]'):
88+
pass
89+
90+
import only_used_in_annotated_assignment
91+
92+
var : 'Optional[only_used_in_annotated_assignment]' = 5

0 commit comments

Comments
 (0)