qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Max Reitz <mreitz@redhat.com>
To: qemu-block@nongnu.org
Cc: Kevin Wolf <kwolf@redhat.com>,
	qemu-devel@nongnu.org, Michael Roth <mdroth@linux.vnet.ibm.com>,
	Markus Armbruster <armbru@redhat.com>,
	Max Reitz <mreitz@redhat.com>
Subject: [Qemu-devel] [PATCH v4 03/14] qapi: Introduce default values for struct members
Date: Mon, 24 Jun 2019 19:39:23 +0200	[thread overview]
Message-ID: <20190624173935.25747-4-mreitz@redhat.com> (raw)
In-Reply-To: <20190624173935.25747-1-mreitz@redhat.com>

With this change, it is possible to give default values for struct
members, as follows:

  What you had to do so far:

    # @member: Some description, defaults to 42.
    { 'struct': 'Foo',
      'data': { '*member': 'int' } }

  What you can do now:

    { 'struct': 'Foo',
      'data': { '*member': { 'type': 'int', 'default': 42 } }

On the C side, this change would remove Foo.has_member, because
Foo.member is always valid now.  The input visitor deals with setting
it.  (Naturally, this means that such defaults are useful only for input
parameters.)

At least three things are left unimplemented:

First, support for alternate data types.  This is because supporting
them would mean having to allocate the object in the input visitor, and
then potentially going through multiple levels of nested types.  In any
case, it would have been difficult and I do not think there is need for
such support at this point.

Second, support for null.  The most important reason for this is that
introspection already uses "'default': null" for "no default, but this
field is optional".  The second reason is that without support for
alternate data types, there is not really a point in supporting null.

Third, full support for default lists.  This has a similar reason to the
lack of support for alternate data types: Allocating a default list is
not trivial -- unless the list is empty, which is exactly what we have
support for.

Signed-off-by: Max Reitz <mreitz@redhat.com>
---
 qapi/introspect.json       |   9 +-
 scripts/qapi/commands.py   |   2 +-
 scripts/qapi/common.py     | 167 +++++++++++++++++++++++++++++++++++--
 scripts/qapi/doc.py        |  20 ++++-
 scripts/qapi/introspect.py |   2 +-
 scripts/qapi/types.py      |   2 +-
 scripts/qapi/visit.py      |  38 ++++++++-
 7 files changed, 217 insertions(+), 23 deletions(-)

diff --git a/qapi/introspect.json b/qapi/introspect.json
index 1843c1cb17..db703135f9 100644
--- a/qapi/introspect.json
+++ b/qapi/introspect.json
@@ -198,11 +198,10 @@
 #
 # @default: default when used as command parameter.
 #           If absent, the parameter is mandatory.
-#           If present, the value must be null.  The parameter is
-#           optional, and behavior when it's missing is not specified
-#           here.
-#           Future extension: if present and non-null, the parameter
-#           is optional, and defaults to this value.
+#           If present and null, the parameter is optional, and
+#           behavior when it's missing is not specified here.
+#           If present and non-null, the parameter is optional, and
+#           defaults to this value.
 #
 # Since: 2.5
 ##
diff --git a/scripts/qapi/commands.py b/scripts/qapi/commands.py
index b929e07be4..6c407cd4ba 100644
--- a/scripts/qapi/commands.py
+++ b/scripts/qapi/commands.py
@@ -35,7 +35,7 @@ def gen_call(name, arg_type, boxed, ret_type):
     elif arg_type:
         assert not arg_type.variants
         for memb in arg_type.members:
-            if memb.optional:
+            if memb.optional and memb.default is None:
                 argstr += 'arg.has_%s, ' % c_name(memb.name)
             argstr += 'arg.%s, ' % c_name(memb.name)
 
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index c6754a5856..8c57d0c67a 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -14,6 +14,7 @@
 from __future__ import print_function
 from contextlib import contextmanager
 import errno
+import math
 import os
 import re
 import string
@@ -800,6 +801,136 @@ def check_if(expr, info):
         check_if_str(ifcond, info)
 
 
+def check_value_str(info, value):
+    return 'g_strdup(%s)' % to_c_string(value) if type(value) is str else False
+
+def check_value_number(info, value):
+    if type(value) is not float:
+        return False
+    if math.isinf(value):
+        return 'INFINITY' if value > 0 else '-INFINITY'
+    elif math.isnan(value):
+        return 'NAN'
+    else:
+        return '%.16e' % value
+
+def check_value_bool(info, value):
+    if type(value) is not bool:
+        return False
+    return 'true' if value else 'false'
+
+def is_int_type(value):
+    if type(value) is int:
+        return True
+    # 'long' does not exist in Python 3
+    try:
+        if type(value) is long:
+            return True
+    except NameError:
+        pass
+
+    return False
+
+def gen_check_value_int(bits):
+    def check_value_int(info, value):
+        if not is_int_type(value) or \
+           value < -(2 ** (bits - 1)) or value >= 2 ** (bits - 1):
+            return False
+        if bits > 32:
+            return '%ill' % value
+        else:
+            return '%i' % value
+
+    return check_value_int
+
+def gen_check_value_uint(bits):
+    def check_value_uint(info, value):
+        if not is_int_type(value) or value < 0 or value >= 2 ** bits:
+            return False
+        if bits > 32:
+            return '%uull' % value
+        elif bits > 16:
+            return '%uu' % value
+        else:
+            return '%u' % value
+
+    return check_value_uint
+
+# Check whether the given value fits the given QAPI type.
+# If so, return a C representation of the value (pointers point to
+# newly allocated objects).
+# Otherwise, raise an exception.
+def check_value(info, qapi_type, value):
+    builtin_type_checks = {
+        'str':      check_value_str,
+        'int':      gen_check_value_int(64),
+        'number':   check_value_number,
+        'bool':     check_value_bool,
+        'int8':     gen_check_value_int(8),
+        'int16':    gen_check_value_int(16),
+        'int32':    gen_check_value_int(32),
+        'int64':    gen_check_value_int(64),
+        'uint8':    gen_check_value_uint(8),
+        'uint16':   gen_check_value_uint(16),
+        'uint32':   gen_check_value_uint(32),
+        'uint64':   gen_check_value_uint(64),
+        'size':     gen_check_value_uint(64),
+    }
+
+    # Cannot support null because that would require a value of "None"
+    # (which is reserved for no default)
+    unsupported_builtin_types = ['null', 'any', 'QType']
+
+    if type(qapi_type) is list:
+        has_list = True
+        qapi_type = qapi_type[0]
+    elif qapi_type.endswith('List'):
+        has_list = True
+        qapi_type = qapi_type[:-4]
+    else:
+        has_list = False
+
+    if has_list:
+        if value == []:
+            return 'NULL'
+        else:
+            raise QAPISemError(info,
+                "Support for non-empty lists as default values has not been " \
+                "implemented yet: '{}'".format(value))
+
+    if qapi_type in builtin_type_checks:
+        c_val = builtin_type_checks[qapi_type](info, value)
+        if not c_val:
+            raise QAPISemError(info,
+                "Value '{}' does not match type {}".format(value, qapi_type))
+        return c_val
+
+    if qapi_type in unsupported_builtin_types:
+        raise QAPISemError(info,
+                           "Cannot specify values for type %s" % qapi_type)
+
+    if qapi_type in enum_types:
+        if not check_value_str(info, value):
+            raise QAPISemError(info,
+                "Enum values must be strings, but '{}' is no string" \
+                        .format(value))
+
+        enum_values = enum_types[qapi_type]['data']
+        for ev in enum_values:
+            if ev['name'] == value:
+                return c_enum_const(qapi_type, value,
+                                    enum_types[qapi_type].get('prefix'))
+
+        raise QAPISemError(info,
+            "Value '{}' does not occur in enum {}".format(value, qapi_type))
+
+    # TODO: Support alternates
+
+    raise QAPISemError(info,
+        "Cannot specify values for type %s (not built-in or an enum)" %
+        qapi_type)
+
+
 def check_type(info, source, value, allow_array=False,
                allow_dict=False, allow_optional=False,
                allow_metas=[]):
@@ -842,15 +973,22 @@ def check_type(info, source, value, allow_array=False,
         if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
             raise QAPISemError(info, "Member of %s uses reserved name '%s'"
                                % (source, key))
-        # Todo: allow dictionaries to represent default values of
-        # an optional argument.
+
         check_known_keys(info, "member '%s' of %s" % (key, source),
-                         arg, ['type'], ['if'])
+                         arg, ['type'], ['if', 'default'])
         check_type(info, "Member '%s' of %s" % (key, source),
                    arg['type'], allow_array=True,
                    allow_metas=['built-in', 'union', 'alternate', 'struct',
                                 'enum'])
 
+        if 'default' in arg:
+            if key[0] != '*':
+                raise QAPISemError(info,
+                    "'%s' is not optional, so it cannot have a default value" %
+                    key)
+
+            check_value(info, arg['type'], arg['default'])
+
 
 def check_command(expr, info):
     name = expr['command']
@@ -1601,13 +1739,14 @@ class QAPISchemaFeature(QAPISchemaMember):
 
 
 class QAPISchemaObjectTypeMember(QAPISchemaMember):
-    def __init__(self, name, typ, optional, ifcond=None):
+    def __init__(self, name, typ, optional, ifcond=None, default=None):
         QAPISchemaMember.__init__(self, name, ifcond)
         assert isinstance(typ, str)
         assert isinstance(optional, bool)
         self._type_name = typ
         self.type = None
         self.optional = optional
+        self.default = default
 
     def check(self, schema):
         assert self.owner
@@ -1917,7 +2056,7 @@ class QAPISchema(object):
             name, info, doc, ifcond,
             self._make_enum_members(data), prefix))
 
-    def _make_member(self, name, typ, ifcond, info):
+    def _make_member(self, name, typ, ifcond, default, info):
         optional = False
         if name.startswith('*'):
             name = name[1:]
@@ -1925,10 +2064,11 @@ class QAPISchema(object):
         if isinstance(typ, list):
             assert len(typ) == 1
             typ = self._make_array_type(typ[0], info)
-        return QAPISchemaObjectTypeMember(name, typ, optional, ifcond)
+        return QAPISchemaObjectTypeMember(name, typ, optional, ifcond, default)
 
     def _make_members(self, data, info):
-        return [self._make_member(key, value['type'], value.get('if'), info)
+        return [self._make_member(key, value['type'], value.get('if'),
+                                  value.get('default'), info)
                 for (key, value) in data.items()]
 
     def _def_struct_type(self, expr, info, doc):
@@ -1951,7 +2091,7 @@ class QAPISchema(object):
             typ = self._make_array_type(typ[0], info)
         typ = self._make_implicit_object_type(
             typ, info, None, self.lookup_type(typ),
-            'wrapper', [self._make_member('data', typ, None, info)])
+            'wrapper', [self._make_member('data', typ, None, None, info)])
         return QAPISchemaObjectTypeVariant(case, typ, ifcond)
 
     def _def_union_type(self, expr, info, doc):
@@ -2234,6 +2374,15 @@ def to_c_string(string):
     return result
 
 
+# Translates a value for the given QAPI type to its C representation.
+# The caller must have called check_value() during parsing to be sure
+# that the given value fits the type.
+def c_value(qapi_type, value):
+    pseudo_info = {'file': '(generator bug)', 'line': 0, 'parent': None}
+    # The caller guarantees this does not raise an exception
+    return check_value(pseudo_info, qapi_type, value)
+
+
 def guardstart(name):
     return mcgen('''
 #ifndef %(name)s
@@ -2356,7 +2505,7 @@ def build_params(arg_type, boxed, extra=None):
         for memb in arg_type.members:
             ret += sep
             sep = ', '
-            if memb.optional:
+            if memb.optional and memb.default is None:
                 ret += 'bool has_%s, ' % c_name(memb.name)
             ret += '%s %s' % (memb.type.c_param_type(),
                               c_name(memb.name))
diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 5fc0fc7e06..78a9052738 100755
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -139,13 +139,29 @@ def texi_enum_value(value, desc, suffix):
         value.name, desc, texi_if(value.ifcond, prefix='@*'))
 
 
+def doc_value(value):
+    if value is True:
+        return 'true'
+    elif value is False:
+        return 'false'
+    elif value is None:
+        return 'null'
+    else:
+        return '{}'.format(value)
+
 def texi_member(member, desc, suffix):
     """Format a table of members item for an object type member"""
     typ = member.type.doc_type()
     membertype = ': ' + typ if typ else ''
+
+    optional_info = ''
+    if member.default is not None:
+        optional_info = ' (optional, default: %s)' % doc_value(member.default)
+    elif member.optional:
+        optional_info = ' (optional)'
+
     return '@item @code{%s%s}%s%s\n%s%s' % (
-        member.name, membertype,
-        ' (optional)' if member.optional else '',
+        member.name, membertype, optional_info,
         suffix, desc, texi_if(member.ifcond, prefix='@*'))
 
 
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 572e0b8331..7d73020a42 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -159,7 +159,7 @@ const QLitObject %(c_name)s = %(c_string)s;
     def _gen_member(self, member):
         ret = {'name': member.name, 'type': self._use_type(member.type)}
         if member.optional:
-            ret['default'] = None
+            ret['default'] = member.default
         if member.ifcond:
             ret = (ret, {'if': member.ifcond})
         return ret
diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py
index 3edd9374aa..46a6d33379 100644
--- a/scripts/qapi/types.py
+++ b/scripts/qapi/types.py
@@ -44,7 +44,7 @@ def gen_struct_members(members):
     ret = ''
     for memb in members:
         ret += gen_if(memb.ifcond)
-        if memb.optional:
+        if memb.optional and memb.default is None:
             ret += mcgen('''
     bool has_%(c_name)s;
 ''',
diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py
index 484ebb66ad..0960e25a25 100644
--- a/scripts/qapi/visit.py
+++ b/scripts/qapi/visit.py
@@ -40,10 +40,14 @@ def gen_visit_object_members(name, base, members, variants):
 void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
 {
     Error *err = NULL;
-
 ''',
                 c_name=c_name(name))
 
+    if len([m for m in members if m.default is not None]) > 0:
+        ret += mcgen('''
+    bool has_optional;
+''')
+
     if base:
         ret += mcgen('''
     visit_type_%(c_type)s_members(v, (%(c_type)s *)obj, &err);
@@ -53,13 +57,28 @@ void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
 ''',
                      c_type=base.c_name())
 
+    ret += mcgen('''
+
+''')
+
     for memb in members:
         ret += gen_if(memb.ifcond)
         if memb.optional:
+            if memb.default is not None:
+                optional_target = 'has_optional'
+                # Visitors other than the input visitor do not have to implement
+                # .optional().  Therefore, we have to initialize has_optional.
+                # Initialize it to true, because the field's value is always
+                # present when using any visitor but the input visitor.
+                ret += mcgen('''
+    has_optional = true;
+''')
+            else:
+                optional_target = 'obj->has_' + c_name(memb.name)
             ret += mcgen('''
-    if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) {
+    if (visit_optional(v, "%(name)s", &%(opt_target)s)) {
 ''',
-                         name=memb.name, c_name=c_name(memb.name))
+                         name=memb.name, opt_target=optional_target)
             push_indent()
         ret += mcgen('''
     visit_type_%(c_type)s(v, "%(name)s", &obj->%(c_name)s, &err);
@@ -69,7 +88,16 @@ void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp)
 ''',
                      c_type=memb.type.c_name(), name=memb.name,
                      c_name=c_name(memb.name))
-        if memb.optional:
+        if memb.default is not None:
+            pop_indent()
+            ret += mcgen('''
+    } else {
+        obj->%(c_name)s = %(c_value)s;
+    }
+''',
+                         c_name=c_name(memb.name),
+                         c_value=c_value(memb._type_name, memb.default))
+        elif memb.optional:
             pop_indent()
             ret += mcgen('''
     }
@@ -287,6 +315,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
         self._add_system_module(None, ' * Built-in QAPI visitors')
         self._genc.preamble_add(mcgen('''
 #include "qemu/osdep.h"
+#include <math.h>
 #include "qapi/error.h"
 #include "qapi/qapi-builtin-visit.h"
 '''))
@@ -302,6 +331,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor):
         visit = self._module_basename('qapi-visit', name)
         self._genc.preamble_add(mcgen('''
 #include "qemu/osdep.h"
+#include <math.h>
 #include "qapi/error.h"
 #include "qapi/qmp/qerror.h"
 #include "%(visit)s.h"
-- 
2.21.0



  parent reply	other threads:[~2019-06-24 17:50 UTC|newest]

Thread overview: 41+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-06-24 17:39 [Qemu-devel] [PATCH v4 00/14] block: Try to create well-typed json:{} filenames Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 01/14] qapi: Parse numeric values Max Reitz
2019-11-14  9:15   ` Markus Armbruster
2019-11-14  9:50     ` Max Reitz
2019-11-14 12:01       ` Markus Armbruster
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 02/14] qapi: Move to_c_string() to common.py Max Reitz
2019-11-14  9:20   ` Markus Armbruster
2019-11-14  9:58     ` Max Reitz
2019-06-24 17:39 ` Max Reitz [this message]
2019-11-14 15:53   ` [Qemu-devel] [PATCH v4 03/14] qapi: Introduce default values for struct members Markus Armbruster
2019-11-21 15:07   ` Markus Armbruster
2019-11-21 15:24     ` Eric Blake
2019-11-21 19:46       ` Markus Armbruster
2019-11-21 19:56         ` Eric Blake
2019-11-22  7:29           ` Markus Armbruster
2019-11-22 10:25             ` Kevin Wolf
2019-11-22 14:40               ` Markus Armbruster
2019-11-22 16:12                 ` Kevin Wolf
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 04/14] qapi: Allow optional discriminators Max Reitz
2019-11-21 15:13   ` Markus Armbruster
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 05/14] qapi: Document default values for struct members Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 06/14] test-qapi: Print struct members' default values Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 07/14] tests: Test QAPI default values for struct members Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 08/14] tests: Add QAPI optional discriminator tests Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 09/14] qapi: Formalize qcow2 encryption probing Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 10/14] qapi: Formalize qcow " Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 11/14] block: Try to create well typed json:{} filenames Max Reitz
2019-06-24 20:06   ` Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 12/14] iotests: Test internal option typing Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 13/14] iotests: qcow2's encrypt.format is now optional Max Reitz
2019-06-24 17:39 ` [Qemu-devel] [PATCH v4 14/14] block: Make use of QAPI defaults Max Reitz
2019-06-24 18:35 ` [Qemu-devel] [PATCH v4 00/14] block: Try to create well-typed json:{} filenames no-reply
2019-06-24 19:04   ` Max Reitz
2019-06-24 19:06     ` Max Reitz
2019-06-24 19:00 ` no-reply
2019-06-24 20:06   ` Max Reitz
2019-08-19 16:49 ` Max Reitz
2019-09-13 11:49 ` Max Reitz
2019-11-06 13:01   ` Max Reitz
2019-11-14  8:54     ` Markus Armbruster
2019-11-21 15:17       ` Markus Armbruster

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190624173935.25747-4-mreitz@redhat.com \
    --to=mreitz@redhat.com \
    --cc=armbru@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).