[Qemu-devel] [PATCH v6 16/27] qapi: add a dictionary form with 'type' key for members

Marc-André Lureau posted 27 patches 7 years, 4 months ago
There is a newer version of this series
[Qemu-devel] [PATCH v6 16/27] qapi: add a dictionary form with 'type' key for members
Posted by Marc-André Lureau 7 years, 4 months ago
Wherever a struct/union/alternate/command/event member with NAME: TYPE
form is accepted, desugar it to a NAME: { 'type': TYPE } form.

This will allow to add new member details, such as 'if' in the
following patch to introduce conditionals, or 'default' for default
values etc.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
 scripts/qapi/common.py                        | 56 +++++++++++++------
 tests/Makefile.include                        |  3 +
 tests/qapi-schema/alternate-invalid-dict.err  |  1 +
 tests/qapi-schema/alternate-invalid-dict.exit |  1 +
 tests/qapi-schema/alternate-invalid-dict.json |  4 ++
 tests/qapi-schema/alternate-invalid-dict.out  |  0
 tests/qapi-schema/event-nest-struct.err       |  2 +-
 tests/qapi-schema/flat-union-inline.err       |  2 +-
 tests/qapi-schema/nested-struct-data.err      |  2 +-
 tests/qapi-schema/qapi-schema-test.json       | 10 ++--
 .../struct-member-invalid-dict.err            |  1 +
 .../struct-member-invalid-dict.exit           |  1 +
 .../struct-member-invalid-dict.json           |  3 +
 .../struct-member-invalid-dict.out            |  0
 .../qapi-schema/union-branch-invalid-dict.err |  1 +
 .../union-branch-invalid-dict.exit            |  1 +
 .../union-branch-invalid-dict.json            |  4 ++
 .../qapi-schema/union-branch-invalid-dict.out |  0
 18 files changed, 66 insertions(+), 26 deletions(-)
 create mode 100644 tests/qapi-schema/alternate-invalid-dict.err
 create mode 100644 tests/qapi-schema/alternate-invalid-dict.exit
 create mode 100644 tests/qapi-schema/alternate-invalid-dict.json
 create mode 100644 tests/qapi-schema/alternate-invalid-dict.out
 create mode 100644 tests/qapi-schema/struct-member-invalid-dict.err
 create mode 100644 tests/qapi-schema/struct-member-invalid-dict.exit
 create mode 100644 tests/qapi-schema/struct-member-invalid-dict.json
 create mode 100644 tests/qapi-schema/struct-member-invalid-dict.out
 create mode 100644 tests/qapi-schema/union-branch-invalid-dict.err
 create mode 100644 tests/qapi-schema/union-branch-invalid-dict.exit
 create mode 100644 tests/qapi-schema/union-branch-invalid-dict.json
 create mode 100644 tests/qapi-schema/union-branch-invalid-dict.out

diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index d83fa1900e..fc68bb472e 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -588,11 +588,11 @@ def discriminator_find_enum_define(expr):
     if not base_members:
         return None
 
-    discriminator_type = base_members.get(discriminator)
-    if not discriminator_type:
+    discriminator_member = base_members.get(discriminator)
+    if not discriminator_member:
         return None
 
-    return enum_types.get(discriminator_type)
+    return enum_types.get(discriminator_member['type'])
 
 
 # Names must be letters, numbers, -, and _.  They must start with letter,
@@ -660,6 +660,15 @@ def check_if(expr, info):
         check_if_str(ifcond, info)
 
 
+def normalize_members(expr, field):
+    members = expr.get(field)
+    if isinstance(members, OrderedDict):
+        for key, arg in members.items():
+            if isinstance(arg, dict):
+                continue
+            members[key] = {'type': arg}
+
+
 def check_type(info, source, value, allow_array=False,
                allow_implicit=False, allow_optional=False,
                allow_metas=[]):
@@ -704,8 +713,10 @@ def check_type(info, source, value, allow_array=False,
                                % (source, key))
         # Todo: allow dictionaries to represent default values of
         # an optional argument.
-        check_type(info, "Member '%s' of %s" % (key, source), arg,
-                   allow_array=True,
+        check_known_keys(info, "member '%s' of %s" % (key, source),
+                         arg, ['type'], [])
+        check_type(info, "Member '%s' of %s" % (key, source),
+                   arg['type'], allow_array=True,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
                                 'enum'])
 
@@ -776,13 +787,13 @@ def check_union(expr, info):
         # member of the base struct.
         check_name(info, "Discriminator of flat union '%s'" % name,
                    discriminator)
-        discriminator_type = base_members.get(discriminator)
-        if not discriminator_type:
+        discriminator_member = base_members.get(discriminator)
+        if not discriminator_member:
             raise QAPISemError(info,
                                "Discriminator '%s' is not a member of base "
                                "struct '%s'"
                                % (discriminator, base))
-        enum_define = enum_types.get(discriminator_type)
+        enum_define = enum_types.get(discriminator_member['type'])
         allow_metas = ['struct']
         # Do not allow string discriminator
         if not enum_define:
@@ -795,10 +806,13 @@ def check_union(expr, info):
         raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
     for (key, value) in members.items():
         check_name(info, "Member of union '%s'" % name, key)
+        source = "member '%s' of union '%s'" % (key, name)
+        check_known_keys(info, source, value, ['type'], [])
+        typ = value['type']
 
         # Each value must name a known type
         check_type(info, "Member '%s' of union '%s'" % (key, name),
-                   value, allow_array=not base, allow_metas=allow_metas)
+                   typ, allow_array=not base, allow_metas=allow_metas)
 
         # If the discriminator names an enum type, then all members
         # of 'data' must also be members of the enum type.
@@ -822,18 +836,20 @@ def check_alternate(expr, info):
                            "in 'data'" % name)
     for (key, value) in members.items():
         check_name(info, "Member of alternate '%s'" % name, key)
+        source = "member '%s' of alternate '%s'" % (key, name)
+        check_known_keys(info, source, value, ['type'], [])
+        typ = value['type']
 
         # Ensure alternates have no type conflicts.
-        check_type(info, "Member '%s' of alternate '%s'" % (key, name),
-                   value,
+        check_type(info, "Member '%s' of alternate '%s'" % (key, name), typ,
                    allow_metas=['built-in', 'union', 'struct', 'enum'])
-        qtype = find_alternate_member_qtype(value)
+        qtype = find_alternate_member_qtype(typ)
         if not qtype:
             raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
-                               "type '%s'" % (name, key, value))
+                               "type '%s'" % (name, key, typ))
         conflicting = set([qtype])
         if qtype == 'QTYPE_QSTRING':
-            enum_expr = enum_types.get(value)
+            enum_expr = enum_types.get(typ)
             if enum_expr:
                 for v in enum_get_names(enum_expr):
                     if v in ['on', 'off']:
@@ -947,6 +963,10 @@ def check_exprs(exprs):
         info = expr_elem['info']
         if 'enum' in expr:
             normalize_enum(expr, info)
+        elif 'union' in expr:
+            normalize_members(expr, 'base')
+        if {'union', 'alternate', 'struct', 'command', 'event'} & set(expr):
+            normalize_members(expr, 'data')
 
     # Learn the types and check for valid expression keys
     for expr_elem in exprs:
@@ -1735,7 +1755,7 @@ class QAPISchema(object):
         return QAPISchemaObjectTypeMember(name, typ, optional)
 
     def _make_members(self, data, info):
-        return [self._make_member(key, value, info)
+        return [self._make_member(key, value['type'], info)
                 for (key, value) in data.items()]
 
     def _def_struct_type(self, expr, info, doc):
@@ -1771,11 +1791,11 @@ class QAPISchema(object):
                 name, info, doc, ifcond,
                 'base', self._make_members(base, info))
         if tag_name:
-            variants = [self._make_variant(key, value)
+            variants = [self._make_variant(key, value['type'])
                         for (key, value) in data.items()]
             members = []
         else:
-            variants = [self._make_simple_variant(key, value, info)
+            variants = [self._make_simple_variant(key, value['type'], info)
                         for (key, value) in data.items()]
             typ = self._make_implicit_enum_type(name, info, ifcond,
                                                 [v.name for v in variants])
@@ -1791,7 +1811,7 @@ class QAPISchema(object):
         name = expr['alternate']
         data = expr['data']
         ifcond = expr.get('if')
-        variants = [self._make_variant(key, value)
+        variants = [self._make_variant(key, value['type'])
                     for (key, value) in data.items()]
         tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
         self._def_entity(
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 3ba9097892..43e100a6cd 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -421,6 +421,7 @@ qapi-schema += alternate-conflict-string.json
 qapi-schema += alternate-conflict-bool-string.json
 qapi-schema += alternate-conflict-num-string.json
 qapi-schema += alternate-empty.json
+qapi-schema += alternate-invalid-dict.json
 qapi-schema += alternate-nested.json
 qapi-schema += alternate-unknown.json
 qapi-schema += args-alternate.json
@@ -563,6 +564,7 @@ qapi-schema += returns-whitelist.json
 qapi-schema += struct-base-clash-deep.json
 qapi-schema += struct-base-clash.json
 qapi-schema += struct-data-invalid.json
+qapi-schema += struct-member-invalid-dict.json
 qapi-schema += struct-member-invalid.json
 qapi-schema += trailing-comma-list.json
 qapi-schema += trailing-comma-object.json
@@ -574,6 +576,7 @@ qapi-schema += unicode-str.json
 qapi-schema += union-base-empty.json
 qapi-schema += union-base-no-discriminator.json
 qapi-schema += union-branch-case.json
+qapi-schema += union-branch-invalid-dict.json
 qapi-schema += union-clash-branches.json
 qapi-schema += union-empty.json
 qapi-schema += union-invalid-base.json
diff --git a/tests/qapi-schema/alternate-invalid-dict.err b/tests/qapi-schema/alternate-invalid-dict.err
new file mode 100644
index 0000000000..631d46628e
--- /dev/null
+++ b/tests/qapi-schema/alternate-invalid-dict.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-invalid-dict.json:2: Key 'type' is missing from member 'two' of alternate 'Alt'
diff --git a/tests/qapi-schema/alternate-invalid-dict.exit b/tests/qapi-schema/alternate-invalid-dict.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/alternate-invalid-dict.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/alternate-invalid-dict.json b/tests/qapi-schema/alternate-invalid-dict.json
new file mode 100644
index 0000000000..45f2c8ebef
--- /dev/null
+++ b/tests/qapi-schema/alternate-invalid-dict.json
@@ -0,0 +1,4 @@
+# invalid field dictionnary, missing type
+{ 'alternate': 'Alt',
+  'data': { 'one': 'str',
+            'two': { 'if': 'foo' } } }
diff --git a/tests/qapi-schema/alternate-invalid-dict.out b/tests/qapi-schema/alternate-invalid-dict.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/event-nest-struct.err b/tests/qapi-schema/event-nest-struct.err
index 5a42701b8f..ea1be65d89 100644
--- a/tests/qapi-schema/event-nest-struct.err
+++ b/tests/qapi-schema/event-nest-struct.err
@@ -1 +1 @@
-tests/qapi-schema/event-nest-struct.json:1: Member 'a' of 'data' for event 'EVENT_A' should be a type name
+tests/qapi-schema/event-nest-struct.json:1: Key 'type' is missing from member 'a' of 'data' for event 'EVENT_A'
diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
index 2333358d28..3d29433bfb 100644
--- a/tests/qapi-schema/flat-union-inline.err
+++ b/tests/qapi-schema/flat-union-inline.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-inline.json:7: Member 'value1' of union 'TestUnion' should be a type name
+tests/qapi-schema/flat-union-inline.json:7: Key 'type' is missing from member 'value1' of union 'TestUnion'
diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err
index da767bade2..48355adcbe 100644
--- a/tests/qapi-schema/nested-struct-data.err
+++ b/tests/qapi-schema/nested-struct-data.err
@@ -1 +1 @@
-tests/qapi-schema/nested-struct-data.json:2: Member 'a' of 'data' for command 'foo' should be a type name
+tests/qapi-schema/nested-struct-data.json:2: Key 'type' is missing from member 'a' of 'data' for command 'foo'
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 35ca94d991..30f635d484 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -11,7 +11,7 @@
         'guest-sync' ] } }
 
 { 'struct': 'TestStruct',
-  'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } }
+  'data': { 'integer': {'type': 'int'}, 'boolean': 'bool', 'string': 'str' } }
 
 # for testing enums
 { 'struct': 'NestedEnumsOne',
@@ -77,7 +77,7 @@
 { 'union': 'UserDefFlatUnion',
   'base': 'UserDefUnionBase',   # intentional forward reference
   'discriminator': 'enum1',
-  'data': { 'value1' : 'UserDefA',
+  'data': { 'value1' : {'type': 'UserDefA'},
             'value2' : 'UserDefB',
             'value3' : 'UserDefB'
             # 'value4' defaults to empty
@@ -98,7 +98,7 @@
 { 'struct': 'WrapAlternate',
   'data': { 'alt': 'UserDefAlternate' } }
 { 'alternate': 'UserDefAlternate',
-  'data': { 'udfu': 'UserDefFlatUnion', 'e': 'EnumOne', 'i': 'int',
+  'data': { 'udfu': {'type': 'UserDefFlatUnion'}, 'e': 'EnumOne', 'i': 'int',
             'n': 'null' } }
 
 { 'struct': 'UserDefC',
@@ -134,7 +134,7 @@
 { 'command': 'user_def_cmd', 'data': {} }
 { 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} }
 { 'command': 'user_def_cmd2',
-  'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'},
+  'data': {'ud1a': {'type': 'UserDefOne'}, '*ud1b': 'UserDefOne'},
   'returns': 'UserDefTwo' }
 
 # Returning a non-dictionary requires a name from the whitelist
@@ -164,7 +164,7 @@
 
 # testing event
 { 'struct': 'EventStructOne',
-  'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }
+  'data': { 'struct1': {'type': 'UserDefOne'}, 'string': 'str', '*enum2': 'EnumOne' } }
 
 { 'event': 'EVENT_A' }
 { 'event': 'EVENT_B',
diff --git a/tests/qapi-schema/struct-member-invalid-dict.err b/tests/qapi-schema/struct-member-invalid-dict.err
new file mode 100644
index 0000000000..6a765bc668
--- /dev/null
+++ b/tests/qapi-schema/struct-member-invalid-dict.err
@@ -0,0 +1 @@
+tests/qapi-schema/struct-member-invalid-dict.json:2: Key 'type' is missing from member '*a' of 'data' for struct 'foo'
diff --git a/tests/qapi-schema/struct-member-invalid-dict.exit b/tests/qapi-schema/struct-member-invalid-dict.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/struct-member-invalid-dict.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/struct-member-invalid-dict.json b/tests/qapi-schema/struct-member-invalid-dict.json
new file mode 100644
index 0000000000..ebd9733b49
--- /dev/null
+++ b/tests/qapi-schema/struct-member-invalid-dict.json
@@ -0,0 +1,3 @@
+# exploded member form must have a 'type'
+{ 'struct': 'foo',
+  'data': { '*a': { 'case': 'foo' } } }
diff --git a/tests/qapi-schema/struct-member-invalid-dict.out b/tests/qapi-schema/struct-member-invalid-dict.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/qapi-schema/union-branch-invalid-dict.err b/tests/qapi-schema/union-branch-invalid-dict.err
new file mode 100644
index 0000000000..89f9b36791
--- /dev/null
+++ b/tests/qapi-schema/union-branch-invalid-dict.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-branch-invalid-dict.json:2: Key 'type' is missing from member 'integer' of union 'UnionInvalidBranch'
diff --git a/tests/qapi-schema/union-branch-invalid-dict.exit b/tests/qapi-schema/union-branch-invalid-dict.exit
new file mode 100644
index 0000000000..d00491fd7e
--- /dev/null
+++ b/tests/qapi-schema/union-branch-invalid-dict.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/union-branch-invalid-dict.json b/tests/qapi-schema/union-branch-invalid-dict.json
new file mode 100644
index 0000000000..19c5d9cacd
--- /dev/null
+++ b/tests/qapi-schema/union-branch-invalid-dict.json
@@ -0,0 +1,4 @@
+# exploded member form must have a 'type'
+{ 'union': 'UnionInvalidBranch',
+  'data': { 'integer': { 'if': 'foo'},
+            's8': 'int8' } }
diff --git a/tests/qapi-schema/union-branch-invalid-dict.out b/tests/qapi-schema/union-branch-invalid-dict.out
new file mode 100644
index 0000000000..e69de29bb2
-- 
2.18.0.rc1


Re: [Qemu-devel] [PATCH v6 16/27] qapi: add a dictionary form with 'type' key for members
Posted by Markus Armbruster 6 years, 11 months ago
Marc-André Lureau <marcandre.lureau@redhat.com> writes:

> Wherever a struct/union/alternate/command/event member with NAME: TYPE
> form is accepted, desugar it to a NAME: { 'type': TYPE } form.
>
> This will allow to add new member details, such as 'if' in the
> following patch to introduce conditionals, or 'default' for default
> values etc.
>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
>  scripts/qapi/common.py                        | 56 +++++++++++++------
>  tests/Makefile.include                        |  3 +
>  tests/qapi-schema/alternate-invalid-dict.err  |  1 +
>  tests/qapi-schema/alternate-invalid-dict.exit |  1 +
>  tests/qapi-schema/alternate-invalid-dict.json |  4 ++
>  tests/qapi-schema/alternate-invalid-dict.out  |  0
>  tests/qapi-schema/event-nest-struct.err       |  2 +-
>  tests/qapi-schema/flat-union-inline.err       |  2 +-
>  tests/qapi-schema/nested-struct-data.err      |  2 +-
>  tests/qapi-schema/qapi-schema-test.json       | 10 ++--
>  .../struct-member-invalid-dict.err            |  1 +
>  .../struct-member-invalid-dict.exit           |  1 +
>  .../struct-member-invalid-dict.json           |  3 +
>  .../struct-member-invalid-dict.out            |  0
>  .../qapi-schema/union-branch-invalid-dict.err |  1 +
>  .../union-branch-invalid-dict.exit            |  1 +
>  .../union-branch-invalid-dict.json            |  4 ++
>  .../qapi-schema/union-branch-invalid-dict.out |  0
>  18 files changed, 66 insertions(+), 26 deletions(-)
>  create mode 100644 tests/qapi-schema/alternate-invalid-dict.err
>  create mode 100644 tests/qapi-schema/alternate-invalid-dict.exit
>  create mode 100644 tests/qapi-schema/alternate-invalid-dict.json
>  create mode 100644 tests/qapi-schema/alternate-invalid-dict.out
>  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.err
>  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.exit
>  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.json
>  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.out
>  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.err
>  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.exit
>  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.json
>  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.out
>
> diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> index d83fa1900e..fc68bb472e 100644
> --- a/scripts/qapi/common.py
> +++ b/scripts/qapi/common.py
> @@ -588,11 +588,11 @@ def discriminator_find_enum_define(expr):
>      if not base_members:
>          return None
>  
> -    discriminator_type = base_members.get(discriminator)
> -    if not discriminator_type:
> +    discriminator_member = base_members.get(discriminator)
> +    if not discriminator_member:
>          return None
>  
> -    return enum_types.get(discriminator_type)
> +    return enum_types.get(discriminator_member['type'])
>  
>  

The name discriminator_member is confusing.  It suggests its value is
the (desugared) member definition, i.e. something like

     {'enum1': {'type': 'EnumOne'}}

it's actually only the definition's value part, i.e. something like

     {'type': 'EnumOne'}

Naming is hard... discriminator_value?  discriminator_def_rhs?

Same in check_union() below.

>  # Names must be letters, numbers, -, and _.  They must start with letter,
> @@ -660,6 +660,15 @@ def check_if(expr, info):
>          check_if_str(ifcond, info)
>  
>  
> +def normalize_members(expr, field):
> +    members = expr.get(field)
> +    if isinstance(members, OrderedDict):
> +        for key, arg in members.items():
> +            if isinstance(arg, dict):
> +                continue
> +            members[key] = {'type': arg}
> +
> +

PATCH 12's normalize_enum() conflates normalization and checking.  This
one doesn't.  Better.

>  def check_type(info, source, value, allow_array=False,
>                 allow_implicit=False, allow_optional=False,
>                 allow_metas=[]):
> @@ -704,8 +713,10 @@ def check_type(info, source, value, allow_array=False,
>                                 % (source, key))
>          # Todo: allow dictionaries to represent default values of
>          # an optional argument.
> -        check_type(info, "Member '%s' of %s" % (key, source), arg,
> -                   allow_array=True,
> +        check_known_keys(info, "member '%s' of %s" % (key, source),
> +                         arg, ['type'], [])
> +        check_type(info, "Member '%s' of %s" % (key, source),
> +                   arg['type'], allow_array=True,
>                     allow_metas=['built-in', 'union', 'alternate', 'struct',
>                                  'enum'])
>  
> @@ -776,13 +787,13 @@ def check_union(expr, info):
>          # member of the base struct.
>          check_name(info, "Discriminator of flat union '%s'" % name,
>                     discriminator)
> -        discriminator_type = base_members.get(discriminator)
> -        if not discriminator_type:
> +        discriminator_member = base_members.get(discriminator)
> +        if not discriminator_member:
>              raise QAPISemError(info,
>                                 "Discriminator '%s' is not a member of base "
>                                 "struct '%s'"
>                                 % (discriminator, base))
> -        enum_define = enum_types.get(discriminator_type)
> +        enum_define = enum_types.get(discriminator_member['type'])
>          allow_metas = ['struct']
>          # Do not allow string discriminator
>          if not enum_define:
> @@ -795,10 +806,13 @@ def check_union(expr, info):
>          raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
>      for (key, value) in members.items():
>          check_name(info, "Member of union '%s'" % name, key)
> +        source = "member '%s' of union '%s'" % (key, name)
> +        check_known_keys(info, source, value, ['type'], [])

Elsewhere, you do it like

           check_known_keys(info, "member '%s' of union '%s'" % (key, name),
                            value, ['type'], [])

Let's stick to that.

> +        typ = value['type']
>  
>          # Each value must name a known type
>          check_type(info, "Member '%s' of union '%s'" % (key, name),
> -                   value, allow_array=not base, allow_metas=allow_metas)
> +                   typ, allow_array=not base, allow_metas=allow_metas)
>  
>          # If the discriminator names an enum type, then all members
>          # of 'data' must also be members of the enum type.
> @@ -822,18 +836,20 @@ def check_alternate(expr, info):
>                             "in 'data'" % name)
>      for (key, value) in members.items():
>          check_name(info, "Member of alternate '%s'" % name, key)
> +        source = "member '%s' of alternate '%s'" % (key, name)
> +        check_known_keys(info, source, value, ['type'], [])

Likewise.

> +        typ = value['type']
>  
>          # Ensure alternates have no type conflicts.
> -        check_type(info, "Member '%s' of alternate '%s'" % (key, name),
> -                   value,
> +        check_type(info, "Member '%s' of alternate '%s'" % (key, name), typ,
>                     allow_metas=['built-in', 'union', 'struct', 'enum'])
> -        qtype = find_alternate_member_qtype(value)
> +        qtype = find_alternate_member_qtype(typ)
>          if not qtype:
>              raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
> -                               "type '%s'" % (name, key, value))
> +                               "type '%s'" % (name, key, typ))
>          conflicting = set([qtype])
>          if qtype == 'QTYPE_QSTRING':
> -            enum_expr = enum_types.get(value)
> +            enum_expr = enum_types.get(typ)
>              if enum_expr:
>                  for v in enum_get_names(enum_expr):
>                      if v in ['on', 'off']:
> @@ -947,6 +963,10 @@ def check_exprs(exprs):
>          info = expr_elem['info']
>          if 'enum' in expr:
>              normalize_enum(expr, info)
> +        elif 'union' in expr:
> +            normalize_members(expr, 'base')
> +        if {'union', 'alternate', 'struct', 'command', 'event'} & set(expr):
> +            normalize_members(expr, 'data')
>  
>      # Learn the types and check for valid expression keys
>      for expr_elem in exprs:

PATCH 12 had an issue in this loop, and the obvious fix was to fold it
into the next loop.  I think this can be done here too.

> @@ -1735,7 +1755,7 @@ class QAPISchema(object):
>          return QAPISchemaObjectTypeMember(name, typ, optional)
>  
>      def _make_members(self, data, info):
> -        return [self._make_member(key, value, info)
> +        return [self._make_member(key, value['type'], info)
>                  for (key, value) in data.items()]
>  
>      def _def_struct_type(self, expr, info, doc):
> @@ -1771,11 +1791,11 @@ class QAPISchema(object):
>                  name, info, doc, ifcond,
>                  'base', self._make_members(base, info))
>          if tag_name:
> -            variants = [self._make_variant(key, value)
> +            variants = [self._make_variant(key, value['type'])
>                          for (key, value) in data.items()]
>              members = []
>          else:
> -            variants = [self._make_simple_variant(key, value, info)
> +            variants = [self._make_simple_variant(key, value['type'], info)
>                          for (key, value) in data.items()]
>              typ = self._make_implicit_enum_type(name, info, ifcond,
>                                                  [v.name for v in variants])
> @@ -1791,7 +1811,7 @@ class QAPISchema(object):
>          name = expr['alternate']
>          data = expr['data']
>          ifcond = expr.get('if')
> -        variants = [self._make_variant(key, value)
> +        variants = [self._make_variant(key, value['type'])
>                      for (key, value) in data.items()]
>          tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
>          self._def_entity(
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index 3ba9097892..43e100a6cd 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -421,6 +421,7 @@ qapi-schema += alternate-conflict-string.json
>  qapi-schema += alternate-conflict-bool-string.json
>  qapi-schema += alternate-conflict-num-string.json
>  qapi-schema += alternate-empty.json
> +qapi-schema += alternate-invalid-dict.json
>  qapi-schema += alternate-nested.json
>  qapi-schema += alternate-unknown.json
>  qapi-schema += args-alternate.json
> @@ -563,6 +564,7 @@ qapi-schema += returns-whitelist.json
>  qapi-schema += struct-base-clash-deep.json
>  qapi-schema += struct-base-clash.json
>  qapi-schema += struct-data-invalid.json
> +qapi-schema += struct-member-invalid-dict.json
>  qapi-schema += struct-member-invalid.json
>  qapi-schema += trailing-comma-list.json
>  qapi-schema += trailing-comma-object.json
> @@ -574,6 +576,7 @@ qapi-schema += unicode-str.json
>  qapi-schema += union-base-empty.json
>  qapi-schema += union-base-no-discriminator.json
>  qapi-schema += union-branch-case.json
> +qapi-schema += union-branch-invalid-dict.json
>  qapi-schema += union-clash-branches.json
>  qapi-schema += union-empty.json
>  qapi-schema += union-invalid-base.json
> diff --git a/tests/qapi-schema/alternate-invalid-dict.err b/tests/qapi-schema/alternate-invalid-dict.err
> new file mode 100644
> index 0000000000..631d46628e
> --- /dev/null
> +++ b/tests/qapi-schema/alternate-invalid-dict.err
> @@ -0,0 +1 @@
> +tests/qapi-schema/alternate-invalid-dict.json:2: Key 'type' is missing from member 'two' of alternate 'Alt'
> diff --git a/tests/qapi-schema/alternate-invalid-dict.exit b/tests/qapi-schema/alternate-invalid-dict.exit
> new file mode 100644
> index 0000000000..d00491fd7e
> --- /dev/null
> +++ b/tests/qapi-schema/alternate-invalid-dict.exit
> @@ -0,0 +1 @@
> +1
> diff --git a/tests/qapi-schema/alternate-invalid-dict.json b/tests/qapi-schema/alternate-invalid-dict.json
> new file mode 100644
> index 0000000000..45f2c8ebef
> --- /dev/null
> +++ b/tests/qapi-schema/alternate-invalid-dict.json
> @@ -0,0 +1,4 @@
> +# invalid field dictionnary, missing type

dictionary

Suggest a grep over the whole series.

Even better, reuse struct-member-invalid-dict.json's comment here.

> +{ 'alternate': 'Alt',
> +  'data': { 'one': 'str',
> +            'two': { 'if': 'foo' } } }
> diff --git a/tests/qapi-schema/alternate-invalid-dict.out b/tests/qapi-schema/alternate-invalid-dict.out
> new file mode 100644
> index 0000000000..e69de29bb2
> diff --git a/tests/qapi-schema/event-nest-struct.err b/tests/qapi-schema/event-nest-struct.err
> index 5a42701b8f..ea1be65d89 100644
> --- a/tests/qapi-schema/event-nest-struct.err
> +++ b/tests/qapi-schema/event-nest-struct.err
> @@ -1 +1 @@
> -tests/qapi-schema/event-nest-struct.json:1: Member 'a' of 'data' for event 'EVENT_A' should be a type name
> +tests/qapi-schema/event-nest-struct.json:1: Key 'type' is missing from member 'a' of 'data' for event 'EVENT_A'

We lose the test of the "should be a type name" error.  Let's copy this
test to event-member-invalid-dict.json to cover "Key 'type' is missing",
and update the original to keep covering "should be a type name".


> diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
> index 2333358d28..3d29433bfb 100644
> --- a/tests/qapi-schema/flat-union-inline.err
> +++ b/tests/qapi-schema/flat-union-inline.err
> @@ -1 +1 @@
> -tests/qapi-schema/flat-union-inline.json:7: Member 'value1' of union 'TestUnion' should be a type name
> +tests/qapi-schema/flat-union-inline.json:7: Key 'type' is missing from member 'value1' of union 'TestUnion'

Likewise.

> diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err
> index da767bade2..48355adcbe 100644
> --- a/tests/qapi-schema/nested-struct-data.err
> +++ b/tests/qapi-schema/nested-struct-data.err
> @@ -1 +1 @@
> -tests/qapi-schema/nested-struct-data.json:2: Member 'a' of 'data' for command 'foo' should be a type name
> +tests/qapi-schema/nested-struct-data.json:2: Key 'type' is missing from member 'a' of 'data' for command 'foo'

Likewise.

> diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> index 35ca94d991..30f635d484 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json

Positive tests for ...

> @@ -11,7 +11,7 @@
>          'guest-sync' ] } }
>  
>  { 'struct': 'TestStruct',
> -  'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } }
> +  'data': { 'integer': {'type': 'int'}, 'boolean': 'bool', 'string': 'str' } }

... struct member, ...

>  
>  # for testing enums
>  { 'struct': 'NestedEnumsOne',
> @@ -77,7 +77,7 @@
>  { 'union': 'UserDefFlatUnion',
>    'base': 'UserDefUnionBase',   # intentional forward reference
>    'discriminator': 'enum1',
> -  'data': { 'value1' : 'UserDefA',
> +  'data': { 'value1' : {'type': 'UserDefA'},
>              'value2' : 'UserDefB',
>              'value3' : 'UserDefB'
>              # 'value4' defaults to empty

... flat union member, ...

> @@ -98,7 +98,7 @@
>  { 'struct': 'WrapAlternate',
>    'data': { 'alt': 'UserDefAlternate' } }
>  { 'alternate': 'UserDefAlternate',
> -  'data': { 'udfu': 'UserDefFlatUnion', 'e': 'EnumOne', 'i': 'int',
> +  'data': { 'udfu': {'type': 'UserDefFlatUnion'}, 'e': 'EnumOne', 'i': 'int',
>              'n': 'null' } }
>  

... alternate member, ...

>  { 'struct': 'UserDefC',
> @@ -134,7 +134,7 @@
>  { 'command': 'user_def_cmd', 'data': {} }
>  { 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} }
>  { 'command': 'user_def_cmd2',
> -  'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'},
> +  'data': {'ud1a': {'type': 'UserDefOne'}, '*ud1b': 'UserDefOne'},
>    'returns': 'UserDefTwo' }
>  

... command argument, ...

>  # Returning a non-dictionary requires a name from the whitelist
> @@ -164,7 +164,7 @@
>  
>  # testing event
>  { 'struct': 'EventStructOne',
> -  'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }
> +  'data': { 'struct1': {'type': 'UserDefOne'}, 'string': 'str', '*enum2': 'EnumOne' } }
>  
>  { 'event': 'EVENT_A' }
>  { 'event': 'EVENT_B',

... and event argument.

Missing: simple union.  Okay because the desugaring is oblivious of the
difference between flat and simple unions.

> diff --git a/tests/qapi-schema/struct-member-invalid-dict.err b/tests/qapi-schema/struct-member-invalid-dict.err
> new file mode 100644
> index 0000000000..6a765bc668
> --- /dev/null
> +++ b/tests/qapi-schema/struct-member-invalid-dict.err
> @@ -0,0 +1 @@
> +tests/qapi-schema/struct-member-invalid-dict.json:2: Key 'type' is missing from member '*a' of 'data' for struct 'foo'
> diff --git a/tests/qapi-schema/struct-member-invalid-dict.exit b/tests/qapi-schema/struct-member-invalid-dict.exit
> new file mode 100644
> index 0000000000..d00491fd7e
> --- /dev/null
> +++ b/tests/qapi-schema/struct-member-invalid-dict.exit
> @@ -0,0 +1 @@
> +1
> diff --git a/tests/qapi-schema/struct-member-invalid-dict.json b/tests/qapi-schema/struct-member-invalid-dict.json
> new file mode 100644
> index 0000000000..ebd9733b49
> --- /dev/null
> +++ b/tests/qapi-schema/struct-member-invalid-dict.json
> @@ -0,0 +1,3 @@
> +# exploded member form must have a 'type'

Suggest

   # Long form of member must have a value member 'type'

or simply

   # Missing type

> +{ 'struct': 'foo',
> +  'data': { '*a': { 'case': 'foo' } } }
> diff --git a/tests/qapi-schema/struct-member-invalid-dict.out b/tests/qapi-schema/struct-member-invalid-dict.out
> new file mode 100644
> index 0000000000..e69de29bb2
> diff --git a/tests/qapi-schema/union-branch-invalid-dict.err b/tests/qapi-schema/union-branch-invalid-dict.err
> new file mode 100644
> index 0000000000..89f9b36791
> --- /dev/null
> +++ b/tests/qapi-schema/union-branch-invalid-dict.err
> @@ -0,0 +1 @@
> +tests/qapi-schema/union-branch-invalid-dict.json:2: Key 'type' is missing from member 'integer' of union 'UnionInvalidBranch'
> diff --git a/tests/qapi-schema/union-branch-invalid-dict.exit b/tests/qapi-schema/union-branch-invalid-dict.exit
> new file mode 100644
> index 0000000000..d00491fd7e
> --- /dev/null
> +++ b/tests/qapi-schema/union-branch-invalid-dict.exit
> @@ -0,0 +1 @@
> +1
> diff --git a/tests/qapi-schema/union-branch-invalid-dict.json b/tests/qapi-schema/union-branch-invalid-dict.json
> new file mode 100644
> index 0000000000..19c5d9cacd
> --- /dev/null
> +++ b/tests/qapi-schema/union-branch-invalid-dict.json
> @@ -0,0 +1,4 @@
> +# exploded member form must have a 'type'

Likewise.

> +{ 'union': 'UnionInvalidBranch',
> +  'data': { 'integer': { 'if': 'foo'},
> +            's8': 'int8' } }

I avoid simple unions like the plague.  That said, having one here is
tolerable; we'll just have to replace it when we abolish simple unions.

> diff --git a/tests/qapi-schema/union-branch-invalid-dict.out b/tests/qapi-schema/union-branch-invalid-dict.out
> new file mode 100644
> index 0000000000..e69de29bb2

Re: [Qemu-devel] [PATCH v6 16/27] qapi: add a dictionary form with 'type' key for members
Posted by Marc-André Lureau 6 years, 11 months ago
Hi

On Thu, Dec 6, 2018 at 7:56 PM Markus Armbruster <armbru@redhat.com> wrote:
>
> Marc-André Lureau <marcandre.lureau@redhat.com> writes:
>
> > Wherever a struct/union/alternate/command/event member with NAME: TYPE
> > form is accepted, desugar it to a NAME: { 'type': TYPE } form.
> >
> > This will allow to add new member details, such as 'if' in the
> > following patch to introduce conditionals, or 'default' for default
> > values etc.
> >
> > Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> > ---
> >  scripts/qapi/common.py                        | 56 +++++++++++++------
> >  tests/Makefile.include                        |  3 +
> >  tests/qapi-schema/alternate-invalid-dict.err  |  1 +
> >  tests/qapi-schema/alternate-invalid-dict.exit |  1 +
> >  tests/qapi-schema/alternate-invalid-dict.json |  4 ++
> >  tests/qapi-schema/alternate-invalid-dict.out  |  0
> >  tests/qapi-schema/event-nest-struct.err       |  2 +-
> >  tests/qapi-schema/flat-union-inline.err       |  2 +-
> >  tests/qapi-schema/nested-struct-data.err      |  2 +-
> >  tests/qapi-schema/qapi-schema-test.json       | 10 ++--
> >  .../struct-member-invalid-dict.err            |  1 +
> >  .../struct-member-invalid-dict.exit           |  1 +
> >  .../struct-member-invalid-dict.json           |  3 +
> >  .../struct-member-invalid-dict.out            |  0
> >  .../qapi-schema/union-branch-invalid-dict.err |  1 +
> >  .../union-branch-invalid-dict.exit            |  1 +
> >  .../union-branch-invalid-dict.json            |  4 ++
> >  .../qapi-schema/union-branch-invalid-dict.out |  0
> >  18 files changed, 66 insertions(+), 26 deletions(-)
> >  create mode 100644 tests/qapi-schema/alternate-invalid-dict.err
> >  create mode 100644 tests/qapi-schema/alternate-invalid-dict.exit
> >  create mode 100644 tests/qapi-schema/alternate-invalid-dict.json
> >  create mode 100644 tests/qapi-schema/alternate-invalid-dict.out
> >  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.err
> >  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.exit
> >  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.json
> >  create mode 100644 tests/qapi-schema/struct-member-invalid-dict.out
> >  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.err
> >  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.exit
> >  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.json
> >  create mode 100644 tests/qapi-schema/union-branch-invalid-dict.out
> >
> > diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
> > index d83fa1900e..fc68bb472e 100644
> > --- a/scripts/qapi/common.py
> > +++ b/scripts/qapi/common.py
> > @@ -588,11 +588,11 @@ def discriminator_find_enum_define(expr):
> >      if not base_members:
> >          return None
> >
> > -    discriminator_type = base_members.get(discriminator)
> > -    if not discriminator_type:
> > +    discriminator_member = base_members.get(discriminator)
> > +    if not discriminator_member:
> >          return None
> >
> > -    return enum_types.get(discriminator_type)
> > +    return enum_types.get(discriminator_member['type'])
> >
> >
>
> The name discriminator_member is confusing.  It suggests its value is
> the (desugared) member definition, i.e. something like
>
>      {'enum1': {'type': 'EnumOne'}}
>
> it's actually only the definition's value part, i.e. something like
>
>      {'type': 'EnumOne'}
>
> Naming is hard... discriminator_value?  discriminator_def_rhs?
>
> Same in check_union() below.
>

discriminator_value, ok

> >  # Names must be letters, numbers, -, and _.  They must start with letter,
> > @@ -660,6 +660,15 @@ def check_if(expr, info):
> >          check_if_str(ifcond, info)
> >
> >
> > +def normalize_members(expr, field):
> > +    members = expr.get(field)
> > +    if isinstance(members, OrderedDict):
> > +        for key, arg in members.items():
> > +            if isinstance(arg, dict):
> > +                continue
> > +            members[key] = {'type': arg}
> > +
> > +
>
> PATCH 12's normalize_enum() conflates normalization and checking.  This
> one doesn't.  Better.

I fixed patch 12

>
> >  def check_type(info, source, value, allow_array=False,
> >                 allow_implicit=False, allow_optional=False,
> >                 allow_metas=[]):
> > @@ -704,8 +713,10 @@ def check_type(info, source, value, allow_array=False,
> >                                 % (source, key))
> >          # Todo: allow dictionaries to represent default values of
> >          # an optional argument.
> > -        check_type(info, "Member '%s' of %s" % (key, source), arg,
> > -                   allow_array=True,
> > +        check_known_keys(info, "member '%s' of %s" % (key, source),
> > +                         arg, ['type'], [])
> > +        check_type(info, "Member '%s' of %s" % (key, source),
> > +                   arg['type'], allow_array=True,
> >                     allow_metas=['built-in', 'union', 'alternate', 'struct',
> >                                  'enum'])
> >
> > @@ -776,13 +787,13 @@ def check_union(expr, info):
> >          # member of the base struct.
> >          check_name(info, "Discriminator of flat union '%s'" % name,
> >                     discriminator)
> > -        discriminator_type = base_members.get(discriminator)
> > -        if not discriminator_type:
> > +        discriminator_member = base_members.get(discriminator)
> > +        if not discriminator_member:
> >              raise QAPISemError(info,
> >                                 "Discriminator '%s' is not a member of base "
> >                                 "struct '%s'"
> >                                 % (discriminator, base))
> > -        enum_define = enum_types.get(discriminator_type)
> > +        enum_define = enum_types.get(discriminator_member['type'])
> >          allow_metas = ['struct']
> >          # Do not allow string discriminator
> >          if not enum_define:
> > @@ -795,10 +806,13 @@ def check_union(expr, info):
> >          raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
> >      for (key, value) in members.items():
> >          check_name(info, "Member of union '%s'" % name, key)
> > +        source = "member '%s' of union '%s'" % (key, name)
> > +        check_known_keys(info, source, value, ['type'], [])
>
> Elsewhere, you do it like
>
>            check_known_keys(info, "member '%s' of union '%s'" % (key, name),
>                             value, ['type'], [])
>
> Let's stick to that.

ok

>
> > +        typ = value['type']
> >
> >          # Each value must name a known type
> >          check_type(info, "Member '%s' of union '%s'" % (key, name),
> > -                   value, allow_array=not base, allow_metas=allow_metas)
> > +                   typ, allow_array=not base, allow_metas=allow_metas)
> >
> >          # If the discriminator names an enum type, then all members
> >          # of 'data' must also be members of the enum type.
> > @@ -822,18 +836,20 @@ def check_alternate(expr, info):
> >                             "in 'data'" % name)
> >      for (key, value) in members.items():
> >          check_name(info, "Member of alternate '%s'" % name, key)
> > +        source = "member '%s' of alternate '%s'" % (key, name)
> > +        check_known_keys(info, source, value, ['type'], [])
>
> Likewise.
>
> > +        typ = value['type']
> >
> >          # Ensure alternates have no type conflicts.
> > -        check_type(info, "Member '%s' of alternate '%s'" % (key, name),
> > -                   value,
> > +        check_type(info, "Member '%s' of alternate '%s'" % (key, name), typ,
> >                     allow_metas=['built-in', 'union', 'struct', 'enum'])
> > -        qtype = find_alternate_member_qtype(value)
> > +        qtype = find_alternate_member_qtype(typ)
> >          if not qtype:
> >              raise QAPISemError(info, "Alternate '%s' member '%s' cannot use "
> > -                               "type '%s'" % (name, key, value))
> > +                               "type '%s'" % (name, key, typ))
> >          conflicting = set([qtype])
> >          if qtype == 'QTYPE_QSTRING':
> > -            enum_expr = enum_types.get(value)
> > +            enum_expr = enum_types.get(typ)
> >              if enum_expr:
> >                  for v in enum_get_names(enum_expr):
> >                      if v in ['on', 'off']:
> > @@ -947,6 +963,10 @@ def check_exprs(exprs):
> >          info = expr_elem['info']
> >          if 'enum' in expr:
> >              normalize_enum(expr, info)
> > +        elif 'union' in expr:
> > +            normalize_members(expr, 'base')
> > +        if {'union', 'alternate', 'struct', 'command', 'event'} & set(expr):
> > +            normalize_members(expr, 'data')
> >
> >      # Learn the types and check for valid expression keys
> >      for expr_elem in exprs:
>
> PATCH 12 had an issue in this loop, and the obvious fix was to fold it
> into the next loop.  I think this can be done here too.

moved to end-of-check

>
> > @@ -1735,7 +1755,7 @@ class QAPISchema(object):
> >          return QAPISchemaObjectTypeMember(name, typ, optional)
> >
> >      def _make_members(self, data, info):
> > -        return [self._make_member(key, value, info)
> > +        return [self._make_member(key, value['type'], info)
> >                  for (key, value) in data.items()]
> >
> >      def _def_struct_type(self, expr, info, doc):
> > @@ -1771,11 +1791,11 @@ class QAPISchema(object):
> >                  name, info, doc, ifcond,
> >                  'base', self._make_members(base, info))
> >          if tag_name:
> > -            variants = [self._make_variant(key, value)
> > +            variants = [self._make_variant(key, value['type'])
> >                          for (key, value) in data.items()]
> >              members = []
> >          else:
> > -            variants = [self._make_simple_variant(key, value, info)
> > +            variants = [self._make_simple_variant(key, value['type'], info)
> >                          for (key, value) in data.items()]
> >              typ = self._make_implicit_enum_type(name, info, ifcond,
> >                                                  [v.name for v in variants])
> > @@ -1791,7 +1811,7 @@ class QAPISchema(object):
> >          name = expr['alternate']
> >          data = expr['data']
> >          ifcond = expr.get('if')
> > -        variants = [self._make_variant(key, value)
> > +        variants = [self._make_variant(key, value['type'])
> >                      for (key, value) in data.items()]
> >          tag_member = QAPISchemaObjectTypeMember('type', 'QType', False)
> >          self._def_entity(
> > diff --git a/tests/Makefile.include b/tests/Makefile.include
> > index 3ba9097892..43e100a6cd 100644
> > --- a/tests/Makefile.include
> > +++ b/tests/Makefile.include
> > @@ -421,6 +421,7 @@ qapi-schema += alternate-conflict-string.json
> >  qapi-schema += alternate-conflict-bool-string.json
> >  qapi-schema += alternate-conflict-num-string.json
> >  qapi-schema += alternate-empty.json
> > +qapi-schema += alternate-invalid-dict.json
> >  qapi-schema += alternate-nested.json
> >  qapi-schema += alternate-unknown.json
> >  qapi-schema += args-alternate.json
> > @@ -563,6 +564,7 @@ qapi-schema += returns-whitelist.json
> >  qapi-schema += struct-base-clash-deep.json
> >  qapi-schema += struct-base-clash.json
> >  qapi-schema += struct-data-invalid.json
> > +qapi-schema += struct-member-invalid-dict.json
> >  qapi-schema += struct-member-invalid.json
> >  qapi-schema += trailing-comma-list.json
> >  qapi-schema += trailing-comma-object.json
> > @@ -574,6 +576,7 @@ qapi-schema += unicode-str.json
> >  qapi-schema += union-base-empty.json
> >  qapi-schema += union-base-no-discriminator.json
> >  qapi-schema += union-branch-case.json
> > +qapi-schema += union-branch-invalid-dict.json
> >  qapi-schema += union-clash-branches.json
> >  qapi-schema += union-empty.json
> >  qapi-schema += union-invalid-base.json
> > diff --git a/tests/qapi-schema/alternate-invalid-dict.err b/tests/qapi-schema/alternate-invalid-dict.err
> > new file mode 100644
> > index 0000000000..631d46628e
> > --- /dev/null
> > +++ b/tests/qapi-schema/alternate-invalid-dict.err
> > @@ -0,0 +1 @@
> > +tests/qapi-schema/alternate-invalid-dict.json:2: Key 'type' is missing from member 'two' of alternate 'Alt'
> > diff --git a/tests/qapi-schema/alternate-invalid-dict.exit b/tests/qapi-schema/alternate-invalid-dict.exit
> > new file mode 100644
> > index 0000000000..d00491fd7e
> > --- /dev/null
> > +++ b/tests/qapi-schema/alternate-invalid-dict.exit
> > @@ -0,0 +1 @@
> > +1
> > diff --git a/tests/qapi-schema/alternate-invalid-dict.json b/tests/qapi-schema/alternate-invalid-dict.json
> > new file mode 100644
> > index 0000000000..45f2c8ebef
> > --- /dev/null
> > +++ b/tests/qapi-schema/alternate-invalid-dict.json
> > @@ -0,0 +1,4 @@
> > +# invalid field dictionnary, missing type
>
> dictionary

argh..

>
> Suggest a grep over the whole series.
>
> Even better, reuse struct-member-invalid-dict.json's comment here.

ok

>
> > +{ 'alternate': 'Alt',
> > +  'data': { 'one': 'str',
> > +            'two': { 'if': 'foo' } } }
> > diff --git a/tests/qapi-schema/alternate-invalid-dict.out b/tests/qapi-schema/alternate-invalid-dict.out
> > new file mode 100644
> > index 0000000000..e69de29bb2
> > diff --git a/tests/qapi-schema/event-nest-struct.err b/tests/qapi-schema/event-nest-struct.err
> > index 5a42701b8f..ea1be65d89 100644
> > --- a/tests/qapi-schema/event-nest-struct.err
> > +++ b/tests/qapi-schema/event-nest-struct.err
> > @@ -1 +1 @@
> > -tests/qapi-schema/event-nest-struct.json:1: Member 'a' of 'data' for event 'EVENT_A' should be a type name
> > +tests/qapi-schema/event-nest-struct.json:1: Key 'type' is missing from member 'a' of 'data' for event 'EVENT_A'
>
> We lose the test of the "should be a type name" error.  Let's copy this
> test to event-member-invalid-dict.json to cover "Key 'type' is missing",
> and update the original to keep covering "should be a type name".
>

ok

>
> > diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
> > index 2333358d28..3d29433bfb 100644
> > --- a/tests/qapi-schema/flat-union-inline.err
> > +++ b/tests/qapi-schema/flat-union-inline.err
> > @@ -1 +1 @@
> > -tests/qapi-schema/flat-union-inline.json:7: Member 'value1' of union 'TestUnion' should be a type name
> > +tests/qapi-schema/flat-union-inline.json:7: Key 'type' is missing from member 'value1' of union 'TestUnion'
>
> Likewise.
>
> > diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err
> > index da767bade2..48355adcbe 100644
> > --- a/tests/qapi-schema/nested-struct-data.err
> > +++ b/tests/qapi-schema/nested-struct-data.err
> > @@ -1 +1 @@
> > -tests/qapi-schema/nested-struct-data.json:2: Member 'a' of 'data' for command 'foo' should be a type name
> > +tests/qapi-schema/nested-struct-data.json:2: Key 'type' is missing from member 'a' of 'data' for command 'foo'
>
> Likewise.
>
> > diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> > index 35ca94d991..30f635d484 100644
> > --- a/tests/qapi-schema/qapi-schema-test.json
> > +++ b/tests/qapi-schema/qapi-schema-test.json
>
> Positive tests for ...
>
> > @@ -11,7 +11,7 @@
> >          'guest-sync' ] } }
> >
> >  { 'struct': 'TestStruct',
> > -  'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } }
> > +  'data': { 'integer': {'type': 'int'}, 'boolean': 'bool', 'string': 'str' } }
>
> ... struct member, ...
>
> >
> >  # for testing enums
> >  { 'struct': 'NestedEnumsOne',
> > @@ -77,7 +77,7 @@
> >  { 'union': 'UserDefFlatUnion',
> >    'base': 'UserDefUnionBase',   # intentional forward reference
> >    'discriminator': 'enum1',
> > -  'data': { 'value1' : 'UserDefA',
> > +  'data': { 'value1' : {'type': 'UserDefA'},
> >              'value2' : 'UserDefB',
> >              'value3' : 'UserDefB'
> >              # 'value4' defaults to empty
>
> ... flat union member, ...
>
> > @@ -98,7 +98,7 @@
> >  { 'struct': 'WrapAlternate',
> >    'data': { 'alt': 'UserDefAlternate' } }
> >  { 'alternate': 'UserDefAlternate',
> > -  'data': { 'udfu': 'UserDefFlatUnion', 'e': 'EnumOne', 'i': 'int',
> > +  'data': { 'udfu': {'type': 'UserDefFlatUnion'}, 'e': 'EnumOne', 'i': 'int',
> >              'n': 'null' } }
> >
>
> ... alternate member, ...
>
> >  { 'struct': 'UserDefC',
> > @@ -134,7 +134,7 @@
> >  { 'command': 'user_def_cmd', 'data': {} }
> >  { 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} }
> >  { 'command': 'user_def_cmd2',
> > -  'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'},
> > +  'data': {'ud1a': {'type': 'UserDefOne'}, '*ud1b': 'UserDefOne'},
> >    'returns': 'UserDefTwo' }
> >
>
> ... command argument, ...
>
> >  # Returning a non-dictionary requires a name from the whitelist
> > @@ -164,7 +164,7 @@
> >
> >  # testing event
> >  { 'struct': 'EventStructOne',
> > -  'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }
> > +  'data': { 'struct1': {'type': 'UserDefOne'}, 'string': 'str', '*enum2': 'EnumOne' } }
> >
> >  { 'event': 'EVENT_A' }
> >  { 'event': 'EVENT_B',
>
> ... and event argument.
>
> Missing: simple union.  Okay because the desugaring is oblivious of the
> difference between flat and simple unions.
>
> > diff --git a/tests/qapi-schema/struct-member-invalid-dict.err b/tests/qapi-schema/struct-member-invalid-dict.err
> > new file mode 100644
> > index 0000000000..6a765bc668
> > --- /dev/null
> > +++ b/tests/qapi-schema/struct-member-invalid-dict.err
> > @@ -0,0 +1 @@
> > +tests/qapi-schema/struct-member-invalid-dict.json:2: Key 'type' is missing from member '*a' of 'data' for struct 'foo'
> > diff --git a/tests/qapi-schema/struct-member-invalid-dict.exit b/tests/qapi-schema/struct-member-invalid-dict.exit
> > new file mode 100644
> > index 0000000000..d00491fd7e
> > --- /dev/null
> > +++ b/tests/qapi-schema/struct-member-invalid-dict.exit
> > @@ -0,0 +1 @@
> > +1
> > diff --git a/tests/qapi-schema/struct-member-invalid-dict.json b/tests/qapi-schema/struct-member-invalid-dict.json
> > new file mode 100644
> > index 0000000000..ebd9733b49
> > --- /dev/null
> > +++ b/tests/qapi-schema/struct-member-invalid-dict.json
> > @@ -0,0 +1,3 @@
> > +# exploded member form must have a 'type'
>
> Suggest
>
>    # Long form of member must have a value member 'type'
>

ok

> or simply
>
>    # Missing type
>
> > +{ 'struct': 'foo',
> > +  'data': { '*a': { 'case': 'foo' } } }
> > diff --git a/tests/qapi-schema/struct-member-invalid-dict.out b/tests/qapi-schema/struct-member-invalid-dict.out
> > new file mode 100644
> > index 0000000000..e69de29bb2
> > diff --git a/tests/qapi-schema/union-branch-invalid-dict.err b/tests/qapi-schema/union-branch-invalid-dict.err
> > new file mode 100644
> > index 0000000000..89f9b36791
> > --- /dev/null
> > +++ b/tests/qapi-schema/union-branch-invalid-dict.err
> > @@ -0,0 +1 @@
> > +tests/qapi-schema/union-branch-invalid-dict.json:2: Key 'type' is missing from member 'integer' of union 'UnionInvalidBranch'
> > diff --git a/tests/qapi-schema/union-branch-invalid-dict.exit b/tests/qapi-schema/union-branch-invalid-dict.exit
> > new file mode 100644
> > index 0000000000..d00491fd7e
> > --- /dev/null
> > +++ b/tests/qapi-schema/union-branch-invalid-dict.exit
> > @@ -0,0 +1 @@
> > +1
> > diff --git a/tests/qapi-schema/union-branch-invalid-dict.json b/tests/qapi-schema/union-branch-invalid-dict.json
> > new file mode 100644
> > index 0000000000..19c5d9cacd
> > --- /dev/null
> > +++ b/tests/qapi-schema/union-branch-invalid-dict.json
> > @@ -0,0 +1,4 @@
> > +# exploded member form must have a 'type'
>
> Likewise.
>
> > +{ 'union': 'UnionInvalidBranch',
> > +  'data': { 'integer': { 'if': 'foo'},
> > +            's8': 'int8' } }
>
> I avoid simple unions like the plague.  That said, having one here is
> tolerable; we'll just have to replace it when we abolish simple unions.
>
> > diff --git a/tests/qapi-schema/union-branch-invalid-dict.out b/tests/qapi-schema/union-branch-invalid-dict.out
> > new file mode 100644
> > index 0000000000..e69de29bb2