qapi/parser: add type hint annotations (QAPIDoc)

Annotations do not change runtime behavior.
This commit consists of only annotations.

Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20210930205716.1148693-10-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
John Snow 2021-09-30 16:57:12 -04:00 committed by Markus Armbruster
parent e7ac60fcd0
commit 5f0d9f3bc7

View file

@ -37,6 +37,9 @@ if TYPE_CHECKING:
from .schema import QAPISchemaFeature, QAPISchemaMember from .schema import QAPISchemaFeature, QAPISchemaMember
#: Represents a single Top Level QAPI schema expression.
TopLevelExpr = Dict[str, object]
# Return value alias for get_expr(). # Return value alias for get_expr().
_ExprValue = Union[List[object], Dict[str, object], str, bool] _ExprValue = Union[List[object], Dict[str, object], str, bool]
@ -454,7 +457,8 @@ class QAPIDoc:
""" """
class Section: class Section:
def __init__(self, parser, name=None, indent=0): def __init__(self, parser: QAPISchemaParser,
name: Optional[str] = None, indent: int = 0):
# parser, for error messages about indentation # parser, for error messages about indentation
self._parser = parser self._parser = parser
# optional section name (argument/member or section name) # optional section name (argument/member or section name)
@ -463,7 +467,7 @@ class QAPIDoc:
# the expected indent level of the text of this section # the expected indent level of the text of this section
self._indent = indent self._indent = indent
def append(self, line): def append(self, line: str) -> None:
# Strip leading spaces corresponding to the expected indent level # Strip leading spaces corresponding to the expected indent level
# Blank lines are always OK. # Blank lines are always OK.
if line: if line:
@ -478,7 +482,8 @@ class QAPIDoc:
self.text += line.rstrip() + '\n' self.text += line.rstrip() + '\n'
class ArgSection(Section): class ArgSection(Section):
def __init__(self, parser, name, indent=0): def __init__(self, parser: QAPISchemaParser,
name: str, indent: int = 0):
super().__init__(parser, name, indent) super().__init__(parser, name, indent)
self.member: Optional['QAPISchemaMember'] = None self.member: Optional['QAPISchemaMember'] = None
@ -489,35 +494,34 @@ class QAPIDoc:
""" """
Immutable dummy section for use at the end of a doc block. Immutable dummy section for use at the end of a doc block.
""" """
def append(self, line): def append(self, line: str) -> None:
assert False, "Text appended after end_comment() called." assert False, "Text appended after end_comment() called."
def __init__(self, parser, info): def __init__(self, parser: QAPISchemaParser, info: QAPISourceInfo):
# self._parser is used to report errors with QAPIParseError. The # self._parser is used to report errors with QAPIParseError. The
# resulting error position depends on the state of the parser. # resulting error position depends on the state of the parser.
# It happens to be the beginning of the comment. More or less # It happens to be the beginning of the comment. More or less
# servicable, but action at a distance. # servicable, but action at a distance.
self._parser = parser self._parser = parser
self.info = info self.info = info
self.symbol = None self.symbol: Optional[str] = None
self.body = QAPIDoc.Section(parser) self.body = QAPIDoc.Section(parser)
# dict mapping parameter name to ArgSection # dicts mapping parameter/feature names to their ArgSection
self.args = OrderedDict() self.args: Dict[str, QAPIDoc.ArgSection] = OrderedDict()
self.features = OrderedDict() self.features: Dict[str, QAPIDoc.ArgSection] = OrderedDict()
# a list of Section self.sections: List[QAPIDoc.Section] = []
self.sections = []
# the current section # the current section
self._section = self.body self._section = self.body
self._append_line = self._append_body_line self._append_line = self._append_body_line
def has_section(self, name): def has_section(self, name: str) -> bool:
"""Return True if we have a section with this name.""" """Return True if we have a section with this name."""
for i in self.sections: for i in self.sections:
if i.name == name: if i.name == name:
return True return True
return False return False
def append(self, line): def append(self, line: str) -> None:
""" """
Parse a comment line and add it to the documentation. Parse a comment line and add it to the documentation.
@ -538,18 +542,18 @@ class QAPIDoc:
line = line[1:] line = line[1:]
self._append_line(line) self._append_line(line)
def end_comment(self): def end_comment(self) -> None:
self._switch_section(QAPIDoc.NullSection(self._parser)) self._switch_section(QAPIDoc.NullSection(self._parser))
@staticmethod @staticmethod
def _is_section_tag(name): def _is_section_tag(name: str) -> bool:
return name in ('Returns:', 'Since:', return name in ('Returns:', 'Since:',
# those are often singular or plural # those are often singular or plural
'Note:', 'Notes:', 'Note:', 'Notes:',
'Example:', 'Examples:', 'Example:', 'Examples:',
'TODO:') 'TODO:')
def _append_body_line(self, line): def _append_body_line(self, line: str) -> None:
""" """
Process a line of documentation text in the body section. Process a line of documentation text in the body section.
@ -591,7 +595,7 @@ class QAPIDoc:
# This is a free-form documentation block # This is a free-form documentation block
self._append_freeform(line) self._append_freeform(line)
def _append_args_line(self, line): def _append_args_line(self, line: str) -> None:
""" """
Process a line of documentation text in an argument section. Process a line of documentation text in an argument section.
@ -637,7 +641,7 @@ class QAPIDoc:
self._append_freeform(line) self._append_freeform(line)
def _append_features_line(self, line): def _append_features_line(self, line: str) -> None:
name = line.split(' ', 1)[0] name = line.split(' ', 1)[0]
if name.startswith('@') and name.endswith(':'): if name.startswith('@') and name.endswith(':'):
@ -669,7 +673,7 @@ class QAPIDoc:
self._append_freeform(line) self._append_freeform(line)
def _append_various_line(self, line): def _append_various_line(self, line: str) -> None:
""" """
Process a line of documentation text in an additional section. Process a line of documentation text in an additional section.
@ -705,7 +709,11 @@ class QAPIDoc:
self._append_freeform(line) self._append_freeform(line)
def _start_symbol_section(self, symbols_dict, name, indent): def _start_symbol_section(
self,
symbols_dict: Dict[str, 'QAPIDoc.ArgSection'],
name: str,
indent: int) -> None:
# FIXME invalid names other than the empty string aren't flagged # FIXME invalid names other than the empty string aren't flagged
if not name: if not name:
raise QAPIParseError(self._parser, "invalid parameter name") raise QAPIParseError(self._parser, "invalid parameter name")
@ -717,13 +725,14 @@ class QAPIDoc:
self._switch_section(new_section) self._switch_section(new_section)
symbols_dict[name] = new_section symbols_dict[name] = new_section
def _start_args_section(self, name, indent): def _start_args_section(self, name: str, indent: int) -> None:
self._start_symbol_section(self.args, name, indent) self._start_symbol_section(self.args, name, indent)
def _start_features_section(self, name, indent): def _start_features_section(self, name: str, indent: int) -> None:
self._start_symbol_section(self.features, name, indent) self._start_symbol_section(self.features, name, indent)
def _start_section(self, name=None, indent=0): def _start_section(self, name: Optional[str] = None,
indent: int = 0) -> None:
if name in ('Returns', 'Since') and self.has_section(name): if name in ('Returns', 'Since') and self.has_section(name):
raise QAPIParseError(self._parser, raise QAPIParseError(self._parser,
"duplicated '%s' section" % name) "duplicated '%s' section" % name)
@ -731,7 +740,7 @@ class QAPIDoc:
self._switch_section(new_section) self._switch_section(new_section)
self.sections.append(new_section) self.sections.append(new_section)
def _switch_section(self, new_section): def _switch_section(self, new_section: 'QAPIDoc.Section') -> None:
text = self._section.text = self._section.text.strip() text = self._section.text = self._section.text.strip()
# Only the 'body' section is allowed to have an empty body. # Only the 'body' section is allowed to have an empty body.
@ -746,7 +755,7 @@ class QAPIDoc:
self._section = new_section self._section = new_section
def _append_freeform(self, line): def _append_freeform(self, line: str) -> None:
match = re.match(r'(@\S+:)', line) match = re.match(r'(@\S+:)', line)
if match: if match:
raise QAPIParseError(self._parser, raise QAPIParseError(self._parser,
@ -768,14 +777,16 @@ class QAPIDoc:
% feature.name) % feature.name)
self.features[feature.name].connect(feature) self.features[feature.name].connect(feature)
def check_expr(self, expr): def check_expr(self, expr: TopLevelExpr) -> None:
if self.has_section('Returns') and 'command' not in expr: if self.has_section('Returns') and 'command' not in expr:
raise QAPISemError(self.info, raise QAPISemError(self.info,
"'Returns:' is only valid for commands") "'Returns:' is only valid for commands")
def check(self): def check(self) -> None:
def check_args_section(args, what): def check_args_section(
args: Dict[str, QAPIDoc.ArgSection], what: str
) -> None:
bogus = [name for name, section in args.items() bogus = [name for name, section in args.items()
if not section.member] if not section.member]
if bogus: if bogus: