All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
@ 2015-04-05  4:07 Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 01/36] qapi: Add copyright declaration on docs Eric Blake
                   ` (39 more replies)
  0 siblings, 40 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

We want to eventually allow qapi defaults, by making:
 'data':{'*flag':'bool'}
as shorthand for something like:
 'data':{'flag':{'type':'bool', 'optional':true}}
so that the default can be specified:
 'data':{'flag':{'type':'bool', 'optional':true, 'default':true}}

This series does not quite get us there, but it DOES do a number
of other things.  It gets rid of the three uses of nested inline
structs, changes anonymous unions to use a specific 'alternate'
metatype rather than abusing 'union', changes the ambiguous 'type'
to the obvious 'struct', and fixes lots of other parser bugs found
while designing the testsuite.  The testsuite changes make the
bulk of this series, with a repeating pattern of writing tests
that expose weaknesses in the old parser, then beefing up the
generator to catch the problem during the initial parse rather
than choking with an obscure python message or even causing a C
compilation failure.  There are a couple of ideas for things to
still fix before all is said and done (such as preventing a
collision of members between a struct and its base class, or
fixing the parser to understand \uXXXX escape sequences in
strings), but at this point, anything raised as a review comment
is probably better addressed as followup patches rather than
respinning a v7.

v5 was here:
https://lists.gnu.org/archive/html/qemu-devel/2015-03/msg05034.html

v6 changes are noted in each patch; in particular, several new
patches were added (additional tests, split some patches, conversion
to 'struct' instead of 'type'). But most of the changes were in
direct response to review comments or rebase fallout, so I kept
in Reviewed-by markings where possible, to help focus review on
the remainder.

I wrote another patch while working on this series, but it was
independent enough that I posted it separately (although I based
the documentation in this patch as if that, or Markus' alternative,
had been applied):
https://lists.gnu.org/archive/html/qemu-devel/2015-04/msg00373.html

Eric Blake (35):
  qapi: Add copyright declaration on docs
  qapi: Document type-safety considerations
  qapi: Simplify builtin type handling
  qapi: Fix generation of 'size' builtin type
  qapi: Require ASCII in schema
  qapi: Add some enum tests
  qapi: Better error messages for bad enums
  qapi: Add some union tests
  qapi: Clean up test coverage of simple unions
  qapi: Forbid base without discriminator in unions
  qapi: Tighten checking of unions
  qapi: Prepare for catching more semantic parse errors
  qapi: Segregate anonymous unions into alternates in generator
  qapi: Rename anonymous union type in test
  qapi: Document new 'alternate' meta-type
  qapi: Use 'alternate' to replace anonymous union
  qapi: Add some expr tests
  qapi: Better error messages for bad expressions
  qapi: Add tests of redefined expressions
  qapi: Better error messages for duplicated expressions
  qapi: Unify type bypass and add tests
  qapi: Add some type check tests
  qapi: More rigourous checking of types
  qapi: Require valid names
  qapi: Whitelist commands that don't return dictionary
  qapi: More rigorous checking for type safety bypass
  qapi: Prefer 'struct' over 'type' in generator
  qapi: Document 'struct' metatype
  qapi: Use 'struct' instead of 'type' in schema
  qapi: Merge UserDefTwo and UserDefNested in tests
  qapi: Drop tests for inline nested structs
  qapi: Drop inline nested struct in query-version
  qapi: Drop inline nested structs in query-pci
  qapi: Drop support for inline nested types
  qapi: Tweak doc references to QMP when QGA is also meant

Fam Zheng (1):
  qapi: Allow true, false and null in schema json

 docs/qapi-code-gen.txt                             | 479 ++++++++++++++++-----
 docs/qmp/qmp-spec.txt                              | 120 ++++--
 hmp.c                                              |  28 +-
 hw/pci/pci.c                                       |  42 +-
 qapi-schema.json                                   | 270 +++++++-----
 qapi/block-core.json                               |  68 ++-
 qapi/block.json                                    |   2 +-
 qapi/common.json                                   |  30 +-
 qapi/trace.json                                    |   2 +-
 qga/qapi-schema.json                               |  36 +-
 qmp.c                                              |   9 +-
 scripts/qapi-commands.py                           |   8 +-
 scripts/qapi-event.py                              |   4 +-
 scripts/qapi-types.py                              |  77 ++--
 scripts/qapi-visit.py                              |  83 ++--
 scripts/qapi.py                                    | 479 +++++++++++++++++----
 tests/Makefile                                     |  35 +-
 tests/qapi-schema/alternate-array.err              |   1 +
 tests/qapi-schema/alternate-array.exit             |   1 +
 tests/qapi-schema/alternate-array.json             |   7 +
 tests/qapi-schema/alternate-array.out              |   0
 tests/qapi-schema/alternate-base.err               |   1 +
 tests/qapi-schema/alternate-base.exit              |   1 +
 tests/qapi-schema/alternate-base.json              |   6 +
 tests/qapi-schema/alternate-base.out               |   0
 tests/qapi-schema/alternate-clash.err              |   1 +
 tests/qapi-schema/alternate-clash.exit             |   1 +
 tests/qapi-schema/alternate-clash.json             |   3 +
 tests/qapi-schema/alternate-clash.out              |   0
 tests/qapi-schema/alternate-conflict-dict.err      |   1 +
 tests/qapi-schema/alternate-conflict-dict.exit     |   1 +
 tests/qapi-schema/alternate-conflict-dict.json     |   8 +
 tests/qapi-schema/alternate-conflict-dict.out      |   0
 tests/qapi-schema/alternate-conflict-string.err    |   1 +
 tests/qapi-schema/alternate-conflict-string.exit   |   1 +
 tests/qapi-schema/alternate-conflict-string.json   |   6 +
 tests/qapi-schema/alternate-conflict-string.out    |   0
 tests/qapi-schema/alternate-good.err               |   0
 tests/qapi-schema/alternate-good.exit              |   1 +
 tests/qapi-schema/alternate-good.json              |   9 +
 tests/qapi-schema/alternate-good.out               |   6 +
 tests/qapi-schema/alternate-nested.err             |   1 +
 tests/qapi-schema/alternate-nested.exit            |   1 +
 tests/qapi-schema/alternate-nested.json            |   5 +
 tests/qapi-schema/alternate-nested.out             |   0
 tests/qapi-schema/alternate-unknown.err            |   1 +
 tests/qapi-schema/alternate-unknown.exit           |   1 +
 tests/qapi-schema/alternate-unknown.json           |   3 +
 tests/qapi-schema/alternate-unknown.out            |   0
 tests/qapi-schema/bad-base.err                     |   1 +
 tests/qapi-schema/bad-base.exit                    |   1 +
 tests/qapi-schema/bad-base.json                    |   3 +
 tests/qapi-schema/bad-base.out                     |   0
 tests/qapi-schema/bad-data.err                     |   1 +
 tests/qapi-schema/bad-data.exit                    |   1 +
 tests/qapi-schema/bad-data.json                    |   2 +
 tests/qapi-schema/bad-data.out                     |   0
 tests/qapi-schema/bad-ident.err                    |   1 +
 tests/qapi-schema/bad-ident.exit                   |   1 +
 tests/qapi-schema/bad-ident.json                   |   2 +
 tests/qapi-schema/bad-ident.out                    |   0
 tests/qapi-schema/bad-type-bool.err                |   1 +
 tests/qapi-schema/bad-type-bool.exit               |   1 +
 tests/qapi-schema/bad-type-bool.json               |   2 +
 tests/qapi-schema/bad-type-bool.out                |   0
 tests/qapi-schema/bad-type-dict.err                |   1 +
 tests/qapi-schema/bad-type-dict.exit               |   1 +
 tests/qapi-schema/bad-type-dict.json               |   2 +
 tests/qapi-schema/bad-type-dict.out                |   0
 tests/qapi-schema/bad-type-int.err                 |   1 +
 tests/qapi-schema/bad-type-int.exit                |   1 +
 tests/qapi-schema/bad-type-int.json                |   3 +
 tests/qapi-schema/bad-type-int.out                 |   0
 tests/qapi-schema/command-int.err                  |   1 +
 tests/qapi-schema/command-int.exit                 |   1 +
 tests/qapi-schema/command-int.json                 |   3 +
 tests/qapi-schema/command-int.out                  |   0
 tests/qapi-schema/data-array-empty.err             |   1 +
 tests/qapi-schema/data-array-empty.exit            |   1 +
 tests/qapi-schema/data-array-empty.json            |   2 +
 tests/qapi-schema/data-array-empty.out             |   0
 tests/qapi-schema/data-array-unknown.err           |   1 +
 tests/qapi-schema/data-array-unknown.exit          |   1 +
 tests/qapi-schema/data-array-unknown.json          |   2 +
 tests/qapi-schema/data-array-unknown.out           |   0
 tests/qapi-schema/data-int.err                     |   1 +
 tests/qapi-schema/data-int.exit                    |   1 +
 tests/qapi-schema/data-int.json                    |   2 +
 tests/qapi-schema/data-int.out                     |   0
 tests/qapi-schema/data-member-array-bad.err        |   1 +
 tests/qapi-schema/data-member-array-bad.exit       |   1 +
 tests/qapi-schema/data-member-array-bad.json       |   2 +
 tests/qapi-schema/data-member-array-bad.out        |   0
 tests/qapi-schema/data-member-array.err            |   0
 tests/qapi-schema/data-member-array.exit           |   1 +
 tests/qapi-schema/data-member-array.json           |   4 +
 tests/qapi-schema/data-member-array.out            |   5 +
 tests/qapi-schema/data-member-unknown.err          |   1 +
 tests/qapi-schema/data-member-unknown.exit         |   1 +
 tests/qapi-schema/data-member-unknown.json         |   2 +
 tests/qapi-schema/data-member-unknown.out          |   0
 tests/qapi-schema/data-unknown.err                 |   1 +
 tests/qapi-schema/data-unknown.exit                |   1 +
 tests/qapi-schema/data-unknown.json                |   2 +
 tests/qapi-schema/data-unknown.out                 |   0
 tests/qapi-schema/double-data.err                  |   1 +
 tests/qapi-schema/double-data.exit                 |   1 +
 tests/qapi-schema/double-data.json                 |   2 +
 tests/qapi-schema/double-data.out                  |   0
 tests/qapi-schema/double-type.err                  |   1 +
 tests/qapi-schema/double-type.exit                 |   1 +
 tests/qapi-schema/double-type.json                 |   2 +
 tests/qapi-schema/double-type.out                  |   0
 tests/qapi-schema/enum-bad-name.err                |   1 +
 tests/qapi-schema/enum-bad-name.exit               |   1 +
 tests/qapi-schema/enum-bad-name.json               |   2 +
 tests/qapi-schema/enum-bad-name.out                |   0
 tests/qapi-schema/enum-clash-member.err            |   1 +
 tests/qapi-schema/enum-clash-member.exit           |   1 +
 tests/qapi-schema/enum-clash-member.json           |   2 +
 tests/qapi-schema/enum-clash-member.out            |   0
 tests/qapi-schema/enum-dict-member.err             |   1 +
 tests/qapi-schema/enum-dict-member.exit            |   1 +
 tests/qapi-schema/enum-dict-member.json            |   2 +
 tests/qapi-schema/enum-dict-member.out             |   0
 tests/qapi-schema/enum-empty.err                   |   0
 tests/qapi-schema/enum-empty.exit                  |   1 +
 tests/qapi-schema/enum-empty.json                  |   2 +
 tests/qapi-schema/enum-empty.out                   |   3 +
 tests/qapi-schema/enum-int-member.err              |   1 +
 tests/qapi-schema/enum-int-member.exit             |   1 +
 tests/qapi-schema/enum-int-member.json             |   3 +
 tests/qapi-schema/enum-int-member.out              |   0
 tests/qapi-schema/enum-max-member.err              |   1 +
 tests/qapi-schema/enum-max-member.exit             |   1 +
 tests/qapi-schema/enum-max-member.json             |   3 +
 tests/qapi-schema/enum-max-member.out              |   0
 tests/qapi-schema/enum-missing-data.err            |   1 +
 tests/qapi-schema/enum-missing-data.exit           |   1 +
 tests/qapi-schema/enum-missing-data.json           |   2 +
 tests/qapi-schema/enum-missing-data.out            |   0
 tests/qapi-schema/enum-union-clash.err             |   1 +
 tests/qapi-schema/enum-union-clash.exit            |   1 +
 tests/qapi-schema/enum-union-clash.json            |   4 +
 tests/qapi-schema/enum-union-clash.out             |   0
 tests/qapi-schema/enum-wrong-data.err              |   1 +
 tests/qapi-schema/enum-wrong-data.exit             |   1 +
 tests/qapi-schema/enum-wrong-data.json             |   2 +
 tests/qapi-schema/enum-wrong-data.out              |   0
 tests/qapi-schema/event-case.err                   |   0
 tests/qapi-schema/event-case.exit                  |   1 +
 tests/qapi-schema/event-case.json                  |   3 +
 tests/qapi-schema/event-case.out                   |   3 +
 tests/qapi-schema/event-max.err                    |   1 +
 tests/qapi-schema/event-max.exit                   |   1 +
 tests/qapi-schema/event-max.json                   |   2 +
 tests/qapi-schema/event-max.out                    |   0
 tests/qapi-schema/event-nest-struct.err            |   2 +-
 tests/qapi-schema/flat-union-bad-base.err          |   1 +
 tests/qapi-schema/flat-union-bad-base.exit         |   1 +
 tests/qapi-schema/flat-union-bad-base.json         |  13 +
 tests/qapi-schema/flat-union-bad-base.out          |   0
 tests/qapi-schema/flat-union-bad-discriminator.err |   1 +
 .../qapi-schema/flat-union-bad-discriminator.exit  |   1 +
 .../qapi-schema/flat-union-bad-discriminator.json  |  15 +
 tests/qapi-schema/flat-union-bad-discriminator.out |   0
 tests/qapi-schema/flat-union-base-star.err         |   1 +
 tests/qapi-schema/flat-union-base-star.exit        |   1 +
 tests/qapi-schema/flat-union-base-star.json        |  12 +
 tests/qapi-schema/flat-union-base-star.out         |   0
 tests/qapi-schema/flat-union-base-union.err        |   1 +
 tests/qapi-schema/flat-union-base-union.exit       |   1 +
 tests/qapi-schema/flat-union-base-union.json       |  15 +
 tests/qapi-schema/flat-union-base-union.out        |   0
 tests/qapi-schema/flat-union-branch-clash.err      |   0
 tests/qapi-schema/flat-union-branch-clash.exit     |   1 +
 tests/qapi-schema/flat-union-branch-clash.json     |  14 +
 tests/qapi-schema/flat-union-branch-clash.out      |   9 +
 tests/qapi-schema/flat-union-inline.err            |   1 +
 tests/qapi-schema/flat-union-inline.exit           |   1 +
 tests/qapi-schema/flat-union-inline.json           |  11 +
 tests/qapi-schema/flat-union-inline.out            |   0
 tests/qapi-schema/flat-union-int-branch.err        |   1 +
 tests/qapi-schema/flat-union-int-branch.exit       |   1 +
 tests/qapi-schema/flat-union-int-branch.json       |  12 +
 tests/qapi-schema/flat-union-int-branch.out        |   0
 .../qapi-schema/flat-union-invalid-branch-key.json |   6 +-
 .../flat-union-invalid-discriminator.err           |   2 +-
 .../flat-union-invalid-discriminator.json          |   6 +-
 tests/qapi-schema/flat-union-no-base.err           |   2 +-
 tests/qapi-schema/flat-union-no-base.json          |  12 +-
 .../flat-union-optional-discriminator.err          |   1 +
 .../flat-union-optional-discriminator.exit         |   1 +
 .../flat-union-optional-discriminator.json         |  10 +
 .../flat-union-optional-discriminator.out          |   0
 tests/qapi-schema/flat-union-reverse-define.json   |   6 +-
 tests/qapi-schema/flat-union-reverse-define.out    |  12 +-
 .../flat-union-string-discriminator.json           |   6 +-
 tests/qapi-schema/ident-with-escape.err            |   1 +
 tests/qapi-schema/ident-with-escape.exit           |   1 +
 tests/qapi-schema/ident-with-escape.json           |   4 +
 tests/qapi-schema/ident-with-escape.out            |   0
 tests/qapi-schema/indented-expr.json               |   4 +-
 tests/qapi-schema/indented-expr.out                |   2 +-
 tests/qapi-schema/missing-type.err                 |   1 +
 tests/qapi-schema/missing-type.exit                |   1 +
 tests/qapi-schema/missing-type.json                |   2 +
 tests/qapi-schema/missing-type.out                 |   0
 tests/qapi-schema/nested-struct-data.err           |   1 +
 tests/qapi-schema/nested-struct-data.exit          |   1 +
 tests/qapi-schema/nested-struct-data.json          |   4 +
 tests/qapi-schema/nested-struct-data.out           |   0
 tests/qapi-schema/nested-struct-returns.err        |   1 +
 tests/qapi-schema/nested-struct-returns.exit       |   1 +
 tests/qapi-schema/nested-struct-returns.json       |   3 +
 tests/qapi-schema/nested-struct-returns.out        |   0
 tests/qapi-schema/qapi-schema-test.json            |  46 +-
 tests/qapi-schema/qapi-schema-test.out             |  54 +--
 tests/qapi-schema/redefined-builtin.err            |   1 +
 tests/qapi-schema/redefined-builtin.exit           |   1 +
 tests/qapi-schema/redefined-builtin.json           |   2 +
 tests/qapi-schema/redefined-builtin.out            |   0
 tests/qapi-schema/redefined-command.err            |   1 +
 tests/qapi-schema/redefined-command.exit           |   1 +
 tests/qapi-schema/redefined-command.json           |   3 +
 tests/qapi-schema/redefined-command.out            |   0
 tests/qapi-schema/redefined-event.err              |   1 +
 tests/qapi-schema/redefined-event.exit             |   1 +
 tests/qapi-schema/redefined-event.json             |   3 +
 tests/qapi-schema/redefined-event.out              |   0
 tests/qapi-schema/redefined-type.err               |   1 +
 tests/qapi-schema/redefined-type.exit              |   1 +
 tests/qapi-schema/redefined-type.json              |   3 +
 tests/qapi-schema/redefined-type.out               |   0
 tests/qapi-schema/returns-alternate.err            |   1 +
 tests/qapi-schema/returns-alternate.exit           |   1 +
 tests/qapi-schema/returns-alternate.json           |   3 +
 tests/qapi-schema/returns-alternate.out            |   0
 tests/qapi-schema/returns-array-bad.err            |   1 +
 tests/qapi-schema/returns-array-bad.exit           |   1 +
 tests/qapi-schema/returns-array-bad.json           |   2 +
 tests/qapi-schema/returns-array-bad.out            |   0
 tests/qapi-schema/returns-int.err                  |   0
 tests/qapi-schema/returns-int.exit                 |   1 +
 tests/qapi-schema/returns-int.json                 |   3 +
 tests/qapi-schema/returns-int.out                  |   3 +
 tests/qapi-schema/returns-unknown.err              |   1 +
 tests/qapi-schema/returns-unknown.exit             |   1 +
 tests/qapi-schema/returns-unknown.json             |   2 +
 tests/qapi-schema/returns-unknown.out              |   0
 tests/qapi-schema/returns-whitelist.err            |   1 +
 tests/qapi-schema/returns-whitelist.exit           |   1 +
 tests/qapi-schema/returns-whitelist.json           |  11 +
 tests/qapi-schema/returns-whitelist.out            |   0
 tests/qapi-schema/type-bypass-bad-gen.err          |   1 +
 tests/qapi-schema/type-bypass-bad-gen.exit         |   1 +
 tests/qapi-schema/type-bypass-bad-gen.json         |   2 +
 tests/qapi-schema/type-bypass-bad-gen.out          |   0
 tests/qapi-schema/type-bypass-no-gen.err           |   1 +
 tests/qapi-schema/type-bypass-no-gen.exit          |   1 +
 tests/qapi-schema/type-bypass-no-gen.json          |   2 +
 tests/qapi-schema/type-bypass-no-gen.out           |   0
 tests/qapi-schema/type-bypass.err                  |   0
 tests/qapi-schema/type-bypass.exit                 |   1 +
 tests/qapi-schema/type-bypass.json                 |   2 +
 tests/qapi-schema/type-bypass.out                  |   3 +
 tests/qapi-schema/union-bad-branch.err             |   1 +
 tests/qapi-schema/union-bad-branch.exit            |   1 +
 tests/qapi-schema/union-bad-branch.json            |   8 +
 tests/qapi-schema/union-bad-branch.out             |   0
 tests/qapi-schema/union-base-no-discriminator.err  |   1 +
 tests/qapi-schema/union-base-no-discriminator.exit |   1 +
 tests/qapi-schema/union-base-no-discriminator.json |  14 +
 tests/qapi-schema/union-base-no-discriminator.out  |   0
 tests/qapi-schema/union-invalid-base.err           |   2 +-
 tests/qapi-schema/union-invalid-base.json          |   8 +-
 tests/qapi-schema/union-max.err                    |   1 +
 tests/qapi-schema/union-max.exit                   |   1 +
 tests/qapi-schema/union-max.json                   |   3 +
 tests/qapi-schema/union-max.out                    |   0
 tests/qapi-schema/union-optional-branch.err        |   1 +
 tests/qapi-schema/union-optional-branch.exit       |   1 +
 tests/qapi-schema/union-optional-branch.json       |   2 +
 tests/qapi-schema/union-optional-branch.out        |   0
 tests/qapi-schema/union-unknown.err                |   1 +
 tests/qapi-schema/union-unknown.exit               |   1 +
 tests/qapi-schema/union-unknown.json               |   3 +
 tests/qapi-schema/union-unknown.out                |   0
 tests/qapi-schema/unknown-expr-key.err             |   1 +
 tests/qapi-schema/unknown-expr-key.exit            |   1 +
 tests/qapi-schema/unknown-expr-key.json            |   2 +
 tests/qapi-schema/unknown-expr-key.out             |   0
 tests/test-qmp-commands.c                          |  37 +-
 tests/test-qmp-input-strict.c                      |  98 +++--
 tests/test-qmp-input-visitor.c                     | 117 +++--
 tests/test-qmp-output-visitor.c                    | 152 +++----
 tests/test-visitor-serialization.c                 |  88 ++--
 297 files changed, 2003 insertions(+), 882 deletions(-)
 create mode 100644 tests/qapi-schema/alternate-array.err
 create mode 100644 tests/qapi-schema/alternate-array.exit
 create mode 100644 tests/qapi-schema/alternate-array.json
 create mode 100644 tests/qapi-schema/alternate-array.out
 create mode 100644 tests/qapi-schema/alternate-base.err
 create mode 100644 tests/qapi-schema/alternate-base.exit
 create mode 100644 tests/qapi-schema/alternate-base.json
 create mode 100644 tests/qapi-schema/alternate-base.out
 create mode 100644 tests/qapi-schema/alternate-clash.err
 create mode 100644 tests/qapi-schema/alternate-clash.exit
 create mode 100644 tests/qapi-schema/alternate-clash.json
 create mode 100644 tests/qapi-schema/alternate-clash.out
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.err
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.exit
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.json
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.out
 create mode 100644 tests/qapi-schema/alternate-conflict-string.err
 create mode 100644 tests/qapi-schema/alternate-conflict-string.exit
 create mode 100644 tests/qapi-schema/alternate-conflict-string.json
 create mode 100644 tests/qapi-schema/alternate-conflict-string.out
 create mode 100644 tests/qapi-schema/alternate-good.err
 create mode 100644 tests/qapi-schema/alternate-good.exit
 create mode 100644 tests/qapi-schema/alternate-good.json
 create mode 100644 tests/qapi-schema/alternate-good.out
 create mode 100644 tests/qapi-schema/alternate-nested.err
 create mode 100644 tests/qapi-schema/alternate-nested.exit
 create mode 100644 tests/qapi-schema/alternate-nested.json
 create mode 100644 tests/qapi-schema/alternate-nested.out
 create mode 100644 tests/qapi-schema/alternate-unknown.err
 create mode 100644 tests/qapi-schema/alternate-unknown.exit
 create mode 100644 tests/qapi-schema/alternate-unknown.json
 create mode 100644 tests/qapi-schema/alternate-unknown.out
 create mode 100644 tests/qapi-schema/bad-base.err
 create mode 100644 tests/qapi-schema/bad-base.exit
 create mode 100644 tests/qapi-schema/bad-base.json
 create mode 100644 tests/qapi-schema/bad-base.out
 create mode 100644 tests/qapi-schema/bad-data.err
 create mode 100644 tests/qapi-schema/bad-data.exit
 create mode 100644 tests/qapi-schema/bad-data.json
 create mode 100644 tests/qapi-schema/bad-data.out
 create mode 100644 tests/qapi-schema/bad-ident.err
 create mode 100644 tests/qapi-schema/bad-ident.exit
 create mode 100644 tests/qapi-schema/bad-ident.json
 create mode 100644 tests/qapi-schema/bad-ident.out
 create mode 100644 tests/qapi-schema/bad-type-bool.err
 create mode 100644 tests/qapi-schema/bad-type-bool.exit
 create mode 100644 tests/qapi-schema/bad-type-bool.json
 create mode 100644 tests/qapi-schema/bad-type-bool.out
 create mode 100644 tests/qapi-schema/bad-type-dict.err
 create mode 100644 tests/qapi-schema/bad-type-dict.exit
 create mode 100644 tests/qapi-schema/bad-type-dict.json
 create mode 100644 tests/qapi-schema/bad-type-dict.out
 create mode 100644 tests/qapi-schema/bad-type-int.err
 create mode 100644 tests/qapi-schema/bad-type-int.exit
 create mode 100644 tests/qapi-schema/bad-type-int.json
 create mode 100644 tests/qapi-schema/bad-type-int.out
 create mode 100644 tests/qapi-schema/command-int.err
 create mode 100644 tests/qapi-schema/command-int.exit
 create mode 100644 tests/qapi-schema/command-int.json
 create mode 100644 tests/qapi-schema/command-int.out
 create mode 100644 tests/qapi-schema/data-array-empty.err
 create mode 100644 tests/qapi-schema/data-array-empty.exit
 create mode 100644 tests/qapi-schema/data-array-empty.json
 create mode 100644 tests/qapi-schema/data-array-empty.out
 create mode 100644 tests/qapi-schema/data-array-unknown.err
 create mode 100644 tests/qapi-schema/data-array-unknown.exit
 create mode 100644 tests/qapi-schema/data-array-unknown.json
 create mode 100644 tests/qapi-schema/data-array-unknown.out
 create mode 100644 tests/qapi-schema/data-int.err
 create mode 100644 tests/qapi-schema/data-int.exit
 create mode 100644 tests/qapi-schema/data-int.json
 create mode 100644 tests/qapi-schema/data-int.out
 create mode 100644 tests/qapi-schema/data-member-array-bad.err
 create mode 100644 tests/qapi-schema/data-member-array-bad.exit
 create mode 100644 tests/qapi-schema/data-member-array-bad.json
 create mode 100644 tests/qapi-schema/data-member-array-bad.out
 create mode 100644 tests/qapi-schema/data-member-array.err
 create mode 100644 tests/qapi-schema/data-member-array.exit
 create mode 100644 tests/qapi-schema/data-member-array.json
 create mode 100644 tests/qapi-schema/data-member-array.out
 create mode 100644 tests/qapi-schema/data-member-unknown.err
 create mode 100644 tests/qapi-schema/data-member-unknown.exit
 create mode 100644 tests/qapi-schema/data-member-unknown.json
 create mode 100644 tests/qapi-schema/data-member-unknown.out
 create mode 100644 tests/qapi-schema/data-unknown.err
 create mode 100644 tests/qapi-schema/data-unknown.exit
 create mode 100644 tests/qapi-schema/data-unknown.json
 create mode 100644 tests/qapi-schema/data-unknown.out
 create mode 100644 tests/qapi-schema/double-data.err
 create mode 100644 tests/qapi-schema/double-data.exit
 create mode 100644 tests/qapi-schema/double-data.json
 create mode 100644 tests/qapi-schema/double-data.out
 create mode 100644 tests/qapi-schema/double-type.err
 create mode 100644 tests/qapi-schema/double-type.exit
 create mode 100644 tests/qapi-schema/double-type.json
 create mode 100644 tests/qapi-schema/double-type.out
 create mode 100644 tests/qapi-schema/enum-bad-name.err
 create mode 100644 tests/qapi-schema/enum-bad-name.exit
 create mode 100644 tests/qapi-schema/enum-bad-name.json
 create mode 100644 tests/qapi-schema/enum-bad-name.out
 create mode 100644 tests/qapi-schema/enum-clash-member.err
 create mode 100644 tests/qapi-schema/enum-clash-member.exit
 create mode 100644 tests/qapi-schema/enum-clash-member.json
 create mode 100644 tests/qapi-schema/enum-clash-member.out
 create mode 100644 tests/qapi-schema/enum-dict-member.err
 create mode 100644 tests/qapi-schema/enum-dict-member.exit
 create mode 100644 tests/qapi-schema/enum-dict-member.json
 create mode 100644 tests/qapi-schema/enum-dict-member.out
 create mode 100644 tests/qapi-schema/enum-empty.err
 create mode 100644 tests/qapi-schema/enum-empty.exit
 create mode 100644 tests/qapi-schema/enum-empty.json
 create mode 100644 tests/qapi-schema/enum-empty.out
 create mode 100644 tests/qapi-schema/enum-int-member.err
 create mode 100644 tests/qapi-schema/enum-int-member.exit
 create mode 100644 tests/qapi-schema/enum-int-member.json
 create mode 100644 tests/qapi-schema/enum-int-member.out
 create mode 100644 tests/qapi-schema/enum-max-member.err
 create mode 100644 tests/qapi-schema/enum-max-member.exit
 create mode 100644 tests/qapi-schema/enum-max-member.json
 create mode 100644 tests/qapi-schema/enum-max-member.out
 create mode 100644 tests/qapi-schema/enum-missing-data.err
 create mode 100644 tests/qapi-schema/enum-missing-data.exit
 create mode 100644 tests/qapi-schema/enum-missing-data.json
 create mode 100644 tests/qapi-schema/enum-missing-data.out
 create mode 100644 tests/qapi-schema/enum-union-clash.err
 create mode 100644 tests/qapi-schema/enum-union-clash.exit
 create mode 100644 tests/qapi-schema/enum-union-clash.json
 create mode 100644 tests/qapi-schema/enum-union-clash.out
 create mode 100644 tests/qapi-schema/enum-wrong-data.err
 create mode 100644 tests/qapi-schema/enum-wrong-data.exit
 create mode 100644 tests/qapi-schema/enum-wrong-data.json
 create mode 100644 tests/qapi-schema/enum-wrong-data.out
 create mode 100644 tests/qapi-schema/event-case.err
 create mode 100644 tests/qapi-schema/event-case.exit
 create mode 100644 tests/qapi-schema/event-case.json
 create mode 100644 tests/qapi-schema/event-case.out
 create mode 100644 tests/qapi-schema/event-max.err
 create mode 100644 tests/qapi-schema/event-max.exit
 create mode 100644 tests/qapi-schema/event-max.json
 create mode 100644 tests/qapi-schema/event-max.out
 create mode 100644 tests/qapi-schema/flat-union-bad-base.err
 create mode 100644 tests/qapi-schema/flat-union-bad-base.exit
 create mode 100644 tests/qapi-schema/flat-union-bad-base.json
 create mode 100644 tests/qapi-schema/flat-union-bad-base.out
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.err
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.exit
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.json
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.out
 create mode 100644 tests/qapi-schema/flat-union-base-star.err
 create mode 100644 tests/qapi-schema/flat-union-base-star.exit
 create mode 100644 tests/qapi-schema/flat-union-base-star.json
 create mode 100644 tests/qapi-schema/flat-union-base-star.out
 create mode 100644 tests/qapi-schema/flat-union-base-union.err
 create mode 100644 tests/qapi-schema/flat-union-base-union.exit
 create mode 100644 tests/qapi-schema/flat-union-base-union.json
 create mode 100644 tests/qapi-schema/flat-union-base-union.out
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.err
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.exit
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.json
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.out
 create mode 100644 tests/qapi-schema/flat-union-inline.err
 create mode 100644 tests/qapi-schema/flat-union-inline.exit
 create mode 100644 tests/qapi-schema/flat-union-inline.json
 create mode 100644 tests/qapi-schema/flat-union-inline.out
 create mode 100644 tests/qapi-schema/flat-union-int-branch.err
 create mode 100644 tests/qapi-schema/flat-union-int-branch.exit
 create mode 100644 tests/qapi-schema/flat-union-int-branch.json
 create mode 100644 tests/qapi-schema/flat-union-int-branch.out
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.err
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.exit
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.json
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.out
 create mode 100644 tests/qapi-schema/ident-with-escape.err
 create mode 100644 tests/qapi-schema/ident-with-escape.exit
 create mode 100644 tests/qapi-schema/ident-with-escape.json
 create mode 100644 tests/qapi-schema/ident-with-escape.out
 create mode 100644 tests/qapi-schema/missing-type.err
 create mode 100644 tests/qapi-schema/missing-type.exit
 create mode 100644 tests/qapi-schema/missing-type.json
 create mode 100644 tests/qapi-schema/missing-type.out
 create mode 100644 tests/qapi-schema/nested-struct-data.err
 create mode 100644 tests/qapi-schema/nested-struct-data.exit
 create mode 100644 tests/qapi-schema/nested-struct-data.json
 create mode 100644 tests/qapi-schema/nested-struct-data.out
 create mode 100644 tests/qapi-schema/nested-struct-returns.err
 create mode 100644 tests/qapi-schema/nested-struct-returns.exit
 create mode 100644 tests/qapi-schema/nested-struct-returns.json
 create mode 100644 tests/qapi-schema/nested-struct-returns.out
 create mode 100644 tests/qapi-schema/redefined-builtin.err
 create mode 100644 tests/qapi-schema/redefined-builtin.exit
 create mode 100644 tests/qapi-schema/redefined-builtin.json
 create mode 100644 tests/qapi-schema/redefined-builtin.out
 create mode 100644 tests/qapi-schema/redefined-command.err
 create mode 100644 tests/qapi-schema/redefined-command.exit
 create mode 100644 tests/qapi-schema/redefined-command.json
 create mode 100644 tests/qapi-schema/redefined-command.out
 create mode 100644 tests/qapi-schema/redefined-event.err
 create mode 100644 tests/qapi-schema/redefined-event.exit
 create mode 100644 tests/qapi-schema/redefined-event.json
 create mode 100644 tests/qapi-schema/redefined-event.out
 create mode 100644 tests/qapi-schema/redefined-type.err
 create mode 100644 tests/qapi-schema/redefined-type.exit
 create mode 100644 tests/qapi-schema/redefined-type.json
 create mode 100644 tests/qapi-schema/redefined-type.out
 create mode 100644 tests/qapi-schema/returns-alternate.err
 create mode 100644 tests/qapi-schema/returns-alternate.exit
 create mode 100644 tests/qapi-schema/returns-alternate.json
 create mode 100644 tests/qapi-schema/returns-alternate.out
 create mode 100644 tests/qapi-schema/returns-array-bad.err
 create mode 100644 tests/qapi-schema/returns-array-bad.exit
 create mode 100644 tests/qapi-schema/returns-array-bad.json
 create mode 100644 tests/qapi-schema/returns-array-bad.out
 create mode 100644 tests/qapi-schema/returns-int.err
 create mode 100644 tests/qapi-schema/returns-int.exit
 create mode 100644 tests/qapi-schema/returns-int.json
 create mode 100644 tests/qapi-schema/returns-int.out
 create mode 100644 tests/qapi-schema/returns-unknown.err
 create mode 100644 tests/qapi-schema/returns-unknown.exit
 create mode 100644 tests/qapi-schema/returns-unknown.json
 create mode 100644 tests/qapi-schema/returns-unknown.out
 create mode 100644 tests/qapi-schema/returns-whitelist.err
 create mode 100644 tests/qapi-schema/returns-whitelist.exit
 create mode 100644 tests/qapi-schema/returns-whitelist.json
 create mode 100644 tests/qapi-schema/returns-whitelist.out
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.err
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.exit
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.json
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.out
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.err
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.exit
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.json
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.out
 create mode 100644 tests/qapi-schema/type-bypass.err
 create mode 100644 tests/qapi-schema/type-bypass.exit
 create mode 100644 tests/qapi-schema/type-bypass.json
 create mode 100644 tests/qapi-schema/type-bypass.out
 create mode 100644 tests/qapi-schema/union-bad-branch.err
 create mode 100644 tests/qapi-schema/union-bad-branch.exit
 create mode 100644 tests/qapi-schema/union-bad-branch.json
 create mode 100644 tests/qapi-schema/union-bad-branch.out
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.err
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.exit
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.json
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.out
 create mode 100644 tests/qapi-schema/union-max.err
 create mode 100644 tests/qapi-schema/union-max.exit
 create mode 100644 tests/qapi-schema/union-max.json
 create mode 100644 tests/qapi-schema/union-max.out
 create mode 100644 tests/qapi-schema/union-optional-branch.err
 create mode 100644 tests/qapi-schema/union-optional-branch.exit
 create mode 100644 tests/qapi-schema/union-optional-branch.json
 create mode 100644 tests/qapi-schema/union-optional-branch.out
 create mode 100644 tests/qapi-schema/union-unknown.err
 create mode 100644 tests/qapi-schema/union-unknown.exit
 create mode 100644 tests/qapi-schema/union-unknown.json
 create mode 100644 tests/qapi-schema/union-unknown.out
 create mode 100644 tests/qapi-schema/unknown-expr-key.err
 create mode 100644 tests/qapi-schema/unknown-expr-key.exit
 create mode 100644 tests/qapi-schema/unknown-expr-key.json
 create mode 100644 tests/qapi-schema/unknown-expr-key.out

-- 
2.1.0

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 01/36] qapi: Add copyright declaration on docs
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-27 14:42   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations Eric Blake
                   ` (38 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru, Michael Roth

While our top-level COPYING with its GPLv2+ license applies to
any documentation file that omits explicit instructions, these
days it's better to be a good example of calling out our
intentions.  Correct use of GPL requires the use of a copyright
statement, so I'm adding notice to two QAPI documents, by
attributing these files to the initial authors and major
contributors.  I used:

$ git blame --line-porcelain $file \
  | sed -n 's/^author //p' | sort | uniq -c | sort -rn

to determine authorship of these two files.  qmp-spec.txt blames
entirely to Red Hat (easy, since my contribution falls in that
category); while qapi-code-gen.txt has multiple contributors
representing multiple entities.  But since it was originally
supplied by Michael Roth, the notice I added there copies the
notice he has used in other files.  As there is no intended
change in license from the implicit one previously present from
the top level, I have not bothered to CC other contributors;
if we want to weaken things to something looser (such as LGPL)
so that there is no question that someone re-implementing the
spec is not forced to use GPL, that would be a different commit.

CC: Michael Roth <mdroth@linux.vnet.ibm.com>
Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: split off from v5:1/28, add Copyright lines
---
 docs/qapi-code-gen.txt |  8 ++++++++
 docs/qmp/qmp-spec.txt  | 10 ++++++++++
 2 files changed, 18 insertions(+)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 8313ba6..e8bbaf8 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -1,5 +1,13 @@
 = How to use the QAPI code generator =

+Copyright IBM Corp. 2011
+Copyright (C) 2012-2015 Red Hat, Inc.
+
+This work is licensed under the terms of the GNU GPL, version 2 or
+later. See the COPYING file in the top-level directory.
+
+== Introduction ==
+
 QAPI is a native C API within QEMU which provides management-level
 functionality to internal/external users. For external
 users/processes, this interface is made available by a JSON-based
diff --git a/docs/qmp/qmp-spec.txt b/docs/qmp/qmp-spec.txt
index 22568c6..3e503e6 100644
--- a/docs/qmp/qmp-spec.txt
+++ b/docs/qmp/qmp-spec.txt
@@ -1,5 +1,15 @@
                       QEMU Machine Protocol Specification

+0. About This Document
+======================
+
+Copyright (C) 2009-2015 Red Hat, Inc.
+
+This work is licensed under the terms of the GNU GPL, version 2 or
+later. See the COPYING file in the top-level directory.
+
+Last revised in March 2015.
+
 1. Introduction
 ===============

-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 01/36] qapi: Add copyright declaration on docs Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-08 16:50   ` Eric Blake
                     ` (2 more replies)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 03/36] qapi: Simplify builtin type handling Eric Blake
                   ` (37 subsequent siblings)
  39 siblings, 3 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Go into more details about the various types of valid expressions
in a qapi schema, including tweaks to document fixes being done
later in the current patch series.  Also fix some stale and missing
documentation in the QMP specification.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: split copyright change into another patch; merge in 1/5, 3/5,
and 5/5 of doc cleanup series; drop simple union with base docs,
and mention relation between simple and flat unions; mention QMP
is encoded as UTF-8; mention extensions of 'single \' quote' and
"\'"; mention that repeating json-object (dict) keys is unspecified.
---
 docs/qapi-code-gen.txt | 418 ++++++++++++++++++++++++++++++++++++++-----------
 docs/qmp/qmp-spec.txt  | 110 ++++++++++---
 2 files changed, 411 insertions(+), 117 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index e8bbaf8..585cb24 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -9,61 +9,178 @@ later. See the COPYING file in the top-level directory.
 == Introduction ==

 QAPI is a native C API within QEMU which provides management-level
-functionality to internal/external users. For external
-users/processes, this interface is made available by a JSON-based
-QEMU Monitor protocol that is provided by the QMP server.
+functionality to internal and external users. For external
+users/processes, this interface is made available by a JSON-based wire
+format for the QEMU Monitor Protocol (QMP) for controlling qemu, as
+well as the QEMU Guest Agent (QGA) for communicating with the guest.

-To map QMP-defined interfaces to the native C QAPI implementations,
-a JSON-based schema is used to define types and function
-signatures, and a set of scripts is used to generate types/signatures,
-and marshaling/dispatch code. The QEMU Guest Agent also uses these
-scripts, paired with a separate schema, to generate
-marshaling/dispatch code for the guest agent server running in the
-guest.
-
-This document will describe how the schemas, scripts, and resulting
-code are used.
+To map QMP and QGA interfaces to the native C QAPI implementations, a
+JSON-based schema is used to define types and function signatures, and
+a set of scripts is used to generate types, signatures, and
+marshaling/dispatch code. This document will describe how the schemas,
+scripts, and resulting code are used.


 == QMP/Guest agent schema ==

-This file defines the types, commands, and events used by QMP.  It should
-fully describe the interface used by QMP.
+A QAPI schema file is designed to be loosely based on JSON
+(http://www.ietf.org/rfc/rfc7159.txt) with changes for quoting style
+and the use of comments; a QAPI schema file is then parsed by a python
+code generation program.  A valid QAPI schema consists of a series of
+top-level expressions, with no commas between them.  Where
+dictionaries (JSON objects) are used, they are parsed as python
+OrderedDicts so that ordering is preserved (for predictable layout of
+generated C structs and parameter lists).  Ordering doesn't matter
+between top-level expressions or the keys within an expression, but
+does matter within dictionary values for 'data' and 'returns' members
+of a single expression.  QAPI schema input is written using 'single
+quotes' instead of JSON's "double quotes" (in contrast, QMP usage is
+strict JSON and only uses "double quotes", with no comments).  As in
+JSON, trailing commas are not permitted in arrays or dictionaries.
+Input must be ASCII (although QMP supports full Unicode strings, the
+QAPI parser does not).  At present, there is no place where a QAPI
+schema requires the use of JSON numbers or null.

-This file is designed to be loosely based on JSON although it's technically
-executable Python.  While dictionaries are used, they are parsed as
-OrderedDicts so that ordering is preserved.
+Comments are allowed; anything between an unquoted # and the following
+newline is ignored.  Although there is not yet a documentation
+generator, a form of stylized comments has developed for consistently
+documenting details about an expression and when it was added to the
+schema.  The documentation is delimited between two lines of ##, then
+the first line names the expression, an optional overview is provided,
+then individual documentation about each member of 'data' is provided,
+and finally, a 'Since: x.y.z' tag lists the release that introduced
+the expression.  Optional fields are tagged with the phrase
+'#optional', often with their default value; and extensions added
+after the expression was first released are also given a '(since
+x.y.z)' comment.  For example:

-There are two basic syntaxes used, type definitions and command definitions.
+    ##
+    # @BlockStats:
+    #
+    # Statistics of a virtual block device or a block backing device.
+    #
+    # @device: #optional If the stats are for a virtual block device, the name
+    #          corresponding to the virtual block device.
+    #
+    # @stats:  A @BlockDeviceStats for the device.
+    #
+    # @parent: #optional This describes the file block device if it has one.
+    #
+    # @backing: #optional This describes the backing block device if it has one.
+    #           (Since 2.0)
+    #
+    # Since: 0.14.0
+    ##
+    { 'type': 'BlockStats',
+      'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
+               '*parent': 'BlockStats',
+               '*backing': 'BlockStats'} }

-The first syntax defines a type and is represented by a dictionary.  There are
-three kinds of user-defined types that are supported: complex types,
-enumeration types and union types.
+The schema sets up a series of types, as well as commands and events
+that will use those types.  Forward references are allowed: the parser
+scans in two passes, where the first pass learns all type names, and
+the second validates the schema and generates the code.  This allows
+the definition of complex structs that can have mutually recursive
+types, and allows for indefinite nesting of QMP that satisfies the
+schema.  A type name should not be defined more than once.

-Generally speaking, types definitions should always use CamelCase for the type
-names. Command names should be all lower case with words separated by a hyphen.
+There are six top-level expressions recognized by the parser:
+'include', 'command', 'type', 'enum', 'union', and 'event'.  There are
+several built-in types, such as 'int' and 'str'; additionally, the
+top-level expressions can define complex types, enumeration types, and
+several flavors of union types.  The 'command' and 'event' expressions
+can refer to existing types by name, or list an anonymous type as a
+dictionary. Listing a type name inside an array refers to a
+single-dimension array of that type; multi-dimension arrays are not
+directly supported (although an array of a complex struct that
+contains an array member is possible).
+
+Types, commands, and events share a common namespace.  Therefore,
+generally speaking, type definitions should always use CamelCase for
+user-defined type names, while built-in types are lowercase. Type
+definitions should not end in 'Kind', as this namespace is used for
+creating implicit C enums for visiting union types.  Command names,
+and field names within a type, should be all lower case with words
+separated by a hyphen.  However, some existing older commands and
+complex types use underscore; when extending such expressions,
+consistency is preferred over blindly avoiding underscore.  Event
+names should be ALL_CAPS with words separated by underscore.  The
+special string '**' appears for some commands that manually perform
+their own type checking rather than relying on the type-safe code
+produced by the qapi code generators.
+
+Any name (command, event, type, field, or enum value) beginning with
+"x-" is marked experimental, and may be withdrawn or changed
+incompatibly in a future release.  Downstream vendors may add
+extensions; such extensions should begin with a prefix matching
+"__RFQDN_" (for the reverse-fully-qualified-domain-name of the
+vendor), even if the rest of the name uses dash (example:
+__com.redhat_drive-mirror).  Other than downstream extensions (with
+leading underscore and the use of dots), all names should begin with a
+letter, and contain only ASCII letters, digits, dash, and underscore.
+It is okay to reuse names that match C keywords; the generator will
+rename a field named "default" in the QAPI to "q_default" in the
+generated C code.
+
+In the rest of this document, usage lines are given for each
+expression type, with literal strings written in lower case and
+placeholders written in capitals.  If a literal string includes a
+prefix of '*', that key/value pair can be omitted from the expression.
+For example, a usage statement that includes '*base':COMPLEX-TYPE-NAME
+means that an expression has an optional key 'base', which if present
+must have a value that forms a complex type name.
+
+
+=== Built-in Types ===
+
+The following types are built-in to the parser:
+  'str' - arbitrary UTF-8 string
+  'int' - 64-bit signed integer (although the C code may place further
+          restrictions on acceptable range)
+  'number' - floating point number
+  'bool' - JSON value of true or false
+  'int8', 'int16', 'int32', 'int64' - like 'int', but enforce maximum
+                                      bit size
+  'uint8', 'uint16', 'uint32', 'uint64' - unsigned counterparts
+  'size' - like 'uint64', but allows scaled suffix from command line
+           visitor


 === Includes ===

+Usage: { 'include': STRING }
+
 The QAPI schema definitions can be modularized using the 'include' directive:

- { 'include': 'path/to/file.json'}
+ { 'include': 'path/to/file.json' }

 The directive is evaluated recursively, and include paths are relative to the
-file using the directive. Multiple includes of the same file are safe.
+file using the directive. Multiple includes of the same file are
+safe.  No other keys should appear in the expression, and the include
+value should be a string.
+
+As a matter of style, it is a good idea to have all files be
+self-contained, but at the moment, nothing prevents an included file
+from making a forward reference to a type that is only introduced by
+an outer file.  The parser may be made stricter in the future to
+prevent incomplete include files.


 === Complex types ===

-A complex type is a dictionary containing a single key whose value is a
-dictionary.  This corresponds to a struct in C or an Object in JSON.  An
-example of a complex type is:
+Usage: { 'type': STRING, 'data': DICT, '*base': COMPLEX-TYPE-NAME }
+
+A complex type is a dictionary containing a single 'data' key whose
+value is a dictionary.  This corresponds to a struct in C or an Object
+in JSON. Each value of the 'data' dictionary must be the name of a
+type, or a one-element array containing a type name.  An example of a
+complex type is:

  { 'type': 'MyType',
    'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } }

-The use of '*' as a prefix to the name means the member is optional.
+The use of '*' as a prefix to the name means the member is optional in
+the corresponding QMP usage.

 The default initialization value of an optional argument should not be changed
 between versions of QEMU unless the new default maintains backward
@@ -108,22 +225,52 @@ both fields like this:
  { "file": "/some/place/my-image",
    "backing": "/some/place/my-backing-file" }

+
 === Enumeration types ===

-An enumeration type is a dictionary containing a single key whose value is a
-list of strings.  An example enumeration is:
+Usage: { 'enum': STRING, 'data': ARRAY-OF-STRING }
+
+An enumeration type is a dictionary containing a single 'data' key
+whose value is a list of strings.  An example enumeration is:

  { 'enum': 'MyEnum', 'data': [ 'value1', 'value2', 'value3' ] }

+Nothing prevents an empty enumeration, although it is probably not
+useful.  The list of strings should be lower case; if an enum name
+represents multiple words, use '-' between words.  The string 'max' is
+not allowed as an enum value, and values should not be repeated.
+
+The enumeration values are passed as strings over the QMP protocol,
+but are encoded as C enum integral values in generated code.  While
+the C code starts numbering at 0, it is better to use explicit
+comparisons to enum values than implicit comparisons to 0; the C code
+will also include a generated enum member ending in _MAX for tracking
+the size of the enum, useful when using common functions for
+converting between strings and enum values.  Since the wire format
+always passes by name, it is acceptable to reorder or add new
+enumeration members in any location without breaking QMP clients;
+however, removing enum values would break compatibility.  For any
+complex type that has a field that will only contain a finite set of
+string values, using an enum type for that field is better than
+open-coding the field to be type 'str'.
+
+
 === Union types ===

-Union types are used to let the user choose between several different data
-types.  A union type is defined using a dictionary as explained in the
+Usage: { 'union': STRING, 'data': DICT }
+or:    { 'union': STRING, 'data': DICT, 'base': COMPLEX-TYPE-NAME,
+         'discriminator': ENUM-MEMBER-OF-BASE }
+or:    { 'union': STRING, 'data': DICT, 'discriminator': {} }
+
+Union types are used to let the user choose between several different
+variants for an object.  There are three flavors: simple (no
+discriminator or base), flat (both base and discriminator are
+strings), and anonymous (discriminator is an empty dictionary).  A
+union type is defined using a data dictionary as explained in the
 following paragraphs.

-
-A simple union type defines a mapping from discriminator values to data types
-like in this example:
+A simple union type defines a mapping from automatic discriminator
+values to data types like in this example:

  { 'type': 'FileOptions', 'data': { 'filename': 'str' } }
  { 'type': 'Qcow2Options',
@@ -133,36 +280,34 @@ like in this example:
    'data': { 'file': 'FileOptions',
              'qcow2': 'Qcow2Options' } }

-In the QMP wire format, a simple union is represented by a dictionary that
-contains the 'type' field as a discriminator, and a 'data' field that is of the
-specified data type corresponding to the discriminator value:
+In the QMP wire format, a simple union is represented by a dictionary
+that contains the 'type' field as a discriminator, and a 'data' field
+that is of the specified data type corresponding to the discriminator
+value, as in these examples:

+ { "type": "file", "data" : { "filename": "/some/place/my-image" } }
  { "type": "qcow2", "data" : { "backing-file": "/some/place/my-image",
                                "lazy-refcounts": true } }

+The generated C code uses a struct containing a union. Additionally,
+an implicit C enum 'NameKind' is created, corresponding to the union
+'Name', for accessing the various branches of the union.  No branch of
+the union can be named 'max', as this would collide with the implicit
+enum.  The value for each branch can be of any type.

-A union definition can specify a complex type as its base. In this case, the
-fields of the complex type are included as top-level fields of the union
-dictionary in the QMP wire format. An example definition is:

- { 'type': 'BlockdevCommonOptions', 'data': { 'readonly': 'bool' } }
- { 'union': 'BlockdevOptions',
-   'base': 'BlockdevCommonOptions',
-   'data': { 'raw': 'RawOptions',
-             'qcow2': 'Qcow2Options' } }
+A flat union definition specifies a complex type as its base, and
+avoids nesting on the wire.  All branches of the union must be
+complex types, and the top-level fields of the union dictionary on
+the wire will be combination of fields from both the base type and the
+appropriate branch type (when merging two dictionaries, there must be
+no keys in common).  The 'discriminator' field must be the name of an
+enum-typed member of the base type.

-And it looks like this on the wire:
-
- { "type": "qcow2",
-   "readonly": false,
-   "data" : { "backing-file": "/some/place/my-image",
-              "lazy-refcounts": true } }
-
-
-Flat union types avoid the nesting on the wire. They are used whenever a
-specific field of the base type is declared as the discriminator ('type' is
-then no longer generated). The discriminator must be of enumeration type.
-The above example can then be modified as follows:
+The following example enhances the above simple union example by
+adding a common field 'readonly', renaming the discriminator to
+something more applicable, and reducing the number of {} required on
+the wire:

  { 'enum': 'BlockdevDriver', 'data': [ 'raw', 'qcow2' ] }
  { 'type': 'BlockdevCommonOptions',
@@ -170,28 +315,47 @@ The above example can then be modified as follows:
  { 'union': 'BlockdevOptions',
    'base': 'BlockdevCommonOptions',
    'discriminator': 'driver',
-   'data': { 'raw': 'RawOptions',
+   'data': { 'file': 'FileOptions',
              'qcow2': 'Qcow2Options' } }

-Resulting in this JSON object:
+Resulting in these JSON objects:

- { "driver": "qcow2",
-   "readonly": false,
-   "backing-file": "/some/place/my-image",
-   "lazy-refcounts": true }
+ { "driver": "file", "readonly": true,
+   "filename": "/some/place/my-image" }
+ { "driver": "qcow2", "readonly": false,
+   "backing-file": "/some/place/my-image", "lazy-refcounts": true }

+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
+represented as a struct with the base member fields included directly,
+and then a union of structures for each branch of the struct.

-A special type of unions are anonymous unions. They don't form a dictionary in
-the wire format but allow the direct use of different types in their place. As
-they aren't structured, they don't have any explicit discriminator but use
-the (QObject) data type of their value as an implicit discriminator. This means
-that they are restricted to using only one discriminator value per QObject
-type. For example, you cannot have two different complex types in an anonymous
-union, or two different integer types.
+A simple union can always be re-written as a flat union where the base
+class has a single member named 'type', and where each branch of the
+union has a complex type with a single member named 'data'.  That is,

-Anonymous unions are declared using an empty dictionary as their discriminator.
-The discriminator values never appear on the wire, they are only used in the
-generated C code. Anonymous unions cannot have a base type.
+ { 'union': 'Simple', 'data': { 'one': 'str', 'two': 'int' } }
+
+is identical on the wire to:
+
+ { 'enum': 'Enum', 'data': ['one', 'two'] }
+ { 'type': 'Base', 'data': { 'type': 'Enum' } }
+ { 'type': 'Branch1', 'data': { 'data': 'str' } }
+ { 'type': 'Branch2', 'data': { 'data': 'int' } }
+ { 'union': 'Flat': 'base': 'Base', 'discriminator': 'type',
+   'data': { 'one': 'Branch1', 'two': 'Branch2' } }
+
+
+The final flavor of unions is an anonymous union. While the other two
+union types are always passed as a JSON object in the wire format, an
+anonymous union instead allows the direct use of different types in
+its place. Anonymous unions are declared using an empty dictionary as
+their discriminator. The discriminator values never appear on the
+wire, they are only used in the generated C code. Anonymous unions
+cannot have a base type.

  { 'union': 'BlockRef',
    'discriminator': {},
@@ -208,23 +372,95 @@ This example allows using both of the following example objects:

 === Commands ===

-Commands are defined by using a list containing three members.  The first
-member is the command name, the second member is a dictionary containing
-arguments, and the third member is the return type.
+Usage: { 'command': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT,
+         '*returns': TYPE-NAME-OR-DICT,
+         '*gen': false, '*success-response': false }

-An example command is:
+Commands are defined by using a dictionary containing several members,
+where three members are most common.  The 'command' member is a
+mandatory string, and determines the "execute" value passed in a QMP
+command exchange.
+
+The 'data' argument maps to the "arguments" dictionary passed in as
+part of a QMP command.  The 'data' member is optional and defaults to
+{} (an empty dictionary).  If present, it must be the string name of a
+complex type, a one-element array containing the name of a complex
+type, or a dictionary that declares an anonymous type with the same
+semantics as a 'type' expression, with one exception noted below when
+'gen' is used.
+
+The 'returns' member describes what will appear in the "return" field
+of a QMP reply on successful completion of a command.  The member is
+optional from the command declaration; if absent, the "return" field
+will be an empty dictionary.  If 'returns' is present, it must be the
+string name of a complex or built-in type, a one-element array
+containing the name of a complex or built-in type, or a dictionary
+that declares an anonymous type with the same semantics as a 'type'
+expression, with one exception noted below when 'gen' is used.
+Although it is permitted to have the 'returns' member name a built-in
+type or an array of built-in types, any command that does this cannot
+be extended to return additional information in the future; thus, new
+commands should strongly consider returning a dictionary-based type or
+an array of dictionaries, even if the dictionary only contains one
+field at the present.
+
+All commands use a dictionary to report failure, with no way to
+specify that in QAPI.  Where the error return is different than the
+usual GenericError class in order to help the client react differently
+to certain error conditions, it is worth documenting this in the
+comments before the command declaration.
+
+Some example commands:
+
+ { 'command': 'my-first-command',
+   'data': { 'arg1': 'str', '*arg2': 'str' } }
+ { 'type': 'MyType', 'data': { '*value': 'str' } }
+ { 'command': 'my-second-command',
+   'returns': [ 'MyType' ] }
+
+which would validate this QMP transaction:
+
+ => { "execute": "my-first-command",
+      "arguments": { "arg1": "hello" } }
+ <= { "return": { } }
+ => { "execute": "my-second-command" }
+ <= { "return": [ { "value": "one" }, { } ] }
+
+In rare cases, QAPI cannot express a type-safe representation of a
+corresponding QMP command.  In these cases, if the command expression
+includes the key 'gen' with boolean value false, then the 'data' or
+'returns' member that intends to bypass generated type-safety and do
+its own manual validation should use an inline dictionary definition,
+with a value of '**' rather than a valid type name for the keys that
+the generated code will not validate.  Please try to avoid adding new
+commands that rely on this, and instead use type-safe unions.  For an
+example of bypass usage:
+
+ { 'command': 'netdev_add',
+   'data': {'type': 'str', 'id': 'str', '*props': '**'},
+   'gen': false }
+
+Normally, the QAPI schema is used to describe synchronous exchanges,
+where a response is expected.  But in some cases, the action of a
+command is expected to change state in a way that a successful
+response is not possible (although the command will still return a
+normal dictionary error on failure).  When a successful reply is not
+possible, the command expression should include the optional key
+'success-response' with boolean value false.  So far, only QGA makes
+use of this field.

- { 'command': 'my-command',
-   'data': { 'arg1': 'str', '*arg2': 'str' },
-   'returns': 'str' }

 === Events ===

-Events are defined with the keyword 'event'.  When 'data' is also specified,
-additional info will be included in the event.  Finally there will be C API
-generated in qapi-event.h; when called by QEMU code, a message with timestamp
-will be emitted on the wire.  If timestamp is -1, it means failure to retrieve
-host time.
+Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT }
+
+Events are defined with the keyword 'event'.  It is not allowed to
+name an event 'MAX', since the generator also produces a C enumeration
+of all event names with a generated _MAX value at the end.  When
+'data' is also specified, additional info will be included in the
+event, with similar semantics to a 'type' expression.  Finally there
+will be C API generated in qapi-event.h; when called by QEMU code, a
+message with timestamp will be emitted on the wire.

 An example event is:

@@ -319,7 +555,7 @@ Example:
     #ifndef EXAMPLE_QAPI_TYPES_H
     #define EXAMPLE_QAPI_TYPES_H

-[Builtin types omitted...]
+[Built-in types omitted...]

     typedef struct UserDefOne UserDefOne;

@@ -332,7 +568,7 @@ Example:
         struct UserDefOneList *next;
     } UserDefOneList;

-[Functions on builtin types omitted...]
+[Functions on built-in types omitted...]

     struct UserDefOne
     {
@@ -431,7 +667,7 @@ Example:
     #ifndef EXAMPLE_QAPI_VISIT_H
     #define EXAMPLE_QAPI_VISIT_H

-[Visitors for builtin types omitted...]
+[Visitors for built-in types omitted...]

     void visit_type_UserDefOne(Visitor *m, UserDefOne **obj, const char *name, Error **errp);
     void visit_type_UserDefOneList(Visitor *m, UserDefOneList **obj, const char *name, Error **errp);
diff --git a/docs/qmp/qmp-spec.txt b/docs/qmp/qmp-spec.txt
index 3e503e6..e04dbe3 100644
--- a/docs/qmp/qmp-spec.txt
+++ b/docs/qmp/qmp-spec.txt
@@ -13,8 +13,11 @@ Last revised in March 2015.
 1. Introduction
 ===============

-This document specifies the QEMU Machine Protocol (QMP), a JSON-based protocol
-which is available for applications to operate QEMU at the machine-level.
+This document specifies the QEMU Machine Protocol (QMP), a JSON-based
+protocol which is available for applications to operate QEMU at the
+machine-level.  It is also in use by the QEMU Guest Agent (QGA), which
+is available for host applications to interact with the guest
+operating system.

 2. Protocol Specification
 =========================
@@ -28,14 +31,27 @@ following format:

     json-DATA-STRUCTURE-NAME

-Where DATA-STRUCTURE-NAME is any valid JSON data structure, as defined by
-the JSON standard:
+Where DATA-STRUCTURE-NAME is any valid JSON data structure, as defined
+by the JSON standard:

-http://www.ietf.org/rfc/rfc4627.txt
+http://www.ietf.org/rfc/rfc7159.txt

-For convenience, json-object members and json-array elements mentioned in
-this document will be in a certain order. However, in real protocol usage
-they can be in ANY order, thus no particular order should be assumed.
+The protocol is always encoded in UTF-8 except for synchronization
+bytes (documented below); although thanks to json-string escape
+sequences, the server will reply using only the strict ASCII subset.
+
+For convenience, json-object members mentioned in this document will
+be in a certain order. However, in real protocol usage they can be in
+ANY order, thus no particular order should be assumed. On the other
+hand, use of json-array elements presumes that preserving order is
+important unless specifically documented otherwise.  Repeating a key
+within a json-object gives unpredictable results.
+
+Also for convenience, the server will accept an extension of
+'single-quoted' strings in place of the usual "double-quoted"
+json-string, and both input forms of strings understand an additional
+escape sequence of "\'" for a single quote. The server will only use
+double quoting on output.

 2.1 General Definitions
 -----------------------
@@ -62,7 +78,19 @@ The greeting message format is:
 - The "version" member contains the Server's version information (the format
   is the same of the query-version command)
 - The "capabilities" member specify the availability of features beyond the
-  baseline specification
+  baseline specification; the order of elements in this array has no
+  particular significance, so a client must search the entire array
+  when looking for a particular capability
+
+When first connecting to the server, the connection is in a capability
+exchange mode, further documented below.
+
+2.2.1 Capabilities
+------------------
+
+As of the date this document was last revised, no server or client
+capability strings have been defined.
+

 2.3 Issuing Commands
 --------------------
@@ -75,10 +103,14 @@ The format for command execution is:

 - The "execute" member identifies the command to be executed by the Server
 - The "arguments" member is used to pass any arguments required for the
-  execution of the command, it is optional when no arguments are required
+  execution of the command, it is optional when no arguments are
+  required. Each command documents what contents will be considered
+  valid when handling the json-argument
 - The "id" member is a transaction identification associated with the
   command execution, it is optional and will be part of the response if
-  provided
+  provided. The "id" member can be any json-value, although most
+  clients merely use a json-number incremented for each successive
+  command

 2.4 Commands Responses
 ----------------------
@@ -91,13 +123,15 @@ of a command execution: success or error.

 The format of a success response is:

-{ "return": json-object, "id": json-value }
+{ "return": json-value, "id": json-value }

  Where,

-- The "return" member contains the command returned data, which is defined
-  in a per-command basis or an empty json-object if the command does not
-  return data
+- The "return" member contains the data returned by the command, which
+  is defined on a per-command basis (usually a json-object or
+  json-array of json-objects, but sometimes a json-number, json-string,
+  or json-array of json-strings); it is an empty json-object if the
+  command does not return data
 - The "id" member contains the transaction identification associated
   with the command execution if issued by the Client

@@ -124,7 +158,8 @@ if provided by the client.
 -----------------------

 As a result of state changes, the Server may send messages unilaterally
-to the Client at any time. They are called "asynchronous events".
+to the Client at any time, when not in the middle of any other
+response. They are called "asynchronous events".

 The format of asynchronous events is:

@@ -136,13 +171,27 @@ The format of asynchronous events is:
 - The "event" member contains the event's name
 - The "data" member contains event specific data, which is defined in a
   per-event basis, it is optional
-- The "timestamp" member contains the exact time of when the event occurred
-  in the Server. It is a fixed json-object with time in seconds and
-  microseconds
+- The "timestamp" member contains the exact time of when the event
+  occurred in the Server. It is a fixed json-object with time in
+  seconds and microseconds relative to the Unix Epoch (1 Jan 1970); if
+  there is a failure to retrieve host time, both members of the
+  timestamp will be set to -1.

 For a listing of supported asynchronous events, please, refer to the
 qmp-events.txt file.

+2.5 QGA Synchronization
+-----------------------
+
+When using QGA, an additional synchronization feature is built into
+the protocol.  If the Client sends a raw 0xFF sentinel byte (not valid
+JSON), then the Server will reset its state and discard all pending
+data prior to the sentinel.  Conversely, if the Client makes use of
+the 'guest-sync-delimited' command, the Server will send a raw 0xFF
+sentinel byte prior to its response, to aid the Client in discarding
+any data prior to the sentinel.
+
+
 3. QMP Examples
 ===============

@@ -155,32 +204,37 @@ This section provides some examples of real QMP usage, in all of them
 S: { "QMP": { "version": { "qemu": { "micro": 50, "minor": 6, "major": 1 },
      "package": ""}, "capabilities": []}}

-3.2 Simple 'stop' execution
+3.2 Client QMP negotiation
+--------------------------
+C: { "execute": "qmp_capabilities" }
+S: { "return": {}}
+
+3.3 Simple 'stop' execution
 ---------------------------

 C: { "execute": "stop" }
 S: { "return": {} }

-3.3 KVM information
+3.4 KVM information
 -------------------

 C: { "execute": "query-kvm", "id": "example" }
 S: { "return": { "enabled": true, "present": true }, "id": "example"}

-3.4 Parsing error
+3.5 Parsing error
 ------------------

 C: { "execute": }
 S: { "error": { "class": "GenericError", "desc": "Invalid JSON syntax" } }

-3.5 Powerdown event
+3.6 Powerdown event
 -------------------

 S: { "timestamp": { "seconds": 1258551470, "microseconds": 802384 },
     "event": "POWERDOWN" }

 4. Capabilities Negotiation
-----------------------------
+===========================

 When a Client successfully establishes a connection, the Server is in
 Capabilities Negotiation mode.
@@ -199,7 +253,7 @@ effect, all commands (except qmp_capabilities) are allowed and asynchronous
 messages are delivered.

 5 Compatibility Considerations
-------------------------------
+==============================

 All protocol changes or new features which modify the protocol format in an
 incompatible way are disabled by default and will be advertised by the
@@ -223,12 +277,16 @@ However, Clients must not assume any particular:
 - Amount of errors generated by a command, that is, new errors can be added
   to any existing command in newer versions of the Server

+Any command or field name beginning with "x-" is deemed experimental,
+and may be withdrawn or changed in an incompatible manner in a future
+release.
+
 Of course, the Server does guarantee to send valid JSON.  But apart from
 this, a Client should be "conservative in what they send, and liberal in
 what they accept".

 6. Downstream extension of QMP
-------------------------------
+==============================

 We recommend that downstream consumers of QEMU do *not* modify QMP.
 Management tools should be able to support both upstream and downstream
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 03/36] qapi: Simplify builtin type handling
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 01/36] qapi: Add copyright declaration on docs Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 04/36] qapi: Fix generation of 'size' builtin type Eric Blake
                   ` (36 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

There was some redundancy between builtin_types[] and
builtin_type_qtypes{}.  Merge them into one.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: split from 2/28
---
 scripts/qapi-types.py | 10 +++++-----
 scripts/qapi-visit.py |  6 +++---
 scripts/qapi.py       |  8 +-------
 3 files changed, 9 insertions(+), 15 deletions(-)

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index db87218..e400b03 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -182,8 +182,8 @@ const int %(name)s_qtypes[QTYPE_MAX] = {

     for key in members:
         qapi_type = members[key]
-        if builtin_type_qtypes.has_key(qapi_type):
-            qtype = builtin_type_qtypes[qapi_type]
+        if builtin_types.has_key(qapi_type):
+            qtype = builtin_types[qapi_type]
         elif find_struct(qapi_type):
             qtype = "QTYPE_QDICT"
         elif find_union(qapi_type):
@@ -398,7 +398,7 @@ exprs = parse_schema(input_file)
 exprs = filter(lambda expr: not expr.has_key('gen'), exprs)

 fdecl.write(guardstart("QAPI_TYPES_BUILTIN_STRUCT_DECL"))
-for typename in builtin_types:
+for typename in builtin_types.keys():
     fdecl.write(generate_fwd_struct(typename, None, builtin_type=True))
 fdecl.write(guardend("QAPI_TYPES_BUILTIN_STRUCT_DECL"))

@@ -426,7 +426,7 @@ for expr in exprs:
 # to avoid header dependency hell, we always generate declarations
 # for built-in types in our header files and simply guard them
 fdecl.write(guardstart("QAPI_TYPES_BUILTIN_CLEANUP_DECL"))
-for typename in builtin_types:
+for typename in builtin_types.keys():
     fdecl.write(generate_type_cleanup_decl(typename + "List"))
 fdecl.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DECL"))

@@ -435,7 +435,7 @@ fdecl.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DECL"))
 # over these cases
 if do_builtins:
     fdef.write(guardstart("QAPI_TYPES_BUILTIN_CLEANUP_DEF"))
-    for typename in builtin_types:
+    for typename in builtin_types.keys():
         fdef.write(generate_type_cleanup(typename + "List"))
     fdef.write(guardend("QAPI_TYPES_BUILTIN_CLEANUP_DEF"))

diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 8f845a2..4416677 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -261,7 +261,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e
     disc_type = '%sKind' % (name)

     for key in members:
-        assert (members[key] in builtin_types
+        assert (members[key] in builtin_types.keys()
             or find_struct(members[key])
             or find_union(members[key])
             or find_enum(members[key])), "Invalid anonymous union member"
@@ -541,7 +541,7 @@ exprs = parse_schema(input_file)
 # to avoid header dependency hell, we always generate declarations
 # for built-in types in our header files and simply guard them
 fdecl.write(guardstart("QAPI_VISIT_BUILTIN_VISITOR_DECL"))
-for typename in builtin_types:
+for typename in builtin_types.keys():
     fdecl.write(generate_declaration(typename, None, genlist=True,
                                      builtin_type=True))
 fdecl.write(guardend("QAPI_VISIT_BUILTIN_VISITOR_DECL"))
@@ -550,7 +550,7 @@ fdecl.write(guardend("QAPI_VISIT_BUILTIN_VISITOR_DECL"))
 # have the functions defined, so we use -b option to provide control
 # over these cases
 if do_builtins:
-    for typename in builtin_types:
+    for typename in builtin_types.keys():
         fdef.write(generate_visit_list(typename, None))

 for expr in exprs:
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 77d46aa..2b5775d 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -16,13 +16,7 @@ from ordereddict import OrderedDict
 import os
 import sys

-builtin_types = [
-    'str', 'int', 'number', 'bool',
-    'int8', 'int16', 'int32', 'int64',
-    'uint8', 'uint16', 'uint32', 'uint64'
-]
-
-builtin_type_qtypes = {
+builtin_types = {
     'str':      'QTYPE_QSTRING',
     'int':      'QTYPE_QINT',
     'number':   'QTYPE_QFLOAT',
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 04/36] qapi: Fix generation of 'size' builtin type
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (2 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 03/36] qapi: Simplify builtin type handling Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 05/36] qapi: Require ASCII in schema Eric Blake
                   ` (35 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

We were missing the 'size' builtin type (which means that QAPI using
[ 'size' ] would fail to compile).

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: split from 2/28
---
 scripts/qapi.py                         | 1 +
 tests/qapi-schema/qapi-schema-test.json | 3 ++-
 tests/qapi-schema/qapi-schema-test.out  | 2 +-
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 2b5775d..d470347 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -29,6 +29,7 @@ builtin_types = {
     'uint16':   'QTYPE_QINT',
     'uint32':   'QTYPE_QINT',
     'uint64':   'QTYPE_QINT',
+    'size':     'QTYPE_QINT',
 }

 def error_path(parent):
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index d43b5fd..84f0f07 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -74,7 +74,8 @@
             'u64': ['uint64'],
             'number': ['number'],
             'boolean': ['bool'],
-            'string': ['str'] } }
+            'string': ['str'],
+            'sizes': ['size'] } }

 # testing commands
 { 'command': 'user_def_cmd', 'data': {} }
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 08d7304..915a61b 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -12,7 +12,7 @@
  OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]),
  OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]),
  OrderedDict([('union', 'UserDefAnonUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]),
- OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('integer', ['int']), ('s8', ['int8']), ('s16', ['int16']), ('s32', ['int32']), ('s64', ['int64']), ('u8', ['uint8']), ('u16', ['uint16']), ('u32', ['uint32']), ('u64', ['uint64']), ('number', ['number']), ('boolean', ['bool']), ('string', ['str'])]))]),
+ OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('integer', ['int']), ('s8', ['int8']), ('s16', ['int16']), ('s32', ['int32']), ('s64', ['int64']), ('u8', ['uint8']), ('u16', ['uint16']), ('u32', ['uint32']), ('u64', ['uint64']), ('number', ['number']), ('boolean', ['bool']), ('string', ['str']), ('sizes', ['size'])]))]),
  OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]),
  OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]),
  OrderedDict([('command', 'user_def_cmd2'), ('data', OrderedDict([('ud1a', 'UserDefOne'), ('*ud1b', 'UserDefOne')])), ('returns', 'UserDefTwo')]),
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 05/36] qapi: Require ASCII in schema
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (3 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 04/36] qapi: Fix generation of 'size' builtin type Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 06/36] qapi: Add some enum tests Eric Blake
                   ` (34 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Python 2 and Python 3 have a wild history of whether strings
default to ascii or unicode, where Python 3 requires checking
isinstance(foo, basestr) to cover all strings, but where that
code is not portable to Python 2.  It's simpler to just state
that we don't care about Unicode strings, and to just always
use the simpler isinstance(foo, str) everywhere.

I'm no python expert, so I'm basing it on this conversation:
https://lists.gnu.org/archive/html/qemu-devel/2014-09/msg05278.html

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: no change
---
 scripts/qapi.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index d470347..20ee505 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -2,7 +2,7 @@
 # QAPI helper library
 #
 # Copyright IBM, Corp. 2011
-# Copyright (c) 2013 Red Hat Inc.
+# Copyright (c) 2013-2015 Red Hat Inc.
 #
 # Authors:
 #  Anthony Liguori <aliguori@us.ibm.com>
@@ -354,7 +354,7 @@ def parse_schema(input_file):
     return exprs

 def parse_args(typeinfo):
-    if isinstance(typeinfo, basestring):
+    if isinstance(typeinfo, str):
         struct = find_struct(typeinfo)
         assert struct != None
         typeinfo = struct['data']
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 06/36] qapi: Add some enum tests
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (4 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 05/36] qapi: Require ASCII in schema Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-27 16:00   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 07/36] qapi: Better error messages for bad enums Eric Blake
                   ` (33 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Demonstrate that the qapi generator doesn't deal well with enums
that aren't up to par. Later patches will update the expected
results as the generator is made stricter.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: add test enum-bad-name (everything else was R-b Markus, but the
new test means this still needs a review)
---
 tests/Makefile                           | 6 +++++-
 tests/qapi-schema/enum-bad-name.err      | 0
 tests/qapi-schema/enum-bad-name.exit     | 1 +
 tests/qapi-schema/enum-bad-name.json     | 2 ++
 tests/qapi-schema/enum-bad-name.out      | 3 +++
 tests/qapi-schema/enum-clash-member.err  | 0
 tests/qapi-schema/enum-clash-member.exit | 1 +
 tests/qapi-schema/enum-clash-member.json | 2 ++
 tests/qapi-schema/enum-clash-member.out  | 3 +++
 tests/qapi-schema/enum-dict-member.err   | 0
 tests/qapi-schema/enum-dict-member.exit  | 1 +
 tests/qapi-schema/enum-dict-member.json  | 2 ++
 tests/qapi-schema/enum-dict-member.out   | 3 +++
 tests/qapi-schema/enum-empty.err         | 0
 tests/qapi-schema/enum-empty.exit        | 1 +
 tests/qapi-schema/enum-empty.json        | 2 ++
 tests/qapi-schema/enum-empty.out         | 3 +++
 tests/qapi-schema/enum-int-member.err    | 1 +
 tests/qapi-schema/enum-int-member.exit   | 1 +
 tests/qapi-schema/enum-int-member.json   | 3 +++
 tests/qapi-schema/enum-int-member.out    | 0
 tests/qapi-schema/enum-max-member.err    | 0
 tests/qapi-schema/enum-max-member.exit   | 1 +
 tests/qapi-schema/enum-max-member.json   | 3 +++
 tests/qapi-schema/enum-max-member.out    | 3 +++
 tests/qapi-schema/enum-missing-data.err  | 6 ++++++
 tests/qapi-schema/enum-missing-data.exit | 1 +
 tests/qapi-schema/enum-missing-data.json | 2 ++
 tests/qapi-schema/enum-missing-data.out  | 0
 tests/qapi-schema/enum-union-clash.err   | 0
 tests/qapi-schema/enum-union-clash.exit  | 1 +
 tests/qapi-schema/enum-union-clash.json  | 4 ++++
 tests/qapi-schema/enum-union-clash.out   | 5 +++++
 tests/qapi-schema/enum-wrong-data.err    | 0
 tests/qapi-schema/enum-wrong-data.exit   | 1 +
 tests/qapi-schema/enum-wrong-data.json   | 2 ++
 tests/qapi-schema/enum-wrong-data.out    | 3 +++
 37 files changed, 66 insertions(+), 1 deletion(-)
 create mode 100644 tests/qapi-schema/enum-bad-name.err
 create mode 100644 tests/qapi-schema/enum-bad-name.exit
 create mode 100644 tests/qapi-schema/enum-bad-name.json
 create mode 100644 tests/qapi-schema/enum-bad-name.out
 create mode 100644 tests/qapi-schema/enum-clash-member.err
 create mode 100644 tests/qapi-schema/enum-clash-member.exit
 create mode 100644 tests/qapi-schema/enum-clash-member.json
 create mode 100644 tests/qapi-schema/enum-clash-member.out
 create mode 100644 tests/qapi-schema/enum-dict-member.err
 create mode 100644 tests/qapi-schema/enum-dict-member.exit
 create mode 100644 tests/qapi-schema/enum-dict-member.json
 create mode 100644 tests/qapi-schema/enum-dict-member.out
 create mode 100644 tests/qapi-schema/enum-empty.err
 create mode 100644 tests/qapi-schema/enum-empty.exit
 create mode 100644 tests/qapi-schema/enum-empty.json
 create mode 100644 tests/qapi-schema/enum-empty.out
 create mode 100644 tests/qapi-schema/enum-int-member.err
 create mode 100644 tests/qapi-schema/enum-int-member.exit
 create mode 100644 tests/qapi-schema/enum-int-member.json
 create mode 100644 tests/qapi-schema/enum-int-member.out
 create mode 100644 tests/qapi-schema/enum-max-member.err
 create mode 100644 tests/qapi-schema/enum-max-member.exit
 create mode 100644 tests/qapi-schema/enum-max-member.json
 create mode 100644 tests/qapi-schema/enum-max-member.out
 create mode 100644 tests/qapi-schema/enum-missing-data.err
 create mode 100644 tests/qapi-schema/enum-missing-data.exit
 create mode 100644 tests/qapi-schema/enum-missing-data.json
 create mode 100644 tests/qapi-schema/enum-missing-data.out
 create mode 100644 tests/qapi-schema/enum-union-clash.err
 create mode 100644 tests/qapi-schema/enum-union-clash.exit
 create mode 100644 tests/qapi-schema/enum-union-clash.json
 create mode 100644 tests/qapi-schema/enum-union-clash.out
 create mode 100644 tests/qapi-schema/enum-wrong-data.err
 create mode 100644 tests/qapi-schema/enum-wrong-data.exit
 create mode 100644 tests/qapi-schema/enum-wrong-data.json
 create mode 100644 tests/qapi-schema/enum-wrong-data.out

diff --git a/tests/Makefile b/tests/Makefile
index b2adf16..06e7d2f 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -207,7 +207,11 @@ $(foreach target,$(SYSEMU_TARGET_LIST), \
 		$(eval check-qtest-$(target)-y += tests/qom-test$(EXESUF))))

 check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
-	comments.json empty.json funny-char.json indented-expr.json \
+	comments.json empty.json enum-empty.json enum-missing-data.json \
+	enum-wrong-data.json enum-int-member.json enum-dict-member.json \
+	enum-clash-member.json enum-max-member.json enum-union-clash.json \
+	enum-bad-name.json \
+	funny-char.json indented-expr.json \
 	missing-colon.json missing-comma-list.json \
 	missing-comma-object.json non-objects.json \
 	qapi-schema-test.json quoted-structural-chars.json \
diff --git a/tests/qapi-schema/enum-bad-name.err b/tests/qapi-schema/enum-bad-name.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-bad-name.exit b/tests/qapi-schema/enum-bad-name.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/enum-bad-name.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/enum-bad-name.json b/tests/qapi-schema/enum-bad-name.json
new file mode 100644
index 0000000..0c32448
--- /dev/null
+++ b/tests/qapi-schema/enum-bad-name.json
@@ -0,0 +1,2 @@
+# FIXME: we should ensure all enum names can map to C
+{ 'enum': 'MyEnum', 'data': [ 'not^possible' ] }
diff --git a/tests/qapi-schema/enum-bad-name.out b/tests/qapi-schema/enum-bad-name.out
new file mode 100644
index 0000000..d24ea49
--- /dev/null
+++ b/tests/qapi-schema/enum-bad-name.out
@@ -0,0 +1,3 @@
+[OrderedDict([('enum', 'MyEnum'), ('data', ['not^possible'])])]
+[{'enum_name': 'MyEnum', 'enum_values': ['not^possible']}]
+[]
diff --git a/tests/qapi-schema/enum-clash-member.err b/tests/qapi-schema/enum-clash-member.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-clash-member.exit b/tests/qapi-schema/enum-clash-member.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/enum-clash-member.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/enum-clash-member.json b/tests/qapi-schema/enum-clash-member.json
new file mode 100644
index 0000000..99d442a
--- /dev/null
+++ b/tests/qapi-schema/enum-clash-member.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject enums where members will clash when mapped to C enum
+{ 'enum': 'MyEnum', 'data': [ 'one', 'ONE' ] }
diff --git a/tests/qapi-schema/enum-clash-member.out b/tests/qapi-schema/enum-clash-member.out
new file mode 100644
index 0000000..0814459
--- /dev/null
+++ b/tests/qapi-schema/enum-clash-member.out
@@ -0,0 +1,3 @@
+[OrderedDict([('enum', 'MyEnum'), ('data', ['one', 'ONE'])])]
+[{'enum_name': 'MyEnum', 'enum_values': ['one', 'ONE']}]
+[]
diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-dict-member.exit b/tests/qapi-schema/enum-dict-member.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/enum-dict-member.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/enum-dict-member.json b/tests/qapi-schema/enum-dict-member.json
new file mode 100644
index 0000000..de4d6bf
--- /dev/null
+++ b/tests/qapi-schema/enum-dict-member.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject any enum member that is not a string
+{ 'enum': 'MyEnum', 'data': [ { 'value': 'str' } ] }
diff --git a/tests/qapi-schema/enum-dict-member.out b/tests/qapi-schema/enum-dict-member.out
new file mode 100644
index 0000000..8b293f8
--- /dev/null
+++ b/tests/qapi-schema/enum-dict-member.out
@@ -0,0 +1,3 @@
+[OrderedDict([('enum', 'MyEnum'), ('data', [OrderedDict([('value', 'str')])])])]
+[{'enum_name': 'MyEnum', 'enum_values': [OrderedDict([('value', 'str')])]}]
+[]
diff --git a/tests/qapi-schema/enum-empty.err b/tests/qapi-schema/enum-empty.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-empty.exit b/tests/qapi-schema/enum-empty.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/enum-empty.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/enum-empty.json b/tests/qapi-schema/enum-empty.json
new file mode 100644
index 0000000..40d4e85
--- /dev/null
+++ b/tests/qapi-schema/enum-empty.json
@@ -0,0 +1,2 @@
+# An empty enum, although unusual, is currently acceptable
+{ 'enum': 'MyEnum', 'data': [ ] }
diff --git a/tests/qapi-schema/enum-empty.out b/tests/qapi-schema/enum-empty.out
new file mode 100644
index 0000000..3b75c16
--- /dev/null
+++ b/tests/qapi-schema/enum-empty.out
@@ -0,0 +1,3 @@
+[OrderedDict([('enum', 'MyEnum'), ('data', [])])]
+[{'enum_name': 'MyEnum', 'enum_values': []}]
+[]
diff --git a/tests/qapi-schema/enum-int-member.err b/tests/qapi-schema/enum-int-member.err
new file mode 100644
index 0000000..071c521
--- /dev/null
+++ b/tests/qapi-schema/enum-int-member.err
@@ -0,0 +1 @@
+tests/qapi-schema/enum-int-member.json:3:31: Stray "1"
diff --git a/tests/qapi-schema/enum-int-member.exit b/tests/qapi-schema/enum-int-member.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/enum-int-member.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/enum-int-member.json b/tests/qapi-schema/enum-int-member.json
new file mode 100644
index 0000000..6c9c32e
--- /dev/null
+++ b/tests/qapi-schema/enum-int-member.json
@@ -0,0 +1,3 @@
+# we reject any enum member that is not a string
+# FIXME: once the parser understands integer inputs, improve the error message
+{ 'enum': 'MyEnum', 'data': [ 1 ] }
diff --git a/tests/qapi-schema/enum-int-member.out b/tests/qapi-schema/enum-int-member.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-max-member.err b/tests/qapi-schema/enum-max-member.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-max-member.exit b/tests/qapi-schema/enum-max-member.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/enum-max-member.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/enum-max-member.json b/tests/qapi-schema/enum-max-member.json
new file mode 100644
index 0000000..ea854c4
--- /dev/null
+++ b/tests/qapi-schema/enum-max-member.json
@@ -0,0 +1,3 @@
+# FIXME: we should either reject user-supplied 'max', or munge the implicit
+# max value we generate at the end of an array
+{ 'enum': 'MyEnum', 'data': [ 'max' ] }
diff --git a/tests/qapi-schema/enum-max-member.out b/tests/qapi-schema/enum-max-member.out
new file mode 100644
index 0000000..c933044
--- /dev/null
+++ b/tests/qapi-schema/enum-max-member.out
@@ -0,0 +1,3 @@
+[OrderedDict([('enum', 'MyEnum'), ('data', ['max'])])]
+[{'enum_name': 'MyEnum', 'enum_values': ['max']}]
+[]
diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err
new file mode 100644
index 0000000..814ab26
--- /dev/null
+++ b/tests/qapi-schema/enum-missing-data.err
@@ -0,0 +1,6 @@
+Traceback (most recent call last):
+  File "tests/qapi-schema/test-qapi.py", line 19, in <module>
+    exprs = parse_schema(sys.argv[1])
+  File "scripts/qapi.py", line 334, in parse_schema
+    add_enum(expr['enum'], expr['data'])
+KeyError: 'data'
diff --git a/tests/qapi-schema/enum-missing-data.exit b/tests/qapi-schema/enum-missing-data.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/enum-missing-data.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/enum-missing-data.json b/tests/qapi-schema/enum-missing-data.json
new file mode 100644
index 0000000..01f3f32
--- /dev/null
+++ b/tests/qapi-schema/enum-missing-data.json
@@ -0,0 +1,2 @@
+# FIXME: we should require that all QAPI enums have a data array
+{ 'enum': 'MyEnum' }
diff --git a/tests/qapi-schema/enum-missing-data.out b/tests/qapi-schema/enum-missing-data.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-union-clash.err b/tests/qapi-schema/enum-union-clash.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-union-clash.exit b/tests/qapi-schema/enum-union-clash.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/enum-union-clash.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/enum-union-clash.json b/tests/qapi-schema/enum-union-clash.json
new file mode 100644
index 0000000..714ff6d
--- /dev/null
+++ b/tests/qapi-schema/enum-union-clash.json
@@ -0,0 +1,4 @@
+# FIXME: we should reject types that would conflict with implicit union enum
+{ 'enum': 'UnionKind', 'data': [ 'oops' ] }
+{ 'union': 'Union',
+  'data': { 'a': 'int' } }
diff --git a/tests/qapi-schema/enum-union-clash.out b/tests/qapi-schema/enum-union-clash.out
new file mode 100644
index 0000000..d45f5e8
--- /dev/null
+++ b/tests/qapi-schema/enum-union-clash.out
@@ -0,0 +1,5 @@
+[OrderedDict([('enum', 'UnionKind'), ('data', ['oops'])]),
+ OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int')]))])]
+[{'enum_name': 'UnionKind', 'enum_values': ['oops']},
+ {'enum_name': 'UnionKind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/enum-wrong-data.err b/tests/qapi-schema/enum-wrong-data.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/enum-wrong-data.exit b/tests/qapi-schema/enum-wrong-data.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/enum-wrong-data.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/enum-wrong-data.json b/tests/qapi-schema/enum-wrong-data.json
new file mode 100644
index 0000000..61d25ec
--- /dev/null
+++ b/tests/qapi-schema/enum-wrong-data.json
@@ -0,0 +1,2 @@
+# FIXME: we should require that all qapi enums have an array for data
+{ 'enum': 'MyEnum', 'data': { 'value': 'str' } }
diff --git a/tests/qapi-schema/enum-wrong-data.out b/tests/qapi-schema/enum-wrong-data.out
new file mode 100644
index 0000000..28d2211
--- /dev/null
+++ b/tests/qapi-schema/enum-wrong-data.out
@@ -0,0 +1,3 @@
+[OrderedDict([('enum', 'MyEnum'), ('data', OrderedDict([('value', 'str')]))])]
+[{'enum_name': 'MyEnum', 'enum_values': OrderedDict([('value', 'str')])}]
+[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 07/36] qapi: Better error messages for bad enums
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (5 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 06/36] qapi: Add some enum tests Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-28 23:08   ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 08/36] qapi: Add some union tests Eric Blake
                   ` (32 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

The previous commit demonstrated that the generator had several
flaws with less-than-perfect enums:
- an enum that listed the same string twice (or two variant
strings that map to the same C enumerator) ended up generating
an invalid C enum
- because the generator adds a _MAX terminator to each enum,
the use of an enum member 'max' can also cause this clash
- if an enum omits 'data', the generator left a python stack
trace rather than a graceful message
- an enum that used a non-array 'data' was silently accepted by
the parser
- an enum that used non-string members in the 'data' member
was silently accepted by the parser

Add check_enum to cover these situations, and update testcases
to match.  While valid .json files won't trigger any of these
cases, we might as well be nicer to developers that make a typo
while trying to add new QAPI code.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: s/FIXME/TODO/ on enum-max-member comment that represents future
idea rather than bug
---
 scripts/qapi.py                          | 34 +++++++++++++++++++++++++++-----
 tests/qapi-schema/enum-clash-member.err  |  1 +
 tests/qapi-schema/enum-clash-member.exit |  2 +-
 tests/qapi-schema/enum-clash-member.json |  2 +-
 tests/qapi-schema/enum-clash-member.out  |  3 ---
 tests/qapi-schema/enum-dict-member.err   |  1 +
 tests/qapi-schema/enum-dict-member.exit  |  2 +-
 tests/qapi-schema/enum-dict-member.json  |  2 +-
 tests/qapi-schema/enum-dict-member.out   |  3 ---
 tests/qapi-schema/enum-max-member.err    |  1 +
 tests/qapi-schema/enum-max-member.exit   |  2 +-
 tests/qapi-schema/enum-max-member.json   |  4 ++--
 tests/qapi-schema/enum-max-member.out    |  3 ---
 tests/qapi-schema/enum-missing-data.err  |  7 +------
 tests/qapi-schema/enum-missing-data.json |  2 +-
 tests/qapi-schema/enum-wrong-data.err    |  1 +
 tests/qapi-schema/enum-wrong-data.exit   |  2 +-
 tests/qapi-schema/enum-wrong-data.json   |  2 +-
 tests/qapi-schema/enum-wrong-data.out    |  3 ---
 19 files changed, 44 insertions(+), 33 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 20ee505..3ce8c33 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -311,13 +311,37 @@ def check_union(expr, expr_info):
         # Todo: add checking for values. Key is checked as above, value can be
         # also checked here, but we need more functions to handle array case.

+def check_enum(expr, expr_info):
+    name = expr['enum']
+    members = expr.get('data')
+    values = { 'MAX': '(automatic)' }
+
+    if not isinstance(members, list):
+        raise QAPIExprError(expr_info,
+                            "Enum '%s' requires an array for 'data'" % name)
+    for member in members:
+        if not isinstance(member, str):
+            raise QAPIExprError(expr_info,
+                                "Enum '%s' member '%s' is not a string"
+                                % (name, member))
+        key = _generate_enum_string(member)
+        if key in values:
+            raise QAPIExprError(expr_info,
+                                "Enum '%s' member '%s' clashes with '%s'"
+                                % (name, member, values[key]))
+        values[key] = member
+
 def check_exprs(schema):
     for expr_elem in schema.exprs:
         expr = expr_elem['expr']
-        if expr.has_key('union'):
-            check_union(expr, expr_elem['info'])
-        if expr.has_key('event'):
-            check_event(expr, expr_elem['info'])
+        info = expr_elem['info']
+
+        if expr.has_key('enum'):
+            check_enum(expr, info)
+        elif expr.has_key('union'):
+            check_union(expr, info)
+        elif expr.has_key('event'):
+            check_event(expr, info)

 def parse_schema(input_file):
     try:
@@ -331,7 +355,7 @@ def parse_schema(input_file):
     for expr_elem in schema.exprs:
         expr = expr_elem['expr']
         if expr.has_key('enum'):
-            add_enum(expr['enum'], expr['data'])
+            add_enum(expr['enum'], expr.get('data'))
         elif expr.has_key('union'):
             add_union(expr)
         elif expr.has_key('type'):
diff --git a/tests/qapi-schema/enum-clash-member.err b/tests/qapi-schema/enum-clash-member.err
index e69de29..48bd136 100644
--- a/tests/qapi-schema/enum-clash-member.err
+++ b/tests/qapi-schema/enum-clash-member.err
@@ -0,0 +1 @@
+tests/qapi-schema/enum-clash-member.json:2: Enum 'MyEnum' member 'ONE' clashes with 'one'
diff --git a/tests/qapi-schema/enum-clash-member.exit b/tests/qapi-schema/enum-clash-member.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/enum-clash-member.exit
+++ b/tests/qapi-schema/enum-clash-member.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/enum-clash-member.json b/tests/qapi-schema/enum-clash-member.json
index 99d442a..b7dc02a 100644
--- a/tests/qapi-schema/enum-clash-member.json
+++ b/tests/qapi-schema/enum-clash-member.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject enums where members will clash when mapped to C enum
+# we reject enums where members will clash when mapped to C enum
 { 'enum': 'MyEnum', 'data': [ 'one', 'ONE' ] }
diff --git a/tests/qapi-schema/enum-clash-member.out b/tests/qapi-schema/enum-clash-member.out
index 0814459..e69de29 100644
--- a/tests/qapi-schema/enum-clash-member.out
+++ b/tests/qapi-schema/enum-clash-member.out
@@ -1,3 +0,0 @@
-[OrderedDict([('enum', 'MyEnum'), ('data', ['one', 'ONE'])])]
-[{'enum_name': 'MyEnum', 'enum_values': ['one', 'ONE']}]
-[]
diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err
index e69de29..7e966a8 100644
--- a/tests/qapi-schema/enum-dict-member.err
+++ b/tests/qapi-schema/enum-dict-member.err
@@ -0,0 +1 @@
+tests/qapi-schema/enum-dict-member.json:2: Enum 'MyEnum' member 'OrderedDict([('value', 'str')])' is not a string
diff --git a/tests/qapi-schema/enum-dict-member.exit b/tests/qapi-schema/enum-dict-member.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/enum-dict-member.exit
+++ b/tests/qapi-schema/enum-dict-member.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/enum-dict-member.json b/tests/qapi-schema/enum-dict-member.json
index de4d6bf..79672e0 100644
--- a/tests/qapi-schema/enum-dict-member.json
+++ b/tests/qapi-schema/enum-dict-member.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject any enum member that is not a string
+# we reject any enum member that is not a string
 { 'enum': 'MyEnum', 'data': [ { 'value': 'str' } ] }
diff --git a/tests/qapi-schema/enum-dict-member.out b/tests/qapi-schema/enum-dict-member.out
index 8b293f8..e69de29 100644
--- a/tests/qapi-schema/enum-dict-member.out
+++ b/tests/qapi-schema/enum-dict-member.out
@@ -1,3 +0,0 @@
-[OrderedDict([('enum', 'MyEnum'), ('data', [OrderedDict([('value', 'str')])])])]
-[{'enum_name': 'MyEnum', 'enum_values': [OrderedDict([('value', 'str')])]}]
-[]
diff --git a/tests/qapi-schema/enum-max-member.err b/tests/qapi-schema/enum-max-member.err
index e69de29..f77837f 100644
--- a/tests/qapi-schema/enum-max-member.err
+++ b/tests/qapi-schema/enum-max-member.err
@@ -0,0 +1 @@
+tests/qapi-schema/enum-max-member.json:3: Enum 'MyEnum' member 'max' clashes with '(automatic)'
diff --git a/tests/qapi-schema/enum-max-member.exit b/tests/qapi-schema/enum-max-member.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/enum-max-member.exit
+++ b/tests/qapi-schema/enum-max-member.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/enum-max-member.json b/tests/qapi-schema/enum-max-member.json
index ea854c4..970a2cd 100644
--- a/tests/qapi-schema/enum-max-member.json
+++ b/tests/qapi-schema/enum-max-member.json
@@ -1,3 +1,3 @@
-# FIXME: we should either reject user-supplied 'max', or munge the implicit
-# max value we generate at the end of an array
+# we reject user-supplied 'max' for clashing with implicit enum end
+# TODO: should we instead munge the the implicit value to avoid the clash?
 { 'enum': 'MyEnum', 'data': [ 'max' ] }
diff --git a/tests/qapi-schema/enum-max-member.out b/tests/qapi-schema/enum-max-member.out
index c933044..e69de29 100644
--- a/tests/qapi-schema/enum-max-member.out
+++ b/tests/qapi-schema/enum-max-member.out
@@ -1,3 +0,0 @@
-[OrderedDict([('enum', 'MyEnum'), ('data', ['max'])])]
-[{'enum_name': 'MyEnum', 'enum_values': ['max']}]
-[]
diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err
index 814ab26..b8ccae0 100644
--- a/tests/qapi-schema/enum-missing-data.err
+++ b/tests/qapi-schema/enum-missing-data.err
@@ -1,6 +1 @@
-Traceback (most recent call last):
-  File "tests/qapi-schema/test-qapi.py", line 19, in <module>
-    exprs = parse_schema(sys.argv[1])
-  File "scripts/qapi.py", line 334, in parse_schema
-    add_enum(expr['enum'], expr['data'])
-KeyError: 'data'
+tests/qapi-schema/enum-missing-data.json:2: Enum 'MyEnum' requires an array for 'data'
diff --git a/tests/qapi-schema/enum-missing-data.json b/tests/qapi-schema/enum-missing-data.json
index 01f3f32..558fd35 100644
--- a/tests/qapi-schema/enum-missing-data.json
+++ b/tests/qapi-schema/enum-missing-data.json
@@ -1,2 +1,2 @@
-# FIXME: we should require that all QAPI enums have a data array
+# we require that all QAPI enums have a data array
 { 'enum': 'MyEnum' }
diff --git a/tests/qapi-schema/enum-wrong-data.err b/tests/qapi-schema/enum-wrong-data.err
index e69de29..11b4347 100644
--- a/tests/qapi-schema/enum-wrong-data.err
+++ b/tests/qapi-schema/enum-wrong-data.err
@@ -0,0 +1 @@
+tests/qapi-schema/enum-wrong-data.json:2: Enum 'MyEnum' requires an array for 'data'
diff --git a/tests/qapi-schema/enum-wrong-data.exit b/tests/qapi-schema/enum-wrong-data.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/enum-wrong-data.exit
+++ b/tests/qapi-schema/enum-wrong-data.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/enum-wrong-data.json b/tests/qapi-schema/enum-wrong-data.json
index 61d25ec..7b3e255 100644
--- a/tests/qapi-schema/enum-wrong-data.json
+++ b/tests/qapi-schema/enum-wrong-data.json
@@ -1,2 +1,2 @@
-# FIXME: we should require that all qapi enums have an array for data
+# we require that all qapi enums have an array for data
 { 'enum': 'MyEnum', 'data': { 'value': 'str' } }
diff --git a/tests/qapi-schema/enum-wrong-data.out b/tests/qapi-schema/enum-wrong-data.out
index 28d2211..e69de29 100644
--- a/tests/qapi-schema/enum-wrong-data.out
+++ b/tests/qapi-schema/enum-wrong-data.out
@@ -1,3 +0,0 @@
-[OrderedDict([('enum', 'MyEnum'), ('data', OrderedDict([('value', 'str')]))])]
-[{'enum_name': 'MyEnum', 'enum_values': OrderedDict([('value', 'str')])}]
-[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 08/36] qapi: Add some union tests
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (6 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 07/36] qapi: Better error messages for bad enums Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-27 16:18   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 09/36] qapi: Clean up test coverage of simple unions Eric Blake
                   ` (31 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Demonstrate that the qapi generator doesn't deal well with unions
that aren't up to par. Later patches will update the expected
reseults as the generator is made stricter.  A few tests work
as planned, but most show poor or missing error messages.

Of particular note, qapi-code-gen.txt documents 'base' only for
flat unions, but the tests here demonstrate that we currently allow
a 'base' to a simple union, although it is exercised only in the
testsuite.  Later patches will remove this undocumented feature, to
give us more flexibility in adding other future extensions to union
types.  For example, one possible extension is the idea of a
type-safe simple enum, where added fields tie the discriminator to
a user-defined enum type rather than creating an implicit enum from
the names in 'data'.  But adding such safety on top of a simple
enum with a base type could look ambiguous with a flat enum;
besides, the documentation also mentions how any simple union can
be represented by an equivalent flat union.  So it will be simpler
to just outlaw support for something we aren't using.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: improve the commit message; add flat-union-base-star,
flat-union-inline, flat-union-int-branch, and
flat-union-branch-clash tests; fix comments and code in
alternate-array, flat-union-bad-base, flat-union-no-base,
and union-base-no-discriminator; drop trailing blank line in
alternate-conflict-string; drop TABs in tests
---
 tests/Makefile                                           | 14 +++++++++++---
 tests/qapi-schema/alternate-array.err                    |  0
 tests/qapi-schema/alternate-array.exit                   |  1 +
 tests/qapi-schema/alternate-array.json                   |  8 ++++++++
 tests/qapi-schema/alternate-array.out                    |  4 ++++
 tests/qapi-schema/alternate-base.err                     |  0
 tests/qapi-schema/alternate-base.exit                    |  1 +
 tests/qapi-schema/alternate-base.json                    |  7 +++++++
 tests/qapi-schema/alternate-base.out                     |  4 ++++
 tests/qapi-schema/alternate-clash.err                    |  0
 tests/qapi-schema/alternate-clash.exit                   |  1 +
 tests/qapi-schema/alternate-clash.json                   |  4 ++++
 tests/qapi-schema/alternate-clash.out                    |  3 +++
 tests/qapi-schema/alternate-conflict-dict.err            |  0
 tests/qapi-schema/alternate-conflict-dict.exit           |  1 +
 tests/qapi-schema/alternate-conflict-dict.json           |  9 +++++++++
 tests/qapi-schema/alternate-conflict-dict.out            |  6 ++++++
 tests/qapi-schema/alternate-conflict-string.err          |  0
 tests/qapi-schema/alternate-conflict-string.exit         |  1 +
 tests/qapi-schema/alternate-conflict-string.json         |  7 +++++++
 tests/qapi-schema/alternate-conflict-string.out          |  5 +++++
 tests/qapi-schema/alternate-good.err                     |  0
 tests/qapi-schema/alternate-good.exit                    |  1 +
 tests/qapi-schema/alternate-good.json                    | 10 ++++++++++
 tests/qapi-schema/alternate-good.out                     |  6 ++++++
 tests/qapi-schema/alternate-nested.err                   |  0
 tests/qapi-schema/alternate-nested.exit                  |  1 +
 tests/qapi-schema/alternate-nested.json                  |  7 +++++++
 tests/qapi-schema/alternate-nested.out                   |  5 +++++
 tests/qapi-schema/alternate-unknown.err                  |  0
 tests/qapi-schema/alternate-unknown.exit                 |  1 +
 tests/qapi-schema/alternate-unknown.json                 |  4 ++++
 tests/qapi-schema/alternate-unknown.out                  |  3 +++
 tests/qapi-schema/flat-union-bad-base.err                |  1 +
 tests/qapi-schema/flat-union-bad-base.exit               |  1 +
 tests/qapi-schema/flat-union-bad-base.json               | 13 +++++++++++++
 tests/qapi-schema/flat-union-bad-base.out                |  0
 tests/qapi-schema/flat-union-bad-discriminator.err       |  0
 tests/qapi-schema/flat-union-bad-discriminator.exit      |  1 +
 tests/qapi-schema/flat-union-bad-discriminator.json      | 14 ++++++++++++++
 tests/qapi-schema/flat-union-bad-discriminator.out       | 10 ++++++++++
 tests/qapi-schema/flat-union-base-star.err               |  1 +
 tests/qapi-schema/flat-union-base-star.exit              |  1 +
 tests/qapi-schema/flat-union-base-star.json              | 12 ++++++++++++
 tests/qapi-schema/flat-union-base-star.out               |  0
 tests/qapi-schema/flat-union-base-union.err              |  1 +
 tests/qapi-schema/flat-union-base-union.exit             |  1 +
 tests/qapi-schema/flat-union-base-union.json             | 15 +++++++++++++++
 tests/qapi-schema/flat-union-base-union.out              |  0
 tests/qapi-schema/flat-union-branch-clash.err            |  0
 tests/qapi-schema/flat-union-branch-clash.exit           |  1 +
 tests/qapi-schema/flat-union-branch-clash.json           | 14 ++++++++++++++
 tests/qapi-schema/flat-union-branch-clash.out            |  9 +++++++++
 tests/qapi-schema/flat-union-inline.err                  |  1 +
 tests/qapi-schema/flat-union-inline.exit                 |  1 +
 tests/qapi-schema/flat-union-inline.json                 | 11 +++++++++++
 tests/qapi-schema/flat-union-inline.out                  |  0
 tests/qapi-schema/flat-union-int-branch.err              |  0
 tests/qapi-schema/flat-union-int-branch.exit             |  1 +
 tests/qapi-schema/flat-union-int-branch.json             | 12 ++++++++++++
 tests/qapi-schema/flat-union-int-branch.out              |  7 +++++++
 tests/qapi-schema/flat-union-no-base.err                 |  2 +-
 tests/qapi-schema/flat-union-no-base.json                |  8 +++++---
 tests/qapi-schema/flat-union-optional-discriminator.err  |  0
 tests/qapi-schema/flat-union-optional-discriminator.exit |  1 +
 tests/qapi-schema/flat-union-optional-discriminator.json | 10 ++++++++++
 tests/qapi-schema/flat-union-optional-discriminator.out  |  7 +++++++
 tests/qapi-schema/union-bad-branch.err                   |  0
 tests/qapi-schema/union-bad-branch.exit                  |  1 +
 tests/qapi-schema/union-bad-branch.json                  |  8 ++++++++
 tests/qapi-schema/union-bad-branch.out                   |  6 ++++++
 tests/qapi-schema/union-base-no-discriminator.err        |  0
 tests/qapi-schema/union-base-no-discriminator.exit       |  1 +
 tests/qapi-schema/union-base-no-discriminator.json       | 14 ++++++++++++++
 tests/qapi-schema/union-base-no-discriminator.out        |  8 ++++++++
 tests/qapi-schema/union-invalid-base.err                 |  2 +-
 tests/qapi-schema/union-invalid-base.json                |  4 +++-
 tests/qapi-schema/union-max.err                          |  0
 tests/qapi-schema/union-max.exit                         |  1 +
 tests/qapi-schema/union-max.json                         |  3 +++
 tests/qapi-schema/union-max.out                          |  3 +++
 tests/qapi-schema/union-optional-branch.err              |  0
 tests/qapi-schema/union-optional-branch.exit             |  1 +
 tests/qapi-schema/union-optional-branch.json             |  2 ++
 tests/qapi-schema/union-optional-branch.out              |  3 +++
 tests/qapi-schema/union-unknown.err                      |  0
 tests/qapi-schema/union-unknown.exit                     |  1 +
 tests/qapi-schema/union-unknown.json                     |  3 +++
 tests/qapi-schema/union-unknown.out                      |  3 +++
 89 files changed, 325 insertions(+), 9 deletions(-)
 create mode 100644 tests/qapi-schema/alternate-array.err
 create mode 100644 tests/qapi-schema/alternate-array.exit
 create mode 100644 tests/qapi-schema/alternate-array.json
 create mode 100644 tests/qapi-schema/alternate-array.out
 create mode 100644 tests/qapi-schema/alternate-base.err
 create mode 100644 tests/qapi-schema/alternate-base.exit
 create mode 100644 tests/qapi-schema/alternate-base.json
 create mode 100644 tests/qapi-schema/alternate-base.out
 create mode 100644 tests/qapi-schema/alternate-clash.err
 create mode 100644 tests/qapi-schema/alternate-clash.exit
 create mode 100644 tests/qapi-schema/alternate-clash.json
 create mode 100644 tests/qapi-schema/alternate-clash.out
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.err
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.exit
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.json
 create mode 100644 tests/qapi-schema/alternate-conflict-dict.out
 create mode 100644 tests/qapi-schema/alternate-conflict-string.err
 create mode 100644 tests/qapi-schema/alternate-conflict-string.exit
 create mode 100644 tests/qapi-schema/alternate-conflict-string.json
 create mode 100644 tests/qapi-schema/alternate-conflict-string.out
 create mode 100644 tests/qapi-schema/alternate-good.err
 create mode 100644 tests/qapi-schema/alternate-good.exit
 create mode 100644 tests/qapi-schema/alternate-good.json
 create mode 100644 tests/qapi-schema/alternate-good.out
 create mode 100644 tests/qapi-schema/alternate-nested.err
 create mode 100644 tests/qapi-schema/alternate-nested.exit
 create mode 100644 tests/qapi-schema/alternate-nested.json
 create mode 100644 tests/qapi-schema/alternate-nested.out
 create mode 100644 tests/qapi-schema/alternate-unknown.err
 create mode 100644 tests/qapi-schema/alternate-unknown.exit
 create mode 100644 tests/qapi-schema/alternate-unknown.json
 create mode 100644 tests/qapi-schema/alternate-unknown.out
 create mode 100644 tests/qapi-schema/flat-union-bad-base.err
 create mode 100644 tests/qapi-schema/flat-union-bad-base.exit
 create mode 100644 tests/qapi-schema/flat-union-bad-base.json
 create mode 100644 tests/qapi-schema/flat-union-bad-base.out
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.err
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.exit
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.json
 create mode 100644 tests/qapi-schema/flat-union-bad-discriminator.out
 create mode 100644 tests/qapi-schema/flat-union-base-star.err
 create mode 100644 tests/qapi-schema/flat-union-base-star.exit
 create mode 100644 tests/qapi-schema/flat-union-base-star.json
 create mode 100644 tests/qapi-schema/flat-union-base-star.out
 create mode 100644 tests/qapi-schema/flat-union-base-union.err
 create mode 100644 tests/qapi-schema/flat-union-base-union.exit
 create mode 100644 tests/qapi-schema/flat-union-base-union.json
 create mode 100644 tests/qapi-schema/flat-union-base-union.out
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.err
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.exit
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.json
 create mode 100644 tests/qapi-schema/flat-union-branch-clash.out
 create mode 100644 tests/qapi-schema/flat-union-inline.err
 create mode 100644 tests/qapi-schema/flat-union-inline.exit
 create mode 100644 tests/qapi-schema/flat-union-inline.json
 create mode 100644 tests/qapi-schema/flat-union-inline.out
 create mode 100644 tests/qapi-schema/flat-union-int-branch.err
 create mode 100644 tests/qapi-schema/flat-union-int-branch.exit
 create mode 100644 tests/qapi-schema/flat-union-int-branch.json
 create mode 100644 tests/qapi-schema/flat-union-int-branch.out
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.err
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.exit
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.json
 create mode 100644 tests/qapi-schema/flat-union-optional-discriminator.out
 create mode 100644 tests/qapi-schema/union-bad-branch.err
 create mode 100644 tests/qapi-schema/union-bad-branch.exit
 create mode 100644 tests/qapi-schema/union-bad-branch.json
 create mode 100644 tests/qapi-schema/union-bad-branch.out
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.err
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.exit
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.json
 create mode 100644 tests/qapi-schema/union-base-no-discriminator.out
 create mode 100644 tests/qapi-schema/union-max.err
 create mode 100644 tests/qapi-schema/union-max.exit
 create mode 100644 tests/qapi-schema/union-max.json
 create mode 100644 tests/qapi-schema/union-max.out
 create mode 100644 tests/qapi-schema/union-optional-branch.err
 create mode 100644 tests/qapi-schema/union-optional-branch.exit
 create mode 100644 tests/qapi-schema/union-optional-branch.json
 create mode 100644 tests/qapi-schema/union-optional-branch.out
 create mode 100644 tests/qapi-schema/union-unknown.err
 create mode 100644 tests/qapi-schema/union-unknown.exit
 create mode 100644 tests/qapi-schema/union-unknown.json
 create mode 100644 tests/qapi-schema/union-unknown.out

diff --git a/tests/Makefile b/tests/Makefile
index 06e7d2f..7b6de1c 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -217,10 +217,18 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	qapi-schema-test.json quoted-structural-chars.json \
 	trailing-comma-list.json trailing-comma-object.json \
 	unclosed-list.json unclosed-object.json unclosed-string.json \
-	duplicate-key.json union-invalid-base.json flat-union-no-base.json \
-	flat-union-invalid-discriminator.json \
+	duplicate-key.json union-invalid-base.json union-bad-branch.json \
+	union-optional-branch.json union-unknown.json union-max.json \
+	flat-union-optional-discriminator.json flat-union-no-base.json \
+	flat-union-invalid-discriminator.json flat-union-inline.json \
 	flat-union-invalid-branch-key.json flat-union-reverse-define.json \
-	flat-union-string-discriminator.json \
+	flat-union-string-discriminator.json union-base-no-discriminator.json \
+	flat-union-bad-discriminator.json flat-union-bad-base.json \
+	flat-union-base-star.json flat-union-int-branch.json \
+	flat-union-base-union.json flat-union-branch-clash.json \
+	alternate-nested.json alternate-unknown.json alternate-clash.json \
+	alternate-good.json alternate-base.json alternate-array.json \
+	alternate-conflict-string.json alternate-conflict-dict.json \
 	include-simple.json include-relpath.json include-format-err.json \
 	include-non-file.json include-no-file.json include-before-err.json \
 	include-nested-err.json include-self-cycle.json include-cycle.json \
diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-array.exit b/tests/qapi-schema/alternate-array.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-array.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json
new file mode 100644
index 0000000..c2965cf
--- /dev/null
+++ b/tests/qapi-schema/alternate-array.json
@@ -0,0 +1,8 @@
+# FIXME: we do not support array branches of anonymous unions yet
+# TODO: should we support this?
+{ 'type': 'One',
+  'data': { 'name': 'str' } }
+{ 'union': 'MyUnion',
+  'discriminator': {},
+  'data': { 'one': 'One',
+            'two': [ 'int' ] } }
diff --git a/tests/qapi-schema/alternate-array.out b/tests/qapi-schema/alternate-array.out
new file mode 100644
index 0000000..90dc22c
--- /dev/null
+++ b/tests/qapi-schema/alternate-array.out
@@ -0,0 +1,4 @@
+[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', ['int'])]))])]
+[{'enum_name': 'MyUnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))])]
diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-base.exit b/tests/qapi-schema/alternate-base.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-base.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json
new file mode 100644
index 0000000..2d36db1
--- /dev/null
+++ b/tests/qapi-schema/alternate-base.json
@@ -0,0 +1,7 @@
+# FIXME: we should reject anonymous union with base type
+{ 'type': 'Base',
+  'data': { 'string': 'str' } }
+{ 'union': 'MyUnion',
+  'base': 'Base',
+  'discriminator': {},
+  'data': { 'number': 'int' } }
diff --git a/tests/qapi-schema/alternate-base.out b/tests/qapi-schema/alternate-base.out
new file mode 100644
index 0000000..7fb31f5
--- /dev/null
+++ b/tests/qapi-schema/alternate-base.out
@@ -0,0 +1,4 @@
+[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', OrderedDict()), ('data', OrderedDict([('number', 'int')]))])]
+[{'enum_name': 'MyUnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])]
diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-clash.exit b/tests/qapi-schema/alternate-clash.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-clash.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-clash.json b/tests/qapi-schema/alternate-clash.json
new file mode 100644
index 0000000..7e2ef23
--- /dev/null
+++ b/tests/qapi-schema/alternate-clash.json
@@ -0,0 +1,4 @@
+# FIXME: we should detect C enum collisions in an anonymous union
+{ 'union': 'Union1',
+  'discriminator': {},
+  'data': { 'one': 'str', 'ONE': 'int' } }
diff --git a/tests/qapi-schema/alternate-clash.out b/tests/qapi-schema/alternate-clash.out
new file mode 100644
index 0000000..c6687fa
--- /dev/null
+++ b/tests/qapi-schema/alternate-clash.out
@@ -0,0 +1,3 @@
+[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('ONE', 'int')]))])]
+[{'enum_name': 'Union1Kind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-conflict-dict.exit b/tests/qapi-schema/alternate-conflict-dict.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-conflict-dict.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json
new file mode 100644
index 0000000..d2ed9de
--- /dev/null
+++ b/tests/qapi-schema/alternate-conflict-dict.json
@@ -0,0 +1,9 @@
+# FIXME: we should reject anonymous unions with multiple object branches
+{ 'type': 'One',
+  'data': { 'name': 'str' } }
+{ 'type': 'Two',
+  'data': { 'value': 'int' } }
+{ 'union': 'MyUnion',
+  'discriminator': {},
+  'data': { 'one': 'One',
+            'two': 'Two' } }
diff --git a/tests/qapi-schema/alternate-conflict-dict.out b/tests/qapi-schema/alternate-conflict-dict.out
new file mode 100644
index 0000000..b9ac945
--- /dev/null
+++ b/tests/qapi-schema/alternate-conflict-dict.out
@@ -0,0 +1,6 @@
+[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))]),
+ OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', 'Two')]))])]
+[{'enum_name': 'MyUnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))])]
diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-conflict-string.exit b/tests/qapi-schema/alternate-conflict-string.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-conflict-string.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json
new file mode 100644
index 0000000..35245a3
--- /dev/null
+++ b/tests/qapi-schema/alternate-conflict-string.json
@@ -0,0 +1,7 @@
+# FIXME: we should reject anonymous unions with multiple string-like branches
+{ 'enum': 'Enum',
+  'data': [ 'hello', 'world' ] }
+{ 'union': 'MyUnion',
+  'discriminator': {},
+  'data': { 'one': 'str',
+            'two': 'Enum' } }
diff --git a/tests/qapi-schema/alternate-conflict-string.out b/tests/qapi-schema/alternate-conflict-string.out
new file mode 100644
index 0000000..e7b39a2
--- /dev/null
+++ b/tests/qapi-schema/alternate-conflict-string.out
@@ -0,0 +1,5 @@
+[OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]),
+ OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('two', 'Enum')]))])]
+[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']},
+ {'enum_name': 'MyUnionKind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/alternate-good.err b/tests/qapi-schema/alternate-good.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-good.exit b/tests/qapi-schema/alternate-good.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-good.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-good.json b/tests/qapi-schema/alternate-good.json
new file mode 100644
index 0000000..73d0993
--- /dev/null
+++ b/tests/qapi-schema/alternate-good.json
@@ -0,0 +1,10 @@
+# Working example of anonymous union
+{ 'type': 'Data',
+  'data': { '*number': 'int', '*name': 'str' } }
+{ 'enum': 'Enum',
+  'data': [ 'hello', 'world' ] }
+{ 'union': 'MyUnion',
+  'discriminator': {},
+  'data': { 'value': 'int',
+            'string': 'Enum',
+            'struct': 'Data' } }
diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out
new file mode 100644
index 0000000..b5117d1
--- /dev/null
+++ b/tests/qapi-schema/alternate-good.out
@@ -0,0 +1,6 @@
+[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]),
+ OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]),
+ OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])]
+[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']},
+ {'enum_name': 'MyUnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])]
diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-nested.exit b/tests/qapi-schema/alternate-nested.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-nested.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json
new file mode 100644
index 0000000..d5812bf
--- /dev/null
+++ b/tests/qapi-schema/alternate-nested.json
@@ -0,0 +1,7 @@
+# FIXME: we should reject a nested anonymous union branch
+{ 'union': 'Union1',
+  'discriminator': {},
+  'data': { 'name': 'str', 'value': 'int' } }
+{ 'union': 'Union2',
+  'discriminator': {},
+  'data': { 'nested': 'Union1' } }
diff --git a/tests/qapi-schema/alternate-nested.out b/tests/qapi-schema/alternate-nested.out
new file mode 100644
index 0000000..0137c1f
--- /dev/null
+++ b/tests/qapi-schema/alternate-nested.out
@@ -0,0 +1,5 @@
+[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('name', 'str'), ('value', 'int')]))]),
+ OrderedDict([('union', 'Union2'), ('discriminator', OrderedDict()), ('data', OrderedDict([('nested', 'Union1')]))])]
+[{'enum_name': 'Union1Kind', 'enum_values': None},
+ {'enum_name': 'Union2Kind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/alternate-unknown.exit b/tests/qapi-schema/alternate-unknown.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/alternate-unknown.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json
new file mode 100644
index 0000000..0bab9c2
--- /dev/null
+++ b/tests/qapi-schema/alternate-unknown.json
@@ -0,0 +1,4 @@
+# FIXME: we should reject an anonymous union with unknown type in branch
+{ 'union': 'Union',
+  'discriminator': {},
+  'data': { 'unknown': 'MissingType' } }
diff --git a/tests/qapi-schema/alternate-unknown.out b/tests/qapi-schema/alternate-unknown.out
new file mode 100644
index 0000000..0911cdc
--- /dev/null
+++ b/tests/qapi-schema/alternate-unknown.out
@@ -0,0 +1,3 @@
+[OrderedDict([('union', 'Union'), ('discriminator', OrderedDict()), ('data', OrderedDict([('unknown', 'MissingType')]))])]
+[{'enum_name': 'UnionKind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err
new file mode 100644
index 0000000..5962ff4
--- /dev/null
+++ b/tests/qapi-schema/flat-union-bad-base.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-bad-base.json:9: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type
diff --git a/tests/qapi-schema/flat-union-bad-base.exit b/tests/qapi-schema/flat-union-bad-base.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/flat-union-bad-base.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json
new file mode 100644
index 0000000..6c14132
--- /dev/null
+++ b/tests/qapi-schema/flat-union-bad-base.json
@@ -0,0 +1,13 @@
+# FIXME: poor message: we require the base to be an existing complex type
+# TODO: should we allow an anonymous inline base type?
+{ 'enum': 'TestEnum',
+  'data': [ 'value1', 'value2' ] }
+{ 'type': 'TestTypeA',
+  'data': { 'string': 'str' } }
+{ 'type': 'TestTypeB',
+  'data': { 'integer': 'int' } }
+{ 'union': 'TestUnion',
+  'base': { 'enum1': 'TestEnum', 'kind': 'str' },
+  'discriminator': 'enum1',
+  'data': { 'value1': 'TestTypeA',
+            'value2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-bad-base.out b/tests/qapi-schema/flat-union-bad-base.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.exit b/tests/qapi-schema/flat-union-bad-discriminator.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-bad-discriminator.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json
new file mode 100644
index 0000000..1599a59
--- /dev/null
+++ b/tests/qapi-schema/flat-union-bad-discriminator.json
@@ -0,0 +1,14 @@
+# FIXME: we should require the discriminator to be a string naming a base-type member
+{ 'enum': 'TestEnum',
+  'data': [ 'value1', 'value2' ] }
+{ 'type': 'TestBase',
+  'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
+{ 'type': 'TestTypeA',
+  'data': { 'string': 'str' } }
+{ 'type': 'TestTypeB',
+  'data': { 'integer': 'int' } }
+{ 'union': 'TestUnion',
+  'base': 'TestBase',
+  'discriminator': [],
+  'data': { 'kind1': 'TestTypeA',
+            'kind2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.out b/tests/qapi-schema/flat-union-bad-discriminator.out
new file mode 100644
index 0000000..b6ce217
--- /dev/null
+++ b/tests/qapi-schema/flat-union-bad-discriminator.out
@@ -0,0 +1,10 @@
+[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
+ OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]),
+ OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', []), ('data', OrderedDict([('kind1', 'TestTypeA'), ('kind2', 'TestTypeB')]))])]
+[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']},
+ {'enum_name': 'TestUnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]),
+ OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
diff --git a/tests/qapi-schema/flat-union-base-star.err b/tests/qapi-schema/flat-union-base-star.err
new file mode 100644
index 0000000..60e47ef
--- /dev/null
+++ b/tests/qapi-schema/flat-union-base-star.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-base-star.json:8: Base '**' is not a valid type
diff --git a/tests/qapi-schema/flat-union-base-star.exit b/tests/qapi-schema/flat-union-base-star.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/flat-union-base-star.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/flat-union-base-star.json b/tests/qapi-schema/flat-union-base-star.json
new file mode 100644
index 0000000..994533a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-base-star.json
@@ -0,0 +1,12 @@
+# we require the base to be an existing complex type
+{ 'enum': 'TestEnum',
+  'data': [ 'value1', 'value2' ] }
+{ 'type': 'TestTypeA',
+  'data': { 'string': 'str' } }
+{ 'type': 'TestTypeB',
+  'data': { 'integer': 'int' } }
+{ 'union': 'TestUnion',
+  'base': '**',
+  'discriminator': 'enum1',
+  'data': { 'value1': 'TestTypeA',
+            'value2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-base-star.out b/tests/qapi-schema/flat-union-base-star.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err
new file mode 100644
index 0000000..185bf51
--- /dev/null
+++ b/tests/qapi-schema/flat-union-base-union.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid type
diff --git a/tests/qapi-schema/flat-union-base-union.exit b/tests/qapi-schema/flat-union-base-union.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/flat-union-base-union.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json
new file mode 100644
index 0000000..838986c
--- /dev/null
+++ b/tests/qapi-schema/flat-union-base-union.json
@@ -0,0 +1,15 @@
+# FIXME: the error message needs help: we require the base to be a struct
+{ 'enum': 'TestEnum',
+  'data': [ 'value1', 'value2' ] }
+{ 'type': 'TestTypeA',
+  'data': { 'string': 'str' } }
+{ 'type': 'TestTypeB',
+  'data': { 'integer': 'int' } }
+{ 'union': 'UnionBase',
+  'data': { 'kind1': 'TestTypeA',
+            'kind2': 'TestTypeB' } }
+{ 'union': 'TestUnion',
+  'base': 'UnionBase',
+  'discriminator': 'type',
+  'data': { 'kind1': 'TestTypeA',
+            'kind2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-base-union.out b/tests/qapi-schema/flat-union-base-union.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-branch-clash.err b/tests/qapi-schema/flat-union-branch-clash.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-branch-clash.exit b/tests/qapi-schema/flat-union-branch-clash.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-branch-clash.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json
new file mode 100644
index 0000000..4091477
--- /dev/null
+++ b/tests/qapi-schema/flat-union-branch-clash.json
@@ -0,0 +1,14 @@
+# FIXME: we should check for no duplicate keys between branches and base
+{ 'enum': 'TestEnum',
+  'data': [ 'value1', 'value2' ] }
+{ 'type': 'Base',
+  'data': { 'enum1': 'TestEnum', 'name': 'str' } }
+{ 'type': 'Branch1',
+  'data': { 'name': 'str' } }
+{ 'type': 'Branch2',
+  'data': { 'value': 'int' } }
+{ 'union': 'TestUnion',
+  'base': 'Base',
+  'discriminator': 'enum1',
+  'data': { 'value1': 'Branch1',
+            'value2': 'Branch2' } }
diff --git a/tests/qapi-schema/flat-union-branch-clash.out b/tests/qapi-schema/flat-union-branch-clash.out
new file mode 100644
index 0000000..5d54113
--- /dev/null
+++ b/tests/qapi-schema/flat-union-branch-clash.out
@@ -0,0 +1,9 @@
+[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
+ OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
+ OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]),
+ OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'Branch1'), ('value2', 'Branch2')]))])]
+[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}]
+[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
+ OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])]
diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
new file mode 100644
index 0000000..51fbe54
--- /dev/null
+++ b/tests/qapi-schema/flat-union-inline.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-inline.json:7: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type
diff --git a/tests/qapi-schema/flat-union-inline.exit b/tests/qapi-schema/flat-union-inline.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/flat-union-inline.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json
new file mode 100644
index 0000000..2bdffeb
--- /dev/null
+++ b/tests/qapi-schema/flat-union-inline.json
@@ -0,0 +1,11 @@
+# FIXME: poor message: we require branches to be a complex type name
+# TODO: should we allow anonymous inline types?
+{ 'enum': 'TestEnum',
+  'data': [ 'value1', 'value2' ] }
+{ 'type': 'Base',
+  'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
+{ 'union': 'TestUnion',
+  'base': { 'enum1': 'TestEnum', 'kind': 'str' },
+  'discriminator': 'enum1',
+  'data': { 'value1': { 'string': 'str' },
+            'value2': { 'integer': 'int' } } }
diff --git a/tests/qapi-schema/flat-union-inline.out b/tests/qapi-schema/flat-union-inline.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-int-branch.err b/tests/qapi-schema/flat-union-int-branch.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-int-branch.exit b/tests/qapi-schema/flat-union-int-branch.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-int-branch.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json
new file mode 100644
index 0000000..3543215
--- /dev/null
+++ b/tests/qapi-schema/flat-union-int-branch.json
@@ -0,0 +1,12 @@
+# FIXME: we should require flat union branches to be a complex type
+{ 'enum': 'TestEnum',
+  'data': [ 'value1', 'value2' ] }
+{ 'type': 'Base',
+  'data': { 'enum1': 'TestEnum' } }
+{ 'type': 'TestTypeB',
+  'data': { 'integer': 'int' } }
+{ 'union': 'TestUnion',
+  'base': 'Base',
+  'discriminator': 'enum1',
+  'data': { 'value1': 'int',
+            'value2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-int-branch.out b/tests/qapi-schema/flat-union-int-branch.out
new file mode 100644
index 0000000..cd40e6c
--- /dev/null
+++ b/tests/qapi-schema/flat-union-int-branch.out
@@ -0,0 +1,7 @@
+[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
+ OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
+ OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'int'), ('value2', 'TestTypeB')]))])]
+[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}]
+[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
+ OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err
index a59749e..97323a0 100644
--- a/tests/qapi-schema/flat-union-no-base.err
+++ b/tests/qapi-schema/flat-union-no-base.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-no-base.json:7: Flat union 'TestUnion' must have a base field
+tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a base field
diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json
index 50f2673..08a0247 100644
--- a/tests/qapi-schema/flat-union-no-base.json
+++ b/tests/qapi-schema/flat-union-no-base.json
@@ -1,10 +1,12 @@
+# FIXME: flat unions should require a base
+# TODO: simple unions should be able to use an enum discriminator
 { 'type': 'TestTypeA',
   'data': { 'string': 'str' } }
-
 { 'type': 'TestTypeB',
   'data': { 'integer': 'int' } }
-
+{ 'enum': 'Enum',
+  'data': [ 'value1', 'value2' ] }
 { 'union': 'TestUnion',
-  'discriminator': 'enum1',
+  'discriminator': 'Enum',
   'data': { 'value1': 'TestTypeA',
             'value2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.err b/tests/qapi-schema/flat-union-optional-discriminator.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.exit b/tests/qapi-schema/flat-union-optional-discriminator.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/flat-union-optional-discriminator.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json
new file mode 100644
index 0000000..ece0d31
--- /dev/null
+++ b/tests/qapi-schema/flat-union-optional-discriminator.json
@@ -0,0 +1,10 @@
+# FIXME: we should require the discriminator to be non-optional
+{ 'enum': 'Enum', 'data': [ 'one', 'two' ] }
+{ 'type': 'Base',
+  'data': { '*switch': 'Enum' } }
+{ 'type': 'Branch', 'data': { 'name': 'str' } }
+{ 'union': 'MyUnion',
+  'base': 'Base',
+  'discriminator': '*switch',
+  'data': { 'one': 'Branch',
+            'two': 'Branch' } }
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.out b/tests/qapi-schema/flat-union-optional-discriminator.out
new file mode 100644
index 0000000..bb7db00
--- /dev/null
+++ b/tests/qapi-schema/flat-union-optional-discriminator.out
@@ -0,0 +1,7 @@
+[OrderedDict([('enum', 'Enum'), ('data', ['one', 'two'])]),
+ OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]),
+ OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', '*switch'), ('data', OrderedDict([('one', 'Branch'), ('two', 'Branch')]))])]
+[{'enum_name': 'Enum', 'enum_values': ['one', 'two']}]
+[OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]),
+ OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))])]
diff --git a/tests/qapi-schema/union-bad-branch.err b/tests/qapi-schema/union-bad-branch.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-bad-branch.exit b/tests/qapi-schema/union-bad-branch.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/union-bad-branch.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/union-bad-branch.json b/tests/qapi-schema/union-bad-branch.json
new file mode 100644
index 0000000..11e46de
--- /dev/null
+++ b/tests/qapi-schema/union-bad-branch.json
@@ -0,0 +1,8 @@
+# FIXME: we should reject normal unions where branches would collide in C
+{ 'type': 'One',
+  'data': { 'string': 'str' } }
+{ 'type': 'Two',
+  'data': { 'number': 'int' } }
+{ 'union': 'MyUnion',
+  'data': { 'one': 'One',
+            'ONE': 'Two' } }
diff --git a/tests/qapi-schema/union-bad-branch.out b/tests/qapi-schema/union-bad-branch.out
new file mode 100644
index 0000000..6baf01b
--- /dev/null
+++ b/tests/qapi-schema/union-bad-branch.out
@@ -0,0 +1,6 @@
+[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))]),
+ OrderedDict([('union', 'MyUnion'), ('data', OrderedDict([('one', 'One'), ('ONE', 'Two')]))])]
+[{'enum_name': 'MyUnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))])]
diff --git a/tests/qapi-schema/union-base-no-discriminator.err b/tests/qapi-schema/union-base-no-discriminator.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-base-no-discriminator.exit b/tests/qapi-schema/union-base-no-discriminator.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/union-base-no-discriminator.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json
new file mode 100644
index 0000000..c8cba3a
--- /dev/null
+++ b/tests/qapi-schema/union-base-no-discriminator.json
@@ -0,0 +1,14 @@
+# FIXME: we should reject simple unions with a base
+{ 'type': 'TestTypeA',
+  'data': { 'string': 'str' } }
+
+{ 'type': 'TestTypeB',
+  'data': { 'integer': 'int' } }
+
+{ 'type': 'Base',
+  'data': { 'string': 'str' } }
+
+{ 'union': 'TestUnion',
+  'base': 'Base',
+  'data': { 'value1': 'TestTypeA',
+            'value2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/union-base-no-discriminator.out b/tests/qapi-schema/union-base-no-discriminator.out
new file mode 100644
index 0000000..505fd57
--- /dev/null
+++ b/tests/qapi-schema/union-base-no-discriminator.out
@@ -0,0 +1,8 @@
+[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('data', OrderedDict([('value1', 'TestTypeA'), ('value2', 'TestTypeB')]))])]
+[{'enum_name': 'TestUnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])]
diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err
index 938f969..3cc82c0 100644
--- a/tests/qapi-schema/union-invalid-base.err
+++ b/tests/qapi-schema/union-invalid-base.err
@@ -1 +1 @@
-tests/qapi-schema/union-invalid-base.json:7: Base 'TestBaseWrong' is not a valid type
+tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid type
diff --git a/tests/qapi-schema/union-invalid-base.json b/tests/qapi-schema/union-invalid-base.json
index 1fa4930..bc5dc8d 100644
--- a/tests/qapi-schema/union-invalid-base.json
+++ b/tests/qapi-schema/union-invalid-base.json
@@ -1,3 +1,4 @@
+# a union base type must be a struct
 { 'type': 'TestTypeA',
   'data': { 'string': 'str' } }

@@ -5,6 +6,7 @@
   'data': { 'integer': 'int' } }

 { 'union': 'TestUnion',
-  'base': 'TestBaseWrong',
+  'base': 'int',
+  'discriminator': 'int',
   'data': { 'value1': 'TestTypeA',
             'value2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/union-max.err b/tests/qapi-schema/union-max.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-max.exit b/tests/qapi-schema/union-max.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/union-max.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/union-max.json b/tests/qapi-schema/union-max.json
new file mode 100644
index 0000000..45648c4
--- /dev/null
+++ b/tests/qapi-schema/union-max.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject 'max' branch in a union, for collision with C enum
+{ 'union': 'Union',
+  'data': { 'max': 'int' } }
diff --git a/tests/qapi-schema/union-max.out b/tests/qapi-schema/union-max.out
new file mode 100644
index 0000000..2757d36
--- /dev/null
+++ b/tests/qapi-schema/union-max.out
@@ -0,0 +1,3 @@
+[OrderedDict([('union', 'Union'), ('data', OrderedDict([('max', 'int')]))])]
+[{'enum_name': 'UnionKind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/union-optional-branch.err b/tests/qapi-schema/union-optional-branch.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-optional-branch.exit b/tests/qapi-schema/union-optional-branch.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/union-optional-branch.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/union-optional-branch.json b/tests/qapi-schema/union-optional-branch.json
new file mode 100644
index 0000000..c513db7
--- /dev/null
+++ b/tests/qapi-schema/union-optional-branch.json
@@ -0,0 +1,2 @@
+# FIXME: union branches cannot be optional
+{ 'union': 'Union', 'data': { '*a': 'int', 'b': 'str' } }
diff --git a/tests/qapi-schema/union-optional-branch.out b/tests/qapi-schema/union-optional-branch.out
new file mode 100644
index 0000000..b03b5d2
--- /dev/null
+++ b/tests/qapi-schema/union-optional-branch.out
@@ -0,0 +1,3 @@
+[OrderedDict([('union', 'Union'), ('data', OrderedDict([('*a', 'int'), ('b', 'str')]))])]
+[{'enum_name': 'UnionKind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/union-unknown.err b/tests/qapi-schema/union-unknown.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/union-unknown.exit b/tests/qapi-schema/union-unknown.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/union-unknown.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/union-unknown.json b/tests/qapi-schema/union-unknown.json
new file mode 100644
index 0000000..258f1d3
--- /dev/null
+++ b/tests/qapi-schema/union-unknown.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject a union with unknown type in branch
+{ 'union': 'Union',
+  'data': { 'unknown': 'MissingType' } }
diff --git a/tests/qapi-schema/union-unknown.out b/tests/qapi-schema/union-unknown.out
new file mode 100644
index 0000000..8223dcf
--- /dev/null
+++ b/tests/qapi-schema/union-unknown.out
@@ -0,0 +1,3 @@
+[OrderedDict([('union', 'Union'), ('data', OrderedDict([('unknown', 'MissingType')]))])]
+[{'enum_name': 'UnionKind', 'enum_values': None}]
+[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 09/36] qapi: Clean up test coverage of simple unions
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (7 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 08/36] qapi: Add some union tests Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 10/36] qapi: Forbid base without discriminator in unions Eric Blake
                   ` (30 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

The tests of UserDefNativeListUnion serve to validate code
generation of simple unions without a base type, except that it
did not have full coverage in the strict test.  The next commits
will remove tests and support for simple unions with a base type,
so there is no real loss at repurposing that test here as
opposed to churn of adding a new test then deleting the old one.

Fix some indentation and long lines while at it.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: split from v5:7/28, use ' instead of " for qmp string
---
 tests/test-qmp-input-strict.c   | 57 +++++++++++++++++++-------------------
 tests/test-qmp-input-visitor.c  | 61 +++++++++++++++++++++--------------------
 tests/test-qmp-output-visitor.c | 38 ++++++++++++++++---------
 3 files changed, 86 insertions(+), 70 deletions(-)

diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index d5360c6..486848e 100644
--- a/tests/test-qmp-input-strict.c
+++ b/tests/test-qmp-input-strict.c
@@ -1,7 +1,7 @@
 /*
  * QMP Input Visitor unit-tests (strict mode).
  *
- * Copyright (C) 2011-2012 Red Hat Inc.
+ * Copyright (C) 2011-2012, 2015 Red Hat Inc.
  *
  * Authors:
  *  Luiz Capitulino <lcapitulino@redhat.com>
@@ -141,18 +141,18 @@ static void test_validate_list(TestInputVisitorData *data,
     qapi_free_UserDefOneList(head);
 }

-static void test_validate_union(TestInputVisitorData *data,
-                                 const void *unused)
+static void test_validate_union_native_list(TestInputVisitorData *data,
+                                            const void *unused)
 {
-    UserDefUnion *tmp = NULL;
+    UserDefNativeListUnion *tmp = NULL;
     Visitor *v;
     Error *err = NULL;

-    v = validate_test_init(data, "{ 'type': 'b', 'integer': 41, 'data' : { 'integer': 42 } }");
+    v = validate_test_init(data, "{ 'type': 'integer', 'data' : [ 1, 2 ] }");

-    visit_type_UserDefUnion(v, &tmp, NULL, &err);
+    visit_type_UserDefNativeListUnion(v, &tmp, NULL, &err);
     g_assert(!err);
-    qapi_free_UserDefUnion(tmp);
+    qapi_free_UserDefNativeListUnion(tmp);
 }

 static void test_validate_union_flat(TestInputVisitorData *data,
@@ -232,18 +232,19 @@ static void test_validate_fail_list(TestInputVisitorData *data,
     qapi_free_UserDefOneList(head);
 }

-static void test_validate_fail_union(TestInputVisitorData *data,
-                                      const void *unused)
+static void test_validate_fail_union_native_list(TestInputVisitorData *data,
+                                                 const void *unused)
 {
-    UserDefUnion *tmp = NULL;
+    UserDefNativeListUnion *tmp = NULL;
     Error *err = NULL;
     Visitor *v;

-    v = validate_test_init(data, "{ 'type': 'b', 'data' : { 'integer': 42 } }");
+    v = validate_test_init(data,
+                           "{ 'type': 'integer', 'data' : [ 'string' ] }");

-    visit_type_UserDefUnion(v, &tmp, NULL, &err);
+    visit_type_UserDefNativeListUnion(v, &tmp, NULL, &err);
     g_assert(err);
-    qapi_free_UserDefUnion(tmp);
+    qapi_free_UserDefNativeListUnion(tmp);
 }

 static void test_validate_fail_union_flat(TestInputVisitorData *data,
@@ -304,31 +305,31 @@ int main(int argc, char **argv)
     g_test_init(&argc, &argv, NULL);

     validate_test_add("/visitor/input-strict/pass/struct",
-                       &testdata, test_validate_struct);
+                      &testdata, test_validate_struct);
     validate_test_add("/visitor/input-strict/pass/struct-nested",
-                       &testdata, test_validate_struct_nested);
+                      &testdata, test_validate_struct_nested);
     validate_test_add("/visitor/input-strict/pass/list",
-                       &testdata, test_validate_list);
-    validate_test_add("/visitor/input-strict/pass/union",
-                       &testdata, test_validate_union);
+                      &testdata, test_validate_list);
     validate_test_add("/visitor/input-strict/pass/union-flat",
-                       &testdata, test_validate_union_flat);
+                      &testdata, test_validate_union_flat);
     validate_test_add("/visitor/input-strict/pass/union-anon",
-                       &testdata, test_validate_union_anon);
+                      &testdata, test_validate_union_anon);
+    validate_test_add("/visitor/input-strict/pass/union-native-list",
+                      &testdata, test_validate_union_native_list);
     validate_test_add("/visitor/input-strict/fail/struct",
-                       &testdata, test_validate_fail_struct);
+                      &testdata, test_validate_fail_struct);
     validate_test_add("/visitor/input-strict/fail/struct-nested",
-                       &testdata, test_validate_fail_struct_nested);
+                      &testdata, test_validate_fail_struct_nested);
     validate_test_add("/visitor/input-strict/fail/list",
-                       &testdata, test_validate_fail_list);
-    validate_test_add("/visitor/input-strict/fail/union",
-                       &testdata, test_validate_fail_union);
+                      &testdata, test_validate_fail_list);
     validate_test_add("/visitor/input-strict/fail/union-flat",
-                       &testdata, test_validate_fail_union_flat);
+                      &testdata, test_validate_fail_union_flat);
     validate_test_add("/visitor/input-strict/fail/union-flat-no-discriminator",
-                       &testdata, test_validate_fail_union_flat_no_discrim);
+                      &testdata, test_validate_fail_union_flat_no_discrim);
     validate_test_add("/visitor/input-strict/fail/union-anon",
-                       &testdata, test_validate_fail_union_anon);
+                      &testdata, test_validate_fail_union_anon);
+    validate_test_add("/visitor/input-strict/fail/union-native-list",
+                      &testdata, test_validate_fail_union_native_list);

     g_test_run();

diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 1c8e872..0039ff6 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -1,7 +1,7 @@
 /*
  * QMP Input Visitor unit-tests.
  *
- * Copyright (C) 2011 Red Hat Inc.
+ * Copyright (C) 2011, 2015 Red Hat Inc.
  *
  * Authors:
  *  Luiz Capitulino <lcapitulino@redhat.com>
@@ -670,55 +670,58 @@ int main(int argc, char **argv)
     input_visitor_test_add("/visitor/input/number",
                            &in_visitor_data, test_visitor_in_number);
     input_visitor_test_add("/visitor/input/string",
-                            &in_visitor_data, test_visitor_in_string);
+                           &in_visitor_data, test_visitor_in_string);
     input_visitor_test_add("/visitor/input/enum",
-                            &in_visitor_data, test_visitor_in_enum);
+                           &in_visitor_data, test_visitor_in_enum);
     input_visitor_test_add("/visitor/input/struct",
-                            &in_visitor_data, test_visitor_in_struct);
+                           &in_visitor_data, test_visitor_in_struct);
     input_visitor_test_add("/visitor/input/struct-nested",
-                            &in_visitor_data, test_visitor_in_struct_nested);
+                           &in_visitor_data, test_visitor_in_struct_nested);
     input_visitor_test_add("/visitor/input/list",
-                            &in_visitor_data, test_visitor_in_list);
+                           &in_visitor_data, test_visitor_in_list);
     input_visitor_test_add("/visitor/input/union",
                             &in_visitor_data, test_visitor_in_union);
     input_visitor_test_add("/visitor/input/union-flat",
-                            &in_visitor_data, test_visitor_in_union_flat);
+                           &in_visitor_data, test_visitor_in_union_flat);
     input_visitor_test_add("/visitor/input/union-anon",
-                            &in_visitor_data, test_visitor_in_union_anon);
+                           &in_visitor_data, test_visitor_in_union_anon);
     input_visitor_test_add("/visitor/input/errors",
-                            &in_visitor_data, test_visitor_in_errors);
+                           &in_visitor_data, test_visitor_in_errors);
     input_visitor_test_add("/visitor/input/native_list/int",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_int);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_int);
     input_visitor_test_add("/visitor/input/native_list/int8",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_int8);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_int8);
     input_visitor_test_add("/visitor/input/native_list/int16",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_int16);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_int16);
     input_visitor_test_add("/visitor/input/native_list/int32",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_int32);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_int32);
     input_visitor_test_add("/visitor/input/native_list/int64",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_int64);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_int64);
     input_visitor_test_add("/visitor/input/native_list/uint8",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_uint8);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_uint8);
     input_visitor_test_add("/visitor/input/native_list/uint16",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_uint16);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_uint16);
     input_visitor_test_add("/visitor/input/native_list/uint32",
-                            &in_visitor_data,
-                            test_visitor_in_native_list_uint32);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_uint32);
     input_visitor_test_add("/visitor/input/native_list/uint64",
-                            &in_visitor_data, test_visitor_in_native_list_uint64);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_uint64);
     input_visitor_test_add("/visitor/input/native_list/bool",
-                            &in_visitor_data, test_visitor_in_native_list_bool);
+                           &in_visitor_data, test_visitor_in_native_list_bool);
     input_visitor_test_add("/visitor/input/native_list/str",
-                            &in_visitor_data, test_visitor_in_native_list_string);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_string);
     input_visitor_test_add("/visitor/input/native_list/number",
-                            &in_visitor_data, test_visitor_in_native_list_number);
+                           &in_visitor_data,
+                           test_visitor_in_native_list_number);

     g_test_run();

diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 74020de..e5bf40f 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -1,7 +1,7 @@
 /*
  * QMP Output Visitor unit-tests.
  *
- * Copyright (C) 2011 Red Hat Inc.
+ * Copyright (C) 2011, 2015 Red Hat Inc.
  *
  * Authors:
  *  Luiz Capitulino <lcapitulino@redhat.com>
@@ -871,29 +871,41 @@ int main(int argc, char **argv)
     output_visitor_test_add("/visitor/output/empty",
                             &out_visitor_data, test_visitor_out_empty);
     output_visitor_test_add("/visitor/output/native_list/int",
-                            &out_visitor_data, test_visitor_out_native_list_int);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_int);
     output_visitor_test_add("/visitor/output/native_list/int8",
-                            &out_visitor_data, test_visitor_out_native_list_int8);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_int8);
     output_visitor_test_add("/visitor/output/native_list/int16",
-                            &out_visitor_data, test_visitor_out_native_list_int16);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_int16);
     output_visitor_test_add("/visitor/output/native_list/int32",
-                            &out_visitor_data, test_visitor_out_native_list_int32);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_int32);
     output_visitor_test_add("/visitor/output/native_list/int64",
-                            &out_visitor_data, test_visitor_out_native_list_int64);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_int64);
     output_visitor_test_add("/visitor/output/native_list/uint8",
-                            &out_visitor_data, test_visitor_out_native_list_uint8);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_uint8);
     output_visitor_test_add("/visitor/output/native_list/uint16",
-                            &out_visitor_data, test_visitor_out_native_list_uint16);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_uint16);
     output_visitor_test_add("/visitor/output/native_list/uint32",
-                            &out_visitor_data, test_visitor_out_native_list_uint32);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_uint32);
     output_visitor_test_add("/visitor/output/native_list/uint64",
-                            &out_visitor_data, test_visitor_out_native_list_uint64);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_uint64);
     output_visitor_test_add("/visitor/output/native_list/bool",
-                            &out_visitor_data, test_visitor_out_native_list_bool);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_bool);
     output_visitor_test_add("/visitor/output/native_list/string",
-                            &out_visitor_data, test_visitor_out_native_list_str);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_str);
     output_visitor_test_add("/visitor/output/native_list/number",
-                            &out_visitor_data, test_visitor_out_native_list_number);
+                            &out_visitor_data,
+                            test_visitor_out_native_list_number);

     g_test_run();

-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 10/36] qapi: Forbid base without discriminator in unions
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (8 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 09/36] qapi: Clean up test coverage of simple unions Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-27 17:36   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions Eric Blake
                   ` (29 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

None of the existing QMP or QGA interfaces uses a union with a
base type but no discriminator; it is easier to avoid this in the
generator to save room for other future extensions more likely to
be useful.  A previous commit added a union-base-no-discriminator
test to ensure that we eventually give a decent error message;
now is the time to actually forbid it, and remove the last
vestiges of that usage from the testsuite.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: split from v5:7/28; change subject line; hoist hunk from
v5:8/28 into here to make this be the patch that forbids
simple-with-base
---
 scripts/qapi-types.py                              |  7 ++---
 scripts/qapi-visit.py                              | 13 ++++----
 scripts/qapi.py                                    | 20 ++++++------
 tests/qapi-schema/qapi-schema-test.json            |  4 ---
 tests/qapi-schema/qapi-schema-test.out             |  2 --
 tests/qapi-schema/union-base-no-discriminator.err  |  1 +
 tests/qapi-schema/union-base-no-discriminator.exit |  2 +-
 tests/qapi-schema/union-base-no-discriminator.json |  2 +-
 tests/qapi-schema/union-base-no-discriminator.out  |  8 -----
 tests/test-qmp-input-visitor.c                     | 19 ------------
 tests/test-qmp-output-visitor.c                    | 36 ----------------------
 11 files changed, 22 insertions(+), 92 deletions(-)

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index e400b03..f6fb930 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -242,10 +242,9 @@ struct %(name)s
 ''')

     if base:
-        base_fields = find_struct(base)['data']
-        if discriminator:
-            base_fields = base_fields.copy()
-            del base_fields[discriminator]
+        assert discriminator
+        base_fields = find_struct(base)['data'].copy()
+        del base_fields[discriminator]
         ret += generate_struct_fields(base_fields)
     else:
         assert not discriminator
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 4416677..3f82bd4 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -2,7 +2,7 @@
 # QAPI visitor generator
 #
 # Copyright IBM, Corp. 2011
-# Copyright (C) 2014 Red Hat, Inc.
+# Copyright (C) 2014-2015 Red Hat, Inc.
 #
 # Authors:
 #  Anthony Liguori <aliguori@us.ibm.com>
@@ -310,16 +310,15 @@ def generate_visit_union(expr):
         ret = ""
         disc_type = enum_define['enum_name']
     else:
-        # There will always be a discriminator in the C switch code, by default it
-        # is an enum type generated silently as "'%sKind' % (name)"
+        # There will always be a discriminator in the C switch code, by default
+        # it is an enum type generated silently as "'%sKind' % (name)"
         ret = generate_visit_enum('%sKind' % name, members.keys())
         disc_type = '%sKind' % (name)

     if base:
-        base_fields = find_struct(base)['data']
-        if discriminator:
-            base_fields = base_fields.copy()
-            del base_fields[discriminator]
+        assert discriminator
+        base_fields = find_struct(base)['data'].copy()
+        del base_fields[discriminator]
         ret += generate_visit_struct_fields(name, "", "", base_fields)

     if discriminator:
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 3ce8c33..438468e 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -259,22 +259,22 @@ def check_union(expr, expr_info):
     discriminator = expr.get('discriminator')
     members = expr['data']

-    # If the object has a member 'base', its value must name a complex type.
-    if base:
+    # If the object has a member 'base', its value must name a complex type,
+    # and there must be a discriminator.
+    if base is not None:
+        if discriminator is None:
+            raise QAPIExprError(expr_info,
+                                "Union '%s' requires a discriminator to go "
+                                "along with base" %name)
         base_fields = find_base_fields(base)
         if not base_fields:
             raise QAPIExprError(expr_info,
                                 "Base '%s' is not a valid type"
                                 % base)

-    # If the union object has no member 'discriminator', it's an
-    # ordinary union.
-    if not discriminator:
-        enum_define = None
-
-    # Else if the value of member 'discriminator' is {}, it's an
-    # anonymous union.
-    elif discriminator == {}:
+    # If the union object has no member 'discriminator', it's a
+    # simple union. If 'discriminator' is {}, it is an anonymous union.
+    if not discriminator or discriminator == {}:
         enum_define = None

     # Else, it's a flat union.
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index 84f0f07..b134f3f 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -36,10 +36,6 @@
 { 'type': 'UserDefC',
   'data': { 'string1': 'str', 'string2': 'str' } }

-{ 'union': 'UserDefUnion',
-  'base': 'UserDefZero',
-  'data': { 'a' : 'UserDefA', 'b' : 'UserDefB' } }
-
 { 'type': 'UserDefUnionBase',
   'data': { 'string': 'str', 'enum1': 'EnumOne' } }

diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 915a61b..664ae7b 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -7,7 +7,6 @@
  OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
  OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
- OrderedDict([('union', 'UserDefUnion'), ('base', 'UserDefZero'), ('data', OrderedDict([('a', 'UserDefA'), ('b', 'UserDefB')]))]),
  OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]),
  OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]),
  OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]),
@@ -24,7 +23,6 @@
  OrderedDict([('event', 'EVENT_C'), ('data', OrderedDict([('*a', 'int'), ('*b', 'UserDefOne'), ('c', 'str')]))]),
  OrderedDict([('event', 'EVENT_D'), ('data', OrderedDict([('a', 'EventStructOne'), ('b', 'str'), ('*c', 'str'), ('*enum3', 'EnumOne')]))])]
 [{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']},
- {'enum_name': 'UserDefUnionKind', 'enum_values': None},
  {'enum_name': 'UserDefAnonUnionKind', 'enum_values': None},
  {'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None}]
 [OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
diff --git a/tests/qapi-schema/union-base-no-discriminator.err b/tests/qapi-schema/union-base-no-discriminator.err
index e69de29..fc8b79c 100644
--- a/tests/qapi-schema/union-base-no-discriminator.err
+++ b/tests/qapi-schema/union-base-no-discriminator.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-base-no-discriminator.json:11: Union 'TestUnion' requires a discriminator to go along with base
diff --git a/tests/qapi-schema/union-base-no-discriminator.exit b/tests/qapi-schema/union-base-no-discriminator.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/union-base-no-discriminator.exit
+++ b/tests/qapi-schema/union-base-no-discriminator.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json
index c8cba3a..052596c 100644
--- a/tests/qapi-schema/union-base-no-discriminator.json
+++ b/tests/qapi-schema/union-base-no-discriminator.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject simple unions with a base
+# we reject simple unions with a base (or flat unions without discriminator)
 { 'type': 'TestTypeA',
   'data': { 'string': 'str' } }

diff --git a/tests/qapi-schema/union-base-no-discriminator.out b/tests/qapi-schema/union-base-no-discriminator.out
index 505fd57..e69de29 100644
--- a/tests/qapi-schema/union-base-no-discriminator.out
+++ b/tests/qapi-schema/union-base-no-discriminator.out
@@ -1,8 +0,0 @@
-[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('data', OrderedDict([('value1', 'TestTypeA'), ('value2', 'TestTypeB')]))])]
-[{'enum_name': 'TestUnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])]
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 0039ff6..cc33f64 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -293,23 +293,6 @@ static void test_visitor_in_list(TestInputVisitorData *data,
     qapi_free_UserDefOneList(head);
 }

-static void test_visitor_in_union(TestInputVisitorData *data,
-                                  const void *unused)
-{
-    Visitor *v;
-    Error *err = NULL;
-    UserDefUnion *tmp;
-
-    v = visitor_input_test_init(data, "{ 'type': 'b', 'integer': 41, 'data' : { 'integer': 42 } }");
-
-    visit_type_UserDefUnion(v, &tmp, NULL, &err);
-    g_assert(err == NULL);
-    g_assert_cmpint(tmp->kind, ==, USER_DEF_UNION_KIND_B);
-    g_assert_cmpint(tmp->integer, ==, 41);
-    g_assert_cmpint(tmp->b->integer, ==, 42);
-    qapi_free_UserDefUnion(tmp);
-}
-
 static void test_visitor_in_union_flat(TestInputVisitorData *data,
                                        const void *unused)
 {
@@ -679,8 +662,6 @@ int main(int argc, char **argv)
                            &in_visitor_data, test_visitor_in_struct_nested);
     input_visitor_test_add("/visitor/input/list",
                            &in_visitor_data, test_visitor_in_list);
-    input_visitor_test_add("/visitor/input/union",
-                            &in_visitor_data, test_visitor_in_union);
     input_visitor_test_add("/visitor/input/union-flat",
                            &in_visitor_data, test_visitor_in_union_flat);
     input_visitor_test_add("/visitor/input/union-anon",
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index e5bf40f..ebe6ea3 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -422,40 +422,6 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data,
     qapi_free_UserDefNestedList(head);
 }

-static void test_visitor_out_union(TestOutputVisitorData *data,
-                                   const void *unused)
-{
-    QObject *arg, *qvalue;
-    QDict *qdict, *value;
-
-    Error *err = NULL;
-
-    UserDefUnion *tmp = g_malloc0(sizeof(UserDefUnion));
-    tmp->kind = USER_DEF_UNION_KIND_A;
-    tmp->integer = 41;
-    tmp->a = g_malloc0(sizeof(UserDefA));
-    tmp->a->boolean = true;
-
-    visit_type_UserDefUnion(data->ov, &tmp, NULL, &err);
-    g_assert(err == NULL);
-    arg = qmp_output_get_qobject(data->qov);
-
-    g_assert(qobject_type(arg) == QTYPE_QDICT);
-    qdict = qobject_to_qdict(arg);
-
-    g_assert_cmpstr(qdict_get_str(qdict, "type"), ==, "a");
-    g_assert_cmpint(qdict_get_int(qdict, "integer"), ==, 41);
-
-    qvalue = qdict_get(qdict, "data");
-    g_assert(data != NULL);
-    g_assert(qobject_type(qvalue) == QTYPE_QDICT);
-    value = qobject_to_qdict(qvalue);
-    g_assert_cmpint(qdict_get_bool(value, "boolean"), ==, true);
-
-    qapi_free_UserDefUnion(tmp);
-    QDECREF(qdict);
-}
-
 static void test_visitor_out_union_flat(TestOutputVisitorData *data,
                                         const void *unused)
 {
@@ -862,8 +828,6 @@ int main(int argc, char **argv)
                             &out_visitor_data, test_visitor_out_list);
     output_visitor_test_add("/visitor/output/list-qapi-free",
                             &out_visitor_data, test_visitor_out_list_qapi_free);
-    output_visitor_test_add("/visitor/output/union",
-                            &out_visitor_data, test_visitor_out_union);
     output_visitor_test_add("/visitor/output/union-flat",
                             &out_visitor_data, test_visitor_out_union_flat);
     output_visitor_test_add("/visitor/output/union-anon",
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (9 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 10/36] qapi: Forbid base without discriminator in unions Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-27 18:15   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 12/36] qapi: Prepare for catching more semantic parse errors Eric Blake
                   ` (28 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Previous commits demonstrated that the generator had several
flaws with less-than-perfect unions:
- a simple union that listed the same branch twice (or two variant
names that map to the same C enumerator, including the implicit
MAX sentinel) ended up generating invalid C code
- an anonymous union that listed two branches with the same qtype
ended up generating invalid C code
- the generator crashed on anonymous union attempts to use an
array type
- the generator was silently ignoring a base type for anonymous
unions
- the generator allowed unknown types or nested anonymous unions
as a branch in an anonymous union

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: rebase tests on top of tweaks earlier in series; subject changed
from v5:8/28; s/ordinary/simple/; tweak error message for
alternate-conflict-string; drop fixme in union-base-no-discriminator;
split out removal of simple-with-base into earlier commit
---
 scripts/qapi-types.py                              | 13 +---
 scripts/qapi.py                                    | 89 +++++++++++++++++-----
 tests/qapi-schema/alternate-array.err              |  1 +
 tests/qapi-schema/alternate-array.exit             |  2 +-
 tests/qapi-schema/alternate-array.json             |  2 +-
 tests/qapi-schema/alternate-array.out              |  4 -
 tests/qapi-schema/alternate-base.err               |  1 +
 tests/qapi-schema/alternate-base.exit              |  2 +-
 tests/qapi-schema/alternate-base.json              |  2 +-
 tests/qapi-schema/alternate-base.out               |  4 -
 tests/qapi-schema/alternate-clash.err              |  1 +
 tests/qapi-schema/alternate-clash.exit             |  2 +-
 tests/qapi-schema/alternate-clash.json             |  2 +-
 tests/qapi-schema/alternate-clash.out              |  3 -
 tests/qapi-schema/alternate-conflict-dict.err      |  1 +
 tests/qapi-schema/alternate-conflict-dict.exit     |  2 +-
 tests/qapi-schema/alternate-conflict-dict.json     |  2 +-
 tests/qapi-schema/alternate-conflict-dict.out      |  6 --
 tests/qapi-schema/alternate-conflict-string.err    |  1 +
 tests/qapi-schema/alternate-conflict-string.exit   |  2 +-
 tests/qapi-schema/alternate-conflict-string.json   |  2 +-
 tests/qapi-schema/alternate-conflict-string.out    |  5 --
 tests/qapi-schema/alternate-nested.err             |  1 +
 tests/qapi-schema/alternate-nested.exit            |  2 +-
 tests/qapi-schema/alternate-nested.json            |  2 +-
 tests/qapi-schema/alternate-nested.out             |  5 --
 tests/qapi-schema/alternate-unknown.err            |  1 +
 tests/qapi-schema/alternate-unknown.exit           |  2 +-
 tests/qapi-schema/alternate-unknown.json           |  2 +-
 tests/qapi-schema/alternate-unknown.out            |  3 -
 tests/qapi-schema/flat-union-bad-base.err          |  2 +-
 tests/qapi-schema/flat-union-bad-base.json         |  2 +-
 tests/qapi-schema/flat-union-bad-discriminator.err |  1 +
 .../qapi-schema/flat-union-bad-discriminator.exit  |  2 +-
 .../qapi-schema/flat-union-bad-discriminator.json  |  2 +-
 tests/qapi-schema/flat-union-bad-discriminator.out | 10 ---
 tests/qapi-schema/flat-union-inline.err            |  2 +-
 tests/qapi-schema/flat-union-inline.json           |  2 +-
 tests/qapi-schema/flat-union-no-base.err           |  2 +-
 tests/qapi-schema/flat-union-no-base.json          |  2 +-
 tests/qapi-schema/union-bad-branch.err             |  1 +
 tests/qapi-schema/union-bad-branch.exit            |  2 +-
 tests/qapi-schema/union-bad-branch.json            |  2 +-
 tests/qapi-schema/union-bad-branch.out             |  6 --
 tests/qapi-schema/union-max.err                    |  1 +
 tests/qapi-schema/union-max.exit                   |  2 +-
 tests/qapi-schema/union-max.json                   |  2 +-
 tests/qapi-schema/union-max.out                    |  3 -
 48 files changed, 110 insertions(+), 103 deletions(-)

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index f6fb930..2390887 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -181,17 +181,8 @@ const int %(name)s_qtypes[QTYPE_MAX] = {
     name=name)

     for key in members:
-        qapi_type = members[key]
-        if builtin_types.has_key(qapi_type):
-            qtype = builtin_types[qapi_type]
-        elif find_struct(qapi_type):
-            qtype = "QTYPE_QDICT"
-        elif find_union(qapi_type):
-            qtype = "QTYPE_QDICT"
-        elif find_enum(qapi_type):
-            qtype = "QTYPE_QSTRING"
-        else:
-            assert False, "Invalid anonymous union member"
+        qtype = find_anonymous_member_qtype(members[key])
+        assert qtype, "Invalid anonymous union member"

         ret += mcgen('''
     [ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s,
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 438468e..5f0f699 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -224,6 +224,23 @@ 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):
+    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"
+    return None
+
 # Return the discriminator enum define if discriminator is specified as an
 # enum type, otherwise return None.
 def discriminator_find_enum_define(expr):
@@ -258,6 +275,8 @@ def check_union(expr, expr_info):
     base = expr.get('base')
     discriminator = expr.get('discriminator')
     members = expr['data']
+    values = { 'MAX': '(automatic)' }
+    types_seen = {}

     # If the object has a member 'base', its value must name a complex type,
     # and there must be a discriminator.
@@ -266,26 +285,35 @@ def check_union(expr, expr_info):
             raise QAPIExprError(expr_info,
                                 "Union '%s' requires a discriminator to go "
                                 "along with base" %name)
-        base_fields = find_base_fields(base)
-        if not base_fields:
-            raise QAPIExprError(expr_info,
-                                "Base '%s' is not a valid type"
-                                % base)

     # If the union object has no member 'discriminator', it's a
     # simple union. If 'discriminator' is {}, it is an anonymous union.
-    if not discriminator or discriminator == {}:
+    if discriminator is None or discriminator == {}:
         enum_define = None
+        if base is not None:
+            raise QAPIExprError(expr_info,
+                                "Union '%s' must not have a base"
+                                % name)

     # Else, it's a flat union.
     else:
-        # The object must have a member 'base'.
-        if not base:
+        # The object must have a string member 'base'.
+        if not isinstance(base, str):
             raise QAPIExprError(expr_info,
-                                "Flat union '%s' must have a base field"
+                                "Flat union '%s' must have a string base field"
                                 % name)
+        base_fields = find_base_fields(base)
+        if not base_fields:
+            raise QAPIExprError(expr_info,
+                                "Base '%s' is not a valid type"
+                                % base)
+
         # The value of member 'discriminator' must name a member of the
         # base type.
+        if not isinstance(discriminator, str):
+            raise QAPIExprError(expr_info,
+                                "Flat union '%s' discriminator must be a string"
+                                % name)
         discriminator_type = base_fields.get(discriminator)
         if not discriminator_type:
             raise QAPIExprError(expr_info,
@@ -301,15 +329,42 @@ def check_union(expr, expr_info):

     # Check every branch
     for (key, value) in members.items():
-        # If this named member's value names an enum type, then all members
+        # If the discriminator names an enum type, then all members
         # of 'data' must also be members of the enum type.
-        if enum_define and not key in enum_define['enum_values']:
-            raise QAPIExprError(expr_info,
-                                "Discriminator value '%s' is not found in "
-                                "enum '%s'" %
-                                (key, enum_define["enum_name"]))
-        # Todo: add checking for values. Key is checked as above, value can be
-        # also checked here, but we need more functions to handle array case.
+        if enum_define:
+            if not key in enum_define['enum_values']:
+                raise QAPIExprError(expr_info,
+                                    "Discriminator value '%s' is not found in "
+                                    "enum '%s'" %
+                                    (key, enum_define["enum_name"]))
+
+        # Otherwise, check for conflicts in the generated enum
+        else:
+            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 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' can't "
+                                    "be distinguished from member '%s'"
+                                    % (name, key, types_seen[qtype]))
+            types_seen[qtype] = key
+

 def check_enum(expr, expr_info):
     name = expr['enum']
diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err
index e69de29..8d6ccc7 100644
--- a/tests/qapi-schema/alternate-array.err
+++ b/tests/qapi-schema/alternate-array.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-array.json:5: Anonymous union 'MyUnion' member 'two' must not be array type
diff --git a/tests/qapi-schema/alternate-array.exit b/tests/qapi-schema/alternate-array.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-array.exit
+++ b/tests/qapi-schema/alternate-array.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json
index c2965cf..77970d9 100644
--- a/tests/qapi-schema/alternate-array.json
+++ b/tests/qapi-schema/alternate-array.json
@@ -1,4 +1,4 @@
-# FIXME: we do not support array branches of anonymous unions yet
+# we do not support array branches of anonymous unions yet
 # TODO: should we support this?
 { 'type': 'One',
   'data': { 'name': 'str' } }
diff --git a/tests/qapi-schema/alternate-array.out b/tests/qapi-schema/alternate-array.out
index 90dc22c..e69de29 100644
--- a/tests/qapi-schema/alternate-array.out
+++ b/tests/qapi-schema/alternate-array.out
@@ -1,4 +0,0 @@
-[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', ['int'])]))])]
-[{'enum_name': 'MyUnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))])]
diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err
index e69de29..85595b2 100644
--- a/tests/qapi-schema/alternate-base.err
+++ b/tests/qapi-schema/alternate-base.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-base.json:4: Union 'MyUnion' must not have a base
diff --git a/tests/qapi-schema/alternate-base.exit b/tests/qapi-schema/alternate-base.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-base.exit
+++ b/tests/qapi-schema/alternate-base.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json
index 2d36db1..dad7f02 100644
--- a/tests/qapi-schema/alternate-base.json
+++ b/tests/qapi-schema/alternate-base.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject anonymous union with base type
+# we reject anonymous union with base type
 { 'type': 'Base',
   'data': { 'string': 'str' } }
 { 'union': 'MyUnion',
diff --git a/tests/qapi-schema/alternate-base.out b/tests/qapi-schema/alternate-base.out
index 7fb31f5..e69de29 100644
--- a/tests/qapi-schema/alternate-base.out
+++ b/tests/qapi-schema/alternate-base.out
@@ -1,4 +0,0 @@
-[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', OrderedDict()), ('data', OrderedDict([('number', 'int')]))])]
-[{'enum_name': 'MyUnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'Base'), ('data', OrderedDict([('string', 'str')]))])]
diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err
index e69de29..1130c12 100644
--- a/tests/qapi-schema/alternate-clash.err
+++ b/tests/qapi-schema/alternate-clash.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-clash.json:2: Union 'Union1' member 'ONE' clashes with 'one'
diff --git a/tests/qapi-schema/alternate-clash.exit b/tests/qapi-schema/alternate-clash.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-clash.exit
+++ b/tests/qapi-schema/alternate-clash.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-clash.json b/tests/qapi-schema/alternate-clash.json
index 7e2ef23..fa2d27e 100644
--- a/tests/qapi-schema/alternate-clash.json
+++ b/tests/qapi-schema/alternate-clash.json
@@ -1,4 +1,4 @@
-# FIXME: we should detect C enum collisions in an anonymous union
+# we detect C enum collisions in an anonymous union
 { 'union': 'Union1',
   'discriminator': {},
   'data': { 'one': 'str', 'ONE': 'int' } }
diff --git a/tests/qapi-schema/alternate-clash.out b/tests/qapi-schema/alternate-clash.out
index c6687fa..e69de29 100644
--- a/tests/qapi-schema/alternate-clash.out
+++ b/tests/qapi-schema/alternate-clash.out
@@ -1,3 +0,0 @@
-[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('ONE', 'int')]))])]
-[{'enum_name': 'Union1Kind', 'enum_values': None}]
-[]
diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err
index e69de29..2d4550c 100644
--- a/tests/qapi-schema/alternate-conflict-dict.err
+++ b/tests/qapi-schema/alternate-conflict-dict.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-conflict-dict.json:6: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one'
diff --git a/tests/qapi-schema/alternate-conflict-dict.exit b/tests/qapi-schema/alternate-conflict-dict.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-conflict-dict.exit
+++ b/tests/qapi-schema/alternate-conflict-dict.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json
index d2ed9de..ded302e 100644
--- a/tests/qapi-schema/alternate-conflict-dict.json
+++ b/tests/qapi-schema/alternate-conflict-dict.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject anonymous unions with multiple object branches
+# we reject anonymous unions with multiple object branches
 { 'type': 'One',
   'data': { 'name': 'str' } }
 { 'type': 'Two',
diff --git a/tests/qapi-schema/alternate-conflict-dict.out b/tests/qapi-schema/alternate-conflict-dict.out
index b9ac945..e69de29 100644
--- a/tests/qapi-schema/alternate-conflict-dict.out
+++ b/tests/qapi-schema/alternate-conflict-dict.out
@@ -1,6 +0,0 @@
-[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))]),
- OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'One'), ('two', 'Two')]))])]
-[{'enum_name': 'MyUnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'One'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('type', 'Two'), ('data', OrderedDict([('value', 'int')]))])]
diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err
index e69de29..271ddcd 100644
--- a/tests/qapi-schema/alternate-conflict-string.err
+++ b/tests/qapi-schema/alternate-conflict-string.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-conflict-string.json:4: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one'
diff --git a/tests/qapi-schema/alternate-conflict-string.exit b/tests/qapi-schema/alternate-conflict-string.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-conflict-string.exit
+++ b/tests/qapi-schema/alternate-conflict-string.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json
index 35245a3..3834a3d 100644
--- a/tests/qapi-schema/alternate-conflict-string.json
+++ b/tests/qapi-schema/alternate-conflict-string.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject anonymous unions with multiple string-like branches
+# we reject anonymous unions with multiple string-like branches
 { 'enum': 'Enum',
   'data': [ 'hello', 'world' ] }
 { 'union': 'MyUnion',
diff --git a/tests/qapi-schema/alternate-conflict-string.out b/tests/qapi-schema/alternate-conflict-string.out
index e7b39a2..e69de29 100644
--- a/tests/qapi-schema/alternate-conflict-string.out
+++ b/tests/qapi-schema/alternate-conflict-string.out
@@ -1,5 +0,0 @@
-[OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]),
- OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('one', 'str'), ('two', 'Enum')]))])]
-[{'enum_name': 'Enum', 'enum_values': ['hello', 'world']},
- {'enum_name': 'MyUnionKind', 'enum_values': None}]
-[]
diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err
index e69de29..59df96e 100644
--- a/tests/qapi-schema/alternate-nested.err
+++ b/tests/qapi-schema/alternate-nested.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-nested.json:5: Anonymous union 'Union2' member 'nested' has invalid type 'Union1'
diff --git a/tests/qapi-schema/alternate-nested.exit b/tests/qapi-schema/alternate-nested.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-nested.exit
+++ b/tests/qapi-schema/alternate-nested.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json
index d5812bf..ed2b6b7 100644
--- a/tests/qapi-schema/alternate-nested.json
+++ b/tests/qapi-schema/alternate-nested.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject a nested anonymous union branch
+# we reject a nested anonymous union branch
 { 'union': 'Union1',
   'discriminator': {},
   'data': { 'name': 'str', 'value': 'int' } }
diff --git a/tests/qapi-schema/alternate-nested.out b/tests/qapi-schema/alternate-nested.out
index 0137c1f..e69de29 100644
--- a/tests/qapi-schema/alternate-nested.out
+++ b/tests/qapi-schema/alternate-nested.out
@@ -1,5 +0,0 @@
-[OrderedDict([('union', 'Union1'), ('discriminator', OrderedDict()), ('data', OrderedDict([('name', 'str'), ('value', 'int')]))]),
- OrderedDict([('union', 'Union2'), ('discriminator', OrderedDict()), ('data', OrderedDict([('nested', 'Union1')]))])]
-[{'enum_name': 'Union1Kind', 'enum_values': None},
- {'enum_name': 'Union2Kind', 'enum_values': None}]
-[]
diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err
index e69de29..bf8e9ae 100644
--- a/tests/qapi-schema/alternate-unknown.err
+++ b/tests/qapi-schema/alternate-unknown.err
@@ -0,0 +1 @@
+tests/qapi-schema/alternate-unknown.json:2: Anonymous union 'Union' member 'unknown' has invalid type 'MissingType'
diff --git a/tests/qapi-schema/alternate-unknown.exit b/tests/qapi-schema/alternate-unknown.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/alternate-unknown.exit
+++ b/tests/qapi-schema/alternate-unknown.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json
index 0bab9c2..0c305c2 100644
--- a/tests/qapi-schema/alternate-unknown.json
+++ b/tests/qapi-schema/alternate-unknown.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject an anonymous union with unknown type in branch
+# we reject an anonymous union with unknown type in branch
 { 'union': 'Union',
   'discriminator': {},
   'data': { 'unknown': 'MissingType' } }
diff --git a/tests/qapi-schema/alternate-unknown.out b/tests/qapi-schema/alternate-unknown.out
index 0911cdc..e69de29 100644
--- a/tests/qapi-schema/alternate-unknown.out
+++ b/tests/qapi-schema/alternate-unknown.out
@@ -1,3 +0,0 @@
-[OrderedDict([('union', 'Union'), ('discriminator', OrderedDict()), ('data', OrderedDict([('unknown', 'MissingType')]))])]
-[{'enum_name': 'UnionKind', 'enum_values': None}]
-[]
diff --git a/tests/qapi-schema/flat-union-bad-base.err b/tests/qapi-schema/flat-union-bad-base.err
index 5962ff4..f9c31b2 100644
--- a/tests/qapi-schema/flat-union-bad-base.err
+++ b/tests/qapi-schema/flat-union-bad-base.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-bad-base.json:9: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type
+tests/qapi-schema/flat-union-bad-base.json:9: Flat union 'TestUnion' must have a string base field
diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json
index 6c14132..bb0f02d 100644
--- a/tests/qapi-schema/flat-union-bad-base.json
+++ b/tests/qapi-schema/flat-union-bad-base.json
@@ -1,4 +1,4 @@
-# FIXME: poor message: we require the base to be an existing complex type
+# we require the base to be an existing complex type
 # TODO: should we allow an anonymous inline base type?
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err
index e69de29..1661c52 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.err
+++ b/tests/qapi-schema/flat-union-bad-discriminator.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-bad-discriminator.json:10: Flat union 'TestUnion' discriminator must be a string
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.exit b/tests/qapi-schema/flat-union-bad-discriminator.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.exit
+++ b/tests/qapi-schema/flat-union-bad-discriminator.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json
index 1599a59..3ce43e8 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.json
+++ b/tests/qapi-schema/flat-union-bad-discriminator.json
@@ -1,4 +1,4 @@
-# FIXME: we should require the discriminator to be a string naming a base-type member
+# we require the discriminator to be a string naming a base-type member
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 { 'type': 'TestBase',
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.out b/tests/qapi-schema/flat-union-bad-discriminator.out
index b6ce217..e69de29 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.out
+++ b/tests/qapi-schema/flat-union-bad-discriminator.out
@@ -1,10 +0,0 @@
-[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
- OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]),
- OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', []), ('data', OrderedDict([('kind1', 'TestTypeA'), ('kind2', 'TestTypeB')]))])]
-[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']},
- {'enum_name': 'TestUnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')]))]),
- OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
diff --git a/tests/qapi-schema/flat-union-inline.err b/tests/qapi-schema/flat-union-inline.err
index 51fbe54..ec58627 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: Base 'OrderedDict([('enum1', 'TestEnum'), ('kind', 'str')])' is not a valid type
+tests/qapi-schema/flat-union-inline.json:7: Flat union 'TestUnion' must have a string base field
diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json
index 2bdffeb..f3da117 100644
--- a/tests/qapi-schema/flat-union-inline.json
+++ b/tests/qapi-schema/flat-union-inline.json
@@ -1,4 +1,4 @@
-# FIXME: poor message: we require branches to be a complex type name
+# we require branches to be a complex type name
 # TODO: should we allow anonymous inline types?
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
diff --git a/tests/qapi-schema/flat-union-no-base.err b/tests/qapi-schema/flat-union-no-base.err
index 97323a0..bb3f708 100644
--- a/tests/qapi-schema/flat-union-no-base.err
+++ b/tests/qapi-schema/flat-union-no-base.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a base field
+tests/qapi-schema/flat-union-no-base.json:9: Flat union 'TestUnion' must have a string base field
diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json
index 08a0247..9547bb8 100644
--- a/tests/qapi-schema/flat-union-no-base.json
+++ b/tests/qapi-schema/flat-union-no-base.json
@@ -1,4 +1,4 @@
-# FIXME: flat unions should require a base
+# flat unions require a base
 # TODO: simple unions should be able to use an enum discriminator
 { 'type': 'TestTypeA',
   'data': { 'string': 'str' } }
diff --git a/tests/qapi-schema/union-bad-branch.err b/tests/qapi-schema/union-bad-branch.err
index e69de29..8822735 100644
--- a/tests/qapi-schema/union-bad-branch.err
+++ b/tests/qapi-schema/union-bad-branch.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-bad-branch.json:6: Union 'MyUnion' member 'ONE' clashes with 'one'
diff --git a/tests/qapi-schema/union-bad-branch.exit b/tests/qapi-schema/union-bad-branch.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/union-bad-branch.exit
+++ b/tests/qapi-schema/union-bad-branch.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/union-bad-branch.json b/tests/qapi-schema/union-bad-branch.json
index 11e46de..4303666 100644
--- a/tests/qapi-schema/union-bad-branch.json
+++ b/tests/qapi-schema/union-bad-branch.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject normal unions where branches would collide in C
+# we reject normal unions where branches would collide in C
 { 'type': 'One',
   'data': { 'string': 'str' } }
 { 'type': 'Two',
diff --git a/tests/qapi-schema/union-bad-branch.out b/tests/qapi-schema/union-bad-branch.out
index 6baf01b..e69de29 100644
--- a/tests/qapi-schema/union-bad-branch.out
+++ b/tests/qapi-schema/union-bad-branch.out
@@ -1,6 +0,0 @@
-[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))]),
- OrderedDict([('union', 'MyUnion'), ('data', OrderedDict([('one', 'One'), ('ONE', 'Two')]))])]
-[{'enum_name': 'MyUnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'One'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'Two'), ('data', OrderedDict([('number', 'int')]))])]
diff --git a/tests/qapi-schema/union-max.err b/tests/qapi-schema/union-max.err
index e69de29..55ce439 100644
--- a/tests/qapi-schema/union-max.err
+++ b/tests/qapi-schema/union-max.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-max.json:2: Union 'Union' member 'max' clashes with '(automatic)'
diff --git a/tests/qapi-schema/union-max.exit b/tests/qapi-schema/union-max.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/union-max.exit
+++ b/tests/qapi-schema/union-max.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/union-max.json b/tests/qapi-schema/union-max.json
index 45648c4..d6ad986 100644
--- a/tests/qapi-schema/union-max.json
+++ b/tests/qapi-schema/union-max.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject 'max' branch in a union, for collision with C enum
+# we reject 'max' branch in a union, for collision with C enum
 { 'union': 'Union',
   'data': { 'max': 'int' } }
diff --git a/tests/qapi-schema/union-max.out b/tests/qapi-schema/union-max.out
index 2757d36..e69de29 100644
--- a/tests/qapi-schema/union-max.out
+++ b/tests/qapi-schema/union-max.out
@@ -1,3 +0,0 @@
-[OrderedDict([('union', 'Union'), ('data', OrderedDict([('max', 'int')]))])]
-[{'enum_name': 'UnionKind', 'enum_values': None}]
-[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 12/36] qapi: Prepare for catching more semantic parse errors
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (10 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 13/36] qapi: Segregate anonymous unions into alternates in generator Eric Blake
                   ` (27 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

This patch widens the scope of a try block (with the attending
reindentation required by Python) in preparation for a future
patch adding more instances of QAPIExprError inside the block.
It's easier to separate indentation from semantic changes, so
this patch has no real behavior change.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: no change
---
 scripts/qapi.py | 37 ++++++++++++++++++++-----------------
 1 file changed, 20 insertions(+), 17 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 5f0f699..0c3459b 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -399,6 +399,7 @@ def check_exprs(schema):
             check_event(expr, info)

 def parse_schema(input_file):
+    # First pass: read entire file into memory
     try:
         schema = QAPISchema(open(input_file, "r"))
     except (QAPISchemaError, QAPIExprError), e:
@@ -407,24 +408,26 @@ def parse_schema(input_file):

     exprs = []

-    for expr_elem in schema.exprs:
-        expr = expr_elem['expr']
-        if expr.has_key('enum'):
-            add_enum(expr['enum'], expr.get('data'))
-        elif expr.has_key('union'):
-            add_union(expr)
-        elif expr.has_key('type'):
-            add_struct(expr)
-        exprs.append(expr)
-
-    # Try again for hidden UnionKind enum
-    for expr_elem in schema.exprs:
-        expr = expr_elem['expr']
-        if expr.has_key('union'):
-            if not discriminator_find_enum_define(expr):
-                add_enum('%sKind' % expr['union'])
-
     try:
+        # Next pass: learn the types.
+        for expr_elem in schema.exprs:
+            expr = expr_elem['expr']
+            if expr.has_key('enum'):
+                add_enum(expr['enum'], expr.get('data'))
+            elif expr.has_key('union'):
+                add_union(expr)
+            elif expr.has_key('type'):
+                add_struct(expr)
+            exprs.append(expr)
+
+        # Try again for hidden UnionKind enum
+        for expr_elem in schema.exprs:
+            expr = expr_elem['expr']
+            if expr.has_key('union'):
+                if not discriminator_find_enum_define(expr):
+                    add_enum('%sKind' % expr['union'])
+
+        # Final pass - validate that exprs make sense
         check_exprs(schema)
     except QAPIExprError, e:
         print >>sys.stderr, e
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 13/36] qapi: Segregate anonymous unions into alternates in generator
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (11 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 12/36] qapi: Prepare for catching more semantic parse errors Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 14/36] qapi: Rename anonymous union type in test Eric Blake
                   ` (26 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

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

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

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase to changes earlier in series
---
 scripts/qapi-types.py                 |  6 +--
 scripts/qapi-visit.py                 |  4 +-
 scripts/qapi.py                       | 88 ++++++++++++++++++++++-------------
 tests/qapi-schema/alternate-base.err  |  2 +-
 tests/qapi-schema/alternate-clash.err |  2 +-
 5 files changed, 62 insertions(+), 40 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 0c3459b..0b88325 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,7 +271,6 @@ def check_union(expr, expr_info):
     discriminator = expr.get('discriminator')
     members = expr['data']
     values = { 'MAX': '(automatic)' }
-    types_seen = {}

     # If the object has a member 'base', its value must name a complex type,
     # and there must be a discriminator.
@@ -286,13 +280,15 @@ def check_union(expr, expr_info):
                                 "Union '%s' requires a discriminator to go "
                                 "along with base" %name)

-    # If the union object has no member 'discriminator', it's a
-    # simple union. If 'discriminator' is {}, it is an anonymous union.
-    if discriminator is None or discriminator == {}:
+    # Two types of unions, determined by discriminator.
+    assert discriminator != {}
+
+    # With no discriminator it is a simple union.
+    if discriminator is None:
         enum_define = None
         if base is not None:
             raise QAPIExprError(expr_info,
-                                "Union '%s' must not have a base"
+                                "Simple union '%s' must not have a base"
                                 % name)

     # Else, it's a flat union.
@@ -347,24 +343,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' can't "
-                                    "be distinguished from 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 is not None:
+        raise QAPIExprError(expr_info,
+                            "Anonymous union '%s' must not have a base"
+                            % name)
+
+    # Check every branch
+    for (key, value) in members.items():
+        # Check for conflicts in the generated enum
+        c_key = _generate_enum_string(key)
+        if c_key in values:
+            raise QAPIExprError(expr_info,
+                                "Anonymous union '%s' member '%s' clashes "
+                                "with '%s'" % (name, key, values[c_key]))
+        values[c_key] = key
+
+        # Ensure alternates have no type conflicts.
+        if isinstance(value, list):
+            raise QAPIExprError(expr_info,
+                                "Anonymous union '%s' member '%s' must "
+                                "not be array type" % (name, key))
+        qtype = find_alternate_member_qtype(value)
+        if not qtype:
+            raise QAPIExprError(expr_info,
+                                "Anonymous union '%s' member '%s' has "
+                                "invalid type '%s'" % (name, key, value))
+        if qtype in types_seen:
+            raise QAPIExprError(expr_info,
+                                "Anonymous union '%s' member '%s' can't "
+                                "be distinguished from member '%s'"
+                                % (name, key, types_seen[qtype]))
+        types_seen[qtype] = key

 def check_enum(expr, expr_info):
     name = expr['enum']
@@ -394,7 +412,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)

@@ -536,7 +557,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
diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err
index 85595b2..a2486b8 100644
--- a/tests/qapi-schema/alternate-base.err
+++ b/tests/qapi-schema/alternate-base.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-base.json:4: Union 'MyUnion' must not have a base
+tests/qapi-schema/alternate-base.json:4: Anonymous union 'MyUnion' must not have a base
diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err
index 1130c12..8949f52 100644
--- a/tests/qapi-schema/alternate-clash.err
+++ b/tests/qapi-schema/alternate-clash.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-clash.json:2: Union 'Union1' member 'ONE' clashes with 'one'
+tests/qapi-schema/alternate-clash.json:2: Anonymous union 'Union1' member 'ONE' clashes with 'one'
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 14/36] qapi: Rename anonymous union type in test
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (12 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 13/36] qapi: Segregate anonymous unions into alternates in generator Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 15/36] qapi: Document new 'alternate' meta-type Eric Blake
                   ` (25 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Reduce churn in the future patch that replaces anonymous unions
with a new metatype 'alternate' by changing 'AnonUnion' to
'Alternate'.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: no change
---
 tests/qapi-schema/qapi-schema-test.json |  2 +-
 tests/qapi-schema/qapi-schema-test.out  |  4 ++--
 tests/test-qmp-input-strict.c           | 28 ++++++++++++++--------------
 tests/test-qmp-input-visitor.c          | 16 ++++++++--------
 tests/test-qmp-output-visitor.c         | 16 ++++++++--------
 5 files changed, 33 insertions(+), 33 deletions(-)

diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index b134f3f..e1d35e1 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -53,7 +53,7 @@
   'discriminator': 'enum1',
   'data': { 'value1' : 'UserDefC', 'value2' : 'UserDefB', 'value3' : 'UserDefA' } }

-{ 'union': 'UserDefAnonUnion',
+{ 'union': 'UserDefAlternate',
   'discriminator': {},
   'data': { 'uda': 'UserDefA', 's': 'str', 'i': 'int' } }

diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 664ae7b..b55ab8d 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -10,7 +10,7 @@
  OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]),
  OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]),
  OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]),
- OrderedDict([('union', 'UserDefAnonUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]),
+ OrderedDict([('union', 'UserDefAlternate'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]),
  OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('integer', ['int']), ('s8', ['int8']), ('s16', ['int16']), ('s32', ['int32']), ('s64', ['int64']), ('u8', ['uint8']), ('u16', ['uint16']), ('u32', ['uint32']), ('u64', ['uint64']), ('number', ['number']), ('boolean', ['bool']), ('string', ['str']), ('sizes', ['size'])]))]),
  OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]),
  OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]),
@@ -23,7 +23,7 @@
  OrderedDict([('event', 'EVENT_C'), ('data', OrderedDict([('*a', 'int'), ('*b', 'UserDefOne'), ('c', 'str')]))]),
  OrderedDict([('event', 'EVENT_D'), ('data', OrderedDict([('a', 'EventStructOne'), ('b', 'str'), ('*c', 'str'), ('*enum3', 'EnumOne')]))])]
 [{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']},
- {'enum_name': 'UserDefAnonUnionKind', 'enum_values': None},
+ {'enum_name': 'UserDefAlternateKind', 'enum_values': None},
  {'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None}]
 [OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
  OrderedDict([('type', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index 486848e..a5f7bf3 100644
--- a/tests/test-qmp-input-strict.c
+++ b/tests/test-qmp-input-strict.c
@@ -173,18 +173,18 @@ static void test_validate_union_flat(TestInputVisitorData *data,
     qapi_free_UserDefFlatUnion(tmp);
 }

-static void test_validate_union_anon(TestInputVisitorData *data,
-                                     const void *unused)
+static void test_validate_alternate(TestInputVisitorData *data,
+                                    const void *unused)
 {
-    UserDefAnonUnion *tmp = NULL;
+    UserDefAlternate *tmp = NULL;
     Visitor *v;
     Error *err = NULL;

     v = validate_test_init(data, "42");

-    visit_type_UserDefAnonUnion(v, &tmp, NULL, &err);
+    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
     g_assert(!err);
-    qapi_free_UserDefAnonUnion(tmp);
+    qapi_free_UserDefAlternate(tmp);
 }

 static void test_validate_fail_struct(TestInputVisitorData *data,
@@ -276,18 +276,18 @@ static void test_validate_fail_union_flat_no_discrim(TestInputVisitorData *data,
     qapi_free_UserDefFlatUnion2(tmp);
 }

-static void test_validate_fail_union_anon(TestInputVisitorData *data,
-                                          const void *unused)
+static void test_validate_fail_alternate(TestInputVisitorData *data,
+                                         const void *unused)
 {
-    UserDefAnonUnion *tmp = NULL;
+    UserDefAlternate *tmp = NULL;
     Visitor *v;
     Error *err = NULL;

     v = validate_test_init(data, "3.14");

-    visit_type_UserDefAnonUnion(v, &tmp, NULL, &err);
+    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
     g_assert(err);
-    qapi_free_UserDefAnonUnion(tmp);
+    qapi_free_UserDefAlternate(tmp);
 }

 static void validate_test_add(const char *testpath,
@@ -312,8 +312,8 @@ int main(int argc, char **argv)
                       &testdata, test_validate_list);
     validate_test_add("/visitor/input-strict/pass/union-flat",
                       &testdata, test_validate_union_flat);
-    validate_test_add("/visitor/input-strict/pass/union-anon",
-                      &testdata, test_validate_union_anon);
+    validate_test_add("/visitor/input-strict/pass/alternate",
+                      &testdata, test_validate_alternate);
     validate_test_add("/visitor/input-strict/pass/union-native-list",
                       &testdata, test_validate_union_native_list);
     validate_test_add("/visitor/input-strict/fail/struct",
@@ -326,8 +326,8 @@ int main(int argc, char **argv)
                       &testdata, test_validate_fail_union_flat);
     validate_test_add("/visitor/input-strict/fail/union-flat-no-discriminator",
                       &testdata, test_validate_fail_union_flat_no_discrim);
-    validate_test_add("/visitor/input-strict/fail/union-anon",
-                      &testdata, test_validate_fail_union_anon);
+    validate_test_add("/visitor/input-strict/fail/alternate",
+                      &testdata, test_validate_fail_alternate);
     validate_test_add("/visitor/input-strict/fail/union-native-list",
                       &testdata, test_validate_fail_union_native_list);

diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index cc33f64..882359a 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -315,20 +315,20 @@ static void test_visitor_in_union_flat(TestInputVisitorData *data,
     qapi_free_UserDefFlatUnion(tmp);
 }

-static void test_visitor_in_union_anon(TestInputVisitorData *data,
-                                       const void *unused)
+static void test_visitor_in_alternate(TestInputVisitorData *data,
+                                      const void *unused)
 {
     Visitor *v;
     Error *err = NULL;
-    UserDefAnonUnion *tmp;
+    UserDefAlternate *tmp;

     v = visitor_input_test_init(data, "42");

-    visit_type_UserDefAnonUnion(v, &tmp, NULL, &err);
+    visit_type_UserDefAlternate(v, &tmp, NULL, &err);
     g_assert(err == NULL);
-    g_assert_cmpint(tmp->kind, ==, USER_DEF_ANON_UNION_KIND_I);
+    g_assert_cmpint(tmp->kind, ==, USER_DEF_ALTERNATE_KIND_I);
     g_assert_cmpint(tmp->i, ==, 42);
-    qapi_free_UserDefAnonUnion(tmp);
+    qapi_free_UserDefAlternate(tmp);
 }

 static void test_native_list_integer_helper(TestInputVisitorData *data,
@@ -664,8 +664,8 @@ int main(int argc, char **argv)
                            &in_visitor_data, test_visitor_in_list);
     input_visitor_test_add("/visitor/input/union-flat",
                            &in_visitor_data, test_visitor_in_union_flat);
-    input_visitor_test_add("/visitor/input/union-anon",
-                           &in_visitor_data, test_visitor_in_union_anon);
+    input_visitor_test_add("/visitor/input/alternate",
+                           &in_visitor_data, test_visitor_in_alternate);
     input_visitor_test_add("/visitor/input/errors",
                            &in_visitor_data, test_visitor_in_errors);
     input_visitor_test_add("/visitor/input/native_list/int",
diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index ebe6ea3..602bc12 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -453,24 +453,24 @@ static void test_visitor_out_union_flat(TestOutputVisitorData *data,
     QDECREF(qdict);
 }

-static void test_visitor_out_union_anon(TestOutputVisitorData *data,
-                                        const void *unused)
+static void test_visitor_out_alternate(TestOutputVisitorData *data,
+                                       const void *unused)
 {
     QObject *arg;
     Error *err = NULL;

-    UserDefAnonUnion *tmp = g_malloc0(sizeof(UserDefAnonUnion));
-    tmp->kind = USER_DEF_ANON_UNION_KIND_I;
+    UserDefAlternate *tmp = g_malloc0(sizeof(UserDefAlternate));
+    tmp->kind = USER_DEF_ALTERNATE_KIND_I;
     tmp->i = 42;

-    visit_type_UserDefAnonUnion(data->ov, &tmp, NULL, &err);
+    visit_type_UserDefAlternate(data->ov, &tmp, NULL, &err);
     g_assert(err == NULL);
     arg = qmp_output_get_qobject(data->qov);

     g_assert(qobject_type(arg) == QTYPE_QINT);
     g_assert_cmpint(qint_get_int(qobject_to_qint(arg)), ==, 42);

-    qapi_free_UserDefAnonUnion(tmp);
+    qapi_free_UserDefAlternate(tmp);
 }

 static void test_visitor_out_empty(TestOutputVisitorData *data,
@@ -830,8 +830,8 @@ int main(int argc, char **argv)
                             &out_visitor_data, test_visitor_out_list_qapi_free);
     output_visitor_test_add("/visitor/output/union-flat",
                             &out_visitor_data, test_visitor_out_union_flat);
-    output_visitor_test_add("/visitor/output/union-anon",
-                            &out_visitor_data, test_visitor_out_union_anon);
+    output_visitor_test_add("/visitor/output/alternate",
+                            &out_visitor_data, test_visitor_out_alternate);
     output_visitor_test_add("/visitor/output/empty",
                             &out_visitor_data, test_visitor_out_empty);
     output_visitor_test_add("/visitor/output/native_list/int",
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 15/36] qapi: Document new 'alternate' meta-type
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (13 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 14/36] qapi: Rename anonymous union type in test Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-28  8:27   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 16/36] qapi: Use 'alternate' to replace anonymous union Eric Blake
                   ` (24 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

The next patch will quit special-casing "'union':'Foo',
'discriminator':{}" and instead use "'alternate':'Foo'".

Separating docs from implementation makes it easier to focus
on wording without holding up code.  In particular, making
alternate a separate type makes for a nice type hierarchy:

          /-------- meta-type ------\
         /              |            \
    simple types    alternate     complex types
    |         |                   |           |
 built-in   enum             type(struct)   union
 |       \    /                            /    \
numeric  string                         simple  flat

A later patch will then clean up 'type' vs. 'struct'
confusion.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: split from v5:12/28, merge in 2/5 of doc followup series, as
well as Markus' wording improvements
---
 docs/qapi-code-gen.txt | 57 +++++++++++++++++++++++++++++++-------------------
 1 file changed, 36 insertions(+), 21 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 585cb24..8a76cc1 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -84,11 +84,12 @@ the definition of complex structs that can have mutually recursive
 types, and allows for indefinite nesting of QMP that satisfies the
 schema.  A type name should not be defined more than once.

-There are six top-level expressions recognized by the parser:
-'include', 'command', 'type', 'enum', 'union', and 'event'.  There are
-several built-in types, such as 'int' and 'str'; additionally, the
-top-level expressions can define complex types, enumeration types, and
-several flavors of union types.  The 'command' and 'event' expressions
+There are seven top-level expressions recognized by the parser:
+'include', 'command', 'type', 'enum', 'union', 'alternate', and
+'event'.  There are several groups of types: simple types (a number of
+built-in types, such as 'int' and 'str'; as well as enumerations),
+complex types (structs and two flavors of unions), and alternate types
+(a choice between other types).  The 'command' and 'event' expressions
 can refer to existing types by name, or list an anonymous type as a
 dictionary. Listing a type name inside an array refers to a
 single-dimension array of that type; multi-dimension arrays are not
@@ -260,14 +261,12 @@ open-coding the field to be type 'str'.
 Usage: { 'union': STRING, 'data': DICT }
 or:    { 'union': STRING, 'data': DICT, 'base': COMPLEX-TYPE-NAME,
          'discriminator': ENUM-MEMBER-OF-BASE }
-or:    { 'union': STRING, 'data': DICT, 'discriminator': {} }

 Union types are used to let the user choose between several different
-variants for an object.  There are three flavors: simple (no
-discriminator or base), flat (both base and discriminator are
-strings), and anonymous (discriminator is an empty dictionary).  A
-union type is defined using a data dictionary as explained in the
-following paragraphs.
+variants for an object.  There are two flavors: simple (no
+discriminator or base), flat (both discriminator and base).  A union
+type is defined using a data dictionary as explained in the following
+paragraphs.

 A simple union type defines a mapping from automatic discriminator
 values to data types like in this example:
@@ -349,20 +348,36 @@ is identical on the wire to:
    'data': { 'one': 'Branch1', 'two': 'Branch2' } }


-The final flavor of unions is an anonymous union. While the other two
-union types are always passed as a JSON object in the wire format, an
-anonymous union instead allows the direct use of different types in
-its place. Anonymous unions are declared using an empty dictionary as
-their discriminator. The discriminator values never appear on the
-wire, they are only used in the generated C code. Anonymous unions
-cannot have a base type.
+=== Alternate types ===

- { 'union': 'BlockRef',
-   'discriminator': {},
+Usage: { 'alternate': STRING, 'data': DICT }
+
+An alternate type is one that allows a choice between two or more JSON
+data types (string, integer, number, or object, but currently not
+array) on the wire.  The definition is similar to a simple union type,
+where each branch of the union names a QAPI type.  For example:
+
+ { 'alternate': 'BlockRef',
    'data': { 'definition': 'BlockdevOptions',
              'reference': 'str' } }

-This example allows using both of the following example objects:
+Just like for a simple union, an implicit C enum 'NameKind' is created
+to enumerate the branches for the alternate 'Name'.
+
+Unlike a union, the discriminator string is never passed on the wire
+for QMP.  Instead, the value's JSON type serves as an implicit
+discriminator, which in turn means that an alternate can only express
+a choice between types represented differently in JSON.  If a branch
+is typed as the 'bool' built-in, the alternate accepts true and false;
+if it is typed as any of the various numeric built-ins, it accepts a
+JSON number; if it is typed as a 'str' built-in or named enum type, it
+accepts a JSON string; and if it is typed as a complex type (struct or
+union), it accepts a JSON object.  Two different complex types, for
+instance, aren't permitted, because both are represented as a JSON
+object.
+
+The example alternate declaration above allows using both of the
+following example objects:

  { "file": "my_existing_block_device_id" }
  { "file": { "driver": "file",
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 16/36] qapi: Use 'alternate' to replace anonymous union
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (14 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 15/36] qapi: Document new 'alternate' meta-type Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-28  8:41   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 17/36] qapi: Add some expr tests Eric Blake
                   ` (23 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Previous patches have led up to the point where I create the
new meta-type "'alternate':'Foo'".  See the previous patches
for documentation; I intentionally split as much work into
earlier patches to minimize the size of this patch, but a lot
of it is churn due to testsuite fallout after updating to the
new type.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: split out doc patches, rebase onto fixes earlier in series,
hoist several unrelated minor bug fixes earlier into series,
fix some more instances of 'anonymous union'.
---
 qapi/block-core.json                               |  6 ++--
 scripts/qapi-types.py                              | 26 ++++++++++++------
 scripts/qapi-visit.py                              | 17 ++++++++----
 scripts/qapi.py                                    | 32 ++++++++++------------
 tests/qapi-schema/alternate-array.err              |  2 +-
 tests/qapi-schema/alternate-array.json             |  5 ++--
 tests/qapi-schema/alternate-base.err               |  2 +-
 tests/qapi-schema/alternate-base.json              |  5 ++--
 tests/qapi-schema/alternate-clash.err              |  2 +-
 tests/qapi-schema/alternate-clash.json             |  5 ++--
 tests/qapi-schema/alternate-conflict-dict.err      |  2 +-
 tests/qapi-schema/alternate-conflict-dict.json     |  5 ++--
 tests/qapi-schema/alternate-conflict-string.err    |  2 +-
 tests/qapi-schema/alternate-conflict-string.json   |  5 ++--
 tests/qapi-schema/alternate-good.json              |  5 ++--
 tests/qapi-schema/alternate-good.out               |  4 +--
 tests/qapi-schema/alternate-nested.err             |  2 +-
 tests/qapi-schema/alternate-nested.json            | 10 +++----
 tests/qapi-schema/alternate-unknown.err            |  2 +-
 tests/qapi-schema/alternate-unknown.json           |  5 ++--
 tests/qapi-schema/flat-union-bad-discriminator.err |  2 +-
 .../qapi-schema/flat-union-bad-discriminator.json  |  3 +-
 tests/qapi-schema/flat-union-base-union.json       |  2 +-
 tests/qapi-schema/qapi-schema-test.json            |  3 +-
 tests/qapi-schema/qapi-schema-test.out             |  2 +-
 25 files changed, 78 insertions(+), 78 deletions(-)

diff --git a/qapi/block-core.json b/qapi/block-core.json
index 7873084..a76be74 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -1419,8 +1419,7 @@
 #
 # Since: 2.2
 ##
-{ 'union': 'Qcow2OverlapChecks',
-  'discriminator': {},
+{ 'alternate': 'Qcow2OverlapChecks',
   'data': { 'flags': 'Qcow2OverlapCheckFlags',
             'mode':  'Qcow2OverlapCheckMode' } }

@@ -1711,8 +1710,7 @@
 #
 # Since: 1.7
 ##
-{ 'union': 'BlockdevRef',
-  'discriminator': {},
+{ 'alternate': 'BlockdevRef',
   'data': { 'definition': 'BlockdevOptions',
             'reference': 'str' } }

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

 def generate_alternate_qtypes(expr):

-    name = expr['union']
+    name = expr['alternate']
     members = expr['data']

     ret = mcgen('''
@@ -182,7 +182,7 @@ const int %(name)s_qtypes[QTYPE_MAX] = {

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

         ret += mcgen('''
     [ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s,
@@ -197,9 +197,9 @@ const int %(name)s_qtypes[QTYPE_MAX] = {
     return ret


-def generate_union(expr):
+def generate_union(expr, meta):

-    name = expr['union']
+    name = expr[meta]
     typeinfo = expr['data']

     base = expr.get('base')
@@ -243,7 +243,7 @@ struct %(name)s
     ret += mcgen('''
 };
 ''')
-    if discriminator == {}:
+    if meta == 'alternate':
         ret += mcgen('''
 extern const int %(name)s_qtypes[];
 ''',
@@ -407,8 +407,12 @@ for expr in exprs:
             ret += generate_enum('%sKind' % expr['union'], expr['data'].keys())
             fdef.write(generate_enum_lookup('%sKind' % expr['union'],
                                             expr['data'].keys()))
-        if expr.get('discriminator') == {}:
-            fdef.write(generate_alternate_qtypes(expr))
+    elif expr.has_key('alternate'):
+        ret += generate_fwd_struct(expr['alternate'], expr['data']) + "\n"
+        ret += generate_enum('%sKind' % expr['alternate'], expr['data'].keys())
+        fdef.write(generate_enum_lookup('%sKind' % expr['alternate'],
+                                        expr['data'].keys()))
+        fdef.write(generate_alternate_qtypes(expr))
     else:
         continue
     fdecl.write(ret)
@@ -438,11 +442,17 @@ for expr in exprs:
         ret += generate_type_cleanup_decl(expr['type'])
         fdef.write(generate_type_cleanup(expr['type']) + "\n")
     elif expr.has_key('union'):
-        ret += generate_union(expr)
+        ret += generate_union(expr, 'union')
         ret += generate_type_cleanup_decl(expr['union'] + "List")
         fdef.write(generate_type_cleanup(expr['union'] + "List") + "\n")
         ret += generate_type_cleanup_decl(expr['union'])
         fdef.write(generate_type_cleanup(expr['union']) + "\n")
+    elif expr.has_key('alternate'):
+        ret += generate_union(expr, 'alternate')
+        ret += generate_type_cleanup_decl(expr['alternate'] + "List")
+        fdef.write(generate_type_cleanup(expr['alternate'] + "List") + "\n")
+        ret += generate_type_cleanup_decl(expr['alternate'])
+        fdef.write(generate_type_cleanup(expr['alternate']) + "\n")
     elif expr.has_key('enum'):
         ret += generate_type_cleanup_decl(expr['enum'] + "List")
         fdef.write(generate_type_cleanup(expr['enum'] + "List") + "\n")
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 77b0a1f..3e11089 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -256,7 +256,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e
 ''',
     name=name)

-    # For anon union, always use the default enum type automatically generated
+    # For alternate, always use the default enum type automatically generated
     # as "'%sKind' % (name)"
     disc_type = '%sKind' % (name)

@@ -264,7 +264,7 @@ void visit_type_%(name)s(Visitor *m, %(name)s **obj, const char *name, Error **e
         assert (members[key] in builtin_types.keys()
             or find_struct(members[key])
             or find_union(members[key])
-            or find_enum(members[key])), "Invalid anonymous union member"
+            or find_enum(members[key])), "Invalid alternate member"

         enum_full_value = generate_enum_full_value(disc_type, key)
         ret += mcgen('''
@@ -300,10 +300,6 @@ def generate_visit_union(expr):
     base = expr.get('base')
     discriminator = expr.get('discriminator')

-    if discriminator == {}:
-        assert not base
-        return generate_visit_alternate(name, members)
-
     enum_define = discriminator_find_enum_define(expr)
     if enum_define:
         # Use the enum type as discriminator
@@ -572,6 +568,15 @@ for expr in exprs:
                                      expr['data'].keys())
         ret += generate_declaration(expr['union'], expr['data'])
         fdecl.write(ret)
+    elif expr.has_key('alternate'):
+        ret = generate_visit_alternate(expr['alternate'], expr['data'])
+        ret += generate_visit_list(expr['alternate'], expr['data'])
+        fdef.write(ret)
+
+        ret = generate_decl_enum('%sKind' % expr['alternate'],
+                                 expr['data'].keys())
+        ret += generate_declaration(expr['alternate'], expr['data'])
+        fdecl.write(ret)
     elif expr.has_key('enum'):
         ret = generate_visit_list(expr['enum'], expr['data'])
         ret += generate_visit_enum(expr['enum'], expr['data'])
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 0b88325..05c38c5 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -281,7 +281,6 @@ def check_union(expr, expr_info):
                                 "along with base" %name)

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

     # With no discriminator it is a simple union.
     if discriminator is None:
@@ -344,17 +343,14 @@ def check_union(expr, expr_info):
             values[c_key] = key

 def check_alternate(expr, expr_info):
-    name = expr['union']
-    base = expr.get('base')
-    discriminator = expr.get('discriminator')
+    name = expr['alternate']
     members = expr['data']
     values = { 'MAX': '(automatic)' }
     types_seen = {}

-    assert discriminator == {}
-    if base is not None:
+    if expr.get('base') is not None:
         raise QAPIExprError(expr_info,
-                            "Anonymous union '%s' must not have a base"
+                            "Alternate '%s' must not have a base"
                             % name)

     # Check every branch
@@ -363,23 +359,23 @@ def check_alternate(expr, expr_info):
         c_key = _generate_enum_string(key)
         if c_key in values:
             raise QAPIExprError(expr_info,
-                                "Anonymous union '%s' member '%s' clashes "
-                                "with '%s'" % (name, key, values[c_key]))
+                                "Alternate '%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 "
+                                "Alternate '%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 "
+                                "Alternate '%s' member '%s' has "
                                 "invalid type '%s'" % (name, key, value))
         if qtype in types_seen:
             raise QAPIExprError(expr_info,
-                                "Anonymous union '%s' member '%s' can't "
+                                "Alternate '%s' member '%s' can't "
                                 "be distinguished from member '%s'"
                                 % (name, key, types_seen[qtype]))
         types_seen[qtype] = key
@@ -412,10 +408,9 @@ def check_exprs(schema):
         if expr.has_key('enum'):
             check_enum(expr, info)
         elif expr.has_key('union'):
-            if expr.get('discriminator') == {}:
-                check_alternate(expr, info)
-            else:
-                check_union(expr, info)
+            check_union(expr, info)
+        elif expr.has_key('alternate'):
+            check_alternate(expr, info)
         elif expr.has_key('event'):
             check_event(expr, info)

@@ -447,6 +442,8 @@ def parse_schema(input_file):
             if expr.has_key('union'):
                 if not discriminator_find_enum_define(expr):
                     add_enum('%sKind' % expr['union'])
+            elif expr.has_key('alternate'):
+                add_enum('%sKind' % expr['alternate'])

         # Final pass - validate that exprs make sense
         check_exprs(schema)
@@ -557,8 +554,7 @@ def find_struct(name):

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

 def find_union(name):
     global union_types
diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err
index 8d6ccc7..e2a5fc2 100644
--- a/tests/qapi-schema/alternate-array.err
+++ b/tests/qapi-schema/alternate-array.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-array.json:5: Anonymous union 'MyUnion' member 'two' must not be array type
+tests/qapi-schema/alternate-array.json:5: Alternate 'Alt' member 'two' must not be array type
diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json
index 77970d9..043c0fb 100644
--- a/tests/qapi-schema/alternate-array.json
+++ b/tests/qapi-schema/alternate-array.json
@@ -1,8 +1,7 @@
-# we do not support array branches of anonymous unions yet
+# we do not support array branches of alternates yet
 # TODO: should we support this?
 { 'type': 'One',
   'data': { 'name': 'str' } }
-{ 'union': 'MyUnion',
-  'discriminator': {},
+{ 'alternate': 'Alt',
   'data': { 'one': 'One',
             'two': [ 'int' ] } }
diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err
index a2486b8..4a2566e 100644
--- a/tests/qapi-schema/alternate-base.err
+++ b/tests/qapi-schema/alternate-base.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-base.json:4: Anonymous union 'MyUnion' must not have a base
+tests/qapi-schema/alternate-base.json:4: Alternate 'Alt' must not have a base
diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json
index dad7f02..66edc89 100644
--- a/tests/qapi-schema/alternate-base.json
+++ b/tests/qapi-schema/alternate-base.json
@@ -1,7 +1,6 @@
-# we reject anonymous union with base type
+# we reject alternate with base type
 { 'type': 'Base',
   'data': { 'string': 'str' } }
-{ 'union': 'MyUnion',
+{ 'alternate': 'Alt',
   'base': 'Base',
-  'discriminator': {},
   'data': { 'number': 'int' } }
diff --git a/tests/qapi-schema/alternate-clash.err b/tests/qapi-schema/alternate-clash.err
index 8949f52..51bea3e 100644
--- a/tests/qapi-schema/alternate-clash.err
+++ b/tests/qapi-schema/alternate-clash.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-clash.json:2: Anonymous union 'Union1' member 'ONE' clashes with 'one'
+tests/qapi-schema/alternate-clash.json:2: Alternate 'Alt1' member 'ONE' clashes with 'one'
diff --git a/tests/qapi-schema/alternate-clash.json b/tests/qapi-schema/alternate-clash.json
index fa2d27e..3947935 100644
--- a/tests/qapi-schema/alternate-clash.json
+++ b/tests/qapi-schema/alternate-clash.json
@@ -1,4 +1,3 @@
-# we detect C enum collisions in an anonymous union
-{ 'union': 'Union1',
-  'discriminator': {},
+# we detect C enum collisions in an alternate
+{ 'alternate': 'Alt1',
   'data': { 'one': 'str', 'ONE': 'int' } }
diff --git a/tests/qapi-schema/alternate-conflict-dict.err b/tests/qapi-schema/alternate-conflict-dict.err
index 2d4550c..0f411f4 100644
--- a/tests/qapi-schema/alternate-conflict-dict.err
+++ b/tests/qapi-schema/alternate-conflict-dict.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-conflict-dict.json:6: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one'
+tests/qapi-schema/alternate-conflict-dict.json:6: Alternate 'Alt' member 'two' can't be distinguished from member 'one'
diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json
index ded302e..fcb3e36 100644
--- a/tests/qapi-schema/alternate-conflict-dict.json
+++ b/tests/qapi-schema/alternate-conflict-dict.json
@@ -1,9 +1,8 @@
-# we reject anonymous unions with multiple object branches
+# we reject alternates with multiple object branches
 { 'type': 'One',
   'data': { 'name': 'str' } }
 { 'type': 'Two',
   'data': { 'value': 'int' } }
-{ 'union': 'MyUnion',
-  'discriminator': {},
+{ 'alternate': 'Alt',
   'data': { 'one': 'One',
             'two': 'Two' } }
diff --git a/tests/qapi-schema/alternate-conflict-string.err b/tests/qapi-schema/alternate-conflict-string.err
index 271ddcd..fc523b0 100644
--- a/tests/qapi-schema/alternate-conflict-string.err
+++ b/tests/qapi-schema/alternate-conflict-string.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-conflict-string.json:4: Anonymous union 'MyUnion' member 'two' can't be distinguished from member 'one'
+tests/qapi-schema/alternate-conflict-string.json:4: Alternate 'Alt' member 'two' can't be distinguished from member 'one'
diff --git a/tests/qapi-schema/alternate-conflict-string.json b/tests/qapi-schema/alternate-conflict-string.json
index 3834a3d..72f04a8 100644
--- a/tests/qapi-schema/alternate-conflict-string.json
+++ b/tests/qapi-schema/alternate-conflict-string.json
@@ -1,7 +1,6 @@
-# we reject anonymous unions with multiple string-like branches
+# we reject alternates with multiple string-like branches
 { 'enum': 'Enum',
   'data': [ 'hello', 'world' ] }
-{ 'union': 'MyUnion',
-  'discriminator': {},
+{ 'alternate': 'Alt',
   'data': { 'one': 'str',
             'two': 'Enum' } }
diff --git a/tests/qapi-schema/alternate-good.json b/tests/qapi-schema/alternate-good.json
index 73d0993..99d614f 100644
--- a/tests/qapi-schema/alternate-good.json
+++ b/tests/qapi-schema/alternate-good.json
@@ -1,10 +1,9 @@
-# Working example of anonymous union
+# Working example of alternate
 { 'type': 'Data',
   'data': { '*number': 'int', '*name': 'str' } }
 { 'enum': 'Enum',
   'data': [ 'hello', 'world' ] }
-{ 'union': 'MyUnion',
-  'discriminator': {},
+{ 'alternate': 'Alt',
   'data': { 'value': 'int',
             'string': 'Enum',
             'struct': 'Data' } }
diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out
index b5117d1..c3a6b77 100644
--- a/tests/qapi-schema/alternate-good.out
+++ b/tests/qapi-schema/alternate-good.out
@@ -1,6 +1,6 @@
 [OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]),
  OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]),
- OrderedDict([('union', 'MyUnion'), ('discriminator', OrderedDict()), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])]
+ OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])]
 [{'enum_name': 'Enum', 'enum_values': ['hello', 'world']},
- {'enum_name': 'MyUnionKind', 'enum_values': None}]
+ {'enum_name': 'AltKind', 'enum_values': None}]
 [OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])]
diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err
index 59df96e..00b05c6 100644
--- a/tests/qapi-schema/alternate-nested.err
+++ b/tests/qapi-schema/alternate-nested.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-nested.json:5: Anonymous union 'Union2' member 'nested' has invalid type 'Union1'
+tests/qapi-schema/alternate-nested.json:4: Alternate 'Alt2' member 'nested' has invalid type 'Alt1'
diff --git a/tests/qapi-schema/alternate-nested.json b/tests/qapi-schema/alternate-nested.json
index ed2b6b7..c4233b9 100644
--- a/tests/qapi-schema/alternate-nested.json
+++ b/tests/qapi-schema/alternate-nested.json
@@ -1,7 +1,5 @@
-# we reject a nested anonymous union branch
-{ 'union': 'Union1',
-  'discriminator': {},
+# we reject a nested alternate branch
+{ 'alternate': 'Alt1',
   'data': { 'name': 'str', 'value': 'int' } }
-{ 'union': 'Union2',
-  'discriminator': {},
-  'data': { 'nested': 'Union1' } }
+{ 'alternate': 'Alt2',
+  'data': { 'nested': 'Alt1' } }
diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err
index bf8e9ae..7af1b4c 100644
--- a/tests/qapi-schema/alternate-unknown.err
+++ b/tests/qapi-schema/alternate-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-unknown.json:2: Anonymous union 'Union' member 'unknown' has invalid type 'MissingType'
+tests/qapi-schema/alternate-unknown.json:2: Alternate 'Alt' member 'unknown' has invalid type 'MissingType'
diff --git a/tests/qapi-schema/alternate-unknown.json b/tests/qapi-schema/alternate-unknown.json
index 0c305c2..ad5c103 100644
--- a/tests/qapi-schema/alternate-unknown.json
+++ b/tests/qapi-schema/alternate-unknown.json
@@ -1,4 +1,3 @@
-# we reject an anonymous union with unknown type in branch
-{ 'union': 'Union',
-  'discriminator': {},
+# we reject an alternate with unknown type in branch
+{ 'alternate': 'Alt',
   'data': { 'unknown': 'MissingType' } }
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err
index 1661c52..507e2ba 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.err
+++ b/tests/qapi-schema/flat-union-bad-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-bad-discriminator.json:10: Flat union 'TestUnion' discriminator must be a string
+tests/qapi-schema/flat-union-bad-discriminator.json:11: Flat union 'TestUnion' discriminator must be a string
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json
index 3ce43e8..982f072 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.json
+++ b/tests/qapi-schema/flat-union-bad-discriminator.json
@@ -1,4 +1,5 @@
 # we require the discriminator to be a string naming a base-type member
+# this tests the old syntax for anonymous unions before we added alternates
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 { 'type': 'TestBase',
@@ -9,6 +10,6 @@
   'data': { 'integer': 'int' } }
 { 'union': 'TestUnion',
   'base': 'TestBase',
-  'discriminator': [],
+  'discriminator': {},
   'data': { 'kind1': 'TestTypeA',
             'kind2': 'TestTypeB' } }
diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json
index 838986c..0ba6e28 100644
--- a/tests/qapi-schema/flat-union-base-union.json
+++ b/tests/qapi-schema/flat-union-base-union.json
@@ -1,4 +1,4 @@
-# FIXME: the error message needs help: we require the base to be a struct
+# we require the base to be a struct
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 { 'type': 'TestTypeA',
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index e1d35e1..dec8a7c 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -53,8 +53,7 @@
   'discriminator': 'enum1',
   'data': { 'value1' : 'UserDefC', 'value2' : 'UserDefB', 'value3' : 'UserDefA' } }

-{ 'union': 'UserDefAlternate',
-  'discriminator': {},
+{ 'alternate': 'UserDefAlternate',
   'data': { 'uda': 'UserDefA', 's': 'str', 'i': 'int' } }

 # for testing native lists
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index b55ab8d..313ecf3 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -10,7 +10,7 @@
  OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]),
  OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]),
  OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]),
- OrderedDict([('union', 'UserDefAlternate'), ('discriminator', OrderedDict()), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]),
+ OrderedDict([('alternate', 'UserDefAlternate'), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]),
  OrderedDict([('union', 'UserDefNativeListUnion'), ('data', OrderedDict([('integer', ['int']), ('s8', ['int8']), ('s16', ['int16']), ('s32', ['int32']), ('s64', ['int64']), ('u8', ['uint8']), ('u16', ['uint16']), ('u32', ['uint32']), ('u64', ['uint64']), ('number', ['number']), ('boolean', ['bool']), ('string', ['str']), ('sizes', ['size'])]))]),
  OrderedDict([('command', 'user_def_cmd'), ('data', OrderedDict())]),
  OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]),
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 17/36] qapi: Add some expr tests
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (15 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 16/36] qapi: Use 'alternate' to replace anonymous union Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-28 11:01   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 18/36] qapi: Better error messages for bad expressions Eric Blake
                   ` (22 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Demonstrate that the qapi generator doesn't deal well with
expressions that aren't up to par. Later patches will improve
the expected results as the generator is made stricter.  Only
a few of the the added tests actually behaves sanely at
rejecting obvious problems or demonstrating success.

Note that in some cases, we reject bad QAPI merely because our
pseudo-JSON parser does not yet know how to parse numbers.  This
series does not address that, but when a later series adds support
for numeric defaults of integer fields, the testsuite will ensure
that we don't lose the error (and hopefully that the error
message quality is improved).

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: fix blank line, add new test ident-with-escape; relax comment
about event-case (as I no longer intend to enforce it this series)
---
 tests/Makefile                           | 8 +++++---
 tests/qapi-schema/bad-base.err           | 0
 tests/qapi-schema/bad-base.exit          | 1 +
 tests/qapi-schema/bad-base.json          | 3 +++
 tests/qapi-schema/bad-base.out           | 4 ++++
 tests/qapi-schema/bad-ident.err          | 0
 tests/qapi-schema/bad-ident.exit         | 1 +
 tests/qapi-schema/bad-ident.json         | 2 ++
 tests/qapi-schema/bad-ident.out          | 3 +++
 tests/qapi-schema/bad-type-bool.err      | 1 +
 tests/qapi-schema/bad-type-bool.exit     | 1 +
 tests/qapi-schema/bad-type-bool.json     | 3 +++
 tests/qapi-schema/bad-type-bool.out      | 0
 tests/qapi-schema/bad-type-dict.err      | 0
 tests/qapi-schema/bad-type-dict.exit     | 1 +
 tests/qapi-schema/bad-type-dict.json     | 2 ++
 tests/qapi-schema/bad-type-dict.out      | 3 +++
 tests/qapi-schema/bad-type-int.err       | 1 +
 tests/qapi-schema/bad-type-int.exit      | 1 +
 tests/qapi-schema/bad-type-int.json      | 3 +++
 tests/qapi-schema/bad-type-int.out       | 0
 tests/qapi-schema/double-data.err        | 1 +
 tests/qapi-schema/double-data.exit       | 1 +
 tests/qapi-schema/double-data.json       | 2 ++
 tests/qapi-schema/double-data.out        | 0
 tests/qapi-schema/double-type.err        | 0
 tests/qapi-schema/double-type.exit       | 1 +
 tests/qapi-schema/double-type.json       | 2 ++
 tests/qapi-schema/double-type.out        | 3 +++
 tests/qapi-schema/event-case.err         | 0
 tests/qapi-schema/event-case.exit        | 1 +
 tests/qapi-schema/event-case.json        | 3 +++
 tests/qapi-schema/event-case.out         | 3 +++
 tests/qapi-schema/ident-with-escape.err  | 0
 tests/qapi-schema/ident-with-escape.exit | 1 +
 tests/qapi-schema/ident-with-escape.json | 4 ++++
 tests/qapi-schema/ident-with-escape.out  | 3 +++
 tests/qapi-schema/missing-type.err       | 0
 tests/qapi-schema/missing-type.exit      | 1 +
 tests/qapi-schema/missing-type.json      | 2 ++
 tests/qapi-schema/missing-type.out       | 3 +++
 tests/qapi-schema/unknown-expr-key.err   | 0
 tests/qapi-schema/unknown-expr-key.exit  | 1 +
 tests/qapi-schema/unknown-expr-key.json  | 2 ++
 tests/qapi-schema/unknown-expr-key.out   | 3 +++
 45 files changed, 72 insertions(+), 3 deletions(-)
 create mode 100644 tests/qapi-schema/bad-base.err
 create mode 100644 tests/qapi-schema/bad-base.exit
 create mode 100644 tests/qapi-schema/bad-base.json
 create mode 100644 tests/qapi-schema/bad-base.out
 create mode 100644 tests/qapi-schema/bad-ident.err
 create mode 100644 tests/qapi-schema/bad-ident.exit
 create mode 100644 tests/qapi-schema/bad-ident.json
 create mode 100644 tests/qapi-schema/bad-ident.out
 create mode 100644 tests/qapi-schema/bad-type-bool.err
 create mode 100644 tests/qapi-schema/bad-type-bool.exit
 create mode 100644 tests/qapi-schema/bad-type-bool.json
 create mode 100644 tests/qapi-schema/bad-type-bool.out
 create mode 100644 tests/qapi-schema/bad-type-dict.err
 create mode 100644 tests/qapi-schema/bad-type-dict.exit
 create mode 100644 tests/qapi-schema/bad-type-dict.json
 create mode 100644 tests/qapi-schema/bad-type-dict.out
 create mode 100644 tests/qapi-schema/bad-type-int.err
 create mode 100644 tests/qapi-schema/bad-type-int.exit
 create mode 100644 tests/qapi-schema/bad-type-int.json
 create mode 100644 tests/qapi-schema/bad-type-int.out
 create mode 100644 tests/qapi-schema/double-data.err
 create mode 100644 tests/qapi-schema/double-data.exit
 create mode 100644 tests/qapi-schema/double-data.json
 create mode 100644 tests/qapi-schema/double-data.out
 create mode 100644 tests/qapi-schema/double-type.err
 create mode 100644 tests/qapi-schema/double-type.exit
 create mode 100644 tests/qapi-schema/double-type.json
 create mode 100644 tests/qapi-schema/double-type.out
 create mode 100644 tests/qapi-schema/event-case.err
 create mode 100644 tests/qapi-schema/event-case.exit
 create mode 100644 tests/qapi-schema/event-case.json
 create mode 100644 tests/qapi-schema/event-case.out
 create mode 100644 tests/qapi-schema/ident-with-escape.err
 create mode 100644 tests/qapi-schema/ident-with-escape.exit
 create mode 100644 tests/qapi-schema/ident-with-escape.json
 create mode 100644 tests/qapi-schema/ident-with-escape.out
 create mode 100644 tests/qapi-schema/missing-type.err
 create mode 100644 tests/qapi-schema/missing-type.exit
 create mode 100644 tests/qapi-schema/missing-type.json
 create mode 100644 tests/qapi-schema/missing-type.out
 create mode 100644 tests/qapi-schema/unknown-expr-key.err
 create mode 100644 tests/qapi-schema/unknown-expr-key.exit
 create mode 100644 tests/qapi-schema/unknown-expr-key.json
 create mode 100644 tests/qapi-schema/unknown-expr-key.out

diff --git a/tests/Makefile b/tests/Makefile
index 7b6de1c..bc5e0a7 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -210,8 +210,10 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	comments.json empty.json enum-empty.json enum-missing-data.json \
 	enum-wrong-data.json enum-int-member.json enum-dict-member.json \
 	enum-clash-member.json enum-max-member.json enum-union-clash.json \
-	enum-bad-name.json \
-	funny-char.json indented-expr.json \
+	enum-bad-name.json funny-char.json indented-expr.json \
+	missing-type.json bad-ident.json ident-with-escape.json \
+	double-type.json bad-base.json bad-type-bool.json bad-type-int.json \
+	bad-type-dict.json double-data.json unknown-expr-key.json \
 	missing-colon.json missing-comma-list.json \
 	missing-comma-object.json non-objects.json \
 	qapi-schema-test.json quoted-structural-chars.json \
@@ -232,7 +234,7 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	include-simple.json include-relpath.json include-format-err.json \
 	include-non-file.json include-no-file.json include-before-err.json \
 	include-nested-err.json include-self-cycle.json include-cycle.json \
-	include-repetition.json event-nest-struct.json)
+	include-repetition.json event-nest-struct.json event-case.json)

 GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
 		     tests/test-qmp-commands.h tests/test-qapi-event.h
diff --git a/tests/qapi-schema/bad-base.err b/tests/qapi-schema/bad-base.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/bad-base.exit b/tests/qapi-schema/bad-base.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/bad-base.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/bad-base.json b/tests/qapi-schema/bad-base.json
new file mode 100644
index 0000000..de964a0
--- /dev/null
+++ b/tests/qapi-schema/bad-base.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject a base that is not a struct
+{ 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } }
+{ 'type': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } }
diff --git a/tests/qapi-schema/bad-base.out b/tests/qapi-schema/bad-base.out
new file mode 100644
index 0000000..91d12fc
--- /dev/null
+++ b/tests/qapi-schema/bad-base.out
@@ -0,0 +1,4 @@
+[OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]),
+ OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])]
+[{'enum_name': 'UnionKind', 'enum_values': None}]
+[OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])]
diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/bad-ident.exit b/tests/qapi-schema/bad-ident.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/bad-ident.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/bad-ident.json b/tests/qapi-schema/bad-ident.json
new file mode 100644
index 0000000..f139110
--- /dev/null
+++ b/tests/qapi-schema/bad-ident.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject creating a type name with bad name
+{ 'type': '*oops', 'data': { 'i': 'int' } }
diff --git a/tests/qapi-schema/bad-ident.out b/tests/qapi-schema/bad-ident.out
new file mode 100644
index 0000000..165e346
--- /dev/null
+++ b/tests/qapi-schema/bad-ident.out
@@ -0,0 +1,3 @@
+[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])]
+[]
+[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])]
diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err
new file mode 100644
index 0000000..badb7c2
--- /dev/null
+++ b/tests/qapi-schema/bad-type-bool.err
@@ -0,0 +1 @@
+tests/qapi-schema/bad-type-bool.json:3:11: Stray "t"
diff --git a/tests/qapi-schema/bad-type-bool.exit b/tests/qapi-schema/bad-type-bool.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/bad-type-bool.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/bad-type-bool.json b/tests/qapi-schema/bad-type-bool.json
new file mode 100644
index 0000000..22d6369
--- /dev/null
+++ b/tests/qapi-schema/bad-type-bool.json
@@ -0,0 +1,3 @@
+# we reject an expression with a metatype that is not a string
+# FIXME: once the parser understands bool inputs, improve the error message
+{ 'type': true, 'data': { } }
diff --git a/tests/qapi-schema/bad-type-bool.out b/tests/qapi-schema/bad-type-bool.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/bad-type-dict.err b/tests/qapi-schema/bad-type-dict.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/bad-type-dict.exit b/tests/qapi-schema/bad-type-dict.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/bad-type-dict.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/bad-type-dict.json b/tests/qapi-schema/bad-type-dict.json
new file mode 100644
index 0000000..3c392a7
--- /dev/null
+++ b/tests/qapi-schema/bad-type-dict.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject an expression with a metatype that is not a string
+{ 'command': { } }
diff --git a/tests/qapi-schema/bad-type-dict.out b/tests/qapi-schema/bad-type-dict.out
new file mode 100644
index 0000000..c62f1ed
--- /dev/null
+++ b/tests/qapi-schema/bad-type-dict.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', OrderedDict())])]
+[]
+[]
diff --git a/tests/qapi-schema/bad-type-int.err b/tests/qapi-schema/bad-type-int.err
new file mode 100644
index 0000000..9808550
--- /dev/null
+++ b/tests/qapi-schema/bad-type-int.err
@@ -0,0 +1 @@
+tests/qapi-schema/bad-type-int.json:3:11: Stray "1"
diff --git a/tests/qapi-schema/bad-type-int.exit b/tests/qapi-schema/bad-type-int.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/bad-type-int.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/bad-type-int.json b/tests/qapi-schema/bad-type-int.json
new file mode 100644
index 0000000..398879d
--- /dev/null
+++ b/tests/qapi-schema/bad-type-int.json
@@ -0,0 +1,3 @@
+# we reject an expression with a metatype that is not a string
+# FIXME: once the parser understands integer inputs, improve the error message
+{ 'type': 1, 'data': { } }
diff --git a/tests/qapi-schema/bad-type-int.out b/tests/qapi-schema/bad-type-int.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/double-data.err b/tests/qapi-schema/double-data.err
new file mode 100644
index 0000000..6f1a67b
--- /dev/null
+++ b/tests/qapi-schema/double-data.err
@@ -0,0 +1 @@
+tests/qapi-schema/double-data.json:2:39: Duplicate key "data"
diff --git a/tests/qapi-schema/double-data.exit b/tests/qapi-schema/double-data.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/double-data.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/double-data.json b/tests/qapi-schema/double-data.json
new file mode 100644
index 0000000..a94b7df
--- /dev/null
+++ b/tests/qapi-schema/double-data.json
@@ -0,0 +1,2 @@
+# we reject an expression with duplicate top-level keys
+{ 'type': 'bar', 'data': { }, 'data': { 'string': 'str'} }
diff --git a/tests/qapi-schema/double-data.out b/tests/qapi-schema/double-data.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/double-type.exit b/tests/qapi-schema/double-type.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/double-type.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/double-type.json b/tests/qapi-schema/double-type.json
new file mode 100644
index 0000000..6ca96b9
--- /dev/null
+++ b/tests/qapi-schema/double-type.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject an expression with ambiguous metatype
+{ 'command': 'foo', 'type': 'bar', 'data': { } }
diff --git a/tests/qapi-schema/double-type.out b/tests/qapi-schema/double-type.out
new file mode 100644
index 0000000..3e244f5
--- /dev/null
+++ b/tests/qapi-schema/double-type.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])]
+[]
+[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])]
diff --git a/tests/qapi-schema/event-case.err b/tests/qapi-schema/event-case.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/event-case.exit b/tests/qapi-schema/event-case.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/event-case.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/event-case.json b/tests/qapi-schema/event-case.json
new file mode 100644
index 0000000..3a92d8b
--- /dev/null
+++ b/tests/qapi-schema/event-case.json
@@ -0,0 +1,3 @@
+# TODO: might be nice to enforce naming conventions; but until then this works
+# even though events should usually be ALL_CAPS
+{ 'event': 'oops' }
diff --git a/tests/qapi-schema/event-case.out b/tests/qapi-schema/event-case.out
new file mode 100644
index 0000000..3764bc7
--- /dev/null
+++ b/tests/qapi-schema/event-case.out
@@ -0,0 +1,3 @@
+[OrderedDict([('event', 'oops')])]
+[]
+[]
diff --git a/tests/qapi-schema/ident-with-escape.err b/tests/qapi-schema/ident-with-escape.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/ident-with-escape.exit b/tests/qapi-schema/ident-with-escape.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/ident-with-escape.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/ident-with-escape.json b/tests/qapi-schema/ident-with-escape.json
new file mode 100644
index 0000000..cfb2050
--- /dev/null
+++ b/tests/qapi-schema/ident-with-escape.json
@@ -0,0 +1,4 @@
+# FIXME: we should allow escape sequences in strings, if they map back to ASCII
+# { 'command': 'fooA', 'data': { 'bar1': 'str' } }
+{ 'c\u006fmmand': '\u0066\u006f\u006FA',
+  'd\u0061ta': { '\u0062\u0061\u00721': '\u0073\u0074\u0072' } }
diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out
new file mode 100644
index 0000000..a44623f
--- /dev/null
+++ b/tests/qapi-schema/ident-with-escape.out
@@ -0,0 +1,3 @@
+[OrderedDict([('cu006fmmand', 'u0066u006fu006FA'), ('du0061ta', OrderedDict([('u0062u0061u00721', 'u0073u0074u0072')]))])]
+[]
+[]
diff --git a/tests/qapi-schema/missing-type.err b/tests/qapi-schema/missing-type.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/missing-type.exit b/tests/qapi-schema/missing-type.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/missing-type.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/missing-type.json b/tests/qapi-schema/missing-type.json
new file mode 100644
index 0000000..1696f5c
--- /dev/null
+++ b/tests/qapi-schema/missing-type.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject an expression with missing metatype
+{ 'data': { } }
diff --git a/tests/qapi-schema/missing-type.out b/tests/qapi-schema/missing-type.out
new file mode 100644
index 0000000..67fd4fa
--- /dev/null
+++ b/tests/qapi-schema/missing-type.out
@@ -0,0 +1,3 @@
+[OrderedDict([('data', OrderedDict())])]
+[]
+[]
diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/unknown-expr-key.exit b/tests/qapi-schema/unknown-expr-key.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/unknown-expr-key.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json
new file mode 100644
index 0000000..1e9282d
--- /dev/null
+++ b/tests/qapi-schema/unknown-expr-key.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject an expression with unknown top-level keys
+{ 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }
diff --git a/tests/qapi-schema/unknown-expr-key.out b/tests/qapi-schema/unknown-expr-key.out
new file mode 100644
index 0000000..c93f020
--- /dev/null
+++ b/tests/qapi-schema/unknown-expr-key.out
@@ -0,0 +1,3 @@
+[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])]
+[]
+[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 18/36] qapi: Better error messages for bad expressions
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (16 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 17/36] qapi: Add some expr tests Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 19/36] qapi: Add tests of redefined expressions Eric Blake
                   ` (21 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

The previous commit demonstrated that the generator overlooked some
fairly basic broken expressions:
- missing metataype
- metatype key has a non-string value
- unknown key in relation to the metatype
- conflicting metatype (this patch treats the second metatype as an
unknown key of the first key visited, which is not necessarily the
first key the user typed)

Add check_keys to cover these situations, and update testcases to
match.  A couple other tests (enum-missing-data, indented-expr) had
to change since the validation added here occurs so early.
Conversely, changes to ident-with-escape results show that we still
have problems where our handling of escape sequences differs from
true JSON, which will matter down the road if we allow arbitrary
default string values for optional parameters (but for now is not
too bad, as we currently can avoid unicode escaping as we don't
need to represent anything beyond C identifier material).

While valid .json files won't trigger any of these cases, we might
as well be nicer to developers that make a typo while trying to add
new QAPI code.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase onto earlier changes, enhance commit message,
tweak error messages to consistently start with capitals
---
 scripts/qapi.py                          | 44 +++++++++++++++++++++++++++-----
 tests/qapi-schema/alternate-base.err     |  2 +-
 tests/qapi-schema/bad-type-dict.err      |  1 +
 tests/qapi-schema/bad-type-dict.exit     |  2 +-
 tests/qapi-schema/bad-type-dict.json     |  2 +-
 tests/qapi-schema/bad-type-dict.out      |  3 ---
 tests/qapi-schema/double-type.err        |  1 +
 tests/qapi-schema/double-type.exit       |  2 +-
 tests/qapi-schema/double-type.json       |  2 +-
 tests/qapi-schema/double-type.out        |  3 ---
 tests/qapi-schema/enum-missing-data.err  |  2 +-
 tests/qapi-schema/ident-with-escape.err  |  1 +
 tests/qapi-schema/ident-with-escape.exit |  2 +-
 tests/qapi-schema/ident-with-escape.out  |  3 ---
 tests/qapi-schema/indented-expr.json     |  4 +--
 tests/qapi-schema/indented-expr.out      |  2 +-
 tests/qapi-schema/missing-type.err       |  1 +
 tests/qapi-schema/missing-type.exit      |  2 +-
 tests/qapi-schema/missing-type.json      |  2 +-
 tests/qapi-schema/missing-type.out       |  3 ---
 tests/qapi-schema/unknown-expr-key.err   |  1 +
 tests/qapi-schema/unknown-expr-key.exit  |  2 +-
 tests/qapi-schema/unknown-expr-key.json  |  2 +-
 tests/qapi-schema/unknown-expr-key.out   |  3 ---
 24 files changed, 56 insertions(+), 36 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 05c38c5..868f08b 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -348,11 +348,6 @@ def check_alternate(expr, expr_info):
     values = { 'MAX': '(automatic)' }
     types_seen = {}

-    if expr.get('base') is not None:
-        raise QAPIExprError(expr_info,
-                            "Alternate '%s' must not have a base"
-                            % name)
-
     # Check every branch
     for (key, value) in members.items():
         # Check for conflicts in the generated enum
@@ -414,6 +409,26 @@ def check_exprs(schema):
         elif expr.has_key('event'):
             check_event(expr, info)

+def check_keys(expr_elem, meta, required, optional=[]):
+    expr = expr_elem['expr']
+    info = expr_elem['info']
+    name = expr[meta]
+    if not isinstance(name, str):
+        raise QAPIExprError(info,
+                            "'%s' key must have a string value" % meta)
+    required = required + [ meta ]
+    for (key, value) in expr.items():
+        if not key in required and not key in optional:
+            raise QAPIExprError(info,
+                                "Unknown key '%s' in %s '%s'"
+                                % (key, meta, name))
+    for key in required:
+        if not expr.has_key(key):
+            raise QAPIExprError(info,
+                                "Key '%s' is missing from %s '%s'"
+                                % (key, meta, name))
+
+
 def parse_schema(input_file):
     # First pass: read entire file into memory
     try:
@@ -425,15 +440,30 @@ def parse_schema(input_file):
     exprs = []

     try:
-        # Next pass: learn the types.
+        # Next pass: learn the types and check for valid expression keys. At
+        # this point, top-level 'include' has already been flattened.
         for expr_elem in schema.exprs:
             expr = expr_elem['expr']
             if expr.has_key('enum'):
-                add_enum(expr['enum'], expr.get('data'))
+                check_keys(expr_elem, 'enum', ['data'])
+                add_enum(expr['enum'], expr['data'])
             elif expr.has_key('union'):
+                check_keys(expr_elem, 'union', ['data'],
+                           ['base', 'discriminator'])
                 add_union(expr)
+            elif expr.has_key('alternate'):
+                check_keys(expr_elem, 'alternate', ['data'])
             elif expr.has_key('type'):
+                check_keys(expr_elem, 'type', ['data'], ['base'])
                 add_struct(expr)
+            elif expr.has_key('command'):
+                check_keys(expr_elem, 'command', [],
+                           ['data', 'returns', 'gen', 'success-response'])
+            elif expr.has_key('event'):
+                check_keys(expr_elem, 'event', [], ['data'])
+            else:
+                raise QAPIExprError(expr_elem['info'],
+                                    "Expression is missing metatype")
             exprs.append(expr)

         # Try again for hidden UnionKind enum
diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err
index 4a2566e..30d8a34 100644
--- a/tests/qapi-schema/alternate-base.err
+++ b/tests/qapi-schema/alternate-base.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-base.json:4: Alternate 'Alt' must not have a base
+tests/qapi-schema/alternate-base.json:4: Unknown key 'base' in alternate 'Alt'
diff --git a/tests/qapi-schema/bad-type-dict.err b/tests/qapi-schema/bad-type-dict.err
index e69de29..0b2a2ae 100644
--- a/tests/qapi-schema/bad-type-dict.err
+++ b/tests/qapi-schema/bad-type-dict.err
@@ -0,0 +1 @@
+tests/qapi-schema/bad-type-dict.json:2: 'command' key must have a string value
diff --git a/tests/qapi-schema/bad-type-dict.exit b/tests/qapi-schema/bad-type-dict.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/bad-type-dict.exit
+++ b/tests/qapi-schema/bad-type-dict.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/bad-type-dict.json b/tests/qapi-schema/bad-type-dict.json
index 3c392a7..2a91b24 100644
--- a/tests/qapi-schema/bad-type-dict.json
+++ b/tests/qapi-schema/bad-type-dict.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject an expression with a metatype that is not a string
+# we reject an expression with a metatype that is not a string
 { 'command': { } }
diff --git a/tests/qapi-schema/bad-type-dict.out b/tests/qapi-schema/bad-type-dict.out
index c62f1ed..e69de29 100644
--- a/tests/qapi-schema/bad-type-dict.out
+++ b/tests/qapi-schema/bad-type-dict.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', OrderedDict())])]
-[]
-[]
diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err
index e69de29..ceb6e46 100644
--- a/tests/qapi-schema/double-type.err
+++ b/tests/qapi-schema/double-type.err
@@ -0,0 +1 @@
+tests/qapi-schema/double-type.json:2: Unknown key 'command' in type 'bar'
diff --git a/tests/qapi-schema/double-type.exit b/tests/qapi-schema/double-type.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/double-type.exit
+++ b/tests/qapi-schema/double-type.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/double-type.json b/tests/qapi-schema/double-type.json
index 6ca96b9..471623a 100644
--- a/tests/qapi-schema/double-type.json
+++ b/tests/qapi-schema/double-type.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject an expression with ambiguous metatype
+# we reject an expression with ambiguous metatype
 { 'command': 'foo', 'type': 'bar', 'data': { } }
diff --git a/tests/qapi-schema/double-type.out b/tests/qapi-schema/double-type.out
index 3e244f5..e69de29 100644
--- a/tests/qapi-schema/double-type.out
+++ b/tests/qapi-schema/double-type.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])]
-[]
-[OrderedDict([('command', 'foo'), ('type', 'bar'), ('data', OrderedDict())])]
diff --git a/tests/qapi-schema/enum-missing-data.err b/tests/qapi-schema/enum-missing-data.err
index b8ccae0..ba4873a 100644
--- a/tests/qapi-schema/enum-missing-data.err
+++ b/tests/qapi-schema/enum-missing-data.err
@@ -1 +1 @@
-tests/qapi-schema/enum-missing-data.json:2: Enum 'MyEnum' requires an array for 'data'
+tests/qapi-schema/enum-missing-data.json:2: Key 'data' is missing from enum 'MyEnum'
diff --git a/tests/qapi-schema/ident-with-escape.err b/tests/qapi-schema/ident-with-escape.err
index e69de29..f7d1c55 100644
--- a/tests/qapi-schema/ident-with-escape.err
+++ b/tests/qapi-schema/ident-with-escape.err
@@ -0,0 +1 @@
+tests/qapi-schema/ident-with-escape.json:3: Expression is missing metatype
diff --git a/tests/qapi-schema/ident-with-escape.exit b/tests/qapi-schema/ident-with-escape.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/ident-with-escape.exit
+++ b/tests/qapi-schema/ident-with-escape.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out
index a44623f..e69de29 100644
--- a/tests/qapi-schema/ident-with-escape.out
+++ b/tests/qapi-schema/ident-with-escape.out
@@ -1,3 +0,0 @@
-[OrderedDict([('cu006fmmand', 'u0066u006fu006FA'), ('du0061ta', OrderedDict([('u0062u0061u00721', 'u0073u0074u0072')]))])]
-[]
-[]
diff --git a/tests/qapi-schema/indented-expr.json b/tests/qapi-schema/indented-expr.json
index d80af60..7115d31 100644
--- a/tests/qapi-schema/indented-expr.json
+++ b/tests/qapi-schema/indented-expr.json
@@ -1,2 +1,2 @@
-{ 'id' : 'eins' }
- { 'id' : 'zwei' }
+{ 'command' : 'eins' }
+ { 'command' : 'zwei' }
diff --git a/tests/qapi-schema/indented-expr.out b/tests/qapi-schema/indented-expr.out
index 98af89a..b5ce915 100644
--- a/tests/qapi-schema/indented-expr.out
+++ b/tests/qapi-schema/indented-expr.out
@@ -1,3 +1,3 @@
-[OrderedDict([('id', 'eins')]), OrderedDict([('id', 'zwei')])]
+[OrderedDict([('command', 'eins')]), OrderedDict([('command', 'zwei')])]
 []
 []
diff --git a/tests/qapi-schema/missing-type.err b/tests/qapi-schema/missing-type.err
index e69de29..b3e7b14 100644
--- a/tests/qapi-schema/missing-type.err
+++ b/tests/qapi-schema/missing-type.err
@@ -0,0 +1 @@
+tests/qapi-schema/missing-type.json:2: Expression is missing metatype
diff --git a/tests/qapi-schema/missing-type.exit b/tests/qapi-schema/missing-type.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/missing-type.exit
+++ b/tests/qapi-schema/missing-type.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/missing-type.json b/tests/qapi-schema/missing-type.json
index 1696f5c..ff5349d 100644
--- a/tests/qapi-schema/missing-type.json
+++ b/tests/qapi-schema/missing-type.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject an expression with missing metatype
+# we reject an expression with missing metatype
 { 'data': { } }
diff --git a/tests/qapi-schema/missing-type.out b/tests/qapi-schema/missing-type.out
index 67fd4fa..e69de29 100644
--- a/tests/qapi-schema/missing-type.out
+++ b/tests/qapi-schema/missing-type.out
@@ -1,3 +0,0 @@
-[OrderedDict([('data', OrderedDict())])]
-[]
-[]
diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err
index e69de29..0a35bfd 100644
--- a/tests/qapi-schema/unknown-expr-key.err
+++ b/tests/qapi-schema/unknown-expr-key.err
@@ -0,0 +1 @@
+tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in type 'bar'
diff --git a/tests/qapi-schema/unknown-expr-key.exit b/tests/qapi-schema/unknown-expr-key.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/unknown-expr-key.exit
+++ b/tests/qapi-schema/unknown-expr-key.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json
index 1e9282d..ba7bdf3 100644
--- a/tests/qapi-schema/unknown-expr-key.json
+++ b/tests/qapi-schema/unknown-expr-key.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject an expression with unknown top-level keys
+# we reject an expression with unknown top-level keys
 { 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }
diff --git a/tests/qapi-schema/unknown-expr-key.out b/tests/qapi-schema/unknown-expr-key.out
index c93f020..e69de29 100644
--- a/tests/qapi-schema/unknown-expr-key.out
+++ b/tests/qapi-schema/unknown-expr-key.out
@@ -1,3 +0,0 @@
-[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])]
-[]
-[OrderedDict([('type', 'bar'), ('data', OrderedDict([('string', 'str')])), ('bogus', OrderedDict())])]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 19/36] qapi: Add tests of redefined expressions
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (17 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 18/36] qapi: Better error messages for bad expressions Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 20/36] qapi: Better error messages for duplicated expressions Eric Blake
                   ` (20 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Demonstrate that the qapi generator doesn't deal very well with
redefined expressions.  At the parse level, they are silently
accepted; and while the testsuite just stops at parsing, I've
further tested that many of them cause generator crashes or
invalid C code if they were appended to qapi-schema-test.json.
A later patch will tighten things up and adjust the testsuite
to match.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: no change
---
 tests/Makefile                           | 2 ++
 tests/qapi-schema/command-int.err        | 0
 tests/qapi-schema/command-int.exit       | 1 +
 tests/qapi-schema/command-int.json       | 3 +++
 tests/qapi-schema/command-int.out        | 3 +++
 tests/qapi-schema/event-max.err          | 0
 tests/qapi-schema/event-max.exit         | 1 +
 tests/qapi-schema/event-max.json         | 2 ++
 tests/qapi-schema/event-max.out          | 3 +++
 tests/qapi-schema/redefined-builtin.err  | 0
 tests/qapi-schema/redefined-builtin.exit | 1 +
 tests/qapi-schema/redefined-builtin.json | 2 ++
 tests/qapi-schema/redefined-builtin.out  | 3 +++
 tests/qapi-schema/redefined-command.err  | 0
 tests/qapi-schema/redefined-command.exit | 1 +
 tests/qapi-schema/redefined-command.json | 3 +++
 tests/qapi-schema/redefined-command.out  | 4 ++++
 tests/qapi-schema/redefined-event.err    | 0
 tests/qapi-schema/redefined-event.exit   | 1 +
 tests/qapi-schema/redefined-event.json   | 3 +++
 tests/qapi-schema/redefined-event.out    | 4 ++++
 tests/qapi-schema/redefined-type.err     | 0
 tests/qapi-schema/redefined-type.exit    | 1 +
 tests/qapi-schema/redefined-type.json    | 3 +++
 tests/qapi-schema/redefined-type.out     | 4 ++++
 25 files changed, 45 insertions(+)
 create mode 100644 tests/qapi-schema/command-int.err
 create mode 100644 tests/qapi-schema/command-int.exit
 create mode 100644 tests/qapi-schema/command-int.json
 create mode 100644 tests/qapi-schema/command-int.out
 create mode 100644 tests/qapi-schema/event-max.err
 create mode 100644 tests/qapi-schema/event-max.exit
 create mode 100644 tests/qapi-schema/event-max.json
 create mode 100644 tests/qapi-schema/event-max.out
 create mode 100644 tests/qapi-schema/redefined-builtin.err
 create mode 100644 tests/qapi-schema/redefined-builtin.exit
 create mode 100644 tests/qapi-schema/redefined-builtin.json
 create mode 100644 tests/qapi-schema/redefined-builtin.out
 create mode 100644 tests/qapi-schema/redefined-command.err
 create mode 100644 tests/qapi-schema/redefined-command.exit
 create mode 100644 tests/qapi-schema/redefined-command.json
 create mode 100644 tests/qapi-schema/redefined-command.out
 create mode 100644 tests/qapi-schema/redefined-event.err
 create mode 100644 tests/qapi-schema/redefined-event.exit
 create mode 100644 tests/qapi-schema/redefined-event.json
 create mode 100644 tests/qapi-schema/redefined-event.out
 create mode 100644 tests/qapi-schema/redefined-type.err
 create mode 100644 tests/qapi-schema/redefined-type.exit
 create mode 100644 tests/qapi-schema/redefined-type.json
 create mode 100644 tests/qapi-schema/redefined-type.out

diff --git a/tests/Makefile b/tests/Makefile
index bc5e0a7..0dd74ec 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -214,6 +214,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	missing-type.json bad-ident.json ident-with-escape.json \
 	double-type.json bad-base.json bad-type-bool.json bad-type-int.json \
 	bad-type-dict.json double-data.json unknown-expr-key.json \
+	redefined-type.json redefined-command.json redefined-builtin.json \
+	redefined-event.json command-int.json event-max.json \
 	missing-colon.json missing-comma-list.json \
 	missing-comma-object.json non-objects.json \
 	qapi-schema-test.json quoted-structural-chars.json \
diff --git a/tests/qapi-schema/command-int.err b/tests/qapi-schema/command-int.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/command-int.exit b/tests/qapi-schema/command-int.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/command-int.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/command-int.json b/tests/qapi-schema/command-int.json
new file mode 100644
index 0000000..fcbb643
--- /dev/null
+++ b/tests/qapi-schema/command-int.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject collisions between commands and types
+{ 'command': 'int', 'data': { 'character': 'str' },
+  'returns': { 'value': 'int' } }
diff --git a/tests/qapi-schema/command-int.out b/tests/qapi-schema/command-int.out
new file mode 100644
index 0000000..d8e1854
--- /dev/null
+++ b/tests/qapi-schema/command-int.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'int'), ('data', OrderedDict([('character', 'str')])), ('returns', OrderedDict([('value', 'int')]))])]
+[]
+[]
diff --git a/tests/qapi-schema/event-max.err b/tests/qapi-schema/event-max.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/event-max.exit b/tests/qapi-schema/event-max.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/event-max.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/event-max.json b/tests/qapi-schema/event-max.json
new file mode 100644
index 0000000..997c61c
--- /dev/null
+++ b/tests/qapi-schema/event-max.json
@@ -0,0 +1,2 @@
+# FIXME: an event named 'MAX' would conflict with implicit C enum
+{ 'event': 'MAX' }
diff --git a/tests/qapi-schema/event-max.out b/tests/qapi-schema/event-max.out
new file mode 100644
index 0000000..010c42b
--- /dev/null
+++ b/tests/qapi-schema/event-max.out
@@ -0,0 +1,3 @@
+[OrderedDict([('event', 'MAX')])]
+[]
+[]
diff --git a/tests/qapi-schema/redefined-builtin.err b/tests/qapi-schema/redefined-builtin.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/redefined-builtin.exit b/tests/qapi-schema/redefined-builtin.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/redefined-builtin.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json
new file mode 100644
index 0000000..a10050d
--- /dev/null
+++ b/tests/qapi-schema/redefined-builtin.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject types that duplicate builtin names
+{ 'type': 'size', 'data': { 'myint': 'size' } }
diff --git a/tests/qapi-schema/redefined-builtin.out b/tests/qapi-schema/redefined-builtin.out
new file mode 100644
index 0000000..b0a9aea
--- /dev/null
+++ b/tests/qapi-schema/redefined-builtin.out
@@ -0,0 +1,3 @@
+[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])]
+[]
+[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])]
diff --git a/tests/qapi-schema/redefined-command.err b/tests/qapi-schema/redefined-command.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/redefined-command.exit b/tests/qapi-schema/redefined-command.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/redefined-command.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/redefined-command.json b/tests/qapi-schema/redefined-command.json
new file mode 100644
index 0000000..d8c9975
--- /dev/null
+++ b/tests/qapi-schema/redefined-command.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject commands defined more than once
+{ 'command': 'foo', 'data': { 'one': 'str' } }
+{ 'command': 'foo', 'data': { '*two': 'str' } }
diff --git a/tests/qapi-schema/redefined-command.out b/tests/qapi-schema/redefined-command.out
new file mode 100644
index 0000000..44ddba6
--- /dev/null
+++ b/tests/qapi-schema/redefined-command.out
@@ -0,0 +1,4 @@
+[OrderedDict([('command', 'foo'), ('data', OrderedDict([('one', 'str')]))]),
+ OrderedDict([('command', 'foo'), ('data', OrderedDict([('*two', 'str')]))])]
+[]
+[]
diff --git a/tests/qapi-schema/redefined-event.err b/tests/qapi-schema/redefined-event.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/redefined-event.exit b/tests/qapi-schema/redefined-event.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/redefined-event.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/redefined-event.json b/tests/qapi-schema/redefined-event.json
new file mode 100644
index 0000000..152cce7
--- /dev/null
+++ b/tests/qapi-schema/redefined-event.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject duplicate events
+{ 'event': 'EVENT_A', 'data': { 'myint': 'int' } }
+{ 'event': 'EVENT_A', 'data': { 'myint': 'int' } }
diff --git a/tests/qapi-schema/redefined-event.out b/tests/qapi-schema/redefined-event.out
new file mode 100644
index 0000000..261b183
--- /dev/null
+++ b/tests/qapi-schema/redefined-event.out
@@ -0,0 +1,4 @@
+[OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))]),
+ OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))])]
+[]
+[]
diff --git a/tests/qapi-schema/redefined-type.err b/tests/qapi-schema/redefined-type.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/redefined-type.exit b/tests/qapi-schema/redefined-type.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/redefined-type.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json
new file mode 100644
index 0000000..7972194
--- /dev/null
+++ b/tests/qapi-schema/redefined-type.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject types defined more than once
+{ 'type': 'foo', 'data': { 'one': 'str' } }
+{ 'enum': 'foo', 'data': [ 'two' ] }
diff --git a/tests/qapi-schema/redefined-type.out b/tests/qapi-schema/redefined-type.out
new file mode 100644
index 0000000..b509e57
--- /dev/null
+++ b/tests/qapi-schema/redefined-type.out
@@ -0,0 +1,4 @@
+[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))]),
+ OrderedDict([('enum', 'foo'), ('data', ['two'])])]
+[{'enum_name': 'foo', 'enum_values': ['two']}]
+[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))])]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 20/36] qapi: Better error messages for duplicated expressions
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (18 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 19/36] qapi: Add tests of redefined expressions Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 21/36] qapi: Allow true, false and null in schema json Eric Blake
                   ` (19 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

The previous commit demonstrated that the generator overlooked
duplicate expressions:
- a complex type or command reusing a built-in type name
- redeclaration of a type name, whether by the same or different
metatype
- redeclaration of a command or event
- collision of a type with implicit 'Kind' enum for a union
- collision with an implicit MAX enum constant

Since the c_type() function in the generator treats all names
as being in the same namespace, this patch adds a global array
to track all known names and their source, to prevent collisions
before it can cause further problems.  While valid .json files
won't trigger any of these cases, we might as well be nicer to
developers that make a typo while trying to add new QAPI code.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: Don't enforce ALL_CAPS event names now: if we enforce naming
conventions, it should be for all elements in a single patch;
float an unrelated hunk to earlier in the series
---
 scripts/qapi.py                          | 63 +++++++++++++++++++++++++-------
 tests/qapi-schema/command-int.err        |  1 +
 tests/qapi-schema/command-int.exit       |  2 +-
 tests/qapi-schema/command-int.json       |  2 +-
 tests/qapi-schema/command-int.out        |  3 --
 tests/qapi-schema/enum-union-clash.err   |  1 +
 tests/qapi-schema/enum-union-clash.exit  |  2 +-
 tests/qapi-schema/enum-union-clash.json  |  2 +-
 tests/qapi-schema/enum-union-clash.out   |  5 ---
 tests/qapi-schema/event-max.err          |  1 +
 tests/qapi-schema/event-max.exit         |  2 +-
 tests/qapi-schema/event-max.json         |  2 +-
 tests/qapi-schema/event-max.out          |  3 --
 tests/qapi-schema/redefined-builtin.err  |  1 +
 tests/qapi-schema/redefined-builtin.exit |  2 +-
 tests/qapi-schema/redefined-builtin.json |  2 +-
 tests/qapi-schema/redefined-builtin.out  |  3 --
 tests/qapi-schema/redefined-command.err  |  1 +
 tests/qapi-schema/redefined-command.exit |  2 +-
 tests/qapi-schema/redefined-command.json |  2 +-
 tests/qapi-schema/redefined-command.out  |  4 --
 tests/qapi-schema/redefined-event.err    |  1 +
 tests/qapi-schema/redefined-event.exit   |  2 +-
 tests/qapi-schema/redefined-event.json   |  2 +-
 tests/qapi-schema/redefined-event.out    |  4 --
 tests/qapi-schema/redefined-type.err     |  1 +
 tests/qapi-schema/redefined-type.exit    |  2 +-
 tests/qapi-schema/redefined-type.json    |  2 +-
 tests/qapi-schema/redefined-type.out     |  4 --
 29 files changed, 70 insertions(+), 54 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 868f08b..eea0976 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -32,6 +32,12 @@ builtin_types = {
     'size':     'QTYPE_QINT',
 }

+enum_types = []
+struct_types = []
+union_types = []
+events = []
+all_names = {}
+
 def error_path(parent):
     res = ""
     while parent:
@@ -256,7 +262,14 @@ def discriminator_find_enum_define(expr):
     return find_enum(discriminator_type)

 def check_event(expr, expr_info):
+    global events
+    name = expr['event']
     params = expr.get('data')
+
+    if name.upper() == 'MAX':
+        raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created")
+    events.append(name)
+
     if params:
         for argname, argentry, optional, structured in parse_args(params):
             if structured:
@@ -430,6 +443,9 @@ def check_keys(expr_elem, meta, required, optional=[]):


 def parse_schema(input_file):
+    global all_names
+    exprs = []
+
     # First pass: read entire file into memory
     try:
         schema = QAPISchema(open(input_file, "r"))
@@ -437,30 +453,34 @@ def parse_schema(input_file):
         print >>sys.stderr, e
         exit(1)

-    exprs = []
-
     try:
         # Next pass: learn the types and check for valid expression keys. At
         # this point, top-level 'include' has already been flattened.
+        for builtin in builtin_types.keys():
+            all_names[builtin] = 'built-in'
         for expr_elem in schema.exprs:
             expr = expr_elem['expr']
+            info = expr_elem['info']
             if expr.has_key('enum'):
                 check_keys(expr_elem, 'enum', ['data'])
-                add_enum(expr['enum'], expr['data'])
+                add_enum(expr['enum'], info, expr['data'])
             elif expr.has_key('union'):
                 check_keys(expr_elem, 'union', ['data'],
                            ['base', 'discriminator'])
-                add_union(expr)
+                add_union(expr, info)
             elif expr.has_key('alternate'):
                 check_keys(expr_elem, 'alternate', ['data'])
+                add_name(expr['alternate'], info, 'alternate')
             elif expr.has_key('type'):
                 check_keys(expr_elem, 'type', ['data'], ['base'])
-                add_struct(expr)
+                add_struct(expr, info)
             elif expr.has_key('command'):
                 check_keys(expr_elem, 'command', [],
                            ['data', 'returns', 'gen', 'success-response'])
+                add_name(expr['command'], info, 'command')
             elif expr.has_key('event'):
                 check_keys(expr_elem, 'event', [], ['data'])
+                add_name(expr['event'], info, 'event')
             else:
                 raise QAPIExprError(expr_elem['info'],
                                     "Expression is missing metatype")
@@ -471,9 +491,11 @@ def parse_schema(input_file):
             expr = expr_elem['expr']
             if expr.has_key('union'):
                 if not discriminator_find_enum_define(expr):
-                    add_enum('%sKind' % expr['union'])
+                    add_enum('%sKind' % expr['union'], expr_elem['info'],
+                             implicit=True)
             elif expr.has_key('alternate'):
-                add_enum('%sKind' % expr['alternate'])
+                add_enum('%sKind' % expr['alternate'], expr_elem['info'],
+                         implicit=True)

         # Final pass - validate that exprs make sense
         check_exprs(schema)
@@ -567,12 +589,22 @@ def type_name(name):
         return c_list_type(name[0])
     return name

-enum_types = []
-struct_types = []
-union_types = []
+def add_name(name, info, meta, implicit = False):
+    global all_names
+    if name in all_names:
+        raise QAPIExprError(info,
+                            "%s '%s' is already defined"
+                            %(all_names[name], name))
+    if not implicit and name[-4:] == 'Kind':
+        raise QAPIExprError(info,
+                            "%s '%s' should not end in 'Kind'"
+                            %(meta, name))
+    all_names[name] = meta

-def add_struct(definition):
+def add_struct(definition, info):
     global struct_types
+    name = definition['type']
+    add_name(name, info, 'struct')
     struct_types.append(definition)

 def find_struct(name):
@@ -582,8 +614,10 @@ def find_struct(name):
             return struct
     return None

-def add_union(definition):
+def add_union(definition, info):
     global union_types
+    name = definition['union']
+    add_name(name, info, 'union')
     union_types.append(definition)

 def find_union(name):
@@ -593,8 +627,9 @@ def find_union(name):
             return union
     return None

-def add_enum(name, enum_values = None):
+def add_enum(name, info, enum_values = None, implicit = False):
     global enum_types
+    add_name(name, info, 'enum', implicit)
     enum_types.append({"enum_name": name, "enum_values": enum_values})

 def find_enum(name):
@@ -636,7 +671,7 @@ def c_type(name, is_param=False):
         return name
     elif name == None or len(name) == 0:
         return 'void'
-    elif name == name.upper():
+    elif name in events:
         return '%sEvent *%s' % (camel_case(name), eatspace)
     else:
         return '%s *%s' % (name, eatspace)
diff --git a/tests/qapi-schema/command-int.err b/tests/qapi-schema/command-int.err
index e69de29..0f93006 100644
--- a/tests/qapi-schema/command-int.err
+++ b/tests/qapi-schema/command-int.err
@@ -0,0 +1 @@
+tests/qapi-schema/command-int.json:2: built-in 'int' is already defined
diff --git a/tests/qapi-schema/command-int.exit b/tests/qapi-schema/command-int.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/command-int.exit
+++ b/tests/qapi-schema/command-int.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/command-int.json b/tests/qapi-schema/command-int.json
index fcbb643..c90d408 100644
--- a/tests/qapi-schema/command-int.json
+++ b/tests/qapi-schema/command-int.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject collisions between commands and types
+# we reject collisions between commands and types
 { 'command': 'int', 'data': { 'character': 'str' },
   'returns': { 'value': 'int' } }
diff --git a/tests/qapi-schema/command-int.out b/tests/qapi-schema/command-int.out
index d8e1854..e69de29 100644
--- a/tests/qapi-schema/command-int.out
+++ b/tests/qapi-schema/command-int.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'int'), ('data', OrderedDict([('character', 'str')])), ('returns', OrderedDict([('value', 'int')]))])]
-[]
-[]
diff --git a/tests/qapi-schema/enum-union-clash.err b/tests/qapi-schema/enum-union-clash.err
index e69de29..c04e1a8 100644
--- a/tests/qapi-schema/enum-union-clash.err
+++ b/tests/qapi-schema/enum-union-clash.err
@@ -0,0 +1 @@
+tests/qapi-schema/enum-union-clash.json:2: enum 'UnionKind' should not end in 'Kind'
diff --git a/tests/qapi-schema/enum-union-clash.exit b/tests/qapi-schema/enum-union-clash.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/enum-union-clash.exit
+++ b/tests/qapi-schema/enum-union-clash.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/enum-union-clash.json b/tests/qapi-schema/enum-union-clash.json
index 714ff6d..593282b 100644
--- a/tests/qapi-schema/enum-union-clash.json
+++ b/tests/qapi-schema/enum-union-clash.json
@@ -1,4 +1,4 @@
-# FIXME: we should reject types that would conflict with implicit union enum
+# we reject types that would conflict with implicit union enum
 { 'enum': 'UnionKind', 'data': [ 'oops' ] }
 { 'union': 'Union',
   'data': { 'a': 'int' } }
diff --git a/tests/qapi-schema/enum-union-clash.out b/tests/qapi-schema/enum-union-clash.out
index d45f5e8..e69de29 100644
--- a/tests/qapi-schema/enum-union-clash.out
+++ b/tests/qapi-schema/enum-union-clash.out
@@ -1,5 +0,0 @@
-[OrderedDict([('enum', 'UnionKind'), ('data', ['oops'])]),
- OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int')]))])]
-[{'enum_name': 'UnionKind', 'enum_values': ['oops']},
- {'enum_name': 'UnionKind', 'enum_values': None}]
-[]
diff --git a/tests/qapi-schema/event-max.err b/tests/qapi-schema/event-max.err
index e69de29..c856534 100644
--- a/tests/qapi-schema/event-max.err
+++ b/tests/qapi-schema/event-max.err
@@ -0,0 +1 @@
+tests/qapi-schema/event-max.json:2: Event name 'MAX' cannot be created
diff --git a/tests/qapi-schema/event-max.exit b/tests/qapi-schema/event-max.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/event-max.exit
+++ b/tests/qapi-schema/event-max.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/event-max.json b/tests/qapi-schema/event-max.json
index 997c61c..f3d7de2 100644
--- a/tests/qapi-schema/event-max.json
+++ b/tests/qapi-schema/event-max.json
@@ -1,2 +1,2 @@
-# FIXME: an event named 'MAX' would conflict with implicit C enum
+# an event named 'MAX' would conflict with implicit C enum
 { 'event': 'MAX' }
diff --git a/tests/qapi-schema/event-max.out b/tests/qapi-schema/event-max.out
index 010c42b..e69de29 100644
--- a/tests/qapi-schema/event-max.out
+++ b/tests/qapi-schema/event-max.out
@@ -1,3 +0,0 @@
-[OrderedDict([('event', 'MAX')])]
-[]
-[]
diff --git a/tests/qapi-schema/redefined-builtin.err b/tests/qapi-schema/redefined-builtin.err
index e69de29..b275722 100644
--- a/tests/qapi-schema/redefined-builtin.err
+++ b/tests/qapi-schema/redefined-builtin.err
@@ -0,0 +1 @@
+tests/qapi-schema/redefined-builtin.json:2: built-in 'size' is already defined
diff --git a/tests/qapi-schema/redefined-builtin.exit b/tests/qapi-schema/redefined-builtin.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/redefined-builtin.exit
+++ b/tests/qapi-schema/redefined-builtin.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json
index a10050d..df328cc 100644
--- a/tests/qapi-schema/redefined-builtin.json
+++ b/tests/qapi-schema/redefined-builtin.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject types that duplicate builtin names
+# we reject types that duplicate builtin names
 { 'type': 'size', 'data': { 'myint': 'size' } }
diff --git a/tests/qapi-schema/redefined-builtin.out b/tests/qapi-schema/redefined-builtin.out
index b0a9aea..e69de29 100644
--- a/tests/qapi-schema/redefined-builtin.out
+++ b/tests/qapi-schema/redefined-builtin.out
@@ -1,3 +0,0 @@
-[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])]
-[]
-[OrderedDict([('type', 'size'), ('data', OrderedDict([('myint', 'size')]))])]
diff --git a/tests/qapi-schema/redefined-command.err b/tests/qapi-schema/redefined-command.err
index e69de29..82ae256 100644
--- a/tests/qapi-schema/redefined-command.err
+++ b/tests/qapi-schema/redefined-command.err
@@ -0,0 +1 @@
+tests/qapi-schema/redefined-command.json:3: command 'foo' is already defined
diff --git a/tests/qapi-schema/redefined-command.exit b/tests/qapi-schema/redefined-command.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/redefined-command.exit
+++ b/tests/qapi-schema/redefined-command.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/redefined-command.json b/tests/qapi-schema/redefined-command.json
index d8c9975..247e401 100644
--- a/tests/qapi-schema/redefined-command.json
+++ b/tests/qapi-schema/redefined-command.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject commands defined more than once
+# we reject commands defined more than once
 { 'command': 'foo', 'data': { 'one': 'str' } }
 { 'command': 'foo', 'data': { '*two': 'str' } }
diff --git a/tests/qapi-schema/redefined-command.out b/tests/qapi-schema/redefined-command.out
index 44ddba6..e69de29 100644
--- a/tests/qapi-schema/redefined-command.out
+++ b/tests/qapi-schema/redefined-command.out
@@ -1,4 +0,0 @@
-[OrderedDict([('command', 'foo'), ('data', OrderedDict([('one', 'str')]))]),
- OrderedDict([('command', 'foo'), ('data', OrderedDict([('*two', 'str')]))])]
-[]
-[]
diff --git a/tests/qapi-schema/redefined-event.err b/tests/qapi-schema/redefined-event.err
index e69de29..35429cb 100644
--- a/tests/qapi-schema/redefined-event.err
+++ b/tests/qapi-schema/redefined-event.err
@@ -0,0 +1 @@
+tests/qapi-schema/redefined-event.json:3: event 'EVENT_A' is already defined
diff --git a/tests/qapi-schema/redefined-event.exit b/tests/qapi-schema/redefined-event.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/redefined-event.exit
+++ b/tests/qapi-schema/redefined-event.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/redefined-event.json b/tests/qapi-schema/redefined-event.json
index 152cce7..7717e91 100644
--- a/tests/qapi-schema/redefined-event.json
+++ b/tests/qapi-schema/redefined-event.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject duplicate events
+# we reject duplicate events
 { 'event': 'EVENT_A', 'data': { 'myint': 'int' } }
 { 'event': 'EVENT_A', 'data': { 'myint': 'int' } }
diff --git a/tests/qapi-schema/redefined-event.out b/tests/qapi-schema/redefined-event.out
index 261b183..e69de29 100644
--- a/tests/qapi-schema/redefined-event.out
+++ b/tests/qapi-schema/redefined-event.out
@@ -1,4 +0,0 @@
-[OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))]),
- OrderedDict([('event', 'EVENT_A'), ('data', OrderedDict([('myint', 'int')]))])]
-[]
-[]
diff --git a/tests/qapi-schema/redefined-type.err b/tests/qapi-schema/redefined-type.err
index e69de29..06ea78c 100644
--- a/tests/qapi-schema/redefined-type.err
+++ b/tests/qapi-schema/redefined-type.err
@@ -0,0 +1 @@
+tests/qapi-schema/redefined-type.json:3: struct 'foo' is already defined
diff --git a/tests/qapi-schema/redefined-type.exit b/tests/qapi-schema/redefined-type.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/redefined-type.exit
+++ b/tests/qapi-schema/redefined-type.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json
index 7972194..e6a5f24 100644
--- a/tests/qapi-schema/redefined-type.json
+++ b/tests/qapi-schema/redefined-type.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject types defined more than once
+# we reject types defined more than once
 { 'type': 'foo', 'data': { 'one': 'str' } }
 { 'enum': 'foo', 'data': [ 'two' ] }
diff --git a/tests/qapi-schema/redefined-type.out b/tests/qapi-schema/redefined-type.out
index b509e57..e69de29 100644
--- a/tests/qapi-schema/redefined-type.out
+++ b/tests/qapi-schema/redefined-type.out
@@ -1,4 +0,0 @@
-[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))]),
- OrderedDict([('enum', 'foo'), ('data', ['two'])])]
-[{'enum_name': 'foo', 'enum_values': ['two']}]
-[OrderedDict([('type', 'foo'), ('data', OrderedDict([('one', 'str')]))])]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 21/36] qapi: Allow true, false and null in schema json
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (19 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 20/36] qapi: Better error messages for duplicated expressions Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 22/36] qapi: Unify type bypass and add tests Eric Blake
                   ` (18 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, Fam Zheng, berto, armbru

From: Fam Zheng <famz@redhat.com>

In the near term, we will use it for a sensible-looking
'gen':false inside command declarations, instead of the
current ugly 'gen':'no'.

In the long term, it will allow conversion from shorthand
with defaults mentioned only in side-band documentation:
 'data':{'*flag':'bool', '*string':'str'}
into an explicit default value documentation, as in:
 'data':{'flag':{'type':'bool', 'optional':true, 'default':true},
         'string':{'type':'str', 'optional':true, 'default':null}}

We still don't parse integer values (also necessary before
we can allow explicit defaults), but that can come in a later
series.

Update the testsuite to match an improved error message.

Signed-off-by: Fam Zheng <famz@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: no change (although v5 sparked a lot of conversation, it
didn't really affect this patch)
---
 scripts/qapi.py                      | 21 ++++++++++++++++++---
 tests/qapi-schema/bad-type-bool.err  |  2 +-
 tests/qapi-schema/bad-type-bool.json |  1 -
 3 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index eea0976..686bc86 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -158,6 +158,20 @@ class QAPISchema:
                         return
                     else:
                         string += ch
+            elif self.tok in "tfn":
+                val = self.src[self.cursor - 1:]
+                if val.startswith("true"):
+                    self.val = True
+                    self.cursor += 3
+                    return
+                elif val.startswith("false"):
+                    self.val = False
+                    self.cursor += 4
+                    return
+                elif val.startswith("null"):
+                    self.val = None
+                    self.cursor += 3
+                    return
             elif self.tok == '\n':
                 if self.cursor == len(self.src):
                     self.tok = None
@@ -197,8 +211,9 @@ class QAPISchema:
         if self.tok == ']':
             self.accept()
             return expr
-        if not self.tok in [ '{', '[', "'" ]:
-            raise QAPISchemaError(self, 'Expected "{", "[", "]" or string')
+        if not self.tok in "{['tfn":
+            raise QAPISchemaError(self, 'Expected "{", "[", "]", string, '
+                                  'boolean or "null"')
         while True:
             expr.append(self.get_expr(True))
             if self.tok == ']':
@@ -217,7 +232,7 @@ class QAPISchema:
         elif self.tok == '[':
             self.accept()
             expr = self.get_values()
-        elif self.tok == "'":
+        elif self.tok in "'tfn":
             expr = self.val
             self.accept()
         else:
diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err
index badb7c2..de6168c 100644
--- a/tests/qapi-schema/bad-type-bool.err
+++ b/tests/qapi-schema/bad-type-bool.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-bool.json:3:11: Stray "t"
+tests/qapi-schema/bad-type-bool.json:2: 'type' key must have a string value
diff --git a/tests/qapi-schema/bad-type-bool.json b/tests/qapi-schema/bad-type-bool.json
index 22d6369..e1e9fb0 100644
--- a/tests/qapi-schema/bad-type-bool.json
+++ b/tests/qapi-schema/bad-type-bool.json
@@ -1,3 +1,2 @@
 # we reject an expression with a metatype that is not a string
-# FIXME: once the parser understands bool inputs, improve the error message
 { 'type': true, 'data': { } }
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 22/36] qapi: Unify type bypass and add tests
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (20 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 21/36] qapi: Allow true, false and null in schema json Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 23/36] qapi: Add some type check tests Eric Blake
                   ` (17 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

For a few QMP commands, we are forced to pass an arbitrary type
without tracking it properly in QAPI.  Among the existing clients,
this unnamed type was spelled 'dict', 'visitor', and '**'; this
patch standardizes on '**', matching the documentation changes
earlier in the series.

Meanwhile, for both 'gen' and 'success-response' keys, we have been
ignoring the value, although the schema consistently used "'no'".
But now that we can support a literal "false" in the schema, we
might as well use that rather than ignoring the value or
special-casing a random string.  Note that these are one-way
switches (use of 'gen':true is not the same as omitting 'gen').
Also, the use of '**' requires 'gen':false, but the use of
'gen':false does not mandate the use of '**'.

There is no difference to the generated code.  Add some tests on
what we'd like to guarantee, although it will take later patches
to clean up test results and actually enforce the use of a bool
parameter.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: touch up commit message
---
 qapi-schema.json                           | 14 +++++++-------
 qga/qapi-schema.json                       |  8 ++++----
 tests/Makefile                             |  1 +
 tests/qapi-schema/type-bypass-bad-gen.err  |  0
 tests/qapi-schema/type-bypass-bad-gen.exit |  1 +
 tests/qapi-schema/type-bypass-bad-gen.json |  2 ++
 tests/qapi-schema/type-bypass-bad-gen.out  |  3 +++
 tests/qapi-schema/type-bypass-no-gen.err   |  0
 tests/qapi-schema/type-bypass-no-gen.exit  |  1 +
 tests/qapi-schema/type-bypass-no-gen.json  |  2 ++
 tests/qapi-schema/type-bypass-no-gen.out   |  3 +++
 tests/qapi-schema/type-bypass.err          |  0
 tests/qapi-schema/type-bypass.exit         |  1 +
 tests/qapi-schema/type-bypass.json         |  2 ++
 tests/qapi-schema/type-bypass.out          |  3 +++
 15 files changed, 30 insertions(+), 11 deletions(-)
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.err
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.exit
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.json
 create mode 100644 tests/qapi-schema/type-bypass-bad-gen.out
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.err
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.exit
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.json
 create mode 100644 tests/qapi-schema/type-bypass-no-gen.out
 create mode 100644 tests/qapi-schema/type-bypass.err
 create mode 100644 tests/qapi-schema/type-bypass.exit
 create mode 100644 tests/qapi-schema/type-bypass.json
 create mode 100644 tests/qapi-schema/type-bypass.out

diff --git a/qapi-schema.json b/qapi-schema.json
index ac9594d..7f4cf86 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1561,8 +1561,8 @@
 ##
 { 'command': 'qom-get',
   'data': { 'path': 'str', 'property': 'str' },
-  'returns': 'visitor',
-  'gen': 'no' }
+  'returns': '**',
+  'gen': false }

 ##
 # @qom-set:
@@ -1579,8 +1579,8 @@
 # Since: 1.2
 ##
 { 'command': 'qom-set',
-  'data': { 'path': 'str', 'property': 'str', 'value': 'visitor' },
-  'gen': 'no' }
+  'data': { 'path': 'str', 'property': 'str', 'value': '**' },
+  'gen': false }

 ##
 # @set_password:
@@ -1943,7 +1943,7 @@
 ##
 { 'command': 'netdev_add',
   'data': {'type': 'str', 'id': 'str', '*props': '**'},
-  'gen': 'no' }
+  'gen': false }

 ##
 # @netdev_del:
@@ -1976,8 +1976,8 @@
 # Since: 2.0
 ##
 { 'command': 'object-add',
-  'data': {'qom-type': 'str', 'id': 'str', '*props': 'dict'},
-  'gen': 'no' }
+  'data': {'qom-type': 'str', 'id': 'str', '*props': '**'},
+  'gen': false }

 ##
 # @object-del:
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index 95f49e3..fe5be7e 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -195,7 +195,7 @@
 # Since: 0.15.0
 ##
 { 'command': 'guest-shutdown', 'data': { '*mode': 'str' },
-  'success-response': 'no' }
+  'success-response': false }

 ##
 # @guest-file-open:
@@ -470,7 +470,7 @@
 #
 # Since: 1.1
 ##
-{ 'command': 'guest-suspend-disk', 'success-response': 'no' }
+{ 'command': 'guest-suspend-disk', 'success-response': false }

 ##
 # @guest-suspend-ram
@@ -502,7 +502,7 @@
 #
 # Since: 1.1
 ##
-{ 'command': 'guest-suspend-ram', 'success-response': 'no' }
+{ 'command': 'guest-suspend-ram', 'success-response': false }

 ##
 # @guest-suspend-hybrid
@@ -529,7 +529,7 @@
 #
 # Since: 1.1
 ##
-{ 'command': 'guest-suspend-hybrid', 'success-response': 'no' }
+{ 'command': 'guest-suspend-hybrid', 'success-response': false }

 ##
 # @GuestIpAddressType:
diff --git a/tests/Makefile b/tests/Makefile
index 0dd74ec..2d3de31 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -216,6 +216,7 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	bad-type-dict.json double-data.json unknown-expr-key.json \
 	redefined-type.json redefined-command.json redefined-builtin.json \
 	redefined-event.json command-int.json event-max.json \
+	type-bypass.json type-bypass-no-gen.json type-bypass-bad-gen.json \
 	missing-colon.json missing-comma-list.json \
 	missing-comma-object.json non-objects.json \
 	qapi-schema-test.json quoted-structural-chars.json \
diff --git a/tests/qapi-schema/type-bypass-bad-gen.err b/tests/qapi-schema/type-bypass-bad-gen.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/type-bypass-bad-gen.exit b/tests/qapi-schema/type-bypass-bad-gen.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/type-bypass-bad-gen.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/type-bypass-bad-gen.json b/tests/qapi-schema/type-bypass-bad-gen.json
new file mode 100644
index 0000000..bb70bee
--- /dev/null
+++ b/tests/qapi-schema/type-bypass-bad-gen.json
@@ -0,0 +1,2 @@
+# FIXME: 'gen' should only appear with value false
+{ 'command': 'foo', 'gen': 'whatever' }
diff --git a/tests/qapi-schema/type-bypass-bad-gen.out b/tests/qapi-schema/type-bypass-bad-gen.out
new file mode 100644
index 0000000..e678f2c
--- /dev/null
+++ b/tests/qapi-schema/type-bypass-bad-gen.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'foo'), ('gen', 'whatever')])]
+[]
+[]
diff --git a/tests/qapi-schema/type-bypass-no-gen.err b/tests/qapi-schema/type-bypass-no-gen.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/type-bypass-no-gen.exit b/tests/qapi-schema/type-bypass-no-gen.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/type-bypass-no-gen.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/type-bypass-no-gen.json b/tests/qapi-schema/type-bypass-no-gen.json
new file mode 100644
index 0000000..af87c19
--- /dev/null
+++ b/tests/qapi-schema/type-bypass-no-gen.json
@@ -0,0 +1,2 @@
+# FIXME: type bypass should only work with 'gen':false
+{ 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**' }
diff --git a/tests/qapi-schema/type-bypass-no-gen.out b/tests/qapi-schema/type-bypass-no-gen.out
new file mode 100644
index 0000000..8b2a9ac
--- /dev/null
+++ b/tests/qapi-schema/type-bypass-no-gen.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'unsafe'), ('data', OrderedDict([('arg', '**')])), ('returns', '**')])]
+[]
+[]
diff --git a/tests/qapi-schema/type-bypass.err b/tests/qapi-schema/type-bypass.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/type-bypass.exit b/tests/qapi-schema/type-bypass.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/type-bypass.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/type-bypass.json b/tests/qapi-schema/type-bypass.json
new file mode 100644
index 0000000..48b2137
--- /dev/null
+++ b/tests/qapi-schema/type-bypass.json
@@ -0,0 +1,2 @@
+# Use of 'gen':false allows bypassing type system
+{ 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**', 'gen': false }
diff --git a/tests/qapi-schema/type-bypass.out b/tests/qapi-schema/type-bypass.out
new file mode 100644
index 0000000..eaf20f8
--- /dev/null
+++ b/tests/qapi-schema/type-bypass.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'unsafe'), ('data', OrderedDict([('arg', '**')])), ('returns', '**'), ('gen', False)])]
+[]
+[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 23/36] qapi: Add some type check tests
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (21 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 22/36] qapi: Unify type bypass and add tests Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 24/36] qapi: More rigourous checking of types Eric Blake
                   ` (16 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Demonstrate that the qapi generator silently parses confusing
types, which may cause other errors later on. Later patches
will update the expected results as the generator is made stricter.

Most of the new tests focus on blatant errors.  But
returns-whitelist is a case where we have historically allowed
returning something other than a JSON object from particular
commands; we have to keep that behavior to avoid breaking clients,
but it would be nicer to avoid adding such commands in the future,
because any return that is not an (array of) object cannot be
easily extended if future qemu wants to return additional
information.  The QMP protocol already documents that clients
should ignore unknown dictionary keys, but does not require
clients to have to handle more than one type of JSON object.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: new test bad-data (makes up for a test in v4 that was lost in v5),
update commit message to explain returns-whitelist
---
 tests/Makefile                               | 10 +++++++---
 tests/qapi-schema/bad-data.err               |  0
 tests/qapi-schema/bad-data.exit              |  1 +
 tests/qapi-schema/bad-data.json              |  2 ++
 tests/qapi-schema/bad-data.out               |  3 +++
 tests/qapi-schema/data-array-empty.err       |  0
 tests/qapi-schema/data-array-empty.exit      |  1 +
 tests/qapi-schema/data-array-empty.json      |  2 ++
 tests/qapi-schema/data-array-empty.out       |  3 +++
 tests/qapi-schema/data-array-unknown.err     |  0
 tests/qapi-schema/data-array-unknown.exit    |  1 +
 tests/qapi-schema/data-array-unknown.json    |  2 ++
 tests/qapi-schema/data-array-unknown.out     |  3 +++
 tests/qapi-schema/data-int.err               |  0
 tests/qapi-schema/data-int.exit              |  1 +
 tests/qapi-schema/data-int.json              |  2 ++
 tests/qapi-schema/data-int.out               |  3 +++
 tests/qapi-schema/data-member-array-bad.err  |  0
 tests/qapi-schema/data-member-array-bad.exit |  1 +
 tests/qapi-schema/data-member-array-bad.json |  2 ++
 tests/qapi-schema/data-member-array-bad.out  |  3 +++
 tests/qapi-schema/data-member-array.err      |  0
 tests/qapi-schema/data-member-array.exit     |  1 +
 tests/qapi-schema/data-member-array.json     |  4 ++++
 tests/qapi-schema/data-member-array.out      |  5 +++++
 tests/qapi-schema/data-member-unknown.err    |  0
 tests/qapi-schema/data-member-unknown.exit   |  1 +
 tests/qapi-schema/data-member-unknown.json   |  2 ++
 tests/qapi-schema/data-member-unknown.out    |  3 +++
 tests/qapi-schema/data-unknown.err           |  0
 tests/qapi-schema/data-unknown.exit          |  1 +
 tests/qapi-schema/data-unknown.json          |  2 ++
 tests/qapi-schema/data-unknown.out           |  3 +++
 tests/qapi-schema/nested-struct-data.err     |  0
 tests/qapi-schema/nested-struct-data.exit    |  1 +
 tests/qapi-schema/nested-struct-data.json    |  4 ++++
 tests/qapi-schema/nested-struct-data.out     |  3 +++
 tests/qapi-schema/nested-struct-returns.err  |  0
 tests/qapi-schema/nested-struct-returns.exit |  1 +
 tests/qapi-schema/nested-struct-returns.json |  3 +++
 tests/qapi-schema/nested-struct-returns.out  |  3 +++
 tests/qapi-schema/returns-alternate.err      |  0
 tests/qapi-schema/returns-alternate.exit     |  1 +
 tests/qapi-schema/returns-alternate.json     |  3 +++
 tests/qapi-schema/returns-alternate.out      |  4 ++++
 tests/qapi-schema/returns-array-bad.err      |  0
 tests/qapi-schema/returns-array-bad.exit     |  1 +
 tests/qapi-schema/returns-array-bad.json     |  2 ++
 tests/qapi-schema/returns-array-bad.out      |  3 +++
 tests/qapi-schema/returns-int.err            |  0
 tests/qapi-schema/returns-int.exit           |  1 +
 tests/qapi-schema/returns-int.json           |  2 ++
 tests/qapi-schema/returns-int.out            |  3 +++
 tests/qapi-schema/returns-unknown.err        |  0
 tests/qapi-schema/returns-unknown.exit       |  1 +
 tests/qapi-schema/returns-unknown.json       |  2 ++
 tests/qapi-schema/returns-unknown.out        |  3 +++
 tests/qapi-schema/returns-whitelist.err      |  0
 tests/qapi-schema/returns-whitelist.exit     |  1 +
 tests/qapi-schema/returns-whitelist.json     | 11 +++++++++++
 tests/qapi-schema/returns-whitelist.out      |  7 +++++++
 61 files changed, 119 insertions(+), 3 deletions(-)
 create mode 100644 tests/qapi-schema/bad-data.err
 create mode 100644 tests/qapi-schema/bad-data.exit
 create mode 100644 tests/qapi-schema/bad-data.json
 create mode 100644 tests/qapi-schema/bad-data.out
 create mode 100644 tests/qapi-schema/data-array-empty.err
 create mode 100644 tests/qapi-schema/data-array-empty.exit
 create mode 100644 tests/qapi-schema/data-array-empty.json
 create mode 100644 tests/qapi-schema/data-array-empty.out
 create mode 100644 tests/qapi-schema/data-array-unknown.err
 create mode 100644 tests/qapi-schema/data-array-unknown.exit
 create mode 100644 tests/qapi-schema/data-array-unknown.json
 create mode 100644 tests/qapi-schema/data-array-unknown.out
 create mode 100644 tests/qapi-schema/data-int.err
 create mode 100644 tests/qapi-schema/data-int.exit
 create mode 100644 tests/qapi-schema/data-int.json
 create mode 100644 tests/qapi-schema/data-int.out
 create mode 100644 tests/qapi-schema/data-member-array-bad.err
 create mode 100644 tests/qapi-schema/data-member-array-bad.exit
 create mode 100644 tests/qapi-schema/data-member-array-bad.json
 create mode 100644 tests/qapi-schema/data-member-array-bad.out
 create mode 100644 tests/qapi-schema/data-member-array.err
 create mode 100644 tests/qapi-schema/data-member-array.exit
 create mode 100644 tests/qapi-schema/data-member-array.json
 create mode 100644 tests/qapi-schema/data-member-array.out
 create mode 100644 tests/qapi-schema/data-member-unknown.err
 create mode 100644 tests/qapi-schema/data-member-unknown.exit
 create mode 100644 tests/qapi-schema/data-member-unknown.json
 create mode 100644 tests/qapi-schema/data-member-unknown.out
 create mode 100644 tests/qapi-schema/data-unknown.err
 create mode 100644 tests/qapi-schema/data-unknown.exit
 create mode 100644 tests/qapi-schema/data-unknown.json
 create mode 100644 tests/qapi-schema/data-unknown.out
 create mode 100644 tests/qapi-schema/nested-struct-data.err
 create mode 100644 tests/qapi-schema/nested-struct-data.exit
 create mode 100644 tests/qapi-schema/nested-struct-data.json
 create mode 100644 tests/qapi-schema/nested-struct-data.out
 create mode 100644 tests/qapi-schema/nested-struct-returns.err
 create mode 100644 tests/qapi-schema/nested-struct-returns.exit
 create mode 100644 tests/qapi-schema/nested-struct-returns.json
 create mode 100644 tests/qapi-schema/nested-struct-returns.out
 create mode 100644 tests/qapi-schema/returns-alternate.err
 create mode 100644 tests/qapi-schema/returns-alternate.exit
 create mode 100644 tests/qapi-schema/returns-alternate.json
 create mode 100644 tests/qapi-schema/returns-alternate.out
 create mode 100644 tests/qapi-schema/returns-array-bad.err
 create mode 100644 tests/qapi-schema/returns-array-bad.exit
 create mode 100644 tests/qapi-schema/returns-array-bad.json
 create mode 100644 tests/qapi-schema/returns-array-bad.out
 create mode 100644 tests/qapi-schema/returns-int.err
 create mode 100644 tests/qapi-schema/returns-int.exit
 create mode 100644 tests/qapi-schema/returns-int.json
 create mode 100644 tests/qapi-schema/returns-int.out
 create mode 100644 tests/qapi-schema/returns-unknown.err
 create mode 100644 tests/qapi-schema/returns-unknown.exit
 create mode 100644 tests/qapi-schema/returns-unknown.json
 create mode 100644 tests/qapi-schema/returns-unknown.out
 create mode 100644 tests/qapi-schema/returns-whitelist.err
 create mode 100644 tests/qapi-schema/returns-whitelist.exit
 create mode 100644 tests/qapi-schema/returns-whitelist.json
 create mode 100644 tests/qapi-schema/returns-whitelist.out

diff --git a/tests/Makefile b/tests/Makefile
index 2d3de31..f37cd01 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -215,10 +215,14 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	double-type.json bad-base.json bad-type-bool.json bad-type-int.json \
 	bad-type-dict.json double-data.json unknown-expr-key.json \
 	redefined-type.json redefined-command.json redefined-builtin.json \
-	redefined-event.json command-int.json event-max.json \
+	redefined-event.json command-int.json bad-data.json event-max.json \
 	type-bypass.json type-bypass-no-gen.json type-bypass-bad-gen.json \
-	missing-colon.json missing-comma-list.json \
-	missing-comma-object.json non-objects.json \
+	data-array-empty.json data-array-unknown.json data-int.json \
+	data-unknown.json data-member-unknown.json data-member-array.json \
+	data-member-array-bad.json returns-array-bad.json returns-int.json \
+	returns-unknown.json returns-alternate.json returns-whitelist.json \
+	missing-colon.json missing-comma-list.json missing-comma-object.json \
+	nested-struct-data.json nested-struct-returns.json non-objects.json \
 	qapi-schema-test.json quoted-structural-chars.json \
 	trailing-comma-list.json trailing-comma-object.json \
 	unclosed-list.json unclosed-object.json unclosed-string.json \
diff --git a/tests/qapi-schema/bad-data.err b/tests/qapi-schema/bad-data.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/bad-data.exit b/tests/qapi-schema/bad-data.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/bad-data.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/bad-data.json b/tests/qapi-schema/bad-data.json
new file mode 100644
index 0000000..ff1616d
--- /dev/null
+++ b/tests/qapi-schema/bad-data.json
@@ -0,0 +1,2 @@
+# FIXME: we should ensure 'data' is a dictionary for all but enums
+{ 'command': 'oops', 'data': [ ] }
diff --git a/tests/qapi-schema/bad-data.out b/tests/qapi-schema/bad-data.out
new file mode 100644
index 0000000..67802be
--- /dev/null
+++ b/tests/qapi-schema/bad-data.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('data', [])])]
+[]
+[]
diff --git a/tests/qapi-schema/data-array-empty.err b/tests/qapi-schema/data-array-empty.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/data-array-empty.exit b/tests/qapi-schema/data-array-empty.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/data-array-empty.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/data-array-empty.json b/tests/qapi-schema/data-array-empty.json
new file mode 100644
index 0000000..edb543a
--- /dev/null
+++ b/tests/qapi-schema/data-array-empty.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject an array for data if it does not contain a known type
+{ 'command': 'oops', 'data': { 'empty': [ ] } }
diff --git a/tests/qapi-schema/data-array-empty.out b/tests/qapi-schema/data-array-empty.out
new file mode 100644
index 0000000..6f25c9e
--- /dev/null
+++ b/tests/qapi-schema/data-array-empty.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('data', OrderedDict([('empty', [])]))])]
+[]
+[]
diff --git a/tests/qapi-schema/data-array-unknown.err b/tests/qapi-schema/data-array-unknown.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/data-array-unknown.exit b/tests/qapi-schema/data-array-unknown.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/data-array-unknown.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/data-array-unknown.json b/tests/qapi-schema/data-array-unknown.json
new file mode 100644
index 0000000..20cd3c0
--- /dev/null
+++ b/tests/qapi-schema/data-array-unknown.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject an array for data if it does not contain a known type
+{ 'command': 'oops', 'data': { 'array': [ 'NoSuchType' ] } }
diff --git a/tests/qapi-schema/data-array-unknown.out b/tests/qapi-schema/data-array-unknown.out
new file mode 100644
index 0000000..4314ab5
--- /dev/null
+++ b/tests/qapi-schema/data-array-unknown.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('data', OrderedDict([('array', ['NoSuchType'])]))])]
+[]
+[]
diff --git a/tests/qapi-schema/data-int.err b/tests/qapi-schema/data-int.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/data-int.exit b/tests/qapi-schema/data-int.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/data-int.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/data-int.json b/tests/qapi-schema/data-int.json
new file mode 100644
index 0000000..37916e0
--- /dev/null
+++ b/tests/qapi-schema/data-int.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject commands where data is not an array or complex type
+{ 'command': 'oops', 'data': 'int' }
diff --git a/tests/qapi-schema/data-int.out b/tests/qapi-schema/data-int.out
new file mode 100644
index 0000000..e589a4f
--- /dev/null
+++ b/tests/qapi-schema/data-int.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('data', 'int')])]
+[]
+[]
diff --git a/tests/qapi-schema/data-member-array-bad.err b/tests/qapi-schema/data-member-array-bad.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/data-member-array-bad.exit b/tests/qapi-schema/data-member-array-bad.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/data-member-array-bad.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/data-member-array-bad.json b/tests/qapi-schema/data-member-array-bad.json
new file mode 100644
index 0000000..c954af1
--- /dev/null
+++ b/tests/qapi-schema/data-member-array-bad.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject data if it does not contain a valid array type
+{ 'command': 'oops', 'data': { 'member': [ { 'nested': 'str' } ] } }
diff --git a/tests/qapi-schema/data-member-array-bad.out b/tests/qapi-schema/data-member-array-bad.out
new file mode 100644
index 0000000..0e00c41
--- /dev/null
+++ b/tests/qapi-schema/data-member-array-bad.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', [OrderedDict([('nested', 'str')])])]))])]
+[]
+[]
diff --git a/tests/qapi-schema/data-member-array.err b/tests/qapi-schema/data-member-array.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/data-member-array.exit b/tests/qapi-schema/data-member-array.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/data-member-array.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/data-member-array.json b/tests/qapi-schema/data-member-array.json
new file mode 100644
index 0000000..7cce276
--- /dev/null
+++ b/tests/qapi-schema/data-member-array.json
@@ -0,0 +1,4 @@
+# valid array members
+{ 'enum': 'abc', 'data': [ 'a', 'b', 'c' ] }
+{ 'type': 'def', 'data': { 'array': [ 'abc' ] } }
+{ 'command': 'okay', 'data': { 'member1': [ 'int' ], 'member2': [ 'def' ] } }
diff --git a/tests/qapi-schema/data-member-array.out b/tests/qapi-schema/data-member-array.out
new file mode 100644
index 0000000..8287120
--- /dev/null
+++ b/tests/qapi-schema/data-member-array.out
@@ -0,0 +1,5 @@
+[OrderedDict([('enum', 'abc'), ('data', ['a', 'b', 'c'])]),
+ OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))]),
+ OrderedDict([('command', 'okay'), ('data', OrderedDict([('member1', ['int']), ('member2', ['def'])]))])]
+[{'enum_name': 'abc', 'enum_values': ['a', 'b', 'c']}]
+[OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))])]
diff --git a/tests/qapi-schema/data-member-unknown.err b/tests/qapi-schema/data-member-unknown.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/data-member-unknown.exit b/tests/qapi-schema/data-member-unknown.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/data-member-unknown.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/data-member-unknown.json b/tests/qapi-schema/data-member-unknown.json
new file mode 100644
index 0000000..40e6252
--- /dev/null
+++ b/tests/qapi-schema/data-member-unknown.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject data if it does not contain a known type
+{ 'command': 'oops', 'data': { 'member': 'NoSuchType' } }
diff --git a/tests/qapi-schema/data-member-unknown.out b/tests/qapi-schema/data-member-unknown.out
new file mode 100644
index 0000000..36252a5
--- /dev/null
+++ b/tests/qapi-schema/data-member-unknown.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', 'NoSuchType')]))])]
+[]
+[]
diff --git a/tests/qapi-schema/data-unknown.err b/tests/qapi-schema/data-unknown.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/data-unknown.exit b/tests/qapi-schema/data-unknown.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/data-unknown.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/data-unknown.json b/tests/qapi-schema/data-unknown.json
new file mode 100644
index 0000000..776bd34
--- /dev/null
+++ b/tests/qapi-schema/data-unknown.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject data if it does not contain a known type
+{ 'command': 'oops', 'data': 'NoSuchType' }
diff --git a/tests/qapi-schema/data-unknown.out b/tests/qapi-schema/data-unknown.out
new file mode 100644
index 0000000..2c60726
--- /dev/null
+++ b/tests/qapi-schema/data-unknown.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('data', 'NoSuchType')])]
+[]
+[]
diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/nested-struct-data.exit b/tests/qapi-schema/nested-struct-data.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/nested-struct-data.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/nested-struct-data.json b/tests/qapi-schema/nested-struct-data.json
new file mode 100644
index 0000000..0247c8c
--- /dev/null
+++ b/tests/qapi-schema/nested-struct-data.json
@@ -0,0 +1,4 @@
+# FIXME: inline subtypes collide with our desired future use of defaults
+{ 'command': 'foo',
+  'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' },
+  'returns': {} }
diff --git a/tests/qapi-schema/nested-struct-data.out b/tests/qapi-schema/nested-struct-data.out
new file mode 100644
index 0000000..999cbb8
--- /dev/null
+++ b/tests/qapi-schema/nested-struct-data.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'foo'), ('data', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')])), ('returns', OrderedDict())])]
+[]
+[]
diff --git a/tests/qapi-schema/nested-struct-returns.err b/tests/qapi-schema/nested-struct-returns.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/nested-struct-returns.exit b/tests/qapi-schema/nested-struct-returns.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/nested-struct-returns.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/nested-struct-returns.json b/tests/qapi-schema/nested-struct-returns.json
new file mode 100644
index 0000000..5a46840
--- /dev/null
+++ b/tests/qapi-schema/nested-struct-returns.json
@@ -0,0 +1,3 @@
+# FIXME: inline subtypes collide with our desired future use of defaults
+{ 'command': 'foo',
+  'returns': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } }
diff --git a/tests/qapi-schema/nested-struct-returns.out b/tests/qapi-schema/nested-struct-returns.out
new file mode 100644
index 0000000..c53d23b
--- /dev/null
+++ b/tests/qapi-schema/nested-struct-returns.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'foo'), ('returns', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')]))])]
+[]
+[]
diff --git a/tests/qapi-schema/returns-alternate.err b/tests/qapi-schema/returns-alternate.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/returns-alternate.exit b/tests/qapi-schema/returns-alternate.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/returns-alternate.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/returns-alternate.json b/tests/qapi-schema/returns-alternate.json
new file mode 100644
index 0000000..b3b91fd
--- /dev/null
+++ b/tests/qapi-schema/returns-alternate.json
@@ -0,0 +1,3 @@
+# FIXME: we should reject returns if it is an alternate type
+{ 'alternate': 'Alt', 'data': { 'a': 'int', 'b': 'str' } }
+{ 'command': 'oops', 'returns': 'Alt' }
diff --git a/tests/qapi-schema/returns-alternate.out b/tests/qapi-schema/returns-alternate.out
new file mode 100644
index 0000000..8a03ed3
--- /dev/null
+++ b/tests/qapi-schema/returns-alternate.out
@@ -0,0 +1,4 @@
+[OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]),
+ OrderedDict([('command', 'oops'), ('returns', 'Alt')])]
+[{'enum_name': 'AltKind', 'enum_values': None}]
+[]
diff --git a/tests/qapi-schema/returns-array-bad.err b/tests/qapi-schema/returns-array-bad.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/returns-array-bad.exit b/tests/qapi-schema/returns-array-bad.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/returns-array-bad.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/returns-array-bad.json b/tests/qapi-schema/returns-array-bad.json
new file mode 100644
index 0000000..14882c1
--- /dev/null
+++ b/tests/qapi-schema/returns-array-bad.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject an array return that is not a single type
+{ 'command': 'oops', 'returns': [ 'str', 'str' ] }
diff --git a/tests/qapi-schema/returns-array-bad.out b/tests/qapi-schema/returns-array-bad.out
new file mode 100644
index 0000000..eccad55
--- /dev/null
+++ b/tests/qapi-schema/returns-array-bad.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('returns', ['str', 'str'])])]
+[]
+[]
diff --git a/tests/qapi-schema/returns-int.err b/tests/qapi-schema/returns-int.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/returns-int.exit b/tests/qapi-schema/returns-int.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/returns-int.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/returns-int.json b/tests/qapi-schema/returns-int.json
new file mode 100644
index 0000000..7888fb1
--- /dev/null
+++ b/tests/qapi-schema/returns-int.json
@@ -0,0 +1,2 @@
+# It is okay (although not extensible) to return a non-dictionary
+{ 'command': 'okay', 'returns': 'int' }
diff --git a/tests/qapi-schema/returns-int.out b/tests/qapi-schema/returns-int.out
new file mode 100644
index 0000000..36b00a9
--- /dev/null
+++ b/tests/qapi-schema/returns-int.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'okay'), ('returns', 'int')])]
+[]
+[]
diff --git a/tests/qapi-schema/returns-unknown.err b/tests/qapi-schema/returns-unknown.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/returns-unknown.exit b/tests/qapi-schema/returns-unknown.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/returns-unknown.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/returns-unknown.json b/tests/qapi-schema/returns-unknown.json
new file mode 100644
index 0000000..61f20eb
--- /dev/null
+++ b/tests/qapi-schema/returns-unknown.json
@@ -0,0 +1,2 @@
+# FIXME: we should reject returns if it does not contain a known type
+{ 'command': 'oops', 'returns': 'NoSuchType' }
diff --git a/tests/qapi-schema/returns-unknown.out b/tests/qapi-schema/returns-unknown.out
new file mode 100644
index 0000000..a482c83
--- /dev/null
+++ b/tests/qapi-schema/returns-unknown.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'oops'), ('returns', 'NoSuchType')])]
+[]
+[]
diff --git a/tests/qapi-schema/returns-whitelist.err b/tests/qapi-schema/returns-whitelist.err
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/returns-whitelist.exit b/tests/qapi-schema/returns-whitelist.exit
new file mode 100644
index 0000000..573541a
--- /dev/null
+++ b/tests/qapi-schema/returns-whitelist.exit
@@ -0,0 +1 @@
+0
diff --git a/tests/qapi-schema/returns-whitelist.json b/tests/qapi-schema/returns-whitelist.json
new file mode 100644
index 0000000..8328563
--- /dev/null
+++ b/tests/qapi-schema/returns-whitelist.json
@@ -0,0 +1,11 @@
+# FIXME: we should enforce that 'returns' be a dict or array of dict unless whitelisted
+{ 'command': 'human-monitor-command',
+  'data': {'command-line': 'str', '*cpu-index': 'int'},
+  'returns': 'str' }
+{ 'enum': 'TpmModel', 'data': [ 'tpm-tis' ] }
+{ 'command': 'query-tpm-models', 'returns': ['TpmModel'] }
+{ 'command': 'guest-get-time',
+  'returns': 'int' }
+
+{ 'command': 'no-way-this-will-get-whitelisted',
+  'returns': [ 'int' ] }
diff --git a/tests/qapi-schema/returns-whitelist.out b/tests/qapi-schema/returns-whitelist.out
new file mode 100644
index 0000000..2adcd8b
--- /dev/null
+++ b/tests/qapi-schema/returns-whitelist.out
@@ -0,0 +1,7 @@
+[OrderedDict([('command', 'human-monitor-command'), ('data', OrderedDict([('command-line', 'str'), ('*cpu-index', 'int')])), ('returns', 'str')]),
+ OrderedDict([('enum', 'TpmModel'), ('data', ['tpm-tis'])]),
+ OrderedDict([('command', 'query-tpm-models'), ('returns', ['TpmModel'])]),
+ OrderedDict([('command', 'guest-get-time'), ('returns', 'int')]),
+ OrderedDict([('command', 'no-way-this-will-get-whitelisted'), ('returns', ['int'])])]
+[{'enum_name': 'TpmModel', 'enum_values': ['tpm-tis']}]
+[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 24/36] qapi: More rigourous checking of types
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (22 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 23/36] qapi: Add some type check tests Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-28 11:30   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 25/36] qapi: Require valid names Eric Blake
                   ` (15 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Now that we know every expression is valid with regards to
its keys, we can add further tests that those keys refer to
valid types.  With this patch, all uses of a type (the 'data':
of command, type, union, alternate, and event; the 'returns':
of command; the 'base': of type and union) must resolve to an
appropriate subset of metatypes  declared by the current qapi
parse; this includes recursing into each member of a data
dictionary.  Dealing with '**' and nested anonymous structs
will be done in later patches.

Update the testsuite to match improved output.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: switch check_type defaults to allow nothing by default, and
adjust all callers; s/data/value/ for parameter name; tweak
error messages to remember if a name was an array; require dict
for flat union branches
---
 scripts/qapi.py                              | 96 +++++++++++++++++++++++++---
 tests/qapi-schema/alternate-array.err        |  2 +-
 tests/qapi-schema/alternate-nested.err       |  2 +-
 tests/qapi-schema/alternate-unknown.err      |  2 +-
 tests/qapi-schema/bad-base.err               |  1 +
 tests/qapi-schema/bad-base.exit              |  2 +-
 tests/qapi-schema/bad-base.json              |  2 +-
 tests/qapi-schema/bad-base.out               |  4 --
 tests/qapi-schema/bad-data.err               |  1 +
 tests/qapi-schema/bad-data.exit              |  2 +-
 tests/qapi-schema/bad-data.json              |  2 +-
 tests/qapi-schema/bad-data.out               |  3 -
 tests/qapi-schema/data-array-empty.err       |  1 +
 tests/qapi-schema/data-array-empty.exit      |  2 +-
 tests/qapi-schema/data-array-empty.json      |  2 +-
 tests/qapi-schema/data-array-empty.out       |  3 -
 tests/qapi-schema/data-array-unknown.err     |  1 +
 tests/qapi-schema/data-array-unknown.exit    |  2 +-
 tests/qapi-schema/data-array-unknown.json    |  2 +-
 tests/qapi-schema/data-array-unknown.out     |  3 -
 tests/qapi-schema/data-int.err               |  1 +
 tests/qapi-schema/data-int.exit              |  2 +-
 tests/qapi-schema/data-int.json              |  2 +-
 tests/qapi-schema/data-int.out               |  3 -
 tests/qapi-schema/data-member-array-bad.err  |  1 +
 tests/qapi-schema/data-member-array-bad.exit |  2 +-
 tests/qapi-schema/data-member-array-bad.json |  2 +-
 tests/qapi-schema/data-member-array-bad.out  |  3 -
 tests/qapi-schema/data-member-unknown.err    |  1 +
 tests/qapi-schema/data-member-unknown.exit   |  2 +-
 tests/qapi-schema/data-member-unknown.json   |  2 +-
 tests/qapi-schema/data-member-unknown.out    |  3 -
 tests/qapi-schema/data-unknown.err           |  1 +
 tests/qapi-schema/data-unknown.exit          |  2 +-
 tests/qapi-schema/data-unknown.json          |  2 +-
 tests/qapi-schema/data-unknown.out           |  3 -
 tests/qapi-schema/flat-union-int-branch.err  |  1 +
 tests/qapi-schema/flat-union-int-branch.exit |  2 +-
 tests/qapi-schema/flat-union-int-branch.json |  2 +-
 tests/qapi-schema/flat-union-int-branch.out  |  7 --
 tests/qapi-schema/returns-array-bad.err      |  1 +
 tests/qapi-schema/returns-array-bad.exit     |  2 +-
 tests/qapi-schema/returns-array-bad.json     |  2 +-
 tests/qapi-schema/returns-array-bad.out      |  3 -
 tests/qapi-schema/returns-unknown.err        |  1 +
 tests/qapi-schema/returns-unknown.exit       |  2 +-
 tests/qapi-schema/returns-unknown.json       |  2 +-
 tests/qapi-schema/returns-unknown.out        |  3 -
 tests/qapi-schema/union-unknown.err          |  1 +
 tests/qapi-schema/union-unknown.exit         |  2 +-
 tests/qapi-schema/union-unknown.json         |  2 +-
 tests/qapi-schema/union-unknown.out          |  3 -
 52 files changed, 126 insertions(+), 77 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 686bc86..9f64a0d 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -276,6 +276,64 @@ def discriminator_find_enum_define(expr):

     return find_enum(discriminator_type)

+def check_type(expr_info, source, value, allow_array = False,
+               allow_dict = False, allow_metas = []):
+    global all_names
+    orig_value = value
+
+    if value is None:
+        return
+
+    if value == '**':
+        return
+
+    # Check if array type for value is okay
+    if isinstance(value, list):
+        if not allow_array:
+            raise QAPIExprError(expr_info,
+                                "%s cannot be an array" % source)
+        if len(value) != 1 or not isinstance(value[0], str):
+            raise QAPIExprError(expr_info,
+                                "%s: array type must contain single type name"
+                                % source)
+        value = value[0]
+        orig_value = "array of %s" %value
+
+    # Check if type name for value is okay
+    if isinstance(value, str):
+        if not value in all_names:
+            raise QAPIExprError(expr_info,
+                                "%s uses unknown type '%s'"
+                                % (source, orig_value))
+        if not all_names[value] in allow_metas:
+            raise QAPIExprError(expr_info,
+                                "%s cannot use %s type '%s'"
+                                % (source, all_names[value], orig_value))
+        return
+
+    # value is a dictionary, check that each member is okay
+    if not isinstance(value, OrderedDict):
+        raise QAPIExprError(expr_info,
+                            "%s should be a dictionary" % source)
+    if not allow_dict:
+        raise QAPIExprError(expr_info,
+                            "%s should be a type name" % source)
+    for (key, arg) in value.items():
+        check_type(expr_info, "Member '%s' of %s" % (key, source), arg,
+                   allow_array=True, allow_dict=True,
+                   allow_metas=['built-in', 'union', 'alternate', 'struct',
+                                'enum'])
+
+def check_command(expr, expr_info):
+    name = expr['command']
+    check_type(expr_info, "'data' for command '%s'" % name,
+               expr.get('data'), allow_dict=True,
+               allow_metas=['union', 'struct'])
+    check_type(expr_info, "'returns' for command '%s'" % name,
+               expr.get('returns'), allow_array=True, allow_dict=True,
+               allow_metas=['built-in', 'union', 'alternate', 'struct',
+                            'enum'])
+
 def check_event(expr, expr_info):
     global events
     name = expr['event']
@@ -284,7 +342,9 @@ def check_event(expr, expr_info):
     if name.upper() == 'MAX':
         raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created")
     events.append(name)
-
+    check_type(expr_info, "'data' for event '%s'" % name,
+               expr.get('data'), allow_dict=True,
+               allow_metas=['union', 'struct'])
     if params:
         for argname, argentry, optional, structured in parse_args(params):
             if structured:
@@ -313,6 +373,7 @@ def check_union(expr, expr_info):
     # With no discriminator it is a simple union.
     if discriminator is None:
         enum_define = None
+        allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']
         if base is not None:
             raise QAPIExprError(expr_info,
                                 "Simple union '%s' must not have a base"
@@ -344,6 +405,7 @@ def check_union(expr, expr_info):
                                 "type '%s'"
                                 % (discriminator, base))
         enum_define = find_enum(discriminator_type)
+        allow_metas=['struct']
         # Do not allow string discriminator
         if not enum_define:
             raise QAPIExprError(expr_info,
@@ -352,6 +414,11 @@ def check_union(expr, expr_info):

     # Check every branch
     for (key, value) in members.items():
+        # Each value must name a known type; futhermore, in flat unions,
+        # branches must be a struct
+        check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
+                   value, allow_array=True, allow_metas=allow_metas)
+
         # If the discriminator names an enum type, then all members
         # of 'data' must also be members of the enum type.
         if enum_define:
@@ -387,15 +454,11 @@ def check_alternate(expr, expr_info):
         values[c_key] = key

         # Ensure alternates have no type conflicts.
-        if isinstance(value, list):
-            raise QAPIExprError(expr_info,
-                                "Alternate '%s' member '%s' must "
-                                "not be array type" % (name, key))
+        check_type(expr_info, "Member '%s' of alternate '%s'" % (key, name),
+                   value,
+                   allow_metas=['built-in', 'union', 'struct', 'enum'])
         qtype = find_alternate_member_qtype(value)
-        if not qtype:
-            raise QAPIExprError(expr_info,
-                                "Alternate '%s' member '%s' has "
-                                "invalid type '%s'" % (name, key, value))
+        assert qtype
         if qtype in types_seen:
             raise QAPIExprError(expr_info,
                                 "Alternate '%s' member '%s' can't "
@@ -423,6 +486,15 @@ def check_enum(expr, expr_info):
                                 % (name, member, values[key]))
         values[key] = member

+def check_struct(expr, expr_info):
+    name = expr['type']
+    members = expr['data']
+
+    check_type(expr_info, "'data' for type '%s'" % name, members,
+               allow_dict=True)
+    check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'),
+               allow_metas=['struct'])
+
 def check_exprs(schema):
     for expr_elem in schema.exprs:
         expr = expr_elem['expr']
@@ -434,8 +506,14 @@ def check_exprs(schema):
             check_union(expr, info)
         elif expr.has_key('alternate'):
             check_alternate(expr, info)
+        elif expr.has_key('type'):
+            check_struct(expr, info)
+        elif expr.has_key('command'):
+            check_command(expr, info)
         elif expr.has_key('event'):
             check_event(expr, info)
+        else:
+            assert False, 'unexpected meta type'

 def check_keys(expr_elem, meta, required, optional=[]):
     expr = expr_elem['expr']
diff --git a/tests/qapi-schema/alternate-array.err b/tests/qapi-schema/alternate-array.err
index e2a5fc2..7b930c6 100644
--- a/tests/qapi-schema/alternate-array.err
+++ b/tests/qapi-schema/alternate-array.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-array.json:5: Alternate 'Alt' member 'two' must not be array type
+tests/qapi-schema/alternate-array.json:5: Member 'two' of alternate 'Alt' cannot be an array
diff --git a/tests/qapi-schema/alternate-nested.err b/tests/qapi-schema/alternate-nested.err
index 00b05c6..4d1187e 100644
--- a/tests/qapi-schema/alternate-nested.err
+++ b/tests/qapi-schema/alternate-nested.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-nested.json:4: Alternate 'Alt2' member 'nested' has invalid type 'Alt1'
+tests/qapi-schema/alternate-nested.json:4: Member 'nested' of alternate 'Alt2' cannot use alternate type 'Alt1'
diff --git a/tests/qapi-schema/alternate-unknown.err b/tests/qapi-schema/alternate-unknown.err
index 7af1b4c..dea45dc 100644
--- a/tests/qapi-schema/alternate-unknown.err
+++ b/tests/qapi-schema/alternate-unknown.err
@@ -1 +1 @@
-tests/qapi-schema/alternate-unknown.json:2: Alternate 'Alt' member 'unknown' has invalid type 'MissingType'
+tests/qapi-schema/alternate-unknown.json:2: Member 'unknown' of alternate 'Alt' uses unknown type 'MissingType'
diff --git a/tests/qapi-schema/bad-base.err b/tests/qapi-schema/bad-base.err
index e69de29..f398bbb 100644
--- a/tests/qapi-schema/bad-base.err
+++ b/tests/qapi-schema/bad-base.err
@@ -0,0 +1 @@
+tests/qapi-schema/bad-base.json:3: 'base' for type 'MyType' cannot use union type 'Union'
diff --git a/tests/qapi-schema/bad-base.exit b/tests/qapi-schema/bad-base.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/bad-base.exit
+++ b/tests/qapi-schema/bad-base.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/bad-base.json b/tests/qapi-schema/bad-base.json
index de964a0..a690470 100644
--- a/tests/qapi-schema/bad-base.json
+++ b/tests/qapi-schema/bad-base.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject a base that is not a struct
+# we reject a base that is not a struct
 { 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } }
 { 'type': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } }
diff --git a/tests/qapi-schema/bad-base.out b/tests/qapi-schema/bad-base.out
index 91d12fc..e69de29 100644
--- a/tests/qapi-schema/bad-base.out
+++ b/tests/qapi-schema/bad-base.out
@@ -1,4 +0,0 @@
-[OrderedDict([('union', 'Union'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]),
- OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])]
-[{'enum_name': 'UnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'MyType'), ('base', 'Union'), ('data', OrderedDict([('c', 'int')]))])]
diff --git a/tests/qapi-schema/bad-data.err b/tests/qapi-schema/bad-data.err
index e69de29..8523ac4 100644
--- a/tests/qapi-schema/bad-data.err
+++ b/tests/qapi-schema/bad-data.err
@@ -0,0 +1 @@
+tests/qapi-schema/bad-data.json:2: 'data' for command 'oops' cannot be an array
diff --git a/tests/qapi-schema/bad-data.exit b/tests/qapi-schema/bad-data.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/bad-data.exit
+++ b/tests/qapi-schema/bad-data.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/bad-data.json b/tests/qapi-schema/bad-data.json
index ff1616d..832eeb7 100644
--- a/tests/qapi-schema/bad-data.json
+++ b/tests/qapi-schema/bad-data.json
@@ -1,2 +1,2 @@
-# FIXME: we should ensure 'data' is a dictionary for all but enums
+# we ensure 'data' is a dictionary for all but enums
 { 'command': 'oops', 'data': [ ] }
diff --git a/tests/qapi-schema/bad-data.out b/tests/qapi-schema/bad-data.out
index 67802be..e69de29 100644
--- a/tests/qapi-schema/bad-data.out
+++ b/tests/qapi-schema/bad-data.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('data', [])])]
-[]
-[]
diff --git a/tests/qapi-schema/data-array-empty.err b/tests/qapi-schema/data-array-empty.err
index e69de29..f713f14 100644
--- a/tests/qapi-schema/data-array-empty.err
+++ b/tests/qapi-schema/data-array-empty.err
@@ -0,0 +1 @@
+tests/qapi-schema/data-array-empty.json:2: Member 'empty' of 'data' for command 'oops': array type must contain single type name
diff --git a/tests/qapi-schema/data-array-empty.exit b/tests/qapi-schema/data-array-empty.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/data-array-empty.exit
+++ b/tests/qapi-schema/data-array-empty.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/data-array-empty.json b/tests/qapi-schema/data-array-empty.json
index edb543a..652dcfb 100644
--- a/tests/qapi-schema/data-array-empty.json
+++ b/tests/qapi-schema/data-array-empty.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject an array for data if it does not contain a known type
+# we reject an array for data if it does not contain a known type
 { 'command': 'oops', 'data': { 'empty': [ ] } }
diff --git a/tests/qapi-schema/data-array-empty.out b/tests/qapi-schema/data-array-empty.out
index 6f25c9e..e69de29 100644
--- a/tests/qapi-schema/data-array-empty.out
+++ b/tests/qapi-schema/data-array-empty.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('data', OrderedDict([('empty', [])]))])]
-[]
-[]
diff --git a/tests/qapi-schema/data-array-unknown.err b/tests/qapi-schema/data-array-unknown.err
index e69de29..8b731bb 100644
--- a/tests/qapi-schema/data-array-unknown.err
+++ b/tests/qapi-schema/data-array-unknown.err
@@ -0,0 +1 @@
+tests/qapi-schema/data-array-unknown.json:2: Member 'array' of 'data' for command 'oops' uses unknown type 'array of NoSuchType'
diff --git a/tests/qapi-schema/data-array-unknown.exit b/tests/qapi-schema/data-array-unknown.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/data-array-unknown.exit
+++ b/tests/qapi-schema/data-array-unknown.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/data-array-unknown.json b/tests/qapi-schema/data-array-unknown.json
index 20cd3c0..6f3e883 100644
--- a/tests/qapi-schema/data-array-unknown.json
+++ b/tests/qapi-schema/data-array-unknown.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject an array for data if it does not contain a known type
+# we reject an array for data if it does not contain a known type
 { 'command': 'oops', 'data': { 'array': [ 'NoSuchType' ] } }
diff --git a/tests/qapi-schema/data-array-unknown.out b/tests/qapi-schema/data-array-unknown.out
index 4314ab5..e69de29 100644
--- a/tests/qapi-schema/data-array-unknown.out
+++ b/tests/qapi-schema/data-array-unknown.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('data', OrderedDict([('array', ['NoSuchType'])]))])]
-[]
-[]
diff --git a/tests/qapi-schema/data-int.err b/tests/qapi-schema/data-int.err
index e69de29..1a9b077 100644
--- a/tests/qapi-schema/data-int.err
+++ b/tests/qapi-schema/data-int.err
@@ -0,0 +1 @@
+tests/qapi-schema/data-int.json:2: 'data' for command 'oops' cannot use built-in type 'int'
diff --git a/tests/qapi-schema/data-int.exit b/tests/qapi-schema/data-int.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/data-int.exit
+++ b/tests/qapi-schema/data-int.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/data-int.json b/tests/qapi-schema/data-int.json
index 37916e0..a334d92 100644
--- a/tests/qapi-schema/data-int.json
+++ b/tests/qapi-schema/data-int.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject commands where data is not an array or complex type
+# we reject commands where data is not an array or complex type
 { 'command': 'oops', 'data': 'int' }
diff --git a/tests/qapi-schema/data-int.out b/tests/qapi-schema/data-int.out
index e589a4f..e69de29 100644
--- a/tests/qapi-schema/data-int.out
+++ b/tests/qapi-schema/data-int.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('data', 'int')])]
-[]
-[]
diff --git a/tests/qapi-schema/data-member-array-bad.err b/tests/qapi-schema/data-member-array-bad.err
index e69de29..2c072d5 100644
--- a/tests/qapi-schema/data-member-array-bad.err
+++ b/tests/qapi-schema/data-member-array-bad.err
@@ -0,0 +1 @@
+tests/qapi-schema/data-member-array-bad.json:2: Member 'member' of 'data' for command 'oops': array type must contain single type name
diff --git a/tests/qapi-schema/data-member-array-bad.exit b/tests/qapi-schema/data-member-array-bad.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/data-member-array-bad.exit
+++ b/tests/qapi-schema/data-member-array-bad.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/data-member-array-bad.json b/tests/qapi-schema/data-member-array-bad.json
index c954af1..b2ff144 100644
--- a/tests/qapi-schema/data-member-array-bad.json
+++ b/tests/qapi-schema/data-member-array-bad.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject data if it does not contain a valid array type
+# we reject data if it does not contain a valid array type
 { 'command': 'oops', 'data': { 'member': [ { 'nested': 'str' } ] } }
diff --git a/tests/qapi-schema/data-member-array-bad.out b/tests/qapi-schema/data-member-array-bad.out
index 0e00c41..e69de29 100644
--- a/tests/qapi-schema/data-member-array-bad.out
+++ b/tests/qapi-schema/data-member-array-bad.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', [OrderedDict([('nested', 'str')])])]))])]
-[]
-[]
diff --git a/tests/qapi-schema/data-member-unknown.err b/tests/qapi-schema/data-member-unknown.err
index e69de29..ab905db 100644
--- a/tests/qapi-schema/data-member-unknown.err
+++ b/tests/qapi-schema/data-member-unknown.err
@@ -0,0 +1 @@
+tests/qapi-schema/data-member-unknown.json:2: Member 'member' of 'data' for command 'oops' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/data-member-unknown.exit b/tests/qapi-schema/data-member-unknown.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/data-member-unknown.exit
+++ b/tests/qapi-schema/data-member-unknown.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/data-member-unknown.json b/tests/qapi-schema/data-member-unknown.json
index 40e6252..342a41e 100644
--- a/tests/qapi-schema/data-member-unknown.json
+++ b/tests/qapi-schema/data-member-unknown.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject data if it does not contain a known type
+# we reject data if it does not contain a known type
 { 'command': 'oops', 'data': { 'member': 'NoSuchType' } }
diff --git a/tests/qapi-schema/data-member-unknown.out b/tests/qapi-schema/data-member-unknown.out
index 36252a5..e69de29 100644
--- a/tests/qapi-schema/data-member-unknown.out
+++ b/tests/qapi-schema/data-member-unknown.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('data', OrderedDict([('member', 'NoSuchType')]))])]
-[]
-[]
diff --git a/tests/qapi-schema/data-unknown.err b/tests/qapi-schema/data-unknown.err
index e69de29..5b07277 100644
--- a/tests/qapi-schema/data-unknown.err
+++ b/tests/qapi-schema/data-unknown.err
@@ -0,0 +1 @@
+tests/qapi-schema/data-unknown.json:2: 'data' for command 'oops' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/data-unknown.exit b/tests/qapi-schema/data-unknown.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/data-unknown.exit
+++ b/tests/qapi-schema/data-unknown.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/data-unknown.json b/tests/qapi-schema/data-unknown.json
index 776bd34..32aba43 100644
--- a/tests/qapi-schema/data-unknown.json
+++ b/tests/qapi-schema/data-unknown.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject data if it does not contain a known type
+# we reject data if it does not contain a known type
 { 'command': 'oops', 'data': 'NoSuchType' }
diff --git a/tests/qapi-schema/data-unknown.out b/tests/qapi-schema/data-unknown.out
index 2c60726..e69de29 100644
--- a/tests/qapi-schema/data-unknown.out
+++ b/tests/qapi-schema/data-unknown.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('data', 'NoSuchType')])]
-[]
-[]
diff --git a/tests/qapi-schema/flat-union-int-branch.err b/tests/qapi-schema/flat-union-int-branch.err
index e69de29..faf0157 100644
--- a/tests/qapi-schema/flat-union-int-branch.err
+++ b/tests/qapi-schema/flat-union-int-branch.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-int-branch.json:8: Member 'value1' of union 'TestUnion' cannot use built-in type 'int'
diff --git a/tests/qapi-schema/flat-union-int-branch.exit b/tests/qapi-schema/flat-union-int-branch.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/flat-union-int-branch.exit
+++ b/tests/qapi-schema/flat-union-int-branch.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json
index 3543215..d373131 100644
--- a/tests/qapi-schema/flat-union-int-branch.json
+++ b/tests/qapi-schema/flat-union-int-branch.json
@@ -1,4 +1,4 @@
-# FIXME: we should require flat union branches to be a complex type
+# we require flat union branches to be a complex type
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 { 'type': 'Base',
diff --git a/tests/qapi-schema/flat-union-int-branch.out b/tests/qapi-schema/flat-union-int-branch.out
index cd40e6c..e69de29 100644
--- a/tests/qapi-schema/flat-union-int-branch.out
+++ b/tests/qapi-schema/flat-union-int-branch.out
@@ -1,7 +0,0 @@
-[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
- OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'int'), ('value2', 'TestTypeB')]))])]
-[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}]
-[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
diff --git a/tests/qapi-schema/returns-array-bad.err b/tests/qapi-schema/returns-array-bad.err
index e69de29..138095c 100644
--- a/tests/qapi-schema/returns-array-bad.err
+++ b/tests/qapi-schema/returns-array-bad.err
@@ -0,0 +1 @@
+tests/qapi-schema/returns-array-bad.json:2: 'returns' for command 'oops': array type must contain single type name
diff --git a/tests/qapi-schema/returns-array-bad.exit b/tests/qapi-schema/returns-array-bad.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/returns-array-bad.exit
+++ b/tests/qapi-schema/returns-array-bad.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/returns-array-bad.json b/tests/qapi-schema/returns-array-bad.json
index 14882c1..09b0b1f 100644
--- a/tests/qapi-schema/returns-array-bad.json
+++ b/tests/qapi-schema/returns-array-bad.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject an array return that is not a single type
+# we reject an array return that is not a single type
 { 'command': 'oops', 'returns': [ 'str', 'str' ] }
diff --git a/tests/qapi-schema/returns-array-bad.out b/tests/qapi-schema/returns-array-bad.out
index eccad55..e69de29 100644
--- a/tests/qapi-schema/returns-array-bad.out
+++ b/tests/qapi-schema/returns-array-bad.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('returns', ['str', 'str'])])]
-[]
-[]
diff --git a/tests/qapi-schema/returns-unknown.err b/tests/qapi-schema/returns-unknown.err
index e69de29..1f43e3a 100644
--- a/tests/qapi-schema/returns-unknown.err
+++ b/tests/qapi-schema/returns-unknown.err
@@ -0,0 +1 @@
+tests/qapi-schema/returns-unknown.json:2: 'returns' for command 'oops' uses unknown type 'NoSuchType'
diff --git a/tests/qapi-schema/returns-unknown.exit b/tests/qapi-schema/returns-unknown.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/returns-unknown.exit
+++ b/tests/qapi-schema/returns-unknown.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/returns-unknown.json b/tests/qapi-schema/returns-unknown.json
index 61f20eb..25bd498 100644
--- a/tests/qapi-schema/returns-unknown.json
+++ b/tests/qapi-schema/returns-unknown.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject returns if it does not contain a known type
+# we reject returns if it does not contain a known type
 { 'command': 'oops', 'returns': 'NoSuchType' }
diff --git a/tests/qapi-schema/returns-unknown.out b/tests/qapi-schema/returns-unknown.out
index a482c83..e69de29 100644
--- a/tests/qapi-schema/returns-unknown.out
+++ b/tests/qapi-schema/returns-unknown.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'oops'), ('returns', 'NoSuchType')])]
-[]
-[]
diff --git a/tests/qapi-schema/union-unknown.err b/tests/qapi-schema/union-unknown.err
index e69de29..54fe456 100644
--- a/tests/qapi-schema/union-unknown.err
+++ b/tests/qapi-schema/union-unknown.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-unknown.json:2: Member 'unknown' of union 'Union' uses unknown type 'MissingType'
diff --git a/tests/qapi-schema/union-unknown.exit b/tests/qapi-schema/union-unknown.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/union-unknown.exit
+++ b/tests/qapi-schema/union-unknown.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/union-unknown.json b/tests/qapi-schema/union-unknown.json
index 258f1d3..aa7e814 100644
--- a/tests/qapi-schema/union-unknown.json
+++ b/tests/qapi-schema/union-unknown.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject a union with unknown type in branch
+# we reject a union with unknown type in branch
 { 'union': 'Union',
   'data': { 'unknown': 'MissingType' } }
diff --git a/tests/qapi-schema/union-unknown.out b/tests/qapi-schema/union-unknown.out
index 8223dcf..e69de29 100644
--- a/tests/qapi-schema/union-unknown.out
+++ b/tests/qapi-schema/union-unknown.out
@@ -1,3 +0,0 @@
-[OrderedDict([('union', 'Union'), ('data', OrderedDict([('unknown', 'MissingType')]))])]
-[{'enum_name': 'UnionKind', 'enum_values': None}]
-[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 25/36] qapi: Require valid names
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (23 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 24/36] qapi: More rigourous checking of types Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-28 11:46   ` Markus Armbruster
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 26/36] qapi: Whitelist commands that don't return dictionary Eric Blake
                   ` (14 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Previous commits demonstrated that the generator overlooked various
bad naming situations:
- types, commands, and events need a valid name
- enum members must be valid names, when combined with prefix
- union and alternate branches cannot be marked optional

The set of valid names includes [a-zA-Z_][a-zA-Z0-9._-]* (where
'.' is useful only in downstream extensions).  Since we have
existing enum values beginning with a digit (see QKeyCode), a
small hack is used to pass the same regex.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: rebase onto earlier changes; use regex instead of sets, and
ensure leading letter or _; don't force event case; drop dead
code for exempting '**'; extend coverage to enum members
---
 scripts/qapi.py                                    | 63 ++++++++++++++++------
 tests/qapi-schema/bad-ident.err                    |  1 +
 tests/qapi-schema/bad-ident.exit                   |  2 +-
 tests/qapi-schema/bad-ident.json                   |  2 +-
 tests/qapi-schema/bad-ident.out                    |  3 --
 tests/qapi-schema/enum-bad-name.err                |  1 +
 tests/qapi-schema/enum-bad-name.exit               |  2 +-
 tests/qapi-schema/enum-bad-name.json               |  2 +-
 tests/qapi-schema/enum-bad-name.out                |  3 --
 tests/qapi-schema/enum-dict-member.err             |  2 +-
 tests/qapi-schema/flat-union-bad-discriminator.err |  2 +-
 .../flat-union-optional-discriminator.err          |  1 +
 .../flat-union-optional-discriminator.exit         |  2 +-
 .../flat-union-optional-discriminator.json         |  2 +-
 .../flat-union-optional-discriminator.out          |  7 ---
 tests/qapi-schema/union-optional-branch.err        |  1 +
 tests/qapi-schema/union-optional-branch.exit       |  2 +-
 tests/qapi-schema/union-optional-branch.json       |  2 +-
 tests/qapi-schema/union-optional-branch.out        |  3 --
 19 files changed, 60 insertions(+), 43 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 9f64a0d..9b97683 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -276,8 +276,31 @@ def discriminator_find_enum_define(expr):

     return find_enum(discriminator_type)

+valid_name = re.compile('^[a-zA-Z_][a-zA-Z0-9_.-]*$')
+def check_name(expr_info, source, name, allow_optional = False,
+               enum_member = False):
+    global valid_name
+    membername = name
+
+    if not isinstance(name, str):
+        raise QAPIExprError(expr_info,
+                            "%s requires a string name" % source)
+    if name.startswith('*'):
+        membername = name[1:]
+        if not allow_optional:
+            raise QAPIExprError(expr_info,
+                                "%s does not allow optional name '%s'"
+                                % (source, name))
+    # Enum members can start with a digit, because the generated C
+    # code always prefixes it with the enum name
+    if enum_member:
+        membername = "_%s" %membername
+    if not valid_name.match(membername):
+        raise QAPIExprError(expr_info,
+                            "%s uses invalid name '%s'" % (source, name))
+
 def check_type(expr_info, source, value, allow_array = False,
-               allow_dict = False, allow_metas = []):
+               allow_dict = False, allow_optional = False, allow_metas = []):
     global all_names
     orig_value = value

@@ -319,18 +342,21 @@ def check_type(expr_info, source, value, allow_array = False,
         raise QAPIExprError(expr_info,
                             "%s should be a type name" % source)
     for (key, arg) in value.items():
+        check_name(expr_info, "Member of %s" % source, key,
+                   allow_optional=allow_optional)
         check_type(expr_info, "Member '%s' of %s" % (key, source), arg,
-                   allow_array=True, allow_dict=True,
+                   allow_array=True, allow_dict=True, allow_optional=True,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
                                 'enum'])

 def check_command(expr, expr_info):
     name = expr['command']
     check_type(expr_info, "'data' for command '%s'" % name,
-               expr.get('data'), allow_dict=True,
+               expr.get('data'), allow_dict=True, allow_optional=True,
                allow_metas=['union', 'struct'])
     check_type(expr_info, "'returns' for command '%s'" % name,
                expr.get('returns'), allow_array=True, allow_dict=True,
+               allow_optional=True,
                allow_metas=['built-in', 'union', 'alternate', 'struct',
                             'enum'])

@@ -343,7 +369,7 @@ def check_event(expr, expr_info):
         raise QAPIExprError(expr_info, "Event name 'MAX' cannot be created")
     events.append(name)
     check_type(expr_info, "'data' for event '%s'" % name,
-               expr.get('data'), allow_dict=True,
+               expr.get('data'), allow_dict=True, allow_optional=True,
                allow_metas=['union', 'struct'])
     if params:
         for argname, argentry, optional, structured in parse_args(params):
@@ -392,12 +418,10 @@ def check_union(expr, expr_info):
                                 "Base '%s' is not a valid type"
                                 % base)

-        # The value of member 'discriminator' must name a member of the
-        # base type.
-        if not isinstance(discriminator, str):
-            raise QAPIExprError(expr_info,
-                                "Flat union '%s' discriminator must be a string"
-                                % name)
+        # The value of member 'discriminator' must name a non-optional
+        # member of the base type.
+        check_name(expr_info, "Discriminator of flat union '%s'" % name,
+                   discriminator)
         discriminator_type = base_fields.get(discriminator)
         if not discriminator_type:
             raise QAPIExprError(expr_info,
@@ -414,6 +438,8 @@ def check_union(expr, expr_info):

     # Check every branch
     for (key, value) in members.items():
+        check_name(expr_info, "Member of union '%s'" % name, key)
+
         # Each value must name a known type; futhermore, in flat unions,
         # branches must be a struct
         check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
@@ -445,6 +471,8 @@ def check_alternate(expr, expr_info):

     # Check every branch
     for (key, value) in members.items():
+        check_name(expr_info, "Member of alternate '%s'" % name, key)
+
         # Check for conflicts in the generated enum
         c_key = _generate_enum_string(key)
         if c_key in values:
@@ -475,10 +503,8 @@ def check_enum(expr, expr_info):
         raise QAPIExprError(expr_info,
                             "Enum '%s' requires an array for 'data'" % name)
     for member in members:
-        if not isinstance(member, str):
-            raise QAPIExprError(expr_info,
-                                "Enum '%s' member '%s' is not a string"
-                                % (name, member))
+        check_name(expr_info, "Member of enum '%s'" %name, member,
+                   enum_member=True)
         key = _generate_enum_string(member)
         if key in values:
             raise QAPIExprError(expr_info,
@@ -491,7 +517,7 @@ def check_struct(expr, expr_info):
     members = expr['data']

     check_type(expr_info, "'data' for type '%s'" % name, members,
-               allow_dict=True)
+               allow_dict=True, allow_optional=True)
     check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'),
                allow_metas=['struct'])

@@ -682,8 +708,11 @@ def type_name(name):
         return c_list_type(name[0])
     return name

-def add_name(name, info, meta, implicit = False):
+def add_name(name, info, meta, implicit = False, source = None):
     global all_names
+    if not source:
+        source = "'%s'" % meta
+    check_name(info, source, name)
     if name in all_names:
         raise QAPIExprError(info,
                             "%s '%s' is already defined"
@@ -697,7 +726,7 @@ def add_name(name, info, meta, implicit = False):
 def add_struct(definition, info):
     global struct_types
     name = definition['type']
-    add_name(name, info, 'struct')
+    add_name(name, info, 'struct', source="'type'")
     struct_types.append(definition)

 def find_struct(name):
diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err
index e69de29..42b490c 100644
--- a/tests/qapi-schema/bad-ident.err
+++ b/tests/qapi-schema/bad-ident.err
@@ -0,0 +1 @@
+tests/qapi-schema/bad-ident.json:2: 'type' does not allow optional name '*oops'
diff --git a/tests/qapi-schema/bad-ident.exit b/tests/qapi-schema/bad-ident.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/bad-ident.exit
+++ b/tests/qapi-schema/bad-ident.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/bad-ident.json b/tests/qapi-schema/bad-ident.json
index f139110..da949e8 100644
--- a/tests/qapi-schema/bad-ident.json
+++ b/tests/qapi-schema/bad-ident.json
@@ -1,2 +1,2 @@
-# FIXME: we should reject creating a type name with bad name
+# we reject creating a type name with bad name
 { 'type': '*oops', 'data': { 'i': 'int' } }
diff --git a/tests/qapi-schema/bad-ident.out b/tests/qapi-schema/bad-ident.out
index 165e346..e69de29 100644
--- a/tests/qapi-schema/bad-ident.out
+++ b/tests/qapi-schema/bad-ident.out
@@ -1,3 +0,0 @@
-[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])]
-[]
-[OrderedDict([('type', '*oops'), ('data', OrderedDict([('i', 'int')]))])]
diff --git a/tests/qapi-schema/enum-bad-name.err b/tests/qapi-schema/enum-bad-name.err
index e69de29..9c3c100 100644
--- a/tests/qapi-schema/enum-bad-name.err
+++ b/tests/qapi-schema/enum-bad-name.err
@@ -0,0 +1 @@
+tests/qapi-schema/enum-bad-name.json:2: Member of enum 'MyEnum' uses invalid name 'not^possible'
diff --git a/tests/qapi-schema/enum-bad-name.exit b/tests/qapi-schema/enum-bad-name.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/enum-bad-name.exit
+++ b/tests/qapi-schema/enum-bad-name.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/enum-bad-name.json b/tests/qapi-schema/enum-bad-name.json
index 0c32448..8506562 100644
--- a/tests/qapi-schema/enum-bad-name.json
+++ b/tests/qapi-schema/enum-bad-name.json
@@ -1,2 +1,2 @@
-# FIXME: we should ensure all enum names can map to C
+# we ensure all enum names can map to C
 { 'enum': 'MyEnum', 'data': [ 'not^possible' ] }
diff --git a/tests/qapi-schema/enum-bad-name.out b/tests/qapi-schema/enum-bad-name.out
index d24ea49..e69de29 100644
--- a/tests/qapi-schema/enum-bad-name.out
+++ b/tests/qapi-schema/enum-bad-name.out
@@ -1,3 +0,0 @@
-[OrderedDict([('enum', 'MyEnum'), ('data', ['not^possible'])])]
-[{'enum_name': 'MyEnum', 'enum_values': ['not^possible']}]
-[]
diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err
index 7e966a8..8ca146e 100644
--- a/tests/qapi-schema/enum-dict-member.err
+++ b/tests/qapi-schema/enum-dict-member.err
@@ -1 +1 @@
-tests/qapi-schema/enum-dict-member.json:2: Enum 'MyEnum' member 'OrderedDict([('value', 'str')])' is not a string
+tests/qapi-schema/enum-dict-member.json:2: Member of enum 'MyEnum' requires a string name
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.err b/tests/qapi-schema/flat-union-bad-discriminator.err
index 507e2ba..c38cc8e 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.err
+++ b/tests/qapi-schema/flat-union-bad-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-bad-discriminator.json:11: Flat union 'TestUnion' discriminator must be a string
+tests/qapi-schema/flat-union-bad-discriminator.json:11: Discriminator of flat union 'TestUnion' requires a string name
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.err b/tests/qapi-schema/flat-union-optional-discriminator.err
index e69de29..aaabedb 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.err
+++ b/tests/qapi-schema/flat-union-optional-discriminator.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-optional-discriminator.json:6: Discriminator of flat union 'MyUnion' does not allow optional name '*switch'
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.exit b/tests/qapi-schema/flat-union-optional-discriminator.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.exit
+++ b/tests/qapi-schema/flat-union-optional-discriminator.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json
index ece0d31..25ce0e6 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.json
+++ b/tests/qapi-schema/flat-union-optional-discriminator.json
@@ -1,4 +1,4 @@
-# FIXME: we should require the discriminator to be non-optional
+# we require the discriminator to be non-optional
 { 'enum': 'Enum', 'data': [ 'one', 'two' ] }
 { 'type': 'Base',
   'data': { '*switch': 'Enum' } }
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.out b/tests/qapi-schema/flat-union-optional-discriminator.out
index bb7db00..e69de29 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.out
+++ b/tests/qapi-schema/flat-union-optional-discriminator.out
@@ -1,7 +0,0 @@
-[OrderedDict([('enum', 'Enum'), ('data', ['one', 'two'])]),
- OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]),
- OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('union', 'MyUnion'), ('base', 'Base'), ('discriminator', '*switch'), ('data', OrderedDict([('one', 'Branch'), ('two', 'Branch')]))])]
-[{'enum_name': 'Enum', 'enum_values': ['one', 'two']}]
-[OrderedDict([('type', 'Base'), ('data', OrderedDict([('*switch', 'Enum')]))]),
- OrderedDict([('type', 'Branch'), ('data', OrderedDict([('name', 'str')]))])]
diff --git a/tests/qapi-schema/union-optional-branch.err b/tests/qapi-schema/union-optional-branch.err
index e69de29..3ada133 100644
--- a/tests/qapi-schema/union-optional-branch.err
+++ b/tests/qapi-schema/union-optional-branch.err
@@ -0,0 +1 @@
+tests/qapi-schema/union-optional-branch.json:2: Member of union 'Union' does not allow optional name '*a'
diff --git a/tests/qapi-schema/union-optional-branch.exit b/tests/qapi-schema/union-optional-branch.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/union-optional-branch.exit
+++ b/tests/qapi-schema/union-optional-branch.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/union-optional-branch.json b/tests/qapi-schema/union-optional-branch.json
index c513db7..591615f 100644
--- a/tests/qapi-schema/union-optional-branch.json
+++ b/tests/qapi-schema/union-optional-branch.json
@@ -1,2 +1,2 @@
-# FIXME: union branches cannot be optional
+# union branches cannot be optional
 { 'union': 'Union', 'data': { '*a': 'int', 'b': 'str' } }
diff --git a/tests/qapi-schema/union-optional-branch.out b/tests/qapi-schema/union-optional-branch.out
index b03b5d2..e69de29 100644
--- a/tests/qapi-schema/union-optional-branch.out
+++ b/tests/qapi-schema/union-optional-branch.out
@@ -1,3 +0,0 @@
-[OrderedDict([('union', 'Union'), ('data', OrderedDict([('*a', 'int'), ('b', 'str')]))])]
-[{'enum_name': 'UnionKind', 'enum_values': None}]
-[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 26/36] qapi: Whitelist commands that don't return dictionary
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (24 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 25/36] qapi: Require valid names Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 27/36] qapi: More rigorous checking for type safety bypass Eric Blake
                   ` (13 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

...or an array of dictionaries.  Although we have to cater to
existing commands, returning a non-dictionary means the command
is not extensible (no new name/value pairs can be added if more
information must be returned in parallel).  By making the
whitelist explicit, any new command that falls foul of this
practice will have to be self-documenting, which will encourage
developers to either justify the action or rework the design to
use a dictionary after all.

It's a little bit sloppy that we share a single whitelist among
three clients (it's too permissive for each).  If this is a
problem, a future patch could tighten things by having the
generator take the whitelist as an argument (as in
scripts/qapi-commands.py --legacy-returns=...), or by having
the generator output C code that requires explicit use of the
whitelist (as in:
 #ifndef FROBNICATE_LEGACY_RETURN_OK
 # error Command 'frobnicate' should return a dictionary
 #endif
then having the callers define appropriate macros).  But until
we need such fine-grained separation (if ever), this patch does
the job just fine.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase onto earlier changes, update commit message
---
 scripts/qapi.py                          | 31 ++++++++++++++++++++++++++++---
 tests/qapi-schema/returns-alternate.err  |  1 +
 tests/qapi-schema/returns-alternate.exit |  2 +-
 tests/qapi-schema/returns-alternate.json |  2 +-
 tests/qapi-schema/returns-alternate.out  |  4 ----
 tests/qapi-schema/returns-int.json       |  3 ++-
 tests/qapi-schema/returns-int.out        |  2 +-
 tests/qapi-schema/returns-whitelist.err  |  1 +
 tests/qapi-schema/returns-whitelist.exit |  2 +-
 tests/qapi-schema/returns-whitelist.json |  2 +-
 tests/qapi-schema/returns-whitelist.out  |  7 -------
 11 files changed, 37 insertions(+), 20 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 9b97683..963f088 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -32,6 +32,30 @@ builtin_types = {
     'size':     'QTYPE_QINT',
 }

+# Whitelist of commands allowed to return a non-dictionary
+returns_whitelist = [
+    # From QMP:
+    'human-monitor-command',
+    'query-migrate-cache-size',
+    'query-tpm-models',
+    'query-tpm-types',
+    'ringbuf-read',
+
+    # From QGA:
+    'guest-file-open',
+    'guest-fsfreeze-freeze',
+    'guest-fsfreeze-freeze-list',
+    'guest-fsfreeze-status',
+    'guest-fsfreeze-thaw',
+    'guest-get-time',
+    'guest-set-vcpus',
+    'guest-sync',
+    'guest-sync-delimited',
+
+    # From qapi-schema-test:
+    'user_def_cmd3',
+]
+
 enum_types = []
 struct_types = []
 union_types = []
@@ -354,11 +378,12 @@ def check_command(expr, expr_info):
     check_type(expr_info, "'data' for command '%s'" % name,
                expr.get('data'), allow_dict=True, allow_optional=True,
                allow_metas=['union', 'struct'])
+    returns_meta = ['union', 'struct']
+    if name in returns_whitelist:
+        returns_meta += ['built-in', 'alternate', 'enum']
     check_type(expr_info, "'returns' for command '%s'" % name,
                expr.get('returns'), allow_array=True, allow_dict=True,
-               allow_optional=True,
-               allow_metas=['built-in', 'union', 'alternate', 'struct',
-                            'enum'])
+               allow_optional=True, allow_metas=returns_meta)

 def check_event(expr, expr_info):
     global events
diff --git a/tests/qapi-schema/returns-alternate.err b/tests/qapi-schema/returns-alternate.err
index e69de29..dfbb419 100644
--- a/tests/qapi-schema/returns-alternate.err
+++ b/tests/qapi-schema/returns-alternate.err
@@ -0,0 +1 @@
+tests/qapi-schema/returns-alternate.json:3: 'returns' for command 'oops' cannot use alternate type 'Alt'
diff --git a/tests/qapi-schema/returns-alternate.exit b/tests/qapi-schema/returns-alternate.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/returns-alternate.exit
+++ b/tests/qapi-schema/returns-alternate.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/returns-alternate.json b/tests/qapi-schema/returns-alternate.json
index b3b91fd..972390c 100644
--- a/tests/qapi-schema/returns-alternate.json
+++ b/tests/qapi-schema/returns-alternate.json
@@ -1,3 +1,3 @@
-# FIXME: we should reject returns if it is an alternate type
+# we reject returns if it is an alternate type
 { 'alternate': 'Alt', 'data': { 'a': 'int', 'b': 'str' } }
 { 'command': 'oops', 'returns': 'Alt' }
diff --git a/tests/qapi-schema/returns-alternate.out b/tests/qapi-schema/returns-alternate.out
index 8a03ed3..e69de29 100644
--- a/tests/qapi-schema/returns-alternate.out
+++ b/tests/qapi-schema/returns-alternate.out
@@ -1,4 +0,0 @@
-[OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('a', 'int'), ('b', 'str')]))]),
- OrderedDict([('command', 'oops'), ('returns', 'Alt')])]
-[{'enum_name': 'AltKind', 'enum_values': None}]
-[]
diff --git a/tests/qapi-schema/returns-int.json b/tests/qapi-schema/returns-int.json
index 7888fb1..870ec63 100644
--- a/tests/qapi-schema/returns-int.json
+++ b/tests/qapi-schema/returns-int.json
@@ -1,2 +1,3 @@
 # It is okay (although not extensible) to return a non-dictionary
-{ 'command': 'okay', 'returns': 'int' }
+# But to make it work, the name must be in a whitelist
+{ 'command': 'guest-get-time', 'returns': 'int' }
diff --git a/tests/qapi-schema/returns-int.out b/tests/qapi-schema/returns-int.out
index 36b00a9..70b3ac5 100644
--- a/tests/qapi-schema/returns-int.out
+++ b/tests/qapi-schema/returns-int.out
@@ -1,3 +1,3 @@
-[OrderedDict([('command', 'okay'), ('returns', 'int')])]
+[OrderedDict([('command', 'guest-get-time'), ('returns', 'int')])]
 []
 []
diff --git a/tests/qapi-schema/returns-whitelist.err b/tests/qapi-schema/returns-whitelist.err
index e69de29..a41f019 100644
--- a/tests/qapi-schema/returns-whitelist.err
+++ b/tests/qapi-schema/returns-whitelist.err
@@ -0,0 +1 @@
+tests/qapi-schema/returns-whitelist.json:10: 'returns' for command 'no-way-this-will-get-whitelisted' cannot use built-in type 'array of int'
diff --git a/tests/qapi-schema/returns-whitelist.exit b/tests/qapi-schema/returns-whitelist.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/returns-whitelist.exit
+++ b/tests/qapi-schema/returns-whitelist.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/returns-whitelist.json b/tests/qapi-schema/returns-whitelist.json
index 8328563..e8b3cea 100644
--- a/tests/qapi-schema/returns-whitelist.json
+++ b/tests/qapi-schema/returns-whitelist.json
@@ -1,4 +1,4 @@
-# FIXME: we should enforce that 'returns' be a dict or array of dict unless whitelisted
+# we enforce that 'returns' be a dict or array of dict unless whitelisted
 { 'command': 'human-monitor-command',
   'data': {'command-line': 'str', '*cpu-index': 'int'},
   'returns': 'str' }
diff --git a/tests/qapi-schema/returns-whitelist.out b/tests/qapi-schema/returns-whitelist.out
index 2adcd8b..e69de29 100644
--- a/tests/qapi-schema/returns-whitelist.out
+++ b/tests/qapi-schema/returns-whitelist.out
@@ -1,7 +0,0 @@
-[OrderedDict([('command', 'human-monitor-command'), ('data', OrderedDict([('command-line', 'str'), ('*cpu-index', 'int')])), ('returns', 'str')]),
- OrderedDict([('enum', 'TpmModel'), ('data', ['tpm-tis'])]),
- OrderedDict([('command', 'query-tpm-models'), ('returns', ['TpmModel'])]),
- OrderedDict([('command', 'guest-get-time'), ('returns', 'int')]),
- OrderedDict([('command', 'no-way-this-will-get-whitelisted'), ('returns', ['int'])])]
-[{'enum_name': 'TpmModel', 'enum_values': ['tpm-tis']}]
-[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 27/36] qapi: More rigorous checking for type safety bypass
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (25 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 26/36] qapi: Whitelist commands that don't return dictionary Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-27 17:02   ` Eric Blake
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 28/36] qapi: Prefer 'struct' over 'type' in generator Eric Blake
                   ` (12 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Now that we have a way to validate every type, we can also be
stricter about enforcing that callers that want to bypass
type safety in generated code.  Prior to this patch, it didn't
matter what value was associated with the key 'gen', but it
looked odd that 'gen':'yes' could result in bypassing the
generated code.  These changes also enforce the changes made
earlier in the series for documentation and consolidation of
using '**' as the wildcard type, as well as 'gen':false as the
canonical spelling for requesting type bypass.

Note that 'gen':false is a one-way switch away from the default;
we do not support 'gen':true (similar for 'success-response).
In practice, this doesn't matter.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase onto earlier changes; enhance commit message
---
 scripts/qapi.py                            | 22 +++++++++++++++++-----
 tests/qapi-schema/type-bypass-bad-gen.err  |  1 +
 tests/qapi-schema/type-bypass-bad-gen.exit |  2 +-
 tests/qapi-schema/type-bypass-bad-gen.json |  2 +-
 tests/qapi-schema/type-bypass-bad-gen.out  |  3 ---
 tests/qapi-schema/type-bypass-no-gen.err   |  1 +
 tests/qapi-schema/type-bypass-no-gen.exit  |  2 +-
 tests/qapi-schema/type-bypass-no-gen.json  |  2 +-
 tests/qapi-schema/type-bypass-no-gen.out   |  3 ---
 9 files changed, 23 insertions(+), 15 deletions(-)

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 963f088..8c83de0 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -324,14 +324,15 @@ def check_name(expr_info, source, name, allow_optional = False,
                             "%s uses invalid name '%s'" % (source, name))

 def check_type(expr_info, source, value, allow_array = False,
-               allow_dict = False, allow_optional = False, allow_metas = []):
+               allow_dict = False, allow_optional = False,
+               allow_star = False, allow_metas = []):
     global all_names
     orig_value = value

     if value is None:
         return

-    if value == '**':
+    if allow_star and value == '**':
         return

     # Check if array type for value is okay
@@ -348,6 +349,10 @@ def check_type(expr_info, source, value, allow_array = False,

     # Check if type name for value is okay
     if isinstance(value, str):
+        if value == '**':
+            raise QAPIExprError(expr_info,
+                                "%s uses '**' but did not request 'gen':false"
+                                % source)
         if not value in all_names:
             raise QAPIExprError(expr_info,
                                 "%s uses unknown type '%s'"
@@ -371,19 +376,22 @@ def check_type(expr_info, source, value, allow_array = False,
         check_type(expr_info, "Member '%s' of %s" % (key, source), arg,
                    allow_array=True, allow_dict=True, allow_optional=True,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
-                                'enum'])
+                                'enum'], allow_star=allow_star)

 def check_command(expr, expr_info):
     name = expr['command']
+    allow_star = expr.has_key('gen')
+
     check_type(expr_info, "'data' for command '%s'" % name,
                expr.get('data'), allow_dict=True, allow_optional=True,
-               allow_metas=['union', 'struct'])
+               allow_metas=['union', 'struct'], allow_star=allow_star)
     returns_meta = ['union', 'struct']
     if name in returns_whitelist:
         returns_meta += ['built-in', 'alternate', 'enum']
     check_type(expr_info, "'returns' for command '%s'" % name,
                expr.get('returns'), allow_array=True, allow_dict=True,
-               allow_optional=True, allow_metas=returns_meta)
+               allow_optional=True, allow_metas=returns_meta,
+               allow_star=allow_star)

 def check_event(expr, expr_info):
     global events
@@ -579,6 +587,10 @@ def check_keys(expr_elem, meta, required, optional=[]):
             raise QAPIExprError(info,
                                 "Unknown key '%s' in %s '%s'"
                                 % (key, meta, name))
+        if (key == 'gen' or key == 'success-response') and value != False:
+            raise QAPIExprError(info,
+                                "'%s' of %s '%s' should only use false value"
+                                % (key, meta, name))
     for key in required:
         if not expr.has_key(key):
             raise QAPIExprError(info,
diff --git a/tests/qapi-schema/type-bypass-bad-gen.err b/tests/qapi-schema/type-bypass-bad-gen.err
index e69de29..a83c3c6 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.err
+++ b/tests/qapi-schema/type-bypass-bad-gen.err
@@ -0,0 +1 @@
+tests/qapi-schema/type-bypass-bad-gen.json:2: 'gen' of command 'foo' should only use false value
diff --git a/tests/qapi-schema/type-bypass-bad-gen.exit b/tests/qapi-schema/type-bypass-bad-gen.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.exit
+++ b/tests/qapi-schema/type-bypass-bad-gen.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/type-bypass-bad-gen.json b/tests/qapi-schema/type-bypass-bad-gen.json
index bb70bee..e8dec34 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.json
+++ b/tests/qapi-schema/type-bypass-bad-gen.json
@@ -1,2 +1,2 @@
-# FIXME: 'gen' should only appear with value false
+# 'gen' should only appear with value false
 { 'command': 'foo', 'gen': 'whatever' }
diff --git a/tests/qapi-schema/type-bypass-bad-gen.out b/tests/qapi-schema/type-bypass-bad-gen.out
index e678f2c..e69de29 100644
--- a/tests/qapi-schema/type-bypass-bad-gen.out
+++ b/tests/qapi-schema/type-bypass-bad-gen.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'foo'), ('gen', 'whatever')])]
-[]
-[]
diff --git a/tests/qapi-schema/type-bypass-no-gen.err b/tests/qapi-schema/type-bypass-no-gen.err
index e69de29..20cef0a 100644
--- a/tests/qapi-schema/type-bypass-no-gen.err
+++ b/tests/qapi-schema/type-bypass-no-gen.err
@@ -0,0 +1 @@
+tests/qapi-schema/type-bypass-no-gen.json:2: Member 'arg' of 'data' for command 'unsafe' uses '**' but did not request 'gen':false
diff --git a/tests/qapi-schema/type-bypass-no-gen.exit b/tests/qapi-schema/type-bypass-no-gen.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/type-bypass-no-gen.exit
+++ b/tests/qapi-schema/type-bypass-no-gen.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/type-bypass-no-gen.json b/tests/qapi-schema/type-bypass-no-gen.json
index af87c19..4feae37 100644
--- a/tests/qapi-schema/type-bypass-no-gen.json
+++ b/tests/qapi-schema/type-bypass-no-gen.json
@@ -1,2 +1,2 @@
-# FIXME: type bypass should only work with 'gen':false
+# type bypass only works with 'gen':false
 { 'command': 'unsafe', 'data': { 'arg': '**' }, 'returns': '**' }
diff --git a/tests/qapi-schema/type-bypass-no-gen.out b/tests/qapi-schema/type-bypass-no-gen.out
index 8b2a9ac..e69de29 100644
--- a/tests/qapi-schema/type-bypass-no-gen.out
+++ b/tests/qapi-schema/type-bypass-no-gen.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'unsafe'), ('data', OrderedDict([('arg', '**')])), ('returns', '**')])]
-[]
-[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 28/36] qapi: Prefer 'struct' over 'type' in generator
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (26 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 27/36] qapi: More rigorous checking for type safety bypass Eric Blake
@ 2015-04-05  4:07 ` Eric Blake
  2015-04-28 12:04   ` Markus Armbruster
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 29/36] qapi: Document 'struct' metatype Eric Blake
                   ` (11 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:07 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Referring to "type" as both a meta-type (built-in, enum, union,
alternte, or struct) and a specific type (the name that the
schema uses for declaring structs) is confusing.  The confusion
is only made worse by the fact that the generator mostly already
refers to struct even when dealing with expr['type'].  This
commit changes the generator to consistently refer to it as
struct everywhere, plus a single back-compat tweak that allows
accepting the existing .json files as-is, so that the meat of
this change is separate from the mindless churn of that change.

Fix the testsuite fallout for error messages that change, and
in some cases, become more legible.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: new patch
---
 scripts/qapi-types.py                              | 16 ++++----
 scripts/qapi-visit.py                              |  8 ++--
 scripts/qapi.py                                    | 42 +++++++++++++--------
 tests/qapi-schema/alternate-good.out               |  4 +-
 tests/qapi-schema/bad-ident.err                    |  2 +-
 tests/qapi-schema/bad-type-bool.err                |  2 +-
 tests/qapi-schema/data-member-array.out            |  4 +-
 tests/qapi-schema/double-type.err                  |  2 +-
 tests/qapi-schema/flat-union-base-star.err         |  2 +-
 tests/qapi-schema/flat-union-base-union.err        |  2 +-
 tests/qapi-schema/flat-union-branch-clash.out      | 12 +++---
 .../flat-union-invalid-discriminator.err           |  2 +-
 tests/qapi-schema/flat-union-reverse-define.out    | 12 +++---
 tests/qapi-schema/qapi-schema-test.out             | 44 +++++++++++-----------
 tests/qapi-schema/union-invalid-base.err           |  2 +-
 tests/qapi-schema/unknown-expr-key.err             |  2 +-
 16 files changed, 85 insertions(+), 73 deletions(-)

diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index 9c8d68c..a429d9e 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -83,7 +83,7 @@ def generate_struct_fields(members):

 def generate_struct(expr):

-    structname = expr.get('type', "")
+    structname = expr.get('struct', "")
     fieldname = expr.get('field', "")
     members = expr['data']
     base = expr.get('base')
@@ -394,8 +394,8 @@ fdecl.write(guardend("QAPI_TYPES_BUILTIN_STRUCT_DECL"))

 for expr in exprs:
     ret = "\n"
-    if expr.has_key('type'):
-        ret += generate_fwd_struct(expr['type'], expr['data'])
+    if expr.has_key('struct'):
+        ret += generate_fwd_struct(expr['struct'], expr['data'])
     elif expr.has_key('enum'):
         ret += generate_enum(expr['enum'], expr['data']) + "\n"
         ret += generate_fwd_enum_struct(expr['enum'], expr['data'])
@@ -435,12 +435,12 @@ if do_builtins:

 for expr in exprs:
     ret = "\n"
-    if expr.has_key('type'):
+    if expr.has_key('struct'):
         ret += generate_struct(expr) + "\n"
-        ret += generate_type_cleanup_decl(expr['type'] + "List")
-        fdef.write(generate_type_cleanup(expr['type'] + "List") + "\n")
-        ret += generate_type_cleanup_decl(expr['type'])
-        fdef.write(generate_type_cleanup(expr['type']) + "\n")
+        ret += generate_type_cleanup_decl(expr['struct'] + "List")
+        fdef.write(generate_type_cleanup(expr['struct'] + "List") + "\n")
+        ret += generate_type_cleanup_decl(expr['struct'])
+        fdef.write(generate_type_cleanup(expr['struct']) + "\n")
     elif expr.has_key('union'):
         ret += generate_union(expr, 'union')
         ret += generate_type_cleanup_decl(expr['union'] + "List")
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 3e11089..eeeca82 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -178,7 +178,7 @@ def generate_visit_struct_body(field_prefix, name, members):

 def generate_visit_struct(expr):

-    name = expr['type']
+    name = expr['struct']
     members = expr['data']
     base = expr.get('base')

@@ -549,12 +549,12 @@ if do_builtins:
         fdef.write(generate_visit_list(typename, None))

 for expr in exprs:
-    if expr.has_key('type'):
+    if expr.has_key('struct'):
         ret = generate_visit_struct(expr)
-        ret += generate_visit_list(expr['type'], expr['data'])
+        ret += generate_visit_list(expr['struct'], expr['data'])
         fdef.write(ret)

-        ret = generate_declaration(expr['type'], expr['data'])
+        ret = generate_declaration(expr['struct'], expr['data'])
         fdecl.write(ret)
     elif expr.has_key('union'):
         ret = generate_visit_union(expr)
diff --git a/scripts/qapi.py b/scripts/qapi.py
index 8c83de0..c246570 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -419,7 +419,7 @@ def check_union(expr, expr_info):
     members = expr['data']
     values = { 'MAX': '(automatic)' }

-    # If the object has a member 'base', its value must name a complex type,
+    # If the object has a member 'base', its value must name a struct,
     # and there must be a discriminator.
     if base is not None:
         if discriminator is None:
@@ -448,18 +448,18 @@ def check_union(expr, expr_info):
         base_fields = find_base_fields(base)
         if not base_fields:
             raise QAPIExprError(expr_info,
-                                "Base '%s' is not a valid type"
+                                "Base '%s' is not a valid struct"
                                 % base)

         # The value of member 'discriminator' must name a non-optional
-        # member of the base type.
+        # member of the base struct.
         check_name(expr_info, "Discriminator of flat union '%s'" % name,
                    discriminator)
         discriminator_type = base_fields.get(discriminator)
         if not discriminator_type:
             raise QAPIExprError(expr_info,
                                 "Discriminator '%s' is not a member of base "
-                                "type '%s'"
+                                "struct '%s'"
                                 % (discriminator, base))
         enum_define = find_enum(discriminator_type)
         allow_metas=['struct']
@@ -546,7 +546,7 @@ def check_enum(expr, expr_info):
         values[key] = member

 def check_struct(expr, expr_info):
-    name = expr['type']
+    name = expr['struct']
     members = expr['data']

     check_type(expr_info, "'data' for type '%s'" % name, members,
@@ -565,7 +565,7 @@ def check_exprs(schema):
             check_union(expr, info)
         elif expr.has_key('alternate'):
             check_alternate(expr, info)
-        elif expr.has_key('type'):
+        elif expr.has_key('struct'):
             check_struct(expr, info)
         elif expr.has_key('command'):
             check_command(expr, info)
@@ -617,6 +617,20 @@ def parse_schema(input_file):
         for expr_elem in schema.exprs:
             expr = expr_elem['expr']
             info = expr_elem['info']
+
+            # back-compat hack until all schemas have been converted;
+            # preserve the ordering of the original expression
+            if expr.has_key('type'):
+                seen_type = False
+                for (key, value) in expr.items():
+                    if key == 'type':
+                        seen_type = True
+                        del expr['type']
+                        expr['struct'] = value
+                    elif seen_type:
+                        del expr[key]
+                        expr[key] = value
+
             if expr.has_key('enum'):
                 check_keys(expr_elem, 'enum', ['data'])
                 add_enum(expr['enum'], info, expr['data'])
@@ -627,8 +641,8 @@ def parse_schema(input_file):
             elif expr.has_key('alternate'):
                 check_keys(expr_elem, 'alternate', ['data'])
                 add_name(expr['alternate'], info, 'alternate')
-            elif expr.has_key('type'):
-                check_keys(expr_elem, 'type', ['data'], ['base'])
+            elif expr.has_key('struct'):
+                check_keys(expr_elem, 'struct', ['data'], ['base'])
                 add_struct(expr, info)
             elif expr.has_key('command'):
                 check_keys(expr_elem, 'command', [],
@@ -745,11 +759,9 @@ def type_name(name):
         return c_list_type(name[0])
     return name

-def add_name(name, info, meta, implicit = False, source = None):
+def add_name(name, info, meta, implicit = False):
     global all_names
-    if not source:
-        source = "'%s'" % meta
-    check_name(info, source, name)
+    check_name(info, "'%s'" % meta, name)
     if name in all_names:
         raise QAPIExprError(info,
                             "%s '%s' is already defined"
@@ -762,14 +774,14 @@ def add_name(name, info, meta, implicit = False, source = None):

 def add_struct(definition, info):
     global struct_types
-    name = definition['type']
-    add_name(name, info, 'struct', source="'type'")
+    name = definition['struct']
+    add_name(name, info, 'struct')
     struct_types.append(definition)

 def find_struct(name):
     global struct_types
     for struct in struct_types:
-        if struct['type'] == name:
+        if struct['struct'] == name:
             return struct
     return None

diff --git a/tests/qapi-schema/alternate-good.out b/tests/qapi-schema/alternate-good.out
index c3a6b77..99848ee 100644
--- a/tests/qapi-schema/alternate-good.out
+++ b/tests/qapi-schema/alternate-good.out
@@ -1,6 +1,6 @@
-[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]),
+[OrderedDict([('struct', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))]),
  OrderedDict([('enum', 'Enum'), ('data', ['hello', 'world'])]),
  OrderedDict([('alternate', 'Alt'), ('data', OrderedDict([('value', 'int'), ('string', 'Enum'), ('struct', 'Data')]))])]
 [{'enum_name': 'Enum', 'enum_values': ['hello', 'world']},
  {'enum_name': 'AltKind', 'enum_values': None}]
-[OrderedDict([('type', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])]
+[OrderedDict([('struct', 'Data'), ('data', OrderedDict([('*number', 'int'), ('*name', 'str')]))])]
diff --git a/tests/qapi-schema/bad-ident.err b/tests/qapi-schema/bad-ident.err
index 42b490c..c419060 100644
--- a/tests/qapi-schema/bad-ident.err
+++ b/tests/qapi-schema/bad-ident.err
@@ -1 +1 @@
-tests/qapi-schema/bad-ident.json:2: 'type' does not allow optional name '*oops'
+tests/qapi-schema/bad-ident.json:2: 'struct' does not allow optional name '*oops'
diff --git a/tests/qapi-schema/bad-type-bool.err b/tests/qapi-schema/bad-type-bool.err
index de6168c..62fd70b 100644
--- a/tests/qapi-schema/bad-type-bool.err
+++ b/tests/qapi-schema/bad-type-bool.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-bool.json:2: 'type' key must have a string value
+tests/qapi-schema/bad-type-bool.json:2: 'struct' key must have a string value
diff --git a/tests/qapi-schema/data-member-array.out b/tests/qapi-schema/data-member-array.out
index 8287120..c39fa25 100644
--- a/tests/qapi-schema/data-member-array.out
+++ b/tests/qapi-schema/data-member-array.out
@@ -1,5 +1,5 @@
 [OrderedDict([('enum', 'abc'), ('data', ['a', 'b', 'c'])]),
- OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))]),
+ OrderedDict([('struct', 'def'), ('data', OrderedDict([('array', ['abc'])]))]),
  OrderedDict([('command', 'okay'), ('data', OrderedDict([('member1', ['int']), ('member2', ['def'])]))])]
 [{'enum_name': 'abc', 'enum_values': ['a', 'b', 'c']}]
-[OrderedDict([('type', 'def'), ('data', OrderedDict([('array', ['abc'])]))])]
+[OrderedDict([('struct', 'def'), ('data', OrderedDict([('array', ['abc'])]))])]
diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err
index ceb6e46..f9613c6 100644
--- a/tests/qapi-schema/double-type.err
+++ b/tests/qapi-schema/double-type.err
@@ -1 +1 @@
-tests/qapi-schema/double-type.json:2: Unknown key 'command' in type 'bar'
+tests/qapi-schema/double-type.json:2: Unknown key 'command' in struct 'bar'
diff --git a/tests/qapi-schema/flat-union-base-star.err b/tests/qapi-schema/flat-union-base-star.err
index 60e47ef..b7748f0 100644
--- a/tests/qapi-schema/flat-union-base-star.err
+++ b/tests/qapi-schema/flat-union-base-star.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-base-star.json:8: Base '**' is not a valid type
+tests/qapi-schema/flat-union-base-star.json:8: Base '**' is not a valid struct
diff --git a/tests/qapi-schema/flat-union-base-union.err b/tests/qapi-schema/flat-union-base-union.err
index 185bf51..ede9859 100644
--- a/tests/qapi-schema/flat-union-base-union.err
+++ b/tests/qapi-schema/flat-union-base-union.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid type
+tests/qapi-schema/flat-union-base-union.json:11: Base 'UnionBase' is not a valid struct
diff --git a/tests/qapi-schema/flat-union-branch-clash.out b/tests/qapi-schema/flat-union-branch-clash.out
index 5d54113..04c2395 100644
--- a/tests/qapi-schema/flat-union-branch-clash.out
+++ b/tests/qapi-schema/flat-union-branch-clash.out
@@ -1,9 +1,9 @@
 [OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
- OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
- OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]),
+ OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
+ OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]),
  OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'Branch1'), ('value2', 'Branch2')]))])]
 [{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}]
-[OrderedDict([('type', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
- OrderedDict([('type', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('type', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])]
+[OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
+ OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
+ OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])]
diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.err b/tests/qapi-schema/flat-union-invalid-discriminator.err
index 790b675..5f40556 100644
--- a/tests/qapi-schema/flat-union-invalid-discriminator.err
+++ b/tests/qapi-schema/flat-union-invalid-discriminator.err
@@ -1 +1 @@
-tests/qapi-schema/flat-union-invalid-discriminator.json:13: Discriminator 'enum_wrong' is not a member of base type 'TestBase'
+tests/qapi-schema/flat-union-invalid-discriminator.json:13: Discriminator 'enum_wrong' is not a member of base struct 'TestBase'
diff --git a/tests/qapi-schema/flat-union-reverse-define.out b/tests/qapi-schema/flat-union-reverse-define.out
index 03c952e..1ed7b8a 100644
--- a/tests/qapi-schema/flat-union-reverse-define.out
+++ b/tests/qapi-schema/flat-union-reverse-define.out
@@ -1,9 +1,9 @@
 [OrderedDict([('union', 'TestUnion'), ('base', 'TestBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'TestTypeA'), ('value2', 'TestTypeB')]))]),
- OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
+ OrderedDict([('struct', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
  OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
- OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
+ OrderedDict([('struct', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('struct', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
 [{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}]
-[OrderedDict([('type', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
- OrderedDict([('type', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
- OrderedDict([('type', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
+[OrderedDict([('struct', 'TestBase'), ('data', OrderedDict([('enum1', 'TestEnum')]))]),
+ OrderedDict([('struct', 'TestTypeA'), ('data', OrderedDict([('string', 'str')]))]),
+ OrderedDict([('struct', 'TestTypeB'), ('data', OrderedDict([('integer', 'int')]))])]
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 313ecf3..83ab1a5 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -1,13 +1,13 @@
 [OrderedDict([('enum', 'EnumOne'), ('data', ['value1', 'value2', 'value3'])]),
- OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
- OrderedDict([('type', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('type', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
- OrderedDict([('type', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
- OrderedDict([('type', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]),
- OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
- OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
- OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]),
+ OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
+ OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
+ OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
+ OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
+ OrderedDict([('struct', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]),
  OrderedDict([('union', 'UserDefFlatUnion'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefA'), ('value2', 'UserDefB'), ('value3', 'UserDefB')]))]),
  OrderedDict([('union', 'UserDefFlatUnion2'), ('base', 'UserDefUnionBase'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'UserDefC'), ('value2', 'UserDefB'), ('value3', 'UserDefA')]))]),
  OrderedDict([('alternate', 'UserDefAlternate'), ('data', OrderedDict([('uda', 'UserDefA'), ('s', 'str'), ('i', 'int')]))]),
@@ -16,8 +16,8 @@
  OrderedDict([('command', 'user_def_cmd1'), ('data', OrderedDict([('ud1a', 'UserDefOne')]))]),
  OrderedDict([('command', 'user_def_cmd2'), ('data', OrderedDict([('ud1a', 'UserDefOne'), ('*ud1b', 'UserDefOne')])), ('returns', 'UserDefTwo')]),
  OrderedDict([('command', 'user_def_cmd3'), ('data', OrderedDict([('a', 'int'), ('*b', 'int')])), ('returns', 'int')]),
- OrderedDict([('type', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]),
- OrderedDict([('type', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))]),
+ OrderedDict([('struct', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]),
+ OrderedDict([('struct', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))]),
  OrderedDict([('event', 'EVENT_A')]),
  OrderedDict([('event', 'EVENT_B'), ('data', OrderedDict())]),
  OrderedDict([('event', 'EVENT_C'), ('data', OrderedDict([('*a', 'int'), ('*b', 'UserDefOne'), ('c', 'str')]))]),
@@ -25,14 +25,14 @@
 [{'enum_name': 'EnumOne', 'enum_values': ['value1', 'value2', 'value3']},
  {'enum_name': 'UserDefAlternateKind', 'enum_values': None},
  {'enum_name': 'UserDefNativeListUnionKind', 'enum_values': None}]
-[OrderedDict([('type', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
- OrderedDict([('type', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('type', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
- OrderedDict([('type', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
- OrderedDict([('type', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]),
- OrderedDict([('type', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
- OrderedDict([('type', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
- OrderedDict([('type', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
- OrderedDict([('type', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]),
- OrderedDict([('type', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]),
- OrderedDict([('type', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))])]
+[OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
+ OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
+ OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
+ OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
+ OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
+ OrderedDict([('struct', 'UserDefUnionBase'), ('data', OrderedDict([('string', 'str'), ('enum1', 'EnumOne')]))]),
+ OrderedDict([('struct', 'UserDefOptions'), ('data', OrderedDict([('*i64', ['int']), ('*u64', ['uint64']), ('*u16', ['uint16']), ('*i64x', 'int'), ('*u64x', 'uint64')]))]),
+ OrderedDict([('struct', 'EventStructOne'), ('data', OrderedDict([('struct1', 'UserDefOne'), ('string', 'str'), ('*enum2', 'EnumOne')]))])]
diff --git a/tests/qapi-schema/union-invalid-base.err b/tests/qapi-schema/union-invalid-base.err
index 3cc82c0..9f63796 100644
--- a/tests/qapi-schema/union-invalid-base.err
+++ b/tests/qapi-schema/union-invalid-base.err
@@ -1 +1 @@
-tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid type
+tests/qapi-schema/union-invalid-base.json:8: Base 'int' is not a valid struct
diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err
index 0a35bfd..12f5ed5 100644
--- a/tests/qapi-schema/unknown-expr-key.err
+++ b/tests/qapi-schema/unknown-expr-key.err
@@ -1 +1 @@
-tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in type 'bar'
+tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in struct 'bar'
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 29/36] qapi: Document 'struct' metatype
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (27 preceding siblings ...)
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 28/36] qapi: Prefer 'struct' over 'type' in generator Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-28 12:12   ` Markus Armbruster
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 30/36] qapi: Use 'struct' instead of 'type' in schema Eric Blake
                   ` (10 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Referring to "type" as both a meta-type (built-in, enum, union,
alternte, or struct) and a specific type (the name that the
schema uses for declaring structs) is confusing.  Now that the
generator accepts 'struct' as a synonym for 'type', update all
documentation to use saner wording.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: new patch
---
 docs/qapi-code-gen.txt | 56 +++++++++++++++++++++++++-------------------------
 1 file changed, 28 insertions(+), 28 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 8a76cc1..6206032 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -71,7 +71,7 @@ x.y.z)' comment.  For example:
     #
     # Since: 0.14.0
     ##
-    { 'type': 'BlockStats',
+    { 'struct': 'BlockStats',
       'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
                '*parent': 'BlockStats',
                '*backing': 'BlockStats'} }
@@ -85,7 +85,7 @@ types, and allows for indefinite nesting of QMP that satisfies the
 schema.  A type name should not be defined more than once.

 There are seven top-level expressions recognized by the parser:
-'include', 'command', 'type', 'enum', 'union', 'alternate', and
+'include', 'command', 'struct', 'enum', 'union', 'alternate', and
 'event'.  There are several groups of types: simple types (a number of
 built-in types, such as 'int' and 'str'; as well as enumerations),
 complex types (structs and two flavors of unions), and alternate types
@@ -127,9 +127,9 @@ In the rest of this document, usage lines are given for each
 expression type, with literal strings written in lower case and
 placeholders written in capitals.  If a literal string includes a
 prefix of '*', that key/value pair can be omitted from the expression.
-For example, a usage statement that includes '*base':COMPLEX-TYPE-NAME
+For example, a usage statement that includes '*base':STRUCT-NAME
 means that an expression has an optional key 'base', which if present
-must have a value that forms a complex type name.
+must have a value that forms a struct name.


 === Built-in Types ===
@@ -167,17 +167,17 @@ an outer file.  The parser may be made stricter in the future to
 prevent incomplete include files.


-=== Complex types ===
+=== Struct types ===

-Usage: { 'type': STRING, 'data': DICT, '*base': COMPLEX-TYPE-NAME }
+Usage: { 'struct': STRING, 'data': DICT, '*base': STRUCT-NAME }

-A complex type is a dictionary containing a single 'data' key whose
+A struct is a dictionary containing a single 'data' key whose
 value is a dictionary.  This corresponds to a struct in C or an Object
 in JSON. Each value of the 'data' dictionary must be the name of a
 type, or a one-element array containing a type name.  An example of a
-complex type is:
+struct is:

- { 'type': 'MyType',
+ { 'struct': 'MyType',
    'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } }

 The use of '*' as a prefix to the name means the member is optional in
@@ -210,13 +210,13 @@ A structure that is used in both input and output of various commands
 must consider the backwards compatibility constraints of both directions
 of use.

-A complex type definition can specify another complex type as its base.
+A struct definition can specify another struct as its base.
 In this case, the fields of the base type are included as top-level fields
-of the new complex type's dictionary in the QMP wire format. An example
+of the new struct's dictionary in the QMP wire format. An example
 definition is:

- { 'type': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } }
- { 'type': 'BlockdevOptionsGenericCOWFormat',
+ { 'struct': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } }
+ { 'struct': 'BlockdevOptionsGenericCOWFormat',
    'base': 'BlockdevOptionsGenericFormat',
    'data': { '*backing': 'str' } }

@@ -251,7 +251,7 @@ converting between strings and enum values.  Since the wire format
 always passes by name, it is acceptable to reorder or add new
 enumeration members in any location without breaking QMP clients;
 however, removing enum values would break compatibility.  For any
-complex type that has a field that will only contain a finite set of
+struct that has a field that will only contain a finite set of
 string values, using an enum type for that field is better than
 open-coding the field to be type 'str'.

@@ -259,7 +259,7 @@ open-coding the field to be type 'str'.
 === Union types ===

 Usage: { 'union': STRING, 'data': DICT }
-or:    { 'union': STRING, 'data': DICT, 'base': COMPLEX-TYPE-NAME,
+or:    { 'union': STRING, 'data': DICT, 'base': STRUCT-NAME,
          'discriminator': ENUM-MEMBER-OF-BASE }

 Union types are used to let the user choose between several different
@@ -271,8 +271,8 @@ paragraphs.
 A simple union type defines a mapping from automatic discriminator
 values to data types like in this example:

- { 'type': 'FileOptions', 'data': { 'filename': 'str' } }
- { 'type': 'Qcow2Options',
+ { 'struct': 'FileOptions', 'data': { 'filename': 'str' } }
+ { 'struct': 'Qcow2Options',
    'data': { 'backing-file': 'str', 'lazy-refcounts': 'bool' } }

  { 'union': 'BlockdevOptions',
@@ -295,13 +295,13 @@ the union can be named 'max', as this would collide with the implicit
 enum.  The value for each branch can be of any type.


-A flat union definition specifies a complex type as its base, and
+A flat union definition specifies a struct as its base, and
 avoids nesting on the wire.  All branches of the union must be
 complex types, and the top-level fields of the union dictionary on
 the wire will be combination of fields from both the base type and the
 appropriate branch type (when merging two dictionaries, there must be
 no keys in common).  The 'discriminator' field must be the name of an
-enum-typed member of the base type.
+enum-typed member of the base struct.

 The following example enhances the above simple union example by
 adding a common field 'readonly', renaming the discriminator to
@@ -309,7 +309,7 @@ something more applicable, and reducing the number of {} required on
 the wire:

  { 'enum': 'BlockdevDriver', 'data': [ 'raw', 'qcow2' ] }
- { 'type': 'BlockdevCommonOptions',
+ { 'struct': 'BlockdevCommonOptions',
    'data': { 'driver': 'BlockdevDriver', 'readonly': 'bool' } }
  { 'union': 'BlockdevOptions',
    'base': 'BlockdevCommonOptions',
@@ -341,9 +341,9 @@ union has a complex type with a single member named 'data'.  That is,
 is identical on the wire to:

  { 'enum': 'Enum', 'data': ['one', 'two'] }
- { 'type': 'Base', 'data': { 'type': 'Enum' } }
- { 'type': 'Branch1', 'data': { 'data': 'str' } }
- { 'type': 'Branch2', 'data': { 'data': 'int' } }
+ { 'struct': 'Base', 'data': { 'type': 'Enum' } }
+ { 'struct': 'Branch1', 'data': { 'data': 'str' } }
+ { 'struct': 'Branch2', 'data': { 'data': 'int' } }
  { 'union': 'Flat': 'base': 'Base', 'discriminator': 'type',
    'data': { 'one': 'Branch1', 'two': 'Branch2' } }

@@ -401,7 +401,7 @@ part of a QMP command.  The 'data' member is optional and defaults to
 {} (an empty dictionary).  If present, it must be the string name of a
 complex type, a one-element array containing the name of a complex
 type, or a dictionary that declares an anonymous type with the same
-semantics as a 'type' expression, with one exception noted below when
+semantics as a 'struct' expression, with one exception noted below when
 'gen' is used.

 The 'returns' member describes what will appear in the "return" field
@@ -410,7 +410,7 @@ optional from the command declaration; if absent, the "return" field
 will be an empty dictionary.  If 'returns' is present, it must be the
 string name of a complex or built-in type, a one-element array
 containing the name of a complex or built-in type, or a dictionary
-that declares an anonymous type with the same semantics as a 'type'
+that declares an anonymous type with the same semantics as a 'struct'
 expression, with one exception noted below when 'gen' is used.
 Although it is permitted to have the 'returns' member name a built-in
 type or an array of built-in types, any command that does this cannot
@@ -429,7 +429,7 @@ Some example commands:

  { 'command': 'my-first-command',
    'data': { 'arg1': 'str', '*arg2': 'str' } }
- { 'type': 'MyType', 'data': { '*value': 'str' } }
+ { 'struct': 'MyType', 'data': { '*value': 'str' } }
  { 'command': 'my-second-command',
    'returns': [ 'MyType' ] }

@@ -473,7 +473,7 @@ Events are defined with the keyword 'event'.  It is not allowed to
 name an event 'MAX', since the generator also produces a C enumeration
 of all event names with a generated _MAX value at the end.  When
 'data' is also specified, additional info will be included in the
-event, with similar semantics to a 'type' expression.  Finally there
+event, with similar semantics to a 'struct' expression.  Finally there
 will be C API generated in qapi-event.h; when called by QEMU code, a
 message with timestamp will be emitted on the wire.

@@ -504,7 +504,7 @@ case we want to accept/return a list of this type with a command), and a
 command which takes that type as a parameter and returns the same type:

     $ cat example-schema.json
-    { 'type': 'UserDefOne',
+    { 'struct': 'UserDefOne',
       'data': { 'integer': 'int', 'string': 'str' } }

     { 'command': 'my-command',
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 30/36] qapi: Use 'struct' instead of 'type' in schema
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (28 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 29/36] qapi: Document 'struct' metatype Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-28 12:23   ` Markus Armbruster
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 31/36] qapi: Merge UserDefTwo and UserDefNested in tests Eric Blake
                   ` (9 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Referring to "type" as both a meta-type (built-in, enum, union,
alternte, or struct) and a specific type (the name that the
schema uses for declaring structs) is confusing.  Finish up the
conversion to using "struct" in qapi schema by coverting all
users, then remove the hack in the generator that allowed 'type'.
Tweak a couple of tests whose output changes slightly due to
longer lines.  The json files were changed with:

for f in `find -name '*.json'; do sed -i "s/'type'/'struct'/"; done

followed by manually filtering out the places where we have a
'type' embedded in 'data'.  I also verified that the generated
files for QMP and QGA (such as qmp-commands.h) are the same
before and after, as assurance that I didn't leave in any
accidental member name changes.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: new patch
---
 qapi-schema.json                                   | 166 ++++++++++-----------
 qapi/block-core.json                               |  62 ++++----
 qapi/block.json                                    |   2 +-
 qapi/common.json                                   |   4 +-
 qapi/trace.json                                    |   2 +-
 qga/qapi-schema.json                               |  28 ++--
 scripts/qapi.py                                    |  14 --
 tests/qapi-schema/alternate-array.json             |   2 +-
 tests/qapi-schema/alternate-base.json              |   2 +-
 tests/qapi-schema/alternate-conflict-dict.json     |   4 +-
 tests/qapi-schema/alternate-good.json              |   2 +-
 tests/qapi-schema/bad-base.json                    |   2 +-
 tests/qapi-schema/bad-ident.json                   |   2 +-
 tests/qapi-schema/bad-type-bool.json               |   2 +-
 tests/qapi-schema/bad-type-int.err                 |   2 +-
 tests/qapi-schema/bad-type-int.json                |   2 +-
 tests/qapi-schema/data-member-array.json           |   2 +-
 tests/qapi-schema/double-data.err                  |   2 +-
 tests/qapi-schema/double-data.json                 |   2 +-
 tests/qapi-schema/double-type.json                 |   2 +-
 tests/qapi-schema/flat-union-bad-base.json         |   4 +-
 .../qapi-schema/flat-union-bad-discriminator.json  |   6 +-
 tests/qapi-schema/flat-union-base-star.json        |   4 +-
 tests/qapi-schema/flat-union-base-union.json       |   4 +-
 tests/qapi-schema/flat-union-branch-clash.json     |   6 +-
 tests/qapi-schema/flat-union-inline.json           |   2 +-
 tests/qapi-schema/flat-union-int-branch.json       |   4 +-
 .../qapi-schema/flat-union-invalid-branch-key.json |   6 +-
 .../flat-union-invalid-discriminator.json          |   6 +-
 tests/qapi-schema/flat-union-no-base.json          |   4 +-
 .../flat-union-optional-discriminator.json         |   4 +-
 tests/qapi-schema/flat-union-reverse-define.json   |   6 +-
 .../flat-union-string-discriminator.json           |   6 +-
 tests/qapi-schema/qapi-schema-test.json            |  22 +--
 tests/qapi-schema/redefined-builtin.json           |   2 +-
 tests/qapi-schema/redefined-type.json              |   2 +-
 tests/qapi-schema/union-bad-branch.json            |   4 +-
 tests/qapi-schema/union-base-no-discriminator.json |   6 +-
 tests/qapi-schema/union-invalid-base.json          |   4 +-
 tests/qapi-schema/unknown-expr-key.json            |   2 +-
 40 files changed, 198 insertions(+), 212 deletions(-)

diff --git a/qapi-schema.json b/qapi-schema.json
index 7f4cf86..6a4e0df 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -71,7 +71,7 @@
 #
 # Since 0.14.0
 ##
-{ 'type': 'NameInfo', 'data': {'*name': 'str'} }
+{ 'struct': 'NameInfo', 'data': {'*name': 'str'} }

 ##
 # @query-name:
@@ -95,7 +95,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} }
+{ 'struct': 'KvmInfo', 'data': {'enabled': 'bool', 'present': 'bool'} }

 ##
 # @query-kvm:
@@ -170,7 +170,7 @@
 #
 # Notes: @singlestep is enabled through the GDB stub
 ##
-{ 'type': 'StatusInfo',
+{ 'struct': 'StatusInfo',
   'data': {'running': 'bool', 'singlestep': 'bool', 'status': 'RunState'} }

 ##
@@ -195,7 +195,7 @@
 #
 # Notes: If no UUID was specified for the guest, a null UUID is returned.
 ##
-{ 'type': 'UuidInfo', 'data': {'UUID': 'str'} }
+{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} }

 ##
 # @query-uuid:
@@ -226,7 +226,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'ChardevInfo', 'data': {'label': 'str',
+{ 'struct': 'ChardevInfo', 'data': {'label': 'str',
                                   'filename': 'str',
                                   'frontend-open': 'bool'} }

@@ -250,7 +250,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'ChardevBackendInfo', 'data': {'name': 'str'} }
+{ 'struct': 'ChardevBackendInfo', 'data': {'name': 'str'} }

 ##
 # @query-chardev-backends:
@@ -339,7 +339,7 @@
 #
 # Since: 1.2.0
 ##
-{ 'type': 'EventInfo', 'data': {'name': 'str'} }
+{ 'struct': 'EventInfo', 'data': {'name': 'str'} }

 ##
 # @query-events:
@@ -380,7 +380,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'MigrationStats',
+{ 'struct': 'MigrationStats',
   'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' ,
            'duplicate': 'int', 'skipped': 'int', 'normal': 'int',
            'normal-bytes': 'int', 'dirty-pages-rate' : 'int',
@@ -405,7 +405,7 @@
 #
 # Since: 1.2
 ##
-{ 'type': 'XBZRLECacheStats',
+{ 'struct': 'XBZRLECacheStats',
   'data': {'cache-size': 'int', 'bytes': 'int', 'pages': 'int',
            'cache-miss': 'int', 'cache-miss-rate': 'number',
            'overflow': 'int' } }
@@ -476,7 +476,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'MigrationInfo',
+{ 'struct': 'MigrationInfo',
   'data': {'*status': 'MigrationStatus', '*ram': 'MigrationStats',
            '*disk': 'MigrationStats',
            '*xbzrle-cache': 'XBZRLECacheStats',
@@ -534,7 +534,7 @@
 #
 # Since: 1.2
 ##
-{ 'type': 'MigrationCapabilityStatus',
+{ 'struct': 'MigrationCapabilityStatus',
   'data': { 'capability' : 'MigrationCapability', 'state' : 'bool' } }

 ##
@@ -575,7 +575,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'MouseInfo',
+{ 'struct': 'MouseInfo',
   'data': {'name': 'str', 'index': 'int', 'current': 'bool',
            'absolute': 'bool'} }

@@ -621,7 +621,7 @@
 # Notes: @halted is a transient state that changes frequently.  By the time the
 #        data is sent to the client, the guest may no longer be halted.
 ##
-{ 'type': 'CpuInfo',
+{ 'struct': 'CpuInfo',
   'data': {'CPU': 'int', 'current': 'bool', 'halted': 'bool', '*pc': 'int',
            '*nip': 'int', '*npc': 'int', '*PC': 'int', 'thread_id': 'int'} }

@@ -647,7 +647,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'IOThreadInfo',
+{ 'struct': 'IOThreadInfo',
   'data': {'id': 'str', 'thread-id': 'int'} }

 ##
@@ -700,7 +700,7 @@
 #
 # Since: 2.1
 ##
-{ 'type': 'VncBasicInfo',
+{ 'struct': 'VncBasicInfo',
   'data': { 'host': 'str',
             'service': 'str',
             'family': 'NetworkAddressFamily',
@@ -715,7 +715,7 @@
 #
 # Since: 2.1
 ##
-{ 'type': 'VncServerInfo',
+{ 'struct': 'VncServerInfo',
   'base': 'VncBasicInfo',
   'data': { '*auth': 'str' } }

@@ -732,7 +732,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'VncClientInfo',
+{ 'struct': 'VncClientInfo',
   'base': 'VncBasicInfo',
   'data': { '*x509_dname': 'str', '*sasl_username': 'str' } }

@@ -772,7 +772,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'VncInfo',
+{ 'struct': 'VncInfo',
   'data': {'enabled': 'bool', '*host': 'str',
            '*family': 'NetworkAddressFamily',
            '*service': 'str', '*auth': 'str', '*clients': ['VncClientInfo']} }
@@ -826,7 +826,7 @@
 #
 # Since: 2.3
 ##
-{ 'type': 'VncInfo2',
+{ 'struct': 'VncInfo2',
   'data': { 'id'        : 'str',
             'server'    : ['VncBasicInfo'],
             'clients'   : ['VncClientInfo'],
@@ -869,7 +869,7 @@
 #
 # Since: 2.1
 ##
-{ 'type': 'SpiceBasicInfo',
+{ 'struct': 'SpiceBasicInfo',
   'data': { 'host': 'str',
             'port': 'str',
             'family': 'NetworkAddressFamily' } }
@@ -883,7 +883,7 @@
 #
 # Since: 2.1
 ##
-{ 'type': 'SpiceServerInfo',
+{ 'struct': 'SpiceServerInfo',
   'base': 'SpiceBasicInfo',
   'data': { '*auth': 'str' } }

@@ -907,7 +907,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'SpiceChannel',
+{ 'struct': 'SpiceChannel',
   'base': 'SpiceBasicInfo',
   'data': {'connection-id': 'int', 'channel-type': 'int', 'channel-id': 'int',
            'tls': 'bool'} }
@@ -965,7 +965,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'SpiceInfo',
+{ 'struct': 'SpiceInfo',
   'data': {'enabled': 'bool', 'migrated': 'bool', '*host': 'str', '*port': 'int',
            '*tls-port': 'int', '*auth': 'str', '*compiled-version': 'str',
            'mouse-mode': 'SpiceQueryMouseMode', '*channels': ['SpiceChannel']} }
@@ -991,7 +991,7 @@
 # Since: 0.14.0
 #
 ##
-{ 'type': 'BalloonInfo', 'data': {'actual': 'int' } }
+{ 'struct': 'BalloonInfo', 'data': {'actual': 'int' } }

 ##
 # @query-balloon:
@@ -1018,7 +1018,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'PciMemoryRange', 'data': {'base': 'int', 'limit': 'int'} }
+{ 'struct': 'PciMemoryRange', 'data': {'base': 'int', 'limit': 'int'} }

 ##
 # @PciMemoryRegion
@@ -1036,7 +1036,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'PciMemoryRegion',
+{ 'struct': 'PciMemoryRegion',
   'data': {'bar': 'int', 'type': 'str', 'address': 'int', 'size': 'int',
            '*prefetch': 'bool', '*mem_type_64': 'bool' } }

@@ -1065,7 +1065,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'PciBridgeInfo',
+{ 'struct': 'PciBridgeInfo',
   'data': {'bus': { 'number': 'int', 'secondary': 'int', 'subordinate': 'int',
                     'io_range': 'PciMemoryRange',
                     'memory_range': 'PciMemoryRange',
@@ -1104,7 +1104,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'PciDeviceInfo',
+{ 'struct': 'PciDeviceInfo',
   'data': {'bus': 'int', 'slot': 'int', 'function': 'int',
            'class_info': {'*desc': 'str', 'class': 'int'},
            'id': {'device': 'int', 'vendor': 'int'},
@@ -1122,7 +1122,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'PciInfo', 'data': {'bus': 'int', 'devices': ['PciDeviceInfo']} }
+{ 'struct': 'PciInfo', 'data': {'bus': 'int', 'devices': ['PciDeviceInfo']} }

 ##
 # @query-pci:
@@ -1341,7 +1341,7 @@
 #
 # Since: 1.6
 ###
-{ 'type': 'Abort',
+{ 'struct': 'Abort',
   'data': { } }

 ##
@@ -1506,7 +1506,7 @@
 #
 # Since: 1.2
 ##
-{ 'type': 'ObjectPropertyInfo',
+{ 'struct': 'ObjectPropertyInfo',
   'data': { 'name': 'str', 'type': 'str' } }

 ##
@@ -1691,7 +1691,7 @@
 #
 # Notes: This command is experimental and may change syntax in future releases.
 ##
-{ 'type': 'ObjectTypeInfo',
+{ 'struct': 'ObjectTypeInfo',
   'data': { 'name': 'str' } }

 ##
@@ -1723,7 +1723,7 @@
 #
 # Since: 1.2
 ##
-{ 'type': 'DevicePropertyInfo',
+{ 'struct': 'DevicePropertyInfo',
   'data': { 'name': 'str', 'type': 'str', '*description': 'str' } }

 ##
@@ -1903,7 +1903,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'DumpGuestMemoryCapability',
+{ 'struct': 'DumpGuestMemoryCapability',
   'data': {
       'formats': ['DumpGuestMemoryFormat'] } }

@@ -2000,7 +2000,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevNoneOptions',
+{ 'struct': 'NetdevNoneOptions',
   'data': { } }

 ##
@@ -2020,7 +2020,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetLegacyNicOptions',
+{ 'struct': 'NetLegacyNicOptions',
   'data': {
     '*netdev':  'str',
     '*macaddr': 'str',
@@ -2035,7 +2035,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'String',
+{ 'struct': 'String',
   'data': {
     'str': 'str' } }

@@ -2078,7 +2078,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevUserOptions',
+{ 'struct': 'NetdevUserOptions',
   'data': {
     '*hostname':  'str',
     '*restrict':  'bool',
@@ -2130,7 +2130,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevTapOptions',
+{ 'struct': 'NetdevTapOptions',
   'data': {
     '*ifname':     'str',
     '*fd':         'str',
@@ -2166,7 +2166,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevSocketOptions',
+{ 'struct': 'NetdevSocketOptions',
   'data': {
     '*fd':        'str',
     '*listen':    'str',
@@ -2214,7 +2214,7 @@
 #
 # Since 2.1
 ##
-{ 'type': 'NetdevL2TPv3Options',
+{ 'struct': 'NetdevL2TPv3Options',
   'data': {
     'src':          'str',
     'dst':          'str',
@@ -2246,7 +2246,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevVdeOptions',
+{ 'struct': 'NetdevVdeOptions',
   'data': {
     '*sock':  'str',
     '*port':  'uint16',
@@ -2265,7 +2265,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevDumpOptions',
+{ 'struct': 'NetdevDumpOptions',
   'data': {
     '*len':  'size',
     '*file': 'str' } }
@@ -2281,7 +2281,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevBridgeOptions',
+{ 'struct': 'NetdevBridgeOptions',
   'data': {
     '*br':     'str',
     '*helper': 'str' } }
@@ -2295,7 +2295,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetdevHubPortOptions',
+{ 'struct': 'NetdevHubPortOptions',
   'data': {
     'hubid':     'int32' } }

@@ -2315,7 +2315,7 @@
 #
 # Since 2.0
 ##
-{ 'type': 'NetdevNetmapOptions',
+{ 'struct': 'NetdevNetmapOptions',
   'data': {
     'ifname':     'str',
     '*devname':    'str' } }
@@ -2331,7 +2331,7 @@
 #
 # Since 2.1
 ##
-{ 'type': 'NetdevVhostUserOptions',
+{ 'struct': 'NetdevVhostUserOptions',
   'data': {
     'chardev':        'str',
     '*vhostforce':    'bool' } }
@@ -2376,7 +2376,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'NetLegacy',
+{ 'struct': 'NetLegacy',
   'data': {
     '*vlan': 'int32',
     '*id':   'str',
@@ -2394,7 +2394,7 @@
 #
 # Since 1.2
 ##
-{ 'type': 'Netdev',
+{ 'struct': 'Netdev',
   'data': {
     'id':   'str',
     'opts': 'NetClientOptions' } }
@@ -2418,7 +2418,7 @@
 #
 # Since 1.3
 ##
-{ 'type': 'InetSocketAddress',
+{ 'struct': 'InetSocketAddress',
   'data': {
     'host': 'str',
     'port': 'str',
@@ -2435,7 +2435,7 @@
 #
 # Since 1.3
 ##
-{ 'type': 'UnixSocketAddress',
+{ 'struct': 'UnixSocketAddress',
   'data': {
     'path': 'str' } }

@@ -2500,7 +2500,7 @@
 #
 # Since: 1.2.0
 ##
-{ 'type': 'MachineInfo',
+{ 'struct': 'MachineInfo',
   'data': { 'name': 'str', '*alias': 'str',
             '*is-default': 'bool', 'cpu-max': 'int' } }

@@ -2524,7 +2524,7 @@
 #
 # Since: 1.2.0
 ##
-{ 'type': 'CpuDefinitionInfo',
+{ 'struct': 'CpuDefinitionInfo',
   'data': { 'name': 'str' } }

 ##
@@ -2549,7 +2549,7 @@
 #
 # Since: 1.2.0
 ##
-{ 'type': 'AddfdInfo', 'data': {'fdset-id': 'int', 'fd': 'int'} }
+{ 'struct': 'AddfdInfo', 'data': {'fdset-id': 'int', 'fd': 'int'} }

 ##
 # @add-fd:
@@ -2605,7 +2605,7 @@
 #
 # Since: 1.2.0
 ##
-{ 'type': 'FdsetFdInfo',
+{ 'struct': 'FdsetFdInfo',
   'data': {'fd': 'int', '*opaque': 'str'} }

 ##
@@ -2619,7 +2619,7 @@
 #
 # Since: 1.2.0
 ##
-{ 'type': 'FdsetInfo',
+{ 'struct': 'FdsetInfo',
   'data': {'fdset-id': 'int', 'fds': ['FdsetFdInfo']} }

 ##
@@ -2645,7 +2645,7 @@
 #
 # Since: 1.2.0
 ##
-{ 'type': 'TargetInfo',
+{ 'struct': 'TargetInfo',
   'data': { 'arch': 'str' } }

 ##
@@ -2745,7 +2745,7 @@
 #
 # Since: 1.4
 ##
-{ 'type': 'ChardevFile', 'data': { '*in' : 'str',
+{ 'struct': 'ChardevFile', 'data': { '*in' : 'str',
                                    'out' : 'str' } }

 ##
@@ -2759,7 +2759,7 @@
 #
 # Since: 1.4
 ##
-{ 'type': 'ChardevHostdev', 'data': { 'device' : 'str' } }
+{ 'struct': 'ChardevHostdev', 'data': { 'device' : 'str' } }

 ##
 # @ChardevSocket:
@@ -2781,7 +2781,7 @@
 #
 # Since: 1.4
 ##
-{ 'type': 'ChardevSocket', 'data': { 'addr'       : 'SocketAddress',
+{ 'struct': 'ChardevSocket', 'data': { 'addr'       : 'SocketAddress',
                                      '*server'    : 'bool',
                                      '*wait'      : 'bool',
                                      '*nodelay'   : 'bool',
@@ -2798,7 +2798,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'ChardevUdp', 'data': { 'remote' : 'SocketAddress',
+{ 'struct': 'ChardevUdp', 'data': { 'remote' : 'SocketAddress',
                                   '*local' : 'SocketAddress' } }

 ##
@@ -2810,7 +2810,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'ChardevMux', 'data': { 'chardev' : 'str' } }
+{ 'struct': 'ChardevMux', 'data': { 'chardev' : 'str' } }

 ##
 # @ChardevStdio:
@@ -2823,7 +2823,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'ChardevStdio', 'data': { '*signal' : 'bool' } }
+{ 'struct': 'ChardevStdio', 'data': { '*signal' : 'bool' } }

 ##
 # @ChardevSpiceChannel:
@@ -2834,7 +2834,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'ChardevSpiceChannel', 'data': { 'type'  : 'str' } }
+{ 'struct': 'ChardevSpiceChannel', 'data': { 'type'  : 'str' } }

 ##
 # @ChardevSpicePort:
@@ -2845,7 +2845,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'ChardevSpicePort', 'data': { 'fqdn'  : 'str' } }
+{ 'struct': 'ChardevSpicePort', 'data': { 'fqdn'  : 'str' } }

 ##
 # @ChardevVC:
@@ -2859,7 +2859,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'ChardevVC', 'data': { '*width'  : 'int',
+{ 'struct': 'ChardevVC', 'data': { '*width'  : 'int',
                                  '*height' : 'int',
                                  '*cols'   : 'int',
                                  '*rows'   : 'int' } }
@@ -2873,7 +2873,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'ChardevRingbuf', 'data': { '*size'  : 'int' } }
+{ 'struct': 'ChardevRingbuf', 'data': { '*size'  : 'int' } }

 ##
 # @ChardevBackend:
@@ -2882,7 +2882,7 @@
 #
 # Since: 1.4 (testdev since 2.2)
 ##
-{ 'type': 'ChardevDummy', 'data': { } }
+{ 'struct': 'ChardevDummy', 'data': { } }

 { 'union': 'ChardevBackend', 'data': { 'file'   : 'ChardevFile',
                                        'serial' : 'ChardevHostdev',
@@ -2915,7 +2915,7 @@
 #
 # Since: 1.4
 ##
-{ 'type' : 'ChardevReturn', 'data': { '*pty' : 'str' } }
+{ 'struct' : 'ChardevReturn', 'data': { '*pty' : 'str' } }

 ##
 # @chardev-add:
@@ -3002,7 +3002,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'TPMPassthroughOptions', 'data': { '*path' : 'str',
+{ 'struct': 'TPMPassthroughOptions', 'data': { '*path' : 'str',
                                              '*cancel-path' : 'str'} }

 ##
@@ -3030,7 +3030,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'TPMInfo',
+{ 'struct': 'TPMInfo',
   'data': {'id': 'str',
            'model': 'TpmModel',
            'options': 'TpmTypeOptions' } }
@@ -3092,7 +3092,7 @@
 #
 # Since 1.5
 ##
-{ 'type': 'AcpiTableOptions',
+{ 'struct': 'AcpiTableOptions',
   'data': {
     '*sig':               'str',
     '*rev':               'uint8',
@@ -3138,7 +3138,7 @@
 #
 # Since 1.5
 ##
-{ 'type': 'CommandLineParameterInfo',
+{ 'struct': 'CommandLineParameterInfo',
   'data': { 'name': 'str',
             'type': 'CommandLineParameterType',
             '*help': 'str',
@@ -3155,7 +3155,7 @@
 #
 # Since 1.5
 ##
-{ 'type': 'CommandLineOptionInfo',
+{ 'struct': 'CommandLineOptionInfo',
   'data': { 'option': 'str', 'parameters': ['CommandLineParameterInfo'] } }

 ##
@@ -3199,7 +3199,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'X86CPUFeatureWordInfo',
+{ 'struct': 'X86CPUFeatureWordInfo',
   'data': { 'cpuid-input-eax': 'int',
             '*cpuid-input-ecx': 'int',
             'cpuid-register': 'X86CPURegister32',
@@ -3252,7 +3252,7 @@
 # Since 1.6
 ##

-{ 'type': 'RxFilterInfo',
+{ 'struct': 'RxFilterInfo',
   'data': {
     'name':               'str',
     'promiscuous':        'bool',
@@ -3314,7 +3314,7 @@
 #
 # Since: 2.0
 ##
-{ 'type'  : 'InputKeyEvent',
+{ 'struct'  : 'InputKeyEvent',
   'data'  : { 'key'     : 'KeyValue',
               'down'    : 'bool' } }

@@ -3328,7 +3328,7 @@
 #
 # Since: 2.0
 ##
-{ 'type'  : 'InputBtnEvent',
+{ 'struct'  : 'InputBtnEvent',
   'data'  : { 'button'  : 'InputButton',
               'down'    : 'bool' } }

@@ -3343,7 +3343,7 @@
 #
 # Since: 2.0
 ##
-{ 'type'  : 'InputMoveEvent',
+{ 'struct'  : 'InputMoveEvent',
   'data'  : { 'axis'    : 'InputAxis',
               'value'   : 'int' } }

@@ -3426,7 +3426,7 @@
 #
 # Since: 2.1
 ##
-{ 'type': 'NumaNodeOptions',
+{ 'struct': 'NumaNodeOptions',
   'data': {
    '*nodeid': 'uint16',
    '*cpus':   ['uint16'],
@@ -3473,7 +3473,7 @@
 # Since: 2.1
 ##

-{ 'type': 'Memdev',
+{ 'struct': 'Memdev',
   'data': {
     'size':       'size',
     'merge':      'bool',
@@ -3516,7 +3516,7 @@
 #
 # Since: 2.1
 ##
-{ 'type': 'PCDIMMDeviceInfo',
+{ 'struct': 'PCDIMMDeviceInfo',
   'data': { '*id': 'str',
             'addr': 'int',
             'size': 'int',
@@ -3570,7 +3570,7 @@
 #
 # Since: 2.1
 ##
-{ 'type': 'ACPIOSTInfo',
+{ 'struct': 'ACPIOSTInfo',
   'data'  : { '*device': 'str',
               'slot': 'str',
               'slot-type': 'ACPISlotType',
diff --git a/qapi/block-core.json b/qapi/block-core.json
index a76be74..0a06106 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -26,7 +26,7 @@
 #
 ##

-{ 'type': 'SnapshotInfo',
+{ 'struct': 'SnapshotInfo',
   'data': { 'id': 'str', 'name': 'str', 'vm-state-size': 'int',
             'date-sec': 'int', 'date-nsec': 'int',
             'vm-clock-sec': 'int', 'vm-clock-nsec': 'int' } }
@@ -45,7 +45,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'ImageInfoSpecificQCow2',
+{ 'struct': 'ImageInfoSpecificQCow2',
   'data': {
       'compat': 'str',
       '*lazy-refcounts': 'bool',
@@ -66,7 +66,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'ImageInfoSpecificVmdk',
+{ 'struct': 'ImageInfoSpecificVmdk',
   'data': {
       'create-type': 'str',
       'cid': 'int',
@@ -126,7 +126,7 @@
 #
 ##

-{ 'type': 'ImageInfo',
+{ 'struct': 'ImageInfo',
   'data': {'filename': 'str', 'format': 'str', '*dirty-flag': 'bool',
            '*actual-size': 'int', 'virtual-size': 'int',
            '*cluster-size': 'int', '*encrypted': 'bool', '*compressed': 'bool',
@@ -178,7 +178,7 @@
 #
 ##

-{ 'type': 'ImageCheck',
+{ 'struct': 'ImageCheck',
   'data': {'filename': 'str', 'format': 'str', 'check-errors': 'int',
            '*image-end-offset': 'int', '*corruptions': 'int', '*leaks': 'int',
            '*corruptions-fixed': 'int', '*leaks-fixed': 'int',
@@ -196,7 +196,7 @@
 #
 # Since: 2.3
 ##
-{ 'type': 'BlockdevCacheInfo',
+{ 'struct': 'BlockdevCacheInfo',
   'data': { 'writeback': 'bool',
             'direct': 'bool',
             'no-flush': 'bool' } }
@@ -267,7 +267,7 @@
 # Since: 0.14.0
 #
 ##
-{ 'type': 'BlockDeviceInfo',
+{ 'struct': 'BlockDeviceInfo',
   'data': { 'file': 'str', '*node-name': 'str', 'ro': 'bool', 'drv': 'str',
             '*backing_file': 'str', 'backing_file_depth': 'int',
             'encrypted': 'bool', 'encryption_key_missing': 'bool',
@@ -321,7 +321,7 @@
 #
 # Since 1.7
 ##
-{ 'type': 'BlockDeviceMapEntry',
+{ 'struct': 'BlockDeviceMapEntry',
   'data': { 'start': 'int', 'length': 'int', 'depth': 'int', 'zero': 'bool',
             'data': 'bool', '*offset': 'int' } }

@@ -336,7 +336,7 @@
 #
 # Since: 1.3
 ##
-{ 'type': 'BlockDirtyInfo',
+{ 'struct': 'BlockDirtyInfo',
   'data': {'count': 'int', 'granularity': 'int'} }

 ##
@@ -370,7 +370,7 @@
 #
 # Since:  0.14.0
 ##
-{ 'type': 'BlockInfo',
+{ 'struct': 'BlockInfo',
   'data': {'device': 'str', 'type': 'str', 'removable': 'bool',
            'locked': 'bool', '*inserted': 'BlockDeviceInfo',
            '*tray_open': 'bool', '*io-status': 'BlockDeviceIoStatus',
@@ -423,7 +423,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'BlockDeviceStats',
+{ 'struct': 'BlockDeviceStats',
   'data': {'rd_bytes': 'int', 'wr_bytes': 'int', 'rd_operations': 'int',
            'wr_operations': 'int', 'flush_operations': 'int',
            'flush_total_time_ns': 'int', 'wr_total_time_ns': 'int',
@@ -449,7 +449,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'BlockStats',
+{ 'struct': 'BlockStats',
   'data': {'*device': 'str', '*node-name': 'str',
            'stats': 'BlockDeviceStats',
            '*parent': 'BlockStats',
@@ -560,7 +560,7 @@
 #
 # Since: 1.1
 ##
-{ 'type': 'BlockJobInfo',
+{ 'struct': 'BlockJobInfo',
   'data': {'type': 'str', 'device': 'str', 'len': 'int',
            'offset': 'int', 'busy': 'bool', 'paused': 'bool', 'speed': 'int',
            'io-status': 'BlockDeviceIoStatus', 'ready': 'bool'} }
@@ -670,7 +670,7 @@
 # @mode: #optional whether and how QEMU should create a new image, default is
 #        'absolute-paths'.
 ##
-{ 'type': 'BlockdevSnapshot',
+{ 'struct': 'BlockdevSnapshot',
   'data': { '*device': 'str', '*node-name': 'str',
             'snapshot-file': 'str', '*snapshot-node-name': 'str',
             '*format': 'str', '*mode': 'NewImageMode' } }
@@ -710,7 +710,7 @@
 #
 # Since: 1.6
 ##
-{ 'type': 'DriveBackup',
+{ 'struct': 'DriveBackup',
   'data': { 'device': 'str', 'target': 'str', '*format': 'str',
             'sync': 'MirrorSyncMode', '*mode': 'NewImageMode',
             '*speed': 'int',
@@ -745,7 +745,7 @@
 #
 # Since: 2.3
 ##
-{ 'type': 'BlockdevBackup',
+{ 'struct': 'BlockdevBackup',
   'data': { 'device': 'str', 'target': 'str',
             'sync': 'MirrorSyncMode',
             '*speed': 'int',
@@ -1232,7 +1232,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevCacheOptions',
+{ 'struct': 'BlockdevCacheOptions',
   'data': { '*writeback': 'bool',
             '*direct': 'bool',
             '*no-flush': 'bool' } }
@@ -1279,7 +1279,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevOptionsBase',
+{ 'struct': 'BlockdevOptionsBase',
   'data': { 'driver': 'BlockdevDriver',
             '*id': 'str',
             '*node-name': 'str',
@@ -1301,7 +1301,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevOptionsFile',
+{ 'struct': 'BlockdevOptionsFile',
   'data': { 'filename': 'str' } }

 ##
@@ -1313,7 +1313,7 @@
 #
 # Since: 2.2
 ##
-{ 'type': 'BlockdevOptionsNull',
+{ 'struct': 'BlockdevOptionsNull',
   'data': { '*size': 'int' } }

 ##
@@ -1329,7 +1329,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevOptionsVVFAT',
+{ 'struct': 'BlockdevOptionsVVFAT',
   'data': { 'dir': 'str', '*fat-type': 'int', '*floppy': 'bool',
             '*rw': 'bool' } }

@@ -1343,7 +1343,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevOptionsGenericFormat',
+{ 'struct': 'BlockdevOptionsGenericFormat',
   'data': { 'file': 'BlockdevRef' } }

 ##
@@ -1359,7 +1359,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevOptionsGenericCOWFormat',
+{ 'struct': 'BlockdevOptionsGenericCOWFormat',
   'base': 'BlockdevOptionsGenericFormat',
   'data': { '*backing': 'BlockdevRef' } }

@@ -1395,7 +1395,7 @@
 #
 # Since: 2.2
 ##
-{ 'type': 'Qcow2OverlapCheckFlags',
+{ 'struct': 'Qcow2OverlapCheckFlags',
   'data': { '*template':       'Qcow2OverlapCheckMode',
             '*main-header':    'bool',
             '*active-l1':      'bool',
@@ -1456,7 +1456,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevOptionsQcow2',
+{ 'struct': 'BlockdevOptionsQcow2',
   'base': 'BlockdevOptionsGenericCOWFormat',
   'data': { '*lazy-refcounts': 'bool',
             '*pass-discard-request': 'bool',
@@ -1491,7 +1491,7 @@
 #                       use the default value, 'archipelago'.
 # Since: 2.2
 ##
-{ 'type': 'BlockdevOptionsArchipelago',
+{ 'struct': 'BlockdevOptionsArchipelago',
   'data': { 'volume': 'str',
             '*mport': 'int',
             '*vport': 'int',
@@ -1543,7 +1543,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'BlkdebugInjectErrorOptions',
+{ 'struct': 'BlkdebugInjectErrorOptions',
   'data': { 'event': 'BlkdebugEvent',
             '*state': 'int',
             '*errno': 'int',
@@ -1566,7 +1566,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'BlkdebugSetStateOptions',
+{ 'struct': 'BlkdebugSetStateOptions',
   'data': { 'event': 'BlkdebugEvent',
             '*state': 'int',
             'new_state': 'int' } }
@@ -1588,7 +1588,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'BlockdevOptionsBlkdebug',
+{ 'struct': 'BlockdevOptionsBlkdebug',
   'data': { 'image': 'BlockdevRef',
             '*config': 'str',
             '*align': 'int',
@@ -1606,7 +1606,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'BlockdevOptionsBlkverify',
+{ 'struct': 'BlockdevOptionsBlkverify',
   'data': { 'test': 'BlockdevRef',
             'raw': 'BlockdevRef' } }

@@ -1643,7 +1643,7 @@
 #
 # Since: 2.0
 ##
-{ 'type': 'BlockdevOptionsQuorum',
+{ 'struct': 'BlockdevOptionsQuorum',
   'data': { '*blkverify': 'bool',
             'children': [ 'BlockdevRef' ],
             'vote-threshold': 'int',
diff --git a/qapi/block.json b/qapi/block.json
index e313465..aad645c 100644
--- a/qapi/block.json
+++ b/qapi/block.json
@@ -52,7 +52,7 @@
 #
 # Since: 1.7
 ##
-{ 'type': 'BlockdevSnapshotInternal',
+{ 'struct': 'BlockdevSnapshotInternal',
   'data': { 'device': 'str', 'name': 'str' } }

 ##
diff --git a/qapi/common.json b/qapi/common.json
index 63ef3b4..12431c6 100644
--- a/qapi/common.json
+++ b/qapi/common.json
@@ -50,7 +50,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'VersionInfo',
+{ 'struct': 'VersionInfo',
   'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'},
            'package': 'str'} }

@@ -74,7 +74,7 @@
 #
 # Since: 0.14.0
 ##
-{ 'type': 'CommandInfo', 'data': {'name': 'str'} }
+{ 'struct': 'CommandInfo', 'data': {'name': 'str'} }

 ##
 # @query-commands:
diff --git a/qapi/trace.json b/qapi/trace.json
index 06c613c..01b0a52 100644
--- a/qapi/trace.json
+++ b/qapi/trace.json
@@ -32,7 +32,7 @@
 #
 # Since 2.2
 ##
-{ 'type': 'TraceEventInfo',
+{ 'struct': 'TraceEventInfo',
   'data': {'name': 'str', 'state': 'TraceEventState'} }

 ##
diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json
index fe5be7e..7f5c500 100644
--- a/qga/qapi-schema.json
+++ b/qga/qapi-schema.json
@@ -150,7 +150,7 @@
 #
 # Since 1.1.0
 ##
-{ 'type': 'GuestAgentCommandInfo',
+{ 'struct': 'GuestAgentCommandInfo',
   'data': { 'name': 'str', 'enabled': 'bool', 'success-response': 'bool' } }

 ##
@@ -164,7 +164,7 @@
 #
 # Since 0.15.0
 ##
-{ 'type': 'GuestAgentInfo',
+{ 'struct': 'GuestAgentInfo',
   'data': { 'version': 'str',
             'supported_commands': ['GuestAgentCommandInfo'] } }
 ##
@@ -242,7 +242,7 @@
 #
 # Since: 0.15.0
 ##
-{ 'type': 'GuestFileRead',
+{ 'struct': 'GuestFileRead',
   'data': { 'count': 'int', 'buf-b64': 'str', 'eof': 'bool' } }

 ##
@@ -274,7 +274,7 @@
 #
 # Since: 0.15.0
 ##
-{ 'type': 'GuestFileWrite',
+{ 'struct': 'GuestFileWrite',
   'data': { 'count': 'int', 'eof': 'bool' } }

 ##
@@ -309,7 +309,7 @@
 #
 # Since: 0.15.0
 ##
-{ 'type': 'GuestFileSeek',
+{ 'struct': 'GuestFileSeek',
   'data': { 'position': 'int', 'eof': 'bool' } }

 ##
@@ -556,7 +556,7 @@
 #
 # Since: 1.1
 ##
-{ 'type': 'GuestIpAddress',
+{ 'struct': 'GuestIpAddress',
   'data': {'ip-address': 'str',
            'ip-address-type': 'GuestIpAddressType',
            'prefix': 'int'} }
@@ -572,7 +572,7 @@
 #
 # Since: 1.1
 ##
-{ 'type': 'GuestNetworkInterface',
+{ 'struct': 'GuestNetworkInterface',
   'data': {'name': 'str',
            '*hardware-address': 'str',
            '*ip-addresses': ['GuestIpAddress'] } }
@@ -604,7 +604,7 @@
 #
 # Since: 1.5
 ##
-{ 'type': 'GuestLogicalProcessor',
+{ 'struct': 'GuestLogicalProcessor',
   'data': {'logical-id': 'int',
            'online': 'bool',
            '*can-offline': 'bool'} }
@@ -694,7 +694,7 @@
 #
 # Since: 2.2
 ##
-{ 'type': 'GuestPCIAddress',
+{ 'struct': 'GuestPCIAddress',
   'data': {'domain': 'int', 'bus': 'int',
            'slot': 'int', 'function': 'int'} }

@@ -709,7 +709,7 @@
 #
 # Since: 2.2
 ##
-{ 'type': 'GuestDiskAddress',
+{ 'struct': 'GuestDiskAddress',
   'data': {'pci-controller': 'GuestPCIAddress',
            'bus-type': 'GuestDiskBusType',
            'bus': 'int', 'target': 'int', 'unit': 'int'} }
@@ -725,7 +725,7 @@
 #
 # Since: 2.2
 ##
-{ 'type': 'GuestFilesystemInfo',
+{ 'struct': 'GuestFilesystemInfo',
   'data': {'name': 'str', 'mountpoint': 'str', 'type': 'str',
            'disk': ['GuestDiskAddress']} }

@@ -782,7 +782,7 @@
 #
 # Since: 2.3
 ##
-{ 'type': 'GuestMemoryBlock',
+{ 'struct': 'GuestMemoryBlock',
   'data': {'phys-index': 'uint64',
            'online': 'bool',
            '*can-offline': 'bool'} }
@@ -835,7 +835,7 @@
 #
 # Since: 2.3
 ##
-{ 'type': 'GuestMemoryBlockResponse',
+{ 'struct': 'GuestMemoryBlockResponse',
   'data': { 'phys-index': 'uint64',
             'response': 'GuestMemoryBlockResponseType',
             '*error-code': 'int' }}
@@ -876,7 +876,7 @@
 #
 # Since: 2.3
 ##
-{ 'type': 'GuestMemoryBlockInfo',
+{ 'struct': 'GuestMemoryBlockInfo',
   'data': {'size': 'uint64'} }

 ##
diff --git a/scripts/qapi.py b/scripts/qapi.py
index c246570..effba38 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -617,20 +617,6 @@ def parse_schema(input_file):
         for expr_elem in schema.exprs:
             expr = expr_elem['expr']
             info = expr_elem['info']
-
-            # back-compat hack until all schemas have been converted;
-            # preserve the ordering of the original expression
-            if expr.has_key('type'):
-                seen_type = False
-                for (key, value) in expr.items():
-                    if key == 'type':
-                        seen_type = True
-                        del expr['type']
-                        expr['struct'] = value
-                    elif seen_type:
-                        del expr[key]
-                        expr[key] = value
-
             if expr.has_key('enum'):
                 check_keys(expr_elem, 'enum', ['data'])
                 add_enum(expr['enum'], info, expr['data'])
diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json
index 043c0fb..606c3e4 100644
--- a/tests/qapi-schema/alternate-array.json
+++ b/tests/qapi-schema/alternate-array.json
@@ -1,6 +1,6 @@
 # we do not support array branches of alternates yet
 # TODO: should we support this?
-{ 'type': 'One',
+{ 'struct': 'One',
   'data': { 'name': 'str' } }
 { 'alternate': 'Alt',
   'data': { 'one': 'One',
diff --git a/tests/qapi-schema/alternate-base.json b/tests/qapi-schema/alternate-base.json
index 66edc89..529430ec 100644
--- a/tests/qapi-schema/alternate-base.json
+++ b/tests/qapi-schema/alternate-base.json
@@ -1,5 +1,5 @@
 # we reject alternate with base type
-{ 'type': 'Base',
+{ 'struct': 'Base',
   'data': { 'string': 'str' } }
 { 'alternate': 'Alt',
   'base': 'Base',
diff --git a/tests/qapi-schema/alternate-conflict-dict.json b/tests/qapi-schema/alternate-conflict-dict.json
index fcb3e36..d566cca 100644
--- a/tests/qapi-schema/alternate-conflict-dict.json
+++ b/tests/qapi-schema/alternate-conflict-dict.json
@@ -1,7 +1,7 @@
 # we reject alternates with multiple object branches
-{ 'type': 'One',
+{ 'struct': 'One',
   'data': { 'name': 'str' } }
-{ 'type': 'Two',
+{ 'struct': 'Two',
   'data': { 'value': 'int' } }
 { 'alternate': 'Alt',
   'data': { 'one': 'One',
diff --git a/tests/qapi-schema/alternate-good.json b/tests/qapi-schema/alternate-good.json
index 99d614f..3371770 100644
--- a/tests/qapi-schema/alternate-good.json
+++ b/tests/qapi-schema/alternate-good.json
@@ -1,5 +1,5 @@
 # Working example of alternate
-{ 'type': 'Data',
+{ 'struct': 'Data',
   'data': { '*number': 'int', '*name': 'str' } }
 { 'enum': 'Enum',
   'data': [ 'hello', 'world' ] }
diff --git a/tests/qapi-schema/bad-base.json b/tests/qapi-schema/bad-base.json
index a690470..a634331 100644
--- a/tests/qapi-schema/bad-base.json
+++ b/tests/qapi-schema/bad-base.json
@@ -1,3 +1,3 @@
 # we reject a base that is not a struct
 { 'union': 'Union', 'data': { 'a': 'int', 'b': 'str' } }
-{ 'type': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } }
+{ 'struct': 'MyType', 'base': 'Union', 'data': { 'c': 'int' } }
diff --git a/tests/qapi-schema/bad-ident.json b/tests/qapi-schema/bad-ident.json
index da949e8..763627a 100644
--- a/tests/qapi-schema/bad-ident.json
+++ b/tests/qapi-schema/bad-ident.json
@@ -1,2 +1,2 @@
 # we reject creating a type name with bad name
-{ 'type': '*oops', 'data': { 'i': 'int' } }
+{ 'struct': '*oops', 'data': { 'i': 'int' } }
diff --git a/tests/qapi-schema/bad-type-bool.json b/tests/qapi-schema/bad-type-bool.json
index e1e9fb0..bde17b5 100644
--- a/tests/qapi-schema/bad-type-bool.json
+++ b/tests/qapi-schema/bad-type-bool.json
@@ -1,2 +1,2 @@
 # we reject an expression with a metatype that is not a string
-{ 'type': true, 'data': { } }
+{ 'struct': true, 'data': { } }
diff --git a/tests/qapi-schema/bad-type-int.err b/tests/qapi-schema/bad-type-int.err
index 9808550..da89895 100644
--- a/tests/qapi-schema/bad-type-int.err
+++ b/tests/qapi-schema/bad-type-int.err
@@ -1 +1 @@
-tests/qapi-schema/bad-type-int.json:3:11: Stray "1"
+tests/qapi-schema/bad-type-int.json:3:13: Stray "1"
diff --git a/tests/qapi-schema/bad-type-int.json b/tests/qapi-schema/bad-type-int.json
index 398879d..56fc6f8 100644
--- a/tests/qapi-schema/bad-type-int.json
+++ b/tests/qapi-schema/bad-type-int.json
@@ -1,3 +1,3 @@
 # we reject an expression with a metatype that is not a string
 # FIXME: once the parser understands integer inputs, improve the error message
-{ 'type': 1, 'data': { } }
+{ 'struct': 1, 'data': { } }
diff --git a/tests/qapi-schema/data-member-array.json b/tests/qapi-schema/data-member-array.json
index 7cce276..e6f7f5d 100644
--- a/tests/qapi-schema/data-member-array.json
+++ b/tests/qapi-schema/data-member-array.json
@@ -1,4 +1,4 @@
 # valid array members
 { 'enum': 'abc', 'data': [ 'a', 'b', 'c' ] }
-{ 'type': 'def', 'data': { 'array': [ 'abc' ] } }
+{ 'struct': 'def', 'data': { 'array': [ 'abc' ] } }
 { 'command': 'okay', 'data': { 'member1': [ 'int' ], 'member2': [ 'def' ] } }
diff --git a/tests/qapi-schema/double-data.err b/tests/qapi-schema/double-data.err
index 6f1a67b..cc765c4 100644
--- a/tests/qapi-schema/double-data.err
+++ b/tests/qapi-schema/double-data.err
@@ -1 +1 @@
-tests/qapi-schema/double-data.json:2:39: Duplicate key "data"
+tests/qapi-schema/double-data.json:2:41: Duplicate key "data"
diff --git a/tests/qapi-schema/double-data.json b/tests/qapi-schema/double-data.json
index a94b7df..e76b519 100644
--- a/tests/qapi-schema/double-data.json
+++ b/tests/qapi-schema/double-data.json
@@ -1,2 +1,2 @@
 # we reject an expression with duplicate top-level keys
-{ 'type': 'bar', 'data': { }, 'data': { 'string': 'str'} }
+{ 'struct': 'bar', 'data': { }, 'data': { 'string': 'str'} }
diff --git a/tests/qapi-schema/double-type.json b/tests/qapi-schema/double-type.json
index 471623a..911fa7a 100644
--- a/tests/qapi-schema/double-type.json
+++ b/tests/qapi-schema/double-type.json
@@ -1,2 +1,2 @@
 # we reject an expression with ambiguous metatype
-{ 'command': 'foo', 'type': 'bar', 'data': { } }
+{ 'command': 'foo', 'struct': 'bar', 'data': { } }
diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json
index bb0f02d..3f9c8fc 100644
--- a/tests/qapi-schema/flat-union-bad-base.json
+++ b/tests/qapi-schema/flat-union-bad-base.json
@@ -2,9 +2,9 @@
 # TODO: should we allow an anonymous inline base type?
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 { 'union': 'TestUnion',
   'base': { 'enum1': 'TestEnum', 'kind': 'str' },
diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json
index 982f072..cd10b9d 100644
--- a/tests/qapi-schema/flat-union-bad-discriminator.json
+++ b/tests/qapi-schema/flat-union-bad-discriminator.json
@@ -2,11 +2,11 @@
 # this tests the old syntax for anonymous unions before we added alternates
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
-{ 'type': 'TestBase',
+{ 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 { 'union': 'TestUnion',
   'base': 'TestBase',
diff --git a/tests/qapi-schema/flat-union-base-star.json b/tests/qapi-schema/flat-union-base-star.json
index 994533a..ed9dbf1 100644
--- a/tests/qapi-schema/flat-union-base-star.json
+++ b/tests/qapi-schema/flat-union-base-star.json
@@ -1,9 +1,9 @@
 # we require the base to be an existing complex type
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 { 'union': 'TestUnion',
   'base': '**',
diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json
index 0ba6e28..6a8ea68 100644
--- a/tests/qapi-schema/flat-union-base-union.json
+++ b/tests/qapi-schema/flat-union-base-union.json
@@ -1,9 +1,9 @@
 # we require the base to be a struct
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 { 'union': 'UnionBase',
   'data': { 'kind1': 'TestTypeA',
diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json
index 4091477..8b0b807 100644
--- a/tests/qapi-schema/flat-union-branch-clash.json
+++ b/tests/qapi-schema/flat-union-branch-clash.json
@@ -1,11 +1,11 @@
 # FIXME: we should check for no duplicate keys between branches and base
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
-{ 'type': 'Base',
+{ 'struct': 'Base',
   'data': { 'enum1': 'TestEnum', 'name': 'str' } }
-{ 'type': 'Branch1',
+{ 'struct': 'Branch1',
   'data': { 'name': 'str' } }
-{ 'type': 'Branch2',
+{ 'struct': 'Branch2',
   'data': { 'value': 'int' } }
 { 'union': 'TestUnion',
   'base': 'Base',
diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json
index f3da117..d95618b 100644
--- a/tests/qapi-schema/flat-union-inline.json
+++ b/tests/qapi-schema/flat-union-inline.json
@@ -2,7 +2,7 @@
 # TODO: should we allow anonymous inline types?
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
-{ 'type': 'Base',
+{ 'struct': 'Base',
   'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
 { 'union': 'TestUnion',
   'base': { 'enum1': 'TestEnum', 'kind': 'str' },
diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json
index d373131..108faef 100644
--- a/tests/qapi-schema/flat-union-int-branch.json
+++ b/tests/qapi-schema/flat-union-int-branch.json
@@ -1,9 +1,9 @@
 # we require flat union branches to be a complex type
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
-{ 'type': 'Base',
+{ 'struct': 'Base',
   'data': { 'enum1': 'TestEnum' } }
-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 { 'union': 'TestUnion',
   'base': 'Base',
diff --git a/tests/qapi-schema/flat-union-invalid-branch-key.json b/tests/qapi-schema/flat-union-invalid-branch-key.json
index a624282..95ff774 100644
--- a/tests/qapi-schema/flat-union-invalid-branch-key.json
+++ b/tests/qapi-schema/flat-union-invalid-branch-key.json
@@ -1,13 +1,13 @@
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }

-{ 'type': 'TestBase',
+{ 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum' } }

-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }

-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }

 { 'union': 'TestUnion',
diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.json b/tests/qapi-schema/flat-union-invalid-discriminator.json
index 887157e..48b94c3 100644
--- a/tests/qapi-schema/flat-union-invalid-discriminator.json
+++ b/tests/qapi-schema/flat-union-invalid-discriminator.json
@@ -1,13 +1,13 @@
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }

-{ 'type': 'TestBase',
+{ 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum' } }

-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }

-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }

 { 'union': 'TestUnion',
diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json
index 9547bb8..ffc4c6f 100644
--- a/tests/qapi-schema/flat-union-no-base.json
+++ b/tests/qapi-schema/flat-union-no-base.json
@@ -1,8 +1,8 @@
 # flat unions require a base
 # TODO: simple unions should be able to use an enum discriminator
-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }
-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
 { 'enum': 'Enum',
   'data': [ 'value1', 'value2' ] }
diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json
index 25ce0e6..08a8f7e 100644
--- a/tests/qapi-schema/flat-union-optional-discriminator.json
+++ b/tests/qapi-schema/flat-union-optional-discriminator.json
@@ -1,8 +1,8 @@
 # we require the discriminator to be non-optional
 { 'enum': 'Enum', 'data': [ 'one', 'two' ] }
-{ 'type': 'Base',
+{ 'struct': 'Base',
   'data': { '*switch': 'Enum' } }
-{ 'type': 'Branch', 'data': { 'name': 'str' } }
+{ 'struct': 'Branch', 'data': { 'name': 'str' } }
 { 'union': 'MyUnion',
   'base': 'Base',
   'discriminator': '*switch',
diff --git a/tests/qapi-schema/flat-union-reverse-define.json b/tests/qapi-schema/flat-union-reverse-define.json
index 9ea7e72..648bbfe 100644
--- a/tests/qapi-schema/flat-union-reverse-define.json
+++ b/tests/qapi-schema/flat-union-reverse-define.json
@@ -4,14 +4,14 @@
   'data': { 'value1': 'TestTypeA',
             'value2': 'TestTypeB' } }

-{ 'type': 'TestBase',
+{ 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum' } }

 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }

-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }

-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }
diff --git a/tests/qapi-schema/flat-union-string-discriminator.json b/tests/qapi-schema/flat-union-string-discriminator.json
index e966aeb..8af6033 100644
--- a/tests/qapi-schema/flat-union-string-discriminator.json
+++ b/tests/qapi-schema/flat-union-string-discriminator.json
@@ -1,13 +1,13 @@
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }

-{ 'type': 'TestBase',
+{ 'struct': 'TestBase',
   'data': { 'enum1': 'TestEnum', 'kind': 'str' } }

-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }

-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }

 { 'union': 'TestUnion',
diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index dec8a7c..f10efe2 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -3,40 +3,40 @@
 # for testing enums
 { 'enum': 'EnumOne',
   'data': [ 'value1', 'value2', 'value3' ] }
-{ 'type': 'NestedEnumsOne',
+{ 'struct': 'NestedEnumsOne',
   'data': { 'enum1': 'EnumOne', '*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } }

 # for testing nested structs
-{ 'type': 'UserDefZero',
+{ 'struct': 'UserDefZero',
   'data': { 'integer': 'int' } }

-{ 'type': 'UserDefOne',
+{ 'struct': 'UserDefOne',
   'base': 'UserDefZero',
   'data': { 'string': 'str', '*enum1': 'EnumOne' } }

-{ 'type': 'UserDefTwo',
+{ 'struct': 'UserDefTwo',
   'data': { 'string': 'str',
             'dict': { 'string': 'str',
                       'dict': { 'userdef': 'UserDefOne', 'string': 'str' },
                       '*dict2': { 'userdef': 'UserDefOne', 'string': 'str' } } } }

-{ 'type': 'UserDefNested',
+{ 'struct': 'UserDefNested',
   'data': { 'string0': 'str',
             'dict1': { 'string1': 'str',
                        'dict2': { 'userdef1': 'UserDefOne', 'string2': 'str' },
                        '*dict3': { 'userdef2': 'UserDefOne', 'string3': 'str' } } } }

 # for testing unions
-{ 'type': 'UserDefA',
+{ 'struct': 'UserDefA',
   'data': { 'boolean': 'bool' } }

-{ 'type': 'UserDefB',
+{ 'struct': 'UserDefB',
   'data': { 'integer': 'int' } }

-{ 'type': 'UserDefC',
+{ 'struct': 'UserDefC',
   'data': { 'string1': 'str', 'string2': 'str' } }

-{ 'type': 'UserDefUnionBase',
+{ 'struct': 'UserDefUnionBase',
   'data': { 'string': 'str', 'enum1': 'EnumOne' } }

 { 'union': 'UserDefFlatUnion',
@@ -88,7 +88,7 @@
 #
 # For simplicity, this example doesn't use [type=]discriminator nor optargs
 # specific to discriminator values.
-{ 'type': 'UserDefOptions',
+{ 'struct': 'UserDefOptions',
   'data': {
     '*i64' : [ 'int'    ],
     '*u64' : [ 'uint64' ],
@@ -97,7 +97,7 @@
     '*u64x':   'uint64'  } }

 # testing event
-{ 'type': 'EventStructOne',
+{ 'struct': 'EventStructOne',
   'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }

 { 'event': 'EVENT_A' }
diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json
index df328cc..45b8a55 100644
--- a/tests/qapi-schema/redefined-builtin.json
+++ b/tests/qapi-schema/redefined-builtin.json
@@ -1,2 +1,2 @@
 # we reject types that duplicate builtin names
-{ 'type': 'size', 'data': { 'myint': 'size' } }
+{ 'struct': 'size', 'data': { 'myint': 'size' } }
diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json
index e6a5f24..a09e768 100644
--- a/tests/qapi-schema/redefined-type.json
+++ b/tests/qapi-schema/redefined-type.json
@@ -1,3 +1,3 @@
 # we reject types defined more than once
-{ 'type': 'foo', 'data': { 'one': 'str' } }
+{ 'struct': 'foo', 'data': { 'one': 'str' } }
 { 'enum': 'foo', 'data': [ 'two' ] }
diff --git a/tests/qapi-schema/union-bad-branch.json b/tests/qapi-schema/union-bad-branch.json
index 4303666..913aa38 100644
--- a/tests/qapi-schema/union-bad-branch.json
+++ b/tests/qapi-schema/union-bad-branch.json
@@ -1,7 +1,7 @@
 # we reject normal unions where branches would collide in C
-{ 'type': 'One',
+{ 'struct': 'One',
   'data': { 'string': 'str' } }
-{ 'type': 'Two',
+{ 'struct': 'Two',
   'data': { 'number': 'int' } }
 { 'union': 'MyUnion',
   'data': { 'one': 'One',
diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json
index 052596c..1409cf5 100644
--- a/tests/qapi-schema/union-base-no-discriminator.json
+++ b/tests/qapi-schema/union-base-no-discriminator.json
@@ -1,11 +1,11 @@
 # we reject simple unions with a base (or flat unions without discriminator)
-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }

-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }

-{ 'type': 'Base',
+{ 'struct': 'Base',
   'data': { 'string': 'str' } }

 { 'union': 'TestUnion',
diff --git a/tests/qapi-schema/union-invalid-base.json b/tests/qapi-schema/union-invalid-base.json
index bc5dc8d..92be39d 100644
--- a/tests/qapi-schema/union-invalid-base.json
+++ b/tests/qapi-schema/union-invalid-base.json
@@ -1,8 +1,8 @@
 # a union base type must be a struct
-{ 'type': 'TestTypeA',
+{ 'struct': 'TestTypeA',
   'data': { 'string': 'str' } }

-{ 'type': 'TestTypeB',
+{ 'struct': 'TestTypeB',
   'data': { 'integer': 'int' } }

 { 'union': 'TestUnion',
diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json
index ba7bdf3..3b2be00 100644
--- a/tests/qapi-schema/unknown-expr-key.json
+++ b/tests/qapi-schema/unknown-expr-key.json
@@ -1,2 +1,2 @@
 # we reject an expression with unknown top-level keys
-{ 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }
+{ 'struct': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 31/36] qapi: Merge UserDefTwo and UserDefNested in tests
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (29 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 30/36] qapi: Use 'struct' instead of 'type' in schema Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-28 12:28   ` Markus Armbruster
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 32/36] qapi: Drop tests for inline nested structs Eric Blake
                   ` (8 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

In the testsuite, UserDefTwo and UserDefNested were identical
structs other than the member names.  Reduce code duplication by
having just one type, and choose names that also favor reuse.
This will also make it easier for a later patch to get rid of
inline nested types in QAPI.  When touching code related to
allocations, convert g_malloc0(sizeof(Type)) to the more typesafe
g_new0(Type, 1).

Ensure that 'make check-qapi-schema check-unit' still passes.

Signed-off-by: Eric Blake <eblake@redhat.com>

---

v6: rebase onto earlier changes; update commit message
---
 tests/qapi-schema/qapi-schema-test.json | 10 +----
 tests/qapi-schema/qapi-schema-test.out  |  6 +--
 tests/test-qmp-commands.c               | 34 +++++++--------
 tests/test-qmp-input-strict.c           | 17 ++++----
 tests/test-qmp-input-visitor.c          | 17 ++++----
 tests/test-qmp-output-visitor.c         | 48 +++++++++++-----------
 tests/test-visitor-serialization.c      | 73 +++++++++++++++++----------------
 7 files changed, 102 insertions(+), 103 deletions(-)

diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index f10efe2..a6be983 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -15,16 +15,10 @@
   'data': { 'string': 'str', '*enum1': 'EnumOne' } }

 { 'struct': 'UserDefTwo',
-  'data': { 'string': 'str',
-            'dict': { 'string': 'str',
-                      'dict': { 'userdef': 'UserDefOne', 'string': 'str' },
-                      '*dict2': { 'userdef': 'UserDefOne', 'string': 'str' } } } }
-
-{ 'struct': 'UserDefNested',
   'data': { 'string0': 'str',
             'dict1': { 'string1': 'str',
-                       'dict2': { 'userdef1': 'UserDefOne', 'string2': 'str' },
-                       '*dict3': { 'userdef2': 'UserDefOne', 'string3': 'str' } } } }
+                       'dict2': { 'userdef': 'UserDefOne', 'string': 'str' },
+                       '*dict3': { 'userdef': 'UserDefOne', 'string': 'str' } } } }

 # for testing unions
 { 'struct': 'UserDefA',
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 83ab1a5..48f8f0e 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -2,8 +2,7 @@
  OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
  OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
- OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
- OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
  OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
  OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
@@ -28,8 +27,7 @@
 [OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
  OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
- OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string', 'str'), ('dict', OrderedDict([('string', 'str'), ('dict', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
- OrderedDict([('struct', 'UserDefNested'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef1', 'UserDefOne'), ('string2', 'str')])), ('*dict3', OrderedDict([('userdef2', 'UserDefOne'), ('string3', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
  OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
  OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 554e222..8b50eac 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -31,14 +31,14 @@ UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a,
     ud1d->base = g_new0(UserDefZero, 1);
     ud1d->base->integer = has_udb1 ? ud1b->base->integer : 0;

-    ret = g_malloc0(sizeof(UserDefTwo));
-    ret->string = strdup("blah1");
-    ret->dict.string = strdup("blah2");
-    ret->dict.dict.userdef = ud1c;
-    ret->dict.dict.string = strdup("blah3");
-    ret->dict.has_dict2 = true;
-    ret->dict.dict2.userdef = ud1d;
-    ret->dict.dict2.string = strdup("blah4");
+    ret = g_new0(UserDefTwo, 1);
+    ret->string0 = strdup("blah1");
+    ret->dict1.string1 = strdup("blah2");
+    ret->dict1.dict2.userdef = ud1c;
+    ret->dict1.dict2.string = strdup("blah3");
+    ret->dict1.has_dict3 = true;
+    ret->dict1.dict3.userdef = ud1d;
+    ret->dict1.dict3.string = strdup("blah4");

     return ret;
 }
@@ -120,15 +120,15 @@ static void test_dispatch_cmd_io(void)

     ret = qobject_to_qdict(test_qmp_dispatch(req));

-    assert(!strcmp(qdict_get_str(ret, "string"), "blah1"));
-    ret_dict = qdict_get_qdict(ret, "dict");
-    assert(!strcmp(qdict_get_str(ret_dict, "string"), "blah2"));
-    ret_dict_dict = qdict_get_qdict(ret_dict, "dict");
+    assert(!strcmp(qdict_get_str(ret, "string0"), "blah1"));
+    ret_dict = qdict_get_qdict(ret, "dict1");
+    assert(!strcmp(qdict_get_str(ret_dict, "string1"), "blah2"));
+    ret_dict_dict = qdict_get_qdict(ret_dict, "dict2");
     ret_dict_dict_userdef = qdict_get_qdict(ret_dict_dict, "userdef");
     assert(qdict_get_int(ret_dict_dict_userdef, "integer") == 42);
     assert(!strcmp(qdict_get_str(ret_dict_dict_userdef, "string"), "hello"));
     assert(!strcmp(qdict_get_str(ret_dict_dict, "string"), "blah3"));
-    ret_dict_dict2 = qdict_get_qdict(ret_dict, "dict2");
+    ret_dict_dict2 = qdict_get_qdict(ret_dict, "dict3");
     ret_dict_dict2_userdef = qdict_get_qdict(ret_dict_dict2, "userdef");
     assert(qdict_get_int(ret_dict_dict2_userdef, "integer") == 422);
     assert(!strcmp(qdict_get_str(ret_dict_dict2_userdef, "string"), "hello2"));
@@ -192,7 +192,7 @@ static void test_dealloc_partial(void)
         QmpInputVisitor *qiv;

         ud2_dict = qdict_new();
-        qdict_put_obj(ud2_dict, "string", QOBJECT(qstring_from_str(text)));
+        qdict_put_obj(ud2_dict, "string0", QOBJECT(qstring_from_str(text)));

         qiv = qmp_input_visitor_new(QOBJECT(ud2_dict));
         visit_type_UserDefTwo(qmp_input_get_visitor(qiv), &ud2, NULL, &err);
@@ -202,9 +202,9 @@ static void test_dealloc_partial(void)

     /* verify partial success */
     assert(ud2 != NULL);
-    assert(ud2->string != NULL);
-    assert(strcmp(ud2->string, text) == 0);
-    assert(ud2->dict.dict.userdef == NULL);
+    assert(ud2->string0 != NULL);
+    assert(strcmp(ud2->string0, text) == 0);
+    assert(ud2->dict1.dict2.userdef == NULL);

     /* confirm & release construction error */
     assert(err != NULL);
diff --git a/tests/test-qmp-input-strict.c b/tests/test-qmp-input-strict.c
index a5f7bf3..68f855b 100644
--- a/tests/test-qmp-input-strict.c
+++ b/tests/test-qmp-input-strict.c
@@ -116,15 +116,18 @@ static void test_validate_struct(TestInputVisitorData *data,
 static void test_validate_struct_nested(TestInputVisitorData *data,
                                          const void *unused)
 {
-    UserDefNested *udp = NULL;
+    UserDefTwo *udp = NULL;
     Error *err = NULL;
     Visitor *v;

-    v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}");
+    v = validate_test_init(data, "{ 'string0': 'string0', "
+                           "'dict1': { 'string1': 'string1', "
+                           "'dict2': { 'userdef': { 'integer': 42, "
+                           "'string': 'string' }, 'string': 'string2'}}}");

-    visit_type_UserDefNested(v, &udp, NULL, &err);
+    visit_type_UserDefTwo(v, &udp, NULL, &err);
     g_assert(!err);
-    qapi_free_UserDefNested(udp);
+    qapi_free_UserDefTwo(udp);
 }

 static void test_validate_list(TestInputVisitorData *data,
@@ -207,15 +210,15 @@ static void test_validate_fail_struct(TestInputVisitorData *data,
 static void test_validate_fail_struct_nested(TestInputVisitorData *data,
                                               const void *unused)
 {
-    UserDefNested *udp = NULL;
+    UserDefTwo *udp = NULL;
     Error *err = NULL;
     Visitor *v;

     v = validate_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string', 'extra': [42, 23, {'foo':'bar'}] }, 'string2': 'string2'}}}");

-    visit_type_UserDefNested(v, &udp, NULL, &err);
+    visit_type_UserDefTwo(v, &udp, NULL, &err);
     g_assert(err);
-    qapi_free_UserDefNested(udp);
+    qapi_free_UserDefTwo(udp);
 }

 static void test_validate_fail_list(TestInputVisitorData *data,
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 882359a..115fb22 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -248,23 +248,26 @@ static void check_and_free_str(char *str, const char *cmp)
 static void test_visitor_in_struct_nested(TestInputVisitorData *data,
                                           const void *unused)
 {
-    UserDefNested *udp = NULL;
+    UserDefTwo *udp = NULL;
     Error *err = NULL;
     Visitor *v;

-    v = visitor_input_test_init(data, "{ 'string0': 'string0', 'dict1': { 'string1': 'string1', 'dict2': { 'userdef1': { 'integer': 42, 'string': 'string' }, 'string2': 'string2'}}}");
+    v = visitor_input_test_init(data, "{ 'string0': 'string0', "
+                                "'dict1': { 'string1': 'string1', "
+                                "'dict2': { 'userdef': { 'integer': 42, "
+                                "'string': 'string' }, 'string': 'string2'}}}");

-    visit_type_UserDefNested(v, &udp, NULL, &err);
+    visit_type_UserDefTwo(v, &udp, NULL, &err);
     g_assert(!err);

     check_and_free_str(udp->string0, "string0");
     check_and_free_str(udp->dict1.string1, "string1");
-    g_assert_cmpint(udp->dict1.dict2.userdef1->base->integer, ==, 42);
-    check_and_free_str(udp->dict1.dict2.userdef1->string, "string");
-    check_and_free_str(udp->dict1.dict2.string2, "string2");
+    g_assert_cmpint(udp->dict1.dict2.userdef->base->integer, ==, 42);
+    check_and_free_str(udp->dict1.dict2.userdef->string, "string");
+    check_and_free_str(udp->dict1.dict2.string, "string2");
     g_assert(udp->dict1.has_dict3 == false);

-    g_free(udp->dict1.dict2.userdef1);
+    g_free(udp->dict1.dict2.userdef);
     g_free(udp);
 }

diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 602bc12..0584b4d 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -234,7 +234,7 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
 {
     int64_t value = 42;
     Error *err = NULL;
-    UserDefNested *ud2;
+    UserDefTwo *ud2;
     QObject *obj;
     QDict *qdict, *dict1, *dict2, *dict3, *userdef;
     const char *string = "user def string";
@@ -245,20 +245,20 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
     ud2->string0 = g_strdup(strings[0]);

     ud2->dict1.string1 = g_strdup(strings[1]);
-    ud2->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne));
-    ud2->dict1.dict2.userdef1->string = g_strdup(string);
-    ud2->dict1.dict2.userdef1->base = g_new0(UserDefZero, 1);
-    ud2->dict1.dict2.userdef1->base->integer = value;
-    ud2->dict1.dict2.string2 = g_strdup(strings[2]);
+    ud2->dict1.dict2.userdef = g_new0(UserDefOne, 1);
+    ud2->dict1.dict2.userdef->string = g_strdup(string);
+    ud2->dict1.dict2.userdef->base = g_new0(UserDefZero, 1);
+    ud2->dict1.dict2.userdef->base->integer = value;
+    ud2->dict1.dict2.string = g_strdup(strings[2]);

     ud2->dict1.has_dict3 = true;
-    ud2->dict1.dict3.userdef2 = g_malloc0(sizeof(UserDefOne));
-    ud2->dict1.dict3.userdef2->string = g_strdup(string);
-    ud2->dict1.dict3.userdef2->base = g_new0(UserDefZero, 1);
-    ud2->dict1.dict3.userdef2->base->integer = value;
-    ud2->dict1.dict3.string3 = g_strdup(strings[3]);
+    ud2->dict1.dict3.userdef = g_new0(UserDefOne, 1);
+    ud2->dict1.dict3.userdef->string = g_strdup(string);
+    ud2->dict1.dict3.userdef->base = g_new0(UserDefZero, 1);
+    ud2->dict1.dict3.userdef->base->integer = value;
+    ud2->dict1.dict3.string = g_strdup(strings[3]);

-    visit_type_UserDefNested(data->ov, &ud2, "unused", &err);
+    visit_type_UserDefTwo(data->ov, &ud2, "unused", &err);
     g_assert(!err);

     obj = qmp_output_get_qobject(data->qov);
@@ -275,22 +275,22 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data,

     dict2 = qdict_get_qdict(dict1, "dict2");
     g_assert_cmpint(qdict_size(dict2), ==, 2);
-    g_assert_cmpstr(qdict_get_str(dict2, "string2"), ==, strings[2]);
-    userdef = qdict_get_qdict(dict2, "userdef1");
+    g_assert_cmpstr(qdict_get_str(dict2, "string"), ==, strings[2]);
+    userdef = qdict_get_qdict(dict2, "userdef");
     g_assert_cmpint(qdict_size(userdef), ==, 2);
     g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value);
     g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string);

     dict3 = qdict_get_qdict(dict1, "dict3");
     g_assert_cmpint(qdict_size(dict3), ==, 2);
-    g_assert_cmpstr(qdict_get_str(dict3, "string3"), ==, strings[3]);
-    userdef = qdict_get_qdict(dict3, "userdef2");
+    g_assert_cmpstr(qdict_get_str(dict3, "string"), ==, strings[3]);
+    userdef = qdict_get_qdict(dict3, "userdef");
     g_assert_cmpint(qdict_size(userdef), ==, 2);
     g_assert_cmpint(qdict_get_int(userdef, "integer"), ==, value);
     g_assert_cmpstr(qdict_get_str(userdef, "string"), ==, string);

     QDECREF(qdict);
-    qapi_free_UserDefNested(ud2);
+    qapi_free_UserDefTwo(ud2);
 }

 static void test_visitor_out_struct_errors(TestOutputVisitorData *data,
@@ -398,7 +398,7 @@ static void test_visitor_out_list(TestOutputVisitorData *data,
 static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data,
                                             const void *unused)
 {
-    UserDefNestedList *p, *head = NULL;
+    UserDefTwoList *p, *head = NULL;
     const char string[] = "foo bar";
     int i, max_count = 1024;

@@ -408,18 +408,18 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data,

         p->value->string0 = g_strdup(string);
         p->value->dict1.string1 = g_strdup(string);
-        p->value->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne));
-        p->value->dict1.dict2.userdef1->string = g_strdup(string);
-        p->value->dict1.dict2.userdef1->base = g_new0(UserDefZero, 1);
-        p->value->dict1.dict2.userdef1->base->integer = 42;
-        p->value->dict1.dict2.string2 = g_strdup(string);
+        p->value->dict1.dict2.userdef = g_new0(UserDefOne, 1);
+        p->value->dict1.dict2.userdef->string = g_strdup(string);
+        p->value->dict1.dict2.userdef->base = g_new0(UserDefZero, 1);
+        p->value->dict1.dict2.userdef->base->integer = 42;
+        p->value->dict1.dict2.string = g_strdup(string);
         p->value->dict1.has_dict3 = false;

         p->next = head;
         head = p;
     }

-    qapi_free_UserDefNestedList(head);
+    qapi_free_UserDefTwoList(head);
 }

 static void test_visitor_out_union_flat(TestOutputVisitorData *data,
diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
index 7ad1886..1ccaaa9 100644
--- a/tests/test-visitor-serialization.c
+++ b/tests/test-visitor-serialization.c
@@ -249,57 +249,57 @@ static void visit_struct(Visitor *v, void **native, Error **errp)
     visit_type_TestStruct(v, (TestStruct **)native, NULL, errp);
 }

-static UserDefNested *nested_struct_create(void)
+static UserDefTwo *nested_struct_create(void)
 {
-    UserDefNested *udnp = g_malloc0(sizeof(*udnp));
+    UserDefTwo *udnp = g_malloc0(sizeof(*udnp));
     udnp->string0 = strdup("test_string0");
     udnp->dict1.string1 = strdup("test_string1");
-    udnp->dict1.dict2.userdef1 = g_malloc0(sizeof(UserDefOne));
-    udnp->dict1.dict2.userdef1->base = g_new0(UserDefZero, 1);
-    udnp->dict1.dict2.userdef1->base->integer = 42;
-    udnp->dict1.dict2.userdef1->string = strdup("test_string");
-    udnp->dict1.dict2.string2 = strdup("test_string2");
+    udnp->dict1.dict2.userdef = g_new0(UserDefOne, 1);
+    udnp->dict1.dict2.userdef->base = g_new0(UserDefZero, 1);
+    udnp->dict1.dict2.userdef->base->integer = 42;
+    udnp->dict1.dict2.userdef->string = strdup("test_string");
+    udnp->dict1.dict2.string = strdup("test_string2");
     udnp->dict1.has_dict3 = true;
-    udnp->dict1.dict3.userdef2 = g_malloc0(sizeof(UserDefOne));
-    udnp->dict1.dict3.userdef2->base = g_new0(UserDefZero, 1);
-    udnp->dict1.dict3.userdef2->base->integer = 43;
-    udnp->dict1.dict3.userdef2->string = strdup("test_string");
-    udnp->dict1.dict3.string3 = strdup("test_string3");
+    udnp->dict1.dict3.userdef = g_new0(UserDefOne, 1);
+    udnp->dict1.dict3.userdef->base = g_new0(UserDefZero, 1);
+    udnp->dict1.dict3.userdef->base->integer = 43;
+    udnp->dict1.dict3.userdef->string = strdup("test_string");
+    udnp->dict1.dict3.string = strdup("test_string3");
     return udnp;
 }

-static void nested_struct_compare(UserDefNested *udnp1, UserDefNested *udnp2)
+static void nested_struct_compare(UserDefTwo *udnp1, UserDefTwo *udnp2)
 {
     g_assert(udnp1);
     g_assert(udnp2);
     g_assert_cmpstr(udnp1->string0, ==, udnp2->string0);
     g_assert_cmpstr(udnp1->dict1.string1, ==, udnp2->dict1.string1);
-    g_assert_cmpint(udnp1->dict1.dict2.userdef1->base->integer, ==,
-                    udnp2->dict1.dict2.userdef1->base->integer);
-    g_assert_cmpstr(udnp1->dict1.dict2.userdef1->string, ==,
-                    udnp2->dict1.dict2.userdef1->string);
-    g_assert_cmpstr(udnp1->dict1.dict2.string2, ==, udnp2->dict1.dict2.string2);
+    g_assert_cmpint(udnp1->dict1.dict2.userdef->base->integer, ==,
+                    udnp2->dict1.dict2.userdef->base->integer);
+    g_assert_cmpstr(udnp1->dict1.dict2.userdef->string, ==,
+                    udnp2->dict1.dict2.userdef->string);
+    g_assert_cmpstr(udnp1->dict1.dict2.string, ==, udnp2->dict1.dict2.string);
     g_assert(udnp1->dict1.has_dict3 == udnp2->dict1.has_dict3);
-    g_assert_cmpint(udnp1->dict1.dict3.userdef2->base->integer, ==,
-                    udnp2->dict1.dict3.userdef2->base->integer);
-    g_assert_cmpstr(udnp1->dict1.dict3.userdef2->string, ==,
-                    udnp2->dict1.dict3.userdef2->string);
-    g_assert_cmpstr(udnp1->dict1.dict3.string3, ==, udnp2->dict1.dict3.string3);
+    g_assert_cmpint(udnp1->dict1.dict3.userdef->base->integer, ==,
+                    udnp2->dict1.dict3.userdef->base->integer);
+    g_assert_cmpstr(udnp1->dict1.dict3.userdef->string, ==,
+                    udnp2->dict1.dict3.userdef->string);
+    g_assert_cmpstr(udnp1->dict1.dict3.string, ==, udnp2->dict1.dict3.string);
 }

-static void nested_struct_cleanup(UserDefNested *udnp)
+static void nested_struct_cleanup(UserDefTwo *udnp)
 {
-    qapi_free_UserDefNested(udnp);
+    qapi_free_UserDefTwo(udnp);
 }

 static void visit_nested_struct(Visitor *v, void **native, Error **errp)
 {
-    visit_type_UserDefNested(v, (UserDefNested **)native, NULL, errp);
+    visit_type_UserDefTwo(v, (UserDefTwo **)native, NULL, errp);
 }

 static void visit_nested_struct_list(Visitor *v, void **native, Error **errp)
 {
-    visit_type_UserDefNestedList(v, (UserDefNestedList **)native, NULL, errp);
+    visit_type_UserDefTwoList(v, (UserDefTwoList **)native, NULL, errp);
 }

 /* test cases */
@@ -715,13 +715,14 @@ static void test_nested_struct(gconstpointer opaque)
 {
     TestArgs *args = (TestArgs *) opaque;
     const SerializeOps *ops = args->ops;
-    UserDefNested *udnp = nested_struct_create();
-    UserDefNested *udnp_copy = NULL;
+    UserDefTwo *udnp = nested_struct_create();
+    UserDefTwo *udnp_copy = NULL;
     Error *err = NULL;
     void *serialize_data;
-    
+
     ops->serialize(udnp, &serialize_data, visit_nested_struct, &err);
-    ops->deserialize((void **)&udnp_copy, serialize_data, visit_nested_struct, &err); 
+    ops->deserialize((void **)&udnp_copy, serialize_data, visit_nested_struct,
+                     &err);

     g_assert(err == NULL);
     nested_struct_compare(udnp, udnp_copy);
@@ -737,18 +738,18 @@ static void test_nested_struct_list(gconstpointer opaque)
 {
     TestArgs *args = (TestArgs *) opaque;
     const SerializeOps *ops = args->ops;
-    UserDefNestedList *listp = NULL, *tmp, *tmp_copy, *listp_copy = NULL;
+    UserDefTwoList *listp = NULL, *tmp, *tmp_copy, *listp_copy = NULL;
     Error *err = NULL;
     void *serialize_data;
     int i = 0;

     for (i = 0; i < 8; i++) {
-        tmp = g_malloc0(sizeof(UserDefNestedList));
+        tmp = g_new0(UserDefTwoList, 1);
         tmp->value = nested_struct_create();
         tmp->next = listp;
         listp = tmp;
     }
-    
+
     ops->serialize(listp, &serialize_data, visit_nested_struct_list, &err);
     ops->deserialize((void **)&listp_copy, serialize_data,
                      visit_nested_struct_list, &err); 
@@ -764,8 +765,8 @@ static void test_nested_struct_list(gconstpointer opaque)
         listp_copy = listp_copy->next;
     }

-    qapi_free_UserDefNestedList(tmp);
-    qapi_free_UserDefNestedList(tmp_copy);
+    qapi_free_UserDefTwoList(tmp);
+    qapi_free_UserDefTwoList(tmp_copy);

     ops->cleanup(serialize_data);
     g_free(args);
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 32/36] qapi: Drop tests for inline nested structs
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (30 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 31/36] qapi: Merge UserDefTwo and UserDefNested in tests Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-28 13:00   ` Markus Armbruster
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 33/36] qapi: Drop inline nested struct in query-version Eric Blake
                   ` (7 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

A future patch will be using a 'name':{dictionary} entry in the
QAPI schema to specify a default value for an optional argument;
but existing use of inline nested structs conflicts with that goal.

More precisely, a definition in the QAPI schema associates a name
with a set of properties:

Example 1: { 'struct': 'Foo', 'data': { MEMBERS... } }
associates the global name 'Foo' with properties (meta-type struct)
and MEMBERS...

Example 2: 'mumble': TYPE
within MEMBERS... above associates 'mumble' with properties (type
TYPE) and (optional false) within type Foo

The syntax of example 1 is extensible; if we need another property,
we add another name/value pair to the dictionary (such as
'base':TYPE).  The syntax of example 2 is not extensible, because
the right hand side can only be a type.

We have used name encoding to add a property: "'*mumble': 'int'"
associates 'mumble' with (type int) and (optional true).  Nice,
but doesn't scale.  So the solution is to change our existing uses
to be syntactic sugar to an extensible form:

   NAME: TYPE   --> NAME:  { 'type': TYPE, 'optional': false }
   *ONAME: TYPE --> ONAME: { 'type': TYPE, 'optional': true }

This patch fixes the testsuite to avoid inline nested types, by
breaking the nesting into explicit types; it means that the type
is now boxed instead of unboxed in C code, but makes no difference
on the wire (and if desired, a later patch could change the
generator to not do so much boxing in C).  When touching code to
add new allocations, also convert existing allocations to
consistently prefer typesafe g_new0 over g_malloc0 when a type
name is involved.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase onto earlier changes, actually use g_new0; enhance
commit message with Markus' great write-up
---
 tests/qapi-schema/qapi-schema-test.json | 12 +++++--
 tests/qapi-schema/qapi-schema-test.out  |  8 +++--
 tests/test-qmp-commands.c               | 17 +++++-----
 tests/test-qmp-input-visitor.c          | 14 +++++----
 tests/test-qmp-output-visitor.c         | 44 ++++++++++++++------------
 tests/test-visitor-serialization.c      | 55 ++++++++++++++++++---------------
 6 files changed, 88 insertions(+), 62 deletions(-)

diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
index a6be983..8193dc1 100644
--- a/tests/qapi-schema/qapi-schema-test.json
+++ b/tests/qapi-schema/qapi-schema-test.json
@@ -14,11 +14,17 @@
   'base': 'UserDefZero',
   'data': { 'string': 'str', '*enum1': 'EnumOne' } }

+{ 'struct': 'UserDefTwoDictDict',
+  'data': { 'userdef': 'UserDefOne', 'string': 'str' } }
+
+{ 'struct': 'UserDefTwoDict',
+  'data': { 'string1': 'str',
+            'dict2': 'UserDefTwoDictDict',
+            '*dict3': 'UserDefTwoDictDict' } }
+
 { 'struct': 'UserDefTwo',
   'data': { 'string0': 'str',
-            'dict1': { 'string1': 'str',
-                       'dict2': { 'userdef': 'UserDefOne', 'string': 'str' },
-                       '*dict3': { 'userdef': 'UserDefOne', 'string': 'str' } } } }
+            'dict1': 'UserDefTwoDict' } }

 # for testing unions
 { 'struct': 'UserDefA',
diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out
index 48f8f0e..93c4963 100644
--- a/tests/qapi-schema/qapi-schema-test.out
+++ b/tests/qapi-schema/qapi-schema-test.out
@@ -2,7 +2,9 @@
  OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
  OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
- OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefTwoDictDict'), ('data', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]),
+ OrderedDict([('struct', 'UserDefTwoDict'), ('data', OrderedDict([('string1', 'str'), ('dict2', 'UserDefTwoDictDict'), ('*dict3', 'UserDefTwoDictDict')]))]),
+ OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', 'UserDefTwoDict')]))]),
  OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
  OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
@@ -27,7 +29,9 @@
 [OrderedDict([('struct', 'NestedEnumsOne'), ('data', OrderedDict([('enum1', 'EnumOne'), ('*enum2', 'EnumOne'), ('enum3', 'EnumOne'), ('*enum4', 'EnumOne')]))]),
  OrderedDict([('struct', 'UserDefZero'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefOne'), ('base', 'UserDefZero'), ('data', OrderedDict([('string', 'str'), ('*enum1', 'EnumOne')]))]),
- OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', OrderedDict([('string1', 'str'), ('dict2', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')])), ('*dict3', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]))]))]),
+ OrderedDict([('struct', 'UserDefTwoDictDict'), ('data', OrderedDict([('userdef', 'UserDefOne'), ('string', 'str')]))]),
+ OrderedDict([('struct', 'UserDefTwoDict'), ('data', OrderedDict([('string1', 'str'), ('dict2', 'UserDefTwoDictDict'), ('*dict3', 'UserDefTwoDictDict')]))]),
+ OrderedDict([('struct', 'UserDefTwo'), ('data', OrderedDict([('string0', 'str'), ('dict1', 'UserDefTwoDict')]))]),
  OrderedDict([('struct', 'UserDefA'), ('data', OrderedDict([('boolean', 'bool')]))]),
  OrderedDict([('struct', 'UserDefB'), ('data', OrderedDict([('integer', 'int')]))]),
  OrderedDict([('struct', 'UserDefC'), ('data', OrderedDict([('string1', 'str'), ('string2', 'str')]))]),
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 8b50eac..ad2e403 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -33,12 +33,15 @@ UserDefTwo *qmp_user_def_cmd2(UserDefOne *ud1a,

     ret = g_new0(UserDefTwo, 1);
     ret->string0 = strdup("blah1");
-    ret->dict1.string1 = strdup("blah2");
-    ret->dict1.dict2.userdef = ud1c;
-    ret->dict1.dict2.string = strdup("blah3");
-    ret->dict1.has_dict3 = true;
-    ret->dict1.dict3.userdef = ud1d;
-    ret->dict1.dict3.string = strdup("blah4");
+    ret->dict1 = g_new0(UserDefTwoDict, 1);
+    ret->dict1->string1 = strdup("blah2");
+    ret->dict1->dict2 = g_new0(UserDefTwoDictDict, 1);
+    ret->dict1->dict2->userdef = ud1c;
+    ret->dict1->dict2->string = strdup("blah3");
+    ret->dict1->dict3 = g_new0(UserDefTwoDictDict, 1);
+    ret->dict1->has_dict3 = true;
+    ret->dict1->dict3->userdef = ud1d;
+    ret->dict1->dict3->string = strdup("blah4");

     return ret;
 }
@@ -204,7 +207,7 @@ static void test_dealloc_partial(void)
     assert(ud2 != NULL);
     assert(ud2->string0 != NULL);
     assert(strcmp(ud2->string0, text) == 0);
-    assert(ud2->dict1.dict2.userdef == NULL);
+    assert(ud2->dict1 == NULL);

     /* confirm & release construction error */
     assert(err != NULL);
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index 115fb22..b961953 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -261,13 +261,15 @@ static void test_visitor_in_struct_nested(TestInputVisitorData *data,
     g_assert(!err);

     check_and_free_str(udp->string0, "string0");
-    check_and_free_str(udp->dict1.string1, "string1");
-    g_assert_cmpint(udp->dict1.dict2.userdef->base->integer, ==, 42);
-    check_and_free_str(udp->dict1.dict2.userdef->string, "string");
-    check_and_free_str(udp->dict1.dict2.string, "string2");
-    g_assert(udp->dict1.has_dict3 == false);
+    check_and_free_str(udp->dict1->string1, "string1");
+    g_assert_cmpint(udp->dict1->dict2->userdef->base->integer, ==, 42);
+    check_and_free_str(udp->dict1->dict2->userdef->string, "string");
+    check_and_free_str(udp->dict1->dict2->string, "string2");
+    g_assert(udp->dict1->has_dict3 == false);

-    g_free(udp->dict1.dict2.userdef);
+    g_free(udp->dict1->dict2->userdef);
+    g_free(udp->dict1->dict2);
+    g_free(udp->dict1);
     g_free(udp);
 }

diff --git a/tests/test-qmp-output-visitor.c b/tests/test-qmp-output-visitor.c
index 0584b4d..f8c9367 100644
--- a/tests/test-qmp-output-visitor.c
+++ b/tests/test-qmp-output-visitor.c
@@ -244,19 +244,23 @@ static void test_visitor_out_struct_nested(TestOutputVisitorData *data,
     ud2 = g_malloc0(sizeof(*ud2));
     ud2->string0 = g_strdup(strings[0]);

-    ud2->dict1.string1 = g_strdup(strings[1]);
-    ud2->dict1.dict2.userdef = g_new0(UserDefOne, 1);
-    ud2->dict1.dict2.userdef->string = g_strdup(string);
-    ud2->dict1.dict2.userdef->base = g_new0(UserDefZero, 1);
-    ud2->dict1.dict2.userdef->base->integer = value;
-    ud2->dict1.dict2.string = g_strdup(strings[2]);
+    ud2->dict1 = g_malloc0(sizeof(*ud2->dict1));
+    ud2->dict1->string1 = g_strdup(strings[1]);

-    ud2->dict1.has_dict3 = true;
-    ud2->dict1.dict3.userdef = g_new0(UserDefOne, 1);
-    ud2->dict1.dict3.userdef->string = g_strdup(string);
-    ud2->dict1.dict3.userdef->base = g_new0(UserDefZero, 1);
-    ud2->dict1.dict3.userdef->base->integer = value;
-    ud2->dict1.dict3.string = g_strdup(strings[3]);
+    ud2->dict1->dict2 = g_malloc0(sizeof(*ud2->dict1->dict2));
+    ud2->dict1->dict2->userdef = g_new0(UserDefOne, 1);
+    ud2->dict1->dict2->userdef->string = g_strdup(string);
+    ud2->dict1->dict2->userdef->base = g_new0(UserDefZero, 1);
+    ud2->dict1->dict2->userdef->base->integer = value;
+    ud2->dict1->dict2->string = g_strdup(strings[2]);
+
+    ud2->dict1->dict3 = g_malloc0(sizeof(*ud2->dict1->dict3));
+    ud2->dict1->has_dict3 = true;
+    ud2->dict1->dict3->userdef = g_new0(UserDefOne, 1);
+    ud2->dict1->dict3->userdef->string = g_strdup(string);
+    ud2->dict1->dict3->userdef->base = g_new0(UserDefZero, 1);
+    ud2->dict1->dict3->userdef->base->integer = value;
+    ud2->dict1->dict3->string = g_strdup(strings[3]);

     visit_type_UserDefTwo(data->ov, &ud2, "unused", &err);
     g_assert(!err);
@@ -407,13 +411,15 @@ static void test_visitor_out_list_qapi_free(TestOutputVisitorData *data,
         p->value = g_malloc0(sizeof(*p->value));

         p->value->string0 = g_strdup(string);
-        p->value->dict1.string1 = g_strdup(string);
-        p->value->dict1.dict2.userdef = g_new0(UserDefOne, 1);
-        p->value->dict1.dict2.userdef->string = g_strdup(string);
-        p->value->dict1.dict2.userdef->base = g_new0(UserDefZero, 1);
-        p->value->dict1.dict2.userdef->base->integer = 42;
-        p->value->dict1.dict2.string = g_strdup(string);
-        p->value->dict1.has_dict3 = false;
+        p->value->dict1 = g_new0(UserDefTwoDict, 1);
+        p->value->dict1->string1 = g_strdup(string);
+        p->value->dict1->dict2 = g_new0(UserDefTwoDictDict, 1);
+        p->value->dict1->dict2->userdef = g_new0(UserDefOne, 1);
+        p->value->dict1->dict2->userdef->string = g_strdup(string);
+        p->value->dict1->dict2->userdef->base = g_new0(UserDefZero, 1);
+        p->value->dict1->dict2->userdef->base->integer = 42;
+        p->value->dict1->dict2->string = g_strdup(string);
+        p->value->dict1->has_dict3 = false;

         p->next = head;
         head = p;
diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
index 1ccaaa9..52c8d1a 100644
--- a/tests/test-visitor-serialization.c
+++ b/tests/test-visitor-serialization.c
@@ -1,7 +1,7 @@
 /*
  * Unit-tests for visitor-based serialization
  *
- * Copyright IBM, Corp. 2012
+ * Copyright IBM, Corp. 2012, 2015
  *
  * Authors:
  *  Michael Roth <mdroth@linux.vnet.ibm.com>
@@ -253,18 +253,21 @@ static UserDefTwo *nested_struct_create(void)
 {
     UserDefTwo *udnp = g_malloc0(sizeof(*udnp));
     udnp->string0 = strdup("test_string0");
-    udnp->dict1.string1 = strdup("test_string1");
-    udnp->dict1.dict2.userdef = g_new0(UserDefOne, 1);
-    udnp->dict1.dict2.userdef->base = g_new0(UserDefZero, 1);
-    udnp->dict1.dict2.userdef->base->integer = 42;
-    udnp->dict1.dict2.userdef->string = strdup("test_string");
-    udnp->dict1.dict2.string = strdup("test_string2");
-    udnp->dict1.has_dict3 = true;
-    udnp->dict1.dict3.userdef = g_new0(UserDefOne, 1);
-    udnp->dict1.dict3.userdef->base = g_new0(UserDefZero, 1);
-    udnp->dict1.dict3.userdef->base->integer = 43;
-    udnp->dict1.dict3.userdef->string = strdup("test_string");
-    udnp->dict1.dict3.string = strdup("test_string3");
+    udnp->dict1 = g_malloc0(sizeof(*udnp->dict1));
+    udnp->dict1->string1 = strdup("test_string1");
+    udnp->dict1->dict2 = g_malloc0(sizeof(*udnp->dict1->dict2));
+    udnp->dict1->dict2->userdef = g_new0(UserDefOne, 1);
+    udnp->dict1->dict2->userdef->base = g_new0(UserDefZero, 1);
+    udnp->dict1->dict2->userdef->base->integer = 42;
+    udnp->dict1->dict2->userdef->string = strdup("test_string");
+    udnp->dict1->dict2->string = strdup("test_string2");
+    udnp->dict1->dict3 = g_malloc0(sizeof(*udnp->dict1->dict3));
+    udnp->dict1->has_dict3 = true;
+    udnp->dict1->dict3->userdef = g_new0(UserDefOne, 1);
+    udnp->dict1->dict3->userdef->base = g_new0(UserDefZero, 1);
+    udnp->dict1->dict3->userdef->base->integer = 43;
+    udnp->dict1->dict3->userdef->string = strdup("test_string");
+    udnp->dict1->dict3->string = strdup("test_string3");
     return udnp;
 }

@@ -273,18 +276,20 @@ static void nested_struct_compare(UserDefTwo *udnp1, UserDefTwo *udnp2)
     g_assert(udnp1);
     g_assert(udnp2);
     g_assert_cmpstr(udnp1->string0, ==, udnp2->string0);
-    g_assert_cmpstr(udnp1->dict1.string1, ==, udnp2->dict1.string1);
-    g_assert_cmpint(udnp1->dict1.dict2.userdef->base->integer, ==,
-                    udnp2->dict1.dict2.userdef->base->integer);
-    g_assert_cmpstr(udnp1->dict1.dict2.userdef->string, ==,
-                    udnp2->dict1.dict2.userdef->string);
-    g_assert_cmpstr(udnp1->dict1.dict2.string, ==, udnp2->dict1.dict2.string);
-    g_assert(udnp1->dict1.has_dict3 == udnp2->dict1.has_dict3);
-    g_assert_cmpint(udnp1->dict1.dict3.userdef->base->integer, ==,
-                    udnp2->dict1.dict3.userdef->base->integer);
-    g_assert_cmpstr(udnp1->dict1.dict3.userdef->string, ==,
-                    udnp2->dict1.dict3.userdef->string);
-    g_assert_cmpstr(udnp1->dict1.dict3.string, ==, udnp2->dict1.dict3.string);
+    g_assert_cmpstr(udnp1->dict1->string1, ==, udnp2->dict1->string1);
+    g_assert_cmpint(udnp1->dict1->dict2->userdef->base->integer, ==,
+                    udnp2->dict1->dict2->userdef->base->integer);
+    g_assert_cmpstr(udnp1->dict1->dict2->userdef->string, ==,
+                    udnp2->dict1->dict2->userdef->string);
+    g_assert_cmpstr(udnp1->dict1->dict2->string, ==,
+                    udnp2->dict1->dict2->string);
+    g_assert(udnp1->dict1->has_dict3 == udnp2->dict1->has_dict3);
+    g_assert_cmpint(udnp1->dict1->dict3->userdef->base->integer, ==,
+                    udnp2->dict1->dict3->userdef->base->integer);
+    g_assert_cmpstr(udnp1->dict1->dict3->userdef->string, ==,
+                    udnp2->dict1->dict3->userdef->string);
+    g_assert_cmpstr(udnp1->dict1->dict3->string, ==,
+                    udnp2->dict1->dict3->string);
 }

 static void nested_struct_cleanup(UserDefTwo *udnp)
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 33/36] qapi: Drop inline nested struct in query-version
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (31 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 32/36] qapi: Drop tests for inline nested structs Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 34/36] qapi: Drop inline nested structs in query-pci Eric Blake
                   ` (6 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

A future patch will be using a 'name':{dictionary} entry in the
QAPI schema to specify a default value for an optional argument
(see previous commit message for more details why); but existing
use of inline nested structs conflicts with that goal. This patch
fixes one of only two commands relying on nested types, by
breaking the nesting into an explicit type; it means that the
type is now boxed instead of unboxed in C code, but the QMP wire
format is unaffected by this change.

Prefer the safer g_new0() while making the conversion.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase onto earlier changes
---
 hmp.c            |  2 +-
 qapi/common.json | 26 +++++++++++++++++++-------
 qmp.c            |  9 +++++----
 3 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/hmp.c b/hmp.c
index f31ae27..c59b823 100644
--- a/hmp.c
+++ b/hmp.c
@@ -60,7 +60,7 @@ void hmp_info_version(Monitor *mon, const QDict *qdict)
     info = qmp_query_version(NULL);

     monitor_printf(mon, "%" PRId64 ".%" PRId64 ".%" PRId64 "%s\n",
-                   info->qemu.major, info->qemu.minor, info->qemu.micro,
+                   info->qemu->major, info->qemu->minor, info->qemu->micro,
                    info->package);

     qapi_free_VersionInfo(info);
diff --git a/qapi/common.json b/qapi/common.json
index 12431c6..bad56bf 100644
--- a/qapi/common.json
+++ b/qapi/common.json
@@ -29,15 +29,28 @@
             'DeviceNotActive', 'DeviceNotFound', 'KVMMissingCap' ] }

 ##
+# @VersionTriple
+#
+# A three-part version number.
+#
+# @qemu.major:  The major version number.
+#
+# @qemu.minor:  The minor version number.
+#
+# @qemu.micro:  The micro version number.
+#
+# Since: 2.4
+##
+{ 'struct': 'VersionTriple',
+  'data': {'major': 'int', 'minor': 'int', 'micro': 'int'} }
+
+
+##
 # @VersionInfo:
 #
 # A description of QEMU's version.
 #
-# @qemu.major:  The major version of QEMU
-#
-# @qemu.minor:  The minor version of QEMU
-#
-# @qemu.micro:  The micro version of QEMU.  By current convention, a micro
+# @qemu:        The version of QEMU.  By current convention, a micro
 #               version of 50 signifies a development branch.  A micro version
 #               greater than or equal to 90 signifies a release candidate for
 #               the next minor version.  A micro version of less than 50
@@ -51,8 +64,7 @@
 # Since: 0.14.0
 ##
 { 'struct': 'VersionInfo',
-  'data': {'qemu': {'major': 'int', 'minor': 'int', 'micro': 'int'},
-           'package': 'str'} }
+  'data': {'qemu': 'VersionTriple', 'package': 'str'} }

 ##
 # @query-version:
diff --git a/qmp.c b/qmp.c
index e6c7050..3f5dfe3 100644
--- a/qmp.c
+++ b/qmp.c
@@ -45,15 +45,16 @@ NameInfo *qmp_query_name(Error **errp)

 VersionInfo *qmp_query_version(Error **errp)
 {
-    VersionInfo *info = g_malloc0(sizeof(*info));
+    VersionInfo *info = g_new0(VersionInfo, 1);
     const char *version = QEMU_VERSION;
     char *tmp;

-    info->qemu.major = strtol(version, &tmp, 10);
+    info->qemu = g_new0(VersionTriple, 1);
+    info->qemu->major = strtol(version, &tmp, 10);
     tmp++;
-    info->qemu.minor = strtol(tmp, &tmp, 10);
+    info->qemu->minor = strtol(tmp, &tmp, 10);
     tmp++;
-    info->qemu.micro = strtol(tmp, &tmp, 10);
+    info->qemu->micro = strtol(tmp, &tmp, 10);
     info->package = g_strdup(QEMU_PKGVERSION);

     return info;
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 34/36] qapi: Drop inline nested structs in query-pci
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (32 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 33/36] qapi: Drop inline nested struct in query-version Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 35/36] qapi: Drop support for inline nested types Eric Blake
                   ` (5 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

A future patch will be using a 'name':{dictionary} entry in the
QAPI schema to specify a default value for an optional argument
(see previous commit message for more details why); but existing
use of inline nested structs conflicts with that goal. This patch
fixes one of only two commands relying on nested types, by
breaking the nesting into an explicit type; it means that the
type is now boxed instead of unboxed in C code, but the QMP wire
format is unaffected by this change.

Prefer the safer g_new0() while making the conversion, and reduce
some long lines.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---

v6: rebase onto earlier changes
---
 hmp.c            | 26 ++++++++--------
 hw/pci/pci.c     | 42 ++++++++++++++------------
 qapi-schema.json | 90 ++++++++++++++++++++++++++++++++++++++------------------
 3 files changed, 98 insertions(+), 60 deletions(-)

diff --git a/hmp.c b/hmp.c
index c59b823..dbc7fec 100644
--- a/hmp.c
+++ b/hmp.c
@@ -648,14 +648,14 @@ static void hmp_info_pci_device(Monitor *mon, const PciDeviceInfo *dev)
                    dev->slot, dev->function);
     monitor_printf(mon, "    ");

-    if (dev->class_info.has_desc) {
-        monitor_printf(mon, "%s", dev->class_info.desc);
+    if (dev->class_info->has_desc) {
+        monitor_printf(mon, "%s", dev->class_info->desc);
     } else {
-        monitor_printf(mon, "Class %04" PRId64, dev->class_info.q_class);
+        monitor_printf(mon, "Class %04" PRId64, dev->class_info->q_class);
     }

     monitor_printf(mon, ": PCI device %04" PRIx64 ":%04" PRIx64 "\n",
-                   dev->id.vendor, dev->id.device);
+                   dev->id->vendor, dev->id->device);

     if (dev->has_irq) {
         monitor_printf(mon, "      IRQ %" PRId64 ".\n", dev->irq);
@@ -663,25 +663,25 @@ static void hmp_info_pci_device(Monitor *mon, const PciDeviceInfo *dev)

     if (dev->has_pci_bridge) {
         monitor_printf(mon, "      BUS %" PRId64 ".\n",
-                       dev->pci_bridge->bus.number);
+                       dev->pci_bridge->bus->number);
         monitor_printf(mon, "      secondary bus %" PRId64 ".\n",
-                       dev->pci_bridge->bus.secondary);
+                       dev->pci_bridge->bus->secondary);
         monitor_printf(mon, "      subordinate bus %" PRId64 ".\n",
-                       dev->pci_bridge->bus.subordinate);
+                       dev->pci_bridge->bus->subordinate);

         monitor_printf(mon, "      IO range [0x%04"PRIx64", 0x%04"PRIx64"]\n",
-                       dev->pci_bridge->bus.io_range->base,
-                       dev->pci_bridge->bus.io_range->limit);
+                       dev->pci_bridge->bus->io_range->base,
+                       dev->pci_bridge->bus->io_range->limit);

         monitor_printf(mon,
                        "      memory range [0x%08"PRIx64", 0x%08"PRIx64"]\n",
-                       dev->pci_bridge->bus.memory_range->base,
-                       dev->pci_bridge->bus.memory_range->limit);
+                       dev->pci_bridge->bus->memory_range->base,
+                       dev->pci_bridge->bus->memory_range->limit);

         monitor_printf(mon, "      prefetchable memory range "
                        "[0x%08"PRIx64", 0x%08"PRIx64"]\n",
-                       dev->pci_bridge->bus.prefetchable_range->base,
-                       dev->pci_bridge->bus.prefetchable_range->limit);
+                       dev->pci_bridge->bus->prefetchable_range->base,
+                       dev->pci_bridge->bus->prefetchable_range->limit);
     }

     for (region = dev->regions; region; region = region->next) {
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index 6941a82..c1d0068 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -1456,24 +1456,26 @@ static PciBridgeInfo *qmp_query_pci_bridge(PCIDevice *dev, PCIBus *bus,
                                            int bus_num)
 {
     PciBridgeInfo *info;
+    PciMemoryRange *range;

-    info = g_malloc0(sizeof(*info));
+    info = g_new0(PciBridgeInfo, 1);

-    info->bus.number = dev->config[PCI_PRIMARY_BUS];
-    info->bus.secondary = dev->config[PCI_SECONDARY_BUS];
-    info->bus.subordinate = dev->config[PCI_SUBORDINATE_BUS];
+    info->bus = g_new0(PciBusInfo, 1);
+    info->bus->number = dev->config[PCI_PRIMARY_BUS];
+    info->bus->secondary = dev->config[PCI_SECONDARY_BUS];
+    info->bus->subordinate = dev->config[PCI_SUBORDINATE_BUS];

-    info->bus.io_range = g_malloc0(sizeof(*info->bus.io_range));
-    info->bus.io_range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO);
-    info->bus.io_range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO);
+    range = info->bus->io_range = g_new0(PciMemoryRange, 1);
+    range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO);
+    range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO);

-    info->bus.memory_range = g_malloc0(sizeof(*info->bus.memory_range));
-    info->bus.memory_range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
-    info->bus.memory_range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+    range = info->bus->memory_range = g_new0(PciMemoryRange, 1);
+    range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+    range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);

-    info->bus.prefetchable_range = g_malloc0(sizeof(*info->bus.prefetchable_range));
-    info->bus.prefetchable_range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
-    info->bus.prefetchable_range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+    range = info->bus->prefetchable_range = g_new0(PciMemoryRange, 1);
+    range->base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+    range->limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);

     if (dev->config[PCI_SECONDARY_BUS] != 0) {
         PCIBus *child_bus = pci_find_bus_nr(bus, dev->config[PCI_SECONDARY_BUS]);
@@ -1494,21 +1496,23 @@ static PciDeviceInfo *qmp_query_pci_device(PCIDevice *dev, PCIBus *bus,
     uint8_t type;
     int class;

-    info = g_malloc0(sizeof(*info));
+    info = g_new0(PciDeviceInfo, 1);
     info->bus = bus_num;
     info->slot = PCI_SLOT(dev->devfn);
     info->function = PCI_FUNC(dev->devfn);

+    info->class_info = g_new0(PciDeviceClass, 1);
     class = pci_get_word(dev->config + PCI_CLASS_DEVICE);
-    info->class_info.q_class = class;
+    info->class_info->q_class = class;
     desc = get_class_desc(class);
     if (desc->desc) {
-        info->class_info.has_desc = true;
-        info->class_info.desc = g_strdup(desc->desc);
+        info->class_info->has_desc = true;
+        info->class_info->desc = g_strdup(desc->desc);
     }

-    info->id.vendor = pci_get_word(dev->config + PCI_VENDOR_ID);
-    info->id.device = pci_get_word(dev->config + PCI_DEVICE_ID);
+    info->id = g_new0(PciDeviceId, 1);
+    info->id->vendor = pci_get_word(dev->config + PCI_VENDOR_ID);
+    info->id->device = pci_get_word(dev->config + PCI_DEVICE_ID);
     info->regions = qmp_query_pci_regions(dev);
     info->qdev_id = g_strdup(dev->qdev.id ? dev->qdev.id : "");

diff --git a/qapi-schema.json b/qapi-schema.json
index 6a4e0df..27ec988 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1041,36 +1041,75 @@
            '*prefetch': 'bool', '*mem_type_64': 'bool' } }

 ##
+# @PciBusInfo:
+#
+# Information about a bus of a PCI Bridge device
+#
+# @number: primary bus interface number.  This should be the number of the
+#          bus the device resides on.
+#
+# @secondary: secondary bus interface number.  This is the number of the
+#             main bus for the bridge
+#
+# @subordinate: This is the highest number bus that resides below the
+#               bridge.
+#
+# @io_range: The PIO range for all devices on this bridge
+#
+# @memory_range: The MMIO range for all devices on this bridge
+#
+# @prefetchable_range: The range of prefetchable MMIO for all devices on
+#                      this bridge
+#
+# Since: 2.4
+##
+{ 'struct': 'PciBusInfo',
+  'data': {'number': 'int', 'secondary': 'int', 'subordinate': 'int',
+           'io_range': 'PciMemoryRange',
+           'memory_range': 'PciMemoryRange',
+           'prefetchable_range': 'PciMemoryRange' } }
+
+##
 # @PciBridgeInfo:
 #
 # Information about a PCI Bridge device
 #
-# @bus.number: primary bus interface number.  This should be the number of the
-#              bus the device resides on.
-#
-# @bus.secondary: secondary bus interface number.  This is the number of the
-#                 main bus for the bridge
-#
-# @bus.subordinate: This is the highest number bus that resides below the
-#                   bridge.
-#
-# @bus.io_range: The PIO range for all devices on this bridge
-#
-# @bus.memory_range: The MMIO range for all devices on this bridge
-#
-# @bus.prefetchable_range: The range of prefetchable MMIO for all devices on
-#                          this bridge
+# @bus: information about the bus the device resides on
 #
 # @devices: a list of @PciDeviceInfo for each device on this bridge
 #
 # Since: 0.14.0
 ##
 { 'struct': 'PciBridgeInfo',
-  'data': {'bus': { 'number': 'int', 'secondary': 'int', 'subordinate': 'int',
-                    'io_range': 'PciMemoryRange',
-                    'memory_range': 'PciMemoryRange',
-                    'prefetchable_range': 'PciMemoryRange' },
-           '*devices': ['PciDeviceInfo']} }
+  'data': {'bus': 'PciBusInfo', '*devices': ['PciDeviceInfo']} }
+
+##
+# @PciDeviceClass:
+#
+# Information about the Class of a PCI device
+#
+# @desc: #optional a string description of the device's class
+#
+# @class: the class code of the device
+#
+# Since: 2.4
+##
+{ 'struct': 'PciDeviceClass',
+  'data': {'*desc': 'str', 'class': 'int'} }
+
+##
+# @PciDeviceId:
+#
+# Information about the Id of a PCI device
+#
+# @device: the PCI device id
+#
+# @vendor: the PCI vendor id
+#
+# Since: 2.4
+##
+{ 'struct': 'PciDeviceId',
+  'data': {'device': 'int', 'vendor': 'int'} }

 ##
 # @PciDeviceInfo:
@@ -1083,13 +1122,9 @@
 #
 # @function: the function of the slot used by the device
 #
-# @class_info.desc: #optional a string description of the device's class
+# @class_info: the class of the device
 #
-# @class_info.class: the class code of the device
-#
-# @id.device: the PCI device id
-#
-# @id.vendor: the PCI vendor id
+# @id: the PCI device id
 #
 # @irq: #optional if an IRQ is assigned to the device, the IRQ number
 #
@@ -1106,8 +1141,7 @@
 ##
 { 'struct': 'PciDeviceInfo',
   'data': {'bus': 'int', 'slot': 'int', 'function': 'int',
-           'class_info': {'*desc': 'str', 'class': 'int'},
-           'id': {'device': 'int', 'vendor': 'int'},
+           'class_info': 'PciDeviceClass', 'id': 'PciDeviceId',
            '*irq': 'int', 'qdev_id': 'str', '*pci_bridge': 'PciBridgeInfo',
            'regions': ['PciMemoryRegion']} }

-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 35/36] qapi: Drop support for inline nested types
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (33 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 34/36] qapi: Drop inline nested structs in query-pci Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 36/36] qapi: Tweak doc references to QMP when QGA is also meant Eric Blake
                   ` (4 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

A future patch will be using a 'name':{dictionary} entry in the
QAPI schema to specify a default value for an optional argument
(see previous commit messages for more details why); but existing
use of inline nested structs conflicts with that goal. Now that
all commands have been changed to avoid inline nested structs,
nuke support for them, and turn it into a hard error. Update the
testsuite to reflect tighter parsing rules.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase onto earlier changes
---
 scripts/qapi-commands.py                     |  8 +++---
 scripts/qapi-event.py                        |  4 +--
 scripts/qapi-types.py                        |  9 ++-----
 scripts/qapi-visit.py                        | 37 ++++------------------------
 scripts/qapi.py                              | 20 ++++++---------
 tests/qapi-schema/event-nest-struct.err      |  2 +-
 tests/qapi-schema/nested-struct-data.err     |  1 +
 tests/qapi-schema/nested-struct-data.exit    |  2 +-
 tests/qapi-schema/nested-struct-data.json    |  2 +-
 tests/qapi-schema/nested-struct-data.out     |  3 ---
 tests/qapi-schema/nested-struct-returns.err  |  1 +
 tests/qapi-schema/nested-struct-returns.exit |  2 +-
 tests/qapi-schema/nested-struct-returns.json |  2 +-
 tests/qapi-schema/nested-struct-returns.out  |  3 ---
 14 files changed, 27 insertions(+), 69 deletions(-)

diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py
index 053ba85..db81044 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi-commands.py
@@ -28,7 +28,7 @@ def type_visitor(name):

 def generate_command_decl(name, args, ret_type):
     arglist=""
-    for argname, argtype, optional, structured in parse_args(args):
+    for argname, argtype, optional in parse_args(args):
         argtype = c_type(argtype, is_param=True)
         if optional:
             arglist += "bool has_%s, " % c_var(argname)
@@ -53,7 +53,7 @@ def gen_sync_call(name, args, ret_type, indent=0):
     retval=""
     if ret_type:
         retval = "retval = "
-    for argname, argtype, optional, structured in parse_args(args):
+    for argname, argtype, optional in parse_args(args):
         if optional:
             arglist += "has_%s, " % c_var(argname)
         arglist += "%s, " % (c_var(argname))
@@ -96,7 +96,7 @@ Visitor *v;
 def gen_visitor_input_vars_decl(args):
     ret = ""
     push_indent()
-    for argname, argtype, optional, structured in parse_args(args):
+    for argname, argtype, optional in parse_args(args):
         if optional:
             ret += mcgen('''
 bool has_%(argname)s = false;
@@ -139,7 +139,7 @@ v = qapi_dealloc_get_visitor(md);
 v = qmp_input_get_visitor(mi);
 ''')

-    for argname, argtype, optional, structured in parse_args(args):
+    for argname, argtype, optional in parse_args(args):
         if optional:
             ret += mcgen('''
 visit_optional(v, &has_%(c_name)s, "%(name)s", %(errp)s);
diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py
index 601e307..47dc041 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi-event.py
@@ -21,7 +21,7 @@ def _generate_event_api_name(event_name, params):
     l = len(api_name)

     if params:
-        for argname, argentry, optional, structured in parse_args(params):
+        for argname, argentry, optional in parse_args(params):
             if optional:
                 api_name += "bool has_%s,\n" % c_var(argname)
                 api_name += "".ljust(l)
@@ -93,7 +93,7 @@ def generate_event_implement(api_name, event_name, params):
 """,
                 event_name = event_name)

-        for argname, argentry, optional, structured in parse_args(params):
+        for argname, argentry, optional in parse_args(params):
             if optional:
                 ret += mcgen("""
     if (has_%(var)s) {
diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
index a429d9e..2bf8145 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi-types.py
@@ -63,18 +63,13 @@ typedef struct %(name)sList
 def generate_struct_fields(members):
     ret = ''

-    for argname, argentry, optional, structured in parse_args(members):
+    for argname, argentry, optional in parse_args(members):
         if optional:
             ret += mcgen('''
     bool has_%(c_name)s;
 ''',
                          c_name=c_var(argname))
-        if structured:
-            push_indent()
-            ret += generate_struct({ "field": argname, "data": argentry})
-            pop_indent()
-        else:
-            ret += mcgen('''
+        ret += mcgen('''
     %(c_type)s %(c_name)s;
 ''',
                      c_type=c_type(argentry), c_name=c_var(argname))
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index eeeca82..f5d4355 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -51,27 +51,6 @@ def generate_visit_struct_fields(name, field_prefix, fn_prefix, members, base =
     else:
         full_name = "%s_%s" % (name, fn_prefix)

-    for argname, argentry, optional, structured in parse_args(members):
-        if structured:
-            if not fn_prefix:
-                nested_fn_prefix = argname
-            else:
-                nested_fn_prefix = "%s_%s" % (fn_prefix, argname)
-
-            nested_field_prefix = "%s%s." % (field_prefix, argname)
-            ret += generate_visit_struct_fields(name, nested_field_prefix,
-                                                nested_fn_prefix, argentry)
-            ret += mcgen('''
-
-static void visit_type_%(full_name)s_field_%(c_name)s(Visitor *m, %(name)s **obj, Error **errp)
-{
-''',
-                         name=name, full_name=full_name, c_name=c_var(argname))
-            ret += generate_visit_struct_body(full_name, argname, argentry)
-            ret += mcgen('''
-}
-''')
-
     if base:
         ret += generate_visit_implicit_struct(base)

@@ -94,7 +73,7 @@ if (err) {
                      c_prefix=c_var(field_prefix),
                      type=type_name(base), c_name=c_var('base'))

-    for argname, argentry, optional, structured in parse_args(members):
+    for argname, argentry, optional in parse_args(members):
         if optional:
             ret += mcgen('''
 visit_optional(m, &(*obj)->%(c_prefix)shas_%(c_name)s, "%(name)s", &err);
@@ -104,18 +83,12 @@ if (!err && (*obj)->%(prefix)shas_%(c_name)s) {
                          c_name=c_var(argname), name=argname)
             push_indent()

-        if structured:
-            ret += mcgen('''
-visit_type_%(full_name)s_field_%(c_name)s(m, obj, &err);
-''',
-                         full_name=full_name, c_name=c_var(argname))
-        else:
-            ret += mcgen('''
+        ret += mcgen('''
 visit_type_%(type)s(m, &(*obj)->%(c_prefix)s%(c_name)s, "%(name)s", &err);
 ''',
-                         c_prefix=c_var(field_prefix), prefix=field_prefix,
-                         type=type_name(argentry), c_name=c_var(argname),
-                         name=argname)
+                     c_prefix=c_var(field_prefix), prefix=field_prefix,
+                     type=type_name(argentry), c_name=c_var(argname),
+                     name=argname)

         if optional:
             pop_indent()
diff --git a/scripts/qapi.py b/scripts/qapi.py
index effba38..9fd9b96 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -373,10 +373,12 @@ def check_type(expr_info, source, value, allow_array = False,
     for (key, arg) in value.items():
         check_name(expr_info, "Member of %s" % source, key,
                    allow_optional=allow_optional)
+        # Todo: allow dictionaries to represent default values of
+        # an optional argument.
         check_type(expr_info, "Member '%s' of %s" % (key, source), arg,
-                   allow_array=True, allow_dict=True, allow_optional=True,
+                   allow_array=True, allow_star=allow_star,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
-                                'enum'], allow_star=allow_star)
+                                'enum'])

 def check_command(expr, expr_info):
     name = expr['command']
@@ -404,13 +406,6 @@ def check_event(expr, expr_info):
     check_type(expr_info, "'data' for event '%s'" % name,
                expr.get('data'), allow_dict=True, allow_optional=True,
                allow_metas=['union', 'struct'])
-    if params:
-        for argname, argentry, optional, structured in parse_args(params):
-            if structured:
-                raise QAPIExprError(expr_info,
-                                    "Nested structure define in event is not "
-                                    "supported, event '%s', argname '%s'"
-                                    % (expr['event'], argname))

 def check_union(expr, expr_info):
     name = expr['union']
@@ -671,13 +666,12 @@ def parse_args(typeinfo):
         argname = member
         argentry = typeinfo[member]
         optional = False
-        structured = False
         if member.startswith('*'):
             argname = member[1:]
             optional = True
-        if isinstance(argentry, OrderedDict):
-            structured = True
-        yield (argname, argentry, optional, structured)
+        # Todo: allow argentry to be OrderedDict, for providing the
+        # value of an optional argument.
+        yield (argname, argentry, optional)

 def de_camel_case(name):
     new_name = ''
diff --git a/tests/qapi-schema/event-nest-struct.err b/tests/qapi-schema/event-nest-struct.err
index 91bde1c..5a42701 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: Nested structure define in event is not supported, event 'EVENT_A', argname 'a'
+tests/qapi-schema/event-nest-struct.json:1: Member 'a' of 'data' for event 'EVENT_A' should be a type name
diff --git a/tests/qapi-schema/nested-struct-data.err b/tests/qapi-schema/nested-struct-data.err
index e69de29..da767ba 100644
--- a/tests/qapi-schema/nested-struct-data.err
+++ b/tests/qapi-schema/nested-struct-data.err
@@ -0,0 +1 @@
+tests/qapi-schema/nested-struct-data.json:2: Member 'a' of 'data' for command 'foo' should be a type name
diff --git a/tests/qapi-schema/nested-struct-data.exit b/tests/qapi-schema/nested-struct-data.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/nested-struct-data.exit
+++ b/tests/qapi-schema/nested-struct-data.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/nested-struct-data.json b/tests/qapi-schema/nested-struct-data.json
index 0247c8c..3d52d2b 100644
--- a/tests/qapi-schema/nested-struct-data.json
+++ b/tests/qapi-schema/nested-struct-data.json
@@ -1,4 +1,4 @@
-# FIXME: inline subtypes collide with our desired future use of defaults
+# inline subtypes collide with our desired future use of defaults
 { 'command': 'foo',
   'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' },
   'returns': {} }
diff --git a/tests/qapi-schema/nested-struct-data.out b/tests/qapi-schema/nested-struct-data.out
index 999cbb8..e69de29 100644
--- a/tests/qapi-schema/nested-struct-data.out
+++ b/tests/qapi-schema/nested-struct-data.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'foo'), ('data', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')])), ('returns', OrderedDict())])]
-[]
-[]
diff --git a/tests/qapi-schema/nested-struct-returns.err b/tests/qapi-schema/nested-struct-returns.err
index e69de29..5238d07 100644
--- a/tests/qapi-schema/nested-struct-returns.err
+++ b/tests/qapi-schema/nested-struct-returns.err
@@ -0,0 +1 @@
+tests/qapi-schema/nested-struct-returns.json:2: Member 'a' of 'returns' for command 'foo' should be a type name
diff --git a/tests/qapi-schema/nested-struct-returns.exit b/tests/qapi-schema/nested-struct-returns.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/nested-struct-returns.exit
+++ b/tests/qapi-schema/nested-struct-returns.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/nested-struct-returns.json b/tests/qapi-schema/nested-struct-returns.json
index 5a46840..d2cd047 100644
--- a/tests/qapi-schema/nested-struct-returns.json
+++ b/tests/qapi-schema/nested-struct-returns.json
@@ -1,3 +1,3 @@
-# FIXME: inline subtypes collide with our desired future use of defaults
+# inline subtypes collide with our desired future use of defaults
 { 'command': 'foo',
   'returns': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } }
diff --git a/tests/qapi-schema/nested-struct-returns.out b/tests/qapi-schema/nested-struct-returns.out
index c53d23b..e69de29 100644
--- a/tests/qapi-schema/nested-struct-returns.out
+++ b/tests/qapi-schema/nested-struct-returns.out
@@ -1,3 +0,0 @@
-[OrderedDict([('command', 'foo'), ('returns', OrderedDict([('a', OrderedDict([('string', 'str'), ('integer', 'int')])), ('b', 'str')]))])]
-[]
-[]
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 36/36] qapi: Tweak doc references to QMP when QGA is also meant
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (34 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 35/36] qapi: Drop support for inline nested types Eric Blake
@ 2015-04-05  4:08 ` Eric Blake
  2015-04-06 13:13 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (3 subsequent siblings)
  39 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-05  4:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

We have more than one qapi schema in use by more than one protocol.
Add a new term 'Client JSON Protocol' for use throughout the
document, to avoid confusion on whether something refers only to
QMP and not QGA.

Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>

---

v6: rebase to earlier changes (was patch 4/5 in followup series)
---
 docs/qapi-code-gen.txt | 156 +++++++++++++++++++++++++------------------------
 1 file changed, 81 insertions(+), 75 deletions(-)

diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index 6206032..a2efcf3 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -13,12 +13,14 @@ functionality to internal and external users. For external
 users/processes, this interface is made available by a JSON-based wire
 format for the QEMU Monitor Protocol (QMP) for controlling qemu, as
 well as the QEMU Guest Agent (QGA) for communicating with the guest.
+The remainder of this document uses "Client JSON Protocol" when
+referring to the wire contents of a QMP or QGA connection.

-To map QMP and QGA interfaces to the native C QAPI implementations, a
-JSON-based schema is used to define types and function signatures, and
-a set of scripts is used to generate types, signatures, and
-marshaling/dispatch code. This document will describe how the schemas,
-scripts, and resulting code are used.
+To map Client JSON Protocol interfaces to the native C QAPI
+implementations, a JSON-based schema is used to define types and
+function signatures, and a set of scripts is used to generate types,
+signatures, and marshaling/dispatch code. This document will describe
+how the schemas, scripts, and resulting code are used.


 == QMP/Guest agent schema ==
@@ -34,12 +36,13 @@ generated C structs and parameter lists).  Ordering doesn't matter
 between top-level expressions or the keys within an expression, but
 does matter within dictionary values for 'data' and 'returns' members
 of a single expression.  QAPI schema input is written using 'single
-quotes' instead of JSON's "double quotes" (in contrast, QMP usage is
-strict JSON and only uses "double quotes", with no comments).  As in
-JSON, trailing commas are not permitted in arrays or dictionaries.
-Input must be ASCII (although QMP supports full Unicode strings, the
-QAPI parser does not).  At present, there is no place where a QAPI
-schema requires the use of JSON numbers or null.
+quotes' instead of JSON's "double quotes" (in contrast, Client JSON
+Protocol usage is strict JSON and only uses "double quotes", with no
+comments).  As in JSON, trailing commas are not permitted in arrays or
+dictionaries. Input must be ASCII (although a Client JSON Protocol
+supports full Unicode strings, the QAPI parser does not).  At present,
+there is no place where a QAPI schema requires the use of JSON numbers
+or null.

 Comments are allowed; anything between an unquoted # and the following
 newline is ignored.  Although there is not yet a documentation
@@ -81,8 +84,11 @@ that will use those types.  Forward references are allowed: the parser
 scans in two passes, where the first pass learns all type names, and
 the second validates the schema and generates the code.  This allows
 the definition of complex structs that can have mutually recursive
-types, and allows for indefinite nesting of QMP that satisfies the
-schema.  A type name should not be defined more than once.
+types, and allows for indefinite nesting of Client JSON Protocol that
+satisfies the schema.  A type name should not be defined more than
+once.  It is permissible for the schema to contain additional types
+not used by any commands or events in the Client JSON Protocol, for
+the side effect of generated C code used internally.

 There are seven top-level expressions recognized by the parser:
 'include', 'command', 'struct', 'enum', 'union', 'alternate', and
@@ -181,7 +187,7 @@ struct is:
    'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } }

 The use of '*' as a prefix to the name means the member is optional in
-the corresponding QMP usage.
+the corresponding JSON protocol usage.

 The default initialization value of an optional argument should not be changed
 between versions of QEMU unless the new default maintains backward
@@ -212,8 +218,8 @@ of use.

 A struct definition can specify another struct as its base.
 In this case, the fields of the base type are included as top-level fields
-of the new struct's dictionary in the QMP wire format. An example
-definition is:
+of the new struct's dictionary in the Client JSON Protocol wire
+format. An example definition is:

  { 'struct': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } }
  { 'struct': 'BlockdevOptionsGenericCOWFormat',
@@ -241,19 +247,19 @@ useful.  The list of strings should be lower case; if an enum name
 represents multiple words, use '-' between words.  The string 'max' is
 not allowed as an enum value, and values should not be repeated.

-The enumeration values are passed as strings over the QMP protocol,
-but are encoded as C enum integral values in generated code.  While
-the C code starts numbering at 0, it is better to use explicit
+The enumeration values are passed as strings over the Client JSON
+Protocol, but are encoded as C enum integral values in generated code.
+While the C code starts numbering at 0, it is better to use explicit
 comparisons to enum values than implicit comparisons to 0; the C code
 will also include a generated enum member ending in _MAX for tracking
 the size of the enum, useful when using common functions for
 converting between strings and enum values.  Since the wire format
 always passes by name, it is acceptable to reorder or add new
-enumeration members in any location without breaking QMP clients;
-however, removing enum values would break compatibility.  For any
-struct that has a field that will only contain a finite set of
-string values, using an enum type for that field is better than
-open-coding the field to be type 'str'.
+enumeration members in any location without breaking clients of Client
+JSON Protocol; however, removing enum values would break
+compatibility.  For any struct that has a field that will only contain
+a finite set of string values, using an enum type for that field is
+better than open-coding the field to be type 'str'.


 === Union types ===
@@ -279,10 +285,10 @@ values to data types like in this example:
    'data': { 'file': 'FileOptions',
              'qcow2': 'Qcow2Options' } }

-In the QMP wire format, a simple union is represented by a dictionary
-that contains the 'type' field as a discriminator, and a 'data' field
-that is of the specified data type corresponding to the discriminator
-value, as in these examples:
+In the Client JSON Protocol, a simple union is represented by a
+dictionary that contains the 'type' field as a discriminator, and a
+'data' field that is of the specified data type corresponding to the
+discriminator value, as in these examples:

  { "type": "file", "data" : { "filename": "/some/place/my-image" } }
  { "type": "qcow2", "data" : { "backing-file": "/some/place/my-image",
@@ -365,16 +371,16 @@ Just like for a simple union, an implicit C enum 'NameKind' is created
 to enumerate the branches for the alternate 'Name'.

 Unlike a union, the discriminator string is never passed on the wire
-for QMP.  Instead, the value's JSON type serves as an implicit
-discriminator, which in turn means that an alternate can only express
-a choice between types represented differently in JSON.  If a branch
-is typed as the 'bool' built-in, the alternate accepts true and false;
-if it is typed as any of the various numeric built-ins, it accepts a
-JSON number; if it is typed as a 'str' built-in or named enum type, it
-accepts a JSON string; and if it is typed as a complex type (struct or
-union), it accepts a JSON object.  Two different complex types, for
-instance, aren't permitted, because both are represented as a JSON
-object.
+for the Client JSON Protocol.  Instead, the value's JSON type serves
+as an implicit discriminator, which in turn means that an alternate
+can only express a choice between types represented differently in
+JSON.  If a branch is typed as the 'bool' built-in, the alternate
+accepts true and false; if it is typed as any of the various numeric
+built-ins, it accepts a JSON number; if it is typed as a 'str'
+built-in or named enum type, it accepts a JSON string; and if it is
+typed as a complex type (struct or union), it accepts a JSON object.
+Two different complex types, for instance, aren't permitted, because
+both are represented as a JSON object.

 The example alternate declaration above allows using both of the
 following example objects:
@@ -393,37 +399,37 @@ Usage: { 'command': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT,

 Commands are defined by using a dictionary containing several members,
 where three members are most common.  The 'command' member is a
-mandatory string, and determines the "execute" value passed in a QMP
-command exchange.
+mandatory string, and determines the "execute" value passed in a
+Client JSON Protocol command exchange.

 The 'data' argument maps to the "arguments" dictionary passed in as
-part of a QMP command.  The 'data' member is optional and defaults to
-{} (an empty dictionary).  If present, it must be the string name of a
-complex type, a one-element array containing the name of a complex
-type, or a dictionary that declares an anonymous type with the same
-semantics as a 'struct' expression, with one exception noted below when
-'gen' is used.
+part of a Client JSON Protocol command.  The 'data' member is optional
+and defaults to {} (an empty dictionary).  If present, it must be the
+string name of a complex type, a one-element array containing the name
+of a complex type, or a dictionary that declares an anonymous type
+with the same semantics as a 'struct' expression, with one exception
+noted below when 'gen' is used.

 The 'returns' member describes what will appear in the "return" field
-of a QMP reply on successful completion of a command.  The member is
-optional from the command declaration; if absent, the "return" field
-will be an empty dictionary.  If 'returns' is present, it must be the
-string name of a complex or built-in type, a one-element array
-containing the name of a complex or built-in type, or a dictionary
-that declares an anonymous type with the same semantics as a 'struct'
-expression, with one exception noted below when 'gen' is used.
-Although it is permitted to have the 'returns' member name a built-in
-type or an array of built-in types, any command that does this cannot
-be extended to return additional information in the future; thus, new
-commands should strongly consider returning a dictionary-based type or
-an array of dictionaries, even if the dictionary only contains one
-field at the present.
+of a Client JSON Protocol reply on successful completion of a command.
+The member is optional from the command declaration; if absent, the
+"return" field will be an empty dictionary.  If 'returns' is present,
+it must be the string name of a complex or built-in type, a
+one-element array containing the name of a complex or built-in type,
+or a dictionary that declares an anonymous type with the same
+semantics as a 'struct' expression, with one exception noted below
+when 'gen' is used.  Although it is permitted to have the 'returns'
+member name a built-in type or an array of built-in types, any command
+that does this cannot be extended to return additional information in
+the future; thus, new commands should strongly consider returning a
+dictionary-based type or an array of dictionaries, even if the
+dictionary only contains one field at the present.

-All commands use a dictionary to report failure, with no way to
-specify that in QAPI.  Where the error return is different than the
-usual GenericError class in order to help the client react differently
-to certain error conditions, it is worth documenting this in the
-comments before the command declaration.
+All commands in Client JSON Protocol use a dictionary to report
+failure, with no way to specify that in QAPI.  Where the error return
+is different than the usual GenericError class in order to help the
+client react differently to certain error conditions, it is worth
+documenting this in the comments before the command declaration.

 Some example commands:

@@ -433,7 +439,7 @@ Some example commands:
  { 'command': 'my-second-command',
    'returns': [ 'MyType' ] }

-which would validate this QMP transaction:
+which would validate this Client JSON Protocol transaction:

  => { "execute": "my-first-command",
       "arguments": { "arg1": "hello" } }
@@ -442,14 +448,14 @@ which would validate this QMP transaction:
  <= { "return": [ { "value": "one" }, { } ] }

 In rare cases, QAPI cannot express a type-safe representation of a
-corresponding QMP command.  In these cases, if the command expression
-includes the key 'gen' with boolean value false, then the 'data' or
-'returns' member that intends to bypass generated type-safety and do
-its own manual validation should use an inline dictionary definition,
-with a value of '**' rather than a valid type name for the keys that
-the generated code will not validate.  Please try to avoid adding new
-commands that rely on this, and instead use type-safe unions.  For an
-example of bypass usage:
+corresponding Client JSON Protocol command.  In these cases, if the
+command expression includes the key 'gen' with boolean value false,
+then the 'data' or 'returns' member that intends to bypass generated
+type-safety and do its own manual validation should use an inline
+dictionary definition, with a value of '**' rather than a valid type
+name for the keys that the generated code will not validate.  Please
+try to avoid adding new commands that rely on this, and instead use
+type-safe unions.  For an example of bypass usage:

  { 'command': 'netdev_add',
    'data': {'type': 'str', 'id': 'str', '*props': '**'},
@@ -493,9 +499,9 @@ Resulting in this JSON object:

 Schemas are fed into 3 scripts to generate all the code/files that, paired
 with the core QAPI libraries, comprise everything required to take JSON
-commands read in by a QMP/guest agent server, unmarshal the arguments into
+commands read in by a Client JSON Protocol server, unmarshal the arguments into
 the underlying C types, call into the corresponding C function, and map the
-response back to a QMP/guest agent response to be returned to the user.
+response back to a Client JSON Protocol response to be returned to the user.

 As an example, we'll use the following schema, which describes a single
 complex user-defined type (which will produce a C struct, along with a list
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (35 preceding siblings ...)
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 36/36] qapi: Tweak doc references to QMP when QGA is also meant Eric Blake
@ 2015-04-06 13:13 ` Eric Blake
  2015-04-08  7:35   ` Alberto Garcia
  2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 37/36] qapi: Support (subset of) \u escapes in strings Eric Blake
                   ` (2 subsequent siblings)
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-06 13:13 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, Alberto Garcia, armbru

[-- Attachment #1: Type: text/plain, Size: 636 bytes --]

On 04/04/2015 10:07 PM, Eric Blake wrote:
> We want to eventually allow qapi defaults, by making:
>  'data':{'*flag':'bool'}
> as shorthand for something like:
>  'data':{'flag':{'type':'bool', 'optional':true}}
> so that the default can be specified:
>  'data':{'flag':{'type':'bool', 'optional':true, 'default':true}}

My apologies - I mis-typed Berto's email address, so if you
reply-to-all, you'll get a bounce message if you don't manually fix it.
Is it worth me resending the series with an updated CC list?

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
  2015-04-06 13:13 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
@ 2015-04-08  7:35   ` Alberto Garcia
  0 siblings, 0 replies; 72+ messages in thread
From: Alberto Garcia @ 2015-04-08  7:35 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, qemu-devel, armbru

On Mon, Apr 06, 2015 at 07:13:28AM -0600, Eric Blake wrote:

> My apologies - I mis-typed Berto's email address, so if you
> reply-to-all, you'll get a bounce message if you don't manually fix
> it.  Is it worth me resending the series with an updated CC list?

For me it's not necessary, but thanks for pointing it out!

Berto

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations Eric Blake
@ 2015-04-08 16:50   ` Eric Blake
  2015-04-27 15:42   ` Markus Armbruster
  2015-04-28 19:42   ` Eric Blake
  2 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-08 16:50 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, Alberto Garcia, armbru

[-- Attachment #1: Type: text/plain, Size: 2629 bytes --]

On 04/04/2015 10:07 PM, Eric Blake wrote:
> Go into more details about the various types of valid expressions
> in a qapi schema, including tweaks to document fixes being done
> later in the current patch series.  Also fix some stale and missing
> documentation in the QMP specification.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> 
> ---
> 
> v6: split copyright change into another patch; merge in 1/5, 3/5,
> and 5/5 of doc cleanup series; drop simple union with base docs,
> and mention relation between simple and flat unions; mention QMP
> is encoded as UTF-8; mention extensions of 'single \' quote' and
> "\'"; mention that repeating json-object (dict) keys is unspecified.

A bit of self-review:

> +++ b/docs/qmp/qmp-spec.txt
> @@ -13,8 +13,11 @@ Last revised in March 2015.
> @@ -62,7 +78,19 @@ The greeting message format is:
>  - The "version" member contains the Server's version information (the format
>    is the same of the query-version command)
>  - The "capabilities" member specify the availability of features beyond the
> -  baseline specification
> +  baseline specification; the order of elements in this array has no
> +  particular significance, so a client must search the entire array
> +  when looking for a particular capability
> +
> +When first connecting to the server, the connection is in a capability
> +exchange mode, further documented below.

This sentence is redundant with the earlier sentence in the same section:

>> Right when connected the Server will issue a greeting message, which signals
>> that the connection has been successfully established and that the Server is
>> ready for capabilities negotiation (for more information refer to section
>> '4. Capabilities Negotiation').

I'll wait for other reviews before deciding whether it is easier to do
as a patch that a maintainer squashes in on commit of v6, as a v7
respin, or as an independent followup patch.

>  4. Capabilities Negotiation
> -----------------------------
> +===========================
> 
>  When a Client successfully establishes a connection, the Server is in
>  Capabilities Negotiation mode.

Meanwhile, as this has just come up on the list, I'm now looking at how
easy it would be to make capability negotiation give saner error messages :)
https://lists.gnu.org/archive/html/qemu-devel/2015-04/msg00833.html

Which of course may mean further touching of this file, and therefore
justifying a separate cleanup patch anyways.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 37/36] qapi: Support (subset of) \u escapes in strings
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (36 preceding siblings ...)
  2015-04-06 13:13 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
@ 2015-04-10 20:28 ` Eric Blake
  2015-04-28 13:23   ` Markus Armbruster
  2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 38/36] qapi: Check for member name conflicts with a base class Eric Blake
  2015-04-28 14:02 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Markus Armbruster
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-10 20:28 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

The handling of \ inside QAPI strings was less than ideal, and
really only worked JSON's \/, \\, \", and our extension of \'
(an obvious extension, when you realize we use '' instead of ""
for strings).  For other things, like '\n', it resulted in a
literal 'n' instead of a newline.

Of course, at the moment, we really have no use for escaped
characters, as QAPI has to map to C identifiers, and we currently
support ASCII only for that.  But down the road, we may add
support for default values for string parameters to a command
or struct; if that happens, it would be nice to correctly support
all JSON escape sequences, such as \n or \uXXXX.  This gets us
closer, by supporting Unicode escapes in the ASCII range.

Since JSON does not require \OCTAL or \xXX escapes, I did not
add it here, but it would be an easy addition if we desired it.

Signed-off-by: Eric Blake <eblake@redhat.com>
---

v6: new patch

---
 scripts/qapi.py                          | 33 +++++++++++++++++++++++++++++++-
 tests/Makefile                           |  1 +
 tests/qapi-schema/escape-too-big.err     |  1 +
 tests/qapi-schema/escape-too-big.exit    |  1 +
 tests/qapi-schema/escape-too-big.json    |  3 +++
 tests/qapi-schema/escape-too-big.out     |  0
 tests/qapi-schema/escape-too-short.err   |  1 +
 tests/qapi-schema/escape-too-short.exit  |  1 +
 tests/qapi-schema/escape-too-short.json  |  3 +++
 tests/qapi-schema/escape-too-short.out   |  0
 tests/qapi-schema/ident-with-escape.err  |  1 -
 tests/qapi-schema/ident-with-escape.exit |  2 +-
 tests/qapi-schema/ident-with-escape.json |  2 +-
 tests/qapi-schema/ident-with-escape.out  |  3 +++
 tests/qapi-schema/unicode-str.err        |  1 +
 tests/qapi-schema/unicode-str.exit       |  1 +
 tests/qapi-schema/unicode-str.json       |  2 ++
 tests/qapi-schema/unicode-str.out        |  0
 18 files changed, 52 insertions(+), 4 deletions(-)
 create mode 100644 tests/qapi-schema/escape-too-big.err
 create mode 100644 tests/qapi-schema/escape-too-big.exit
 create mode 100644 tests/qapi-schema/escape-too-big.json
 create mode 100644 tests/qapi-schema/escape-too-big.out
 create mode 100644 tests/qapi-schema/escape-too-short.err
 create mode 100644 tests/qapi-schema/escape-too-short.exit
 create mode 100644 tests/qapi-schema/escape-too-short.json
 create mode 100644 tests/qapi-schema/escape-too-short.out
 create mode 100644 tests/qapi-schema/unicode-str.err
 create mode 100644 tests/qapi-schema/unicode-str.exit
 create mode 100644 tests/qapi-schema/unicode-str.json
 create mode 100644 tests/qapi-schema/unicode-str.out

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 60ed34a..853f9a3 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -173,7 +173,38 @@ class QAPISchema:
                         raise QAPISchemaError(self,
                                               'Missing terminating "\'"')
                     if esc:
-                        string += ch
+                        if ch == 'b':
+                            string += '\b'
+                        elif ch == 'f':
+                            string += '\f'
+                        elif ch == 'n':
+                            string += '\n'
+                        elif ch == 'r':
+                            string += '\r'
+                        elif ch == 't':
+                            string += '\t'
+                        elif ch == 'u':
+                            value = 0
+                            for x in range(0, 4):
+                                ch = self.src[self.cursor]
+                                self.cursor += 1
+                                if ch not in "0123456789abcdefABCDEF":
+                                    raise QAPISchemaError(self,
+                                                          '\\u escape needs 4 '
+                                                          'hex digits')
+                                value = (value << 4) + int(ch, 16)
+                            # If Python 2 and 3 didn't disagree so much on
+                            # how to handle Unicode, then we could allow
+                            # Unicode string defaults.  But most of QAPI is
+                            # ASCII-only, so we aren't losing much for now.
+                            if value > 0x7f:
+                                raise QAPISchemaError(self,
+                                                      'For now, \\u escape '
+                                                      'only supports values '
+                                                      'up to \\u007f')
+                            string += chr(value)
+                        else:
+                            string += ch
                         esc = False
                     elif ch == "\\":
                         esc = True
diff --git a/tests/Makefile b/tests/Makefile
index f37cd01..0cd114f 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -212,6 +212,7 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	enum-clash-member.json enum-max-member.json enum-union-clash.json \
 	enum-bad-name.json funny-char.json indented-expr.json \
 	missing-type.json bad-ident.json ident-with-escape.json \
+	escape-too-short.json escape-too-big.json unicode-str.json \
 	double-type.json bad-base.json bad-type-bool.json bad-type-int.json \
 	bad-type-dict.json double-data.json unknown-expr-key.json \
 	redefined-type.json redefined-command.json redefined-builtin.json \
diff --git a/tests/qapi-schema/escape-too-big.err b/tests/qapi-schema/escape-too-big.err
new file mode 100644
index 0000000..7f3976e
--- /dev/null
+++ b/tests/qapi-schema/escape-too-big.err
@@ -0,0 +1 @@
+tests/qapi-schema/escape-too-big.json:3:14: For now, \u escape only supports values up to \u007f
diff --git a/tests/qapi-schema/escape-too-big.exit b/tests/qapi-schema/escape-too-big.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/escape-too-big.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/escape-too-big.json b/tests/qapi-schema/escape-too-big.json
new file mode 100644
index 0000000..62bcecd
--- /dev/null
+++ b/tests/qapi-schema/escape-too-big.json
@@ -0,0 +1,3 @@
+# we don't support full Unicode strings, yet
+# { 'command': 'é' }
+{ 'command': '\u00e9' }
diff --git a/tests/qapi-schema/escape-too-big.out b/tests/qapi-schema/escape-too-big.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/escape-too-short.err b/tests/qapi-schema/escape-too-short.err
new file mode 100644
index 0000000..934de59
--- /dev/null
+++ b/tests/qapi-schema/escape-too-short.err
@@ -0,0 +1 @@
+tests/qapi-schema/escape-too-short.json:3:14: \u escape needs 4 hex digits
diff --git a/tests/qapi-schema/escape-too-short.exit b/tests/qapi-schema/escape-too-short.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/escape-too-short.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/escape-too-short.json b/tests/qapi-schema/escape-too-short.json
new file mode 100644
index 0000000..6cb1dec
--- /dev/null
+++ b/tests/qapi-schema/escape-too-short.json
@@ -0,0 +1,3 @@
+# the \u escape requires 4 hex digits
+# { 'command': 'a' }
+{ 'command': '\u61' }
diff --git a/tests/qapi-schema/escape-too-short.out b/tests/qapi-schema/escape-too-short.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/ident-with-escape.err b/tests/qapi-schema/ident-with-escape.err
index f7d1c55..e69de29 100644
--- a/tests/qapi-schema/ident-with-escape.err
+++ b/tests/qapi-schema/ident-with-escape.err
@@ -1 +0,0 @@
-tests/qapi-schema/ident-with-escape.json:3: Expression is missing metatype
diff --git a/tests/qapi-schema/ident-with-escape.exit b/tests/qapi-schema/ident-with-escape.exit
index d00491f..573541a 100644
--- a/tests/qapi-schema/ident-with-escape.exit
+++ b/tests/qapi-schema/ident-with-escape.exit
@@ -1 +1 @@
-1
+0
diff --git a/tests/qapi-schema/ident-with-escape.json b/tests/qapi-schema/ident-with-escape.json
index cfb2050..5661750 100644
--- a/tests/qapi-schema/ident-with-escape.json
+++ b/tests/qapi-schema/ident-with-escape.json
@@ -1,4 +1,4 @@
-# FIXME: we should allow escape sequences in strings, if they map back to ASCII
+# we allow escape sequences in strings, if they map back to ASCII
 # { 'command': 'fooA', 'data': { 'bar1': 'str' } }
 { 'c\u006fmmand': '\u0066\u006f\u006FA',
   'd\u0061ta': { '\u0062\u0061\u00721': '\u0073\u0074\u0072' } }
diff --git a/tests/qapi-schema/ident-with-escape.out b/tests/qapi-schema/ident-with-escape.out
index e69de29..4028430 100644
--- a/tests/qapi-schema/ident-with-escape.out
+++ b/tests/qapi-schema/ident-with-escape.out
@@ -0,0 +1,3 @@
+[OrderedDict([('command', 'fooA'), ('data', OrderedDict([('bar1', 'str')]))])]
+[]
+[]
diff --git a/tests/qapi-schema/unicode-str.err b/tests/qapi-schema/unicode-str.err
new file mode 100644
index 0000000..f621cd6
--- /dev/null
+++ b/tests/qapi-schema/unicode-str.err
@@ -0,0 +1 @@
+tests/qapi-schema/unicode-str.json:2: 'command' uses invalid name 'é'
diff --git a/tests/qapi-schema/unicode-str.exit b/tests/qapi-schema/unicode-str.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/unicode-str.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/unicode-str.json b/tests/qapi-schema/unicode-str.json
new file mode 100644
index 0000000..5253a1b
--- /dev/null
+++ b/tests/qapi-schema/unicode-str.json
@@ -0,0 +1,2 @@
+# we don't support full Unicode strings, yet
+{ 'command': 'é' }
diff --git a/tests/qapi-schema/unicode-str.out b/tests/qapi-schema/unicode-str.out
new file mode 100644
index 0000000..e69de29
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* [Qemu-devel] [PATCH v6 38/36] qapi: Check for member name conflicts with a base class
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (37 preceding siblings ...)
  2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 37/36] qapi: Support (subset of) \u escapes in strings Eric Blake
@ 2015-04-10 20:28 ` Eric Blake
  2015-04-28 13:35   ` Markus Armbruster
  2015-04-28 14:02 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Markus Armbruster
  39 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-10 20:28 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

Our type inheritance for both 'struct' and for flat 'union' merges
key/value pairs from the base class with those from the type in
question.  Although the C code currently boxes things so that there
is a distinction between which member is referred to, the QMP wire
format does not allow passing a key more than once in a single
object.  Besides, if we ever change the generated C code to not be
quite so boxy, we'd want to avoid duplicate member names there,
too.

Fix a testsuite entry added in an earlier patch, as well as adding
a couple more tests to ensure we have appropriate coverage.

Signed-off-by: Eric Blake <eblake@redhat.com>
---

v6: new patch

---
 scripts/qapi.py                                | 21 ++++++++++++++++++++-
 tests/Makefile                                 |  3 ++-
 tests/qapi-schema/flat-union-branch-clash.err  |  1 +
 tests/qapi-schema/flat-union-branch-clash.exit |  2 +-
 tests/qapi-schema/flat-union-branch-clash.json |  2 +-
 tests/qapi-schema/flat-union-branch-clash.out  |  9 ---------
 tests/qapi-schema/struct-base-clash-deep.err   |  1 +
 tests/qapi-schema/struct-base-clash-deep.exit  |  1 +
 tests/qapi-schema/struct-base-clash-deep.json  |  9 +++++++++
 tests/qapi-schema/struct-base-clash-deep.out   |  0
 tests/qapi-schema/struct-base-clash.err        |  1 +
 tests/qapi-schema/struct-base-clash.exit       |  1 +
 tests/qapi-schema/struct-base-clash.json       |  6 ++++++
 tests/qapi-schema/struct-base-clash.out        |  0
 14 files changed, 44 insertions(+), 13 deletions(-)
 create mode 100644 tests/qapi-schema/struct-base-clash-deep.err
 create mode 100644 tests/qapi-schema/struct-base-clash-deep.exit
 create mode 100644 tests/qapi-schema/struct-base-clash-deep.json
 create mode 100644 tests/qapi-schema/struct-base-clash-deep.out
 create mode 100644 tests/qapi-schema/struct-base-clash.err
 create mode 100644 tests/qapi-schema/struct-base-clash.exit
 create mode 100644 tests/qapi-schema/struct-base-clash.json
 create mode 100644 tests/qapi-schema/struct-base-clash.out

diff --git a/scripts/qapi.py b/scripts/qapi.py
index 853f9a3..281e762 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -429,6 +429,18 @@ def check_type(expr_info, source, value, allow_array = False,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
                                 'enum'])

+def check_member_clash(expr_info, base_name, data, source = ""):
+    base = find_struct(base_name)
+    assert base
+    base_members = base['data']
+    for key in data.keys():
+        if key in base_members:
+            raise QAPIExprError(expr_info,
+                                "Member name '%s'%s clashes with base '%s'"
+                                %(key, source, base_name))
+    if base.get('base'):
+        check_member_clash(expr_info, base['base'], data, source)
+
 def check_command(expr, expr_info):
     name = expr['command']
     allow_star = expr.has_key('gen')
@@ -518,9 +530,14 @@ def check_union(expr, expr_info):
         check_name(expr_info, "Member of union '%s'" % name, key)

         # Each value must name a known type; futhermore, in flat unions,
-        # branches must be a struct
+        # branches must be a struct with no overlapping member names
         check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
                    value, allow_array=True, allow_metas=allow_metas)
+        if base:
+            branch_struct = find_struct(value)
+            assert branch_struct
+            check_member_clash(expr_info, expr['base'], branch_struct['data'],
+                               " of branch '%s'" %key)

         # If the discriminator names an enum type, then all members
         # of 'data' must also be members of the enum type.
@@ -597,6 +614,8 @@ def check_struct(expr, expr_info):
                allow_dict=True, allow_optional=True)
     check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'),
                allow_metas=['struct'])
+    if expr.get('base'):
+        check_member_clash(expr_info, expr['base'], expr['data'])

 def check_exprs(schema):
     for expr_elem in schema.exprs:
diff --git a/tests/Makefile b/tests/Makefile
index 0cd114f..eb5655e 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -242,7 +242,8 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/, \
 	include-simple.json include-relpath.json include-format-err.json \
 	include-non-file.json include-no-file.json include-before-err.json \
 	include-nested-err.json include-self-cycle.json include-cycle.json \
-	include-repetition.json event-nest-struct.json event-case.json)
+	include-repetition.json event-nest-struct.json event-case.json \
+	struct-base-clash.json struct-base-clash-deep.json )

 GENERATED_HEADERS += tests/test-qapi-types.h tests/test-qapi-visit.h \
 		     tests/test-qmp-commands.h tests/test-qapi-event.h
diff --git a/tests/qapi-schema/flat-union-branch-clash.err b/tests/qapi-schema/flat-union-branch-clash.err
index e69de29..f112766 100644
--- a/tests/qapi-schema/flat-union-branch-clash.err
+++ b/tests/qapi-schema/flat-union-branch-clash.err
@@ -0,0 +1 @@
+tests/qapi-schema/flat-union-branch-clash.json:10: Member name 'name' of branch 'value1' clashes with base 'Base'
diff --git a/tests/qapi-schema/flat-union-branch-clash.exit b/tests/qapi-schema/flat-union-branch-clash.exit
index 573541a..d00491f 100644
--- a/tests/qapi-schema/flat-union-branch-clash.exit
+++ b/tests/qapi-schema/flat-union-branch-clash.exit
@@ -1 +1 @@
-0
+1
diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json
index 8b0b807..b3c6ffe 100644
--- a/tests/qapi-schema/flat-union-branch-clash.json
+++ b/tests/qapi-schema/flat-union-branch-clash.json
@@ -1,4 +1,4 @@
-# FIXME: we should check for no duplicate keys between branches and base
+# we check for no duplicate keys between branches and base
 { 'enum': 'TestEnum',
   'data': [ 'value1', 'value2' ] }
 { 'struct': 'Base',
diff --git a/tests/qapi-schema/flat-union-branch-clash.out b/tests/qapi-schema/flat-union-branch-clash.out
index 04c2395..e69de29 100644
--- a/tests/qapi-schema/flat-union-branch-clash.out
+++ b/tests/qapi-schema/flat-union-branch-clash.out
@@ -1,9 +0,0 @@
-[OrderedDict([('enum', 'TestEnum'), ('data', ['value1', 'value2'])]),
- OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
- OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))]),
- OrderedDict([('union', 'TestUnion'), ('base', 'Base'), ('discriminator', 'enum1'), ('data', OrderedDict([('value1', 'Branch1'), ('value2', 'Branch2')]))])]
-[{'enum_name': 'TestEnum', 'enum_values': ['value1', 'value2']}]
-[OrderedDict([('struct', 'Base'), ('data', OrderedDict([('enum1', 'TestEnum'), ('name', 'str')]))]),
- OrderedDict([('struct', 'Branch1'), ('data', OrderedDict([('name', 'str')]))]),
- OrderedDict([('struct', 'Branch2'), ('data', OrderedDict([('value', 'int')]))])]
diff --git a/tests/qapi-schema/struct-base-clash-deep.err b/tests/qapi-schema/struct-base-clash-deep.err
new file mode 100644
index 0000000..e3e9f8d
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash-deep.err
@@ -0,0 +1 @@
+tests/qapi-schema/struct-base-clash-deep.json:7: Member name 'name' clashes with base 'Base'
diff --git a/tests/qapi-schema/struct-base-clash-deep.exit b/tests/qapi-schema/struct-base-clash-deep.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash-deep.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/struct-base-clash-deep.json b/tests/qapi-schema/struct-base-clash-deep.json
new file mode 100644
index 0000000..08c8c9c
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash-deep.json
@@ -0,0 +1,9 @@
+# we check for no duplicate keys with indirect base
+{ 'struct': 'Base',
+  'data': { 'name': 'str' } }
+{ 'struct': 'Mid',
+  'base': 'Base',
+  'data': { 'value': 'int' } }
+{ 'struct': 'Sub',
+  'base': 'Mid',
+  'data': { 'name': 'str' } }
diff --git a/tests/qapi-schema/struct-base-clash-deep.out b/tests/qapi-schema/struct-base-clash-deep.out
new file mode 100644
index 0000000..e69de29
diff --git a/tests/qapi-schema/struct-base-clash.err b/tests/qapi-schema/struct-base-clash.err
new file mode 100644
index 0000000..3ac37fb
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash.err
@@ -0,0 +1 @@
+tests/qapi-schema/struct-base-clash.json:4: Member name 'name' clashes with base 'Base'
diff --git a/tests/qapi-schema/struct-base-clash.exit b/tests/qapi-schema/struct-base-clash.exit
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash.exit
@@ -0,0 +1 @@
+1
diff --git a/tests/qapi-schema/struct-base-clash.json b/tests/qapi-schema/struct-base-clash.json
new file mode 100644
index 0000000..f2afc9b
--- /dev/null
+++ b/tests/qapi-schema/struct-base-clash.json
@@ -0,0 +1,6 @@
+# we check for no duplicate keys with base
+{ 'struct': 'Base',
+  'data': { 'name': 'str' } }
+{ 'struct': 'Sub',
+  'base': 'Base',
+  'data': { 'name': 'str' } }
diff --git a/tests/qapi-schema/struct-base-clash.out b/tests/qapi-schema/struct-base-clash.out
new file mode 100644
index 0000000..e69de29
-- 
2.1.0

^ permalink raw reply related	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 01/36] qapi: Add copyright declaration on docs
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 01/36] qapi: Add copyright declaration on docs Eric Blake
@ 2015-04-27 14:42   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-27 14:42 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel, Michael Roth

Eric Blake <eblake@redhat.com> writes:

> While our top-level COPYING with its GPLv2+ license applies to
> any documentation file that omits explicit instructions, these
> days it's better to be a good example of calling out our
> intentions.  Correct use of GPL requires the use of a copyright
> statement, so I'm adding notice to two QAPI documents, by
> attributing these files to the initial authors and major
> contributors.  I used:
>
> $ git blame --line-porcelain $file \
>   | sed -n 's/^author //p' | sort | uniq -c | sort -rn
>
> to determine authorship of these two files.  qmp-spec.txt blames
> entirely to Red Hat (easy, since my contribution falls in that
> category); while qapi-code-gen.txt has multiple contributors
> representing multiple entities.  But since it was originally
> supplied by Michael Roth, the notice I added there copies the
> notice he has used in other files.  As there is no intended
> change in license from the implicit one previously present from
> the top level, I have not bothered to CC other contributors;
> if we want to weaken things to something looser (such as LGPL)
> so that there is no question that someone re-implementing the
> spec is not forced to use GPL, that would be a different commit.
>
> CC: Michael Roth <mdroth@linux.vnet.ibm.com>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v6: split off from v5:1/28, add Copyright lines
> ---
>  docs/qapi-code-gen.txt |  8 ++++++++
>  docs/qmp/qmp-spec.txt  | 10 ++++++++++
>  2 files changed, 18 insertions(+)
>
> diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
> index 8313ba6..e8bbaf8 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -1,5 +1,13 @@
>  = How to use the QAPI code generator =
>
> +Copyright IBM Corp. 2011
> +Copyright (C) 2012-2015 Red Hat, Inc.
> +
> +This work is licensed under the terms of the GNU GPL, version 2 or
> +later. See the COPYING file in the top-level directory.
> +
> +== Introduction ==
> +
>  QAPI is a native C API within QEMU which provides management-level
>  functionality to internal/external users. For external
>  users/processes, this interface is made available by a JSON-based
> diff --git a/docs/qmp/qmp-spec.txt b/docs/qmp/qmp-spec.txt
> index 22568c6..3e503e6 100644
> --- a/docs/qmp/qmp-spec.txt
> +++ b/docs/qmp/qmp-spec.txt
> @@ -1,5 +1,15 @@
>                        QEMU Machine Protocol Specification
>
> +0. About This Document
> +======================
> +
> +Copyright (C) 2009-2015 Red Hat, Inc.
> +
> +This work is licensed under the terms of the GNU GPL, version 2 or
> +later. See the COPYING file in the top-level directory.
> +
> +Last revised in March 2015.

s/March/April/

I recomment not to add this line.  It goes stale easily, and git is much
more reliable source of revision information anyway.  Matter of taste,
so...

> +
>  1. Introduction
>  ===============

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations Eric Blake
  2015-04-08 16:50   ` Eric Blake
@ 2015-04-27 15:42   ` Markus Armbruster
  2015-04-28 19:42   ` Eric Blake
  2 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-27 15:42 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Go into more details about the various types of valid expressions
> in a qapi schema, including tweaks to document fixes being done
> later in the current patch series.  Also fix some stale and missing
> documentation in the QMP specification.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

Loses a bit of detail on anonymous unions, but PATCH 15 more than
compensates.

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 06/36] qapi: Add some enum tests
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 06/36] qapi: Add some enum tests Eric Blake
@ 2015-04-27 16:00   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-27 16:00 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Demonstrate that the qapi generator doesn't deal well with enums
> that aren't up to par. Later patches will update the expected
> results as the generator is made stricter.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 08/36] qapi: Add some union tests
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 08/36] qapi: Add some union tests Eric Blake
@ 2015-04-27 16:18   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-27 16:18 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Demonstrate that the qapi generator doesn't deal well with unions
> that aren't up to par. Later patches will update the expected
> reseults as the generator is made stricter.  A few tests work
> as planned, but most show poor or missing error messages.
>
> Of particular note, qapi-code-gen.txt documents 'base' only for
> flat unions, but the tests here demonstrate that we currently allow
> a 'base' to a simple union, although it is exercised only in the
> testsuite.  Later patches will remove this undocumented feature, to
> give us more flexibility in adding other future extensions to union
> types.  For example, one possible extension is the idea of a
> type-safe simple enum, where added fields tie the discriminator to
> a user-defined enum type rather than creating an implicit enum from
> the names in 'data'.  But adding such safety on top of a simple
> enum with a base type could look ambiguous with a flat enum;
> besides, the documentation also mentions how any simple union can
> be represented by an equivalent flat union.  So it will be simpler
> to just outlaw support for something we aren't using.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 27/36] qapi: More rigorous checking for type safety bypass
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 27/36] qapi: More rigorous checking for type safety bypass Eric Blake
@ 2015-04-27 17:02   ` Eric Blake
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-27 17:02 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

[-- Attachment #1: Type: text/plain, Size: 1081 bytes --]

On 04/04/2015 10:07 PM, Eric Blake wrote:
> Now that we have a way to validate every type, we can also be
> stricter about enforcing that callers that want to bypass
> type safety in generated code.  Prior to this patch, it didn't
> matter what value was associated with the key 'gen', but it
> looked odd that 'gen':'yes' could result in bypassing the
> generated code.  These changes also enforce the changes made
> earlier in the series for documentation and consolidation of
> using '**' as the wildcard type, as well as 'gen':false as the
> canonical spelling for requesting type bypass.
> 
> Note that 'gen':false is a one-way switch away from the default;
> we do not support 'gen':true (similar for 'success-response).

Missing close ' for success-response, if committer wants to touch that up.

> In practice, this doesn't matter.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> Reviewed-by: Markus Armbruster <armbru@redhat.com>
> 


-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 10/36] qapi: Forbid base without discriminator in unions
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 10/36] qapi: Forbid base without discriminator in unions Eric Blake
@ 2015-04-27 17:36   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-27 17:36 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> None of the existing QMP or QGA interfaces uses a union with a
> base type but no discriminator; it is easier to avoid this in the
> generator to save room for other future extensions more likely to
> be useful.  A previous commit added a union-base-no-discriminator
> test to ensure that we eventually give a decent error message;
> now is the time to actually forbid it, and remove the last
> vestiges of that usage from the testsuite.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v6: split from v5:7/28; change subject line; hoist hunk from
> v5:8/28 into here to make this be the patch that forbids
> simple-with-base
> ---
>  scripts/qapi-types.py                              |  7 ++---
>  scripts/qapi-visit.py                              | 13 ++++----
>  scripts/qapi.py                                    | 20 ++++++------
>  tests/qapi-schema/qapi-schema-test.json            |  4 ---
>  tests/qapi-schema/qapi-schema-test.out             |  2 --
>  tests/qapi-schema/union-base-no-discriminator.err  |  1 +
>  tests/qapi-schema/union-base-no-discriminator.exit |  2 +-
>  tests/qapi-schema/union-base-no-discriminator.json |  2 +-
>  tests/qapi-schema/union-base-no-discriminator.out  |  8 -----
>  tests/test-qmp-input-visitor.c                     | 19 ------------
>  tests/test-qmp-output-visitor.c                    | 36 ----------------------
>  11 files changed, 22 insertions(+), 92 deletions(-)
>
> diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
> index e400b03..f6fb930 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi-types.py
> @@ -242,10 +242,9 @@ struct %(name)s
>  ''')
>
>      if base:
> -        base_fields = find_struct(base)['data']
> -        if discriminator:
> -            base_fields = base_fields.copy()
> -            del base_fields[discriminator]
> +        assert discriminator
> +        base_fields = find_struct(base)['data'].copy()
> +        del base_fields[discriminator]
>          ret += generate_struct_fields(base_fields)
>      else:
>          assert not discriminator
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 4416677..3f82bd4 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -2,7 +2,7 @@
>  # QAPI visitor generator
>  #
>  # Copyright IBM, Corp. 2011
> -# Copyright (C) 2014 Red Hat, Inc.
> +# Copyright (C) 2014-2015 Red Hat, Inc.
>  #
>  # Authors:
>  #  Anthony Liguori <aliguori@us.ibm.com>
> @@ -310,16 +310,15 @@ def generate_visit_union(expr):
>          ret = ""
>          disc_type = enum_define['enum_name']
>      else:
> -        # There will always be a discriminator in the C switch code, by default it
> -        # is an enum type generated silently as "'%sKind' % (name)"
> +        # There will always be a discriminator in the C switch code, by default
> +        # it is an enum type generated silently as "'%sKind' % (name)"
>          ret = generate_visit_enum('%sKind' % name, members.keys())
>          disc_type = '%sKind' % (name)
>
>      if base:
> -        base_fields = find_struct(base)['data']
> -        if discriminator:
> -            base_fields = base_fields.copy()
> -            del base_fields[discriminator]
> +        assert discriminator
> +        base_fields = find_struct(base)['data'].copy()
> +        del base_fields[discriminator]
>          ret += generate_visit_struct_fields(name, "", "", base_fields)
>
>      if discriminator:
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 3ce8c33..438468e 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -259,22 +259,22 @@ def check_union(expr, expr_info):
>      discriminator = expr.get('discriminator')
>      members = expr['data']
>
> -    # If the object has a member 'base', its value must name a complex type.
> -    if base:
> +    # If the object has a member 'base', its value must name a complex type,
> +    # and there must be a discriminator.
> +    if base is not None:
> +        if discriminator is None:
> +            raise QAPIExprError(expr_info,
> +                                "Union '%s' requires a discriminator to go "
> +                                "along with base" %name)
>          base_fields = find_base_fields(base)
>          if not base_fields:
>              raise QAPIExprError(expr_info,
>                                  "Base '%s' is not a valid type"
>                                  % base)
>
> -    # If the union object has no member 'discriminator', it's an
> -    # ordinary union.
> -    if not discriminator:
> -        enum_define = None
> -
> -    # Else if the value of member 'discriminator' is {}, it's an
> -    # anonymous union.
> -    elif discriminator == {}:
> +    # If the union object has no member 'discriminator', it's a
> +    # simple union. If 'discriminator' is {}, it is an anonymous union.
> +    if not discriminator or discriminator == {}:
>          enum_define = None
>
>      # Else, it's a flat union.
> diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> index 84f0f07..b134f3f 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json
> @@ -36,10 +36,6 @@
>  { 'type': 'UserDefC',
>    'data': { 'string1': 'str', 'string2': 'str' } }
>
> -{ 'union': 'UserDefUnion',
> -  'base': 'UserDefZero',
> -  'data': { 'a' : 'UserDefA', 'b' : 'UserDefB' } }
> -
>  { 'type': 'UserDefUnionBase',
>    'data': { 'string': 'str', 'enum1': 'EnumOne' } }
>

Removing UserDefUnion outright is okay, because we still have another
simple union, namely UserDefNativeListUnion, and the previous patch
moved the tests we want to keep from UserDefUnion to
UserDefNativeListUnion.

[...]

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions Eric Blake
@ 2015-04-27 18:15   ` Markus Armbruster
  2015-04-27 18:32     ` Eric Blake
  0 siblings, 1 reply; 72+ messages in thread
From: Markus Armbruster @ 2015-04-27 18:15 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Previous commits demonstrated that the generator had several
> flaws with less-than-perfect unions:
> - a simple union that listed the same branch twice (or two variant
> names that map to the same C enumerator, including the implicit
> MAX sentinel) ended up generating invalid C code
> - an anonymous union that listed two branches with the same qtype
> ended up generating invalid C code
> - the generator crashed on anonymous union attempts to use an
> array type
> - the generator was silently ignoring a base type for anonymous
> unions
> - the generator allowed unknown types or nested anonymous unions
> as a branch in an anonymous union
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
[...]
> diff --git a/tests/qapi-schema/alternate-array.json b/tests/qapi-schema/alternate-array.json
> index c2965cf..77970d9 100644
> --- a/tests/qapi-schema/alternate-array.json
> +++ b/tests/qapi-schema/alternate-array.json
> @@ -1,4 +1,4 @@
> -# FIXME: we do not support array branches of anonymous unions yet
> +# we do not support array branches of anonymous unions yet
>  # TODO: should we support this?
>  { 'type': 'One',
>    'data': { 'name': 'str' } }

FIXME added in PATCH 08, demoted to plain comment now.  Any particular
reason for the churn?

[...]

Regardless,

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions
  2015-04-27 18:15   ` Markus Armbruster
@ 2015-04-27 18:32     ` Eric Blake
  2015-04-29  2:51       ` Eric Blake
  0 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-27 18:32 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: kwolf, berto, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1244 bytes --]

On 04/27/2015 12:15 PM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Previous commits demonstrated that the generator had several
>> flaws with less-than-perfect unions:

>> +++ b/tests/qapi-schema/alternate-array.json
>> @@ -1,4 +1,4 @@
>> -# FIXME: we do not support array branches of anonymous unions yet
>> +# we do not support array branches of anonymous unions yet
>>  # TODO: should we support this?
>>  { 'type': 'One',
>>    'data': { 'name': 'str' } }
> 
> FIXME added in PATCH 08, demoted to plain comment now.  Any particular
> reason for the churn?

Poor rebasing skills on my part.  I should have nuked the 'FIXME' out of
commit 8, instead of churning here.

> 
> [...]
> 
> Regardless,

Yeah, the churn doesn't hurt, and unless there's a good reason for a
respin, I'd rather see this series go in near the beginning of 2.4 since
the change to 'struct' later in the series WILL cause merge conflicts to
additions to *.json files, so sooner is better than later and fighting
repetitive rebases.

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

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 15/36] qapi: Document new 'alternate' meta-type
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 15/36] qapi: Document new 'alternate' meta-type Eric Blake
@ 2015-04-28  8:27   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28  8:27 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> The next patch will quit special-casing "'union':'Foo',
> 'discriminator':{}" and instead use "'alternate':'Foo'".
>
> Separating docs from implementation makes it easier to focus
> on wording without holding up code.  In particular, making
> alternate a separate type makes for a nice type hierarchy:
>
>           /-------- meta-type ------\
>          /              |            \
>     simple types    alternate     complex types
>     |         |                   |           |
>  built-in   enum             type(struct)   union
>  |       \    /                            /    \
> numeric  string                         simple  flat
>
> A later patch will then clean up 'type' vs. 'struct'
> confusion.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 16/36] qapi: Use 'alternate' to replace anonymous union
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 16/36] qapi: Use 'alternate' to replace anonymous union Eric Blake
@ 2015-04-28  8:41   ` Markus Armbruster
  2015-04-28 17:29     ` Eric Blake
  0 siblings, 1 reply; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28  8:41 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Previous patches have led up to the point where I create the
> new meta-type "'alternate':'Foo'".  See the previous patches
> for documentation; I intentionally split as much work into
> earlier patches to minimize the size of this patch, but a lot
> of it is churn due to testsuite fallout after updating to the
> new type.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
[...]
> diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json
> index 838986c..0ba6e28 100644
> --- a/tests/qapi-schema/flat-union-base-union.json
> +++ b/tests/qapi-schema/flat-union-base-union.json
> @@ -1,4 +1,4 @@
> -# FIXME: the error message needs help: we require the base to be a struct
> +# we require the base to be a struct
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
>  { 'type': 'TestTypeA',

This looks unrelated.  Does it belong to PATCH 25?

[...]

Regardless,
Reviewed-by: Markus Armbruster <armbru@redhat.com>

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 17/36] qapi: Add some expr tests
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 17/36] qapi: Add some expr tests Eric Blake
@ 2015-04-28 11:01   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 11:01 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Demonstrate that the qapi generator doesn't deal well with
> expressions that aren't up to par. Later patches will improve
> the expected results as the generator is made stricter.  Only
> a few of the the added tests actually behaves sanely at

behave

> rejecting obvious problems or demonstrating success.
>
> Note that in some cases, we reject bad QAPI merely because our
> pseudo-JSON parser does not yet know how to parse numbers.  This
> series does not address that, but when a later series adds support
> for numeric defaults of integer fields, the testsuite will ensure
> that we don't lose the error (and hopefully that the error
> message quality is improved).
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 24/36] qapi: More rigourous checking of types
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 24/36] qapi: More rigourous checking of types Eric Blake
@ 2015-04-28 11:30   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 11:30 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Now that we know every expression is valid with regards to
> its keys, we can add further tests that those keys refer to
> valid types.  With this patch, all uses of a type (the 'data':
> of command, type, union, alternate, and event; the 'returns':
> of command; the 'base': of type and union) must resolve to an
> appropriate subset of metatypes  declared by the current qapi
> parse; this includes recursing into each member of a data
> dictionary.  Dealing with '**' and nested anonymous structs
> will be done in later patches.
>
> Update the testsuite to match improved output.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
[...]
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 686bc86..9f64a0d 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
[...]
> @@ -352,6 +414,11 @@ def check_union(expr, expr_info):
>
>      # Check every branch
>      for (key, value) in members.items():
> +        # Each value must name a known type; futhermore, in flat unions,

furthermore

> +        # branches must be a struct
> +        check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
> +                   value, allow_array=True, allow_metas=allow_metas)
> +
>          # If the discriminator names an enum type, then all members
>          # of 'data' must also be members of the enum type.
>          if enum_define:
[...]

Regardless,
Reviewed-by: Markus Armbruster <armbru@redhat.com>

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 25/36] qapi: Require valid names
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 25/36] qapi: Require valid names Eric Blake
@ 2015-04-28 11:46   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 11:46 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Previous commits demonstrated that the generator overlooked various
> bad naming situations:
> - types, commands, and events need a valid name
> - enum members must be valid names, when combined with prefix
> - union and alternate branches cannot be marked optional
>
> The set of valid names includes [a-zA-Z_][a-zA-Z0-9._-]* (where
> '.' is useful only in downstream extensions).  Since we have
> existing enum values beginning with a digit (see QKeyCode), a

Meh.  Didn't see the patch, or else I would've shot down these names.
Too late now.

> small hack is used to pass the same regex.

A bit vague.

>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v6: rebase onto earlier changes; use regex instead of sets, and
> ensure leading letter or _; don't force event case; drop dead
> code for exempting '**'; extend coverage to enum members
> ---
>  scripts/qapi.py                                    | 63 ++++++++++++++++------
>  tests/qapi-schema/bad-ident.err                    |  1 +
>  tests/qapi-schema/bad-ident.exit                   |  2 +-
>  tests/qapi-schema/bad-ident.json                   |  2 +-
>  tests/qapi-schema/bad-ident.out                    |  3 --
>  tests/qapi-schema/enum-bad-name.err                |  1 +
>  tests/qapi-schema/enum-bad-name.exit               |  2 +-
>  tests/qapi-schema/enum-bad-name.json               |  2 +-
>  tests/qapi-schema/enum-bad-name.out                |  3 --
>  tests/qapi-schema/enum-dict-member.err             |  2 +-
>  tests/qapi-schema/flat-union-bad-discriminator.err |  2 +-
>  .../flat-union-optional-discriminator.err          |  1 +
>  .../flat-union-optional-discriminator.exit         |  2 +-
>  .../flat-union-optional-discriminator.json         |  2 +-
>  .../flat-union-optional-discriminator.out          |  7 ---
>  tests/qapi-schema/union-optional-branch.err        |  1 +
>  tests/qapi-schema/union-optional-branch.exit       |  2 +-
>  tests/qapi-schema/union-optional-branch.json       |  2 +-
>  tests/qapi-schema/union-optional-branch.out        |  3 --
>  19 files changed, 60 insertions(+), 43 deletions(-)
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 9f64a0d..9b97683 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -276,8 +276,31 @@ def discriminator_find_enum_define(expr):
>
>      return find_enum(discriminator_type)
>
> +valid_name = re.compile('^[a-zA-Z_][a-zA-Z0-9_.-]*$')
> +def check_name(expr_info, source, name, allow_optional = False,
> +               enum_member = False):
> +    global valid_name
> +    membername = name
> +
> +    if not isinstance(name, str):
> +        raise QAPIExprError(expr_info,
> +                            "%s requires a string name" % source)
> +    if name.startswith('*'):
> +        membername = name[1:]
> +        if not allow_optional:
> +            raise QAPIExprError(expr_info,
> +                                "%s does not allow optional name '%s'"
> +                                % (source, name))
> +    # Enum members can start with a digit, because the generated C
> +    # code always prefixes it with the enum name
> +    if enum_member:
> +        membername = "_%s" %membername

This is the hack.

Less vague commit message:

    Valid names match [a-zA-Z_][a-zA-Z0-9._-]*.  Except for enumeration
    names, which match [a-zA-Z0-9._-]+.  By convention, '.' is used only
    in downstream extensions.

> +    if not valid_name.match(membername):
> +        raise QAPIExprError(expr_info,
> +                            "%s uses invalid name '%s'" % (source, name))
> +
>  def check_type(expr_info, source, value, allow_array = False,
> -               allow_dict = False, allow_metas = []):
> +               allow_dict = False, allow_optional = False, allow_metas = []):
>      global all_names
>      orig_value = value
>

We could reject new enumeration names beginning with a digit with a
whitelist, similar to how we reject new commands returning
non-dictionaries in the next patch.  Probably not worth the bother.

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 28/36] qapi: Prefer 'struct' over 'type' in generator
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 28/36] qapi: Prefer 'struct' over 'type' in generator Eric Blake
@ 2015-04-28 12:04   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 12:04 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Referring to "type" as both a meta-type (built-in, enum, union,
> alternte, or struct) and a specific type (the name that the
> schema uses for declaring structs) is confusing.  The confusion
> is only made worse by the fact that the generator mostly already
> refers to struct even when dealing with expr['type'].  This
> commit changes the generator to consistently refer to it as
> struct everywhere, plus a single back-compat tweak that allows
> accepting the existing .json files as-is, so that the meat of
> this change is separate from the mindless churn of that change.
>
> Fix the testsuite fallout for error messages that change, and
> in some cases, become more legible.

Some become temporarily confusing: message refers to 'struct', while
schema still has 'type'.  I don't mind.

>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v6: new patch
> ---
>  scripts/qapi-types.py                              | 16 ++++----
>  scripts/qapi-visit.py                              |  8 ++--
>  scripts/qapi.py                                    | 42 +++++++++++++--------
>  tests/qapi-schema/alternate-good.out               |  4 +-
>  tests/qapi-schema/bad-ident.err                    |  2 +-
>  tests/qapi-schema/bad-type-bool.err                |  2 +-
>  tests/qapi-schema/data-member-array.out            |  4 +-
>  tests/qapi-schema/double-type.err                  |  2 +-
>  tests/qapi-schema/flat-union-base-star.err         |  2 +-
>  tests/qapi-schema/flat-union-base-union.err        |  2 +-
>  tests/qapi-schema/flat-union-branch-clash.out      | 12 +++---
>  .../flat-union-invalid-discriminator.err           |  2 +-
>  tests/qapi-schema/flat-union-reverse-define.out    | 12 +++---
>  tests/qapi-schema/qapi-schema-test.out             | 44 +++++++++++-----------
>  tests/qapi-schema/union-invalid-base.err           |  2 +-
>  tests/qapi-schema/unknown-expr-key.err             |  2 +-
>  16 files changed, 85 insertions(+), 73 deletions(-)
>
> diff --git a/scripts/qapi-types.py b/scripts/qapi-types.py
> index 9c8d68c..a429d9e 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi-types.py
> @@ -83,7 +83,7 @@ def generate_struct_fields(members):
>
>  def generate_struct(expr):
>
> -    structname = expr.get('type', "")
> +    structname = expr.get('struct', "")
>      fieldname = expr.get('field', "")
>      members = expr['data']
>      base = expr.get('base')
> @@ -394,8 +394,8 @@ fdecl.write(guardend("QAPI_TYPES_BUILTIN_STRUCT_DECL"))
>
>  for expr in exprs:
>      ret = "\n"
> -    if expr.has_key('type'):
> -        ret += generate_fwd_struct(expr['type'], expr['data'])
> +    if expr.has_key('struct'):
> +        ret += generate_fwd_struct(expr['struct'], expr['data'])
>      elif expr.has_key('enum'):
>          ret += generate_enum(expr['enum'], expr['data']) + "\n"
>          ret += generate_fwd_enum_struct(expr['enum'], expr['data'])
> @@ -435,12 +435,12 @@ if do_builtins:
>
>  for expr in exprs:
>      ret = "\n"
> -    if expr.has_key('type'):
> +    if expr.has_key('struct'):
>          ret += generate_struct(expr) + "\n"
> -        ret += generate_type_cleanup_decl(expr['type'] + "List")
> -        fdef.write(generate_type_cleanup(expr['type'] + "List") + "\n")
> -        ret += generate_type_cleanup_decl(expr['type'])
> -        fdef.write(generate_type_cleanup(expr['type']) + "\n")
> +        ret += generate_type_cleanup_decl(expr['struct'] + "List")
> +        fdef.write(generate_type_cleanup(expr['struct'] + "List") + "\n")
> +        ret += generate_type_cleanup_decl(expr['struct'])
> +        fdef.write(generate_type_cleanup(expr['struct']) + "\n")
>      elif expr.has_key('union'):
>          ret += generate_union(expr, 'union')
>          ret += generate_type_cleanup_decl(expr['union'] + "List")
> diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
> index 3e11089..eeeca82 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi-visit.py
> @@ -178,7 +178,7 @@ def generate_visit_struct_body(field_prefix, name, members):
>
>  def generate_visit_struct(expr):
>
> -    name = expr['type']
> +    name = expr['struct']
>      members = expr['data']
>      base = expr.get('base')
>
> @@ -549,12 +549,12 @@ if do_builtins:
>          fdef.write(generate_visit_list(typename, None))
>
>  for expr in exprs:
> -    if expr.has_key('type'):
> +    if expr.has_key('struct'):
>          ret = generate_visit_struct(expr)
> -        ret += generate_visit_list(expr['type'], expr['data'])
> +        ret += generate_visit_list(expr['struct'], expr['data'])
>          fdef.write(ret)
>
> -        ret = generate_declaration(expr['type'], expr['data'])
> +        ret = generate_declaration(expr['struct'], expr['data'])
>          fdecl.write(ret)
>      elif expr.has_key('union'):
>          ret = generate_visit_union(expr)
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 8c83de0..c246570 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -419,7 +419,7 @@ def check_union(expr, expr_info):
>      members = expr['data']
>      values = { 'MAX': '(automatic)' }
>
> -    # If the object has a member 'base', its value must name a complex type,
> +    # If the object has a member 'base', its value must name a struct,
>      # and there must be a discriminator.
>      if base is not None:
>          if discriminator is None:
> @@ -448,18 +448,18 @@ def check_union(expr, expr_info):
>          base_fields = find_base_fields(base)
>          if not base_fields:
>              raise QAPIExprError(expr_info,
> -                                "Base '%s' is not a valid type"
> +                                "Base '%s' is not a valid struct"
>                                  % base)
>
>          # The value of member 'discriminator' must name a non-optional
> -        # member of the base type.
> +        # member of the base struct.
>          check_name(expr_info, "Discriminator of flat union '%s'" % name,
>                     discriminator)
>          discriminator_type = base_fields.get(discriminator)
>          if not discriminator_type:
>              raise QAPIExprError(expr_info,
>                                  "Discriminator '%s' is not a member of base "
> -                                "type '%s'"
> +                                "struct '%s'"
>                                  % (discriminator, base))
>          enum_define = find_enum(discriminator_type)
>          allow_metas=['struct']
> @@ -546,7 +546,7 @@ def check_enum(expr, expr_info):
>          values[key] = member
>
>  def check_struct(expr, expr_info):
> -    name = expr['type']
> +    name = expr['struct']
>      members = expr['data']
>
>      check_type(expr_info, "'data' for type '%s'" % name, members,
                  allow_dict=True, allow_optional=True)
       check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'),
                  allow_metas=['struct'])

Want to say "for struct '%s'" here?

> @@ -565,7 +565,7 @@ def check_exprs(schema):
>              check_union(expr, info)
>          elif expr.has_key('alternate'):
>              check_alternate(expr, info)
> -        elif expr.has_key('type'):
> +        elif expr.has_key('struct'):
>              check_struct(expr, info)
>          elif expr.has_key('command'):
>              check_command(expr, info)
> @@ -617,6 +617,20 @@ def parse_schema(input_file):
>          for expr_elem in schema.exprs:
>              expr = expr_elem['expr']
>              info = expr_elem['info']
> +
> +            # back-compat hack until all schemas have been converted;
> +            # preserve the ordering of the original expression
> +            if expr.has_key('type'):
> +                seen_type = False
> +                for (key, value) in expr.items():
> +                    if key == 'type':
> +                        seen_type = True
> +                        del expr['type']
> +                        expr['struct'] = value
> +                    elif seen_type:
> +                        del expr[key]
> +                        expr[key] = value
> +
>              if expr.has_key('enum'):
>                  check_keys(expr_elem, 'enum', ['data'])
>                  add_enum(expr['enum'], info, expr['data'])
> @@ -627,8 +641,8 @@ def parse_schema(input_file):
>              elif expr.has_key('alternate'):
>                  check_keys(expr_elem, 'alternate', ['data'])
>                  add_name(expr['alternate'], info, 'alternate')
> -            elif expr.has_key('type'):
> -                check_keys(expr_elem, 'type', ['data'], ['base'])
> +            elif expr.has_key('struct'):
> +                check_keys(expr_elem, 'struct', ['data'], ['base'])
>                  add_struct(expr, info)
>              elif expr.has_key('command'):
>                  check_keys(expr_elem, 'command', [],
> @@ -745,11 +759,9 @@ def type_name(name):
>          return c_list_type(name[0])
>      return name
>
> -def add_name(name, info, meta, implicit = False, source = None):
> +def add_name(name, info, meta, implicit = False):
>      global all_names
> -    if not source:
> -        source = "'%s'" % meta
> -    check_name(info, source, name)
> +    check_name(info, "'%s'" % meta, name)
>      if name in all_names:
>          raise QAPIExprError(info,
>                              "%s '%s' is already defined"
> @@ -762,14 +774,14 @@ def add_name(name, info, meta, implicit = False, source = None):
>
>  def add_struct(definition, info):
>      global struct_types
> -    name = definition['type']
> -    add_name(name, info, 'struct', source="'type'")
> +    name = definition['struct']
> +    add_name(name, info, 'struct')
>      struct_types.append(definition)
>
>  def find_struct(name):
>      global struct_types
>      for struct in struct_types:
> -        if struct['type'] == name:
> +        if struct['struct'] == name:
>              return struct
>      return None
>
[...]

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 29/36] qapi: Document 'struct' metatype
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 29/36] qapi: Document 'struct' metatype Eric Blake
@ 2015-04-28 12:12   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 12:12 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Referring to "type" as both a meta-type (built-in, enum, union,
> alternte, or struct) and a specific type (the name that the
> schema uses for declaring structs) is confusing.  Now that the
> generator accepts 'struct' as a synonym for 'type', update all
> documentation to use saner wording.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
>
> ---
>
> v6: new patch
> ---
>  docs/qapi-code-gen.txt | 56 +++++++++++++++++++++++++-------------------------
>  1 file changed, 28 insertions(+), 28 deletions(-)
>
> diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
> index 8a76cc1..6206032 100644
> --- a/docs/qapi-code-gen.txt
> +++ b/docs/qapi-code-gen.txt
> @@ -71,7 +71,7 @@ x.y.z)' comment.  For example:
>      #
>      # Since: 0.14.0
>      ##
> -    { 'type': 'BlockStats',
> +    { 'struct': 'BlockStats',
>        'data': {'*device': 'str', 'stats': 'BlockDeviceStats',
>                 '*parent': 'BlockStats',
>                 '*backing': 'BlockStats'} }
> @@ -85,7 +85,7 @@ types, and allows for indefinite nesting of QMP that satisfies the
>  schema.  A type name should not be defined more than once.
>
>  There are seven top-level expressions recognized by the parser:
> -'include', 'command', 'type', 'enum', 'union', 'alternate', and
> +'include', 'command', 'struct', 'enum', 'union', 'alternate', and
>  'event'.  There are several groups of types: simple types (a number of
>  built-in types, such as 'int' and 'str'; as well as enumerations),
>  complex types (structs and two flavors of unions), and alternate types
> @@ -127,9 +127,9 @@ In the rest of this document, usage lines are given for each
>  expression type, with literal strings written in lower case and
>  placeholders written in capitals.  If a literal string includes a
>  prefix of '*', that key/value pair can be omitted from the expression.
> -For example, a usage statement that includes '*base':COMPLEX-TYPE-NAME
> +For example, a usage statement that includes '*base':STRUCT-NAME
>  means that an expression has an optional key 'base', which if present
> -must have a value that forms a complex type name.
> +must have a value that forms a struct name.
>
>
>  === Built-in Types ===
> @@ -167,17 +167,17 @@ an outer file.  The parser may be made stricter in the future to
>  prevent incomplete include files.
>
>
> -=== Complex types ===
> +=== Struct types ===
>
> -Usage: { 'type': STRING, 'data': DICT, '*base': COMPLEX-TYPE-NAME }
> +Usage: { 'struct': STRING, 'data': DICT, '*base': STRUCT-NAME }
>
> -A complex type is a dictionary containing a single 'data' key whose
> +A struct is a dictionary containing a single 'data' key whose
>  value is a dictionary.  This corresponds to a struct in C or an Object
>  in JSON. Each value of the 'data' dictionary must be the name of a
>  type, or a one-element array containing a type name.  An example of a
> -complex type is:
> +struct is:
>
> - { 'type': 'MyType',
> + { 'struct': 'MyType',
>     'data': { 'member1': 'str', 'member2': 'int', '*member3': 'str' } }
>
>  The use of '*' as a prefix to the name means the member is optional in
> @@ -210,13 +210,13 @@ A structure that is used in both input and output of various commands
>  must consider the backwards compatibility constraints of both directions
>  of use.
>
> -A complex type definition can specify another complex type as its base.
> +A struct definition can specify another struct as its base.
>  In this case, the fields of the base type are included as top-level fields
> -of the new complex type's dictionary in the QMP wire format. An example
> +of the new struct's dictionary in the QMP wire format. An example
>  definition is:
>
> - { 'type': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } }
> - { 'type': 'BlockdevOptionsGenericCOWFormat',
> + { 'struct': 'BlockdevOptionsGenericFormat', 'data': { 'file': 'str' } }
> + { 'struct': 'BlockdevOptionsGenericCOWFormat',
>     'base': 'BlockdevOptionsGenericFormat',
>     'data': { '*backing': 'str' } }
>
> @@ -251,7 +251,7 @@ converting between strings and enum values.  Since the wire format
>  always passes by name, it is acceptable to reorder or add new
>  enumeration members in any location without breaking QMP clients;
>  however, removing enum values would break compatibility.  For any
> -complex type that has a field that will only contain a finite set of
> +struct that has a field that will only contain a finite set of
>  string values, using an enum type for that field is better than
>  open-coding the field to be type 'str'.
>
> @@ -259,7 +259,7 @@ open-coding the field to be type 'str'.
>  === Union types ===
>
>  Usage: { 'union': STRING, 'data': DICT }
> -or:    { 'union': STRING, 'data': DICT, 'base': COMPLEX-TYPE-NAME,
> +or:    { 'union': STRING, 'data': DICT, 'base': STRUCT-NAME,
>           'discriminator': ENUM-MEMBER-OF-BASE }
>
>  Union types are used to let the user choose between several different
> @@ -271,8 +271,8 @@ paragraphs.
>  A simple union type defines a mapping from automatic discriminator
>  values to data types like in this example:
>
> - { 'type': 'FileOptions', 'data': { 'filename': 'str' } }
> - { 'type': 'Qcow2Options',
> + { 'struct': 'FileOptions', 'data': { 'filename': 'str' } }
> + { 'struct': 'Qcow2Options',
>     'data': { 'backing-file': 'str', 'lazy-refcounts': 'bool' } }
>
>   { 'union': 'BlockdevOptions',
> @@ -295,13 +295,13 @@ the union can be named 'max', as this would collide with the implicit
>  enum.  The value for each branch can be of any type.
>
>
> -A flat union definition specifies a complex type as its base, and
> +A flat union definition specifies a struct as its base, and
>  avoids nesting on the wire.  All branches of the union must be
>  complex types, and the top-level fields of the union dictionary on
>  the wire will be combination of fields from both the base type and the
>  appropriate branch type (when merging two dictionaries, there must be
>  no keys in common).  The 'discriminator' field must be the name of an
> -enum-typed member of the base type.
> +enum-typed member of the base struct.
>
>  The following example enhances the above simple union example by
>  adding a common field 'readonly', renaming the discriminator to
> @@ -309,7 +309,7 @@ something more applicable, and reducing the number of {} required on
>  the wire:
>
>   { 'enum': 'BlockdevDriver', 'data': [ 'raw', 'qcow2' ] }
> - { 'type': 'BlockdevCommonOptions',
> + { 'struct': 'BlockdevCommonOptions',
>     'data': { 'driver': 'BlockdevDriver', 'readonly': 'bool' } }
>   { 'union': 'BlockdevOptions',
>     'base': 'BlockdevCommonOptions',
> @@ -341,9 +341,9 @@ union has a complex type with a single member named 'data'.  That is,
   A simple union can always be re-written as a flat union where the base
   class has a single member named 'type', and where each branch of the
   union has a complex type with a single member named 'data'.  That is,

Should we use 'struct' instead of 'complex type' here?

    { 'union': 'Simple', 'data': { 'one': 'str', 'two': 'int' } }

>  is identical on the wire to:
>
>   { 'enum': 'Enum', 'data': ['one', 'two'] }
> - { 'type': 'Base', 'data': { 'type': 'Enum' } }
> - { 'type': 'Branch1', 'data': { 'data': 'str' } }
> - { 'type': 'Branch2', 'data': { 'data': 'int' } }
> + { 'struct': 'Base', 'data': { 'type': 'Enum' } }
> + { 'struct': 'Branch1', 'data': { 'data': 'str' } }
> + { 'struct': 'Branch2', 'data': { 'data': 'int' } }
>   { 'union': 'Flat': 'base': 'Base', 'discriminator': 'type',
>     'data': { 'one': 'Branch1', 'two': 'Branch2' } }
>
> @@ -401,7 +401,7 @@ part of a QMP command.  The 'data' member is optional and defaults to
>  {} (an empty dictionary).  If present, it must be the string name of a
>  complex type, a one-element array containing the name of a complex
>  type, or a dictionary that declares an anonymous type with the same
> -semantics as a 'type' expression, with one exception noted below when
> +semantics as a 'struct' expression, with one exception noted below when
>  'gen' is used.
>
>  The 'returns' member describes what will appear in the "return" field
> @@ -410,7 +410,7 @@ optional from the command declaration; if absent, the "return" field
>  will be an empty dictionary.  If 'returns' is present, it must be the
>  string name of a complex or built-in type, a one-element array
>  containing the name of a complex or built-in type, or a dictionary
> -that declares an anonymous type with the same semantics as a 'type'
> +that declares an anonymous type with the same semantics as a 'struct'
>  expression, with one exception noted below when 'gen' is used.
>  Although it is permitted to have the 'returns' member name a built-in
>  type or an array of built-in types, any command that does this cannot
> @@ -429,7 +429,7 @@ Some example commands:
>
>   { 'command': 'my-first-command',
>     'data': { 'arg1': 'str', '*arg2': 'str' } }
> - { 'type': 'MyType', 'data': { '*value': 'str' } }
> + { 'struct': 'MyType', 'data': { '*value': 'str' } }
>   { 'command': 'my-second-command',
>     'returns': [ 'MyType' ] }
>
> @@ -473,7 +473,7 @@ Events are defined with the keyword 'event'.  It is not allowed to
>  name an event 'MAX', since the generator also produces a C enumeration
>  of all event names with a generated _MAX value at the end.  When
>  'data' is also specified, additional info will be included in the
> -event, with similar semantics to a 'type' expression.  Finally there
> +event, with similar semantics to a 'struct' expression.  Finally there
>  will be C API generated in qapi-event.h; when called by QEMU code, a
>  message with timestamp will be emitted on the wire.
>
> @@ -504,7 +504,7 @@ case we want to accept/return a list of this type with a command), and a
>  command which takes that type as a parameter and returns the same type:
>
>      $ cat example-schema.json
> -    { 'type': 'UserDefOne',
> +    { 'struct': 'UserDefOne',
>        'data': { 'integer': 'int', 'string': 'str' } }
>
>      { 'command': 'my-command',

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 30/36] qapi: Use 'struct' instead of 'type' in schema
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 30/36] qapi: Use 'struct' instead of 'type' in schema Eric Blake
@ 2015-04-28 12:23   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 12:23 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Referring to "type" as both a meta-type (built-in, enum, union,
> alternte, or struct) and a specific type (the name that the
> schema uses for declaring structs) is confusing.  Finish up the
> conversion to using "struct" in qapi schema by coverting all
> users, then remove the hack in the generator that allowed 'type'.
> Tweak a couple of tests whose output changes slightly due to
> longer lines.  The json files were changed with:
>
> for f in `find -name '*.json'; do sed -i "s/'type'/'struct'/"; done
>
> followed by manually filtering out the places where we have a
> 'type' embedded in 'data'.  I also verified that the generated
> files for QMP and QGA (such as qmp-commands.h) are the same
> before and after, as assurance that I didn't leave in any
> accidental member name changes.

Sounds safe enough.  Could miss comments, though.  See possible misses
inline.

> Signed-off-by: Eric Blake <eblake@redhat.com>
[...]
> diff --git a/tests/qapi-schema/flat-union-bad-base.json b/tests/qapi-schema/flat-union-bad-base.json
> index bb0f02d..3f9c8fc 100644
> --- a/tests/qapi-schema/flat-union-bad-base.json
> +++ b/tests/qapi-schema/flat-union-bad-base.json
> @@ -2,9 +2,9 @@
   # we require the base to be an existing complex type

Should this be 'an existing struct type'?

>  # TODO: should we allow an anonymous inline base type?
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>  { 'union': 'TestUnion',
>    'base': { 'enum1': 'TestEnum', 'kind': 'str' },
> diff --git a/tests/qapi-schema/flat-union-bad-discriminator.json b/tests/qapi-schema/flat-union-bad-discriminator.json
> index 982f072..cd10b9d 100644
> --- a/tests/qapi-schema/flat-union-bad-discriminator.json
> +++ b/tests/qapi-schema/flat-union-bad-discriminator.json
> @@ -2,11 +2,11 @@
>  # this tests the old syntax for anonymous unions before we added alternates
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
> -{ 'type': 'TestBase',
> +{ 'struct': 'TestBase',
>    'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>  { 'union': 'TestUnion',
>    'base': 'TestBase',
> diff --git a/tests/qapi-schema/flat-union-base-star.json b/tests/qapi-schema/flat-union-base-star.json
> index 994533a..ed9dbf1 100644
> --- a/tests/qapi-schema/flat-union-base-star.json
> +++ b/tests/qapi-schema/flat-union-base-star.json
> @@ -1,9 +1,9 @@
>  # we require the base to be an existing complex type

Likewise.

>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>  { 'union': 'TestUnion',
>    'base': '**',
> diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json
> index 0ba6e28..6a8ea68 100644
> --- a/tests/qapi-schema/flat-union-base-union.json
> +++ b/tests/qapi-schema/flat-union-base-union.json
> @@ -1,9 +1,9 @@
>  # we require the base to be a struct
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>  { 'union': 'UnionBase',
>    'data': { 'kind1': 'TestTypeA',
> diff --git a/tests/qapi-schema/flat-union-branch-clash.json b/tests/qapi-schema/flat-union-branch-clash.json
> index 4091477..8b0b807 100644
> --- a/tests/qapi-schema/flat-union-branch-clash.json
> +++ b/tests/qapi-schema/flat-union-branch-clash.json
> @@ -1,11 +1,11 @@
>  # FIXME: we should check for no duplicate keys between branches and base
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
> -{ 'type': 'Base',
> +{ 'struct': 'Base',
>    'data': { 'enum1': 'TestEnum', 'name': 'str' } }
> -{ 'type': 'Branch1',
> +{ 'struct': 'Branch1',
>    'data': { 'name': 'str' } }
> -{ 'type': 'Branch2',
> +{ 'struct': 'Branch2',
>    'data': { 'value': 'int' } }
>  { 'union': 'TestUnion',
>    'base': 'Base',
> diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json
> index f3da117..d95618b 100644
> --- a/tests/qapi-schema/flat-union-inline.json
> +++ b/tests/qapi-schema/flat-union-inline.json
> @@ -2,7 +2,7 @@
   # we require branches to be a complex type name

Should this be 'a struct type name'?

>  # TODO: should we allow anonymous inline types?
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
> -{ 'type': 'Base',
> +{ 'struct': 'Base',
>    'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
>  { 'union': 'TestUnion',
>    'base': { 'enum1': 'TestEnum', 'kind': 'str' },
> diff --git a/tests/qapi-schema/flat-union-int-branch.json b/tests/qapi-schema/flat-union-int-branch.json
> index d373131..108faef 100644
> --- a/tests/qapi-schema/flat-union-int-branch.json
> +++ b/tests/qapi-schema/flat-union-int-branch.json
> @@ -1,9 +1,9 @@
>  # we require flat union branches to be a complex type

Likewise.

>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
> -{ 'type': 'Base',
> +{ 'struct': 'Base',
>    'data': { 'enum1': 'TestEnum' } }
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>  { 'union': 'TestUnion',
>    'base': 'Base',
> diff --git a/tests/qapi-schema/flat-union-invalid-branch-key.json b/tests/qapi-schema/flat-union-invalid-branch-key.json
> index a624282..95ff774 100644
> --- a/tests/qapi-schema/flat-union-invalid-branch-key.json
> +++ b/tests/qapi-schema/flat-union-invalid-branch-key.json
> @@ -1,13 +1,13 @@
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
>
> -{ 'type': 'TestBase',
> +{ 'struct': 'TestBase',
>    'data': { 'enum1': 'TestEnum' } }
>
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
>
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>
>  { 'union': 'TestUnion',
> diff --git a/tests/qapi-schema/flat-union-invalid-discriminator.json b/tests/qapi-schema/flat-union-invalid-discriminator.json
> index 887157e..48b94c3 100644
> --- a/tests/qapi-schema/flat-union-invalid-discriminator.json
> +++ b/tests/qapi-schema/flat-union-invalid-discriminator.json
> @@ -1,13 +1,13 @@
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
>
> -{ 'type': 'TestBase',
> +{ 'struct': 'TestBase',
>    'data': { 'enum1': 'TestEnum' } }
>
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
>
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>
>  { 'union': 'TestUnion',
> diff --git a/tests/qapi-schema/flat-union-no-base.json b/tests/qapi-schema/flat-union-no-base.json
> index 9547bb8..ffc4c6f 100644
> --- a/tests/qapi-schema/flat-union-no-base.json
> +++ b/tests/qapi-schema/flat-union-no-base.json
> @@ -1,8 +1,8 @@
>  # flat unions require a base
>  # TODO: simple unions should be able to use an enum discriminator
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>  { 'enum': 'Enum',
>    'data': [ 'value1', 'value2' ] }
> diff --git a/tests/qapi-schema/flat-union-optional-discriminator.json b/tests/qapi-schema/flat-union-optional-discriminator.json
> index 25ce0e6..08a8f7e 100644
> --- a/tests/qapi-schema/flat-union-optional-discriminator.json
> +++ b/tests/qapi-schema/flat-union-optional-discriminator.json
> @@ -1,8 +1,8 @@
>  # we require the discriminator to be non-optional
>  { 'enum': 'Enum', 'data': [ 'one', 'two' ] }
> -{ 'type': 'Base',
> +{ 'struct': 'Base',
>    'data': { '*switch': 'Enum' } }
> -{ 'type': 'Branch', 'data': { 'name': 'str' } }
> +{ 'struct': 'Branch', 'data': { 'name': 'str' } }
>  { 'union': 'MyUnion',
>    'base': 'Base',
>    'discriminator': '*switch',
> diff --git a/tests/qapi-schema/flat-union-reverse-define.json b/tests/qapi-schema/flat-union-reverse-define.json
> index 9ea7e72..648bbfe 100644
> --- a/tests/qapi-schema/flat-union-reverse-define.json
> +++ b/tests/qapi-schema/flat-union-reverse-define.json
> @@ -4,14 +4,14 @@
>    'data': { 'value1': 'TestTypeA',
>              'value2': 'TestTypeB' } }
>
> -{ 'type': 'TestBase',
> +{ 'struct': 'TestBase',
>    'data': { 'enum1': 'TestEnum' } }
>
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
>
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
>
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
> diff --git a/tests/qapi-schema/flat-union-string-discriminator.json b/tests/qapi-schema/flat-union-string-discriminator.json
> index e966aeb..8af6033 100644
> --- a/tests/qapi-schema/flat-union-string-discriminator.json
> +++ b/tests/qapi-schema/flat-union-string-discriminator.json
> @@ -1,13 +1,13 @@
>  { 'enum': 'TestEnum',
>    'data': [ 'value1', 'value2' ] }
>
> -{ 'type': 'TestBase',
> +{ 'struct': 'TestBase',
>    'data': { 'enum1': 'TestEnum', 'kind': 'str' } }
>
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
>
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>
>  { 'union': 'TestUnion',
> diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json
> index dec8a7c..f10efe2 100644
> --- a/tests/qapi-schema/qapi-schema-test.json
> +++ b/tests/qapi-schema/qapi-schema-test.json
> @@ -3,40 +3,40 @@
>  # for testing enums
>  { 'enum': 'EnumOne',
>    'data': [ 'value1', 'value2', 'value3' ] }
> -{ 'type': 'NestedEnumsOne',
> +{ 'struct': 'NestedEnumsOne',
>    'data': { 'enum1': 'EnumOne', '*enum2': 'EnumOne', 'enum3': 'EnumOne', '*enum4': 'EnumOne' } }
>
>  # for testing nested structs
> -{ 'type': 'UserDefZero',
> +{ 'struct': 'UserDefZero',
>    'data': { 'integer': 'int' } }
>
> -{ 'type': 'UserDefOne',
> +{ 'struct': 'UserDefOne',
>    'base': 'UserDefZero',
>    'data': { 'string': 'str', '*enum1': 'EnumOne' } }
>
> -{ 'type': 'UserDefTwo',
> +{ 'struct': 'UserDefTwo',
>    'data': { 'string': 'str',
>              'dict': { 'string': 'str',
>                        'dict': { 'userdef': 'UserDefOne', 'string': 'str' },
>                        '*dict2': { 'userdef': 'UserDefOne', 'string': 'str' } } } }
>
> -{ 'type': 'UserDefNested',
> +{ 'struct': 'UserDefNested',
>    'data': { 'string0': 'str',
>              'dict1': { 'string1': 'str',
>                         'dict2': { 'userdef1': 'UserDefOne', 'string2': 'str' },
>                         '*dict3': { 'userdef2': 'UserDefOne', 'string3': 'str' } } } }
>
>  # for testing unions
> -{ 'type': 'UserDefA',
> +{ 'struct': 'UserDefA',
>    'data': { 'boolean': 'bool' } }
>
> -{ 'type': 'UserDefB',
> +{ 'struct': 'UserDefB',
>    'data': { 'integer': 'int' } }
>
> -{ 'type': 'UserDefC',
> +{ 'struct': 'UserDefC',
>    'data': { 'string1': 'str', 'string2': 'str' } }
>
> -{ 'type': 'UserDefUnionBase',
> +{ 'struct': 'UserDefUnionBase',
>    'data': { 'string': 'str', 'enum1': 'EnumOne' } }
>
>  { 'union': 'UserDefFlatUnion',
> @@ -88,7 +88,7 @@
>  #
>  # For simplicity, this example doesn't use [type=]discriminator nor optargs
>  # specific to discriminator values.
> -{ 'type': 'UserDefOptions',
> +{ 'struct': 'UserDefOptions',
>    'data': {
>      '*i64' : [ 'int'    ],
>      '*u64' : [ 'uint64' ],
> @@ -97,7 +97,7 @@
>      '*u64x':   'uint64'  } }
>
>  # testing event
> -{ 'type': 'EventStructOne',
> +{ 'struct': 'EventStructOne',
>    'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } }
>
>  { 'event': 'EVENT_A' }
> diff --git a/tests/qapi-schema/redefined-builtin.json b/tests/qapi-schema/redefined-builtin.json
> index df328cc..45b8a55 100644
> --- a/tests/qapi-schema/redefined-builtin.json
> +++ b/tests/qapi-schema/redefined-builtin.json
> @@ -1,2 +1,2 @@
>  # we reject types that duplicate builtin names
> -{ 'type': 'size', 'data': { 'myint': 'size' } }
> +{ 'struct': 'size', 'data': { 'myint': 'size' } }
> diff --git a/tests/qapi-schema/redefined-type.json b/tests/qapi-schema/redefined-type.json
> index e6a5f24..a09e768 100644
> --- a/tests/qapi-schema/redefined-type.json
> +++ b/tests/qapi-schema/redefined-type.json
> @@ -1,3 +1,3 @@
>  # we reject types defined more than once
> -{ 'type': 'foo', 'data': { 'one': 'str' } }
> +{ 'struct': 'foo', 'data': { 'one': 'str' } }
>  { 'enum': 'foo', 'data': [ 'two' ] }
> diff --git a/tests/qapi-schema/union-bad-branch.json b/tests/qapi-schema/union-bad-branch.json
> index 4303666..913aa38 100644
> --- a/tests/qapi-schema/union-bad-branch.json
> +++ b/tests/qapi-schema/union-bad-branch.json
> @@ -1,7 +1,7 @@
>  # we reject normal unions where branches would collide in C
> -{ 'type': 'One',
> +{ 'struct': 'One',
>    'data': { 'string': 'str' } }
> -{ 'type': 'Two',
> +{ 'struct': 'Two',
>    'data': { 'number': 'int' } }
>  { 'union': 'MyUnion',
>    'data': { 'one': 'One',
> diff --git a/tests/qapi-schema/union-base-no-discriminator.json b/tests/qapi-schema/union-base-no-discriminator.json
> index 052596c..1409cf5 100644
> --- a/tests/qapi-schema/union-base-no-discriminator.json
> +++ b/tests/qapi-schema/union-base-no-discriminator.json
> @@ -1,11 +1,11 @@
>  # we reject simple unions with a base (or flat unions without discriminator)
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
>
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>
> -{ 'type': 'Base',
> +{ 'struct': 'Base',
>    'data': { 'string': 'str' } }
>
>  { 'union': 'TestUnion',
> diff --git a/tests/qapi-schema/union-invalid-base.json b/tests/qapi-schema/union-invalid-base.json
> index bc5dc8d..92be39d 100644
> --- a/tests/qapi-schema/union-invalid-base.json
> +++ b/tests/qapi-schema/union-invalid-base.json
> @@ -1,8 +1,8 @@
>  # a union base type must be a struct
> -{ 'type': 'TestTypeA',
> +{ 'struct': 'TestTypeA',
>    'data': { 'string': 'str' } }
>
> -{ 'type': 'TestTypeB',
> +{ 'struct': 'TestTypeB',
>    'data': { 'integer': 'int' } }
>
>  { 'union': 'TestUnion',
> diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json
> index ba7bdf3..3b2be00 100644
> --- a/tests/qapi-schema/unknown-expr-key.json
> +++ b/tests/qapi-schema/unknown-expr-key.json
> @@ -1,2 +1,2 @@
>  # we reject an expression with unknown top-level keys
> -{ 'type': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }
> +{ 'struct': 'bar', 'data': { 'string': 'str'}, 'bogus': { } }

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 31/36] qapi: Merge UserDefTwo and UserDefNested in tests
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 31/36] qapi: Merge UserDefTwo and UserDefNested in tests Eric Blake
@ 2015-04-28 12:28   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 12:28 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> In the testsuite, UserDefTwo and UserDefNested were identical
> structs other than the member names.  Reduce code duplication by
> having just one type, and choose names that also favor reuse.
> This will also make it easier for a later patch to get rid of
> inline nested types in QAPI.  When touching code related to
> allocations, convert g_malloc0(sizeof(Type)) to the more typesafe
> g_new0(Type, 1).
>
> Ensure that 'make check-qapi-schema check-unit' still passes.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 32/36] qapi: Drop tests for inline nested structs
  2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 32/36] qapi: Drop tests for inline nested structs Eric Blake
@ 2015-04-28 13:00   ` Markus Armbruster
  2015-04-28 17:29     ` Eric Blake
  0 siblings, 1 reply; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 13:00 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> A future patch will be using a 'name':{dictionary} entry in the
> QAPI schema to specify a default value for an optional argument;
> but existing use of inline nested structs conflicts with that goal.
>
> More precisely, a definition in the QAPI schema associates a name
> with a set of properties:
>
> Example 1: { 'struct': 'Foo', 'data': { MEMBERS... } }
> associates the global name 'Foo' with properties (meta-type struct)
> and MEMBERS...
>
> Example 2: 'mumble': TYPE
> within MEMBERS... above associates 'mumble' with properties (type
> TYPE) and (optional false) within type Foo
>
> The syntax of example 1 is extensible; if we need another property,
> we add another name/value pair to the dictionary (such as
> 'base':TYPE).  The syntax of example 2 is not extensible, because
> the right hand side can only be a type.
>
> We have used name encoding to add a property: "'*mumble': 'int'"
> associates 'mumble' with (type int) and (optional true).  Nice,
> but doesn't scale.  So the solution is to change our existing uses
> to be syntactic sugar to an extensible form:
>
>    NAME: TYPE   --> NAME:  { 'type': TYPE, 'optional': false }
>    *ONAME: TYPE --> ONAME: { 'type': TYPE, 'optional': true }
>
> This patch fixes the testsuite to avoid inline nested types, by
> breaking the nesting into explicit types; it means that the type
> is now boxed instead of unboxed in C code, but makes no difference
> on the wire (and if desired, a later patch could change the
> generator to not do so much boxing in C).  When touching code to
> add new allocations, also convert existing allocations to
> consistently prefer typesafe g_new0 over g_malloc0 when a type
> name is involved.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> Reviewed-by: Markus Armbruster <armbru@redhat.com>
[...]
> diff --git a/tests/test-visitor-serialization.c b/tests/test-visitor-serialization.c
> index 1ccaaa9..52c8d1a 100644
> --- a/tests/test-visitor-serialization.c
> +++ b/tests/test-visitor-serialization.c
> @@ -1,7 +1,7 @@
>  /*
>   * Unit-tests for visitor-based serialization
>   *
> - * Copyright IBM, Corp. 2012
> + * Copyright IBM, Corp. 2012, 2015

More accurate would be:

  - * Copyright IBM, Corp. 2012,2013
  + * Copyright IBM, Corp. 2014, 2015
  + * Copyright (C) 2014, 2015 Red Hat Inc.

>   *
>   * Authors:
>   *  Michael Roth <mdroth@linux.vnet.ibm.com>

R-by stands all the same.

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 37/36] qapi: Support (subset of) \u escapes in strings
  2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 37/36] qapi: Support (subset of) \u escapes in strings Eric Blake
@ 2015-04-28 13:23   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 13:23 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> The handling of \ inside QAPI strings was less than ideal, and
> really only worked JSON's \/, \\, \", and our extension of \'
> (an obvious extension, when you realize we use '' instead of ""
> for strings).  For other things, like '\n', it resulted in a
> literal 'n' instead of a newline.
>
> Of course, at the moment, we really have no use for escaped
> characters, as QAPI has to map to C identifiers, and we currently
> support ASCII only for that.  But down the road, we may add
> support for default values for string parameters to a command
> or struct; if that happens, it would be nice to correctly support
> all JSON escape sequences, such as \n or \uXXXX.  This gets us
> closer, by supporting Unicode escapes in the ASCII range.
>
> Since JSON does not require \OCTAL or \xXX escapes, I did not
> add it here, but it would be an easy addition if we desired it.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
> ---
>
> v6: new patch
>
> ---
>  scripts/qapi.py                          | 33 +++++++++++++++++++++++++++++++-
>  tests/Makefile                           |  1 +
>  tests/qapi-schema/escape-too-big.err     |  1 +
>  tests/qapi-schema/escape-too-big.exit    |  1 +
>  tests/qapi-schema/escape-too-big.json    |  3 +++
>  tests/qapi-schema/escape-too-big.out     |  0
>  tests/qapi-schema/escape-too-short.err   |  1 +
>  tests/qapi-schema/escape-too-short.exit  |  1 +
>  tests/qapi-schema/escape-too-short.json  |  3 +++
>  tests/qapi-schema/escape-too-short.out   |  0
>  tests/qapi-schema/ident-with-escape.err  |  1 -
>  tests/qapi-schema/ident-with-escape.exit |  2 +-
>  tests/qapi-schema/ident-with-escape.json |  2 +-
>  tests/qapi-schema/ident-with-escape.out  |  3 +++
>  tests/qapi-schema/unicode-str.err        |  1 +
>  tests/qapi-schema/unicode-str.exit       |  1 +
>  tests/qapi-schema/unicode-str.json       |  2 ++
>  tests/qapi-schema/unicode-str.out        |  0
>  18 files changed, 52 insertions(+), 4 deletions(-)
>  create mode 100644 tests/qapi-schema/escape-too-big.err
>  create mode 100644 tests/qapi-schema/escape-too-big.exit
>  create mode 100644 tests/qapi-schema/escape-too-big.json
>  create mode 100644 tests/qapi-schema/escape-too-big.out
>  create mode 100644 tests/qapi-schema/escape-too-short.err
>  create mode 100644 tests/qapi-schema/escape-too-short.exit
>  create mode 100644 tests/qapi-schema/escape-too-short.json
>  create mode 100644 tests/qapi-schema/escape-too-short.out
>  create mode 100644 tests/qapi-schema/unicode-str.err
>  create mode 100644 tests/qapi-schema/unicode-str.exit
>  create mode 100644 tests/qapi-schema/unicode-str.json
>  create mode 100644 tests/qapi-schema/unicode-str.out
>
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 60ed34a..853f9a3 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -173,7 +173,38 @@ class QAPISchema:
>                          raise QAPISchemaError(self,
>                                                'Missing terminating "\'"')
>                      if esc:
> -                        string += ch
> +                        if ch == 'b':
> +                            string += '\b'
> +                        elif ch == 'f':
> +                            string += '\f'
> +                        elif ch == 'n':
> +                            string += '\n'
> +                        elif ch == 'r':
> +                            string += '\r'
> +                        elif ch == 't':
> +                            string += '\t'
> +                        elif ch == 'u':
> +                            value = 0
> +                            for x in range(0, 4):
> +                                ch = self.src[self.cursor]
> +                                self.cursor += 1
> +                                if ch not in "0123456789abcdefABCDEF":
> +                                    raise QAPISchemaError(self,
> +                                                          '\\u escape needs 4 '
> +                                                          'hex digits')
> +                                value = (value << 4) + int(ch, 16)
> +                            # If Python 2 and 3 didn't disagree so much on
> +                            # how to handle Unicode, then we could allow
> +                            # Unicode string defaults.  But most of QAPI is
> +                            # ASCII-only, so we aren't losing much for now.
> +                            if value > 0x7f:
> +                                raise QAPISchemaError(self,
> +                                                      'For now, \\u escape '
> +                                                      'only supports values '
> +                                                      'up to \\u007f')
> +                            string += chr(value)
> +                        else:
> +                            string += ch
>                          esc = False
>                      elif ch == "\\":
>                          esc = True

RFC 7159 accepts escapes ["\/bfnrtu], where u is followed by four
hexadecimal digits.

Our C JSON parser additionally accepts ', see json-lexer.c.

This code accepts any character.  I'd prefer to make it consistent with
our C JSON parser instead.

[...]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 38/36] qapi: Check for member name conflicts with a base class
  2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 38/36] qapi: Check for member name conflicts with a base class Eric Blake
@ 2015-04-28 13:35   ` Markus Armbruster
  0 siblings, 0 replies; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 13:35 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> Our type inheritance for both 'struct' and for flat 'union' merges
> key/value pairs from the base class with those from the type in
> question.  Although the C code currently boxes things so that there
> is a distinction between which member is referred to, the QMP wire
> format does not allow passing a key more than once in a single
> object.  Besides, if we ever change the generated C code to not be
> quite so boxy, we'd want to avoid duplicate member names there,
> too.
>
> Fix a testsuite entry added in an earlier patch, as well as adding
> a couple more tests to ensure we have appropriate coverage.
>
> Signed-off-by: Eric Blake <eblake@redhat.com>
[...]
> diff --git a/scripts/qapi.py b/scripts/qapi.py
> index 853f9a3..281e762 100644
> --- a/scripts/qapi.py
> +++ b/scripts/qapi.py
> @@ -429,6 +429,18 @@ def check_type(expr_info, source, value, allow_array = False,
>                     allow_metas=['built-in', 'union', 'alternate', 'struct',
>                                  'enum'])
>
> +def check_member_clash(expr_info, base_name, data, source = ""):
> +    base = find_struct(base_name)
> +    assert base
> +    base_members = base['data']
> +    for key in data.keys():
> +        if key in base_members:
> +            raise QAPIExprError(expr_info,
> +                                "Member name '%s'%s clashes with base '%s'"
> +                                %(key, source, base_name))
> +    if base.get('base'):
> +        check_member_clash(expr_info, base['base'], data, source)
> +
>  def check_command(expr, expr_info):
>      name = expr['command']
>      allow_star = expr.has_key('gen')
> @@ -518,9 +530,14 @@ def check_union(expr, expr_info):
>          check_name(expr_info, "Member of union '%s'" % name, key)
>
>          # Each value must name a known type; futhermore, in flat unions,
> -        # branches must be a struct
> +        # branches must be a struct with no overlapping member names
>          check_type(expr_info, "Member '%s' of union '%s'" % (key, name),
>                     value, allow_array=True, allow_metas=allow_metas)
> +        if base:
> +            branch_struct = find_struct(value)
> +            assert branch_struct
> +            check_member_clash(expr_info, expr['base'], branch_struct['data'],
> +                               " of branch '%s'" %key)

Could pass base instead of expr['base'].

>
>          # If the discriminator names an enum type, then all members
>          # of 'data' must also be members of the enum type.
> @@ -597,6 +614,8 @@ def check_struct(expr, expr_info):
>                 allow_dict=True, allow_optional=True)
>      check_type(expr_info, "'base' for type '%s'" % name, expr.get('base'),
>                 allow_metas=['struct'])
> +    if expr.get('base'):
> +        check_member_clash(expr_info, expr['base'], expr['data'])
>
>  def check_exprs(schema):
>      for expr_elem in schema.exprs:
[...]

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

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
  2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
                   ` (38 preceding siblings ...)
  2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 38/36] qapi: Check for member name conflicts with a base class Eric Blake
@ 2015-04-28 14:02 ` Markus Armbruster
  2015-04-28 17:51   ` Eric Blake
  39 siblings, 1 reply; 72+ messages in thread
From: Markus Armbruster @ 2015-04-28 14:02 UTC (permalink / raw)
  To: Eric Blake; +Cc: kwolf, berto, qemu-devel

Eric Blake <eblake@redhat.com> writes:

> We want to eventually allow qapi defaults, by making:
>  'data':{'*flag':'bool'}
> as shorthand for something like:
>  'data':{'flag':{'type':'bool', 'optional':true}}
> so that the default can be specified:
>  'data':{'flag':{'type':'bool', 'optional':true, 'default':true}}
>
> This series does not quite get us there, but it DOES do a number
> of other things.  It gets rid of the three uses of nested inline
> structs, changes anonymous unions to use a specific 'alternate'
> metatype rather than abusing 'union', changes the ambiguous 'type'
> to the obvious 'struct', and fixes lots of other parser bugs found
> while designing the testsuite.  The testsuite changes make the
> bulk of this series, with a repeating pattern of writing tests
> that expose weaknesses in the old parser, then beefing up the
> generator to catch the problem during the initial parse rather
> than choking with an obscure python message or even causing a C
> compilation failure.  There are a couple of ideas for things to
> still fix before all is said and done (such as preventing a
> collision of members between a struct and its base class, or
> fixing the parser to understand \uXXXX escape sequences in
> strings), but at this point, anything raised as a review comment
> is probably better addressed as followup patches rather than
> respinning a v7.
>
> v5 was here:
> https://lists.gnu.org/archive/html/qemu-devel/2015-03/msg05034.html
>
> v6 changes are noted in each patch; in particular, several new
> patches were added (additional tests, split some patches, conversion
> to 'struct' instead of 'type'). But most of the changes were in
> direct response to review comments or rebase fallout, so I kept
> in Reviewed-by markings where possible, to help focus review on
> the remainder.

Looks good to go to me, except for new PATCH 37, where I suggested a
small change.  Shouldn't hold up this series.  If any other patch still
lacks my R-by, let me know.

> I wrote another patch while working on this series, but it was
> independent enough that I posted it separately (although I based
> the documentation in this patch as if that, or Markus' alternative,
> had been applied):
> https://lists.gnu.org/archive/html/qemu-devel/2015-04/msg00373.html

Both need a trivial respin to correct a pasto.  Yours has a more
elaborate commit message, and a test.  Mine is less code, in part
because it uses a single qnull object instead of allocating one for each
use, and it has separate patches for the qobject and the json-parser
change.

We can pick one, of we can combine the best of both into a new
mini-series.  Preferences?

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 32/36] qapi: Drop tests for inline nested structs
  2015-04-28 13:00   ` Markus Armbruster
@ 2015-04-28 17:29     ` Eric Blake
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-28 17:29 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: kwolf, berto, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1111 bytes --]

On 04/28/2015 07:00 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> A future patch will be using a 'name':{dictionary} entry in the
>> QAPI schema to specify a default value for an optional argument;
>> but existing use of inline nested structs conflicts with that goal.
>>

>> +++ b/tests/test-visitor-serialization.c
>> @@ -1,7 +1,7 @@
>>  /*
>>   * Unit-tests for visitor-based serialization
>>   *
>> - * Copyright IBM, Corp. 2012
>> + * Copyright IBM, Corp. 2012, 2015
> 
> More accurate would be:
> 
>   - * Copyright IBM, Corp. 2012,2013
>   + * Copyright IBM, Corp. 2014, 2015
>   + * Copyright (C) 2014, 2015 Red Hat Inc.

Bummer - serves me right for trusting emacs' ability to auto-add
copyright, without checking which line it added to.

> 
>>   *
>>   * Authors:
>>   *  Michael Roth <mdroth@linux.vnet.ibm.com>
> 
> R-by stands all the same.

I'm adding this fix to a series followup, if there is no other reason to
respin.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 16/36] qapi: Use 'alternate' to replace anonymous union
  2015-04-28  8:41   ` Markus Armbruster
@ 2015-04-28 17:29     ` Eric Blake
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-28 17:29 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: kwolf, berto, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1439 bytes --]

On 04/28/2015 02:41 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> Previous patches have led up to the point where I create the
>> new meta-type "'alternate':'Foo'".  See the previous patches
>> for documentation; I intentionally split as much work into
>> earlier patches to minimize the size of this patch, but a lot
>> of it is churn due to testsuite fallout after updating to the
>> new type.
>>
>> Signed-off-by: Eric Blake <eblake@redhat.com>
> [...]
>> diff --git a/tests/qapi-schema/flat-union-base-union.json b/tests/qapi-schema/flat-union-base-union.json
>> index 838986c..0ba6e28 100644
>> --- a/tests/qapi-schema/flat-union-base-union.json
>> +++ b/tests/qapi-schema/flat-union-base-union.json
>> @@ -1,4 +1,4 @@
>> -# FIXME: the error message needs help: we require the base to be a struct
>> +# we require the base to be a struct
>>  { 'enum': 'TestEnum',
>>    'data': [ 'value1', 'value2' ] }
>>  { 'type': 'TestTypeA',
> 
> This looks unrelated.  Does it belong to PATCH 25?

Uggh - more evidence of botched hunk motion across rebase.

> 
> [...]
> 
> Regardless,
> Reviewed-by: Markus Armbruster <armbru@redhat.com>

End result is the same, so unless I need to rebase on top of all the
other patches going in right now, I won't bother to respin.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
  2015-04-28 14:02 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Markus Armbruster
@ 2015-04-28 17:51   ` Eric Blake
  2015-04-29  6:48     ` Markus Armbruster
  0 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-28 17:51 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: kwolf, berto, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1793 bytes --]

On 04/28/2015 08:02 AM, Markus Armbruster wrote:

>> v6 changes are noted in each patch; in particular, several new
>> patches were added (additional tests, split some patches, conversion
>> to 'struct' instead of 'type'). But most of the changes were in
>> direct response to review comments or rebase fallout, so I kept
>> in Reviewed-by markings where possible, to help focus review on
>> the remainder.
> 
> Looks good to go to me, except for new PATCH 37, where I suggested a
> small change.  Shouldn't hold up this series.  If any other patch still
> lacks my R-by, let me know.

Easier as a followup? Respin just the one patch? Or bite the bullet and
rebase the entire series (fixing the other trivial items and adding R-b
along the way)?

> 
>> I wrote another patch while working on this series, but it was
>> independent enough that I posted it separately (although I based
>> the documentation in this patch as if that, or Markus' alternative,
>> had been applied):
>> https://lists.gnu.org/archive/html/qemu-devel/2015-04/msg00373.html
> 
> Both need a trivial respin to correct a pasto.  Yours has a more
> elaborate commit message, and a test.  Mine is less code, in part
> because it uses a single qnull object instead of allocating one for each
> use, and it has separate patches for the qobject and the json-parser
> change.
> 
> We can pick one, of we can combine the best of both into a new
> mini-series.  Preferences?

I'll tackle a new mini-series with the best of both (I like your idea of
reusing the same object across all uses, instead of creating a new one
each time, particularly since it resulted in a smaller patch)

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations Eric Blake
  2015-04-08 16:50   ` Eric Blake
  2015-04-27 15:42   ` Markus Armbruster
@ 2015-04-28 19:42   ` Eric Blake
  2 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-28 19:42 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berto, armbru

[-- Attachment #1: Type: text/plain, Size: 1669 bytes --]

On 04/04/2015 10:07 PM, Eric Blake wrote:
> Go into more details about the various types of valid expressions
> in a qapi schema, including tweaks to document fixes being done
> later in the current patch series.  Also fix some stale and missing
> documentation in the QMP specification.
> 
> Signed-off-by: Eric Blake <eblake@redhat.com>
> 
> ---

Another self-review:

> +generated C structs and parameter lists).  Ordering doesn't matter
> +between top-level expressions or the keys within an expression, but
> +does matter within dictionary values for 'data' and 'returns' members
> +of a single expression.  QAPI schema input is written using 'single
> +quotes' instead of JSON's "double quotes" (in contrast, QMP usage is
> +strict JSON and only uses "double quotes", with no comments).  As in
> +JSON, trailing commas are not permitted in arrays or dictionaries.
> +Input must be ASCII (although QMP supports full Unicode strings, the
> +QAPI parser does not).  At present, there is no place where a QAPI
> +schema requires the use of JSON numbers or null.

Here I claim that QMP is "" only...

> +++ b/docs/qmp/qmp-spec.txt

> +
> +Also for convenience, the server will accept an extension of
> +'single-quoted' strings in place of the usual "double-quoted"
> +json-string, and both input forms of strings understand an additional
> +escape sequence of "\'" for a single quote. The server will only use
> +double quoting on output.

...which is at odds with reality.

Will fix in either a followup or a v7 respin

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 07/36] qapi: Better error messages for bad enums
  2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 07/36] qapi: Better error messages for bad enums Eric Blake
@ 2015-04-28 23:08   ` Eric Blake
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-28 23:08 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, Alberto Garcia, armbru

[-- Attachment #1: Type: text/plain, Size: 640 bytes --]

On 04/04/2015 10:07 PM, Eric Blake wrote:
> The previous commit demonstrated that the generator had several
> flaws with less-than-perfect enums:

> +++ b/tests/qapi-schema/enum-max-member.json
> @@ -1,3 +1,3 @@
> -# FIXME: we should either reject user-supplied 'max', or munge the implicit
> -# max value we generate at the end of an array
> +# we reject user-supplied 'max' for clashing with implicit enum end
> +# TODO: should we instead munge the the implicit value to avoid the clash?

double "the" needs fixing

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions
  2015-04-27 18:32     ` Eric Blake
@ 2015-04-29  2:51       ` Eric Blake
  0 siblings, 0 replies; 72+ messages in thread
From: Eric Blake @ 2015-04-29  2:51 UTC (permalink / raw)
  To: Markus Armbruster; +Cc: kwolf, Alberto Garcia, qemu-devel

[-- Attachment #1: Type: text/plain, Size: 1229 bytes --]

On 04/27/2015 12:32 PM, Eric Blake wrote:
> On 04/27/2015 12:15 PM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>>
>>> Previous commits demonstrated that the generator had several
>>> flaws with less-than-perfect unions:
> 
>>> +++ b/tests/qapi-schema/alternate-array.json
>>> @@ -1,4 +1,4 @@
>>> -# FIXME: we do not support array branches of anonymous unions yet
>>> +# we do not support array branches of anonymous unions yet
>>>  # TODO: should we support this?
>>>  { 'type': 'One',
>>>    'data': { 'name': 'str' } }
>>
>> FIXME added in PATCH 08, demoted to plain comment now.  Any particular
>> reason for the churn?
> 
> Poor rebasing skills on my part.  I should have nuked the 'FIXME' out of
> commit 8, instead of churning here.

Actually, commit 8 was demonstrating that we didn't flag arrays as
invalid even though they don't work, and this commit fixes things to
start rejecting them. Meanwhile, the TODO reminds us that we may once
again want to accept them, but in a working configuration. At any rate,
minor wording tweaks should make it more obvious.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
  2015-04-28 17:51   ` Eric Blake
@ 2015-04-29  6:48     ` Markus Armbruster
  2015-04-29 12:34       ` Eric Blake
  0 siblings, 1 reply; 72+ messages in thread
From: Markus Armbruster @ 2015-04-29  6:48 UTC (permalink / raw)
  To: Eric Blake
  Cc: kwolf, berto, Michael S. Tsirkin, Michael Roth, qemu-devel,
	Luiz Capitulino

General question first: through which tree should this go?

MAINTAINERS doesn't cover the QAPI generators.  Closest related stanza
is QAPI (Luiz, Michael R.).  Should it cover the generators?  Next
closest is QAPI Schema (Luiz, you, myself).

For completeness: because we touch qmp_query_pci_bridge() and
qmp_query_pci_devices(), get_maintainers.pl also fingers the PCI tree.

If nobody objects, I can take it through my tree.  Cc'ing the
maintainers just mentioned to give them a chance to chime in.

Eric Blake <eblake@redhat.com> writes:

> On 04/28/2015 08:02 AM, Markus Armbruster wrote:
>
>>> v6 changes are noted in each patch; in particular, several new
>>> patches were added (additional tests, split some patches, conversion
>>> to 'struct' instead of 'type'). But most of the changes were in
>>> direct response to review comments or rebase fallout, so I kept
>>> in Reviewed-by markings where possible, to help focus review on
>>> the remainder.
>> 
>> Looks good to go to me, except for new PATCH 37, where I suggested a
>> small change.  Shouldn't hold up this series.  If any other patch still
>> lacks my R-by, let me know.
>
> Easier as a followup? Respin just the one patch? Or bite the bullet and
> rebase the entire series (fixing the other trivial items and adding R-b
> along the way)?

I think we can either

* Respin, but keep the changes really simple.  Feel free to leave some
  issues to followup patches.

* Merge the series except for PATCH 37, with trivial touch-ups on
  commit.  Followup patches to address any remaining issues.

  If we take it through my tree, I'm happy to do the touch-ups.

>>> I wrote another patch while working on this series, but it was
>>> independent enough that I posted it separately (although I based
>>> the documentation in this patch as if that, or Markus' alternative,
>>> had been applied):
>>> https://lists.gnu.org/archive/html/qemu-devel/2015-04/msg00373.html
>> 
>> Both need a trivial respin to correct a pasto.  Yours has a more
>> elaborate commit message, and a test.  Mine is less code, in part
>> because it uses a single qnull object instead of allocating one for each
>> use, and it has separate patches for the qobject and the json-parser
>> change.
>> 
>> We can pick one, of we can combine the best of both into a new
>> mini-series.  Preferences?
>
> I'll tackle a new mini-series with the best of both (I like your idea of
> reusing the same object across all uses, instead of creating a new one
> each time, particularly since it resulted in a smaller patch)

Sounds good!

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
  2015-04-29  6:48     ` Markus Armbruster
@ 2015-04-29 12:34       ` Eric Blake
  2015-04-29 12:40         ` Luiz Capitulino
  0 siblings, 1 reply; 72+ messages in thread
From: Eric Blake @ 2015-04-29 12:34 UTC (permalink / raw)
  To: Markus Armbruster
  Cc: kwolf, berto, Michael S. Tsirkin, Michael Roth, qemu-devel,
	Luiz Capitulino

[-- Attachment #1: Type: text/plain, Size: 1265 bytes --]

On 04/29/2015 12:48 AM, Markus Armbruster wrote:
> General question first: through which tree should this go?
> 
> MAINTAINERS doesn't cover the QAPI generators.  Closest related stanza
> is QAPI (Luiz, Michael R.).  Should it cover the generators?  Next
> closest is QAPI Schema (Luiz, you, myself).
> 
> For completeness: because we touch qmp_query_pci_bridge() and
> qmp_query_pci_devices(), get_maintainers.pl also fingers the PCI tree.
> 
> If nobody objects, I can take it through my tree.  Cc'ing the
> maintainers just mentioned to give them a chance to chime in.

Works for me, but that means we may want to also add the qapi generators
into MAINTAINERS along-side QAPI Schema for future changes.  Of course,
as a separate patch.

>> Easier as a followup? Respin just the one patch? Or bite the bullet and
>> rebase the entire series (fixing the other trivial items and adding R-b
>> along the way)?
> 
> I think we can either
> 
> * Respin, but keep the changes really simple.  Feel free to leave some
>   issues to followup patches.

Changes in the qapi schema (new structs) require a rebase, so respin on
its way.

-- 
Eric Blake   eblake redhat com    +1-919-301-3266
Libvirt virtualization library http://libvirt.org


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]

^ permalink raw reply	[flat|nested] 72+ messages in thread

* Re: [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs
  2015-04-29 12:34       ` Eric Blake
@ 2015-04-29 12:40         ` Luiz Capitulino
  0 siblings, 0 replies; 72+ messages in thread
From: Luiz Capitulino @ 2015-04-29 12:40 UTC (permalink / raw)
  To: Eric Blake
  Cc: kwolf, berto, Michael S. Tsirkin, qemu-devel, Michael Roth,
	Markus Armbruster

On Wed, 29 Apr 2015 06:34:12 -0600
Eric Blake <eblake@redhat.com> wrote:

> On 04/29/2015 12:48 AM, Markus Armbruster wrote:
> > General question first: through which tree should this go?
> > 
> > MAINTAINERS doesn't cover the QAPI generators.  Closest related stanza
> > is QAPI (Luiz, Michael R.).  Should it cover the generators?  Next
> > closest is QAPI Schema (Luiz, you, myself).
> > 
> > For completeness: because we touch qmp_query_pci_bridge() and
> > qmp_query_pci_devices(), get_maintainers.pl also fingers the PCI tree.
> > 
> > If nobody objects, I can take it through my tree.  Cc'ing the
> > maintainers just mentioned to give them a chance to chime in.

I not only don't object, but I support this!

> Works for me, but that means we may want to also add the qapi generators
> into MAINTAINERS along-side QAPI Schema for future changes.  Of course,
> as a separate patch.

Makes a lot of sense to me. Just CC me so that I'm aware which parts
of QMP/QAPI you guys are taking.

> >> Easier as a followup? Respin just the one patch? Or bite the bullet and
> >> rebase the entire series (fixing the other trivial items and adding R-b
> >> along the way)?
> > 
> > I think we can either
> > 
> > * Respin, but keep the changes really simple.  Feel free to leave some
> >   issues to followup patches.
> 
> Changes in the qapi schema (new structs) require a rebase, so respin on
> its way.
> 

^ permalink raw reply	[flat|nested] 72+ messages in thread

end of thread, other threads:[~2015-04-29 12:41 UTC | newest]

Thread overview: 72+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-04-05  4:07 [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 01/36] qapi: Add copyright declaration on docs Eric Blake
2015-04-27 14:42   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 02/36] qapi: Document type-safety considerations Eric Blake
2015-04-08 16:50   ` Eric Blake
2015-04-27 15:42   ` Markus Armbruster
2015-04-28 19:42   ` Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 03/36] qapi: Simplify builtin type handling Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 04/36] qapi: Fix generation of 'size' builtin type Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 05/36] qapi: Require ASCII in schema Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 06/36] qapi: Add some enum tests Eric Blake
2015-04-27 16:00   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 07/36] qapi: Better error messages for bad enums Eric Blake
2015-04-28 23:08   ` Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 08/36] qapi: Add some union tests Eric Blake
2015-04-27 16:18   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 09/36] qapi: Clean up test coverage of simple unions Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 10/36] qapi: Forbid base without discriminator in unions Eric Blake
2015-04-27 17:36   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 11/36] qapi: Tighten checking of unions Eric Blake
2015-04-27 18:15   ` Markus Armbruster
2015-04-27 18:32     ` Eric Blake
2015-04-29  2:51       ` Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 12/36] qapi: Prepare for catching more semantic parse errors Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 13/36] qapi: Segregate anonymous unions into alternates in generator Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 14/36] qapi: Rename anonymous union type in test Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 15/36] qapi: Document new 'alternate' meta-type Eric Blake
2015-04-28  8:27   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 16/36] qapi: Use 'alternate' to replace anonymous union Eric Blake
2015-04-28  8:41   ` Markus Armbruster
2015-04-28 17:29     ` Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 17/36] qapi: Add some expr tests Eric Blake
2015-04-28 11:01   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 18/36] qapi: Better error messages for bad expressions Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 19/36] qapi: Add tests of redefined expressions Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 20/36] qapi: Better error messages for duplicated expressions Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 21/36] qapi: Allow true, false and null in schema json Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 22/36] qapi: Unify type bypass and add tests Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 23/36] qapi: Add some type check tests Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 24/36] qapi: More rigourous checking of types Eric Blake
2015-04-28 11:30   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 25/36] qapi: Require valid names Eric Blake
2015-04-28 11:46   ` Markus Armbruster
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 26/36] qapi: Whitelist commands that don't return dictionary Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 27/36] qapi: More rigorous checking for type safety bypass Eric Blake
2015-04-27 17:02   ` Eric Blake
2015-04-05  4:07 ` [Qemu-devel] [PATCH v6 28/36] qapi: Prefer 'struct' over 'type' in generator Eric Blake
2015-04-28 12:04   ` Markus Armbruster
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 29/36] qapi: Document 'struct' metatype Eric Blake
2015-04-28 12:12   ` Markus Armbruster
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 30/36] qapi: Use 'struct' instead of 'type' in schema Eric Blake
2015-04-28 12:23   ` Markus Armbruster
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 31/36] qapi: Merge UserDefTwo and UserDefNested in tests Eric Blake
2015-04-28 12:28   ` Markus Armbruster
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 32/36] qapi: Drop tests for inline nested structs Eric Blake
2015-04-28 13:00   ` Markus Armbruster
2015-04-28 17:29     ` Eric Blake
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 33/36] qapi: Drop inline nested struct in query-version Eric Blake
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 34/36] qapi: Drop inline nested structs in query-pci Eric Blake
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 35/36] qapi: Drop support for inline nested types Eric Blake
2015-04-05  4:08 ` [Qemu-devel] [PATCH v6 36/36] qapi: Tweak doc references to QMP when QGA is also meant Eric Blake
2015-04-06 13:13 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Eric Blake
2015-04-08  7:35   ` Alberto Garcia
2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 37/36] qapi: Support (subset of) \u escapes in strings Eric Blake
2015-04-28 13:23   ` Markus Armbruster
2015-04-10 20:28 ` [Qemu-devel] [PATCH v6 38/36] qapi: Check for member name conflicts with a base class Eric Blake
2015-04-28 13:35   ` Markus Armbruster
2015-04-28 14:02 ` [Qemu-devel] [PATCH v6 00/36] drop qapi nested structs Markus Armbruster
2015-04-28 17:51   ` Eric Blake
2015-04-29  6:48     ` Markus Armbruster
2015-04-29 12:34       ` Eric Blake
2015-04-29 12:40         ` Luiz Capitulino

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.