@samitouri / QOSamiQemu / commits / 7b4680b03f

minikconf: add mypy annotations

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

Paolo Bonzini committed Apr 24, 2026 at 11:37 UTC 7b4680b03f874357132b2dceae08041316eaa8b7
2 files changed +122 -101
python/tests/linters.py
+3
@@ -65,6 +65,9 @@ class TestLinters:
65 ]
66 )
67
68 + def test_mypy_minikconf(self):
69 + check_call([sys.executable, "-m", "mypy", "../scripts/minikconf.py"])
70 +
71 def test_mypy_pkg(self):
72 check_call([sys.executable, "-m", "mypy", "-p", "qemu"])
73
scripts/minikconf.py
+119 -101
@@ -17,31 +17,34 @@ import os
17 import random
18 import re
19 import sys
20 +import typing as T
21 from dataclasses import dataclass
22
23 __all__ = [ 'KconfigDataError', 'KconfigParserError',
24 'KconfigData', 'KconfigParser' ,
25 'defconfig', 'allyesconfig', 'allnoconfig', 'randconfig' ]
26
27 +Mangler = T.Callable[[bool], bool]
28 +
29 @dataclass
30 class IncludeInfo:
31 file: str
32 line: int
33 parent: IncludeInfo | None
34
32 - def __iter__(self):
33 - inf = self
35 + def __iter__(self) -> T.Iterator[str]:
36 + inf: IncludeInfo | None = self
37 while inf is not None:
38 yield "%s:%d" % (inf.file, inf.line)
39 inf = inf.parent
40
38 - def error_path(self):
41 + def error_path(self) -> str:
42 res = ""
43 for loc in self:
44 res = "In file included from %s:\n" % loc + res
45 return res
46
44 -def debug_print(*args):
47 +def debug_print(*args: object) -> None:
48 #print('# ' + (' '.join(str(x) for x in args)))
49 pass
50
@@ -56,81 +59,81 @@ def debug_print(*args):
59 # -------------------------------------------
60
61 class KconfigDataError(Exception):
59 - def __init__(self, msg):
62 + def __init__(self, msg: str) -> None:
63 self.msg = msg
64
62 - def __str__(self):
65 + def __str__(self) -> str:
66 return self.msg
67
65 -allyesconfig = lambda x: True
66 -allnoconfig = lambda x: False
67 -defconfig = lambda x: x
68 -randconfig = lambda x: random.randint(0, 1) == 1
68 +allyesconfig: Mangler = lambda x: True
69 +allnoconfig: Mangler = lambda x: False
70 +defconfig: Mangler = lambda x: x
71 +randconfig: Mangler = lambda x: random.randint(0, 1) == 1
72
73 class KconfigData:
74 class Expr:
72 - def __and__(self, rhs):
75 + def __and__(self, rhs: KconfigData.Expr) -> KconfigData.Expr:
76 return KconfigData.AND(self, rhs)
74 - def __or__(self, rhs):
77 + def __or__(self, rhs: KconfigData.Expr) -> KconfigData.Expr:
78 return KconfigData.OR(self, rhs)
76 - def __invert__(self):
79 + def __invert__(self) -> KconfigData.Expr:
80 return KconfigData.NOT(self)
81
82 # Abstract methods
80 - def add_edges_to(self, var):
83 + def add_edges_to(self, var: KconfigData.Var) -> None:
84 pass
82 - def evaluate(self):
85 + def evaluate(self) -> bool:
86 assert False
87
88 class AND(Expr):
86 - def __init__(self, lhs, rhs):
89 + def __init__(self, lhs: KconfigData.Expr, rhs: KconfigData.Expr) -> None:
90 self.lhs = lhs
91 self.rhs = rhs
89 - def __str__(self):
92 + def __str__(self) -> str:
93 return "(%s && %s)" % (self.lhs, self.rhs)
94
92 - def add_edges_to(self, var):
95 + def add_edges_to(self, var: KconfigData.Var) -> None:
96 self.lhs.add_edges_to(var)
97 self.rhs.add_edges_to(var)
95 - def evaluate(self):
98 + def evaluate(self) -> bool:
99 return self.lhs.evaluate() and self.rhs.evaluate()
100
101 class OR(Expr):
99 - def __init__(self, lhs, rhs):
102 + def __init__(self, lhs: KconfigData.Expr, rhs: KconfigData.Expr) -> None:
103 self.lhs = lhs
104 self.rhs = rhs
102 - def __str__(self):
105 + def __str__(self) -> str:
106 return "(%s || %s)" % (self.lhs, self.rhs)
107
105 - def add_edges_to(self, var):
108 + def add_edges_to(self, var: KconfigData.Var) -> None:
109 self.lhs.add_edges_to(var)
110 self.rhs.add_edges_to(var)
108 - def evaluate(self):
111 + def evaluate(self) -> bool:
112 return self.lhs.evaluate() or self.rhs.evaluate()
113
114 class NOT(Expr):
112 - def __init__(self, lhs):
115 + def __init__(self, lhs: KconfigData.Expr) -> None:
116 self.lhs = lhs
114 - def __str__(self):
117 + def __str__(self) -> str:
118 return "!%s" % (self.lhs)
119
117 - def add_edges_to(self, var):
120 + def add_edges_to(self, var: KconfigData.Var) -> None:
121 self.lhs.add_edges_to(var)
119 - def evaluate(self):
122 + def evaluate(self) -> bool:
123 return not self.lhs.evaluate()
124
125 class Var(Expr):
123 - def __init__(self, name):
126 + def __init__(self, name: str) -> None:
127 self.name = name
125 - self.value = None
126 - self.outgoing = set()
127 - self.clauses_for_var = list()
128 - def __str__(self):
128 + self.value: bool | None = None
129 + self.outgoing: set[KconfigData.Var] = set()
130 + self.clauses_for_var: list[KconfigData.Clause] = []
131 + def __str__(self) -> str:
132 return self.name
133
131 - def has_value(self):
134 + def has_value(self) -> bool:
135 return self.value is not None
133 - def set_value(self, val, clause):
136 + def set_value(self, val: bool, clause: KconfigData.Clause) -> None:
137 self.clauses_for_var.append(clause)
138 if self.has_value() and self.value != val:
139 print("The following clauses were found for " + self.name, file=sys.stderr)
@@ -141,7 +144,8 @@ class KconfigData:
144 self.value = val
145
146 # depth first search of the dependency graph
144 - def dfs(self, visited, f):
147 + def dfs(self, visited: set[KconfigData.Var],
148 + f: T.Callable[[KconfigData.Var], None]) -> None:
149 if self in visited:
150 return
151 visited.add(self)
@@ -149,87 +153,89 @@ class KconfigData:
153 v.dfs(visited, f)
154 f(self)
155
152 - def add_edges_to(self, var):
156 + def add_edges_to(self, var: KconfigData.Var) -> None:
157 self.outgoing.add(var)
154 - def evaluate(self):
158 + def evaluate(self) -> bool:
159 if not self.has_value():
160 raise KconfigDataError('cycle found including %s' % self)
161 + assert self.value is not None
162 return self.value
163
164 class Clause:
160 - def __init__(self, dest):
165 + def __init__(self, dest: KconfigData.Var) -> None:
166 self.dest = dest
162 - def priority(self):
167 + def priority(self) -> int:
168 return 0
164 - def process(self):
169 + def process(self) -> None:
170 pass
171
172 class AssignmentClause(Clause):
168 - def __init__(self, dest, value):
173 + def __init__(self, dest: KconfigData.Var, value: bool) -> None:
174 KconfigData.Clause.__init__(self, dest)
175 self.value = value
171 - def __str__(self):
176 + def __str__(self) -> str:
177 return "CONFIG_%s=%s" % (self.dest, 'y' if self.value else 'n')
178
174 - def process(self):
179 + def process(self) -> None:
180 self.dest.set_value(self.value, self)
181
182 class DefaultClause(Clause):
178 - def __init__(self, dest, value, cond=None):
183 + def __init__(self, dest: KconfigData.Var, value: bool,
184 + cond: KconfigData.Expr | None = None) -> None:
185 KconfigData.Clause.__init__(self, dest)
186 self.value = value
187 self.cond = cond
188 if self.cond is not None:
189 self.cond.add_edges_to(self.dest)
184 - def __str__(self):
190 + def __str__(self) -> str:
191 value = 'y' if self.value else 'n'
192 if self.cond is None:
193 return "config %s default %s" % (self.dest, value)
194 else:
195 return "config %s default %s if %s" % (self.dest, value, self.cond)
196
191 - def priority(self):
197 + def priority(self) -> int:
198 # Defaults are processed just before leaving the variable
199 return -1
194 - def process(self):
200 + def process(self) -> None:
201 if not self.dest.has_value() and \
202 (self.cond is None or self.cond.evaluate()):
203 self.dest.set_value(self.value, self)
204
205 class DependsOnClause(Clause):
200 - def __init__(self, dest, expr):
206 + def __init__(self, dest: KconfigData.Var, expr: KconfigData.Expr) -> None:
207 KconfigData.Clause.__init__(self, dest)
208 self.expr = expr
209 self.expr.add_edges_to(self.dest)
204 - def __str__(self):
210 + def __str__(self) -> str:
211 return "config %s depends on %s" % (self.dest, self.expr)
212
207 - def process(self):
213 + def process(self) -> None:
214 if not self.expr.evaluate():
215 self.dest.set_value(False, self)
216
217 class SelectClause(Clause):
212 - def __init__(self, dest, cond):
218 + def __init__(self, dest: KconfigData.Var, cond: KconfigData.Expr) -> None:
219 KconfigData.Clause.__init__(self, dest)
220 self.cond = cond
221 self.cond.add_edges_to(self.dest)
216 - def __str__(self):
222 + def __str__(self) -> str:
223 return "select %s if %s" % (self.dest, self.cond)
224
219 - def process(self):
225 + def process(self) -> None:
226 if self.cond.evaluate():
227 self.dest.set_value(True, self)
228
223 - def __init__(self, value_mangler=defconfig):
229 + def __init__(self, value_mangler: Mangler = defconfig) -> None:
230 self.value_mangler = value_mangler
225 - self.previously_included = []
226 - self.defined_vars = set()
227 - self.referenced_vars = dict()
228 - self.clauses = list()
231 + self.previously_included: list[str] = []
232 + self.defined_vars: set[str] = set()
233 + self.referenced_vars: dict[str, KconfigData.Var] = {}
234 + self.clauses: list[KconfigData.Clause] = []
235
236 # semantic analysis -------------
237
232 - def check_undefined(self):
238 + def check_undefined(self) -> bool:
239 undef = False
240 for i in self.referenced_vars:
241 if i not in self.defined_vars:
@@ -237,7 +243,7 @@ class KconfigData:
243 undef = True
244 return undef
245
240 - def compute_config(self):
246 + def compute_config(self) -> dict[str, bool]:
247 if self.check_undefined():
248 raise KconfigDataError("there were undefined symbols")
249
@@ -250,10 +256,10 @@ class KconfigData:
256 debug_print(source, "->", [str(x) for x in edges.outgoing])
257
258 # The reverse of the depth-first order is the topological sort
253 - dfo = dict()
254 - visited = set()
259 + dfo: dict[KconfigData.Var, int] = {}
260 + visited: set[KconfigData.Var] = set()
261 debug_print("\n")
256 - def visit_fn(var):
262 + def visit_fn(var: KconfigData.Var) -> None:
263 debug_print(var, "has DFS number", len(dfo))
264 dfo[var] = len(dfo)
265
@@ -272,7 +278,7 @@ class KconfigData:
278 clause.process()
279
280 debug_print("")
275 - values = dict()
281 + values: dict[str, bool] = {}
282 for name, v in self.referenced_vars.items():
283 debug_print("Evaluating", name)
284 values[name] = v.evaluate()
@@ -281,39 +287,42 @@ class KconfigData:
287
288 # semantic actions -------------
289
284 - def do_declaration(self, var):
290 + def do_declaration(self, var: KconfigData.Var) -> None:
291 if var.name in self.defined_vars:
292 raise KconfigDataError('variable "%s" defined twice' % var.name)
287 -
293 self.defined_vars.add(var.name)
294
295 # var is a string with the variable's name.
291 - def do_var(self, var):
296 + def do_var(self, var: str) -> KconfigData.Var:
297 if var in self.referenced_vars:
298 return self.referenced_vars[var]
299
300 var_obj = self.referenced_vars[var] = KconfigData.Var(var)
301 return var_obj
302
298 - def do_assignment(self, var, val):
303 + def do_assignment(self, var: KconfigData.Var, val: bool) -> None:
304 self.clauses.append(KconfigData.AssignmentClause(var, val))
305
301 - def do_cmdline_assignment(self, var, val):
306 + def do_cmdline_assignment(self, var: str, val: bool) -> None:
307 assert var.startswith("CONFIG_")
308 self.do_assignment(self.do_var(var[7:]), val)
309
305 - def do_default(self, var, val, cond=None):
310 + def do_default(self, var: KconfigData.Var, val: bool,
311 + cond: KconfigData.Expr | None = None) -> None:
312 val = self.value_mangler(val)
313 self.clauses.append(KconfigData.DefaultClause(var, val, cond))
314
309 - def do_depends_on(self, var, expr):
315 + def do_depends_on(self, var: KconfigData.Var,
316 + expr: KconfigData.Expr) -> None:
317 self.clauses.append(KconfigData.DependsOnClause(var, expr))
318
312 - def do_select(self, var, symbol, cond=None):
319 + def do_select(self, var: KconfigData.Var, symbol: KconfigData.Var,
320 + cond: KconfigData.Expr | None = None) -> None:
321 cond = (cond & var) if cond is not None else var
322 self.clauses.append(KconfigData.SelectClause(symbol, cond))
323
316 - def do_imply(self, var, symbol, cond=None):
324 + def do_imply(self, var: KconfigData.Var, symbol: KconfigData.Var,
325 + cond: KconfigData.Expr | None = None) -> None:
326 # "config X imply Y [if COND]" is the same as
327 # "config Y default y if X [&& COND]"
328 cond = (cond & var) if cond is not None else var
@@ -325,7 +334,7 @@ class KconfigData:
334 # -------------------------------------------
335
336 # tokens table
328 -TOKENS = {}
337 +TOKENS: dict[int, str] = {}
338 TOK_NONE = -1
339 TOK_LPAREN = 0; TOKENS[TOK_LPAREN] = '"("'
340 TOK_RPAREN = 1; TOKENS[TOK_RPAREN] = '")"'
@@ -348,7 +357,8 @@ TOK_ID = 17; TOKENS[TOK_ID] = 'identifier'
357 TOK_EOF = 18; TOKENS[TOK_EOF] = 'end of file'
358
359 class KconfigParserError(Exception):
351 - def __init__(self, parser, msg, tok=None):
360 + def __init__(self, parser: KconfigParser, msg: str,
361 + tok: int | str | None = None) -> None:
362 self.loc = parser.location()
363 tok = tok if tok is not None else parser.tok
364 if tok != TOK_NONE:
@@ -356,33 +366,37 @@ class KconfigParserError(Exception):
366 msg = '%s before %s' % (msg, location)
367 self.msg = msg
368
359 - def __str__(self):
369 + def __str__(self) -> str:
370 return "%s: %s" % (self.loc, self.msg)
371
372 class KconfigParser:
373
374 @classmethod
365 - def parse(cls, fp, data, incl_info=None):
375 + def parse(cls, fp: T.TextIO, data: KconfigData, incl_info: IncludeInfo | None = None) -> None:
376 cls(fp, data, incl_info).parse_config()
377
368 - def __init__(self, fp, data, incl_info):
378 + def __init__(self, fp: T.TextIO, data: KconfigData, incl_info: IncludeInfo | None = None):
379 self.data = data
380 self.incl_info = incl_info
381 self.abs_fname = os.path.abspath(fp.name)
382 self.fname = fp.name
383 self.data.previously_included.append(self.abs_fname)
384 +
385 src = fp.read()
386 if src == '' or src[-1] != '\n':
387 src += '\n'
388 self.src = src
378 - self.cursor = 0
379 - self.line = 1
380 - self.line_pos = 0
389 + self.cursor: int = 0
390 + self.line: int = 1
391 + self.line_pos: int = 0
392 + self.pos: int = 0
393 + self.tok: int = TOK_NONE
394 + self.val: str | None = None
395 self.get_token()
396
397 # file management -----
398
385 - def location(self):
399 + def location(self) -> str:
400 col = 1
401 for ch in self.src[self.line_pos:self.pos]:
402 if ch == '\t':
@@ -393,7 +407,7 @@ class KconfigParser:
407 incl_chain = inf.error_path() if inf is not None else ""
408 return '%s%s:%d:%d' % (incl_chain, self.fname, self.line, col)
409
396 - def do_include(self, include):
410 + def do_include(self, include: str) -> None:
411 incl_abs_fname = os.path.join(os.path.dirname(self.abs_fname),
412 include)
413 # catch inclusion cycle
@@ -421,7 +435,7 @@ class KconfigParser:
435 # recursive descent parser -----
436
437 # y_or_n: Y | N
424 - def parse_y_or_n(self):
438 + def parse_y_or_n(self) -> bool:
439 if self.tok == TOK_Y:
440 self.get_token()
441 return True
@@ -431,7 +445,7 @@ class KconfigParser:
445 raise KconfigParserError(self, 'Expected "y" or "n"')
446
447 # var: ID
434 - def parse_var(self):
448 + def parse_var(self) -> KconfigData.Var:
449 if self.tok != TOK_ID:
450 raise KconfigParserError(self, 'Expected identifier')
451 val = self.val
@@ -440,7 +454,7 @@ class KconfigParser:
454 return self.data.do_var(val)
455
456 # assignment_var: ID (starting with "CONFIG_")
443 - def parse_assignment_var(self):
457 + def parse_assignment_var(self) -> KconfigData.Var:
458 if self.tok != TOK_ID:
459 raise KconfigParserError(self, 'Expected identifier')
460 val = self.val
@@ -452,7 +466,7 @@ class KconfigParser:
466 return self.data.do_var(val[7:])
467
468 # assignment: var EQUAL y_or_n
455 - def parse_assignment(self):
469 + def parse_assignment(self) -> None:
470 var = self.parse_assignment_var()
471 if self.tok != TOK_EQUAL:
472 raise KconfigParserError(self, 'Expected "="')
@@ -462,7 +476,7 @@ class KconfigParser:
476 # primary: NOT primary
477 # | LPAREN expr RPAREN
478 # | var
465 - def parse_primary(self):
479 + def parse_primary(self) -> KconfigData.Expr:
480 if self.tok == TOK_NOT:
481 self.get_token()
482 val = ~self.parse_primary()
@@ -479,7 +493,7 @@ class KconfigParser:
493 return val
494
495 # disj: primary (OR primary)*
482 - def parse_disj(self):
496 + def parse_disj(self) -> KconfigData.Expr:
497 lhs = self.parse_primary()
498 while self.tok == TOK_OR:
499 self.get_token()
@@ -487,7 +501,7 @@ class KconfigParser:
501 return lhs
502
503 # expr: disj (AND disj)*
490 - def parse_expr(self):
504 + def parse_expr(self) -> KconfigData.Expr:
505 lhs = self.parse_disj()
506 while self.tok == TOK_AND:
507 self.get_token()
@@ -496,7 +510,7 @@ class KconfigParser:
510
511 # condition: IF expr
512 # | empty
499 - def parse_condition(self):
513 + def parse_condition(self) -> KconfigData.Expr | None:
514 if self.tok != TOK_IF:
515 return None
516 self.get_token()
@@ -506,7 +520,7 @@ class KconfigParser:
520 # | DEPENDS ON expr
521 # | SELECT var condition
522 # | BOOL
509 - def parse_property(self, var):
523 + def parse_property(self, var: KconfigData.Var) -> None:
524 if self.tok == TOK_DEFAULT:
525 self.get_token()
526 val = self.parse_y_or_n()
@@ -535,7 +549,7 @@ class KconfigParser:
549
550 # properties: properties property
551 # | /* empty */
538 - def parse_properties(self, var):
552 + def parse_properties(self, var: KconfigData.Var) -> None:
553 while self.tok == TOK_DEFAULT or self.tok == TOK_DEPENDS or \
554 self.tok == TOK_SELECT or self.tok == TOK_BOOL or \
555 self.tok == TOK_IMPLY:
@@ -548,7 +562,7 @@ class KconfigParser:
562 + '"default", "depends on", "imply" or "select"')
563
564 # declaration: config var properties
551 - def parse_declaration(self):
565 + def parse_declaration(self) -> None:
566 if self.tok == TOK_CONFIG:
567 self.get_token()
568 var = self.parse_var()
@@ -560,9 +574,10 @@ class KconfigParser:
574 # clause: SOURCE
575 # | declaration
576 # | assignment
563 - def parse_clause(self):
577 + def parse_clause(self) -> None:
578 if self.tok == TOK_SOURCE:
579 val = self.val
580 + assert val is not None
581 self.get_token()
582 self.do_include(val)
583 elif self.tok == TOK_CONFIG:
@@ -573,14 +588,15 @@ class KconfigParser:
588 raise KconfigParserError(self, 'expected "source", "config" or identifier')
589
590 # config: clause+ EOF
576 - def parse_config(self):
591 + def parse_config(self) -> KconfigData:
592 while self.tok != TOK_EOF:
593 self.parse_clause()
594 return self.data
595
596 # scanner -----
597
583 - def get_token(self):
598 + def get_token(self) -> None:
599 + assert self.src is not None
600 while True:
601 ch = self.src[self.cursor]
602 self.pos = self.cursor
@@ -592,7 +608,8 @@ class KconfigParser:
608 self.tok = tok
609 return
610
595 - def check_keyword(self, rest):
611 + def check_keyword(self, rest: str) -> bool:
612 + assert self.src is not None
613 if not self.src.startswith(rest, self.cursor):
614 return False
615 length = len(rest)
@@ -601,7 +618,8 @@ class KconfigParser:
618 self.cursor += length
619 return True
620
604 - def scan_token(self, ch):
621 + def scan_token(self, ch: str) -> int | None:
622 + assert self.src is not None
623 if ch == '#':
624 self.cursor = self.src.find('\n', self.cursor)
625 return None
@@ -669,7 +687,7 @@ class KconfigParser:
687
688 def main() -> None:
689 argv = sys.argv
672 - mode = defconfig
690 + mode: Mangler = defconfig
691 if len(sys.argv) > 1:
692 if argv[1] == '--defconfig':
693 del argv[1]
@@ -693,7 +711,7 @@ def main() -> None:
711 sys.exit(1)
712
713 data = KconfigData(mode)
696 - external_vars = set()
714 + external_vars: set[str] = set()
715 for arg in argv[3:]:
716 m = re.match(r'^(CONFIG_[A-Z0-9_]+)=([yn]?)$', arg)
717 if m is not None: