From 57516863644817ca59fab023e0c68d139929f3e0 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Sat, 8 Dec 2018 15:15:42 +0400 Subject: qapi: rename QAPISchemaEnumType.values to .members Rename QAPISchemaEnumType.values and related variables to members. Makes sense ever since commit 93bda4dd4 changed .values from list of string to list of QAPISchemaMember. Obvious no-op. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181208111606.8505-4-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 7b62a4c7b0..046b7e5681 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -1161,22 +1161,22 @@ class QAPISchemaBuiltinType(QAPISchemaType): class QAPISchemaEnumType(QAPISchemaType): - def __init__(self, name, info, doc, ifcond, values, prefix): + def __init__(self, name, info, doc, ifcond, members, prefix): QAPISchemaType.__init__(self, name, info, doc, ifcond) - for v in values: - assert isinstance(v, QAPISchemaMember) - v.set_owner(name) + for m in members: + assert isinstance(m, QAPISchemaMember) + m.set_owner(name) assert prefix is None or isinstance(prefix, str) - self.values = values + self.members = members self.prefix = prefix def check(self, schema): QAPISchemaType.check(self, schema) seen = {} - for v in self.values: - v.check_clash(self.info, seen) + for m in self.members: + m.check_clash(self.info, seen) if self.doc: - self.doc.connect_member(v) + self.doc.connect_member(m) def is_implicit(self): # See QAPISchema._make_implicit_enum_type() and ._def_predefineds() @@ -1186,7 +1186,7 @@ class QAPISchemaEnumType(QAPISchemaType): return c_name(self.name) def member_names(self): - return [v.name for v in self.values] + return [m.name for m in self.members] def json_type(self): return 'string' @@ -1403,9 +1403,9 @@ class QAPISchemaObjectTypeVariants(object): if self._tag_name: # flat union # branches that are not explicitly covered get an empty type cases = set([v.name for v in self.variants]) - for val in self.tag_member.type.values: - if val.name not in cases: - v = QAPISchemaObjectTypeVariant(val.name, 'q_empty') + for m in self.tag_member.type.members: + if m.name not in cases: + v = QAPISchemaObjectTypeVariant(m.name, 'q_empty') v.set_owner(self.tag_member.owner) self.variants.append(v) for v in self.variants: -- cgit v1.2.3-55-g7522 From 9c2f56e9f9d5a1f9ddac77dda35f997738e85d11 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:03 +0400 Subject: qapi: Do not define enumeration value explicitly The generated C enumeration types explicitly set the enumeration constants to 0, 1, 2, ... That's exactly what you get when you don't supply values. Drop the explicit values. No change now, but it will avoid gaps in the values when we later add support for 'if' conditions. Avoiding such gaps will save us the trouble of changing the ENUM_lookup[] tables to work without a sentinel. We'll have to take care to ensure the headers required by the 'if' conditions get always included before the generated QAPI code. Fortunately, our convention to include "qemu/osdep.h" first in any .c ensures that's the case for our CONFIG_FOO macros. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-2-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 046b7e5681..55c914ec44 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -2045,14 +2045,11 @@ typedef enum %(c_name)s { ''', c_name=c_name(name)) - i = 0 for value in enum_values: ret += mcgen(''' - %(c_enum)s = %(i)d, + %(c_enum)s, ''', - c_enum=c_enum_const(name, value, prefix), - i=i) - i += 1 + c_enum=c_enum_const(name, value, prefix)) ret += mcgen(''' } %(c_name)s; -- cgit v1.2.3-55-g7522 From 1962bd39d567e8b44646e558b07b2742a5a61339 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:04 +0400 Subject: qapi: change enum visitor and gen_enum* to take QAPISchemaMember This will allow to add and access more properties associated with enum values/members, like the associated 'if' condition. We may want to have a specialized type QAPISchemaEnumMember, for now this will do. Modify gen_enum() and gen_enum_lookup() for the same reason. Suggested-by: Markus Armbruster Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-3-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 22 +++++++++++----------- scripts/qapi/doc.py | 2 +- scripts/qapi/events.py | 13 +++++++------ scripts/qapi/introspect.py | 5 +++-- scripts/qapi/types.py | 6 +++--- scripts/qapi/visit.py | 2 +- tests/qapi-schema/test-qapi.py | 4 ++-- 7 files changed, 28 insertions(+), 26 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 55c914ec44..1fa2f79990 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -1063,7 +1063,7 @@ class QAPISchemaVisitor(object): def visit_builtin_type(self, name, info, json_type): pass - def visit_enum_type(self, name, info, ifcond, values, prefix): + def visit_enum_type(self, name, info, ifcond, members, prefix): pass def visit_array_type(self, name, info, ifcond, element_type): @@ -1193,7 +1193,7 @@ class QAPISchemaEnumType(QAPISchemaType): def visit(self, visitor): visitor.visit_enum_type(self.name, self.info, self.ifcond, - self.member_names(), self.prefix) + self.members, self.prefix) class QAPISchemaArrayType(QAPISchemaType): @@ -2012,19 +2012,19 @@ def _wrap_ifcond(ifcond, before, after): return out -def gen_enum_lookup(name, values, prefix=None): +def gen_enum_lookup(name, members, prefix=None): ret = mcgen(''' const QEnumLookup %(c_name)s_lookup = { .array = (const char *const[]) { ''', c_name=c_name(name)) - for value in values: - index = c_enum_const(name, value, prefix) + for m in members: + index = c_enum_const(name, m.name, prefix) ret += mcgen(''' - [%(index)s] = "%(value)s", + [%(index)s] = "%(name)s", ''', - index=index, value=value) + index=index, name=m.name) ret += mcgen(''' }, @@ -2035,9 +2035,9 @@ const QEnumLookup %(c_name)s_lookup = { return ret -def gen_enum(name, values, prefix=None): +def gen_enum(name, members, prefix=None): # append automatically generated _MAX value - enum_values = values + ['_MAX'] + enum_members = members + [QAPISchemaMember('_MAX')] ret = mcgen(''' @@ -2045,11 +2045,11 @@ typedef enum %(c_name)s { ''', c_name=c_name(name)) - for value in enum_values: + for m in enum_members: ret += mcgen(''' %(c_enum)s, ''', - c_enum=c_enum_const(name, value, prefix)) + c_enum=c_enum_const(name, m.name, prefix)) ret += mcgen(''' } %(c_name)s; diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py index 987fd3c943..76cb186ff9 100755 --- a/scripts/qapi/doc.py +++ b/scripts/qapi/doc.py @@ -206,7 +206,7 @@ class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor): def write(self, output_dir): self._gen.write(output_dir, self._prefix + 'qapi-doc.texi') - def visit_enum_type(self, name, info, ifcond, values, prefix): + def visit_enum_type(self, name, info, ifcond, members, prefix): doc = self.cur_doc self._gen.add(TYPE_FMT(type='Enum', name=doc.symbol, diff --git a/scripts/qapi/events.py b/scripts/qapi/events.py index 2ed7902424..f1b88d8786 100644 --- a/scripts/qapi/events.py +++ b/scripts/qapi/events.py @@ -143,8 +143,8 @@ class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor): QAPISchemaModularCVisitor.__init__( self, prefix, 'qapi-events', ' * Schema-defined QAPI/QMP events', __doc__) - self._enum_name = c_name(prefix + 'QAPIEvent', protect=False) - self._event_names = [] + self._event_enum_name = c_name(prefix + 'QAPIEvent', protect=False) + self._event_enum_members = [] def _begin_module(self, name): types = self._module_basename('qapi-types', name) @@ -170,15 +170,16 @@ class QAPISchemaGenEventVisitor(QAPISchemaModularCVisitor): def visit_end(self): (genc, genh) = self._module[self._main_module] - genh.add(gen_enum(self._enum_name, self._event_names)) - genc.add(gen_enum_lookup(self._enum_name, self._event_names)) + genh.add(gen_enum(self._event_enum_name, self._event_enum_members)) + genc.add(gen_enum_lookup(self._event_enum_name, + self._event_enum_members)) def visit_event(self, name, info, ifcond, arg_type, boxed): with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_event_send_decl(name, arg_type, boxed)) self._genc.add(gen_event_send(name, arg_type, boxed, - self._enum_name)) - self._event_names.append(name) + self._event_enum_name)) + self._event_enum_members.append(QAPISchemaMember(name)) def gen_events(schema, output_dir, prefix): diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 67d6106f77..417625d54b 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -174,8 +174,9 @@ const QLitObject %(c_name)s = %(c_string)s; def visit_builtin_type(self, name, info, json_type): self._gen_qlit(name, 'builtin', {'json-type': json_type}, []) - def visit_enum_type(self, name, info, ifcond, values, prefix): - self._gen_qlit(name, 'enum', {'values': values}, ifcond) + def visit_enum_type(self, name, info, ifcond, members, prefix): + self._gen_qlit(name, 'enum', + {'values': [m.name for m in members]}, ifcond) def visit_array_type(self, name, info, ifcond, element_type): element = self._use_type(element_type) diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index fd7808103c..e8d22c5081 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -212,10 +212,10 @@ class QAPISchemaGenTypeVisitor(QAPISchemaModularCVisitor): self._genh.add(gen_type_cleanup_decl(name)) self._genc.add(gen_type_cleanup(name)) - def visit_enum_type(self, name, info, ifcond, values, prefix): + def visit_enum_type(self, name, info, ifcond, members, prefix): with ifcontext(ifcond, self._genh, self._genc): - self._genh.preamble_add(gen_enum(name, values, prefix)) - self._genc.add(gen_enum_lookup(name, values, prefix)) + self._genh.preamble_add(gen_enum(name, members, prefix)) + self._genc.add(gen_enum_lookup(name, members, prefix)) def visit_array_type(self, name, info, ifcond, element_type): with ifcontext(ifcond, self._genh, self._genc): diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index 460cf12989..24f85a2e85 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -310,7 +310,7 @@ class QAPISchemaGenVisitVisitor(QAPISchemaModularCVisitor): ''', types=types)) - def visit_enum_type(self, name, info, ifcond, values, prefix): + def visit_enum_type(self, name, info, ifcond, members, prefix): with ifcontext(ifcond, self._genh, self._genc): self._genh.add(gen_visit_decl(name, scalar=True)) self._genc.add(gen_visit_enum(name)) diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index cea21c773a..27f776693e 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -23,8 +23,8 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): def visit_include(self, name, info): print('include %s' % name) - def visit_enum_type(self, name, info, ifcond, values, prefix): - print('enum %s %s' % (name, values)) + def visit_enum_type(self, name, info, ifcond, members, prefix): + print('enum %s %s' % (name, [m.name for m in members])) if prefix: print(' prefix %s' % prefix) self._print_if(ifcond) -- cgit v1.2.3-55-g7522 From 563bd35d87f7bcab644381319d6247e8aee8a4f4 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:06 +0400 Subject: qapi: factor out checking for keys Introduce a new helper function to check if the given keys are known, and if mandatory keys are present. The function will be reused in other places in the following code changes. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-5-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 1fa2f79990..18f5872808 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -873,6 +873,17 @@ def check_struct(expr, info): allow_metas=['struct']) +def check_known_keys(info, source, keys, required, optional): + for key in keys: + if key not in required and key not in optional: + raise QAPISemError(info, "Unknown key '%s' in %s" % (key, source)) + + for key in required: + if key not in keys: + raise QAPISemError(info, "Key '%s' is missing from %s" + % (key, source)) + + def check_keys(expr_elem, meta, required, optional=[]): expr = expr_elem['expr'] info = expr_elem['info'] @@ -880,10 +891,9 @@ def check_keys(expr_elem, meta, required, optional=[]): if not isinstance(name, str): raise QAPISemError(info, "'%s' key must have a string value" % meta) required = required + [meta] + source = "%s '%s'" % (meta, name) + check_known_keys(info, source, expr.keys(), required, optional) for (key, value) in expr.items(): - if key not in required and key not in optional: - raise QAPISemError(info, "Unknown key '%s' in %s '%s'" - % (key, meta, name)) if key in ['gen', 'success-response'] and value is not False: raise QAPISemError(info, "'%s' of %s '%s' should only use false value" @@ -895,10 +905,6 @@ def check_keys(expr_elem, meta, required, optional=[]): % (key, meta, name)) if key == 'if': check_if(expr, info) - for key in required: - if key not in expr: - raise QAPISemError(info, "Key '%s' is missing from %s '%s'" - % (key, meta, name)) def check_exprs(exprs): -- cgit v1.2.3-55-g7522 From 7e80d48001c8ebc11358596f10ba48daddee05f5 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:07 +0400 Subject: qapi: improve reporting of unknown or missing keys Report the set of missing or unknown keys. And give a hint about the accepted keys. The error message for multiple meta type members (visible in tests/qapi-schema/double-type.err) is not improved. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-6-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 23 +++++++++++++++-------- tests/qapi-schema/alternate-base.err | 1 + tests/qapi-schema/double-type.err | 1 + tests/qapi-schema/unknown-expr-key.err | 3 ++- tests/qapi-schema/unknown-expr-key.json | 2 +- 5 files changed, 20 insertions(+), 10 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 18f5872808..f205805751 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -874,14 +874,21 @@ def check_struct(expr, info): def check_known_keys(info, source, keys, required, optional): - for key in keys: - if key not in required and key not in optional: - raise QAPISemError(info, "Unknown key '%s' in %s" % (key, source)) - - for key in required: - if key not in keys: - raise QAPISemError(info, "Key '%s' is missing from %s" - % (key, source)) + + def pprint(elems): + return ', '.join("'" + e + "'" for e in sorted(elems)) + + missing = set(required) - set(keys) + if missing: + raise QAPISemError(info, "Key%s %s %s missing from %s" + % ('s' if len(missing) > 1 else '', pprint(missing), + 'are' if len(missing) > 1 else 'is', source)) + allowed = set(required + optional) + unknown = set(keys) - allowed + if unknown: + raise QAPISemError(info, "Unknown key%s %s in %s\nValid keys are %s." + % ('s' if len(unknown) > 1 else '', pprint(unknown), + source, pprint(allowed))) def check_keys(expr_elem, meta, required, optional=[]): diff --git a/tests/qapi-schema/alternate-base.err b/tests/qapi-schema/alternate-base.err index 30d8a34373..ebe05bc898 100644 --- a/tests/qapi-schema/alternate-base.err +++ b/tests/qapi-schema/alternate-base.err @@ -1 +1,2 @@ tests/qapi-schema/alternate-base.json:4: Unknown key 'base' in alternate 'Alt' +Valid keys are 'alternate', 'data', 'if'. diff --git a/tests/qapi-schema/double-type.err b/tests/qapi-schema/double-type.err index f9613c6d6b..799193dba1 100644 --- a/tests/qapi-schema/double-type.err +++ b/tests/qapi-schema/double-type.err @@ -1 +1,2 @@ tests/qapi-schema/double-type.json:2: Unknown key 'command' in struct 'bar' +Valid keys are 'base', 'data', 'if', 'struct'. diff --git a/tests/qapi-schema/unknown-expr-key.err b/tests/qapi-schema/unknown-expr-key.err index 12f5ed5b43..6ff8bb99c5 100644 --- a/tests/qapi-schema/unknown-expr-key.err +++ b/tests/qapi-schema/unknown-expr-key.err @@ -1 +1,2 @@ -tests/qapi-schema/unknown-expr-key.json:2: Unknown key 'bogus' in struct 'bar' +tests/qapi-schema/unknown-expr-key.json:2: Unknown keys 'bogus', 'phony' in struct 'bar' +Valid keys are 'base', 'data', 'if', 'struct'. diff --git a/tests/qapi-schema/unknown-expr-key.json b/tests/qapi-schema/unknown-expr-key.json index 3b2be00cc4..13292d75ed 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 -{ 'struct': 'bar', 'data': { 'string': 'str'}, 'bogus': { } } +{ 'struct': 'bar', 'data': { 'string': 'str'}, 'bogus': { }, 'phony': { } } -- cgit v1.2.3-55-g7522 From ea738b21685814dc54352858afabc2ff33ade53e Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:08 +0400 Subject: qapi: add a dictionary form with 'name' key for enum members Desugar the enum NAME form to { 'name': NAME }. This will allow to add new enum members, such as 'if' in the following patch. Signed-off-by: Marc-André Lureau Message-Id: <20181213123724.4866-7-marcandre.lureau@redhat.com> Message-Id: <20181213123724.4866-8-marcandre.lureau@redhat.com> Message-Id: <20181213123724.4866-9-marcandre.lureau@redhat.com> Reviewed-by: Markus Armbruster [Harmless accidental move backed out, long line wrapped, patches squashed] Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 36 ++++++++++++++++++------- tests/Makefile.include | 3 ++- tests/qapi-schema/enum-bad-member.err | 1 + tests/qapi-schema/enum-bad-member.exit | 1 + tests/qapi-schema/enum-bad-member.json | 2 ++ tests/qapi-schema/enum-bad-member.out | 0 tests/qapi-schema/enum-dict-member-unknown.err | 2 ++ tests/qapi-schema/enum-dict-member-unknown.exit | 1 + tests/qapi-schema/enum-dict-member-unknown.json | 2 ++ tests/qapi-schema/enum-dict-member-unknown.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 14 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 tests/qapi-schema/enum-bad-member.err create mode 100644 tests/qapi-schema/enum-bad-member.exit create mode 100644 tests/qapi-schema/enum-bad-member.json create mode 100644 tests/qapi-schema/enum-bad-member.out create mode 100644 tests/qapi-schema/enum-dict-member-unknown.err create mode 100644 tests/qapi-schema/enum-dict-member-unknown.exit create mode 100644 tests/qapi-schema/enum-dict-member-unknown.json create mode 100644 tests/qapi-schema/enum-dict-member-unknown.out delete mode 100644 tests/qapi-schema/enum-dict-member.err delete mode 100644 tests/qapi-schema/enum-dict-member.exit delete mode 100644 tests/qapi-schema/enum-dict-member.json delete mode 100644 tests/qapi-schema/enum-dict-member.out (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index f205805751..b1cf33af21 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -740,6 +740,10 @@ def check_event(expr, info): allow_metas=meta) +def enum_get_names(expr): + return [e['name'] for e in expr['data']] + + def check_union(expr, info): name = expr['union'] base = expr.get('base') @@ -799,7 +803,7 @@ def check_union(expr, info): # If the discriminator names an enum type, then all members # of 'data' must also be members of the enum type. if enum_define: - if key not in enum_define['data']: + if key not in enum_get_names(enum_define): raise QAPISemError(info, "Discriminator value '%s' is not found in " "enum '%s'" @@ -831,7 +835,7 @@ def check_alternate(expr, info): if qtype == 'QTYPE_QSTRING': enum_expr = enum_types.get(value) if enum_expr: - for v in enum_expr['data']: + for v in enum_get_names(enum_expr): if v in ['on', 'off']: conflicting.add('QTYPE_QBOOL') if re.match(r'[-+0-9.]', v): # lazy, could be tightened @@ -847,9 +851,15 @@ def check_alternate(expr, info): types_seen[qt] = key +def normalize_enum(expr): + if isinstance(expr['data'], list): + expr['data'] = [m if isinstance(m, dict) else {'name': m} + for m in expr['data']] + + def check_enum(expr, info): name = expr['enum'] - members = expr.get('data') + members = expr['data'] prefix = expr.get('prefix') if not isinstance(members, list): @@ -858,8 +868,11 @@ def check_enum(expr, info): if prefix is not None and not isinstance(prefix, str): raise QAPISemError(info, "Enum '%s' requires a string for 'prefix'" % name) + for member in members: - check_name(info, "Member of enum '%s'" % name, member, + source = "dictionary member of enum '%s'" % name + check_known_keys(info, source, member, ['name'], []) + check_name(info, "Member of enum '%s'" % name, member['name'], enum_member=True) @@ -937,6 +950,7 @@ def check_exprs(exprs): if 'enum' in expr: meta = 'enum' check_keys(expr_elem, 'enum', ['data'], ['if', 'prefix']) + normalize_enum(expr) enum_types[expr[meta]] = expr elif 'union' in expr: meta = 'union' @@ -1633,14 +1647,16 @@ class QAPISchema(object): self.the_empty_object_type = QAPISchemaObjectType( 'q_empty', None, None, None, None, [], None) self._def_entity(self.the_empty_object_type) - qtype_values = self._make_enum_members(['none', 'qnull', 'qnum', - 'qstring', 'qdict', 'qlist', - 'qbool']) + + qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist', + 'qbool'] + qtype_values = self._make_enum_members([{'name': n} for n in qtypes]) + self._def_entity(QAPISchemaEnumType('QType', None, None, None, qtype_values, 'QTYPE')) def _make_enum_members(self, values): - return [QAPISchemaMember(v) for v in values] + return [QAPISchemaMember(v['name']) for v in values] def _make_implicit_enum_type(self, name, info, ifcond, values): # See also QAPISchemaObjectTypeMember._pretty_owner() @@ -1740,8 +1756,8 @@ class QAPISchema(object): else: variants = [self._make_simple_variant(key, value, info) for (key, value) in data.items()] - typ = self._make_implicit_enum_type(name, info, ifcond, - [v.name for v in variants]) + enum = [{'name': v.name} for v in variants] + typ = self._make_implicit_enum_type(name, info, ifcond, enum) tag_member = QAPISchemaObjectTypeMember('type', typ, False) members = [tag_member] self._def_entity( diff --git a/tests/Makefile.include b/tests/Makefile.include index fb0b449c02..2e894c1037 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -379,10 +379,11 @@ qapi-schema += double-data.json qapi-schema += double-type.json qapi-schema += duplicate-key.json qapi-schema += empty.json +qapi-schema += enum-bad-member.json qapi-schema += enum-bad-name.json qapi-schema += enum-bad-prefix.json qapi-schema += enum-clash-member.json -qapi-schema += enum-dict-member.json +qapi-schema += enum-dict-member-unknown.json qapi-schema += enum-int-member.json qapi-schema += enum-member-case.json qapi-schema += enum-missing-data.json diff --git a/tests/qapi-schema/enum-bad-member.err b/tests/qapi-schema/enum-bad-member.err new file mode 100644 index 0000000000..211db9e6fc --- /dev/null +++ b/tests/qapi-schema/enum-bad-member.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-bad-member.json:2: Member of enum 'MyEnum' requires a string name diff --git a/tests/qapi-schema/enum-bad-member.exit b/tests/qapi-schema/enum-bad-member.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/enum-bad-member.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/enum-bad-member.json b/tests/qapi-schema/enum-bad-member.json new file mode 100644 index 0000000000..98da6828b4 --- /dev/null +++ b/tests/qapi-schema/enum-bad-member.json @@ -0,0 +1,2 @@ +# we reject any enum member that is not a string +{ 'enum': 'MyEnum', 'data': [ [ ] ] } diff --git a/tests/qapi-schema/enum-bad-member.out b/tests/qapi-schema/enum-bad-member.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-dict-member-unknown.err b/tests/qapi-schema/enum-dict-member-unknown.err new file mode 100644 index 0000000000..76bd0471db --- /dev/null +++ b/tests/qapi-schema/enum-dict-member-unknown.err @@ -0,0 +1,2 @@ +tests/qapi-schema/enum-dict-member-unknown.json:2: Unknown key 'bad-key' in dictionary member of enum 'MyEnum' +Valid keys are 'name'. diff --git a/tests/qapi-schema/enum-dict-member-unknown.exit b/tests/qapi-schema/enum-dict-member-unknown.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/enum-dict-member-unknown.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/enum-dict-member-unknown.json b/tests/qapi-schema/enum-dict-member-unknown.json new file mode 100644 index 0000000000..6664c59201 --- /dev/null +++ b/tests/qapi-schema/enum-dict-member-unknown.json @@ -0,0 +1,2 @@ +# we reject any enum member that is not a string or a dict with 'name' +{ 'enum': 'MyEnum', 'data': [ { 'name': 'foo', 'bad-key': 'str' } ] } diff --git a/tests/qapi-schema/enum-dict-member-unknown.out b/tests/qapi-schema/enum-dict-member-unknown.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/enum-dict-member.err b/tests/qapi-schema/enum-dict-member.err deleted file mode 100644 index 8ca146ea59..0000000000 --- a/tests/qapi-schema/enum-dict-member.err +++ /dev/null @@ -1 +0,0 @@ -tests/qapi-schema/enum-dict-member.json:2: Member of enum 'MyEnum' requires a string name diff --git a/tests/qapi-schema/enum-dict-member.exit b/tests/qapi-schema/enum-dict-member.exit deleted file mode 100644 index d00491fd7e..0000000000 --- a/tests/qapi-schema/enum-dict-member.exit +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/tests/qapi-schema/enum-dict-member.json b/tests/qapi-schema/enum-dict-member.json deleted file mode 100644 index 79672e0f09..0000000000 --- a/tests/qapi-schema/enum-dict-member.json +++ /dev/null @@ -1,2 +0,0 @@ -# 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 deleted file mode 100644 index e69de29bb2..0000000000 -- cgit v1.2.3-55-g7522 From 6cc32b0e14b3f91e15a9511d90b222abc1df391d Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:11 +0400 Subject: qapi: add 'if' to enum members QAPISchemaMember gains .ifcond for enum members: inherited classes, such as QAPISchemaObjectTypeMember, will thus have an ifcond member after this (those different types will also use the .ifcond to store the condition and generate conditional code in the following patches). The generated code remains unconditional for now. Later patches generate the conditionals. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-10-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- docs/devel/qapi-code-gen.txt | 9 +++++++++ scripts/qapi/common.py | 8 +++++--- tests/Makefile.include | 1 + tests/qapi-schema/enum-dict-member-unknown.err | 2 +- tests/qapi-schema/enum-if-invalid.err | 1 + tests/qapi-schema/enum-if-invalid.exit | 1 + tests/qapi-schema/enum-if-invalid.json | 3 +++ tests/qapi-schema/enum-if-invalid.out | 0 tests/qapi-schema/qapi-schema-test.json | 5 +++-- tests/qapi-schema/qapi-schema-test.out | 2 ++ tests/qapi-schema/test-qapi.py | 1 + 11 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 tests/qapi-schema/enum-if-invalid.err create mode 100644 tests/qapi-schema/enum-if-invalid.exit create mode 100644 tests/qapi-schema/enum-if-invalid.json create mode 100644 tests/qapi-schema/enum-if-invalid.out (limited to 'scripts/qapi/common.py') diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt index 2c8b392b20..7ba9066eac 100644 --- a/docs/devel/qapi-code-gen.txt +++ b/docs/devel/qapi-code-gen.txt @@ -752,6 +752,15 @@ gets its generated code guarded like this: #endif /* defined(HAVE_BAR) */ #endif /* defined(CONFIG_FOO) */ +An enum value can be replaced by a dictionary with a 'name' and a 'if' +key. + +Example: a conditional 'bar' enum member. + +{ 'enum': 'IfEnum', 'data': + [ 'foo', + { 'name' : 'bar', 'if': 'defined(IFCOND)' } ] } + Please note that you are responsible to ensure that the C code will compile with an arbitrary combination of conditions, since the generators are unable to check it at this point. diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index b1cf33af21..a609ffcfee 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -871,7 +871,8 @@ def check_enum(expr, info): for member in members: source = "dictionary member of enum '%s'" % name - check_known_keys(info, source, member, ['name'], []) + check_known_keys(info, source, member, ['name'], ['if']) + check_if(member, info) check_name(info, "Member of enum '%s'" % name, member['name'], enum_member=True) @@ -1345,9 +1346,10 @@ class QAPISchemaObjectType(QAPISchemaType): class QAPISchemaMember(object): role = 'member' - def __init__(self, name): + def __init__(self, name, ifcond=None): assert isinstance(name, str) self.name = name + self.ifcond = listify_cond(ifcond) self.owner = None def set_owner(self, name): @@ -1656,7 +1658,7 @@ class QAPISchema(object): qtype_values, 'QTYPE')) def _make_enum_members(self, values): - return [QAPISchemaMember(v['name']) for v in values] + return [QAPISchemaMember(v['name'], v.get('if')) for v in values] def _make_implicit_enum_type(self, name, info, ifcond, values): # See also QAPISchemaObjectTypeMember._pretty_owner() diff --git a/tests/Makefile.include b/tests/Makefile.include index 2e894c1037..3c9eea27fd 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -384,6 +384,7 @@ qapi-schema += enum-bad-name.json qapi-schema += enum-bad-prefix.json qapi-schema += enum-clash-member.json qapi-schema += enum-dict-member-unknown.json +qapi-schema += enum-if-invalid.json qapi-schema += enum-int-member.json qapi-schema += enum-member-case.json qapi-schema += enum-missing-data.json diff --git a/tests/qapi-schema/enum-dict-member-unknown.err b/tests/qapi-schema/enum-dict-member-unknown.err index 76bd0471db..2aae618be0 100644 --- a/tests/qapi-schema/enum-dict-member-unknown.err +++ b/tests/qapi-schema/enum-dict-member-unknown.err @@ -1,2 +1,2 @@ tests/qapi-schema/enum-dict-member-unknown.json:2: Unknown key 'bad-key' in dictionary member of enum 'MyEnum' -Valid keys are 'name'. +Valid keys are 'if', 'name'. diff --git a/tests/qapi-schema/enum-if-invalid.err b/tests/qapi-schema/enum-if-invalid.err new file mode 100644 index 0000000000..54c3cf887b --- /dev/null +++ b/tests/qapi-schema/enum-if-invalid.err @@ -0,0 +1 @@ +tests/qapi-schema/enum-if-invalid.json:2: 'if' condition must be a string or a list of strings diff --git a/tests/qapi-schema/enum-if-invalid.exit b/tests/qapi-schema/enum-if-invalid.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/enum-if-invalid.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/enum-if-invalid.json b/tests/qapi-schema/enum-if-invalid.json new file mode 100644 index 0000000000..60bd0ef1d7 --- /dev/null +++ b/tests/qapi-schema/enum-if-invalid.json @@ -0,0 +1,3 @@ +# check invalid 'if' type +{ 'enum': 'TestIfEnum', 'data': + [ 'foo', { 'name' : 'bar', 'if': { 'val': 'foo' } } ] } diff --git a/tests/qapi-schema/enum-if-invalid.out b/tests/qapi-schema/enum-if-invalid.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 15388ae9a4..8bfaf5aedd 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -204,7 +204,8 @@ { 'struct': 'TestIfStruct', 'data': { 'foo': 'int' }, 'if': 'defined(TEST_IF_STRUCT)' } -{ 'enum': 'TestIfEnum', 'data': [ 'foo', 'bar' ], +{ 'enum': 'TestIfEnum', 'data': + [ 'foo', { 'name' : 'bar', 'if': 'defined(TEST_IF_ENUM_BAR)' } ], 'if': 'defined(TEST_IF_ENUM)' } { 'union': 'TestIfUnion', 'data': { 'foo': 'TestStruct' }, @@ -219,7 +220,7 @@ { 'command': 'TestIfAlternateCmd', 'data': { 'alt_cmd_arg': 'TestIfAlternate' }, 'if': 'defined(TEST_IF_ALT)' } -{ 'command': 'TestIfCmd', 'data': { 'foo': 'TestIfStruct' }, +{ 'command': 'TestIfCmd', 'data': { 'foo': 'TestIfStruct', 'bar': 'TestIfEnum' }, 'returns': 'UserDefThree', 'if': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 06e80e5b04..d90d987651 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -272,6 +272,7 @@ object TestIfStruct enum TestIfEnum member foo member bar + if ['defined(TEST_IF_ENUM_BAR)'] if ['defined(TEST_IF_ENUM)'] object q_obj_TestStruct-wrapper member data: TestStruct optional=False @@ -302,6 +303,7 @@ command TestIfAlternateCmd q_obj_TestIfAlternateCmd-arg -> None if ['defined(TEST_IF_ALT)'] object q_obj_TestIfCmd-arg member foo: TestIfStruct optional=False + member bar: TestIfEnum optional=False if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] command TestIfCmd q_obj_TestIfCmd-arg -> UserDefThree gen=True success_response=True boxed=False oob=False preconfig=False diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 641a18f06d..aadf252d9d 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -29,6 +29,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): print(' prefix %s' % prefix) for m in members: print(' member %s' % m.name) + self._print_if(m.ifcond, indent=8) self._print_if(ifcond) def visit_object_type(self, name, info, ifcond, base, members, variants): -- cgit v1.2.3-55-g7522 From 87adbbffd4b03c68d039ce0be5dcfde38a6a7b1b Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:14 +0400 Subject: qapi: add a dictionary form for TYPE Wherever a struct/union/alternate/command/event member with NAME: TYPE form is accepted, desugar it to a NAME: { 'type': TYPE } form. This will allow to add new member details, such as 'if' in the following patch to introduce conditionals, or 'default' for default values etc. Signed-off-by: Marc-André Lureau Message-Id: <20181213123724.4866-13-marcandre.lureau@redhat.com> Reviewed-by: Markus Armbruster Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 70 ++++++++++++++-------- tests/Makefile.include | 6 ++ tests/qapi-schema/alternate-invalid-dict.err | 1 + tests/qapi-schema/alternate-invalid-dict.exit | 1 + tests/qapi-schema/alternate-invalid-dict.json | 4 ++ tests/qapi-schema/alternate-invalid-dict.out | 0 tests/qapi-schema/event-member-invalid-dict.err | 1 + tests/qapi-schema/event-member-invalid-dict.exit | 1 + tests/qapi-schema/event-member-invalid-dict.json | 2 + tests/qapi-schema/event-member-invalid-dict.out | 0 tests/qapi-schema/event-nest-struct.json | 2 +- .../qapi-schema/flat-union-inline-invalid-dict.err | 1 + .../flat-union-inline-invalid-dict.exit | 1 + .../flat-union-inline-invalid-dict.json | 11 ++++ .../qapi-schema/flat-union-inline-invalid-dict.out | 0 tests/qapi-schema/flat-union-inline.json | 2 +- .../nested-struct-data-invalid-dict.err | 1 + .../nested-struct-data-invalid-dict.exit | 1 + .../nested-struct-data-invalid-dict.json | 3 + .../nested-struct-data-invalid-dict.out | 0 tests/qapi-schema/nested-struct-data.json | 2 +- tests/qapi-schema/qapi-schema-test.json | 10 ++-- tests/qapi-schema/struct-member-invalid-dict.err | 1 + tests/qapi-schema/struct-member-invalid-dict.exit | 1 + tests/qapi-schema/struct-member-invalid-dict.json | 3 + tests/qapi-schema/struct-member-invalid-dict.out | 0 tests/qapi-schema/union-branch-invalid-dict.err | 1 + tests/qapi-schema/union-branch-invalid-dict.exit | 1 + tests/qapi-schema/union-branch-invalid-dict.json | 4 ++ tests/qapi-schema/union-branch-invalid-dict.out | 0 30 files changed, 99 insertions(+), 32 deletions(-) create mode 100644 tests/qapi-schema/alternate-invalid-dict.err create mode 100644 tests/qapi-schema/alternate-invalid-dict.exit create mode 100644 tests/qapi-schema/alternate-invalid-dict.json create mode 100644 tests/qapi-schema/alternate-invalid-dict.out create mode 100644 tests/qapi-schema/event-member-invalid-dict.err create mode 100644 tests/qapi-schema/event-member-invalid-dict.exit create mode 100644 tests/qapi-schema/event-member-invalid-dict.json create mode 100644 tests/qapi-schema/event-member-invalid-dict.out create mode 100644 tests/qapi-schema/flat-union-inline-invalid-dict.err create mode 100644 tests/qapi-schema/flat-union-inline-invalid-dict.exit create mode 100644 tests/qapi-schema/flat-union-inline-invalid-dict.json create mode 100644 tests/qapi-schema/flat-union-inline-invalid-dict.out create mode 100644 tests/qapi-schema/nested-struct-data-invalid-dict.err create mode 100644 tests/qapi-schema/nested-struct-data-invalid-dict.exit create mode 100644 tests/qapi-schema/nested-struct-data-invalid-dict.json create mode 100644 tests/qapi-schema/nested-struct-data-invalid-dict.out create mode 100644 tests/qapi-schema/struct-member-invalid-dict.err create mode 100644 tests/qapi-schema/struct-member-invalid-dict.exit create mode 100644 tests/qapi-schema/struct-member-invalid-dict.json create mode 100644 tests/qapi-schema/struct-member-invalid-dict.out create mode 100644 tests/qapi-schema/union-branch-invalid-dict.err create mode 100644 tests/qapi-schema/union-branch-invalid-dict.exit create mode 100644 tests/qapi-schema/union-branch-invalid-dict.json create mode 100644 tests/qapi-schema/union-branch-invalid-dict.out (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index a609ffcfee..cdfe2bf2a5 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -588,11 +588,11 @@ def discriminator_find_enum_define(expr): if not base_members: return None - discriminator_type = base_members.get(discriminator) - if not discriminator_type: + discriminator_value = base_members.get(discriminator) + if not discriminator_value: return None - return enum_types.get(discriminator_type) + return enum_types.get(discriminator_value['type']) # Names must be letters, numbers, -, and _. They must start with letter, @@ -704,8 +704,10 @@ def check_type(info, source, value, allow_array=False, % (source, key)) # Todo: allow dictionaries to represent default values of # an optional argument. - check_type(info, "Member '%s' of %s" % (key, source), arg, - allow_array=True, + check_known_keys(info, "member '%s' of %s" % (key, source), + arg, ['type'], []) + check_type(info, "Member '%s' of %s" % (key, source), + arg['type'], allow_array=True, allow_metas=['built-in', 'union', 'alternate', 'struct', 'enum']) @@ -776,13 +778,13 @@ def check_union(expr, info): # member of the base struct. check_name(info, "Discriminator of flat union '%s'" % name, discriminator) - discriminator_type = base_members.get(discriminator) - if not discriminator_type: + discriminator_value = base_members.get(discriminator) + if not discriminator_value: raise QAPISemError(info, "Discriminator '%s' is not a member of base " "struct '%s'" % (discriminator, base)) - enum_define = enum_types.get(discriminator_type) + enum_define = enum_types.get(discriminator_value['type']) allow_metas = ['struct'] # Do not allow string discriminator if not enum_define: @@ -796,9 +798,12 @@ def check_union(expr, info): for (key, value) in members.items(): check_name(info, "Member of union '%s'" % name, key) + check_known_keys(info, "member '%s' of union '%s'" % (key, name), + value, ['type'], []) # Each value must name a known type check_type(info, "Member '%s' of union '%s'" % (key, name), - value, allow_array=not base, allow_metas=allow_metas) + value['type'], + allow_array=not base, allow_metas=allow_metas) # If the discriminator names an enum type, then all members # of 'data' must also be members of the enum type. @@ -822,18 +827,21 @@ def check_alternate(expr, info): "in 'data'" % name) for (key, value) in members.items(): check_name(info, "Member of alternate '%s'" % name, key) + check_known_keys(info, + "member '%s' of alternate '%s'" % (key, name), + value, ['type'], []) + typ = value['type'] # Ensure alternates have no type conflicts. - check_type(info, "Member '%s' of alternate '%s'" % (key, name), - value, + check_type(info, "Member '%s' of alternate '%s'" % (key, name), typ, allow_metas=['built-in', 'union', 'struct', 'enum']) - qtype = find_alternate_member_qtype(value) + qtype = find_alternate_member_qtype(typ) if not qtype: raise QAPISemError(info, "Alternate '%s' member '%s' cannot use " - "type '%s'" % (name, key, value)) + "type '%s'" % (name, key, typ)) conflicting = set([qtype]) if qtype == 'QTYPE_QSTRING': - enum_expr = enum_types.get(value) + enum_expr = enum_types.get(typ) if enum_expr: for v in enum_get_names(enum_expr): if v in ['on', 'off']: @@ -851,12 +859,6 @@ def check_alternate(expr, info): types_seen[qt] = key -def normalize_enum(expr): - if isinstance(expr['data'], list): - expr['data'] = [m if isinstance(m, dict) else {'name': m} - for m in expr['data']] - - def check_enum(expr, info): name = expr['enum'] members = expr['data'] @@ -928,6 +930,20 @@ def check_keys(expr_elem, meta, required, optional=[]): check_if(expr, info) +def normalize_enum(expr): + if isinstance(expr['data'], list): + expr['data'] = [m if isinstance(m, dict) else {'name': m} + for m in expr['data']] + + +def normalize_members(members): + if isinstance(members, OrderedDict): + for key, arg in members.items(): + if isinstance(arg, dict): + continue + members[key] = {'type': arg} + + def check_exprs(exprs): global all_names @@ -957,22 +973,28 @@ def check_exprs(exprs): meta = 'union' check_keys(expr_elem, 'union', ['data'], ['base', 'discriminator', 'if']) + normalize_members(expr.get('base')) + normalize_members(expr['data']) union_types[expr[meta]] = expr elif 'alternate' in expr: meta = 'alternate' check_keys(expr_elem, 'alternate', ['data'], ['if']) + normalize_members(expr['data']) elif 'struct' in expr: meta = 'struct' check_keys(expr_elem, 'struct', ['data'], ['base', 'if']) + normalize_members(expr['data']) struct_types[expr[meta]] = expr elif 'command' in expr: meta = 'command' check_keys(expr_elem, 'command', [], ['data', 'returns', 'gen', 'success-response', 'boxed', 'allow-oob', 'allow-preconfig', 'if']) + normalize_members(expr.get('data')) elif 'event' in expr: meta = 'event' check_keys(expr_elem, 'event', [], ['data', 'boxed', 'if']) + normalize_members(expr.get('data')) else: raise QAPISemError(expr_elem['info'], "Expression is missing metatype") @@ -1716,7 +1738,7 @@ class QAPISchema(object): return QAPISchemaObjectTypeMember(name, typ, optional) def _make_members(self, data, info): - return [self._make_member(key, value, info) + return [self._make_member(key, value['type'], info) for (key, value) in data.items()] def _def_struct_type(self, expr, info, doc): @@ -1752,11 +1774,11 @@ class QAPISchema(object): name, info, doc, ifcond, 'base', self._make_members(base, info)) if tag_name: - variants = [self._make_variant(key, value) + variants = [self._make_variant(key, value['type']) for (key, value) in data.items()] members = [] else: - variants = [self._make_simple_variant(key, value, info) + variants = [self._make_simple_variant(key, value['type'], info) for (key, value) in data.items()] enum = [{'name': v.name} for v in variants] typ = self._make_implicit_enum_type(name, info, ifcond, enum) @@ -1772,7 +1794,7 @@ class QAPISchema(object): name = expr['alternate'] data = expr['data'] ifcond = expr.get('if') - variants = [self._make_variant(key, value) + variants = [self._make_variant(key, value['type']) for (key, value) in data.items()] tag_member = QAPISchemaObjectTypeMember('type', 'QType', False) self._def_entity( diff --git a/tests/Makefile.include b/tests/Makefile.include index 3c9eea27fd..ea5d1e8787 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -318,6 +318,7 @@ qapi-schema += alternate-conflict-string.json qapi-schema += alternate-conflict-bool-string.json qapi-schema += alternate-conflict-num-string.json qapi-schema += alternate-empty.json +qapi-schema += alternate-invalid-dict.json qapi-schema += alternate-nested.json qapi-schema += alternate-unknown.json qapi-schema += args-alternate.json @@ -394,6 +395,7 @@ 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-member-invalid-dict.json qapi-schema += event-nest-struct.json qapi-schema += flat-union-array-branch.json qapi-schema += flat-union-bad-base.json @@ -403,6 +405,7 @@ qapi-schema += flat-union-base-union.json qapi-schema += flat-union-clash-member.json qapi-schema += flat-union-empty.json qapi-schema += flat-union-inline.json +qapi-schema += flat-union-inline-invalid-dict.json qapi-schema += flat-union-int-branch.json qapi-schema += flat-union-invalid-branch-key.json qapi-schema += flat-union-invalid-discriminator.json @@ -430,6 +433,7 @@ qapi-schema += missing-comma-list.json qapi-schema += missing-comma-object.json qapi-schema += missing-type.json qapi-schema += nested-struct-data.json +qapi-schema += nested-struct-data-invalid-dict.json qapi-schema += non-objects.json qapi-schema += oob-test.json qapi-schema += allow-preconfig-test.json @@ -460,6 +464,7 @@ qapi-schema += returns-whitelist.json qapi-schema += struct-base-clash-deep.json qapi-schema += struct-base-clash.json qapi-schema += struct-data-invalid.json +qapi-schema += struct-member-invalid-dict.json qapi-schema += struct-member-invalid.json qapi-schema += trailing-comma-list.json qapi-schema += trailing-comma-object.json @@ -471,6 +476,7 @@ qapi-schema += unicode-str.json qapi-schema += union-base-empty.json qapi-schema += union-base-no-discriminator.json qapi-schema += union-branch-case.json +qapi-schema += union-branch-invalid-dict.json qapi-schema += union-clash-branches.json qapi-schema += union-empty.json qapi-schema += union-invalid-base.json diff --git a/tests/qapi-schema/alternate-invalid-dict.err b/tests/qapi-schema/alternate-invalid-dict.err new file mode 100644 index 0000000000..631d46628e --- /dev/null +++ b/tests/qapi-schema/alternate-invalid-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/alternate-invalid-dict.json:2: Key 'type' is missing from member 'two' of alternate 'Alt' diff --git a/tests/qapi-schema/alternate-invalid-dict.exit b/tests/qapi-schema/alternate-invalid-dict.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/alternate-invalid-dict.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/alternate-invalid-dict.json b/tests/qapi-schema/alternate-invalid-dict.json new file mode 100644 index 0000000000..8e0b2ac287 --- /dev/null +++ b/tests/qapi-schema/alternate-invalid-dict.json @@ -0,0 +1,4 @@ +# exploded member form must have a 'type' +{ 'alternate': 'Alt', + 'data': { 'one': 'str', + 'two': { 'if': 'foo' } } } diff --git a/tests/qapi-schema/alternate-invalid-dict.out b/tests/qapi-schema/alternate-invalid-dict.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/event-member-invalid-dict.err b/tests/qapi-schema/event-member-invalid-dict.err new file mode 100644 index 0000000000..1a57fa29b0 --- /dev/null +++ b/tests/qapi-schema/event-member-invalid-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/event-member-invalid-dict.json:1: Key 'type' is missing from member 'a' of 'data' for event 'EVENT_A' diff --git a/tests/qapi-schema/event-member-invalid-dict.exit b/tests/qapi-schema/event-member-invalid-dict.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/event-member-invalid-dict.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/event-member-invalid-dict.json b/tests/qapi-schema/event-member-invalid-dict.json new file mode 100644 index 0000000000..ee6f3ecb6f --- /dev/null +++ b/tests/qapi-schema/event-member-invalid-dict.json @@ -0,0 +1,2 @@ +{ 'event': 'EVENT_A', + 'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } } diff --git a/tests/qapi-schema/event-member-invalid-dict.out b/tests/qapi-schema/event-member-invalid-dict.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/event-nest-struct.json b/tests/qapi-schema/event-nest-struct.json index ee6f3ecb6f..355ddaeff1 100644 --- a/tests/qapi-schema/event-nest-struct.json +++ b/tests/qapi-schema/event-nest-struct.json @@ -1,2 +1,2 @@ { 'event': 'EVENT_A', - 'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } } + 'data': { 'a' : { 'type' : { 'integer': 'int' } }, 'b' : 'str' } } diff --git a/tests/qapi-schema/flat-union-inline-invalid-dict.err b/tests/qapi-schema/flat-union-inline-invalid-dict.err new file mode 100644 index 0000000000..9c4c45b7f0 --- /dev/null +++ b/tests/qapi-schema/flat-union-inline-invalid-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-inline-invalid-dict.json:7: Key 'type' is missing from member 'value1' of union 'TestUnion' diff --git a/tests/qapi-schema/flat-union-inline-invalid-dict.exit b/tests/qapi-schema/flat-union-inline-invalid-dict.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/flat-union-inline-invalid-dict.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/flat-union-inline-invalid-dict.json b/tests/qapi-schema/flat-union-inline-invalid-dict.json new file mode 100644 index 0000000000..62c7cda617 --- /dev/null +++ b/tests/qapi-schema/flat-union-inline-invalid-dict.json @@ -0,0 +1,11 @@ +# we require branches to be a struct name +# TODO: should we allow anonymous inline branch types? +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } +{ 'struct': 'Base', + 'data': { 'enum1': 'TestEnum', 'kind': 'str' } } +{ 'union': 'TestUnion', + 'base': 'Base', + 'discriminator': 'enum1', + 'data': { 'value1': { 'string': 'str' }, + 'value2': { 'integer': 'int' } } } diff --git a/tests/qapi-schema/flat-union-inline-invalid-dict.out b/tests/qapi-schema/flat-union-inline-invalid-dict.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/flat-union-inline.json b/tests/qapi-schema/flat-union-inline.json index 62c7cda617..a9b3ce3f0d 100644 --- a/tests/qapi-schema/flat-union-inline.json +++ b/tests/qapi-schema/flat-union-inline.json @@ -7,5 +7,5 @@ { 'union': 'TestUnion', 'base': 'Base', 'discriminator': 'enum1', - 'data': { 'value1': { 'string': 'str' }, + 'data': { 'value1': { 'type': {} }, 'value2': { 'integer': 'int' } } } diff --git a/tests/qapi-schema/nested-struct-data-invalid-dict.err b/tests/qapi-schema/nested-struct-data-invalid-dict.err new file mode 100644 index 0000000000..5bd364e8d9 --- /dev/null +++ b/tests/qapi-schema/nested-struct-data-invalid-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/nested-struct-data-invalid-dict.json:2: Key 'type' is missing from member 'a' of 'data' for command 'foo' diff --git a/tests/qapi-schema/nested-struct-data-invalid-dict.exit b/tests/qapi-schema/nested-struct-data-invalid-dict.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/nested-struct-data-invalid-dict.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/nested-struct-data-invalid-dict.json b/tests/qapi-schema/nested-struct-data-invalid-dict.json new file mode 100644 index 0000000000..efbe773ded --- /dev/null +++ b/tests/qapi-schema/nested-struct-data-invalid-dict.json @@ -0,0 +1,3 @@ +# inline subtypes collide with our desired future use of defaults +{ 'command': 'foo', + 'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } } diff --git a/tests/qapi-schema/nested-struct-data-invalid-dict.out b/tests/qapi-schema/nested-struct-data-invalid-dict.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/nested-struct-data.json b/tests/qapi-schema/nested-struct-data.json index efbe773ded..5b8a40cca3 100644 --- a/tests/qapi-schema/nested-struct-data.json +++ b/tests/qapi-schema/nested-struct-data.json @@ -1,3 +1,3 @@ # inline subtypes collide with our desired future use of defaults { 'command': 'foo', - 'data': { 'a' : { 'string' : 'str', 'integer': 'int' }, 'b' : 'str' } } + 'data': { 'a' : { 'type': {} }, 'b' : 'str' } } diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 8bfaf5aedd..40b162664d 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -11,7 +11,7 @@ 'guest-sync' ] } } { 'struct': 'TestStruct', - 'data': { 'integer': 'int', 'boolean': 'bool', 'string': 'str' } } + 'data': { 'integer': {'type': 'int'}, 'boolean': 'bool', 'string': 'str' } } # for testing enums { 'struct': 'NestedEnumsOne', @@ -77,7 +77,7 @@ { 'union': 'UserDefFlatUnion', 'base': 'UserDefUnionBase', # intentional forward reference 'discriminator': 'enum1', - 'data': { 'value1' : 'UserDefA', + 'data': { 'value1' : {'type': 'UserDefA'}, 'value2' : 'UserDefB', 'value3' : 'UserDefB' # 'value4' defaults to empty @@ -98,7 +98,7 @@ { 'struct': 'WrapAlternate', 'data': { 'alt': 'UserDefAlternate' } } { 'alternate': 'UserDefAlternate', - 'data': { 'udfu': 'UserDefFlatUnion', 'e': 'EnumOne', 'i': 'int', + 'data': { 'udfu': {'type': 'UserDefFlatUnion'}, 'e': 'EnumOne', 'i': 'int', 'n': 'null' } } { 'struct': 'UserDefC', @@ -134,7 +134,7 @@ { 'command': 'user_def_cmd', 'data': {} } { 'command': 'user_def_cmd1', 'data': {'ud1a': 'UserDefOne'} } { 'command': 'user_def_cmd2', - 'data': {'ud1a': 'UserDefOne', '*ud1b': 'UserDefOne'}, + 'data': {'ud1a': {'type': 'UserDefOne'}, '*ud1b': 'UserDefOne'}, 'returns': 'UserDefTwo' } { 'command': 'cmd-success-response', 'data': {}, 'success-response': false } @@ -166,7 +166,7 @@ # testing event { 'struct': 'EventStructOne', - 'data': { 'struct1': 'UserDefOne', 'string': 'str', '*enum2': 'EnumOne' } } + 'data': { 'struct1': {'type': 'UserDefOne'}, 'string': 'str', '*enum2': 'EnumOne' } } { 'event': 'EVENT_A' } { 'event': 'EVENT_B', diff --git a/tests/qapi-schema/struct-member-invalid-dict.err b/tests/qapi-schema/struct-member-invalid-dict.err new file mode 100644 index 0000000000..6a765bc668 --- /dev/null +++ b/tests/qapi-schema/struct-member-invalid-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/struct-member-invalid-dict.json:2: Key 'type' is missing from member '*a' of 'data' for struct 'foo' diff --git a/tests/qapi-schema/struct-member-invalid-dict.exit b/tests/qapi-schema/struct-member-invalid-dict.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/struct-member-invalid-dict.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/struct-member-invalid-dict.json b/tests/qapi-schema/struct-member-invalid-dict.json new file mode 100644 index 0000000000..9fe0d455a9 --- /dev/null +++ b/tests/qapi-schema/struct-member-invalid-dict.json @@ -0,0 +1,3 @@ +# Long form of member must have a value member 'type' +{ 'struct': 'foo', + 'data': { '*a': { 'case': 'foo' } } } diff --git a/tests/qapi-schema/struct-member-invalid-dict.out b/tests/qapi-schema/struct-member-invalid-dict.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/union-branch-invalid-dict.err b/tests/qapi-schema/union-branch-invalid-dict.err new file mode 100644 index 0000000000..89f9b36791 --- /dev/null +++ b/tests/qapi-schema/union-branch-invalid-dict.err @@ -0,0 +1 @@ +tests/qapi-schema/union-branch-invalid-dict.json:2: Key 'type' is missing from member 'integer' of union 'UnionInvalidBranch' diff --git a/tests/qapi-schema/union-branch-invalid-dict.exit b/tests/qapi-schema/union-branch-invalid-dict.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/union-branch-invalid-dict.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/union-branch-invalid-dict.json b/tests/qapi-schema/union-branch-invalid-dict.json new file mode 100644 index 0000000000..9778598dbd --- /dev/null +++ b/tests/qapi-schema/union-branch-invalid-dict.json @@ -0,0 +1,4 @@ +# Long form of member must have a value member 'type' +{ 'union': 'UnionInvalidBranch', + 'data': { 'integer': { 'if': 'foo'}, + 's8': 'int8' } } diff --git a/tests/qapi-schema/union-branch-invalid-dict.out b/tests/qapi-schema/union-branch-invalid-dict.out new file mode 100644 index 0000000000..e69de29bb2 -- cgit v1.2.3-55-g7522 From ccadd6bcba3bef6c1954f533b10224fb3db7148e Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:15 +0400 Subject: qapi: Add 'if' to implicit struct members The generated code is for now *unconditional*. Later patches generate the conditionals. Note that union discriminators may not have 'if' conditionals. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-14-marcandre.lureau@redhat.com> Message-Id: <20181213123724.4866-15-marcandre.lureau@redhat.com> [Patches squashed, commit message tweaked] Signed-off-by: Markus Armbruster --- docs/devel/qapi-code-gen.txt | 10 ++++++++++ scripts/qapi/common.py | 18 +++++++++++------- tests/Makefile.include | 1 + .../flat-union-invalid-if-discriminator.err | 1 + .../flat-union-invalid-if-discriminator.exit | 1 + .../flat-union-invalid-if-discriminator.json | 17 +++++++++++++++++ .../flat-union-invalid-if-discriminator.out | 0 tests/qapi-schema/qapi-schema-test.json | 12 +++++++++--- tests/qapi-schema/qapi-schema-test.out | 5 +++++ tests/qapi-schema/test-qapi.py | 1 + 10 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 tests/qapi-schema/flat-union-invalid-if-discriminator.err create mode 100644 tests/qapi-schema/flat-union-invalid-if-discriminator.exit create mode 100644 tests/qapi-schema/flat-union-invalid-if-discriminator.json create mode 100644 tests/qapi-schema/flat-union-invalid-if-discriminator.out (limited to 'scripts/qapi/common.py') diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt index 7ba9066eac..3895808b4a 100644 --- a/docs/devel/qapi-code-gen.txt +++ b/docs/devel/qapi-code-gen.txt @@ -752,6 +752,16 @@ gets its generated code guarded like this: #endif /* defined(HAVE_BAR) */ #endif /* defined(CONFIG_FOO) */ +Where a member can be defined with a single string value for its type, +it is also possible to supply a dictionary instead with both 'type' +and 'if' keys. (TODO: union and alternate) + +Example: a conditional 'bar' member + +{ 'struct': 'IfStruct', 'data': + { 'foo': 'int', + 'bar': { 'type': 'int', 'if': 'defined(IFCOND)'} } } + An enum value can be replaced by a dictionary with a 'name' and a 'if' key. diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index cdfe2bf2a5..98e9d6f109 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -705,7 +705,7 @@ def check_type(info, source, value, allow_array=False, # Todo: allow dictionaries to represent default values of # an optional argument. check_known_keys(info, "member '%s' of %s" % (key, source), - arg, ['type'], []) + arg, ['type'], ['if']) check_type(info, "Member '%s' of %s" % (key, source), arg['type'], allow_array=True, allow_metas=['built-in', 'union', 'alternate', 'struct', @@ -784,6 +784,10 @@ def check_union(expr, info): "Discriminator '%s' is not a member of base " "struct '%s'" % (discriminator, base)) + if discriminator_value.get('if'): + raise QAPISemError(info, 'The discriminator %s.%s for union %s ' + 'must not be conditional' % + (base, discriminator, name)) enum_define = enum_types.get(discriminator_value['type']) allow_metas = ['struct'] # Do not allow string discriminator @@ -1412,8 +1416,8 @@ class QAPISchemaMember(object): class QAPISchemaObjectTypeMember(QAPISchemaMember): - def __init__(self, name, typ, optional): - QAPISchemaMember.__init__(self, name) + def __init__(self, name, typ, optional, ifcond=None): + QAPISchemaMember.__init__(self, name, ifcond) assert isinstance(typ, str) assert isinstance(optional, bool) self._type_name = typ @@ -1727,7 +1731,7 @@ class QAPISchema(object): name, info, doc, ifcond, self._make_enum_members(data), prefix)) - def _make_member(self, name, typ, info): + def _make_member(self, name, typ, ifcond, info): optional = False if name.startswith('*'): name = name[1:] @@ -1735,10 +1739,10 @@ class QAPISchema(object): if isinstance(typ, list): assert len(typ) == 1 typ = self._make_array_type(typ[0], info) - return QAPISchemaObjectTypeMember(name, typ, optional) + return QAPISchemaObjectTypeMember(name, typ, optional, ifcond) def _make_members(self, data, info): - return [self._make_member(key, value['type'], info) + return [self._make_member(key, value['type'], value.get('if'), info) for (key, value) in data.items()] def _def_struct_type(self, expr, info, doc): @@ -1759,7 +1763,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, info)]) + 'wrapper', [self._make_member('data', typ, None, info)]) return QAPISchemaObjectTypeVariant(case, typ) def _def_union_type(self, expr, info, doc): diff --git a/tests/Makefile.include b/tests/Makefile.include index ea5d1e8787..3f5a1d0c30 100644 --- a/tests/Makefile.include +++ b/tests/Makefile.include @@ -409,6 +409,7 @@ qapi-schema += flat-union-inline-invalid-dict.json qapi-schema += flat-union-int-branch.json qapi-schema += flat-union-invalid-branch-key.json qapi-schema += flat-union-invalid-discriminator.json +qapi-schema += flat-union-invalid-if-discriminator.json qapi-schema += flat-union-no-base.json qapi-schema += flat-union-optional-discriminator.json qapi-schema += flat-union-string-discriminator.json diff --git a/tests/qapi-schema/flat-union-invalid-if-discriminator.err b/tests/qapi-schema/flat-union-invalid-if-discriminator.err new file mode 100644 index 0000000000..0c94c9860d --- /dev/null +++ b/tests/qapi-schema/flat-union-invalid-if-discriminator.err @@ -0,0 +1 @@ +tests/qapi-schema/flat-union-invalid-if-discriminator.json:13: The discriminator TestBase.enum1 for union TestUnion must not be conditional diff --git a/tests/qapi-schema/flat-union-invalid-if-discriminator.exit b/tests/qapi-schema/flat-union-invalid-if-discriminator.exit new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/tests/qapi-schema/flat-union-invalid-if-discriminator.exit @@ -0,0 +1 @@ +1 diff --git a/tests/qapi-schema/flat-union-invalid-if-discriminator.json b/tests/qapi-schema/flat-union-invalid-if-discriminator.json new file mode 100644 index 0000000000..618ec36396 --- /dev/null +++ b/tests/qapi-schema/flat-union-invalid-if-discriminator.json @@ -0,0 +1,17 @@ +{ 'enum': 'TestEnum', + 'data': [ 'value1', 'value2' ] } + +{ 'struct': 'TestBase', + 'data': { 'enum1': { 'type': 'TestEnum', 'if': 'FOO' } } } + +{ 'struct': 'TestTypeA', + 'data': { 'string': 'str' } } + +{ 'struct': 'TestTypeB', + 'data': { 'integer': 'int' } } + +{ 'union': 'TestUnion', + 'base': 'TestBase', + 'discriminator': 'enum1', + 'data': { 'value1': 'TestTypeA', + 'value2': 'TestTypeB' } } diff --git a/tests/qapi-schema/flat-union-invalid-if-discriminator.out b/tests/qapi-schema/flat-union-invalid-if-discriminator.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index 40b162664d..c46f3b5732 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -201,7 +201,9 @@ # test 'if' condition handling -{ 'struct': 'TestIfStruct', 'data': { 'foo': 'int' }, +{ 'struct': 'TestIfStruct', 'data': + { 'foo': 'int', + 'bar': { 'type': 'int', 'if': 'defined(TEST_IF_STRUCT_BAR)'} }, 'if': 'defined(TEST_IF_STRUCT)' } { 'enum': 'TestIfEnum', 'data': @@ -220,11 +222,15 @@ { 'command': 'TestIfAlternateCmd', 'data': { 'alt_cmd_arg': 'TestIfAlternate' }, 'if': 'defined(TEST_IF_ALT)' } -{ 'command': 'TestIfCmd', 'data': { 'foo': 'TestIfStruct', 'bar': 'TestIfEnum' }, +{ 'command': 'TestIfCmd', 'data': + { 'foo': 'TestIfStruct', + 'bar': { 'type': 'TestIfEnum', 'if': 'defined(TEST_IF_CMD_BAR)' } }, 'returns': 'UserDefThree', 'if': ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] } { 'command': 'TestCmdReturnDefThree', 'returns': 'UserDefThree' } -{ 'event': 'TestIfEvent', 'data': { 'foo': 'TestIfStruct' }, +{ 'event': 'TestIfEvent', 'data': + { 'foo': 'TestIfStruct', + 'bar': { 'type': 'TestIfEnum', 'if': 'defined(TEST_IF_EVT_BAR)' } }, 'if': 'defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)' } diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index d90d987651..7987b23403 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -268,6 +268,8 @@ command __org.qemu_x-command q_obj___org.qemu_x-command-arg -> __org.qemu_x-Unio gen=True success_response=True boxed=False oob=False preconfig=False object TestIfStruct member foo: int optional=False + member bar: int optional=False + if ['defined(TEST_IF_STRUCT_BAR)'] if ['defined(TEST_IF_STRUCT)'] enum TestIfEnum member foo @@ -304,6 +306,7 @@ command TestIfAlternateCmd q_obj_TestIfAlternateCmd-arg -> None object q_obj_TestIfCmd-arg member foo: TestIfStruct optional=False member bar: TestIfEnum optional=False + if ['defined(TEST_IF_CMD_BAR)'] if ['defined(TEST_IF_CMD)', 'defined(TEST_IF_STRUCT)'] command TestIfCmd q_obj_TestIfCmd-arg -> UserDefThree gen=True success_response=True boxed=False oob=False preconfig=False @@ -312,6 +315,8 @@ command TestCmdReturnDefThree None -> UserDefThree gen=True success_response=True boxed=False oob=False preconfig=False object q_obj_TestIfEvent-arg member foo: TestIfStruct optional=False + member bar: TestIfEnum optional=False + if ['defined(TEST_IF_EVT_BAR)'] if ['defined(TEST_IF_EVT) && defined(TEST_IF_STRUCT)'] event TestIfEvent q_obj_TestIfEvent-arg boxed=False diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index aadf252d9d..27081cb50c 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -39,6 +39,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): for m in members: print(' member %s: %s optional=%s' % (m.name, m.type.name, m.optional)) + self._print_if(m.ifcond, 8) self._print_variants(variants) self._print_if(ifcond) -- cgit v1.2.3-55-g7522 From a2724280fbed914e517403890b4b1568261f0cf9 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:17 +0400 Subject: qapi: add 'if' to union members Add 'if' key to union members: { 'union': 'TestIfUnion', 'data': 'mem': { 'type': 'str', 'if': 'COND'} } The generated code remains unconditional for now. Later patches generate the conditionals. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-16-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- docs/devel/qapi-code-gen.txt | 2 +- scripts/qapi/common.py | 15 ++++++++------- tests/qapi-schema/qapi-schema-test.json | 4 +++- tests/qapi-schema/qapi-schema-test.out | 4 ++++ tests/qapi-schema/test-qapi.py | 1 + 5 files changed, 17 insertions(+), 9 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt index 3895808b4a..0a0e53ede5 100644 --- a/docs/devel/qapi-code-gen.txt +++ b/docs/devel/qapi-code-gen.txt @@ -754,7 +754,7 @@ gets its generated code guarded like this: Where a member can be defined with a single string value for its type, it is also possible to supply a dictionary instead with both 'type' -and 'if' keys. (TODO: union and alternate) +and 'if' keys. (TODO: alternate) Example: a conditional 'bar' member diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 98e9d6f109..44fe8868ed 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -803,7 +803,7 @@ def check_union(expr, info): check_name(info, "Member of union '%s'" % name, key) check_known_keys(info, "member '%s' of union '%s'" % (key, name), - value, ['type'], []) + value, ['type'], ['if']) # Each value must name a known type check_type(info, "Member '%s' of union '%s'" % (key, name), value['type'], @@ -1483,8 +1483,8 @@ class QAPISchemaObjectTypeVariants(object): class QAPISchemaObjectTypeVariant(QAPISchemaObjectTypeMember): role = 'branch' - def __init__(self, name, typ): - QAPISchemaObjectTypeMember.__init__(self, name, typ, False) + def __init__(self, name, typ, ifcond=None): + QAPISchemaObjectTypeMember.__init__(self, name, typ, False, ifcond) class QAPISchemaAlternateType(QAPISchemaType): @@ -1757,14 +1757,14 @@ class QAPISchema(object): def _make_variant(self, case, typ): return QAPISchemaObjectTypeVariant(case, typ) - def _make_simple_variant(self, case, typ, info): + def _make_simple_variant(self, case, typ, ifcond, info): if isinstance(typ, list): assert len(typ) == 1 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)]) - return QAPISchemaObjectTypeVariant(case, typ) + return QAPISchemaObjectTypeVariant(case, typ, ifcond) def _def_union_type(self, expr, info, doc): name = expr['union'] @@ -1782,9 +1782,10 @@ class QAPISchema(object): for (key, value) in data.items()] members = [] else: - variants = [self._make_simple_variant(key, value['type'], info) + variants = [self._make_simple_variant(key, value['type'], + value.get('if'), info) for (key, value) in data.items()] - enum = [{'name': v.name} for v in variants] + enum = [{'name': v.name, 'if': v.ifcond} for v in variants] typ = self._make_implicit_enum_type(name, info, ifcond, enum) tag_member = QAPISchemaObjectTypeMember('type', typ, False) members = [tag_member] diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index c46f3b5732..a33eff8f86 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -210,7 +210,9 @@ [ 'foo', { 'name' : 'bar', 'if': 'defined(TEST_IF_ENUM_BAR)' } ], 'if': 'defined(TEST_IF_ENUM)' } -{ 'union': 'TestIfUnion', 'data': { 'foo': 'TestStruct' }, +{ 'union': 'TestIfUnion', 'data': + { 'foo': 'TestStruct', + 'union_bar': { 'type': 'str', 'if': 'defined(TEST_IF_UNION_BAR)'} }, 'if': 'defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)' } { 'command': 'TestIfUnionCmd', 'data': { 'union_cmd_arg': 'TestIfUnion' }, diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index 7987b23403..f4c11f1e6a 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -280,11 +280,15 @@ object q_obj_TestStruct-wrapper member data: TestStruct optional=False enum TestIfUnionKind member foo + member union_bar + if ['defined(TEST_IF_UNION_BAR)'] if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'] object TestIfUnion member type: TestIfUnionKind optional=False tag type case foo: q_obj_TestStruct-wrapper + case union_bar: q_obj_str-wrapper + if ['defined(TEST_IF_UNION_BAR)'] if ['defined(TEST_IF_UNION) && defined(TEST_IF_STRUCT)'] object q_obj_TestIfUnionCmd-arg member union_cmd_arg: TestIfUnion optional=False diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py index 27081cb50c..d592854601 100644 --- a/tests/qapi-schema/test-qapi.py +++ b/tests/qapi-schema/test-qapi.py @@ -68,6 +68,7 @@ class QAPISchemaTestVisitor(QAPISchemaVisitor): print(' tag %s' % variants.tag_member.name) for v in variants.variants: print(' case %s: %s' % (v.name, v.type.name)) + QAPISchemaTestVisitor._print_if(v.ifcond, indent=8) @staticmethod def _print_if(ifcond, indent=4): -- cgit v1.2.3-55-g7522 From 3e270dcacc08cca45e694ca48159915f81303cfa Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:18 +0400 Subject: qapi: add 'if' to alternate members Add 'if' key to alternate members: { 'alternate': 'TestIfAlternate', 'data': { 'alt': { 'type': 'TestStruct', 'if': 'COND' } } } Generated code is not changed by this patch but with "qapi: add #if conditions to generated code". Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-17-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- docs/devel/qapi-code-gen.txt | 2 +- scripts/qapi/common.py | 10 +++++----- tests/qapi-schema/qapi-schema-test.json | 4 +++- tests/qapi-schema/qapi-schema-test.out | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/docs/devel/qapi-code-gen.txt b/docs/devel/qapi-code-gen.txt index 0a0e53ede5..43bd853e69 100644 --- a/docs/devel/qapi-code-gen.txt +++ b/docs/devel/qapi-code-gen.txt @@ -754,7 +754,7 @@ gets its generated code guarded like this: Where a member can be defined with a single string value for its type, it is also possible to supply a dictionary instead with both 'type' -and 'if' keys. (TODO: alternate) +and 'if' keys. Example: a conditional 'bar' member diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 44fe8868ed..17707b6de7 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -833,7 +833,7 @@ def check_alternate(expr, info): check_name(info, "Member of alternate '%s'" % name, key) check_known_keys(info, "member '%s' of alternate '%s'" % (key, name), - value, ['type'], []) + value, ['type'], ['if']) typ = value['type'] # Ensure alternates have no type conflicts. @@ -1754,8 +1754,8 @@ class QAPISchema(object): self._make_members(data, info), None)) - def _make_variant(self, case, typ): - return QAPISchemaObjectTypeVariant(case, typ) + def _make_variant(self, case, typ, ifcond): + return QAPISchemaObjectTypeVariant(case, typ, ifcond) def _make_simple_variant(self, case, typ, ifcond, info): if isinstance(typ, list): @@ -1778,7 +1778,7 @@ class QAPISchema(object): name, info, doc, ifcond, 'base', self._make_members(base, info)) if tag_name: - variants = [self._make_variant(key, value['type']) + variants = [self._make_variant(key, value['type'], value.get('if')) for (key, value) in data.items()] members = [] else: @@ -1799,7 +1799,7 @@ class QAPISchema(object): name = expr['alternate'] data = expr['data'] ifcond = expr.get('if') - variants = [self._make_variant(key, value['type']) + variants = [self._make_variant(key, value['type'], value.get('if')) for (key, value) in data.items()] tag_member = QAPISchemaObjectTypeMember('type', 'QType', False) self._def_entity( diff --git a/tests/qapi-schema/qapi-schema-test.json b/tests/qapi-schema/qapi-schema-test.json index a33eff8f86..cb0857df52 100644 --- a/tests/qapi-schema/qapi-schema-test.json +++ b/tests/qapi-schema/qapi-schema-test.json @@ -218,7 +218,9 @@ { 'command': 'TestIfUnionCmd', 'data': { 'union_cmd_arg': 'TestIfUnion' }, 'if': 'defined(TEST_IF_UNION)' } -{ 'alternate': 'TestIfAlternate', 'data': { 'foo': 'int', 'bar': 'TestStruct' }, +{ 'alternate': 'TestIfAlternate', 'data': + { 'foo': 'int', + 'bar': { 'type': 'TestStruct', 'if': 'defined(TEST_IF_ALT_BAR)'} }, 'if': 'defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)' } { 'command': 'TestIfAlternateCmd', 'data': { 'alt_cmd_arg': 'TestIfAlternate' }, diff --git a/tests/qapi-schema/qapi-schema-test.out b/tests/qapi-schema/qapi-schema-test.out index f4c11f1e6a..9464101d26 100644 --- a/tests/qapi-schema/qapi-schema-test.out +++ b/tests/qapi-schema/qapi-schema-test.out @@ -300,6 +300,7 @@ alternate TestIfAlternate tag type case foo: int case bar: TestStruct + if ['defined(TEST_IF_ALT_BAR)'] if ['defined(TEST_IF_ALT) && defined(TEST_IF_STRUCT)'] object q_obj_TestIfAlternateCmd-arg member alt_cmd_arg: TestIfAlternate optional=False -- cgit v1.2.3-55-g7522 From 8ee06f61e13701a54a9f76ceadafc856d279cdb6 Mon Sep 17 00:00:00 2001 From: Marc-André Lureau Date: Thu, 13 Dec 2018 16:37:19 +0400 Subject: qapi: Add #if conditions to generated code members Wrap generated enum and struct members and their supporting code with #if/#endif, using the .ifcond members added in the previous patches. We do enum and struct in a single patch because union tag enum and the associated variants tie them together, and dealing with that to split the patch doesn't seem worthwhile. Signed-off-by: Marc-André Lureau Reviewed-by: Markus Armbruster Message-Id: <20181213123724.4866-18-marcandre.lureau@redhat.com> Signed-off-by: Markus Armbruster --- scripts/qapi/common.py | 4 ++++ scripts/qapi/introspect.py | 14 ++++++++++---- scripts/qapi/types.py | 4 ++++ scripts/qapi/visit.py | 6 ++++++ 4 files changed, 24 insertions(+), 4 deletions(-) (limited to 'scripts/qapi/common.py') diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py index 17707b6de7..8c2d97369e 100644 --- a/scripts/qapi/common.py +++ b/scripts/qapi/common.py @@ -2078,11 +2078,13 @@ const QEnumLookup %(c_name)s_lookup = { ''', c_name=c_name(name)) for m in members: + ret += gen_if(m.ifcond) index = c_enum_const(name, m.name, prefix) ret += mcgen(''' [%(index)s] = "%(name)s", ''', index=index, name=m.name) + ret += gen_endif(m.ifcond) ret += mcgen(''' }, @@ -2104,10 +2106,12 @@ typedef enum %(c_name)s { c_name=c_name(name)) for m in enum_members: + ret += gen_if(m.ifcond) ret += mcgen(''' %(c_enum)s, ''', c_enum=c_enum_const(name, m.name, prefix)) + ret += gen_endif(m.ifcond) ret += mcgen(''' } %(c_name)s; diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index 417625d54b..f7f2ca07e4 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -162,6 +162,8 @@ const QLitObject %(c_name)s = %(c_string)s; ret = {'name': member.name, 'type': self._use_type(member.type)} if member.optional: ret['default'] = None + if member.ifcond: + ret = (ret, {'if': member.ifcond}) return ret def _gen_variants(self, tag_name, variants): @@ -169,14 +171,17 @@ const QLitObject %(c_name)s = %(c_string)s; 'variants': [self._gen_variant(v) for v in variants]} def _gen_variant(self, variant): - return {'case': variant.name, 'type': self._use_type(variant.type)} + return ({'case': variant.name, 'type': self._use_type(variant.type)}, + {'if': variant.ifcond}) def visit_builtin_type(self, name, info, json_type): self._gen_qlit(name, 'builtin', {'json-type': json_type}, []) def visit_enum_type(self, name, info, ifcond, members, prefix): self._gen_qlit(name, 'enum', - {'values': [m.name for m in members]}, ifcond) + {'values': + [(m.name, {'if': m.ifcond}) for m in members]}, + ifcond) def visit_array_type(self, name, info, ifcond, element_type): element = self._use_type(element_type) @@ -192,8 +197,9 @@ const QLitObject %(c_name)s = %(c_string)s; def visit_alternate_type(self, name, info, ifcond, variants): self._gen_qlit(name, 'alternate', - {'members': [{'type': self._use_type(m.type)} - for m in variants.variants]}, ifcond) + {'members': [ + ({'type': self._use_type(m.type)}, {'if': m.ifcond}) + for m in variants.variants]}, ifcond) def visit_command(self, name, info, ifcond, arg_type, ret_type, gen, success_response, boxed, allow_oob, allow_preconfig): diff --git a/scripts/qapi/types.py b/scripts/qapi/types.py index e8d22c5081..62d4cf9f95 100644 --- a/scripts/qapi/types.py +++ b/scripts/qapi/types.py @@ -43,6 +43,7 @@ struct %(c_name)s { def gen_struct_members(members): ret = '' for memb in members: + ret += gen_if(memb.ifcond) if memb.optional: ret += mcgen(''' bool has_%(c_name)s; @@ -52,6 +53,7 @@ def gen_struct_members(members): %(c_type)s %(c_name)s; ''', c_type=memb.type.c_type(), c_name=c_name(memb.name)) + ret += gen_endif(memb.ifcond) return ret @@ -131,11 +133,13 @@ def gen_variants(variants): for var in variants.variants: if var.type.name == 'q_empty': continue + ret += gen_if(var.ifcond) ret += mcgen(''' %(c_type)s %(c_name)s; ''', c_type=var.type.c_unboxed_type(), c_name=c_name(var.name)) + ret += gen_endif(var.ifcond) ret += mcgen(''' } u; diff --git a/scripts/qapi/visit.py b/scripts/qapi/visit.py index 24f85a2e85..82eab72b21 100644 --- a/scripts/qapi/visit.py +++ b/scripts/qapi/visit.py @@ -54,6 +54,7 @@ void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) c_type=base.c_name()) for memb in members: + ret += gen_if(memb.ifcond) if memb.optional: ret += mcgen(''' if (visit_optional(v, "%(name)s", &obj->has_%(c_name)s)) { @@ -73,6 +74,7 @@ void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) ret += mcgen(''' } ''') + ret += gen_endif(memb.ifcond) if variants: ret += mcgen(''' @@ -84,6 +86,7 @@ void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) case_str = c_enum_const(variants.tag_member.type.name, var.name, variants.tag_member.type.prefix) + ret += gen_if(var.ifcond) if var.type.name == 'q_empty': # valid variant and nothing to do ret += mcgen(''' @@ -100,6 +103,7 @@ void visit_type_%(c_name)s_members(Visitor *v, %(c_name)s *obj, Error **errp) case=case_str, c_type=var.type.c_name(), c_name=c_name(var.name)) + ret += gen_endif(var.ifcond) ret += mcgen(''' default: abort(); @@ -190,6 +194,7 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error c_name=c_name(name)) for var in variants.variants: + ret += gen_if(var.ifcond) ret += mcgen(''' case %(case)s: ''', @@ -217,6 +222,7 @@ void visit_type_%(c_name)s(Visitor *v, const char *name, %(c_name)s **obj, Error ret += mcgen(''' break; ''') + ret += gen_endif(var.ifcond) ret += mcgen(''' case QTYPE_NONE: -- cgit v1.2.3-55-g7522