@@ -414,7 +414,8 @@
'stdio': 'ChardevStdio',
'console': 'ChardevCommon',
'spicevmc': { 'type': 'ChardevSpiceChannel',
- 'if': 'defined(CONFIG_SPICE)' },
+ 'if': 'defined(CONFIG_SPICE)',
+ 'allow-flat': false },
'spiceport': { 'type': 'ChardevSpicePort',
'if': 'defined(CONFIG_SPICE)' },
'vc': 'ChardevVC',
@@ -295,7 +295,9 @@ Syntax:
'*features': FEATURES }
BRANCHES = { BRANCH, ... }
BRANCH = STRING : TYPE-REF
- | STRING : { 'type': TYPE-REF, '*if': COND }
+ | STRING : { 'type': TYPE-REF,
+ '*if': COND,
+ '*allow-flat': BOOL }
Member 'union' names the union type.
@@ -334,6 +336,13 @@ values to data types like in this example:
'data': { 'file': 'BlockdevOptionsFile',
'qcow2': 'BlockdevOptionsQcow2' } }
+Simple unions can support both wrapped and flat representation of
+branches that have a struct type, unless it is explicitly disabled in
+the schema with 'allow-flat': false. Branches of other types are
+always wrapped. Which representation is used in the generated visitor
+C code can be configured per visitor. Flat representation is
+appropriate when parsing command line options.
+
In the Client JSON Protocol, all simple union branches have wrapped
representation, as in these examples:
@@ -121,6 +121,9 @@ struct Visitor
/* Must be set */
void (*free)(Visitor *v);
+
+ /* Set to true to make simple unions look like flat unions */
+ bool flat_simple_unions;
};
#endif
@@ -496,6 +496,16 @@ bool visit_is_input(Visitor *v);
*/
bool visit_is_dealloc(Visitor *v);
+/*
+ * Check if simple unions should be treated as flat.
+ */
+bool visit_flat_simple_unions(Visitor *v);
+
+/*
+ * Set if simple unions should be treated as flat.
+ */
+void visit_set_flat_simple_unions(Visitor *v, bool flat);
+
/*** Visiting built-in types ***/
/*
@@ -145,6 +145,16 @@ bool visit_is_dealloc(Visitor *v)
return v->type == VISITOR_DEALLOC;
}
+bool visit_flat_simple_unions(Visitor *v)
+{
+ return v->flat_simple_unions;
+}
+
+void visit_set_flat_simple_unions(Visitor *v, bool flat)
+{
+ v->flat_simple_unions = flat;
+}
+
bool visit_type_int(Visitor *v, const char *name, int64_t *obj, Error **errp)
{
assert(obj);
@@ -248,7 +248,12 @@ def check_union(expr, info):
for (key, value) in members.items():
source = "'data' member '%s'" % key
check_name_str(key, info, source)
- check_keys(value, info, source, ['type'], ['if'])
+ check_keys(value, info, source, ['type'], ['if', 'allow-flat'])
+ if 'allow-flat' in value:
+ if discriminator is not None:
+ raise QAPISemError(info, "'allow-flat' requires simple union")
+ if not isinstance(value['allow-flat'], bool):
+ raise QAPISemError(info, "'allow-flat' must be a boolean")
check_if(value, info, source)
check_type(value['type'], info, source, allow_array=not base)
@@ -612,7 +612,18 @@ class QAPISchemaVariants:
# branch do not affect another branch. Variants that are
# never flat don't even conflict with the base.
if isinstance(v.type, QAPISchemaObjectType):
- v.type.check_clash(info, dict(seen) if v.flat else {})
+ nested_seen = dict(seen) if v.flat else {}
+ v.type.check_clash(info, nested_seen)
+
+ # Make sure that the presence of a 'data' member in
+ # some input always implies wrapped representation so
+ # that visitors can unambiguously accept both forms.
+ if v.wrapped and 'data' in nested_seen:
+ raise QAPISemError(
+ info,
+ "%s collides with flat representation of %s"
+ % (nested_seen['data'].describe(info),
+ v.describe(info)))
class QAPISchemaMember:
@@ -721,9 +732,9 @@ class QAPISchemaVariant(QAPISchemaObjectTypeMember):
self.wrapped = bool(wrapper_type)
self.wrapper_type = wrapper_type
- # For now, unions are either flat or wrapped, never both
+ # Unions that are both flat and wrapped can look like either one,
+ # depending on Visitor.flat_simple_unions
assert self.flat or self.wrapped
- assert not (self.flat and self.wrapped)
def check(self, schema):
super().check(schema)
@@ -1038,7 +1049,7 @@ class QAPISchema:
def _make_variant(self, case, typ, ifcond, info):
return QAPISchemaVariant(case, info, typ, ifcond)
- def _make_simple_variant(self, union_name, case, typ, ifcond, info):
+ def _make_simple_variant(self, union_name, case, typ, ifcond, flat, info):
if isinstance(typ, list):
assert len(typ) == 1
typ = self._make_array_type(typ[0], info)
@@ -1049,7 +1060,14 @@ class QAPISchema:
wrapper_member = self._make_member('data', typ, None, None, info)
wrapper_type = QAPISchemaObjectType(wrapper_name, info, None, ifcond,
None, None, [wrapper_member], None)
- return QAPISchemaVariant(case, info, typ, ifcond, flat=False,
+
+ # Default to allowing flat representation for object types.
+ # Other types require a wrapper, so disable flat for them by default.
+ if flat is None:
+ variant_type = self.resolve_type(typ, info, 'variant type')
+ flat = isinstance(variant_type, QAPISchemaObjectType)
+
+ return QAPISchemaVariant(case, info, typ, ifcond, flat=flat,
wrapper_type=wrapper_type)
def _def_union_type(self, expr, info, doc):
@@ -1070,9 +1088,13 @@ class QAPISchema:
for (key, value) in data.items()]
members = []
else:
- variants = [self._make_simple_variant(name, key, value['type'],
- value.get('if'), info)
- for (key, value) in data.items()]
+ variants = [
+ self._make_simple_variant(name, key, value['type'],
+ value.get('if'),
+ value.get('allow-flat'),
+ 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)
@@ -127,23 +127,31 @@ bool visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
case=case_str,
c_type=var.type.c_name(), c_name=c_name(var.name))
elif var.wrapped:
+ if var.flat:
+ cond = "!visit_flat_simple_unions(v)"
+ else:
+ cond = "true"
ret += mcgen('''
case %(case)s:
{
bool ok;
- if (!visit_start_struct(v, "data", NULL, 0, errp)) {
- return false;
+ if (%(cond)s) {
+ if (!visit_start_struct(v, "data", NULL, 0, errp)) {
+ return false;
+ }
}
ok = visit_type_%(c_type)s_members(v, &obj->u.%(c_name)s, errp);
- if (ok) {
- ok = visit_check_struct(v, errp);
+ if (%(cond)s) {
+ if (ok) {
+ ok = visit_check_struct(v, errp);
+ }
+ visit_end_struct(v, NULL);
}
- visit_end_struct(v, NULL);
return ok;
}
''',
- case=case_str,
+ case=case_str, cond=cond,
c_type=var.type.c_name(), c_name=c_name(var.name))
else:
This extends the Visitor interface with an option that can enable flat representation (without the 'data' wrapper) for simple unions. This way, a command line parser can enable a more user friendly syntax while QMP doesn't enable the option and uses the same representation as before. We need to disable flat representation for ChardevSpiceChannel, which has a 'type' option that conflicts with the ChardevBackend 'type'. This variant will get nesting even when flat parsing is enabled in the Visitor. Signed-off-by: Kevin Wolf <kwolf@redhat.com> --- qapi/char.json | 3 ++- docs/devel/qapi-code-gen.txt | 11 ++++++++++- include/qapi/visitor-impl.h | 3 +++ include/qapi/visitor.h | 10 ++++++++++ qapi/qapi-visit-core.c | 10 ++++++++++ scripts/qapi/expr.py | 7 ++++++- scripts/qapi/schema.py | 38 ++++++++++++++++++++++++++++-------- scripts/qapi/visit.py | 20 +++++++++++++------ 8 files changed, 85 insertions(+), 17 deletions(-)