فهرست منبع

qapi: allow empty branches in flat unions

It often happens that just a few discriminator values imply extra data in
a flat union. Existing checks did not make possible to leave other values
uncovered. Such cases had to be worked around by either stating a dummy
(empty) type or introducing another (subset) discriminator enumeration.

Both options create redundant entities in qapi files for little profit.

With this patch it is not necessary anymore to add designated union
fields for every possible value of a discriminator enumeration.

Signed-off-by: Anton Nefedov <anton.nefedov@virtuozzo.com>
Message-Id: <1529311206-76847-2-git-send-email-anton.nefedov@virtuozzo.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Anton Nefedov 7 سال پیش
والد
کامیت
800877bb16

+ 5 - 3
docs/devel/qapi-code-gen.txt

@@ -496,9 +496,11 @@ Resulting in these JSON objects:
 
 
 Notice that in a flat union, the discriminator name is controlled by
 Notice that in a flat union, the discriminator name is controlled by
 the user, but because it must map to a base member with enum type, the
 the user, but because it must map to a base member with enum type, the
-code generator can ensure that branches exist for all values of the
-enum (although the order of the keys need not match the declaration of
-the enum).  In the resulting generated C data types, a flat union is
+code generator ensures that branches match the existing values of the
+enum. The order of the keys need not match the declaration of the enum.
+The keys need not cover all possible enum values. Omitted enum values
+are still valid branches that add no additional members to the data type.
+In the resulting generated C data types, a flat union is
 represented as a struct with the base members included directly, and
 represented as a struct with the base members included directly, and
 then a union of structures for each branch of the struct.
 then a union of structures for each branch of the struct.
 
 

+ 8 - 7
scripts/qapi/common.py

@@ -779,13 +779,6 @@ def check_union(expr, info):
                                    "enum '%s'"
                                    "enum '%s'"
                                    % (key, enum_define['enum']))
                                    % (key, enum_define['enum']))
 
 
-    # If discriminator is user-defined, ensure all values are covered
-    if enum_define:
-        for value in enum_define['data']:
-            if value not in members.keys():
-                raise QAPISemError(info, "Union '%s' data missing '%s' branch"
-                                   % (name, value))
-
 
 
 def check_alternate(expr, info):
 def check_alternate(expr, info):
     name = expr['alternate']
     name = expr['alternate']
@@ -1357,6 +1350,14 @@ def check(self, schema, seen):
             self.tag_member = seen[c_name(self._tag_name)]
             self.tag_member = seen[c_name(self._tag_name)]
             assert self._tag_name == self.tag_member.name
             assert self._tag_name == self.tag_member.name
         assert isinstance(self.tag_member.type, QAPISchemaEnumType)
         assert isinstance(self.tag_member.type, QAPISchemaEnumType)
+        if self._tag_name:    # flat union
+            # branches that are not explicitly covered get an empty type
+            cases = set([v.name for v in self.variants])
+            for val in self.tag_member.type.values:
+                if val.name not in cases:
+                    v = QAPISchemaObjectTypeVariant(val.name, 'q_empty')
+                    v.set_owner(self.tag_member.owner)
+                    self.variants.append(v)
         for v in self.variants:
         for v in self.variants:
             v.check(schema)
             v.check(schema)
             # Union names must match enum values; alternate names are
             # Union names must match enum values; alternate names are

+ 2 - 0
scripts/qapi/types.py

@@ -125,6 +125,8 @@ def gen_variants(variants):
                 c_name=c_name(variants.tag_member.name))
                 c_name=c_name(variants.tag_member.name))
 
 
     for var in variants.variants:
     for var in variants.variants:
+        if var.type.name == 'q_empty':
+            continue
         ret += mcgen('''
         ret += mcgen('''
         %(c_type)s %(c_name)s;
         %(c_type)s %(c_name)s;
 ''',
 ''',

+ 14 - 5
scripts/qapi/visit.py

@@ -81,15 +81,24 @@ def gen_visit_object_members(name, base, members, variants):
                      c_name=c_name(variants.tag_member.name))
                      c_name=c_name(variants.tag_member.name))
 
 
         for var in variants.variants:
         for var in variants.variants:
-            ret += mcgen('''
+            case_str = c_enum_const(variants.tag_member.type.name,
+                                    var.name,
+                                    variants.tag_member.type.prefix)
+            if var.type.name == 'q_empty':
+                # valid variant and nothing to do
+                ret += mcgen('''
+    case %(case)s:
+        break;
+''',
+                             case=case_str)
+            else:
+                ret += mcgen('''
     case %(case)s:
     case %(case)s:
         visit_type_%(c_type)s_members(v, &obj->u.%(c_name)s, &err);
         visit_type_%(c_type)s_members(v, &obj->u.%(c_name)s, &err);
         break;
         break;
 ''',
 ''',
-                         case=c_enum_const(variants.tag_member.type.name,
-                                           var.name,
-                                           variants.tag_member.type.prefix),
-                         c_type=var.type.c_name(), c_name=c_name(var.name))
+                             case=case_str,
+                             c_type=var.type.c_name(), c_name=c_name(var.name))
 
 
         ret += mcgen('''
         ret += mcgen('''
     default:
     default:

+ 0 - 1
tests/Makefile.include

@@ -499,7 +499,6 @@ qapi-schema += flat-union-base-any.json
 qapi-schema += flat-union-base-union.json
 qapi-schema += flat-union-base-union.json
 qapi-schema += flat-union-clash-member.json
 qapi-schema += flat-union-clash-member.json
 qapi-schema += flat-union-empty.json
 qapi-schema += flat-union-empty.json
-qapi-schema += flat-union-incomplete-branch.json
 qapi-schema += flat-union-inline.json
 qapi-schema += flat-union-inline.json
 qapi-schema += flat-union-int-branch.json
 qapi-schema += flat-union-int-branch.json
 qapi-schema += flat-union-invalid-branch-key.json
 qapi-schema += flat-union-invalid-branch-key.json

+ 0 - 1
tests/qapi-schema/flat-union-incomplete-branch.err

@@ -1 +0,0 @@
-tests/qapi-schema/flat-union-incomplete-branch.json:6: Union 'TestUnion' data missing 'value2' branch

+ 0 - 1
tests/qapi-schema/flat-union-incomplete-branch.exit

@@ -1 +0,0 @@
-1

+ 0 - 9
tests/qapi-schema/flat-union-incomplete-branch.json

@@ -1,9 +0,0 @@
-# we require all branches of the union to be covered
-{ 'enum': 'TestEnum',
-  'data': [ 'value1', 'value2' ] }
-{ 'struct': 'TestTypeA',
-  'data': { 'string': 'str' } }
-{ 'union': 'TestUnion',
-  'base': { 'type': 'TestEnum' },
-  'discriminator': 'type',
-  'data': { 'value1': 'TestTypeA' } }

+ 0 - 0
tests/qapi-schema/flat-union-incomplete-branch.out


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

@@ -39,7 +39,7 @@
             '*enum1': 'EnumOne' } }   # intentional forward reference
             '*enum1': 'EnumOne' } }   # intentional forward reference
 
 
 { 'enum': 'EnumOne',
 { 'enum': 'EnumOne',
-  'data': [ 'value1', 'value2', 'value3' ] }
+  'data': [ 'value1', 'value2', 'value3', 'value4' ] }
 
 
 { 'struct': 'UserDefZero',
 { 'struct': 'UserDefZero',
   'data': { 'integer': 'int' } }
   'data': { 'integer': 'int' } }
@@ -76,7 +76,9 @@
   'discriminator': 'enum1',
   'discriminator': 'enum1',
   'data': { 'value1' : 'UserDefA',
   'data': { 'value1' : 'UserDefA',
             'value2' : 'UserDefB',
             'value2' : 'UserDefB',
-            'value3' : 'UserDefB' } }
+            'value3' : 'UserDefB'
+            # 'value4' defaults to empty
+  } }
 
 
 { 'struct': 'UserDefUnionBase',
 { 'struct': 'UserDefUnionBase',
   'base': 'UserDefZero',
   'base': 'UserDefZero',

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

@@ -23,7 +23,7 @@ object UserDefOne
     base UserDefZero
     base UserDefZero
     member string: str optional=False
     member string: str optional=False
     member enum1: EnumOne optional=True
     member enum1: EnumOne optional=True
-enum EnumOne ['value1', 'value2', 'value3']
+enum EnumOne ['value1', 'value2', 'value3', 'value4']
 object UserDefZero
 object UserDefZero
     member integer: int optional=False
     member integer: int optional=False
 object UserDefTwoDictDict
 object UserDefTwoDictDict
@@ -52,6 +52,7 @@ object UserDefFlatUnion
     case value1: UserDefA
     case value1: UserDefA
     case value2: UserDefB
     case value2: UserDefB
     case value3: UserDefB
     case value3: UserDefB
+    case value4: q_empty
 object UserDefUnionBase
 object UserDefUnionBase
     base UserDefZero
     base UserDefZero
     member string: str optional=False
     member string: str optional=False