qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor
@ 2021-04-29 13:40 marcandre.lureau
  2021-04-29 13:40 ` [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
                   ` (9 more replies)
  0 siblings, 10 replies; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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)

This is based on John Snow QAPI pt4:
https://patchew.org/QEMU/20210421192233.3542904-1-jsnow@redhat.com/

Based-on: <20210421192233.3542904-2-jsnow@redhat.com>

thanks

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):
  qapi: replace List[str] by QAPISchemaIfCond
  qapi: move gen_if/gen_endif to QAPISchemaIfCond
  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: update the documentation about schema configuration

 docs/devel/qapi-code-gen.txt                  |  33 +++---
 docs/sphinx/qapidoc.py                        |   6 +-
 qapi/block-core.json                          |  16 +--
 qapi/block-export.json                        |   6 +-
 qapi/char.json                                |   8 +-
 qapi/machine-target.json                      |  28 +++--
 qapi/migration.json                           |  10 +-
 qapi/misc-target.json                         |  37 +++---
 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                        | 106 +++++++++++++++---
 scripts/qapi/events.py                        |   5 +-
 scripts/qapi/expr.py                          |  62 +++++++---
 scripts/qapi/gen.py                           |  16 ++-
 scripts/qapi/introspect.py                    |  33 +++---
 scripts/qapi/schema.py                        |  99 ++++++++++++----
 scripts/qapi/types.py                         |  43 ++++---
 scripts/qapi/visit.py                         |  25 ++---
 .../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.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       |  58 +++++-----
 tests/qapi-schema/qapi-schema-test.out        |  67 ++++++-----
 .../qapi-schema/struct-member-if-invalid.err  |   2 +-
 tests/qapi-schema/union-branch-if-invalid.err |   2 +-
 37 files changed, 482 insertions(+), 297 deletions(-)

-- 
2.29.0




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

* [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-12 20:53   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond marcandre.lureau
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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

Wrap the 'if' condition in a higher-level object. Not only this allows
more type safety but also 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>
---
 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     | 78 +++++++++++++++++++++++++++-----------
 scripts/qapi/types.py      | 33 ++++++++--------
 scripts/qapi/visit.py      | 23 +++++------
 8 files changed, 110 insertions(+), 75 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 3a4172fb74..7d6f390fa6 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -25,6 +25,22 @@
 from .parser import QAPISchemaParser
 
 
+class QAPISchemaIfCond:
+    def __init__(self, ifcond=None):
+        self.ifcond = ifcond or []
+
+    def __bool__(self):
+        return bool(self.ifcond)
+
+    def __repr__(self):
+        return repr(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 +58,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 +93,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 +617,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 +662,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):
@@ -958,14 +974,20 @@ def _def_predefineds(self):
         self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
                                             qtype_values, 'QTYPE'))
 
+    def _get_if(self, f) -> QAPISchemaIfCond:
+        ifcond = f.get('if')
+        if isinstance(ifcond, QAPISchemaIfCond):
+            return ifcond
+        return QAPISchemaIfCond(ifcond)
+
     def _make_features(self, features, info):
         if features is None:
             return []
-        return [QAPISchemaFeature(f['name'], info, f.get('if'))
+        return [QAPISchemaFeature(f['name'], info, self._get_if(f))
                 for f in features]
 
     def _make_enum_members(self, values, info):
-        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
+        return [QAPISchemaEnumMember(v['name'], info, self._get_if(v))
                 for v in values]
 
     def _make_implicit_enum_type(self, name, info, ifcond, values):
@@ -1001,7 +1023,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))
@@ -1011,7 +1033,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,
@@ -1029,7 +1051,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()]
 
@@ -1037,7 +1060,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,
@@ -1060,7 +1083,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
@@ -1069,14 +1092,20 @@ 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()]
+            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} for v in variants]
             typ = self._make_implicit_enum_type(name, info, ifcond, enum)
             tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
@@ -1090,11 +1119,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,
@@ -1111,7 +1143,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(
@@ -1130,7 +1162,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):
-- 
2.29.0



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

* [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
  2021-04-29 13:40 ` [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-11 16:39   ` Stefan Hajnoczi
  2021-05-12 21:01   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 3/9] qapi: start building an 'if' predicate tree marcandre.lureau
                   ` (7 subsequent siblings)
  9 siblings, 2 replies; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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

Move the generating function to the QAPISchemaIfCond class.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/common.py     | 20 +-------------------
 scripts/qapi/gen.py        |  6 ++----
 scripts/qapi/introspect.py | 11 +++--------
 scripts/qapi/schema.py     | 18 +++++++++++++++++-
 scripts/qapi/types.py      | 28 +++++++++++-----------------
 scripts/qapi/visit.py      | 14 ++++++--------
 6 files changed, 40 insertions(+), 57 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index cbd3fd81d3..b7f475a160 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 Optional, Sequence
+from typing import Optional
 
 
 #: Magic string that gets removed along with all space to its right.
@@ -192,21 +192,3 @@ def guardend(name: str) -> str:
 #endif /* %(name)s */
 ''',
                  name=c_fname(name).upper())
-
-
-def gen_if(ifcond: Sequence[str]) -> str:
-    ret = ''
-    for ifc in ifcond:
-        ret += mcgen('''
-#if %(cond)s
-''', cond=ifc)
-    return ret
-
-
-def gen_endif(ifcond: Sequence[str]) -> str:
-    ret = ''
-    for ifc in reversed(ifcond):
-        ret += mcgen('''
-#endif /* %(cond)s */
-''', cond=ifc)
-    return ret
diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
index 1c5b190276..ab26d5c937 100644
--- a/scripts/qapi/gen.py
+++ b/scripts/qapi/gen.py
@@ -24,8 +24,6 @@
 from .common import (
     c_fname,
     c_name,
-    gen_endif,
-    gen_if,
     guardend,
     guardstart,
     mcgen,
@@ -95,9 +93,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 += ifcond.gen_if()
     out += added
-    out += gen_endif(ifcond.ifcond)
+    out += ifcond.gen_endif()
     return out
 
 
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 77a8c33ad4..a2a8a57b9a 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -22,12 +22,7 @@
     Union,
 )
 
-from .common import (
-    c_name,
-    gen_endif,
-    gen_if,
-    mcgen,
-)
+from .common import c_name, mcgen
 from .gen import QAPISchemaMonolithicCVisitor
 from .schema import (
     QAPISchema,
@@ -124,10 +119,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 += obj.ifcond.gen_if()
         ret += _tree_to_qlit(obj.value, level)
         if obj.ifcond:
-            ret += '\n' + gen_endif(obj.ifcond.ifcond)
+            ret += '\n' + obj.ifcond.gen_endif()
         return ret
 
     ret = ''
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 7d6f390fa6..8e6d0a5296 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -19,7 +19,7 @@
 import re
 from typing import Optional
 
-from .common import POINTER_SUFFIX, c_name
+from .common import POINTER_SUFFIX, c_name, mcgen
 from .error import QAPISemError, QAPISourceError
 from .expr import check_exprs
 from .parser import QAPISchemaParser
@@ -29,6 +29,22 @@ class QAPISchemaIfCond:
     def __init__(self, ifcond=None):
         self.ifcond = ifcond or []
 
+    def gen_if(self):
+        ret = ''
+        for ifc in self.ifcond:
+            ret += mcgen('''
+#if %(cond)s
+''', cond=ifc)
+        return ret
+
+    def gen_endif(self):
+        ret = ''
+        for ifc in reversed(self.ifcond):
+            ret += mcgen('''
+#endif /* %(cond)s */
+''', cond=ifc)
+        return ret
+
     def __bool__(self):
         return bool(self.ifcond)
 
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 3673cf0f49..831294fe42 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -15,13 +15,7 @@
 
 from typing import List, Optional
 
-from .common import (
-    c_enum_const,
-    c_name,
-    gen_endif,
-    gen_if,
-    mcgen,
-)
+from .common import c_enum_const, c_name, mcgen
 from .gen import QAPISchemaModularCVisitor, ifcontext
 from .schema import (
     QAPISchema,
@@ -51,13 +45,13 @@ def gen_enum_lookup(name: str,
 ''',
                 c_name=c_name(name))
     for memb in members:
-        ret += gen_if(memb.ifcond.ifcond)
+        ret += memb.ifcond.gen_if()
         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 += memb.ifcond.gen_endif()
 
     ret += mcgen('''
     },
@@ -81,12 +75,12 @@ def gen_enum(name: str,
                 c_name=c_name(name))
 
     for memb in enum_members:
-        ret += gen_if(memb.ifcond.ifcond)
+        ret += memb.ifcond.gen_if()
         ret += mcgen('''
     %(c_enum)s,
 ''',
                      c_enum=c_enum_const(name, memb.name, prefix))
-        ret += gen_endif(memb.ifcond.ifcond)
+        ret += memb.ifcond.gen_endif()
 
     ret += mcgen('''
 } %(c_name)s;
@@ -126,7 +120,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 += memb.ifcond.gen_if()
         if memb.optional:
             ret += mcgen('''
     bool has_%(c_name)s;
@@ -136,7 +130,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 += memb.ifcond.gen_endif()
     return ret
 
 
@@ -159,7 +153,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
     ret += mcgen('''
 
 ''')
-    ret += gen_if(ifcond.ifcond)
+    ret += ifcond.gen_if()
     ret += mcgen('''
 struct %(c_name)s {
 ''',
@@ -193,7 +187,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
     ret += mcgen('''
 };
 ''')
-    ret += gen_endif(ifcond.ifcond)
+    ret += ifcond.gen_endif()
 
     return ret
 
@@ -220,13 +214,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 += var.ifcond.gen_if()
         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 += var.ifcond.gen_endif()
 
     ret += mcgen('''
     } u;
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index 67721b2470..9d9196a143 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -18,8 +18,6 @@
 from .common import (
     c_enum_const,
     c_name,
-    gen_endif,
-    gen_if,
     indent,
     mcgen,
 )
@@ -79,7 +77,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 += memb.ifcond.gen_if()
         if memb.optional:
             ret += mcgen('''
     if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
@@ -112,7 +110,7 @@ def gen_visit_object_members(name: str,
             ret += mcgen('''
     }
 ''')
-        ret += gen_endif(memb.ifcond.ifcond)
+        ret += memb.ifcond.gen_endif()
 
     if variants:
         tag_member = variants.tag_member
@@ -126,7 +124,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 += var.ifcond.gen_if()
             if var.type.name == 'q_empty':
                 # valid variant and nothing to do
                 ret += mcgen('''
@@ -142,7 +140,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 += var.ifcond.gen_endif()
         ret += mcgen('''
     default:
         abort();
@@ -228,7 +226,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 += var.ifcond.gen_if()
         ret += mcgen('''
     case %(case)s:
 ''',
@@ -254,7 +252,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
         ret += mcgen('''
         break;
 ''')
-        ret += gen_endif(var.ifcond.ifcond)
+        ret += var.ifcond.gen_endif()
 
     ret += mcgen('''
     case QTYPE_NONE:
-- 
2.29.0



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

* [PATCH v3 3/9] qapi: start building an 'if' predicate tree
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
  2021-04-29 13:40 ` [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
  2021-04-29 13:40 ` [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-12 21:39   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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>
---
 docs/sphinx/qapidoc.py                 |  6 +--
 scripts/qapi/common.py                 | 54 +++++++++++++++++++++++-
 scripts/qapi/schema.py                 | 42 ++++++++++++-------
 tests/qapi-schema/doc-good.out         | 12 +++---
 tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
 5 files changed, 116 insertions(+), 56 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index b737949007..a93f3f1c4d 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.gen_doc())]
         if not with_if:
             return condlist
 
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index b7f475a160..59a7ee2f32 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -11,8 +11,9 @@
 # This work is licensed under the terms of the GNU GPL, version 2.
 # See the COPYING file in the top-level directory.
 
+from abc import ABC, abstractmethod
 import re
-from typing import Optional
+from typing import Optional, Sequence
 
 
 #: Magic string that gets removed along with all space to its right.
@@ -192,3 +193,54 @@ def guardend(name: str) -> str:
 #endif /* %(name)s */
 ''',
                  name=c_fname(name).upper())
+
+
+class IfPredicate(ABC):
+    @abstractmethod
+    def cgen(self) -> str:
+        pass
+
+    @abstractmethod
+    def docgen(self) -> str:
+        pass
+
+
+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 repr(self.option)
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, IfOption):
+            return False
+        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"IfAll({self.pred_list})"
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, IfAll):
+            return False
+        return self.pred_list == other.pred_list
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 8e6d0a5296..366a53ab64 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -19,7 +19,13 @@
 import re
 from typing import Optional
 
-from .common import POINTER_SUFFIX, c_name, mcgen
+from .common import (
+    POINTER_SUFFIX,
+    IfAll,
+    IfOption,
+    c_name,
+    mcgen,
+)
 from .error import QAPISemError, QAPISourceError
 from .expr import check_exprs
 from .parser import QAPISchemaParser
@@ -27,34 +33,38 @@
 
 class QAPISchemaIfCond:
     def __init__(self, ifcond=None):
-        self.ifcond = ifcond or []
+        pred_list = [IfOption(opt) for opt in ifcond or []]
+        self.pred = IfAll(pred_list)
+
+    def gen_doc(self):
+        if self.pred:
+            return self.pred.docgen()
+        return ""
 
     def gen_if(self):
-        ret = ''
-        for ifc in self.ifcond:
-            ret += mcgen('''
+        if self.pred:
+            return mcgen('''
 #if %(cond)s
-''', cond=ifc)
-        return ret
+''', cond=self.pred.cgen())
+        return ""
 
     def gen_endif(self):
-        ret = ''
-        for ifc in reversed(self.ifcond):
-            ret += mcgen('''
-#endif /* %(cond)s */
-''', cond=ifc)
-        return ret
+        if self.pred:
+            return mcgen('''
+#endif // %(cond)s
+''', cond=self.pred.cgen())
+        return ""
 
     def __bool__(self):
-        return bool(self.ifcond)
+        return bool(self.pred)
 
     def __repr__(self):
-        return repr(self.ifcond)
+        return repr(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..6bf996f539 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(['defined(IFONE)'])
     member two
-    if ['defined(IFCOND)']
+    if IfAll(['defined(IFCOND)'])
     feature enum-feat
 object Base
     member base1: Enum optional=False
 object Variant1
     member var1: str optional=False
-        if ['defined(IFSTR)']
+        if IfAll(['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(['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(['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(['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..c2d303aa18 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(['defined(TEST_IF_STRUCT_BAR)'])
+    if IfAll(['defined(TEST_IF_STRUCT)'])
 enum TestIfEnum
     member foo
     member bar
-        if ['defined(TEST_IF_ENUM_BAR)']
-    if ['defined(TEST_IF_ENUM)']
+        if IfAll(['defined(TEST_IF_ENUM_BAR)'])
+    if IfAll(['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(['defined(TEST_IF_UNION_BAR)'])
+    if IfAll(['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(['defined(TEST_IF_UNION_BAR)'])
+    if IfAll(['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(['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(['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(['defined(TEST_IF_ALT_BAR)'])
+    if IfAll(['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(['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(['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(['defined(TEST_IF_CMD_BAR)'])
+    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
 command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
+    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
 command test-cmd-return-def-three None -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
 array TestIfEnumList TestIfEnum
-    if ['defined(TEST_IF_ENUM)']
+    if IfAll(['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(['defined(TEST_IF_EVT_BAR)'])
+    if IfAll(['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(['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(['defined(TEST_IF_FEATURE_1)'])
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if IfAll(['defined(TEST_IF_FEATURE_1)'])
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if IfAll(['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(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
 enum FeatureEnum1
     member eins
     member zwei
@@ -429,17 +429,17 @@ command test-command-features3 None -> None
 command test-command-cond-features1 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if ['defined(TEST_IF_FEATURE_1)']
+        if IfAll(['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(['defined(TEST_IF_FEATURE_1)'])
     feature feature2
-        if ['defined(TEST_IF_FEATURE_2)']
+        if IfAll(['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(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
 event TEST_EVENT_FEATURES0 FeatureStruct1
     boxed=False
 event TEST_EVENT_FEATURES1 None
-- 
2.29.0



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

* [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (2 preceding siblings ...)
  2021-04-29 13:40 ` [PATCH v3 3/9] qapi: start building an 'if' predicate tree marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-12 23:26   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 5/9] qapi: add IfNot marcandre.lureau
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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>
---
 scripts/qapi/common.py | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 59a7ee2f32..102d347348 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -224,23 +224,45 @@ 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)
 
     def __repr__(self) -> str:
-        return f"IfAll({self.pred_list})"
+        ty = type(self)
+        return f"{ty.__qualname__}({self.pred_list})"
 
     def __eq__(self, other: object) -> bool:
-        if not isinstance(other, IfAll):
+        if not isinstance(other, type(self)):
             return False
         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] 40+ messages in thread

* [PATCH v3 5/9] qapi: add IfNot
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (3 preceding siblings ...)
  2021-04-29 13:40 ` [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-12 23:32   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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>
---
 scripts/qapi/common.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index 102d347348..6236bfc457 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -224,6 +224,28 @@ def __eq__(self, other: object) -> bool:
         return self.option == other.option
 
 
+class IfNot(IfPredicate):
+    def __init__(self, pred: IfPredicate):
+        self.pred = pred
+
+    def docgen(self) -> str:
+        return "not " + self.pred.docgen()
+
+    def cgen(self) -> str:
+        return "!" + self.pred.cgen()
+
+    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] 40+ messages in thread

* [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (4 preceding siblings ...)
  2021-04-29 13:40 ` [PATCH v3 5/9] qapi: add IfNot marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-12 23:47   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 7/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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

Modify check_if() to build an IfPredicate tree (the schema
documentation is updated in a following patch).

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 tests/unit/test-qmp-cmds.c                    |  1 +
 scripts/qapi/expr.py                          | 62 ++++++++++++++-----
 scripts/qapi/schema.py                        | 13 +---
 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       | 20 +++---
 tests/qapi-schema/qapi-schema-test.out        | 59 ++++++++++--------
 .../qapi-schema/struct-member-if-invalid.err  |  2 +-
 10 files changed, 106 insertions(+), 71 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..0a97a6f020 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -42,7 +42,14 @@
     cast,
 )
 
-from .common import c_name
+from .common import (
+    IfAll,
+    IfAny,
+    IfNot,
+    IfOption,
+    IfPredicate,
+    c_name,
+)
 from .error import QAPISemError
 from .parser import QAPIDoc
 from .source import QAPISourceInfo
@@ -261,6 +268,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
     """
     Normalize and validate the ``if`` member of an object.
 
+    The ``if`` field may be either a ``str``, a ``List[str]`` or a dict.
+    A ``str`` element or a ``List[str]`` will be normalized to
+    ``IfAll([str])``.
+
     The ``if`` member may be either a ``str`` or a ``List[str]``.
     A ``str`` value will be normalized to ``List[str]``.
 
@@ -281,25 +292,44 @@ 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, List[str], object]) -> IfPredicate:
+        if isinstance(cond, str):
+            if not cond.strip():
+                raise QAPISemError(
+                    info,
+                    "'if' condition '%s' of %s makes no sense"
+                    % (cond, source))
+            return IfOption(cond)
+        if isinstance(cond, list):
+            cond = {"all": 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, "
+                "a list of strings 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 oper == "not":
+            return IfNot(normalize(operands))
+        if not operands:
+            raise QAPISemError(
+                info, "'if' condition [] of %s is useless" % source)
+        if 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 IfAll(operands) if oper == "all" else IfAny(operands)
+
+    ifcond = expr.get('if')
+    if ifcond is None:
+        return
+    expr['if'] = normalize(ifcond)
 
 
 def normalize_members(members: object) -> None:
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index 366a53ab64..61664a4c5e 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -19,22 +19,15 @@
 import re
 from typing import Optional
 
-from .common import (
-    POINTER_SUFFIX,
-    IfAll,
-    IfOption,
-    c_name,
-    mcgen,
-)
+from .common import POINTER_SUFFIX, c_name, mcgen
 from .error import QAPISemError, QAPISourceError
 from .expr import check_exprs
 from .parser import QAPISchemaParser
 
 
 class QAPISchemaIfCond:
-    def __init__(self, ifcond=None):
-        pred_list = [IfOption(opt) for opt in ifcond or []]
-        self.pred = IfAll(pred_list)
+    def __init__(self, pred=None):
+        self.pred = pred
 
     def gen_doc(self):
         if self.pred:
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 6bf996f539..ca7e53f3b5 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(['defined(IFONE)'])
+        if 'defined(IFONE)'
     member two
-    if IfAll(['defined(IFCOND)'])
+    if 'defined(IFCOND)'
     feature enum-feat
 object Base
     member base1: Enum optional=False
 object Variant1
     member var1: str optional=False
-        if IfAll(['defined(IFSTR)'])
+        if '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(['IFTWO'])
+        if '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(['IFTWO'])
+        if 'IFTWO'
 object SugaredUnion
     member type: SugaredUnionKind optional=False
     tag type
     case one: q_obj_Variant1-wrapper
     case two: q_obj_Variant2-wrapper
-        if IfAll(['IFTWO'])
+        if '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..724a810086 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, a list of strings or a dict
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 84b9d41f15..2d5e480b44 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': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] }
 
 { 'command': 'test-if-union-cmd',
   'data': { 'union-cmd-arg': 'TestIfUnion' },
@@ -241,11 +241,10 @@
 { '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': {
@@ -259,7 +258,7 @@
 { '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': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] }
 
 # test 'features'
 
@@ -290,6 +289,10 @@
   'data': { 'foo': 'int' },
   'features': [ { 'name': 'feature1', 'if': [ '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 +316,8 @@
             '*fs4': 'FeatureStruct4',
             '*cfs1': 'CondFeatureStruct1',
             '*cfs2': 'CondFeatureStruct2',
-            '*cfs3': 'CondFeatureStruct3' },
+            '*cfs3': 'CondFeatureStruct3',
+            '*cfs4': 'CondFeatureStruct4' },
   'returns': 'FeatureStruct1',
   'features': [] }
 
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index c2d303aa18..f859bf648d 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(['defined(TEST_IF_STRUCT_BAR)'])
-    if IfAll(['defined(TEST_IF_STRUCT)'])
+        if 'defined(TEST_IF_STRUCT_BAR)'
+    if 'defined(TEST_IF_STRUCT)'
 enum TestIfEnum
     member foo
     member bar
-        if IfAll(['defined(TEST_IF_ENUM_BAR)'])
-    if IfAll(['defined(TEST_IF_ENUM)'])
+        if 'defined(TEST_IF_ENUM_BAR)'
+    if 'defined(TEST_IF_ENUM)'
 object q_obj_TestStruct-wrapper
     member data: TestStruct optional=False
 enum TestIfUnionKind
     member foo
-    member bar
-        if IfAll(['defined(TEST_IF_UNION_BAR)'])
-    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
+    member union-bar
+        if 'defined(TEST_IF_UNION_BAR)'
+    if IfAll(['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 IfAll(['defined(TEST_IF_UNION_BAR)'])
-    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
+    case union-bar: q_obj_str-wrapper
+        if 'defined(TEST_IF_UNION_BAR)'
+    if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])
 object q_obj_test-if-union-cmd-arg
     member union-cmd-arg: TestIfUnion optional=False
-    if IfAll(['defined(TEST_IF_UNION)'])
+    if 'defined(TEST_IF_UNION)'
 command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if IfAll(['defined(TEST_IF_UNION)'])
+    if 'defined(TEST_IF_UNION)'
 alternate TestIfAlternate
     tag type
     case foo: int
     case bar: TestStruct
-        if IfAll(['defined(TEST_IF_ALT_BAR)'])
-    if IfAll(['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)'])
+        if 'defined(TEST_IF_ALT_BAR)'
+    if IfAll(['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])
 object q_obj_test-if-alternate-cmd-arg
     member alt-cmd-arg: TestIfAlternate optional=False
-    if IfAll(['defined(TEST_IF_ALT)'])
+    if IfAll(['defined(TEST_IF_ALT)', IfNot('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(['defined(TEST_IF_ALT)'])
+    if IfAll(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')])
 object q_obj_test-if-cmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-        if IfAll(['defined(TEST_IF_CMD_BAR)'])
+        if 'defined(TEST_IF_CMD_BAR)'
     if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
 command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
@@ -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(['defined(TEST_IF_ENUM)'])
+    if 'defined(TEST_IF_ENUM)'
 object q_obj_TEST_IF_EVENT-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnumList optional=False
-        if IfAll(['defined(TEST_IF_EVT_BAR)'])
-    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
+        if 'defined(TEST_IF_EVT_BAR)'
+    if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])
 event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
     boxed=False
-    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
+    if IfAll(['defined(TEST_IF_EVT)', '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(['defined(TEST_IF_FEATURE_1)'])
+        if 'defined(TEST_IF_FEATURE_1)'
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if IfAll(['defined(TEST_IF_FEATURE_1)'])
+        if 'defined(TEST_IF_FEATURE_1)'
     feature feature2
-        if IfAll(['defined(TEST_IF_FEATURE_2)'])
+        if 'defined(TEST_IF_FEATURE_2)'
 object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
         if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
+object CondFeatureStruct4
+    member foo: int optional=False
+    feature feature1
+        if IfAny(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
 enum FeatureEnum1
     member eins
     member zwei
@@ -417,6 +421,7 @@ object q_obj_test-features0-arg
     member cfs1: CondFeatureStruct1 optional=True
     member cfs2: CondFeatureStruct2 optional=True
     member cfs3: CondFeatureStruct3 optional=True
+    member cfs4: CondFeatureStruct4 optional=True
 command test-features0 q_obj_test-features0-arg -> FeatureStruct1
     gen=True success_response=True boxed=False oob=False preconfig=False
 command test-command-features1 None -> None
@@ -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(['defined(TEST_IF_FEATURE_1)'])
+        if 'defined(TEST_IF_FEATURE_1)'
 command test-command-cond-features2 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if IfAll(['defined(TEST_IF_FEATURE_1)'])
+        if 'defined(TEST_IF_FEATURE_1)'
     feature feature2
-        if IfAll(['defined(TEST_IF_FEATURE_2)'])
+        if 'defined(TEST_IF_FEATURE_2)'
 command test-command-cond-features3 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
diff --git a/tests/qapi-schema/struct-member-if-invalid.err b/tests/qapi-schema/struct-member-if-invalid.err
index 42e7fdae3c..c18157c1f9 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, a list of strings or a dict
-- 
2.29.0



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

* [PATCH v3 7/9] qapi: convert 'if' C-expressions to the new syntax tree
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (5 preceding siblings ...)
  2021-04-29 13:40 ` [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-12 23:51   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 8/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 qapi/machine-target.json | 20 ++++++++++++++++----
 qapi/misc-target.json    | 13 ++++++++++++-
 2 files changed, 28 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 0c7491cd82..2891df6890 100644
--- a/qapi/misc-target.json
+++ b/qapi/misc-target.json
@@ -23,7 +23,18 @@
 ##
 { '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_MOXIE) || 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_MOXIE)',
+                   '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] 40+ messages in thread

* [PATCH v3 8/9] qapi: make 'if' condition strings simple identifiers
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (6 preceding siblings ...)
  2021-04-29 13:40 ` [PATCH v3 7/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-12 23:56   ` John Snow
  2021-04-29 13:40 ` [PATCH v3 9/9] docs: update the documentation about schema configuration marcandre.lureau
  2021-05-11 16:52 ` [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor Stefan Hajnoczi
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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>
---
 docs/devel/qapi-code-gen.txt                  |  8 +--
 qapi/block-core.json                          | 16 ++---
 qapi/block-export.json                        |  6 +-
 qapi/char.json                                |  8 +--
 qapi/machine-target.json                      | 40 ++++++-------
 qapi/migration.json                           | 10 ++--
 qapi/misc-target.json                         | 48 +++++++--------
 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 +-
 24 files changed, 177 insertions(+), 177 deletions(-)

diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
index c1cb6f987d..edaaf7ec40 100644
--- a/docs/devel/qapi-code-gen.txt
+++ b/docs/devel/qapi-code-gen.txt
@@ -791,7 +791,7 @@ will then be guarded by #if STRING for each STRING in the COND list.
 Example: a conditional struct
 
  { 'struct': 'IfStruct', 'data': { 'foo': 'int' },
-   'if': ['defined(CONFIG_FOO)', 'defined(HAVE_BAR)'] }
+   'if': ['CONFIG_FOO', 'HAVE_BAR'] }
 
 gets its generated code guarded like this:
 
@@ -810,7 +810,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 +822,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 +832,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
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 6d227924d0..82213feb18 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' },
             'sheepdog',
             'ssh', 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
 
@@ -2860,10 +2860,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:
@@ -3683,7 +3683,7 @@
 # Since: 2.9
 ##
 { 'enum' : 'ReplicationMode', 'data' : [ 'primary', 'secondary' ],
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @BlockdevOptionsReplication:
@@ -3702,7 +3702,7 @@
   'base': 'BlockdevOptionsGenericFormat',
   'data': { 'mode': 'ReplicationMode',
             '*top-id': 'str' },
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @NFSTransport:
@@ -4036,7 +4036,7 @@
       'raw':        'BlockdevOptionsRaw',
       'rbd':        'BlockdevOptionsRbd',
       'replication': { 'type': 'BlockdevOptionsReplication',
-                       'if': 'defined(CONFIG_REPLICATION)' },
+                       'if': 'CONFIG_REPLICATION' },
       'sheepdog':   'BlockdevOptionsSheepdog',
       'ssh':        'BlockdevOptionsSsh',
       'throttle':   'BlockdevOptionsThrottle',
@@ -4338,7 +4338,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 6413970fa7..bf3f8d54e5 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:
@@ -414,9 +414,9 @@
             'stdio': 'ChardevStdio',
             'console': 'ChardevCommon',
             'spicevmc': { 'type': 'ChardevSpiceChannel',
-                          'if': 'defined(CONFIG_SPICE)' },
+                          'if': 'CONFIG_SPICE' },
             'spiceport': { 'type': 'ChardevSpicePort',
-                           'if': 'defined(CONFIG_SPICE)' },
+                           'if': 'CONFIG_SPICE' },
             '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 0b17cce46b..43e7b7620d 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -539,7 +539,7 @@
 ##
 { 'enum': 'MultiFDCompression',
   'data': [ 'none', 'zlib',
-            { 'name': 'zstd', 'if': 'defined(CONFIG_ZSTD)' } ] }
+            { 'name': 'zstd', 'if': 'CONFIG_ZSTD' } ] }
 
 ##
 # @BitmapMigrationBitmapAliasTransform:
@@ -1568,7 +1568,7 @@
 ##
 { 'command': 'xen-set-replication',
   'data': { 'enable': 'bool', 'primary': 'bool', '*failover' : 'bool' },
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @ReplicationStatus:
@@ -1584,7 +1584,7 @@
 ##
 { 'struct': 'ReplicationStatus',
   'data': { 'error': 'bool', '*desc': 'str' },
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @query-xen-replication-status:
@@ -1602,7 +1602,7 @@
 ##
 { 'command': 'query-xen-replication-status',
   'returns': 'ReplicationStatus',
-  'if': 'defined(CONFIG_REPLICATION)' }
+  'if': 'CONFIG_REPLICATION' }
 
 ##
 # @xen-colo-do-checkpoint:
@@ -1619,7 +1619,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 2891df6890..5d3c816eb8 100644
--- a/qapi/misc-target.json
+++ b/qapi/misc-target.json
@@ -23,18 +23,18 @@
 ##
 { '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_MOXIE)',
-                   '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_MOXIE',
+                   'TARGET_PPC',
+                   'TARGET_PPC64',
+                   'TARGET_S390X',
+                   'TARGET_SH4',
+                   'TARGET_SPARC' ] } }
 
 ##
 # @rtc-reset-reinjection:
@@ -53,7 +53,7 @@
 #
 ##
 { 'command': 'rtc-reset-reinjection',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 
 ##
@@ -80,7 +80,7 @@
 { 'enum': 'SevState',
   'data': ['uninit', 'launch-update', 'launch-secret', 'running',
            'send-update', 'receive-update' ],
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @SevInfo:
@@ -112,7 +112,7 @@
               'state' : 'SevState',
               'handle' : 'uint32'
             },
-  'if': 'defined(TARGET_I386)'
+  'if': 'TARGET_I386'
 }
 
 ##
@@ -133,7 +133,7 @@
 #
 ##
 { 'command': 'query-sev', 'returns': 'SevInfo',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 
 ##
@@ -147,7 +147,7 @@
 #
 ##
 { 'struct': 'SevLaunchMeasureInfo', 'data': {'data': 'str'},
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @query-sev-launch-measure:
@@ -165,7 +165,7 @@
 #
 ##
 { 'command': 'query-sev-launch-measure', 'returns': 'SevLaunchMeasureInfo',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 
 ##
@@ -190,7 +190,7 @@
             'cert-chain': 'str',
             'cbitpos': 'int',
             'reduced-phys-bits': 'int'},
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @query-sev-capabilities:
@@ -210,7 +210,7 @@
 #
 ##
 { 'command': 'query-sev-capabilities', 'returns': 'SevCapability',
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @sev-inject-launch-secret:
@@ -228,7 +228,7 @@
 ##
 { 'command': 'sev-inject-launch-secret',
   'data': { 'packet-header': 'str', 'secret': 'str', '*gpa': 'uint64' },
-  'if': 'defined(TARGET_I386)' }
+  'if': 'TARGET_I386' }
 
 ##
 # @dump-skeys:
@@ -250,7 +250,7 @@
 ##
 { 'command': 'dump-skeys',
   'data': { 'filename': 'str' },
-  'if': 'defined(TARGET_S390X)' }
+  'if': 'TARGET_S390X' }
 
 ##
 # @GICCapability:
@@ -275,7 +275,7 @@
   'data': { 'version': 'int',
             'emulated': 'bool',
             'kernel': 'bool' },
-  'if': 'defined(TARGET_ARM)' }
+  'if': 'TARGET_ARM' }
 
 ##
 # @query-gic-capabilities:
@@ -295,4 +295,4 @@
 #
 ##
 { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
-  'if': 'defined(TARGET_ARM)' }
+  'if': 'TARGET_ARM' }
diff --git a/qapi/qom.json b/qapi/qom.json
index cd0e76d564..6c41ec61b0 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' } }
 
 ##
@@ -752,7 +752,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',
@@ -765,7 +765,7 @@
     'iothread',
     'memory-backend-file',
     { 'name': 'memory-backend-memfd',
-      'if': 'defined(CONFIG_LINUX)' },
+      'if': 'CONFIG_LINUX' },
     'memory-backend-ram',
     'pef-guest',
     'pr-manager-helper',
@@ -809,7 +809,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',
@@ -822,7 +822,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',
       'rng-builtin':                'RngProperties',
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 6236bfc457..2a36e0366e 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -210,7 +210,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 0a97a6f020..a605a796db 100644
--- a/scripts/qapi/expr.py
+++ b/scripts/qapi/expr.py
@@ -294,10 +294,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
 
     def normalize(cond: Union[str, List[str], object]) -> IfPredicate:
         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 IfOption(cond)
         if isinstance(cond, list):
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 ca7e53f3b5..869d0b8636 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 'IFONE'
     member two
-    if 'defined(IFCOND)'
+    if 'IFCOND'
     feature enum-feat
 object Base
     member base1: Enum optional=False
 object Variant1
     member var1: str optional=False
-        if 'defined(IFSTR)'
+        if 'IFSTR'
         feature member-feat
     feature variant1-feat
 object Variant2
diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
index 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 2d5e480b44..fad64d879f 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': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] }
+    'union-bar': { 'type': 'str', 'if': 'TEST_IF_UNION_BAR'} },
+  'if': ['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': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] }
+  'if': ['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': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] }
+    'bar': { 'type': ['TestIfEnum'], 'if': 'TEST_IF_EVT_BAR' } },
+  'if': ['TEST_IF_EVT', 'TEST_IF_STRUCT'] }
 
 # test 'features'
 
@@ -280,19 +280,19 @@
 
 { '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': [ 'defined(TEST_IF_COND_1)',
-                                              'defined(TEST_IF_COND_2)'] } ] }
+  'features': [ { 'name': 'feature1', 'if': [ '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' ],
@@ -327,13 +327,13 @@
   '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': [ 'defined(TEST_IF_COND_1)',
-                                              'defined(TEST_IF_COND_2)'] } ] }
+  'features': [ { 'name': 'feature1', 'if': [ '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 f859bf648d..95006e60c4 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -298,65 +298,65 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio
 object TestIfStruct
     member foo: int optional=False
     member bar: int optional=False
-        if 'defined(TEST_IF_STRUCT_BAR)'
-    if 'defined(TEST_IF_STRUCT)'
+        if 'TEST_IF_STRUCT_BAR'
+    if 'TEST_IF_STRUCT'
 enum TestIfEnum
     member foo
     member bar
-        if 'defined(TEST_IF_ENUM_BAR)'
-    if 'defined(TEST_IF_ENUM)'
+        if 'TEST_IF_ENUM_BAR'
+    if 'TEST_IF_ENUM'
 object q_obj_TestStruct-wrapper
     member data: TestStruct optional=False
 enum TestIfUnionKind
     member foo
     member union-bar
-        if 'defined(TEST_IF_UNION_BAR)'
-    if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])
+        if 'TEST_IF_UNION_BAR'
+    if IfAll(['TEST_IF_UNION', 'TEST_IF_STRUCT'])
 object TestIfUnion
     member type: TestIfUnionKind optional=False
     tag type
     case foo: q_obj_TestStruct-wrapper
     case union-bar: q_obj_str-wrapper
-        if 'defined(TEST_IF_UNION_BAR)'
-    if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])
+        if 'TEST_IF_UNION_BAR'
+    if IfAll(['TEST_IF_UNION', 'TEST_IF_STRUCT'])
 object q_obj_test-if-union-cmd-arg
     member union-cmd-arg: TestIfUnion optional=False
-    if 'defined(TEST_IF_UNION)'
+    if 'TEST_IF_UNION'
 command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if 'defined(TEST_IF_UNION)'
+    if 'TEST_IF_UNION'
 alternate TestIfAlternate
     tag type
     case foo: int
     case bar: TestStruct
-        if 'defined(TEST_IF_ALT_BAR)'
-    if IfAll(['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])
+        if 'TEST_IF_ALT_BAR'
+    if IfAll(['TEST_IF_ALT', 'TEST_IF_STRUCT'])
 object q_obj_test-if-alternate-cmd-arg
     member alt-cmd-arg: TestIfAlternate optional=False
-    if IfAll(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')])
+    if IfAll(['TEST_IF_ALT', IfNot('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(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')])
+    if IfAll(['TEST_IF_ALT', IfNot('TEST_IF_NOT_ALT')])
 object q_obj_test-if-cmd-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnum optional=False
-        if 'defined(TEST_IF_CMD_BAR)'
-    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
+        if 'TEST_IF_CMD_BAR'
+    if IfAll(['TEST_IF_CMD', 'TEST_IF_STRUCT'])
 command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
-    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
+    if IfAll(['TEST_IF_CMD', 'TEST_IF_STRUCT'])
 command test-cmd-return-def-three None -> UserDefThree
     gen=True success_response=True boxed=False oob=False preconfig=False
 array TestIfEnumList TestIfEnum
-    if 'defined(TEST_IF_ENUM)'
+    if 'TEST_IF_ENUM'
 object q_obj_TEST_IF_EVENT-arg
     member foo: TestIfStruct optional=False
     member bar: TestIfEnumList optional=False
-        if 'defined(TEST_IF_EVT_BAR)'
-    if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])
+        if 'TEST_IF_EVT_BAR'
+    if IfAll(['TEST_IF_EVT', 'TEST_IF_STRUCT'])
 event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
     boxed=False
-    if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])
+    if IfAll(['TEST_IF_EVT', 'TEST_IF_STRUCT'])
 object FeatureStruct0
     member foo: int optional=False
 object FeatureStruct1
@@ -379,21 +379,21 @@ object FeatureStruct4
 object CondFeatureStruct1
     member foo: int optional=False
     feature feature1
-        if 'defined(TEST_IF_FEATURE_1)'
+        if 'TEST_IF_FEATURE_1'
 object CondFeatureStruct2
     member foo: int optional=False
     feature feature1
-        if 'defined(TEST_IF_FEATURE_1)'
+        if 'TEST_IF_FEATURE_1'
     feature feature2
-        if 'defined(TEST_IF_FEATURE_2)'
+        if 'TEST_IF_FEATURE_2'
 object CondFeatureStruct3
     member foo: int optional=False
     feature feature1
-        if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
+        if IfAll(['TEST_IF_COND_1', 'TEST_IF_COND_2'])
 object CondFeatureStruct4
     member foo: int optional=False
     feature feature1
-        if IfAny(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
+        if IfAny(['TEST_IF_COND_1', 'TEST_IF_COND_2'])
 enum FeatureEnum1
     member eins
     member zwei
@@ -434,17 +434,17 @@ command test-command-features3 None -> None
 command test-command-cond-features1 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if 'defined(TEST_IF_FEATURE_1)'
+        if 'TEST_IF_FEATURE_1'
 command test-command-cond-features2 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if 'defined(TEST_IF_FEATURE_1)'
+        if 'TEST_IF_FEATURE_1'
     feature feature2
-        if 'defined(TEST_IF_FEATURE_2)'
+        if 'TEST_IF_FEATURE_2'
 command test-command-cond-features3 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
-        if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
+        if IfAll(['TEST_IF_COND_1', 'TEST_IF_COND_2'])
 event TEST_EVENT_FEATURES0 FeatureStruct1
     boxed=False
 event TEST_EVENT_FEATURES1 None
diff --git a/tests/qapi-schema/union-branch-if-invalid.err b/tests/qapi-schema/union-branch-if-invalid.err
index dd4518233e..046187a5b9 100644
--- a/tests/qapi-schema/union-branch-if-invalid.err
+++ b/tests/qapi-schema/union-branch-if-invalid.err
@@ -1,2 +1,2 @@
 union-branch-if-invalid.json: In union 'Uni':
-union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' makes no sense
+union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' is not a valid identifier
-- 
2.29.0



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

* [PATCH v3 9/9] docs: update the documentation about schema configuration
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (7 preceding siblings ...)
  2021-04-29 13:40 ` [PATCH v3 8/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
@ 2021-04-29 13:40 ` marcandre.lureau
  2021-05-13  0:01   ` John Snow
  2021-05-11 16:52 ` [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor Stefan Hajnoczi
  9 siblings, 1 reply; 40+ messages in thread
From: marcandre.lureau @ 2021-04-29 13:40 UTC (permalink / raw)
  To: qemu-devel; +Cc: Marc-André Lureau, jsnow, armbru

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

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 docs/devel/qapi-code-gen.txt | 27 ++++++++++++++++-----------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
index edaaf7ec40..4a3fd02723 100644
--- a/docs/devel/qapi-code-gen.txt
+++ b/docs/devel/qapi-code-gen.txt
@@ -780,26 +780,31 @@ downstream command __com.redhat_drive-mirror.
 === Configuring the schema ===
 
 Syntax:
-    COND = STRING
-         | [ STRING, ... ]
+    COND = CFG-ID
+         | [ COND, ... ]
+         | { '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.
+    CFG-ID = STRING
+
+All definitions take an optional 'if' member. Its value must be a string, a list
+of strings or an object with a single member 'all', 'any' or 'not'. A string is
+shorthand for a list containing just that string. A list is a shorthand for a
+'all'-member object. The C code generated for the definition will then be guarded
+by an #if precessor expression generated from that condition: 'all': [COND, ...]
+will generate '(COND && ...)', 'any': [COND, ...] '(COND || ...)', 'not': COND '!COND'.
 
 Example: a conditional struct
 
  { 'struct': 'IfStruct', 'data': { 'foo': 'int' },
-   'if': ['CONFIG_FOO', '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
-- 
2.29.0



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

* Re: [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond
  2021-04-29 13:40 ` [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond marcandre.lureau
@ 2021-05-11 16:39   ` Stefan Hajnoczi
  2021-05-12 12:36     ` Marc-André Lureau
  2021-05-12 18:53     ` John Snow
  2021-05-12 21:01   ` John Snow
  1 sibling, 2 replies; 40+ messages in thread
From: Stefan Hajnoczi @ 2021-05-11 16:39 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, qemu-devel, armbru

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

On Thu, Apr 29, 2021 at 05:40:25PM +0400, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Move the generating function to the QAPISchemaIfCond class.

I'm not familiar enough with the QAPI code generator to know whether
schema.py is supposed to generate C code directly. Otherwise this
refactoring makes sense.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor
  2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
                   ` (8 preceding siblings ...)
  2021-04-29 13:40 ` [PATCH v3 9/9] docs: update the documentation about schema configuration marcandre.lureau
@ 2021-05-11 16:52 ` Stefan Hajnoczi
  2021-05-12 12:39   ` Marc-André Lureau
  9 siblings, 1 reply; 40+ messages in thread
From: Stefan Hajnoczi @ 2021-05-11 16:52 UTC (permalink / raw)
  To: marcandre.lureau; +Cc: jsnow, qemu-devel, armbru

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

On Thu, Apr 29, 2021 at 05:40:23PM +0400, marcandre.lureau@redhat.com wrote:
> 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)
> 
> This is based on John Snow QAPI pt4:
> https://patchew.org/QEMU/20210421192233.3542904-1-jsnow@redhat.com/
> 
> Based-on: <20210421192233.3542904-2-jsnow@redhat.com>
> 
> thanks
> 
> 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):
>   qapi: replace List[str] by QAPISchemaIfCond
>   qapi: move gen_if/gen_endif to QAPISchemaIfCond
>   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: update the documentation about schema configuration
> 
>  docs/devel/qapi-code-gen.txt                  |  33 +++---
>  docs/sphinx/qapidoc.py                        |   6 +-
>  qapi/block-core.json                          |  16 +--
>  qapi/block-export.json                        |   6 +-
>  qapi/char.json                                |   8 +-
>  qapi/machine-target.json                      |  28 +++--
>  qapi/migration.json                           |  10 +-
>  qapi/misc-target.json                         |  37 +++---
>  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                        | 106 +++++++++++++++---
>  scripts/qapi/events.py                        |   5 +-
>  scripts/qapi/expr.py                          |  62 +++++++---
>  scripts/qapi/gen.py                           |  16 ++-
>  scripts/qapi/introspect.py                    |  33 +++---
>  scripts/qapi/schema.py                        |  99 ++++++++++++----
>  scripts/qapi/types.py                         |  43 ++++---
>  scripts/qapi/visit.py                         |  25 ++---
>  .../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.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       |  58 +++++-----
>  tests/qapi-schema/qapi-schema-test.out        |  67 ++++++-----
>  .../qapi-schema/struct-member-if-invalid.err  |   2 +-
>  tests/qapi-schema/union-branch-if-invalid.err |   2 +-
>  37 files changed, 482 insertions(+), 297 deletions(-)
> 
> -- 
> 2.29.0
> 
> 
> 

Please double-check that the build and tests pass after each commit (for
bisectability).

I'm not familiar with the details of the QAPI code generator but in
overall this looks like a nice step:

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

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

* Re: [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond
  2021-05-11 16:39   ` Stefan Hajnoczi
@ 2021-05-12 12:36     ` Marc-André Lureau
  2021-05-12 18:53     ` John Snow
  1 sibling, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-12 12:36 UTC (permalink / raw)
  To: Stefan Hajnoczi; +Cc: jsnow, qemu-devel, armbru

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

Hi

On Tue, May 11, 2021 at 8:42 PM Stefan Hajnoczi <stefanha@gmail.com> wrote:

> On Thu, Apr 29, 2021 at 05:40:25PM +0400, marcandre.lureau@redhat.com
> wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Move the generating function to the QAPISchemaIfCond class.
>
> I'm not familiar enough with the QAPI code generator to know whether
> schema.py is supposed to generate C code directly. Otherwise this
> refactoring makes sense.
>

I made the QAPISchemaIfCond simple enough so it can generate C or
doc-friendly conditions in a couple of lines. So it has both data and
generation in its current form. We could make it use a visitor pattern for
the generation, but that's overkill at this point.

thanks for the review!

-- 
Marc-André Lureau

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

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

* Re: [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor
  2021-05-11 16:52 ` [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor Stefan Hajnoczi
@ 2021-05-12 12:39   ` Marc-André Lureau
  2021-05-12 17:43     ` Markus Armbruster
  0 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-12 12:39 UTC (permalink / raw)
  To: Stefan Hajnoczi; +Cc: jsnow, qemu-devel, armbru

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

Hi Markus

On Tue, May 11, 2021 at 8:53 PM Stefan Hajnoczi <stefanha@gmail.com> wrote:

> On Thu, Apr 29, 2021 at 05:40:23PM +0400, marcandre.lureau@redhat.com
> wrote:
> > 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)
> >
> > This is based on John Snow QAPI pt4:
> > https://patchew.org/QEMU/20210421192233.3542904-1-jsnow@redhat.com/
> >
> > Based-on: <20210421192233.3542904-2-jsnow@redhat.com>
> >
>

The patch series applies cleanly on top of master now. I checked no
regression between each commit, including python style checks.

If you are overloaded, can I make a pull request for it?

thanks

> thanks
> >
> > 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):
> >   qapi: replace List[str] by QAPISchemaIfCond
> >   qapi: move gen_if/gen_endif to QAPISchemaIfCond
> >   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: update the documentation about schema configuration
> >
> >  docs/devel/qapi-code-gen.txt                  |  33 +++---
> >  docs/sphinx/qapidoc.py                        |   6 +-
> >  qapi/block-core.json                          |  16 +--
> >  qapi/block-export.json                        |   6 +-
> >  qapi/char.json                                |   8 +-
> >  qapi/machine-target.json                      |  28 +++--
> >  qapi/migration.json                           |  10 +-
> >  qapi/misc-target.json                         |  37 +++---
> >  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                        | 106 +++++++++++++++---
> >  scripts/qapi/events.py                        |   5 +-
> >  scripts/qapi/expr.py                          |  62 +++++++---
> >  scripts/qapi/gen.py                           |  16 ++-
> >  scripts/qapi/introspect.py                    |  33 +++---
> >  scripts/qapi/schema.py                        |  99 ++++++++++++----
> >  scripts/qapi/types.py                         |  43 ++++---
> >  scripts/qapi/visit.py                         |  25 ++---
> >  .../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.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       |  58 +++++-----
> >  tests/qapi-schema/qapi-schema-test.out        |  67 ++++++-----
> >  .../qapi-schema/struct-member-if-invalid.err  |   2 +-
> >  tests/qapi-schema/union-branch-if-invalid.err |   2 +-
> >  37 files changed, 482 insertions(+), 297 deletions(-)
> >
> > --
> > 2.29.0
> >
> >
> >
>
> Please double-check that the build and tests pass after each commit (for
> bisectability).
>
> I'm not familiar with the details of the QAPI code generator but in
> overall this looks like a nice step:
>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
>


-- 
Marc-André Lureau

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

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

* Re: [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor
  2021-05-12 12:39   ` Marc-André Lureau
@ 2021-05-12 17:43     ` Markus Armbruster
  2021-05-12 18:58       ` John Snow
  0 siblings, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2021-05-12 17:43 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: Stefan Hajnoczi, jsnow, qemu-devel

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

> Hi Markus
>
> On Tue, May 11, 2021 at 8:53 PM Stefan Hajnoczi <stefanha@gmail.com> wrote:
>
>> On Thu, Apr 29, 2021 at 05:40:23PM +0400, marcandre.lureau@redhat.com
>> wrote:
>> > 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)
>> >
>> > This is based on John Snow QAPI pt4:
>> > https://patchew.org/QEMU/20210421192233.3542904-1-jsnow@redhat.com/
>> >
>> > Based-on: <20210421192233.3542904-2-jsnow@redhat.com>
>> >
>>
>
> The patch series applies cleanly on top of master now. I checked no
> regression between each commit, including python style checks.

Appears to conflict with John's "[PATCH v2 00/21] qapi: static typing
conversion, pt5a".  I didn't examine the conflicts.

Since I reviewed John's v1 recently, and git-range-diff to v2 looks
fairly innocent at a glance, I'd prefer not to rock that boat.  Let's
discuss what to do as soon as I reviewed John's v2.

> If you are overloaded, can I make a pull request for it?

Not yet, please.



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

* Re: [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond
  2021-05-11 16:39   ` Stefan Hajnoczi
  2021-05-12 12:36     ` Marc-André Lureau
@ 2021-05-12 18:53     ` John Snow
  2021-05-17 11:18       ` Marc-André Lureau
  1 sibling, 1 reply; 40+ messages in thread
From: John Snow @ 2021-05-12 18:53 UTC (permalink / raw)
  To: Stefan Hajnoczi, marcandre.lureau; +Cc: qemu-devel, armbru

On 5/11/21 12:39 PM, Stefan Hajnoczi wrote:
> On Thu, Apr 29, 2021 at 05:40:25PM +0400, marcandre.lureau@redhat.com wrote:
>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>
>> Move the generating function to the QAPISchemaIfCond class.
> 
> I'm not familiar enough with the QAPI code generator to know whether
> schema.py is supposed to generate C code directly. Otherwise this
> refactoring makes sense.
> 

It's not supposed to, necessarily, but schema.py *does* already have 
some "built-in" ties to the C language -- in particular its use of 
POINTER_SUFFIX and c_name. It isn't split ultra-cleanly.

Ideally, and I am neither asking for nor requiring this, I would like to 
see code generator backends factored neatly such that the C generative 
code is all in one place, or at least outside of the abstract QAPI 
internals.

(Maybe gen.py can be split into gen.py and c_gen.py, and the C-specific 
bits for gen_if et al can go into c_gen.py.)

Since you are working towards a Rust backend, I assume that the 
C-specific portions of this code don't remain in schema.py for too long? 
I can't imagine them being here helps you with your Rust generator, but 
I could be wrong.

--js



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

* Re: [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor
  2021-05-12 17:43     ` Markus Armbruster
@ 2021-05-12 18:58       ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-12 18:58 UTC (permalink / raw)
  To: Markus Armbruster, Marc-André Lureau; +Cc: Stefan Hajnoczi, qemu-devel

On 5/12/21 1:43 PM, Markus Armbruster wrote:
> Marc-André Lureau <marcandre.lureau@gmail.com> writes:
> 
>> Hi Markus
>>
>> On Tue, May 11, 2021 at 8:53 PM Stefan Hajnoczi <stefanha@gmail.com> wrote:
>>
>>> On Thu, Apr 29, 2021 at 05:40:23PM +0400, marcandre.lureau@redhat.com
>>> wrote:
>>>> 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)
>>>>
>>>> This is based on John Snow QAPI pt4:
>>>> https://patchew.org/QEMU/20210421192233.3542904-1-jsnow@redhat.com/
>>>>
>>>> Based-on: <20210421192233.3542904-2-jsnow@redhat.com>
>>>>
>>>
>>
>> The patch series applies cleanly on top of master now. I checked no
>> regression between each commit, including python style checks.
> 
> Appears to conflict with John's "[PATCH v2 00/21] qapi: static typing
> conversion, pt5a".  I didn't examine the conflicts.
> 
> Since I reviewed John's v1 recently, and git-range-diff to v2 looks
> fairly innocent at a glance, I'd prefer not to rock that boat.  Let's
> discuss what to do as soon as I reviewed John's v2.
> 

It should hopefully be very minimal. I advised Marc-Andre to rebase on 
master as this series did not appear to touch the parser. Conflict 
should be minimal-ish.

(Maybe it's clashing in Schema just a little bit? I do touch schema.py 
very gently...)

>> If you are overloaded, can I make a pull request for it?
> 
> Not yet, please.
> 

As soon as I am done sending a new version of my Python packaging and CI 
series I will run this through the wringer and give my reviews.

--js



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

* Re: [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-04-29 13:40 ` [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
@ 2021-05-12 20:53   ` John Snow
  2021-05-17 11:17     ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: John Snow @ 2021-05-12 20:53 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Wrap the 'if' condition in a higher-level object. Not only this allows
> more type safety but also further refactoring without too much churn.
> 

Would have done it myself if I had gotten to it first. I like having a 
named type for this, it matches the other properties we have.

> 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>
> ---
>   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     | 78 +++++++++++++++++++++++++++-----------
>   scripts/qapi/types.py      | 33 ++++++++--------
>   scripts/qapi/visit.py      | 23 +++++------
>   8 files changed, 110 insertions(+), 75 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 3a4172fb74..7d6f390fa6 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -25,6 +25,22 @@
>   from .parser import QAPISchemaParser
>   
>   
> +class QAPISchemaIfCond:
> +    def __init__(self, ifcond=None):
> +        self.ifcond = ifcond or []
> +
> +    def __bool__(self):
> +        return bool(self.ifcond)
> +
> +    def __repr__(self):
> +        return repr(self.ifcond)
> +

I suggest using:

f"QAPISchemaIfCond({self.ifcond!r})"

unless future patches make that weirder. It's nice when repr() returns 
something you can use to make a new, equivalent object.

eval(repr(x)) == x

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

Missing annotations, but ... yeah, schema.py isn't typed yet so I will 
handle it myself in pt6. No biggie.

>   class QAPISchemaEntity:
>       meta: Optional[str] = None
>   
> @@ -42,7 +58,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 +93,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 +617,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 +662,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):
> @@ -958,14 +974,20 @@ def _def_predefineds(self):
>           self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
>                                               qtype_values, 'QTYPE'))
>   
> +    def _get_if(self, f) -> QAPISchemaIfCond:
> +        ifcond = f.get('if')
> +        if isinstance(ifcond, QAPISchemaIfCond):

would "is not None" suffice here?

> +            return ifcond
> +        return QAPISchemaIfCond(ifcond)

Under which circumstances do we expect 'ifcond' here to be something 
other than None but *not* a QAPISchemaIfCond?

This helper seems a little suspect in the sense that it appears to be 
taking liberties with the dynamic runtime object system.

> + >       def _make_features(self, features, info):
>           if features is None:
>               return []
> -        return [QAPISchemaFeature(f['name'], info, f.get('if'))
> +        return [QAPISchemaFeature(f['name'], info, self._get_if(f))
>                   for f in features]
>   
>       def _make_enum_members(self, values, info):
> -        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
> +        return [QAPISchemaEnumMember(v['name'], info, self._get_if(v))
>                   for v in values]
>   
>       def _make_implicit_enum_type(self, name, info, ifcond, values):
> @@ -1001,7 +1023,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))
> @@ -1011,7 +1033,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,
> @@ -1029,7 +1051,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()]
>   
> @@ -1037,7 +1060,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,
> @@ -1060,7 +1083,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
> @@ -1069,14 +1092,20 @@ 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()]
> +            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} for v in variants]
>               typ = self._make_implicit_enum_type(name, info, ifcond, enum)
>               tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
> @@ -1090,11 +1119,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,
> @@ -1111,7 +1143,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(
> @@ -1130,7 +1162,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):
> 

looks good.

Tested-by: John Snow <jsnow@redhat.com>



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

* Re: [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond
  2021-04-29 13:40 ` [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond marcandre.lureau
  2021-05-11 16:39   ` Stefan Hajnoczi
@ 2021-05-12 21:01   ` John Snow
  2021-05-21 14:35     ` Markus Armbruster
  1 sibling, 1 reply; 40+ messages in thread
From: John Snow @ 2021-05-12 21:01 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Move the generating function to the QAPISchemaIfCond class.
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   scripts/qapi/common.py     | 20 +-------------------
>   scripts/qapi/gen.py        |  6 ++----
>   scripts/qapi/introspect.py | 11 +++--------
>   scripts/qapi/schema.py     | 18 +++++++++++++++++-
>   scripts/qapi/types.py      | 28 +++++++++++-----------------
>   scripts/qapi/visit.py      | 14 ++++++--------
>   6 files changed, 40 insertions(+), 57 deletions(-)
> 
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index cbd3fd81d3..b7f475a160 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 Optional, Sequence
> +from typing import Optional
>   
>   
>   #: Magic string that gets removed along with all space to its right.
> @@ -192,21 +192,3 @@ def guardend(name: str) -> str:
>   #endif /* %(name)s */
>   ''',
>                    name=c_fname(name).upper())
> -
> -
> -def gen_if(ifcond: Sequence[str]) -> str:
> -    ret = ''
> -    for ifc in ifcond:
> -        ret += mcgen('''
> -#if %(cond)s
> -''', cond=ifc)
> -    return ret
> -
> -
> -def gen_endif(ifcond: Sequence[str]) -> str:
> -    ret = ''
> -    for ifc in reversed(ifcond):
> -        ret += mcgen('''
> -#endif /* %(cond)s */
> -''', cond=ifc)
> -    return ret
> diff --git a/scripts/qapi/gen.py b/scripts/qapi/gen.py
> index 1c5b190276..ab26d5c937 100644
> --- a/scripts/qapi/gen.py
> +++ b/scripts/qapi/gen.py
> @@ -24,8 +24,6 @@
>   from .common import (
>       c_fname,
>       c_name,
> -    gen_endif,
> -    gen_if,
>       guardend,
>       guardstart,
>       mcgen,
> @@ -95,9 +93,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 += ifcond.gen_if()
>       out += added
> -    out += gen_endif(ifcond.ifcond)
> +    out += ifcond.gen_endif()
>       return out
>   
>   
> diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
> index 77a8c33ad4..a2a8a57b9a 100644
> --- a/scripts/qapi/introspect.py
> +++ b/scripts/qapi/introspect.py
> @@ -22,12 +22,7 @@
>       Union,
>   )
>   
> -from .common import (
> -    c_name,
> -    gen_endif,
> -    gen_if,
> -    mcgen,
> -)
> +from .common import c_name, mcgen
>   from .gen import QAPISchemaMonolithicCVisitor
>   from .schema import (
>       QAPISchema,
> @@ -124,10 +119,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 += obj.ifcond.gen_if()
>           ret += _tree_to_qlit(obj.value, level)
>           if obj.ifcond:
> -            ret += '\n' + gen_endif(obj.ifcond.ifcond)
> +            ret += '\n' + obj.ifcond.gen_endif()
>           return ret
>   
>       ret = ''
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index 7d6f390fa6..8e6d0a5296 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -19,7 +19,7 @@
>   import re
>   from typing import Optional
>   
> -from .common import POINTER_SUFFIX, c_name
> +from .common import POINTER_SUFFIX, c_name, mcgen
>   from .error import QAPISemError, QAPISourceError
>   from .expr import check_exprs
>   from .parser import QAPISchemaParser
> @@ -29,6 +29,22 @@ class QAPISchemaIfCond:
>       def __init__(self, ifcond=None):
>           self.ifcond = ifcond or []
>   
> +    def gen_if(self):
> +        ret = ''
> +        for ifc in self.ifcond:
> +            ret += mcgen('''
> +#if %(cond)s
> +''', cond=ifc)
> +        return ret
> +
> +    def gen_endif(self):
> +        ret = ''
> +        for ifc in reversed(self.ifcond):
> +            ret += mcgen('''
> +#endif /* %(cond)s */
> +''', cond=ifc)
> +        return ret
> +
>       def __bool__(self):
>           return bool(self.ifcond)
>   
> diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
> index 3673cf0f49..831294fe42 100644
> --- a/scripts/qapi/types.py
> +++ b/scripts/qapi/types.py
> @@ -15,13 +15,7 @@
>   
>   from typing import List, Optional
>   
> -from .common import (
> -    c_enum_const,
> -    c_name,
> -    gen_endif,
> -    gen_if,
> -    mcgen,
> -)
> +from .common import c_enum_const, c_name, mcgen
>   from .gen import QAPISchemaModularCVisitor, ifcontext
>   from .schema import (
>       QAPISchema,
> @@ -51,13 +45,13 @@ def gen_enum_lookup(name: str,
>   ''',
>                   c_name=c_name(name))
>       for memb in members:
> -        ret += gen_if(memb.ifcond.ifcond)
> +        ret += memb.ifcond.gen_if()
>           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 += memb.ifcond.gen_endif()
>   
>       ret += mcgen('''
>       },
> @@ -81,12 +75,12 @@ def gen_enum(name: str,
>                   c_name=c_name(name))
>   
>       for memb in enum_members:
> -        ret += gen_if(memb.ifcond.ifcond)
> +        ret += memb.ifcond.gen_if()
>           ret += mcgen('''
>       %(c_enum)s,
>   ''',
>                        c_enum=c_enum_const(name, memb.name, prefix))
> -        ret += gen_endif(memb.ifcond.ifcond)
> +        ret += memb.ifcond.gen_endif()
>   
>       ret += mcgen('''
>   } %(c_name)s;
> @@ -126,7 +120,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 += memb.ifcond.gen_if()
>           if memb.optional:
>               ret += mcgen('''
>       bool has_%(c_name)s;
> @@ -136,7 +130,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 += memb.ifcond.gen_endif()
>       return ret
>   
>   
> @@ -159,7 +153,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
>       ret += mcgen('''
>   
>   ''')
> -    ret += gen_if(ifcond.ifcond)
> +    ret += ifcond.gen_if()
>       ret += mcgen('''
>   struct %(c_name)s {
>   ''',
> @@ -193,7 +187,7 @@ def gen_object(name: str, ifcond: QAPISchemaIfCond,
>       ret += mcgen('''
>   };
>   ''')
> -    ret += gen_endif(ifcond.ifcond)
> +    ret += ifcond.gen_endif()
>   
>       return ret
>   
> @@ -220,13 +214,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 += var.ifcond.gen_if()
>           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 += var.ifcond.gen_endif()
>   
>       ret += mcgen('''
>       } u;
> diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
> index 67721b2470..9d9196a143 100644
> --- a/scripts/qapi/visit.py
> +++ b/scripts/qapi/visit.py
> @@ -18,8 +18,6 @@
>   from .common import (
>       c_enum_const,
>       c_name,
> -    gen_endif,
> -    gen_if,
>       indent,
>       mcgen,
>   )
> @@ -79,7 +77,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 += memb.ifcond.gen_if()
>           if memb.optional:
>               ret += mcgen('''
>       if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
> @@ -112,7 +110,7 @@ def gen_visit_object_members(name: str,
>               ret += mcgen('''
>       }
>   ''')
> -        ret += gen_endif(memb.ifcond.ifcond)
> +        ret += memb.ifcond.gen_endif()
>   
>       if variants:
>           tag_member = variants.tag_member
> @@ -126,7 +124,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 += var.ifcond.gen_if()
>               if var.type.name == 'q_empty':
>                   # valid variant and nothing to do
>                   ret += mcgen('''
> @@ -142,7 +140,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 += var.ifcond.gen_endif()
>           ret += mcgen('''
>       default:
>           abort();
> @@ -228,7 +226,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 += var.ifcond.gen_if()
>           ret += mcgen('''
>       case %(case)s:
>   ''',
> @@ -254,7 +252,7 @@ def gen_visit_alternate(name: str, variants: QAPISchemaVariants) -> str:
>           ret += mcgen('''
>           break;
>   ''')
> -        ret += gen_endif(var.ifcond.ifcond)
> +        ret += var.ifcond.gen_endif()
>   
>       ret += mcgen('''
>       case QTYPE_NONE:
> 

Tested-by: John Snow <jsnow@redhat.com>

Seems fine, though I'm a lot less sure of baking the C-specific stuff 
right into the class -- I want a bit more distance to the C output 
instead of less.

(Though I do admit that I'm quite fond of centralizing things into 
classes like this myself. I think if we want to add Rust, Go, Python and 
other generators that it won't scale the way we want it to.)

--js



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

* Re: [PATCH v3 3/9] qapi: start building an 'if' predicate tree
  2021-04-29 13:40 ` [PATCH v3 3/9] qapi: start building an 'if' predicate tree marcandre.lureau
@ 2021-05-12 21:39   ` John Snow
  2021-05-17 11:18     ` Marc-André Lureau
  2021-05-21 14:48     ` Markus Armbruster
  0 siblings, 2 replies; 40+ messages in thread
From: John Snow @ 2021-05-12 21:39 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> 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>
> ---
>   docs/sphinx/qapidoc.py                 |  6 +--
>   scripts/qapi/common.py                 | 54 +++++++++++++++++++++++-
>   scripts/qapi/schema.py                 | 42 ++++++++++++-------
>   tests/qapi-schema/doc-good.out         | 12 +++---
>   tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
>   5 files changed, 116 insertions(+), 56 deletions(-)
> 
> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> index b737949007..a93f3f1c4d 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(', '))

was this the last user of intersperse?

mm, no, there's one more.

> +        condlist = [nodes.literal('', ifcond.gen_doc())]
>           if not with_if:
>               return condlist
>   
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index b7f475a160..59a7ee2f32 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -11,8 +11,9 @@
>   # This work is licensed under the terms of the GNU GPL, version 2.
>   # See the COPYING file in the top-level directory.
>   
> +from abc import ABC, abstractmethod
>   import re
> -from typing import Optional
> +from typing import Optional, Sequence
>   
>   
>   #: Magic string that gets removed along with all space to its right.
> @@ -192,3 +193,54 @@ def guardend(name: str) -> str:
>   #endif /* %(name)s */
>   ''',
>                    name=c_fname(name).upper())
> +
> +
> +class IfPredicate(ABC):
> +    @abstractmethod
> +    def cgen(self) -> str:

Like the review to patch 2, I'm not sure we want to bake cgen stuff 
directly into this class. Are you going to have cgen() and rustgen() 
methods all here?

> +        pass
> +

I think you want raise NotImplementedError to specify a function that 
the inheriting class MUST implement. Otherwise, there's little value to 
allow a child class to call super() on a method that doesn't have a 
default implementation.

You *could* drop the (ABC) and the @abstractmethod decorators if you do so.

Matters of taste and style.

> +    @abstractmethod
> +    def docgen(self) -> str:
> +        pass
> + > +
> +class IfOption(IfPredicate):

missing a docstring here, but I suppose I haven't written a style guide 
on how to write those yet.

I assume IfOption is one single element?

e.g. X && Y && Z is

All(
     Option(X),
     Option(Y),
     Option(Z),
)

Right?

> +    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 repr(self.option)
> +

type(self).__name__ + f"({self.option!r})"

or just hard-code the IfOption in there

> +    def __eq__(self, other: object) -> bool:
> +        if not isinstance(other, IfOption):
> +            return False

maybe NotImplemented?

(Admitting I don't know the FULL consequences of doing it either way, 
just prompting you for an explanation.)

> +        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"IfAll({self.pred_list})"
> +

{self.pred_list!r} to get the recursive repr.

You can consider adding a __str__ method to add a kind of short-hand 
that skips the class names and stuff and keeps the output less chatty.

> +    def __eq__(self, other: object) -> bool:
> +        if not isinstance(other, IfAll):
> +            return False

NotImplemented again?

> +        return self.pred_list == other.pred_list
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index 8e6d0a5296..366a53ab64 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -19,7 +19,13 @@
>   import re
>   from typing import Optional
>   
> -from .common import POINTER_SUFFIX, c_name, mcgen
> +from .common import (
> +    POINTER_SUFFIX,
> +    IfAll,
> +    IfOption,
> +    c_name,
> +    mcgen,
> +)
>   from .error import QAPISemError, QAPISourceError
>   from .expr import check_exprs
>   from .parser import QAPISchemaParser
> @@ -27,34 +33,38 @@
>   
>   class QAPISchemaIfCond:
>       def __init__(self, ifcond=None):
> -        self.ifcond = ifcond or []
> +        pred_list = [IfOption(opt) for opt in ifcond or []]
> +        self.pred = IfAll(pred_list)
> +
> +    def gen_doc(self):
> +        if self.pred:
> +            return self.pred.docgen()
> +        return ""
>   

pred should always be IfAll as of here, can we remove the conditional? 
.join() will deform to the empty string when it's given an empty sequence

>       def gen_if(self):
> -        ret = ''
> -        for ifc in self.ifcond:
> -            ret += mcgen('''
> +        if self.pred:
> +            return mcgen('''
>   #if %(cond)s
> -''', cond=ifc)
> -        return ret
> +''', cond=self.pred.cgen())
> +        return ""
>   
>       def gen_endif(self):
> -        ret = ''
> -        for ifc in reversed(self.ifcond):
> -            ret += mcgen('''
> -#endif /* %(cond)s */
> -''', cond=ifc)
> -        return ret
> +        if self.pred:
> +            return mcgen('''
> +#endif // %(cond)s
> +''', cond=self.pred.cgen())
> +        return ""
>   
>       def __bool__(self):
> -        return bool(self.ifcond)
> +        return bool(self.pred)
>   
>       def __repr__(self):
> -        return repr(self.ifcond)
> +        return repr(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..6bf996f539 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(['defined(IFONE)'])

I suppose the test output changing, no longer representing a single 
language makes sense.

>       member two
> -    if ['defined(IFCOND)']
> +    if IfAll(['defined(IFCOND)'])
>       feature enum-feat
>   object Base
>       member base1: Enum optional=False
>   object Variant1
>       member var1: str optional=False
> -        if ['defined(IFSTR)']
> +        if IfAll(['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(['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(['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(['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..c2d303aa18 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(['defined(TEST_IF_STRUCT_BAR)'])
> +    if IfAll(['defined(TEST_IF_STRUCT)'])
>   enum TestIfEnum
>       member foo
>       member bar
> -        if ['defined(TEST_IF_ENUM_BAR)']
> -    if ['defined(TEST_IF_ENUM)']
> +        if IfAll(['defined(TEST_IF_ENUM_BAR)'])
> +    if IfAll(['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(['defined(TEST_IF_UNION_BAR)'])
> +    if IfAll(['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(['defined(TEST_IF_UNION_BAR)'])
> +    if IfAll(['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(['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(['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(['defined(TEST_IF_ALT_BAR)'])
> +    if IfAll(['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(['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(['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(['defined(TEST_IF_CMD_BAR)'])
> +    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
>   command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
>       gen=True success_response=True boxed=False oob=False preconfig=False
> -    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
> +    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
>   command test-cmd-return-def-three None -> UserDefThree
>       gen=True success_response=True boxed=False oob=False preconfig=False
>   array TestIfEnumList TestIfEnum
> -    if ['defined(TEST_IF_ENUM)']
> +    if IfAll(['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(['defined(TEST_IF_EVT_BAR)'])
> +    if IfAll(['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(['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(['defined(TEST_IF_FEATURE_1)'])
>   object CondFeatureStruct2
>       member foo: int optional=False
>       feature feature1
> -        if ['defined(TEST_IF_FEATURE_1)']
> +        if IfAll(['defined(TEST_IF_FEATURE_1)'])
>       feature feature2
> -        if ['defined(TEST_IF_FEATURE_2)']
> +        if IfAll(['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(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
>   enum FeatureEnum1
>       member eins
>       member zwei
> @@ -429,17 +429,17 @@ command test-command-features3 None -> None
>   command test-command-cond-features1 None -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
>       feature feature1
> -        if ['defined(TEST_IF_FEATURE_1)']
> +        if IfAll(['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(['defined(TEST_IF_FEATURE_1)'])
>       feature feature2
> -        if ['defined(TEST_IF_FEATURE_2)']
> +        if IfAll(['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(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
>   event TEST_EVENT_FEATURES0 FeatureStruct1
>       boxed=False
>   event TEST_EVENT_FEATURES1 None
> 

Tested-by: John Snow <jsnow@redhat.com>

Seems fine again, minor style qualms about Python I am not that direly 
opinionated on. More worried about the integration of cgen and docgen, 
though I see why with a predicate tree it becomes *extremely* convenient 
to integrate them right into the class.

Is there a way to have our cake and eat it, too ... ?

(Maybe a visitor of some kind is the right way to go?)

--js



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

* Re: [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny
  2021-04-29 13:40 ` [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
@ 2021-05-12 23:26   ` John Snow
  2021-05-17 11:18     ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: John Snow @ 2021-05-12 23:26 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> 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>
> ---
>   scripts/qapi/common.py | 32 +++++++++++++++++++++++++++-----
>   1 file changed, 27 insertions(+), 5 deletions(-)
> 
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index 59a7ee2f32..102d347348 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -224,23 +224,45 @@ 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)
>   
>       def __repr__(self) -> str:
> -        return f"IfAll({self.pred_list})"
> +        ty = type(self)
> +        return f"{ty.__qualname__}({self.pred_list})"
>   
>       def __eq__(self, other: object) -> bool:
> -        if not isinstance(other, IfAll):
> +        if not isinstance(other, type(self)):
>               return False
>           return self.pred_list == other.pred_list
> +
> +
> +class IfAll(IfPredicateList):
> +    C_SEP = "&&"
> +    DOC_SEP = "and"
> +
> +
> +class IfAny(IfPredicateList):
> +    C_SEP = "||"
> +    DOC_SEP = "or"
> 

I do like the way these get combined. Is there a reason it's not 
squashed into the earlier commit?

(Qualms about not having a visitor or a callback or whatever you want to 
call it remain, but I'll stop remarking on that for the rest of this 
series.)

Tested-by: John Snow <jsnow@redhat.com>



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

* Re: [PATCH v3 5/9] qapi: add IfNot
  2021-04-29 13:40 ` [PATCH v3 5/9] qapi: add IfNot marcandre.lureau
@ 2021-05-12 23:32   ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-12 23:32 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> 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>
> ---
>   scripts/qapi/common.py | 22 ++++++++++++++++++++++
>   1 file changed, 22 insertions(+)
> 
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index 102d347348..6236bfc457 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -224,6 +224,28 @@ def __eq__(self, other: object) -> bool:
>           return self.option == other.option
>   
>   
> +class IfNot(IfPredicate):
> +    def __init__(self, pred: IfPredicate):
> +        self.pred = pred
> +
> +    def docgen(self) -> str:
> +        return "not " + self.pred.docgen()
> +
> +    def cgen(self) -> str:
> +        return "!" + self.pred.cgen()
> +
> +    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
> +
> +

Seems fine. Lot of boilerplate going on, but I don't have quick ideas 
for reducing it.

So far we have:

IfPredicate (abstract)

IfOption (wraps a <str>)
IfNot (wraps <IfPredicate>)
IfPredicateList (wraps <List[IfPredicate]>)
   - IfAll
   - IfAny

with fairly similar methods and boilerplate. I'm itching to reduce it a 
little, somehow...

--js

>   class IfPredicateList(IfPredicate):
>       C_SEP = ""
>       DOC_SEP = ""
> 

Tested-by: John Snow <jsnow@redhat.com>



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

* Re: [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree
  2021-04-29 13:40 ` [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
@ 2021-05-12 23:47   ` John Snow
  2021-05-17 11:18     ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: John Snow @ 2021-05-12 23:47 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Modify check_if() to build an IfPredicate tree (the schema
> documentation is updated in a following patch).
> 

I'm wondering if check_if() is the right place to do this. It's 
certainly convenient, but we don't build any other domain-specific types 
here at all -- that all happens in schema.py.

Before this patch, the return value from expr.py is conceivably 
something you'd get "exactly as-is" from a JSON parser. This patch would 
end that, and collapses the waveform.

I think we should build a function that turns the raw (or slightly 
normalized) 'ifcond' AST into the IfPredicate object like we do for 
other structures, like Members, Features, etc.

I'd also like the documentation changes to eventually be squashed 
directly into this patch if it changes syntax, but keeping it separate 
during review makes sense.

Tentatively, I think the expanded "IF" syntax makes sense.

'if': 'COND'
'if': ['COND']
'if': { 'any': ['COND'] }

Seems fine. I want to play around a little bit with a JsonSchema for it 
though to make sure that it's something I can provide good IntelliSense 
tooltips for in e.g. vscode. (A bit of a pipe-dream side project, I 
admit, but if you'll humor me I'd like the chance to give it a shot. 
Some constructs are simply easier to type and validate than others.)

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

Tested-by: John Snow <jsnow@redhat.com>

> ---
>   tests/unit/test-qmp-cmds.c                    |  1 +
>   scripts/qapi/expr.py                          | 62 ++++++++++++++-----
>   scripts/qapi/schema.py                        | 13 +---
>   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       | 20 +++---
>   tests/qapi-schema/qapi-schema-test.out        | 59 ++++++++++--------
>   .../qapi-schema/struct-member-if-invalid.err  |  2 +-
>   10 files changed, 106 insertions(+), 71 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..0a97a6f020 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -42,7 +42,14 @@
>       cast,
>   )
>   
> -from .common import c_name
> +from .common import (
> +    IfAll,
> +    IfAny,
> +    IfNot,
> +    IfOption,
> +    IfPredicate,
> +    c_name,
> +)
>   from .error import QAPISemError
>   from .parser import QAPIDoc
>   from .source import QAPISourceInfo
> @@ -261,6 +268,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>       """
>       Normalize and validate the ``if`` member of an object.
>   
> +    The ``if`` field may be either a ``str``, a ``List[str]`` or a dict.
> +    A ``str`` element or a ``List[str]`` will be normalized to
> +    ``IfAll([str])``.
> +
>       The ``if`` member may be either a ``str`` or a ``List[str]``.
>       A ``str`` value will be normalized to ``List[str]``.
>   
> @@ -281,25 +292,44 @@ 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, List[str], object]) -> IfPredicate:
> +        if isinstance(cond, str):
> +            if not cond.strip():
> +                raise QAPISemError(
> +                    info,
> +                    "'if' condition '%s' of %s makes no sense"
> +                    % (cond, source))
> +            return IfOption(cond)
> +        if isinstance(cond, list):
> +            cond = {"all": 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, "
> +                "a list of strings 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 oper == "not":
> +            return IfNot(normalize(operands))
> +        if not operands:
> +            raise QAPISemError(
> +                info, "'if' condition [] of %s is useless" % source)
> +        if 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 IfAll(operands) if oper == "all" else IfAny(operands)
> +
> +    ifcond = expr.get('if')
> +    if ifcond is None:
> +        return
> +    expr['if'] = normalize(ifcond)
>   
>   
>   def normalize_members(members: object) -> None:
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index 366a53ab64..61664a4c5e 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -19,22 +19,15 @@
>   import re
>   from typing import Optional
>   
> -from .common import (
> -    POINTER_SUFFIX,
> -    IfAll,
> -    IfOption,
> -    c_name,
> -    mcgen,
> -)
> +from .common import POINTER_SUFFIX, c_name, mcgen
>   from .error import QAPISemError, QAPISourceError
>   from .expr import check_exprs
>   from .parser import QAPISchemaParser
>   
>   
>   class QAPISchemaIfCond:
> -    def __init__(self, ifcond=None):
> -        pred_list = [IfOption(opt) for opt in ifcond or []]
> -        self.pred = IfAll(pred_list)
> +    def __init__(self, pred=None):
> +        self.pred = pred
>   
>       def gen_doc(self):
>           if self.pred:
> 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 6bf996f539..ca7e53f3b5 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(['defined(IFONE)'])
> +        if 'defined(IFONE)'
>       member two
> -    if IfAll(['defined(IFCOND)'])
> +    if 'defined(IFCOND)'
>       feature enum-feat
>   object Base
>       member base1: Enum optional=False
>   object Variant1
>       member var1: str optional=False
> -        if IfAll(['defined(IFSTR)'])
> +        if '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(['IFTWO'])
> +        if '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(['IFTWO'])
> +        if 'IFTWO'
>   object SugaredUnion
>       member type: SugaredUnionKind optional=False
>       tag type
>       case one: q_obj_Variant1-wrapper
>       case two: q_obj_Variant2-wrapper
> -        if IfAll(['IFTWO'])
> +        if '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..724a810086 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, a list of strings or a dict
> diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> index 84b9d41f15..2d5e480b44 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': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] }
>   
>   { 'command': 'test-if-union-cmd',
>     'data': { 'union-cmd-arg': 'TestIfUnion' },
> @@ -241,11 +241,10 @@
>   { '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': {
> @@ -259,7 +258,7 @@
>   { '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': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] }
>   
>   # test 'features'
>   
> @@ -290,6 +289,10 @@
>     'data': { 'foo': 'int' },
>     'features': [ { 'name': 'feature1', 'if': [ '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 +316,8 @@
>               '*fs4': 'FeatureStruct4',
>               '*cfs1': 'CondFeatureStruct1',
>               '*cfs2': 'CondFeatureStruct2',
> -            '*cfs3': 'CondFeatureStruct3' },
> +            '*cfs3': 'CondFeatureStruct3',
> +            '*cfs4': 'CondFeatureStruct4' },
>     'returns': 'FeatureStruct1',
>     'features': [] }
>   
> diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
> index c2d303aa18..f859bf648d 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(['defined(TEST_IF_STRUCT_BAR)'])
> -    if IfAll(['defined(TEST_IF_STRUCT)'])
> +        if 'defined(TEST_IF_STRUCT_BAR)'
> +    if 'defined(TEST_IF_STRUCT)'
>   enum TestIfEnum
>       member foo
>       member bar
> -        if IfAll(['defined(TEST_IF_ENUM_BAR)'])
> -    if IfAll(['defined(TEST_IF_ENUM)'])
> +        if 'defined(TEST_IF_ENUM_BAR)'
> +    if 'defined(TEST_IF_ENUM)'
>   object q_obj_TestStruct-wrapper
>       member data: TestStruct optional=False
>   enum TestIfUnionKind
>       member foo
> -    member bar
> -        if IfAll(['defined(TEST_IF_UNION_BAR)'])
> -    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
> +    member union-bar
> +        if 'defined(TEST_IF_UNION_BAR)'
> +    if IfAll(['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 IfAll(['defined(TEST_IF_UNION_BAR)'])
> -    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
> +    case union-bar: q_obj_str-wrapper
> +        if 'defined(TEST_IF_UNION_BAR)'
> +    if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])
>   object q_obj_test-if-union-cmd-arg
>       member union-cmd-arg: TestIfUnion optional=False
> -    if IfAll(['defined(TEST_IF_UNION)'])
> +    if 'defined(TEST_IF_UNION)'
>   command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
> -    if IfAll(['defined(TEST_IF_UNION)'])
> +    if 'defined(TEST_IF_UNION)'
>   alternate TestIfAlternate
>       tag type
>       case foo: int
>       case bar: TestStruct
> -        if IfAll(['defined(TEST_IF_ALT_BAR)'])
> -    if IfAll(['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)'])
> +        if 'defined(TEST_IF_ALT_BAR)'
> +    if IfAll(['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])
>   object q_obj_test-if-alternate-cmd-arg
>       member alt-cmd-arg: TestIfAlternate optional=False
> -    if IfAll(['defined(TEST_IF_ALT)'])
> +    if IfAll(['defined(TEST_IF_ALT)', IfNot('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(['defined(TEST_IF_ALT)'])
> +    if IfAll(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')])
>   object q_obj_test-if-cmd-arg
>       member foo: TestIfStruct optional=False
>       member bar: TestIfEnum optional=False
> -        if IfAll(['defined(TEST_IF_CMD_BAR)'])
> +        if 'defined(TEST_IF_CMD_BAR)'
>       if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
>   command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
>       gen=True success_response=True boxed=False oob=False preconfig=False
> @@ -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(['defined(TEST_IF_ENUM)'])
> +    if 'defined(TEST_IF_ENUM)'
>   object q_obj_TEST_IF_EVENT-arg
>       member foo: TestIfStruct optional=False
>       member bar: TestIfEnumList optional=False
> -        if IfAll(['defined(TEST_IF_EVT_BAR)'])
> -    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
> +        if 'defined(TEST_IF_EVT_BAR)'
> +    if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])
>   event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
>       boxed=False
> -    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
> +    if IfAll(['defined(TEST_IF_EVT)', '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(['defined(TEST_IF_FEATURE_1)'])
> +        if 'defined(TEST_IF_FEATURE_1)'
>   object CondFeatureStruct2
>       member foo: int optional=False
>       feature feature1
> -        if IfAll(['defined(TEST_IF_FEATURE_1)'])
> +        if 'defined(TEST_IF_FEATURE_1)'
>       feature feature2
> -        if IfAll(['defined(TEST_IF_FEATURE_2)'])
> +        if 'defined(TEST_IF_FEATURE_2)'
>   object CondFeatureStruct3
>       member foo: int optional=False
>       feature feature1
>           if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
> +object CondFeatureStruct4
> +    member foo: int optional=False
> +    feature feature1
> +        if IfAny(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
>   enum FeatureEnum1
>       member eins
>       member zwei
> @@ -417,6 +421,7 @@ object q_obj_test-features0-arg
>       member cfs1: CondFeatureStruct1 optional=True
>       member cfs2: CondFeatureStruct2 optional=True
>       member cfs3: CondFeatureStruct3 optional=True
> +    member cfs4: CondFeatureStruct4 optional=True
>   command test-features0 q_obj_test-features0-arg -> FeatureStruct1
>       gen=True success_response=True boxed=False oob=False preconfig=False
>   command test-command-features1 None -> None
> @@ -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(['defined(TEST_IF_FEATURE_1)'])
> +        if 'defined(TEST_IF_FEATURE_1)'
>   command test-command-cond-features2 None -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
>       feature feature1
> -        if IfAll(['defined(TEST_IF_FEATURE_1)'])
> +        if 'defined(TEST_IF_FEATURE_1)'
>       feature feature2
> -        if IfAll(['defined(TEST_IF_FEATURE_2)'])
> +        if 'defined(TEST_IF_FEATURE_2)'
>   command test-command-cond-features3 None -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
>       feature feature1
> diff --git a/tests/qapi-schema/struct-member-if-invalid.err b/tests/qapi-schema/struct-member-if-invalid.err
> index 42e7fdae3c..c18157c1f9 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, a list of strings or a dict
> 



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

* Re: [PATCH v3 7/9] qapi: convert 'if' C-expressions to the new syntax tree
  2021-04-29 13:40 ` [PATCH v3 7/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
@ 2021-05-12 23:51   ` John Snow
  2021-05-17 11:20     ` Marc-André Lureau
  0 siblings, 1 reply; 40+ messages in thread
From: John Snow @ 2021-05-12 23:51 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   qapi/machine-target.json | 20 ++++++++++++++++----
>   qapi/misc-target.json    | 13 ++++++++++++-
>   2 files changed, 28 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 0c7491cd82..2891df6890 100644
> --- a/qapi/misc-target.json
> +++ b/qapi/misc-target.json
> @@ -23,7 +23,18 @@
>   ##
>   { '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_MOXIE) || 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_MOXIE)',
> +                   'defined(TARGET_PPC)',
> +                   'defined(TARGET_PPC64)',
> +                   'defined(TARGET_S390X)',
> +                   'defined(TARGET_SH4)',
> +                   'defined(TARGET_SPARC)' ] } }
>   
>   ##
>   # @rtc-reset-reinjection:
> 

I suppose these are necessary to convert because they were a single 
giant string, and not actually a list.

If you're going to keep the list-of-strings sugar, is it worth just 
changing these to a list of strings?

(How does your rust generator handle the defined(xxx) strings? does it 
strip the defined(...) off somehow?)

Tested-by: John Snow <jsnow@redhat.com>

--js



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

* Re: [PATCH v3 8/9] qapi: make 'if' condition strings simple identifiers
  2021-04-29 13:40 ` [PATCH v3 8/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
@ 2021-05-12 23:56   ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-12 23:56 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Change the 'if' condition strings to be C-agnostic and be simple
> identifiers.
> 

Oh, the answer to my last question. We strip off the defined(...) stuff 
here and now.

Seems fine to me, as I like removing C-specific bits of information from 
the QAPI schema.

As a "downgrade", we can't write arbitrary ifconds anymore. I only 
skimmed this list, but ... were we using any? I won't shed a tear for 
them, personally. This DOES give me more meaningful information to work 
with for other generators, for other languages.

Tested-by: John Snow <jsnow@redhat.com>

> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>   docs/devel/qapi-code-gen.txt                  |  8 +--
>   qapi/block-core.json                          | 16 ++---
>   qapi/block-export.json                        |  6 +-
>   qapi/char.json                                |  8 +--
>   qapi/machine-target.json                      | 40 ++++++-------
>   qapi/migration.json                           | 10 ++--
>   qapi/misc-target.json                         | 48 +++++++--------
>   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 +-
>   24 files changed, 177 insertions(+), 177 deletions(-)
> 
> diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
> index c1cb6f987d..edaaf7ec40 100644
> --- a/docs/devel/qapi-code-gen.txt
> +++ b/docs/devel/qapi-code-gen.txt
> @@ -791,7 +791,7 @@ will then be guarded by #if STRING for each STRING in the COND list.
>   Example: a conditional struct
>   
>    { 'struct': 'IfStruct', 'data': { 'foo': 'int' },
> -   'if': ['defined(CONFIG_FOO)', 'defined(HAVE_BAR)'] }
> +   'if': ['CONFIG_FOO', 'HAVE_BAR'] }
>   
>   gets its generated code guarded like this:
>   
> @@ -810,7 +810,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 +822,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 +832,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
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 6d227924d0..82213feb18 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' },
>               'sheepdog',
>               'ssh', 'throttle', 'vdi', 'vhdx', 'vmdk', 'vpc', 'vvfat' ] }
>   
> @@ -2860,10 +2860,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:
> @@ -3683,7 +3683,7 @@
>   # Since: 2.9
>   ##
>   { 'enum' : 'ReplicationMode', 'data' : [ 'primary', 'secondary' ],
> -  'if': 'defined(CONFIG_REPLICATION)' }
> +  'if': 'CONFIG_REPLICATION' }
>   
>   ##
>   # @BlockdevOptionsReplication:
> @@ -3702,7 +3702,7 @@
>     'base': 'BlockdevOptionsGenericFormat',
>     'data': { 'mode': 'ReplicationMode',
>               '*top-id': 'str' },
> -  'if': 'defined(CONFIG_REPLICATION)' }
> +  'if': 'CONFIG_REPLICATION' }
>   
>   ##
>   # @NFSTransport:
> @@ -4036,7 +4036,7 @@
>         'raw':        'BlockdevOptionsRaw',
>         'rbd':        'BlockdevOptionsRbd',
>         'replication': { 'type': 'BlockdevOptionsReplication',
> -                       'if': 'defined(CONFIG_REPLICATION)' },
> +                       'if': 'CONFIG_REPLICATION' },
>         'sheepdog':   'BlockdevOptionsSheepdog',
>         'ssh':        'BlockdevOptionsSsh',
>         'throttle':   'BlockdevOptionsThrottle',
> @@ -4338,7 +4338,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 6413970fa7..bf3f8d54e5 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:
> @@ -414,9 +414,9 @@
>               'stdio': 'ChardevStdio',
>               'console': 'ChardevCommon',
>               'spicevmc': { 'type': 'ChardevSpiceChannel',
> -                          'if': 'defined(CONFIG_SPICE)' },
> +                          'if': 'CONFIG_SPICE' },
>               'spiceport': { 'type': 'ChardevSpicePort',
> -                           'if': 'defined(CONFIG_SPICE)' },
> +                           'if': 'CONFIG_SPICE' },
>               '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 0b17cce46b..43e7b7620d 100644
> --- a/qapi/migration.json
> +++ b/qapi/migration.json
> @@ -539,7 +539,7 @@
>   ##
>   { 'enum': 'MultiFDCompression',
>     'data': [ 'none', 'zlib',
> -            { 'name': 'zstd', 'if': 'defined(CONFIG_ZSTD)' } ] }
> +            { 'name': 'zstd', 'if': 'CONFIG_ZSTD' } ] }
>   
>   ##
>   # @BitmapMigrationBitmapAliasTransform:
> @@ -1568,7 +1568,7 @@
>   ##
>   { 'command': 'xen-set-replication',
>     'data': { 'enable': 'bool', 'primary': 'bool', '*failover' : 'bool' },
> -  'if': 'defined(CONFIG_REPLICATION)' }
> +  'if': 'CONFIG_REPLICATION' }
>   
>   ##
>   # @ReplicationStatus:
> @@ -1584,7 +1584,7 @@
>   ##
>   { 'struct': 'ReplicationStatus',
>     'data': { 'error': 'bool', '*desc': 'str' },
> -  'if': 'defined(CONFIG_REPLICATION)' }
> +  'if': 'CONFIG_REPLICATION' }
>   
>   ##
>   # @query-xen-replication-status:
> @@ -1602,7 +1602,7 @@
>   ##
>   { 'command': 'query-xen-replication-status',
>     'returns': 'ReplicationStatus',
> -  'if': 'defined(CONFIG_REPLICATION)' }
> +  'if': 'CONFIG_REPLICATION' }
>   
>   ##
>   # @xen-colo-do-checkpoint:
> @@ -1619,7 +1619,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 2891df6890..5d3c816eb8 100644
> --- a/qapi/misc-target.json
> +++ b/qapi/misc-target.json
> @@ -23,18 +23,18 @@
>   ##
>   { '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_MOXIE)',
> -                   '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_MOXIE',
> +                   'TARGET_PPC',
> +                   'TARGET_PPC64',
> +                   'TARGET_S390X',
> +                   'TARGET_SH4',
> +                   'TARGET_SPARC' ] } }
>   
>   ##
>   # @rtc-reset-reinjection:
> @@ -53,7 +53,7 @@
>   #
>   ##
>   { 'command': 'rtc-reset-reinjection',
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   
>   ##
> @@ -80,7 +80,7 @@
>   { 'enum': 'SevState',
>     'data': ['uninit', 'launch-update', 'launch-secret', 'running',
>              'send-update', 'receive-update' ],
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   ##
>   # @SevInfo:
> @@ -112,7 +112,7 @@
>                 'state' : 'SevState',
>                 'handle' : 'uint32'
>               },
> -  'if': 'defined(TARGET_I386)'
> +  'if': 'TARGET_I386'
>   }
>   
>   ##
> @@ -133,7 +133,7 @@
>   #
>   ##
>   { 'command': 'query-sev', 'returns': 'SevInfo',
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   
>   ##
> @@ -147,7 +147,7 @@
>   #
>   ##
>   { 'struct': 'SevLaunchMeasureInfo', 'data': {'data': 'str'},
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   ##
>   # @query-sev-launch-measure:
> @@ -165,7 +165,7 @@
>   #
>   ##
>   { 'command': 'query-sev-launch-measure', 'returns': 'SevLaunchMeasureInfo',
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   
>   ##
> @@ -190,7 +190,7 @@
>               'cert-chain': 'str',
>               'cbitpos': 'int',
>               'reduced-phys-bits': 'int'},
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   ##
>   # @query-sev-capabilities:
> @@ -210,7 +210,7 @@
>   #
>   ##
>   { 'command': 'query-sev-capabilities', 'returns': 'SevCapability',
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   ##
>   # @sev-inject-launch-secret:
> @@ -228,7 +228,7 @@
>   ##
>   { 'command': 'sev-inject-launch-secret',
>     'data': { 'packet-header': 'str', 'secret': 'str', '*gpa': 'uint64' },
> -  'if': 'defined(TARGET_I386)' }
> +  'if': 'TARGET_I386' }
>   
>   ##
>   # @dump-skeys:
> @@ -250,7 +250,7 @@
>   ##
>   { 'command': 'dump-skeys',
>     'data': { 'filename': 'str' },
> -  'if': 'defined(TARGET_S390X)' }
> +  'if': 'TARGET_S390X' }
>   
>   ##
>   # @GICCapability:
> @@ -275,7 +275,7 @@
>     'data': { 'version': 'int',
>               'emulated': 'bool',
>               'kernel': 'bool' },
> -  'if': 'defined(TARGET_ARM)' }
> +  'if': 'TARGET_ARM' }
>   
>   ##
>   # @query-gic-capabilities:
> @@ -295,4 +295,4 @@
>   #
>   ##
>   { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
> -  'if': 'defined(TARGET_ARM)' }
> +  'if': 'TARGET_ARM' }
> diff --git a/qapi/qom.json b/qapi/qom.json
> index cd0e76d564..6c41ec61b0 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' } }
>   
>   ##
> @@ -752,7 +752,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',
> @@ -765,7 +765,7 @@
>       'iothread',
>       'memory-backend-file',
>       { 'name': 'memory-backend-memfd',
> -      'if': 'defined(CONFIG_LINUX)' },
> +      'if': 'CONFIG_LINUX' },
>       'memory-backend-ram',
>       'pef-guest',
>       'pr-manager-helper',
> @@ -809,7 +809,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',
> @@ -822,7 +822,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',
>         'rng-builtin':                'RngProperties',
> 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 6236bfc457..2a36e0366e 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -210,7 +210,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 0a97a6f020..a605a796db 100644
> --- a/scripts/qapi/expr.py
> +++ b/scripts/qapi/expr.py
> @@ -294,10 +294,10 @@ def check_if(expr: _JSONObject, info: QAPISourceInfo, source: str) -> None:
>   
>       def normalize(cond: Union[str, List[str], object]) -> IfPredicate:
>           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 IfOption(cond)
>           if isinstance(cond, list):
> 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 ca7e53f3b5..869d0b8636 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 'IFONE'
>       member two
> -    if 'defined(IFCOND)'
> +    if 'IFCOND'
>       feature enum-feat
>   object Base
>       member base1: Enum optional=False
>   object Variant1
>       member var1: str optional=False
> -        if 'defined(IFSTR)'
> +        if 'IFSTR'
>           feature member-feat
>       feature variant1-feat
>   object Variant2
> diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt
> index 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 2d5e480b44..fad64d879f 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': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] }
> +    'union-bar': { 'type': 'str', 'if': 'TEST_IF_UNION_BAR'} },
> +  'if': ['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': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] }
> +  'if': ['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': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] }
> +    'bar': { 'type': ['TestIfEnum'], 'if': 'TEST_IF_EVT_BAR' } },
> +  'if': ['TEST_IF_EVT', 'TEST_IF_STRUCT'] }
>   
>   # test 'features'
>   
> @@ -280,19 +280,19 @@
>   
>   { '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': [ 'defined(TEST_IF_COND_1)',
> -                                              'defined(TEST_IF_COND_2)'] } ] }
> +  'features': [ { 'name': 'feature1', 'if': [ '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' ],
> @@ -327,13 +327,13 @@
>     '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': [ 'defined(TEST_IF_COND_1)',
> -                                              'defined(TEST_IF_COND_2)'] } ] }
> +  'features': [ { 'name': 'feature1', 'if': [ '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 f859bf648d..95006e60c4 100644
> --- a/tests/qapi-schema/qapi-schema-test.out
> +++ b/tests/qapi-schema/qapi-schema-test.out
> @@ -298,65 +298,65 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio
>   object TestIfStruct
>       member foo: int optional=False
>       member bar: int optional=False
> -        if 'defined(TEST_IF_STRUCT_BAR)'
> -    if 'defined(TEST_IF_STRUCT)'
> +        if 'TEST_IF_STRUCT_BAR'
> +    if 'TEST_IF_STRUCT'
>   enum TestIfEnum
>       member foo
>       member bar
> -        if 'defined(TEST_IF_ENUM_BAR)'
> -    if 'defined(TEST_IF_ENUM)'
> +        if 'TEST_IF_ENUM_BAR'
> +    if 'TEST_IF_ENUM'
>   object q_obj_TestStruct-wrapper
>       member data: TestStruct optional=False
>   enum TestIfUnionKind
>       member foo
>       member union-bar
> -        if 'defined(TEST_IF_UNION_BAR)'
> -    if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])
> +        if 'TEST_IF_UNION_BAR'
> +    if IfAll(['TEST_IF_UNION', 'TEST_IF_STRUCT'])
>   object TestIfUnion
>       member type: TestIfUnionKind optional=False
>       tag type
>       case foo: q_obj_TestStruct-wrapper
>       case union-bar: q_obj_str-wrapper
> -        if 'defined(TEST_IF_UNION_BAR)'
> -    if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])
> +        if 'TEST_IF_UNION_BAR'
> +    if IfAll(['TEST_IF_UNION', 'TEST_IF_STRUCT'])
>   object q_obj_test-if-union-cmd-arg
>       member union-cmd-arg: TestIfUnion optional=False
> -    if 'defined(TEST_IF_UNION)'
> +    if 'TEST_IF_UNION'
>   command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
> -    if 'defined(TEST_IF_UNION)'
> +    if 'TEST_IF_UNION'
>   alternate TestIfAlternate
>       tag type
>       case foo: int
>       case bar: TestStruct
> -        if 'defined(TEST_IF_ALT_BAR)'
> -    if IfAll(['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])
> +        if 'TEST_IF_ALT_BAR'
> +    if IfAll(['TEST_IF_ALT', 'TEST_IF_STRUCT'])
>   object q_obj_test-if-alternate-cmd-arg
>       member alt-cmd-arg: TestIfAlternate optional=False
> -    if IfAll(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')])
> +    if IfAll(['TEST_IF_ALT', IfNot('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(['defined(TEST_IF_ALT)', IfNot('defined(TEST_IF_NOT_ALT)')])
> +    if IfAll(['TEST_IF_ALT', IfNot('TEST_IF_NOT_ALT')])
>   object q_obj_test-if-cmd-arg
>       member foo: TestIfStruct optional=False
>       member bar: TestIfEnum optional=False
> -        if 'defined(TEST_IF_CMD_BAR)'
> -    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
> +        if 'TEST_IF_CMD_BAR'
> +    if IfAll(['TEST_IF_CMD', 'TEST_IF_STRUCT'])
>   command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
>       gen=True success_response=True boxed=False oob=False preconfig=False
> -    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
> +    if IfAll(['TEST_IF_CMD', 'TEST_IF_STRUCT'])
>   command test-cmd-return-def-three None -> UserDefThree
>       gen=True success_response=True boxed=False oob=False preconfig=False
>   array TestIfEnumList TestIfEnum
> -    if 'defined(TEST_IF_ENUM)'
> +    if 'TEST_IF_ENUM'
>   object q_obj_TEST_IF_EVENT-arg
>       member foo: TestIfStruct optional=False
>       member bar: TestIfEnumList optional=False
> -        if 'defined(TEST_IF_EVT_BAR)'
> -    if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])
> +        if 'TEST_IF_EVT_BAR'
> +    if IfAll(['TEST_IF_EVT', 'TEST_IF_STRUCT'])
>   event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
>       boxed=False
> -    if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])
> +    if IfAll(['TEST_IF_EVT', 'TEST_IF_STRUCT'])
>   object FeatureStruct0
>       member foo: int optional=False
>   object FeatureStruct1
> @@ -379,21 +379,21 @@ object FeatureStruct4
>   object CondFeatureStruct1
>       member foo: int optional=False
>       feature feature1
> -        if 'defined(TEST_IF_FEATURE_1)'
> +        if 'TEST_IF_FEATURE_1'
>   object CondFeatureStruct2
>       member foo: int optional=False
>       feature feature1
> -        if 'defined(TEST_IF_FEATURE_1)'
> +        if 'TEST_IF_FEATURE_1'
>       feature feature2
> -        if 'defined(TEST_IF_FEATURE_2)'
> +        if 'TEST_IF_FEATURE_2'
>   object CondFeatureStruct3
>       member foo: int optional=False
>       feature feature1
> -        if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
> +        if IfAll(['TEST_IF_COND_1', 'TEST_IF_COND_2'])
>   object CondFeatureStruct4
>       member foo: int optional=False
>       feature feature1
> -        if IfAny(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
> +        if IfAny(['TEST_IF_COND_1', 'TEST_IF_COND_2'])
>   enum FeatureEnum1
>       member eins
>       member zwei
> @@ -434,17 +434,17 @@ command test-command-features3 None -> None
>   command test-command-cond-features1 None -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
>       feature feature1
> -        if 'defined(TEST_IF_FEATURE_1)'
> +        if 'TEST_IF_FEATURE_1'
>   command test-command-cond-features2 None -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
>       feature feature1
> -        if 'defined(TEST_IF_FEATURE_1)'
> +        if 'TEST_IF_FEATURE_1'
>       feature feature2
> -        if 'defined(TEST_IF_FEATURE_2)'
> +        if 'TEST_IF_FEATURE_2'
>   command test-command-cond-features3 None -> None
>       gen=True success_response=True boxed=False oob=False preconfig=False
>       feature feature1
> -        if IfAll(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
> +        if IfAll(['TEST_IF_COND_1', 'TEST_IF_COND_2'])
>   event TEST_EVENT_FEATURES0 FeatureStruct1
>       boxed=False
>   event TEST_EVENT_FEATURES1 None
> diff --git a/tests/qapi-schema/union-branch-if-invalid.err b/tests/qapi-schema/union-branch-if-invalid.err
> index dd4518233e..046187a5b9 100644
> --- a/tests/qapi-schema/union-branch-if-invalid.err
> +++ b/tests/qapi-schema/union-branch-if-invalid.err
> @@ -1,2 +1,2 @@
>   union-branch-if-invalid.json: In union 'Uni':
> -union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' makes no sense
> +union-branch-if-invalid.json:4: 'if' condition '' of 'data' member 'branch1' is not a valid identifier
> 



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

* Re: [PATCH v3 9/9] docs: update the documentation about schema configuration
  2021-04-29 13:40 ` [PATCH v3 9/9] docs: update the documentation about schema configuration marcandre.lureau
@ 2021-05-13  0:01   ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-13  0:01 UTC (permalink / raw)
  To: marcandre.lureau, qemu-devel; +Cc: armbru

On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

I see why the docs are here at the end now -- you change the AST first, 
and then the meaning of the conditional string second. You didn't wanna 
write two versions.

I'm fine with that personally, but I'm not the one to convince.

> ---
>   docs/devel/qapi-code-gen.txt | 27 ++++++++++++++++-----------
>   1 file changed, 16 insertions(+), 11 deletions(-)
> 
> diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
> index edaaf7ec40..4a3fd02723 100644
> --- a/docs/devel/qapi-code-gen.txt
> +++ b/docs/devel/qapi-code-gen.txt
> @@ -780,26 +780,31 @@ downstream command __com.redhat_drive-mirror.
>   === Configuring the schema ===
>   
>   Syntax:
> -    COND = STRING
> -         | [ STRING, ... ]
> +    COND = CFG-ID
> +         | [ COND, ... ]
> +         | { '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.
> +    CFG-ID = STRING
> +
> +All definitions take an optional 'if' member. Its value must be a string, a list
> +of strings or an object with a single member 'all', 'any' or 'not'. A string is
> +shorthand for a list containing just that string. A list is a shorthand for a
> +'all'-member object. The C code generated for the definition will then be guarded
> +by an #if precessor expression generated from that condition: 'all': [COND, ...]
> +will generate '(COND && ...)', 'any': [COND, ...] '(COND || ...)', 'not': COND '!COND'.
>   
>   Example: a conditional struct
>   
>    { 'struct': 'IfStruct', 'data': { 'foo': 'int' },
> -   'if': ['CONFIG_FOO', '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
> 

Tentatively pretty OK with the gist of the changes in this series in 
general; pending possibly some re-ordering/re-basing changes and the 
like to make various things flow better that I don't personally insist on.

Biggest personal concerns are:

- Inlining C-specific information into schema.py
- Inlining C-generators into the IfPredicate classes
- Building IfPredicate trees directly in expr.py

Tested-by: John Snow <jsnow@redhat.com>

--js



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

* Re: [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-05-12 20:53   ` John Snow
@ 2021-05-17 11:17     ` Marc-André Lureau
  2021-05-17 16:30       ` John Snow
  0 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-17 11:17 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-devel, Armbruster, Markus

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

Hi

On Thu, May 13, 2021 at 12:53 AM John Snow <jsnow@redhat.com> wrote:

> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Wrap the 'if' condition in a higher-level object. Not only this allows
> > more type safety but also further refactoring without too much churn.
> >
>
> Would have done it myself if I had gotten to it first. I like having a
> named type for this, it matches the other properties we have.
>
> > 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>
> > ---
> >   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     | 78 +++++++++++++++++++++++++++-----------
> >   scripts/qapi/types.py      | 33 ++++++++--------
> >   scripts/qapi/visit.py      | 23 +++++------
> >   8 files changed, 110 insertions(+), 75 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 3a4172fb74..7d6f390fa6 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -25,6 +25,22 @@
> >   from .parser import QAPISchemaParser
> >
> >
> > +class QAPISchemaIfCond:
> > +    def __init__(self, ifcond=None):
> > +        self.ifcond = ifcond or []
> > +
> > +    def __bool__(self):
> > +        return bool(self.ifcond)
> > +
> > +    def __repr__(self):
> > +        return repr(self.ifcond)
> > +
>
> I suggest using:
>
> f"QAPISchemaIfCond({self.ifcond!r})"
>
> unless future patches make that weirder. It's nice when repr() returns
> something you can use to make a new, equivalent object.
>
> eval(repr(x)) == x
>
>
I implemented it this way for compatibility with test-qapi output. But it
is simpler to actually no implement __repr__ and just change the test.

> +    def __eq__(self, other):
> > +        if not isinstance(other, QAPISchemaIfCond):
> > +            return NotImplemented
> > +        return self.ifcond == other.ifcond
> > +
> > +
>
> Missing annotations, but ... yeah, schema.py isn't typed yet so I will
> handle it myself in pt6. No biggie.
>

There used to be annotations all over in earlier versions, but I rebased
and had to drop most of them.


> >   class QAPISchemaEntity:
> >       meta: Optional[str] = None
> >
> > @@ -42,7 +58,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 +93,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 +617,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 +662,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):
> > @@ -958,14 +974,20 @@ def _def_predefineds(self):
> >           self._def_entity(QAPISchemaEnumType('QType', None, None, None,
> None,
> >                                               qtype_values, 'QTYPE'))
> >
> > +    def _get_if(self, f) -> QAPISchemaIfCond:
> > +        ifcond = f.get('if')
> > +        if isinstance(ifcond, QAPISchemaIfCond):
>
> would "is not None" suffice here?
>

> > +            return ifcond
> > +        return QAPISchemaIfCond(ifcond)
>
> Under which circumstances do we expect 'ifcond' here to be something
> other than None but *not* a QAPISchemaIfCond?
>
> This helper seems a little suspect in the sense that it appears to be
> taking liberties with the dynamic runtime object system.
>

Yes, this is due to the adhoc enum being created in _def_union_type().
There, the ifcond of the variant is used, which is already a
QAPISchemaIfCond. I'll lower it again to the list form.


> > + >       def _make_features(self, features, info):
> >           if features is None:
> >               return []
> > -        return [QAPISchemaFeature(f['name'], info, f.get('if'))
> > +        return [QAPISchemaFeature(f['name'], info, self._get_if(f))
> >                   for f in features]
> >
> >       def _make_enum_members(self, values, info):
> > -        return [QAPISchemaEnumMember(v['name'], info, v.get('if'))
> > +        return [QAPISchemaEnumMember(v['name'], info, self._get_if(v))
> >                   for v in values]
> >
> >       def _make_implicit_enum_type(self, name, info, ifcond, values):
> > @@ -1001,7 +1023,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))
> > @@ -1011,7 +1033,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,
> > @@ -1029,7 +1051,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()]
> >
> > @@ -1037,7 +1060,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,
> > @@ -1060,7 +1083,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
> > @@ -1069,14 +1092,20 @@ 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()]
> > +            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} for v in
> variants]
> >               typ = self._make_implicit_enum_type(name, info, ifcond,
> enum)
> >               tag_member = QAPISchemaObjectTypeMember('type', info, typ,
> False)
> > @@ -1090,11 +1119,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,
> > @@ -1111,7 +1143,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(
> > @@ -1130,7 +1162,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):
> >
>
> looks good.
>
> Tested-by: John Snow <jsnow@redhat.com>
>
>

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

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

* Re: [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond
  2021-05-12 18:53     ` John Snow
@ 2021-05-17 11:18       ` Marc-André Lureau
  0 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-17 11:18 UTC (permalink / raw)
  To: John Snow; +Cc: Stefan Hajnoczi, qemu-devel, Armbruster, Markus

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

Hi

On Wed, May 12, 2021 at 10:53 PM John Snow <jsnow@redhat.com> wrote:

> On 5/11/21 12:39 PM, Stefan Hajnoczi wrote:
> > On Thu, Apr 29, 2021 at 05:40:25PM +0400, marcandre.lureau@redhat.com
> wrote:
> >> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >>
> >> Move the generating function to the QAPISchemaIfCond class.
> >
> > I'm not familiar enough with the QAPI code generator to know whether
> > schema.py is supposed to generate C code directly. Otherwise this
> > refactoring makes sense.
> >
>
> It's not supposed to, necessarily, but schema.py *does* already have
> some "built-in" ties to the C language -- in particular its use of
> POINTER_SUFFIX and c_name. It isn't split ultra-cleanly.
>
> Ideally, and I am neither asking for nor requiring this, I would like to
> see code generator backends factored neatly such that the C generative
> code is all in one place, or at least outside of the abstract QAPI
> internals.
>
> (Maybe gen.py can be split into gen.py and c_gen.py, and the C-specific
> bits for gen_if et al can go into c_gen.py.)
>
> Since you are working towards a Rust backend, I assume that the
> C-specific portions of this code don't remain in schema.py for too long?
> I can't imagine them being here helps you with your Rust generator, but
> I could be wrong.
>

I agree. Let's not mix everything in this series which would delay it even
further. Let's address this when we get to actually add other bindings.

thanks

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

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

* Re: [PATCH v3 3/9] qapi: start building an 'if' predicate tree
  2021-05-12 21:39   ` John Snow
@ 2021-05-17 11:18     ` Marc-André Lureau
  2021-05-17 16:34       ` John Snow
  2021-05-21 14:48     ` Markus Armbruster
  1 sibling, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-17 11:18 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-devel, Armbruster, Markus

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

Hi

On Thu, May 13, 2021 at 1:39 AM John Snow <jsnow@redhat.com> wrote:

> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> > 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>
> > ---
> >   docs/sphinx/qapidoc.py                 |  6 +--
> >   scripts/qapi/common.py                 | 54 +++++++++++++++++++++++-
> >   scripts/qapi/schema.py                 | 42 ++++++++++++-------
> >   tests/qapi-schema/doc-good.out         | 12 +++---
> >   tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
> >   5 files changed, 116 insertions(+), 56 deletions(-)
> >
> > diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
> > index b737949007..a93f3f1c4d 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(', '))
>
> was this the last user of intersperse?
>
> mm, no, there's one more.
>
> > +        condlist = [nodes.literal('', ifcond.gen_doc())]
> >           if not with_if:
> >               return condlist
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index b7f475a160..59a7ee2f32 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -11,8 +11,9 @@
> >   # This work is licensed under the terms of the GNU GPL, version 2.
> >   # See the COPYING file in the top-level directory.
> >
> > +from abc import ABC, abstractmethod
> >   import re
> > -from typing import Optional
> > +from typing import Optional, Sequence
> >
> >
> >   #: Magic string that gets removed along with all space to its right.
> > @@ -192,3 +193,54 @@ def guardend(name: str) -> str:
> >   #endif /* %(name)s */
> >   ''',
> >                    name=c_fname(name).upper())
> > +
> > +
> > +class IfPredicate(ABC):
> > +    @abstractmethod
> > +    def cgen(self) -> str:
>
> Like the review to patch 2, I'm not sure we want to bake cgen stuff
> directly into this class. Are you going to have cgen() and rustgen()
> methods all here?
>
>
As discussed, we'll see that when we get to it, I don't remember.

> +        pass
> > +
>
> I think you want raise NotImplementedError to specify a function that
> the inheriting class MUST implement. Otherwise, there's little value to
> allow a child class to call super() on a method that doesn't have a
> default implementation.
>
> You *could* drop the (ABC) and the @abstractmethod decorators if you do so.
>
> Matters of taste and style.
>

ok


> > +    @abstractmethod
> > +    def docgen(self) -> str:
> > +        pass
> > + > +
> > +class IfOption(IfPredicate):
>
> missing a docstring here, but I suppose I haven't written a style guide
> on how to write those yet.
>
> I assume IfOption is one single element?
>

e.g. X && Y && Z is
>
> All(
>      Option(X),
>      Option(Y),
>      Option(Z),
> )
>
> Right?
>
>
Correct


> > +    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 repr(self.option)
> > +
>
> type(self).__name__ + f"({self.option!r})"
>
> or just hard-code the IfOption in there
>

ok


> > +    def __eq__(self, other: object) -> bool:
> > +        if not isinstance(other, IfOption):
> > +            return False
>
> maybe NotImplemented?
>
> (Admitting I don't know the FULL consequences of doing it either way,
> just prompting you for an explanation.)
>

 Apparently NotImplemented is the best thing to do, as it will fallback in
other ways.

It doesn't really matter it seems, let's use 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"IfAll({self.pred_list})"
> > +
>
> {self.pred_list!r} to get the recursive repr.
>

ok


> You can consider adding a __str__ method to add a kind of short-hand
> that skips the class names and stuff and keeps the output less chatty.
>
>
ok

> +    def __eq__(self, other: object) -> bool:
> > +        if not isinstance(other, IfAll):
> > +            return False
>
> NotImplemented again?
>
> > +        return self.pred_list == other.pred_list
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index 8e6d0a5296..366a53ab64 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -19,7 +19,13 @@
> >   import re
> >   from typing import Optional
> >
> > -from .common import POINTER_SUFFIX, c_name, mcgen
> > +from .common import (
> > +    POINTER_SUFFIX,
> > +    IfAll,
> > +    IfOption,
> > +    c_name,
> > +    mcgen,
> > +)
> >   from .error import QAPISemError, QAPISourceError
> >   from .expr import check_exprs
> >   from .parser import QAPISchemaParser
> > @@ -27,34 +33,38 @@
> >
> >   class QAPISchemaIfCond:
> >       def __init__(self, ifcond=None):
> > -        self.ifcond = ifcond or []
> > +        pred_list = [IfOption(opt) for opt in ifcond or []]
> > +        self.pred = IfAll(pred_list)
> > +
> > +    def gen_doc(self):
> > +        if self.pred:
> > +            return self.pred.docgen()
> > +        return ""
> >
>
> pred should always be IfAll as of here, can we remove the conditional?
> .join() will deform to the empty string when it's given an empty sequence
>
>
ok

>       def gen_if(self):
> > -        ret = ''
> > -        for ifc in self.ifcond:
> > -            ret += mcgen('''
> > +        if self.pred:
> > +            return mcgen('''
> >   #if %(cond)s
> > -''', cond=ifc)
> > -        return ret
> > +''', cond=self.pred.cgen())
> > +        return ""
> >
> >       def gen_endif(self):
> > -        ret = ''
> > -        for ifc in reversed(self.ifcond):
> > -            ret += mcgen('''
> > -#endif /* %(cond)s */
> > -''', cond=ifc)
> > -        return ret
> > +        if self.pred:
> > +            return mcgen('''
> > +#endif // %(cond)s
> > +''', cond=self.pred.cgen())
> > +        return ""
> >
> >       def __bool__(self):
> > -        return bool(self.ifcond)
> > +        return bool(self.pred)
> >
> >       def __repr__(self):
> > -        return repr(self.ifcond)
> > +        return repr(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..6bf996f539 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(['defined(IFONE)'])
>
> I suppose the test output changing, no longer representing a single
> language makes sense.
>
> >       member two
> > -    if ['defined(IFCOND)']
> > +    if IfAll(['defined(IFCOND)'])
> >       feature enum-feat
> >   object Base
> >       member base1: Enum optional=False
> >   object Variant1
> >       member var1: str optional=False
> > -        if ['defined(IFSTR)']
> > +        if IfAll(['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(['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(['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(['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..c2d303aa18 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(['defined(TEST_IF_STRUCT_BAR)'])
> > +    if IfAll(['defined(TEST_IF_STRUCT)'])
> >   enum TestIfEnum
> >       member foo
> >       member bar
> > -        if ['defined(TEST_IF_ENUM_BAR)']
> > -    if ['defined(TEST_IF_ENUM)']
> > +        if IfAll(['defined(TEST_IF_ENUM_BAR)'])
> > +    if IfAll(['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(['defined(TEST_IF_UNION_BAR)'])
> > +    if IfAll(['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(['defined(TEST_IF_UNION_BAR)'])
> > +    if IfAll(['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(['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(['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(['defined(TEST_IF_ALT_BAR)'])
> > +    if IfAll(['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(['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(['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(['defined(TEST_IF_CMD_BAR)'])
> > +    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
> >   command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> > -    if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)']
> > +    if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
> >   command test-cmd-return-def-three None -> UserDefThree
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> >   array TestIfEnumList TestIfEnum
> > -    if ['defined(TEST_IF_ENUM)']
> > +    if IfAll(['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(['defined(TEST_IF_EVT_BAR)'])
> > +    if IfAll(['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(['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(['defined(TEST_IF_FEATURE_1)'])
> >   object CondFeatureStruct2
> >       member foo: int optional=False
> >       feature feature1
> > -        if ['defined(TEST_IF_FEATURE_1)']
> > +        if IfAll(['defined(TEST_IF_FEATURE_1)'])
> >       feature feature2
> > -        if ['defined(TEST_IF_FEATURE_2)']
> > +        if IfAll(['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(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
> >   enum FeatureEnum1
> >       member eins
> >       member zwei
> > @@ -429,17 +429,17 @@ command test-command-features3 None -> None
> >   command test-command-cond-features1 None -> None
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> >       feature feature1
> > -        if ['defined(TEST_IF_FEATURE_1)']
> > +        if IfAll(['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(['defined(TEST_IF_FEATURE_1)'])
> >       feature feature2
> > -        if ['defined(TEST_IF_FEATURE_2)']
> > +        if IfAll(['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(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
> >   event TEST_EVENT_FEATURES0 FeatureStruct1
> >       boxed=False
> >   event TEST_EVENT_FEATURES1 None
> >
>
> Tested-by: John Snow <jsnow@redhat.com>
>
> Seems fine again, minor style qualms about Python I am not that direly
> opinionated on. More worried about the integration of cgen and docgen,
> though I see why with a predicate tree it becomes *extremely* convenient
> to integrate them right into the class.
>
> Is there a way to have our cake and eat it, too ... ?
>
> (Maybe a visitor of some kind is the right way to go?)
>

That's also what I thought (it was in my commit message comments).
Repeating myself, I'd defer this, there is no urge to make the code more
complex yet. It can easily be done in a following iteration.

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

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

* Re: [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny
  2021-05-12 23:26   ` John Snow
@ 2021-05-17 11:18     ` Marc-André Lureau
  2021-05-17 16:35       ` John Snow
  0 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-17 11:18 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-devel, Armbruster, Markus

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

Hi

On Thu, May 13, 2021 at 3:26 AM John Snow <jsnow@redhat.com> wrote:

> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> > 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>
> > ---
> >   scripts/qapi/common.py | 32 +++++++++++++++++++++++++++-----
> >   1 file changed, 27 insertions(+), 5 deletions(-)
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index 59a7ee2f32..102d347348 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -224,23 +224,45 @@ 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)
> >
> >       def __repr__(self) -> str:
> > -        return f"IfAll({self.pred_list})"
> > +        ty = type(self)
> > +        return f"{ty.__qualname__}({self.pred_list})"
> >
> >       def __eq__(self, other: object) -> bool:
> > -        if not isinstance(other, IfAll):
> > +        if not isinstance(other, type(self)):
> >               return False
> >           return self.pred_list == other.pred_list
> > +
> > +
> > +class IfAll(IfPredicateList):
> > +    C_SEP = "&&"
> > +    DOC_SEP = "and"
> > +
> > +
> > +class IfAny(IfPredicateList):
> > +    C_SEP = "||"
> > +    DOC_SEP = "or"
> >
>
> I do like the way these get combined. Is there a reason it's not
> squashed into the earlier commit?
>
>
No, just the sake of doing things iteratively.

(Qualms about not having a visitor or a callback or whatever you want to
> call it remain, but I'll stop remarking on that for the rest of this
> series.)
>
>
thanks :)

Tested-by: John Snow <jsnow@redhat.com>
>
>

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

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

* Re: [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree
  2021-05-12 23:47   ` John Snow
@ 2021-05-17 11:18     ` Marc-André Lureau
  2021-05-17 16:41       ` John Snow
  0 siblings, 1 reply; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-17 11:18 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-devel, Armbruster, Markus

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

Hi

On Thu, May 13, 2021 at 3:47 AM John Snow <jsnow@redhat.com> wrote:

> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Modify check_if() to build an IfPredicate tree (the schema
> > documentation is updated in a following patch).
> >
>
> I'm wondering if check_if() is the right place to do this. It's
> certainly convenient, but we don't build any other domain-specific types
> here at all -- that all happens in schema.py.
>

Ok, I moved the conversion to schema.py, like other _make_foo() there


>
> Before this patch, the return value from expr.py is conceivably
> something you'd get "exactly as-is" from a JSON parser. This patch would
> end that, and collapses the waveform.
>
> I think we should build a function that turns the raw (or slightly
> normalized) 'ifcond' AST into the IfPredicate object like we do for
> other structures, like Members, Features, etc.
>
>
There is now a _make_if() to convert the raw json to SchemaIfCond.

I'd also like the documentation changes to eventually be squashed
> directly into this patch if it changes syntax, but keeping it separate
> during review makes sense.
>
> Tentatively, I think the expanded "IF" syntax makes sense.
>
> 'if': 'COND'
> 'if': ['COND']
> 'if': { 'any': ['COND'] }
>

Actually, a simple list is short form for { 'all': [] }


> Seems fine. I want to play around a little bit with a JsonSchema for it
> though to make sure that it's something I can provide good IntelliSense
> tooltips for in e.g. vscode. (A bit of a pipe-dream side project, I
> admit, but if you'll humor me I'd like the chance to give it a shot.
> Some constructs are simply easier to type and validate than others.)
>
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Tested-by: John Snow <jsnow@redhat.com>
>
> > ---
> >   tests/unit/test-qmp-cmds.c                    |  1 +
> >   scripts/qapi/expr.py                          | 62 ++++++++++++++-----
> >   scripts/qapi/schema.py                        | 13 +---
> >   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       | 20 +++---
> >   tests/qapi-schema/qapi-schema-test.out        | 59 ++++++++++--------
> >   .../qapi-schema/struct-member-if-invalid.err  |  2 +-
> >   10 files changed, 106 insertions(+), 71 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..0a97a6f020 100644
> > --- a/scripts/qapi/expr.py
> > +++ b/scripts/qapi/expr.py
> > @@ -42,7 +42,14 @@
> >       cast,
> >   )
> >
> > -from .common import c_name
> > +from .common import (
> > +    IfAll,
> > +    IfAny,
> > +    IfNot,
> > +    IfOption,
> > +    IfPredicate,
> > +    c_name,
> > +)
> >   from .error import QAPISemError
> >   from .parser import QAPIDoc
> >   from .source import QAPISourceInfo
> > @@ -261,6 +268,10 @@ def check_if(expr: _JSONObject, info:
> QAPISourceInfo, source: str) -> None:
> >       """
> >       Normalize and validate the ``if`` member of an object.
> >
> > +    The ``if`` field may be either a ``str``, a ``List[str]`` or a dict.
> > +    A ``str`` element or a ``List[str]`` will be normalized to
> > +    ``IfAll([str])``.
> > +
> >       The ``if`` member may be either a ``str`` or a ``List[str]``.
> >       A ``str`` value will be normalized to ``List[str]``.
> >
> > @@ -281,25 +292,44 @@ 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, List[str], object]) -> IfPredicate:
> > +        if isinstance(cond, str):
> > +            if not cond.strip():
> > +                raise QAPISemError(
> > +                    info,
> > +                    "'if' condition '%s' of %s makes no sense"
> > +                    % (cond, source))
> > +            return IfOption(cond)
> > +        if isinstance(cond, list):
> > +            cond = {"all": 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, "
> > +                "a list of strings 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 oper == "not":
> > +            return IfNot(normalize(operands))
> > +        if not operands:
> > +            raise QAPISemError(
> > +                info, "'if' condition [] of %s is useless" % source)
> > +        if 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 IfAll(operands) if oper == "all" else IfAny(operands)
> > +
> > +    ifcond = expr.get('if')
> > +    if ifcond is None:
> > +        return
> > +    expr['if'] = normalize(ifcond)
> >
> >
> >   def normalize_members(members: object) -> None:
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index 366a53ab64..61664a4c5e 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -19,22 +19,15 @@
> >   import re
> >   from typing import Optional
> >
> > -from .common import (
> > -    POINTER_SUFFIX,
> > -    IfAll,
> > -    IfOption,
> > -    c_name,
> > -    mcgen,
> > -)
> > +from .common import POINTER_SUFFIX, c_name, mcgen
> >   from .error import QAPISemError, QAPISourceError
> >   from .expr import check_exprs
> >   from .parser import QAPISchemaParser
> >
> >
> >   class QAPISchemaIfCond:
> > -    def __init__(self, ifcond=None):
> > -        pred_list = [IfOption(opt) for opt in ifcond or []]
> > -        self.pred = IfAll(pred_list)
> > +    def __init__(self, pred=None):
> > +        self.pred = pred
> >
> >       def gen_doc(self):
> >           if self.pred:
> > 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 6bf996f539..ca7e53f3b5 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(['defined(IFONE)'])
> > +        if 'defined(IFONE)'
> >       member two
> > -    if IfAll(['defined(IFCOND)'])
> > +    if 'defined(IFCOND)'
> >       feature enum-feat
> >   object Base
> >       member base1: Enum optional=False
> >   object Variant1
> >       member var1: str optional=False
> > -        if IfAll(['defined(IFSTR)'])
> > +        if '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(['IFTWO'])
> > +        if '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(['IFTWO'])
> > +        if 'IFTWO'
> >   object SugaredUnion
> >       member type: SugaredUnionKind optional=False
> >       tag type
> >       case one: q_obj_Variant1-wrapper
> >       case two: q_obj_Variant2-wrapper
> > -        if IfAll(['IFTWO'])
> > +        if '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..724a810086 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, a list of strings or a dict
> > diff --git a/tests/qapi-schema/qapi-schema-test.json
> b/tests/qapi-schema/qapi-schema-test.json
> > index 84b9d41f15..2d5e480b44 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': ['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'] }
> >
> >   { 'command': 'test-if-union-cmd',
> >     'data': { 'union-cmd-arg': 'TestIfUnion' },
> > @@ -241,11 +241,10 @@
> >   { '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': {
> > @@ -259,7 +258,7 @@
> >   { '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': ['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'] }
> >
> >   # test 'features'
> >
> > @@ -290,6 +289,10 @@
> >     'data': { 'foo': 'int' },
> >     'features': [ { 'name': 'feature1', 'if': [
> '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 +316,8 @@
> >               '*fs4': 'FeatureStruct4',
> >               '*cfs1': 'CondFeatureStruct1',
> >               '*cfs2': 'CondFeatureStruct2',
> > -            '*cfs3': 'CondFeatureStruct3' },
> > +            '*cfs3': 'CondFeatureStruct3',
> > +            '*cfs4': 'CondFeatureStruct4' },
> >     'returns': 'FeatureStruct1',
> >     'features': [] }
> >
> > diff --git a/tests/qapi-schema/qapi-schema-test.out
> b/tests/qapi-schema/qapi-schema-test.out
> > index c2d303aa18..f859bf648d 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(['defined(TEST_IF_STRUCT_BAR)'])
> > -    if IfAll(['defined(TEST_IF_STRUCT)'])
> > +        if 'defined(TEST_IF_STRUCT_BAR)'
> > +    if 'defined(TEST_IF_STRUCT)'
> >   enum TestIfEnum
> >       member foo
> >       member bar
> > -        if IfAll(['defined(TEST_IF_ENUM_BAR)'])
> > -    if IfAll(['defined(TEST_IF_ENUM)'])
> > +        if 'defined(TEST_IF_ENUM_BAR)'
> > +    if 'defined(TEST_IF_ENUM)'
> >   object q_obj_TestStruct-wrapper
> >       member data: TestStruct optional=False
> >   enum TestIfUnionKind
> >       member foo
> > -    member bar
> > -        if IfAll(['defined(TEST_IF_UNION_BAR)'])
> > -    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
> > +    member union-bar
> > +        if 'defined(TEST_IF_UNION_BAR)'
> > +    if IfAll(['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 IfAll(['defined(TEST_IF_UNION_BAR)'])
> > -    if IfAll(['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'])
> > +    case union-bar: q_obj_str-wrapper
> > +        if 'defined(TEST_IF_UNION_BAR)'
> > +    if IfAll(['defined(TEST_IF_UNION)', 'defined(TEST_IF_STRUCT)'])
> >   object q_obj_test-if-union-cmd-arg
> >       member union-cmd-arg: TestIfUnion optional=False
> > -    if IfAll(['defined(TEST_IF_UNION)'])
> > +    if 'defined(TEST_IF_UNION)'
> >   command test-if-union-cmd q_obj_test-if-union-cmd-arg -> None
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> > -    if IfAll(['defined(TEST_IF_UNION)'])
> > +    if 'defined(TEST_IF_UNION)'
> >   alternate TestIfAlternate
> >       tag type
> >       case foo: int
> >       case bar: TestStruct
> > -        if IfAll(['defined(TEST_IF_ALT_BAR)'])
> > -    if IfAll(['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)'])
> > +        if 'defined(TEST_IF_ALT_BAR)'
> > +    if IfAll(['defined(TEST_IF_ALT)', 'defined(TEST_IF_STRUCT)'])
> >   object q_obj_test-if-alternate-cmd-arg
> >       member alt-cmd-arg: TestIfAlternate optional=False
> > -    if IfAll(['defined(TEST_IF_ALT)'])
> > +    if IfAll(['defined(TEST_IF_ALT)',
> IfNot('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(['defined(TEST_IF_ALT)'])
> > +    if IfAll(['defined(TEST_IF_ALT)',
> IfNot('defined(TEST_IF_NOT_ALT)')])
> >   object q_obj_test-if-cmd-arg
> >       member foo: TestIfStruct optional=False
> >       member bar: TestIfEnum optional=False
> > -        if IfAll(['defined(TEST_IF_CMD_BAR)'])
> > +        if 'defined(TEST_IF_CMD_BAR)'
> >       if IfAll(['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'])
> >   command test-if-cmd q_obj_test-if-cmd-arg -> UserDefThree
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> > @@ -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(['defined(TEST_IF_ENUM)'])
> > +    if 'defined(TEST_IF_ENUM)'
> >   object q_obj_TEST_IF_EVENT-arg
> >       member foo: TestIfStruct optional=False
> >       member bar: TestIfEnumList optional=False
> > -        if IfAll(['defined(TEST_IF_EVT_BAR)'])
> > -    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
> > +        if 'defined(TEST_IF_EVT_BAR)'
> > +    if IfAll(['defined(TEST_IF_EVT)', 'defined(TEST_IF_STRUCT)'])
> >   event TEST_IF_EVENT q_obj_TEST_IF_EVENT-arg
> >       boxed=False
> > -    if IfAll(['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'])
> > +    if IfAll(['defined(TEST_IF_EVT)', '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(['defined(TEST_IF_FEATURE_1)'])
> > +        if 'defined(TEST_IF_FEATURE_1)'
> >   object CondFeatureStruct2
> >       member foo: int optional=False
> >       feature feature1
> > -        if IfAll(['defined(TEST_IF_FEATURE_1)'])
> > +        if 'defined(TEST_IF_FEATURE_1)'
> >       feature feature2
> > -        if IfAll(['defined(TEST_IF_FEATURE_2)'])
> > +        if 'defined(TEST_IF_FEATURE_2)'
> >   object CondFeatureStruct3
> >       member foo: int optional=False
> >       feature feature1
> >           if IfAll(['defined(TEST_IF_COND_1)',
> 'defined(TEST_IF_COND_2)'])
> > +object CondFeatureStruct4
> > +    member foo: int optional=False
> > +    feature feature1
> > +        if IfAny(['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)'])
> >   enum FeatureEnum1
> >       member eins
> >       member zwei
> > @@ -417,6 +421,7 @@ object q_obj_test-features0-arg
> >       member cfs1: CondFeatureStruct1 optional=True
> >       member cfs2: CondFeatureStruct2 optional=True
> >       member cfs3: CondFeatureStruct3 optional=True
> > +    member cfs4: CondFeatureStruct4 optional=True
> >   command test-features0 q_obj_test-features0-arg -> FeatureStruct1
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> >   command test-command-features1 None -> None
> > @@ -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(['defined(TEST_IF_FEATURE_1)'])
> > +        if 'defined(TEST_IF_FEATURE_1)'
> >   command test-command-cond-features2 None -> None
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> >       feature feature1
> > -        if IfAll(['defined(TEST_IF_FEATURE_1)'])
> > +        if 'defined(TEST_IF_FEATURE_1)'
> >       feature feature2
> > -        if IfAll(['defined(TEST_IF_FEATURE_2)'])
> > +        if 'defined(TEST_IF_FEATURE_2)'
> >   command test-command-cond-features3 None -> None
> >       gen=True success_response=True boxed=False oob=False
> preconfig=False
> >       feature feature1
> > diff --git a/tests/qapi-schema/struct-member-if-invalid.err
> b/tests/qapi-schema/struct-member-if-invalid.err
> > index 42e7fdae3c..c18157c1f9 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, a list of strings or a dict
> >
>
>

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

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

* Re: [PATCH v3 7/9] qapi: convert 'if' C-expressions to the new syntax tree
  2021-05-12 23:51   ` John Snow
@ 2021-05-17 11:20     ` Marc-André Lureau
  0 siblings, 0 replies; 40+ messages in thread
From: Marc-André Lureau @ 2021-05-17 11:20 UTC (permalink / raw)
  To: John Snow; +Cc: qemu-devel, Armbruster, Markus

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

Hi

On Thu, May 13, 2021 at 3:51 AM John Snow <jsnow@redhat.com> wrote:

> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
> > From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >   qapi/machine-target.json | 20 ++++++++++++++++----
> >   qapi/misc-target.json    | 13 ++++++++++++-
> >   2 files changed, 28 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 0c7491cd82..2891df6890 100644
> > --- a/qapi/misc-target.json
> > +++ b/qapi/misc-target.json
> > @@ -23,7 +23,18 @@
> >   ##
> >   { '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_MOXIE) || 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_MOXIE)',
> > +                   'defined(TARGET_PPC)',
> > +                   'defined(TARGET_PPC64)',
> > +                   'defined(TARGET_S390X)',
> > +                   'defined(TARGET_SH4)',
> > +                   'defined(TARGET_SPARC)' ] } }
> >
> >   ##
> >   # @rtc-reset-reinjection:
> >
>
> I suppose these are necessary to convert because they were a single
> giant string, and not actually a list.
>
> If you're going to keep the list-of-strings sugar, is it worth just
> changing these to a list of strings?
>
> (How does your rust generator handle the defined(xxx) strings? does it
> strip the defined(...) off somehow?)
>

As you figured out, the next patch removes the C-ism (this is really the
purpose of this series)


>
> Tested-by: John Snow <jsnow@redhat.com>
>
> --js
>
>

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

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

* Re: [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond
  2021-05-17 11:17     ` Marc-André Lureau
@ 2021-05-17 16:30       ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-17 16:30 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel, Armbruster, Markus

On 5/17/21 7:17 AM, Marc-André Lureau wrote:
> Hi
> 
> On Thu, May 13, 2021 at 12:53 AM John Snow <jsnow@redhat.com 
> <mailto:jsnow@redhat.com>> wrote:
> 
>     On 4/29/21 9:40 AM, marcandre.lureau@redhat.com
>     <mailto:marcandre.lureau@redhat.com> wrote:
>      > From: Marc-André Lureau <marcandre.lureau@redhat.com
>     <mailto:marcandre.lureau@redhat.com>>
>      >
>      > Wrap the 'if' condition in a higher-level object. Not only this
>     allows
>      > more type safety but also further refactoring without too much churn.
>      >
> 
>     Would have done it myself if I had gotten to it first. I like having a
>     named type for this, it matches the other properties we have.
> 
>      > 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
>     <mailto:marcandre.lureau@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     | 78
>     +++++++++++++++++++++++++++-----------
>      >   scripts/qapi/types.py      | 33 ++++++++--------
>      >   scripts/qapi/visit.py      | 23 +++++------
>      >   8 files changed, 110 insertions(+), 75 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 <http://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 3a4172fb74..7d6f390fa6 100644
>      > --- a/scripts/qapi/schema.py
>      > +++ b/scripts/qapi/schema.py
>      > @@ -25,6 +25,22 @@
>      >   from .parser import QAPISchemaParser
>      >
>      >
>      > +class QAPISchemaIfCond:
>      > +    def __init__(self, ifcond=None):
>      > +        self.ifcond = ifcond or []
>      > +
>      > +    def __bool__(self):
>      > +        return bool(self.ifcond)
>      > +
>      > +    def __repr__(self):
>      > +        return repr(self.ifcond)
>      > +
> 
>     I suggest using:
> 
>     f"QAPISchemaIfCond({self.ifcond!r})"
> 
>     unless future patches make that weirder. It's nice when repr() returns
>     something you can use to make a new, equivalent object.
> 
>     eval(repr(x)) == x
> 
> 
> I implemented it this way for compatibility with test-qapi output. But 
> it is simpler to actually no implement __repr__ and just change the test.
> 

I like having the __repr__ methods personally, but if it's for test 
output purposes, probably best to change the test, yeah.

You can use __str__ instead and have the tests print str(node) or 
{node!s} without worrying about __repr__ hygiene.

>      > +    def __eq__(self, other):
>      > +        if not isinstance(other, QAPISchemaIfCond):
>      > +            return NotImplemented
>      > +        return self.ifcond == other.ifcond
>      > +
>      > +
> 
>     Missing annotations, but ... yeah, schema.py isn't typed yet so I will
>     handle it myself in pt6. No biggie.
> 
> 
> There used to be annotations all over in earlier versions, but I rebased 
> and had to drop most of them.
> 

Yep, don't worry about it for now. I'll get to it.



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

* Re: [PATCH v3 3/9] qapi: start building an 'if' predicate tree
  2021-05-17 11:18     ` Marc-André Lureau
@ 2021-05-17 16:34       ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-17 16:34 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel, Armbruster, Markus

On 5/17/21 7:18 AM, Marc-André Lureau wrote:
> That's also what I thought (it was in my commit message comments). 

I did read them, I promise :)

> Repeating myself, I'd defer this, there is no urge to make the code more 
> complex yet. It can easily be done in a following iteration.

I think I can accept this, as long as we don't create a lot of work for 
ourselves splitting it back out later.

I think it'll be up to Markus, ultimately.

--js



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

* Re: [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny
  2021-05-17 11:18     ` Marc-André Lureau
@ 2021-05-17 16:35       ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-17 16:35 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel, Armbruster, Markus

On 5/17/21 7:18 AM, Marc-André Lureau wrote:
> No, just the sake of doing things iteratively.

ACK, Understood!

--js



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

* Re: [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree
  2021-05-17 11:18     ` Marc-André Lureau
@ 2021-05-17 16:41       ` John Snow
  0 siblings, 0 replies; 40+ messages in thread
From: John Snow @ 2021-05-17 16:41 UTC (permalink / raw)
  To: Marc-André Lureau; +Cc: qemu-devel, Armbruster, Markus

On 5/17/21 7:18 AM, Marc-André Lureau wrote:
> 
> 
>     'if': 'COND'
>     'if': ['COND']
>     'if': { 'any': ['COND'] }
> 
> 
> Actually, a simple list is short form for { 'all': [] }

Typo on my part. It maintains compatibility with what it used to mean, 
so it's good.

I wonder if we want *three* forms available directly in the schema. Can 
we condense it back down to two somehow? Could we possibly remove the 
list-less form?

(The less forms we have, the easier it is to offer e.g. intellisense 
plugins for vscode and things of that nature, which is something I have 
an interest in to improve the usability of the generator for 
contributors who are less invested in the QAPI subsystem.)

--js



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

* Re: [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond
  2021-05-12 21:01   ` John Snow
@ 2021-05-21 14:35     ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2021-05-21 14:35 UTC (permalink / raw)
  To: John Snow; +Cc: marcandre.lureau, qemu-devel

John Snow <jsnow@redhat.com> writes:

> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>> Move the generating function to the QAPISchemaIfCond class.
>> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

[...]

> Tested-by: John Snow <jsnow@redhat.com>
>
> Seems fine, though I'm a lot less sure of baking the C-specific stuff
> right into the class -- I want a bit more distance to the C output 
> instead of less.
>
> (Though I do admit that I'm quite fond of centralizing things into
> classes like this myself. I think if we want to add Rust, Go, Python
> and other generators that it won't scale the way we want it to.)

I share this concern.

In a compiler, we commonly have one or more intermediate
representations, and several separate tasks that work on an IR.

The classical organisation is to define a "thin" IR data type, and have
the tasks work on that.

If you are (or your implementation language is) religious about OO, keep
a task separate gets a bit more complicated.  The visitor pattern is a
common solution.  The temptation to just put it in the IR class and call
it a day is always there, of course.

schema.py is my attempt at writing compiler in an OO way.  It's not 100%
pure.  For instance, there is QAPISchemaEntity.c_name().  Note that most
of the actual work is done *outside* schema.py, in function c_name().
The separation could certainly be cleaner.  But this is simple, and it
feels tolerably clean to me.

However, such things do add up.  At some point, we need to stop, think,
and maybe refactor.  I haven't looked at the patch, so I can't say
whether this patch gets us to this point already.



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

* Re: [PATCH v3 3/9] qapi: start building an 'if' predicate tree
  2021-05-12 21:39   ` John Snow
  2021-05-17 11:18     ` Marc-André Lureau
@ 2021-05-21 14:48     ` Markus Armbruster
  2021-05-21 16:18       ` John Snow
  1 sibling, 1 reply; 40+ messages in thread
From: Markus Armbruster @ 2021-05-21 14:48 UTC (permalink / raw)
  To: John Snow; +Cc: marcandre.lureau, qemu-devel

Beware, I'm skimming, not really reviewing.

John Snow <jsnow@redhat.com> writes:

> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
>> 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>
>> ---
>>   docs/sphinx/qapidoc.py                 |  6 +--
>>   scripts/qapi/common.py                 | 54 +++++++++++++++++++++++-
>>   scripts/qapi/schema.py                 | 42 ++++++++++++-------
>>   tests/qapi-schema/doc-good.out         | 12 +++---
>>   tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
>>   5 files changed, 116 insertions(+), 56 deletions(-)
>> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>> index b737949007..a93f3f1c4d 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(', '))
>
> was this the last user of intersperse?
>
> mm, no, there's one more.
>
>> +        condlist = [nodes.literal('', ifcond.gen_doc())]
>>           if not with_if:
>>               return condlist
>>   diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
>> index b7f475a160..59a7ee2f32 100644
>> --- a/scripts/qapi/common.py
>> +++ b/scripts/qapi/common.py
>> @@ -11,8 +11,9 @@
>>   # This work is licensed under the terms of the GNU GPL, version 2.
>>   # See the COPYING file in the top-level directory.
>>   +from abc import ABC, abstractmethod
>>   import re
>> -from typing import Optional
>> +from typing import Optional, Sequence
>>     
>>   #: Magic string that gets removed along with all space to its right.
>> @@ -192,3 +193,54 @@ def guardend(name: str) -> str:
>>   #endif /* %(name)s */
>>   ''',
>>                    name=c_fname(name).upper())
>> +
>> +
>> +class IfPredicate(ABC):
>> +    @abstractmethod
>> +    def cgen(self) -> str:
>
> Like the review to patch 2, I'm not sure we want to bake cgen stuff
> directly into this class. Are you going to have cgen() and rustgen() 
> methods all here?
>
>> +        pass
>> +
>
> I think you want raise NotImplementedError to specify a function that
> the inheriting class MUST implement. Otherwise, there's little value
> to allow a child class to call super() on a method that doesn't have a 
> default implementation.
>
> You *could* drop the (ABC) and the @abstractmethod decorators if you do so.
>
> Matters of taste and style.

We're not coding a library for use by thousands of people.  If we did,
then complicating the code to guard against misuse could be a win.  But
we don't.

schema.py is full of methods that pass.  Maybe some of them need to be
overriden by any conceivable subtype.  Who cares?  The subtypes either
work or they don't.  I prefer

    def frobnicate:
        pass

to

    def frobnicate:
        raise NotImplementedError

One, pass is easier on the eyes.  Two, a subtype's .frobnicate() can
blindly call super().frobnicate().

I don't see a need to fuzz around with module abc, either.  Let's stick
to the stupidest solution that could possibly work.

[...]



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

* Re: [PATCH v3 3/9] qapi: start building an 'if' predicate tree
  2021-05-21 14:48     ` Markus Armbruster
@ 2021-05-21 16:18       ` John Snow
  2021-06-08 13:57         ` Markus Armbruster
  0 siblings, 1 reply; 40+ messages in thread
From: John Snow @ 2021-05-21 16:18 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: marcandre.lureau, qemu-devel

On 5/21/21 10:48 AM, Markus Armbruster wrote:
> Beware, I'm skimming, not really reviewing.
> 
> John Snow <jsnow@redhat.com> writes:
> 
>> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:
>>> 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>
>>> ---
>>>    docs/sphinx/qapidoc.py                 |  6 +--
>>>    scripts/qapi/common.py                 | 54 +++++++++++++++++++++++-
>>>    scripts/qapi/schema.py                 | 42 ++++++++++++-------
>>>    tests/qapi-schema/doc-good.out         | 12 +++---
>>>    tests/qapi-schema/qapi-schema-test.out | 58 +++++++++++++-------------
>>>    5 files changed, 116 insertions(+), 56 deletions(-)
>>> diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
>>> index b737949007..a93f3f1c4d 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(', '))
>>
>> was this the last user of intersperse?
>>
>> mm, no, there's one more.
>>
>>> +        condlist = [nodes.literal('', ifcond.gen_doc())]
>>>            if not with_if:
>>>                return condlist
>>>    diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
>>> index b7f475a160..59a7ee2f32 100644
>>> --- a/scripts/qapi/common.py
>>> +++ b/scripts/qapi/common.py
>>> @@ -11,8 +11,9 @@
>>>    # This work is licensed under the terms of the GNU GPL, version 2.
>>>    # See the COPYING file in the top-level directory.
>>>    +from abc import ABC, abstractmethod
>>>    import re
>>> -from typing import Optional
>>> +from typing import Optional, Sequence
>>>      
>>>    #: Magic string that gets removed along with all space to its right.
>>> @@ -192,3 +193,54 @@ def guardend(name: str) -> str:
>>>    #endif /* %(name)s */
>>>    ''',
>>>                     name=c_fname(name).upper())
>>> +
>>> +
>>> +class IfPredicate(ABC):
>>> +    @abstractmethod
>>> +    def cgen(self) -> str:
>>
>> Like the review to patch 2, I'm not sure we want to bake cgen stuff
>> directly into this class. Are you going to have cgen() and rustgen()
>> methods all here?
>>
>>> +        pass
>>> +
>>
>> I think you want raise NotImplementedError to specify a function that
>> the inheriting class MUST implement. Otherwise, there's little value
>> to allow a child class to call super() on a method that doesn't have a
>> default implementation.
>>
>> You *could* drop the (ABC) and the @abstractmethod decorators if you do so.
>>
>> Matters of taste and style.
> 
> We're not coding a library for use by thousands of people.  If we did,
> then complicating the code to guard against misuse could be a win.  But
> we don't.
> 
> schema.py is full of methods that pass.  Maybe some of them need to be
> overriden by any conceivable subtype.  Who cares?  The subtypes either
> work or they don't.  I prefer
> 
>      def frobnicate:
>          pass
> 
> to
> 
>      def frobnicate:
>          raise NotImplementedError
> 
> One, pass is easier on the eyes.  Two, a subtype's .frobnicate() can
> blindly call super().frobnicate().
> 

"pass" here operates as a syntactic sugar for "return None" which has 
implications on the return type. It's short, but wrong.

raise NotImplementedError means something different semantically.

To me, pass is *NOT* easier on the eyes, it is misleading. It is 
idiomatic to use NotImplementedError if it is not acceptable for a 
default implementation to return None.

I understand perfectly well the desire to keep things simple, but what 
is actually "simple" depends on the expectations of the programmer. I 
err towards idiomatic Python.

> I don't see a need to fuzz around with module abc, either.  Let's stick
> to the stupidest solution that could possibly work.
> 
> [...]
> 



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

* Re: [PATCH v3 3/9] qapi: start building an 'if' predicate tree
  2021-05-21 16:18       ` John Snow
@ 2021-06-08 13:57         ` Markus Armbruster
  0 siblings, 0 replies; 40+ messages in thread
From: Markus Armbruster @ 2021-06-08 13:57 UTC (permalink / raw)
  To: John Snow; +Cc: marcandre.lureau, qemu-devel

John Snow <jsnow@redhat.com> writes:

> On 5/21/21 10:48 AM, Markus Armbruster wrote:
>> Beware, I'm skimming, not really reviewing.
>> 
>> John Snow <jsnow@redhat.com> writes:
>> 
>>> On 4/29/21 9:40 AM, marcandre.lureau@redhat.com wrote:

[...]

>>>> index b7f475a160..59a7ee2f32 100644
>>>> --- a/scripts/qapi/common.py
>>>> +++ b/scripts/qapi/common.py
>>>> @@ -11,8 +11,9 @@
>>>>    # This work is licensed under the terms of the GNU GPL, version 2.
>>>>    # See the COPYING file in the top-level directory.
>>>>    +from abc import ABC, abstractmethod
>>>>    import re
>>>> -from typing import Optional
>>>> +from typing import Optional, Sequence
>>>>      
>>>>    #: Magic string that gets removed along with all space to its right.
>>>> @@ -192,3 +193,54 @@ def guardend(name: str) -> str:
>>>>    #endif /* %(name)s */
>>>>    ''',
>>>>                     name=c_fname(name).upper())
>>>> +
>>>> +
>>>> +class IfPredicate(ABC):
>>>> +    @abstractmethod
>>>> +    def cgen(self) -> str:
>>>
>>> Like the review to patch 2, I'm not sure we want to bake cgen stuff
>>> directly into this class. Are you going to have cgen() and rustgen()
>>> methods all here?
>>>
>>>> +        pass
>>>> +
>>>
>>> I think you want raise NotImplementedError to specify a function that
>>> the inheriting class MUST implement. Otherwise, there's little value
>>> to allow a child class to call super() on a method that doesn't have a
>>> default implementation.
>>>
>>> You *could* drop the (ABC) and the @abstractmethod decorators if you do so.
>>>
>>> Matters of taste and style.
>> 
>> We're not coding a library for use by thousands of people.  If we did,
>> then complicating the code to guard against misuse could be a win.  But
>> we don't.
>> 
>> schema.py is full of methods that pass.  Maybe some of them need to be
>> overriden by any conceivable subtype.  Who cares?  The subtypes either
>> work or they don't.  I prefer
>> 
>>      def frobnicate:
>>          pass
>> 
>> to
>> 
>>      def frobnicate:
>>          raise NotImplementedError
>> 
>> One, pass is easier on the eyes.  Two, a subtype's .frobnicate() can
>> blindly call super().frobnicate().
>> 
>
> "pass" here operates as a syntactic sugar for "return None" which has 
> implications on the return type. It's short, but wrong.

Nitpicking...

pass is not specified as syntactic sugar:

    7.4. The pass statement

    pass_stmt ::=  "pass"

    pass is a null operation — when it is executed, nothing happens. It is useful as a placeholder when a statement is required syntactically, but no code needs to be executed, for example:

    def f(arg): pass    # a function that does nothing (yet)

    class C: pass       # a class with no methods (yet)

What really happens in

    def foo(): pass

is what *always* happens when control reaches the end of the function
body: the function returns None.

Further evidence:

    >>> def baz():
    ...     pass
    ...     return 42
    ... 
    >>> baz()
    42

> raise NotImplementedError means something different semantically.
>
> To me, pass is *NOT* easier on the eyes, it is misleading. It is 
> idiomatic to use NotImplementedError if it is not acceptable for a 
> default implementation to return None.
>
> I understand perfectly well the desire to keep things simple, but what 
> is actually "simple" depends on the expectations of the programmer. I 
> err towards idiomatic Python.

What's your take on "Two, a subtype's .frobnicate() can blindly call
super().frobnicate()"?

[...]



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

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

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-29 13:40 [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor marcandre.lureau
2021-04-29 13:40 ` [PATCH v3 1/9] qapi: replace List[str] by QAPISchemaIfCond marcandre.lureau
2021-05-12 20:53   ` John Snow
2021-05-17 11:17     ` Marc-André Lureau
2021-05-17 16:30       ` John Snow
2021-04-29 13:40 ` [PATCH v3 2/9] qapi: move gen_if/gen_endif to QAPISchemaIfCond marcandre.lureau
2021-05-11 16:39   ` Stefan Hajnoczi
2021-05-12 12:36     ` Marc-André Lureau
2021-05-12 18:53     ` John Snow
2021-05-17 11:18       ` Marc-André Lureau
2021-05-12 21:01   ` John Snow
2021-05-21 14:35     ` Markus Armbruster
2021-04-29 13:40 ` [PATCH v3 3/9] qapi: start building an 'if' predicate tree marcandre.lureau
2021-05-12 21:39   ` John Snow
2021-05-17 11:18     ` Marc-André Lureau
2021-05-17 16:34       ` John Snow
2021-05-21 14:48     ` Markus Armbruster
2021-05-21 16:18       ` John Snow
2021-06-08 13:57         ` Markus Armbruster
2021-04-29 13:40 ` [PATCH v3 4/9] qapi: introduce IfPredicateList and IfAny marcandre.lureau
2021-05-12 23:26   ` John Snow
2021-05-17 11:18     ` Marc-André Lureau
2021-05-17 16:35       ` John Snow
2021-04-29 13:40 ` [PATCH v3 5/9] qapi: add IfNot marcandre.lureau
2021-05-12 23:32   ` John Snow
2021-04-29 13:40 ` [PATCH v3 6/9] qapi: normalize 'if' condition to IfPredicate tree marcandre.lureau
2021-05-12 23:47   ` John Snow
2021-05-17 11:18     ` Marc-André Lureau
2021-05-17 16:41       ` John Snow
2021-04-29 13:40 ` [PATCH v3 7/9] qapi: convert 'if' C-expressions to the new syntax tree marcandre.lureau
2021-05-12 23:51   ` John Snow
2021-05-17 11:20     ` Marc-André Lureau
2021-04-29 13:40 ` [PATCH v3 8/9] qapi: make 'if' condition strings simple identifiers marcandre.lureau
2021-05-12 23:56   ` John Snow
2021-04-29 13:40 ` [PATCH v3 9/9] docs: update the documentation about schema configuration marcandre.lureau
2021-05-13  0:01   ` John Snow
2021-05-11 16:52 ` [PATCH v3 0/9] qapi: untie 'if' conditions from C preprocessor Stefan Hajnoczi
2021-05-12 12:39   ` Marc-André Lureau
2021-05-12 17:43     ` Markus Armbruster
2021-05-12 18:58       ` John Snow

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).