qapi: Segregate anonymous unions into alternates in generator

Special-casing 'discriminator == {}' for handling anonymous unions
is getting awkward; since this particular type is not always a
dictionary on the wire, it is easier to treat it as a completely
different class of type, "alternate", so that if a type is listed
in the union_types array, we know it is not an anonymous union.

This patch just further segregates union handling, to make sure that
anonymous unions are not stored in union_types, and splitting up
check_union() into separate functions.  A future patch will change
the qapi grammar, and having the segregation already in place will
make it easier to deal with the distinct meta-type.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
This commit is contained in:
Eric Blake 2015-05-04 09:05:10 -06:00 committed by Markus Armbruster
parent 268a1c5eb1
commit 811d04fd0c
5 changed files with 62 additions and 40 deletions

View File

@ -170,7 +170,7 @@ def generate_enum(name, values):
return lookup_decl + enum_decl return lookup_decl + enum_decl
def generate_anon_union_qtypes(expr): def generate_alternate_qtypes(expr):
name = expr['union'] name = expr['union']
members = expr['data'] members = expr['data']
@ -181,7 +181,7 @@ def generate_anon_union_qtypes(expr):
name=name) name=name)
for key in members: for key in members:
qtype = find_anonymous_member_qtype(members[key]) qtype = find_alternate_member_qtype(members[key])
assert qtype, "Invalid anonymous union member" assert qtype, "Invalid anonymous union member"
ret += mcgen(''' ret += mcgen('''
@ -408,7 +408,7 @@ def maybe_open(really, name, opt):
fdef.write(generate_enum_lookup('%sKind' % expr['union'], fdef.write(generate_enum_lookup('%sKind' % expr['union'],
expr['data'].keys())) expr['data'].keys()))
if expr.get('discriminator') == {}: if expr.get('discriminator') == {}:
fdef.write(generate_anon_union_qtypes(expr)) fdef.write(generate_alternate_qtypes(expr))
else: else:
continue continue
fdecl.write(ret) fdecl.write(ret)

View File

@ -237,7 +237,7 @@ def generate_visit_enum(name, members):
''', ''',
name=name) name=name)
def generate_visit_anon_union(name, members): def generate_visit_alternate(name, members):
ret = mcgen(''' ret = mcgen('''
void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp) void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **errp)
@ -302,7 +302,7 @@ def generate_visit_union(expr):
if discriminator == {}: if discriminator == {}:
assert not base assert not base
return generate_visit_anon_union(name, members) return generate_visit_alternate(name, members)
enum_define = discriminator_find_enum_define(expr) enum_define = discriminator_find_enum_define(expr)
if enum_define: if enum_define:

View File

@ -224,21 +224,16 @@ def find_base_fields(base):
return None return None
return base_struct_define['data'] return base_struct_define['data']
# Return the qtype of an anonymous union branch, or None on error. # Return the qtype of an alternate branch, or None on error.
def find_anonymous_member_qtype(qapi_type): def find_alternate_member_qtype(qapi_type):
if builtin_types.has_key(qapi_type): if builtin_types.has_key(qapi_type):
return builtin_types[qapi_type] return builtin_types[qapi_type]
elif find_struct(qapi_type): elif find_struct(qapi_type):
return "QTYPE_QDICT" return "QTYPE_QDICT"
elif find_enum(qapi_type): elif find_enum(qapi_type):
return "QTYPE_QSTRING" return "QTYPE_QSTRING"
else: elif find_union(qapi_type):
union = find_union(qapi_type) return "QTYPE_QDICT"
if union:
discriminator = union.get('discriminator')
if discriminator == {}:
return None
return "QTYPE_QDICT"
return None return None
# Return the discriminator enum define if discriminator is specified as an # Return the discriminator enum define if discriminator is specified as an
@ -276,7 +271,6 @@ def check_union(expr, expr_info):
discriminator = expr.get('discriminator') discriminator = expr.get('discriminator')
members = expr['data'] members = expr['data']
values = { 'MAX': '(automatic)' } values = { 'MAX': '(automatic)' }
types_seen = {}
# If the object has a member 'base', its value must name a complex type, # If the object has a member 'base', its value must name a complex type,
# and there must be a discriminator. # and there must be a discriminator.
@ -286,13 +280,15 @@ def check_union(expr, expr_info):
"Union '%s' requires a discriminator to go " "Union '%s' requires a discriminator to go "
"along with base" %name) "along with base" %name)
# If the union object has no member 'discriminator', it's a # Two types of unions, determined by discriminator.
# simple union. If 'discriminator' is {}, it is an anonymous union. assert discriminator != {}
if discriminator is None or discriminator == {}:
# With no discriminator it is a simple union.
if discriminator is None:
enum_define = None enum_define = None
if base is not None: if base is not None:
raise QAPIExprError(expr_info, raise QAPIExprError(expr_info,
"Union '%s' must not have a base" "Simple union '%s' must not have a base"
% name) % name)
# Else, it's a flat union. # Else, it's a flat union.
@ -347,24 +343,46 @@ def check_union(expr, expr_info):
% (name, key, values[c_key])) % (name, key, values[c_key]))
values[c_key] = key values[c_key] = key
# Ensure anonymous unions have no type conflicts. def check_alternate(expr, expr_info):
if discriminator == {}: name = expr['union']
if isinstance(value, list): base = expr.get('base')
raise QAPIExprError(expr_info, discriminator = expr.get('discriminator')
"Anonymous union '%s' member '%s' must " members = expr['data']
"not be array type" % (name, key)) values = { 'MAX': '(automatic)' }
qtype = find_anonymous_member_qtype(value) types_seen = {}
if not qtype:
raise QAPIExprError(expr_info,
"Anonymous union '%s' member '%s' has "
"invalid type '%s'" % (name, key, value))
if qtype in types_seen:
raise QAPIExprError(expr_info,
"Anonymous union '%s' member '%s' can't "
"be distinguished from member '%s'"
% (name, key, types_seen[qtype]))
types_seen[qtype] = key
assert discriminator == {}
if base is not None:
raise QAPIExprError(expr_info,
"Anonymous union '%s' must not have a base"
% name)
# Check every branch
for (key, value) in members.items():
# Check for conflicts in the generated enum
c_key = _generate_enum_string(key)
if c_key in values:
raise QAPIExprError(expr_info,
"Anonymous union '%s' member '%s' clashes "
"with '%s'" % (name, key, values[c_key]))
values[c_key] = key
# Ensure alternates have no type conflicts.
if isinstance(value, list):
raise QAPIExprError(expr_info,
"Anonymous union '%s' member '%s' must "
"not be array type" % (name, key))
qtype = find_alternate_member_qtype(value)
if not qtype:
raise QAPIExprError(expr_info,
"Anonymous union '%s' member '%s' has "
"invalid type '%s'" % (name, key, value))
if qtype in types_seen:
raise QAPIExprError(expr_info,
"Anonymous union '%s' member '%s' can't "
"be distinguished from member '%s'"
% (name, key, types_seen[qtype]))
types_seen[qtype] = key
def check_enum(expr, expr_info): def check_enum(expr, expr_info):
name = expr['enum'] name = expr['enum']
@ -394,7 +412,10 @@ def check_exprs(schema):
if expr.has_key('enum'): if expr.has_key('enum'):
check_enum(expr, info) check_enum(expr, info)
elif expr.has_key('union'): elif expr.has_key('union'):
check_union(expr, info) if expr.get('discriminator') == {}:
check_alternate(expr, info)
else:
check_union(expr, info)
elif expr.has_key('event'): elif expr.has_key('event'):
check_event(expr, info) check_event(expr, info)
@ -536,7 +557,8 @@ def find_struct(name):
def add_union(definition): def add_union(definition):
global union_types global union_types
union_types.append(definition) if definition.get('discriminator') != {}:
union_types.append(definition)
def find_union(name): def find_union(name):
global union_types global union_types

View File

@ -1 +1 @@
tests/qapi-schema/alternate-base.json:4: Union 'MyUnion' must not have a base tests/qapi-schema/alternate-base.json:4: Anonymous union 'MyUnion' must not have a base

View File

@ -1 +1 @@
tests/qapi-schema/alternate-clash.json:2: Union 'Union1' member 'ONE' clashes with 'one' tests/qapi-schema/alternate-clash.json:2: Anonymous union 'Union1' member 'ONE' clashes with 'one'