All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor
@ 2021-06-18 10:24 marcandre.lureau
  2021-06-18 10:24 ` [PATCH v6 01/11] docs: update the documentation upfront about schema configuration marcandre.lureau
                   ` (11 more replies)
  0 siblings, 12 replies; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:24 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Hi,

This series makes the 'if' conditions less liberal, by formalizing a simple
expression tree based on bare boolean logic of configure option identifiers.

(this allows to express conditions in Rust in my QAPI-Rust PoC series)

thanks

v6: after Markus review
 - drop the predicate tree, QAPISchemaIfCond simply holds the original object
 - introduce the dict operations ('all', 'any', 'not') in multiple patches
 - split QAPISchemaIfCond introduction in multiple patches
 - replace __bool__ usage with is_present()
 - removed __eq__
 - move cgen/docgen implementation to common.py
 - doc & commit message updates
 - rebased

v5:
 - drop the [ COND, ... ] sugar form
 - move documentation update as first patch
 - documentation and commit message tweaks

v4:
 - keep gen_if/gen_endif in common.py, reducing C codegen in schema.py
 - raise NotImplemented instead of False for unhandled __eq__
 - change check_if() to keep the json/raw form, add _make_if() to build a
   QAPISchemaIfCond
 - improve __repr__ usage
 - drop ABC usage
 - tweaks here and there
 - add various commit tags

v3:
 - rebasing on queued pt4 (after waiting for it to land)
 - improve documentation generation, to be more human-friendly
 - drop typing annotations from schema.py (not yet queued)
 - commit message tweaks

v2:
 - fix the normalization step to handle recursive expr
 - replace IfCond by QAPISchemaIf (JohnS)
 - commit message and documentation tweaks
 - mypy/flake8/isort

Marc-André Lureau (11):
  docs: update the documentation upfront about schema configuration
  qapi: wrap Sequence[str] in an object
  qapi: add QAPISchemaIfCond.is_present()
  qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond
  qapi: introduce QAPISchemaIfCond.cgen()
  qapidoc: introduce QAPISchemaIfCond.docgen()
  qapi: replace if condition list with dict {'all': [...]}
  qapi: add 'any' condition
  qapi: convert 'if' C-expressions to the new syntax tree
  qapi: add 'not' condition operation
  qapi: make 'if' condition strings simple identifiers

 docs/devel/qapi-code-gen.txt                  | 30 ++++---
 docs/sphinx/qapidoc.py                        | 22 ++---
 qapi/block-core.json                          | 16 ++--
 qapi/block-export.json                        |  6 +-
 qapi/char.json                                | 12 +--
 qapi/machine-target.json                      | 28 ++++--
 qapi/migration.json                           | 10 +--
 qapi/misc-target.json                         | 40 +++++----
 qapi/qom.json                                 | 10 +--
 qapi/sockets.json                             |  6 +-
 qapi/tpm.json                                 | 18 ++--
 qapi/ui.json                                  | 48 +++++------
 qga/qapi-schema.json                          |  8 +-
 tests/unit/test-qmp-cmds.c                    |  1 +
 scripts/qapi/commands.py                      |  4 +-
 scripts/qapi/common.py                        | 58 ++++++++++---
 scripts/qapi/events.py                        |  5 +-
 scripts/qapi/expr.py                          | 55 +++++++-----
 scripts/qapi/gen.py                           | 14 +--
 scripts/qapi/introspect.py                    | 26 +++---
 scripts/qapi/schema.py                        | 85 +++++++++++++------
 scripts/qapi/types.py                         | 33 +++----
 scripts/qapi/visit.py                         | 23 ++---
 .../alternate-branch-if-invalid.err           |  2 +-
 tests/qapi-schema/bad-if-empty-list.json      |  2 +-
 tests/qapi-schema/bad-if-empty.err            |  2 +-
 tests/qapi-schema/bad-if-list.err             |  2 +-
 tests/qapi-schema/bad-if-list.json            |  2 +-
 tests/qapi-schema/bad-if.err                  |  3 +-
 tests/qapi-schema/bad-if.json                 |  2 +-
 tests/qapi-schema/doc-good.json               | 15 ++--
 tests/qapi-schema/doc-good.out                | 14 +--
 tests/qapi-schema/doc-good.txt                | 21 ++++-
 tests/qapi-schema/enum-if-invalid.err         |  3 +-
 tests/qapi-schema/features-if-invalid.err     |  2 +-
 tests/qapi-schema/features-missing-name.json  |  2 +-
 tests/qapi-schema/qapi-schema-test.json       | 60 +++++++------
 tests/qapi-schema/qapi-schema-test.out        | 67 ++++++++-------
 .../qapi-schema/struct-member-if-invalid.err  |  2 +-
 tests/qapi-schema/test-qapi.py                |  4 +-
 tests/qapi-schema/union-branch-if-invalid.err |  2 +-
 .../qapi-schema/union-branch-if-invalid.json  |  2 +-
 42 files changed, 458 insertions(+), 309 deletions(-)

-- 
2.29.0




^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH v6 01/11] docs: update the documentation upfront about schema configuration
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
@ 2021-06-18 10:24 ` marcandre.lureau
  2021-07-12 14:07   ` Markus Armbruster
  2021-06-18 10:24 ` [PATCH v6 02/11] qapi: wrap Sequence[str] in an object marcandre.lureau
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:24 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Update the documentation describing the changes in this series.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Tested-by: John Snow <jsnow@redhat.com>
---
 docs/devel/qapi-code-gen.txt | 30 ++++++++++++++++++------------
 1 file changed, 18 insertions(+), 12 deletions(-)

diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
index c1cb6f987d..ec815ab47b 100644
--- a/docs/devel/qapi-code-gen.txt
+++ b/docs/devel/qapi-code-gen.txt
@@ -781,25 +781,31 @@ downstream command __com.redhat_drive-mirror.
 
 Syntax:
     COND = STRING
-         | [ STRING, ... ]
+         | { 'all: [ COND, ... ] }
+         | { 'any: [ COND, ... ] }
+         | { 'not': COND }
 
 All definitions take an optional 'if' member.  Its value must be a
-string or a list of strings.  A string is shorthand for a list
-containing just that string.  The code generated for the definition
-will then be guarded by #if STRING for each STRING in the COND list.
+string, or an object with a single member 'all', 'any' or 'not'.
+
+The C code generated for the definition will then be guarded by an #if
+preprocessing directive with an operand generated from that condition:
+
+ * STRING will generate defined(STRING)
+ * { 'all': [COND, ...] } will generate (COND && ...)
+ * { 'any': [COND, ...] } will generate (COND || ...)
+ * { 'not': COND } will generate !COND
 
 Example: a conditional struct
 
  { 'struct': 'IfStruct', 'data': { 'foo': 'int' },
-   'if': ['defined(CONFIG_FOO)', 'defined(HAVE_BAR)'] }
+   'if': { 'all': [ 'CONFIG_FOO', 'HAVE_BAR' ] } }
 
 gets its generated code guarded like this:
 
- #if defined(CONFIG_FOO)
- #if defined(HAVE_BAR)
+ #if defined(CONFIG_FOO) && defined(HAVE_BAR)
  ... generated code ...
- #endif /* defined(HAVE_BAR) */
- #endif /* defined(CONFIG_FOO) */
+ #endif /* defined(HAVE_BAR) && defined(CONFIG_FOO) */
 
 Individual members of complex types, commands arguments, and
 event-specific data can also be made conditional.  This requires the
@@ -810,7 +816,7 @@ member 'bar'
 
 { 'struct': 'IfStruct', 'data':
   { 'foo': 'int',
-    'bar': { 'type': 'int', 'if': 'defined(IFCOND)'} } }
+    'bar': { 'type': 'int', 'if': 'IFCOND'} } }
 
 A union's discriminator may not be conditional.
 
@@ -822,7 +828,7 @@ value 'bar'
 
 { 'enum': 'IfEnum', 'data':
   [ 'foo',
-    { 'name' : 'bar', 'if': 'defined(IFCOND)' } ] }
+    { 'name' : 'bar', 'if': 'IFCOND' } ] }
 
 Likewise, features can be conditional.  This requires the longhand
 form of FEATURE.
@@ -832,7 +838,7 @@ Example: a struct with conditional feature 'allow-negative-numbers'
 { 'struct': 'TestType',
   'data': { 'number': 'int' },
   'features': [ { 'name': 'allow-negative-numbers',
-                  'if': 'defined(IFCOND)' } ] }
+                  'if': 'IFCOND' } ] }
 
 Please note that you are responsible to ensure that the C code will
 compile with an arbitrary combination of conditions, since the
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 02/11] qapi: wrap Sequence[str] in an object
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
  2021-06-18 10:24 ` [PATCH v6 01/11] docs: update the documentation upfront about schema configuration marcandre.lureau
@ 2021-06-18 10:24 ` marcandre.lureau
  2021-08-02  9:21   ` Markus Armbruster
  2021-06-18 10:24 ` [PATCH v6 03/11] qapi: add QAPISchemaIfCond.is_present() marcandre.lureau
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:24 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Except for the special casing assert in _make_implicit_object_type(),
which needs to handle schema objects, it's a mechanical change.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/sphinx/qapidoc.py         | 10 +++---
 scripts/qapi/commands.py       |  4 +--
 scripts/qapi/events.py         |  5 +--
 scripts/qapi/gen.py            | 14 ++++----
 scripts/qapi/introspect.py     | 26 +++++++-------
 scripts/qapi/schema.py         | 66 +++++++++++++++++++++-------------
 scripts/qapi/types.py          | 33 ++++++++---------
 scripts/qapi/visit.py          | 23 ++++++------
 tests/qapi-schema/test-qapi.py |  4 +--
 9 files changed, 103 insertions(+), 82 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 87c67ab23f..0eac3308b2 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -116,7 +116,7 @@ def _nodes_for_ifcond(self, ifcond, with_if=True):
         the conditions are in literal-text and the commas are not.
         If with_if is False, we don't return the "(If: " and ")".
         """
-        condlist = intersperse([nodes.literal('', c) for c in ifcond],
+        condlist = intersperse([nodes.literal('', c) for c in ifcond.ifcond],
                                nodes.Text(', '))
         if not with_if:
             return condlist
@@ -139,7 +139,7 @@ def _nodes_for_one_member(self, member):
             term.append(nodes.literal('', member.type.doc_type()))
         if member.optional:
             term.append(nodes.Text(' (optional)'))
-        if member.ifcond:
+        if member.ifcond.ifcond:
             term.extend(self._nodes_for_ifcond(member.ifcond))
         return term
 
@@ -154,7 +154,7 @@ def _nodes_for_variant_when(self, variants, variant):
                 nodes.literal('', variants.tag_member.name),
                 nodes.Text(' is '),
                 nodes.literal('', '"%s"' % variant.name)]
-        if variant.ifcond:
+        if variant.ifcond.ifcond:
             term.extend(self._nodes_for_ifcond(variant.ifcond))
         return term
 
@@ -209,7 +209,7 @@ def _nodes_for_enum_values(self, doc):
         dlnode = nodes.definition_list()
         for section in doc.args.values():
             termtext = [nodes.literal('', section.member.name)]
-            if section.member.ifcond:
+            if section.member.ifcond.ifcond:
                 termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
             # TODO drop fallbacks when undocumented members are outlawed
             if section.text:
@@ -277,7 +277,7 @@ def _nodes_for_sections(self, doc):
     def _nodes_for_if_section(self, ifcond):
         """Return list of doctree nodes for the "If" section"""
         nodelist = []
-        if ifcond:
+        if ifcond.ifcond:
             snode = self._make_section('If')
             snode += nodes.paragraph(
                 '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index 0e13d51054..3654825968 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -17,7 +17,6 @@
     Dict,
     List,
     Optional,
-    Sequence,
     Set,
 )
 
@@ -31,6 +30,7 @@
 from .schema import (
     QAPISchema,
     QAPISchemaFeature,
+    QAPISchemaIfCond,
     QAPISchemaObjectType,
     QAPISchemaType,
 )
@@ -301,7 +301,7 @@ def visit_end(self) -> None:
     def visit_command(self,
                       name: str,
                       info: Optional[QAPISourceInfo],
-                      ifcond: Sequence[str],
+                      ifcond: QAPISchemaIfCond,
                       features: List[QAPISchemaFeature],
                       arg_type: Optional[QAPISchemaObjectType],
                       ret_type: Optional[QAPISchemaType],
diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
index fee8c671e7..82475e84ec 100644
--- a/scripts/qapi/events.py
+++ b/scripts/qapi/events.py
@@ -12,7 +12,7 @@
 See the COPYING file in the top-level directory.
 """
 
-from typing import List, Optional, Sequence
+from typing import List, Optional
 
 from .common import c_enum_const, c_name, mcgen
 from .gen import QAPISchemaModularCVisitor, build_params, ifcontext
@@ -20,6 +20,7 @@
     QAPISchema,
     QAPISchemaEnumMember,
     QAPISchemaFeature,
+    QAPISchemaIfCond,
     QAPISchemaObjectType,
 )
 from .source import QAPISourceInfo
@@ -227,7 +228,7 @@ def visit_end(self) -> None:
     def visit_event(self,
                     name: str,
                     info: Optional[QAPISourceInfo],
-                    ifcond: Sequence[str],
+                    ifcond: QAPISchemaIfCond,
                     features: List[QAPISchemaFeature],
                     arg_type: Optional[QAPISchemaObjectType],
                     boxed: bool) -> None:
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
index 1fa503bdbd..1c5b190276 100644
--- a/scripts/qapi/gen.py
+++ b/scripts/qapi/gen.py
@@ -18,7 +18,6 @@
     Dict,
     Iterator,
     Optional,
-    Sequence,
     Tuple,
 )
 
@@ -32,6 +31,7 @@
     mcgen,
 )
 from .schema import (
+    QAPISchemaIfCond,
     QAPISchemaModule,
     QAPISchemaObjectType,
     QAPISchemaVisitor,
@@ -85,7 +85,7 @@ def write(self, output_dir: str) -> None:
                 fp.write(text)
 
 
-def _wrap_ifcond(ifcond: Sequence[str], before: str, after: str) -> str:
+def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
     if before == after:
         return after   # suppress empty #if ... #endif
 
@@ -95,9 +95,9 @@ def _wrap_ifcond(ifcond: Sequence[str], before: str, after: str) -> str:
     if added[0] == '\n':
         out += '\n'
         added = added[1:]
-    out += gen_if(ifcond)
+    out += gen_if(ifcond.ifcond)
     out += added
-    out += gen_endif(ifcond)
+    out += gen_endif(ifcond.ifcond)
     return out
 
 
@@ -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[Sequence[str], str, str]] = None
+        self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
 
-    def start_if(self, ifcond: Sequence[str]) -> None:
+    def start_if(self, ifcond: QAPISchemaIfCond) -> None:
         assert self._start_if is None
         self._start_if = (ifcond, self._body, self._preamble)
 
@@ -187,7 +187,7 @@ def _bottom(self) -> str:
 
 
 @contextmanager
-def ifcontext(ifcond: Sequence[str], *args: QAPIGenCCode) -> Iterator[None]:
+def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
     """
     A with-statement context manager that wraps with `start_if()` / `end_if()`.
 
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 9a348ca2e5..77a8c33ad4 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -15,11 +15,9 @@
     Any,
     Dict,
     Generic,
-    Iterable,
     List,
     Optional,
     Sequence,
-    Tuple,
     TypeVar,
     Union,
 )
@@ -38,6 +36,7 @@
     QAPISchemaEntity,
     QAPISchemaEnumMember,
     QAPISchemaFeature,
+    QAPISchemaIfCond,
     QAPISchemaObjectType,
     QAPISchemaObjectTypeMember,
     QAPISchemaType,
@@ -91,11 +90,11 @@ class Annotated(Generic[_ValueT]):
     """
     # TODO: Remove after Python 3.7 adds @dataclass:
     # pylint: disable=too-few-public-methods
-    def __init__(self, value: _ValueT, ifcond: Iterable[str],
+    def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond,
                  comment: Optional[str] = None):
         self.value = value
         self.comment: Optional[str] = comment
-        self.ifcond: Tuple[str, ...] = tuple(ifcond)
+        self.ifcond = ifcond
 
 
 def _tree_to_qlit(obj: JSONValue,
@@ -125,10 +124,10 @@ def indent(level: int) -> str:
         if obj.comment:
             ret += indent(level) + f"/* {obj.comment} */\n"
         if obj.ifcond:
-            ret += gen_if(obj.ifcond)
+            ret += gen_if(obj.ifcond.ifcond)
         ret += _tree_to_qlit(obj.value, level)
         if obj.ifcond:
-            ret += '\n' + gen_endif(obj.ifcond)
+            ret += '\n' + gen_endif(obj.ifcond.ifcond)
         return ret
 
     ret = ''
@@ -254,7 +253,7 @@ def _gen_features(features: Sequence[QAPISchemaFeature]
         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: QAPISchemaIfCond = QAPISchemaIfCond(),
                   features: Sequence[QAPISchemaFeature] = ()) -> None:
         """
         Build and append a SchemaInfo object to self._trees.
@@ -305,7 +304,7 @@ def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo],
         self._gen_tree(name, 'builtin', {'json-type': json_type})
 
     def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo],
-                        ifcond: Sequence[str],
+                        ifcond: QAPISchemaIfCond,
                         features: List[QAPISchemaFeature],
                         members: List[QAPISchemaEnumMember],
                         prefix: Optional[str]) -> None:
@@ -316,14 +315,14 @@ def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo],
         )
 
     def visit_array_type(self, name: str, info: Optional[QAPISourceInfo],
-                         ifcond: Sequence[str],
+                         ifcond: QAPISchemaIfCond,
                          element_type: QAPISchemaType) -> None:
         element = self._use_type(element_type)
         self._gen_tree('[' + element + ']', 'array', {'element-type': element},
                        ifcond)
 
     def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo],
-                               ifcond: Sequence[str],
+                               ifcond: QAPISchemaIfCond,
                                features: List[QAPISchemaFeature],
                                members: List[QAPISchemaObjectTypeMember],
                                variants: Optional[QAPISchemaVariants]) -> None:
@@ -336,7 +335,7 @@ def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo],
         self._gen_tree(name, 'object', obj, ifcond, features)
 
     def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo],
-                             ifcond: Sequence[str],
+                             ifcond: QAPISchemaIfCond,
                              features: List[QAPISchemaFeature],
                              variants: QAPISchemaVariants) -> None:
         self._gen_tree(
@@ -348,7 +347,7 @@ def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo],
         )
 
     def visit_command(self, name: str, info: Optional[QAPISourceInfo],
-                      ifcond: Sequence[str],
+                      ifcond: QAPISchemaIfCond,
                       features: List[QAPISchemaFeature],
                       arg_type: Optional[QAPISchemaObjectType],
                       ret_type: Optional[QAPISchemaType], gen: bool,
@@ -367,7 +366,8 @@ def visit_command(self, name: str, info: Optional[QAPISourceInfo],
         self._gen_tree(name, 'command', obj, ifcond, features)
 
     def visit_event(self, name: str, info: Optional[QAPISourceInfo],
-                    ifcond: Sequence[str], features: List[QAPISchemaFeature],
+                    ifcond: QAPISchemaIfCond,
+                    features: List[QAPISchemaFeature],
                     arg_type: Optional[QAPISchemaObjectType],
                     boxed: bool) -> None:
         assert self._schema is not None
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index d1d27ff7ee..5e44164bd1 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -25,6 +25,11 @@
 from .parser import QAPISchemaParser
 
 
+class QAPISchemaIfCond:
+    def __init__(self, ifcond=None):
+        self.ifcond = ifcond or []
+
+
 class QAPISchemaEntity:
     meta: Optional[str] = None
 
@@ -42,7 +47,7 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
         # such place).
         self.info = info
         self.doc = doc
-        self._ifcond = ifcond or []
+        self._ifcond = ifcond or QAPISchemaIfCond()
         self.features = features or []
         self._checked = False
 
@@ -78,6 +83,7 @@ def set_module(self, schema):
     @property
     def ifcond(self):
         assert self._checked
+        assert isinstance(self._ifcond, QAPISchemaIfCond)
         return self._ifcond
 
     def is_implicit(self):
@@ -593,7 +599,7 @@ def check(self, schema, seen):
                     self.info,
                     "discriminator member '%s' of %s must not be optional"
                     % (self._tag_name, base))
-            if self.tag_member.ifcond:
+            if self.tag_member.ifcond.ifcond:
                 raise QAPISemError(
                     self.info,
                     "discriminator member '%s' of %s must not be conditional"
@@ -601,7 +607,7 @@ def check(self, schema, seen):
         else:                   # simple union
             assert isinstance(self.tag_member.type, QAPISchemaEnumType)
             assert not self.tag_member.optional
-            assert self.tag_member.ifcond == []
+            assert self.tag_member.ifcond.ifcond == []
         if self._tag_name:    # flat union
             # branches that are not explicitly covered get an empty type
             cases = {v.name for v in self.variants}
@@ -646,7 +652,7 @@ def __init__(self, name, info, ifcond=None):
         assert isinstance(name, str)
         self.name = name
         self.info = info
-        self.ifcond = ifcond or []
+        self.ifcond = ifcond or QAPISchemaIfCond()
         self.defined_in = None
 
     def set_defined_in(self, name):
@@ -968,11 +974,13 @@ def _def_predefineds(self):
     def _make_features(self, features, info):
         if features is None:
             return []
-        return [QAPISchemaFeature(f['name'], info, f.get('if'))
+        return [QAPISchemaFeature(f['name'], info,
+                                  QAPISchemaIfCond(f.get('if')))
                 for f in features]
 
     def _make_enum_members(self, values, info):
-        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
+        return [QAPISchemaEnumMember(v['name'], info,
+                                     QAPISchemaIfCond(v.get('if')))
                 for v in values]
 
     def _make_implicit_enum_type(self, name, info, ifcond, values):
@@ -1008,7 +1016,10 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
             # TODO kill simple unions or implement the disjunction
 
             # pylint: disable=protected-access
-            assert (ifcond or []) == typ._ifcond
+            if isinstance(ifcond, QAPISchemaIfCond):
+                assert ifcond.ifcond == typ._ifcond.ifcond
+            else:
+                assert ifcond == typ._ifcond
         else:
             self._def_entity(QAPISchemaObjectType(
                 name, info, None, ifcond, None, None, members, None))
@@ -1018,7 +1029,7 @@ def _def_enum_type(self, expr, info, doc):
         name = expr['enum']
         data = expr['data']
         prefix = expr.get('prefix')
-        ifcond = expr.get('if')
+        ifcond = QAPISchemaIfCond(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         self._def_entity(QAPISchemaEnumType(
             name, info, doc, ifcond, features,
@@ -1036,7 +1047,8 @@ def _make_member(self, name, typ, ifcond, features, info):
                                           self._make_features(features, info))
 
     def _make_members(self, data, info):
-        return [self._make_member(key, value['type'], value.get('if'),
+        return [self._make_member(key, value['type'],
+                                  QAPISchemaIfCond(value.get('if')),
                                   value.get('features'), info)
                 for (key, value) in data.items()]
 
@@ -1044,7 +1056,7 @@ def _def_struct_type(self, expr, info, doc):
         name = expr['struct']
         base = expr.get('base')
         data = expr['data']
-        ifcond = expr.get('if')
+        ifcond = QAPISchemaIfCond(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         self._def_entity(QAPISchemaObjectType(
             name, info, doc, ifcond, features, base,
@@ -1067,7 +1079,7 @@ def _def_union_type(self, expr, info, doc):
         name = expr['union']
         data = expr['data']
         base = expr.get('base')
-        ifcond = expr.get('if')
+        ifcond = QAPISchemaIfCond(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         tag_name = expr.get('discriminator')
         tag_member = None
@@ -1076,15 +1088,19 @@ def _def_union_type(self, expr, info, doc):
                 name, info, ifcond,
                 'base', self._make_members(base, info))
         if tag_name:
-            variants = [self._make_variant(key, value['type'],
-                                           value.get('if'), info)
-                        for (key, value) in data.items()]
+            variants = [
+                self._make_variant(key, value['type'],
+                                   QAPISchemaIfCond(value.get('if')),
+                                   info)
+                for (key, value) in data.items()]
             members = []
         else:
-            variants = [self._make_simple_variant(key, value['type'],
-                                                  value.get('if'), info)
-                        for (key, value) in data.items()]
-            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
+            variants = [
+                self._make_simple_variant(key, value['type'],
+                                          QAPISchemaIfCond(value.get('if')),
+                                          info)
+                for (key, value) in data.items()]
+            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in variants]
             typ = self._make_implicit_enum_type(name, info, ifcond, enum)
             tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
             members = [tag_member]
@@ -1097,11 +1113,13 @@ def _def_union_type(self, expr, info, doc):
     def _def_alternate_type(self, expr, info, doc):
         name = expr['alternate']
         data = expr['data']
-        ifcond = expr.get('if')
+        ifcond = QAPISchemaIfCond(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
-        variants = [self._make_variant(key, value['type'], value.get('if'),
-                                       info)
-                    for (key, value) in data.items()]
+        variants = [
+            self._make_variant(key, value['type'],
+                               QAPISchemaIfCond(value.get('if')),
+                               info)
+            for (key, value) in data.items()]
         tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
         self._def_entity(
             QAPISchemaAlternateType(name, info, doc, ifcond, features,
@@ -1118,7 +1136,7 @@ def _def_command(self, expr, info, doc):
         allow_oob = expr.get('allow-oob', False)
         allow_preconfig = expr.get('allow-preconfig', False)
         coroutine = expr.get('coroutine', False)
-        ifcond = expr.get('if')
+        ifcond = QAPISchemaIfCond(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
@@ -1137,7 +1155,7 @@ def _def_event(self, expr, info, doc):
         name = expr['event']
         data = expr.get('data')
         boxed = expr.get('boxed', False)
-        ifcond = expr.get('if')
+        ifcond = QAPISchemaIfCond(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 20d572a23a..3673cf0f49 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -13,7 +13,7 @@
 # See the COPYING file in the top-level directory.
 """
 
-from typing import List, Optional, Sequence
+from typing import List, Optional
 
 from .common import (
     c_enum_const,
@@ -27,6 +27,7 @@
     QAPISchema,
     QAPISchemaEnumMember,
     QAPISchemaFeature,
+    QAPISchemaIfCond,
     QAPISchemaObjectType,
     QAPISchemaObjectTypeMember,
     QAPISchemaType,
@@ -50,13 +51,13 @@ def gen_enum_lookup(name: str,
 ''',
                 c_name=c_name(name))
     for memb in members:
-        ret += gen_if(memb.ifcond)
+        ret += gen_if(memb.ifcond.ifcond)
         index = c_enum_const(name, memb.name, prefix)
         ret += mcgen('''
         [%(index)s] = "%(name)s",
 ''',
                      index=index, name=memb.name)
-        ret += gen_endif(memb.ifcond)
+        ret += gen_endif(memb.ifcond.ifcond)
 
     ret += mcgen('''
     },
@@ -80,12 +81,12 @@ def gen_enum(name: str,
                 c_name=c_name(name))
 
     for memb in enum_members:
-        ret += gen_if(memb.ifcond)
+        ret += gen_if(memb.ifcond.ifcond)
         ret += mcgen('''
     %(c_enum)s,
 ''',
                      c_enum=c_enum_const(name, memb.name, prefix))
-        ret += gen_endif(memb.ifcond)
+        ret += gen_endif(memb.ifcond.ifcond)
 
     ret += mcgen('''
 } %(c_name)s;
@@ -125,7 +126,7 @@ def gen_array(name: str, element_type: QAPISchemaType) -> str:
 def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
     ret = ''
     for memb in members:
-        ret += gen_if(memb.ifcond)
+        ret += gen_if(memb.ifcond.ifcond)
         if memb.optional:
             ret += mcgen('''
     bool has_%(c_name)s;
@@ -135,11 +136,11 @@ def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
     %(c_type)s %(c_name)s;
 ''',
                      c_type=memb.type.c_type(), c_name=c_name(memb.name))
-        ret += gen_endif(memb.ifcond)
+        ret += gen_endif(memb.ifcond.ifcond)
     return ret
 
 
-def gen_object(name: str, ifcond: Sequence[str],
+def gen_object(name: str, ifcond: QAPISchemaIfCond,
                base: Optional[QAPISchemaObjectType],
                members: List[QAPISchemaObjectTypeMember],
                variants: Optional[QAPISchemaVariants]) -> str:
@@ -158,7 +159,7 @@ def gen_object(name: str, ifcond: Sequence[str],
     ret += mcgen('''
 
 ''')
-    ret += gen_if(ifcond)
+    ret += gen_if(ifcond.ifcond)
     ret += mcgen('''
 struct %(c_name)s {
 ''',
@@ -192,7 +193,7 @@ def gen_object(name: str, ifcond: Sequence[str],
     ret += mcgen('''
 };
 ''')
-    ret += gen_endif(ifcond)
+    ret += gen_endif(ifcond.ifcond)
 
     return ret
 
@@ -219,13 +220,13 @@ def gen_variants(variants: QAPISchemaVariants) -> str:
     for var in variants.variants:
         if var.type.name == 'q_empty':
             continue
-        ret += gen_if(var.ifcond)
+        ret += gen_if(var.ifcond.ifcond)
         ret += mcgen('''
         %(c_type)s %(c_name)s;
 ''',
                      c_type=var.type.c_unboxed_type(),
                      c_name=c_name(var.name))
-        ret += gen_endif(var.ifcond)
+        ret += gen_endif(var.ifcond.ifcond)
 
     ret += mcgen('''
     } u;
@@ -307,7 +308,7 @@ def _gen_type_cleanup(self, name: str) -> None:
     def visit_enum_type(self,
                         name: str,
                         info: Optional[QAPISourceInfo],
-                        ifcond: Sequence[str],
+                        ifcond: QAPISchemaIfCond,
                         features: List[QAPISchemaFeature],
                         members: List[QAPISchemaEnumMember],
                         prefix: Optional[str]) -> None:
@@ -318,7 +319,7 @@ def visit_enum_type(self,
     def visit_array_type(self,
                          name: str,
                          info: Optional[QAPISourceInfo],
-                         ifcond: Sequence[str],
+                         ifcond: QAPISchemaIfCond,
                          element_type: QAPISchemaType) -> None:
         with ifcontext(ifcond, self._genh, self._genc):
             self._genh.preamble_add(gen_fwd_object_or_array(name))
@@ -328,7 +329,7 @@ def visit_array_type(self,
     def visit_object_type(self,
                           name: str,
                           info: Optional[QAPISourceInfo],
-                          ifcond: Sequence[str],
+                          ifcond: QAPISchemaIfCond,
                           features: List[QAPISchemaFeature],
                           base: Optional[QAPISchemaObjectType],
                           members: List[QAPISchemaObjectTypeMember],
@@ -351,7 +352,7 @@ def visit_object_type(self,
     def visit_alternate_type(self,
                              name: str,
                              info: Optional[QAPISourceInfo],
-                             ifcond: Sequence[str],
+                             ifcond: QAPISchemaIfCond,
                              features: List[QAPISchemaFeature],
                              variants: QAPISchemaVariants) -> None:
         with ifcontext(ifcond, self._genh):
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index 9e96f3c566..67721b2470 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -13,7 +13,7 @@
 See the COPYING file in the top-level directory.
 """
 
-from typing import List, Optional, Sequence
+from typing import List, Optional
 
 from .common import (
     c_enum_const,
@@ -29,6 +29,7 @@
     QAPISchemaEnumMember,
     QAPISchemaEnumType,
     QAPISchemaFeature,
+    QAPISchemaIfCond,
     QAPISchemaObjectType,
     QAPISchemaObjectTypeMember,
     QAPISchemaType,
@@ -78,7 +79,7 @@ def gen_visit_object_members(name: str,
 
     for memb in members:
         deprecated = 'deprecated' in [f.name for f in memb.features]
-        ret += gen_if(memb.ifcond)
+        ret += gen_if(memb.ifcond.ifcond)
         if memb.optional:
             ret += mcgen('''
     if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
@@ -111,7 +112,7 @@ def gen_visit_object_members(name: str,
             ret += mcgen('''
     }
 ''')
-        ret += gen_endif(memb.ifcond)
+        ret += gen_endif(memb.ifcond.ifcond)
 
     if variants:
         tag_member = variants.tag_member
@@ -125,7 +126,7 @@ def gen_visit_object_members(name: str,
         for var in variants.variants:
             case_str = c_enum_const(tag_member.type.name, var.name,
                                     tag_member.type.prefix)
-            ret += gen_if(var.ifcond)
+            ret += gen_if(var.ifcond.ifcond)
             if var.type.name == 'q_empty':
                 # valid variant and nothing to do
                 ret += mcgen('''
@@ -141,7 +142,7 @@ def gen_visit_object_members(name: str,
                              case=case_str,
                              c_type=var.type.c_name(), c_name=c_name(var.name))
 
-            ret += gen_endif(var.ifcond)
+            ret += gen_endif(var.ifcond.ifcond)
         ret += mcgen('''
     default:
         abort();
@@ -227,7 +228,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
                 c_name=c_name(name))
 
     for var in variants.variants:
-        ret += gen_if(var.ifcond)
+        ret += gen_if(var.ifcond.ifcond)
         ret += mcgen('''
     case %(case)s:
 ''',
@@ -253,7 +254,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
         ret += mcgen('''
         break;
 ''')
-        ret += gen_endif(var.ifcond)
+        ret += gen_endif(var.ifcond.ifcond)
 
     ret += mcgen('''
     case QTYPE_NONE:
@@ -352,7 +353,7 @@ def _begin_user_module(self, name: str) -> None:
     def visit_enum_type(self,
                         name: str,
                         info: Optional[QAPISourceInfo],
-                        ifcond: Sequence[str],
+                        ifcond: QAPISchemaIfCond,
                         features: List[QAPISchemaFeature],
                         members: List[QAPISchemaEnumMember],
                         prefix: Optional[str]) -> None:
@@ -363,7 +364,7 @@ def visit_enum_type(self,
     def visit_array_type(self,
                          name: str,
                          info: Optional[QAPISourceInfo],
-                         ifcond: Sequence[str],
+                         ifcond: QAPISchemaIfCond,
                          element_type: QAPISchemaType) -> None:
         with ifcontext(ifcond, self._genh, self._genc):
             self._genh.add(gen_visit_decl(name))
@@ -372,7 +373,7 @@ def visit_array_type(self,
     def visit_object_type(self,
                           name: str,
                           info: Optional[QAPISourceInfo],
-                          ifcond: Sequence[str],
+                          ifcond: QAPISchemaIfCond,
                           features: List[QAPISchemaFeature],
                           base: Optional[QAPISchemaObjectType],
                           members: List[QAPISchemaObjectTypeMember],
@@ -394,7 +395,7 @@ def visit_object_type(self,
     def visit_alternate_type(self,
                              name: str,
                              info: Optional[QAPISourceInfo],
-                             ifcond: Sequence[str],
+                             ifcond: QAPISchemaIfCond,
                              features: List[QAPISchemaFeature],
                              variants: QAPISchemaVariants) -> None:
         with ifcontext(ifcond, self._genh, self._genc):
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index f1c4deb9a5..7907b4ac3a 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -94,8 +94,8 @@ def _print_variants(variants):
 
     @staticmethod
     def _print_if(ifcond, indent=4):
-        if ifcond:
-            print('%sif %s' % (' ' * indent, ifcond))
+        if ifcond.ifcond:
+            print('%sif %s' % (' ' * indent, ifcond.ifcond))
 
     @classmethod
     def _print_features(cls, features, indent=4):
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 03/11] qapi: add QAPISchemaIfCond.is_present()
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
  2021-06-18 10:24 ` [PATCH v6 01/11] docs: update the documentation upfront about schema configuration marcandre.lureau
  2021-06-18 10:24 ` [PATCH v6 02/11] qapi: wrap Sequence[str] in an object marcandre.lureau
@ 2021-06-18 10:24 ` marcandre.lureau
  2021-08-02  9:52   ` Markus Armbruster
  2021-06-18 10:25 ` [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond marcandre.lureau
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:24 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/sphinx/qapidoc.py         | 8 ++++----
 scripts/qapi/schema.py         | 7 +++++--
 tests/qapi-schema/test-qapi.py | 2 +-
 3 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 0eac3308b2..511520f33f 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -139,7 +139,7 @@ def _nodes_for_one_member(self, member):
             term.append(nodes.literal('', member.type.doc_type()))
         if member.optional:
             term.append(nodes.Text(' (optional)'))
-        if member.ifcond.ifcond:
+        if member.ifcond.is_present():
             term.extend(self._nodes_for_ifcond(member.ifcond))
         return term
 
@@ -154,7 +154,7 @@ def _nodes_for_variant_when(self, variants, variant):
                 nodes.literal('', variants.tag_member.name),
                 nodes.Text(' is '),
                 nodes.literal('', '"%s"' % variant.name)]
-        if variant.ifcond.ifcond:
+        if variant.ifcond.is_present():
             term.extend(self._nodes_for_ifcond(variant.ifcond))
         return term
 
@@ -209,7 +209,7 @@ def _nodes_for_enum_values(self, doc):
         dlnode = nodes.definition_list()
         for section in doc.args.values():
             termtext = [nodes.literal('', section.member.name)]
-            if section.member.ifcond.ifcond:
+            if section.member.ifcond.is_present():
                 termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
             # TODO drop fallbacks when undocumented members are outlawed
             if section.text:
@@ -277,7 +277,7 @@ def _nodes_for_sections(self, doc):
     def _nodes_for_if_section(self, ifcond):
         """Return list of doctree nodes for the "If" section"""
         nodelist = []
-        if ifcond.ifcond:
+        if ifcond.is_present():
             snode = self._make_section('If')
             snode += nodes.paragraph(
                 '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 5e44164bd1..e3bd8f8720 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -29,6 +29,9 @@ class QAPISchemaIfCond:
     def __init__(self, ifcond=None):
         self.ifcond = ifcond or []
 
+    def is_present(self):
+        return bool(self.ifcond)
+
 
 class QAPISchemaEntity:
     meta: Optional[str] = None
@@ -599,7 +602,7 @@ def check(self, schema, seen):
                     self.info,
                     "discriminator member '%s' of %s must not be optional"
                     % (self._tag_name, base))
-            if self.tag_member.ifcond.ifcond:
+            if self.tag_member.ifcond.is_present():
                 raise QAPISemError(
                     self.info,
                     "discriminator member '%s' of %s must not be conditional"
@@ -607,7 +610,7 @@ def check(self, schema, seen):
         else:                   # simple union
             assert isinstance(self.tag_member.type, QAPISchemaEnumType)
             assert not self.tag_member.optional
-            assert self.tag_member.ifcond.ifcond == []
+            assert not self.tag_member.ifcond.is_present()
         if self._tag_name:    # flat union
             # branches that are not explicitly covered get an empty type
             cases = {v.name for v in self.variants}
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 7907b4ac3a..c92be2d086 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -94,7 +94,7 @@ def _print_variants(variants):
 
     @staticmethod
     def _print_if(ifcond, indent=4):
-        if ifcond.ifcond:
+        if ifcond.is_present():
             print('%sif %s' % (' ' * indent, ifcond.ifcond))
 
     @classmethod
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (2 preceding siblings ...)
  2021-06-18 10:24 ` [PATCH v6 03/11] qapi: add QAPISchemaIfCond.is_present() marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-08-02 10:41   ` Markus Armbruster
  2021-06-18 10:25 ` [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen() marcandre.lureau
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Instead of lowering the expression back to its original form, and having
to convert it again, special-case the 'if' condition to be pre-built.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/schema.py | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index e3bd8f8720..c35fa3bf51 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -982,8 +982,13 @@ def _make_features(self, features, info):
                 for f in features]
 
     def _make_enum_members(self, values, info):
-        return [QAPISchemaEnumMember(v['name'], info,
-                                     QAPISchemaIfCond(v.get('if')))
+        def _get_if(v):
+            ifcond = v.get('if')
+            if isinstance(ifcond, QAPISchemaIfCond):
+                return ifcond
+            else:
+                return QAPISchemaIfCond(ifcond)
+        return [QAPISchemaEnumMember(v['name'], info, _get_if(v))
                 for v in values]
 
     def _make_implicit_enum_type(self, name, info, ifcond, values):
@@ -1103,7 +1108,7 @@ def _def_union_type(self, expr, info, doc):
                                           QAPISchemaIfCond(value.get('if')),
                                           info)
                 for (key, value) in data.items()]
-            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in variants]
+            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
             typ = self._make_implicit_enum_type(name, info, ifcond, enum)
             tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
             members = [tag_member]
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen()
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (3 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-08-02 14:46   ` Markus Armbruster
  2021-06-18 10:25 ` [PATCH v6 06/11] qapidoc: introduce QAPISchemaIfCond.docgen() marcandre.lureau
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Instead of building prepocessor conditions from a list of string, use
the result generated from QAPISchemaIfCond.cgen() and hide the
implementation details.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/common.py     | 35 ++++++++++++++++++++++-------------
 scripts/qapi/gen.py        |  4 ++--
 scripts/qapi/introspect.py |  4 ++--
 scripts/qapi/schema.py     |  5 ++++-
 scripts/qapi/types.py      | 20 ++++++++++----------
 scripts/qapi/visit.py      | 12 ++++++------
 6 files changed, 46 insertions(+), 34 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 6ad1eeb61d..ba9fe14e4b 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -12,7 +12,12 @@
 # See the COPYING file in the top-level directory.
 
 import re
-from typing import Match, Optional, Sequence
+from typing import (
+    List,
+    Match,
+    Optional,
+    Union,
+)
 
 
 #: Magic string that gets removed along with all space to its right.
@@ -194,22 +199,26 @@ def guardend(name: str) -> str:
                  name=c_fname(name).upper())
 
 
-def gen_if(ifcond: Sequence[str]) -> str:
-    ret = ''
-    for ifc in ifcond:
-        ret += mcgen('''
+def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
+    if not ifcond:
+        return ''
+    return '(' + ') && ('.join(ifcond) + ')'
+
+
+def gen_if(cond: str) -> str:
+    if not cond:
+        return ''
+    return mcgen('''
 #if %(cond)s
-''', cond=ifc)
-    return ret
+''', cond=cond)
 
 
-def gen_endif(ifcond: Sequence[str]) -> str:
-    ret = ''
-    for ifc in reversed(ifcond):
-        ret += mcgen('''
+def gen_endif(cond: str) -> str:
+    if not cond:
+        return ''
+    return mcgen('''
 #endif /* %(cond)s */
-''', cond=ifc)
-    return ret
+''', cond=cond)
 
 
 def must_match(pattern: str, string: str) -> Match[str]:
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
index 1c5b190276..51a597a025 100644
--- a/scripts/qapi/gen.py
+++ b/scripts/qapi/gen.py
@@ -95,9 +95,9 @@ def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
     if added[0] == '\n':
         out += '\n'
         added = added[1:]
-    out += gen_if(ifcond.ifcond)
+    out += gen_if(ifcond.cgen())
     out += added
-    out += gen_endif(ifcond.ifcond)
+    out += gen_endif(ifcond.cgen())
     return out
 
 
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 77a8c33ad4..474b08fd4d 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -124,10 +124,10 @@ def indent(level: int) -> str:
         if obj.comment:
             ret += indent(level) + f"/* {obj.comment} */\n"
         if obj.ifcond:
-            ret += gen_if(obj.ifcond.ifcond)
+            ret += gen_if(obj.ifcond.cgen())
         ret += _tree_to_qlit(obj.value, level)
         if obj.ifcond:
-            ret += '\n' + gen_endif(obj.ifcond.ifcond)
+            ret += '\n' + gen_endif(obj.ifcond.cgen())
         return ret
 
     ret = ''
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index c35fa3bf51..70120f0dcc 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -19,7 +19,7 @@
 import re
 from typing import Optional
 
-from .common import POINTER_SUFFIX, c_name
+from .common import POINTER_SUFFIX, c_name, cgen_ifcond
 from .error import QAPIError, QAPISemError, QAPISourceError
 from .expr import check_exprs
 from .parser import QAPISchemaParser
@@ -29,6 +29,9 @@ class QAPISchemaIfCond:
     def __init__(self, ifcond=None):
         self.ifcond = ifcond or []
 
+    def cgen(self):
+        return cgen_ifcond(self.ifcond)
+
     def is_present(self):
         return bool(self.ifcond)
 
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 3673cf0f49..db9ff95bd1 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -51,13 +51,13 @@ def gen_enum_lookup(name: str,
 ''',
                 c_name=c_name(name))
     for memb in members:
-        ret += gen_if(memb.ifcond.ifcond)
+        ret += gen_if(memb.ifcond.cgen())
         index = c_enum_const(name, memb.name, prefix)
         ret += mcgen('''
         [%(index)s] = "%(name)s",
 ''',
                      index=index, name=memb.name)
-        ret += gen_endif(memb.ifcond.ifcond)
+        ret += gen_endif(memb.ifcond.cgen())
 
     ret += mcgen('''
     },
@@ -81,12 +81,12 @@ def gen_enum(name: str,
                 c_name=c_name(name))
 
     for memb in enum_members:
-        ret += gen_if(memb.ifcond.ifcond)
+        ret += gen_if(memb.ifcond.cgen())
         ret += mcgen('''
     %(c_enum)s,
 ''',
                      c_enum=c_enum_const(name, memb.name, prefix))
-        ret += gen_endif(memb.ifcond.ifcond)
+        ret += gen_endif(memb.ifcond.cgen())
 
     ret += mcgen('''
 } %(c_name)s;
@@ -126,7 +126,7 @@ def gen_array(name: str, element_type: QAPISchemaType) -> str:
 def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
     ret = ''
     for memb in members:
-        ret += gen_if(memb.ifcond.ifcond)
+        ret += gen_if(memb.ifcond.cgen())
         if memb.optional:
             ret += mcgen('''
     bool has_%(c_name)s;
@@ -136,7 +136,7 @@ def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
     %(c_type)s %(c_name)s;
 ''',
                      c_type=memb.type.c_type(), c_name=c_name(memb.name))
-        ret += gen_endif(memb.ifcond.ifcond)
+        ret += gen_endif(memb.ifcond.cgen())
     return ret
 
 
@@ -159,7 +159,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
     ret += mcgen('''
 
 ''')
-    ret += gen_if(ifcond.ifcond)
+    ret += gen_if(ifcond.cgen())
     ret += mcgen('''
 struct %(c_name)s {
 ''',
@@ -193,7 +193,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
     ret += mcgen('''
 };
 ''')
-    ret += gen_endif(ifcond.ifcond)
+    ret += gen_endif(ifcond.cgen())
 
     return ret
 
@@ -220,13 +220,13 @@ def gen_variants(variants: QAPISchemaVariants) -> str:
     for var in variants.variants:
         if var.type.name == 'q_empty':
             continue
-        ret += gen_if(var.ifcond.ifcond)
+        ret += gen_if(var.ifcond.cgen())
         ret += mcgen('''
         %(c_type)s %(c_name)s;
 ''',
                      c_type=var.type.c_unboxed_type(),
                      c_name=c_name(var.name))
-        ret += gen_endif(var.ifcond.ifcond)
+        ret += gen_endif(var.ifcond.cgen())
 
     ret += mcgen('''
     } u;
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index 67721b2470..56ea516399 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -79,7 +79,7 @@ def gen_visit_object_members(name: str,
 
     for memb in members:
         deprecated = 'deprecated' in [f.name for f in memb.features]
-        ret += gen_if(memb.ifcond.ifcond)
+        ret += gen_if(memb.ifcond.cgen())
         if memb.optional:
             ret += mcgen('''
     if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
@@ -112,7 +112,7 @@ def gen_visit_object_members(name: str,
             ret += mcgen('''
     }
 ''')
-        ret += gen_endif(memb.ifcond.ifcond)
+        ret += gen_endif(memb.ifcond.cgen())
 
     if variants:
         tag_member = variants.tag_member
@@ -126,7 +126,7 @@ def gen_visit_object_members(name: str,
         for var in variants.variants:
             case_str = c_enum_const(tag_member.type.name, var.name,
                                     tag_member.type.prefix)
-            ret += gen_if(var.ifcond.ifcond)
+            ret += gen_if(var.ifcond.cgen())
             if var.type.name == 'q_empty':
                 # valid variant and nothing to do
                 ret += mcgen('''
@@ -142,7 +142,7 @@ def gen_visit_object_members(name: str,
                              case=case_str,
                              c_type=var.type.c_name(), c_name=c_name(var.name))
 
-            ret += gen_endif(var.ifcond.ifcond)
+            ret += gen_endif(var.ifcond.cgen())
         ret += mcgen('''
     default:
         abort();
@@ -228,7 +228,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
                 c_name=c_name(name))
 
     for var in variants.variants:
-        ret += gen_if(var.ifcond.ifcond)
+        ret += gen_if(var.ifcond.cgen())
         ret += mcgen('''
     case %(case)s:
 ''',
@@ -254,7 +254,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
         ret += mcgen('''
         break;
 ''')
-        ret += gen_endif(var.ifcond.ifcond)
+        ret += gen_endif(var.ifcond.cgen())
 
     ret += mcgen('''
     case QTYPE_NONE:
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 06/11] qapidoc: introduce QAPISchemaIfCond.docgen()
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (4 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen() marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-08-02 15:47   ` Markus Armbruster
  2021-06-18 10:25 ` [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]} marcandre.lureau
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Instead of building the condition documentation from a list of string,
use the result generated from QAPISchemaIfCond.docgen().

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/sphinx/qapidoc.py | 14 ++++++++------
 scripts/qapi/common.py |  6 ++++++
 scripts/qapi/schema.py | 10 +++++++++-
 3 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 511520f33f..d791b59492 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -112,17 +112,19 @@ def _make_section(self, title):
     def _nodes_for_ifcond(self, ifcond, with_if=True):
         """Return list of Text, literal nodes for the ifcond
 
-        Return a list which gives text like ' (If: cond1, cond2, cond3)', where
-        the conditions are in literal-text and the commas are not.
+        Return a list which gives text like ' (If: condition)'.
         If with_if is False, we don't return the "(If: " and ")".
         """
-        condlist = intersperse([nodes.literal('', c) for c in ifcond.ifcond],
-                               nodes.Text(', '))
+
+        doc = ifcond.docgen()
+        if not doc:
+            return []
+        doc = nodes.literal('', doc)
         if not with_if:
-            return condlist
+            return [doc]
 
         nodelist = [nodes.Text(' ('), nodes.strong('', 'If: ')]
-        nodelist.extend(condlist)
+        nodelist.append(doc)
         nodelist.append(nodes.Text(')'))
         return nodelist
 
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index ba9fe14e4b..5181a0f167 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -205,6 +205,12 @@ def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
     return '(' + ') && ('.join(ifcond) + ')'
 
 
+def docgen_ifcond(ifcond: Union[str, List[str]]) -> str:
+    if not ifcond:
+        return ''
+    return ' and '.join(ifcond)
+
+
 def gen_if(cond: str) -> str:
     if not cond:
         return ''
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 70120f0dcc..30d6a01ad1 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -19,7 +19,12 @@
 import re
 from typing import Optional
 
-from .common import POINTER_SUFFIX, c_name, cgen_ifcond
+from .common import (
+    POINTER_SUFFIX,
+    c_name,
+    cgen_ifcond,
+    docgen_ifcond,
+)
 from .error import QAPIError, QAPISemError, QAPISourceError
 from .expr import check_exprs
 from .parser import QAPISchemaParser
@@ -32,6 +37,9 @@ def __init__(self, ifcond=None):
     def cgen(self):
         return cgen_ifcond(self.ifcond)
 
+    def docgen(self):
+        return docgen_ifcond(self.ifcond)
+
     def is_present(self):
         return bool(self.ifcond)
 
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]}
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (5 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 06/11] qapidoc: introduce QAPISchemaIfCond.docgen() marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-08-03 13:05   ` Markus Armbruster
  2021-08-03 13:17   ` Markus Armbruster
  2021-06-18 10:25 ` [PATCH v6 08/11] qapi: add 'any' condition marcandre.lureau
                   ` (4 subsequent siblings)
  11 siblings, 2 replies; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Replace the simple list sugar form with a recursive structure that will
accept other operators in the following commits (all, any or not).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/common.py                        | 23 +++++--
 scripts/qapi/expr.py                          | 52 ++++++++++------
 scripts/qapi/schema.py                        |  2 +-
 tests/qapi-schema/bad-if-empty-list.json      |  2 +-
 tests/qapi-schema/bad-if-list.json            |  2 +-
 tests/qapi-schema/bad-if.err                  |  3 +-
 tests/qapi-schema/doc-good.json               |  3 +-
 tests/qapi-schema/doc-good.out                | 13 ++--
 tests/qapi-schema/doc-good.txt                |  6 ++
 tests/qapi-schema/enum-if-invalid.err         |  3 +-
 tests/qapi-schema/features-if-invalid.err     |  2 +-
 tests/qapi-schema/qapi-schema-test.json       | 25 ++++----
 tests/qapi-schema/qapi-schema-test.out        | 62 +++++++++----------
 .../qapi-schema/struct-member-if-invalid.err  |  2 +-
 .../qapi-schema/union-branch-if-invalid.json  |  2 +-
 15 files changed, 119 insertions(+), 83 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 5181a0f167..51463510c9 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -13,7 +13,8 @@
 
 import re
 from typing import (
-    List,
+    Any,
+    Dict,
     Match,
     Optional,
     Union,
@@ -199,16 +200,28 @@ def guardend(name: str) -> str:
                  name=c_fname(name).upper())
 
 
-def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
+def docgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
     if not ifcond:
         return ''
-    return '(' + ') && ('.join(ifcond) + ')'
+    if isinstance(ifcond, str):
+        return ifcond
 
+    oper, operands = next(iter(ifcond.items()))
+    oper = {'all': ' and '}[oper]
+    operands = [docgen_ifcond(o) for o in operands]
+    return '(' + oper.join(operands) + ')'
 
-def docgen_ifcond(ifcond: Union[str, List[str]]) -> str:
+
+def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
     if not ifcond:
         return ''
-    return ' and '.join(ifcond)
+    if isinstance(ifcond, str):
+        return ifcond
+
+    oper, operands = next(iter(ifcond.items()))
+    oper = {'all': '&&'}[oper]
+    operands = [cgen_ifcond(o) for o in operands]
+    return '(' + (') ' + oper + ' (').join(operands) + ')'
 
 
 def gen_if(cond: str) -> str:
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index 496f7e0333..3ee66c5f62 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -259,14 +259,12 @@ def check_flags(expr: _JSONObject, info: QAPISourceInfo) -> None:
 
 def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
     """
-    Normalize and validate the ``if`` member of an object.
+    Validate the ``if`` member of an object.
 
-    The ``if`` member may be either a ``str`` or a ``List[str]``.
-    A ``str`` value will be normalized to ``List[str]``.
+    The ``if`` member may be either a ``str`` or a dict.
 
     :forms:
-      :sugared: ``Union[str, List[str]]``
-      :canonical: ``List[str]``
+      :canonical: ``Union[str, dict]``
 
     :param expr: The expression containing the ``if`` member to validate.
     :param info: QAPI schema source file information.
@@ -275,31 +273,45 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
     :raise QAPISemError:
         When the "if" member fails validation, or when there are no
         non-empty conditions.
-    :return: None, ``expr`` is normalized in-place as needed.
+    :return: None
     """
     ifcond = expr.get('if')
     if ifcond is None:
         return
 
-    if isinstance(ifcond, list):
-        if not ifcond:
-            raise QAPISemError(
-                info, "'if' condition [] of %s is useless" % source)
-    else:
-        # Normalize to a list
-        ifcond = expr['if'] = [ifcond]
+    def _check_if(cond: Union[str, object]) -> None:
+        if isinstance(cond, str):
+            if not cond.strip():
+                raise QAPISemError(
+                    info,
+                    "'if' condition '%s' of %s makes no sense"
+                    % (cond, source))
+            return
 
-    for elt in ifcond:
-        if not isinstance(elt, str):
+        if not isinstance(cond, dict):
             raise QAPISemError(
                 info,
-                "'if' condition of %s must be a string or a list of strings"
-                % source)
-        if not elt.strip():
+                "'if' condition of %s must be a string or a dict" % source)
+        if len(cond) != 1:
             raise QAPISemError(
                 info,
-                "'if' condition '%s' of %s makes no sense"
-                % (elt, source))
+                "'if' condition dict of %s must have one key: "
+                "'all'" % source)
+        check_keys(cond, info, "'if' condition", [],
+                   ["all"])
+
+        oper, operands = next(iter(cond.items()))
+        if not operands:
+            raise QAPISemError(
+                info, "'if' condition [] of %s is useless" % source)
+
+        if oper in ("all") and not isinstance(operands, list):
+            raise QAPISemError(
+                info, "'%s' condition of %s must be a list" % (oper, source))
+        for operand in operands:
+            _check_if(operand)
+
+    _check_if(ifcond)
 
 
 def normalize_members(members: object) -> None:
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 30d6a01ad1..d2fbdbe583 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -32,7 +32,7 @@
 
 class QAPISchemaIfCond:
     def __init__(self, ifcond=None):
-        self.ifcond = ifcond or []
+        self.ifcond = ifcond or {}
 
     def cgen(self):
         return cgen_ifcond(self.ifcond)
diff --git a/tests/qapi-schema/bad-if-empty-list.json b/tests/qapi-schema/bad-if-empty-list.json
index 94f2eb8670..b62b5671df 100644
--- a/tests/qapi-schema/bad-if-empty-list.json
+++ b/tests/qapi-schema/bad-if-empty-list.json
@@ -1,3 +1,3 @@
 # check empty 'if' list
 { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
-  'if': [] }
+  'if': { 'all': [] } }
diff --git a/tests/qapi-schema/bad-if-list.json b/tests/qapi-schema/bad-if-list.json
index ea3d95bb6b..1fefef16a7 100644
--- a/tests/qapi-schema/bad-if-list.json
+++ b/tests/qapi-schema/bad-if-list.json
@@ -1,3 +1,3 @@
 # check invalid 'if' content
 { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
-  'if': ['foo', ' '] }
+  'if': { 'all': ['foo', ' '] } }
diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err
index f83dee65da..8278c49368 100644
--- a/tests/qapi-schema/bad-if.err
+++ b/tests/qapi-schema/bad-if.err
@@ -1,2 +1,3 @@
 bad-if.json: In struct 'TestIfStruct':
-bad-if.json:2: 'if' condition of struct must be a string or a list of strings
+bad-if.json:2: 'if' condition has unknown key 'value'
+Valid keys are 'all'.
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
index 423ea23e07..25b1053e8a 100644
--- a/tests/qapi-schema/doc-good.json
+++ b/tests/qapi-schema/doc-good.json
@@ -70,7 +70,8 @@
 # @base1:
 # the first member
 ##
-{ 'struct': 'Base', 'data': { 'base1': 'Enum' } }
+{ 'struct': 'Base', 'data': { 'base1': 'Enum' },
+  'if': { 'all': ['IFALL1', 'IFALL2'] } }
 
 ##
 # @Variant1:
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index 8f54ceff2e..689d084f3a 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -12,15 +12,16 @@ enum QType
 module doc-good.json
 enum Enum
     member one
-        if ['defined(IFONE)']
+        if defined(IFONE)
     member two
-    if ['defined(IFCOND)']
+    if defined(IFCOND)
     feature enum-feat
 object Base
     member base1: Enum optional=False
+    if OrderedDict([('all', ['IFALL1', 'IFALL2'])])
 object Variant1
     member var1: str optional=False
-        if ['defined(IFSTR)']
+        if defined(IFSTR)
         feature member-feat
     feature variant1-feat
 object Variant2
@@ -29,7 +30,7 @@ object Object
     tag base1
     case one: Variant1
     case two: Variant2
-        if ['IFTWO']
+        if IFTWO
     feature union-feat1
 object q_obj_Variant1-wrapper
     member data: Variant1 optional=False
@@ -38,13 +39,13 @@ object q_obj_Variant2-wrapper
 enum SugaredUnionKind
     member one
     member two
-        if ['IFTWO']
+        if IFTWO
 object SugaredUnion
     member type: SugaredUnionKind optional=False
     tag type
     case one: q_obj_Variant1-wrapper
     case two: q_obj_Variant2-wrapper
-        if ['IFTWO']
+        if IFTWO
     feature union-feat2
 alternate Alternate
     tag type
diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
index 726727af74..4490108cb7 100644
--- a/tests/qapi-schema/doc-good.txt
+++ b/tests/qapi-schema/doc-good.txt
@@ -76,6 +76,12 @@ Members
    the first member
 
 
+If
+~~
+
+"(IFALL1 and IFALL2)"
+
+
 "Variant1" (Object)
 -------------------
 
diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err
index 0556dc967b..df305cd79f 100644
--- a/tests/qapi-schema/enum-if-invalid.err
+++ b/tests/qapi-schema/enum-if-invalid.err
@@ -1,2 +1,3 @@
 enum-if-invalid.json: In enum 'TestIfEnum':
-enum-if-invalid.json:2: 'if' condition of 'data' member 'bar' must be a string or a list of strings
+enum-if-invalid.json:2: 'if' condition has unknown key 'val'
+Valid keys are 'all'.
diff --git a/tests/qapi-schema/features-if-invalid.err b/tests/qapi-schema/features-if-invalid.err
index f63b89535e..8b64318df6 100644
--- a/tests/qapi-schema/features-if-invalid.err
+++ b/tests/qapi-schema/features-if-invalid.err
@@ -1,2 +1,2 @@
 features-if-invalid.json: In struct 'Stru':
-features-if-invalid.json:2: 'if' condition of 'features' member 'f' must be a string or a list of strings
+features-if-invalid.json:2: 'if' condition of 'features' member 'f' must be a string or a dict
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 84b9d41f15..f2e0fff51f 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -231,8 +231,8 @@
 
 { 'union': 'TestIfUnion', 'data':
   { 'foo': 'TestStruct',
-    'bar': { 'type': 'str', 'if': 'defined(TEST_IF_UNION_BAR)'} },
-  'if': 'defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)' }
+    'union-bar': { 'type': 'str', 'if': 'defined(TEST_IF_UNION_BAR)'} },
+  'if': { 'all': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] } }
 
 { 'command': 'test-if-union-cmd',
   'data': { 'union-cmd-arg': 'TestIfUnion' },
@@ -241,25 +241,24 @@
 { 'alternate': 'TestIfAlternate', 'data':
   { 'foo': 'int',
     'bar': { 'type': 'TestStruct', 'if': 'defined(TEST_IF_ALT_BAR)'} },
-  'if': 'defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)' }
+  'if': { 'all': ['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'] } }
 
-{ 'command': 'test-if-alternate-cmd',
-  'data': { 'alt-cmd-arg': 'TestIfAlternate' },
-  'if': 'defined(TEST_IF_ALT)' }
+{ 'command': 'test-if-alternate-cmd', 'data': { 'alt-cmd-arg': 'TestIfAlternate' },
+  'if': { 'all': ['defined(TEST_IF_ALT)'] } }
 
 { 'command': 'test-if-cmd',
   'data': {
     'foo': 'TestIfStruct',
     'bar': { 'type': 'TestIfEnum', 'if': 'defined(TEST_IF_CMD_BAR)' } },
   'returns': 'UserDefThree',
-  'if': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] }
+  'if': { 'all': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] } }
 
 { 'command': 'test-cmd-return-def-three', 'returns': 'UserDefThree' }
 
 { 'event': 'TEST_IF_EVENT', 'data':
   { 'foo': 'TestIfStruct',
     'bar': { 'type': ['TestIfEnum'], 'if': 'defined(TEST_IF_EVT_BAR)' } },
-  'if': 'defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)' }
+  'if': { 'all': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] } }
 
 # test 'features'
 
@@ -288,8 +287,9 @@
                 { 'name': 'feature2', 'if': 'defined(TEST_IF_FEATURE_2)'} ] }
 { 'struct': 'CondFeatureStruct3',
   'data': { 'foo': 'int' },
-  'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
-                                              'defined(TEST_IF_COND_2)'] } ] }
+  'features': [ { 'name': 'feature1',
+                  'if': { 'all': [ 'defined(TEST_IF_COND_1)',
+                                   'defined(TEST_IF_COND_2)'] } } ] }
 
 { 'enum': 'FeatureEnum1',
   'data': [ 'eins', 'zwei', 'drei' ],
@@ -328,8 +328,9 @@
   'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'},
                 { 'name': 'feature2', 'if': 'defined(TEST_IF_FEATURE_2)'} ] }
 { 'command': 'test-command-cond-features3',
-  'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
-                                              'defined(TEST_IF_COND_2)'] } ] }
+  'features': [ { 'name': 'feature1',
+                  'if': { 'all': [ 'defined(TEST_IF_COND_1)',
+                                   'defined(TEST_IF_COND_2)'] } } ] }
 
 { 'event': 'TEST_EVENT_FEATURES0',
   'data': 'FeatureStruct1' }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index e0b8a5f0b6..6a1b3aa341 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -298,65 +298,65 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio
 object TestIfStruct
     member foo: int optional=False
     member bar: int optional=False
-        if ['defined(TEST_IF_STRUCT_BAR)']
-    if ['defined(TEST_IF_STRUCT)']
+        if defined(TEST_IF_STRUCT_BAR)
+    if defined(TEST_IF_STRUCT)
 enum TestIfEnum
     member foo
     member bar
-        if ['defined(TEST_IF_ENUM_BAR)']
-    if ['defined(TEST_IF_ENUM)']
+        if defined(TEST_IF_ENUM_BAR)
+    if defined(TEST_IF_ENUM)
 object q_obj_TestStruct-wrapper
     member data: TestStruct optional=False
 enum TestIfUnionKind
     member foo
-    member bar
-        if ['defined(TEST_IF_UNION_BAR)']
-    if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']
+    member union-bar
+        if defined(TEST_IF_UNION_BAR)
+    if OrderedDict([('all', ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])])
 object TestIfUnion
     member type: TestIfUnionKind optional=False
     tag type
     case foo: q_obj_TestStruct-wrapper
-    case bar: q_obj_str-wrapper
-        if ['defined(TEST_IF_UNION_BAR)']
-    if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)']
+    case union-bar: q_obj_str-wrapper
+        if defined(TEST_IF_UNION_BAR)
+    if OrderedDict([('all', ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])])
 object q_obj_test-if-union-cmd-arg
     member union-cmd-arg: TestIfUnion optional=False
-    if ['defined(TEST_IF_UNION)']
+    if defined(TEST_IF_UNION)
 command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if ['defined(TEST_IF_UNION)']
+    if defined(TEST_IF_UNION)
 alternate TestIfAlternate
     tag type
     case foo: int
     case bar: TestStruct
-        if ['defined(TEST_IF_ALT_BAR)']
-    if ['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)']
+        if defined(TEST_IF_ALT_BAR)
+    if OrderedDict([('all', ['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])])
 object q_obj_test-if-alternate-cmd-arg
     member alt-cmd-arg: TestIfAlternate optional=False
-    if ['defined(TEST_IF_ALT)']
+    if OrderedDict([('all', ['defined(TEST_IF_ALT)'])])
 command test-if-alternate-cmd q_obj_test-if-alternate-cmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if ['defined(TEST_IF_ALT)']
+    if OrderedDict([('all', ['defined(TEST_IF_ALT)'])])
 object q_obj_test-if-cmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-        if ['defined(TEST_IF_CMD_BAR)']
-    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
+        if defined(TEST_IF_CMD_BAR)
+    if OrderedDict([('all', ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])])
 command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
+    if OrderedDict([('all', ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])])
 command test-cmd-return-def-three None -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
 array TestIfEnumList TestIfEnum
-    if ['defined(TEST_IF_ENUM)']
+    if defined(TEST_IF_ENUM)
 object q_obj_TEST_IF_EVENT-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnumList optional=False
-        if ['defined(TEST_IF_EVT_BAR)']
-    if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
+        if defined(TEST_IF_EVT_BAR)
+    if OrderedDict([('all', ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])])
 event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
     boxed=False
-    if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)']
+    if OrderedDict([('all', ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])])
 object FeatureStruct0
     member foo: int optional=False
 object FeatureStruct1
@@ -379,17 +379,17 @@ object FeatureStruct4
 object CondFeatureStruct1
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if defined(TEST_IF_FEATURE_1)
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if defined(TEST_IF_FEATURE_1)
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if defined(TEST_IF_FEATURE_2)
 object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
+        if OrderedDict([('all', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
 enum FeatureEnum1
     member eins
     member zwei
@@ -429,17 +429,17 @@ command test-command-features3 None -> None
 command test-command-cond-features1 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if defined(TEST_IF_FEATURE_1)
 command test-command-cond-features2 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if defined(TEST_IF_FEATURE_1)
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if defined(TEST_IF_FEATURE_2)
 command test-command-cond-features3 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
+        if OrderedDict([('all', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
 event TEST_EVENT_FEATURES0 FeatureStruct1
     boxed=False
 event TEST_EVENT_FEATURES1 None
diff --git a/tests/qapi-schema/struct-member-if-invalid.err b/tests/qapi-schema/struct-member-if-invalid.err
index 42e7fdae3c..eea4c62aaf 100644
--- a/tests/qapi-schema/struct-member-if-invalid.err
+++ b/tests/qapi-schema/struct-member-if-invalid.err
@@ -1,2 +1,2 @@
 struct-member-if-invalid.json: In struct 'Stru':
-struct-member-if-invalid.json:2: 'if' condition of 'data' member 'member' must be a string or a list of strings
+struct-member-if-invalid.json:2: 'if' condition of 'data' member 'member' must be a string or a dict
diff --git a/tests/qapi-schema/union-branch-if-invalid.json b/tests/qapi-schema/union-branch-if-invalid.json
index 46d4239af6..c41633856f 100644
--- a/tests/qapi-schema/union-branch-if-invalid.json
+++ b/tests/qapi-schema/union-branch-if-invalid.json
@@ -3,4 +3,4 @@
 { 'struct': 'Stru', 'data': { 'member': 'str' } }
 { 'union': 'Uni',
   'base': { 'tag': 'Branches' }, 'discriminator': 'tag',
-  'data': { 'branch1': { 'type': 'Stru', 'if': [''] } } }
+  'data': { 'branch1': { 'type': 'Stru', 'if': { 'all': [''] } } } }
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 08/11] qapi: add 'any' condition
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (6 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]} marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-08-03 13:20   ` Markus Armbruster
  2021-06-18 10:25 ` [PATCH v6 09/11] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 tests/unit/test-qmp-cmds.c              | 1 +
 scripts/qapi/common.py                  | 4 ++--
 scripts/qapi/expr.py                    | 6 +++---
 tests/qapi-schema/bad-if.err            | 2 +-
 tests/qapi-schema/doc-good.json         | 3 ++-
 tests/qapi-schema/doc-good.out          | 2 +-
 tests/qapi-schema/doc-good.txt          | 3 ++-
 tests/qapi-schema/enum-if-invalid.err   | 2 +-
 tests/qapi-schema/qapi-schema-test.json | 7 ++++++-
 tests/qapi-schema/qapi-schema-test.out  | 5 +++++
 10 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/tests/unit/test-qmp-cmds.c b/tests/unit/test-qmp-cmds.c
index 1b0b7d99df..83efa39720 100644
--- a/tests/unit/test-qmp-cmds.c
+++ b/tests/unit/test-qmp-cmds.c
@@ -51,6 +51,7 @@ FeatureStruct1 *qmp_test_features0(bool has_fs0, FeatureStruct0 *fs0,
                                    bool has_cfs1, CondFeatureStruct1 *cfs1,
                                    bool has_cfs2, CondFeatureStruct2 *cfs2,
                                    bool has_cfs3, CondFeatureStruct3 *cfs3,
+                                   bool has_cfs4, CondFeatureStruct4 *cfs4,
                                    Error **errp)
 {
     return g_new0(FeatureStruct1, 1);
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 51463510c9..018d2f6996 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -207,7 +207,7 @@ def docgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
         return ifcond
 
     oper, operands = next(iter(ifcond.items()))
-    oper = {'all': ' and '}[oper]
+    oper = {'all': ' and ', 'any': ' or '}[oper]
     operands = [docgen_ifcond(o) for o in operands]
     return '(' + oper.join(operands) + ')'
 
@@ -219,7 +219,7 @@ def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
         return ifcond
 
     oper, operands = next(iter(ifcond.items()))
-    oper = {'all': '&&'}[oper]
+    oper = {'all': '&&', 'any': '||'}[oper]
     operands = [cgen_ifcond(o) for o in operands]
     return '(' + (') ' + oper + ' (').join(operands) + ')'
 
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index 3ee66c5f62..e10a803dc2 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -296,16 +296,16 @@ def _check_if(cond: Union[str, object]) -> None:
             raise QAPISemError(
                 info,
                 "'if' condition dict of %s must have one key: "
-                "'all'" % source)
+                "'all' or 'any'" % source)
         check_keys(cond, info, "'if' condition", [],
-                   ["all"])
+                   ["all", "any"])
 
         oper, operands = next(iter(cond.items()))
         if not operands:
             raise QAPISemError(
                 info, "'if' condition [] of %s is useless" % source)
 
-        if oper in ("all") and not isinstance(operands, list):
+        if oper in ("all", "any") and not isinstance(operands, list):
             raise QAPISemError(
                 info, "'%s' condition of %s must be a list" % (oper, source))
         for operand in operands:
diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err
index 8278c49368..3624e79b0c 100644
--- a/tests/qapi-schema/bad-if.err
+++ b/tests/qapi-schema/bad-if.err
@@ -1,3 +1,3 @@
 bad-if.json: In struct 'TestIfStruct':
 bad-if.json:2: 'if' condition has unknown key 'value'
-Valid keys are 'all'.
+Valid keys are 'all', 'any'.
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
index 25b1053e8a..a67d4d9467 100644
--- a/tests/qapi-schema/doc-good.json
+++ b/tests/qapi-schema/doc-good.json
@@ -103,7 +103,8 @@
   'features': [ 'union-feat1' ],
   'base': 'Base',
   'discriminator': 'base1',
-  'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
+  'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2',
+                                        'if': { 'any': ['IFONE', 'IFTWO'] } } } }
 
 ##
 # @SugaredUnion:
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index 689d084f3a..c44c346ec8 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -30,7 +30,7 @@ object Object
     tag base1
     case one: Variant1
     case two: Variant2
-        if IFTWO
+        if OrderedDict([('any', ['IFONE', 'IFTWO'])])
     feature union-feat1
 object q_obj_Variant1-wrapper
     member data: Variant1 optional=False
diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
index 4490108cb7..251e9b746c 100644
--- a/tests/qapi-schema/doc-good.txt
+++ b/tests/qapi-schema/doc-good.txt
@@ -120,7 +120,8 @@ Members
 
 The members of "Base"
 The members of "Variant1" when "base1" is ""one""
-The members of "Variant2" when "base1" is ""two"" (**If: **"IFTWO")
+The members of "Variant2" when "base1" is ""two"" (**If: **"(IFONE or
+IFTWO)")
 
 Features
 ~~~~~~~~
diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err
index df305cd79f..b96d94c48a 100644
--- a/tests/qapi-schema/enum-if-invalid.err
+++ b/tests/qapi-schema/enum-if-invalid.err
@@ -1,3 +1,3 @@
 enum-if-invalid.json: In enum 'TestIfEnum':
 enum-if-invalid.json:2: 'if' condition has unknown key 'val'
-Valid keys are 'all'.
+Valid keys are 'all', 'any'.
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index f2e0fff51f..252fd8cc86 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -290,6 +290,10 @@
   'features': [ { 'name': 'feature1',
                   'if': { 'all': [ 'defined(TEST_IF_COND_1)',
                                    'defined(TEST_IF_COND_2)'] } } ] }
+{ 'struct': 'CondFeatureStruct4',
+  'data': { 'foo': 'int' },
+  'features': [ { 'name': 'feature1', 'if': {'any': ['defined(TEST_IF_COND_1)',
+                                                     'defined(TEST_IF_COND_2)'] } } ] }
 
 { 'enum': 'FeatureEnum1',
   'data': [ 'eins', 'zwei', 'drei' ],
@@ -313,7 +317,8 @@
             '*fs4': 'FeatureStruct4',
             '*cfs1': 'CondFeatureStruct1',
             '*cfs2': 'CondFeatureStruct2',
-            '*cfs3': 'CondFeatureStruct3' },
+            '*cfs3': 'CondFeatureStruct3',
+            '*cfs4': 'CondFeatureStruct4' },
   'returns': 'FeatureStruct1',
   'features': [] }
 
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 6a1b3aa341..e5625f2542 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -390,6 +390,10 @@ object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
         if OrderedDict([('all', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
+object CondFeatureStruct4
+    member foo: int optional=False
+    feature feature1
+        if OrderedDict([('any', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
 enum FeatureEnum1
     member eins
     member zwei
@@ -417,6 +421,7 @@ object q_obj_test-features0-arg
     member cfs1: CondFeatureStruct1 optional=True
     member cfs2: CondFeatureStruct2 optional=True
     member cfs3: CondFeatureStruct3 optional=True
+    member cfs4: CondFeatureStruct4 optional=True
 command test-features0 q_obj_test-features0-arg -> FeatureStruct1
     gen=True success_response=True boxed=False oob=False preconfig=False
 command test-command-features1 None -> None
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 09/11] qapi: convert 'if' C-expressions to the new syntax tree
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (7 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 08/11] qapi: add 'any' condition marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-08-03 13:22   ` Markus Armbruster
  2021-06-18 10:25 ` [PATCH v6 10/11] qapi: add 'not' condition operation marcandre.lureau
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Tested-by: John Snow <jsnow@redhat.com>
---
 qapi/machine-target.json | 20 ++++++++++++++++----
 qapi/misc-target.json    | 12 +++++++++++-
 2 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/qapi/machine-target.json b/qapi/machine-target.json
index e7811654b7..9b56b81bea 100644
--- a/qapi/machine-target.json
+++ b/qapi/machine-target.json
@@ -213,7 +213,9 @@
 ##
 { 'struct': 'CpuModelExpansionInfo',
   'data': { 'model': 'CpuModelInfo' },
-  'if': 'defined(TARGET_S390X) || defined(TARGET_I386) || defined(TARGET_ARM)' }
+  'if': { 'any': [ 'defined(TARGET_S390X)',
+                   'defined(TARGET_I386)',
+                   'defined(TARGET_ARM)'] } }
 
 ##
 # @query-cpu-model-expansion:
@@ -252,7 +254,9 @@
   'data': { 'type': 'CpuModelExpansionType',
             'model': 'CpuModelInfo' },
   'returns': 'CpuModelExpansionInfo',
-  'if': 'defined(TARGET_S390X) || defined(TARGET_I386) || defined(TARGET_ARM)' }
+  'if': { 'any': [ 'defined(TARGET_S390X)',
+                   'defined(TARGET_I386)',
+                   'defined(TARGET_ARM)' ] } }
 
 ##
 # @CpuDefinitionInfo:
@@ -316,7 +320,11 @@
             'typename': 'str',
             '*alias-of' : 'str',
             'deprecated' : 'bool' },
-  'if': 'defined(TARGET_PPC) || defined(TARGET_ARM) || defined(TARGET_I386) || defined(TARGET_S390X) || defined(TARGET_MIPS)' }
+  'if': { 'any': [ 'defined(TARGET_PPC)',
+                   'defined(TARGET_ARM)',
+                   'defined(TARGET_I386)',
+                   'defined(TARGET_S390X)',
+                   'defined(TARGET_MIPS)' ] } }
 
 ##
 # @query-cpu-definitions:
@@ -328,4 +336,8 @@
 # Since: 1.2
 ##
 { 'command': 'query-cpu-definitions', 'returns': ['CpuDefinitionInfo'],
-  'if': 'defined(TARGET_PPC) || defined(TARGET_ARM) || defined(TARGET_I386) || defined(TARGET_S390X) || defined(TARGET_MIPS)' }
+  'if': { 'any': [ 'defined(TARGET_PPC)',
+                   'defined(TARGET_ARM)',
+                   'defined(TARGET_I386)',
+                   'defined(TARGET_S390X)',
+                   'defined(TARGET_MIPS)' ] } }
diff --git a/qapi/misc-target.json b/qapi/misc-target.json
index 5573dcf8f0..9e2ea4a04a 100644
--- a/qapi/misc-target.json
+++ b/qapi/misc-target.json
@@ -23,7 +23,17 @@
 ##
 { 'event': 'RTC_CHANGE',
   'data': { 'offset': 'int' },
-  'if': 'defined(TARGET_ALPHA) || defined(TARGET_ARM) || defined(TARGET_HPPA) || defined(TARGET_I386) || defined(TARGET_MIPS) || defined(TARGET_MIPS64) || defined(TARGET_PPC) || defined(TARGET_PPC64) || defined(TARGET_S390X) || defined(TARGET_SH4) || defined(TARGET_SPARC)' }
+  'if': { 'any': [ 'defined(TARGET_ALPHA)',
+                   'defined(TARGET_ARM)',
+                   'defined(TARGET_HPPA)',
+                   'defined(TARGET_I386)',
+                   'defined(TARGET_MIPS)',
+                   'defined(TARGET_MIPS64)',
+                   'defined(TARGET_PPC)',
+                   'defined(TARGET_PPC64)',
+                   'defined(TARGET_S390X)',
+                   'defined(TARGET_SH4)',
+                   'defined(TARGET_SPARC)' ] } }
 
 ##
 # @rtc-reset-reinjection:
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 10/11] qapi: add 'not' condition operation
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (8 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 09/11] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-06-18 10:25 ` [PATCH v6 11/11] qapi: make 'if' condition strings simple identifiers marcandre.lureau
  2021-08-03 13:44 ` [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor Markus Armbruster
  11 siblings, 0 replies; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

For the sake of completeness, introduce the 'not' condition.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/common.py                  | 4 ++++
 scripts/qapi/expr.py                    | 7 +++++--
 tests/qapi-schema/bad-if.err            | 2 +-
 tests/qapi-schema/doc-good.json         | 3 ++-
 tests/qapi-schema/doc-good.out          | 1 +
 tests/qapi-schema/doc-good.txt          | 6 ++++++
 tests/qapi-schema/enum-if-invalid.err   | 2 +-
 tests/qapi-schema/qapi-schema-test.json | 2 +-
 tests/qapi-schema/qapi-schema-test.out  | 4 ++--
 9 files changed, 23 insertions(+), 8 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 018d2f6996..f8718e201b 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -207,6 +207,8 @@ def docgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
         return ifcond
 
     oper, operands = next(iter(ifcond.items()))
+    if oper == 'not':
+        return '!' + docgen_ifcond(operands)
     oper = {'all': ' and ', 'any': ' or '}[oper]
     operands = [docgen_ifcond(o) for o in operands]
     return '(' + oper.join(operands) + ')'
@@ -219,6 +221,8 @@ def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
         return ifcond
 
     oper, operands = next(iter(ifcond.items()))
+    if oper == 'not':
+        return '!' + cgen_ifcond(operands)
     oper = {'all': '&&', 'any': '||'}[oper]
     operands = [cgen_ifcond(o) for o in operands]
     return '(' + (') ' + oper + ' (').join(operands) + ')'
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index e10a803dc2..d2bd52c49f 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -296,15 +296,18 @@ def _check_if(cond: Union[str, object]) -> None:
             raise QAPISemError(
                 info,
                 "'if' condition dict of %s must have one key: "
-                "'all' or 'any'" % source)
+                "'all', 'any' or 'not" % source)
         check_keys(cond, info, "'if' condition", [],
-                   ["all", "any"])
+                   ["all", "any", "not"])
 
         oper, operands = next(iter(cond.items()))
         if not operands:
             raise QAPISemError(
                 info, "'if' condition [] of %s is useless" % source)
 
+        if oper == "not":
+            _check_if(operands)
+            return
         if oper in ("all", "any") and not isinstance(operands, list):
             raise QAPISemError(
                 info, "'%s' condition of %s must be a list" % (oper, source))
diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err
index 3624e79b0c..454fbae387 100644
--- a/tests/qapi-schema/bad-if.err
+++ b/tests/qapi-schema/bad-if.err
@@ -1,3 +1,3 @@
 bad-if.json: In struct 'TestIfStruct':
 bad-if.json:2: 'if' condition has unknown key 'value'
-Valid keys are 'all', 'any'.
+Valid keys are 'all', 'any', 'not'.
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
index a67d4d9467..836fb1bdc4 100644
--- a/tests/qapi-schema/doc-good.json
+++ b/tests/qapi-schema/doc-good.json
@@ -125,7 +125,8 @@
 ##
 { 'alternate': 'Alternate',
   'features': [ 'alt-feat' ],
-  'data': { 'i': 'int', 'b': 'bool' } }
+  'data': { 'i': 'int', 'b': 'bool' },
+  'if': { 'not': 'IFNOT' } }
 
 ##
 # == Another subsection
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index c44c346ec8..a8871e8f99 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -51,6 +51,7 @@ alternate Alternate
     tag type
     case i: int
     case b: bool
+    if OrderedDict([('not', 'IFNOT')])
     feature alt-feat
 object q_obj_cmd-arg
     member arg1: int optional=False
diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
index 251e9b746c..03c98c4182 100644
--- a/tests/qapi-schema/doc-good.txt
+++ b/tests/qapi-schema/doc-good.txt
@@ -171,6 +171,12 @@ Features
    a feature
 
 
+If
+~~
+
+"!IFNOT"
+
+
 Another subsection
 ==================
 
diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err
index b96d94c48a..3bb84075a9 100644
--- a/tests/qapi-schema/enum-if-invalid.err
+++ b/tests/qapi-schema/enum-if-invalid.err
@@ -1,3 +1,3 @@
 enum-if-invalid.json: In enum 'TestIfEnum':
 enum-if-invalid.json:2: 'if' condition has unknown key 'val'
-Valid keys are 'all', 'any'.
+Valid keys are 'all', 'any', 'not'.
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 252fd8cc86..a0f09b277e 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -244,7 +244,7 @@
   'if': { 'all': ['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'] } }
 
 { 'command': 'test-if-alternate-cmd', 'data': { 'alt-cmd-arg': 'TestIfAlternate' },
-  'if': { 'all': ['defined(TEST_IF_ALT)'] } }
+  'if': { 'all': ['defined(TEST_IF_ALT)', {'not': 'defined(TEST_IF_NOT_ALT)'}] } }
 
 { 'command': 'test-if-cmd',
   'data': {
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index e5625f2542..df2c57de54 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -333,10 +333,10 @@ alternate TestIfAlternate
     if OrderedDict([('all', ['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])])
 object q_obj_test-if-alternate-cmd-arg
     member alt-cmd-arg: TestIfAlternate optional=False
-    if OrderedDict([('all', ['defined(TEST_IF_ALT)'])])
+    if OrderedDict([('all', ['defined(TEST_IF_ALT)', OrderedDict([('not', 'defined(TEST_IF_NOT_ALT)')])])])
 command test-if-alternate-cmd q_obj_test-if-alternate-cmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if OrderedDict([('all', ['defined(TEST_IF_ALT)'])])
+    if OrderedDict([('all', ['defined(TEST_IF_ALT)', OrderedDict([('not', 'defined(TEST_IF_NOT_ALT)')])])])
 object q_obj_test-if-cmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* [PATCH v6 11/11] qapi: make 'if' condition strings simple identifiers
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (9 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 10/11] qapi: add 'not' condition operation marcandre.lureau
@ 2021-06-18 10:25 ` marcandre.lureau
  2021-08-03 13:35   ` Markus Armbruster
  2021-08-03 13:44 ` [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor Markus Armbruster
  11 siblings, 1 reply; 39+ messages in thread
From: marcandre.lureau @ 2021-06-18 10:25 UTC (permalink / raw)
  To: qemu-devel
  Cc: jsnow, Eric Blake, Markus Armbruster, stefanha, Marc-André Lureau

From: Marc-André Lureau <marcandre.lureau@redhat.com>

Change the 'if' condition strings to be C-agnostic and be simple
identifiers.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Tested-by: John Snow <jsnow@redhat.com>
---
 qapi/block-core.json                          | 16 ++---
 qapi/block-export.json                        |  6 +-
 qapi/char.json                                | 12 ++--
 qapi/machine-target.json                      | 40 ++++++-------
 qapi/migration.json                           | 10 ++--
 qapi/misc-target.json                         | 50 ++++++++--------
 qapi/qom.json                                 | 10 ++--
 qapi/sockets.json                             |  6 +-
 qapi/tpm.json                                 | 18 +++---
 qapi/ui.json                                  | 48 +++++++--------
 qga/qapi-schema.json                          |  8 +--
 scripts/qapi/common.py                        |  2 +-
 scripts/qapi/expr.py                          |  4 +-
 .../alternate-branch-if-invalid.err           |  2 +-
 tests/qapi-schema/bad-if-empty.err            |  2 +-
 tests/qapi-schema/bad-if-list.err             |  2 +-
 tests/qapi-schema/bad-if.json                 |  2 +-
 tests/qapi-schema/doc-good.json               |  6 +-
 tests/qapi-schema/doc-good.out                |  6 +-
 tests/qapi-schema/doc-good.txt                |  6 +-
 tests/qapi-schema/features-missing-name.json  |  2 +-
 tests/qapi-schema/qapi-schema-test.json       | 52 ++++++++--------
 tests/qapi-schema/qapi-schema-test.out        | 60 +++++++++----------
 tests/qapi-schema/union-branch-if-invalid.err |  2 +-
 24 files changed, 186 insertions(+), 186 deletions(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index 2ea294129e..13fcdd6b91 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -2779,7 +2779,7 @@
 ##
 { 'enum': 'BlockdevAioOptions',
   'data': [ 'threads', 'native',
-            { 'name': 'io_uring', 'if': 'defined(CONFIG_LINUX_IO_URING)' } ] }
+            { 'name': 'io_uring', 'if': 'CONFIG_LINUX_IO_URING' } ] }
 
 ##
 # @BlockdevCacheOptions:
@@ -2817,7 +2817,7 @@
             'gluster', 'host_cdrom', 'host_device', 'http', 'https', 'iscsi',
             'luks', 'nbd', 'nfs', 'null-aio', 'null-co', 'nvme', 'parallels',
             'preallocate', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'rbd',
-            { 'name': 'replication', 'if': 'defined(CONFIG_REPLICATION)' },
+            { 'name': 'replication', 'if': 'CONFIG_REPLICATION' },
             'ssh', 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
 
 ##
@@ -2859,10 +2859,10 @@
             '*locking': 'OnOffAuto',
             '*aio': 'BlockdevAioOptions',
             '*drop-cache': {'type': 'bool',
-                            'if': 'defined(CONFIG_LINUX)'},
+                            'if': 'CONFIG_LINUX'},
             '*x-check-cache-dropped': 'bool' },
   'features': [ { 'name': 'dynamic-auto-read-only',
-                  'if': 'defined(CONFIG_POSIX)' } ] }
+                  'if': 'CONFIG_POSIX' } ] }
 
 ##
 # @BlockdevOptionsNull:
@@ -3662,7 +3662,7 @@
 # Since: 2.9
 ##
 { 'enum' : 'ReplicationMode', 'data' : [ 'primary', 'secondary' ],
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @BlockdevOptionsReplication:
@@ -3681,7 +3681,7 @@
   'base': 'BlockdevOptionsGenericFormat',
   'data': { 'mode': 'ReplicationMode',
             '*top-id': 'str' },
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @NFSTransport:
@@ -4015,7 +4015,7 @@
       'raw':        'BlockdevOptionsRaw',
       'rbd':        'BlockdevOptionsRbd',
       'replication': { 'type': 'BlockdevOptionsReplication',
-                       'if': 'defined(CONFIG_REPLICATION)' },
+                       'if': 'CONFIG_REPLICATION' },
       'ssh':        'BlockdevOptionsSsh',
       'throttle':   'BlockdevOptionsThrottle',
       'vdi':        'BlockdevOptionsGenericFormat',
@@ -4316,7 +4316,7 @@
 # Since: 5.1
 ##
 { 'enum': 'Qcow2CompressionType',
-  'data': [ 'zlib', { 'name': 'zstd', 'if': 'defined(CONFIG_ZSTD)' } ] }
+  'data': [ 'zlib', { 'name': 'zstd', 'if': 'CONFIG_ZSTD' } ] }
 
 ##
 # @BlockdevCreateOptionsQcow2:
diff --git a/qapi/block-export.json b/qapi/block-export.json
index e819e70cac..319992b1b0 100644
--- a/qapi/block-export.json
+++ b/qapi/block-export.json
@@ -137,7 +137,7 @@
 { 'struct': 'BlockExportOptionsFuse',
   'data': { 'mountpoint': 'str',
             '*growable': 'bool' },
-  'if': 'defined(CONFIG_FUSE)' }
+  'if': 'CONFIG_FUSE' }
 
 ##
 # @NbdServerAddOptions:
@@ -247,7 +247,7 @@
 ##
 { 'enum': 'BlockExportType',
   'data': [ 'nbd', 'vhost-user-blk',
-            { 'name': 'fuse', 'if': 'defined(CONFIG_FUSE)' } ] }
+            { 'name': 'fuse', 'if': 'CONFIG_FUSE' } ] }
 
 ##
 # @BlockExportOptions:
@@ -290,7 +290,7 @@
       'nbd': 'BlockExportOptionsNbd',
       'vhost-user-blk': 'BlockExportOptionsVhostUserBlk',
       'fuse': { 'type': 'BlockExportOptionsFuse',
-                'if': 'defined(CONFIG_FUSE)' }
+                'if': 'CONFIG_FUSE' }
    } }
 
 ##
diff --git a/qapi/char.json b/qapi/char.json
index adf2685f68..9b18ee3305 100644
--- a/qapi/char.json
+++ b/qapi/char.json
@@ -342,7 +342,7 @@
 { 'struct': 'ChardevSpiceChannel',
   'data': { 'type': 'str' },
   'base': 'ChardevCommon',
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @ChardevSpicePort:
@@ -356,7 +356,7 @@
 { 'struct': 'ChardevSpicePort',
   'data': { 'fqdn': 'str' },
   'base': 'ChardevCommon',
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @ChardevVC:
@@ -405,7 +405,7 @@
   'data': { '*mouse': 'bool',
             '*clipboard': 'bool' },
   'base': 'ChardevCommon',
-  'if': 'defined(CONFIG_SPICE_PROTOCOL)' }
+  'if': 'CONFIG_SPICE_PROTOCOL' }
 
 ##
 # @ChardevBackend:
@@ -431,11 +431,11 @@
             'stdio': 'ChardevStdio',
             'console': 'ChardevCommon',
             'spicevmc': { 'type': 'ChardevSpiceChannel',
-                          'if': 'defined(CONFIG_SPICE)' },
+                          'if': 'CONFIG_SPICE' },
             'spiceport': { 'type': 'ChardevSpicePort',
-                           'if': 'defined(CONFIG_SPICE)' },
+                           'if': 'CONFIG_SPICE' },
             'qemu-vdagent': { 'type': 'ChardevQemuVDAgent',
-                              'if': 'defined(CONFIG_SPICE_PROTOCOL)' },
+                              'if': 'CONFIG_SPICE_PROTOCOL' },
             'vc': 'ChardevVC',
             'ringbuf': 'ChardevRingbuf',
             # next one is just for compatibility
diff --git a/qapi/machine-target.json b/qapi/machine-target.json
index 9b56b81bea..f5ec4bc172 100644
--- a/qapi/machine-target.json
+++ b/qapi/machine-target.json
@@ -89,7 +89,7 @@
 ##
 { 'struct': 'CpuModelBaselineInfo',
   'data': { 'model': 'CpuModelInfo' },
-  'if': 'defined(TARGET_S390X)' }
+  'if': 'TARGET_S390X' }
 
 ##
 # @CpuModelCompareInfo:
@@ -112,7 +112,7 @@
 { 'struct': 'CpuModelCompareInfo',
   'data': { 'result': 'CpuModelCompareResult',
             'responsible-properties': ['str'] },
-  'if': 'defined(TARGET_S390X)' }
+  'if': 'TARGET_S390X' }
 
 ##
 # @query-cpu-model-comparison:
@@ -156,7 +156,7 @@
 { 'command': 'query-cpu-model-comparison',
   'data': { 'modela': 'CpuModelInfo', 'modelb': 'CpuModelInfo' },
   'returns': 'CpuModelCompareInfo',
-  'if': 'defined(TARGET_S390X)' }
+  'if': 'TARGET_S390X' }
 
 ##
 # @query-cpu-model-baseline:
@@ -200,7 +200,7 @@
   'data': { 'modela': 'CpuModelInfo',
             'modelb': 'CpuModelInfo' },
   'returns': 'CpuModelBaselineInfo',
-  'if': 'defined(TARGET_S390X)' }
+  'if': 'TARGET_S390X' }
 
 ##
 # @CpuModelExpansionInfo:
@@ -213,9 +213,9 @@
 ##
 { 'struct': 'CpuModelExpansionInfo',
   'data': { 'model': 'CpuModelInfo' },
-  'if': { 'any': [ 'defined(TARGET_S390X)',
-                   'defined(TARGET_I386)',
-                   'defined(TARGET_ARM)'] } }
+  'if': { 'any': [ 'TARGET_S390X',
+                   'TARGET_I386',
+                   'TARGET_ARM' ] } }
 
 ##
 # @query-cpu-model-expansion:
@@ -254,9 +254,9 @@
   'data': { 'type': 'CpuModelExpansionType',
             'model': 'CpuModelInfo' },
   'returns': 'CpuModelExpansionInfo',
-  'if': { 'any': [ 'defined(TARGET_S390X)',
-                   'defined(TARGET_I386)',
-                   'defined(TARGET_ARM)' ] } }
+  'if': { 'any': [ 'TARGET_S390X',
+                   'TARGET_I386',
+                   'TARGET_ARM' ] } }
 
 ##
 # @CpuDefinitionInfo:
@@ -320,11 +320,11 @@
             'typename': 'str',
             '*alias-of' : 'str',
             'deprecated' : 'bool' },
-  'if': { 'any': [ 'defined(TARGET_PPC)',
-                   'defined(TARGET_ARM)',
-                   'defined(TARGET_I386)',
-                   'defined(TARGET_S390X)',
-                   'defined(TARGET_MIPS)' ] } }
+  'if': { 'any': [ 'TARGET_PPC',
+                   'TARGET_ARM',
+                   'TARGET_I386',
+                   'TARGET_S390X',
+                   'TARGET_MIPS' ] } }
 
 ##
 # @query-cpu-definitions:
@@ -336,8 +336,8 @@
 # Since: 1.2
 ##
 { 'command': 'query-cpu-definitions', 'returns': ['CpuDefinitionInfo'],
-  'if': { 'any': [ 'defined(TARGET_PPC)',
-                   'defined(TARGET_ARM)',
-                   'defined(TARGET_I386)',
-                   'defined(TARGET_S390X)',
-                   'defined(TARGET_MIPS)' ] } }
+  'if': { 'any': [ 'TARGET_PPC',
+                   'TARGET_ARM',
+                   'TARGET_I386',
+                   'TARGET_S390X',
+                   'TARGET_MIPS' ] } }
diff --git a/qapi/migration.json b/qapi/migration.json
index 1124a2dda8..88f07baedd 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -533,7 +533,7 @@
 ##
 { 'enum': 'MultiFDCompression',
   'data': [ 'none', 'zlib',
-            { 'name': 'zstd', 'if': 'defined(CONFIG_ZSTD)' } ] }
+            { 'name': 'zstd', 'if': 'CONFIG_ZSTD' } ] }
 
 ##
 # @BitmapMigrationBitmapAliasTransform:
@@ -1562,7 +1562,7 @@
 ##
 { 'command': 'xen-set-replication',
   'data': { 'enable': 'bool', 'primary': 'bool', '*failover' : 'bool' },
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @ReplicationStatus:
@@ -1578,7 +1578,7 @@
 ##
 { 'struct': 'ReplicationStatus',
   'data': { 'error': 'bool', '*desc': 'str' },
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @query-xen-replication-status:
@@ -1596,7 +1596,7 @@
 ##
 { 'command': 'query-xen-replication-status',
   'returns': 'ReplicationStatus',
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @xen-colo-do-checkpoint:
@@ -1613,7 +1613,7 @@
 # Since: 2.9
 ##
 { 'command': 'xen-colo-do-checkpoint',
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @COLOStatus:
diff --git a/qapi/misc-target.json b/qapi/misc-target.json
index 9e2ea4a04a..3b05ad3dbf 100644
--- a/qapi/misc-target.json
+++ b/qapi/misc-target.json
@@ -23,17 +23,17 @@
 ##
 { 'event': 'RTC_CHANGE',
   'data': { 'offset': 'int' },
-  'if': { 'any': [ 'defined(TARGET_ALPHA)',
-                   'defined(TARGET_ARM)',
-                   'defined(TARGET_HPPA)',
-                   'defined(TARGET_I386)',
-                   'defined(TARGET_MIPS)',
-                   'defined(TARGET_MIPS64)',
-                   'defined(TARGET_PPC)',
-                   'defined(TARGET_PPC64)',
-                   'defined(TARGET_S390X)',
-                   'defined(TARGET_SH4)',
-                   'defined(TARGET_SPARC)' ] } }
+  'if': { 'any': [ 'TARGET_ALPHA',
+                   'TARGET_ARM',
+                   'TARGET_HPPA',
+                   'TARGET_I386',
+                   'TARGET_MIPS',
+                   'TARGET_MIPS64',
+                   'TARGET_PPC',
+                   'TARGET_PPC64',
+                   'TARGET_S390X',
+                   'TARGET_SH4',
+                   'TARGET_SPARC' ] } }
 
 ##
 # @rtc-reset-reinjection:
@@ -52,7 +52,7 @@
 #
 ##
 { 'command': 'rtc-reset-reinjection',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 
 ##
@@ -79,7 +79,7 @@
 { 'enum': 'SevState',
   'data': ['uninit', 'launch-update', 'launch-secret', 'running',
            'send-update', 'receive-update' ],
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @SevInfo:
@@ -111,7 +111,7 @@
               'state' : 'SevState',
               'handle' : 'uint32'
             },
-  'if': 'defined(TARGET_I386)'
+  'if': 'TARGET_I386'
 }
 
 ##
@@ -132,7 +132,7 @@
 #
 ##
 { 'command': 'query-sev', 'returns': 'SevInfo',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 
 ##
@@ -146,7 +146,7 @@
 #
 ##
 { 'struct': 'SevLaunchMeasureInfo', 'data': {'data': 'str'},
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @query-sev-launch-measure:
@@ -164,7 +164,7 @@
 #
 ##
 { 'command': 'query-sev-launch-measure', 'returns': 'SevLaunchMeasureInfo',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 
 ##
@@ -189,7 +189,7 @@
             'cert-chain': 'str',
             'cbitpos': 'int',
             'reduced-phys-bits': 'int'},
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @query-sev-capabilities:
@@ -209,7 +209,7 @@
 #
 ##
 { 'command': 'query-sev-capabilities', 'returns': 'SevCapability',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @sev-inject-launch-secret:
@@ -227,7 +227,7 @@
 ##
 { 'command': 'sev-inject-launch-secret',
   'data': { 'packet-header': 'str', 'secret': 'str', '*gpa': 'uint64' },
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @dump-skeys:
@@ -249,7 +249,7 @@
 ##
 { 'command': 'dump-skeys',
   'data': { 'filename': 'str' },
-  'if': 'defined(TARGET_S390X)' }
+  'if': 'TARGET_S390X' }
 
 ##
 # @GICCapability:
@@ -274,7 +274,7 @@
   'data': { 'version': 'int',
             'emulated': 'bool',
             'kernel': 'bool' },
-  'if': 'defined(TARGET_ARM)' }
+  'if': 'TARGET_ARM' }
 
 ##
 # @query-gic-capabilities:
@@ -294,7 +294,7 @@
 #
 ##
 { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
-  'if': 'defined(TARGET_ARM)' }
+  'if': 'TARGET_ARM' }
 
 
 ##
@@ -310,7 +310,7 @@
 ##
 { 'struct': 'SevAttestationReport',
   'data': { 'data': 'str'},
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @query-sev-attestation-report:
@@ -332,4 +332,4 @@
 ##
 { 'command': 'query-sev-attestation-report', 'data': { 'mnonce': 'str' },
   'returns': 'SevAttestationReport',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
diff --git a/qapi/qom.json b/qapi/qom.json
index 652be317b8..81f991c519 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -613,7 +613,7 @@
   'data': { '*align': 'size',
             '*discard-data': 'bool',
             'mem-path': 'str',
-            '*pmem': { 'type': 'bool', 'if': 'defined(CONFIG_LIBPMEM)' },
+            '*pmem': { 'type': 'bool', 'if': 'CONFIG_LIBPMEM' },
             '*readonly': 'bool' } }
 
 ##
@@ -777,7 +777,7 @@
     'cryptodev-backend',
     'cryptodev-backend-builtin',
     { 'name': 'cryptodev-vhost-user',
-      'if': 'defined(CONFIG_VHOST_CRYPTO)' },
+      'if': 'CONFIG_VHOST_CRYPTO' },
     'dbus-vmstate',
     'filter-buffer',
     'filter-dump',
@@ -790,7 +790,7 @@
     'iothread',
     'memory-backend-file',
     { 'name': 'memory-backend-memfd',
-      'if': 'defined(CONFIG_LINUX)' },
+      'if': 'CONFIG_LINUX' },
     'memory-backend-ram',
     'pef-guest',
     'pr-manager-helper',
@@ -835,7 +835,7 @@
       'cryptodev-backend':          'CryptodevBackendProperties',
       'cryptodev-backend-builtin':  'CryptodevBackendProperties',
       'cryptodev-vhost-user':       { 'type': 'CryptodevVhostUserProperties',
-                                      'if': 'defined(CONFIG_VHOST_CRYPTO)' },
+                                      'if': 'CONFIG_VHOST_CRYPTO' },
       'dbus-vmstate':               'DBusVMStateProperties',
       'filter-buffer':              'FilterBufferProperties',
       'filter-dump':                'FilterDumpProperties',
@@ -848,7 +848,7 @@
       'iothread':                   'IothreadProperties',
       'memory-backend-file':        'MemoryBackendFileProperties',
       'memory-backend-memfd':       { 'type': 'MemoryBackendMemfdProperties',
-                                      'if': 'defined(CONFIG_LINUX)' },
+                                      'if': 'CONFIG_LINUX' },
       'memory-backend-ram':         'MemoryBackendProperties',
       'pr-manager-helper':          'PrManagerHelperProperties',
       'qtest':                      'QtestProperties',
diff --git a/qapi/sockets.json b/qapi/sockets.json
index 735eb4abb5..7866dc27d6 100644
--- a/qapi/sockets.json
+++ b/qapi/sockets.json
@@ -69,7 +69,7 @@
     '*ipv4': 'bool',
     '*ipv6': 'bool',
     '*keep-alive': 'bool',
-    '*mptcp': { 'type': 'bool', 'if': 'defined(IPPROTO_MPTCP)' } } }
+    '*mptcp': { 'type': 'bool', 'if': 'IPPROTO_MPTCP' } } }
 
 ##
 # @UnixSocketAddress:
@@ -89,8 +89,8 @@
 { 'struct': 'UnixSocketAddress',
   'data': {
     'path': 'str',
-    '*abstract': { 'type': 'bool', 'if': 'defined(CONFIG_LINUX)' },
-    '*tight': { 'type': 'bool', 'if': 'defined(CONFIG_LINUX)' } } }
+    '*abstract': { 'type': 'bool', 'if': 'CONFIG_LINUX' },
+    '*tight': { 'type': 'bool', 'if': 'CONFIG_LINUX' } } }
 
 ##
 # @VsockSocketAddress:
diff --git a/qapi/tpm.json b/qapi/tpm.json
index 75590979fd..f4dde2f646 100644
--- a/qapi/tpm.json
+++ b/qapi/tpm.json
@@ -18,7 +18,7 @@
 # Since: 1.5
 ##
 { 'enum': 'TpmModel', 'data': [ 'tpm-tis', 'tpm-crb', 'tpm-spapr' ],
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @query-tpm-models:
@@ -36,7 +36,7 @@
 #
 ##
 { 'command': 'query-tpm-models', 'returns': ['TpmModel'],
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @TpmType:
@@ -50,7 +50,7 @@
 # Since: 1.5
 ##
 { 'enum': 'TpmType', 'data': [ 'passthrough', 'emulator' ],
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @query-tpm-types:
@@ -68,7 +68,7 @@
 #
 ##
 { 'command': 'query-tpm-types', 'returns': ['TpmType'],
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @TPMPassthroughOptions:
@@ -85,7 +85,7 @@
 { 'struct': 'TPMPassthroughOptions',
   'data': { '*path': 'str',
             '*cancel-path': 'str' },
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @TPMEmulatorOptions:
@@ -97,7 +97,7 @@
 # Since: 2.11
 ##
 { 'struct': 'TPMEmulatorOptions', 'data': { 'chardev' : 'str' },
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @TpmTypeOptions:
@@ -112,7 +112,7 @@
 { 'union': 'TpmTypeOptions',
    'data': { 'passthrough' : 'TPMPassthroughOptions',
              'emulator': 'TPMEmulatorOptions' },
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @TPMInfo:
@@ -131,7 +131,7 @@
   'data': {'id': 'str',
            'model': 'TpmModel',
            'options': 'TpmTypeOptions' },
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
 
 ##
 # @query-tpm:
@@ -162,4 +162,4 @@
 #
 ##
 { 'command': 'query-tpm', 'returns': ['TPMInfo'],
-  'if': 'defined(CONFIG_TPM)' }
+  'if': 'CONFIG_TPM' }
diff --git a/qapi/ui.json b/qapi/ui.json
index 1052ca9c38..ec29a66c9f 100644
--- a/qapi/ui.json
+++ b/qapi/ui.json
@@ -123,7 +123,7 @@
   'data': { 'host': 'str',
             'port': 'str',
             'family': 'NetworkAddressFamily' },
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SpiceServerInfo:
@@ -137,7 +137,7 @@
 { 'struct': 'SpiceServerInfo',
   'base': 'SpiceBasicInfo',
   'data': { '*auth': 'str' },
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SpiceChannel:
@@ -163,7 +163,7 @@
   'base': 'SpiceBasicInfo',
   'data': {'connection-id': 'int', 'channel-type': 'int', 'channel-id': 'int',
            'tls': 'bool'},
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SpiceQueryMouseMode:
@@ -183,7 +183,7 @@
 ##
 { 'enum': 'SpiceQueryMouseMode',
   'data': [ 'client', 'server', 'unknown' ],
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SpiceInfo:
@@ -222,7 +222,7 @@
   'data': {'enabled': 'bool', 'migrated': 'bool', '*host': 'str', '*port': 'int',
            '*tls-port': 'int', '*auth': 'str', '*compiled-version': 'str',
            'mouse-mode': 'SpiceQueryMouseMode', '*channels': ['SpiceChannel']},
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @query-spice:
@@ -268,7 +268,7 @@
 #
 ##
 { 'command': 'query-spice', 'returns': 'SpiceInfo',
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SPICE_CONNECTED:
@@ -294,7 +294,7 @@
 { 'event': 'SPICE_CONNECTED',
   'data': { 'server': 'SpiceBasicInfo',
             'client': 'SpiceBasicInfo' },
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SPICE_INITIALIZED:
@@ -323,7 +323,7 @@
 { 'event': 'SPICE_INITIALIZED',
   'data': { 'server': 'SpiceServerInfo',
             'client': 'SpiceChannel' },
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SPICE_DISCONNECTED:
@@ -349,7 +349,7 @@
 { 'event': 'SPICE_DISCONNECTED',
   'data': { 'server': 'SpiceBasicInfo',
             'client': 'SpiceBasicInfo' },
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # @SPICE_MIGRATE_COMPLETED:
@@ -365,7 +365,7 @@
 #
 ##
 { 'event': 'SPICE_MIGRATE_COMPLETED',
-  'if': 'defined(CONFIG_SPICE)' }
+  'if': 'CONFIG_SPICE' }
 
 ##
 # == VNC
@@ -393,7 +393,7 @@
             'service': 'str',
             'family': 'NetworkAddressFamily',
             'websocket': 'bool' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VncServerInfo:
@@ -408,7 +408,7 @@
 { 'struct': 'VncServerInfo',
   'base': 'VncBasicInfo',
   'data': { '*auth': 'str' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VncClientInfo:
@@ -426,7 +426,7 @@
 { 'struct': 'VncClientInfo',
   'base': 'VncBasicInfo',
   'data': { '*x509_dname': 'str', '*sasl_username': 'str' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VncInfo:
@@ -469,7 +469,7 @@
   'data': {'enabled': 'bool', '*host': 'str',
            '*family': 'NetworkAddressFamily',
            '*service': 'str', '*auth': 'str', '*clients': ['VncClientInfo']},
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VncPrimaryAuth:
@@ -481,7 +481,7 @@
 { 'enum': 'VncPrimaryAuth',
   'data': [ 'none', 'vnc', 'ra2', 'ra2ne', 'tight', 'ultra',
             'tls', 'vencrypt', 'sasl' ],
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VncVencryptSubAuth:
@@ -496,7 +496,7 @@
             'tls-vnc',   'x509-vnc',
             'tls-plain', 'x509-plain',
             'tls-sasl',  'x509-sasl' ],
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VncServerInfo2:
@@ -514,7 +514,7 @@
   'base': 'VncBasicInfo',
   'data': { 'auth'      : 'VncPrimaryAuth',
             '*vencrypt' : 'VncVencryptSubAuth' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VncInfo2:
@@ -547,7 +547,7 @@
             'auth'      : 'VncPrimaryAuth',
             '*vencrypt' : 'VncVencryptSubAuth',
             '*display'  : 'str' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @query-vnc:
@@ -579,7 +579,7 @@
 #
 ##
 { 'command': 'query-vnc', 'returns': 'VncInfo',
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 ##
 # @query-vnc-servers:
 #
@@ -590,7 +590,7 @@
 # Since: 2.3
 ##
 { 'command': 'query-vnc-servers', 'returns': ['VncInfo2'],
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @change-vnc-password:
@@ -606,7 +606,7 @@
 ##
 { 'command': 'change-vnc-password',
   'data': { 'password': 'str' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VNC_CONNECTED:
@@ -636,7 +636,7 @@
 { 'event': 'VNC_CONNECTED',
   'data': { 'server': 'VncServerInfo',
             'client': 'VncBasicInfo' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VNC_INITIALIZED:
@@ -664,7 +664,7 @@
 { 'event': 'VNC_INITIALIZED',
   'data': { 'server': 'VncServerInfo',
             'client': 'VncClientInfo' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # @VNC_DISCONNECTED:
@@ -691,7 +691,7 @@
 { 'event': 'VNC_DISCONNECTED',
   'data': { 'server': 'VncServerInfo',
             'client': 'VncClientInfo' },
-  'if': 'defined(CONFIG_VNC)' }
+  'if': 'CONFIG_VNC' }
 
 ##
 # = Input
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index fb17eebde3..c60f5e669d 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -1380,7 +1380,7 @@
   'data': {
       'keys': ['str']
   },
-  'if': 'defined(CONFIG_POSIX)' }
+  'if': 'CONFIG_POSIX' }
 
 
 ##
@@ -1398,7 +1398,7 @@
 { 'command': 'guest-ssh-get-authorized-keys',
   'data': { 'username': 'str' },
   'returns': 'GuestAuthorizedKeys',
-  'if': 'defined(CONFIG_POSIX)' }
+  'if': 'CONFIG_POSIX' }
 
 ##
 # @guest-ssh-add-authorized-keys:
@@ -1416,7 +1416,7 @@
 ##
 { 'command': 'guest-ssh-add-authorized-keys',
   'data': { 'username': 'str', 'keys': ['str'], '*reset': 'bool' },
-  'if': 'defined(CONFIG_POSIX)' }
+  'if': 'CONFIG_POSIX' }
 
 ##
 # @guest-ssh-remove-authorized-keys:
@@ -1434,4 +1434,4 @@
 ##
 { 'command': 'guest-ssh-remove-authorized-keys',
   'data': { 'username': 'str', 'keys': ['str'] },
-  'if': 'defined(CONFIG_POSIX)' }
+  'if': 'CONFIG_POSIX' }
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index f8718e201b..0c718e43c9 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -218,7 +218,7 @@ def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
     if not ifcond:
         return ''
     if isinstance(ifcond, str):
-        return ifcond
+        return 'defined(' + ifcond + ')'
 
     oper, operands = next(iter(ifcond.items()))
     if oper == 'not':
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index d2bd52c49f..d355cbc8c1 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -281,10 +281,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
 
     def _check_if(cond: Union[str, object]) -> None:
         if isinstance(cond, str):
-            if not cond.strip():
+            if not cond.isidentifier():
                 raise QAPISemError(
                     info,
-                    "'if' condition '%s' of %s makes no sense"
+                    "'if' condition '%s' of %s is not a valid identifier"
                     % (cond, source))
             return
 
diff --git a/tests/qapi-schema/alternate-branch-if-invalid.err b/tests/qapi-schema/alternate-branch-if-invalid.err
index d384929c51..03bad877a3 100644
--- a/tests/qapi-schema/alternate-branch-if-invalid.err
+++ b/tests/qapi-schema/alternate-branch-if-invalid.err
@@ -1,2 +1,2 @@
 alternate-branch-if-invalid.json: In alternate 'Alt':
-alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' makes no sense
+alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' is not a valid identifier
diff --git a/tests/qapi-schema/bad-if-empty.err b/tests/qapi-schema/bad-if-empty.err
index a0f3effefb..5208f543ce 100644
--- a/tests/qapi-schema/bad-if-empty.err
+++ b/tests/qapi-schema/bad-if-empty.err
@@ -1,2 +1,2 @@
 bad-if-empty.json: In struct 'TestIfStruct':
-bad-if-empty.json:2: 'if' condition '' of struct makes no sense
+bad-if-empty.json:2: 'if' condition '' of struct is not a valid identifier
diff --git a/tests/qapi-schema/bad-if-list.err b/tests/qapi-schema/bad-if-list.err
index c462f11b90..fa01894d03 100644
--- a/tests/qapi-schema/bad-if-list.err
+++ b/tests/qapi-schema/bad-if-list.err
@@ -1,2 +1,2 @@
 bad-if-list.json: In struct 'TestIfStruct':
-bad-if-list.json:2: 'if' condition ' ' of struct makes no sense
+bad-if-list.json:2: 'if' condition ' ' of struct is not a valid identifier
diff --git a/tests/qapi-schema/bad-if.json b/tests/qapi-schema/bad-if.json
index 3edd1a0bf2..67818888de 100644
--- a/tests/qapi-schema/bad-if.json
+++ b/tests/qapi-schema/bad-if.json
@@ -1,3 +1,3 @@
 # check invalid 'if' type
 { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
-  'if': { 'value': 'defined(TEST_IF_STRUCT)' } }
+  'if': { 'value': 'TEST_IF_STRUCT' } }
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
index 836fb1bdc4..333a7f3dcf 100644
--- a/tests/qapi-schema/doc-good.json
+++ b/tests/qapi-schema/doc-good.json
@@ -61,9 +61,9 @@
 # @two is undocumented
 ##
 { 'enum': 'Enum', 'data':
-  [ { 'name': 'one', 'if': 'defined(IFONE)' }, 'two' ],
+  [ { 'name': 'one', 'if': 'IFONE' }, 'two' ],
   'features': [ 'enum-feat' ],
-  'if': 'defined(IFCOND)' }
+  'if': 'IFCOND' }
 
 ##
 # @Base:
@@ -87,7 +87,7 @@
   'features': [ 'variant1-feat' ],
   'data': { 'var1': { 'type': 'str',
                       'features': [ 'member-feat' ],
-                      'if': 'defined(IFSTR)' } } }
+                      'if': 'IFSTR' } } }
 
 ##
 # @Variant2:
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index a8871e8f99..26d1fa5d28 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -12,16 +12,16 @@ enum QType
 module doc-good.json
 enum Enum
     member one
-        if defined(IFONE)
+        if IFONE
     member two
-    if defined(IFCOND)
+    if IFCOND
     feature enum-feat
 object Base
     member base1: Enum optional=False
     if OrderedDict([('all', ['IFALL1', 'IFALL2'])])
 object Variant1
     member var1: str optional=False
-        if defined(IFSTR)
+        if IFSTR
         feature member-feat
     feature variant1-feat
 object Variant2
diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
index 03c98c4182..5bfe06e14e 100644
--- a/tests/qapi-schema/doc-good.txt
+++ b/tests/qapi-schema/doc-good.txt
@@ -43,7 +43,7 @@ Example:
 Values
 ~~~~~~
 
-"one" (**If: **"defined(IFONE)")
+"one" (**If: **"IFONE")
    The _one_ {and only}
 
 "two"
@@ -62,7 +62,7 @@ Features
 If
 ~~
 
-"defined(IFCOND)"
+"IFCOND"
 
 
 "Base" (Object)
@@ -93,7 +93,7 @@ Another paragraph (but no "var": line)
 Members
 ~~~~~~~
 
-"var1": "string" (**If: **"defined(IFSTR)")
+"var1": "string" (**If: **"IFSTR")
    Not documented
 
 
diff --git a/tests/qapi-schema/features-missing-name.json b/tests/qapi-schema/features-missing-name.json
index 2314f97c00..8772c8f7b3 100644
--- a/tests/qapi-schema/features-missing-name.json
+++ b/tests/qapi-schema/features-missing-name.json
@@ -1,3 +1,3 @@
 { 'struct': 'FeatureStruct0',
   'data': { 'foo': 'int' },
-  'features': [ { 'if': 'defined(NAMELESS_FEATURES)' } ] }
+  'features': [ { 'if': 'NAMELESS_FEATURES' } ] }
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index a0f09b277e..38b2b29d47 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -222,43 +222,43 @@
 
 { 'struct': 'TestIfStruct', 'data':
   { 'foo': 'int',
-    'bar': { 'type': 'int', 'if': 'defined(TEST_IF_STRUCT_BAR)'} },
-  'if': 'defined(TEST_IF_STRUCT)' }
+    'bar': { 'type': 'int', 'if': 'TEST_IF_STRUCT_BAR'} },
+  'if': 'TEST_IF_STRUCT' }
 
 { 'enum': 'TestIfEnum', 'data':
-  [ 'foo', { 'name' : 'bar', 'if': 'defined(TEST_IF_ENUM_BAR)' } ],
-  'if': 'defined(TEST_IF_ENUM)' }
+  [ 'foo', { 'name' : 'bar', 'if': 'TEST_IF_ENUM_BAR' } ],
+  'if': 'TEST_IF_ENUM' }
 
 { 'union': 'TestIfUnion', 'data':
   { 'foo': 'TestStruct',
-    'union-bar': { 'type': 'str', 'if': 'defined(TEST_IF_UNION_BAR)'} },
-  'if': { 'all': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] } }
+    'union-bar': { 'type': 'str', 'if': 'TEST_IF_UNION_BAR'} },
+  'if': { 'all': ['TEST_IF_UNION', 'TEST_IF_STRUCT'] } }
 
 { 'command': 'test-if-union-cmd',
   'data': { 'union-cmd-arg': 'TestIfUnion' },
-  'if': 'defined(TEST_IF_UNION)' }
+  'if': 'TEST_IF_UNION' }
 
 { 'alternate': 'TestIfAlternate', 'data':
   { 'foo': 'int',
-    'bar': { 'type': 'TestStruct', 'if': 'defined(TEST_IF_ALT_BAR)'} },
-  'if': { 'all': ['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'] } }
+    'bar': { 'type': 'TestStruct', 'if': 'TEST_IF_ALT_BAR'} },
+  'if': { 'all': ['TEST_IF_ALT', 'TEST_IF_STRUCT'] } }
 
 { 'command': 'test-if-alternate-cmd', 'data': { 'alt-cmd-arg': 'TestIfAlternate' },
-  'if': { 'all': ['defined(TEST_IF_ALT)', {'not': 'defined(TEST_IF_NOT_ALT)'}] } }
+  'if': { 'all': ['TEST_IF_ALT', {'not': 'TEST_IF_NOT_ALT'}] } }
 
 { 'command': 'test-if-cmd',
   'data': {
     'foo': 'TestIfStruct',
-    'bar': { 'type': 'TestIfEnum', 'if': 'defined(TEST_IF_CMD_BAR)' } },
+    'bar': { 'type': 'TestIfEnum', 'if': 'TEST_IF_CMD_BAR' } },
   'returns': 'UserDefThree',
-  'if': { 'all': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] } }
+  'if': { 'all': ['TEST_IF_CMD', 'TEST_IF_STRUCT'] } }
 
 { 'command': 'test-cmd-return-def-three', 'returns': 'UserDefThree' }
 
 { 'event': 'TEST_IF_EVENT', 'data':
   { 'foo': 'TestIfStruct',
-    'bar': { 'type': ['TestIfEnum'], 'if': 'defined(TEST_IF_EVT_BAR)' } },
-  'if': { 'all': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] } }
+    'bar': { 'type': ['TestIfEnum'], 'if': 'TEST_IF_EVT_BAR' } },
+  'if': { 'all': ['TEST_IF_EVT', 'TEST_IF_STRUCT'] } }
 
 # test 'features'
 
@@ -280,20 +280,20 @@
 
 { 'struct': 'CondFeatureStruct1',
   'data': { 'foo': 'int' },
-  'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'} ] }
+  'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'} ] }
 { 'struct': 'CondFeatureStruct2',
   'data': { 'foo': 'int' },
-  'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'},
-                { 'name': 'feature2', 'if': 'defined(TEST_IF_FEATURE_2)'} ] }
+  'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'},
+                { 'name': 'feature2', 'if': 'TEST_IF_FEATURE_2'} ] }
 { 'struct': 'CondFeatureStruct3',
   'data': { 'foo': 'int' },
   'features': [ { 'name': 'feature1',
-                  'if': { 'all': [ 'defined(TEST_IF_COND_1)',
-                                   'defined(TEST_IF_COND_2)'] } } ] }
+                  'if': { 'all': [ 'TEST_IF_COND_1',
+                                   'TEST_IF_COND_2'] } } ] }
 { 'struct': 'CondFeatureStruct4',
   'data': { 'foo': 'int' },
-  'features': [ { 'name': 'feature1', 'if': {'any': ['defined(TEST_IF_COND_1)',
-                                                     'defined(TEST_IF_COND_2)'] } } ] }
+  'features': [ { 'name': 'feature1', 'if': {'any': ['TEST_IF_COND_1',
+                                                     'TEST_IF_COND_2'] } } ] }
 
 { 'enum': 'FeatureEnum1',
   'data': [ 'eins', 'zwei', 'drei' ],
@@ -328,14 +328,14 @@
   'features': [ 'feature1', 'feature2' ] }
 
 { 'command': 'test-command-cond-features1',
-  'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'} ] }
+  'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'} ] }
 { 'command': 'test-command-cond-features2',
-  'features': [ { 'name': 'feature1', 'if': 'defined(TEST_IF_FEATURE_1)'},
-                { 'name': 'feature2', 'if': 'defined(TEST_IF_FEATURE_2)'} ] }
+  'features': [ { 'name': 'feature1', 'if': 'TEST_IF_FEATURE_1'},
+                { 'name': 'feature2', 'if': 'TEST_IF_FEATURE_2'} ] }
 { 'command': 'test-command-cond-features3',
   'features': [ { 'name': 'feature1',
-                  'if': { 'all': [ 'defined(TEST_IF_COND_1)',
-                                   'defined(TEST_IF_COND_2)'] } } ] }
+                  'if': { 'all': [ 'TEST_IF_COND_1',
+                                   'TEST_IF_COND_2'] } } ] }
 
 { 'event': 'TEST_EVENT_FEATURES0',
   'data': 'FeatureStruct1' }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index df2c57de54..00cfa67809 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -298,65 +298,65 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio
 object TestIfStruct
     member foo: int optional=False
     member bar: int optional=False
-        if defined(TEST_IF_STRUCT_BAR)
-    if defined(TEST_IF_STRUCT)
+        if TEST_IF_STRUCT_BAR
+    if TEST_IF_STRUCT
 enum TestIfEnum
     member foo
     member bar
-        if defined(TEST_IF_ENUM_BAR)
-    if defined(TEST_IF_ENUM)
+        if TEST_IF_ENUM_BAR
+    if TEST_IF_ENUM
 object q_obj_TestStruct-wrapper
     member data: TestStruct optional=False
 enum TestIfUnionKind
     member foo
     member union-bar
-        if defined(TEST_IF_UNION_BAR)
-    if OrderedDict([('all', ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])])
+        if TEST_IF_UNION_BAR
+    if OrderedDict([('all', ['TEST_IF_UNION', 'TEST_IF_STRUCT'])])
 object TestIfUnion
     member type: TestIfUnionKind optional=False
     tag type
     case foo: q_obj_TestStruct-wrapper
     case union-bar: q_obj_str-wrapper
-        if defined(TEST_IF_UNION_BAR)
-    if OrderedDict([('all', ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])])
+        if TEST_IF_UNION_BAR
+    if OrderedDict([('all', ['TEST_IF_UNION', 'TEST_IF_STRUCT'])])
 object q_obj_test-if-union-cmd-arg
     member union-cmd-arg: TestIfUnion optional=False
-    if defined(TEST_IF_UNION)
+    if TEST_IF_UNION
 command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if defined(TEST_IF_UNION)
+    if TEST_IF_UNION
 alternate TestIfAlternate
     tag type
     case foo: int
     case bar: TestStruct
-        if defined(TEST_IF_ALT_BAR)
-    if OrderedDict([('all', ['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])])
+        if TEST_IF_ALT_BAR
+    if OrderedDict([('all', ['TEST_IF_ALT', 'TEST_IF_STRUCT'])])
 object q_obj_test-if-alternate-cmd-arg
     member alt-cmd-arg: TestIfAlternate optional=False
-    if OrderedDict([('all', ['defined(TEST_IF_ALT)', OrderedDict([('not', 'defined(TEST_IF_NOT_ALT)')])])])
+    if OrderedDict([('all', ['TEST_IF_ALT', OrderedDict([('not', 'TEST_IF_NOT_ALT')])])])
 command test-if-alternate-cmd q_obj_test-if-alternate-cmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if OrderedDict([('all', ['defined(TEST_IF_ALT)', OrderedDict([('not', 'defined(TEST_IF_NOT_ALT)')])])])
+    if OrderedDict([('all', ['TEST_IF_ALT', OrderedDict([('not', 'TEST_IF_NOT_ALT')])])])
 object q_obj_test-if-cmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-        if defined(TEST_IF_CMD_BAR)
-    if OrderedDict([('all', ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])])
+        if TEST_IF_CMD_BAR
+    if OrderedDict([('all', ['TEST_IF_CMD', 'TEST_IF_STRUCT'])])
 command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if OrderedDict([('all', ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])])
+    if OrderedDict([('all', ['TEST_IF_CMD', 'TEST_IF_STRUCT'])])
 command test-cmd-return-def-three None -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
 array TestIfEnumList TestIfEnum
-    if defined(TEST_IF_ENUM)
+    if TEST_IF_ENUM
 object q_obj_TEST_IF_EVENT-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnumList optional=False
-        if defined(TEST_IF_EVT_BAR)
-    if OrderedDict([('all', ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])])
+        if TEST_IF_EVT_BAR
+    if OrderedDict([('all', ['TEST_IF_EVT', 'TEST_IF_STRUCT'])])
 event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
     boxed=False
-    if OrderedDict([('all', ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])])
+    if OrderedDict([('all', ['TEST_IF_EVT', 'TEST_IF_STRUCT'])])
 object FeatureStruct0
     member foo: int optional=False
 object FeatureStruct1
@@ -379,21 +379,21 @@ object FeatureStruct4
 object CondFeatureStruct1
     member foo: int optional=False
     feature feature1
-        if defined(TEST_IF_FEATURE_1)
+        if TEST_IF_FEATURE_1
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if defined(TEST_IF_FEATURE_1)
+        if TEST_IF_FEATURE_1
     feature feature2
-        if defined(TEST_IF_FEATURE_2)
+        if TEST_IF_FEATURE_2
 object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
-        if OrderedDict([('all', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
+        if OrderedDict([('all', ['TEST_IF_COND_1', 'TEST_IF_COND_2'])])
 object CondFeatureStruct4
     member foo: int optional=False
     feature feature1
-        if OrderedDict([('any', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
+        if OrderedDict([('any', ['TEST_IF_COND_1', 'TEST_IF_COND_2'])])
 enum FeatureEnum1
     member eins
     member zwei
@@ -434,17 +434,17 @@ command test-command-features3 None -> None
 command test-command-cond-features1 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if defined(TEST_IF_FEATURE_1)
+        if TEST_IF_FEATURE_1
 command test-command-cond-features2 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if defined(TEST_IF_FEATURE_1)
+        if TEST_IF_FEATURE_1
     feature feature2
-        if defined(TEST_IF_FEATURE_2)
+        if TEST_IF_FEATURE_2
 command test-command-cond-features3 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if OrderedDict([('all', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
+        if OrderedDict([('all', ['TEST_IF_COND_1', 'TEST_IF_COND_2'])])
 event TEST_EVENT_FEATURES0 FeatureStruct1
     boxed=False
 event TEST_EVENT_FEATURES1 None
diff --git a/tests/qapi-schema/union-branch-if-invalid.err b/tests/qapi-schema/union-branch-if-invalid.err
index dd4518233e..046187a5b9 100644
--- a/tests/qapi-schema/union-branch-if-invalid.err
+++ b/tests/qapi-schema/union-branch-if-invalid.err
@@ -1,2 +1,2 @@
 union-branch-if-invalid.json: In union 'Uni':
-union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' makes no sense
+union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' is not a valid identifier
-- 
2.29.0



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 01/11] docs: update the documentation upfront about schema configuration
  2021-06-18 10:24 ` [PATCH v6 01/11] docs: update the documentation upfront about schema configuration marcandre.lureau
@ 2021-07-12 14:07   ` Markus Armbruster
  0 siblings, 0 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-07-12 14:07 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Update the documentation describing the changes in this series.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Tested-by: John Snow <jsnow@redhat.com>

Reviewed-by: Markus Armbruster <armbru@redhat.com>



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 02/11] qapi: wrap Sequence[str] in an object
  2021-06-18 10:24 ` [PATCH v6 02/11] qapi: wrap Sequence[str] in an object marcandre.lureau
@ 2021-08-02  9:21   ` Markus Armbruster
  2021-08-03 17:55     ` John Snow
  2021-08-04  8:22     ` Marc-André Lureau
  0 siblings, 2 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-02  9:21 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Except for the special casing assert in _make_implicit_object_type(),
> which needs to handle schema objects, it's a mechanical change.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  docs/sphinx/qapidoc.py         | 10 +++---
>  scripts/qapi/commands.py       |  4 +--
>  scripts/qapi/events.py         |  5 +--
>  scripts/qapi/gen.py            | 14 ++++----
>  scripts/qapi/introspect.py     | 26 +++++++-------
>  scripts/qapi/schema.py         | 66 +++++++++++++++++++++-------------
>  scripts/qapi/types.py          | 33 ++++++++---------
>  scripts/qapi/visit.py          | 23 ++++++------
>  tests/qapi-schema/test-qapi.py |  4 +--
>  9 files changed, 103 insertions(+), 82 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 87c67ab23f..0eac3308b2 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -116,7 +116,7 @@ def _nodes_for_ifcond(self, ifcond, with_if=True):
>          the conditions are in literal-text and the commas are not.
>          If with_if is False, we don't return the "(If: " and ")".
>          """
> -        condlist = intersperse([nodes.literal('', c) for c in ifcond],
> +        condlist = intersperse([nodes.literal('', c) for c in ifcond.ifcond],

Mechanical pattern #1: ifcond becomes ifcond.ifcond to peel off the new
wrapper.  ifcond.ifcond is ugly, but almost all instances go away in
this series.  I'm okay with the remainder.

>                                 nodes.Text(', '))
>          if not with_if:
>              return condlist
> @@ -139,7 +139,7 @@ def _nodes_for_one_member(self, member):
>              term.append(nodes.literal('', member.type.doc_type()))
>          if member.optional:
>              term.append(nodes.Text(' (optional)'))
> -        if member.ifcond:
> +        if member.ifcond.ifcond:
>              term.extend(self._nodes_for_ifcond(member.ifcond))
>          return term
>  
> @@ -154,7 +154,7 @@ def _nodes_for_variant_when(self, variants, variant):
>                  nodes.literal('', variants.tag_member.name),
>                  nodes.Text(' is '),
>                  nodes.literal('', '"%s"' % variant.name)]
> -        if variant.ifcond:
> +        if variant.ifcond.ifcond:
>              term.extend(self._nodes_for_ifcond(variant.ifcond))
>          return term
>  
> @@ -209,7 +209,7 @@ def _nodes_for_enum_values(self, doc):
>          dlnode = nodes.definition_list()
>          for section in doc.args.values():
>              termtext = [nodes.literal('', section.member.name)]
> -            if section.member.ifcond:
> +            if section.member.ifcond.ifcond:
>                  termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
>              # TODO drop fallbacks when undocumented members are outlawed
>              if section.text:
> @@ -277,7 +277,7 @@ def _nodes_for_sections(self, doc):
>      def _nodes_for_if_section(self, ifcond):
>          """Return list of doctree nodes for the "If" section"""
>          nodelist = []
> -        if ifcond:
> +        if ifcond.ifcond:
>              snode = self._make_section('If')
>              snode += nodes.paragraph(
>                  '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
> diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
> index 0e13d51054..3654825968 100644
> --- a/scripts/qapi/commands.py
> +++ b/scripts/qapi/commands.py
> @@ -17,7 +17,6 @@
>      Dict,
>      List,
>      Optional,
> -    Sequence,
>      Set,
>  )
>  
> @@ -31,6 +30,7 @@
>  from .schema import (
>      QAPISchema,
>      QAPISchemaFeature,
> +    QAPISchemaIfCond,
>      QAPISchemaObjectType,
>      QAPISchemaType,
>  )
> @@ -301,7 +301,7 @@ def visit_end(self) -> None:
>      def visit_command(self,
>                        name: str,
>                        info: Optional[QAPISourceInfo],
> -                      ifcond: Sequence[str],
> +                      ifcond: QAPISchemaIfCond,
>                        features: List[QAPISchemaFeature],
>                        arg_type: Optional[QAPISchemaObjectType],
>                        ret_type: Optional[QAPISchemaType],

Mechanical pattern #2: Sequence[str] becomes QAPISchemaIfCond.  Also
obvious import adjustments.

> diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
> index fee8c671e7..82475e84ec 100644
> --- a/scripts/qapi/events.py
> +++ b/scripts/qapi/events.py
> @@ -12,7 +12,7 @@
>  See the COPYING file in the top-level directory.
>  """
>  
> -from typing import List, Optional, Sequence
> +from typing import List, Optional
>  
>  from .common import c_enum_const, c_name, mcgen
>  from .gen import QAPISchemaModularCVisitor, build_params, ifcontext
> @@ -20,6 +20,7 @@
>      QAPISchema,
>      QAPISchemaEnumMember,
>      QAPISchemaFeature,
> +    QAPISchemaIfCond,
>      QAPISchemaObjectType,
>  )
>  from .source import QAPISourceInfo
> @@ -227,7 +228,7 @@ def visit_end(self) -> None:
>      def visit_event(self,
>                      name: str,
>                      info: Optional[QAPISourceInfo],
> -                    ifcond: Sequence[str],
> +                    ifcond: QAPISchemaIfCond,
>                      features: List[QAPISchemaFeature],
>                      arg_type: Optional[QAPISchemaObjectType],
>                      boxed: bool) -> None:
> diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
> index 1fa503bdbd..1c5b190276 100644
> --- a/scripts/qapi/gen.py
> +++ b/scripts/qapi/gen.py
> @@ -18,7 +18,6 @@
>      Dict,
>      Iterator,
>      Optional,
> -    Sequence,
>      Tuple,
>  )
>  
> @@ -32,6 +31,7 @@
>      mcgen,
>  )
>  from .schema import (
> +    QAPISchemaIfCond,
>      QAPISchemaModule,
>      QAPISchemaObjectType,
>      QAPISchemaVisitor,
> @@ -85,7 +85,7 @@ def write(self, output_dir: str) -> None:
>                  fp.write(text)
>  
>  
> -def _wrap_ifcond(ifcond: Sequence[str], before: str, after: str) -> str:
> +def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
>      if before == after:
>          return after   # suppress empty #if ... #endif
>  
> @@ -95,9 +95,9 @@ def _wrap_ifcond(ifcond: Sequence[str], before: str, after: str) -> str:
>      if added[0] == '\n':
>          out += '\n'
>          added = added[1:]
> -    out += gen_if(ifcond)
> +    out += gen_if(ifcond.ifcond)
>      out += added
> -    out += gen_endif(ifcond)
> +    out += gen_endif(ifcond.ifcond)
>      return out
>  
>  
> @@ -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[Sequence[str], str, str]] = None
> +        self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] = None
>  
> -    def start_if(self, ifcond: Sequence[str]) -> None:
> +    def start_if(self, ifcond: QAPISchemaIfCond) -> None:
>          assert self._start_if is None
>          self._start_if = (ifcond, self._body, self._preamble)
>  
> @@ -187,7 +187,7 @@ def _bottom(self) -> str:
>  
>  
>  @contextmanager
> -def ifcontext(ifcond: Sequence[str], *args: QAPIGenCCode) -> Iterator[None]:
> +def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) -> Iterator[None]:
>      """
>      A with-statement context manager that wraps with `start_if()` / `end_if()`.
>  
> diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
> index 9a348ca2e5..77a8c33ad4 100644
> --- a/scripts/qapi/introspect.py
> +++ b/scripts/qapi/introspect.py
> @@ -15,11 +15,9 @@
>      Any,
>      Dict,
>      Generic,
> -    Iterable,
>      List,
>      Optional,
>      Sequence,
> -    Tuple,
>      TypeVar,
>      Union,
>  )
> @@ -38,6 +36,7 @@
>      QAPISchemaEntity,
>      QAPISchemaEnumMember,
>      QAPISchemaFeature,
> +    QAPISchemaIfCond,
>      QAPISchemaObjectType,
>      QAPISchemaObjectTypeMember,
>      QAPISchemaType,
> @@ -91,11 +90,11 @@ class Annotated(Generic[_ValueT]):
>      """
>      # TODO: Remove after Python 3.7 adds @dataclass:
>      # pylint: disable=too-few-public-methods
> -    def __init__(self, value: _ValueT, ifcond: Iterable[str],
> +    def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond,

Here, we have Iterable[str] instead of Sequence[str] before the patch.
I figure use of Iterable was an attempt to be more general.  Minor
variation of pattern #2.

>                   comment: Optional[str] = None):
>          self.value = value
>          self.comment: Optional[str] = comment
> -        self.ifcond: Tuple[str, ...] = tuple(ifcond)
> +        self.ifcond = ifcond

Hmm.  Here we change from Tuple, not from Sequence.

I believe the next hunk has its only uses:

>  
>  
>  def _tree_to_qlit(obj: JSONValue,
> @@ -125,10 +124,10 @@ def indent(level: int) -> str:
>          if obj.comment:
>              ret += indent(level) + f"/* {obj.comment} */\n"
>          if obj.ifcond:
> -            ret += gen_if(obj.ifcond)
> +            ret += gen_if(obj.ifcond.ifcond)
>          ret += _tree_to_qlit(obj.value, level)
>          if obj.ifcond:
> -            ret += '\n' + gen_endif(obj.ifcond)
> +            ret += '\n' + gen_endif(obj.ifcond.ifcond)
>          return ret
>  
>      ret = ''

You update obj.ifcond to obj.ifcond.ifcond when used as argument of
gen_if() and gen_endif().  This changes the argument from Tuple to
Sequence.  Fine, because Tuple is a special Sequence.

Digression: I don't (anymore) understand why we made self.ifcond Tuple.
John, do you remember?

You don't update obj.ifcond when used as conditional.  The code now
calls gen_if() and gen_endif() even for an empty Sequence.

I believe this can't actually happen because check_if() rejects [] with
"'if' condition [] of %s is useless".

Still, the mechanical change should update to obj.ifcond even when used
as conditional.

Are there other, possibly not so harmless uses of values that change
from Sequence to QAPISchemaIfCond the patch doesn't update?

Or asked differently: how did you find what to update?

> @@ -254,7 +253,7 @@ def _gen_features(features: Sequence[QAPISchemaFeature]
>          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: QAPISchemaIfCond = QAPISchemaIfCond(),

Mechanical pattern #3: wrap QAPISchemaIfCond around the Sequence.

For readability, you simplify the pure wrap QAPISchemaIfCond(()) to just
QAPISchemaIfCond(), relying on QAPISchemaIfCond.__init__()'s default.

However, it's also a subtle change from () to [].  I believe that's
okay, because we both are empty Sequences.  Might be worth a mention in
the commit message.

>                    features: Sequence[QAPISchemaFeature] = ()) -> None:
>          """
>          Build and append a SchemaInfo object to self._trees.
> @@ -305,7 +304,7 @@ def visit_builtin_type(self, name: str, info: Optional[QAPISourceInfo],
>          self._gen_tree(name, 'builtin', {'json-type': json_type})
>  
>      def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo],
> -                        ifcond: Sequence[str],
> +                        ifcond: QAPISchemaIfCond,
>                          features: List[QAPISchemaFeature],
>                          members: List[QAPISchemaEnumMember],
>                          prefix: Optional[str]) -> None:
> @@ -316,14 +315,14 @@ def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo],
>          )
>  
>      def visit_array_type(self, name: str, info: Optional[QAPISourceInfo],
> -                         ifcond: Sequence[str],
> +                         ifcond: QAPISchemaIfCond,
>                           element_type: QAPISchemaType) -> None:
>          element = self._use_type(element_type)
>          self._gen_tree('[' + element + ']', 'array', {'element-type': element},
>                         ifcond)
>  
>      def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo],
> -                               ifcond: Sequence[str],
> +                               ifcond: QAPISchemaIfCond,
>                                 features: List[QAPISchemaFeature],
>                                 members: List[QAPISchemaObjectTypeMember],
>                                 variants: Optional[QAPISchemaVariants]) -> None:
> @@ -336,7 +335,7 @@ def visit_object_type_flat(self, name: str, info: Optional[QAPISourceInfo],
>          self._gen_tree(name, 'object', obj, ifcond, features)
>  
>      def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo],
> -                             ifcond: Sequence[str],
> +                             ifcond: QAPISchemaIfCond,
>                               features: List[QAPISchemaFeature],
>                               variants: QAPISchemaVariants) -> None:
>          self._gen_tree(
> @@ -348,7 +347,7 @@ def visit_alternate_type(self, name: str, info: Optional[QAPISourceInfo],
>          )
>  
>      def visit_command(self, name: str, info: Optional[QAPISourceInfo],
> -                      ifcond: Sequence[str],
> +                      ifcond: QAPISchemaIfCond,
>                        features: List[QAPISchemaFeature],
>                        arg_type: Optional[QAPISchemaObjectType],
>                        ret_type: Optional[QAPISchemaType], gen: bool,
> @@ -367,7 +366,8 @@ def visit_command(self, name: str, info: Optional[QAPISourceInfo],
>          self._gen_tree(name, 'command', obj, ifcond, features)
>  
>      def visit_event(self, name: str, info: Optional[QAPISourceInfo],
> -                    ifcond: Sequence[str], features: List[QAPISchemaFeature],
> +                    ifcond: QAPISchemaIfCond,
> +                    features: List[QAPISchemaFeature],
>                      arg_type: Optional[QAPISchemaObjectType],
>                      boxed: bool) -> None:
>          assert self._schema is not None
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index d1d27ff7ee..5e44164bd1 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -25,6 +25,11 @@
>  from .parser import QAPISchemaParser
>  
>  
> +class QAPISchemaIfCond:
> +    def __init__(self, ifcond=None):
> +        self.ifcond = ifcond or []
> +
> +
>  class QAPISchemaEntity:
>      meta: Optional[str] = None
>  
> @@ -42,7 +47,7 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
>          # such place).
>          self.info = info
>          self.doc = doc
> -        self._ifcond = ifcond or []
> +        self._ifcond = ifcond or QAPISchemaIfCond()

This is an instance of mechanical pattern #3 where we wrap the value
without changing it.

Before the patch we use both () and [] as "no conditions".  After the
patch, we always use [], unless something passes another empty sequence
to QAPISchemaIfCond.__init__(), which I don't think is the case.
Observation, not a request.


>          self.features = features or []
>          self._checked = False
>  
> @@ -78,6 +83,7 @@ def set_module(self, schema):
>      @property
>      def ifcond(self):
>          assert self._checked
> +        assert isinstance(self._ifcond, QAPISchemaIfCond)
>          return self._ifcond

Non-mechanical hunk.  The commit message claims to list them ("Except
for ..., it's a mechanical change"), but it doesn't.  Easy enough to
fix.

>  
>      def is_implicit(self):
> @@ -593,7 +599,7 @@ def check(self, schema, seen):
>                      self.info,
>                      "discriminator member '%s' of %s must not be optional"
>                      % (self._tag_name, base))
> -            if self.tag_member.ifcond:
> +            if self.tag_member.ifcond.ifcond:
>                  raise QAPISemError(
>                      self.info,
>                      "discriminator member '%s' of %s must not be conditional"
> @@ -601,7 +607,7 @@ def check(self, schema, seen):
>          else:                   # simple union
>              assert isinstance(self.tag_member.type, QAPISchemaEnumType)
>              assert not self.tag_member.optional
> -            assert self.tag_member.ifcond == []
> +            assert self.tag_member.ifcond.ifcond == []
>          if self._tag_name:    # flat union
>              # branches that are not explicitly covered get an empty type
>              cases = {v.name for v in self.variants}
> @@ -646,7 +652,7 @@ def __init__(self, name, info, ifcond=None):
>          assert isinstance(name, str)
>          self.name = name
>          self.info = info
> -        self.ifcond = ifcond or []
> +        self.ifcond = ifcond or QAPISchemaIfCond()
>          self.defined_in = None
>  
>      def set_defined_in(self, name):
> @@ -968,11 +974,13 @@ def _def_predefineds(self):
>      def _make_features(self, features, info):
>          if features is None:
>              return []
> -        return [QAPISchemaFeature(f['name'], info, f.get('if'))
> +        return [QAPISchemaFeature(f['name'], info,
> +                                  QAPISchemaIfCond(f.get('if')))
>                  for f in features]
>  
>      def _make_enum_members(self, values, info):
> -        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
> +        return [QAPISchemaEnumMember(v['name'], info,
> +                                     QAPISchemaIfCond(v.get('if')))
>                  for v in values]
>  

Two more instances of pattern #3, only here we wrap values we get from
the JSON parser.  These are either None or non-empty lists.

>      def _make_implicit_enum_type(self, name, info, ifcond, values):
> @@ -1008,7 +1016,10 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
           if typ:
               # The implicit object type has multiple users.  This can
               # happen only for simple unions' implicit wrapper types.
               # Its ifcond should be the disjunction of its user's
               # ifconds.  Not implemented.  Instead, we always pass the
               # wrapped type's ifcond, which is trivially the same for all
               # users.  It's also necessary for the wrapper to compile.
               # But it's not tight: the disjunction need not imply it.  We
               # may end up compiling useless wrapper types.
>              # TODO kill simple unions or implement the disjunction
>  
>              # pylint: disable=protected-access
> -            assert (ifcond or []) == typ._ifcond
> +            if isinstance(ifcond, QAPISchemaIfCond):
> +                assert ifcond.ifcond == typ._ifcond.ifcond
> +            else:
> +                assert ifcond == typ._ifcond
>          else:
>              self._def_entity(QAPISchemaObjectType(
>                  name, info, None, ifcond, None, None, members, None))

This is the non-mechanical change mentioned in the commit message.

Can you explain where the two cases come from?

> @@ -1018,7 +1029,7 @@ def _def_enum_type(self, expr, info, doc):
>          name = expr['enum']
>          data = expr['data']
>          prefix = expr.get('prefix')
> -        ifcond = expr.get('if')
> +        ifcond = QAPISchemaIfCond(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          self._def_entity(QAPISchemaEnumType(
>              name, info, doc, ifcond, features,
> @@ -1036,7 +1047,8 @@ def _make_member(self, name, typ, ifcond, features, info):
>                                            self._make_features(features, info))
>  
>      def _make_members(self, data, info):
> -        return [self._make_member(key, value['type'], value.get('if'),
> +        return [self._make_member(key, value['type'],
> +                                  QAPISchemaIfCond(value.get('if')),
>                                    value.get('features'), info)
>                  for (key, value) in data.items()]
>  
> @@ -1044,7 +1056,7 @@ def _def_struct_type(self, expr, info, doc):
>          name = expr['struct']
>          base = expr.get('base')
>          data = expr['data']
> -        ifcond = expr.get('if')
> +        ifcond = QAPISchemaIfCond(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          self._def_entity(QAPISchemaObjectType(
>              name, info, doc, ifcond, features, base,
> @@ -1067,7 +1079,7 @@ def _def_union_type(self, expr, info, doc):
>          name = expr['union']
>          data = expr['data']
>          base = expr.get('base')
> -        ifcond = expr.get('if')
> +        ifcond = QAPISchemaIfCond(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          tag_name = expr.get('discriminator')
>          tag_member = None
> @@ -1076,15 +1088,19 @@ def _def_union_type(self, expr, info, doc):
>                  name, info, ifcond,
>                  'base', self._make_members(base, info))
>          if tag_name:
> -            variants = [self._make_variant(key, value['type'],
> -                                           value.get('if'), info)
> -                        for (key, value) in data.items()]
> +            variants = [
> +                self._make_variant(key, value['type'],
> +                                   QAPISchemaIfCond(value.get('if')),
> +                                   info)
> +                for (key, value) in data.items()]
>              members = []
>          else:
> -            variants = [self._make_simple_variant(key, value['type'],
> -                                                  value.get('if'), info)
> -                        for (key, value) in data.items()]
> -            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
> +            variants = [
> +                self._make_simple_variant(key, value['type'],
> +                                          QAPISchemaIfCond(value.get('if')),
> +                                          info)
> +                for (key, value) in data.items()]
> +            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in variants]
>              typ = self._make_implicit_enum_type(name, info, ifcond, enum)
>              tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
>              members = [tag_member]
> @@ -1097,11 +1113,13 @@ def _def_union_type(self, expr, info, doc):
>      def _def_alternate_type(self, expr, info, doc):
>          name = expr['alternate']
>          data = expr['data']
> -        ifcond = expr.get('if')
> +        ifcond = QAPISchemaIfCond(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
> -        variants = [self._make_variant(key, value['type'], value.get('if'),
> -                                       info)
> -                    for (key, value) in data.items()]
> +        variants = [
> +            self._make_variant(key, value['type'],
> +                               QAPISchemaIfCond(value.get('if')),
> +                               info)
> +            for (key, value) in data.items()]
>          tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
>          self._def_entity(
>              QAPISchemaAlternateType(name, info, doc, ifcond, features,
> @@ -1118,7 +1136,7 @@ def _def_command(self, expr, info, doc):
>          allow_oob = expr.get('allow-oob', False)
>          allow_preconfig = expr.get('allow-preconfig', False)
>          coroutine = expr.get('coroutine', False)
> -        ifcond = expr.get('if')
> +        ifcond = QAPISchemaIfCond(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          if isinstance(data, OrderedDict):
>              data = self._make_implicit_object_type(
> @@ -1137,7 +1155,7 @@ def _def_event(self, expr, info, doc):
>          name = expr['event']
>          data = expr.get('data')
>          boxed = expr.get('boxed', False)
> -        ifcond = expr.get('if')
> +        ifcond = QAPISchemaIfCond(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          if isinstance(data, OrderedDict):
>              data = self._make_implicit_object_type(
> diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
> index 20d572a23a..3673cf0f49 100644
> --- a/scripts/qapi/types.py
> +++ b/scripts/qapi/types.py
> @@ -13,7 +13,7 @@
>  # See the COPYING file in the top-level directory.
>  """
>  
> -from typing import List, Optional, Sequence
> +from typing import List, Optional
>  
>  from .common import (
>      c_enum_const,
> @@ -27,6 +27,7 @@
>      QAPISchema,
>      QAPISchemaEnumMember,
>      QAPISchemaFeature,
> +    QAPISchemaIfCond,
>      QAPISchemaObjectType,
>      QAPISchemaObjectTypeMember,
>      QAPISchemaType,
> @@ -50,13 +51,13 @@ def gen_enum_lookup(name: str,
>  ''',
>                  c_name=c_name(name))
>      for memb in members:
> -        ret += gen_if(memb.ifcond)
> +        ret += gen_if(memb.ifcond.ifcond)
>          index = c_enum_const(name, memb.name, prefix)
>          ret += mcgen('''
>          [%(index)s] = "%(name)s",
>  ''',
>                       index=index, name=memb.name)
> -        ret += gen_endif(memb.ifcond)
> +        ret += gen_endif(memb.ifcond.ifcond)
>  
>      ret += mcgen('''
>      },
> @@ -80,12 +81,12 @@ def gen_enum(name: str,
>                  c_name=c_name(name))
>  
>      for memb in enum_members:
> -        ret += gen_if(memb.ifcond)
> +        ret += gen_if(memb.ifcond.ifcond)
>          ret += mcgen('''
>      %(c_enum)s,
>  ''',
>                       c_enum=c_enum_const(name, memb.name, prefix))
> -        ret += gen_endif(memb.ifcond)
> +        ret += gen_endif(memb.ifcond.ifcond)
>  
>      ret += mcgen('''
>  } %(c_name)s;
> @@ -125,7 +126,7 @@ def gen_array(name: str, element_type: QAPISchemaType) -> str:
>  def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
>      ret = ''
>      for memb in members:
> -        ret += gen_if(memb.ifcond)
> +        ret += gen_if(memb.ifcond.ifcond)
>          if memb.optional:
>              ret += mcgen('''
>      bool has_%(c_name)s;
> @@ -135,11 +136,11 @@ def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
>      %(c_type)s %(c_name)s;
>  ''',
>                       c_type=memb.type.c_type(), c_name=c_name(memb.name))
> -        ret += gen_endif(memb.ifcond)
> +        ret += gen_endif(memb.ifcond.ifcond)
>      return ret
>  
>  
> -def gen_object(name: str, ifcond: Sequence[str],
> +def gen_object(name: str, ifcond: QAPISchemaIfCond,
>                 base: Optional[QAPISchemaObjectType],
>                 members: List[QAPISchemaObjectTypeMember],
>                 variants: Optional[QAPISchemaVariants]) -> str:
> @@ -158,7 +159,7 @@ def gen_object(name: str, ifcond: Sequence[str],
>      ret += mcgen('''
>  
>  ''')
> -    ret += gen_if(ifcond)
> +    ret += gen_if(ifcond.ifcond)
>      ret += mcgen('''
>  struct %(c_name)s {
>  ''',
> @@ -192,7 +193,7 @@ def gen_object(name: str, ifcond: Sequence[str],
>      ret += mcgen('''
>  };
>  ''')
> -    ret += gen_endif(ifcond)
> +    ret += gen_endif(ifcond.ifcond)
>  
>      return ret
>  
> @@ -219,13 +220,13 @@ def gen_variants(variants: QAPISchemaVariants) -> str:
>      for var in variants.variants:
>          if var.type.name == 'q_empty':
>              continue
> -        ret += gen_if(var.ifcond)
> +        ret += gen_if(var.ifcond.ifcond)
>          ret += mcgen('''
>          %(c_type)s %(c_name)s;
>  ''',
>                       c_type=var.type.c_unboxed_type(),
>                       c_name=c_name(var.name))
> -        ret += gen_endif(var.ifcond)
> +        ret += gen_endif(var.ifcond.ifcond)
>  
>      ret += mcgen('''
>      } u;
> @@ -307,7 +308,7 @@ def _gen_type_cleanup(self, name: str) -> None:
>      def visit_enum_type(self,
>                          name: str,
>                          info: Optional[QAPISourceInfo],
> -                        ifcond: Sequence[str],
> +                        ifcond: QAPISchemaIfCond,
>                          features: List[QAPISchemaFeature],
>                          members: List[QAPISchemaEnumMember],
>                          prefix: Optional[str]) -> None:
> @@ -318,7 +319,7 @@ def visit_enum_type(self,
>      def visit_array_type(self,
>                           name: str,
>                           info: Optional[QAPISourceInfo],
> -                         ifcond: Sequence[str],
> +                         ifcond: QAPISchemaIfCond,
>                           element_type: QAPISchemaType) -> None:
>          with ifcontext(ifcond, self._genh, self._genc):
>              self._genh.preamble_add(gen_fwd_object_or_array(name))
> @@ -328,7 +329,7 @@ def visit_array_type(self,
>      def visit_object_type(self,
>                            name: str,
>                            info: Optional[QAPISourceInfo],
> -                          ifcond: Sequence[str],
> +                          ifcond: QAPISchemaIfCond,
>                            features: List[QAPISchemaFeature],
>                            base: Optional[QAPISchemaObjectType],
>                            members: List[QAPISchemaObjectTypeMember],
> @@ -351,7 +352,7 @@ def visit_object_type(self,
>      def visit_alternate_type(self,
>                               name: str,
>                               info: Optional[QAPISourceInfo],
> -                             ifcond: Sequence[str],
> +                             ifcond: QAPISchemaIfCond,
>                               features: List[QAPISchemaFeature],
>                               variants: QAPISchemaVariants) -> None:
>          with ifcontext(ifcond, self._genh):
> diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
> index 9e96f3c566..67721b2470 100644
> --- a/scripts/qapi/visit.py
> +++ b/scripts/qapi/visit.py
> @@ -13,7 +13,7 @@
>  See the COPYING file in the top-level directory.
>  """
>  
> -from typing import List, Optional, Sequence
> +from typing import List, Optional
>  
>  from .common import (
>      c_enum_const,
> @@ -29,6 +29,7 @@
>      QAPISchemaEnumMember,
>      QAPISchemaEnumType,
>      QAPISchemaFeature,
> +    QAPISchemaIfCond,
>      QAPISchemaObjectType,
>      QAPISchemaObjectTypeMember,
>      QAPISchemaType,
> @@ -78,7 +79,7 @@ def gen_visit_object_members(name: str,
>  
>      for memb in members:
>          deprecated = 'deprecated' in [f.name for f in memb.features]
> -        ret += gen_if(memb.ifcond)
> +        ret += gen_if(memb.ifcond.ifcond)
>          if memb.optional:
>              ret += mcgen('''
>      if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
> @@ -111,7 +112,7 @@ def gen_visit_object_members(name: str,
>              ret += mcgen('''
>      }
>  ''')
> -        ret += gen_endif(memb.ifcond)
> +        ret += gen_endif(memb.ifcond.ifcond)
>  
>      if variants:
>          tag_member = variants.tag_member
> @@ -125,7 +126,7 @@ def gen_visit_object_members(name: str,
>          for var in variants.variants:
>              case_str = c_enum_const(tag_member.type.name, var.name,
>                                      tag_member.type.prefix)
> -            ret += gen_if(var.ifcond)
> +            ret += gen_if(var.ifcond.ifcond)
>              if var.type.name == 'q_empty':
>                  # valid variant and nothing to do
>                  ret += mcgen('''
> @@ -141,7 +142,7 @@ def gen_visit_object_members(name: str,
>                               case=case_str,
>                               c_type=var.type.c_name(), c_name=c_name(var.name))
>  
> -            ret += gen_endif(var.ifcond)
> +            ret += gen_endif(var.ifcond.ifcond)
>          ret += mcgen('''
>      default:
>          abort();
> @@ -227,7 +228,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
>                  c_name=c_name(name))
>  
>      for var in variants.variants:
> -        ret += gen_if(var.ifcond)
> +        ret += gen_if(var.ifcond.ifcond)
>          ret += mcgen('''
>      case %(case)s:
>  ''',
> @@ -253,7 +254,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
>          ret += mcgen('''
>          break;
>  ''')
> -        ret += gen_endif(var.ifcond)
> +        ret += gen_endif(var.ifcond.ifcond)
>  
>      ret += mcgen('''
>      case QTYPE_NONE:
> @@ -352,7 +353,7 @@ def _begin_user_module(self, name: str) -> None:
>      def visit_enum_type(self,
>                          name: str,
>                          info: Optional[QAPISourceInfo],
> -                        ifcond: Sequence[str],
> +                        ifcond: QAPISchemaIfCond,
>                          features: List[QAPISchemaFeature],
>                          members: List[QAPISchemaEnumMember],
>                          prefix: Optional[str]) -> None:
> @@ -363,7 +364,7 @@ def visit_enum_type(self,
>      def visit_array_type(self,
>                           name: str,
>                           info: Optional[QAPISourceInfo],
> -                         ifcond: Sequence[str],
> +                         ifcond: QAPISchemaIfCond,
>                           element_type: QAPISchemaType) -> None:
>          with ifcontext(ifcond, self._genh, self._genc):
>              self._genh.add(gen_visit_decl(name))
> @@ -372,7 +373,7 @@ def visit_array_type(self,
>      def visit_object_type(self,
>                            name: str,
>                            info: Optional[QAPISourceInfo],
> -                          ifcond: Sequence[str],
> +                          ifcond: QAPISchemaIfCond,
>                            features: List[QAPISchemaFeature],
>                            base: Optional[QAPISchemaObjectType],
>                            members: List[QAPISchemaObjectTypeMember],
> @@ -394,7 +395,7 @@ def visit_object_type(self,
>      def visit_alternate_type(self,
>                               name: str,
>                               info: Optional[QAPISourceInfo],
> -                             ifcond: Sequence[str],
> +                             ifcond: QAPISchemaIfCond,
>                               features: List[QAPISchemaFeature],
>                               variants: QAPISchemaVariants) -> None:
>          with ifcontext(ifcond, self._genh, self._genc):
> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> index f1c4deb9a5..7907b4ac3a 100755
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -94,8 +94,8 @@ def _print_variants(variants):
>  
>      @staticmethod
>      def _print_if(ifcond, indent=4):
> -        if ifcond:
> -            print('%sif %s' % (' ' * indent, ifcond))
> +        if ifcond.ifcond:
> +            print('%sif %s' % (' ' * indent, ifcond.ifcond))
>  
>      @classmethod
>      def _print_features(cls, features, indent=4):



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 03/11] qapi: add QAPISchemaIfCond.is_present()
  2021-06-18 10:24 ` [PATCH v6 03/11] qapi: add QAPISchemaIfCond.is_present() marcandre.lureau
@ 2021-08-02  9:52   ` Markus Armbruster
  2021-08-04  8:22     ` Marc-André Lureau
  0 siblings, 1 reply; 39+ messages in thread
From: Markus Armbruster @ 2021-08-02  9:52 UTC (permalink / raw)
  To: marcandre.lureau
  Cc: Eric Blake, jsnow, qemu-devel, stefanha, Markus Armbruster

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  docs/sphinx/qapidoc.py         | 8 ++++----
>  scripts/qapi/schema.py         | 7 +++++--
>  tests/qapi-schema/test-qapi.py | 2 +-
>  3 files changed, 10 insertions(+), 7 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 0eac3308b2..511520f33f 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -139,7 +139,7 @@ def _nodes_for_one_member(self, member):
>              term.append(nodes.literal('', member.type.doc_type()))
>          if member.optional:
>              term.append(nodes.Text(' (optional)'))
> -        if member.ifcond.ifcond:
> +        if member.ifcond.is_present():
>              term.extend(self._nodes_for_ifcond(member.ifcond))
>          return term
>  
> @@ -154,7 +154,7 @@ def _nodes_for_variant_when(self, variants, variant):
>                  nodes.literal('', variants.tag_member.name),
>                  nodes.Text(' is '),
>                  nodes.literal('', '"%s"' % variant.name)]
> -        if variant.ifcond.ifcond:
> +        if variant.ifcond.is_present():
>              term.extend(self._nodes_for_ifcond(variant.ifcond))
>          return term
>  
> @@ -209,7 +209,7 @@ def _nodes_for_enum_values(self, doc):
>          dlnode = nodes.definition_list()
>          for section in doc.args.values():
>              termtext = [nodes.literal('', section.member.name)]
> -            if section.member.ifcond.ifcond:
> +            if section.member.ifcond.is_present():
>                  termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
>              # TODO drop fallbacks when undocumented members are outlawed
>              if section.text:
> @@ -277,7 +277,7 @@ def _nodes_for_sections(self, doc):
>      def _nodes_for_if_section(self, ifcond):
>          """Return list of doctree nodes for the "If" section"""
>          nodelist = []
> -        if ifcond.ifcond:
> +        if ifcond.is_present():
>              snode = self._make_section('If')
>              snode += nodes.paragraph(
>                  '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index 5e44164bd1..e3bd8f8720 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -29,6 +29,9 @@ class QAPISchemaIfCond:
>      def __init__(self, ifcond=None):
>          self.ifcond = ifcond or []
>  
> +    def is_present(self):
> +        return bool(self.ifcond)
> +
>  
>  class QAPISchemaEntity:
>      meta: Optional[str] = None
> @@ -599,7 +602,7 @@ def check(self, schema, seen):
>                      self.info,
>                      "discriminator member '%s' of %s must not be optional"
>                      % (self._tag_name, base))
> -            if self.tag_member.ifcond.ifcond:
> +            if self.tag_member.ifcond.is_present():
>                  raise QAPISemError(
>                      self.info,
>                      "discriminator member '%s' of %s must not be conditional"
> @@ -607,7 +610,7 @@ def check(self, schema, seen):
>          else:                   # simple union
>              assert isinstance(self.tag_member.type, QAPISchemaEnumType)
>              assert not self.tag_member.optional
> -            assert self.tag_member.ifcond.ifcond == []
> +            assert not self.tag_member.ifcond.is_present()
>          if self._tag_name:    # flat union
>              # branches that are not explicitly covered get an empty type
>              cases = {v.name for v in self.variants}
> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> index 7907b4ac3a..c92be2d086 100755
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -94,7 +94,7 @@ def _print_variants(variants):
>  
>      @staticmethod
>      def _print_if(ifcond, indent=4):
> -        if ifcond.ifcond:
> +        if ifcond.is_present():
>              print('%sif %s' % (' ' * indent, ifcond.ifcond))
>  
>      @classmethod

In introspect.py:

        if obj.ifcond:
            ret += gen_if(obj.ifcond.ifcond)
        ret += _tree_to_qlit(obj.value, level)
        if obj.ifcond:
            ret += '\n' + gen_endif(obj.ifcond.ifcond)

I believe the previous patch should change it to

        if obj.ifcond.ifcond:
            ret += gen_if(obj.ifcond.ifcond)
        ret += _tree_to_qlit(obj.value, level)
        if obj.ifcond.ifcond:
            ret += '\n' + gen_endif(obj.ifcond.ifcond)

and this one to

        if obj.ifcond.is_present():
            ret += gen_if(obj.ifcond.ifcond)
        ret += _tree_to_qlit(obj.value, level)
        if obj.ifcond.is_present():
            ret += '\n' + gen_endif(obj.ifcond.ifcond)

Other than that:
Reviewed-by: Markus Armbruster <armbru@redhat.com>



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond
  2021-06-18 10:25 ` [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond marcandre.lureau
@ 2021-08-02 10:41   ` Markus Armbruster
  2021-08-04  8:22     ` Marc-André Lureau
  0 siblings, 1 reply; 39+ messages in thread
From: Markus Armbruster @ 2021-08-02 10:41 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Instead of lowering the expression back to its original form, and having
> to convert it again, special-case the 'if' condition to be pre-built.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  scripts/qapi/schema.py | 11 ++++++++---
>  1 file changed, 8 insertions(+), 3 deletions(-)
>
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index e3bd8f8720..c35fa3bf51 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -982,8 +982,13 @@ def _make_features(self, features, info):
>                  for f in features]
>  
>      def _make_enum_members(self, values, info):
> -        return [QAPISchemaEnumMember(v['name'], info,
> -                                     QAPISchemaIfCond(v.get('if')))
> +        def _get_if(v):
> +            ifcond = v.get('if')
> +            if isinstance(ifcond, QAPISchemaIfCond):
> +                return ifcond
> +            else:
> +                return QAPISchemaIfCond(ifcond)
> +        return [QAPISchemaEnumMember(v['name'], info, _get_if(v))
>                  for v in values]
>  
>      def _make_implicit_enum_type(self, name, info, ifcond, values):
> @@ -1103,7 +1108,7 @@ def _def_union_type(self, expr, info, doc):
>                                            QAPISchemaIfCond(value.get('if')),
>                                            info)
>                  for (key, value) in data.items()]
> -            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in variants]
> +            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
>              typ = self._make_implicit_enum_type(name, info, ifcond, enum)
>              tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
>              members = [tag_member]

I'm afraid I don't like this one.

Mapping from QAPISchemaIfCond back to the AST happens to be easy with
the current data structures, but you're right, it's not nice.

Stuffing the QAPISchemaIfCond into the AST is (in my opinion) worse:
it's a layering violation.

Let's take a step back and review what needs to be done here:

    for each simple union branch:
        create a simple variant
        create an implicit enum member
    and
        collect the variants in a list
        collect the enum members in a list

The code splits this work.  It first creates the list of variants from
the AST's simple union branches in @data:

            variants = [
                self._make_simple_variant(key, value['type'],
                                          QAPISchemaIfCond(value.get('if')),
                                          info)
                for (key, value) in data.items()]

It then creates the list of enum of enum members from the list of
variants, *not* from the AST:

            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in variants]

This dots into the QAPISchemaVariant.  Your patch makes this dotting
less deep.

Two solutions I'd dislike less:

1. Create the enum members from the AST, too.

2. Do nothing, and bank on the eventual removal of simple unions.

Minimizing ripple effects on the remainder of the series is of course a
concern.



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen()
  2021-06-18 10:25 ` [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen() marcandre.lureau
@ 2021-08-02 14:46   ` Markus Armbruster
  2021-08-03 11:19     ` Markus Armbruster
  2021-08-04  8:23     ` Marc-André Lureau
  0 siblings, 2 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-02 14:46 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Instead of building prepocessor conditions from a list of string, use
> the result generated from QAPISchemaIfCond.cgen() and hide the
> implementation details.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Please mention that the patch changes generated code.  See below for
details.

> ---
>  scripts/qapi/common.py     | 35 ++++++++++++++++++++++-------------
>  scripts/qapi/gen.py        |  4 ++--
>  scripts/qapi/introspect.py |  4 ++--
>  scripts/qapi/schema.py     |  5 ++++-
>  scripts/qapi/types.py      | 20 ++++++++++----------
>  scripts/qapi/visit.py      | 12 ++++++------
>  6 files changed, 46 insertions(+), 34 deletions(-)
>
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index 6ad1eeb61d..ba9fe14e4b 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -12,7 +12,12 @@
>  # See the COPYING file in the top-level directory.
>  
>  import re
> -from typing import Match, Optional, Sequence
> +from typing import (
> +    List,
> +    Match,
> +    Optional,
> +    Union,
> +)
>  
>  
>  #: Magic string that gets removed along with all space to its right.
> @@ -194,22 +199,26 @@ def guardend(name: str) -> str:
>                   name=c_fname(name).upper())
>  
>  
> -def gen_if(ifcond: Sequence[str]) -> str:
> -    ret = ''
> -    for ifc in ifcond:
> -        ret += mcgen('''
> +def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> +    if not ifcond:
> +        return ''
> +    return '(' + ') && ('.join(ifcond) + ')'
> +
> +
> +def gen_if(cond: str) -> str:
> +    if not cond:
> +        return ''
> +    return mcgen('''
>  #if %(cond)s
> -''', cond=ifc)
> -    return ret
> +''', cond=cond)
>  
>  
> -def gen_endif(ifcond: Sequence[str]) -> str:
> -    ret = ''
> -    for ifc in reversed(ifcond):
> -        ret += mcgen('''
> +def gen_endif(cond: str) -> str:
> +    if not cond:
> +        return ''
> +    return mcgen('''
>  #endif /* %(cond)s */
> -''', cond=ifc)
> -    return ret
> +''', cond=cond)
>  
>  

This patch does three things:

(1) Change gen_if(), gen_endif() to always generate a single #if,
    #endif.  This enables:

(2) Factor cgen_ifcond() out of gen_if() and gen_endif()

(3) Lift the call of cgen_ifcond() into into gen_if()'s, gen_endif()'s
    callers.

I'd split the patch.  This is *not* a demand.

The motivation for (3) is unclear.  Is it so gen_if() doesn't depend on
QAPISchemaIfCond?

Step (1) affects the generated code.  When @ifcond is [COND1, COND2, ...],
gen_if()'s value changes from

    #if COND1
    #if COND2
    ...

to

    #if (COND1) && (COND2)

Example: in tests/test-qapi-introspect.c

    @@ -259,11 +257,9 @@ const QLitObject test_qmp_schema_qlit =
         QLIT_QDICT(((QLitDictEntry[]) {
             { "arg-type", QLIT_QSTR("1"), },
             { "features", QLIT_QLIST(((QLitObject[]) {
    -#if defined(TEST_IF_COND_1)
    -#if defined(TEST_IF_COND_2)
    +#if (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2))
                 QLIT_QSTR("feature1"),
    -#endif /* defined(TEST_IF_COND_2) */
    -#endif /* defined(TEST_IF_COND_1) */
    +#endif /* (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2)) */
                 {}
             })), },
             { "meta-type", QLIT_QSTR("command"), },

The common case: when it's just [COND], the value changes from

    #if COND

to

    #if (COND)

which is a bit ugly.

Example: in qapi-types-block-export.c

    @@ -76,7 +76,7 @@ const QEnumLookup FuseExportAllowOther_l
         .size = FUSE_EXPORT_ALLOW_OTHER__MAX
     };

    -#if defined(CONFIG_FUSE)
    +#if (defined(CONFIG_FUSE))
     void qapi_free_BlockExportOptionsFuse(BlockExportOptionsFuse *obj)
     {
         Visitor *v;
    @@ -89,7 +89,7 @@ void qapi_free_BlockExportOptionsFuse(Bl
         visit_type_BlockExportOptionsFuse(v, NULL, &obj, NULL);
         visit_free(v);
     }
    -#endif /* defined(CONFIG_FUSE) */
    +#endif /* (defined(CONFIG_FUSE)) */

     void qapi_free_NbdServerAddOptions(NbdServerAddOptions *obj)
     {

Avoiding the redundant pair of parenthesis takes another special case.
Let's do it.

>  def must_match(pattern: str, string: str) -> Match[str]:
> diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
> index 1c5b190276..51a597a025 100644
> --- a/scripts/qapi/gen.py
> +++ b/scripts/qapi/gen.py
> @@ -95,9 +95,9 @@ def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) -> str:
>      if added[0] == '\n':
>          out += '\n'
>          added = added[1:]
> -    out += gen_if(ifcond.ifcond)
> +    out += gen_if(ifcond.cgen())
>      out += added
> -    out += gen_endif(ifcond.ifcond)
> +    out += gen_endif(ifcond.cgen())
>      return out
>  
>  

> diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
> index 77a8c33ad4..474b08fd4d 100644
> --- a/scripts/qapi/introspect.py
> +++ b/scripts/qapi/introspect.py
> @@ -124,10 +124,10 @@ def indent(level: int) -> str:
>          if obj.comment:
>              ret += indent(level) + f"/* {obj.comment} */\n"
>          if obj.ifcond:
> -            ret += gen_if(obj.ifcond.ifcond)
> +            ret += gen_if(obj.ifcond.cgen())
>          ret += _tree_to_qlit(obj.value, level)
>          if obj.ifcond:
> -            ret += '\n' + gen_endif(obj.ifcond.ifcond)
> +            ret += '\n' + gen_endif(obj.ifcond.cgen())
>          return ret
>  
>      ret = ''
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index c35fa3bf51..70120f0dcc 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -19,7 +19,7 @@
>  import re
>  from typing import Optional
>  
> -from .common import POINTER_SUFFIX, c_name
> +from .common import POINTER_SUFFIX, c_name, cgen_ifcond
>  from .error import QAPIError, QAPISemError, QAPISourceError
>  from .expr import check_exprs
>  from .parser import QAPISchemaParser
> @@ -29,6 +29,9 @@ class QAPISchemaIfCond:
>      def __init__(self, ifcond=None):
>          self.ifcond = ifcond or []
>  
> +    def cgen(self):
> +        return cgen_ifcond(self.ifcond)
> +

As far as I can tell, this is only ever used like

       gen_if(obj.ifcond.cgen())

and

       gen_endif(obj.ifcond.cgen())

Wouldn't

       obj.ifcond.gen_if()

and

       obj.ifcond.gen_endif()

be neater?

Not a demand, since we can get there on top if we want to.

>      def is_present(self):
>          return bool(self.ifcond)
>  
> diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
> index 3673cf0f49..db9ff95bd1 100644
> --- a/scripts/qapi/types.py
> +++ b/scripts/qapi/types.py
> @@ -51,13 +51,13 @@ def gen_enum_lookup(name: str,
>  ''',
>                  c_name=c_name(name))
>      for memb in members:
> -        ret += gen_if(memb.ifcond.ifcond)
> +        ret += gen_if(memb.ifcond.cgen())
>          index = c_enum_const(name, memb.name, prefix)
>          ret += mcgen('''
>          [%(index)s] = "%(name)s",
>  ''',
>                       index=index, name=memb.name)
> -        ret += gen_endif(memb.ifcond.ifcond)
> +        ret += gen_endif(memb.ifcond.cgen())
>  
>      ret += mcgen('''
>      },
> @@ -81,12 +81,12 @@ def gen_enum(name: str,
>                  c_name=c_name(name))
>  
>      for memb in enum_members:
> -        ret += gen_if(memb.ifcond.ifcond)
> +        ret += gen_if(memb.ifcond.cgen())
>          ret += mcgen('''
>      %(c_enum)s,
>  ''',
>                       c_enum=c_enum_const(name, memb.name, prefix))
> -        ret += gen_endif(memb.ifcond.ifcond)
> +        ret += gen_endif(memb.ifcond.cgen())
>  
>      ret += mcgen('''
>  } %(c_name)s;
> @@ -126,7 +126,7 @@ def gen_array(name: str, element_type: QAPISchemaType) -> str:
>  def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
>      ret = ''
>      for memb in members:
> -        ret += gen_if(memb.ifcond.ifcond)
> +        ret += gen_if(memb.ifcond.cgen())
>          if memb.optional:
>              ret += mcgen('''
>      bool has_%(c_name)s;
> @@ -136,7 +136,7 @@ def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) -> str:
>      %(c_type)s %(c_name)s;
>  ''',
>                       c_type=memb.type.c_type(), c_name=c_name(memb.name))
> -        ret += gen_endif(memb.ifcond.ifcond)
> +        ret += gen_endif(memb.ifcond.cgen())
>      return ret
>  
>  
> @@ -159,7 +159,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
>      ret += mcgen('''
>  
>  ''')
> -    ret += gen_if(ifcond.ifcond)
> +    ret += gen_if(ifcond.cgen())
>      ret += mcgen('''
>  struct %(c_name)s {
>  ''',
> @@ -193,7 +193,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
>      ret += mcgen('''
>  };
>  ''')
> -    ret += gen_endif(ifcond.ifcond)
> +    ret += gen_endif(ifcond.cgen())
>  
>      return ret
>  
> @@ -220,13 +220,13 @@ def gen_variants(variants: QAPISchemaVariants) -> str:
>      for var in variants.variants:
>          if var.type.name == 'q_empty':
>              continue
> -        ret += gen_if(var.ifcond.ifcond)
> +        ret += gen_if(var.ifcond.cgen())
>          ret += mcgen('''
>          %(c_type)s %(c_name)s;
>  ''',
>                       c_type=var.type.c_unboxed_type(),
>                       c_name=c_name(var.name))
> -        ret += gen_endif(var.ifcond.ifcond)
> +        ret += gen_endif(var.ifcond.cgen())
>  
>      ret += mcgen('''
>      } u;
> diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
> index 67721b2470..56ea516399 100644
> --- a/scripts/qapi/visit.py
> +++ b/scripts/qapi/visit.py
> @@ -79,7 +79,7 @@ def gen_visit_object_members(name: str,
>  
>      for memb in members:
>          deprecated = 'deprecated' in [f.name for f in memb.features]
> -        ret += gen_if(memb.ifcond.ifcond)
> +        ret += gen_if(memb.ifcond.cgen())
>          if memb.optional:
>              ret += mcgen('''
>      if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
> @@ -112,7 +112,7 @@ def gen_visit_object_members(name: str,
>              ret += mcgen('''
>      }
>  ''')
> -        ret += gen_endif(memb.ifcond.ifcond)
> +        ret += gen_endif(memb.ifcond.cgen())
>  
>      if variants:
>          tag_member = variants.tag_member
> @@ -126,7 +126,7 @@ def gen_visit_object_members(name: str,
>          for var in variants.variants:
>              case_str = c_enum_const(tag_member.type.name, var.name,
>                                      tag_member.type.prefix)
> -            ret += gen_if(var.ifcond.ifcond)
> +            ret += gen_if(var.ifcond.cgen())
>              if var.type.name == 'q_empty':
>                  # valid variant and nothing to do
>                  ret += mcgen('''
> @@ -142,7 +142,7 @@ def gen_visit_object_members(name: str,
>                               case=case_str,
>                               c_type=var.type.c_name(), c_name=c_name(var.name))
>  
> -            ret += gen_endif(var.ifcond.ifcond)
> +            ret += gen_endif(var.ifcond.cgen())
>          ret += mcgen('''
>      default:
>          abort();
> @@ -228,7 +228,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
>                  c_name=c_name(name))
>  
>      for var in variants.variants:
> -        ret += gen_if(var.ifcond.ifcond)
> +        ret += gen_if(var.ifcond.cgen())
>          ret += mcgen('''
>      case %(case)s:
>  ''',
> @@ -254,7 +254,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
>          ret += mcgen('''
>          break;
>  ''')
> -        ret += gen_endif(var.ifcond.ifcond)
> +        ret += gen_endif(var.ifcond.cgen())
>  
>      ret += mcgen('''
>      case QTYPE_NONE:



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 06/11] qapidoc: introduce QAPISchemaIfCond.docgen()
  2021-06-18 10:25 ` [PATCH v6 06/11] qapidoc: introduce QAPISchemaIfCond.docgen() marcandre.lureau
@ 2021-08-02 15:47   ` Markus Armbruster
  2021-08-04  8:23     ` Marc-André Lureau
  0 siblings, 1 reply; 39+ messages in thread
From: Markus Armbruster @ 2021-08-02 15:47 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Instead of building the condition documentation from a list of string,
> use the result generated from QAPISchemaIfCond.docgen().
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

I suspect this changes the generated documentation, similar to how the
previous patch changes generated code.  True?  If yes, can you show us
how?



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen()
  2021-08-02 14:46   ` Markus Armbruster
@ 2021-08-03 11:19     ` Markus Armbruster
  2021-08-03 11:20       ` Markus Armbruster
  2021-08-04  8:23     ` Marc-André Lureau
  1 sibling, 1 reply; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 11:19 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

Markus Armbruster <armbru@redhat.com> writes:

> marcandre.lureau@redhat.com writes:
>
>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>
>> Instead of building prepocessor conditions from a list of string, use
>> the result generated from QAPISchemaIfCond.cgen() and hide the
>> implementation details.
>>
>> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Please mention that the patch changes generated code.  See below for
> details.

[...]

> This patch does three things:
>
> (1) Change gen_if(), gen_endif() to always generate a single #if,
>     #endif.  This enables:
>
> (2) Factor cgen_ifcond() out of gen_if() and gen_endif()
>
> (3) Lift the call of cgen_ifcond() into into gen_if()'s, gen_endif()'s
>     callers.
>
> I'd split the patch.  This is *not* a demand.
>
> The motivation for (3) is unclear.  Is it so gen_if() doesn't depend on
> QAPISchemaIfCond?
>
> Step (1) affects the generated code.  When @ifcond is [COND1, COND2, ...],
> gen_if()'s value changes from
>
>     #if COND1
>     #if COND2
>     ...
>
> to
>
>     #if (COND1) && (COND2)

[...]

> The common case: when it's just [COND], the value changes from
>
>     #if COND
>
> to
>
>     #if (COND)
>
> which is a bit ugly.
>
> Example: in qapi-types-block-export.c

[...]

> Avoiding the redundant pair of parenthesis takes another special case.
> Let's do it.

Looks like PATCH 07 does it.  Avoiding the temporary change would be
nice, but isn't required.



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen()
  2021-08-03 11:19     ` Markus Armbruster
@ 2021-08-03 11:20       ` Markus Armbruster
  2021-08-03 11:23         ` Markus Armbruster
  0 siblings, 1 reply; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 11:20 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

Markus Armbruster <armbru@redhat.com> writes:

[...]

>> Avoiding the redundant pair of parenthesis takes another special case.
>> Let's do it.
>
> Looks like PATCH 07 does it.  Avoiding the temporary change would be
> nice, but isn't required.

Make that PATCH 08.



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen()
  2021-08-03 11:20       ` Markus Armbruster
@ 2021-08-03 11:23         ` Markus Armbruster
  0 siblings, 0 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 11:23 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

Markus Armbruster <armbru@redhat.com> writes:

> Markus Armbruster <armbru@redhat.com> writes:
>
> [...]
>
>>> Avoiding the redundant pair of parenthesis takes another special case.
>>> Let's do it.
>>
>> Looks like PATCH 07 does it.  Avoiding the temporary change would be
>> nice, but isn't required.
>
> Make that PATCH 08.

Scratch that, it's 07.  Sorry for the noise.



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]}
  2021-06-18 10:25 ` [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]} marcandre.lureau
@ 2021-08-03 13:05   ` Markus Armbruster
  2021-08-04  8:23     ` Marc-André Lureau
  2021-08-05 15:11     ` John Snow
  2021-08-03 13:17   ` Markus Armbruster
  1 sibling, 2 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 13:05 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Replace the simple list sugar form with a recursive structure that will
> accept other operators in the following commits (all, any or not).
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  scripts/qapi/common.py                        | 23 +++++--
>  scripts/qapi/expr.py                          | 52 ++++++++++------
>  scripts/qapi/schema.py                        |  2 +-
>  tests/qapi-schema/bad-if-empty-list.json      |  2 +-
>  tests/qapi-schema/bad-if-list.json            |  2 +-
>  tests/qapi-schema/bad-if.err                  |  3 +-
>  tests/qapi-schema/doc-good.json               |  3 +-
>  tests/qapi-schema/doc-good.out                | 13 ++--
>  tests/qapi-schema/doc-good.txt                |  6 ++
>  tests/qapi-schema/enum-if-invalid.err         |  3 +-
>  tests/qapi-schema/features-if-invalid.err     |  2 +-
>  tests/qapi-schema/qapi-schema-test.json       | 25 ++++----
>  tests/qapi-schema/qapi-schema-test.out        | 62 +++++++++----------
>  .../qapi-schema/struct-member-if-invalid.err  |  2 +-
>  .../qapi-schema/union-branch-if-invalid.json  |  2 +-
>  15 files changed, 119 insertions(+), 83 deletions(-)
>
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index 5181a0f167..51463510c9 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -13,7 +13,8 @@
>  
>  import re
>  from typing import (
> -    List,
> +    Any,
> +    Dict,
>      Match,
>      Optional,
>      Union,
> @@ -199,16 +200,28 @@ def guardend(name: str) -> str:
>                   name=c_fname(name).upper())
>  
>  
> -def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> +def docgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:

Uh, why do you swap cgen_ifcond() and docgen_ifcond()?  Accident?

>      if not ifcond:
>          return ''
> -    return '(' + ') && ('.join(ifcond) + ')'
> +    if isinstance(ifcond, str):
> +        return ifcond
>  
> +    oper, operands = next(iter(ifcond.items()))
> +    oper = {'all': ' and '}[oper]
> +    operands = [docgen_ifcond(o) for o in operands]
> +    return '(' + oper.join(operands) + ')'

What a nice review speedbump you buried here...

The whole block boils down to the much less exciting

       operands = [docgen_ifcond(o) for o in ifcond['all']]
       return '(' + ' and '.join(operands) + ')'

Peeking ahead, I understand that you did it this way here so you can
extend it trivially there.  Matter of taste; what counts is the final
result and minimizing reviewer WTFs/minute along the way.

Since the WTFs/minute is a done deed now, what remains is the final
result, which I expect to review shortly.  But please try a bit harder
to be boring next time ;)

>  
> -def docgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> +
> +def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
>      if not ifcond:
>          return ''
> -    return ' and '.join(ifcond)
> +    if isinstance(ifcond, str):
> +        return ifcond

This is what gets rid of the redundant parenthesises in the common case
"single condition string".

> +
> +    oper, operands = next(iter(ifcond.items()))
> +    oper = {'all': '&&'}[oper]
> +    operands = [cgen_ifcond(o) for o in operands]
> +    return '(' + (') ' + oper + ' (').join(operands) + ')'

This line is hard to read.  Easier, I think:

       oper = {'all': ' && '}[oper]
       operands = ['(' + cgen_ifcond(o) + ')' for o in operands]
       return oper.join(operands)

Neither your version nor mine gets rid of the redundant parenthesises in
the (uncommon) case "complex condition expression".
tests/test-qapi-introspect.c still has

    #if (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2))
                QLIT_QSTR("feature1"),
    #endif /* (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2)) */

Mildly annoying.  I'm willing to leave this for later.

Code smell: cgen_ifcond() and docgen_ifcond() are almost identical.  Can
also be left for later.

>  
>  
>  def gen_if(cond: str) -> str:
> diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> index 496f7e0333..3ee66c5f62 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -259,14 +259,12 @@ def check_flags(expr: _JSONObject, info: QAPISourceInfo) -> None:
>  
>  def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>      """
> -    Normalize and validate the ``if`` member of an object.
> +    Validate the ``if`` member of an object.
>  
> -    The ``if`` member may be either a ``str`` or a ``List[str]``.
> -    A ``str`` value will be normalized to ``List[str]``.
> +    The ``if`` member may be either a ``str`` or a dict.
>  
>      :forms:
> -      :sugared: ``Union[str, List[str]]``
> -      :canonical: ``List[str]``
> +      :canonical: ``Union[str, dict]``

Does this :forms: thing make sense without any :sugared:?  John, you
added (invented?) it in commit a48653638fa, but no explanation made it
into the tree.

>  
>      :param expr: The expression containing the ``if`` member to validate.
>      :param info: QAPI schema source file information.
> @@ -275,31 +273,45 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>      :raise QAPISemError:
>          When the "if" member fails validation, or when there are no
>          non-empty conditions.
> -    :return: None, ``expr`` is normalized in-place as needed.
> +    :return: None
>      """

Looks like there's a bit more going on than the commit message made me
expect.

>      ifcond = expr.get('if')
>      if ifcond is None:
>          return
>  
> -    if isinstance(ifcond, list):
> -        if not ifcond:
> -            raise QAPISemError(
> -                info, "'if' condition [] of %s is useless" % source)
> -    else:
> -        # Normalize to a list
> -        ifcond = expr['if'] = [ifcond]
> +    def _check_if(cond: Union[str, object]) -> None:
> +        if isinstance(cond, str):
> +            if not cond.strip():
> +                raise QAPISemError(
> +                    info,
> +                    "'if' condition '%s' of %s makes no sense"
> +                    % (cond, source))
> +            return
>  
> -    for elt in ifcond:
> -        if not isinstance(elt, str):
> +        if not isinstance(cond, dict):
>              raise QAPISemError(
>                  info,
> -                "'if' condition of %s must be a string or a list of strings"
> -                % source)
> -        if not elt.strip():
> +                "'if' condition of %s must be a string or a dict" % source)
> +        if len(cond) != 1:
>              raise QAPISemError(
>                  info,
> -                "'if' condition '%s' of %s makes no sense"
> -                % (elt, source))
> +                "'if' condition dict of %s must have one key: "
> +                "'all'" % source)
> +        check_keys(cond, info, "'if' condition", [],
> +                   ["all"])
> +
> +        oper, operands = next(iter(cond.items()))
> +        if not operands:
> +            raise QAPISemError(
> +                info, "'if' condition [] of %s is useless" % source)
> +
> +        if oper in ("all") and not isinstance(operands, list):
> +            raise QAPISemError(
> +                info, "'%s' condition of %s must be a list" % (oper, source))
> +        for operand in operands:
> +            _check_if(operand)
> +
> +    _check_if(ifcond)

Putting the function's helper in the middle of the function reminds me
of Mark Twain's "The Awful German Language":

    "The trunks being now ready, he DE- after kissing his mother and
    sisters, and once more pressing to his bosom his adored Gretchen,
    who, dressed in simple white muslin, with a single tuberose in the
    ample folds of her rich brown hair, had tottered feebly down the
    stairs, still pale from the terror and excitement of the past
    evening, but longing to lay her poor aching head yet once again upon
    the breast of him whom she loved more dearly than life itself,
    PARTED."

I find it hard to read.

>  
>  
>  def normalize_members(members: object) -> None:
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index 30d6a01ad1..d2fbdbe583 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -32,7 +32,7 @@
>  
>  class QAPISchemaIfCond:
>      def __init__(self, ifcond=None):
> -        self.ifcond = ifcond or []
> +        self.ifcond = ifcond or {}
>  
>      def cgen(self):
>          return cgen_ifcond(self.ifcond)
> diff --git a/tests/qapi-schema/bad-if-empty-list.json b/tests/qapi-schema/bad-if-empty-list.json
> index 94f2eb8670..b62b5671df 100644
> --- a/tests/qapi-schema/bad-if-empty-list.json
> +++ b/tests/qapi-schema/bad-if-empty-list.json
> @@ -1,3 +1,3 @@
>  # check empty 'if' list
>  { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
> -  'if': [] }
> +  'if': { 'all': [] } }
> diff --git a/tests/qapi-schema/bad-if-list.json b/tests/qapi-schema/bad-if-list.json
> index ea3d95bb6b..1fefef16a7 100644
> --- a/tests/qapi-schema/bad-if-list.json
> +++ b/tests/qapi-schema/bad-if-list.json
> @@ -1,3 +1,3 @@
>  # check invalid 'if' content
>  { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
> -  'if': ['foo', ' '] }
> +  'if': { 'all': ['foo', ' '] } }
> diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err
> index f83dee65da..8278c49368 100644
> --- a/tests/qapi-schema/bad-if.err
> +++ b/tests/qapi-schema/bad-if.err
> @@ -1,2 +1,3 @@
>  bad-if.json: In struct 'TestIfStruct':
> -bad-if.json:2: 'if' condition of struct must be a string or a list of strings
> +bad-if.json:2: 'if' condition has unknown key 'value'
> +Valid keys are 'all'.

"keys are" is awkward when there's just one.  Okay since there soon will
be more.

Test case bad-if.json is meant to cover "value of key 'if' has an
invalid JSON type".  Before the patch, str and list are valid, and the
test uses (invalid) dict.  Afterwards, str and dict are, and the test
still uses (now valid) dict.  In other words, it now tests something
else entirely.

I think this test should be updated to something like

    'if': [ 'defined(TEST_IF_STRUCT)' ]

and a new test added to cover invalid dict key.

> diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
> index 423ea23e07..25b1053e8a 100644
> --- a/tests/qapi-schema/doc-good.json
> +++ b/tests/qapi-schema/doc-good.json
> @@ -70,7 +70,8 @@
>  # @base1:
>  # the first member
>  ##
> -{ 'struct': 'Base', 'data': { 'base1': 'Enum' } }
> +{ 'struct': 'Base', 'data': { 'base1': 'Enum' },
> +  'if': { 'all': ['IFALL1', 'IFALL2'] } }

We lack cover for this before your patch.  Thanks for fixing it.

>  
>  ##
>  # @Variant1:
> diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> index 8f54ceff2e..689d084f3a 100644
> --- a/tests/qapi-schema/doc-good.out
> +++ b/tests/qapi-schema/doc-good.out
> @@ -12,15 +12,16 @@ enum QType
>  module doc-good.json
>  enum Enum
>      member one
> -        if ['defined(IFONE)']
> +        if defined(IFONE)
>      member two
> -    if ['defined(IFCOND)']
> +    if defined(IFCOND)
>      feature enum-feat
>  object Base
>      member base1: Enum optional=False
> +    if OrderedDict([('all', ['IFALL1', 'IFALL2'])])
>  object Variant1
>      member var1: str optional=False
> -        if ['defined(IFSTR)']
> +        if defined(IFSTR)
>          feature member-feat
>      feature variant1-feat
>  object Variant2
> @@ -29,7 +30,7 @@ object Object
>      tag base1
>      case one: Variant1
>      case two: Variant2
> -        if ['IFTWO']
> +        if IFTWO
>      feature union-feat1
>  object q_obj_Variant1-wrapper
>      member data: Variant1 optional=False
> @@ -38,13 +39,13 @@ object q_obj_Variant2-wrapper
>  enum SugaredUnionKind
>      member one
>      member two
> -        if ['IFTWO']
> +        if IFTWO
>  object SugaredUnion
>      member type: SugaredUnionKind optional=False
>      tag type
>      case one: q_obj_Variant1-wrapper
>      case two: q_obj_Variant2-wrapper
> -        if ['IFTWO']
> +        if IFTWO
>      feature union-feat2
>  alternate Alternate
>      tag type
> diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
> index 726727af74..4490108cb7 100644
> --- a/tests/qapi-schema/doc-good.txt
> +++ b/tests/qapi-schema/doc-good.txt
> @@ -76,6 +76,12 @@ Members
>     the first member
>  
>  
> +If
> +~~
> +
> +"(IFALL1 and IFALL2)"
> +
> +

The documentation generated for conditionals is poor before and after
your work.  Observation, not demand.

>  "Variant1" (Object)
>  -------------------
>  

[...]



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]}
  2021-06-18 10:25 ` [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]} marcandre.lureau
  2021-08-03 13:05   ` Markus Armbruster
@ 2021-08-03 13:17   ` Markus Armbruster
  1 sibling, 0 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 13:17 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

One more thing...

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Replace the simple list sugar form with a recursive structure that will
> accept other operators in the following commits (all, any or not).
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---

[...]

> diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> index 496f7e0333..3ee66c5f62 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -259,14 +259,12 @@ def check_flags(expr: _JSONObject, info: QAPISourceInfo) -> None:
>  
>  def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>      """
> -    Normalize and validate the ``if`` member of an object.
> +    Validate the ``if`` member of an object.
>  
> -    The ``if`` member may be either a ``str`` or a ``List[str]``.
> -    A ``str`` value will be normalized to ``List[str]``.
> +    The ``if`` member may be either a ``str`` or a dict.
>  
>      :forms:
> -      :sugared: ``Union[str, List[str]]``
> -      :canonical: ``List[str]``
> +      :canonical: ``Union[str, dict]``
>  
>      :param expr: The expression containing the ``if`` member to validate.
>      :param info: QAPI schema source file information.
> @@ -275,31 +273,45 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>      :raise QAPISemError:
>          When the "if" member fails validation, or when there are no
>          non-empty conditions.
> -    :return: None, ``expr`` is normalized in-place as needed.
> +    :return: None
>      """
>      ifcond = expr.get('if')
>      if ifcond is None:
>          return
>  
> -    if isinstance(ifcond, list):
> -        if not ifcond:
> -            raise QAPISemError(
> -                info, "'if' condition [] of %s is useless" % source)
> -    else:
> -        # Normalize to a list
> -        ifcond = expr['if'] = [ifcond]
> +    def _check_if(cond: Union[str, object]) -> None:
> +        if isinstance(cond, str):
> +            if not cond.strip():
> +                raise QAPISemError(
> +                    info,
> +                    "'if' condition '%s' of %s makes no sense"
> +                    % (cond, source))
> +            return
>  
> -    for elt in ifcond:
> -        if not isinstance(elt, str):
> +        if not isinstance(cond, dict):
>              raise QAPISemError(
>                  info,
> -                "'if' condition of %s must be a string or a list of strings"
> -                % source)
> -        if not elt.strip():
> +                "'if' condition of %s must be a string or a dict" % source)
> +        if len(cond) != 1:
>              raise QAPISemError(
>                  info,
> -                "'if' condition '%s' of %s makes no sense"
> -                % (elt, source))
> +                "'if' condition dict of %s must have one key: "
> +                "'all'" % source)

Not covered by the negative tests in tests/qapi-schema/.  Please
double-check that all new errors are covered.

> +        check_keys(cond, info, "'if' condition", [],
> +                   ["all"])
> +
> +        oper, operands = next(iter(cond.items()))
> +        if not operands:
> +            raise QAPISemError(
> +                info, "'if' condition [] of %s is useless" % source)
> +
> +        if oper in ("all") and not isinstance(operands, list):
> +            raise QAPISemError(
> +                info, "'%s' condition of %s must be a list" % (oper, source))
> +        for operand in operands:
> +            _check_if(operand)
> +
> +    _check_if(ifcond)
>  
>  

[...]



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 08/11] qapi: add 'any' condition
  2021-06-18 10:25 ` [PATCH v6 08/11] qapi: add 'any' condition marcandre.lureau
@ 2021-08-03 13:20   ` Markus Armbruster
  0 siblings, 0 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 13:20 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  tests/unit/test-qmp-cmds.c              | 1 +
>  scripts/qapi/common.py                  | 4 ++--
>  scripts/qapi/expr.py                    | 6 +++---
>  tests/qapi-schema/bad-if.err            | 2 +-
>  tests/qapi-schema/doc-good.json         | 3 ++-
>  tests/qapi-schema/doc-good.out          | 2 +-
>  tests/qapi-schema/doc-good.txt          | 3 ++-
>  tests/qapi-schema/enum-if-invalid.err   | 2 +-
>  tests/qapi-schema/qapi-schema-test.json | 7 ++++++-
>  tests/qapi-schema/qapi-schema-test.out  | 5 +++++
>  10 files changed, 24 insertions(+), 11 deletions(-)
>
> diff --git a/tests/unit/test-qmp-cmds.c b/tests/unit/test-qmp-cmds.c
> index 1b0b7d99df..83efa39720 100644
> --- a/tests/unit/test-qmp-cmds.c
> +++ b/tests/unit/test-qmp-cmds.c
> @@ -51,6 +51,7 @@ FeatureStruct1 *qmp_test_features0(bool has_fs0, FeatureStruct0 *fs0,
>                                     bool has_cfs1, CondFeatureStruct1 *cfs1,
>                                     bool has_cfs2, CondFeatureStruct2 *cfs2,
>                                     bool has_cfs3, CondFeatureStruct3 *cfs3,
> +                                   bool has_cfs4, CondFeatureStruct4 *cfs4,
>                                     Error **errp)
>  {
>      return g_new0(FeatureStruct1, 1);
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index 51463510c9..018d2f6996 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -207,7 +207,7 @@ def docgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
>          return ifcond
>  
>      oper, operands = next(iter(ifcond.items()))
> -    oper = {'all': ' and '}[oper]
> +    oper = {'all': ' and ', 'any': ' or '}[oper]
>      operands = [docgen_ifcond(o) for o in operands]
>      return '(' + oper.join(operands) + ')'
>  
> @@ -219,7 +219,7 @@ def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
>          return ifcond
>  
>      oper, operands = next(iter(ifcond.items()))
> -    oper = {'all': '&&'}[oper]
> +    oper = {'all': '&&', 'any': '||'}[oper]
>      operands = [cgen_ifcond(o) for o in operands]
>      return '(' + (') ' + oper + ' (').join(operands) + ')'
>  
> diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> index 3ee66c5f62..e10a803dc2 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -296,16 +296,16 @@ def _check_if(cond: Union[str, object]) -> None:
>              raise QAPISemError(
>                  info,
>                  "'if' condition dict of %s must have one key: "
> -                "'all'" % source)
> +                "'all' or 'any'" % source)
>          check_keys(cond, info, "'if' condition", [],
> -                   ["all"])
> +                   ["all", "any"])
>  
>          oper, operands = next(iter(cond.items()))
>          if not operands:
>              raise QAPISemError(
>                  info, "'if' condition [] of %s is useless" % source)
>  
> -        if oper in ("all") and not isinstance(operands, list):
> +        if oper in ("all", "any") and not isinstance(operands, list):
>              raise QAPISemError(
>                  info, "'%s' condition of %s must be a list" % (oper, source))
>          for operand in operands:
> diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err
> index 8278c49368..3624e79b0c 100644
> --- a/tests/qapi-schema/bad-if.err
> +++ b/tests/qapi-schema/bad-if.err
> @@ -1,3 +1,3 @@
>  bad-if.json: In struct 'TestIfStruct':
>  bad-if.json:2: 'if' condition has unknown key 'value'
> -Valid keys are 'all'.
> +Valid keys are 'all', 'any'.
> diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json
> index 25b1053e8a..a67d4d9467 100644
> --- a/tests/qapi-schema/doc-good.json
> +++ b/tests/qapi-schema/doc-good.json
> @@ -103,7 +103,8 @@
>    'features': [ 'union-feat1' ],
>    'base': 'Base',
>    'discriminator': 'base1',
> -  'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
> +  'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2',
> +                                        'if': { 'any': ['IFONE', 'IFTWO'] } } } }

Easier to read:

     'data': { 'one': 'Variant1',
               'two': { 'type': 'Variant2',
                        'if': { 'any': ['IFONE', 'IFTWO'] } } } }

>  
>  ##
>  # @SugaredUnion:
> diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> index 689d084f3a..c44c346ec8 100644
> --- a/tests/qapi-schema/doc-good.out
> +++ b/tests/qapi-schema/doc-good.out
> @@ -30,7 +30,7 @@ object Object
>      tag base1
>      case one: Variant1
>      case two: Variant2
> -        if IFTWO
> +        if OrderedDict([('any', ['IFONE', 'IFTWO'])])
>      feature union-feat1
>  object q_obj_Variant1-wrapper
>      member data: Variant1 optional=False
> diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
> index 4490108cb7..251e9b746c 100644
> --- a/tests/qapi-schema/doc-good.txt
> +++ b/tests/qapi-schema/doc-good.txt
> @@ -120,7 +120,8 @@ Members
>  
>  The members of "Base"
>  The members of "Variant1" when "base1" is ""one""
> -The members of "Variant2" when "base1" is ""two"" (**If: **"IFTWO")
> +The members of "Variant2" when "base1" is ""two"" (**If: **"(IFONE or
> +IFTWO)")
>  
>  Features
>  ~~~~~~~~
> diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err
> index df305cd79f..b96d94c48a 100644
> --- a/tests/qapi-schema/enum-if-invalid.err
> +++ b/tests/qapi-schema/enum-if-invalid.err
> @@ -1,3 +1,3 @@
>  enum-if-invalid.json: In enum 'TestIfEnum':
>  enum-if-invalid.json:2: 'if' condition has unknown key 'val'
> -Valid keys are 'all'.
> +Valid keys are 'all', 'any'.
> diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> index f2e0fff51f..252fd8cc86 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json
> @@ -290,6 +290,10 @@
>    'features': [ { 'name': 'feature1',
>                    'if': { 'all': [ 'defined(TEST_IF_COND_1)',
>                                     'defined(TEST_IF_COND_2)'] } } ] }
> +{ 'struct': 'CondFeatureStruct4',
> +  'data': { 'foo': 'int' },
> +  'features': [ { 'name': 'feature1', 'if': {'any': ['defined(TEST_IF_COND_1)',
> +                                                     'defined(TEST_IF_COND_2)'] } } ] }

Easier to read:

   { 'struct': 'CondFeatureStruct4',
     'data': { 'foo': 'int' },
     'features': [ { 'name': 'feature1',
                     'if': {'any': ['defined(TEST_IF_COND_1)',
                                    'defined(TEST_IF_COND_2)'] } } ] }

>  
>  { 'enum': 'FeatureEnum1',
>    'data': [ 'eins', 'zwei', 'drei' ],
> @@ -313,7 +317,8 @@
>              '*fs4': 'FeatureStruct4',
>              '*cfs1': 'CondFeatureStruct1',
>              '*cfs2': 'CondFeatureStruct2',
> -            '*cfs3': 'CondFeatureStruct3' },
> +            '*cfs3': 'CondFeatureStruct3',
> +            '*cfs4': 'CondFeatureStruct4' },
>    'returns': 'FeatureStruct1',
>    'features': [] }
>  
> diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
> index 6a1b3aa341..e5625f2542 100644
> --- a/tests/qapi-schema/qapi-schema-test.out
> +++ b/tests/qapi-schema/qapi-schema-test.out
> @@ -390,6 +390,10 @@ object CondFeatureStruct3
>      member foo: int optional=False
>      feature feature1
>          if OrderedDict([('all', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
> +object CondFeatureStruct4
> +    member foo: int optional=False
> +    feature feature1
> +        if OrderedDict([('any', ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])])
>  enum FeatureEnum1
>      member eins
>      member zwei
> @@ -417,6 +421,7 @@ object q_obj_test-features0-arg
>      member cfs1: CondFeatureStruct1 optional=True
>      member cfs2: CondFeatureStruct2 optional=True
>      member cfs3: CondFeatureStruct3 optional=True
> +    member cfs4: CondFeatureStruct4 optional=True
>  command test-features0 q_obj_test-features0-arg -> FeatureStruct1
>      gen=True success_response=True boxed=False oob=False preconfig=False
>  command test-command-features1 None -> None



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 09/11] qapi: convert 'if' C-expressions to the new syntax tree
  2021-06-18 10:25 ` [PATCH v6 09/11] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
@ 2021-08-03 13:22   ` Markus Armbruster
  0 siblings, 0 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 13:22 UTC (permalink / raw)
  To: marcandre.lureau
  Cc: Eric Blake, jsnow, qemu-devel, stefanha, Markus Armbruster

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Tested-by: John Snow <jsnow@redhat.com>

Possibly clearer:

    qapi: Use 'if': { 'any': ... } where appropriate

Reviewed-by: Markus Armbruster <armbru@redhat.com>



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 11/11] qapi: make 'if' condition strings simple identifiers
  2021-06-18 10:25 ` [PATCH v6 11/11] qapi: make 'if' condition strings simple identifiers marcandre.lureau
@ 2021-08-03 13:35   ` Markus Armbruster
  2021-08-04  8:22     ` Marc-André Lureau
  0 siblings, 1 reply; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 13:35 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Change the 'if' condition strings to be C-agnostic and be simple
> identifiers.

This is less general, and that's okay, we're doing it for a purpose.
But the commit message should mention / explain all this.

> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Tested-by: John Snow <jsnow@redhat.com>
> ---

[...]

> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index f8718e201b..0c718e43c9 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -218,7 +218,7 @@ def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
>      if not ifcond:
>          return ''
>      if isinstance(ifcond, str):
> -        return ifcond
> +        return 'defined(' + ifcond + ')'
>  
>      oper, operands = next(iter(ifcond.items()))
>      if oper == 'not':
> diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> index d2bd52c49f..d355cbc8c1 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -281,10 +281,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>  
>      def _check_if(cond: Union[str, object]) -> None:
>          if isinstance(cond, str):
> -            if not cond.strip():
> +            if not cond.isidentifier():

This accepts *Python* identifiers:

    $ python
    Python 3.9.6 (default, Jul 16 2021, 00:00:00)
    [...]
    >>> 'André'.isidentifier()
    True

These may or may not work for the languages we generate.  Wouldn't
restricting identifiers to something like /[A-Z][A-Z0-9_]*/ make more
sense?

>                  raise QAPISemError(
>                      info,
> -                    "'if' condition '%s' of %s makes no sense"
> +                    "'if' condition '%s' of %s is not a valid identifier"
>                      % (cond, source))
>              return
>  
> diff --git a/tests/qapi-schema/alternate-branch-if-invalid.err b/tests/qapi-schema/alternate-branch-if-invalid.err
> index d384929c51..03bad877a3 100644
> --- a/tests/qapi-schema/alternate-branch-if-invalid.err
> +++ b/tests/qapi-schema/alternate-branch-if-invalid.err
> @@ -1,2 +1,2 @@
>  alternate-branch-if-invalid.json: In alternate 'Alt':
> -alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' makes no sense
> +alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member 'branch' is not a valid identifier

[...]



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor
  2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (10 preceding siblings ...)
  2021-06-18 10:25 ` [PATCH v6 11/11] qapi: make 'if' condition strings simple identifiers marcandre.lureau
@ 2021-08-03 13:44 ` Markus Armbruster
  2021-08-04  8:25   ` Marc-André Lureau
  11 siblings, 1 reply; 39+ messages in thread
From: Markus Armbruster @ 2021-08-03 13:44 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: Eric Blake, jsnow, qemu-devel, stefanha

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Hi,
>
> This series makes the 'if' conditions less liberal, by formalizing a simple
> expression tree based on bare boolean logic of configure option identifiers.
>
> (this allows to express conditions in Rust in my QAPI-Rust PoC series)
>
> thanks

I like this overall.

The commit messages are rather terse in places.  I have a few questions,
I asked for a few minor tweaks, and I also noted a few possible
improvements that can be done on top.  I wonder whether we can drop
PATCH 04.

Let's discuss my findings, then decide whether we want a respin.

Thank you!



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 02/11] qapi: wrap Sequence[str] in an object
  2021-08-02  9:21   ` Markus Armbruster
@ 2021-08-03 17:55     ` John Snow
  2021-08-04  8:22     ` Marc-André Lureau
  1 sibling, 0 replies; 39+ messages in thread
From: John Snow @ 2021-08-03 17:55 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: marcandre.lureau, Eric Blake, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 38193 bytes --]

On Mon, Aug 2, 2021 at 5:21 AM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Except for the special casing assert in _make_implicit_object_type(),
> > which needs to handle schema objects, it's a mechanical change.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  docs/sphinx/qapidoc.py         | 10 +++---
> >  scripts/qapi/commands.py       |  4 +--
> >  scripts/qapi/events.py         |  5 +--
> >  scripts/qapi/gen.py            | 14 ++++----
> >  scripts/qapi/introspect.py     | 26 +++++++-------
> >  scripts/qapi/schema.py         | 66 +++++++++++++++++++++-------------
> >  scripts/qapi/types.py          | 33 ++++++++---------
> >  scripts/qapi/visit.py          | 23 ++++++------
> >  tests/qapi-schema/test-qapi.py |  4 +--
> >  9 files changed, 103 insertions(+), 82 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 87c67ab23f..0eac3308b2 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -116,7 +116,7 @@ def _nodes_for_ifcond(self, ifcond, with_if=True):
> >          the conditions are in literal-text and the commas are not.
> >          If with_if is False, we don't return the "(If: " and ")".
> >          """
> > -        condlist = intersperse([nodes.literal('', c) for c in ifcond],
> > +        condlist = intersperse([nodes.literal('', c) for c in
> ifcond.ifcond],
>
> Mechanical pattern #1: ifcond becomes ifcond.ifcond to peel off the new
> wrapper.  ifcond.ifcond is ugly, but almost all instances go away in
> this series.  I'm okay with the remainder.
>
> >                                 nodes.Text(', '))
> >          if not with_if:
> >              return condlist
> > @@ -139,7 +139,7 @@ def _nodes_for_one_member(self, member):
> >              term.append(nodes.literal('', member.type.doc_type()))
> >          if member.optional:
> >              term.append(nodes.Text(' (optional)'))
> > -        if member.ifcond:
> > +        if member.ifcond.ifcond:
> >              term.extend(self._nodes_for_ifcond(member.ifcond))
> >          return term
> >
> > @@ -154,7 +154,7 @@ def _nodes_for_variant_when(self, variants, variant):
> >                  nodes.literal('', variants.tag_member.name),
> >                  nodes.Text(' is '),
> >                  nodes.literal('', '"%s"' % variant.name)]
> > -        if variant.ifcond:
> > +        if variant.ifcond.ifcond:
> >              term.extend(self._nodes_for_ifcond(variant.ifcond))
> >          return term
> >
> > @@ -209,7 +209,7 @@ def _nodes_for_enum_values(self, doc):
> >          dlnode = nodes.definition_list()
> >          for section in doc.args.values():
> >              termtext = [nodes.literal('', section.member.name)]
> > -            if section.member.ifcond:
> > +            if section.member.ifcond.ifcond:
> >
> termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
> >              # TODO drop fallbacks when undocumented members are outlawed
> >              if section.text:
> > @@ -277,7 +277,7 @@ def _nodes_for_sections(self, doc):
> >      def _nodes_for_if_section(self, ifcond):
> >          """Return list of doctree nodes for the "If" section"""
> >          nodelist = []
> > -        if ifcond:
> > +        if ifcond.ifcond:
> >              snode = self._make_section('If')
> >              snode += nodes.paragraph(
> >                  '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
> > diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
> > index 0e13d51054..3654825968 100644
> > --- a/scripts/qapi/commands.py
> > +++ b/scripts/qapi/commands.py
> > @@ -17,7 +17,6 @@
> >      Dict,
> >      List,
> >      Optional,
> > -    Sequence,
> >      Set,
> >  )
> >
> > @@ -31,6 +30,7 @@
> >  from .schema import (
> >      QAPISchema,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaType,
> >  )
> > @@ -301,7 +301,7 @@ def visit_end(self) -> None:
> >      def visit_command(self,
> >                        name: str,
> >                        info: Optional[QAPISourceInfo],
> > -                      ifcond: Sequence[str],
> > +                      ifcond: QAPISchemaIfCond,
> >                        features: List[QAPISchemaFeature],
> >                        arg_type: Optional[QAPISchemaObjectType],
> >                        ret_type: Optional[QAPISchemaType],
>
> Mechanical pattern #2: Sequence[str] becomes QAPISchemaIfCond.  Also
> obvious import adjustments.
>
> > diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
> > index fee8c671e7..82475e84ec 100644
> > --- a/scripts/qapi/events.py
> > +++ b/scripts/qapi/events.py
> > @@ -12,7 +12,7 @@
> >  See the COPYING file in the top-level directory.
> >  """
> >
> > -from typing import List, Optional, Sequence
> > +from typing import List, Optional
> >
> >  from .common import c_enum_const, c_name, mcgen
> >  from .gen import QAPISchemaModularCVisitor, build_params, ifcontext
> > @@ -20,6 +20,7 @@
> >      QAPISchema,
> >      QAPISchemaEnumMember,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >  )
> >  from .source import QAPISourceInfo
> > @@ -227,7 +228,7 @@ def visit_end(self) -> None:
> >      def visit_event(self,
> >                      name: str,
> >                      info: Optional[QAPISourceInfo],
> > -                    ifcond: Sequence[str],
> > +                    ifcond: QAPISchemaIfCond,
> >                      features: List[QAPISchemaFeature],
> >                      arg_type: Optional[QAPISchemaObjectType],
> >                      boxed: bool) -> None:
> > diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
> > index 1fa503bdbd..1c5b190276 100644
> > --- a/scripts/qapi/gen.py
> > +++ b/scripts/qapi/gen.py
> > @@ -18,7 +18,6 @@
> >      Dict,
> >      Iterator,
> >      Optional,
> > -    Sequence,
> >      Tuple,
> >  )
> >
> > @@ -32,6 +31,7 @@
> >      mcgen,
> >  )
> >  from .schema import (
> > +    QAPISchemaIfCond,
> >      QAPISchemaModule,
> >      QAPISchemaObjectType,
> >      QAPISchemaVisitor,
> > @@ -85,7 +85,7 @@ def write(self, output_dir: str) -> None:
> >                  fp.write(text)
> >
> >
> > -def _wrap_ifcond(ifcond: Sequence[str], before: str, after: str) -> str:
> > +def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) ->
> str:
> >      if before == after:
> >          return after   # suppress empty #if ... #endif
> >
> > @@ -95,9 +95,9 @@ def _wrap_ifcond(ifcond: Sequence[str], before: str,
> after: str) -> str:
> >      if added[0] == '\n':
> >          out += '\n'
> >          added = added[1:]
> > -    out += gen_if(ifcond)
> > +    out += gen_if(ifcond.ifcond)
> >      out += added
> > -    out += gen_endif(ifcond)
> > +    out += gen_endif(ifcond.ifcond)
> >      return out
> >
> >
> > @@ -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[Sequence[str], str, str]] = None
> > +        self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] =
> None
> >
> > -    def start_if(self, ifcond: Sequence[str]) -> None:
> > +    def start_if(self, ifcond: QAPISchemaIfCond) -> None:
> >          assert self._start_if is None
> >          self._start_if = (ifcond, self._body, self._preamble)
> >
> > @@ -187,7 +187,7 @@ def _bottom(self) -> str:
> >
> >
> >  @contextmanager
> > -def ifcontext(ifcond: Sequence[str], *args: QAPIGenCCode) ->
> Iterator[None]:
> > +def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) ->
> Iterator[None]:
> >      """
> >      A with-statement context manager that wraps with `start_if()` /
> `end_if()`.
> >
> > diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
> > index 9a348ca2e5..77a8c33ad4 100644
> > --- a/scripts/qapi/introspect.py
> > +++ b/scripts/qapi/introspect.py
> > @@ -15,11 +15,9 @@
> >      Any,
> >      Dict,
> >      Generic,
> > -    Iterable,
> >      List,
> >      Optional,
> >      Sequence,
> > -    Tuple,
> >      TypeVar,
> >      Union,
> >  )
> > @@ -38,6 +36,7 @@
> >      QAPISchemaEntity,
> >      QAPISchemaEnumMember,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaObjectTypeMember,
> >      QAPISchemaType,
> > @@ -91,11 +90,11 @@ class Annotated(Generic[_ValueT]):
> >      """
> >      # TODO: Remove after Python 3.7 adds @dataclass:
> >      # pylint: disable=too-few-public-methods
> > -    def __init__(self, value: _ValueT, ifcond: Iterable[str],
> > +    def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond,
>
> Here, we have Iterable[str] instead of Sequence[str] before the patch.
> I figure use of Iterable was an attempt to be more general.  Minor
> variation of pattern #2.
>
> >                   comment: Optional[str] = None):
> >          self.value = value
> >          self.comment: Optional[str] = comment
> > -        self.ifcond: Tuple[str, ...] = tuple(ifcond)
> > +        self.ifcond = ifcond
>
> Hmm.  Here we change from Tuple, not from Sequence.
>
> I believe the next hunk has its only uses:
>
> >
> >
> >  def _tree_to_qlit(obj: JSONValue,
> > @@ -125,10 +124,10 @@ def indent(level: int) -> str:
> >          if obj.comment:
> >              ret += indent(level) + f"/* {obj.comment} */\n"
> >          if obj.ifcond:
> > -            ret += gen_if(obj.ifcond)
> > +            ret += gen_if(obj.ifcond.ifcond)
> >          ret += _tree_to_qlit(obj.value, level)
> >          if obj.ifcond:
> > -            ret += '\n' + gen_endif(obj.ifcond)
> > +            ret += '\n' + gen_endif(obj.ifcond.ifcond)
> >          return ret
> >
> >      ret = ''
>
> You update obj.ifcond to obj.ifcond.ifcond when used as argument of
> gen_if() and gen_endif().  This changes the argument from Tuple to
> Sequence.  Fine, because Tuple is a special Sequence.
>
> Digression: I don't (anymore) understand why we made self.ifcond Tuple.
> John, do you remember?
>
>
Not off the top of my head. Tuple[T] is a narrower type than Sequence[T],
and I may have had a scheme to stick to types that were not debatable to
make review go easier.

In general, I like to type things like properties/fields and return values
with the strongest possible types, and input parameters with the weakest
possible types. There are also differences on occasion between
Covariant/Contravariant/Invariant types for collections, and the use of
Sequence vs Tuple vs List can have an effect there depending on how that
structure is actually used.

As long as it type checks and keeps assertions to a reasonable minimum, I'm
pretty happy.

--js


> You don't update obj.ifcond when used as conditional.  The code now
> calls gen_if() and gen_endif() even for an empty Sequence.
>
> I believe this can't actually happen because check_if() rejects [] with
> "'if' condition [] of %s is useless".
>
> Still, the mechanical change should update to obj.ifcond even when used
> as conditional.
>
> Are there other, possibly not so harmless uses of values that change
> from Sequence to QAPISchemaIfCond the patch doesn't update?
>
> Or asked differently: how did you find what to update?
>
> > @@ -254,7 +253,7 @@ def _gen_features(features:
> Sequence[QAPISchemaFeature]
> >          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: QAPISchemaIfCond = QAPISchemaIfCond(),
>
> Mechanical pattern #3: wrap QAPISchemaIfCond around the Sequence.
>
> For readability, you simplify the pure wrap QAPISchemaIfCond(()) to just
> QAPISchemaIfCond(), relying on QAPISchemaIfCond.__init__()'s default.
>
> However, it's also a subtle change from () to [].  I believe that's
> okay, because we both are empty Sequences.  Might be worth a mention in
> the commit message.
>
> >                    features: Sequence[QAPISchemaFeature] = ()) -> None:
> >          """
> >          Build and append a SchemaInfo object to self._trees.
> > @@ -305,7 +304,7 @@ def visit_builtin_type(self, name: str, info:
> Optional[QAPISourceInfo],
> >          self._gen_tree(name, 'builtin', {'json-type': json_type})
> >
> >      def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo],
> > -                        ifcond: Sequence[str],
> > +                        ifcond: QAPISchemaIfCond,
> >                          features: List[QAPISchemaFeature],
> >                          members: List[QAPISchemaEnumMember],
> >                          prefix: Optional[str]) -> None:
> > @@ -316,14 +315,14 @@ def visit_enum_type(self, name: str, info:
> Optional[QAPISourceInfo],
> >          )
> >
> >      def visit_array_type(self, name: str, info:
> Optional[QAPISourceInfo],
> > -                         ifcond: Sequence[str],
> > +                         ifcond: QAPISchemaIfCond,
> >                           element_type: QAPISchemaType) -> None:
> >          element = self._use_type(element_type)
> >          self._gen_tree('[' + element + ']', 'array', {'element-type':
> element},
> >                         ifcond)
> >
> >      def visit_object_type_flat(self, name: str, info:
> Optional[QAPISourceInfo],
> > -                               ifcond: Sequence[str],
> > +                               ifcond: QAPISchemaIfCond,
> >                                 features: List[QAPISchemaFeature],
> >                                 members:
> List[QAPISchemaObjectTypeMember],
> >                                 variants: Optional[QAPISchemaVariants])
> -> None:
> > @@ -336,7 +335,7 @@ def visit_object_type_flat(self, name: str, info:
> Optional[QAPISourceInfo],
> >          self._gen_tree(name, 'object', obj, ifcond, features)
> >
> >      def visit_alternate_type(self, name: str, info:
> Optional[QAPISourceInfo],
> > -                             ifcond: Sequence[str],
> > +                             ifcond: QAPISchemaIfCond,
> >                               features: List[QAPISchemaFeature],
> >                               variants: QAPISchemaVariants) -> None:
> >          self._gen_tree(
> > @@ -348,7 +347,7 @@ def visit_alternate_type(self, name: str, info:
> Optional[QAPISourceInfo],
> >          )
> >
> >      def visit_command(self, name: str, info: Optional[QAPISourceInfo],
> > -                      ifcond: Sequence[str],
> > +                      ifcond: QAPISchemaIfCond,
> >                        features: List[QAPISchemaFeature],
> >                        arg_type: Optional[QAPISchemaObjectType],
> >                        ret_type: Optional[QAPISchemaType], gen: bool,
> > @@ -367,7 +366,8 @@ def visit_command(self, name: str, info:
> Optional[QAPISourceInfo],
> >          self._gen_tree(name, 'command', obj, ifcond, features)
> >
> >      def visit_event(self, name: str, info: Optional[QAPISourceInfo],
> > -                    ifcond: Sequence[str], features:
> List[QAPISchemaFeature],
> > +                    ifcond: QAPISchemaIfCond,
> > +                    features: List[QAPISchemaFeature],
> >                      arg_type: Optional[QAPISchemaObjectType],
> >                      boxed: bool) -> None:
> >          assert self._schema is not None
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index d1d27ff7ee..5e44164bd1 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -25,6 +25,11 @@
> >  from .parser import QAPISchemaParser
> >
> >
> > +class QAPISchemaIfCond:
> > +    def __init__(self, ifcond=None):
> > +        self.ifcond = ifcond or []
> > +
> > +
> >  class QAPISchemaEntity:
> >      meta: Optional[str] = None
> >
> > @@ -42,7 +47,7 @@ def __init__(self, name: str, info, doc, ifcond=None,
> features=None):
> >          # such place).
> >          self.info = info
> >          self.doc = doc
> > -        self._ifcond = ifcond or []
> > +        self._ifcond = ifcond or QAPISchemaIfCond()
>
> This is an instance of mechanical pattern #3 where we wrap the value
> without changing it.
>
> Before the patch we use both () and [] as "no conditions".  After the
> patch, we always use [], unless something passes another empty sequence
> to QAPISchemaIfCond.__init__(), which I don't think is the case.
> Observation, not a request.
>
>
> >          self.features = features or []
> >          self._checked = False
> >
> > @@ -78,6 +83,7 @@ def set_module(self, schema):
> >      @property
> >      def ifcond(self):
> >          assert self._checked
> > +        assert isinstance(self._ifcond, QAPISchemaIfCond)
> >          return self._ifcond
>
> Non-mechanical hunk.  The commit message claims to list them ("Except
> for ..., it's a mechanical change"), but it doesn't.  Easy enough to
> fix.
>
> >
> >      def is_implicit(self):
> > @@ -593,7 +599,7 @@ def check(self, schema, seen):
> >                      self.info,
> >                      "discriminator member '%s' of %s must not be
> optional"
> >                      % (self._tag_name, base))
> > -            if self.tag_member.ifcond:
> > +            if self.tag_member.ifcond.ifcond:
> >                  raise QAPISemError(
> >                      self.info,
> >                      "discriminator member '%s' of %s must not be
> conditional"
> > @@ -601,7 +607,7 @@ def check(self, schema, seen):
> >          else:                   # simple union
> >              assert isinstance(self.tag_member.type, QAPISchemaEnumType)
> >              assert not self.tag_member.optional
> > -            assert self.tag_member.ifcond == []
> > +            assert self.tag_member.ifcond.ifcond == []
> >          if self._tag_name:    # flat union
> >              # branches that are not explicitly covered get an empty type
> >              cases = {v.name for v in self.variants}
> > @@ -646,7 +652,7 @@ def __init__(self, name, info, ifcond=None):
> >          assert isinstance(name, str)
> >          self.name = name
> >          self.info = info
> > -        self.ifcond = ifcond or []
> > +        self.ifcond = ifcond or QAPISchemaIfCond()
> >          self.defined_in = None
> >
> >      def set_defined_in(self, name):
> > @@ -968,11 +974,13 @@ def _def_predefineds(self):
> >      def _make_features(self, features, info):
> >          if features is None:
> >              return []
> > -        return [QAPISchemaFeature(f['name'], info, f.get('if'))
> > +        return [QAPISchemaFeature(f['name'], info,
> > +                                  QAPISchemaIfCond(f.get('if')))
> >                  for f in features]
> >
> >      def _make_enum_members(self, values, info):
> > -        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
> > +        return [QAPISchemaEnumMember(v['name'], info,
> > +                                     QAPISchemaIfCond(v.get('if')))
> >                  for v in values]
> >
>
> Two more instances of pattern #3, only here we wrap values we get from
> the JSON parser.  These are either None or non-empty lists.
>
> >      def _make_implicit_enum_type(self, name, info, ifcond, values):
> > @@ -1008,7 +1016,10 @@ def _make_implicit_object_type(self, name, info,
> ifcond, role, members):
>            if typ:
>                # The implicit object type has multiple users.  This can
>                # happen only for simple unions' implicit wrapper types.
>                # Its ifcond should be the disjunction of its user's
>                # ifconds.  Not implemented.  Instead, we always pass the
>                # wrapped type's ifcond, which is trivially the same for all
>                # users.  It's also necessary for the wrapper to compile.
>                # But it's not tight: the disjunction need not imply it.  We
>                # may end up compiling useless wrapper types.
> >              # TODO kill simple unions or implement the disjunction
> >
> >              # pylint: disable=protected-access
> > -            assert (ifcond or []) == typ._ifcond
> > +            if isinstance(ifcond, QAPISchemaIfCond):
> > +                assert ifcond.ifcond == typ._ifcond.ifcond
> > +            else:
> > +                assert ifcond == typ._ifcond
> >          else:
> >              self._def_entity(QAPISchemaObjectType(
> >                  name, info, None, ifcond, None, None, members, None))
>
> This is the non-mechanical change mentioned in the commit message.
>
> Can you explain where the two cases come from?
>
> > @@ -1018,7 +1029,7 @@ def _def_enum_type(self, expr, info, doc):
> >          name = expr['enum']
> >          data = expr['data']
> >          prefix = expr.get('prefix')
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          self._def_entity(QAPISchemaEnumType(
> >              name, info, doc, ifcond, features,
> > @@ -1036,7 +1047,8 @@ def _make_member(self, name, typ, ifcond,
> features, info):
> >                                            self._make_features(features,
> info))
> >
> >      def _make_members(self, data, info):
> > -        return [self._make_member(key, value['type'], value.get('if'),
> > +        return [self._make_member(key, value['type'],
> > +                                  QAPISchemaIfCond(value.get('if')),
> >                                    value.get('features'), info)
> >                  for (key, value) in data.items()]
> >
> > @@ -1044,7 +1056,7 @@ def _def_struct_type(self, expr, info, doc):
> >          name = expr['struct']
> >          base = expr.get('base')
> >          data = expr['data']
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          self._def_entity(QAPISchemaObjectType(
> >              name, info, doc, ifcond, features, base,
> > @@ -1067,7 +1079,7 @@ def _def_union_type(self, expr, info, doc):
> >          name = expr['union']
> >          data = expr['data']
> >          base = expr.get('base')
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          tag_name = expr.get('discriminator')
> >          tag_member = None
> > @@ -1076,15 +1088,19 @@ def _def_union_type(self, expr, info, doc):
> >                  name, info, ifcond,
> >                  'base', self._make_members(base, info))
> >          if tag_name:
> > -            variants = [self._make_variant(key, value['type'],
> > -                                           value.get('if'), info)
> > -                        for (key, value) in data.items()]
> > +            variants = [
> > +                self._make_variant(key, value['type'],
> > +                                   QAPISchemaIfCond(value.get('if')),
> > +                                   info)
> > +                for (key, value) in data.items()]
> >              members = []
> >          else:
> > -            variants = [self._make_simple_variant(key, value['type'],
> > -                                                  value.get('if'), info)
> > -                        for (key, value) in data.items()]
> > -            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
> > +            variants = [
> > +                self._make_simple_variant(key, value['type'],
> > +
> QAPISchemaIfCond(value.get('if')),
> > +                                          info)
> > +                for (key, value) in data.items()]
> > +            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in
> variants]
> >              typ = self._make_implicit_enum_type(name, info, ifcond,
> enum)
> >              tag_member = QAPISchemaObjectTypeMember('type', info, typ,
> False)
> >              members = [tag_member]
> > @@ -1097,11 +1113,13 @@ def _def_union_type(self, expr, info, doc):
> >      def _def_alternate_type(self, expr, info, doc):
> >          name = expr['alternate']
> >          data = expr['data']
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> > -        variants = [self._make_variant(key, value['type'],
> value.get('if'),
> > -                                       info)
> > -                    for (key, value) in data.items()]
> > +        variants = [
> > +            self._make_variant(key, value['type'],
> > +                               QAPISchemaIfCond(value.get('if')),
> > +                               info)
> > +            for (key, value) in data.items()]
> >          tag_member = QAPISchemaObjectTypeMember('type', info, 'QType',
> False)
> >          self._def_entity(
> >              QAPISchemaAlternateType(name, info, doc, ifcond, features,
> > @@ -1118,7 +1136,7 @@ def _def_command(self, expr, info, doc):
> >          allow_oob = expr.get('allow-oob', False)
> >          allow_preconfig = expr.get('allow-preconfig', False)
> >          coroutine = expr.get('coroutine', False)
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          if isinstance(data, OrderedDict):
> >              data = self._make_implicit_object_type(
> > @@ -1137,7 +1155,7 @@ def _def_event(self, expr, info, doc):
> >          name = expr['event']
> >          data = expr.get('data')
> >          boxed = expr.get('boxed', False)
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          if isinstance(data, OrderedDict):
> >              data = self._make_implicit_object_type(
> > diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
> > index 20d572a23a..3673cf0f49 100644
> > --- a/scripts/qapi/types.py
> > +++ b/scripts/qapi/types.py
> > @@ -13,7 +13,7 @@
> >  # See the COPYING file in the top-level directory.
> >  """
> >
> > -from typing import List, Optional, Sequence
> > +from typing import List, Optional
> >
> >  from .common import (
> >      c_enum_const,
> > @@ -27,6 +27,7 @@
> >      QAPISchema,
> >      QAPISchemaEnumMember,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaObjectTypeMember,
> >      QAPISchemaType,
> > @@ -50,13 +51,13 @@ def gen_enum_lookup(name: str,
> >  ''',
> >                  c_name=c_name(name))
> >      for memb in members:
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          index = c_enum_const(name, memb.name, prefix)
> >          ret += mcgen('''
> >          [%(index)s] = "%(name)s",
> >  ''',
> >                       index=index, name=memb.name)
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >      },
> > @@ -80,12 +81,12 @@ def gen_enum(name: str,
> >                  c_name=c_name(name))
> >
> >      for memb in enum_members:
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          ret += mcgen('''
> >      %(c_enum)s,
> >  ''',
> >                       c_enum=c_enum_const(name, memb.name, prefix))
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >  } %(c_name)s;
> > @@ -125,7 +126,7 @@ def gen_array(name: str, element_type:
> QAPISchemaType) -> str:
> >  def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) ->
> str:
> >      ret = ''
> >      for memb in members:
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          if memb.optional:
> >              ret += mcgen('''
> >      bool has_%(c_name)s;
> > @@ -135,11 +136,11 @@ def gen_struct_members(members:
> List[QAPISchemaObjectTypeMember]) -> str:
> >      %(c_type)s %(c_name)s;
> >  ''',
> >                       c_type=memb.type.c_type(), c_name=c_name(memb.name
> ))
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >      return ret
> >
> >
> > -def gen_object(name: str, ifcond: Sequence[str],
> > +def gen_object(name: str, ifcond: QAPISchemaIfCond,
> >                 base: Optional[QAPISchemaObjectType],
> >                 members: List[QAPISchemaObjectTypeMember],
> >                 variants: Optional[QAPISchemaVariants]) -> str:
> > @@ -158,7 +159,7 @@ def gen_object(name: str, ifcond: Sequence[str],
> >      ret += mcgen('''
> >
> >  ''')
> > -    ret += gen_if(ifcond)
> > +    ret += gen_if(ifcond.ifcond)
> >      ret += mcgen('''
> >  struct %(c_name)s {
> >  ''',
> > @@ -192,7 +193,7 @@ def gen_object(name: str, ifcond: Sequence[str],
> >      ret += mcgen('''
> >  };
> >  ''')
> > -    ret += gen_endif(ifcond)
> > +    ret += gen_endif(ifcond.ifcond)
> >
> >      return ret
> >
> > @@ -219,13 +220,13 @@ def gen_variants(variants: QAPISchemaVariants) ->
> str:
> >      for var in variants.variants:
> >          if var.type.name == 'q_empty':
> >              continue
> > -        ret += gen_if(var.ifcond)
> > +        ret += gen_if(var.ifcond.ifcond)
> >          ret += mcgen('''
> >          %(c_type)s %(c_name)s;
> >  ''',
> >                       c_type=var.type.c_unboxed_type(),
> >                       c_name=c_name(var.name))
> > -        ret += gen_endif(var.ifcond)
> > +        ret += gen_endif(var.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >      } u;
> > @@ -307,7 +308,7 @@ def _gen_type_cleanup(self, name: str) -> None:
> >      def visit_enum_type(self,
> >                          name: str,
> >                          info: Optional[QAPISourceInfo],
> > -                        ifcond: Sequence[str],
> > +                        ifcond: QAPISchemaIfCond,
> >                          features: List[QAPISchemaFeature],
> >                          members: List[QAPISchemaEnumMember],
> >                          prefix: Optional[str]) -> None:
> > @@ -318,7 +319,7 @@ def visit_enum_type(self,
> >      def visit_array_type(self,
> >                           name: str,
> >                           info: Optional[QAPISourceInfo],
> > -                         ifcond: Sequence[str],
> > +                         ifcond: QAPISchemaIfCond,
> >                           element_type: QAPISchemaType) -> None:
> >          with ifcontext(ifcond, self._genh, self._genc):
> >              self._genh.preamble_add(gen_fwd_object_or_array(name))
> > @@ -328,7 +329,7 @@ def visit_array_type(self,
> >      def visit_object_type(self,
> >                            name: str,
> >                            info: Optional[QAPISourceInfo],
> > -                          ifcond: Sequence[str],
> > +                          ifcond: QAPISchemaIfCond,
> >                            features: List[QAPISchemaFeature],
> >                            base: Optional[QAPISchemaObjectType],
> >                            members: List[QAPISchemaObjectTypeMember],
> > @@ -351,7 +352,7 @@ def visit_object_type(self,
> >      def visit_alternate_type(self,
> >                               name: str,
> >                               info: Optional[QAPISourceInfo],
> > -                             ifcond: Sequence[str],
> > +                             ifcond: QAPISchemaIfCond,
> >                               features: List[QAPISchemaFeature],
> >                               variants: QAPISchemaVariants) -> None:
> >          with ifcontext(ifcond, self._genh):
> > diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
> > index 9e96f3c566..67721b2470 100644
> > --- a/scripts/qapi/visit.py
> > +++ b/scripts/qapi/visit.py
> > @@ -13,7 +13,7 @@
> >  See the COPYING file in the top-level directory.
> >  """
> >
> > -from typing import List, Optional, Sequence
> > +from typing import List, Optional
> >
> >  from .common import (
> >      c_enum_const,
> > @@ -29,6 +29,7 @@
> >      QAPISchemaEnumMember,
> >      QAPISchemaEnumType,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaObjectTypeMember,
> >      QAPISchemaType,
> > @@ -78,7 +79,7 @@ def gen_visit_object_members(name: str,
> >
> >      for memb in members:
> >          deprecated = 'deprecated' in [f.name for f in memb.features]
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          if memb.optional:
> >              ret += mcgen('''
> >      if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
> > @@ -111,7 +112,7 @@ def gen_visit_object_members(name: str,
> >              ret += mcgen('''
> >      }
> >  ''')
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >
> >      if variants:
> >          tag_member = variants.tag_member
> > @@ -125,7 +126,7 @@ def gen_visit_object_members(name: str,
> >          for var in variants.variants:
> >              case_str = c_enum_const(tag_member.type.name, var.name,
> >                                      tag_member.type.prefix)
> > -            ret += gen_if(var.ifcond)
> > +            ret += gen_if(var.ifcond.ifcond)
> >              if var.type.name == 'q_empty':
> >                  # valid variant and nothing to do
> >                  ret += mcgen('''
> > @@ -141,7 +142,7 @@ def gen_visit_object_members(name: str,
> >                               case=case_str,
> >                               c_type=var.type.c_name(), c_name=c_name(
> var.name))
> >
> > -            ret += gen_endif(var.ifcond)
> > +            ret += gen_endif(var.ifcond.ifcond)
> >          ret += mcgen('''
> >      default:
> >          abort();
> > @@ -227,7 +228,7 @@ def gen_visit_alternate(name: str, variants:
> QAPISchemaVariants) -> str:
> >                  c_name=c_name(name))
> >
> >      for var in variants.variants:
> > -        ret += gen_if(var.ifcond)
> > +        ret += gen_if(var.ifcond.ifcond)
> >          ret += mcgen('''
> >      case %(case)s:
> >  ''',
> > @@ -253,7 +254,7 @@ def gen_visit_alternate(name: str, variants:
> QAPISchemaVariants) -> str:
> >          ret += mcgen('''
> >          break;
> >  ''')
> > -        ret += gen_endif(var.ifcond)
> > +        ret += gen_endif(var.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >      case QTYPE_NONE:
> > @@ -352,7 +353,7 @@ def _begin_user_module(self, name: str) -> None:
> >      def visit_enum_type(self,
> >                          name: str,
> >                          info: Optional[QAPISourceInfo],
> > -                        ifcond: Sequence[str],
> > +                        ifcond: QAPISchemaIfCond,
> >                          features: List[QAPISchemaFeature],
> >                          members: List[QAPISchemaEnumMember],
> >                          prefix: Optional[str]) -> None:
> > @@ -363,7 +364,7 @@ def visit_enum_type(self,
> >      def visit_array_type(self,
> >                           name: str,
> >                           info: Optional[QAPISourceInfo],
> > -                         ifcond: Sequence[str],
> > +                         ifcond: QAPISchemaIfCond,
> >                           element_type: QAPISchemaType) -> None:
> >          with ifcontext(ifcond, self._genh, self._genc):
> >              self._genh.add(gen_visit_decl(name))
> > @@ -372,7 +373,7 @@ def visit_array_type(self,
> >      def visit_object_type(self,
> >                            name: str,
> >                            info: Optional[QAPISourceInfo],
> > -                          ifcond: Sequence[str],
> > +                          ifcond: QAPISchemaIfCond,
> >                            features: List[QAPISchemaFeature],
> >                            base: Optional[QAPISchemaObjectType],
> >                            members: List[QAPISchemaObjectTypeMember],
> > @@ -394,7 +395,7 @@ def visit_object_type(self,
> >      def visit_alternate_type(self,
> >                               name: str,
> >                               info: Optional[QAPISourceInfo],
> > -                             ifcond: Sequence[str],
> > +                             ifcond: QAPISchemaIfCond,
> >                               features: List[QAPISchemaFeature],
> >                               variants: QAPISchemaVariants) -> None:
> >          with ifcontext(ifcond, self._genh, self._genc):
> > diff --git a/tests/qapi-schema/test-qapi.py
> b/tests/qapi-schema/test-qapi.py
> > index f1c4deb9a5..7907b4ac3a 100755
> > --- a/tests/qapi-schema/test-qapi.py
> > +++ b/tests/qapi-schema/test-qapi.py
> > @@ -94,8 +94,8 @@ def _print_variants(variants):
> >
> >      @staticmethod
> >      def _print_if(ifcond, indent=4):
> > -        if ifcond:
> > -            print('%sif %s' % (' ' * indent, ifcond))
> > +        if ifcond.ifcond:
> > +            print('%sif %s' % (' ' * indent, ifcond.ifcond))
> >
> >      @classmethod
> >      def _print_features(cls, features, indent=4):
>
>

[-- Attachment #2: Type: text/html, Size: 49941 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 11/11] qapi: make 'if' condition strings simple identifiers
  2021-08-03 13:35   ` Markus Armbruster
@ 2021-08-04  8:22     ` Marc-André Lureau
  0 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:22 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 2855 bytes --]

On Tue, Aug 3, 2021 at 5:35 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Change the 'if' condition strings to be C-agnostic and be simple
> > identifiers.
>
> This is less general, and that's okay, we're doing it for a purpose.
> But the commit message should mention / explain all this.
>

ok


> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> > Tested-by: John Snow <jsnow@redhat.com>
> > ---
>
> [...]
>
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index f8718e201b..0c718e43c9 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -218,7 +218,7 @@ def cgen_ifcond(ifcond: Union[str, Dict[str, Any]])
> -> str:
> >      if not ifcond:
> >          return ''
> >      if isinstance(ifcond, str):
> > -        return ifcond
> > +        return 'defined(' + ifcond + ')'
> >
> >      oper, operands = next(iter(ifcond.items()))
> >      if oper == 'not':
> > diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> > index d2bd52c49f..d355cbc8c1 100644
> > --- a/scripts/qapi/expr.py
> > +++ b/scripts/qapi/expr.py
> > @@ -281,10 +281,10 @@ def check_if(expr: _JSONObject, info:
> QAPISourceInfo, source: str) -> None:
> >
> >      def _check_if(cond: Union[str, object]) -> None:
> >          if isinstance(cond, str):
> > -            if not cond.strip():
> > +            if not cond.isidentifier():
>
> This accepts *Python* identifiers:
>
>     $ python
>     Python 3.9.6 (default, Jul 16 2021, 00:00:00)
>     [...]
>     >>> 'André'.isidentifier()
>     True
>
> These may or may not work for the languages we generate.  Wouldn't
> restricting identifiers to something like /[A-Z][A-Z0-9_]*/ make more
> sense?
>
>
yes, works for me


> >                  raise QAPISemError(
> >                      info,
> > -                    "'if' condition '%s' of %s makes no sense"
> > +                    "'if' condition '%s' of %s is not a valid
> identifier"
> >                      % (cond, source))
> >              return
> >
> > diff --git a/tests/qapi-schema/alternate-branch-if-invalid.err
> b/tests/qapi-schema/alternate-branch-if-invalid.err
> > index d384929c51..03bad877a3 100644
> > --- a/tests/qapi-schema/alternate-branch-if-invalid.err
> > +++ b/tests/qapi-schema/alternate-branch-if-invalid.err
> > @@ -1,2 +1,2 @@
> >  alternate-branch-if-invalid.json: In alternate 'Alt':
> > -alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member
> 'branch' makes no sense
> > +alternate-branch-if-invalid.json:2: 'if' condition ' ' of 'data' member
> 'branch' is not a valid identifier
>
> [...]
>
>

[-- Attachment #2: Type: text/html, Size: 4483 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 02/11] qapi: wrap Sequence[str] in an object
  2021-08-02  9:21   ` Markus Armbruster
  2021-08-03 17:55     ` John Snow
@ 2021-08-04  8:22     ` Marc-André Lureau
  2021-08-05 10:44       ` Markus Armbruster
  2021-08-06 11:19       ` Markus Armbruster
  1 sibling, 2 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:22 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 37906 bytes --]

Hi

On Mon, Aug 2, 2021 at 1:21 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Except for the special casing assert in _make_implicit_object_type(),
> > which needs to handle schema objects, it's a mechanical change.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  docs/sphinx/qapidoc.py         | 10 +++---
> >  scripts/qapi/commands.py       |  4 +--
> >  scripts/qapi/events.py         |  5 +--
> >  scripts/qapi/gen.py            | 14 ++++----
> >  scripts/qapi/introspect.py     | 26 +++++++-------
> >  scripts/qapi/schema.py         | 66 +++++++++++++++++++++-------------
> >  scripts/qapi/types.py          | 33 ++++++++---------
> >  scripts/qapi/visit.py          | 23 ++++++------
> >  tests/qapi-schema/test-qapi.py |  4 +--
> >  9 files changed, 103 insertions(+), 82 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 87c67ab23f..0eac3308b2 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -116,7 +116,7 @@ def _nodes_for_ifcond(self, ifcond, with_if=True):
> >          the conditions are in literal-text and the commas are not.
> >          If with_if is False, we don't return the "(If: " and ")".
> >          """
> > -        condlist = intersperse([nodes.literal('', c) for c in ifcond],
> > +        condlist = intersperse([nodes.literal('', c) for c in
> ifcond.ifcond],
>
> Mechanical pattern #1: ifcond becomes ifcond.ifcond to peel off the new
> wrapper.  ifcond.ifcond is ugly, but almost all instances go away in
> this series.  I'm okay with the remainder.
>
> >                                 nodes.Text(', '))
> >          if not with_if:
> >              return condlist
> > @@ -139,7 +139,7 @@ def _nodes_for_one_member(self, member):
> >              term.append(nodes.literal('', member.type.doc_type()))
> >          if member.optional:
> >              term.append(nodes.Text(' (optional)'))
> > -        if member.ifcond:
> > +        if member.ifcond.ifcond:
> >              term.extend(self._nodes_for_ifcond(member.ifcond))
> >          return term
> >
> > @@ -154,7 +154,7 @@ def _nodes_for_variant_when(self, variants, variant):
> >                  nodes.literal('', variants.tag_member.name),
> >                  nodes.Text(' is '),
> >                  nodes.literal('', '"%s"' % variant.name)]
> > -        if variant.ifcond:
> > +        if variant.ifcond.ifcond:
> >              term.extend(self._nodes_for_ifcond(variant.ifcond))
> >          return term
> >
> > @@ -209,7 +209,7 @@ def _nodes_for_enum_values(self, doc):
> >          dlnode = nodes.definition_list()
> >          for section in doc.args.values():
> >              termtext = [nodes.literal('', section.member.name)]
> > -            if section.member.ifcond:
> > +            if section.member.ifcond.ifcond:
> >
> termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
> >              # TODO drop fallbacks when undocumented members are outlawed
> >              if section.text:
> > @@ -277,7 +277,7 @@ def _nodes_for_sections(self, doc):
> >      def _nodes_for_if_section(self, ifcond):
> >          """Return list of doctree nodes for the "If" section"""
> >          nodelist = []
> > -        if ifcond:
> > +        if ifcond.ifcond:
> >              snode = self._make_section('If')
> >              snode += nodes.paragraph(
> >                  '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
> > diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
> > index 0e13d51054..3654825968 100644
> > --- a/scripts/qapi/commands.py
> > +++ b/scripts/qapi/commands.py
> > @@ -17,7 +17,6 @@
> >      Dict,
> >      List,
> >      Optional,
> > -    Sequence,
> >      Set,
> >  )
> >
> > @@ -31,6 +30,7 @@
> >  from .schema import (
> >      QAPISchema,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaType,
> >  )
> > @@ -301,7 +301,7 @@ def visit_end(self) -> None:
> >      def visit_command(self,
> >                        name: str,
> >                        info: Optional[QAPISourceInfo],
> > -                      ifcond: Sequence[str],
> > +                      ifcond: QAPISchemaIfCond,
> >                        features: List[QAPISchemaFeature],
> >                        arg_type: Optional[QAPISchemaObjectType],
> >                        ret_type: Optional[QAPISchemaType],
>
> Mechanical pattern #2: Sequence[str] becomes QAPISchemaIfCond.  Also
> obvious import adjustments.
>
> > diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py
> > index fee8c671e7..82475e84ec 100644
> > --- a/scripts/qapi/events.py
> > +++ b/scripts/qapi/events.py
> > @@ -12,7 +12,7 @@
> >  See the COPYING file in the top-level directory.
> >  """
> >
> > -from typing import List, Optional, Sequence
> > +from typing import List, Optional
> >
> >  from .common import c_enum_const, c_name, mcgen
> >  from .gen import QAPISchemaModularCVisitor, build_params, ifcontext
> > @@ -20,6 +20,7 @@
> >      QAPISchema,
> >      QAPISchemaEnumMember,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >  )
> >  from .source import QAPISourceInfo
> > @@ -227,7 +228,7 @@ def visit_end(self) -> None:
> >      def visit_event(self,
> >                      name: str,
> >                      info: Optional[QAPISourceInfo],
> > -                    ifcond: Sequence[str],
> > +                    ifcond: QAPISchemaIfCond,
> >                      features: List[QAPISchemaFeature],
> >                      arg_type: Optional[QAPISchemaObjectType],
> >                      boxed: bool) -> None:
> > diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
> > index 1fa503bdbd..1c5b190276 100644
> > --- a/scripts/qapi/gen.py
> > +++ b/scripts/qapi/gen.py
> > @@ -18,7 +18,6 @@
> >      Dict,
> >      Iterator,
> >      Optional,
> > -    Sequence,
> >      Tuple,
> >  )
> >
> > @@ -32,6 +31,7 @@
> >      mcgen,
> >  )
> >  from .schema import (
> > +    QAPISchemaIfCond,
> >      QAPISchemaModule,
> >      QAPISchemaObjectType,
> >      QAPISchemaVisitor,
> > @@ -85,7 +85,7 @@ def write(self, output_dir: str) -> None:
> >                  fp.write(text)
> >
> >
> > -def _wrap_ifcond(ifcond: Sequence[str], before: str, after: str) -> str:
> > +def _wrap_ifcond(ifcond: QAPISchemaIfCond, before: str, after: str) ->
> str:
> >      if before == after:
> >          return after   # suppress empty #if ... #endif
> >
> > @@ -95,9 +95,9 @@ def _wrap_ifcond(ifcond: Sequence[str], before: str,
> after: str) -> str:
> >      if added[0] == '\n':
> >          out += '\n'
> >          added = added[1:]
> > -    out += gen_if(ifcond)
> > +    out += gen_if(ifcond.ifcond)
> >      out += added
> > -    out += gen_endif(ifcond)
> > +    out += gen_endif(ifcond.ifcond)
> >      return out
> >
> >
> > @@ -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[Sequence[str], str, str]] = None
> > +        self._start_if: Optional[Tuple[QAPISchemaIfCond, str, str]] =
> None
> >
> > -    def start_if(self, ifcond: Sequence[str]) -> None:
> > +    def start_if(self, ifcond: QAPISchemaIfCond) -> None:
> >          assert self._start_if is None
> >          self._start_if = (ifcond, self._body, self._preamble)
> >
> > @@ -187,7 +187,7 @@ def _bottom(self) -> str:
> >
> >
> >  @contextmanager
> > -def ifcontext(ifcond: Sequence[str], *args: QAPIGenCCode) ->
> Iterator[None]:
> > +def ifcontext(ifcond: QAPISchemaIfCond, *args: QAPIGenCCode) ->
> Iterator[None]:
> >      """
> >      A with-statement context manager that wraps with `start_if()` /
> `end_if()`.
> >
> > diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
> > index 9a348ca2e5..77a8c33ad4 100644
> > --- a/scripts/qapi/introspect.py
> > +++ b/scripts/qapi/introspect.py
> > @@ -15,11 +15,9 @@
> >      Any,
> >      Dict,
> >      Generic,
> > -    Iterable,
> >      List,
> >      Optional,
> >      Sequence,
> > -    Tuple,
> >      TypeVar,
> >      Union,
> >  )
> > @@ -38,6 +36,7 @@
> >      QAPISchemaEntity,
> >      QAPISchemaEnumMember,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaObjectTypeMember,
> >      QAPISchemaType,
> > @@ -91,11 +90,11 @@ class Annotated(Generic[_ValueT]):
> >      """
> >      # TODO: Remove after Python 3.7 adds @dataclass:
> >      # pylint: disable=too-few-public-methods
> > -    def __init__(self, value: _ValueT, ifcond: Iterable[str],
> > +    def __init__(self, value: _ValueT, ifcond: QAPISchemaIfCond,
>
> Here, we have Iterable[str] instead of Sequence[str] before the patch.
> I figure use of Iterable was an attempt to be more general.  Minor
> variation of pattern #2.
>
> >                   comment: Optional[str] = None):
> >          self.value = value
> >          self.comment: Optional[str] = comment
> > -        self.ifcond: Tuple[str, ...] = tuple(ifcond)
> > +        self.ifcond = ifcond
>
> Hmm.  Here we change from Tuple, not from Sequence.
>
> I believe the next hunk has its only uses:
>
> >
> >
> >  def _tree_to_qlit(obj: JSONValue,
> > @@ -125,10 +124,10 @@ def indent(level: int) -> str:
> >          if obj.comment:
> >              ret += indent(level) + f"/* {obj.comment} */\n"
> >          if obj.ifcond:
> > -            ret += gen_if(obj.ifcond)
> > +            ret += gen_if(obj.ifcond.ifcond)
> >          ret += _tree_to_qlit(obj.value, level)
> >          if obj.ifcond:
> > -            ret += '\n' + gen_endif(obj.ifcond)
> > +            ret += '\n' + gen_endif(obj.ifcond.ifcond)
> >          return ret
> >
> >      ret = ''
>
> You update obj.ifcond to obj.ifcond.ifcond when used as argument of
> gen_if() and gen_endif().  This changes the argument from Tuple to
> Sequence.  Fine, because Tuple is a special Sequence.
>
> Digression: I don't (anymore) understand why we made self.ifcond Tuple.
> John, do you remember?
>
> You don't update obj.ifcond when used as conditional.  The code now
> calls gen_if() and gen_endif() even for an empty Sequence.
>
> I believe this can't actually happen because check_if() rejects [] with
> "'if' condition [] of %s is useless".
>
> Still, the mechanical change should update to obj.ifcond even when used
> as conditional.
>
> Are there other, possibly not so harmless uses of values that change
> from Sequence to QAPISchemaIfCond the patch doesn't update?
>
> Or asked differently: how did you find what to update?
>

Eh, you are asking me for something I spent just a few hours a few times
over the last year. Sorry!

Most probably simply with code reading/grepping, linter and the test suite.


>
> > @@ -254,7 +253,7 @@ def _gen_features(features:
> Sequence[QAPISchemaFeature]
> >          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: QAPISchemaIfCond = QAPISchemaIfCond(),
>
> Mechanical pattern #3: wrap QAPISchemaIfCond around the Sequence.
>
> For readability, you simplify the pure wrap QAPISchemaIfCond(()) to just
> QAPISchemaIfCond(), relying on QAPISchemaIfCond.__init__()'s default.
>
> However, it's also a subtle change from () to [].  I believe that's
> okay, because we both are empty Sequences.  Might be worth a mention in
> the commit message.
>
> >                    features: Sequence[QAPISchemaFeature] = ()) -> None:
> >          """
> >          Build and append a SchemaInfo object to self._trees.
> > @@ -305,7 +304,7 @@ def visit_builtin_type(self, name: str, info:
> Optional[QAPISourceInfo],
> >          self._gen_tree(name, 'builtin', {'json-type': json_type})
> >
> >      def visit_enum_type(self, name: str, info: Optional[QAPISourceInfo],
> > -                        ifcond: Sequence[str],
> > +                        ifcond: QAPISchemaIfCond,
> >                          features: List[QAPISchemaFeature],
> >                          members: List[QAPISchemaEnumMember],
> >                          prefix: Optional[str]) -> None:
> > @@ -316,14 +315,14 @@ def visit_enum_type(self, name: str, info:
> Optional[QAPISourceInfo],
> >          )
> >
> >      def visit_array_type(self, name: str, info:
> Optional[QAPISourceInfo],
> > -                         ifcond: Sequence[str],
> > +                         ifcond: QAPISchemaIfCond,
> >                           element_type: QAPISchemaType) -> None:
> >          element = self._use_type(element_type)
> >          self._gen_tree('[' + element + ']', 'array', {'element-type':
> element},
> >                         ifcond)
> >
> >      def visit_object_type_flat(self, name: str, info:
> Optional[QAPISourceInfo],
> > -                               ifcond: Sequence[str],
> > +                               ifcond: QAPISchemaIfCond,
> >                                 features: List[QAPISchemaFeature],
> >                                 members:
> List[QAPISchemaObjectTypeMember],
> >                                 variants: Optional[QAPISchemaVariants])
> -> None:
> > @@ -336,7 +335,7 @@ def visit_object_type_flat(self, name: str, info:
> Optional[QAPISourceInfo],
> >          self._gen_tree(name, 'object', obj, ifcond, features)
> >
> >      def visit_alternate_type(self, name: str, info:
> Optional[QAPISourceInfo],
> > -                             ifcond: Sequence[str],
> > +                             ifcond: QAPISchemaIfCond,
> >                               features: List[QAPISchemaFeature],
> >                               variants: QAPISchemaVariants) -> None:
> >          self._gen_tree(
> > @@ -348,7 +347,7 @@ def visit_alternate_type(self, name: str, info:
> Optional[QAPISourceInfo],
> >          )
> >
> >      def visit_command(self, name: str, info: Optional[QAPISourceInfo],
> > -                      ifcond: Sequence[str],
> > +                      ifcond: QAPISchemaIfCond,
> >                        features: List[QAPISchemaFeature],
> >                        arg_type: Optional[QAPISchemaObjectType],
> >                        ret_type: Optional[QAPISchemaType], gen: bool,
> > @@ -367,7 +366,8 @@ def visit_command(self, name: str, info:
> Optional[QAPISourceInfo],
> >          self._gen_tree(name, 'command', obj, ifcond, features)
> >
> >      def visit_event(self, name: str, info: Optional[QAPISourceInfo],
> > -                    ifcond: Sequence[str], features:
> List[QAPISchemaFeature],
> > +                    ifcond: QAPISchemaIfCond,
> > +                    features: List[QAPISchemaFeature],
> >                      arg_type: Optional[QAPISchemaObjectType],
> >                      boxed: bool) -> None:
> >          assert self._schema is not None
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index d1d27ff7ee..5e44164bd1 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -25,6 +25,11 @@
> >  from .parser import QAPISchemaParser
> >
> >
> > +class QAPISchemaIfCond:
> > +    def __init__(self, ifcond=None):
> > +        self.ifcond = ifcond or []
> > +
> > +
> >  class QAPISchemaEntity:
> >      meta: Optional[str] = None
> >
> > @@ -42,7 +47,7 @@ def __init__(self, name: str, info, doc, ifcond=None,
> features=None):
> >          # such place).
> >          self.info = info
> >          self.doc = doc
> > -        self._ifcond = ifcond or []
> > +        self._ifcond = ifcond or QAPISchemaIfCond()
>
> This is an instance of mechanical pattern #3 where we wrap the value
> without changing it.
>
> Before the patch we use both () and [] as "no conditions".  After the
> patch, we always use [], unless something passes another empty sequence
> to QAPISchemaIfCond.__init__(), which I don't think is the case.
> Observation, not a request.
>
>
> >          self.features = features or []
> >          self._checked = False
> >
> > @@ -78,6 +83,7 @@ def set_module(self, schema):
> >      @property
> >      def ifcond(self):
> >          assert self._checked
> > +        assert isinstance(self._ifcond, QAPISchemaIfCond)
> >          return self._ifcond
>
> Non-mechanical hunk.  The commit message claims to list them ("Except
> for ..., it's a mechanical change"), but it doesn't.  Easy enough to
> fix.
>

dropped


> >
> >      def is_implicit(self):
> > @@ -593,7 +599,7 @@ def check(self, schema, seen):
> >                      self.info,
> >                      "discriminator member '%s' of %s must not be
> optional"
> >                      % (self._tag_name, base))
> > -            if self.tag_member.ifcond:
> > +            if self.tag_member.ifcond.ifcond:
> >                  raise QAPISemError(
> >                      self.info,
> >                      "discriminator member '%s' of %s must not be
> conditional"
> > @@ -601,7 +607,7 @@ def check(self, schema, seen):
> >          else:                   # simple union
> >              assert isinstance(self.tag_member.type, QAPISchemaEnumType)
> >              assert not self.tag_member.optional
> > -            assert self.tag_member.ifcond == []
> > +            assert self.tag_member.ifcond.ifcond == []
> >          if self._tag_name:    # flat union
> >              # branches that are not explicitly covered get an empty type
> >              cases = {v.name for v in self.variants}
> > @@ -646,7 +652,7 @@ def __init__(self, name, info, ifcond=None):
> >          assert isinstance(name, str)
> >          self.name = name
> >          self.info = info
> > -        self.ifcond = ifcond or []
> > +        self.ifcond = ifcond or QAPISchemaIfCond()
> >          self.defined_in = None
> >
> >      def set_defined_in(self, name):
> > @@ -968,11 +974,13 @@ def _def_predefineds(self):
> >      def _make_features(self, features, info):
> >          if features is None:
> >              return []
> > -        return [QAPISchemaFeature(f['name'], info, f.get('if'))
> > +        return [QAPISchemaFeature(f['name'], info,
> > +                                  QAPISchemaIfCond(f.get('if')))
> >                  for f in features]
> >
> >      def _make_enum_members(self, values, info):
> > -        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
> > +        return [QAPISchemaEnumMember(v['name'], info,
> > +                                     QAPISchemaIfCond(v.get('if')))
> >                  for v in values]
> >
>
> Two more instances of pattern #3, only here we wrap values we get from
> the JSON parser.  These are either None or non-empty lists.
>
> >      def _make_implicit_enum_type(self, name, info, ifcond, values):
> > @@ -1008,7 +1016,10 @@ def _make_implicit_object_type(self, name, info,
> ifcond, role, members):
>            if typ:
>                # The implicit object type has multiple users.  This can
>                # happen only for simple unions' implicit wrapper types.
>                # Its ifcond should be the disjunction of its user's
>                # ifconds.  Not implemented.  Instead, we always pass the
>                # wrapped type's ifcond, which is trivially the same for all
>                # users.  It's also necessary for the wrapper to compile.
>                # But it's not tight: the disjunction need not imply it.  We
>                # may end up compiling useless wrapper types.
> >              # TODO kill simple unions or implement the disjunction
> >
> >              # pylint: disable=protected-access
> > -            assert (ifcond or []) == typ._ifcond
> > +            if isinstance(ifcond, QAPISchemaIfCond):
> > +                assert ifcond.ifcond == typ._ifcond.ifcond
> > +            else:
> > +                assert ifcond == typ._ifcond
> >          else:
> >              self._def_entity(QAPISchemaObjectType(
> >                  name, info, None, ifcond, None, None, members, None))
>
> This is the non-mechanical change mentioned in the commit message.
>
> Can you explain where the two cases come from?
>
>
_make_simple_variant() calls _make_implicit_object_type() with
self.lookup_type(typ).

I think it could instead call with the ._ifcond value.

To be done after?



> > @@ -1018,7 +1029,7 @@ def _def_enum_type(self, expr, info, doc):
> >          name = expr['enum']
> >          data = expr['data']
> >          prefix = expr.get('prefix')
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          self._def_entity(QAPISchemaEnumType(
> >              name, info, doc, ifcond, features,
> > @@ -1036,7 +1047,8 @@ def _make_member(self, name, typ, ifcond,
> features, info):
> >                                            self._make_features(features,
> info))
> >
> >      def _make_members(self, data, info):
> > -        return [self._make_member(key, value['type'], value.get('if'),
> > +        return [self._make_member(key, value['type'],
> > +                                  QAPISchemaIfCond(value.get('if')),
> >                                    value.get('features'), info)
> >                  for (key, value) in data.items()]
> >
> > @@ -1044,7 +1056,7 @@ def _def_struct_type(self, expr, info, doc):
> >          name = expr['struct']
> >          base = expr.get('base')
> >          data = expr['data']
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          self._def_entity(QAPISchemaObjectType(
> >              name, info, doc, ifcond, features, base,
> > @@ -1067,7 +1079,7 @@ def _def_union_type(self, expr, info, doc):
> >          name = expr['union']
> >          data = expr['data']
> >          base = expr.get('base')
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          tag_name = expr.get('discriminator')
> >          tag_member = None
> > @@ -1076,15 +1088,19 @@ def _def_union_type(self, expr, info, doc):
> >                  name, info, ifcond,
> >                  'base', self._make_members(base, info))
> >          if tag_name:
> > -            variants = [self._make_variant(key, value['type'],
> > -                                           value.get('if'), info)
> > -                        for (key, value) in data.items()]
> > +            variants = [
> > +                self._make_variant(key, value['type'],
> > +                                   QAPISchemaIfCond(value.get('if')),
> > +                                   info)
> > +                for (key, value) in data.items()]
> >              members = []
> >          else:
> > -            variants = [self._make_simple_variant(key, value['type'],
> > -                                                  value.get('if'), info)
> > -                        for (key, value) in data.items()]
> > -            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
> > +            variants = [
> > +                self._make_simple_variant(key, value['type'],
> > +
> QAPISchemaIfCond(value.get('if')),
> > +                                          info)
> > +                for (key, value) in data.items()]
> > +            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in
> variants]
> >              typ = self._make_implicit_enum_type(name, info, ifcond,
> enum)
> >              tag_member = QAPISchemaObjectTypeMember('type', info, typ,
> False)
> >              members = [tag_member]
> > @@ -1097,11 +1113,13 @@ def _def_union_type(self, expr, info, doc):
> >      def _def_alternate_type(self, expr, info, doc):
> >          name = expr['alternate']
> >          data = expr['data']
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> > -        variants = [self._make_variant(key, value['type'],
> value.get('if'),
> > -                                       info)
> > -                    for (key, value) in data.items()]
> > +        variants = [
> > +            self._make_variant(key, value['type'],
> > +                               QAPISchemaIfCond(value.get('if')),
> > +                               info)
> > +            for (key, value) in data.items()]
> >          tag_member = QAPISchemaObjectTypeMember('type', info, 'QType',
> False)
> >          self._def_entity(
> >              QAPISchemaAlternateType(name, info, doc, ifcond, features,
> > @@ -1118,7 +1136,7 @@ def _def_command(self, expr, info, doc):
> >          allow_oob = expr.get('allow-oob', False)
> >          allow_preconfig = expr.get('allow-preconfig', False)
> >          coroutine = expr.get('coroutine', False)
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          if isinstance(data, OrderedDict):
> >              data = self._make_implicit_object_type(
> > @@ -1137,7 +1155,7 @@ def _def_event(self, expr, info, doc):
> >          name = expr['event']
> >          data = expr.get('data')
> >          boxed = expr.get('boxed', False)
> > -        ifcond = expr.get('if')
> > +        ifcond = QAPISchemaIfCond(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          if isinstance(data, OrderedDict):
> >              data = self._make_implicit_object_type(
> > diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
> > index 20d572a23a..3673cf0f49 100644
> > --- a/scripts/qapi/types.py
> > +++ b/scripts/qapi/types.py
> > @@ -13,7 +13,7 @@
> >  # See the COPYING file in the top-level directory.
> >  """
> >
> > -from typing import List, Optional, Sequence
> > +from typing import List, Optional
> >
> >  from .common import (
> >      c_enum_const,
> > @@ -27,6 +27,7 @@
> >      QAPISchema,
> >      QAPISchemaEnumMember,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaObjectTypeMember,
> >      QAPISchemaType,
> > @@ -50,13 +51,13 @@ def gen_enum_lookup(name: str,
> >  ''',
> >                  c_name=c_name(name))
> >      for memb in members:
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          index = c_enum_const(name, memb.name, prefix)
> >          ret += mcgen('''
> >          [%(index)s] = "%(name)s",
> >  ''',
> >                       index=index, name=memb.name)
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >      },
> > @@ -80,12 +81,12 @@ def gen_enum(name: str,
> >                  c_name=c_name(name))
> >
> >      for memb in enum_members:
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          ret += mcgen('''
> >      %(c_enum)s,
> >  ''',
> >                       c_enum=c_enum_const(name, memb.name, prefix))
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >  } %(c_name)s;
> > @@ -125,7 +126,7 @@ def gen_array(name: str, element_type:
> QAPISchemaType) -> str:
> >  def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) ->
> str:
> >      ret = ''
> >      for memb in members:
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          if memb.optional:
> >              ret += mcgen('''
> >      bool has_%(c_name)s;
> > @@ -135,11 +136,11 @@ def gen_struct_members(members:
> List[QAPISchemaObjectTypeMember]) -> str:
> >      %(c_type)s %(c_name)s;
> >  ''',
> >                       c_type=memb.type.c_type(), c_name=c_name(memb.name
> ))
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >      return ret
> >
> >
> > -def gen_object(name: str, ifcond: Sequence[str],
> > +def gen_object(name: str, ifcond: QAPISchemaIfCond,
> >                 base: Optional[QAPISchemaObjectType],
> >                 members: List[QAPISchemaObjectTypeMember],
> >                 variants: Optional[QAPISchemaVariants]) -> str:
> > @@ -158,7 +159,7 @@ def gen_object(name: str, ifcond: Sequence[str],
> >      ret += mcgen('''
> >
> >  ''')
> > -    ret += gen_if(ifcond)
> > +    ret += gen_if(ifcond.ifcond)
> >      ret += mcgen('''
> >  struct %(c_name)s {
> >  ''',
> > @@ -192,7 +193,7 @@ def gen_object(name: str, ifcond: Sequence[str],
> >      ret += mcgen('''
> >  };
> >  ''')
> > -    ret += gen_endif(ifcond)
> > +    ret += gen_endif(ifcond.ifcond)
> >
> >      return ret
> >
> > @@ -219,13 +220,13 @@ def gen_variants(variants: QAPISchemaVariants) ->
> str:
> >      for var in variants.variants:
> >          if var.type.name == 'q_empty':
> >              continue
> > -        ret += gen_if(var.ifcond)
> > +        ret += gen_if(var.ifcond.ifcond)
> >          ret += mcgen('''
> >          %(c_type)s %(c_name)s;
> >  ''',
> >                       c_type=var.type.c_unboxed_type(),
> >                       c_name=c_name(var.name))
> > -        ret += gen_endif(var.ifcond)
> > +        ret += gen_endif(var.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >      } u;
> > @@ -307,7 +308,7 @@ def _gen_type_cleanup(self, name: str) -> None:
> >      def visit_enum_type(self,
> >                          name: str,
> >                          info: Optional[QAPISourceInfo],
> > -                        ifcond: Sequence[str],
> > +                        ifcond: QAPISchemaIfCond,
> >                          features: List[QAPISchemaFeature],
> >                          members: List[QAPISchemaEnumMember],
> >                          prefix: Optional[str]) -> None:
> > @@ -318,7 +319,7 @@ def visit_enum_type(self,
> >      def visit_array_type(self,
> >                           name: str,
> >                           info: Optional[QAPISourceInfo],
> > -                         ifcond: Sequence[str],
> > +                         ifcond: QAPISchemaIfCond,
> >                           element_type: QAPISchemaType) -> None:
> >          with ifcontext(ifcond, self._genh, self._genc):
> >              self._genh.preamble_add(gen_fwd_object_or_array(name))
> > @@ -328,7 +329,7 @@ def visit_array_type(self,
> >      def visit_object_type(self,
> >                            name: str,
> >                            info: Optional[QAPISourceInfo],
> > -                          ifcond: Sequence[str],
> > +                          ifcond: QAPISchemaIfCond,
> >                            features: List[QAPISchemaFeature],
> >                            base: Optional[QAPISchemaObjectType],
> >                            members: List[QAPISchemaObjectTypeMember],
> > @@ -351,7 +352,7 @@ def visit_object_type(self,
> >      def visit_alternate_type(self,
> >                               name: str,
> >                               info: Optional[QAPISourceInfo],
> > -                             ifcond: Sequence[str],
> > +                             ifcond: QAPISchemaIfCond,
> >                               features: List[QAPISchemaFeature],
> >                               variants: QAPISchemaVariants) -> None:
> >          with ifcontext(ifcond, self._genh):
> > diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
> > index 9e96f3c566..67721b2470 100644
> > --- a/scripts/qapi/visit.py
> > +++ b/scripts/qapi/visit.py
> > @@ -13,7 +13,7 @@
> >  See the COPYING file in the top-level directory.
> >  """
> >
> > -from typing import List, Optional, Sequence
> > +from typing import List, Optional
> >
> >  from .common import (
> >      c_enum_const,
> > @@ -29,6 +29,7 @@
> >      QAPISchemaEnumMember,
> >      QAPISchemaEnumType,
> >      QAPISchemaFeature,
> > +    QAPISchemaIfCond,
> >      QAPISchemaObjectType,
> >      QAPISchemaObjectTypeMember,
> >      QAPISchemaType,
> > @@ -78,7 +79,7 @@ def gen_visit_object_members(name: str,
> >
> >      for memb in members:
> >          deprecated = 'deprecated' in [f.name for f in memb.features]
> > -        ret += gen_if(memb.ifcond)
> > +        ret += gen_if(memb.ifcond.ifcond)
> >          if memb.optional:
> >              ret += mcgen('''
> >      if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
> > @@ -111,7 +112,7 @@ def gen_visit_object_members(name: str,
> >              ret += mcgen('''
> >      }
> >  ''')
> > -        ret += gen_endif(memb.ifcond)
> > +        ret += gen_endif(memb.ifcond.ifcond)
> >
> >      if variants:
> >          tag_member = variants.tag_member
> > @@ -125,7 +126,7 @@ def gen_visit_object_members(name: str,
> >          for var in variants.variants:
> >              case_str = c_enum_const(tag_member.type.name, var.name,
> >                                      tag_member.type.prefix)
> > -            ret += gen_if(var.ifcond)
> > +            ret += gen_if(var.ifcond.ifcond)
> >              if var.type.name == 'q_empty':
> >                  # valid variant and nothing to do
> >                  ret += mcgen('''
> > @@ -141,7 +142,7 @@ def gen_visit_object_members(name: str,
> >                               case=case_str,
> >                               c_type=var.type.c_name(), c_name=c_name(
> var.name))
> >
> > -            ret += gen_endif(var.ifcond)
> > +            ret += gen_endif(var.ifcond.ifcond)
> >          ret += mcgen('''
> >      default:
> >          abort();
> > @@ -227,7 +228,7 @@ def gen_visit_alternate(name: str, variants:
> QAPISchemaVariants) -> str:
> >                  c_name=c_name(name))
> >
> >      for var in variants.variants:
> > -        ret += gen_if(var.ifcond)
> > +        ret += gen_if(var.ifcond.ifcond)
> >          ret += mcgen('''
> >      case %(case)s:
> >  ''',
> > @@ -253,7 +254,7 @@ def gen_visit_alternate(name: str, variants:
> QAPISchemaVariants) -> str:
> >          ret += mcgen('''
> >          break;
> >  ''')
> > -        ret += gen_endif(var.ifcond)
> > +        ret += gen_endif(var.ifcond.ifcond)
> >
> >      ret += mcgen('''
> >      case QTYPE_NONE:
> > @@ -352,7 +353,7 @@ def _begin_user_module(self, name: str) -> None:
> >      def visit_enum_type(self,
> >                          name: str,
> >                          info: Optional[QAPISourceInfo],
> > -                        ifcond: Sequence[str],
> > +                        ifcond: QAPISchemaIfCond,
> >                          features: List[QAPISchemaFeature],
> >                          members: List[QAPISchemaEnumMember],
> >                          prefix: Optional[str]) -> None:
> > @@ -363,7 +364,7 @@ def visit_enum_type(self,
> >      def visit_array_type(self,
> >                           name: str,
> >                           info: Optional[QAPISourceInfo],
> > -                         ifcond: Sequence[str],
> > +                         ifcond: QAPISchemaIfCond,
> >                           element_type: QAPISchemaType) -> None:
> >          with ifcontext(ifcond, self._genh, self._genc):
> >              self._genh.add(gen_visit_decl(name))
> > @@ -372,7 +373,7 @@ def visit_array_type(self,
> >      def visit_object_type(self,
> >                            name: str,
> >                            info: Optional[QAPISourceInfo],
> > -                          ifcond: Sequence[str],
> > +                          ifcond: QAPISchemaIfCond,
> >                            features: List[QAPISchemaFeature],
> >                            base: Optional[QAPISchemaObjectType],
> >                            members: List[QAPISchemaObjectTypeMember],
> > @@ -394,7 +395,7 @@ def visit_object_type(self,
> >      def visit_alternate_type(self,
> >                               name: str,
> >                               info: Optional[QAPISourceInfo],
> > -                             ifcond: Sequence[str],
> > +                             ifcond: QAPISchemaIfCond,
> >                               features: List[QAPISchemaFeature],
> >                               variants: QAPISchemaVariants) -> None:
> >          with ifcontext(ifcond, self._genh, self._genc):
> > diff --git a/tests/qapi-schema/test-qapi.py
> b/tests/qapi-schema/test-qapi.py
> > index f1c4deb9a5..7907b4ac3a 100755
> > --- a/tests/qapi-schema/test-qapi.py
> > +++ b/tests/qapi-schema/test-qapi.py
> > @@ -94,8 +94,8 @@ def _print_variants(variants):
> >
> >      @staticmethod
> >      def _print_if(ifcond, indent=4):
> > -        if ifcond:
> > -            print('%sif %s' % (' ' * indent, ifcond))
> > +        if ifcond.ifcond:
> > +            print('%sif %s' % (' ' * indent, ifcond.ifcond))
> >
> >      @classmethod
> >      def _print_features(cls, features, indent=4):
>
>

[-- Attachment #2: Type: text/html, Size: 50011 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 03/11] qapi: add QAPISchemaIfCond.is_present()
  2021-08-02  9:52   ` Markus Armbruster
@ 2021-08-04  8:22     ` Marc-André Lureau
  0 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:22 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 5359 bytes --]

On Mon, Aug 2, 2021 at 1:52 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  docs/sphinx/qapidoc.py         | 8 ++++----
> >  scripts/qapi/schema.py         | 7 +++++--
> >  tests/qapi-schema/test-qapi.py | 2 +-
> >  3 files changed, 10 insertions(+), 7 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 0eac3308b2..511520f33f 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -139,7 +139,7 @@ def _nodes_for_one_member(self, member):
> >              term.append(nodes.literal('', member.type.doc_type()))
> >          if member.optional:
> >              term.append(nodes.Text(' (optional)'))
> > -        if member.ifcond.ifcond:
> > +        if member.ifcond.is_present():
> >              term.extend(self._nodes_for_ifcond(member.ifcond))
> >          return term
> >
> > @@ -154,7 +154,7 @@ def _nodes_for_variant_when(self, variants, variant):
> >                  nodes.literal('', variants.tag_member.name),
> >                  nodes.Text(' is '),
> >                  nodes.literal('', '"%s"' % variant.name)]
> > -        if variant.ifcond.ifcond:
> > +        if variant.ifcond.is_present():
> >              term.extend(self._nodes_for_ifcond(variant.ifcond))
> >          return term
> >
> > @@ -209,7 +209,7 @@ def _nodes_for_enum_values(self, doc):
> >          dlnode = nodes.definition_list()
> >          for section in doc.args.values():
> >              termtext = [nodes.literal('', section.member.name)]
> > -            if section.member.ifcond.ifcond:
> > +            if section.member.ifcond.is_present():
> >
> termtext.extend(self._nodes_for_ifcond(section.member.ifcond))
> >              # TODO drop fallbacks when undocumented members are outlawed
> >              if section.text:
> > @@ -277,7 +277,7 @@ def _nodes_for_sections(self, doc):
> >      def _nodes_for_if_section(self, ifcond):
> >          """Return list of doctree nodes for the "If" section"""
> >          nodelist = []
> > -        if ifcond.ifcond:
> > +        if ifcond.is_present():
> >              snode = self._make_section('If')
> >              snode += nodes.paragraph(
> >                  '', '', *self._nodes_for_ifcond(ifcond, with_if=False)
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index 5e44164bd1..e3bd8f8720 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -29,6 +29,9 @@ class QAPISchemaIfCond:
> >      def __init__(self, ifcond=None):
> >          self.ifcond = ifcond or []
> >
> > +    def is_present(self):
> > +        return bool(self.ifcond)
> > +
> >
> >  class QAPISchemaEntity:
> >      meta: Optional[str] = None
> > @@ -599,7 +602,7 @@ def check(self, schema, seen):
> >                      self.info,
> >                      "discriminator member '%s' of %s must not be
> optional"
> >                      % (self._tag_name, base))
> > -            if self.tag_member.ifcond.ifcond:
> > +            if self.tag_member.ifcond.is_present():
> >                  raise QAPISemError(
> >                      self.info,
> >                      "discriminator member '%s' of %s must not be
> conditional"
> > @@ -607,7 +610,7 @@ def check(self, schema, seen):
> >          else:                   # simple union
> >              assert isinstance(self.tag_member.type, QAPISchemaEnumType)
> >              assert not self.tag_member.optional
> > -            assert self.tag_member.ifcond.ifcond == []
> > +            assert not self.tag_member.ifcond.is_present()
> >          if self._tag_name:    # flat union
> >              # branches that are not explicitly covered get an empty type
> >              cases = {v.name for v in self.variants}
> > diff --git a/tests/qapi-schema/test-qapi.py
> b/tests/qapi-schema/test-qapi.py
> > index 7907b4ac3a..c92be2d086 100755
> > --- a/tests/qapi-schema/test-qapi.py
> > +++ b/tests/qapi-schema/test-qapi.py
> > @@ -94,7 +94,7 @@ def _print_variants(variants):
> >
> >      @staticmethod
> >      def _print_if(ifcond, indent=4):
> > -        if ifcond.ifcond:
> > +        if ifcond.is_present():
> >              print('%sif %s' % (' ' * indent, ifcond.ifcond))
> >
> >      @classmethod
>
> In introspect.py:
>
>         if obj.ifcond:
>             ret += gen_if(obj.ifcond.ifcond)
>         ret += _tree_to_qlit(obj.value, level)
>         if obj.ifcond:
>             ret += '\n' + gen_endif(obj.ifcond.ifcond)
>
> I believe the previous patch should change it to
>
>         if obj.ifcond.ifcond:
>             ret += gen_if(obj.ifcond.ifcond)
>         ret += _tree_to_qlit(obj.value, level)
>         if obj.ifcond.ifcond:
>             ret += '\n' + gen_endif(obj.ifcond.ifcond)
>
> and this one to
>
>         if obj.ifcond.is_present():
>             ret += gen_if(obj.ifcond.ifcond)
>         ret += _tree_to_qlit(obj.value, level)
>         if obj.ifcond.is_present():
>             ret += '\n' + gen_endif(obj.ifcond.ifcond)
>

done


> Other than that:
> Reviewed-by: Markus Armbruster <armbru@redhat.com>
>
>

[-- Attachment #2: Type: text/html, Size: 7735 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond
  2021-08-02 10:41   ` Markus Armbruster
@ 2021-08-04  8:22     ` Marc-André Lureau
  0 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:22 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 3584 bytes --]

Hi

On Mon, Aug 2, 2021 at 2:41 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Instead of lowering the expression back to its original form, and having
> > to convert it again, special-case the 'if' condition to be pre-built.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  scripts/qapi/schema.py | 11 ++++++++---
> >  1 file changed, 8 insertions(+), 3 deletions(-)
> >
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index e3bd8f8720..c35fa3bf51 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -982,8 +982,13 @@ def _make_features(self, features, info):
> >                  for f in features]
> >
> >      def _make_enum_members(self, values, info):
> > -        return [QAPISchemaEnumMember(v['name'], info,
> > -                                     QAPISchemaIfCond(v.get('if')))
> > +        def _get_if(v):
> > +            ifcond = v.get('if')
> > +            if isinstance(ifcond, QAPISchemaIfCond):
> > +                return ifcond
> > +            else:
> > +                return QAPISchemaIfCond(ifcond)
> > +        return [QAPISchemaEnumMember(v['name'], info, _get_if(v))
> >                  for v in values]
> >
> >      def _make_implicit_enum_type(self, name, info, ifcond, values):
> > @@ -1103,7 +1108,7 @@ def _def_union_type(self, expr, info, doc):
> >
> QAPISchemaIfCond(value.get('if')),
> >                                            info)
> >                  for (key, value) in data.items()]
> > -            enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in
> variants]
> > +            enum = [{'name': v.name, 'if': v.ifcond} for v in variants]
> >              typ = self._make_implicit_enum_type(name, info, ifcond,
> enum)
> >              tag_member = QAPISchemaObjectTypeMember('type', info, typ,
> False)
> >              members = [tag_member]
>
> I'm afraid I don't like this one.
>
> Mapping from QAPISchemaIfCond back to the AST happens to be easy with
> the current data structures, but you're right, it's not nice.
>
> Stuffing the QAPISchemaIfCond into the AST is (in my opinion) worse:
> it's a layering violation.
>
> Let's take a step back and review what needs to be done here:
>
>     for each simple union branch:
>         create a simple variant
>         create an implicit enum member
>     and
>         collect the variants in a list
>         collect the enum members in a list
>
> The code splits this work.  It first creates the list of variants from
> the AST's simple union branches in @data:
>
>             variants = [
>                 self._make_simple_variant(key, value['type'],
>
> QAPISchemaIfCond(value.get('if')),
>                                           info)
>                 for (key, value) in data.items()]
>
> It then creates the list of enum of enum members from the list of
> variants, *not* from the AST:
>
>             enum = [{'name': v.name, 'if': v.ifcond.ifcond} for v in
> variants]
>
> This dots into the QAPISchemaVariant.  Your patch makes this dotting
> less deep.
>
> Two solutions I'd dislike less:
>
> 1. Create the enum members from the AST, too.
>
> 2. Do nothing, and bank on the eventual removal of simple unions.
>
> Minimizing ripple effects on the remainder of the series is of course a
> concern.
>
>
I dropped the patch, I didn't realize it wasn't necessary anymore.

[-- Attachment #2: Type: text/html, Size: 5117 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen()
  2021-08-02 14:46   ` Markus Armbruster
  2021-08-03 11:19     ` Markus Armbruster
@ 2021-08-04  8:23     ` Marc-André Lureau
  1 sibling, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:23 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 13065 bytes --]

Hi

On Mon, Aug 2, 2021 at 6:46 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Instead of building prepocessor conditions from a list of string, use
> > the result generated from QAPISchemaIfCond.cgen() and hide the
> > implementation details.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Please mention that the patch changes generated code.  See below for
> details.
>

I'll add

    Note: this patch introduces a minor regression, generating a redundant
    pair of parenthesis. This is fixed in a later patch in this
    series ("qapi: replace if condition list with dict [..]")


> > ---
> >  scripts/qapi/common.py     | 35 ++++++++++++++++++++++-------------
> >  scripts/qapi/gen.py        |  4 ++--
> >  scripts/qapi/introspect.py |  4 ++--
> >  scripts/qapi/schema.py     |  5 ++++-
> >  scripts/qapi/types.py      | 20 ++++++++++----------
> >  scripts/qapi/visit.py      | 12 ++++++------
> >  6 files changed, 46 insertions(+), 34 deletions(-)
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index 6ad1eeb61d..ba9fe14e4b 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -12,7 +12,12 @@
> >  # See the COPYING file in the top-level directory.
> >
> >  import re
> > -from typing import Match, Optional, Sequence
> > +from typing import (
> > +    List,
> > +    Match,
> > +    Optional,
> > +    Union,
> > +)
> >
> >
> >  #: Magic string that gets removed along with all space to its right.
> > @@ -194,22 +199,26 @@ def guardend(name: str) -> str:
> >                   name=c_fname(name).upper())
> >
> >
> > -def gen_if(ifcond: Sequence[str]) -> str:
> > -    ret = ''
> > -    for ifc in ifcond:
> > -        ret += mcgen('''
> > +def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> > +    if not ifcond:
> > +        return ''
> > +    return '(' + ') && ('.join(ifcond) + ')'
> > +
> > +
> > +def gen_if(cond: str) -> str:
> > +    if not cond:
> > +        return ''
> > +    return mcgen('''
> >  #if %(cond)s
> > -''', cond=ifc)
> > -    return ret
> > +''', cond=cond)
> >
> >
> > -def gen_endif(ifcond: Sequence[str]) -> str:
> > -    ret = ''
> > -    for ifc in reversed(ifcond):
> > -        ret += mcgen('''
> > +def gen_endif(cond: str) -> str:
> > +    if not cond:
> > +        return ''
> > +    return mcgen('''
> >  #endif /* %(cond)s */
> > -''', cond=ifc)
> > -    return ret
> > +''', cond=cond)
> >
> >
>
> This patch does three things:
>
> (1) Change gen_if(), gen_endif() to always generate a single #if,
>     #endif.  This enables:
>
> (2) Factor cgen_ifcond() out of gen_if() and gen_endif()
>
> (3) Lift the call of cgen_ifcond() into into gen_if()'s, gen_endif()'s
>     callers.
>
> I'd split the patch.  This is *not* a demand.
>
> The motivation for (3) is unclear.  Is it so gen_if() doesn't depend on
> QAPISchemaIfCond?
>
> Step (1) affects the generated code.  When @ifcond is [COND1, COND2, ...],
> gen_if()'s value changes from
>
>     #if COND1
>     #if COND2
>     ...
>
> to
>
>     #if (COND1) && (COND2)
>
> Example: in tests/test-qapi-introspect.c
>
>     @@ -259,11 +257,9 @@ const QLitObject test_qmp_schema_qlit =
>          QLIT_QDICT(((QLitDictEntry[]) {
>              { "arg-type", QLIT_QSTR("1"), },
>              { "features", QLIT_QLIST(((QLitObject[]) {
>     -#if defined(TEST_IF_COND_1)
>     -#if defined(TEST_IF_COND_2)
>     +#if (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2))
>                  QLIT_QSTR("feature1"),
>     -#endif /* defined(TEST_IF_COND_2) */
>     -#endif /* defined(TEST_IF_COND_1) */
>     +#endif /* (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2)) */
>                  {}
>              })), },
>              { "meta-type", QLIT_QSTR("command"), },
>
> The common case: when it's just [COND], the value changes from
>
>     #if COND
>
> to
>
>     #if (COND)
>
> which is a bit ugly.
>
> Example: in qapi-types-block-export.c
>
>     @@ -76,7 +76,7 @@ const QEnumLookup FuseExportAllowOther_l
>          .size = FUSE_EXPORT_ALLOW_OTHER__MAX
>      };
>
>     -#if defined(CONFIG_FUSE)
>     +#if (defined(CONFIG_FUSE))
>      void qapi_free_BlockExportOptionsFuse(BlockExportOptionsFuse *obj)
>      {
>          Visitor *v;
>     @@ -89,7 +89,7 @@ void qapi_free_BlockExportOptionsFuse(Bl
>          visit_type_BlockExportOptionsFuse(v, NULL, &obj, NULL);
>          visit_free(v);
>      }
>     -#endif /* defined(CONFIG_FUSE) */
>     +#endif /* (defined(CONFIG_FUSE)) */
>
>      void qapi_free_NbdServerAddOptions(NbdServerAddOptions *obj)
>      {
>
> Avoiding the redundant pair of parenthesis takes another special case.
> Let's do it.
>
> >  def must_match(pattern: str, string: str) -> Match[str]:
> > diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
> > index 1c5b190276..51a597a025 100644
> > --- a/scripts/qapi/gen.py
> > +++ b/scripts/qapi/gen.py
> > @@ -95,9 +95,9 @@ def _wrap_ifcond(ifcond: QAPISchemaIfCond, before:
> str, after: str) -> str:
> >      if added[0] == '\n':
> >          out += '\n'
> >          added = added[1:]
> > -    out += gen_if(ifcond.ifcond)
> > +    out += gen_if(ifcond.cgen())
> >      out += added
> > -    out += gen_endif(ifcond.ifcond)
> > +    out += gen_endif(ifcond.cgen())
> >      return out
> >
> >
>
> > diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
> > index 77a8c33ad4..474b08fd4d 100644
> > --- a/scripts/qapi/introspect.py
> > +++ b/scripts/qapi/introspect.py
> > @@ -124,10 +124,10 @@ def indent(level: int) -> str:
> >          if obj.comment:
> >              ret += indent(level) + f"/* {obj.comment} */\n"
> >          if obj.ifcond:
> > -            ret += gen_if(obj.ifcond.ifcond)
> > +            ret += gen_if(obj.ifcond.cgen())
> >          ret += _tree_to_qlit(obj.value, level)
> >          if obj.ifcond:
> > -            ret += '\n' + gen_endif(obj.ifcond.ifcond)
> > +            ret += '\n' + gen_endif(obj.ifcond.cgen())
> >          return ret
> >
> >      ret = ''
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index c35fa3bf51..70120f0dcc 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -19,7 +19,7 @@
> >  import re
> >  from typing import Optional
> >
> > -from .common import POINTER_SUFFIX, c_name
> > +from .common import POINTER_SUFFIX, c_name, cgen_ifcond
> >  from .error import QAPIError, QAPISemError, QAPISourceError
> >  from .expr import check_exprs
> >  from .parser import QAPISchemaParser
> > @@ -29,6 +29,9 @@ class QAPISchemaIfCond:
> >      def __init__(self, ifcond=None):
> >          self.ifcond = ifcond or []
> >
> > +    def cgen(self):
> > +        return cgen_ifcond(self.ifcond)
> > +
>
> As far as I can tell, this is only ever used like
>
>        gen_if(obj.ifcond.cgen())
>
> and
>
>        gen_endif(obj.ifcond.cgen())
>
> Wouldn't
>
>        obj.ifcond.gen_if()
>
> and
>
>        obj.ifcond.gen_endif()
>
> be neater?
>

Yes


> Not a demand, since we can get there on top if we want to.
>
> >      def is_present(self):
> >          return bool(self.ifcond)
> >
> > diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
> > index 3673cf0f49..db9ff95bd1 100644
> > --- a/scripts/qapi/types.py
> > +++ b/scripts/qapi/types.py
> > @@ -51,13 +51,13 @@ def gen_enum_lookup(name: str,
> >  ''',
> >                  c_name=c_name(name))
> >      for memb in members:
> > -        ret += gen_if(memb.ifcond.ifcond)
> > +        ret += gen_if(memb.ifcond.cgen())
> >          index = c_enum_const(name, memb.name, prefix)
> >          ret += mcgen('''
> >          [%(index)s] = "%(name)s",
> >  ''',
> >                       index=index, name=memb.name)
> > -        ret += gen_endif(memb.ifcond.ifcond)
> > +        ret += gen_endif(memb.ifcond.cgen())
> >
> >      ret += mcgen('''
> >      },
> > @@ -81,12 +81,12 @@ def gen_enum(name: str,
> >                  c_name=c_name(name))
> >
> >      for memb in enum_members:
> > -        ret += gen_if(memb.ifcond.ifcond)
> > +        ret += gen_if(memb.ifcond.cgen())
> >          ret += mcgen('''
> >      %(c_enum)s,
> >  ''',
> >                       c_enum=c_enum_const(name, memb.name, prefix))
> > -        ret += gen_endif(memb.ifcond.ifcond)
> > +        ret += gen_endif(memb.ifcond.cgen())
> >
> >      ret += mcgen('''
> >  } %(c_name)s;
> > @@ -126,7 +126,7 @@ def gen_array(name: str, element_type:
> QAPISchemaType) -> str:
> >  def gen_struct_members(members: List[QAPISchemaObjectTypeMember]) ->
> str:
> >      ret = ''
> >      for memb in members:
> > -        ret += gen_if(memb.ifcond.ifcond)
> > +        ret += gen_if(memb.ifcond.cgen())
> >          if memb.optional:
> >              ret += mcgen('''
> >      bool has_%(c_name)s;
> > @@ -136,7 +136,7 @@ def gen_struct_members(members:
> List[QAPISchemaObjectTypeMember]) -> str:
> >      %(c_type)s %(c_name)s;
> >  ''',
> >                       c_type=memb.type.c_type(), c_name=c_name(memb.name
> ))
> > -        ret += gen_endif(memb.ifcond.ifcond)
> > +        ret += gen_endif(memb.ifcond.cgen())
> >      return ret
> >
> >
> > @@ -159,7 +159,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
> >      ret += mcgen('''
> >
> >  ''')
> > -    ret += gen_if(ifcond.ifcond)
> > +    ret += gen_if(ifcond.cgen())
> >      ret += mcgen('''
> >  struct %(c_name)s {
> >  ''',
> > @@ -193,7 +193,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
> >      ret += mcgen('''
> >  };
> >  ''')
> > -    ret += gen_endif(ifcond.ifcond)
> > +    ret += gen_endif(ifcond.cgen())
> >
> >      return ret
> >
> > @@ -220,13 +220,13 @@ def gen_variants(variants: QAPISchemaVariants) ->
> str:
> >      for var in variants.variants:
> >          if var.type.name == 'q_empty':
> >              continue
> > -        ret += gen_if(var.ifcond.ifcond)
> > +        ret += gen_if(var.ifcond.cgen())
> >          ret += mcgen('''
> >          %(c_type)s %(c_name)s;
> >  ''',
> >                       c_type=var.type.c_unboxed_type(),
> >                       c_name=c_name(var.name))
> > -        ret += gen_endif(var.ifcond.ifcond)
> > +        ret += gen_endif(var.ifcond.cgen())
> >
> >      ret += mcgen('''
> >      } u;
> > diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
> > index 67721b2470..56ea516399 100644
> > --- a/scripts/qapi/visit.py
> > +++ b/scripts/qapi/visit.py
> > @@ -79,7 +79,7 @@ def gen_visit_object_members(name: str,
> >
> >      for memb in members:
> >          deprecated = 'deprecated' in [f.name for f in memb.features]
> > -        ret += gen_if(memb.ifcond.ifcond)
> > +        ret += gen_if(memb.ifcond.cgen())
> >          if memb.optional:
> >              ret += mcgen('''
> >      if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
> > @@ -112,7 +112,7 @@ def gen_visit_object_members(name: str,
> >              ret += mcgen('''
> >      }
> >  ''')
> > -        ret += gen_endif(memb.ifcond.ifcond)
> > +        ret += gen_endif(memb.ifcond.cgen())
> >
> >      if variants:
> >          tag_member = variants.tag_member
> > @@ -126,7 +126,7 @@ def gen_visit_object_members(name: str,
> >          for var in variants.variants:
> >              case_str = c_enum_const(tag_member.type.name, var.name,
> >                                      tag_member.type.prefix)
> > -            ret += gen_if(var.ifcond.ifcond)
> > +            ret += gen_if(var.ifcond.cgen())
> >              if var.type.name == 'q_empty':
> >                  # valid variant and nothing to do
> >                  ret += mcgen('''
> > @@ -142,7 +142,7 @@ def gen_visit_object_members(name: str,
> >                               case=case_str,
> >                               c_type=var.type.c_name(), c_name=c_name(
> var.name))
> >
> > -            ret += gen_endif(var.ifcond.ifcond)
> > +            ret += gen_endif(var.ifcond.cgen())
> >          ret += mcgen('''
> >      default:
> >          abort();
> > @@ -228,7 +228,7 @@ def gen_visit_alternate(name: str, variants:
> QAPISchemaVariants) -> str:
> >                  c_name=c_name(name))
> >
> >      for var in variants.variants:
> > -        ret += gen_if(var.ifcond.ifcond)
> > +        ret += gen_if(var.ifcond.cgen())
> >          ret += mcgen('''
> >      case %(case)s:
> >  ''',
> > @@ -254,7 +254,7 @@ def gen_visit_alternate(name: str, variants:
> QAPISchemaVariants) -> str:
> >          ret += mcgen('''
> >          break;
> >  ''')
> > -        ret += gen_endif(var.ifcond.ifcond)
> > +        ret += gen_endif(var.ifcond.cgen())
> >
> >      ret += mcgen('''
> >      case QTYPE_NONE:
>
>

[-- Attachment #2: Type: text/html, Size: 18001 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 06/11] qapidoc: introduce QAPISchemaIfCond.docgen()
  2021-08-02 15:47   ` Markus Armbruster
@ 2021-08-04  8:23     ` Marc-André Lureau
  0 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:23 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 933 bytes --]

Hi

On Mon, Aug 2, 2021 at 7:47 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Instead of building the condition documentation from a list of string,
> > use the result generated from QAPISchemaIfCond.docgen().
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> I suspect this changes the generated documentation, similar to how the
> previous patch changes generated code.  True?  If yes, can you show us
> how?
>


This changes the generated documentation from:
- COND1, COND2... (where COND1, COND2 are Literal nodes, and ',' is Text)
to:
- COND1 and COND2 (the whole string as a Literal node)

This will allow us to generate more complex conditions in the following
patches, such as "(COND1 and COND2) or COND3".

Adding back the formatting is left to the wish list.

[-- Attachment #2: Type: text/html, Size: 1637 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]}
  2021-08-03 13:05   ` Markus Armbruster
@ 2021-08-04  8:23     ` Marc-André Lureau
  2021-08-05 15:11     ` John Snow
  1 sibling, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:23 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 14137 bytes --]

Hi

On Tue, Aug 3, 2021 at 5:05 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Replace the simple list sugar form with a recursive structure that will
> > accept other operators in the following commits (all, any or not).
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  scripts/qapi/common.py                        | 23 +++++--
> >  scripts/qapi/expr.py                          | 52 ++++++++++------
> >  scripts/qapi/schema.py                        |  2 +-
> >  tests/qapi-schema/bad-if-empty-list.json      |  2 +-
> >  tests/qapi-schema/bad-if-list.json            |  2 +-
> >  tests/qapi-schema/bad-if.err                  |  3 +-
> >  tests/qapi-schema/doc-good.json               |  3 +-
> >  tests/qapi-schema/doc-good.out                | 13 ++--
> >  tests/qapi-schema/doc-good.txt                |  6 ++
> >  tests/qapi-schema/enum-if-invalid.err         |  3 +-
> >  tests/qapi-schema/features-if-invalid.err     |  2 +-
> >  tests/qapi-schema/qapi-schema-test.json       | 25 ++++----
> >  tests/qapi-schema/qapi-schema-test.out        | 62 +++++++++----------
> >  .../qapi-schema/struct-member-if-invalid.err  |  2 +-
> >  .../qapi-schema/union-branch-if-invalid.json  |  2 +-
> >  15 files changed, 119 insertions(+), 83 deletions(-)
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index 5181a0f167..51463510c9 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -13,7 +13,8 @@
> >
> >  import re
> >  from typing import (
> > -    List,
> > +    Any,
> > +    Dict,
> >      Match,
> >      Optional,
> >      Union,
> > @@ -199,16 +200,28 @@ def guardend(name: str) -> str:
> >                   name=c_fname(name).upper())
> >
> >
> > -def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> > +def docgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
>
> Uh, why do you swap cgen_ifcond() and docgen_ifcond()?  Accident?
>

Yes, fixed


> >      if not ifcond:
> >          return ''
> > -    return '(' + ') && ('.join(ifcond) + ')'
> > +    if isinstance(ifcond, str):
> > +        return ifcond
> >
> > +    oper, operands = next(iter(ifcond.items()))
> > +    oper = {'all': ' and '}[oper]
> > +    operands = [docgen_ifcond(o) for o in operands]
> > +    return '(' + oper.join(operands) + ')'
>
> What a nice review speedbump you buried here...
>
> The whole block boils down to the much less exciting
>
>        operands = [docgen_ifcond(o) for o in ifcond['all']]
>        return '(' + ' and '.join(operands) + ')'
>
> Peeking ahead, I understand that you did it this way here so you can
> extend it trivially there.  Matter of taste; what counts is the final
> result and minimizing reviewer WTFs/minute along the way.
>
> Since the WTFs/minute is a done deed now, what remains is the final
> result, which I expect to review shortly.  But please try a bit harder
> to be boring next time ;)
>

Ah sorry, I didn't realize while splitting the patches


> >
> > -def docgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> > +
> > +def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
> >      if not ifcond:
> >          return ''
> > -    return ' and '.join(ifcond)
> > +    if isinstance(ifcond, str):
> > +        return ifcond
>
> This is what gets rid of the redundant parenthesises in the common case
> "single condition string".
>
> > +
> > +    oper, operands = next(iter(ifcond.items()))
> > +    oper = {'all': '&&'}[oper]
> > +    operands = [cgen_ifcond(o) for o in operands]
> > +    return '(' + (') ' + oper + ' (').join(operands) + ')'
>
> This line is hard to read.  Easier, I think:
>
>        oper = {'all': ' && '}[oper]
>        operands = ['(' + cgen_ifcond(o) + ')' for o in operands]
>        return oper.join(operands)
>
> Neither your version nor mine gets rid of the redundant parenthesises in
> the (uncommon) case "complex condition expression".
> tests/test-qapi-introspect.c still has
>
>     #if (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2))
>                 QLIT_QSTR("feature1"),
>     #endif /* (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2)) */
>
> Mildly annoying.  I'm willing to leave this for later.
>

I see


> Code smell: cgen_ifcond() and docgen_ifcond() are almost identical.  Can
> also be left for later.
>

ok


> >
> >
> >  def gen_if(cond: str) -> str:
> > diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> > index 496f7e0333..3ee66c5f62 100644
> > --- a/scripts/qapi/expr.py
> > +++ b/scripts/qapi/expr.py
> > @@ -259,14 +259,12 @@ def check_flags(expr: _JSONObject, info:
> QAPISourceInfo) -> None:
> >
> >  def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) ->
> None:
> >      """
> > -    Normalize and validate the ``if`` member of an object.
> > +    Validate the ``if`` member of an object.
> >
> > -    The ``if`` member may be either a ``str`` or a ``List[str]``.
> > -    A ``str`` value will be normalized to ``List[str]``.
> > +    The ``if`` member may be either a ``str`` or a dict.
> >
> >      :forms:
> > -      :sugared: ``Union[str, List[str]]``
> > -      :canonical: ``List[str]``
> > +      :canonical: ``Union[str, dict]``
>
> Does this :forms: thing make sense without any :sugared:?  John, you
> added (invented?) it in commit a48653638fa, but no explanation made it
> into the tree.
>
> >
> >      :param expr: The expression containing the ``if`` member to
> validate.
> >      :param info: QAPI schema source file information.
> > @@ -275,31 +273,45 @@ def check_if(expr: _JSONObject, info:
> QAPISourceInfo, source: str) -> None:
> >      :raise QAPISemError:
> >          When the "if" member fails validation, or when there are no
> >          non-empty conditions.
> > -    :return: None, ``expr`` is normalized in-place as needed.
> > +    :return: None
> >      """
>
> Looks like there's a bit more going on than the commit message made me
> expect.
>
> >      ifcond = expr.get('if')
> >      if ifcond is None:
> >          return
> >
> > -    if isinstance(ifcond, list):
> > -        if not ifcond:
> > -            raise QAPISemError(
> > -                info, "'if' condition [] of %s is useless" % source)
> > -    else:
> > -        # Normalize to a list
> > -        ifcond = expr['if'] = [ifcond]
> > +    def _check_if(cond: Union[str, object]) -> None:
> > +        if isinstance(cond, str):
> > +            if not cond.strip():
> > +                raise QAPISemError(
> > +                    info,
> > +                    "'if' condition '%s' of %s makes no sense"
> > +                    % (cond, source))
> > +            return
> >
> > -    for elt in ifcond:
> > -        if not isinstance(elt, str):
> > +        if not isinstance(cond, dict):
> >              raise QAPISemError(
> >                  info,
> > -                "'if' condition of %s must be a string or a list of
> strings"
> > -                % source)
> > -        if not elt.strip():
> > +                "'if' condition of %s must be a string or a dict" %
> source)
> > +        if len(cond) != 1:
> >              raise QAPISemError(
> >                  info,
> > -                "'if' condition '%s' of %s makes no sense"
> > -                % (elt, source))
> > +                "'if' condition dict of %s must have one key: "
> > +                "'all'" % source)
> > +        check_keys(cond, info, "'if' condition", [],
> > +                   ["all"])
> > +
> > +        oper, operands = next(iter(cond.items()))
> > +        if not operands:
> > +            raise QAPISemError(
> > +                info, "'if' condition [] of %s is useless" % source)
> > +
> > +        if oper in ("all") and not isinstance(operands, list):
> > +            raise QAPISemError(
> > +                info, "'%s' condition of %s must be a list" % (oper,
> source))
> > +        for operand in operands:
> > +            _check_if(operand)
> > +
> > +    _check_if(ifcond)
>
> Putting the function's helper in the middle of the function reminds me
> of Mark Twain's "The Awful German Language":
>
>     "The trunks being now ready, he DE- after kissing his mother and
>     sisters, and once more pressing to his bosom his adored Gretchen,
>     who, dressed in simple white muslin, with a single tuberose in the
>     ample folds of her rich brown hair, had tottered feebly down the
>     stairs, still pale from the terror and excitement of the past
>     evening, but longing to lay her poor aching head yet once again upon
>     the breast of him whom she loved more dearly than life itself,
>     PARTED."
>
> I find it hard to read.
>
>
Matter of taste, I guess. I'll let you fix it up to your preference as
follow up if you don't mind.


> >
> >
> >  def normalize_members(members: object) -> None:
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index 30d6a01ad1..d2fbdbe583 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -32,7 +32,7 @@
> >
> >  class QAPISchemaIfCond:
> >      def __init__(self, ifcond=None):
> > -        self.ifcond = ifcond or []
> > +        self.ifcond = ifcond or {}
> >
> >      def cgen(self):
> >          return cgen_ifcond(self.ifcond)
> > diff --git a/tests/qapi-schema/bad-if-empty-list.json
> b/tests/qapi-schema/bad-if-empty-list.json
> > index 94f2eb8670..b62b5671df 100644
> > --- a/tests/qapi-schema/bad-if-empty-list.json
> > +++ b/tests/qapi-schema/bad-if-empty-list.json
> > @@ -1,3 +1,3 @@
> >  # check empty 'if' list
> >  { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
> > -  'if': [] }
> > +  'if': { 'all': [] } }
> > diff --git a/tests/qapi-schema/bad-if-list.json
> b/tests/qapi-schema/bad-if-list.json
> > index ea3d95bb6b..1fefef16a7 100644
> > --- a/tests/qapi-schema/bad-if-list.json
> > +++ b/tests/qapi-schema/bad-if-list.json
> > @@ -1,3 +1,3 @@
> >  # check invalid 'if' content
> >  { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' },
> > -  'if': ['foo', ' '] }
> > +  'if': { 'all': ['foo', ' '] } }
> > diff --git a/tests/qapi-schema/bad-if.err b/tests/qapi-schema/bad-if.err
> > index f83dee65da..8278c49368 100644
> > --- a/tests/qapi-schema/bad-if.err
> > +++ b/tests/qapi-schema/bad-if.err
> > @@ -1,2 +1,3 @@
> >  bad-if.json: In struct 'TestIfStruct':
> > -bad-if.json:2: 'if' condition of struct must be a string or a list of
> strings
> > +bad-if.json:2: 'if' condition has unknown key 'value'
> > +Valid keys are 'all'.
>
> "keys are" is awkward when there's just one.  Okay since there soon will
> be more.
>
>
right

Test case bad-if.json is meant to cover "value of key 'if' has an
> invalid JSON type".  Before the patch, str and list are valid, and the
> test uses (invalid) dict.  Afterwards, str and dict are, and the test
> still uses (now valid) dict.  In other words, it now tests something
> else entirely.
>
> I think this test should be updated to something like
>
>     'if': [ 'defined(TEST_IF_STRUCT)' ]
>
> and a new test added to cover invalid dict key.
>

ok, and I added some tests to cover new error paths.


> > diff --git a/tests/qapi-schema/doc-good.json
> b/tests/qapi-schema/doc-good.json
> > index 423ea23e07..25b1053e8a 100644
> > --- a/tests/qapi-schema/doc-good.json
> > +++ b/tests/qapi-schema/doc-good.json
> > @@ -70,7 +70,8 @@
> >  # @base1:
> >  # the first member
> >  ##
> > -{ 'struct': 'Base', 'data': { 'base1': 'Enum' } }
> > +{ 'struct': 'Base', 'data': { 'base1': 'Enum' },
> > +  'if': { 'all': ['IFALL1', 'IFALL2'] } }
>
> We lack cover for this before your patch.  Thanks for fixing it.
>
> >
> >  ##
> >  # @Variant1:
> > diff --git a/tests/qapi-schema/doc-good.out
> b/tests/qapi-schema/doc-good.out
> > index 8f54ceff2e..689d084f3a 100644
> > --- a/tests/qapi-schema/doc-good.out
> > +++ b/tests/qapi-schema/doc-good.out
> > @@ -12,15 +12,16 @@ enum QType
> >  module doc-good.json
> >  enum Enum
> >      member one
> > -        if ['defined(IFONE)']
> > +        if defined(IFONE)
> >      member two
> > -    if ['defined(IFCOND)']
> > +    if defined(IFCOND)
> >      feature enum-feat
> >  object Base
> >      member base1: Enum optional=False
> > +    if OrderedDict([('all', ['IFALL1', 'IFALL2'])])
> >  object Variant1
> >      member var1: str optional=False
> > -        if ['defined(IFSTR)']
> > +        if defined(IFSTR)
> >          feature member-feat
> >      feature variant1-feat
> >  object Variant2
> > @@ -29,7 +30,7 @@ object Object
> >      tag base1
> >      case one: Variant1
> >      case two: Variant2
> > -        if ['IFTWO']
> > +        if IFTWO
> >      feature union-feat1
> >  object q_obj_Variant1-wrapper
> >      member data: Variant1 optional=False
> > @@ -38,13 +39,13 @@ object q_obj_Variant2-wrapper
> >  enum SugaredUnionKind
> >      member one
> >      member two
> > -        if ['IFTWO']
> > +        if IFTWO
> >  object SugaredUnion
> >      member type: SugaredUnionKind optional=False
> >      tag type
> >      case one: q_obj_Variant1-wrapper
> >      case two: q_obj_Variant2-wrapper
> > -        if ['IFTWO']
> > +        if IFTWO
> >      feature union-feat2
> >  alternate Alternate
> >      tag type
> > diff --git a/tests/qapi-schema/doc-good.txt
> b/tests/qapi-schema/doc-good.txt
> > index 726727af74..4490108cb7 100644
> > --- a/tests/qapi-schema/doc-good.txt
> > +++ b/tests/qapi-schema/doc-good.txt
> > @@ -76,6 +76,12 @@ Members
> >     the first member
> >
> >
> > +If
> > +~~
> > +
> > +"(IFALL1 and IFALL2)"
> > +
> > +
>
> The documentation generated for conditionals is poor before and after
> your work.  Observation, not demand.
>
> >  "Variant1" (Object)
> >  -------------------
> >
>
> [...]
>
>

[-- Attachment #2: Type: text/html, Size: 19278 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor
  2021-08-03 13:44 ` [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor Markus Armbruster
@ 2021-08-04  8:25   ` Marc-André Lureau
  0 siblings, 0 replies; 39+ messages in thread
From: Marc-André Lureau @ 2021-08-04  8:25 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 859 bytes --]

Hi

On Tue, Aug 3, 2021 at 5:44 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Hi,
> >
> > This series makes the 'if' conditions less liberal, by formalizing a
> simple
> > expression tree based on bare boolean logic of configure option
> identifiers.
> >
> > (this allows to express conditions in Rust in my QAPI-Rust PoC series)
> >
> > thanks
>
> I like this overall.
>
> The commit messages are rather terse in places.  I have a few questions,
> I asked for a few minor tweaks, and I also noted a few possible
> improvements that can be done on top.  I wonder whether we can drop
> PATCH 04.
>
> Let's discuss my findings, then decide whether we want a respin.
>

Thanks, I am sending v7 with the requested changes.

[-- Attachment #2: Type: text/html, Size: 1409 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 02/11] qapi: wrap Sequence[str] in an object
  2021-08-04  8:22     ` Marc-André Lureau
@ 2021-08-05 10:44       ` Markus Armbruster
  2021-08-06 11:19       ` Markus Armbruster
  1 sibling, 0 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-05 10:44 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Hi
>
> On Mon, Aug 2, 2021 at 1:21 PM Markus Armbruster <armbru@redhat.com> wrote:
>
>> marcandre.lureau@redhat.com writes:
>>
>> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
>> >
>> > Except for the special casing assert in _make_implicit_object_type(),
>> > which needs to handle schema objects, it's a mechanical change.
>> >
>> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>> > ---
[...]
>> > diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
>> > index 9a348ca2e5..77a8c33ad4 100644
>> > --- a/scripts/qapi/introspect.py
>> > +++ b/scripts/qapi/introspect.py
[...]
>> > @@ -125,10 +124,10 @@ def indent(level: int) -> str:
>> >          if obj.comment:
>> >              ret += indent(level) + f"/* {obj.comment} */\n"
>> >          if obj.ifcond:
>> > -            ret += gen_if(obj.ifcond)
>> > +            ret += gen_if(obj.ifcond.ifcond)
>> >          ret += _tree_to_qlit(obj.value, level)
>> >          if obj.ifcond:
>> > -            ret += '\n' + gen_endif(obj.ifcond)
>> > +            ret += '\n' + gen_endif(obj.ifcond.ifcond)
>> >          return ret
>> >
>> >      ret = ''
>>
>> You update obj.ifcond to obj.ifcond.ifcond when used as argument of
>> gen_if() and gen_endif().  This changes the argument from Tuple to
>> Sequence.  Fine, because Tuple is a special Sequence.
>>
>> Digression: I don't (anymore) understand why we made self.ifcond Tuple.
>> John, do you remember?
>>
>> You don't update obj.ifcond when used as conditional.  The code now
>> calls gen_if() and gen_endif() even for an empty Sequence.
>>
>> I believe this can't actually happen because check_if() rejects [] with
>> "'if' condition [] of %s is useless".
>>
>> Still, the mechanical change should update to obj.ifcond even when used
>> as conditional.
>>
>> Are there other, possibly not so harmless uses of values that change
>> from Sequence to QAPISchemaIfCond the patch doesn't update?
>>
>> Or asked differently: how did you find what to update?
>>
>
> Eh, you are asking me for something I spent just a few hours a few times
> over the last year. Sorry!

That's fair.

> Most probably simply with code reading/grepping, linter and the test suite.

Plausible.

I'm somewhat worried about the bug pattern, but a thorough search feels
uneconomical.  Instead, I've attempted two things to flush out such
bugs:

* Run "make check" with the appended patch.  Fails with v6, i.e. it can
  detect execution of the bug pattern.  Passes with v7.

* The variables holding QAPISchemaIfCond are generally named @ifcond.
  Eyeball grep -w ifcond with ifcond.MUMBLE, ifcond: QAPISchemaIfCond,
  and def parameter lists ignored.  Most remaining grep hits move values
  around.  I can see a few hits where @ifcond is used like a boolean:

  - In docgen_ifcond() and cgen_ifcond(), but @ifcond isn't
    QAPISchemaIfCond there.  Okay, but there's an unrelated oddity I'll
    discuss in review of PATCH 07.

  - In QAPISchemaIfCond.__init__(): likewise.

  - In QAPISchemaEntity.__init__() and QAPISchemaMember.__init__():
    okay, because @ifcond is Optional[QAPISchemaIfCond] there.

  - In check_if(), but @ifcond isn't QAPISchemaIfCond there.  Okay.

I think that's all we can sensibly do now.

[...]


diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 627735a431..a71ad31d59 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -43,6 +43,9 @@ def docgen(self):
     def is_present(self):
         return bool(self.ifcond)
 
+    def __bool__(self):
+        assert False
+
 
 class QAPISchemaEntity:
     meta: Optional[str] = None
@@ -61,7 +64,7 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
         # such place).
         self.info = info
         self.doc = doc
-        self._ifcond = ifcond or QAPISchemaIfCond()
+        self._ifcond = ifcond if ifcond != None else QAPISchemaIfCond()
         self.features = features or []
         self._checked = False
 
@@ -665,7 +668,7 @@ def __init__(self, name, info, ifcond=None):
         assert isinstance(name, str)
         self.name = name
         self.info = info
-        self.ifcond = ifcond or QAPISchemaIfCond()
+        self.ifcond = ifcond if ifcond != None else QAPISchemaIfCond()
         self.defined_in = None
 
     def set_defined_in(self, name):



^ permalink raw reply related	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]}
  2021-08-03 13:05   ` Markus Armbruster
  2021-08-04  8:23     ` Marc-André Lureau
@ 2021-08-05 15:11     ` John Snow
  1 sibling, 0 replies; 39+ messages in thread
From: John Snow @ 2021-08-05 15:11 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: Marc-André Lureau, Eric Blake, qemu-devel, Stefan Hajnoczi

[-- Attachment #1: Type: text/plain, Size: 6199 bytes --]

On Tue, Aug 3, 2021 at 9:05 AM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Replace the simple list sugar form with a recursive structure that will
> > accept other operators in the following commits (all, any or not).
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  scripts/qapi/common.py                        | 23 +++++--
> >  scripts/qapi/expr.py                          | 52 ++++++++++------
> >  scripts/qapi/schema.py                        |  2 +-
> >  tests/qapi-schema/bad-if-empty-list.json      |  2 +-
> >  tests/qapi-schema/bad-if-list.json            |  2 +-
> >  tests/qapi-schema/bad-if.err                  |  3 +-
> >  tests/qapi-schema/doc-good.json               |  3 +-
> >  tests/qapi-schema/doc-good.out                | 13 ++--
> >  tests/qapi-schema/doc-good.txt                |  6 ++
> >  tests/qapi-schema/enum-if-invalid.err         |  3 +-
> >  tests/qapi-schema/features-if-invalid.err     |  2 +-
> >  tests/qapi-schema/qapi-schema-test.json       | 25 ++++----
> >  tests/qapi-schema/qapi-schema-test.out        | 62 +++++++++----------
> >  .../qapi-schema/struct-member-if-invalid.err  |  2 +-
> >  .../qapi-schema/union-branch-if-invalid.json  |  2 +-
> >  15 files changed, 119 insertions(+), 83 deletions(-)
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index 5181a0f167..51463510c9 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -13,7 +13,8 @@
> >
> >  import re
> >  from typing import (
> > -    List,
> > +    Any,
> > +    Dict,
> >      Match,
> >      Optional,
> >      Union,
> > @@ -199,16 +200,28 @@ def guardend(name: str) -> str:
> >                   name=c_fname(name).upper())
> >
> >
> > -def cgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> > +def docgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
>
> Uh, why do you swap cgen_ifcond() and docgen_ifcond()?  Accident?
>
> >      if not ifcond:
> >          return ''
> > -    return '(' + ') && ('.join(ifcond) + ')'
> > +    if isinstance(ifcond, str):
> > +        return ifcond
> >
> > +    oper, operands = next(iter(ifcond.items()))
> > +    oper = {'all': ' and '}[oper]
> > +    operands = [docgen_ifcond(o) for o in operands]
> > +    return '(' + oper.join(operands) + ')'
>
> What a nice review speedbump you buried here...
>
> The whole block boils down to the much less exciting
>
>        operands = [docgen_ifcond(o) for o in ifcond['all']]
>        return '(' + ' and '.join(operands) + ')'
>
> Peeking ahead, I understand that you did it this way here so you can
> extend it trivially there.  Matter of taste; what counts is the final
> result and minimizing reviewer WTFs/minute along the way.
>
> Since the WTFs/minute is a done deed now, what remains is the final
> result, which I expect to review shortly.  But please try a bit harder
> to be boring next time ;)
>
> >
> > -def docgen_ifcond(ifcond: Union[str, List[str]]) -> str:
> > +
> > +def cgen_ifcond(ifcond: Union[str, Dict[str, Any]]) -> str:
> >      if not ifcond:
> >          return ''
> > -    return ' and '.join(ifcond)
> > +    if isinstance(ifcond, str):
> > +        return ifcond
>
> This is what gets rid of the redundant parenthesises in the common case
> "single condition string".
>
> > +
> > +    oper, operands = next(iter(ifcond.items()))
> > +    oper = {'all': '&&'}[oper]
> > +    operands = [cgen_ifcond(o) for o in operands]
> > +    return '(' + (') ' + oper + ' (').join(operands) + ')'
>
> This line is hard to read.  Easier, I think:
>
>        oper = {'all': ' && '}[oper]
>        operands = ['(' + cgen_ifcond(o) + ')' for o in operands]
>        return oper.join(operands)
>
> Neither your version nor mine gets rid of the redundant parenthesises in
> the (uncommon) case "complex condition expression".
> tests/test-qapi-introspect.c still has
>
>     #if (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2))
>                 QLIT_QSTR("feature1"),
>     #endif /* (defined(TEST_IF_COND_1)) && (defined(TEST_IF_COND_2)) */
>
> Mildly annoying.  I'm willing to leave this for later.
>
> Code smell: cgen_ifcond() and docgen_ifcond() are almost identical.  Can
> also be left for later.
>
> >
> >
> >  def gen_if(cond: str) -> str:
> > diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
> > index 496f7e0333..3ee66c5f62 100644
> > --- a/scripts/qapi/expr.py
> > +++ b/scripts/qapi/expr.py
> > @@ -259,14 +259,12 @@ def check_flags(expr: _JSONObject, info:
> QAPISourceInfo) -> None:
> >
> >  def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) ->
> None:
> >      """
> > -    Normalize and validate the ``if`` member of an object.
> > +    Validate the ``if`` member of an object.
> >
> > -    The ``if`` member may be either a ``str`` or a ``List[str]``.
> > -    A ``str`` value will be normalized to ``List[str]``.
> > +    The ``if`` member may be either a ``str`` or a dict.
> >
> >      :forms:
> > -      :sugared: ``Union[str, List[str]]``
> > -      :canonical: ``List[str]``
> > +      :canonical: ``Union[str, dict]``
>
> Does this :forms: thing make sense without any :sugared:?  John, you
> added (invented?) it in commit a48653638fa, but no explanation made it
> into the tree.
>
>
This is just a "field list" ... it's just markup that renders like a
bulleted definition list kind of thing. The :field list: syntax is useful
only so far as we use it consistently; does it make sense without at least
2 entries? it CAN, if by analogy with the other docstrings. It's just a
visual consistency thing, it doesn't have any special meaning.

i.e. unlike the other field lists (param, return, raise) it has no special
recognition by the Sphinx python domain.

I won't push very hard for having it be kept either way, though Union[str,
dict] is kind of a cop-out and doesn't actually convey the concrete form,
which was the intent of adding these in the first place.

--js

[-- Attachment #2: Type: text/html, Size: 7983 bytes --]

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH v6 02/11] qapi: wrap Sequence[str] in an object
  2021-08-04  8:22     ` Marc-André Lureau
  2021-08-05 10:44       ` Markus Armbruster
@ 2021-08-06 11:19       ` Markus Armbruster
  1 sibling, 0 replies; 39+ messages in thread
From: Markus Armbruster @ 2021-08-06 11:19 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Eric Blake, John Snow, qemu-devel, Stefan Hajnoczi

Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Hi
>
> On Mon, Aug 2, 2021 at 1:21 PM Markus Armbruster <armbru@redhat.com> wrote:
>
>> marcandre.lureau@redhat.com writes:
>>
>> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
>> >
>> > Except for the special casing assert in _make_implicit_object_type(),
>> > which needs to handle schema objects, it's a mechanical change.
>> >
>> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>> > ---
[...]
>> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
>> > index d1d27ff7ee..5e44164bd1 100644
>> > --- a/scripts/qapi/schema.py
>> > +++ b/scripts/qapi/schema.py
[...]
>> > @@ -968,11 +974,13 @@ def _def_predefineds(self):
>> >      def _make_features(self, features, info):
>> >          if features is None:
>> >              return []
>> > -        return [QAPISchemaFeature(f['name'], info, f.get('if'))
>> > +        return [QAPISchemaFeature(f['name'], info,
>> > +                                  QAPISchemaIfCond(f.get('if')))
>> >                  for f in features]
>> >
>> >      def _make_enum_members(self, values, info):
>> > -        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
>> > +        return [QAPISchemaEnumMember(v['name'], info,
>> > +                                     QAPISchemaIfCond(v.get('if')))
>> >                  for v in values]
>> >
>>
>> Two more instances of pattern #3, only here we wrap values we get from
>> the JSON parser.  These are either None or non-empty lists.
>>
>> >      def _make_implicit_enum_type(self, name, info, ifcond, values):
>> > @@ -1008,7 +1016,10 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
>>            if typ:
>>                # The implicit object type has multiple users.  This can
>>                # happen only for simple unions' implicit wrapper types.
>>                # Its ifcond should be the disjunction of its user's
>>                # ifconds.  Not implemented.  Instead, we always pass the
>>                # wrapped type's ifcond, which is trivially the same for all
>>                # users.  It's also necessary for the wrapper to compile.
>>                # But it's not tight: the disjunction need not imply it.  We
>>                # may end up compiling useless wrapper types.
>> >              # TODO kill simple unions or implement the disjunction
>> >
>> >              # pylint: disable=protected-access
>> > -            assert (ifcond or []) == typ._ifcond
>> > +            if isinstance(ifcond, QAPISchemaIfCond):
>> > +                assert ifcond.ifcond == typ._ifcond.ifcond
>> > +            else:
>> > +                assert ifcond == typ._ifcond
>> >          else:
>> >              self._def_entity(QAPISchemaObjectType(
>> >                  name, info, None, ifcond, None, None, members, None))
>>
>> This is the non-mechanical change mentioned in the commit message.
>>
>> Can you explain where the two cases come from?
>>
>>
> _make_simple_variant() calls _make_implicit_object_type() with
> self.lookup_type(typ).
>
> I think it could instead call with the ._ifcond value.
>
> To be done after?

We can't.

._make_implicit_object_type() is called by ._make_simple_variant(),
._def_union_type(), ._def_command(), and ._def_event().

The latter three all pass QAPISchemaIfCond(expr.get('if')).
Straightforward.

._make_simple_variant() passes self.lookup_type(typ), i.e. a
QAPISchemaType.  The condition is to be inherited from the type.

We can't pass ._ifcond, because it becomes valid only at .check().
Commit f9d1743b9b0 explains:

    * A QAPISchemaEntity's .ifcond becomes valid at .check().  This is due
      to arrays and simple unions.
    
      Most types get ifcond and info passed to their constructor.
    
      Array types don't: they get it from their element type, which
      becomes known only in .element_type.check().
    
      The implicit wrapper object types for simple union branches don't:
      they get it from the wrapped type, which might be an array.
    
      Ditch the idea to set .ifcond in .check().  Instead, turn it into a
      property and compute it on demand.  Safe because it's only used
      after the schema has been checked.
    
      Most types simply return the ifcond passed to their constructor.
    
      Array types forward to their .element_type instead, and the wrapper
      types forward to the wrapped type.

We really need to kill off "simple" unions.

I was tempted to accept your patch as is, but then my spider sense made
me dig deeper.

When an implicit object type has multiple users, we call
._make_implicit_object_type() multiple times.  The first call creates
the type, subsequent calls reuse it.  The assertion ensures we pass the
same condition to subsequent calls, so reuse is actually valid.

Assertion before the patch:

        typ = self.lookup_entity(name, QAPISchemaObjectType)

@typ is the QAPISchemaType created by the first call, if any.

        if typ:

This is a subsequent call, and @ifcond is its argument.

            [...]
            assert (ifcond or []) == typ._ifcond

typ._ifcond comes from the first call's @ifcond argument, which created
@typ and intialized typ._ifcond in QAPISchemaEntity.__init__():

        self._ifcond = ifcond or []

The assertion asserts "same condition argument", as intended.  It
carefully avoids use of potentially invalid .ifcond.  Good.

For simple union branch wrapper types, @ifcond is the QAPISchemaType
we're wrapping.  The assertion boils down to ifcond == typ._ifcond,
i.e. we assert we pass the same type.  Still good.

The comment before the assertion claims this is the only way to get
here:

            # The implicit object type has multiple users.  This can
            # happen only for simple unions' implicit wrapper types.

Not true, as tests/qapi-schema/redefined-command.json demonstrates: we
get here for unboxed redefined commands, too.

For implicit object types other than simple union branch wrapper types,
i.e. for anonymous union base types, command and event argument types,
@ifcond is either None or a non-empty array of condition strings.  Now
the assertion asserts we pass the same Optional[List[str]].  Quack!
This is *wrong*.  To demonstrate, feed it redefined-command.json
modified like this:

    # we reject commands defined more than once
    { 'command': 'foo', 'data': { 'one': 'str' } }
    { 'command': 'foo', 'data': { '*two': 'str' }, 'if': 'defined(FOO)' }

It trips the assertion.

Your patch wraps QAPISchemaIfCond() around non-empty arrays, and
replaces None by QAPISchemaIfCond([]).

Doesn't affect @ifcond arguments for simple union branch wrapper types.
The assertion continues to work as intended.

Does affect @ifcond arguments for the other implicit object types.
There, we now need to compare the wrapped value instead.  Thus, the
quackery doesn't work anymore, and we need something like

            if simple union wrapper type:
                assert (ifcond or []) == typ._ifcond # old assertion
            else:
                assert ifcond.ifcond == typ._ifcond.ifcond

Your patch uses an indirect way to determine the condition, negates it,
and simplifies the old assertion a bit:

            if isinstance(ifcond, QAPISchemaIfCond):
                assert ifcond.ifcond == typ._ifcond.ifcond
            else:
                assert ifcond == typ._ifcond

All fine, except the assertion remains just as wrong as it was :)

I'll send a patch to delete it, to be inserted before your series.

[...]



^ permalink raw reply	[flat|nested] 39+ messages in thread

end of thread, other threads:[~2021-08-06 11:21 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-18 10:24 [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
2021-06-18 10:24 ` [PATCH v6 01/11] docs: update the documentation upfront about schema configuration marcandre.lureau
2021-07-12 14:07   ` Markus Armbruster
2021-06-18 10:24 ` [PATCH v6 02/11] qapi: wrap Sequence[str] in an object marcandre.lureau
2021-08-02  9:21   ` Markus Armbruster
2021-08-03 17:55     ` John Snow
2021-08-04  8:22     ` Marc-André Lureau
2021-08-05 10:44       ` Markus Armbruster
2021-08-06 11:19       ` Markus Armbruster
2021-06-18 10:24 ` [PATCH v6 03/11] qapi: add QAPISchemaIfCond.is_present() marcandre.lureau
2021-08-02  9:52   ` Markus Armbruster
2021-08-04  8:22     ` Marc-André Lureau
2021-06-18 10:25 ` [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built QAPISchemaIfCond marcandre.lureau
2021-08-02 10:41   ` Markus Armbruster
2021-08-04  8:22     ` Marc-André Lureau
2021-06-18 10:25 ` [PATCH v6 05/11] qapi: introduce QAPISchemaIfCond.cgen() marcandre.lureau
2021-08-02 14:46   ` Markus Armbruster
2021-08-03 11:19     ` Markus Armbruster
2021-08-03 11:20       ` Markus Armbruster
2021-08-03 11:23         ` Markus Armbruster
2021-08-04  8:23     ` Marc-André Lureau
2021-06-18 10:25 ` [PATCH v6 06/11] qapidoc: introduce QAPISchemaIfCond.docgen() marcandre.lureau
2021-08-02 15:47   ` Markus Armbruster
2021-08-04  8:23     ` Marc-André Lureau
2021-06-18 10:25 ` [PATCH v6 07/11] qapi: replace if condition list with dict {'all': [...]} marcandre.lureau
2021-08-03 13:05   ` Markus Armbruster
2021-08-04  8:23     ` Marc-André Lureau
2021-08-05 15:11     ` John Snow
2021-08-03 13:17   ` Markus Armbruster
2021-06-18 10:25 ` [PATCH v6 08/11] qapi: add 'any' condition marcandre.lureau
2021-08-03 13:20   ` Markus Armbruster
2021-06-18 10:25 ` [PATCH v6 09/11] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
2021-08-03 13:22   ` Markus Armbruster
2021-06-18 10:25 ` [PATCH v6 10/11] qapi: add 'not' condition operation marcandre.lureau
2021-06-18 10:25 ` [PATCH v6 11/11] qapi: make 'if' condition strings simple identifiers marcandre.lureau
2021-08-03 13:35   ` Markus Armbruster
2021-08-04  8:22     ` Marc-André Lureau
2021-08-03 13:44 ` [PATCH v6 00/11] qapi: untie 'if' conditions from C preprocessor Markus Armbruster
2021-08-04  8:25   ` Marc-André Lureau

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.