qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH v4 1/2] qapi: allow empty branches in flat union


From: Markus Armbruster
Subject: Re: [Qemu-devel] [PATCH v4 1/2] qapi: allow empty branches in flat unions
Date: Thu, 14 Jun 2018 09:19:04 +0200
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/26.1 (gnu/linux)

Anton Nefedov <address@hidden> writes:

> 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 <address@hidden>
> Reviewed-by: Eric Blake <address@hidden>
> ---
>  docs/devel/qapi-code-gen.txt                        |  8 +++++---
>  tests/qapi-schema/flat-union-incomplete-branch.json |  9 ---------
>  tests/qapi-schema/qapi-schema-test.json             |  4 ++--
>  scripts/qapi/common.py                              | 11 ++++-------
>  scripts/qapi/types.py                               |  4 +++-
>  scripts/qapi/visit.py                               | 17 +++++++++++++----
>  tests/Makefile.include                              |  1 -
>  tests/qapi-schema/flat-union-incomplete-branch.err  |  1 -
>  tests/qapi-schema/flat-union-incomplete-branch.exit |  1 -
>  tests/qapi-schema/flat-union-incomplete-branch.out  |  0
>  tests/qapi-schema/qapi-schema-test.out              |  3 ++-
>  11 files changed, 29 insertions(+), 30 deletions(-)
>  delete mode 100644 tests/qapi-schema/flat-union-incomplete-branch.json
>  delete mode 100644 tests/qapi-schema/flat-union-incomplete-branch.err
>  delete mode 100644 tests/qapi-schema/flat-union-incomplete-branch.exit
>  delete mode 100644 tests/qapi-schema/flat-union-incomplete-branch.out
>
> diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt
> index 1366228..88a70e4 100644
> --- a/docs/devel/qapi-code-gen.txt
> +++ b/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
>  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
>  then a union of structures for each branch of the struct.
>  
> diff --git a/tests/qapi-schema/flat-union-incomplete-branch.json 
> b/tests/qapi-schema/flat-union-incomplete-branch.json
> deleted file mode 100644
> index 25a411b..0000000
> --- a/tests/qapi-schema/flat-union-incomplete-branch.json
> +++ /dev/null
> @@ -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' } }
> diff --git a/tests/qapi-schema/qapi-schema-test.json 
> b/tests/qapi-schema/qapi-schema-test.json
> index 46c7282..881b53b 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json
> @@ -39,7 +39,7 @@
>              '*enum1': 'EnumOne' } }   # intentional forward reference
>  
>  { 'enum': 'EnumOne',
> -  'data': [ 'value1', 'value2', 'value3' ] }
> +  'data': [ 'value1', 'value2', 'value3', 'value4' ] }
>  
>  { 'struct': 'UserDefZero',
>    'data': { 'integer': 'int' } }
> @@ -76,7 +76,7 @@
>    'discriminator': 'enum1',
>    'data': { 'value1' : 'UserDefA',
>              'value2' : 'UserDefB',
> -            'value3' : 'UserDefB' } }
> +            'value3' : 'UserDefB' } } # skip 'value4'

Perhaps

               'value3' : 'UserDefB'
                # 'value4' defaults to empty
             } }

would be clearer.

>  
>  { 'struct': 'UserDefUnionBase',
>    'base': 'UserDefZero',
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index 2462fc0..0f3d89b 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -779,13 +779,6 @@ def check_union(expr, info):
>                                     "enum '%s'"
>                                     % (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):
>      name = expr['alternate']
> @@ -1644,6 +1637,10 @@ class QAPISchema(object):
>          if tag_name:
>              variants = [self._make_variant(key, value)
>                          for (key, value) in data.items()]
> +            # branches that are not explicitly covered get an empty type
> +            variants += [self._make_variant(key, 'q_empty')
> +                         for key in 
> discriminator_find_enum_define(expr)['data']

Long line, flagged by pycodestyle --diff.

> +                         if key not in data.keys()]
>              members = []
>          else:
>              variants = [self._make_simple_variant(key, value, info)

The way you desugar the union is simple enough, but it uses
discriminator_find_enum_define() outside check_exprs(), which I'd rather
avoid.  Not something you could be reasonably expected to know.

We could desugar in QAPISchemaObjectTypeVariants.check(), where we have
self.tag_member.type.values.

   @@ -1346,10 +1346,17 @@ class QAPISchemaObjectTypeVariants(object):
                v.set_owner(name)

        def check(self, schema, seen):
   -        if not self.tag_member:    # flat union
   +        if not self.tag_member: # flat union
                self.tag_member = seen[c_name(self._tag_name)]
                assert self._tag_name == self.tag_member.name
            assert isinstance(self.tag_member.type, QAPISchemaEnumType)
   +        if self._tag_name:      # flat union
   +            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:
                v.check(schema)
                # Union names must match enum values; alternate names are

Not so simple anymore.

Simpler: desugar in check_union(), i.e. replace your two hunks by just

   @@ -783,8 +783,7 @@ def check_union(expr, info):
        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))
   +                members[value] = 'q_empty'


    def check_alternate(expr, info):

What do you think?

> diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
> index 64d9c0f..1fb2b6d 100644
> --- a/scripts/qapi/types.py
> +++ b/scripts/qapi/types.py
> @@ -124,7 +124,9 @@ def gen_variants(variants):
>  ''',
>                  c_name=c_name(variants.tag_member.name))
>  
> -    for var in variants.variants:
> +    # filter out the empty types
> +    for var in filter(lambda var: var.type.name != 'q_empty',
> +                      variants.variants):

The comment's position suggests it tells what the loop does, but that's
not the case.  Let's do it the old-fashioned obvious way instead:

       for var in variants.variants:
           if var.type.name == 'q_empty':
               continue

Aside: we need to filter out q_empty, because we don't generate a C type
for it.  Perhaps generating one would be simpler, but that's beyond the
scope of this patch.

>          ret += mcgen('''
>          %(c_type)s %(c_name)s;
>  ''',
> diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
> index 5d72d89..96bd32c 100644
> --- a/scripts/qapi/visit.py
> +++ b/scripts/qapi/visit.py
> @@ -81,14 +81,23 @@ void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s 
> *obj, Error **errp)
>                       c_name=c_name(variants.tag_member.name))
>  
>          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('''

Aside: we need this, because we don't generate a visitor for q_empty.

>      case %(case)s:
>          visit_type_%(c_type)s_members(v, &obj->u.%(c_name)s, &err);
>          break;
>  ''',
> -                         case=c_enum_const(variants.tag_member.type.name,
> -                                           var.name,
> -                                           variants.tag_member.type.prefix),
> +                         case=case_str,
>                           c_type=var.type.c_name(), c_name=c_name(var.name))

Indentation's off, flagged by pycodestyle --diff.

>  
>          ret += mcgen('''
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index bb08e37..afdd0db 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -496,7 +496,6 @@ qapi-schema += flat-union-base-any.json
>  qapi-schema += flat-union-base-union.json
>  qapi-schema += flat-union-clash-member.json
>  qapi-schema += flat-union-empty.json
> -qapi-schema += flat-union-incomplete-branch.json
>  qapi-schema += flat-union-inline.json
>  qapi-schema += flat-union-int-branch.json
>  qapi-schema += flat-union-invalid-branch-key.json
> diff --git a/tests/qapi-schema/flat-union-incomplete-branch.err 
> b/tests/qapi-schema/flat-union-incomplete-branch.err
> deleted file mode 100644
> index e826bf0..0000000
> --- a/tests/qapi-schema/flat-union-incomplete-branch.err
> +++ /dev/null
> @@ -1 +0,0 @@
> -tests/qapi-schema/flat-union-incomplete-branch.json:6: Union 'TestUnion' 
> data missing 'value2' branch
> diff --git a/tests/qapi-schema/flat-union-incomplete-branch.exit 
> b/tests/qapi-schema/flat-union-incomplete-branch.exit
> deleted file mode 100644
> index d00491f..0000000
> --- a/tests/qapi-schema/flat-union-incomplete-branch.exit
> +++ /dev/null
> @@ -1 +0,0 @@
> -1
> diff --git a/tests/qapi-schema/flat-union-incomplete-branch.out 
> b/tests/qapi-schema/flat-union-incomplete-branch.out
> deleted file mode 100644
> index e69de29..0000000
> diff --git a/tests/qapi-schema/qapi-schema-test.out 
> b/tests/qapi-schema/qapi-schema-test.out
> index 542a19c..0dbcdaf 100644
> --- a/tests/qapi-schema/qapi-schema-test.out
> +++ b/tests/qapi-schema/qapi-schema-test.out
> @@ -23,7 +23,7 @@ object UserDefOne
>      base UserDefZero
>      member string: str optional=False
>      member enum1: EnumOne optional=True
> -enum EnumOne ['value1', 'value2', 'value3']
> +enum EnumOne ['value1', 'value2', 'value3', 'value4']
>  object UserDefZero
>      member integer: int optional=False
>  object UserDefTwoDictDict
> @@ -52,6 +52,7 @@ object UserDefFlatUnion
>      case value1: UserDefA
>      case value2: UserDefB
>      case value3: UserDefB
> +    case value4: q_empty
>  object UserDefUnionBase
>      base UserDefZero
>      member string: str optional=False



reply via email to

[Prev in Thread] Current Thread [Next in Thread]