Skip to content

Commit

Permalink
qapi: Anonymous unions
Browse files Browse the repository at this point in the history
The discriminator for anonymous unions is the data type. This allows to
have a union type that allows both of these:

    { 'file': 'my_existing_block_device_id' }
    { 'file': { 'filename': '/tmp/mydisk.qcow2', 'read-only': true } }

Unions like this are specified in the schema with an empty dict as
discriminator. For this example you could take:

    { 'union': 'BlockRef',
      'discriminator': {},
      'data': { 'definition': 'BlockOptions',
                'reference': 'str' } }
    { 'type': 'ExampleObject',
      'data: { 'file': 'BlockRef' } }

Signed-off-by: Kevin Wolf <kwolf@redhat.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
  • Loading branch information
kevmw committed Jul 26, 2013
1 parent ea66c6d commit 69dd62d
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 0 deletions.
25 changes: 25 additions & 0 deletions docs/qapi-code-gen.txt
Expand Up @@ -125,6 +125,31 @@ Resulting in this JSON object:
"lazy-refcounts": true }


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.

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': {},
'data': { 'definition': 'BlockdevOptions',
'reference': 'str' } }

This example allows using both of the following example objects:

{ "file": "my_existing_block_device_id" }
{ "file": { "driver": "file",
"readonly": false,
'filename': "/tmp/mydisk.qcow2" } }


=== Commands ===

Commands are defined by using a list containing three members. The first
Expand Down
1 change: 1 addition & 0 deletions include/qapi/qmp/qobject.h
Expand Up @@ -44,6 +44,7 @@ typedef enum {
QTYPE_QFLOAT,
QTYPE_QBOOL,
QTYPE_QERROR,
QTYPE_MAX,
} qtype_code;

struct QObject;
Expand Down
2 changes: 2 additions & 0 deletions include/qapi/visitor-impl.h
Expand Up @@ -32,6 +32,8 @@ struct Visitor

void (*type_enum)(Visitor *v, int *obj, const char *strings[],
const char *kind, const char *name, Error **errp);
void (*get_next_type)(Visitor *v, int *kind, const int *qobjects,
const char *name, Error **errp);

void (*type_int)(Visitor *v, int64_t *obj, const char *name, Error **errp);
void (*type_bool)(Visitor *v, bool *obj, const char *name, Error **errp);
Expand Down
3 changes: 3 additions & 0 deletions include/qapi/visitor.h
Expand Up @@ -13,6 +13,7 @@
#ifndef QAPI_VISITOR_CORE_H
#define QAPI_VISITOR_CORE_H

#include "qapi/qmp/qobject.h"
#include "qapi/error.h"
#include <stdlib.h>

Expand Down Expand Up @@ -42,6 +43,8 @@ void visit_end_list(Visitor *v, Error **errp);
void visit_start_optional(Visitor *v, bool *present, const char *name,
Error **errp);
void visit_end_optional(Visitor *v, Error **errp);
void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
const char *name, Error **errp);
void visit_type_enum(Visitor *v, int *obj, const char *strings[],
const char *kind, const char *name, Error **errp);
void visit_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp);
Expand Down
9 changes: 9 additions & 0 deletions qapi/qapi-visit-core.c
Expand Up @@ -12,6 +12,7 @@
*/

#include "qemu-common.h"
#include "qapi/qmp/qobject.h"
#include "qapi/qmp/qerror.h"
#include "qapi/visitor.h"
#include "qapi/visitor-impl.h"
Expand Down Expand Up @@ -98,6 +99,14 @@ void visit_end_optional(Visitor *v, Error **errp)
}
}

void visit_get_next_type(Visitor *v, int *obj, const int *qtypes,
const char *name, Error **errp)
{
if (!error_is_set(errp) && v->get_next_type) {
v->get_next_type(v, obj, qtypes, name, errp);
}
}

void visit_type_enum(Visitor *v, int *obj, const char *strings[],
const char *kind, const char *name, Error **errp)
{
Expand Down
14 changes: 14 additions & 0 deletions qapi/qmp-input-visitor.c
Expand Up @@ -208,6 +208,19 @@ static void qmp_input_end_list(Visitor *v, Error **errp)
qmp_input_pop(qiv, errp);
}

static void qmp_input_get_next_type(Visitor *v, int *kind, const int *qobjects,
const char *name, Error **errp)
{
QmpInputVisitor *qiv = to_qiv(v);
QObject *qobj = qmp_input_get_object(qiv, name, false);

if (!qobj) {
error_set(errp, QERR_MISSING_PARAMETER, name ? name : "null");
return;
}
*kind = qobjects[qobject_type(qobj)];
}

static void qmp_input_type_int(Visitor *v, int64_t *obj, const char *name,
Error **errp)
{
Expand Down Expand Up @@ -317,6 +330,7 @@ QmpInputVisitor *qmp_input_visitor_new(QObject *obj)
v->visitor.type_str = qmp_input_type_str;
v->visitor.type_number = qmp_input_type_number;
v->visitor.start_optional = qmp_input_start_optional;
v->visitor.get_next_type = qmp_input_get_next_type;

qmp_input_push(v, obj, NULL);
qobject_incref(obj);
Expand Down
2 changes: 2 additions & 0 deletions qobject/qjson.c
Expand Up @@ -260,6 +260,8 @@ static void to_json(const QObject *obj, QString *str, int pretty, int indent)
/* XXX: should QError be emitted? */
case QTYPE_NONE:
break;
case QTYPE_MAX:
abort();
}
}

Expand Down
42 changes: 42 additions & 0 deletions scripts/qapi-types.py
Expand Up @@ -150,6 +150,40 @@ def generate_enum(name, values):

return lookup_decl + enum_decl

def generate_anon_union_qtypes(expr):

name = expr['union']
members = expr['data']

ret = mcgen('''
const int %(name)s_qtypes[QTYPE_MAX] = {
''',
name=name)

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

ret += mcgen('''
[ %(qtype)s ] = %(abbrev)s_KIND_%(enum)s,
''',
qtype = qtype,
abbrev = de_camel_case(name).upper(),
enum = c_fun(de_camel_case(key),False).upper())

ret += mcgen('''
};
''')
return ret


def generate_union(expr):

name = expr['union']
Expand Down Expand Up @@ -190,6 +224,12 @@ def generate_union(expr):
ret += mcgen('''
};
''')
if discriminator == {}:
ret += mcgen('''
extern const int %(name)s_qtypes[];
''',
name=name)


return ret

Expand Down Expand Up @@ -342,6 +382,8 @@ def maybe_open(really, name, opt):
ret += generate_fwd_struct(expr['union'], expr['data']) + "\n"
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_anon_union_qtypes(expr))
else:
continue
fdecl.write(ret)
Expand Down
47 changes: 47 additions & 0 deletions scripts/qapi-visit.py
Expand Up @@ -176,6 +176,49 @@ def generate_visit_enum(name, members):
''',
name=name)

def generate_visit_anon_union(name, members):
ret = mcgen('''
void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
{
Error *err = NULL;
if (!error_is_set(errp)) {
visit_start_implicit_struct(m, (void**) obj, sizeof(%(name)s), &err);
visit_get_next_type(m, (int*) &(*obj)->kind, %(name)s_qtypes, name, &err);
switch ((*obj)->kind) {
''',
name=name)

for key in members:
assert (members[key] in builtin_types
or find_struct(members[key])
or find_union(members[key])), "Invalid anonymous union member"

ret += mcgen('''
case %(abbrev)s_KIND_%(enum)s:
visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, name, &err);
break;
''',
abbrev = de_camel_case(name).upper(),
enum = c_fun(de_camel_case(key),False).upper(),
c_type = type_name(members[key]),
c_name = c_fun(key))

ret += mcgen('''
default:
abort();
}
error_propagate(errp, err);
err = NULL;
visit_end_implicit_struct(m, &err);
}
}
''')

return ret


def generate_visit_union(expr):

name = expr['union']
Expand All @@ -184,6 +227,10 @@ def generate_visit_union(expr):
base = expr.get('base')
discriminator = expr.get('discriminator')

if discriminator == {}:
assert not base
return generate_visit_anon_union(name, members)

ret = generate_visit_enum('%sKind' % name, members.keys())

if base:
Expand Down
15 changes: 15 additions & 0 deletions scripts/qapi.py
Expand Up @@ -17,6 +17,21 @@
'uint8', 'uint16', 'uint32', 'uint64'
]

builtin_type_qtypes = {
'str': 'QTYPE_QSTRING',
'int': 'QTYPE_QINT',
'number': 'QTYPE_QFLOAT',
'bool': 'QTYPE_QBOOL',
'int8': 'QTYPE_QINT',
'int16': 'QTYPE_QINT',
'int32': 'QTYPE_QINT',
'int64': 'QTYPE_QINT',
'uint8': 'QTYPE_QINT',
'uint16': 'QTYPE_QINT',
'uint32': 'QTYPE_QINT',
'uint64': 'QTYPE_QINT',
}

def tokenize(data):
while len(data):
ch = data[0]
Expand Down

0 comments on commit 69dd62d

Please sign in to comment.