Skip to content

Commit 4141b4f

Browse files
committed
Python: Add metrics query for type annotations
Adds a query that counts the number of type annotations of various kinds. Intended to be used with something like MRVA to inform our modelling decisions. Currently the query counts the following "interesting" types in addition to the total number of types: - Built-in types (which are less likely to be interesting from a modelling perspective) - Forward declarations (i.e. annotations inside strings) which will require a fair bit of QL machinery to interpret. - Simple types (stuff like `foo` or `foo.bar.baz`) - Optional types (stuff like `Optional[foo]` which from a modelling perspective should likely be treated the same as `foo`) - Complex types (anything that contains more complex type constructions such as instantiations of generic types)
1 parent 7a589c4 commit 4141b4f

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* @name Type metrics
3+
* @description Counts of various kinds of type annotations in Python code.
4+
* @kind table
5+
* @id py/type-metrics
6+
*/
7+
8+
import python
9+
10+
class BuiltinType extends Name {
11+
BuiltinType() { this.getId() in ["int", "float", "str", "bool", "bytes", "None"] }
12+
}
13+
14+
newtype TAnnotatable =
15+
TAnnotatedFunction(FunctionExpr f) { exists(f.getReturns()) } or
16+
TAnnotatedParameter(Parameter p) { exists(p.getAnnotation()) } or
17+
TAnnotatedAssignment(AnnAssign a) { exists(a.getAnnotation()) }
18+
19+
abstract class Annotatable extends TAnnotatable {
20+
string toString() { result = "Annotatable" }
21+
22+
abstract Expr getAnnotation();
23+
}
24+
25+
class AnnotatedFunction extends TAnnotatedFunction, Annotatable {
26+
FunctionExpr function;
27+
28+
AnnotatedFunction() { this = TAnnotatedFunction(function) }
29+
30+
override Expr getAnnotation() { result = function.getReturns() }
31+
}
32+
33+
class AnnotatedParameter extends TAnnotatedParameter, Annotatable {
34+
Parameter parameter;
35+
36+
AnnotatedParameter() { this = TAnnotatedParameter(parameter) }
37+
38+
override Expr getAnnotation() { result = parameter.getAnnotation() }
39+
}
40+
41+
class AnnotatedAssignment extends TAnnotatedAssignment, Annotatable {
42+
AnnAssign assignment;
43+
44+
AnnotatedAssignment() { this = TAnnotatedAssignment(assignment) }
45+
46+
override Expr getAnnotation() { result = assignment.getAnnotation() }
47+
}
48+
49+
/** Holds if `e` is a forward declaration of a type. */
50+
predicate is_forward_declaration(Expr e) { e instanceof StringLiteral }
51+
52+
/** Holds if `e` is a type that may be difficult to analyze. */
53+
predicate is_complex_type(Expr e) {
54+
e instanceof Subscript and not is_optional_type(e)
55+
or
56+
e instanceof Tuple
57+
or
58+
e instanceof List
59+
}
60+
61+
/** Holds if `e` is a type of the form `Optional[...]`. */
62+
predicate is_optional_type(Subscript e) { e.getObject().(Name).getId() = "Optional" }
63+
64+
/** Holds if `e` is a simple type, that is either an identifier (excluding built-in types) or an attribute of a simple type. */
65+
predicate is_simple_type(Expr e) {
66+
e instanceof Name and not e instanceof BuiltinType
67+
or
68+
is_simple_type(e.(Attribute).getObject())
69+
}
70+
71+
/** Holds if `e` is a built-in type. */
72+
predicate is_builtin_type(Expr e) { e instanceof BuiltinType }
73+
74+
predicate type_count(
75+
string kind, int total, int built_in_count, int forward_declaration_count, int simple_type_count,
76+
int complex_type_count, int optional_type_count
77+
) {
78+
kind = "Parameter annotation" and
79+
total = count(AnnotatedParameter p) and
80+
built_in_count = count(AnnotatedParameter p | is_builtin_type(p.getAnnotation())) and
81+
forward_declaration_count =
82+
count(AnnotatedParameter p | is_forward_declaration(p.getAnnotation())) and
83+
simple_type_count = count(AnnotatedParameter p | is_simple_type(p.getAnnotation())) and
84+
complex_type_count = count(AnnotatedParameter p | is_complex_type(p.getAnnotation())) and
85+
optional_type_count = count(AnnotatedParameter p | is_optional_type(p.getAnnotation()))
86+
or
87+
kind = "Return type annotation" and
88+
total = count(AnnotatedFunction f) and
89+
built_in_count = count(AnnotatedFunction f | is_builtin_type(f.getAnnotation())) and
90+
forward_declaration_count = count(AnnotatedFunction f | is_forward_declaration(f.getAnnotation())) and
91+
simple_type_count = count(AnnotatedFunction f | is_simple_type(f.getAnnotation())) and
92+
complex_type_count = count(AnnotatedFunction f | is_complex_type(f.getAnnotation())) and
93+
optional_type_count = count(AnnotatedFunction f | is_optional_type(f.getAnnotation()))
94+
or
95+
kind = "Annotated assignment" and
96+
total = count(AnnotatedAssignment a) and
97+
built_in_count = count(AnnotatedAssignment a | is_builtin_type(a.getAnnotation())) and
98+
forward_declaration_count =
99+
count(AnnotatedAssignment a | is_forward_declaration(a.getAnnotation())) and
100+
simple_type_count = count(AnnotatedAssignment a | is_simple_type(a.getAnnotation())) and
101+
complex_type_count = count(AnnotatedAssignment a | is_complex_type(a.getAnnotation())) and
102+
optional_type_count = count(AnnotatedAssignment a | is_optional_type(a.getAnnotation()))
103+
}
104+
105+
from
106+
string message, int total, int built_in, int forward_decl, int simple, int complex, int optional
107+
where type_count(message, total, built_in, forward_decl, simple, complex, optional)
108+
select message, total, built_in, forward_decl, simple, complex, optional

0 commit comments

Comments
 (0)