Skip to content

Commit 3d643c0

Browse files
authored
Merge pull request #18921 from github/tausbn/python-fix-unused-global-variable-in-forward-annotation-fp
Python: Add support for forward references in unused var query
2 parents 4681f28 + 50a01b1 commit 3d643c0

File tree

4 files changed

+51
-1
lines changed

4 files changed

+51
-1
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 |
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
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: fix
3+
---
4+
5+
- The `py/unused-global-variable` now no longer flags variables that are only used in forward references (e.g. the `Foo` in `def bar(x: "Foo"): ...`).

python/ql/test/query-tests/Variables/unused/variables_test.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,21 @@ def test_dict_unpacking(queryset, field_name, value):
137137
for tag in value.split(','):
138138
queryset = queryset.filter(**{field_name + '__name': tag})
139139
return queryset
140+
141+
from typing import TYPE_CHECKING
142+
143+
if TYPE_CHECKING:
144+
ParamAnnotation = int
145+
ReturnAnnotation = int
146+
AssignmentAnnotation = int
147+
ForwardParamAnnotation = int
148+
ForwardReturnAnnotation = int
149+
ForwardAssignmentAnnotation = int
150+
151+
def test_direct_annotation(x: ParamAnnotation) -> ReturnAnnotation:
152+
if x:
153+
y : AssignmentAnnotation = 1
154+
155+
def test_forward_annotation(x: "ForwardParamAnnotation") -> "ForwardReturnAnnotation":
156+
if x:
157+
y : "ForwardAssignmentAnnotation" = 1

0 commit comments

Comments
 (0)