Skip to content

Commit 88615f4

Browse files
committed
Python: Add support for forward declarations in unused var query
Fixes the false positive reported in #18910 Adds a new `Annotation` class (subclass of `Expr`) which encompasses all possible kinds of annotations in Python. Using this, we look for string literals which are part of an annotation, and which have the same content as the name of a (potentially) unused global variable, and in that case we do not produce an alert. In future, we may want to support inspecting such string literals more deeply (e.g. to support stuff like "list[unused_var]"), but I think for now this level of support is sufficient.
1 parent 301ebcb commit 88615f4

File tree

3 files changed

+28
-4
lines changed

3 files changed

+28
-4
lines changed

python/ql/lib/semmle/python/Exprs.qll

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,24 @@ class Guard extends Guard_ {
746746
override Expr getASubExpression() { result = this.getTest() }
747747
}
748748

749+
/** An annotation, such as the `int` part of `x: int` */
750+
class Annotation extends Expr {
751+
Annotation() {
752+
this = any(AnnAssign a).getAnnotation()
753+
or
754+
exists(Arguments args | args = any(FunctionExpr f).getArgs() |
755+
this in [
756+
args.getAnAnnotation(),
757+
args.getAKwAnnotation(),
758+
args.getKwargannotation(),
759+
args.getVarargannotation()
760+
]
761+
)
762+
or
763+
this = any(FunctionExpr f).getReturns()
764+
}
765+
}
766+
749767
/* Expression Contexts */
750768
/** A context in which an expression used */
751769
class ExprContext extends ExprContext_ { }

python/ql/src/Variables/UnusedModuleVariable.ql

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ predicate complex_all(Module m) {
3434
)
3535
}
3636

37+
predicate used_in_forward_declaration(Name used, Module mod) {
38+
exists(StringLiteral s, Annotation annotation |
39+
s.getS() = used.getId() and
40+
s.getEnclosingModule() = mod and
41+
annotation.getASubExpression*() = s
42+
)
43+
}
44+
3745
predicate unused_global(Name unused, GlobalVariable v) {
3846
not exists(ImportingStmt is | is.contains(unused)) and
3947
forex(DefinitionNode defn | defn.getNode() = unused |
@@ -55,7 +63,8 @@ predicate unused_global(Name unused, GlobalVariable v) {
5563
unused.defines(v) and
5664
not name_acceptable_for_unused_variable(v) and
5765
not complex_all(unused.getEnclosingModule())
58-
)
66+
) and
67+
not used_in_forward_declaration(unused, unused.getEnclosingModule())
5968
}
6069

6170
from Name unused, GlobalVariable v

python/ql/test/query-tests/Variables/unused/UnusedModuleVariable.expected

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,3 @@
44
| variables_test.py:86:3:86:3 | b | The global variable 'b' is not used. |
55
| variables_test.py:86:5:86:5 | c | The global variable 'c' is not used. |
66
| variables_test.py:100:1:100:8 | glob_var | The global variable 'glob_var' is not used. |
7-
| variables_test.py:147:5:147:26 | ForwardParamAnnotation | The global variable 'ForwardParamAnnotation' is not used. |
8-
| variables_test.py:148:5:148:27 | ForwardReturnAnnotation | The global variable 'ForwardReturnAnnotation' is not used. |
9-
| variables_test.py:149:5:149:31 | ForwardAssignmentAnnotation | The global variable 'ForwardAssignmentAnnotation' is not used. |

0 commit comments

Comments
 (0)