Răsfoiți Sursa

qapi: Add feature flags to remaining definitions

In v4.1.0, we added feature flags just to struct types (commit
6a8c0b5102^..f3ed93d545), to satisfy an immediate need (commit
c9d4070991 "file-posix: Add dynamic-auto-read-only QAPI feature").  In
v4.2.0, we added them to commands (commit 23394b4c39 "qapi: Add
feature flags to commands") to satisfy another immediate need (commit
d76744e65e "qapi: Allow introspecting fix for savevm's cooperation
with blockdev").

Add them to the remaining definitions: enumeration types, union types,
alternate types, and events.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-Id: <20200317115459.31821-13-armbru@redhat.com>
Markus Armbruster 5 ani în urmă
părinte
comite
013b4efc9b

+ 38 - 16
docs/devel/qapi-code-gen.txt

@@ -172,7 +172,8 @@ Syntax:
     ENUM = { 'enum': STRING,
     ENUM = { 'enum': STRING,
              'data': [ ENUM-VALUE, ... ],
              'data': [ ENUM-VALUE, ... ],
              '*prefix': STRING,
              '*prefix': STRING,
-             '*if': COND }
+             '*if': COND,
+             '*features': FEATURES }
     ENUM-VALUE = STRING
     ENUM-VALUE = STRING
                | { 'name': STRING, '*if': COND }
                | { 'name': STRING, '*if': COND }
 
 
@@ -207,6 +208,9 @@ the job satisfactorily.
 The optional 'if' member specifies a conditional.  See "Configuring
 The optional 'if' member specifies a conditional.  See "Configuring
 the schema" below for more on this.
 the schema" below for more on this.
 
 
+The optional 'features' member specifies features.  See "Features"
+below for more on this.
+
 
 
 === Type references and array types ===
 === Type references and array types ===
 
 
@@ -279,12 +283,14 @@ below for more on this.
 Syntax:
 Syntax:
     UNION = { 'union': STRING,
     UNION = { 'union': STRING,
               'data': BRANCHES,
               'data': BRANCHES,
-              '*if': COND }
+              '*if': COND,
+              '*features': FEATURES }
           | { 'union': STRING,
           | { 'union': STRING,
               'data': BRANCHES,
               'data': BRANCHES,
               'base': ( MEMBERS | STRING ),
               'base': ( MEMBERS | STRING ),
               'discriminator': STRING,
               'discriminator': STRING,
-              '*if': COND }
+              '*if': COND,
+              '*features': FEATURES }
     BRANCHES = { BRANCH, ... }
     BRANCHES = { BRANCH, ... }
     BRANCH = STRING : TYPE-REF
     BRANCH = STRING : TYPE-REF
            | STRING : { 'type': TYPE-REF, '*if': COND }
            | STRING : { 'type': TYPE-REF, '*if': COND }
@@ -391,13 +397,17 @@ is identical on the wire to:
 The optional 'if' member specifies a conditional.  See "Configuring
 The optional 'if' member specifies a conditional.  See "Configuring
 the schema" below for more on this.
 the schema" below for more on this.
 
 
+The optional 'features' member specifies features.  See "Features"
+below for more on this.
+
 
 
 === Alternate types ===
 === Alternate types ===
 
 
 Syntax:
 Syntax:
     ALTERNATE = { 'alternate': STRING,
     ALTERNATE = { 'alternate': STRING,
                   'data': ALTERNATIVES,
                   'data': ALTERNATIVES,
-                  '*if': COND }
+                  '*if': COND,
+                  '*features': FEATURES }
     ALTERNATIVES = { ALTERNATIVE, ... }
     ALTERNATIVES = { ALTERNATIVE, ... }
     ALTERNATIVE = STRING : STRING
     ALTERNATIVE = STRING : STRING
                 | STRING : { 'type': STRING, '*if': COND }
                 | STRING : { 'type': STRING, '*if': COND }
@@ -441,6 +451,9 @@ following example objects:
 The optional 'if' member specifies a conditional.  See "Configuring
 The optional 'if' member specifies a conditional.  See "Configuring
 the schema" below for more on this.
 the schema" below for more on this.
 
 
+The optional 'features' member specifies features.  See "Features"
+below for more on this.
+
 
 
 === Commands ===
 === Commands ===
 
 
@@ -584,6 +597,9 @@ started with --preconfig.
 The optional 'if' member specifies a conditional.  See "Configuring
 The optional 'if' member specifies a conditional.  See "Configuring
 the schema" below for more on this.
 the schema" below for more on this.
 
 
+The optional 'features' member specifies features.  See "Features"
+below for more on this.
+
 
 
 === Events ===
 === Events ===
 
 
@@ -595,7 +611,8 @@ Syntax:
               'data': STRING,
               'data': STRING,
               'boxed': true,
               'boxed': true,
               )
               )
-              '*if': COND }
+              '*if': COND,
+              '*features': FEATURES }
 
 
 Member 'event' names the event.  This is the event name used in the
 Member 'event' names the event.  This is the event name used in the
 Client JSON Protocol.
 Client JSON Protocol.
@@ -628,6 +645,9 @@ complex type.  See section "Code generated for events" for examples.
 The optional 'if' member specifies a conditional.  See "Configuring
 The optional 'if' member specifies a conditional.  See "Configuring
 the schema" below for more on this.
 the schema" below for more on this.
 
 
+The optional 'features' member specifies features.  See "Features"
+below for more on this.
+
 
 
 === Features ===
 === Features ===
 
 
@@ -966,8 +986,9 @@ schema, along with the SchemaInfo type.  This text attempts to give an
 overview how things work.  For details you need to consult the QAPI
 overview how things work.  For details you need to consult the QAPI
 schema.
 schema.
 
 
-SchemaInfo objects have common members "name", "meta-type", and
-additional variant members depending on the value of meta-type.
+SchemaInfo objects have common members "name", "meta-type",
+"features", and additional variant members depending on the value of
+meta-type.
 
 
 Each SchemaInfo object describes a wire ABI entity of a certain
 Each SchemaInfo object describes a wire ABI entity of a certain
 meta-type: a command, event or one of several kinds of type.
 meta-type: a command, event or one of several kinds of type.
@@ -980,19 +1001,21 @@ not.  Therefore, the SchemaInfo for types have auto-generated
 meaningless names.  For readability, the examples in this section use
 meaningless names.  For readability, the examples in this section use
 meaningful type names instead.
 meaningful type names instead.
 
 
+Optional member "features" exposes the entity's feature strings as a
+JSON array of strings.
+
 To examine a type, start with a command or event using it, then follow
 To examine a type, start with a command or event using it, then follow
 references by name.
 references by name.
 
 
 QAPI schema definitions not reachable that way are omitted.
 QAPI schema definitions not reachable that way are omitted.
 
 
 The SchemaInfo for a command has meta-type "command", and variant
 The SchemaInfo for a command has meta-type "command", and variant
-members "arg-type", "ret-type", "allow-oob", and "features".  On the
-wire, the "arguments" member of a client's "execute" command must
-conform to the object type named by "arg-type".  The "return" member
-that the server passes in a success response conforms to the type
-named by "ret-type".  When "allow-oob" is true, it means the command
-supports out-of-band execution.  It defaults to false.  "features"
-exposes the command's feature strings as a JSON array of strings.
+members "arg-type", "ret-type" and "allow-oob".  On the wire, the
+"arguments" member of a client's "execute" command must conform to the
+object type named by "arg-type".  The "return" member that the server
+passes in a success response conforms to the type named by "ret-type".
+When "allow-oob" is true, it means the command supports out-of-band
+execution.  It defaults to false.
 
 
 If the command takes no arguments, "arg-type" names an object type
 If the command takes no arguments, "arg-type" names an object type
 without members.  Likewise, if the command returns nothing, "ret-type"
 without members.  Likewise, if the command returns nothing, "ret-type"
@@ -1027,8 +1050,7 @@ Example: the SchemaInfo for EVENT_C from section Events
 
 
 The SchemaInfo for struct and union types has meta-type "object".
 The SchemaInfo for struct and union types has meta-type "object".
 
 
-The SchemaInfo for a struct type has variant members "members" and
-"features".
+The SchemaInfo for a struct type has variant member "members".
 
 
 The SchemaInfo for a union type additionally has variant members "tag"
 The SchemaInfo for a union type additionally has variant members "tag"
 and "variants".
 and "variants".

+ 9 - 11
qapi/introspect.json

@@ -89,12 +89,18 @@
 #
 #
 # @meta-type: the entity's meta type, inherited from @base.
 # @meta-type: the entity's meta type, inherited from @base.
 #
 #
+# @features: names of features associated with the entity, in no
+#            particular order.
+#            (since 4.1 for object types, 4.2 for commands, 5.0 for
+#            the rest)
+#
 # Additional members depend on the value of @meta-type.
 # Additional members depend on the value of @meta-type.
 #
 #
 # Since: 2.5
 # Since: 2.5
 ##
 ##
 { 'union': 'SchemaInfo',
 { 'union': 'SchemaInfo',
-  'base': { 'name': 'str', 'meta-type': 'SchemaMetaType' },
+  'base': { 'name': 'str', 'meta-type': 'SchemaMetaType',
+            '*features': [ 'str' ] },
   'discriminator': 'meta-type',
   'discriminator': 'meta-type',
   'data': {
   'data': {
       'builtin': 'SchemaInfoBuiltin',
       'builtin': 'SchemaInfoBuiltin',
@@ -174,9 +180,6 @@
 #            and may even differ from the order of the values of the
 #            and may even differ from the order of the values of the
 #            enum type of the @tag.
 #            enum type of the @tag.
 #
 #
-# @features: names of features associated with the type, in no particular
-#            order. (since: 4.1)
-#
 # Values of this type are JSON object on the wire.
 # Values of this type are JSON object on the wire.
 #
 #
 # Since: 2.5
 # Since: 2.5
@@ -184,8 +187,7 @@
 { 'struct': 'SchemaInfoObject',
 { 'struct': 'SchemaInfoObject',
   'data': { 'members': [ 'SchemaInfoObjectMember' ],
   'data': { 'members': [ 'SchemaInfoObjectMember' ],
             '*tag': 'str',
             '*tag': 'str',
-            '*variants': [ 'SchemaInfoObjectVariant' ],
-            '*features': [ 'str' ] } }
+            '*variants': [ 'SchemaInfoObjectVariant' ] } }
 
 
 ##
 ##
 # @SchemaInfoObjectMember:
 # @SchemaInfoObjectMember:
@@ -266,17 +268,13 @@
 # @allow-oob: whether the command allows out-of-band execution,
 # @allow-oob: whether the command allows out-of-band execution,
 #             defaults to false (Since: 2.12)
 #             defaults to false (Since: 2.12)
 #
 #
-# @features: names of features associated with the command, in no particular
-#            order. (since 4.2)
-#
 # TODO: @success-response (currently irrelevant, because it's QGA, not QMP)
 # TODO: @success-response (currently irrelevant, because it's QGA, not QMP)
 #
 #
 # Since: 2.5
 # Since: 2.5
 ##
 ##
 { 'struct': 'SchemaInfoCommand',
 { 'struct': 'SchemaInfoCommand',
   'data': { 'arg-type': 'str', 'ret-type': 'str',
   'data': { 'arg-type': 'str', 'ret-type': 'str',
-            '*allow-oob': 'bool',
-            '*features': [ 'str' ] } }
+            '*allow-oob': 'bool' } }
 
 
 ##
 ##
 # @SchemaInfoEvent:
 # @SchemaInfoEvent:

+ 3 - 3
scripts/qapi/doc.py

@@ -243,7 +243,7 @@ def __init__(self, prefix):
     def write(self, output_dir):
     def write(self, output_dir):
         self._gen.write(output_dir)
         self._gen.write(output_dir)
 
 
-    def visit_enum_type(self, name, info, ifcond, members, prefix):
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
         doc = self.cur_doc
         doc = self.cur_doc
         self._gen.add(texi_type('Enum', doc, ifcond,
         self._gen.add(texi_type('Enum', doc, ifcond,
                                 texi_members(doc, 'Values',
                                 texi_members(doc, 'Values',
@@ -257,7 +257,7 @@ def visit_object_type(self, name, info, ifcond, base, members, variants,
         self._gen.add(texi_type('Object', doc, ifcond,
         self._gen.add(texi_type('Object', doc, ifcond,
                                 texi_members(doc, 'Members', base, variants)))
                                 texi_members(doc, 'Members', base, variants)))
 
 
-    def visit_alternate_type(self, name, info, ifcond, variants):
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
         doc = self.cur_doc
         doc = self.cur_doc
         self._gen.add(texi_type('Alternate', doc, ifcond,
         self._gen.add(texi_type('Alternate', doc, ifcond,
                                 texi_members(doc, 'Members')))
                                 texi_members(doc, 'Members')))
@@ -270,7 +270,7 @@ def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
                                texi_arguments(doc,
                                texi_arguments(doc,
                                               arg_type if boxed else None)))
                                               arg_type if boxed else None)))
 
 
-    def visit_event(self, name, info, ifcond, arg_type, boxed):
+    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
         doc = self.cur_doc
         doc = self.cur_doc
         self._gen.add(texi_msg('Event', doc, ifcond,
         self._gen.add(texi_msg('Event', doc, ifcond,
                                texi_arguments(doc,
                                texi_arguments(doc,

+ 1 - 1
scripts/qapi/events.py

@@ -189,7 +189,7 @@ def visit_end(self):
                              event_emit=self._event_emit_name,
                              event_emit=self._event_emit_name,
                              event_enum=self._event_enum_name))
                              event_enum=self._event_enum_name))
 
 
-    def visit_event(self, name, info, ifcond, arg_type, boxed):
+    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
         with ifcontext(ifcond, self._genh, self._genc):
         with ifcontext(ifcond, self._genh, self._genc):
             self._genh.add(gen_event_send_decl(name, arg_type, boxed))
             self._genh.add(gen_event_send_decl(name, arg_type, boxed))
             self._genc.add(gen_event_send(name, arg_type, boxed,
             self._genc.add(gen_event_send(name, arg_type, boxed,

+ 5 - 6
scripts/qapi/expr.py

@@ -219,7 +219,6 @@ def check_struct(expr, info):
 
 
     check_type(members, info, "'data'", allow_dict=name)
     check_type(members, info, "'data'", allow_dict=name)
     check_type(expr.get('base'), info, "'base'")
     check_type(expr.get('base'), info, "'base'")
-    check_features(expr.get('features'), info)
 
 
 
 
 def check_union(expr, info):
 def check_union(expr, info):
@@ -267,7 +266,6 @@ def check_command(expr, info):
         raise QAPISemError(info, "'boxed': true requires 'data'")
         raise QAPISemError(info, "'boxed': true requires 'data'")
     check_type(args, info, "'data'", allow_dict=not boxed)
     check_type(args, info, "'data'", allow_dict=not boxed)
     check_type(rets, info, "'returns'", allow_array=True)
     check_type(rets, info, "'returns'", allow_array=True)
-    check_features(expr.get('features'), info)
 
 
 
 
 def check_event(expr, info):
 def check_event(expr, info):
@@ -319,18 +317,18 @@ def check_exprs(exprs):
 
 
         if meta == 'enum':
         if meta == 'enum':
             check_keys(expr, info, meta,
             check_keys(expr, info, meta,
-                       ['enum', 'data'], ['if', 'prefix'])
+                       ['enum', 'data'], ['if', 'features', 'prefix'])
             check_enum(expr, info)
             check_enum(expr, info)
         elif meta == 'union':
         elif meta == 'union':
             check_keys(expr, info, meta,
             check_keys(expr, info, meta,
                        ['union', 'data'],
                        ['union', 'data'],
-                       ['base', 'discriminator', 'if'])
+                       ['base', 'discriminator', 'if', 'features'])
             normalize_members(expr.get('base'))
             normalize_members(expr.get('base'))
             normalize_members(expr['data'])
             normalize_members(expr['data'])
             check_union(expr, info)
             check_union(expr, info)
         elif meta == 'alternate':
         elif meta == 'alternate':
             check_keys(expr, info, meta,
             check_keys(expr, info, meta,
-                       ['alternate', 'data'], ['if'])
+                       ['alternate', 'data'], ['if', 'features'])
             normalize_members(expr['data'])
             normalize_members(expr['data'])
             check_alternate(expr, info)
             check_alternate(expr, info)
         elif meta == 'struct':
         elif meta == 'struct':
@@ -348,13 +346,14 @@ def check_exprs(exprs):
             check_command(expr, info)
             check_command(expr, info)
         elif meta == 'event':
         elif meta == 'event':
             check_keys(expr, info, meta,
             check_keys(expr, info, meta,
-                       ['event'], ['data', 'boxed', 'if'])
+                       ['event'], ['data', 'boxed', 'if', 'features'])
             normalize_members(expr.get('data'))
             normalize_members(expr.get('data'))
             check_event(expr, info)
             check_event(expr, info)
         else:
         else:
             assert False, 'unexpected meta type'
             assert False, 'unexpected meta type'
 
 
         check_if(expr, info, meta)
         check_if(expr, info, meta)
+        check_features(expr.get('features'), info)
         check_flags(expr, info)
         check_flags(expr, info)
 
 
     return exprs
     return exprs

+ 14 - 17
scripts/qapi/introspect.py

@@ -144,7 +144,7 @@ def _use_type(self, typ):
             return '[' + self._use_type(typ.element_type) + ']'
             return '[' + self._use_type(typ.element_type) + ']'
         return self._name(typ.name)
         return self._name(typ.name)
 
 
-    def _gen_qlit(self, name, mtype, obj, ifcond):
+    def _gen_qlit(self, name, mtype, obj, ifcond, features):
         extra = {}
         extra = {}
         if mtype not in ('command', 'event', 'builtin', 'array'):
         if mtype not in ('command', 'event', 'builtin', 'array'):
             if not self._unmask:
             if not self._unmask:
@@ -154,6 +154,8 @@ def _gen_qlit(self, name, mtype, obj, ifcond):
             name = self._name(name)
             name = self._name(name)
         obj['name'] = name
         obj['name'] = name
         obj['meta-type'] = mtype
         obj['meta-type'] = mtype
+        if features:
+            obj['features'] = [(f.name, {'if': f.ifcond}) for f in features]
         if ifcond:
         if ifcond:
             extra['if'] = ifcond
             extra['if'] = ifcond
         if extra:
         if extra:
@@ -178,18 +180,18 @@ def _gen_variant(self, variant):
                 {'if': variant.ifcond})
                 {'if': variant.ifcond})
 
 
     def visit_builtin_type(self, name, info, json_type):
     def visit_builtin_type(self, name, info, json_type):
-        self._gen_qlit(name, 'builtin', {'json-type': json_type}, [])
+        self._gen_qlit(name, 'builtin', {'json-type': json_type}, [], None)
 
 
-    def visit_enum_type(self, name, info, ifcond, members, prefix):
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
         self._gen_qlit(name, 'enum',
         self._gen_qlit(name, 'enum',
                        {'values':
                        {'values':
                         [(m.name, {'if': m.ifcond}) for m in members]},
                         [(m.name, {'if': m.ifcond}) for m in members]},
-                       ifcond)
+                       ifcond, features)
 
 
     def visit_array_type(self, name, info, ifcond, element_type):
     def visit_array_type(self, name, info, ifcond, element_type):
         element = self._use_type(element_type)
         element = self._use_type(element_type)
         self._gen_qlit('[' + element + ']', 'array', {'element-type': element},
         self._gen_qlit('[' + element + ']', 'array', {'element-type': element},
-                       ifcond)
+                       ifcond, None)
 
 
     def visit_object_type_flat(self, name, info, ifcond, members, variants,
     def visit_object_type_flat(self, name, info, ifcond, members, variants,
                                features):
                                features):
@@ -197,16 +199,15 @@ def visit_object_type_flat(self, name, info, ifcond, members, variants,
         if variants:
         if variants:
             obj.update(self._gen_variants(variants.tag_member.name,
             obj.update(self._gen_variants(variants.tag_member.name,
                                           variants.variants))
                                           variants.variants))
-        if features:
-            obj['features'] = [(f.name, {'if': f.ifcond}) for f in features]
 
 
-        self._gen_qlit(name, 'object', obj, ifcond)
+        self._gen_qlit(name, 'object', obj, ifcond, features)
 
 
-    def visit_alternate_type(self, name, info, ifcond, variants):
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
         self._gen_qlit(name, 'alternate',
         self._gen_qlit(name, 'alternate',
                        {'members': [
                        {'members': [
                            ({'type': self._use_type(m.type)}, {'if': m.ifcond})
                            ({'type': self._use_type(m.type)}, {'if': m.ifcond})
-                           for m in variants.variants]}, ifcond)
+                           for m in variants.variants]},
+                       ifcond, features)
 
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
                       success_response, boxed, allow_oob, allow_preconfig,
                       success_response, boxed, allow_oob, allow_preconfig,
@@ -217,16 +218,12 @@ def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
                'ret-type': self._use_type(ret_type)}
                'ret-type': self._use_type(ret_type)}
         if allow_oob:
         if allow_oob:
             obj['allow-oob'] = allow_oob
             obj['allow-oob'] = allow_oob
+        self._gen_qlit(name, 'command', obj, ifcond, features)
 
 
-        if features:
-            obj['features'] = [(f.name, {'if': f.ifcond}) for f in features]
-
-        self._gen_qlit(name, 'command', obj, ifcond)
-
-    def visit_event(self, name, info, ifcond, arg_type, boxed):
+    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
         arg_type = arg_type or self._schema.the_empty_object_type
         arg_type = arg_type or self._schema.the_empty_object_type
         self._gen_qlit(name, 'event', {'arg-type': self._use_type(arg_type)},
         self._gen_qlit(name, 'event', {'arg-type': self._use_type(arg_type)},
-                       ifcond)
+                       ifcond, features)
 
 
 
 
 def gen_introspect(schema, output_dir, prefix, opt_unmask):
 def gen_introspect(schema, output_dir, prefix, opt_unmask):

+ 53 - 43
scripts/qapi/schema.py

@@ -109,7 +109,7 @@ def visit_include(self, name, info):
     def visit_builtin_type(self, name, info, json_type):
     def visit_builtin_type(self, name, info, json_type):
         pass
         pass
 
 
-    def visit_enum_type(self, name, info, ifcond, members, prefix):
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
         pass
         pass
 
 
     def visit_array_type(self, name, info, ifcond, element_type):
     def visit_array_type(self, name, info, ifcond, element_type):
@@ -123,7 +123,7 @@ def visit_object_type_flat(self, name, info, ifcond, members, variants,
                                features):
                                features):
         pass
         pass
 
 
-    def visit_alternate_type(self, name, info, ifcond, variants):
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
         pass
         pass
 
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
@@ -131,7 +131,7 @@ def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
                       features):
                       features):
         pass
         pass
 
 
-    def visit_event(self, name, info, ifcond, arg_type, boxed):
+    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
         pass
         pass
 
 
 
 
@@ -234,8 +234,8 @@ def visit(self, visitor):
 class QAPISchemaEnumType(QAPISchemaType):
 class QAPISchemaEnumType(QAPISchemaType):
     meta = 'enum'
     meta = 'enum'
 
 
-    def __init__(self, name, info, doc, ifcond, members, prefix):
-        super().__init__(name, info, doc, ifcond)
+    def __init__(self, name, info, doc, ifcond, features, members, prefix):
+        super().__init__(name, info, doc, ifcond, features)
         for m in members:
         for m in members:
             assert isinstance(m, QAPISchemaEnumMember)
             assert isinstance(m, QAPISchemaEnumMember)
             m.set_defined_in(name)
             m.set_defined_in(name)
@@ -271,15 +271,16 @@ def json_type(self):
 
 
     def visit(self, visitor):
     def visit(self, visitor):
         super().visit(visitor)
         super().visit(visitor)
-        visitor.visit_enum_type(self.name, self.info, self.ifcond,
-                                self.members, self.prefix)
+        visitor.visit_enum_type(
+            self.name, self.info, self.ifcond, self.features,
+            self.members, self.prefix)
 
 
 
 
 class QAPISchemaArrayType(QAPISchemaType):
 class QAPISchemaArrayType(QAPISchemaType):
     meta = 'array'
     meta = 'array'
 
 
     def __init__(self, name, info, element_type):
     def __init__(self, name, info, element_type):
-        super().__init__(name, info, None, None)
+        super().__init__(name, info, None)
         assert isinstance(element_type, str)
         assert isinstance(element_type, str)
         self._element_type_name = element_type
         self._element_type_name = element_type
         self.element_type = None
         self.element_type = None
@@ -325,8 +326,8 @@ def describe(self):
 
 
 
 
 class QAPISchemaObjectType(QAPISchemaType):
 class QAPISchemaObjectType(QAPISchemaType):
-    def __init__(self, name, info, doc, ifcond,
-                 base, local_members, variants, features):
+    def __init__(self, name, info, doc, ifcond, features,
+                 base, local_members, variants):
         # struct has local_members, optional base, and no variants
         # struct has local_members, optional base, and no variants
         # flat union has base, variants, and no local_members
         # flat union has base, variants, and no local_members
         # simple union has local_members, variants, and no base
         # simple union has local_members, variants, and no base
@@ -622,8 +623,8 @@ def __init__(self, name, info, typ, ifcond=None):
 class QAPISchemaAlternateType(QAPISchemaType):
 class QAPISchemaAlternateType(QAPISchemaType):
     meta = 'alternate'
     meta = 'alternate'
 
 
-    def __init__(self, name, info, doc, ifcond, variants):
-        super().__init__(name, info, doc, ifcond)
+    def __init__(self, name, info, doc, ifcond, features, variants):
+        super().__init__(name, info, doc, ifcond, features)
         assert isinstance(variants, QAPISchemaObjectTypeVariants)
         assert isinstance(variants, QAPISchemaObjectTypeVariants)
         assert variants.tag_member
         assert variants.tag_member
         variants.set_defined_in(name)
         variants.set_defined_in(name)
@@ -683,16 +684,16 @@ def json_type(self):
 
 
     def visit(self, visitor):
     def visit(self, visitor):
         super().visit(visitor)
         super().visit(visitor)
-        visitor.visit_alternate_type(self.name, self.info, self.ifcond,
-                                     self.variants)
+        visitor.visit_alternate_type(
+            self.name, self.info, self.ifcond, self.features, self.variants)
 
 
 
 
 class QAPISchemaCommand(QAPISchemaEntity):
 class QAPISchemaCommand(QAPISchemaEntity):
     meta = 'command'
     meta = 'command'
 
 
-    def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
-                 gen, success_response, boxed, allow_oob, allow_preconfig,
-                 features):
+    def __init__(self, name, info, doc, ifcond, features,
+                 arg_type, ret_type,
+                 gen, success_response, boxed, allow_oob, allow_preconfig):
         super().__init__(name, info, doc, ifcond, features)
         super().__init__(name, info, doc, ifcond, features)
         assert not arg_type or isinstance(arg_type, str)
         assert not arg_type or isinstance(arg_type, str)
         assert not ret_type or isinstance(ret_type, str)
         assert not ret_type or isinstance(ret_type, str)
@@ -755,8 +756,8 @@ def visit(self, visitor):
 class QAPISchemaEvent(QAPISchemaEntity):
 class QAPISchemaEvent(QAPISchemaEntity):
     meta = 'event'
     meta = 'event'
 
 
-    def __init__(self, name, info, doc, ifcond, arg_type, boxed):
-        super().__init__(name, info, doc, ifcond)
+    def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
+        super().__init__(name, info, doc, ifcond, features)
         assert not arg_type or isinstance(arg_type, str)
         assert not arg_type or isinstance(arg_type, str)
         self._arg_type_name = arg_type
         self._arg_type_name = arg_type
         self.arg_type = None
         self.arg_type = None
@@ -787,8 +788,9 @@ def connect_doc(self, doc=None):
 
 
     def visit(self, visitor):
     def visit(self, visitor):
         super().visit(visitor)
         super().visit(visitor)
-        visitor.visit_event(self.name, self.info, self.ifcond,
-                            self.arg_type, self.boxed)
+        visitor.visit_event(
+            self.name, self.info, self.ifcond, self.features,
+            self.arg_type, self.boxed)
 
 
 
 
 class QAPISchema:
 class QAPISchema:
@@ -893,7 +895,7 @@ def _def_predefineds(self):
                   ('null',   'null',    'QNull' + pointer_suffix)]:
                   ('null',   'null',    'QNull' + pointer_suffix)]:
             self._def_builtin_type(*t)
             self._def_builtin_type(*t)
         self.the_empty_object_type = QAPISchemaObjectType(
         self.the_empty_object_type = QAPISchemaObjectType(
-            'q_empty', None, None, None, None, [], None, [])
+            'q_empty', None, None, None, None, None, [], None)
         self._def_entity(self.the_empty_object_type)
         self._def_entity(self.the_empty_object_type)
 
 
         qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
         qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
@@ -901,10 +903,11 @@ def _def_predefineds(self):
         qtype_values = self._make_enum_members(
         qtype_values = self._make_enum_members(
             [{'name': n} for n in qtypes], None)
             [{'name': n} for n in qtypes], None)
 
 
-        self._def_entity(QAPISchemaEnumType('QType', None, None, None,
+        self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
                                             qtype_values, 'QTYPE'))
                                             qtype_values, 'QTYPE'))
 
 
-    def _make_features(self, features, info):
+    def _make_features(self, expr, info):
+        features = expr.get('features', [])
         return [QAPISchemaFeature(f['name'], info, f.get('if'))
         return [QAPISchemaFeature(f['name'], info, f.get('if'))
                 for f in features]
                 for f in features]
 
 
@@ -916,7 +919,8 @@ def _make_implicit_enum_type(self, name, info, ifcond, values):
         # See also QAPISchemaObjectTypeMember.describe()
         # See also QAPISchemaObjectTypeMember.describe()
         name = name + 'Kind'    # reserved by check_defn_name_str()
         name = name + 'Kind'    # reserved by check_defn_name_str()
         self._def_entity(QAPISchemaEnumType(
         self._def_entity(QAPISchemaEnumType(
-            name, info, None, ifcond, self._make_enum_members(values, info),
+            name, info, None, ifcond, None,
+            self._make_enum_members(values, info),
             None))
             None))
         return name
         return name
 
 
@@ -944,8 +948,8 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
             # TODO kill simple unions or implement the disjunction
             # TODO kill simple unions or implement the disjunction
             assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
             assert (ifcond or []) == typ._ifcond # pylint: disable=protected-access
         else:
         else:
-            self._def_entity(QAPISchemaObjectType(name, info, None, ifcond,
-                                                  None, members, None, []))
+            self._def_entity(QAPISchemaObjectType(
+                name, info, None, ifcond, None, None, members, None))
         return name
         return name
 
 
     def _def_enum_type(self, expr, info, doc):
     def _def_enum_type(self, expr, info, doc):
@@ -953,8 +957,9 @@ def _def_enum_type(self, expr, info, doc):
         data = expr['data']
         data = expr['data']
         prefix = expr.get('prefix')
         prefix = expr.get('prefix')
         ifcond = expr.get('if')
         ifcond = expr.get('if')
+        features = self._make_features(expr, info)
         self._def_entity(QAPISchemaEnumType(
         self._def_entity(QAPISchemaEnumType(
-            name, info, doc, ifcond,
+            name, info, doc, ifcond, features,
             self._make_enum_members(data, info), prefix))
             self._make_enum_members(data, info), prefix))
 
 
     def _make_member(self, name, typ, ifcond, info):
     def _make_member(self, name, typ, ifcond, info):
@@ -976,12 +981,11 @@ def _def_struct_type(self, expr, info, doc):
         base = expr.get('base')
         base = expr.get('base')
         data = expr['data']
         data = expr['data']
         ifcond = expr.get('if')
         ifcond = expr.get('if')
-        features = expr.get('features', [])
+        features = self._make_features(expr, info)
         self._def_entity(QAPISchemaObjectType(
         self._def_entity(QAPISchemaObjectType(
-            name, info, doc, ifcond, base,
+            name, info, doc, ifcond, features, base,
             self._make_members(data, info),
             self._make_members(data, info),
-            None,
-            self._make_features(features, info)))
+            None))
 
 
     def _make_variant(self, case, typ, ifcond, info):
     def _make_variant(self, case, typ, ifcond, info):
         return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
         return QAPISchemaObjectTypeVariant(case, info, typ, ifcond)
@@ -1000,6 +1004,7 @@ def _def_union_type(self, expr, info, doc):
         data = expr['data']
         data = expr['data']
         base = expr.get('base')
         base = expr.get('base')
         ifcond = expr.get('if')
         ifcond = expr.get('if')
+        features = self._make_features(expr, info)
         tag_name = expr.get('discriminator')
         tag_name = expr.get('discriminator')
         tag_member = None
         tag_member = None
         if isinstance(base, dict):
         if isinstance(base, dict):
@@ -1020,21 +1025,22 @@ def _def_union_type(self, expr, info, doc):
             tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
             tag_member = QAPISchemaObjectTypeMember('type', info, typ, False)
             members = [tag_member]
             members = [tag_member]
         self._def_entity(
         self._def_entity(
-            QAPISchemaObjectType(name, info, doc, ifcond, base, members,
+            QAPISchemaObjectType(name, info, doc, ifcond, features,
+                                 base, members,
                                  QAPISchemaObjectTypeVariants(
                                  QAPISchemaObjectTypeVariants(
-                                     tag_name, info, tag_member, variants),
-                                 []))
+                                     tag_name, info, tag_member, variants)))
 
 
     def _def_alternate_type(self, expr, info, doc):
     def _def_alternate_type(self, expr, info, doc):
         name = expr['alternate']
         name = expr['alternate']
         data = expr['data']
         data = expr['data']
         ifcond = expr.get('if')
         ifcond = expr.get('if')
+        features = self._make_features(expr, info)
         variants = [self._make_variant(key, value['type'], value.get('if'),
         variants = [self._make_variant(key, value['type'], value.get('if'),
                                        info)
                                        info)
                     for (key, value) in data.items()]
                     for (key, value) in data.items()]
         tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
         tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
         self._def_entity(
         self._def_entity(
-            QAPISchemaAlternateType(name, info, doc, ifcond,
+            QAPISchemaAlternateType(name, info, doc, ifcond, features,
                                     QAPISchemaObjectTypeVariants(
                                     QAPISchemaObjectTypeVariants(
                                         None, info, tag_member, variants)))
                                         None, info, tag_member, variants)))
 
 
@@ -1048,27 +1054,31 @@ def _def_command(self, expr, info, doc):
         allow_oob = expr.get('allow-oob', False)
         allow_oob = expr.get('allow-oob', False)
         allow_preconfig = expr.get('allow-preconfig', False)
         allow_preconfig = expr.get('allow-preconfig', False)
         ifcond = expr.get('if')
         ifcond = expr.get('if')
-        features = expr.get('features', [])
+        features = self._make_features(expr, info)
         if isinstance(data, OrderedDict):
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
             data = self._make_implicit_object_type(
-                name, info, ifcond, 'arg', self._make_members(data, info))
+                name, info, ifcond,
+                'arg', self._make_members(data, info))
         if isinstance(rets, list):
         if isinstance(rets, list):
             assert len(rets) == 1
             assert len(rets) == 1
             rets = self._make_array_type(rets[0], info)
             rets = self._make_array_type(rets[0], info)
-        self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, data, rets,
+        self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, features,
+                                           data, rets,
                                            gen, success_response,
                                            gen, success_response,
-                                           boxed, allow_oob, allow_preconfig,
-                                           self._make_features(features, info)))
+                                           boxed, allow_oob, allow_preconfig))
 
 
     def _def_event(self, expr, info, doc):
     def _def_event(self, expr, info, doc):
         name = expr['event']
         name = expr['event']
         data = expr.get('data')
         data = expr.get('data')
         boxed = expr.get('boxed', False)
         boxed = expr.get('boxed', False)
         ifcond = expr.get('if')
         ifcond = expr.get('if')
+        features = self._make_features(expr, info)
         if isinstance(data, OrderedDict):
         if isinstance(data, OrderedDict):
             data = self._make_implicit_object_type(
             data = self._make_implicit_object_type(
-                name, info, ifcond, 'arg', self._make_members(data, info))
-        self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, data, boxed))
+                name, info, ifcond,
+                'arg', self._make_members(data, info))
+        self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, features,
+                                         data, boxed))
 
 
     def _def_exprs(self, exprs):
     def _def_exprs(self, exprs):
         for expr_elem in exprs:
         for expr_elem in exprs:

+ 2 - 2
scripts/qapi/types.py

@@ -278,7 +278,7 @@ def _gen_type_cleanup(self, name):
         self._genh.add(gen_type_cleanup_decl(name))
         self._genh.add(gen_type_cleanup_decl(name))
         self._genc.add(gen_type_cleanup(name))
         self._genc.add(gen_type_cleanup(name))
 
 
-    def visit_enum_type(self, name, info, ifcond, members, prefix):
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
         with ifcontext(ifcond, self._genh, self._genc):
         with ifcontext(ifcond, self._genh, self._genc):
             self._genh.preamble_add(gen_enum(name, members, prefix))
             self._genh.preamble_add(gen_enum(name, members, prefix))
             self._genc.add(gen_enum_lookup(name, members, prefix))
             self._genc.add(gen_enum_lookup(name, members, prefix))
@@ -306,7 +306,7 @@ def visit_object_type(self, name, info, ifcond, base, members, variants,
                 # implicit types won't be directly allocated/freed
                 # implicit types won't be directly allocated/freed
                 self._gen_type_cleanup(name)
                 self._gen_type_cleanup(name)
 
 
-    def visit_alternate_type(self, name, info, ifcond, variants):
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
         with ifcontext(ifcond, self._genh):
         with ifcontext(ifcond, self._genh):
             self._genh.preamble_add(gen_fwd_object_or_array(name))
             self._genh.preamble_add(gen_fwd_object_or_array(name))
         self._genh.add(gen_object(name, ifcond, None,
         self._genh.add(gen_object(name, ifcond, None,

+ 2 - 2
scripts/qapi/visit.py

@@ -316,7 +316,7 @@ def _begin_user_module(self, name):
 ''',
 ''',
                                       types=types))
                                       types=types))
 
 
-    def visit_enum_type(self, name, info, ifcond, members, prefix):
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
         with ifcontext(ifcond, self._genh, self._genc):
         with ifcontext(ifcond, self._genh, self._genc):
             self._genh.add(gen_visit_decl(name, scalar=True))
             self._genh.add(gen_visit_decl(name, scalar=True))
             self._genc.add(gen_visit_enum(name))
             self._genc.add(gen_visit_enum(name))
@@ -342,7 +342,7 @@ def visit_object_type(self, name, info, ifcond, base, members, variants,
                 self._genh.add(gen_visit_decl(name))
                 self._genh.add(gen_visit_decl(name))
                 self._genc.add(gen_visit_object(name, base, members, variants))
                 self._genc.add(gen_visit_object(name, base, members, variants))
 
 
-    def visit_alternate_type(self, name, info, ifcond, variants):
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
         with ifcontext(ifcond, self._genh, self._genc):
         with ifcontext(ifcond, self._genh, self._genc):
             self._genh.add(gen_visit_decl(name))
             self._genh.add(gen_visit_decl(name))
             self._genc.add(gen_visit_alternate(name, variants))
             self._genc.add(gen_visit_alternate(name, variants))

+ 1 - 1
tests/qapi-schema/alternate-base.err

@@ -1,3 +1,3 @@
 alternate-base.json: In alternate 'Alt':
 alternate-base.json: In alternate 'Alt':
 alternate-base.json:4: alternate has unknown key 'base'
 alternate-base.json:4: alternate has unknown key 'base'
-Valid keys are 'alternate', 'data', 'if'.
+Valid keys are 'alternate', 'data', 'features', 'if'.

+ 17 - 0
tests/qapi-schema/doc-good.json

@@ -53,10 +53,14 @@
 # @Enum:
 # @Enum:
 # @one: The _one_ {and only}
 # @one: The _one_ {and only}
 #
 #
+# Features:
+# @enum-feat: Also _one_ {and only}
+#
 # @two is undocumented
 # @two is undocumented
 ##
 ##
 { 'enum': 'Enum', 'data':
 { 'enum': 'Enum', 'data':
   [ { 'name': 'one', 'if': 'defined(IFONE)' }, 'two' ],
   [ { 'name': 'one', 'if': 'defined(IFONE)' }, 'two' ],
+  'features': [ 'enum-feat' ],
   'if': 'defined(IFCOND)' }
   'if': 'defined(IFCOND)' }
 
 
 ##
 ##
@@ -86,24 +90,34 @@
 
 
 ##
 ##
 # @Object:
 # @Object:
+# Features:
+# @union-feat1: a feature
 ##
 ##
 { 'union': 'Object',
 { 'union': 'Object',
+  'features': [ 'union-feat1' ],
   'base': 'Base',
   'base': 'Base',
   'discriminator': 'base1',
   'discriminator': 'base1',
   'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
   'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
 
 
 ##
 ##
 # @SugaredUnion:
 # @SugaredUnion:
+# Features:
+# @union-feat2: a feature
 ##
 ##
 { 'union': 'SugaredUnion',
 { 'union': 'SugaredUnion',
+  'features': [ 'union-feat2' ],
   'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
   'data': { 'one': 'Variant1', 'two': { 'type': 'Variant2', 'if': 'IFTWO' } } }
 
 
 ##
 ##
 # @Alternate:
 # @Alternate:
 # @i: an integer
 # @i: an integer
 # @b is undocumented
 # @b is undocumented
+#
+# Features:
+# @alt-feat: a feature
 ##
 ##
 { 'alternate': 'Alternate',
 { 'alternate': 'Alternate',
+  'features': [ 'alt-feat' ],
   'data': { 'i': 'int', 'b': 'bool' } }
   'data': { 'i': 'int', 'b': 'bool' } }
 
 
 ##
 ##
@@ -160,6 +174,9 @@
 
 
 ##
 ##
 # @EVT-BOXED:
 # @EVT-BOXED:
+# Features:
+# @feat3: a feature
 ##
 ##
 { 'event': 'EVT-BOXED',  'boxed': true,
 { 'event': 'EVT-BOXED',  'boxed': true,
+  'features': [ 'feat3' ],
   'data': 'Object' }
   'data': 'Object' }

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

@@ -15,6 +15,7 @@ enum Enum
         if ['defined(IFONE)']
         if ['defined(IFONE)']
     member two
     member two
     if ['defined(IFCOND)']
     if ['defined(IFCOND)']
+    feature enum-feat
 object Base
 object Base
     member base1: Enum optional=False
     member base1: Enum optional=False
 object Variant1
 object Variant1
@@ -28,6 +29,7 @@ object Object
     case one: Variant1
     case one: Variant1
     case two: Variant2
     case two: Variant2
         if ['IFTWO']
         if ['IFTWO']
+    feature union-feat1
 object q_obj_Variant1-wrapper
 object q_obj_Variant1-wrapper
     member data: Variant1 optional=False
     member data: Variant1 optional=False
 object q_obj_Variant2-wrapper
 object q_obj_Variant2-wrapper
@@ -42,10 +44,12 @@ object SugaredUnion
     case one: q_obj_Variant1-wrapper
     case one: q_obj_Variant1-wrapper
     case two: q_obj_Variant2-wrapper
     case two: q_obj_Variant2-wrapper
         if ['IFTWO']
         if ['IFTWO']
+    feature union-feat2
 alternate Alternate
 alternate Alternate
     tag type
     tag type
     case i: int
     case i: int
     case b: bool
     case b: bool
+    feature alt-feat
 object q_obj_cmd-arg
 object q_obj_cmd-arg
     member arg1: int optional=False
     member arg1: int optional=False
     member arg2: str optional=True
     member arg2: str optional=True
@@ -60,6 +64,7 @@ command cmd-boxed Object -> None
     feature cmd-feat2
     feature cmd-feat2
 event EVT-BOXED Object
 event EVT-BOXED Object
     boxed=True
     boxed=True
+    feature feat3
 doc freeform
 doc freeform
     body=
     body=
 = Section
 = Section
@@ -112,6 +117,8 @@ doc symbol=Enum
 The _one_ {and only}
 The _one_ {and only}
     arg=two
     arg=two
 
 
+    feature=enum-feat
+Also _one_ {and only}
     section=None
     section=None
 @two is undocumented
 @two is undocumented
 doc symbol=Base
 doc symbol=Base
@@ -134,11 +141,15 @@ doc symbol=Variant2
 doc symbol=Object
 doc symbol=Object
     body=
     body=
 
 
+    feature=union-feat1
+a feature
 doc symbol=SugaredUnion
 doc symbol=SugaredUnion
     body=
     body=
 
 
     arg=type
     arg=type
 
 
+    feature=union-feat2
+a feature
 doc symbol=Alternate
 doc symbol=Alternate
     body=
     body=
 
 
@@ -147,6 +158,8 @@ an integer
 @b is undocumented
 @b is undocumented
     arg=b
     arg=b
 
 
+    feature=alt-feat
+a feature
 doc freeform
 doc freeform
     body=
     body=
 == Another subsection
 == Another subsection
@@ -197,3 +210,5 @@ another feature
 doc symbol=EVT-BOXED
 doc symbol=EVT-BOXED
     body=
     body=
 
 
+    feature=feat3
+a feature

+ 30 - 0
tests/qapi-schema/doc-good.texi

@@ -88,6 +88,12 @@ The @emph{one} @{and only@}
 @item @code{two}
 @item @code{two}
 Not documented
 Not documented
 @end table
 @end table
+
+@b{Features:}
+@table @asis
+@item @code{enum-feat}
+Also @emph{one} @{and only@}
+@end table
 @code{two} is undocumented
 @code{two} is undocumented
 
 
 @b{If:} @code{defined(IFCOND)}
 @b{If:} @code{defined(IFCOND)}
@@ -151,6 +157,12 @@ a feature
 @item The members of @code{Variant2} when @code{base1} is @t{"two"} (@b{If:} @code{IFTWO})
 @item The members of @code{Variant2} when @code{base1} is @t{"two"} (@b{If:} @code{IFTWO})
 @end table
 @end table
 
 
+@b{Features:}
+@table @asis
+@item @code{union-feat1}
+a feature
+@end table
+
 @end deftp
 @end deftp
 
 
 
 
@@ -167,6 +179,12 @@ One of @t{"one"}, @t{"two"}
 @item @code{data: Variant2} when @code{type} is @t{"two"} (@b{If:} @code{IFTWO})
 @item @code{data: Variant2} when @code{type} is @t{"two"} (@b{If:} @code{IFTWO})
 @end table
 @end table
 
 
+@b{Features:}
+@table @asis
+@item @code{union-feat2}
+a feature
+@end table
+
 @end deftp
 @end deftp
 
 
 
 
@@ -184,6 +202,12 @@ an integer
 Not documented
 Not documented
 @end table
 @end table
 
 
+@b{Features:}
+@table @asis
+@item @code{alt-feat}
+a feature
+@end table
+
 @end deftp
 @end deftp
 
 
 
 
@@ -283,5 +307,11 @@ another feature
 
 
 @b{Arguments:} the members of @code{Object}
 @b{Arguments:} the members of @code{Object}
 
 
+@b{Features:}
+@table @asis
+@item @code{feat3}
+a feature
+@end table
+
 @end deftypefn
 @end deftypefn
 
 

+ 22 - 7
tests/qapi-schema/qapi-schema-test.json

@@ -252,7 +252,7 @@
     'bar': { 'type': ['TestIfEnum'], 'if': 'defined(TEST_IF_EVT_BAR)' } },
     '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' for structs
+# test 'features'
 
 
 { 'struct': 'FeatureStruct0',
 { 'struct': 'FeatureStruct0',
   'data': { 'foo': 'int' },
   'data': { 'foo': 'int' },
@@ -281,7 +281,22 @@
   'data': { 'foo': 'int' },
   'data': { 'foo': 'int' },
   'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
   'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
                                               'defined(TEST_IF_COND_2)'] } ] }
                                               'defined(TEST_IF_COND_2)'] } ] }
-{ 'command': 'test-features',
+
+{ 'enum': 'FeatureEnum1',
+  'data': [ 'eins', 'zwei', 'drei' ],
+  'features': [ 'feature1' ] }
+
+{ 'union': 'FeatureUnion1',
+  'base': { 'tag': 'FeatureEnum1' },
+  'discriminator': 'tag',
+  'data': { 'eins': 'FeatureStruct1' },
+  'features': [ 'feature1' ] }
+
+{ 'alternate': 'FeatureAlternate1',
+  'data': { 'eins': 'FeatureStruct1' },
+  'features': [ 'feature1' ] }
+
+{ 'command': 'test-features0',
   'data': { 'fs0': 'FeatureStruct0',
   'data': { 'fs0': 'FeatureStruct0',
             'fs1': 'FeatureStruct1',
             'fs1': 'FeatureStruct1',
             'fs2': 'FeatureStruct2',
             'fs2': 'FeatureStruct2',
@@ -289,12 +304,9 @@
             'fs4': 'FeatureStruct4',
             'fs4': 'FeatureStruct4',
             'cfs1': 'CondFeatureStruct1',
             'cfs1': 'CondFeatureStruct1',
             'cfs2': 'CondFeatureStruct2',
             'cfs2': 'CondFeatureStruct2',
-            'cfs3': 'CondFeatureStruct3' } }
-
-# test 'features' for command
-
-{ 'command': 'test-command-features0',
+            'cfs3': 'CondFeatureStruct3' },
   'features': [] }
   'features': [] }
+
 { 'command': 'test-command-features1',
 { 'command': 'test-command-features1',
   'features': [ 'feature1' ] }
   'features': [ 'feature1' ] }
 { 'command': 'test-command-features3',
 { 'command': 'test-command-features3',
@@ -308,3 +320,6 @@
 { 'command': 'test-command-cond-features3',
 { 'command': 'test-command-cond-features3',
   'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
   'features': [ { 'name': 'feature1', 'if': [ 'defined(TEST_IF_COND_1)',
                                               'defined(TEST_IF_COND_2)'] } ] }
                                               'defined(TEST_IF_COND_2)'] } ] }
+
+{ 'event': 'TEST-EVENT-FEATURES1',
+  'features': [ 'feature1' ] }

+ 23 - 4
tests/qapi-schema/qapi-schema-test.out

@@ -387,7 +387,25 @@ object CondFeatureStruct3
     member foo: int optional=False
     member foo: int optional=False
     feature feature1
     feature feature1
         if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
         if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
-object q_obj_test-features-arg
+enum FeatureEnum1
+    member eins
+    member zwei
+    member drei
+    feature feature1
+object q_obj_FeatureUnion1-base
+    member tag: FeatureEnum1 optional=False
+object FeatureUnion1
+    base q_obj_FeatureUnion1-base
+    tag tag
+    case eins: FeatureStruct1
+    case zwei: q_empty
+    case drei: q_empty
+    feature feature1
+alternate FeatureAlternate1
+    tag type
+    case eins: FeatureStruct1
+    feature feature1
+object q_obj_test-features0-arg
     member fs0: FeatureStruct0 optional=False
     member fs0: FeatureStruct0 optional=False
     member fs1: FeatureStruct1 optional=False
     member fs1: FeatureStruct1 optional=False
     member fs2: FeatureStruct2 optional=False
     member fs2: FeatureStruct2 optional=False
@@ -396,9 +414,7 @@ object q_obj_test-features-arg
     member cfs1: CondFeatureStruct1 optional=False
     member cfs1: CondFeatureStruct1 optional=False
     member cfs2: CondFeatureStruct2 optional=False
     member cfs2: CondFeatureStruct2 optional=False
     member cfs3: CondFeatureStruct3 optional=False
     member cfs3: CondFeatureStruct3 optional=False
-command test-features q_obj_test-features-arg -> None
-    gen=True success_response=True boxed=False oob=False preconfig=False
-command test-command-features0 None -> None
+command test-features0 q_obj_test-features0-arg -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     gen=True success_response=True boxed=False oob=False preconfig=False
 command test-command-features1 None -> None
 command test-command-features1 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     gen=True success_response=True boxed=False oob=False preconfig=False
@@ -421,6 +437,9 @@ command test-command-cond-features3 None -> None
     gen=True success_response=True boxed=False oob=False preconfig=False
     gen=True success_response=True boxed=False oob=False preconfig=False
     feature feature1
     feature feature1
         if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
         if ['defined(TEST_IF_COND_1)', 'defined(TEST_IF_COND_2)']
+event TEST-EVENT-FEATURES1 None
+    boxed=False
+    feature feature1
 module include/sub-module.json
 module include/sub-module.json
 include sub-sub-module.json
 include sub-sub-module.json
 object SecondArrayRef
 object SecondArrayRef

+ 6 - 3
tests/qapi-schema/test-qapi.py

@@ -30,7 +30,7 @@ def visit_module(self, name):
     def visit_include(self, name, info):
     def visit_include(self, name, info):
         print('include %s' % name)
         print('include %s' % name)
 
 
-    def visit_enum_type(self, name, info, ifcond, members, prefix):
+    def visit_enum_type(self, name, info, ifcond, features, members, prefix):
         print('enum %s' % name)
         print('enum %s' % name)
         if prefix:
         if prefix:
             print('    prefix %s' % prefix)
             print('    prefix %s' % prefix)
@@ -38,6 +38,7 @@ def visit_enum_type(self, name, info, ifcond, members, prefix):
             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_if(ifcond)
         self._print_if(ifcond)
+        self._print_features(features)
 
 
     def visit_array_type(self, name, info, ifcond, element_type):
     def visit_array_type(self, name, info, ifcond, element_type):
         if not info:
         if not info:
@@ -58,10 +59,11 @@ def visit_object_type(self, name, info, ifcond, base, members, variants,
         self._print_if(ifcond)
         self._print_if(ifcond)
         self._print_features(features)
         self._print_features(features)
 
 
-    def visit_alternate_type(self, name, info, ifcond, variants):
+    def visit_alternate_type(self, name, info, ifcond, features, variants):
         print('alternate %s' % name)
         print('alternate %s' % name)
         self._print_variants(variants)
         self._print_variants(variants)
         self._print_if(ifcond)
         self._print_if(ifcond)
+        self._print_features(features)
 
 
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
     def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
                       success_response, boxed, allow_oob, allow_preconfig,
                       success_response, boxed, allow_oob, allow_preconfig,
@@ -74,10 +76,11 @@ def visit_command(self, name, info, ifcond, arg_type, ret_type, gen,
         self._print_if(ifcond)
         self._print_if(ifcond)
         self._print_features(features)
         self._print_features(features)
 
 
-    def visit_event(self, name, info, ifcond, arg_type, boxed):
+    def visit_event(self, name, info, ifcond, features, arg_type, boxed):
         print('event %s %s' % (name, arg_type and arg_type.name))
         print('event %s %s' % (name, arg_type and arg_type.name))
         print('    boxed=%s' % boxed)
         print('    boxed=%s' % boxed)
         self._print_if(ifcond)
         self._print_if(ifcond)
+        self._print_features(features)
 
 
     @staticmethod
     @staticmethod
     def _print_variants(variants):
     def _print_variants(variants):

+ 1 - 5
tests/test-qmp-cmds.c

@@ -45,7 +45,7 @@ void qmp_user_def_cmd1(UserDefOne * ud1, Error **errp)
 {
 {
 }
 }
 
 
-void qmp_test_features(FeatureStruct0 *fs0, FeatureStruct1 *fs1,
+void qmp_test_features0(FeatureStruct0 *fs0, FeatureStruct1 *fs1,
                        FeatureStruct2 *fs2, FeatureStruct3 *fs3,
                        FeatureStruct2 *fs2, FeatureStruct3 *fs3,
                        FeatureStruct4 *fs4, CondFeatureStruct1 *cfs1,
                        FeatureStruct4 *fs4, CondFeatureStruct1 *cfs1,
                        CondFeatureStruct2 *cfs2, CondFeatureStruct3 *cfs3,
                        CondFeatureStruct2 *cfs2, CondFeatureStruct3 *cfs3,
@@ -53,10 +53,6 @@ void qmp_test_features(FeatureStruct0 *fs0, FeatureStruct1 *fs1,
 {
 {
 }
 }
 
 
-void qmp_test_command_features0(Error **errp)
-{
-}
-
 void qmp_test_command_features1(Error **errp)
 void qmp_test_command_features1(Error **errp)
 {
 {
 }
 }