<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Oct 25, 2021 at 12:24 AM Markus Armbruster <<a href="mailto:armbru@redhat.com">armbru@redhat.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">This is quite similar to commit 84ab008687 "qapi: Add feature flags to<br>
struct members", only for enums instead of structs.<br>
<br>
Special feature flag 'deprecated' is silently ignored there.  This is<br>
okay only because it will be implemented shortly.<br>
<br>
Signed-off-by: Markus Armbruster <<a href="mailto:armbru@redhat.com" target="_blank">armbru@redhat.com</a>><br>
Reviewed-by: Eric Blake <<a href="mailto:eblake@redhat.com" target="_blank">eblake@redhat.com</a>><br></blockquote><div><br></div><div>Reviewed-by: John Snow <<a href="mailto:jsnow@redhat.com">jsnow@redhat.com</a>><br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
---<br>
 docs/devel/qapi-code-gen.rst                  | 16 +++++++++-----<br>
 qapi/compat.json                              |  2 ++<br>
 qapi/introspect.json                          |  5 ++++-<br>
 scripts/qapi/expr.py                          |  3 ++-<br>
 scripts/qapi/introspect.py                    |  5 +++--<br>
 scripts/qapi/schema.py                        | 22 +++++++++++++++++--<br>
 tests/qapi-schema/doc-good.json               |  5 ++++-<br>
 tests/qapi-schema/doc-good.out                |  3 +++<br>
 tests/qapi-schema/doc-good.txt                |  3 +++<br>
 .../qapi-schema/enum-dict-member-unknown.err  |  2 +-<br>
 tests/qapi-schema/qapi-schema-test.json       |  3 ++-<br>
 tests/qapi-schema/qapi-schema-test.out        |  1 +<br>
 tests/qapi-schema/test-qapi.py                |  1 +<br>
 13 files changed, 57 insertions(+), 14 deletions(-)<br>
<br>
diff --git a/docs/devel/qapi-code-gen.rst b/docs/devel/qapi-code-gen.rst<br>
index d267889d2c..4071c9074a 100644<br>
--- a/docs/devel/qapi-code-gen.rst<br>
+++ b/docs/devel/qapi-code-gen.rst<br>
@@ -200,7 +200,9 @@ Syntax::<br>
              '*if': COND,<br>
              '*features': FEATURES }<br>
     ENUM-VALUE = STRING<br>
-               | { 'name': STRING, '*if': COND }<br>
+               | { 'name': STRING,<br>
+                   '*if': COND,<br>
+                   '*features': FEATURES }<br>
<br>
 Member 'enum' names the enum type.<br>
<br>
@@ -706,8 +708,10 @@ QEMU shows a certain behaviour.<br>
 Special features<br>
 ~~~~~~~~~~~~~~~~<br>
<br>
-Feature "deprecated" marks a command, event, or struct member as<br>
-deprecated.  It is not supported elsewhere so far.<br>
+Feature "deprecated" marks a command, event, enum value, or struct<br>
+member as deprecated.  It is not supported elsewhere so far.<br>
+Interfaces so marked may be withdrawn in future releases in accordance<br>
+with QEMU's deprecation policy.<br>
<br>
<br>
 Naming rules and reserved names<br>
@@ -1157,7 +1161,8 @@ and "variants".<br>
<br>
 "members" is a JSON array describing the object's common members, if<br>
 any.  Each element is a JSON object with members "name" (the member's<br>
-name), "type" (the name of its type), and optionally "default".  The<br>
+name), "type" (the name of its type), "features" (a JSON array of<br>
+feature strings), and "default".  The latter two are optional.  The<br>
 member is optional if "default" is present.  Currently, "default" can<br>
 only have value null.  Other values are reserved for future<br>
 extensions.  The "members" array is in no particular order; clients<br>
@@ -1234,7 +1239,8 @@ The SchemaInfo for an enumeration type has meta-type "enum" and<br>
 variant member "members".<br>
<br>
 "members" is a JSON array describing the enumeration values.  Each<br>
-element is a JSON object with member "name" (the member's name).  The<br>
+element is a JSON object with member "name" (the member's name), and<br>
+optionally "features" (a JSON array of feature strings).  The<br>
 "members" array is in no particular order; clients must search the<br>
 entire array when learning whether a particular value is supported.<br>
<br>
diff --git a/qapi/compat.json b/qapi/compat.json<br>
index ae3afc22df..1d2b76f00c 100644<br>
--- a/qapi/compat.json<br>
+++ b/qapi/compat.json<br>
@@ -42,6 +42,8 @@<br>
 # with feature 'deprecated'.  We may want to extend it to cover<br>
 # semantic aspects, CLI, and experimental features.<br>
 #<br>
+# Limitation: not implemented for deprecated enumeration values.<br>
+#<br>
 # @deprecated-input: how to handle deprecated input (default 'accept')<br>
 # @deprecated-output: how to handle deprecated output (default 'accept')<br>
 #<br>
diff --git a/qapi/introspect.json b/qapi/introspect.json<br>
index 9683e884f8..183148b2e9 100644<br>
--- a/qapi/introspect.json<br>
+++ b/qapi/introspect.json<br>
@@ -167,10 +167,13 @@<br>
 #<br>
 # @name: the member's name, as defined in the QAPI schema.<br>
 #<br>
+# @features: names of features associated with the member, in no<br>
+#            particular order.<br>
+#<br>
 # Since: 6.2<br>
 ##<br>
 { 'struct': 'SchemaInfoEnumMember',<br>
-  'data': { 'name': 'str' } }<br>
+  'data': { 'name': 'str', '*features': [ 'str' ] } }<br>
<br>
 ##<br>
 # @SchemaInfoArray:<br>
diff --git a/scripts/qapi/expr.py b/scripts/qapi/expr.py<br>
index 819ea6ad97..3cb389e875 100644<br>
--- a/scripts/qapi/expr.py<br>
+++ b/scripts/qapi/expr.py<br>
@@ -472,7 +472,7 @@ def check_enum(expr: _JSONObject, info: QAPISourceInfo) -> None:<br>
                   for m in members]<br>
     for member in members:<br>
         source = "'data' member"<br>
-        check_keys(member, info, source, ['name'], ['if'])<br>
+        check_keys(member, info, source, ['name'], ['if', 'features'])<br>
         member_name = member['name']<br>
         check_name_is_str(member_name, info, source)<br>
         source = "%s '%s'" % (source, member_name)<br>
@@ -483,6 +483,7 @@ def check_enum(expr: _JSONObject, info: QAPISourceInfo) -> None:<br>
                          permit_upper=permissive,<br>
                          permit_underscore=permissive)<br>
         check_if(member, info, source)<br>
+        check_features(member.get('features'), info)<br>
<br>
<br>
 def check_struct(expr: _JSONObject, info: QAPISourceInfo) -> None:<br>
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py<br>
index 6334546363..67c7d89aae 100644<br>
--- a/scripts/qapi/introspect.py<br>
+++ b/scripts/qapi/introspect.py<br>
@@ -275,12 +275,13 @@ def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object],<br>
             obj['features'] = self._gen_features(features)<br>
         self._trees.append(Annotated(obj, ifcond, comment))<br>
<br>
-    @staticmethod<br>
-    def _gen_enum_member(member: QAPISchemaEnumMember<br>
+    def _gen_enum_member(self, member: QAPISchemaEnumMember<br>
                          ) -> Annotated[SchemaInfoEnumMember]:<br>
         obj: SchemaInfoEnumMember = {<br>
             'name': <a href="http://member.name" rel="noreferrer" target="_blank">member.name</a>,<br>
         }<br>
+        if member.features:<br>
+            obj['features'] = self._gen_features(member.features)<br>
         return Annotated(obj, member.ifcond)<br>
<br>
     def _gen_object_member(self, member: QAPISchemaObjectTypeMember<br>
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py<br>
index 004d7095ff..6d5f46509a 100644<br>
--- a/scripts/qapi/schema.py<br>
+++ b/scripts/qapi/schema.py<br>
@@ -708,6 +708,19 @@ def describe(self, info):<br>
 class QAPISchemaEnumMember(QAPISchemaMember):<br>
     role = 'value'<br>
<br>
+    def __init__(self, name, info, ifcond=None, features=None):<br>
+        super().__init__(name, info, ifcond)<br>
+        for f in features or []:<br>
+            assert isinstance(f, QAPISchemaFeature)<br>
+            f.set_defined_in(name)<br>
+        self.features = features or []<br>
+<br>
+    def connect_doc(self, doc):<br>
+        super().connect_doc(doc)<br>
+        if doc:<br>
+            for f in self.features:<br>
+                doc.connect_feature(f)<br>
+<br>
<br>
 class QAPISchemaFeature(QAPISchemaMember):<br>
     role = 'feature'<br>
@@ -980,9 +993,14 @@ def _make_features(self, features, info):<br>
                                   QAPISchemaIfCond(f.get('if')))<br>
                 for f in features]<br>
<br>
+    def _make_enum_member(self, name, ifcond, features, info):<br>
+        return QAPISchemaEnumMember(name, info,<br>
+                                    QAPISchemaIfCond(ifcond),<br>
+                                    self._make_features(features, info))<br>
+<br>
     def _make_enum_members(self, values, info):<br>
-        return [QAPISchemaEnumMember(v['name'], info,<br>
-                                     QAPISchemaIfCond(v.get('if')))<br>
+        return [self._make_enum_member(v['name'], v.get('if'),<br>
+                                       v.get('features'), info)<br>
                 for v in values]<br>
<br>
     def _make_array_type(self, element_type, info):<br>
diff --git a/tests/qapi-schema/doc-good.json b/tests/qapi-schema/doc-good.json<br>
index 86dc25d2bd..74745fb405 100644<br>
--- a/tests/qapi-schema/doc-good.json<br>
+++ b/tests/qapi-schema/doc-good.json<br>
@@ -58,11 +58,14 @@<br>
 #<br>
 # Features:<br>
 # @enum-feat: Also _one_ {and only}<br>
+# @enum-member-feat: a member feature<br>
 #<br>
 # @two is undocumented<br>
 ##<br>
 { 'enum': 'Enum',<br>
-  'data': [ { 'name': 'one', 'if': 'IFONE' }, 'two' ],<br>
+  'data': [ { 'name': 'one', 'if': 'IFONE',<br>
+              'features': [ 'enum-member-feat' ] },<br>
+            'two' ],<br>
   'features': [ 'enum-feat' ],<br>
   'if': 'IFCOND' }<br>
<br>
diff --git a/tests/qapi-schema/doc-good.out b/tests/qapi-schema/doc-good.out<br>
index 5a324e2627..9dd65b9d92 100644<br>
--- a/tests/qapi-schema/doc-good.out<br>
+++ b/tests/qapi-schema/doc-good.out<br>
@@ -13,6 +13,7 @@ module doc-good.json<br>
 enum Enum<br>
     member one<br>
         if IFONE<br>
+        feature enum-member-feat<br>
     member two<br>
     if IFCOND<br>
     feature enum-feat<br>
@@ -108,6 +109,8 @@ The _one_ {and only}<br>
<br>
     feature=enum-feat<br>
 Also _one_ {and only}<br>
+    feature=enum-member-feat<br>
+a member feature<br>
     section=None<br>
 @two is undocumented<br>
 doc symbol=Base<br>
diff --git a/tests/qapi-schema/doc-good.txt b/tests/qapi-schema/doc-good.txt<br>
index 701402ee5e..b3b76bd43f 100644<br>
--- a/tests/qapi-schema/doc-good.txt<br>
+++ b/tests/qapi-schema/doc-good.txt<br>
@@ -56,6 +56,9 @@ Features<br>
 "enum-feat"<br>
    Also _one_ {and only}<br>
<br>
+"enum-member-feat"<br>
+   a member feature<br>
+<br>
 "two" is undocumented<br>
<br>
<br>
diff --git a/tests/qapi-schema/enum-dict-member-unknown.err b/tests/qapi-schema/enum-dict-member-unknown.err<br>
index f8617ea179..235cde0c49 100644<br>
--- a/tests/qapi-schema/enum-dict-member-unknown.err<br>
+++ b/tests/qapi-schema/enum-dict-member-unknown.err<br>
@@ -1,3 +1,3 @@<br>
 enum-dict-member-unknown.json: In enum 'MyEnum':<br>
 enum-dict-member-unknown.json:2: 'data' member has unknown key 'bad-key'<br>
-Valid keys are 'if', 'name'.<br>
+Valid keys are 'features', 'if', 'name'.<br>
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json<br>
index 2ec50109cb..b677ab861d 100644<br>
--- a/tests/qapi-schema/qapi-schema-test.json<br>
+++ b/tests/qapi-schema/qapi-schema-test.json<br>
@@ -301,7 +301,8 @@<br>
                                  'TEST_IF_COND_2'] } } ] }<br>
<br>
 { 'enum': 'FeatureEnum1',<br>
-  'data': [ 'eins', 'zwei', 'drei' ],<br>
+  'data': [ 'eins', 'zwei',<br>
+            { 'name': 'drei', 'features': [ 'deprecated' ] } ],<br>
   'features': [ 'feature1' ] }<br>
<br>
 { 'union': 'FeatureUnion1',<br>
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out<br>
index 9337adc9ea..16846dbeb8 100644<br>
--- a/tests/qapi-schema/qapi-schema-test.out<br>
+++ b/tests/qapi-schema/qapi-schema-test.out<br>
@@ -341,6 +341,7 @@ enum FeatureEnum1<br>
     member eins<br>
     member zwei<br>
     member drei<br>
+        feature deprecated<br>
     feature feature1<br>
 object q_obj_FeatureUnion1-base<br>
     member tag: FeatureEnum1 optional=False<br>
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py<br>
index c717a7a90b..2160cef082 100755<br>
--- a/tests/qapi-schema/test-qapi.py<br>
+++ b/tests/qapi-schema/test-qapi.py<br>
@@ -37,6 +37,7 @@ def visit_enum_type(self, name, info, ifcond, features, members, prefix):<br>
         for m in members:<br>
             print('    member %s' % <a href="http://m.name" rel="noreferrer" target="_blank">m.name</a>)<br>
             self._print_if(m.ifcond, indent=8)<br>
+            self._print_features(m.features, indent=8)<br>
         self._print_if(ifcond)<br>
         self._print_features(features)<br>
<br>
-- <br>
2.31.1<br>
<br>
</blockquote></div></div>