All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor
@ 2021-06-08 12:07 marcandre.lureau
  2021-06-08 12:07 ` [PATCH v5 1/9] docs: update the documentation about schema configuration marcandre.lureau
                   ` (9 more replies)
  0 siblings, 10 replies; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, 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

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 (9):
  docs: update the documentation about schema configuration
  qapi: replace List[str] by QAPISchemaIfCond
  qapi: make gen_if/gen_endif take a simple string
  qapi: start building an 'if' predicate tree
  qapi: introduce IfPredicateList and IfAny
  qapi: add IfNot
  qapi: normalize 'if' condition to IfPredicate tree
  qapi: convert 'if' C-expressions to the new syntax tree
  qapi: make 'if' condition strings simple identifiers

 docs/devel/qapi-code-gen.txt                  |  30 +++--
 docs/sphinx/qapidoc.py                        |   6 +-
 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                             |   4 +-
 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                        | 116 ++++++++++++++++--
 scripts/qapi/events.py                        |   5 +-
 scripts/qapi/expr.py                          |  51 +++++---
 scripts/qapi/gen.py                           |  14 +--
 scripts/qapi/introspect.py                    |  26 ++--
 scripts/qapi/schema.py                        | 112 +++++++++++++----
 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               |   6 +-
 tests/qapi-schema/doc-good.out                |  12 +-
 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/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                |   2 +-
 tests/qapi-schema/union-branch-if-invalid.err |   2 +-
 .../qapi-schema/union-branch-if-invalid.json  |   2 +-
 41 files changed, 498 insertions(+), 286 deletions(-)

-- 
2.29.0




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

* [PATCH v5 1/9] docs: update the documentation about schema configuration
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-15 11:48   ` Markus Armbruster
  2021-06-08 12:07 ` [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, 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..0162b73119 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 generated from that condition:
+
+   * STRING will generate #if defined(STRING)
+   * { 'all': [COND, ...] } will generate #if (COND && ...)
+   * { 'any': [COND, ...] } will generate #if (COND || ...)
+   * { 'not': COND } will generate #if !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] 25+ messages in thread

* [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
  2021-06-08 12:07 ` [PATCH v5 1/9] docs: update the documentation about schema configuration marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-14 12:20   ` Markus Armbruster
  2021-06-08 12:07 ` [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string marcandre.lureau
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, Marc-André Lureau

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

Wrap the 'if' condition in a higher-level object. Not only does this
provide more type safety but it also enables further refactoring without
too much churn.

The following patches will change the syntax of the schema 'if'
conditions to be predicate expressions, and will generate code for
different target languages (C, and Rust in another 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/sphinx/qapidoc.py         |  2 +-
 scripts/qapi/commands.py       |  4 +-
 scripts/qapi/events.py         |  5 ++-
 scripts/qapi/gen.py            | 14 +++----
 scripts/qapi/introspect.py     | 26 ++++++------
 scripts/qapi/schema.py         | 74 +++++++++++++++++++++++-----------
 scripts/qapi/types.py          | 33 +++++++--------
 scripts/qapi/visit.py          | 23 ++++++-----
 tests/qapi-schema/test-qapi.py |  2 +-
 9 files changed, 106 insertions(+), 77 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 87c67ab23f..b737949007 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
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..bc357ebbfa 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -25,6 +25,20 @@
 from .parser import QAPISchemaParser
 
 
+class QAPISchemaIfCond:
+    def __init__(self, ifcond=None):
+        self.ifcond = ifcond or []
+
+    # Returns true if the condition is not void
+    def __bool__(self):
+        return bool(self.ifcond)
+
+    def __eq__(self, other):
+        if not isinstance(other, QAPISchemaIfCond):
+            return NotImplemented
+        return self.ifcond == other.ifcond
+
+
 class QAPISchemaEntity:
     meta: Optional[str] = None
 
@@ -42,7 +56,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
 
@@ -77,7 +91,7 @@ def set_module(self, schema):
 
     @property
     def ifcond(self):
-        assert self._checked
+        assert self._checked and isinstance(self._ifcond, QAPISchemaIfCond)
         return self._ifcond
 
     def is_implicit(self):
@@ -601,7 +615,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 not self.tag_member.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 +660,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 +982,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 +1024,7 @@ 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
+            assert ifcond == typ._ifcond
         else:
             self._def_entity(QAPISchemaObjectType(
                 name, info, None, ifcond, None, None, members, None))
@@ -1018,7 +1034,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 +1052,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 +1061,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 +1084,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 +1093,21 @@ 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 +1120,14 @@ 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 +1144,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 +1163,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..2ec328b22e 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -95,7 +95,7 @@ def _print_variants(variants):
     @staticmethod
     def _print_if(ifcond, indent=4):
         if ifcond:
-            print('%sif %s' % (' ' * indent, ifcond))
+            print('%sif %s' % (' ' * indent, ifcond.ifcond))
 
     @classmethod
     def _print_features(cls, features, indent=4):
-- 
2.29.0



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

* [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
  2021-06-08 12:07 ` [PATCH v5 1/9] docs: update the documentation about schema configuration marcandre.lureau
  2021-06-08 12:07 ` [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-14 12:45   ` Markus Armbruster
  2021-06-08 12:07 ` [PATCH v5 4/9] qapi: start building an 'if' predicate tree marcandre.lureau
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, 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().

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

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 6ad1eeb61d..c305aaf2f1 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -12,7 +12,7 @@
 # See the COPYING file in the top-level directory.
 
 import re
-from typing import Match, Optional, Sequence
+from typing import Match, Optional
 
 
 #: Magic string that gets removed along with all space to its right.
@@ -194,22 +194,20 @@ 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 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 bc357ebbfa..aa4715c519 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 cgen(self):
+        return ' && '.join(self.ifcond)
+
     # Returns true if the condition is not void
     def __bool__(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] 25+ messages in thread

* [PATCH v5 4/9] qapi: start building an 'if' predicate tree
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (2 preceding siblings ...)
  2021-06-08 12:07 ` [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-14 14:38   ` Markus Armbruster
  2021-06-08 12:07 ` [PATCH v5 5/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, Marc-André Lureau

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

The following patches are going to express schema 'if' conditions in a
target language agnostic way. For that, let's start building a predicate
tree of the configuration options.

This intermediary steps still uses C-preprocessor expressions as
the predicates:

"if: [STR, ..]" is translated to a "IfCond -> IfAll ->
[IfOption, ..]" tree, which will generate "#if STR && .." C code.

Once the boolean operation tree nodes are introduced, the 'if'
expressions will be converted to replace the C syntax (no more
!defined(), &&, ...) and based only on option identifiers.

For now, the condition tree will be less expressive than a full C macro
expression as it will only support these operations: 'all', 'any' and
'not', the only ones needed so far.

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/sphinx/qapidoc.py                 |  6 +--
 scripts/qapi/common.py                 | 53 ++++++++++++++++++++++-
 scripts/qapi/schema.py                 | 17 ++++++--
 tests/qapi-schema/doc-good.out         | 12 +++---
 tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
 tests/qapi-schema/test-qapi.py         |  2 +-
 6 files changed, 103 insertions(+), 45 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index b737949007..0f87fb16ce 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -112,12 +112,10 @@ 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(', '))
+        condlist = [nodes.literal('', ifcond.docgen())]
         if not with_if:
             return condlist
 
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index c305aaf2f1..01e3203545 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -12,7 +12,7 @@
 # See the COPYING file in the top-level directory.
 
 import re
-from typing import Match, Optional
+from typing import Match, Optional, Sequence
 
 
 #: Magic string that gets removed along with all space to its right.
@@ -214,3 +214,54 @@ def must_match(pattern: str, string: str) -> Match[str]:
     match = re.match(pattern, string)
     assert match is not None
     return match
+
+
+class IfPredicate:
+    """An 'if' condition predicate"""
+
+    def cgen(self) -> str:
+        raise NotImplementedError()
+
+    def docgen(self) -> str:
+        raise NotImplementedError()
+
+
+class IfOption(IfPredicate):
+    def __init__(self, option: str):
+        self.option = option
+
+    def cgen(self) -> str:
+        return self.option
+
+    def docgen(self) -> str:
+        return self.option
+
+    def __repr__(self) -> str:
+        return f"{type(self).__name__}({self.option!r})"
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, IfOption):
+            return NotImplemented
+        return self.option == other.option
+
+
+class IfAll(IfPredicate):
+    def __init__(self, pred_list: Sequence[IfPredicate]):
+        self.pred_list = pred_list
+
+    def cgen(self) -> str:
+        return " && ".join([p.cgen() for p in self.pred_list])
+
+    def docgen(self) -> str:
+        return " and ".join([p.docgen() for p in self.pred_list])
+
+    def __bool__(self) -> bool:
+        return bool(self.pred_list)
+
+    def __repr__(self) -> str:
+        return f"{type(self).__name__}({self.pred_list!r})"
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, IfAll):
+            return NotImplemented
+        return self.pred_list == other.pred_list
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index aa4715c519..f52caaeecc 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
+from .common import (
+    POINTER_SUFFIX,
+    IfAll,
+    IfOption,
+    c_name,
+)
 from .error import QAPIError, QAPISemError, QAPISourceError
 from .expr import check_exprs
 from .parser import QAPISchemaParser
@@ -28,18 +33,22 @@
 class QAPISchemaIfCond:
     def __init__(self, ifcond=None):
         self.ifcond = ifcond or []
+        self.pred = IfAll([IfOption(opt) for opt in self.ifcond])
+
+    def docgen(self):
+        return self.pred.docgen()
 
     def cgen(self):
-        return ' && '.join(self.ifcond)
+        return self.pred.cgen()
 
     # Returns true if the condition is not void
     def __bool__(self):
-        return bool(self.ifcond)
+        return bool(self.pred)
 
     def __eq__(self, other):
         if not isinstance(other, QAPISchemaIfCond):
             return NotImplemented
-        return self.ifcond == other.ifcond
+        return self.pred == other.pred
 
 
 class QAPISchemaEntity:
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index 8f54ceff2e..db1d23c6bf 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -12,15 +12,15 @@ enum QType
 module doc-good.json
 enum Enum
     member one
-        if ['defined(IFONE)']
+        if IfAll([IfOption('defined(IFONE)')])
     member two
-    if ['defined(IFCOND)']
+    if IfAll([IfOption('defined(IFCOND)')])
     feature enum-feat
 object Base
     member base1: Enum optional=False
 object Variant1
     member var1: str optional=False
-        if ['defined(IFSTR)']
+        if IfAll([IfOption('defined(IFSTR)')])
         feature member-feat
     feature variant1-feat
 object Variant2
@@ -29,7 +29,7 @@ object Object
     tag base1
     case one: Variant1
     case two: Variant2
-        if ['IFTWO']
+        if IfAll([IfOption('IFTWO')])
     feature union-feat1
 object q_obj_Variant1-wrapper
     member data: Variant1 optional=False
@@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper
 enum SugaredUnionKind
     member one
     member two
-        if ['IFTWO']
+        if IfAll([IfOption('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 IfAll([IfOption('IFTWO')])
     feature union-feat2
 alternate Alternate
     tag type
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index e0b8a5f0b6..e4e0fb173a 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 IfAll([IfOption('defined(TEST_IF_STRUCT_BAR)')])
+    if IfAll([IfOption('defined(TEST_IF_STRUCT)')])
 enum TestIfEnum
     member foo
     member bar
-        if ['defined(TEST_IF_ENUM_BAR)']
-    if ['defined(TEST_IF_ENUM)']
+        if IfAll([IfOption('defined(TEST_IF_ENUM_BAR)')])
+    if IfAll([IfOption('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)']
+        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
+    if IfAll([IfOption('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)']
+        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
+    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_ALT_BAR)')])
+    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_CMD_BAR)')])
+    if IfAll([IfOption('defined(TEST_IF_CMD)'), IfOption('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 IfAll([IfOption('defined(TEST_IF_CMD)'), IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_EVT_BAR)')])
+    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_COND_1)'), IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_COND_1)'), IfOption('defined(TEST_IF_COND_2)')])
 event TEST_EVENT_FEATURES0 FeatureStruct1
     boxed=False
 event TEST_EVENT_FEATURES1 None
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index 2ec328b22e..631e255fba 100755
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -95,7 +95,7 @@ def _print_variants(variants):
     @staticmethod
     def _print_if(ifcond, indent=4):
         if ifcond:
-            print('%sif %s' % (' ' * indent, ifcond.ifcond))
+            print('%sif %s' % (' ' * indent, ifcond.pred))
 
     @classmethod
     def _print_features(cls, features, indent=4):
-- 
2.29.0



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

* [PATCH v5 5/9] qapi: introduce IfPredicateList and IfAny
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (3 preceding siblings ...)
  2021-06-08 12:07 ` [PATCH v5 4/9] qapi: start building an 'if' predicate tree marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-08 12:07 ` [PATCH v5 6/9] qapi: add IfNot marcandre.lureau
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, Marc-André Lureau

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

Refactor IfAll class, to introduce a base class IfPredicateList and add
IfAny for the 'any' conditions.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Tested-by: John Snow <jsnow@redhat.com>
---
 scripts/qapi/common.py | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 01e3203545..a4a41c23bc 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -245,15 +245,26 @@ def __eq__(self, other: object) -> bool:
         return self.option == other.option
 
 
-class IfAll(IfPredicate):
+class IfPredicateList(IfPredicate):
+    C_SEP = ""
+    DOC_SEP = ""
+
     def __init__(self, pred_list: Sequence[IfPredicate]):
         self.pred_list = pred_list
 
     def cgen(self) -> str:
-        return " && ".join([p.cgen() for p in self.pred_list])
+        sep = " " + self.C_SEP + " "
+        gen = sep.join([p.cgen() for p in self.pred_list])
+        if len(self.pred_list) <= 1:
+            return gen
+        return "(%s)" % gen
 
     def docgen(self) -> str:
-        return " and ".join([p.docgen() for p in self.pred_list])
+        sep = " " + self.DOC_SEP + " "
+        gen = sep.join([p.docgen() for p in self.pred_list])
+        if len(self.pred_list) <= 1:
+            return gen
+        return "(%s)" % gen
 
     def __bool__(self) -> bool:
         return bool(self.pred_list)
@@ -262,6 +273,16 @@ def __repr__(self) -> str:
         return f"{type(self).__name__}({self.pred_list!r})"
 
     def __eq__(self, other: object) -> bool:
-        if not isinstance(other, IfAll):
+        if not isinstance(other, type(self)):
             return NotImplemented
         return self.pred_list == other.pred_list
+
+
+class IfAll(IfPredicateList):
+    C_SEP = "&&"
+    DOC_SEP = "and"
+
+
+class IfAny(IfPredicateList):
+    C_SEP = "||"
+    DOC_SEP = "or"
-- 
2.29.0



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

* [PATCH v5 6/9] qapi: add IfNot
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (4 preceding siblings ...)
  2021-06-08 12:07 ` [PATCH v5 5/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-08 12:07 ` [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, Marc-André Lureau

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

Introduce IfNot predicate class, for 'not' condition expressions.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Tested-by: John Snow <jsnow@redhat.com>
---
 scripts/qapi/common.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index a4a41c23bc..1d929cbeec 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -245,6 +245,28 @@ def __eq__(self, other: object) -> bool:
         return self.option == other.option
 
 
+class IfNot(IfPredicate):
+    def __init__(self, pred: IfPredicate):
+        self.pred = pred
+
+    def cgen(self) -> str:
+        return "!" + self.pred.cgen()
+
+    def docgen(self) -> str:
+        return "not " + self.pred.docgen()
+
+    def __bool__(self) -> bool:
+        return bool(self.pred)
+
+    def __repr__(self) -> str:
+        return f"IfNot({self.pred!r})"
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, type(self)):
+            return False
+        return self.pred == other.pred
+
+
 class IfPredicateList(IfPredicate):
     C_SEP = ""
     DOC_SEP = ""
-- 
2.29.0



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

* [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (5 preceding siblings ...)
  2021-06-08 12:07 ` [PATCH v5 6/9] qapi: add IfNot marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-15 11:34   ` Markus Armbruster
  2021-06-08 12:07 ` [PATCH v5 8/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, Marc-André Lureau

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

Modify check_if() to normalize the condition tree.

Add _make_if() to build a QAPISchemaIfCond tree.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Tested-by: John Snow <jsnow@redhat.com>
---
 tests/unit/test-qmp-cmds.c                    |  1 +
 scripts/qapi/expr.py                          | 51 +++++++++------
 scripts/qapi/schema.py                        | 62 +++++++++++++------
 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.out                | 12 ++--
 tests/qapi-schema/enum-if-invalid.err         |  3 +-
 tests/qapi-schema/features-if-invalid.err     |  2 +-
 tests/qapi-schema/qapi-schema-test.json       | 32 ++++++----
 tests/qapi-schema/qapi-schema-test.out        | 59 ++++++++++--------
 .../qapi-schema/struct-member-if-invalid.err  |  2 +-
 .../qapi-schema/union-branch-if-invalid.json  |  2 +-
 13 files changed, 143 insertions(+), 90 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/expr.py b/scripts/qapi/expr.py
index 496f7e0333..60ffe9ef03 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -261,12 +261,12 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
     """
     Normalize and 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`` field may be either a ``str`` or a dict.
+    A ``str`` element will be normalized to ``{'all': List[str]}``.
 
     :forms:
-      :sugared: ``Union[str, List[str]]``
-      :canonical: ``List[str]``
+      :sugared: ``Union[str, dict]``
+      :canonical: ``Union[str, dict]``
 
     :param expr: The expression containing the ``if`` member to validate.
     :param info: QAPI schema source file information.
@@ -281,25 +281,38 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
     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]
-
-    for elt in ifcond:
-        if not isinstance(elt, str):
+    def normalize(cond: Union[str, object]) -> Union[str, object]:
+        if isinstance(cond, str):
+            if not cond.strip():
+                raise QAPISemError(
+                    info,
+                    "'if' condition '%s' of %s makes no sense"
+                    % (cond, source))
+            return cond
+        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', 'any' or 'not'" % source)
+        check_keys(cond, info, "'if' condition", [],
+                   ["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":
+            return {oper: normalize(operands)}
+        if oper in ("all", "any") and not isinstance(operands, list):
+            raise QAPISemError(
+                info, "'%s' condition of %s must be a list" % (oper, source))
+        operands = [normalize(o) for o in operands]
+        return {oper: operands}
+
+    expr['if'] = normalize(ifcond)
 
 
 def normalize_members(members: object) -> None:
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index f52caaeecc..9864e49c54 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -22,6 +22,8 @@
 from .common import (
     POINTER_SUFFIX,
     IfAll,
+    IfAny,
+    IfNot,
     IfOption,
     c_name,
 )
@@ -31,15 +33,14 @@
 
 
 class QAPISchemaIfCond:
-    def __init__(self, ifcond=None):
-        self.ifcond = ifcond or []
-        self.pred = IfAll([IfOption(opt) for opt in self.ifcond])
+    def __init__(self, pred=None):
+        self.pred = pred
 
     def docgen(self):
-        return self.pred.docgen()
+        return self and self.pred.docgen()
 
     def cgen(self):
-        return self.pred.cgen()
+        return self and self.pred.cgen()
 
     # Returns true if the condition is not void
     def __bool__(self):
@@ -991,16 +992,41 @@ def _def_predefineds(self):
         self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
                                             qtype_values, 'QTYPE'))
 
+    def _make_if(self, cond):
+        if isinstance(cond, QAPISchemaIfCond):
+            return cond
+        if cond is None:
+            return QAPISchemaIfCond()
+
+        def make_pred(node):
+            if isinstance(node, str):
+                return IfOption(node)
+            oper, operands = next(iter(node.items()))
+            op2cls = {
+                'all': IfAll,
+                'any': IfAny,
+                'not': IfNot,
+            }
+
+            if isinstance(operands, list):
+                operands = [make_pred(o) for o in operands]
+            else:
+                operands = make_pred(operands)
+
+            return op2cls[oper](operands)
+
+        return QAPISchemaIfCond(make_pred(cond))
+
     def _make_features(self, features, info):
         if features is None:
             return []
         return [QAPISchemaFeature(f['name'], info,
-                                  QAPISchemaIfCond(f.get('if')))
+                                  self._make_if(f.get('if')))
                 for f in features]
 
     def _make_enum_members(self, values, info):
         return [QAPISchemaEnumMember(v['name'], info,
-                                     QAPISchemaIfCond(v.get('if')))
+                                     self._make_if(v.get('if')))
                 for v in values]
 
     def _make_implicit_enum_type(self, name, info, ifcond, values):
@@ -1046,7 +1072,7 @@ def _def_enum_type(self, expr, info, doc):
         name = expr['enum']
         data = expr['data']
         prefix = expr.get('prefix')
-        ifcond = QAPISchemaIfCond(expr.get('if'))
+        ifcond = self._make_if(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         self._def_entity(QAPISchemaEnumType(
             name, info, doc, ifcond, features,
@@ -1065,7 +1091,7 @@ def _make_member(self, name, typ, ifcond, features, info):
 
     def _make_members(self, data, info):
         return [self._make_member(key, value['type'],
-                                  QAPISchemaIfCond(value.get('if')),
+                                  self._make_if(value.get('if')),
                                   value.get('features'), info)
                 for (key, value) in data.items()]
 
@@ -1073,7 +1099,7 @@ def _def_struct_type(self, expr, info, doc):
         name = expr['struct']
         base = expr.get('base')
         data = expr['data']
-        ifcond = QAPISchemaIfCond(expr.get('if'))
+        ifcond = self._make_if(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         self._def_entity(QAPISchemaObjectType(
             name, info, doc, ifcond, features, base,
@@ -1096,7 +1122,7 @@ def _def_union_type(self, expr, info, doc):
         name = expr['union']
         data = expr['data']
         base = expr.get('base')
-        ifcond = QAPISchemaIfCond(expr.get('if'))
+        ifcond = self._make_if(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         tag_name = expr.get('discriminator')
         tag_member = None
@@ -1107,7 +1133,7 @@ def _def_union_type(self, expr, info, doc):
         if tag_name:
             variants = [
                 self._make_variant(key, value['type'],
-                                   QAPISchemaIfCond(value.get('if')),
+                                   self._make_if(value.get('if')),
                                    info)
                 for (key, value) in data.items()
             ]
@@ -1115,11 +1141,11 @@ def _def_union_type(self, expr, info, doc):
         else:
             variants = [
                 self._make_simple_variant(key, value['type'],
-                                          QAPISchemaIfCond(value.get('if')),
+                                          self._make_if(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]
@@ -1132,11 +1158,11 @@ def _def_union_type(self, expr, info, doc):
     def _def_alternate_type(self, expr, info, doc):
         name = expr['alternate']
         data = expr['data']
-        ifcond = QAPISchemaIfCond(expr.get('if'))
+        ifcond = self._make_if(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         variants = [
             self._make_variant(key, value['type'],
-                               QAPISchemaIfCond(value.get('if')),
+                               self._make_if(value.get('if')),
                                info)
             for (key, value) in data.items()
         ]
@@ -1156,7 +1182,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 = QAPISchemaIfCond(expr.get('if'))
+        ifcond = self._make_if(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
@@ -1175,7 +1201,7 @@ def _def_event(self, expr, info, doc):
         name = expr['event']
         data = expr.get('data')
         boxed = expr.get('boxed', False)
-        ifcond = QAPISchemaIfCond(expr.get('if'))
+        ifcond = self._make_if(expr.get('if'))
         features = self._make_features(expr.get('features'), info)
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
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..454fbae387 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', 'any', 'not'.
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
index db1d23c6bf..4d951f97ee 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -12,15 +12,15 @@ enum QType
 module doc-good.json
 enum Enum
     member one
-        if IfAll([IfOption('defined(IFONE)')])
+        if IfOption('defined(IFONE)')
     member two
-    if IfAll([IfOption('defined(IFCOND)')])
+    if IfOption('defined(IFCOND)')
     feature enum-feat
 object Base
     member base1: Enum optional=False
 object Variant1
     member var1: str optional=False
-        if IfAll([IfOption('defined(IFSTR)')])
+        if IfOption('defined(IFSTR)')
         feature member-feat
     feature variant1-feat
 object Variant2
@@ -29,7 +29,7 @@ object Object
     tag base1
     case one: Variant1
     case two: Variant2
-        if IfAll([IfOption('IFTWO')])
+        if IfOption('IFTWO')
     feature union-feat1
 object q_obj_Variant1-wrapper
     member data: Variant1 optional=False
@@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper
 enum SugaredUnionKind
     member one
     member two
-        if IfAll([IfOption('IFTWO')])
+        if IfOption('IFTWO')
 object SugaredUnion
     member type: SugaredUnionKind optional=False
     tag type
     case one: q_obj_Variant1-wrapper
     case two: q_obj_Variant2-wrapper
-        if IfAll([IfOption('IFTWO')])
+        if IfOption('IFTWO')
     feature union-feat2
 alternate Alternate
     tag type
diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err
index 0556dc967b..3bb84075a9 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', 'any', 'not'.
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..a0f09b277e 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)', {'not': 'defined(TEST_IF_NOT_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,13 @@
                 { '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)'] } } ] }
+{ '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': [] }
 
@@ -328,8 +333,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 e4e0fb173a..dd2f83fbae 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -298,49 +298,49 @@ 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 IfAll([IfOption('defined(TEST_IF_STRUCT_BAR)')])
-    if IfAll([IfOption('defined(TEST_IF_STRUCT)')])
+        if IfOption('defined(TEST_IF_STRUCT_BAR)')
+    if IfOption('defined(TEST_IF_STRUCT)')
 enum TestIfEnum
     member foo
     member bar
-        if IfAll([IfOption('defined(TEST_IF_ENUM_BAR)')])
-    if IfAll([IfOption('defined(TEST_IF_ENUM)')])
+        if IfOption('defined(TEST_IF_ENUM_BAR)')
+    if IfOption('defined(TEST_IF_ENUM)')
 object q_obj_TestStruct-wrapper
     member data: TestStruct optional=False
 enum TestIfUnionKind
     member foo
-    member bar
-        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
-    if IfAll([IfOption('defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)')])
+    member union-bar
+        if IfOption('defined(TEST_IF_UNION_BAR)')
+    if IfAll([IfOption('defined(TEST_IF_UNION)'), IfOption('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 IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
-    if IfAll([IfOption('defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)')])
+    case union-bar: q_obj_str-wrapper
+        if IfOption('defined(TEST_IF_UNION_BAR)')
+    if IfAll([IfOption('defined(TEST_IF_UNION)'), IfOption('defined(TEST_IF_STRUCT)')])
 object q_obj_test-if-union-cmd-arg
     member union-cmd-arg: TestIfUnion optional=False
-    if IfAll([IfOption('defined(TEST_IF_UNION)')])
+    if IfOption('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 IfAll([IfOption('defined(TEST_IF_UNION)')])
+    if IfOption('defined(TEST_IF_UNION)')
 alternate TestIfAlternate
     tag type
     case foo: int
     case bar: TestStruct
-        if IfAll([IfOption('defined(TEST_IF_ALT_BAR)')])
-    if IfAll([IfOption('defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)')])
+        if IfOption('defined(TEST_IF_ALT_BAR)')
+    if IfAll([IfOption('defined(TEST_IF_ALT)'), IfOption('defined(TEST_IF_STRUCT)')])
 object q_obj_test-if-alternate-cmd-arg
     member alt-cmd-arg: TestIfAlternate optional=False
-    if IfAll([IfOption('defined(TEST_IF_ALT)')])
+    if IfAll([IfOption('defined(TEST_IF_ALT)'), IfNot(IfOption('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 IfAll([IfOption('defined(TEST_IF_ALT)')])
+    if IfAll([IfOption('defined(TEST_IF_ALT)'), IfNot(IfOption('defined(TEST_IF_NOT_ALT)'))])
 object q_obj_test-if-cmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-        if IfAll([IfOption('defined(TEST_IF_CMD_BAR)')])
+        if IfOption('defined(TEST_IF_CMD_BAR)')
     if IfAll([IfOption('defined(TEST_IF_CMD)'), IfOption('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
@@ -348,15 +348,15 @@ command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
 command test-cmd-return-def-three None -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
 array TestIfEnumList TestIfEnum
-    if IfAll([IfOption('defined(TEST_IF_ENUM)')])
+    if IfOption('defined(TEST_IF_ENUM)')
 object q_obj_TEST_IF_EVENT-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnumList optional=False
-        if IfAll([IfOption('defined(TEST_IF_EVT_BAR)')])
-    if IfAll([IfOption('defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)')])
+        if IfOption('defined(TEST_IF_EVT_BAR)')
+    if IfAll([IfOption('defined(TEST_IF_EVT)'), IfOption('defined(TEST_IF_STRUCT)')])
 event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
     boxed=False
-    if IfAll([IfOption('defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)')])
+    if IfAll([IfOption('defined(TEST_IF_EVT)'), IfOption('defined(TEST_IF_STRUCT)')])
 object FeatureStruct0
     member foo: int optional=False
 object FeatureStruct1
@@ -379,17 +379,21 @@ object FeatureStruct4
 object CondFeatureStruct1
     member foo: int optional=False
     feature feature1
-        if IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
+        if IfOption('defined(TEST_IF_FEATURE_1)')
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
+        if IfOption('defined(TEST_IF_FEATURE_1)')
     feature feature2
-        if IfAll([IfOption('defined(TEST_IF_FEATURE_2)')])
+        if IfOption('defined(TEST_IF_FEATURE_2)')
 object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
         if IfAll([IfOption('defined(TEST_IF_COND_1)'), IfOption('defined(TEST_IF_COND_2)')])
+object CondFeatureStruct4
+    member foo: int optional=False
+    feature feature1
+        if IfAny([IfOption('defined(TEST_IF_COND_1)'), IfOption('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
@@ -429,13 +434,13 @@ 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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
+        if IfOption('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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
+        if IfOption('defined(TEST_IF_FEATURE_1)')
     feature feature2
-        if IfAll([IfOption('defined(TEST_IF_FEATURE_2)')])
+        if IfOption('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
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] 25+ messages in thread

* [PATCH v5 8/9] qapi: convert 'if' C-expressions to the new syntax tree
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (6 preceding siblings ...)
  2021-06-08 12:07 ` [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-08 12:07 ` [PATCH v5 9/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
  2021-06-15 11:56 ` [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor Markus Armbruster
  9 siblings, 0 replies; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, 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] 25+ messages in thread

* [PATCH v5 9/9] qapi: make 'if' condition strings simple identifiers
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (7 preceding siblings ...)
  2021-06-08 12:07 ` [PATCH v5 8/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
@ 2021-06-08 12:07 ` marcandre.lureau
  2021-06-18  9:36   ` Markus Armbruster
  2021-06-15 11:56 ` [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor Markus Armbruster
  9 siblings, 1 reply; 25+ messages in thread
From: marcandre.lureau @ 2021-06-08 12:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: Eric Blake, jsnow, Markus Armbruster, 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                             |  4 +-
 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 +-
 23 files changed, 176 insertions(+), 176 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 7a5bdf9a0d..44d8c90405 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 f7ef30f940..5577fa4868 100644
--- a/qapi/qom.json
+++ b/qapi/qom.json
@@ -603,7 +603,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' } }
 
 ##
@@ -767,7 +767,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',
@@ -780,7 +780,7 @@
     'iothread',
     'memory-backend-file',
     { 'name': 'memory-backend-memfd',
-      'if': 'defined(CONFIG_LINUX)' },
+      'if': 'CONFIG_LINUX' },
     'memory-backend-ram',
     'pef-guest',
     'pr-manager-helper',
@@ -825,7 +825,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',
@@ -838,7 +838,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 2e83452797..a0c08aa4ba 100644
--- a/qapi/sockets.json
+++ b/qapi/sockets.json
@@ -86,8 +86,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/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 1d929cbeec..e6422b1e22 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -231,7 +231,7 @@ def __init__(self, option: str):
         self.option = option
 
     def cgen(self) -> str:
-        return self.option
+        return f"defined({self.option})"
 
     def docgen(self) -> str:
         return self.option
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py
index 60ffe9ef03..31adbe045b 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -283,10 +283,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
 
     def normalize(cond: Union[str, object]) -> Union[str, object]:
         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 cond
         if not isinstance(cond, dict):
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 423ea23e07..ae531e89b5 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:
@@ -86,7 +86,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 4d951f97ee..53e5981028 100644
--- a/tests/qapi-schema/doc-good.out
+++ b/tests/qapi-schema/doc-good.out
@@ -12,15 +12,15 @@ enum QType
 module doc-good.json
 enum Enum
     member one
-        if IfOption('defined(IFONE)')
+        if IfOption('IFONE')
     member two
-    if IfOption('defined(IFCOND)')
+    if IfOption('IFCOND')
     feature enum-feat
 object Base
     member base1: Enum optional=False
 object Variant1
     member var1: str optional=False
-        if IfOption('defined(IFSTR)')
+        if IfOption('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 726727af74..27b7ce8799 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)
@@ -87,7 +87,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 dd2f83fbae..52737debad 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 IfOption('defined(TEST_IF_STRUCT_BAR)')
-    if IfOption('defined(TEST_IF_STRUCT)')
+        if IfOption('TEST_IF_STRUCT_BAR')
+    if IfOption('TEST_IF_STRUCT')
 enum TestIfEnum
     member foo
     member bar
-        if IfOption('defined(TEST_IF_ENUM_BAR)')
-    if IfOption('defined(TEST_IF_ENUM)')
+        if IfOption('TEST_IF_ENUM_BAR')
+    if IfOption('TEST_IF_ENUM')
 object q_obj_TestStruct-wrapper
     member data: TestStruct optional=False
 enum TestIfUnionKind
     member foo
     member union-bar
-        if IfOption('defined(TEST_IF_UNION_BAR)')
-    if IfAll([IfOption('defined(TEST_IF_UNION)'), IfOption('defined(TEST_IF_STRUCT)')])
+        if IfOption('TEST_IF_UNION_BAR')
+    if IfAll([IfOption('TEST_IF_UNION'), IfOption('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 IfOption('defined(TEST_IF_UNION_BAR)')
-    if IfAll([IfOption('defined(TEST_IF_UNION)'), IfOption('defined(TEST_IF_STRUCT)')])
+        if IfOption('TEST_IF_UNION_BAR')
+    if IfAll([IfOption('TEST_IF_UNION'), IfOption('TEST_IF_STRUCT')])
 object q_obj_test-if-union-cmd-arg
     member union-cmd-arg: TestIfUnion optional=False
-    if IfOption('defined(TEST_IF_UNION)')
+    if IfOption('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 IfOption('defined(TEST_IF_UNION)')
+    if IfOption('TEST_IF_UNION')
 alternate TestIfAlternate
     tag type
     case foo: int
     case bar: TestStruct
-        if IfOption('defined(TEST_IF_ALT_BAR)')
-    if IfAll([IfOption('defined(TEST_IF_ALT)'), IfOption('defined(TEST_IF_STRUCT)')])
+        if IfOption('TEST_IF_ALT_BAR')
+    if IfAll([IfOption('TEST_IF_ALT'), IfOption('TEST_IF_STRUCT')])
 object q_obj_test-if-alternate-cmd-arg
     member alt-cmd-arg: TestIfAlternate optional=False
-    if IfAll([IfOption('defined(TEST_IF_ALT)'), IfNot(IfOption('defined(TEST_IF_NOT_ALT)'))])
+    if IfAll([IfOption('TEST_IF_ALT'), IfNot(IfOption('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 IfAll([IfOption('defined(TEST_IF_ALT)'), IfNot(IfOption('defined(TEST_IF_NOT_ALT)'))])
+    if IfAll([IfOption('TEST_IF_ALT'), IfNot(IfOption('TEST_IF_NOT_ALT'))])
 object q_obj_test-if-cmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-        if IfOption('defined(TEST_IF_CMD_BAR)')
-    if IfAll([IfOption('defined(TEST_IF_CMD)'), IfOption('defined(TEST_IF_STRUCT)')])
+        if IfOption('TEST_IF_CMD_BAR')
+    if IfAll([IfOption('TEST_IF_CMD'), IfOption('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 IfAll([IfOption('defined(TEST_IF_CMD)'), IfOption('defined(TEST_IF_STRUCT)')])
+    if IfAll([IfOption('TEST_IF_CMD'), IfOption('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 IfOption('defined(TEST_IF_ENUM)')
+    if IfOption('TEST_IF_ENUM')
 object q_obj_TEST_IF_EVENT-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnumList optional=False
-        if IfOption('defined(TEST_IF_EVT_BAR)')
-    if IfAll([IfOption('defined(TEST_IF_EVT)'), IfOption('defined(TEST_IF_STRUCT)')])
+        if IfOption('TEST_IF_EVT_BAR')
+    if IfAll([IfOption('TEST_IF_EVT'), IfOption('TEST_IF_STRUCT')])
 event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
     boxed=False
-    if IfAll([IfOption('defined(TEST_IF_EVT)'), IfOption('defined(TEST_IF_STRUCT)')])
+    if IfAll([IfOption('TEST_IF_EVT'), IfOption('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 IfOption('defined(TEST_IF_FEATURE_1)')
+        if IfOption('TEST_IF_FEATURE_1')
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if IfOption('defined(TEST_IF_FEATURE_1)')
+        if IfOption('TEST_IF_FEATURE_1')
     feature feature2
-        if IfOption('defined(TEST_IF_FEATURE_2)')
+        if IfOption('TEST_IF_FEATURE_2')
 object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
-        if IfAll([IfOption('defined(TEST_IF_COND_1)'), IfOption('defined(TEST_IF_COND_2)')])
+        if IfAll([IfOption('TEST_IF_COND_1'), IfOption('TEST_IF_COND_2')])
 object CondFeatureStruct4
     member foo: int optional=False
     feature feature1
-        if IfAny([IfOption('defined(TEST_IF_COND_1)'), IfOption('defined(TEST_IF_COND_2)')])
+        if IfAny([IfOption('TEST_IF_COND_1'), IfOption('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 IfOption('defined(TEST_IF_FEATURE_1)')
+        if IfOption('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 IfOption('defined(TEST_IF_FEATURE_1)')
+        if IfOption('TEST_IF_FEATURE_1')
     feature feature2
-        if IfOption('defined(TEST_IF_FEATURE_2)')
+        if IfOption('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 IfAll([IfOption('defined(TEST_IF_COND_1)'), IfOption('defined(TEST_IF_COND_2)')])
+        if IfAll([IfOption('TEST_IF_COND_1'), IfOption('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] 25+ messages in thread

* Re: [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-06-08 12:07 ` [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
@ 2021-06-14 12:20   ` Markus Armbruster
  2021-06-16 10:28     ` Marc-André Lureau
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2021-06-14 12:20 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Wrap the 'if' condition in a higher-level object. Not only does this

I can see "wrap in an object".  I'm afraid don't get what makes it
"higher-level".

> provide more type safety but it also enables further refactoring without
> too much churn.

I figure by "more type safety" you mean "can't accidentally confuse some
other list of strings with a conditional", which is true, but isn't
really about *type* safety.

Maybe:

  Wrap the 'if' condition in an object.  Not only is this a bit safer,
  it also enables further refactoring without too much churn.

> The following patches will change the syntax of the schema 'if'
> conditions to be predicate expressions, and will generate code for
> different target languages (C, and Rust in another series).

Since different target languages aren't actually generated in this
series, I'd say "and will enable generating code for different target
languages, such as Rust."

>
> 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/sphinx/qapidoc.py         |  2 +-
>  scripts/qapi/commands.py       |  4 +-
>  scripts/qapi/events.py         |  5 ++-
>  scripts/qapi/gen.py            | 14 +++----
>  scripts/qapi/introspect.py     | 26 ++++++------
>  scripts/qapi/schema.py         | 74 +++++++++++++++++++++++-----------
>  scripts/qapi/types.py          | 33 +++++++--------
>  scripts/qapi/visit.py          | 23 ++++++-----
>  tests/qapi-schema/test-qapi.py |  2 +-
>  9 files changed, 106 insertions(+), 77 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index 87c67ab23f..b737949007 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

Note for later: many hunks replace ifcond (the unwrapped Sequence[str])
by ifcond.ifcond (the wrapped one, with the wrapper peeled off).
Mechanical.

> 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],

Note for later: many hunks replace ifcond: Sequence[str] or
Iterable[str] by ifcond: QAPISchemaIfCond.  Mechanical.

> 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(),

The same QAPISchemaIfCond object gets reused every time we don't pass an
@ifcond argument.  Looks a bit scary, but works, because we don't ever
mutate it.

Elsewhere, we None as default, though: QAPISchemaEntity.__init__(),
QAPISchemaMember.__init__().

>                    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..bc357ebbfa 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -25,6 +25,20 @@
>  from .parser import QAPISchemaParser
>  
>  
> +class QAPISchemaIfCond:
> +    def __init__(self, ifcond=None):
> +        self.ifcond = ifcond or []
> +
> +    # Returns true if the condition is not void
> +    def __bool__(self):
> +        return bool(self.ifcond)

I'm not sure I like this one.

In the two places where we default parameter icond=None, we use

    ifcond or QAPISchemaIfCond()

to flip to the default value we actually want.  Works as intended for
None and for non-empty QAPISchemaIfCond.  For empty QAPISchemaIfCond, it
throws away the value and creates a new, equivalent one.  Works, but
kind of by accident.

This is an instance of a more general problem: when I see an
Optional[ObjectType], I expect it to be falsy if and only if it's None.
Perhaps I shouldn't.  Doesn't mean we should press __bool__() into
service for checking "is there a condition".  A boring non-dunder method
might be clearer.

I understand what you mean by "condition is void", but it sounds a bit
odd to me.  How do you like "Is a condition present?"

> +
> +    def __eq__(self, other):
> +        if not isinstance(other, QAPISchemaIfCond):
> +            return NotImplemented
> +        return self.ifcond == other.ifcond

Stupid question: why do we need to override __eq__()?

Hmm, probably for _make_implicit_object_type().

Why raise on type mismatch?  Feels rather un-pythonic to me.

> +
> +
>  class QAPISchemaEntity:
>      meta: Optional[str] = None
>  
> @@ -42,7 +56,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
>  
> @@ -77,7 +91,7 @@ def set_module(self, schema):
>  
>      @property
>      def ifcond(self):
> -        assert self._checked
> +        assert self._checked and isinstance(self._ifcond, QAPISchemaIfCond)
>          return self._ifcond
>  
>      def is_implicit(self):
> @@ -601,7 +615,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 not self.tag_member.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 +660,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 +982,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]
>  

Note for later: several hunks wrap condition expressions in an object
like this.  Mechanical.

>      def _make_implicit_enum_type(self, name, info, ifcond, values):
> @@ -1008,7 +1024,7 @@ 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
> +            assert ifcond == typ._ifcond

I'm not sure I understand this part.  Leaving for later.

>          else:
>              self._def_entity(QAPISchemaObjectType(
>                  name, info, None, ifcond, None, None, members, None))
> @@ -1018,7 +1034,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 +1052,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 +1061,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 +1084,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 +1093,21 @@ 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()
> +            ]

I think we more usually put the closing parenthesis like this:

               variants = [
                  ...
                  for (key, value) in data.items()]

More of the same below.

>              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 +1120,14 @@ 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 +1144,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 +1163,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..2ec328b22e 100755
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -95,7 +95,7 @@ def _print_variants(variants):
>      @staticmethod
>      def _print_if(ifcond, indent=4):
>          if ifcond:
> -            print('%sif %s' % (' ' * indent, ifcond))
> +            print('%sif %s' % (' ' * indent, ifcond.ifcond))
>  
>      @classmethod
>      def _print_features(cls, features, indent=4):

If feel this is a bit harder to review than necessary, because you take
two steps at once:

1. Wrap Sequence[str] in an object

2. Add methods to the object to clean up the resulting mess some

Step 1. by itself should be pretty much mechanical: adjust the type
hints, create the wrapper object on write, peel it off on read.  The
result will be slightly ugly in places.

I'd expect step 2 to be much smaller, and easier to understand.  It
could perhaps be split into one patch per method, but I hope it's
reviewable just fine even without.



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

* Re: [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string
  2021-06-08 12:07 ` [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string marcandre.lureau
@ 2021-06-14 12:45   ` Markus Armbruster
  2021-06-14 14:14     ` Markus Armbruster
  2021-06-16 10:44     ` Marc-André Lureau
  0 siblings, 2 replies; 25+ messages in thread
From: Markus Armbruster @ 2021-06-14 12:45 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

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().

I understand why you're doing this, but only because I know where you're
headed.  By itself, it is not an improvement: you move C generation out
of common.py into schema.py.  You need to explain why that's useful.

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

Missing: qapi-code-gen.txt section "Configuring the schema" has an
example, which needs to be updated.

When the generated code changes, always check the examples, and always
consider describing the change in the commit message.

>
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index 6ad1eeb61d..c305aaf2f1 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -12,7 +12,7 @@
>  # See the COPYING file in the top-level directory.
>  
>  import re
> -from typing import Match, Optional, Sequence
> +from typing import Match, Optional
>  
>  
>  #: Magic string that gets removed along with all space to its right.
> @@ -194,22 +194,20 @@ 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 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 bc357ebbfa..aa4715c519 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 cgen(self):
> +        return ' && '.join(self.ifcond)

Fragile.  Better:

           return '(' + ') && ('.join(self.ifcond) + ')'

If we want to keep C generation details out of schema.py, we need a
helper mapping self.ifcond: Sequence[str] to C code, similar to how
QAPISchemaEntity.c_name() works with helper c_name().

> +
>      # Returns true if the condition is not void
>      def __bool__(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('''
>      },
[More of the same snipped...]



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

* Re: [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string
  2021-06-14 12:45   ` Markus Armbruster
@ 2021-06-14 14:14     ` Markus Armbruster
  2021-06-16 10:44     ` Marc-André Lureau
  1 sibling, 0 replies; 25+ messages in thread
From: Markus Armbruster @ 2021-06-14 14:14 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

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().
>
> I understand why you're doing this, but only because I know where you're
> headed.  By itself, it is not an improvement: you move C generation out
> of common.py into schema.py.  You need to explain why that's useful.
>
>>
>> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>> ---
>>  scripts/qapi/common.py     | 24 +++++++++++-------------
>>  scripts/qapi/gen.py        |  4 ++--
>>  scripts/qapi/introspect.py |  4 ++--
>>  scripts/qapi/schema.py     |  3 +++
>>  scripts/qapi/types.py      | 20 ++++++++++----------
>>  scripts/qapi/visit.py      | 12 ++++++------
>>  6 files changed, 34 insertions(+), 33 deletions(-)
>
> Missing: qapi-code-gen.txt section "Configuring the schema" has an
> example, which needs to be updated.

Nope, that's in PATCH 1 already.

> When the generated code changes, always check the examples, and always
> consider describing the change in the commit message.

Describing the change in the commit message is even more useful when the
doc update isn't in the same patch.

[...]



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

* Re: [PATCH v5 4/9] qapi: start building an 'if' predicate tree
  2021-06-08 12:07 ` [PATCH v5 4/9] qapi: start building an 'if' predicate tree marcandre.lureau
@ 2021-06-14 14:38   ` Markus Armbruster
  2021-06-18 10:31     ` Marc-André Lureau
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2021-06-14 14:38 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> The following patches are going to express schema 'if' conditions in a
> target language agnostic way. For that, let's start building a predicate
> tree of the configuration options.
>
> This intermediary steps still uses C-preprocessor expressions as
> the predicates:
>
> "if: [STR, ..]" is translated to a "IfCond -> IfAll ->
> [IfOption, ..]" tree, which will generate "#if STR && .." C code.
>
> Once the boolean operation tree nodes are introduced, the 'if'
> expressions will be converted to replace the C syntax (no more
> !defined(), &&, ...) and based only on option identifiers.
>
> For now, the condition tree will be less expressive than a full C macro
> expression as it will only support these operations: 'all', 'any' and
> 'not', the only ones needed so far.
>
> 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/sphinx/qapidoc.py                 |  6 +--
>  scripts/qapi/common.py                 | 53 ++++++++++++++++++++++-
>  scripts/qapi/schema.py                 | 17 ++++++--
>  tests/qapi-schema/doc-good.out         | 12 +++---
>  tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
>  tests/qapi-schema/test-qapi.py         |  2 +-
>  6 files changed, 103 insertions(+), 45 deletions(-)
>
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index b737949007..0f87fb16ce 100644
> --- a/docs/sphinx/qapidoc.py
> +++ b/docs/sphinx/qapidoc.py
> @@ -112,12 +112,10 @@ 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(', '))
> +        condlist = [nodes.literal('', ifcond.docgen())]
>          if not with_if:
>              return condlist
>  
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index c305aaf2f1..01e3203545 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -12,7 +12,7 @@
>  # See the COPYING file in the top-level directory.
>  
>  import re
> -from typing import Match, Optional
> +from typing import Match, Optional, Sequence
>  
>  
>  #: Magic string that gets removed along with all space to its right.
> @@ -214,3 +214,54 @@ def must_match(pattern: str, string: str) -> Match[str]:
>      match = re.match(pattern, string)
>      assert match is not None
>      return match
> +
> +
> +class IfPredicate:

This is the abstract base class of the two (soon four) forms 'if'.
qapi-code-gen.txt calls them "conditionals", and the code so far uses
names like @ifcond.  I'd prefer not to throw "predicate" into the
cauldron.  IfCond?  IfConditional?

> +    """An 'if' condition predicate"""
> +
> +    def cgen(self) -> str:
> +        raise NotImplementedError()
> +
> +    def docgen(self) -> str:
> +        raise NotImplementedError()
> +
> +
> +class IfOption(IfPredicate):

The name IfOption tells me nothing.

At this point in the series, the IfOption are arbitrary C preprocessor
expressions.

At the end of the series, they are operands of the C preprocessor's
unary operator defined, i.e. a C macro name.

Once I know that, IfOption kind of makes sense.  Hmm.  IfConfigOption?
IfIdentifier?  IfLeaf?  I'm not quite sure which one I dislike the least
:)

> +    def __init__(self, option: str):
> +        self.option = option
> +
> +    def cgen(self) -> str:
> +        return self.option
> +
> +    def docgen(self) -> str:
> +        return self.option
> +
> +    def __repr__(self) -> str:
> +        return f"{type(self).__name__}({self.option!r})"

Intended use?

> +
> +    def __eq__(self, other: object) -> bool:
> +        if not isinstance(other, IfOption):
> +            return NotImplemented
> +        return self.option == other.option

Why raise on type mismatch?  Feels rather un-pythonic to me.

> +
> +
> +class IfAll(IfPredicate):
> +    def __init__(self, pred_list: Sequence[IfPredicate]):
> +        self.pred_list = pred_list
> +
> +    def cgen(self) -> str:
> +        return " && ".join([p.cgen() for p in self.pred_list])

Fragile.  See my review of PATCH 3.

> +
> +    def docgen(self) -> str:
> +        return " and ".join([p.docgen() for p in self.pred_list])
> +
> +    def __bool__(self) -> bool:
> +        return bool(self.pred_list)

Not as confusing as QAPISchemaIfCond.__bool__() as long as it's kept
well away from None.  Still, I'm not sure I like it.

> +
> +    def __repr__(self) -> str:
> +        return f"{type(self).__name__}({self.pred_list!r})"
> +
> +    def __eq__(self, other: object) -> bool:
> +        if not isinstance(other, IfAll):
> +            return NotImplemented
> +        return self.pred_list == other.pred_list

Same as above.

Why are these classes in common.py?

> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index aa4715c519..f52caaeecc 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
> +from .common import (
> +    POINTER_SUFFIX,
> +    IfAll,
> +    IfOption,
> +    c_name,
> +)
>  from .error import QAPIError, QAPISemError, QAPISourceError
>  from .expr import check_exprs
>  from .parser import QAPISchemaParser
> @@ -28,18 +33,22 @@
>  class QAPISchemaIfCond:
>      def __init__(self, ifcond=None):
>          self.ifcond = ifcond or []
> +        self.pred = IfAll([IfOption(opt) for opt in self.ifcond])

In the common case of just one element, we get a no-op IfAll wrapped
around it.  Okay.

self.ifcond goes away in PATCH 7.  Okay.

> +
> +    def docgen(self):
> +        return self.pred.docgen()
>  
>      def cgen(self):
> -        return ' && '.join(self.ifcond)
> +        return self.pred.cgen()
>  
>      # Returns true if the condition is not void
>      def __bool__(self):
> -        return bool(self.ifcond)
> +        return bool(self.pred)
>  
>      def __eq__(self, other):
>          if not isinstance(other, QAPISchemaIfCond):
>              return NotImplemented
> -        return self.ifcond == other.ifcond
> +        return self.pred == other.pred

Not much left in this class, and it's going to get even thinner.

>  
>  
>  class QAPISchemaEntity:
> diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> index 8f54ceff2e..db1d23c6bf 100644
> --- a/tests/qapi-schema/doc-good.out
> +++ b/tests/qapi-schema/doc-good.out
> @@ -12,15 +12,15 @@ enum QType
>  module doc-good.json
>  enum Enum
>      member one
> -        if ['defined(IFONE)']
> +        if IfAll([IfOption('defined(IFONE)')])
>      member two
> -    if ['defined(IFCOND)']
> +    if IfAll([IfOption('defined(IFCOND)')])
>      feature enum-feat
>  object Base
>      member base1: Enum optional=False
>  object Variant1
>      member var1: str optional=False
> -        if ['defined(IFSTR)']
> +        if IfAll([IfOption('defined(IFSTR)')])
>          feature member-feat
>      feature variant1-feat
>  object Variant2
> @@ -29,7 +29,7 @@ object Object
>      tag base1
>      case one: Variant1
>      case two: Variant2
> -        if ['IFTWO']
> +        if IfAll([IfOption('IFTWO')])
>      feature union-feat1
>  object q_obj_Variant1-wrapper
>      member data: Variant1 optional=False
> @@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper
>  enum SugaredUnionKind
>      member one
>      member two
> -        if ['IFTWO']
> +        if IfAll([IfOption('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 IfAll([IfOption('IFTWO')])
>      feature union-feat2
>  alternate Alternate
>      tag type
> diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
> index e0b8a5f0b6..e4e0fb173a 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 IfAll([IfOption('defined(TEST_IF_STRUCT_BAR)')])
> +    if IfAll([IfOption('defined(TEST_IF_STRUCT)')])
>  enum TestIfEnum
>      member foo
>      member bar
> -        if ['defined(TEST_IF_ENUM_BAR)']
> -    if ['defined(TEST_IF_ENUM)']
> +        if IfAll([IfOption('defined(TEST_IF_ENUM_BAR)')])
> +    if IfAll([IfOption('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)']
> +        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
> +    if IfAll([IfOption('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)']
> +        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
> +    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_ALT_BAR)')])
> +    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_CMD_BAR)')])
> +    if IfAll([IfOption('defined(TEST_IF_CMD)'), IfOption('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 IfAll([IfOption('defined(TEST_IF_CMD)'), IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_EVT_BAR)')])
> +    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
>  object CondFeatureStruct2
>      member foo: int optional=False
>      feature feature1
> -        if ['defined(TEST_IF_FEATURE_1)']
> +        if IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
>      feature feature2
> -        if ['defined(TEST_IF_FEATURE_2)']
> +        if IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_COND_1)'), IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
>      feature feature2
> -        if ['defined(TEST_IF_FEATURE_2)']
> +        if IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_COND_1)'), IfOption('defined(TEST_IF_COND_2)')])
>  event TEST_EVENT_FEATURES0 FeatureStruct1
>      boxed=False
>  event TEST_EVENT_FEATURES1 None
> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> index 2ec328b22e..631e255fba 100755
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -95,7 +95,7 @@ def _print_variants(variants):
>      @staticmethod
>      def _print_if(ifcond, indent=4):
>          if ifcond:
> -            print('%sif %s' % (' ' * indent, ifcond.ifcond))
> +            print('%sif %s' % (' ' * indent, ifcond.pred))
>  
>      @classmethod
>      def _print_features(cls, features, indent=4):



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

* Re: [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree
  2021-06-08 12:07 ` [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
@ 2021-06-15 11:34   ` Markus Armbruster
  2021-06-18 10:36     ` Marc-André Lureau
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2021-06-15 11:34 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Modify check_if() to normalize the condition tree.

How is it normalized?  Let me rephrase my question: how does the IR
change?  If the generated code changes, how?

> Add _make_if() to build a QAPISchemaIfCond tree.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> Tested-by: John Snow <jsnow@redhat.com>
> ---
>  tests/unit/test-qmp-cmds.c                    |  1 +
>  scripts/qapi/expr.py                          | 51 +++++++++------
>  scripts/qapi/schema.py                        | 62 +++++++++++++------
>  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.out                | 12 ++--
>  tests/qapi-schema/enum-if-invalid.err         |  3 +-
>  tests/qapi-schema/features-if-invalid.err     |  2 +-
>  tests/qapi-schema/qapi-schema-test.json       | 32 ++++++----
>  tests/qapi-schema/qapi-schema-test.out        | 59 ++++++++++--------
>  .../qapi-schema/struct-member-if-invalid.err  |  2 +-
>  .../qapi-schema/union-branch-if-invalid.json  |  2 +-
>  13 files changed, 143 insertions(+), 90 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/expr.py b/scripts/qapi/expr.py
> index 496f7e0333..60ffe9ef03 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -261,12 +261,12 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>      """
>      Normalize and 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]``.

Double-checking: is this doc comment correct before this patch?

> +    The ``if`` field may be either a ``str`` or a dict.
> +    A ``str`` element will be normalized to ``{'all': List[str]}``.
>  
>      :forms:
> -      :sugared: ``Union[str, List[str]]``
> -      :canonical: ``List[str]``
> +      :sugared: ``Union[str, dict]``
> +      :canonical: ``Union[str, dict]``
>  
>      :param expr: The expression containing the ``if`` member to validate.
>      :param info: QAPI schema source file information.
> @@ -281,25 +281,38 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>      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]
> -
> -    for elt in ifcond:
> -        if not isinstance(elt, str):
> +    def normalize(cond: Union[str, object]) -> Union[str, object]:

This definition is in the middle of check_if()'s body.  Intentional?

> +        if isinstance(cond, str):
> +            if not cond.strip():
> +                raise QAPISemError(
> +                    info,
> +                    "'if' condition '%s' of %s makes no sense"
> +                    % (cond, source))
> +            return cond
> +        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: "

Exactly one key, to be precise.

> +                "'all', 'any' or 'not'" % source)
> +        check_keys(cond, info, "'if' condition", [],
> +                   ["all", "any", "not"])

Hmmm.  Getting members of @cond before check_keys() would be wrong, but
you don't do that.  Still, I like to call check_keys() early, just to
reduce the chance for accidents.

If we check_keys() first, we're left with just two possible errors:
empty dict (len(cond)==0), and conflicting keys (len(cond)>1).  We could
choose to diagnose them separately, but it's probably not worth the
bother.

> +        oper, operands = next(iter(cond.items()))
> +        if not operands:
> +            raise QAPISemError(
> +                info, "'if' condition [] of %s is useless" % source)
> +        if oper == "not":
> +            return {oper: normalize(operands)}
> +        if oper in ("all", "any") and not isinstance(operands, list):
> +            raise QAPISemError(
> +                info, "'%s' condition of %s must be a list" % (oper, source))
> +        operands = [normalize(o) for o in operands]
> +        return {oper: operands}

I guess making this a function enables writing

               return NE

instead of

               expr['if] = NE
               return

which is slightly more compact, and factors out the assignment's left
hand side.  Feels like a wash, but up to you.

> +
> +    expr['if'] = normalize(ifcond)
>  
>  
>  def normalize_members(members: object) -> None:
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index f52caaeecc..9864e49c54 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -22,6 +22,8 @@
>  from .common import (
>      POINTER_SUFFIX,
>      IfAll,
> +    IfAny,
> +    IfNot,
>      IfOption,
>      c_name,
>  )
> @@ -31,15 +33,14 @@
>  
>  
>  class QAPISchemaIfCond:
> -    def __init__(self, ifcond=None):
> -        self.ifcond = ifcond or []
> -        self.pred = IfAll([IfOption(opt) for opt in self.ifcond])
> +    def __init__(self, pred=None):
> +        self.pred = pred
>  
>      def docgen(self):
> -        return self.pred.docgen()
> +        return self and self.pred.docgen()

The more code relying on your __bool__() methods I see, the less I like
them.

Can we do self.pred and self.pred.docgen()?

>  
>      def cgen(self):
> -        return self.pred.cgen()
> +        return self and self.pred.cgen()

Likewise.

>  
>      # Returns true if the condition is not void
>      def __bool__(self):
> @@ -991,16 +992,41 @@ def _def_predefineds(self):
>          self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
>                                              qtype_values, 'QTYPE'))
>  
> +    def _make_if(self, cond):
> +        if isinstance(cond, QAPISchemaIfCond):
> +            return cond
> +        if cond is None:
> +            return QAPISchemaIfCond()
> +
> +        def make_pred(node):
> +            if isinstance(node, str):
> +                return IfOption(node)
> +            oper, operands = next(iter(node.items()))
> +            op2cls = {
> +                'all': IfAll,
> +                'any': IfAny,
> +                'not': IfNot,
> +            }
> +
> +            if isinstance(operands, list):
> +                operands = [make_pred(o) for o in operands]
> +            else:
> +                operands = make_pred(operands)
> +
> +            return op2cls[oper](operands)
> +
> +        return QAPISchemaIfCond(make_pred(cond))

Maybe it's the weather, but I find this pretty impenetrable right now.

make_if() appears to accept either QAPISchemaIfCond, None, or a tree
whose inner nodes are {'any': List[tree]}, {'all': List[tree]}, {'not':
tree}, or str.  Quite the omnivore.

None of the callers I can see pass QAPISchemaIfCond.  Am I confused?

make_pred() appears to accept the tree.  The part dealing with str is
obvious.

next(iter(node.items())) appears to map a dict {key: val} to a tuple
(key, val).  Too clever by half.

val, and thus @operands then is either a list of trees (all, any), or a
tree (not).

The tree(s) in @operands get recursively processed.  Now @operands is
either a List[IfPredicate], or an IfPredicate.

IfAll and IfAny take the former, IfNot takes the latter.  Works out
(*quack*), but I'm not sure mypy will be happy with it.

> +
>      def _make_features(self, features, info):
>          if features is None:
>              return []
>          return [QAPISchemaFeature(f['name'], info,
> -                                  QAPISchemaIfCond(f.get('if')))
> +                                  self._make_if(f.get('if')))
>                  for f in features]
>  
>      def _make_enum_members(self, values, info):
>          return [QAPISchemaEnumMember(v['name'], info,
> -                                     QAPISchemaIfCond(v.get('if')))
> +                                     self._make_if(v.get('if')))
>                  for v in values]
>  
>      def _make_implicit_enum_type(self, name, info, ifcond, values):
> @@ -1046,7 +1072,7 @@ def _def_enum_type(self, expr, info, doc):
>          name = expr['enum']
>          data = expr['data']
>          prefix = expr.get('prefix')
> -        ifcond = QAPISchemaIfCond(expr.get('if'))
> +        ifcond = self._make_if(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          self._def_entity(QAPISchemaEnumType(
>              name, info, doc, ifcond, features,
> @@ -1065,7 +1091,7 @@ def _make_member(self, name, typ, ifcond, features, info):
>  
>      def _make_members(self, data, info):
>          return [self._make_member(key, value['type'],
> -                                  QAPISchemaIfCond(value.get('if')),
> +                                  self._make_if(value.get('if')),
>                                    value.get('features'), info)
>                  for (key, value) in data.items()]
>  
> @@ -1073,7 +1099,7 @@ def _def_struct_type(self, expr, info, doc):
>          name = expr['struct']
>          base = expr.get('base')
>          data = expr['data']
> -        ifcond = QAPISchemaIfCond(expr.get('if'))
> +        ifcond = self._make_if(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          self._def_entity(QAPISchemaObjectType(
>              name, info, doc, ifcond, features, base,
> @@ -1096,7 +1122,7 @@ def _def_union_type(self, expr, info, doc):
>          name = expr['union']
>          data = expr['data']
>          base = expr.get('base')
> -        ifcond = QAPISchemaIfCond(expr.get('if'))
> +        ifcond = self._make_if(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          tag_name = expr.get('discriminator')
>          tag_member = None
> @@ -1107,7 +1133,7 @@ def _def_union_type(self, expr, info, doc):
>          if tag_name:
>              variants = [
>                  self._make_variant(key, value['type'],
> -                                   QAPISchemaIfCond(value.get('if')),
> +                                   self._make_if(value.get('if')),
>                                     info)
>                  for (key, value) in data.items()
>              ]
> @@ -1115,11 +1141,11 @@ def _def_union_type(self, expr, info, doc):
>          else:
>              variants = [
>                  self._make_simple_variant(key, value['type'],
> -                                          QAPISchemaIfCond(value.get('if')),
> +                                          self._make_if(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]

Another riddle for me to solve?

>              typ = self._make_implicit_enum_type(name, info, ifcond, enum)
>              tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
>              members = [tag_member]
> @@ -1132,11 +1158,11 @@ def _def_union_type(self, expr, info, doc):
>      def _def_alternate_type(self, expr, info, doc):
>          name = expr['alternate']
>          data = expr['data']
> -        ifcond = QAPISchemaIfCond(expr.get('if'))
> +        ifcond = self._make_if(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          variants = [
>              self._make_variant(key, value['type'],
> -                               QAPISchemaIfCond(value.get('if')),
> +                               self._make_if(value.get('if')),
>                                 info)
>              for (key, value) in data.items()
>          ]
> @@ -1156,7 +1182,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 = QAPISchemaIfCond(expr.get('if'))
> +        ifcond = self._make_if(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          if isinstance(data, OrderedDict):
>              data = self._make_implicit_object_type(
> @@ -1175,7 +1201,7 @@ def _def_event(self, expr, info, doc):
>          name = expr['event']
>          data = expr.get('data')
>          boxed = expr.get('boxed', False)
> -        ifcond = QAPISchemaIfCond(expr.get('if'))
> +        ifcond = self._make_if(expr.get('if'))
>          features = self._make_features(expr.get('features'), info)
>          if isinstance(data, OrderedDict):
>              data = self._make_implicit_object_type(
> 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..454fbae387 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', 'any', 'not'.
> diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out
> index db1d23c6bf..4d951f97ee 100644
> --- a/tests/qapi-schema/doc-good.out
> +++ b/tests/qapi-schema/doc-good.out
> @@ -12,15 +12,15 @@ enum QType
>  module doc-good.json
>  enum Enum
>      member one
> -        if IfAll([IfOption('defined(IFONE)')])
> +        if IfOption('defined(IFONE)')
>      member two
> -    if IfAll([IfOption('defined(IFCOND)')])
> +    if IfOption('defined(IFCOND)')
>      feature enum-feat
>  object Base
>      member base1: Enum optional=False
>  object Variant1
>      member var1: str optional=False
> -        if IfAll([IfOption('defined(IFSTR)')])
> +        if IfOption('defined(IFSTR)')
>          feature member-feat
>      feature variant1-feat
>  object Variant2
> @@ -29,7 +29,7 @@ object Object
>      tag base1
>      case one: Variant1
>      case two: Variant2
> -        if IfAll([IfOption('IFTWO')])
> +        if IfOption('IFTWO')
>      feature union-feat1
>  object q_obj_Variant1-wrapper
>      member data: Variant1 optional=False
> @@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper
>  enum SugaredUnionKind
>      member one
>      member two
> -        if IfAll([IfOption('IFTWO')])
> +        if IfOption('IFTWO')
>  object SugaredUnion
>      member type: SugaredUnionKind optional=False
>      tag type
>      case one: q_obj_Variant1-wrapper
>      case two: q_obj_Variant2-wrapper
> -        if IfAll([IfOption('IFTWO')])
> +        if IfOption('IFTWO')
>      feature union-feat2
>  alternate Alternate
>      tag type
[...]

Skipping the tests for now because I'm running out of brain-juice.



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

* Re: [PATCH v5 1/9] docs: update the documentation about schema configuration
  2021-06-08 12:07 ` [PATCH v5 1/9] docs: update the documentation about schema configuration marcandre.lureau
@ 2021-06-15 11:48   ` Markus Armbruster
  2021-06-18 10:32     ` Marc-André Lureau
  0 siblings, 1 reply; 25+ messages in thread
From: Markus Armbruster @ 2021-06-15 11:48 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

marcandre.lureau@redhat.com writes:

> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Update the documentation describing the changes in this series.

Suggest to add "upfront" for clarity.

>
> 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..0162b73119 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 generated from that condition:
> +
> +   * STRING will generate #if defined(STRING)
> +   * { 'all': [COND, ...] } will generate #if (COND && ...)
> +   * { 'any': [COND, ...] } will generate #if (COND || ...)
> +   * { 'not': COND } will generate #if !COND

I know this is exactly what I suggested.  It gets the point across, but
it's not quite accurate: the #if of course only at the root of the tree,
not at every level.  Better, I think:

   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



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

* Re: [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor
  2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (8 preceding siblings ...)
  2021-06-08 12:07 ` [PATCH v5 9/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
@ 2021-06-15 11:56 ` Markus Armbruster
  9 siblings, 0 replies; 25+ messages in thread
From: Markus Armbruster @ 2021-06-15 11:56 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

Done, except for the tests in PATCH 7.

I agree with the QAPI schema language change.

Having this many classes just for conditionals feels tiresome.  I'm
tempted to try axing all but one just to see how it comes out.  This is
not a demand.

Let's discuss my review comments, and then figure out what still needs
to be done to get merged.



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

* Re: [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-06-14 12:20   ` Markus Armbruster
@ 2021-06-16 10:28     ` Marc-André Lureau
  2021-06-18  9:35       ` Markus Armbruster
  0 siblings, 1 reply; 25+ messages in thread
From: Marc-André Lureau @ 2021-06-16 10:28 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, QEMU

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

Hi

On Mon, Jun 14, 2021 at 4:20 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Wrap the 'if' condition in a higher-level object. Not only does this
>
> I can see "wrap in an object".  I'm afraid don't get what makes it
> "higher-level".
>

ok


> > provide more type safety but it also enables further refactoring without
> > too much churn.
>
> I figure by "more type safety" you mean "can't accidentally confuse some
> other list of strings with a conditional", which is true, but isn't
> really about *type* safety.
>
> Maybe:
>
>   Wrap the 'if' condition in an object.  Not only is this a bit safer,
>   it also enables further refactoring without too much churn.
>
>
ok

> The following patches will change the syntax of the schema 'if'
> > conditions to be predicate expressions, and will generate code for
> > different target languages (C, and Rust in another series).
>
> Since different target languages aren't actually generated in this
> series, I'd say "and will enable generating code for different target
> languages, such as Rust."
>

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>
> > ---
> >  docs/sphinx/qapidoc.py         |  2 +-
> >  scripts/qapi/commands.py       |  4 +-
> >  scripts/qapi/events.py         |  5 ++-
> >  scripts/qapi/gen.py            | 14 +++----
> >  scripts/qapi/introspect.py     | 26 ++++++------
> >  scripts/qapi/schema.py         | 74 +++++++++++++++++++++++-----------
> >  scripts/qapi/types.py          | 33 +++++++--------
> >  scripts/qapi/visit.py          | 23 ++++++-----
> >  tests/qapi-schema/test-qapi.py |  2 +-
> >  9 files changed, 106 insertions(+), 77 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index 87c67ab23f..b737949007 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
>
> Note for later: many hunks replace ifcond (the unwrapped Sequence[str])
> by ifcond.ifcond (the wrapped one, with the wrapper peeled off).
> Mechanical.
>
> > 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],
>
> Note for later: many hunks replace ifcond: Sequence[str] or
> Iterable[str] by ifcond: QAPISchemaIfCond.  Mechanical.
>
> > 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(),
>
> The same QAPISchemaIfCond object gets reused every time we don't pass an
> @ifcond argument.  Looks a bit scary, but works, because we don't ever
> mutate it.
>
> Elsewhere, we None as default, though: QAPISchemaEntity.__init__(),
> QAPISchemaMember.__init__().
>

A mechanical change, isn't it?


> >                    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..bc357ebbfa 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -25,6 +25,20 @@
> >  from .parser import QAPISchemaParser
> >
> >
> > +class QAPISchemaIfCond:
> > +    def __init__(self, ifcond=None):
> > +        self.ifcond = ifcond or []
> > +
> > +    # Returns true if the condition is not void
> > +    def __bool__(self):
> > +        return bool(self.ifcond)
>
> I'm not sure I like this one.
>
> In the two places where we default parameter icond=None, we use
>
>     ifcond or QAPISchemaIfCond()
>
> to flip to the default value we actually want.  Works as intended for
> None and for non-empty QAPISchemaIfCond.  For empty QAPISchemaIfCond, it
> throws away the value and creates a new, equivalent one.  Works, but
> kind of by accident.
>
> This is an instance of a more general problem: when I see an
> Optional[ObjectType], I expect it to be falsy if and only if it's None.
> Perhaps I shouldn't.  Doesn't mean we should press __bool__() into
> service for checking "is there a condition".  A boring non-dunder method
> might be clearer.
>
> I understand what you mean by "condition is void", but it sounds a bit
> odd to me.  How do you like "Is a condition present?"
>

The current code uses falsy values for ifcond (whether it is [], (), None
whatever). Implementing __bool__() allowed to keep the existing condition
code (ie: if ifcond).

After the wrapping object is introduced, we have "if ifcond.ifcond", which
is quite ugly.

If you think "if ifcond" isn't clear enough (with __bool__()), we can have
"if ifcond.is_present()". I don't have a preference.


> > +
> > +    def __eq__(self, other):
> > +        if not isinstance(other, QAPISchemaIfCond):
> > +            return NotImplemented
> > +        return self.ifcond == other.ifcond
>
> Stupid question: why do we need to override __eq__()?
>
> Hmm, probably for _make_implicit_object_type().
>
>
Yes, the code works with schema objects and ifcond. I'll special case the
assertion for now, and remove that method.


> Why raise on type mismatch?  Feels rather un-pythonic to me.
>

What else should it do? Could probably be removed for now.


> > +
> > +
> >  class QAPISchemaEntity:
> >      meta: Optional[str] = None
> >
> > @@ -42,7 +56,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
> >
> > @@ -77,7 +91,7 @@ def set_module(self, schema):
> >
> >      @property
> >      def ifcond(self):
> > -        assert self._checked
> > +        assert self._checked and isinstance(self._ifcond,
> QAPISchemaIfCond)
> >          return self._ifcond
> >
> >      def is_implicit(self):
> > @@ -601,7 +615,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 not self.tag_member.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 +660,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 +982,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]
> >
>
> Note for later: several hunks wrap condition expressions in an object
> like this.  Mechanical.
>
> >      def _make_implicit_enum_type(self, name, info, ifcond, values):
> > @@ -1008,7 +1024,7 @@ 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
> > +            assert ifcond == typ._ifcond
>
> I'm not sure I understand this part.  Leaving for later.
>
> >          else:
> >              self._def_entity(QAPISchemaObjectType(
> >                  name, info, None, ifcond, None, None, members, None))
> > @@ -1018,7 +1034,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 +1052,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 +1061,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 +1084,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 +1093,21 @@ 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()
> > +            ]
>
> I think we more usually put the closing parenthesis like this:
>
>                variants = [
>                   ...
>                   for (key, value) in data.items()]
>
> More of the same below.
>

Works for me.


> >              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 +1120,14 @@ 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 +1144,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 +1163,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..2ec328b22e 100755
> > --- a/tests/qapi-schema/test-qapi.py
> > +++ b/tests/qapi-schema/test-qapi.py
> > @@ -95,7 +95,7 @@ def _print_variants(variants):
> >      @staticmethod
> >      def _print_if(ifcond, indent=4):
> >          if ifcond:
> > -            print('%sif %s' % (' ' * indent, ifcond))
> > +            print('%sif %s' % (' ' * indent, ifcond.ifcond))
> >
> >      @classmethod
> >      def _print_features(cls, features, indent=4):
>
> If feel this is a bit harder to review than necessary, because you take
> two steps at once:
>
> 1. Wrap Sequence[str] in an object
>
> 2. Add methods to the object to clean up the resulting mess some
>
> Step 1. by itself should be pretty much mechanical: adjust the type
> hints, create the wrapper object on write, peel it off on read.  The
> result will be slightly ugly in places.
>
> I'd expect step 2 to be much smaller, and easier to understand.  It
> could perhaps be split into one patch per method, but I hope it's
> reviewable just fine even without.
>
>
>

-- 
Marc-André Lureau

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

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

* Re: [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string
  2021-06-14 12:45   ` Markus Armbruster
  2021-06-14 14:14     ` Markus Armbruster
@ 2021-06-16 10:44     ` Marc-André Lureau
  2021-06-18  9:41       ` Markus Armbruster
  1 sibling, 1 reply; 25+ messages in thread
From: Marc-André Lureau @ 2021-06-16 10:44 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, QEMU

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

Hi

On Mon, Jun 14, 2021 at 4:48 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().
>
> I understand why you're doing this, but only because I know where you're
> headed.  By itself, it is not an improvement: you move C generation out
> of common.py into schema.py.  You need to explain why that's useful.
>
>
What about?

In the following commits, QAPISchemaIfCond is going to hold an internal
tree structure. Moving cgen() there allows to abstract away the condition
representation.



> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index bc357ebbfa..aa4715c519 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 cgen(self):
> > +        return ' && '.join(self.ifcond)
>
> Fragile.  Better:
>
>            return '(' + ') && ('.join(self.ifcond) + ')'
>
>
This is an intermediary step, but ok.

If we want to keep C generation details out of schema.py, we need a
> helper mapping self.ifcond: Sequence[str] to C code, similar to how
> QAPISchemaEntity.c_name() works with helper c_name().
>

Leaving a FIXME.


> > +
> >      # Returns true if the condition is not void
> >      def __bool__(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('''
> >      },
> [More of the same snipped...]
>
>
>

-- 
Marc-André Lureau

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

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

* Re: [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-06-16 10:28     ` Marc-André Lureau
@ 2021-06-18  9:35       ` Markus Armbruster
  0 siblings, 0 replies; 25+ messages in thread
From: Markus Armbruster @ 2021-06-18  9:35 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: John Snow, Eric Blake, Markus Armbruster, QEMU

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

> Hi
>
> On Mon, Jun 14, 2021 at 4:20 PM Markus Armbruster <armbru@redhat.com> wrote:
>
>> marcandre.lureau@redhat.com writes:
>>
>> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
>> >
>> > Wrap the 'if' condition in a higher-level object. Not only does this
>>
>> I can see "wrap in an object".  I'm afraid don't get what makes it
>> "higher-level".
>>
>
> ok
>
>
>> > provide more type safety but it also enables further refactoring without
>> > too much churn.
>>
>> I figure by "more type safety" you mean "can't accidentally confuse some
>> other list of strings with a conditional", which is true, but isn't
>> really about *type* safety.
>>
>> Maybe:
>>
>>   Wrap the 'if' condition in an object.  Not only is this a bit safer,
>>   it also enables further refactoring without too much churn.
>>
>>
> ok
>
>> > The following patches will change the syntax of the schema 'if'
>> > conditions to be predicate expressions, and will generate code for
>> > different target languages (C, and Rust in another series).
>>
>> Since different target languages aren't actually generated in this
>> series, I'd say "and will enable generating code for different target
>> languages, such as Rust."
>>
>
> 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>
>> > ---
>> >  docs/sphinx/qapidoc.py         |  2 +-
>> >  scripts/qapi/commands.py       |  4 +-
>> >  scripts/qapi/events.py         |  5 ++-
>> >  scripts/qapi/gen.py            | 14 +++----
>> >  scripts/qapi/introspect.py     | 26 ++++++------
>> >  scripts/qapi/schema.py         | 74 +++++++++++++++++++++++-----------
>> >  scripts/qapi/types.py          | 33 +++++++--------
>> >  scripts/qapi/visit.py          | 23 ++++++-----
>> >  tests/qapi-schema/test-qapi.py |  2 +-
>> >  9 files changed, 106 insertions(+), 77 deletions(-)
>> >
>> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> > index 87c67ab23f..b737949007 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
>>
>> Note for later: many hunks replace ifcond (the unwrapped Sequence[str])
>> by ifcond.ifcond (the wrapped one, with the wrapper peeled off).
>> Mechanical.
>>
>> > 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],
>>
>> Note for later: many hunks replace ifcond: Sequence[str] or
>> Iterable[str] by ifcond: QAPISchemaIfCond.  Mechanical.
>>
>> > 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(),
>>
>> The same QAPISchemaIfCond object gets reused every time we don't pass an
>> @ifcond argument.  Looks a bit scary, but works, because we don't ever
>> mutate it.
>>
>> Elsewhere, we None as default, though: QAPISchemaEntity.__init__(),
>> QAPISchemaMember.__init__().
>>
>
> A mechanical change, isn't it?

We do the same thing in two different ways.

One: default parameter ifcond to QAPISchemaIfCond(), done.

Two: default it to None, use it like ifcond or QAPISchemaIfCond().

The first way is less verbose, but looks like a Python rookie mistake at
a glance.

Can we agree on one way to do this?

>> >                    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..bc357ebbfa 100644
>> > --- a/scripts/qapi/schema.py
>> > +++ b/scripts/qapi/schema.py
>> > @@ -25,6 +25,20 @@
>> >  from .parser import QAPISchemaParser
>> >
>> >
>> > +class QAPISchemaIfCond:
>> > +    def __init__(self, ifcond=None):
>> > +        self.ifcond = ifcond or []
>> > +
>> > +    # Returns true if the condition is not void
>> > +    def __bool__(self):
>> > +        return bool(self.ifcond)
>>
>> I'm not sure I like this one.
>>
>> In the two places where we default parameter icond=None, we use
>>
>>     ifcond or QAPISchemaIfCond()
>>
>> to flip to the default value we actually want.  Works as intended for
>> None and for non-empty QAPISchemaIfCond.  For empty QAPISchemaIfCond, it
>> throws away the value and creates a new, equivalent one.  Works, but
>> kind of by accident.
>>
>> This is an instance of a more general problem: when I see an
>> Optional[ObjectType], I expect it to be falsy if and only if it's None.
>> Perhaps I shouldn't.  Doesn't mean we should press __bool__() into
>> service for checking "is there a condition".  A boring non-dunder method
>> might be clearer.
>>
>> I understand what you mean by "condition is void", but it sounds a bit
>> odd to me.  How do you like "Is a condition present?"
>>
>
> The current code uses falsy values for ifcond (whether it is [], (), None
> whatever). Implementing __bool__() allowed to keep the existing condition
> code (ie: if ifcond).
>
> After the wrapping object is introduced, we have "if ifcond.ifcond", which
> is quite ugly.

It is.

> If you think "if ifcond" isn't clear enough (with __bool__()), we can have
> "if ifcond.is_present()". I don't have a preference.

I think I'd prefer .is_present().  I'm kind of wary of overriding
dunders when it can make innocent-looking code behave in possibly
surprising ways.  Perhaps I just haven't been steeped in the Python
sauce long enough.

John, what do you think?

>> > +
>> > +    def __eq__(self, other):
>> > +        if not isinstance(other, QAPISchemaIfCond):
>> > +            return NotImplemented
>> > +        return self.ifcond == other.ifcond
>>
>> Stupid question: why do we need to override __eq__()?
>>
>> Hmm, probably for _make_implicit_object_type().
>>
>>
> Yes, the code works with schema objects and ifcond. I'll special case the
> assertion for now, and remove that method.
>
>
>> Why raise on type mismatch?  Feels rather un-pythonic to me.
>>
>
> What else should it do? Could probably be removed for now.

Python lets me compare arbitrary objects regardless of type.  Different
objects are unqual, unless __eq__ makes them equal.

You want to make different QAPISchemaIfCond wrapping the equal .ifcond
equal.  Makes sense to me.  But I don't see a need for also changing the
case "@other is not a QAPISchemaIfCond" from False to NotImplemented.

>> > +
>> > +
>> >  class QAPISchemaEntity:
>> >      meta: Optional[str] = None
>> >
>> > @@ -42,7 +56,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
>> >
>> > @@ -77,7 +91,7 @@ def set_module(self, schema):
>> >
>> >      @property
>> >      def ifcond(self):
>> > -        assert self._checked
>> > +        assert self._checked and isinstance(self._ifcond, QAPISchemaIfCond)
>> >          return self._ifcond
>> >
>> >      def is_implicit(self):
>> > @@ -601,7 +615,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 not self.tag_member.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 +660,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 +982,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]
>> >
>>
>> Note for later: several hunks wrap condition expressions in an object
>> like this.  Mechanical.
>>
>> >      def _make_implicit_enum_type(self, name, info, ifcond, values):
>> > @@ -1008,7 +1024,7 @@ 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
>> > +            assert ifcond == typ._ifcond
>>
>> I'm not sure I understand this part.  Leaving for later.
>>
>> >          else:
>> >              self._def_entity(QAPISchemaObjectType(
>> >                  name, info, None, ifcond, None, None, members, None))
>> > @@ -1018,7 +1034,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 +1052,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 +1061,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 +1084,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 +1093,21 @@ 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()
>> > +            ]
>>
>> I think we more usually put the closing parenthesis like this:
>>
>>                variants = [
>>                   ...
>>                   for (key, value) in data.items()]
>>
>> More of the same below.
>>
>
> Works for me.
>
>
>> >              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 +1120,14 @@ 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 +1144,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 +1163,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..2ec328b22e 100755
>> > --- a/tests/qapi-schema/test-qapi.py
>> > +++ b/tests/qapi-schema/test-qapi.py
>> > @@ -95,7 +95,7 @@ def _print_variants(variants):
>> >      @staticmethod
>> >      def _print_if(ifcond, indent=4):
>> >          if ifcond:
>> > -            print('%sif %s' % (' ' * indent, ifcond))
>> > +            print('%sif %s' % (' ' * indent, ifcond.ifcond))
>> >
>> >      @classmethod
>> >      def _print_features(cls, features, indent=4):
>>
>> If feel this is a bit harder to review than necessary, because you take
>> two steps at once:
>>
>> 1. Wrap Sequence[str] in an object
>>
>> 2. Add methods to the object to clean up the resulting mess some
>>
>> Step 1. by itself should be pretty much mechanical: adjust the type
>> hints, create the wrapper object on write, peel it off on read.  The
>> result will be slightly ugly in places.
>>
>> I'd expect step 2 to be much smaller, and easier to understand.  It
>> could perhaps be split into one patch per method, but I hope it's
>> reviewable just fine even without.

Hope you didn't miss this remark.



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

* Re: [PATCH v5 9/9] qapi: make 'if' condition strings simple identifiers
  2021-06-08 12:07 ` [PATCH v5 9/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
@ 2021-06-18  9:36   ` Markus Armbruster
  0 siblings, 0 replies; 25+ messages in thread
From: Markus Armbruster @ 2021-06-18  9:36 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, Eric Blake, qemu-devel

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.
>
> 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/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
> index 726727af74..27b7ce8799 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")

Preexisting: the text generated for conditionals is crap.  Your patch
doesn't make it any worse.

>     The _one_ {and only}
>  
>  "two"
> @@ -62,7 +62,7 @@ Features
>  If
>  ~~
>  
> -"defined(IFCOND)"
> +"IFCOND"
>  
>  
>  "Base" (Object)
> @@ -87,7 +87,7 @@ Another paragraph (but no "var": line)
>  Members
>  ~~~~~~~
>  
> -"var1": "string" (**If: **"defined(IFSTR)")
> +"var1": "string" (**If: **"IFSTR")
>     Not documented
>  
>  

[...]



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

* Re: [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string
  2021-06-16 10:44     ` Marc-André Lureau
@ 2021-06-18  9:41       ` Markus Armbruster
  0 siblings, 0 replies; 25+ messages in thread
From: Markus Armbruster @ 2021-06-18  9:41 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: John Snow, Eric Blake, QEMU

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

> Hi
>
> On Mon, Jun 14, 2021 at 4:48 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().
>>
>> I understand why you're doing this, but only because I know where you're
>> headed.  By itself, it is not an improvement: you move C generation out
>> of common.py into schema.py.  You need to explain why that's useful.
>>
>>
> What about?
>
> In the following commits, QAPISchemaIfCond is going to hold an internal
> tree structure. Moving cgen() there allows to abstract away the condition
> representation.

Yes, that's better.

[...]



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

* Re: [PATCH v5 4/9] qapi: start building an 'if' predicate tree
  2021-06-14 14:38   ` Markus Armbruster
@ 2021-06-18 10:31     ` Marc-André Lureau
  0 siblings, 0 replies; 25+ messages in thread
From: Marc-André Lureau @ 2021-06-18 10:31 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, QEMU

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

Hi

On Mon, Jun 14, 2021 at 6:39 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > The following patches are going to express schema 'if' conditions in a
> > target language agnostic way. For that, let's start building a predicate
> > tree of the configuration options.
> >
> > This intermediary steps still uses C-preprocessor expressions as
> > the predicates:
> >
> > "if: [STR, ..]" is translated to a "IfCond -> IfAll ->
> > [IfOption, ..]" tree, which will generate "#if STR && .." C code.
> >
> > Once the boolean operation tree nodes are introduced, the 'if'
> > expressions will be converted to replace the C syntax (no more
> > !defined(), &&, ...) and based only on option identifiers.
> >
> > For now, the condition tree will be less expressive than a full C macro
> > expression as it will only support these operations: 'all', 'any' and
> > 'not', the only ones needed so far.
> >
> > 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/sphinx/qapidoc.py                 |  6 +--
> >  scripts/qapi/common.py                 | 53 ++++++++++++++++++++++-
> >  scripts/qapi/schema.py                 | 17 ++++++--
> >  tests/qapi-schema/doc-good.out         | 12 +++---
> >  tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
> >  tests/qapi-schema/test-qapi.py         |  2 +-
> >  6 files changed, 103 insertions(+), 45 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index b737949007..0f87fb16ce 100644
> > --- a/docs/sphinx/qapidoc.py
> > +++ b/docs/sphinx/qapidoc.py
> > @@ -112,12 +112,10 @@ 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(', '))
> > +        condlist = [nodes.literal('', ifcond.docgen())]
> >          if not with_if:
> >              return condlist
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index c305aaf2f1..01e3203545 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -12,7 +12,7 @@
> >  # See the COPYING file in the top-level directory.
> >
> >  import re
> > -from typing import Match, Optional
> > +from typing import Match, Optional, Sequence
> >
> >
> >  #: Magic string that gets removed along with all space to its right.
> > @@ -214,3 +214,54 @@ def must_match(pattern: str, string: str) ->
> Match[str]:
> >      match = re.match(pattern, string)
> >      assert match is not None
> >      return match
> > +
> > +
> > +class IfPredicate:
>
> This is the abstract base class of the two (soon four) forms 'if'.
> qapi-code-gen.txt calls them "conditionals", and the code so far uses
> names like @ifcond.  I'd prefer not to throw "predicate" into the
> cauldron.  IfCond?  IfConditional?
>
>
ok


> > +    """An 'if' condition predicate"""
> > +
> > +    def cgen(self) -> str:
> > +        raise NotImplementedError()
> > +
> > +    def docgen(self) -> str:
> > +        raise NotImplementedError()
> > +
> > +
> > +class IfOption(IfPredicate):
>
> The name IfOption tells me nothing.
>
> At this point in the series, the IfOption are arbitrary C preprocessor
> expressions.
>
> At the end of the series, they are operands of the C preprocessor's
> unary operator defined, i.e. a C macro name.
>
> Once I know that, IfOption kind of makes sense.  Hmm.  IfConfigOption?
> IfIdentifier?  IfLeaf?  I'm not quite sure which one I dislike the least
> :)
>

Ok IfConfigOption.

>
> > +    def __init__(self, option: str):
> > +        self.option = option
> > +
> > +    def cgen(self) -> str:
> > +        return self.option
> > +
> > +    def docgen(self) -> str:
> > +        return self.option
> > +
> > +    def __repr__(self) -> str:
> > +        return f"{type(self).__name__}({self.option!r})"
>
> Intended use?
>

%s in test-qapi


> > +
> > +    def __eq__(self, other: object) -> bool:
> > +        if not isinstance(other, IfOption):
> > +            return NotImplemented
> > +        return self.option == other.option
>
> Why raise on type mismatch?  Feels rather un-pythonic to me.
>

removed


> > +
> > +
> > +class IfAll(IfPredicate):
> > +    def __init__(self, pred_list: Sequence[IfPredicate]):
> > +        self.pred_list = pred_list
> > +
> > +    def cgen(self) -> str:
> > +        return " && ".join([p.cgen() for p in self.pred_list])
>
> Fragile.  See my review of PATCH 3.
>
>
ok

> +
> > +    def docgen(self) -> str:
> > +        return " and ".join([p.docgen() for p in self.pred_list])
> > +
> > +    def __bool__(self) -> bool:
> > +        return bool(self.pred_list)
>
> Not as confusing as QAPISchemaIfCond.__bool__() as long as it's kept
> well away from None.  Still, I'm not sure I like it.
>
>
removed

> +
> > +    def __repr__(self) -> str:
> > +        return f"{type(self).__name__}({self.pred_list!r})"
> > +
> > +    def __eq__(self, other: object) -> bool:
> > +        if not isinstance(other, IfAll):
> > +            return NotImplemented
> > +        return self.pred_list == other.pred_list
>
> Same as above.
>
> Why are these classes in common.py?
>

moved to schema.py


>
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index aa4715c519..f52caaeecc 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
> > +from .common import (
> > +    POINTER_SUFFIX,
> > +    IfAll,
> > +    IfOption,
> > +    c_name,
> > +)
> >  from .error import QAPIError, QAPISemError, QAPISourceError
> >  from .expr import check_exprs
> >  from .parser import QAPISchemaParser
> > @@ -28,18 +33,22 @@
> >  class QAPISchemaIfCond:
> >      def __init__(self, ifcond=None):
> >          self.ifcond = ifcond or []
> > +        self.pred = IfAll([IfOption(opt) for opt in self.ifcond])
>
> In the common case of just one element, we get a no-op IfAll wrapped
> around it.  Okay.
>
> self.ifcond goes away in PATCH 7.  Okay.
>
> > +
> > +    def docgen(self):
> > +        return self.pred.docgen()
> >
> >      def cgen(self):
> > -        return ' && '.join(self.ifcond)
> > +        return self.pred.cgen()
> >
> >      # Returns true if the condition is not void
> >      def __bool__(self):
> > -        return bool(self.ifcond)
> > +        return bool(self.pred)
> >
> >      def __eq__(self, other):
> >          if not isinstance(other, QAPISchemaIfCond):
> >              return NotImplemented
> > -        return self.ifcond == other.ifcond
> > +        return self.pred == other.pred
>
> Not much left in this class, and it's going to get even thinner.
>

Yes, see v7.


> >
> >
> >  class QAPISchemaEntity:
> > diff --git a/tests/qapi-schema/doc-good.out
> b/tests/qapi-schema/doc-good.out
> > index 8f54ceff2e..db1d23c6bf 100644
> > --- a/tests/qapi-schema/doc-good.out
> > +++ b/tests/qapi-schema/doc-good.out
> > @@ -12,15 +12,15 @@ enum QType
> >  module doc-good.json
> >  enum Enum
> >      member one
> > -        if ['defined(IFONE)']
> > +        if IfAll([IfOption('defined(IFONE)')])
> >      member two
> > -    if ['defined(IFCOND)']
> > +    if IfAll([IfOption('defined(IFCOND)')])
> >      feature enum-feat
> >  object Base
> >      member base1: Enum optional=False
> >  object Variant1
> >      member var1: str optional=False
> > -        if ['defined(IFSTR)']
> > +        if IfAll([IfOption('defined(IFSTR)')])
> >          feature member-feat
> >      feature variant1-feat
> >  object Variant2
> > @@ -29,7 +29,7 @@ object Object
> >      tag base1
> >      case one: Variant1
> >      case two: Variant2
> > -        if ['IFTWO']
> > +        if IfAll([IfOption('IFTWO')])
> >      feature union-feat1
> >  object q_obj_Variant1-wrapper
> >      member data: Variant1 optional=False
> > @@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper
> >  enum SugaredUnionKind
> >      member one
> >      member two
> > -        if ['IFTWO']
> > +        if IfAll([IfOption('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 IfAll([IfOption('IFTWO')])
> >      feature union-feat2
> >  alternate Alternate
> >      tag type
> > diff --git a/tests/qapi-schema/qapi-schema-test.out
> b/tests/qapi-schema/qapi-schema-test.out
> > index e0b8a5f0b6..e4e0fb173a 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 IfAll([IfOption('defined(TEST_IF_STRUCT_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_STRUCT)')])
> >  enum TestIfEnum
> >      member foo
> >      member bar
> > -        if ['defined(TEST_IF_ENUM_BAR)']
> > -    if ['defined(TEST_IF_ENUM)']
> > +        if IfAll([IfOption('defined(TEST_IF_ENUM_BAR)')])
> > +    if IfAll([IfOption('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)']
> > +        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
> > +    if IfAll([IfOption('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)']
> > +        if IfAll([IfOption('defined(TEST_IF_UNION_BAR)')])
> > +    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_ALT_BAR)')])
> > +    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_CMD_BAR)')])
> > +    if IfAll([IfOption('defined(TEST_IF_CMD)'),
> IfOption('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 IfAll([IfOption('defined(TEST_IF_CMD)'),
> IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_EVT_BAR)')])
> > +    if IfAll([IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
> >  object CondFeatureStruct2
> >      member foo: int optional=False
> >      feature feature1
> > -        if ['defined(TEST_IF_FEATURE_1)']
> > +        if IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
> >      feature feature2
> > -        if ['defined(TEST_IF_FEATURE_2)']
> > +        if IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_COND_1)'),
> IfOption('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 IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_FEATURE_1)')])
> >      feature feature2
> > -        if ['defined(TEST_IF_FEATURE_2)']
> > +        if IfAll([IfOption('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 IfAll([IfOption('defined(TEST_IF_COND_1)'),
> IfOption('defined(TEST_IF_COND_2)')])
> >  event TEST_EVENT_FEATURES0 FeatureStruct1
> >      boxed=False
> >  event TEST_EVENT_FEATURES1 None
> > diff --git a/tests/qapi-schema/test-qapi.py
> b/tests/qapi-schema/test-qapi.py
> > index 2ec328b22e..631e255fba 100755
> > --- a/tests/qapi-schema/test-qapi.py
> > +++ b/tests/qapi-schema/test-qapi.py
> > @@ -95,7 +95,7 @@ def _print_variants(variants):
> >      @staticmethod
> >      def _print_if(ifcond, indent=4):
> >          if ifcond:
> > -            print('%sif %s' % (' ' * indent, ifcond.ifcond))
> > +            print('%sif %s' % (' ' * indent, ifcond.pred))
> >
> >      @classmethod
> >      def _print_features(cls, features, indent=4):
>
>
>

-- 
Marc-André Lureau

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

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

* Re: [PATCH v5 1/9] docs: update the documentation about schema configuration
  2021-06-15 11:48   ` Markus Armbruster
@ 2021-06-18 10:32     ` Marc-André Lureau
  0 siblings, 0 replies; 25+ messages in thread
From: Marc-André Lureau @ 2021-06-18 10:32 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, QEMU

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

Hi

On Tue, Jun 15, 2021 at 3:53 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Update the documentation describing the changes in this series.
>
> Suggest to add "upfront" for clarity.
>

done


> >
> > 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..0162b73119 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 generated from that condition:
> > +
> > +   * STRING will generate #if defined(STRING)
> > +   * { 'all': [COND, ...] } will generate #if (COND && ...)
> > +   * { 'any': [COND, ...] } will generate #if (COND || ...)
> > +   * { 'not': COND } will generate #if !COND
>
> I know this is exactly what I suggested.  It gets the point across, but
> it's not quite accurate: the #if of course only at the root of the tree,
> not at every level.  Better, I think:
>
>    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
>
>
ok


> >
> >  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
>
>
>

-- 
Marc-André Lureau

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

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

* Re: [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree
  2021-06-15 11:34   ` Markus Armbruster
@ 2021-06-18 10:36     ` Marc-André Lureau
  0 siblings, 0 replies; 25+ messages in thread
From: Marc-André Lureau @ 2021-06-18 10:36 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: Eric Blake, John Snow, QEMU

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

Hi

On Tue, Jun 15, 2021 at 3:34 PM Markus Armbruster <armbru@redhat.com> wrote:

> marcandre.lureau@redhat.com writes:
>
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Modify check_if() to normalize the condition tree.
>
> How is it normalized?  Let me rephrase my question: how does the IR
> change?  If the generated code changes, how?
>

Not anymore, since we dropped the sugar form. Updated in v6.


> > Add _make_if() to build a QAPISchemaIfCond tree.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> > Tested-by: John Snow <jsnow@redhat.com>
> > ---
> >  tests/unit/test-qmp-cmds.c                    |  1 +
> >  scripts/qapi/expr.py                          | 51 +++++++++------
> >  scripts/qapi/schema.py                        | 62 +++++++++++++------
> >  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.out                | 12 ++--
> >  tests/qapi-schema/enum-if-invalid.err         |  3 +-
> >  tests/qapi-schema/features-if-invalid.err     |  2 +-
> >  tests/qapi-schema/qapi-schema-test.json       | 32 ++++++----
> >  tests/qapi-schema/qapi-schema-test.out        | 59 ++++++++++--------
> >  .../qapi-schema/struct-member-if-invalid.err  |  2 +-
> >  .../qapi-schema/union-branch-if-invalid.json  |  2 +-
> >  13 files changed, 143 insertions(+), 90 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/expr.py b/scripts/qapi/expr.py
> > index 496f7e0333..60ffe9ef03 100644
> > --- a/scripts/qapi/expr.py
> > +++ b/scripts/qapi/expr.py
> > @@ -261,12 +261,12 @@ def check_if(expr: _JSONObject, info:
> QAPISourceInfo, source: str) -> None:
> >      """
> >      Normalize and 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]``.
>
> Double-checking: is this doc comment correct before this patch?
>

I think it was


> > +    The ``if`` field may be either a ``str`` or a dict.
> > +    A ``str`` element will be normalized to ``{'all': List[str]}``.
> >
> >      :forms:
> > -      :sugared: ``Union[str, List[str]]``
> > -      :canonical: ``List[str]``
> > +      :sugared: ``Union[str, dict]``
> > +      :canonical: ``Union[str, dict]``
> >
> >      :param expr: The expression containing the ``if`` member to
> validate.
> >      :param info: QAPI schema source file information.
> > @@ -281,25 +281,38 @@ def check_if(expr: _JSONObject, info:
> QAPISourceInfo, source: str) -> None:
> >      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]
> > -
> > -    for elt in ifcond:
> > -        if not isinstance(elt, str):
> > +    def normalize(cond: Union[str, object]) -> Union[str, object]:
>
> This definition is in the middle of check_if()'s body.  Intentional?
>

why not


> > +        if isinstance(cond, str):
> > +            if not cond.strip():
> > +                raise QAPISemError(
> > +                    info,
> > +                    "'if' condition '%s' of %s makes no sense"
> > +                    % (cond, source))
> > +            return cond
> > +        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: "
>
> Exactly one key, to be precise.
>
> > +                "'all', 'any' or 'not'" % source)
> > +        check_keys(cond, info, "'if' condition", [],
> > +                   ["all", "any", "not"])
>
> Hmmm.  Getting members of @cond before check_keys() would be wrong, but
> you don't do that.  Still, I like to call check_keys() early, just to
> reduce the chance for accidents.
>
> If we check_keys() first, we're left with just two possible errors:
> empty dict (len(cond)==0), and conflicting keys (len(cond)>1).  We could
> choose to diagnose them separately, but it's probably not worth the
> bother.
>
> > +        oper, operands = next(iter(cond.items()))
> > +        if not operands:
> > +            raise QAPISemError(
> > +                info, "'if' condition [] of %s is useless" % source)
> > +        if oper == "not":
> > +            return {oper: normalize(operands)}
> > +        if oper in ("all", "any") and not isinstance(operands, list):
> > +            raise QAPISemError(
> > +                info, "'%s' condition of %s must be a list" % (oper,
> source))
> > +        operands = [normalize(o) for o in operands]
> > +        return {oper: operands}
>
> I guess making this a function enables writing
>
>                return NE
>
> instead of
>
>                expr['if] = NE
>                return
>
> which is slightly more compact, and factors out the assignment's left
> hand side.  Feels like a wash, but up to you.
>
>
gone in v6

> +
> > +    expr['if'] = normalize(ifcond)
> >
> >
> >  def normalize_members(members: object) -> None:
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index f52caaeecc..9864e49c54 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -22,6 +22,8 @@
> >  from .common import (
> >      POINTER_SUFFIX,
> >      IfAll,
> > +    IfAny,
> > +    IfNot,
> >      IfOption,
> >      c_name,
> >  )
> > @@ -31,15 +33,14 @@
> >
> >
> >  class QAPISchemaIfCond:
> > -    def __init__(self, ifcond=None):
> > -        self.ifcond = ifcond or []
> > -        self.pred = IfAll([IfOption(opt) for opt in self.ifcond])
> > +    def __init__(self, pred=None):
> > +        self.pred = pred
> >
> >      def docgen(self):
> > -        return self.pred.docgen()
> > +        return self and self.pred.docgen()
>
> The more code relying on your __bool__() methods I see, the less I like
> them.
>
> Can we do self.pred and self.pred.docgen()?
>
> >
> >      def cgen(self):
> > -        return self.pred.cgen()
> > +        return self and self.pred.cgen()
>
> Likewise.
>
> >
> >      # Returns true if the condition is not void
> >      def __bool__(self):
> > @@ -991,16 +992,41 @@ def _def_predefineds(self):
> >          self._def_entity(QAPISchemaEnumType('QType', None, None, None,
> None,
> >                                              qtype_values, 'QTYPE'))
> >
> > +    def _make_if(self, cond):
> > +        if isinstance(cond, QAPISchemaIfCond):
> > +            return cond
> > +        if cond is None:
> > +            return QAPISchemaIfCond()
> > +
> > +        def make_pred(node):
> > +            if isinstance(node, str):
> > +                return IfOption(node)
> > +            oper, operands = next(iter(node.items()))
> > +            op2cls = {
> > +                'all': IfAll,
> > +                'any': IfAny,
> > +                'not': IfNot,
> > +            }
> > +
> > +            if isinstance(operands, list):
> > +                operands = [make_pred(o) for o in operands]
> > +            else:
> > +                operands = make_pred(operands)
> > +
> > +            return op2cls[oper](operands)
> > +
> > +        return QAPISchemaIfCond(make_pred(cond))
>
> Maybe it's the weather, but I find this pretty impenetrable right now.
>
> make_if() appears to accept either QAPISchemaIfCond, None, or a tree
> whose inner nodes are {'any': List[tree]}, {'all': List[tree]}, {'not':
> tree}, or str.  Quite the omnivore.
>
> None of the callers I can see pass QAPISchemaIfCond.  Am I confused?
>
> make_pred() appears to accept the tree.  The part dealing with str is
> obvious.
>
> next(iter(node.items())) appears to map a dict {key: val} to a tuple
> (key, val).  Too clever by half.
>
> val, and thus @operands then is either a list of trees (all, any), or a
> tree (not).
>
> The tree(s) in @operands get recursively processed.  Now @operands is
> either a List[IfPredicate], or an IfPredicate.
>
> IfAll and IfAny take the former, IfNot takes the latter.  Works out
> (*quack*), but I'm not sure mypy will be happy with it.
>

I removed the IfCond AST altogether in v6.


> > +
> >      def _make_features(self, features, info):
> >          if features is None:
> >              return []
> >          return [QAPISchemaFeature(f['name'], info,
> > -                                  QAPISchemaIfCond(f.get('if')))
> > +                                  self._make_if(f.get('if')))
> >                  for f in features]
> >
> >      def _make_enum_members(self, values, info):
> >          return [QAPISchemaEnumMember(v['name'], info,
> > -                                     QAPISchemaIfCond(v.get('if')))
> > +                                     self._make_if(v.get('if')))
> >                  for v in values]
> >
> >      def _make_implicit_enum_type(self, name, info, ifcond, values):
> > @@ -1046,7 +1072,7 @@ def _def_enum_type(self, expr, info, doc):
> >          name = expr['enum']
> >          data = expr['data']
> >          prefix = expr.get('prefix')
> > -        ifcond = QAPISchemaIfCond(expr.get('if'))
> > +        ifcond = self._make_if(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          self._def_entity(QAPISchemaEnumType(
> >              name, info, doc, ifcond, features,
> > @@ -1065,7 +1091,7 @@ def _make_member(self, name, typ, ifcond,
> features, info):
> >
> >      def _make_members(self, data, info):
> >          return [self._make_member(key, value['type'],
> > -                                  QAPISchemaIfCond(value.get('if')),
> > +                                  self._make_if(value.get('if')),
> >                                    value.get('features'), info)
> >                  for (key, value) in data.items()]
> >
> > @@ -1073,7 +1099,7 @@ def _def_struct_type(self, expr, info, doc):
> >          name = expr['struct']
> >          base = expr.get('base')
> >          data = expr['data']
> > -        ifcond = QAPISchemaIfCond(expr.get('if'))
> > +        ifcond = self._make_if(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          self._def_entity(QAPISchemaObjectType(
> >              name, info, doc, ifcond, features, base,
> > @@ -1096,7 +1122,7 @@ def _def_union_type(self, expr, info, doc):
> >          name = expr['union']
> >          data = expr['data']
> >          base = expr.get('base')
> > -        ifcond = QAPISchemaIfCond(expr.get('if'))
> > +        ifcond = self._make_if(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          tag_name = expr.get('discriminator')
> >          tag_member = None
> > @@ -1107,7 +1133,7 @@ def _def_union_type(self, expr, info, doc):
> >          if tag_name:
> >              variants = [
> >                  self._make_variant(key, value['type'],
> > -                                   QAPISchemaIfCond(value.get('if')),
> > +                                   self._make_if(value.get('if')),
> >                                     info)
> >                  for (key, value) in data.items()
> >              ]
> > @@ -1115,11 +1141,11 @@ def _def_union_type(self, expr, info, doc):
> >          else:
> >              variants = [
> >                  self._make_simple_variant(key, value['type'],
> > -
> QAPISchemaIfCond(value.get('if')),
> > +
> self._make_if(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]
>
> Another riddle for me to solve?
>

See [PATCH v6 04/11] qapi: _make_enum_members() to work with pre-built
QAPISchemaIfCond


> >              typ = self._make_implicit_enum_type(name, info, ifcond,
> enum)
> >              tag_member = QAPISchemaObjectTypeMember('type', info, typ,
> False)
> >              members = [tag_member]
> > @@ -1132,11 +1158,11 @@ def _def_union_type(self, expr, info, doc):
> >      def _def_alternate_type(self, expr, info, doc):
> >          name = expr['alternate']
> >          data = expr['data']
> > -        ifcond = QAPISchemaIfCond(expr.get('if'))
> > +        ifcond = self._make_if(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          variants = [
> >              self._make_variant(key, value['type'],
> > -                               QAPISchemaIfCond(value.get('if')),
> > +                               self._make_if(value.get('if')),
> >                                 info)
> >              for (key, value) in data.items()
> >          ]
> > @@ -1156,7 +1182,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 = QAPISchemaIfCond(expr.get('if'))
> > +        ifcond = self._make_if(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          if isinstance(data, OrderedDict):
> >              data = self._make_implicit_object_type(
> > @@ -1175,7 +1201,7 @@ def _def_event(self, expr, info, doc):
> >          name = expr['event']
> >          data = expr.get('data')
> >          boxed = expr.get('boxed', False)
> > -        ifcond = QAPISchemaIfCond(expr.get('if'))
> > +        ifcond = self._make_if(expr.get('if'))
> >          features = self._make_features(expr.get('features'), info)
> >          if isinstance(data, OrderedDict):
> >              data = self._make_implicit_object_type(
> > 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..454fbae387 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', 'any', 'not'.
> > diff --git a/tests/qapi-schema/doc-good.out
> b/tests/qapi-schema/doc-good.out
> > index db1d23c6bf..4d951f97ee 100644
> > --- a/tests/qapi-schema/doc-good.out
> > +++ b/tests/qapi-schema/doc-good.out
> > @@ -12,15 +12,15 @@ enum QType
> >  module doc-good.json
> >  enum Enum
> >      member one
> > -        if IfAll([IfOption('defined(IFONE)')])
> > +        if IfOption('defined(IFONE)')
> >      member two
> > -    if IfAll([IfOption('defined(IFCOND)')])
> > +    if IfOption('defined(IFCOND)')
> >      feature enum-feat
> >  object Base
> >      member base1: Enum optional=False
> >  object Variant1
> >      member var1: str optional=False
> > -        if IfAll([IfOption('defined(IFSTR)')])
> > +        if IfOption('defined(IFSTR)')
> >          feature member-feat
> >      feature variant1-feat
> >  object Variant2
> > @@ -29,7 +29,7 @@ object Object
> >      tag base1
> >      case one: Variant1
> >      case two: Variant2
> > -        if IfAll([IfOption('IFTWO')])
> > +        if IfOption('IFTWO')
> >      feature union-feat1
> >  object q_obj_Variant1-wrapper
> >      member data: Variant1 optional=False
> > @@ -38,13 +38,13 @@ object q_obj_Variant2-wrapper
> >  enum SugaredUnionKind
> >      member one
> >      member two
> > -        if IfAll([IfOption('IFTWO')])
> > +        if IfOption('IFTWO')
> >  object SugaredUnion
> >      member type: SugaredUnionKind optional=False
> >      tag type
> >      case one: q_obj_Variant1-wrapper
> >      case two: q_obj_Variant2-wrapper
> > -        if IfAll([IfOption('IFTWO')])
> > +        if IfOption('IFTWO')
> >      feature union-feat2
> >  alternate Alternate
> >      tag type
> [...]
>
> Skipping the tests for now because I'm running out of brain-juice.
>
>
>

-- 
Marc-André Lureau

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

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

end of thread, other threads:[~2021-06-18 10:43 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-08 12:07 [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 1/9] docs: update the documentation about schema configuration marcandre.lureau
2021-06-15 11:48   ` Markus Armbruster
2021-06-18 10:32     ` Marc-André Lureau
2021-06-08 12:07 ` [PATCH v5 2/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
2021-06-14 12:20   ` Markus Armbruster
2021-06-16 10:28     ` Marc-André Lureau
2021-06-18  9:35       ` Markus Armbruster
2021-06-08 12:07 ` [PATCH v5 3/9] qapi: make gen_if/gen_endif take a simple string marcandre.lureau
2021-06-14 12:45   ` Markus Armbruster
2021-06-14 14:14     ` Markus Armbruster
2021-06-16 10:44     ` Marc-André Lureau
2021-06-18  9:41       ` Markus Armbruster
2021-06-08 12:07 ` [PATCH v5 4/9] qapi: start building an 'if' predicate tree marcandre.lureau
2021-06-14 14:38   ` Markus Armbruster
2021-06-18 10:31     ` Marc-André Lureau
2021-06-08 12:07 ` [PATCH v5 5/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 6/9] qapi: add IfNot marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 7/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
2021-06-15 11:34   ` Markus Armbruster
2021-06-18 10:36     ` Marc-André Lureau
2021-06-08 12:07 ` [PATCH v5 8/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
2021-06-08 12:07 ` [PATCH v5 9/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
2021-06-18  9:36   ` Markus Armbruster
2021-06-15 11:56 ` [PATCH v5 0/9] qapi: untie 'if' conditions from C preprocessor Markus Armbruster

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.