diff mbox

[v5,10/28] qapi: Segregate anonymous unions into alternates in generator

Message ID 1427227433-5030-11-git-send-email-eblake@redhat.com
State New
Headers show

Commit Message

Eric Blake March 24, 2015, 8:03 p.m. UTC
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>
---
 scripts/qapi-types.py |  6 ++--
 scripts/qapi-visit.py |  4 +--
 scripts/qapi.py       | 94 +++++++++++++++++++++++++++++----------------------
 3 files changed, 58 insertions(+), 46 deletions(-)

Comments

Markus Armbruster March 26, 2015, 2:47 p.m. UTC | #1
Eric Blake <eblake@redhat.com> writes:

> 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>
> ---
>  scripts/qapi-types.py |  6 ++--
>  scripts/qapi-visit.py |  4 +--
>  scripts/qapi.py       | 94 +++++++++++++++++++++++++++++----------------------
>  3 files changed, 58 insertions(+), 46 deletions(-)
>
> diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
> index 2390887..c9e0201 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi-types.py
> @@ -170,7 +170,7 @@ typedef enum %(name)s
>
>      return lookup_decl + enum_decl
>
> -def generate_anon_union_qtypes(expr):
> +def generate_alternate_qtypes(expr):
>
>      name = expr['union']
>      members = expr['data']
> @@ -181,7 +181,7 @@ const int %(name)s_qtypes[QTYPE_MAX] = {
>      name=name)
>
>      for key in members:
> -        qtype = find_anonymous_member_qtype(members[key])
> +        qtype = find_alternate_member_qtype(members[key])
>          assert qtype, "Invalid anonymous union member"
>
>          ret += mcgen('''
> @@ -408,7 +408,7 @@ for expr in exprs:
>              fdef.write(generate_enum_lookup('%sKind' % expr['union'],
>                                              expr['data'].keys()))
>          if expr.get('discriminator') == {}:
> -            fdef.write(generate_anon_union_qtypes(expr))
> +            fdef.write(generate_alternate_qtypes(expr))
>      else:
>          continue
>      fdecl.write(ret)
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 3f82bd4..77b0a1f 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -237,7 +237,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s *obj, const char *name, Error **er
>  ''',
>                   name=name)
>
> -def generate_visit_anon_union(name, members):
> +def generate_visit_alternate(name, members):
>      ret = mcgen('''
>
>  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 == {}:
>          assert not base
> -        return generate_visit_anon_union(name, members)
> +        return generate_visit_alternate(name, members)
>
>      enum_define = discriminator_find_enum_define(expr)
>      if enum_define:
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 39cc88b..17252e9 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -224,21 +224,16 @@ def find_base_fields(base):
>          return None
>      return base_struct_define['data']
>
> -# Return the qtype of an anonymous union branch, or None on error.
> -def find_anonymous_member_qtype(qapi_type):
> +# Return the qtype of an alternate branch, or None on error.
> +def find_alternate_member_qtype(qapi_type):
>      if builtin_types.has_key(qapi_type):
>          return builtin_types[qapi_type]
>      elif find_struct(qapi_type):
>          return "QTYPE_QDICT"
>      elif find_enum(qapi_type):
>          return "QTYPE_QSTRING"
> -    else:
> -        union = find_union(qapi_type)
> -        if union:
> -            discriminator = union.get('discriminator')
> -            if discriminator == {}:
> -                return None
> -            return "QTYPE_QDICT"
> +    elif find_union(qapi_type):
> +        return "QTYPE_QDICT"
>      return None
>
>  # Return the discriminator enum define if discriminator is specified as an
> @@ -276,22 +271,13 @@ def check_union(expr, expr_info):
>      discriminator = expr.get('discriminator')
>      members = expr['data']
>      values = { 'MAX': '(automatic)' }
> -    types_seen = {}
>
> -    # Three types of unions, determined by discriminator.
> +    # Two types of unions, determined by discriminator.
> +    assert discriminator != {}
>
> -    # If the value of member 'discriminator' is {}, it's an
> -    # anonymous union, and must not have a base.
> -    if discriminator == {}:
> -        enum_define = None
> -        if base:
> -            raise QAPIExprError(expr_info,
> -                                "Anonymous union '%s' must not have a base"
> -                                % name)
> -
> -    # Else if the union object has no member 'discriminator', it's an
> +    # If the union object has no member 'discriminator', it's an
>      # ordinary union.  For now, it must not have a base.
> -    elif not discriminator:
> +    if not discriminator:
>          enum_define = None
>          if base:
>              raise QAPIExprError(expr_info,
> @@ -346,24 +332,46 @@ def check_union(expr, expr_info):
>                                      % (name, key, values[c_key]))
>              values[c_key] = key
>
> -        # Ensure anonymous unions have no type conflicts.
> -        if discriminator == {}:
> -            if isinstance(value, list):
> -                raise QAPIExprError(expr_info,
> -                                    "Anonymous union '%s' member '%s' must "
> -                                    "not be array type" % (name, key))
> -            qtype = find_anonymous_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' has "
> -                                    "same QObject type as member '%s'"
> -                                    % (name, key, types_seen[qtype]))
> -            types_seen[qtype] = key
> +def check_alternate(expr, expr_info):
> +    name = expr['union']
> +    base = expr.get('base')
> +    discriminator = expr.get('discriminator')
> +    members = expr['data']
> +    values = { 'MAX': '(automatic)' }
> +    types_seen = {}
>
> +    assert discriminator == {}
> +    if base:
> +        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,
> +                                "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' has "
> +                                "same QObject type as member '%s'"
> +                                % (name, key, types_seen[qtype]))
> +        types_seen[qtype] = key
>
>  def check_enum(expr, expr_info):
>      name = expr['enum']
> @@ -393,7 +401,10 @@ def check_exprs(schema):
>          if expr.has_key('enum'):
>              check_enum(expr, info)
>          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'):
>              check_event(expr, info)
>
> @@ -535,7 +546,8 @@ def find_struct(name):
>
>  def add_union(definition):
>      global union_types
> -    union_types.append(definition)
> +    if definition.get('discriminator') != {}:
> +        union_types.append(definition)
>
>  def find_union(name):
>      global union_types

This is the only unobvious hunk.

union_types is used only through find_union.  The hunk makes
find_union(N) return None when N names an anonymous union.

find_union() is used in two places:

* find_alternate_member_qtype()

  Patched further up.  It really wants only non-anonymous unions, and
  this change to find_union() renders its check for anonymous unions
  superfluous.  Good.

* generate_visit_alternate()

  Asserts that each member's type is either a built-in type, a complex
  type, a union type, or an enum type.

  The change relaxes the assertion not to trigger on anonymous union
  types.  Why is that okay?
Eric Blake March 26, 2015, 3:26 p.m. UTC | #2
On 03/26/2015 08:47 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> 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>
>> ---

>> @@ -535,7 +546,8 @@ def find_struct(name):
>>
>>  def add_union(definition):
>>      global union_types
>> -    union_types.append(definition)
>> +    if definition.get('discriminator') != {}:
>> +        union_types.append(definition)
>>
>>  def find_union(name):
>>      global union_types
> 
> This is the only unobvious hunk.
> 
> union_types is used only through find_union.  The hunk makes
> find_union(N) return None when N names an anonymous union.
> 
> find_union() is used in two places:
> 
> * find_alternate_member_qtype()
> 
>   Patched further up.  It really wants only non-anonymous unions, and
>   this change to find_union() renders its check for anonymous unions
>   superfluous.  Good.
> 
> * generate_visit_alternate()
> 
>   Asserts that each member's type is either a built-in type, a complex
>   type, a union type, or an enum type.
> 
>   The change relaxes the assertion not to trigger on anonymous union
>   types.  Why is that okay?

No, the change tightens the assertion so that it will now fail on an
anonymous union nested as a branch of another anonymous union (where
before it could silently pass the assertion), because the anonymous
union is no longer found by find_union().  And this is okay because the
earlier change to find_alternate_member_qtype means that we don't allow
nested anonymous unions, so making the assertion fail if an anonymous
union gets through anyway is correct.
Markus Armbruster March 27, 2015, 12:32 p.m. UTC | #3
Eric Blake <eblake@redhat.com> writes:

> On 03/26/2015 08:47 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> 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>
>>> ---
>
>>> @@ -535,7 +546,8 @@ def find_struct(name):
>>>
>>>  def add_union(definition):
>>>      global union_types
>>> -    union_types.append(definition)
>>> +    if definition.get('discriminator') != {}:
>>> +        union_types.append(definition)
>>>
>>>  def find_union(name):
>>>      global union_types
>> 
>> This is the only unobvious hunk.
>> 
>> union_types is used only through find_union.  The hunk makes
>> find_union(N) return None when N names an anonymous union.
>> 
>> find_union() is used in two places:
>> 
>> * find_alternate_member_qtype()
>> 
>>   Patched further up.  It really wants only non-anonymous unions, and
>>   this change to find_union() renders its check for anonymous unions
>>   superfluous.  Good.
>> 
>> * generate_visit_alternate()
>> 
>>   Asserts that each member's type is either a built-in type, a complex
>>   type, a union type, or an enum type.
>> 
>>   The change relaxes the assertion not to trigger on anonymous union
>>   types.  Why is that okay?
>
> No, the change tightens the assertion so that it will now fail on an
> anonymous union nested as a branch of another anonymous union (where
> before it could silently pass the assertion), because the anonymous
> union is no longer found by find_union().  And this is okay because the
> earlier change to find_alternate_member_qtype means that we don't allow
> nested anonymous unions, so making the assertion fail if an anonymous
> union gets through anyway is correct.

Thanks for unconfusing me.

Reviewed-by: Markus Armbruster <armbru@redhat.com>
diff mbox

Patch

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index 2390887..c9e0201 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -170,7 +170,7 @@  typedef enum %(name)s

     return lookup_decl + enum_decl

-def generate_anon_union_qtypes(expr):
+def generate_alternate_qtypes(expr):

     name = expr['union']
     members = expr['data']
@@ -181,7 +181,7 @@  const int %(name)s_qtypes[QTYPE_MAX] = {
     name=name)

     for key in members:
-        qtype = find_anonymous_member_qtype(members[key])
+        qtype = find_alternate_member_qtype(members[key])
         assert qtype, "Invalid anonymous union member"

         ret += mcgen('''
@@ -408,7 +408,7 @@  for expr in exprs:
             fdef.write(generate_enum_lookup('%sKind' % expr['union'],
                                             expr['data'].keys()))
         if expr.get('discriminator') == {}:
-            fdef.write(generate_anon_union_qtypes(expr))
+            fdef.write(generate_alternate_qtypes(expr))
     else:
         continue
     fdecl.write(ret)
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 3f82bd4..77b0a1f 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -237,7 +237,7 @@  void visit_type_%(name)s(Visitor *m, %(name)s *obj, const char *name, Error **er
 ''',
                  name=name)

-def generate_visit_anon_union(name, members):
+def generate_visit_alternate(name, members):
     ret = mcgen('''

 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 == {}:
         assert not base
-        return generate_visit_anon_union(name, members)
+        return generate_visit_alternate(name, members)

     enum_define = discriminator_find_enum_define(expr)
     if enum_define:
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 39cc88b..17252e9 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -224,21 +224,16 @@  def find_base_fields(base):
         return None
     return base_struct_define['data']

-# Return the qtype of an anonymous union branch, or None on error.
-def find_anonymous_member_qtype(qapi_type):
+# Return the qtype of an alternate branch, or None on error.
+def find_alternate_member_qtype(qapi_type):
     if builtin_types.has_key(qapi_type):
         return builtin_types[qapi_type]
     elif find_struct(qapi_type):
         return "QTYPE_QDICT"
     elif find_enum(qapi_type):
         return "QTYPE_QSTRING"
-    else:
-        union = find_union(qapi_type)
-        if union:
-            discriminator = union.get('discriminator')
-            if discriminator == {}:
-                return None
-            return "QTYPE_QDICT"
+    elif find_union(qapi_type):
+        return "QTYPE_QDICT"
     return None

 # Return the discriminator enum define if discriminator is specified as an
@@ -276,22 +271,13 @@  def check_union(expr, expr_info):
     discriminator = expr.get('discriminator')
     members = expr['data']
     values = { 'MAX': '(automatic)' }
-    types_seen = {}

-    # Three types of unions, determined by discriminator.
+    # Two types of unions, determined by discriminator.
+    assert discriminator != {}

-    # If the value of member 'discriminator' is {}, it's an
-    # anonymous union, and must not have a base.
-    if discriminator == {}:
-        enum_define = None
-        if base:
-            raise QAPIExprError(expr_info,
-                                "Anonymous union '%s' must not have a base"
-                                % name)
-
-    # Else if the union object has no member 'discriminator', it's an
+    # If the union object has no member 'discriminator', it's an
     # ordinary union.  For now, it must not have a base.
-    elif not discriminator:
+    if not discriminator:
         enum_define = None
         if base:
             raise QAPIExprError(expr_info,
@@ -346,24 +332,46 @@  def check_union(expr, expr_info):
                                     % (name, key, values[c_key]))
             values[c_key] = key

-        # Ensure anonymous unions have no type conflicts.
-        if discriminator == {}:
-            if isinstance(value, list):
-                raise QAPIExprError(expr_info,
-                                    "Anonymous union '%s' member '%s' must "
-                                    "not be array type" % (name, key))
-            qtype = find_anonymous_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' has "
-                                    "same QObject type as member '%s'"
-                                    % (name, key, types_seen[qtype]))
-            types_seen[qtype] = key
+def check_alternate(expr, expr_info):
+    name = expr['union']
+    base = expr.get('base')
+    discriminator = expr.get('discriminator')
+    members = expr['data']
+    values = { 'MAX': '(automatic)' }
+    types_seen = {}

+    assert discriminator == {}
+    if base:
+        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,
+                                "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' has "
+                                "same QObject type as member '%s'"
+                                % (name, key, types_seen[qtype]))
+        types_seen[qtype] = key

 def check_enum(expr, expr_info):
     name = expr['enum']
@@ -393,7 +401,10 @@  def check_exprs(schema):
         if expr.has_key('enum'):
             check_enum(expr, info)
         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'):
             check_event(expr, info)

@@ -535,7 +546,8 @@  def find_struct(name):

 def add_union(definition):
     global union_types
-    union_types.append(definition)
+    if definition.get('discriminator') != {}:
+        union_types.append(definition)

 def find_union(name):
     global union_types