From 2184bca7b17559107032ba4fd8fc6f65345276ed Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:51 -0500 Subject: qapi: Replace List[str] with Sequence[str] for ifcond It does happen to be a list (as of now), but we can describe it in more general terms with no loss in accuracy to allow tuples and other constructs. In the future, we can write "ifcond: Sequence[str] = ()" as a default parameter, which we could not do safely with a Mutable type like a List. Signed-off-by: John Snow Message-Id: <20210216021809.134886-2-jsnow@redhat.com> Reviewed-by: Markus Armbruster [Commit message tweaked] Signed-off-by: Markus Armbruster --- scripts/qapi/commands.py | 3 ++- scripts/qapi/events.py | 4 ++-- scripts/qapi/gen.py | 12 ++++++------ scripts/qapi/types.py | 12 ++++++------ scripts/qapi/visit.py | 10 +++++----- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py index 54af519f44..0a75a9371b 100644 --- a/scripts/qapi/commands.py +++ b/scripts/qapi/commands.py @@ -17,6 +17,7 @@ from typing import ( Dict, List, Optional, + Sequence, Set, ) @@ -297,7 +298,7 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds) def visit_command(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], arg_type: Optional[QAPISchemaObjectType], ret_type: Optional[QAPISchemaType], diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py index 8c57deb2b8..90d2f6156d 100644 --- a/scripts/qapi/events.py +++ b/scripts/qapi/events.py @@ -12,7 +12,7 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ -from typing import List, Optional +from typing import List, Optional, Sequence from .common import c_enum_const, c_name, mcgen from .gen import QAPISchemaModularCVisitor, build_params, ifcontext @@ -214,7 +214,7 @@ void %(event_emit)s(%(event_enum)s event, QDict *qdict); def visit_event(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], arg_type: Optional[QAPISchemaObjectType], boxed: bool) -> None: diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py index 63549cc8d4..1fa503bdbd 100644 --- a/scripts/qapi/gen.py +++ b/scripts/qapi/gen.py @@ -17,8 +17,8 @@ import re from typing import ( Dict, Iterator, - List, Optional, + Sequence, Tuple, ) @@ -85,7 +85,7 @@ class QAPIGen: fp.write(text) -def _wrap_ifcond(ifcond: List[str], before: str, after: str) -> str: +def _wrap_ifcond(ifcond: Sequence[str], before: str, after: str) -> str: if before == after: return after # suppress empty #if ... #endif @@ -127,9 +127,9 @@ def build_params(arg_type: Optional[QAPISchemaObjectType], class QAPIGenCCode(QAPIGen): def __init__(self, fname: str): super().__init__(fname) - self._start_if: Optional[Tuple[List[str], str, str]] = None + self._start_if: Optional[Tuple[Sequence[str], str, str]] = None - def start_if(self, ifcond: List[str]) -> None: + def start_if(self, ifcond: Sequence[str]) -> None: assert self._start_if is None self._start_if = (ifcond, self._body, self._preamble) @@ -187,11 +187,11 @@ class QAPIGenH(QAPIGenC): @contextmanager -def ifcontext(ifcond: List[str], *args: QAPIGenCCode) -> Iterator[None]: +def ifcontext(ifcond: Sequence[str], *args: QAPIGenCCode) -> Iterator[None]: """ A with-statement context manager that wraps with `start_if()` / `end_if()`. - :param ifcond: A list of conditionals, passed to `start_if()`. + :param ifcond: A sequence of conditionals, passed to `start_if()`. :param args: any number of `QAPIGenCCode`. Example:: diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index 2bdd626847..20d572a23a 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -13,7 +13,7 @@ This work is licensed under the terms of the GNU GPL, version 2. # See the COPYING file in the top-level directory. """ -from typing import List, Optional +from typing import List, Optional, Sequence from .common import ( c_enum_const, @@ -139,7 +139,7 @@ def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str: return ret -def gen_object(name: str, ifcond: List[str], +def gen_object(name: str, ifcond: Sequence[str], base: Optional[QAPISchemaObjectType], members: List[QAPISchemaObjectTypeMember], variants: Optional[QAPISchemaVariants]) -> str: @@ -307,7 +307,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], members: List[QAPISchemaEnumMember], prefix: Optional[str]) -> None: @@ -318,7 +318,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): def visit_array_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], element_type: QAPISchemaType) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.preamble_add(gen_fwd_object_or_array(name)) @@ -328,7 +328,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): def visit_object_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], base: Optional[QAPISchemaObjectType], members: List[QAPISchemaObjectTypeMember], @@ -351,7 +351,7 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], variants: QAPISchemaVariants) -> None: with ifcontext(ifcond, self._genh): diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index 22e62df901..9aa0b1e11e 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -13,7 +13,7 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ -from typing import List, Optional +from typing import List, Optional, Sequence from .common import ( c_enum_const, @@ -337,7 +337,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], members: List[QAPISchemaEnumMember], prefix: Optional[str]) -> None: @@ -348,7 +348,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): def visit_array_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], element_type: QAPISchemaType) -> None: with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name)) @@ -357,7 +357,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): def visit_object_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], base: Optional[QAPISchemaObjectType], members: List[QAPISchemaObjectTypeMember], @@ -379,7 +379,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo], - ifcond: List[str], + ifcond: Sequence[str], features: List[QAPISchemaFeature], variants: QAPISchemaVariants) -> None: with ifcontext(ifcond, self._genh, self._genc): -- cgit v1.2.3 From 6b67bcac0f595143f5a5fce4e518b74577bae192 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:52 -0500 Subject: qapi/introspect.py: assert schema is not None The introspect visitor is stateful, but expects that it will have a schema to refer to. Add assertions that state this. Signed-off-by: John Snow Message-Id: <20210216021809.134886-3-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index fafec94e02..43ab4be1f7 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -147,6 +147,8 @@ const QLitObject %(c_name)s = %(c_string)s; return self._name_map[name] def _use_type(self, typ): + assert self._schema is not None + # Map the various integer types to plain int if typ.json_type() == 'int': typ = self._schema.lookup_type('int') @@ -225,6 +227,8 @@ const QLitObject %(c_name)s = %(c_string)s; def visit_command(self, name, info, ifcond, features, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig, coroutine): + assert self._schema is not None + arg_type = arg_type or self._schema.the_empty_object_type ret_type = ret_type or self._schema.the_empty_object_type obj = {'arg-type': self._use_type(arg_type), @@ -234,6 +238,7 @@ const QLitObject %(c_name)s = %(c_string)s; self._gen_tree(name, 'command', obj, ifcond, features) def visit_event(self, name, info, ifcond, features, arg_type, boxed): + assert self._schema is not None arg_type = arg_type or self._schema.the_empty_object_type self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, ifcond, features) -- cgit v1.2.3 From d70f5130f6779042f43d315a9bf2dd182e4708c7 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:53 -0500 Subject: qapi/introspect.py: use _make_tree for features nodes At present, we open-code this in _make_tree itself; but if the structure of the tree changes, this is brittle. Use an explicit recursive call to _make_tree when appropriate to help keep the interior node typing consistent. A consequence of doing this is that the 'ifcond' key of the features dict will be omitted when ifcond is false-ish, just like it is omitted in top-level calls to _make_tree. This also increases consistency in our handling of this property. Signed-off-by: John Snow Message-Id: <20210216021809.134886-4-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 43ab4be1f7..3295a15c98 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -30,7 +30,9 @@ def _make_tree(obj, ifcond, features, extra=None): if ifcond: extra['if'] = ifcond if features: - obj['features'] = [(f.name, {'if': f.ifcond}) for f in features] + obj['features'] = [ + _make_tree(f.name, f.ifcond, None) for f in features + ] if extra: return (obj, extra) return obj -- cgit v1.2.3 From 84bece7dd4ce7ca5a36bc996452ebc882f59ae54 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:54 -0500 Subject: qapi/introspect.py: add _gen_features helper _make_tree might receive a dict (a SchemaInfo object) or some other type (usually, a string) for its obj parameter. Adding features information should arguably be performed by the caller at such a time when we know the type of the object and don't have to re-interrogate it. Signed-off-by: John Snow Message-Id: <20210216021809.134886-5-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 3295a15c98..4749f65ea3 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -24,15 +24,11 @@ from .schema import ( ) -def _make_tree(obj, ifcond, features, extra=None): +def _make_tree(obj, ifcond, extra=None): if extra is None: extra = {} if ifcond: extra['if'] = ifcond - if features: - obj['features'] = [ - _make_tree(f.name, f.ifcond, None) for f in features - ] if extra: return (obj, extra) return obj @@ -169,6 +165,10 @@ const QLitObject %(c_name)s = %(c_string)s; return '[' + self._use_type(typ.element_type) + ']' return self._name(typ.name) + @staticmethod + def _gen_features(features): + return [_make_tree(f.name, f.ifcond) for f in features] + def _gen_tree(self, name, mtype, obj, ifcond, features): extra = None if mtype not in ('command', 'event', 'builtin', 'array'): @@ -179,13 +179,17 @@ const QLitObject %(c_name)s = %(c_string)s; name = self._name(name) obj['name'] = name obj['meta-type'] = mtype - self._trees.append(_make_tree(obj, ifcond, features, extra)) + if features: + obj['features'] = self._gen_features(features) + self._trees.append(_make_tree(obj, ifcond, extra)) def _gen_member(self, member): obj = {'name': member.name, 'type': self._use_type(member.type)} if member.optional: obj['default'] = None - return _make_tree(obj, member.ifcond, member.features) + if member.features: + obj['features'] = self._gen_features(member.features) + return _make_tree(obj, member.ifcond) def _gen_variants(self, tag_name, variants): return {'tag': tag_name, @@ -193,7 +197,7 @@ const QLitObject %(c_name)s = %(c_string)s; def _gen_variant(self, variant): obj = {'case': variant.name, 'type': self._use_type(variant.type)} - return _make_tree(obj, variant.ifcond, None) + return _make_tree(obj, variant.ifcond) def visit_builtin_type(self, name, info, json_type): self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) -- cgit v1.2.3 From 055569603d150a18e4d976bb58092206f5024814 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:55 -0500 Subject: qapi/introspect.py: guard against ifcond/comment misuse _tree_to_qlit is called recursively on dict values (isolated from their keys); at such a point in generating output it is too late to apply an ifcond. Similarly, comments do not necessarily have a "tidy" place they can be printed in such a circumstance. Forbid this usage by renaming "suppress_first_indent" to "dict_value" to emphasize that indents are suppressed only for the benefit of dict values; then add an assertion assuring we do not pass ifcond/comments in this case. Signed-off-by: John Snow Message-Id: <20210216021809.134886-6-jsnow@redhat.com> Reviewed-by: Markus Armbruster [Comment wrapped to conform to PEP 8] Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 4749f65ea3..a111cec725 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -34,7 +34,7 @@ def _make_tree(obj, ifcond, extra=None): return obj -def _tree_to_qlit(obj, level=0, suppress_first_indent=False): +def _tree_to_qlit(obj, level=0, dict_value=False): def indent(level): return level * 4 * ' ' @@ -43,6 +43,13 @@ def _tree_to_qlit(obj, level=0, suppress_first_indent=False): ifobj, extra = obj ifcond = extra.get('if') comment = extra.get('comment') + + # NB: _tree_to_qlit is called recursively on the values of a + # key:value pair; those values can't be decorated with + # comments or conditionals. + msg = "dict values cannot have attached comments or if-conditionals." + assert not dict_value, msg + ret = '' if comment: ret += indent(level) + '/* %s */\n' % comment @@ -54,7 +61,7 @@ def _tree_to_qlit(obj, level=0, suppress_first_indent=False): return ret ret = '' - if not suppress_first_indent: + if not dict_value: ret += indent(level) if obj is None: ret += 'QLIT_QNULL' -- cgit v1.2.3 From 84cf09915fdaba38a5d5f21baff38e94dd98607f Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:56 -0500 Subject: qapi/introspect.py: Unify return type of _make_tree() Returning two different types conditionally can be complicated to type. Return one type for consistency. Signed-off-by: John Snow Message-Id: <20210216021809.134886-7-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index a111cec725..7cce0de975 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -29,9 +29,7 @@ def _make_tree(obj, ifcond, extra=None): extra = {} if ifcond: extra['if'] = ifcond - if extra: - return (obj, extra) - return obj + return (obj, extra) def _tree_to_qlit(obj, level=0, dict_value=False): -- cgit v1.2.3 From 5f50cede4afe71c91b028d6433adb7b8db9a73b8 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:57 -0500 Subject: qapi/introspect.py: replace 'extra' dict with 'comment' argument This is only used to pass in a dictionary with a comment already set, so skip the runaround and just accept the (optional) comment. Signed-off-by: John Snow Message-Id: <20210216021809.134886-8-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 7cce0de975..c4326d42cb 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -10,6 +10,8 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ +from typing import Optional + from .common import ( c_name, gen_endif, @@ -24,11 +26,12 @@ from .schema import ( ) -def _make_tree(obj, ifcond, extra=None): - if extra is None: - extra = {} +def _make_tree(obj, ifcond, comment=None): + extra = {} if ifcond: extra['if'] = ifcond + if comment: + extra['comment'] = comment return (obj, extra) @@ -175,18 +178,18 @@ const QLitObject %(c_name)s = %(c_string)s; return [_make_tree(f.name, f.ifcond) for f in features] def _gen_tree(self, name, mtype, obj, ifcond, features): - extra = None + comment: Optional[str] = None if mtype not in ('command', 'event', 'builtin', 'array'): if not self._unmask: # Output a comment to make it easy to map masked names # back to the source when reading the generated output. - extra = {'comment': '"%s" = %s' % (self._name(name), name)} + comment = f'"{self._name(name)}" = {name}' name = self._name(name) obj['name'] = name obj['meta-type'] = mtype if features: obj['features'] = self._gen_features(features) - self._trees.append(_make_tree(obj, ifcond, extra)) + self._trees.append(_make_tree(obj, ifcond, comment)) def _gen_member(self, member): obj = {'name': member.name, 'type': self._use_type(member.type)} -- cgit v1.2.3 From d4c5b429fd18d30f2e23aef6e944fe40f2319024 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:58 -0500 Subject: qapi/introspect.py: Always define all 'extra' dict keys This mimics how a typed object works, where 'if' and 'comment' are always set, regardless of if they have a value set or not. It is safe to do this because of the way that _tree_to_qlit processes these values (using dict.get with a default of None), resulting in no change of output from _tree_to_qlit. There are no other users of this data. Signed-off-by: John Snow Message-Id: <20210216021809.134886-9-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index c4326d42cb..88af5383d5 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -27,11 +27,10 @@ from .schema import ( def _make_tree(obj, ifcond, comment=None): - extra = {} - if ifcond: - extra['if'] = ifcond - if comment: - extra['comment'] = comment + extra = { + 'if': ifcond, + 'comment': comment + } return (obj, extra) -- cgit v1.2.3 From 9db27346133b5b83699af49d290662d346683259 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:17:59 -0500 Subject: qapi/introspect.py: Introduce preliminary tree typing The types will be used in forthcoming patches to add typing. These types describe the layout and structure of the objects passed to _tree_to_qlit, but lack the power to describe annotations until the next commit. Signed-off-by: John Snow Message-Id: <20210216021809.134886-10-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 88af5383d5..c271006100 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -10,7 +10,13 @@ This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. """ -from typing import Optional +from typing import ( + Any, + Dict, + List, + Optional, + Union, +) from .common import ( c_name, @@ -26,6 +32,29 @@ from .schema import ( ) +# This module constructs a tree data structure that is used to +# generate the introspection information for QEMU. It is shaped +# like a JSON value. +# +# A complexity over JSON is that our values may or may not be annotated. +# +# Un-annotated values may be: +# Scalar: str, bool, None. +# Non-scalar: List, Dict +# _value = Union[str, bool, None, Dict[str, JSONValue], List[JSONValue]] +# +# With optional annotations, the type of all values is: +# JSONValue = Union[_Value, Annotated[_Value]] +# +# Sadly, mypy does not support recursive types; so the _Stub alias is used to +# mark the imprecision in the type model where we'd otherwise use JSONValue. +_Stub = Any +_Scalar = Union[str, bool, None] +_NonScalar = Union[Dict[str, _Stub], List[_Stub]] +_Value = Union[_Scalar, _NonScalar] +# JSONValue = TODO, in a forthcoming commit. + + def _make_tree(obj, ifcond, comment=None): extra = { 'if': ifcond, -- cgit v1.2.3 From 4f7f97a7b3d3321f02f854829f37637d6e144a57 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:00 -0500 Subject: qapi/introspect.py: create a typed 'Annotated' data strutcure Presently, we use a tuple to attach a dict containing annotations (comments and compile-time conditionals) to a tree node. This is undesirable because dicts are difficult to strongly type; promoting it to a real class allows us to name the values and types of the annotations we are expecting. In terms of typing, the Annotated type serves as a generic container where the annotated node's type is preserved, allowing for greater specificity than we'd be able to provide without a generic. Signed-off-by: John Snow Message-Id: <20210216021809.134886-11-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index c271006100..715220afe7 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -13,8 +13,12 @@ See the COPYING file in the top-level directory. from typing import ( Any, Dict, + Generic, + Iterable, List, Optional, + Tuple, + TypeVar, Union, ) @@ -52,15 +56,25 @@ _Stub = Any _Scalar = Union[str, bool, None] _NonScalar = Union[Dict[str, _Stub], List[_Stub]] _Value = Union[_Scalar, _NonScalar] -# JSONValue = TODO, in a forthcoming commit. +JSONValue = Union[_Value, 'Annotated[_Value]'] -def _make_tree(obj, ifcond, comment=None): - extra = { - 'if': ifcond, - 'comment': comment - } - return (obj, extra) +_ValueT = TypeVar('_ValueT', bound=_Value) + + +class Annotated(Generic[_ValueT]): + """ + Annotated generally contains a SchemaInfo-like type (as a dict), + But it also used to wrap comments/ifconds around scalar leaf values, + for the benefit of features and enums. + """ + # TODO: Remove after Python 3.7 adds @dataclass: + # pylint: disable=too-few-public-methods + def __init__(self, value: _ValueT, ifcond: Iterable[str], + comment: Optional[str] = None): + self.value = value + self.comment: Optional[str] = comment + self.ifcond: Tuple[str, ...] = tuple(ifcond) def _tree_to_qlit(obj, level=0, dict_value=False): @@ -68,11 +82,7 @@ def _tree_to_qlit(obj, level=0, dict_value=False): def indent(level): return level * 4 * ' ' - if isinstance(obj, tuple): - ifobj, extra = obj - ifcond = extra.get('if') - comment = extra.get('comment') - + if isinstance(obj, Annotated): # NB: _tree_to_qlit is called recursively on the values of a # key:value pair; those values can't be decorated with # comments or conditionals. @@ -80,13 +90,13 @@ def _tree_to_qlit(obj, level=0, dict_value=False): assert not dict_value, msg ret = '' - if comment: - ret += indent(level) + '/* %s */\n' % comment - if ifcond: - ret += gen_if(ifcond) - ret += _tree_to_qlit(ifobj, level) - if ifcond: - ret += '\n' + gen_endif(ifcond) + if obj.comment: + ret += indent(level) + '/* %s */\n' % obj.comment + if obj.ifcond: + ret += gen_if(obj.ifcond) + ret += _tree_to_qlit(obj.value, level) + if obj.ifcond: + ret += '\n' + gen_endif(obj.ifcond) return ret ret = '' @@ -203,7 +213,7 @@ const QLitObject %(c_name)s = %(c_string)s; @staticmethod def _gen_features(features): - return [_make_tree(f.name, f.ifcond) for f in features] + return [Annotated(f.name, f.ifcond) for f in features] def _gen_tree(self, name, mtype, obj, ifcond, features): comment: Optional[str] = None @@ -217,7 +227,7 @@ const QLitObject %(c_name)s = %(c_string)s; obj['meta-type'] = mtype if features: obj['features'] = self._gen_features(features) - self._trees.append(_make_tree(obj, ifcond, comment)) + self._trees.append(Annotated(obj, ifcond, comment)) def _gen_member(self, member): obj = {'name': member.name, 'type': self._use_type(member.type)} @@ -225,7 +235,7 @@ const QLitObject %(c_name)s = %(c_string)s; obj['default'] = None if member.features: obj['features'] = self._gen_features(member.features) - return _make_tree(obj, member.ifcond) + return Annotated(obj, member.ifcond) def _gen_variants(self, tag_name, variants): return {'tag': tag_name, @@ -233,16 +243,17 @@ const QLitObject %(c_name)s = %(c_string)s; def _gen_variant(self, variant): obj = {'case': variant.name, 'type': self._use_type(variant.type)} - return _make_tree(obj, variant.ifcond) + return Annotated(obj, variant.ifcond) def visit_builtin_type(self, name, info, json_type): self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) def visit_enum_type(self, name, info, ifcond, features, members, prefix): - self._gen_tree(name, 'enum', - {'values': [_make_tree(m.name, m.ifcond, None) - for m in members]}, - ifcond, features) + self._gen_tree( + name, 'enum', + {'values': [Annotated(m.name, m.ifcond) for m in members]}, + ifcond, features + ) def visit_array_type(self, name, info, ifcond, element_type): element = self._use_type(element_type) @@ -259,12 +270,13 @@ const QLitObject %(c_name)s = %(c_string)s; self._gen_tree(name, 'object', obj, ifcond, features) def visit_alternate_type(self, name, info, ifcond, features, variants): - self._gen_tree(name, 'alternate', - {'members': [ - _make_tree({'type': self._use_type(m.type)}, - m.ifcond, None) - for m in variants.variants]}, - ifcond, features) + self._gen_tree( + name, 'alternate', + {'members': [Annotated({'type': self._use_type(m.type)}, + m.ifcond) + for m in variants.variants]}, + ifcond, features + ) def visit_command(self, name, info, ifcond, features, arg_type, ret_type, gen, success_response, boxed, -- cgit v1.2.3 From 2a6c161bbf92a6259cb4b1687a0154492e78b8f5 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:01 -0500 Subject: qapi/introspect.py: improve _tree_to_qlit error message Trivial; make the error message just a pinch more explicit in case we trip this by accident in the future. Signed-off-by: John Snow Message-Id: <20210216021809.134886-12-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 715220afe7..96dfbb4cef 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -126,7 +126,9 @@ def _tree_to_qlit(obj, level=0, dict_value=False): elif isinstance(obj, bool): ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false') else: - assert False # not implemented + raise NotImplementedError( + f"type '{type(obj).__name__}' not implemented" + ) if level > 0: ret += ',' return ret -- cgit v1.2.3 From c0e8d9f3c199357d89f362c54e1edced82c3c084 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:02 -0500 Subject: qapi/introspect.py: improve readability of _tree_to_qlit Subjective, but I find getting rid of the comprehensions helps. Also, divide the sections into scalar and non-scalar sections, and remove old-style string formatting. Signed-off-by: John Snow Message-Id: <20210216021809.134886-13-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 96dfbb4cef..26e6f73e5d 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -91,7 +91,7 @@ def _tree_to_qlit(obj, level=0, dict_value=False): ret = '' if obj.comment: - ret += indent(level) + '/* %s */\n' % obj.comment + ret += indent(level) + f"/* {obj.comment} */\n" if obj.ifcond: ret += gen_if(obj.ifcond) ret += _tree_to_qlit(obj.value, level) @@ -102,33 +102,36 @@ def _tree_to_qlit(obj, level=0, dict_value=False): ret = '' if not dict_value: ret += indent(level) + + # Scalars: if obj is None: ret += 'QLIT_QNULL' elif isinstance(obj, str): - ret += 'QLIT_QSTR(' + to_c_string(obj) + ')' + ret += f"QLIT_QSTR({to_c_string(obj)})" + elif isinstance(obj, bool): + ret += f"QLIT_QBOOL({str(obj).lower()})" + + # Non-scalars: elif isinstance(obj, list): - elts = [_tree_to_qlit(elt, level + 1).strip('\n') - for elt in obj] - elts.append(indent(level + 1) + "{}") ret += 'QLIT_QLIST(((QLitObject[]) {\n' - ret += '\n'.join(elts) + '\n' + for value in obj: + ret += _tree_to_qlit(value, level + 1).strip('\n') + '\n' + ret += indent(level + 1) + '{}\n' ret += indent(level) + '}))' elif isinstance(obj, dict): - elts = [] - for key, value in sorted(obj.items()): - elts.append(indent(level + 1) + '{ %s, %s }' % - (to_c_string(key), - _tree_to_qlit(value, level + 1, True))) - elts.append(indent(level + 1) + '{}') ret += 'QLIT_QDICT(((QLitDictEntry[]) {\n' - ret += ',\n'.join(elts) + '\n' + for key, value in sorted(obj.items()): + ret += indent(level + 1) + "{{ {:s}, {:s} }},\n".format( + to_c_string(key), + _tree_to_qlit(value, level + 1, dict_value=True) + ) + ret += indent(level + 1) + '{}\n' ret += indent(level) + '}))' - elif isinstance(obj, bool): - ret += 'QLIT_QBOOL(%s)' % ('true' if obj else 'false') else: raise NotImplementedError( f"type '{type(obj).__name__}' not implemented" ) + if level > 0: ret += ',' return ret -- cgit v1.2.3 From cf5db2142b674ebab8e0799fd07e3884cfb00fc5 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:03 -0500 Subject: qapi/introspect.py: remove _gen_variants helper It is easier to give a name to all of the dictly-typed objects we pass around in introspect.py by removing this helper, as it does not return an object that has any knowable type by itself. Inline it into its only caller instead. Signed-off-by: John Snow Message-Id: <20210216021809.134886-14-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 26e6f73e5d..da7bc8883c 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -242,10 +242,6 @@ const QLitObject %(c_name)s = %(c_string)s; obj['features'] = self._gen_features(member.features) return Annotated(obj, member.ifcond) - def _gen_variants(self, tag_name, variants): - return {'tag': tag_name, - 'variants': [self._gen_variant(v) for v in variants]} - def _gen_variant(self, variant): obj = {'case': variant.name, 'type': self._use_type(variant.type)} return Annotated(obj, variant.ifcond) @@ -269,9 +265,8 @@ const QLitObject %(c_name)s = %(c_string)s; members, variants): obj = {'members': [self._gen_member(m) for m in members]} if variants: - obj.update(self._gen_variants(variants.tag_member.name, - variants.variants)) - + obj['tag'] = variants.tag_member.name + obj['variants'] = [self._gen_variant(v) for v in variants.variants] self._gen_tree(name, 'object', obj, ifcond, features) def visit_alternate_type(self, name, info, ifcond, features, variants): -- cgit v1.2.3 From 82b52f6b84585f1ea2a6ceb9fc0f264e3b13ed48 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:04 -0500 Subject: qapi/introspect.py: add type hint annotations NB: The type aliases (SchemaInfo et al) declare intent for some of the "dictly-typed" objects we pass around in introspect.py. They do not enforce the shape of those objects, and cannot, until Python 3.7 or later. (And even then, it may not be "worth it".) Annotations are also added to the QAPISchemaEntity __init__ method in schema.py to allow mypy to statically prove the type of typ.name, needed to prove the return type of QAPISchemaGenIntrospectVisitor._use_type(). Signed-off-by: John Snow Message-Id: <20210216021809.134886-15-jsnow@redhat.com> Reviewed-by: Markus Armbruster [Note on QAPISchemaEntity.__init__() squashed into commit message, Comment wrapped to conform to PEP 8] Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 125 +++++++++++++++++++++++++++++++++------------ scripts/qapi/mypy.ini | 5 -- scripts/qapi/schema.py | 2 +- 3 files changed, 93 insertions(+), 39 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index da7bc8883c..05c1a196e9 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -17,6 +17,7 @@ from typing import ( Iterable, List, Optional, + Sequence, Tuple, TypeVar, Union, @@ -30,10 +31,19 @@ from .common import ( ) from .gen import QAPISchemaMonolithicCVisitor from .schema import ( + QAPISchema, QAPISchemaArrayType, QAPISchemaBuiltinType, + QAPISchemaEntity, + QAPISchemaEnumMember, + QAPISchemaFeature, + QAPISchemaObjectType, + QAPISchemaObjectTypeMember, QAPISchemaType, + QAPISchemaVariant, + QAPISchemaVariants, ) +from .source import QAPISourceInfo # This module constructs a tree data structure that is used to @@ -58,6 +68,16 @@ _NonScalar = Union[Dict[str, _Stub], List[_Stub]] _Value = Union[_Scalar, _NonScalar] JSONValue = Union[_Value, 'Annotated[_Value]'] +# These types are based on structures defined in QEMU's schema, so we +# lack precise types for them here. Python 3.6 does not offer +# TypedDict constructs, so they are broadly typed here as simple +# Python Dicts. +SchemaInfo = Dict[str, object] +SchemaInfoObject = Dict[str, object] +SchemaInfoObjectVariant = Dict[str, object] +SchemaInfoObjectMember = Dict[str, object] +SchemaInfoCommand = Dict[str, object] + _ValueT = TypeVar('_ValueT', bound=_Value) @@ -77,9 +97,11 @@ class Annotated(Generic[_ValueT]): self.ifcond: Tuple[str, ...] = tuple(ifcond) -def _tree_to_qlit(obj, level=0, dict_value=False): +def _tree_to_qlit(obj: JSONValue, + level: int = 0, + dict_value: bool = False) -> str: - def indent(level): + def indent(level: int) -> str: return level * 4 * ' ' if isinstance(obj, Annotated): @@ -137,21 +159,21 @@ def _tree_to_qlit(obj, level=0, dict_value=False): return ret -def to_c_string(string): +def to_c_string(string: str) -> str: return '"' + string.replace('\\', r'\\').replace('"', r'\"') + '"' class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): - def __init__(self, prefix, unmask): + def __init__(self, prefix: str, unmask: bool): super().__init__( prefix, 'qapi-introspect', ' * QAPI/QMP schema introspection', __doc__) self._unmask = unmask - self._schema = None - self._trees = [] - self._used_types = [] - self._name_map = {} + self._schema: Optional[QAPISchema] = None + self._trees: List[Annotated[SchemaInfo]] = [] + self._used_types: List[QAPISchemaType] = [] + self._name_map: Dict[str, str] = {} self._genc.add(mcgen(''' #include "qemu/osdep.h" #include "%(prefix)sqapi-introspect.h" @@ -159,10 +181,10 @@ class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor): ''', prefix=prefix)) - def visit_begin(self, schema): + def visit_begin(self, schema: QAPISchema) -> None: self._schema = schema - def visit_end(self): + def visit_end(self) -> None: # visit the types that are actually used for typ in self._used_types: typ.visit(self) @@ -184,18 +206,18 @@ const QLitObject %(c_name)s = %(c_string)s; self._used_types = [] self._name_map = {} - def visit_needed(self, entity): + def visit_needed(self, entity: QAPISchemaEntity) -> bool: # Ignore types on first pass; visit_end() will pick up used types return not isinstance(entity, QAPISchemaType) - def _name(self, name): + def _name(self, name: str) -> str: if self._unmask: return name if name not in self._name_map: self._name_map[name] = '%d' % len(self._name_map) return self._name_map[name] - def _use_type(self, typ): + def _use_type(self, typ: QAPISchemaType) -> str: assert self._schema is not None # Map the various integer types to plain int @@ -217,10 +239,13 @@ const QLitObject %(c_name)s = %(c_string)s; return self._name(typ.name) @staticmethod - def _gen_features(features): + def _gen_features(features: List[QAPISchemaFeature] + ) -> List[Annotated[str]]: return [Annotated(f.name, f.ifcond) for f in features] - def _gen_tree(self, name, mtype, obj, ifcond, features): + def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object], + ifcond: Sequence[str], + features: Optional[List[QAPISchemaFeature]]) -> None: comment: Optional[str] = None if mtype not in ('command', 'event', 'builtin', 'array'): if not self._unmask: @@ -234,42 +259,65 @@ const QLitObject %(c_name)s = %(c_string)s; obj['features'] = self._gen_features(features) self._trees.append(Annotated(obj, ifcond, comment)) - def _gen_member(self, member): - obj = {'name': member.name, 'type': self._use_type(member.type)} + def _gen_member(self, member: QAPISchemaObjectTypeMember + ) -> Annotated[SchemaInfoObjectMember]: + obj: SchemaInfoObjectMember = { + 'name': member.name, + 'type': self._use_type(member.type) + } if member.optional: obj['default'] = None if member.features: obj['features'] = self._gen_features(member.features) return Annotated(obj, member.ifcond) - def _gen_variant(self, variant): - obj = {'case': variant.name, 'type': self._use_type(variant.type)} + def _gen_variant(self, variant: QAPISchemaVariant + ) -> Annotated[SchemaInfoObjectVariant]: + obj: SchemaInfoObjectVariant = { + 'case': variant.name, + 'type': self._use_type(variant.type) + } return Annotated(obj, variant.ifcond) - def visit_builtin_type(self, name, info, json_type): + def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo], + json_type: str) -> None: self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) - def visit_enum_type(self, name, info, ifcond, features, members, prefix): + def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo], + ifcond: Sequence[str], + features: List[QAPISchemaFeature], + members: List[QAPISchemaEnumMember], + prefix: Optional[str]) -> None: self._gen_tree( name, 'enum', {'values': [Annotated(m.name, m.ifcond) for m in members]}, ifcond, features ) - def visit_array_type(self, name, info, ifcond, element_type): + def visit_array_type(self, name: str, info: Optional[QAPISourceInfo], + ifcond: Sequence[str], + element_type: QAPISchemaType) -> None: element = self._use_type(element_type) self._gen_tree('[' + element + ']', 'array', {'element-type': element}, ifcond, None) - def visit_object_type_flat(self, name, info, ifcond, features, - members, variants): - obj = {'members': [self._gen_member(m) for m in members]} + def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo], + ifcond: Sequence[str], + features: List[QAPISchemaFeature], + members: List[QAPISchemaObjectTypeMember], + variants: Optional[QAPISchemaVariants]) -> None: + obj: SchemaInfoObject = { + 'members': [self._gen_member(m) for m in members] + } if variants: obj['tag'] = variants.tag_member.name obj['variants'] = [self._gen_variant(v) for v in variants.variants] self._gen_tree(name, 'object', obj, ifcond, features) - def visit_alternate_type(self, name, info, ifcond, features, variants): + def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo], + ifcond: Sequence[str], + features: List[QAPISchemaFeature], + variants: QAPISchemaVariants) -> None: self._gen_tree( name, 'alternate', {'members': [Annotated({'type': self._use_type(m.type)}, @@ -278,27 +326,38 @@ const QLitObject %(c_name)s = %(c_string)s; ifcond, features ) - def visit_command(self, name, info, ifcond, features, - arg_type, ret_type, gen, success_response, boxed, - allow_oob, allow_preconfig, coroutine): + def visit_command(self, name: str, info: Optional[QAPISourceInfo], + ifcond: Sequence[str], + features: List[QAPISchemaFeature], + arg_type: Optional[QAPISchemaObjectType], + ret_type: Optional[QAPISchemaType], gen: bool, + success_response: bool, boxed: bool, allow_oob: bool, + allow_preconfig: bool, coroutine: bool) -> None: assert self._schema is not None arg_type = arg_type or self._schema.the_empty_object_type ret_type = ret_type or self._schema.the_empty_object_type - obj = {'arg-type': self._use_type(arg_type), - 'ret-type': self._use_type(ret_type)} + obj: SchemaInfoCommand = { + 'arg-type': self._use_type(arg_type), + 'ret-type': self._use_type(ret_type) + } if allow_oob: obj['allow-oob'] = allow_oob self._gen_tree(name, 'command', obj, ifcond, features) - def visit_event(self, name, info, ifcond, features, arg_type, boxed): + def visit_event(self, name: str, info: Optional[QAPISourceInfo], + ifcond: Sequence[str], features: List[QAPISchemaFeature], + arg_type: Optional[QAPISchemaObjectType], + boxed: bool) -> None: assert self._schema is not None + arg_type = arg_type or self._schema.the_empty_object_type self._gen_tree(name, 'event', {'arg-type': self._use_type(arg_type)}, ifcond, features) -def gen_introspect(schema, output_dir, prefix, opt_unmask): +def gen_introspect(schema: QAPISchema, output_dir: str, prefix: str, + opt_unmask: bool) -> None: vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask) schema.visit(vis) vis.write(output_dir) diff --git a/scripts/qapi/mypy.ini b/scripts/qapi/mypy.ini index 04bd5db527..0a000d58b3 100644 --- a/scripts/qapi/mypy.ini +++ b/scripts/qapi/mypy.ini @@ -13,11 +13,6 @@ disallow_untyped_defs = False disallow_incomplete_defs = False check_untyped_defs = False -[mypy-qapi.introspect] -disallow_untyped_defs = False -disallow_incomplete_defs = False -check_untyped_defs = False - [mypy-qapi.parser] disallow_untyped_defs = False disallow_incomplete_defs = False diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 353e8020a2..ff16578f6d 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -28,7 +28,7 @@ from .parser import QAPISchemaParser class QAPISchemaEntity: meta: Optional[str] = None - def __init__(self, name, info, doc, ifcond=None, features=None): + def __init__(self, name: str, info, doc, ifcond=None, features=None): assert name is None or isinstance(name, str) for f in features or []: assert isinstance(f, QAPISchemaFeature) -- cgit v1.2.3 From 5444dedfc723d43fd29e8017e38089aac359c3d1 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:05 -0500 Subject: qapi/introspect.py: Add docstrings to _gen_tree and _tree_to_qlit Signed-off-by: John Snow Message-Id: <20210216021809.134886-16-jsnow@redhat.com> Reviewed-by: Markus Armbruster [Doc string improvements squashed in] Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 05c1a196e9..15cce6854d 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -100,6 +100,15 @@ class Annotated(Generic[_ValueT]): def _tree_to_qlit(obj: JSONValue, level: int = 0, dict_value: bool = False) -> str: + """ + Convert the type tree into a QLIT C string, recursively. + + :param obj: The value to convert. + This value may not be Annotated when dict_value is True. + :param level: The indentation level for this particular value. + :param dict_value: True when the value being processed belongs to a + dict key; which suppresses the output indent. + """ def indent(level: int) -> str: return level * 4 * ' ' @@ -246,6 +255,17 @@ const QLitObject %(c_name)s = %(c_string)s; def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object], ifcond: Sequence[str], features: Optional[List[QAPISchemaFeature]]) -> None: + """ + Build and append a SchemaInfo object to self._trees. + + :param name: The SchemaInfo's name. + :param mtype: The SchemaInfo's meta-type. + :param obj: Additional SchemaInfo members, as appropriate for + the meta-type. + :param ifcond: Conditionals to apply to the SchemaInfo. + :param features: The SchemaInfo's features. + Will be omitted from the output if empty. + """ comment: Optional[str] = None if mtype not in ('command', 'event', 'builtin', 'array'): if not self._unmask: -- cgit v1.2.3 From cf26906c4a1c803e907036a2b45f80dea3a89bbb Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:06 -0500 Subject: qapi/introspect.py: Update copyright and authors list To reflect the work that went into strictly typing introspect.py, punish myself by claiming credit. Signed-off-by: John Snow Message-Id: <20210216021809.134886-17-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 15cce6854d..e770c9432b 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -1,10 +1,11 @@ """ QAPI introspection generator -Copyright (C) 2015-2018 Red Hat, Inc. +Copyright (C) 2015-2021 Red Hat, Inc. Authors: Markus Armbruster + John Snow This work is licensed under the terms of the GNU GPL, version 2. See the COPYING file in the top-level directory. -- cgit v1.2.3 From cea53c31b7b29fcd239b49d738111676700924ea Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:07 -0500 Subject: qapi/introspect.py: Type _gen_tree variants as Sequence[str] Optional[List] is clunky; an empty sequence can more elegantly convey "no variants". By downgrading "List" to "Sequence", we can also accept tuples; this is useful for the empty tuple specifically, which we may use as a default parameter because it is immutable. Signed-off-by: John Snow Message-Id: <20210216021809.134886-18-jsnow@redhat.com> Reviewed-by: Markus Armbruster [Doc string touched up] Reviewed-by: Markus Armbruster --- scripts/qapi/introspect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index e770c9432b..d0b0fd19ed 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -249,13 +249,13 @@ const QLitObject %(c_name)s = %(c_string)s; return self._name(typ.name) @staticmethod - def _gen_features(features: List[QAPISchemaFeature] + def _gen_features(features: Sequence[QAPISchemaFeature] ) -> List[Annotated[str]]: return [Annotated(f.name, f.ifcond) for f in features] def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object], ifcond: Sequence[str], - features: Optional[List[QAPISchemaFeature]]) -> None: + features: Sequence[QAPISchemaFeature] = ()) -> None: """ Build and append a SchemaInfo object to self._trees. @@ -302,7 +302,7 @@ const QLitObject %(c_name)s = %(c_string)s; def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo], json_type: str) -> None: - self._gen_tree(name, 'builtin', {'json-type': json_type}, [], None) + self._gen_tree(name, 'builtin', {'json-type': json_type}, []) def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo], ifcond: Sequence[str], @@ -320,7 +320,7 @@ const QLitObject %(c_name)s = %(c_string)s; element_type: QAPISchemaType) -> None: element = self._use_type(element_type) self._gen_tree('[' + element + ']', 'array', {'element-type': element}, - ifcond, None) + ifcond) def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo], ifcond: Sequence[str], -- cgit v1.2.3 From 9b77d946990e7497469bb98171b90b4f3ab186a9 Mon Sep 17 00:00:00 2001 From: John Snow Date: Mon, 15 Feb 2021 21:18:08 -0500 Subject: qapi/introspect.py: set _gen_tree's default ifcond argument to () We don't need to create an empty, mutable list to pass to _gen_tree; since it is now typed as a Sequence, we can use the empty tuple as a default and omit the argument. Signed-off-by: John Snow Message-Id: <20210216021809.134886-19-jsnow@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/introspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index d0b0fd19ed..9a348ca2e5 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -254,7 +254,7 @@ const QLitObject %(c_name)s = %(c_string)s; return [Annotated(f.name, f.ifcond) for f in features] def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object], - ifcond: Sequence[str], + ifcond: Sequence[str] = (), features: Sequence[QAPISchemaFeature] = ()) -> None: """ Build and append a SchemaInfo object to self._trees. @@ -302,7 +302,7 @@ const QLitObject %(c_name)s = %(c_string)s; def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo], json_type: str) -> None: - self._gen_tree(name, 'builtin', {'json-type': json_type}, []) + self._gen_tree(name, 'builtin', {'json-type': json_type}) def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo], ifcond: Sequence[str], -- cgit v1.2.3