Browse Source

qapi: Add feature flags to enum members

This is quite similar to commit 84ab008687 "qapi: Add feature flags to
struct members", only for enums instead of structs.

Special feature flag 'deprecated' is silently ignored there.  This is
okay only because it will be implemented shortly.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-Id: <20211025042405.3762351-3-armbru@redhat.com>
Reviewed-by: John Snow <jsnow@redhat.com>
Markus Armbruster 3 years ago
parent
commit
b6c18755e4

+ 11 - 5
docs/devel/qapi-code-gen.rst

@@ -200,7 +200,9 @@ Syntax::
              '*if': COND,
              '*if': COND,
              '*features': FEATURES }
              '*features': FEATURES }
     ENUM-VALUE = STRING
     ENUM-VALUE = STRING
-               | { 'name': STRING, '*if': COND }
+               | { 'name': STRING,
+                   '*if': COND,
+                   '*features': FEATURES }
 
 
 Member 'enum' names the enum type.
 Member 'enum' names the enum type.
 
 
@@ -706,8 +708,10 @@ QEMU shows a certain behaviour.
 Special features
 Special features
 ~~~~~~~~~~~~~~~~
 ~~~~~~~~~~~~~~~~
 
 
-Feature "deprecated" marks a command, event, or struct member as
-deprecated.  It is not supported elsewhere so far.
+Feature "deprecated" marks a command, event, enum value, or struct
+member as deprecated.  It is not supported elsewhere so far.
+Interfaces so marked may be withdrawn in future releases in accordance
+with QEMU's deprecation policy.
 
 
 
 
 Naming rules and reserved names
 Naming rules and reserved names
@@ -1157,7 +1161,8 @@ and "variants".
 
 
 "members" is a JSON array describing the object's common members, if
 "members" is a JSON array describing the object's common members, if
 any.  Each element is a JSON object with members "name" (the member's
 any.  Each element is a JSON object with members "name" (the member's
-name), "type" (the name of its type), and optionally "default".  The
+name), "type" (the name of its type), "features" (a JSON array of
+feature strings), and "default".  The latter two are optional.  The
 member is optional if "default" is present.  Currently, "default" can
 member is optional if "default" is present.  Currently, "default" can
 only have value null.  Other values are reserved for future
 only have value null.  Other values are reserved for future
 extensions.  The "members" array is in no particular order; clients
 extensions.  The "members" array is in no particular order; clients
@@ -1234,7 +1239,8 @@ The SchemaInfo for an enumeration type has meta-type "enum" and
 variant member "members".
 variant member "members".
 
 
 "members" is a JSON array describing the enumeration values.  Each
 "members" is a JSON array describing the enumeration values.  Each
-element is a JSON object with member "name" (the member's name).  The
+element is a JSON object with member "name" (the member's name), and
+optionally "features" (a JSON array of feature strings).  The
 "members" array is in no particular order; clients must search the
 "members" array is in no particular order; clients must search the
 entire array when learning whether a particular value is supported.
 entire array when learning whether a particular value is supported.
 
 

+ 2 - 0
qapi/compat.json

@@ -42,6 +42,8 @@
 # with feature 'deprecated'.  We may want to extend it to cover
 # with feature 'deprecated'.  We may want to extend it to cover
 # semantic aspects, CLI, and experimental features.
 # semantic aspects, CLI, and experimental features.
 #
 #
+# Limitation: not implemented for deprecated enumeration values.
+#
 # @deprecated-input: how to handle deprecated input (default 'accept')
 # @deprecated-input: how to handle deprecated input (default 'accept')
 # @deprecated-output: how to handle deprecated output (default 'accept')
 # @deprecated-output: how to handle deprecated output (default 'accept')
 #
 #

+ 4 - 1
qapi/introspect.json

@@ -167,10 +167,13 @@
 #
 #
 # @name: the member's name, as defined in the QAPI schema.
 # @name: the member's name, as defined in the QAPI schema.
 #
 #
+# @features: names of features associated with the member, in no
+#            particular order.
+#
 # Since: 6.2
 # Since: 6.2
 ##
 ##
 { 'struct': 'SchemaInfoEnumMember',
 { 'struct': 'SchemaInfoEnumMember',
-  'data': { 'name': 'str' } }
+  'data': { 'name': 'str', '*features': [ 'str' ] } }
 
 
 ##
 ##
 # @SchemaInfoArray:
 # @SchemaInfoArray:

+ 2 - 1
scripts/qapi/expr.py

@@ -472,7 +472,7 @@ def check_enum(expr: _JSONObject, info: QAPISourceInfo) -> None:
                   for m in members]
                   for m in members]
     for member in members:
     for member in members:
         source = "'data' member"
         source = "'data' member"
-        check_keys(member, info, source, ['name'], ['if'])
+        check_keys(member, info, source, ['name'], ['if', 'features'])
         member_name = member['name']
         member_name = member['name']
         check_name_is_str(member_name, info, source)
         check_name_is_str(member_name, info, source)
         source = "%s '%s'" % (source, member_name)
         source = "%s '%s'" % (source, member_name)
@@ -483,6 +483,7 @@ def check_enum(expr: _JSONObject, info: QAPISourceInfo) -> None:
                          permit_upper=permissive,
                          permit_upper=permissive,
                          permit_underscore=permissive)
                          permit_underscore=permissive)
         check_if(member, info, source)
         check_if(member, info, source)
+        check_features(member.get('features'), info)
 
 
 
 
 def check_struct(expr: _JSONObject, info: QAPISourceInfo) -> None:
 def check_struct(expr: _JSONObject, info: QAPISourceInfo) -> None:

+ 3 - 2
scripts/qapi/introspect.py

@@ -275,12 +275,13 @@ def _gen_tree(self, name: str, mtype: str, obj: Dict[str, object],
             obj['features'] = self._gen_features(features)
             obj['features'] = self._gen_features(features)
         self._trees.append(Annotated(obj, ifcond, comment))
         self._trees.append(Annotated(obj, ifcond, comment))
 
 
-    @staticmethod
-    def _gen_enum_member(member: QAPISchemaEnumMember
+    def _gen_enum_member(self, member: QAPISchemaEnumMember
                          ) -> Annotated[SchemaInfoEnumMember]:
                          ) -> Annotated[SchemaInfoEnumMember]:
         obj: SchemaInfoEnumMember = {
         obj: SchemaInfoEnumMember = {
             'name': member.name,
             'name': member.name,
         }
         }
+        if member.features:
+            obj['features'] = self._gen_features(member.features)
         return Annotated(obj, member.ifcond)
         return Annotated(obj, member.ifcond)
 
 
     def _gen_object_member(self, member: QAPISchemaObjectTypeMember
     def _gen_object_member(self, member: QAPISchemaObjectTypeMember

+ 20 - 2
scripts/qapi/schema.py

@@ -708,6 +708,19 @@ def describe(self, info):
 class QAPISchemaEnumMember(QAPISchemaMember):
 class QAPISchemaEnumMember(QAPISchemaMember):
     role = 'value'
     role = 'value'
 
 
+    def __init__(self, name, info, ifcond=None, features=None):
+        super().__init__(name, info, ifcond)
+        for f in features or []:
+            assert isinstance(f, QAPISchemaFeature)
+            f.set_defined_in(name)
+        self.features = features or []
+
+    def connect_doc(self, doc):
+        super().connect_doc(doc)
+        if doc:
+            for f in self.features:
+                doc.connect_feature(f)
+
 
 
 class QAPISchemaFeature(QAPISchemaMember):
 class QAPISchemaFeature(QAPISchemaMember):
     role = 'feature'
     role = 'feature'
@@ -980,9 +993,14 @@ def _make_features(self, features, info):
                                   QAPISchemaIfCond(f.get('if')))
                                   QAPISchemaIfCond(f.get('if')))
                 for f in features]
                 for f in features]
 
 
+    def _make_enum_member(self, name, ifcond, features, info):
+        return QAPISchemaEnumMember(name, info,
+                                    QAPISchemaIfCond(ifcond),
+                                    self._make_features(features, info))
+
     def _make_enum_members(self, values, info):
     def _make_enum_members(self, values, info):
-        return [QAPISchemaEnumMember(v['name'], info,
-                                     QAPISchemaIfCond(v.get('if')))
+        return [self._make_enum_member(v['name'], v.get('if'),
+                                       v.get('features'), info)
                 for v in values]
                 for v in values]
 
 
     def _make_array_type(self, element_type, info):
     def _make_array_type(self, element_type, info):

+ 4 - 1
tests/qapi-schema/doc-good.json

@@ -58,11 +58,14 @@
 #
 #
 # Features:
 # Features:
 # @enum-feat: Also _one_ {and only}
 # @enum-feat: Also _one_ {and only}
+# @enum-member-feat: a member feature
 #
 #
 # @two is undocumented
 # @two is undocumented
 ##
 ##
 { 'enum': 'Enum',
 { 'enum': 'Enum',
-  'data': [ { 'name': 'one', 'if': 'IFONE' }, 'two' ],
+  'data': [ { 'name': 'one', 'if': 'IFONE',
+              'features': [ 'enum-member-feat' ] },
+            'two' ],
   'features': [ 'enum-feat' ],
   'features': [ 'enum-feat' ],
   'if': 'IFCOND' }
   'if': 'IFCOND' }
 
 

+ 3 - 0
tests/qapi-schema/doc-good.out

@@ -13,6 +13,7 @@ module doc-good.json
 enum Enum
 enum Enum
     member one
     member one
         if IFONE
         if IFONE
+        feature enum-member-feat
     member two
     member two
     if IFCOND
     if IFCOND
     feature enum-feat
     feature enum-feat
@@ -108,6 +109,8 @@ The _one_ {and only}
 
 
     feature=enum-feat
     feature=enum-feat
 Also _one_ {and only}
 Also _one_ {and only}
+    feature=enum-member-feat
+a member feature
     section=None
     section=None
 @two is undocumented
 @two is undocumented
 doc symbol=Base
 doc symbol=Base

+ 3 - 0
tests/qapi-schema/doc-good.txt

@@ -56,6 +56,9 @@ Features
 "enum-feat"
 "enum-feat"
    Also _one_ {and only}
    Also _one_ {and only}
 
 
+"enum-member-feat"
+   a member feature
+
 "two" is undocumented
 "two" is undocumented
 
 
 
 

+ 1 - 1
tests/qapi-schema/enum-dict-member-unknown.err

@@ -1,3 +1,3 @@
 enum-dict-member-unknown.json: In enum 'MyEnum':
 enum-dict-member-unknown.json: In enum 'MyEnum':
 enum-dict-member-unknown.json:2: 'data' member has unknown key 'bad-key'
 enum-dict-member-unknown.json:2: 'data' member has unknown key 'bad-key'
-Valid keys are 'if', 'name'.
+Valid keys are 'features', 'if', 'name'.

+ 2 - 1
tests/qapi-schema/qapi-schema-test.json

@@ -301,7 +301,8 @@
                                  'TEST_IF_COND_2'] } } ] }
                                  'TEST_IF_COND_2'] } } ] }
 
 
 { 'enum': 'FeatureEnum1',
 { 'enum': 'FeatureEnum1',
-  'data': [ 'eins', 'zwei', 'drei' ],
+  'data': [ 'eins', 'zwei',
+            { 'name': 'drei', 'features': [ 'deprecated' ] } ],
   'features': [ 'feature1' ] }
   'features': [ 'feature1' ] }
 
 
 { 'union': 'FeatureUnion1',
 { 'union': 'FeatureUnion1',

+ 1 - 0
tests/qapi-schema/qapi-schema-test.out

@@ -341,6 +341,7 @@ enum FeatureEnum1
     member eins
     member eins
     member zwei
     member zwei
     member drei
     member drei
+        feature deprecated
     feature feature1
     feature feature1
 object q_obj_FeatureUnion1-base
 object q_obj_FeatureUnion1-base
     member tag: FeatureEnum1 optional=False
     member tag: FeatureEnum1 optional=False

+ 1 - 0
tests/qapi-schema/test-qapi.py

@@ -37,6 +37,7 @@ def visit_enum_type(self, name, info, ifcond, features, members, prefix):
         for m in members:
         for m in members:
             print('    member %s' % m.name)
             print('    member %s' % m.name)
             self._print_if(m.ifcond, indent=8)
             self._print_if(m.ifcond, indent=8)
+            self._print_features(m.features, indent=8)
         self._print_if(ifcond)
         self._print_if(ifcond)
         self._print_features(features)
         self._print_features(features)