@@ -410,7 +410,7 @@ following example objects:
=== Commands ===
Usage: { 'command': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT,
- '*returns': TYPE-NAME,
+ '*returns': TYPE-NAME, '*boxed': true,
'*gen': false, '*success-response': false }
Commands are defined by using a dictionary containing several members,
@@ -461,6 +461,20 @@ which would validate this Client JSON Protocol transaction:
=> { "execute": "my-second-command" }
<= { "return": [ { "value": "one" }, { } ] }
+The generator emits a prototype for the user's function implementing
+the command. Normally, 'data' is a dictionary for an anonymous type,
+or names a struct type (possibly empty, but not a union), and its
+members are passed as separate arguments to this function. If the
+command definition includes a key 'boxed' with the boolean value true,
+then 'data' is instead the name of any non-empty complex type
+(struct, union, or alternate), and a pointer to that QAPI type is
+passed as a single argument.
+
+The generator also emits a marshalling function that extracts
+arguments for the user's function out of an input QDict, calls the
+user's function, and if it succeeded, builds an output QObject from
+its return value.
+
In rare cases, QAPI cannot express a type-safe representation of a
corresponding Client JSON Protocol command. You then have to suppress
generation of a marshalling function by including a key 'gen' with
@@ -484,7 +498,8 @@ use of this member.
=== Events ===
-Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT }
+Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT,
+ '*boxed': true }
Events are defined with the keyword 'event'. It is not allowed to
name an event 'MAX', since the generator also produces a C enumeration
@@ -505,6 +520,14 @@ Resulting in this JSON object:
"data": { "b": "test string" },
"timestamp": { "seconds": 1267020223, "microseconds": 435656 } }
+The generator emits a function to send the event. Normally, 'data' is
+a dictionary for an anonymous type, or names a struct type (possibly
+empty, but not a union), and its members are passed as separate
+arguments to this function. If the event definition includes a key
+'boxed' with the boolean value true, then 'data' is instead the name of
+any non-empty complex type (struct, union, or alternate), and a
+pointer to that QAPI type is passed as a single argument.
+
== Client JSON Protocol introspection ==
@@ -30,7 +30,8 @@ def gen_call(name, arg_type, boxed, ret_type):
argstr = ''
if boxed:
- assert False # not implemented
+ assert arg_type and not arg_type.is_empty()
+ argstr = '&arg, '
elif arg_type:
assert not arg_type.variants
for memb in arg_type.members:
@@ -79,7 +79,10 @@ def gen_event_send(name, arg_type, boxed):
QObject *obj;
Visitor *v;
''')
- ret += gen_param_var(arg_type)
+ if not boxed:
+ ret += gen_param_var(arg_type)
+ else:
+ assert not boxed
ret += mcgen('''
@@ -522,10 +522,14 @@ def check_type(expr_info, source, value, allow_array=False,
def check_command(expr, expr_info):
name = expr['command']
+ boxed = expr.get('boxed', False)
+ args_meta = ['struct']
+ if boxed:
+ args_meta += ['union', 'alternate']
check_type(expr_info, "'data' for command '%s'" % name,
- expr.get('data'), allow_dict=True, allow_optional=True,
- allow_metas=['struct'])
+ expr.get('data'), allow_dict=not boxed, allow_optional=True,
+ allow_metas=args_meta)
returns_meta = ['union', 'struct']
if name in returns_whitelist:
returns_meta += ['built-in', 'alternate', 'enum']
@@ -537,11 +541,15 @@ def check_command(expr, expr_info):
def check_event(expr, expr_info):
global events
name = expr['event']
+ boxed = expr.get('boxed', False)
+ meta = ['struct']
+ if boxed:
+ meta += ['union', 'alternate']
events.append(name)
check_type(expr_info, "'data' for event '%s'" % name,
- expr.get('data'), allow_dict=True, allow_optional=True,
- allow_metas=['struct'])
+ expr.get('data'), allow_dict=not boxed, allow_optional=True,
+ allow_metas=meta)
def check_union(expr, expr_info):
@@ -694,6 +702,10 @@ def check_keys(expr_elem, meta, required, optional=[]):
raise QAPIExprError(info,
"'%s' of %s '%s' should only use false value"
% (key, meta, name))
+ if key == 'boxed' and value is not True:
+ raise QAPIExprError(info,
+ "'%s' of %s '%s' should only use true value"
+ % (key, meta, name))
for key in required:
if key not in expr:
raise QAPIExprError(info,
@@ -725,10 +737,10 @@ def check_exprs(exprs):
add_struct(expr, info)
elif 'command' in expr:
check_keys(expr_elem, 'command', [],
- ['data', 'returns', 'gen', 'success-response'])
+ ['data', 'returns', 'gen', 'success-response', 'boxed'])
add_name(expr['command'], info, 'command')
elif 'event' in expr:
- check_keys(expr_elem, 'event', [], ['data'])
+ check_keys(expr_elem, 'event', [], ['data', 'boxed'])
add_name(expr['event'], info, 'event')
else:
raise QAPIExprError(expr_elem['info'],
@@ -1163,6 +1175,9 @@ class QAPISchemaAlternateType(QAPISchemaType):
def visit(self, visitor):
visitor.visit_alternate_type(self.name, self.info, self.variants)
+ def is_empty(self):
+ return False
+
class QAPISchemaCommand(QAPISchemaEntity):
def __init__(self, name, info, arg_type, ret_type, gen, success_response,
@@ -1181,9 +1196,19 @@ class QAPISchemaCommand(QAPISchemaEntity):
def check(self, schema):
if self._arg_type_name:
self.arg_type = schema.lookup_type(self._arg_type_name)
- assert isinstance(self.arg_type, QAPISchemaObjectType)
- assert not self.arg_type.variants # not implemented
- assert not self.boxed # not implemented
+ assert (isinstance(self.arg_type, QAPISchemaObjectType) or
+ isinstance(self.arg_type, QAPISchemaAlternateType))
+ self.arg_type.check(schema)
+ if self.boxed:
+ if self.arg_type.is_empty():
+ raise QAPIExprError(self.info,
+ "Cannot use 'boxed' with empty type")
+ else:
+ assert not isinstance(self.arg_type, QAPISchemaAlternateType)
+ assert not self.arg_type.variants
+ elif self.boxed:
+ raise QAPIExprError(self.info,
+ "Use of 'boxed' requires 'data'")
if self._ret_type_name:
self.ret_type = schema.lookup_type(self._ret_type_name)
assert isinstance(self.ret_type, QAPISchemaType)
@@ -1205,9 +1230,19 @@ class QAPISchemaEvent(QAPISchemaEntity):
def check(self, schema):
if self._arg_type_name:
self.arg_type = schema.lookup_type(self._arg_type_name)
- assert isinstance(self.arg_type, QAPISchemaObjectType)
- assert not self.arg_type.variants # not implemented
- assert not self.boxed # not implemented
+ assert (isinstance(self.arg_type, QAPISchemaObjectType) or
+ isinstance(self.arg_type, QAPISchemaAlternateType))
+ self.arg_type.check(schema)
+ if self.boxed:
+ if self.arg_type.is_empty():
+ raise QAPIExprError(self.info,
+ "Cannot use 'boxed' with empty type")
+ else:
+ assert not isinstance(self.arg_type, QAPISchemaAlternateType)
+ assert not self.arg_type.variants
+ elif self.boxed:
+ raise QAPIExprError(self.info,
+ "Use of 'boxed' requires 'data'")
def visit(self, visitor):
visitor.visit_event(self.name, self.info, self.arg_type, self.boxed)
@@ -1648,11 +1683,13 @@ extern const char *const %(c_name)s_lookup[];
def gen_params(arg_type, boxed, extra):
if not arg_type:
+ assert not boxed
return extra
ret = ''
sep = ''
if boxed:
- assert False # not implemented
+ ret += '%s arg' % arg_type.c_param_type()
+ sep = ', '
else:
assert not arg_type.variants
for memb in arg_type.members:
@@ -284,6 +284,10 @@ qapi-schema += args-alternate.json
qapi-schema += args-any.json
qapi-schema += args-array-empty.json
qapi-schema += args-array-unknown.json
+qapi-schema += args-bad-boxed.json
+qapi-schema += args-boxed-anon.json
+qapi-schema += args-boxed-empty.json
+qapi-schema += args-boxed-string.json
qapi-schema += args-int.json
qapi-schema += args-invalid.json
qapi-schema += args-member-array-bad.json
@@ -317,6 +321,7 @@ qapi-schema += enum-wrong-data.json
qapi-schema += escape-outside-string.json
qapi-schema += escape-too-big.json
qapi-schema += escape-too-short.json
+qapi-schema += event-boxed-empty.json
qapi-schema += event-case.json
qapi-schema += event-nest-struct.json
qapi-schema += flat-union-array-branch.json
new file mode 100644
@@ -0,0 +1 @@
+tests/qapi-schema/args-bad-boxed.json:2: 'boxed' of command 'foo' should only use true value
new file mode 100644
@@ -0,0 +1 @@
+1
new file mode 100644
@@ -0,0 +1,2 @@
+# 'boxed' should only appear with value true
+{ 'command': 'foo', 'boxed': false }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
+tests/qapi-schema/args-boxed-anon.json:2: 'data' for command 'foo' should be a type name
new file mode 100644
@@ -0,0 +1 @@
+1
new file mode 100644
@@ -0,0 +1,2 @@
+# 'boxed' can only be used with named types
+{ 'command': 'foo', 'boxed': true, 'data': { 'string': 'str' } }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
+tests/qapi-schema/args-boxed-empty.json:3: Cannot use 'boxed' with empty type
new file mode 100644
@@ -0,0 +1 @@
+1
new file mode 100644
@@ -0,0 +1,3 @@
+# 'boxed' requires a non-empty type
+{ 'struct': 'Empty', 'data': {} }
+{ 'command': 'foo', 'boxed': true, 'data': 'Empty' }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
+tests/qapi-schema/args-boxed-string.json:2: 'data' for command 'foo' cannot use built-in type 'str'
new file mode 100644
@@ -0,0 +1 @@
+1
new file mode 100644
@@ -0,0 +1,2 @@
+# 'boxed' requires a complex (not built-in) type
+{ 'command': 'foo', 'boxed': true, 'data': 'str' }
new file mode 100644
@@ -1 +1 @@
-tests/qapi-schema/args-union.json:4: 'data' for command 'oops' cannot use union type 'Uni'
+tests/qapi-schema/args-union.json:3: 'data' for command 'oops' cannot use union type 'Uni'
@@ -1,4 +1,3 @@
-# we do not allow union arguments
-# TODO should we support this?
+# use of union arguments requires 'boxed':true
{ 'union': 'Uni', 'data': { 'case1': 'int', 'case2': 'str' } }
{ 'command': 'oops', 'data': 'Uni' }
new file mode 100644
@@ -0,0 +1 @@
+tests/qapi-schema/event-boxed-empty.json:2: Use of 'boxed' requires 'data'
new file mode 100644
@@ -0,0 +1 @@
+1
new file mode 100644
@@ -0,0 +1,2 @@
+# 'boxed' requires a non-empty type
+{ 'event': 'FOO', 'boxed': true }
new file mode 100644
@@ -127,6 +127,8 @@
{ 'command': 'guest-get-time', 'data': {'a': 'int', '*b': 'int' },
'returns': 'int' }
{ 'command': 'guest-sync', 'data': { 'arg': 'any' }, 'returns': 'any' }
+{ 'command': 'boxed-struct', 'boxed': true, 'data': 'UserDefZero' }
+{ 'command': 'boxed-union', 'data': 'UserDefNativeListUnion', 'boxed': true }
# For testing integer range flattening in opts-visitor. The following schema
# corresponds to the option format:
@@ -154,6 +156,8 @@
'data': { '*a': 'int', '*b': 'UserDefOne', 'c': 'str' } }
{ 'event': 'EVENT_D',
'data': { 'a' : 'EventStructOne', 'b' : 'str', '*c': 'str', '*enum3': 'EnumOne' } }
+{ 'event': 'EVENT_E', 'boxed': true, 'data': 'UserDefZero' }
+{ 'event': 'EVENT_F', 'boxed': true, 'data': 'UserDefAlternate' }
# test that we correctly compile downstream extensions, as well as munge
# ticklish names
@@ -30,6 +30,10 @@ event EVENT_C q_obj_EVENT_C-arg
boxed=False
event EVENT_D q_obj_EVENT_D-arg
boxed=False
+event EVENT_E UserDefZero
+ boxed=True
+event EVENT_F UserDefAlternate
+ boxed=True
object Empty1
object Empty2
base Empty1
@@ -153,6 +157,10 @@ object __org.qemu_x-Union2
case __org.qemu_x-value: __org.qemu_x-Struct2
command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Union1
gen=True success_response=True boxed=False
+command boxed-struct UserDefZero -> None
+ gen=True success_response=True boxed=True
+command boxed-union UserDefNativeListUnion -> None
+ gen=True success_response=True boxed=True
command guest-get-time q_obj_guest-get-time-arg -> int
gen=True success_response=True boxed=False
command guest-sync q_obj_guest-sync-arg -> any
@@ -59,6 +59,14 @@ QObject *qmp_guest_sync(QObject *arg, Error **errp)
return arg;
}
+void qmp_boxed_struct(UserDefZero *arg, Error **errp)
+{
+}
+
+void qmp_boxed_union(UserDefNativeListUnion *arg, Error **errp)
+{
+}
+
__org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
__org_qemu_x_StructList *b,
__org_qemu_x_Union2 *c,