Hi On Wed, Aug 16, 2017 at 5:43 PM, Markus Armbruster <arm...@redhat.com> wrote: > Marc-André Lureau <marcandre.lur...@redhat.com> writes: > >> Add 'if' c-preprocessor condition on top-level schema elements: >> struct, enum, union, alternate, command, event. > > An example would be useful here. Your cover letter has nice ones, would > be a shame not to preserve them for posterity in the commit log. >
ok >> Variants objects types are created outside of #if blocks, since they > > What are "variants objects types"? > I think I meant implicit objects types, that may be created by variant. >> may be shared by various types. Even if unused, this shouldn't be an >> issue, since those are internal types. >> >> Note that there is no checks in qapi scripts to verify that elements > > there are > >> have compatible conditions (ex: if-struct used by a if-foo >> command). This may thus fail at C build time if they don't share the >> same subset of conditions. > > Fair enough. > >> Signed-off-by: Marc-André Lureau <marcandre.lur...@redhat.com> >> --- >> scripts/qapi.py | 153 >> +++++++++++++++++++++++++------- >> scripts/qapi-commands.py | 4 +- >> scripts/qapi-event.py | 4 +- >> scripts/qapi-introspect.py | 46 ++++++---- >> scripts/qapi-types.py | 51 +++++++---- >> scripts/qapi-visit.py | 13 ++- >> scripts/qapi2texi.py | 10 +-- >> tests/Makefile.include | 1 + >> tests/qapi-schema/bad-if.err | 1 + >> tests/qapi-schema/bad-if.exit | 1 + >> tests/qapi-schema/bad-if.json | 3 + >> tests/qapi-schema/bad-if.out | 0 >> tests/qapi-schema/qapi-schema-test.json | 20 +++++ >> tests/qapi-schema/qapi-schema-test.out | 31 +++++++ >> tests/qapi-schema/test-qapi.py | 21 +++-- >> 15 files changed, 272 insertions(+), 87 deletions(-) >> create mode 100644 tests/qapi-schema/bad-if.err >> create mode 100644 tests/qapi-schema/bad-if.exit >> create mode 100644 tests/qapi-schema/bad-if.json >> create mode 100644 tests/qapi-schema/bad-if.out >> >> diff --git a/scripts/qapi.py b/scripts/qapi.py >> index 4ecc19e944..79ba1e93da 100644 >> --- a/scripts/qapi.py >> +++ b/scripts/qapi.py >> @@ -639,6 +639,16 @@ def add_name(name, info, meta, implicit=False): >> all_names[name] = meta >> >> >> +def check_if(expr, info): >> + ifcond = expr.get('if') >> + if not ifcond or isinstance(ifcond, str): >> + return >> + if (not isinstance(ifcond, list) or >> + any([not isinstance(elt, str) for elt in ifcond])): >> + raise QAPISemError(info, "'if' condition requires a string or " >> + "a list of string") > > Wait a second! What's this list business? The commit message doesn't > say. Updated commit message, and documented in docs/devel/qapi-code-gen.txt > > Also, pep8 gripes: > > scripts/qapi.py:647:9: E129 visually indented line with same indent as > next logical line > fixed >> + >> + >> def check_type(info, source, value, allow_array=False, >> allow_dict=False, allow_optional=False, >> allow_metas=[]): >> @@ -865,6 +875,7 @@ def check_keys(expr_elem, meta, required, optional=[]): >> expr = expr_elem['expr'] >> info = expr_elem['info'] >> name = expr[meta] >> + optional.append('if') > > Caution! > > $ python > Python 2.7.13 (default, May 10 2017, 20:04:36) > >>> def surprise(arg=[]): > ... arg.append('if') > ... return arg > ... > >>> surprise() > ['if'] > >>> surprise() > ['if', 'if'] > >>> surprise() > ['if', 'if', 'if'] > > Never modify an argument that has list or dictionary default value. To > avoid the temptation, never use such defaul values. Oops! Without bad consequences here, but fixed anyway. > >> if not isinstance(name, str): >> raise QAPISemError(info, "'%s' key must have a string value" % meta) >> required = required + [meta] >> @@ -880,6 +891,8 @@ def check_keys(expr_elem, meta, required, optional=[]): >> raise QAPISemError(info, >> "'%s' of %s '%s' should only use true value" >> % (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'" >> @@ -989,6 +1002,10 @@ class QAPISchemaEntity(object): >> # such place). >> self.info = info >> self.doc = doc >> + self.ifcond = None >> + >> + def set_ifcond(self, ifcond): >> + self.ifcond = ifcond > > @ifcond is an awkward name, but I don't have better ideas. Yeah, I got used to it now ;) > >> >> def c_name(self): >> return c_name(self.name) >> @@ -1017,26 +1034,26 @@ class QAPISchemaVisitor(object): >> def visit_builtin_type(self, name, info, json_type): >> pass >> >> - def visit_enum_type(self, name, info, values, prefix): >> + def visit_enum_type(self, name, info, values, prefix, ifcond): >> pass >> >> - def visit_array_type(self, name, info, element_type): >> + def visit_array_type(self, name, info, element_type, ifcond): >> pass >> >> - def visit_object_type(self, name, info, base, members, variants): >> + def visit_object_type(self, name, info, base, members, variants, >> ifcond): >> pass >> >> - def visit_object_type_flat(self, name, info, members, variants): >> + def visit_object_type_flat(self, name, info, members, variants, ifcond): >> pass >> >> - def visit_alternate_type(self, name, info, variants): >> + def visit_alternate_type(self, name, info, variants, ifcond): >> pass >> >> def visit_command(self, name, info, arg_type, ret_type, >> - gen, success_response, boxed): >> + gen, success_response, boxed, ifcond): >> pass >> >> - def visit_event(self, name, info, arg_type, boxed): >> + def visit_event(self, name, info, arg_type, boxed, ifcond): >> pass >> >> >> @@ -1136,7 +1153,7 @@ class QAPISchemaEnumType(QAPISchemaType): >> >> def visit(self, visitor): >> visitor.visit_enum_type(self.name, self.info, >> - self.member_names(), self.prefix) >> + self.member_names(), self.prefix, >> self.ifcond) >> >> >> class QAPISchemaArrayType(QAPISchemaType): >> @@ -1149,6 +1166,7 @@ class QAPISchemaArrayType(QAPISchemaType): >> def check(self, schema): >> self.element_type = schema.lookup_type(self._element_type_name) >> assert self.element_type >> + self.ifcond = self.element_type.ifcond >> >> def is_implicit(self): >> return True >> @@ -1166,7 +1184,8 @@ class QAPISchemaArrayType(QAPISchemaType): >> return 'array of ' + elt_doc_type >> >> def visit(self, visitor): >> - visitor.visit_array_type(self.name, self.info, self.element_type) >> + visitor.visit_array_type(self.name, self.info, self.element_type, >> + self.ifcond) >> >> >> class QAPISchemaObjectType(QAPISchemaType): >> @@ -1247,9 +1266,10 @@ class QAPISchemaObjectType(QAPISchemaType): >> >> def visit(self, visitor): >> visitor.visit_object_type(self.name, self.info, >> - self.base, self.local_members, >> self.variants) >> + self.base, self.local_members, >> self.variants, >> + self.ifcond) >> visitor.visit_object_type_flat(self.name, self.info, >> - self.members, self.variants) >> + self.members, self.variants, >> self.ifcond) > > You break the line before self.ifcond almost everywhere, and when you > don't, the line gets long. This one goes over the limit: > > scripts/qapi.py:1285:80: E501 line too long (80 > 79 characters) > > Suggest to break it before self.ifcond everywhere. > ok >> >> >> class QAPISchemaMember(object): >> @@ -1392,7 +1412,8 @@ class QAPISchemaAlternateType(QAPISchemaType): >> return 'value' >> >> def visit(self, visitor): >> - visitor.visit_alternate_type(self.name, self.info, self.variants) >> + visitor.visit_alternate_type(self.name, self.info, >> + self.variants, self.ifcond) >> >> def is_empty(self): >> return False >> @@ -1434,7 +1455,8 @@ class QAPISchemaCommand(QAPISchemaEntity): >> def visit(self, visitor): >> visitor.visit_command(self.name, self.info, >> self.arg_type, self.ret_type, >> - self.gen, self.success_response, self.boxed) >> + self.gen, self.success_response, self.boxed, >> + self.ifcond) >> >> >> class QAPISchemaEvent(QAPISchemaEntity): >> @@ -1462,7 +1484,8 @@ class QAPISchemaEvent(QAPISchemaEntity): >> raise QAPISemError(self.info, "Use of 'boxed' requires 'data'") >> >> def visit(self, visitor): >> - visitor.visit_event(self.name, self.info, self.arg_type, self.boxed) >> + visitor.visit_event(self.name, self.info, self.arg_type, self.boxed, >> + self.ifcond) >> >> >> class QAPISchema(object): >> @@ -1481,11 +1504,12 @@ class QAPISchema(object): >> print >>sys.stderr, err >> exit(1) >> >> - def _def_entity(self, ent): >> + def _def_entity(self, ent, ifcond=None): >> # Only the predefined types are allowed to not have info >> assert ent.info or self._predefining >> assert ent.name not in self._entity_dict >> self._entity_dict[ent.name] = ent >> + ent.set_ifcond(ifcond) > > .set_ifcond(None) does the right thing. > > However: > >> >> def lookup_entity(self, name, typ=None): >> ent = self._entity_dict.get(name) >> @@ -1534,11 +1558,11 @@ class QAPISchema(object): >> def _make_enum_members(self, values): >> return [QAPISchemaMember(v) for v in values] >> >> - def _make_implicit_enum_type(self, name, info, values): >> + def _make_implicit_enum_type(self, name, info, values, ifcond): >> # See also QAPISchemaObjectTypeMember._pretty_owner() >> name = name + 'Kind' # Use namespace reserved by add_name() >> self._def_entity(QAPISchemaEnumType( >> - name, info, None, self._make_enum_members(values), None)) >> + name, info, None, self._make_enum_members(values), None), >> ifcond) >> return name > > Why is ifcond not a constructor argument like name, info, and so forth? > What makes it special? > I think it was easier that way (builder pattern), but it make sense as constructor too. Changed >> >> def _make_array_type(self, element_type, info): >> @@ -1547,22 +1571,26 @@ class QAPISchema(object): >> self._def_entity(QAPISchemaArrayType(name, info, element_type)) >> return name >> >> - def _make_implicit_object_type(self, name, info, doc, role, members): >> + def _make_implicit_object_type(self, name, info, doc, role, members, >> + ifcond=None): >> if not members: >> return None >> # See also QAPISchemaObjectTypeMember._pretty_owner() >> name = 'q_obj_%s-%s' % (name, role) >> - if not self.lookup_entity(name, QAPISchemaObjectType): >> + if self.lookup_entity(name, QAPISchemaObjectType): >> + assert ifcond is None >> + else: >> self._def_entity(QAPISchemaObjectType(name, info, doc, None, >> - members, None)) >> + members, None), ifcond) >> return name > > Hmm, this smells like it might be the "variants objects types" mentioned > in the commit message. > > Types made with _make_implicit_object_type(): > > * The wrapper around "simple" union members, by _make_simple_variant() > > * A flat union's base type when it's implicit, by _def_union_type() > > * A command's argument type when it's implicit, by _def_command() > > * An event's argument type when it's implicit, by _def_event() > > Only the first one can be used more than once, namely when a type occurs > in more than one simple union. The "correct" ifcond is the disjunction > of all its user's ifconds. You make it use the *first* ifcond instead. > Wrong: breaks when one of the other simple unions has a condition that > is true when the first one's is false. > > Your commit message suggests you intended to make it unconditional > instead. That would work: the worst that can happen is compiling a few > q_obj_FOO_wrapper typedefs and visit_type_q_FOO_wrapper() functions that > aren't actually used. Tolerable, in particular since I hope to get rid > of "simple" unions some day. > > Sadly, it would prevent us from making the visit functions for implicit > types static, because unused static functions trigger warnings. Let's > not worry about that now. > > Generating the disjunction of all conditions wouldn't be terribly hard. > I'm not asking for it, though. I suggest to tackle it as follow-up then. Added a FIXME > > You assert that implicit types are unconditional from the second use on. > I guess you mean to assert the ones used more than once are > unconditional: > > typ = self.lookup_entity(name, QAPISchemaObjectType) > if typ: > assert ifcond is None and typ.ifcond is None > > But what you *should* assert is that the conditions are the same: > > typ = self.lookup_entity(name, QAPISchemaObjectType) > if typ: > assert ifcond == typ.ifcond > >> ok >> def _def_enum_type(self, expr, info, doc): >> name = expr['enum'] >> data = expr['data'] >> prefix = expr.get('prefix') >> - self._def_entity(QAPISchemaEnumType( >> - name, info, doc, self._make_enum_members(data), prefix)) >> + return self._def_entity(QAPISchemaEnumType( >> + name, info, doc, self._make_enum_members(data), prefix), >> + expr.get('if')) > > Covers enumeration types. > > Why return? The (only) caller throws the value away... left-over, fixed > >> >> def _make_member(self, name, typ, info): >> optional = False >> @@ -1584,7 +1612,8 @@ class QAPISchema(object): >> data = expr['data'] >> self._def_entity(QAPISchemaObjectType(name, info, doc, base, >> self._make_members(data, >> info), >> - None)) >> + None), >> + expr.get('if')) > > Covers struct types. > >> >> def _make_variant(self, case, typ): >> return QAPISchemaObjectTypeVariant(case, typ) >> @@ -1593,8 +1622,10 @@ class QAPISchema(object): >> if isinstance(typ, list): >> assert len(typ) == 1 >> typ = self._make_array_type(typ[0], info) >> + type_entity = self.lookup_type(typ) >> typ = self._make_implicit_object_type( >> - typ, info, None, 'wrapper', [self._make_member('data', typ, >> info)]) >> + typ, info, None, 'wrapper', >> + [self._make_member('data', typ, info)], type_entity.ifcond) >> return QAPISchemaObjectTypeVariant(case, typ) > > A simple union member's wrapper type inherits its condition from the > member type. > > I think you need to pass None instead of type_entity.ifcond here. That doesn't work, visitor symbols are missing in generated code. Why shouldn't the wrapper share the same condition as the underlying type? > >> >> def _def_union_type(self, expr, info, doc): >> @@ -1604,8 +1635,9 @@ class QAPISchema(object): >> tag_name = expr.get('discriminator') >> tag_member = None >> if isinstance(base, dict): >> - base = (self._make_implicit_object_type( >> - name, info, doc, 'base', self._make_members(base, info))) >> + base = self._make_implicit_object_type( >> + name, info, doc, 'base', self._make_members(base, info), >> + expr.get('if')) > > A flat union's implicit base type inherits its condition from the flat > union. Good. > >> if tag_name: >> variants = [self._make_variant(key, value) >> for (key, value) in data.iteritems()] >> @@ -1614,14 +1646,16 @@ class QAPISchema(object): >> variants = [self._make_simple_variant(key, value, info) >> for (key, value) in data.iteritems()] >> typ = self._make_implicit_enum_type(name, info, >> - [v.name for v in variants]) >> + [v.name for v in variants], >> + expr.get('if')) > > A flat union's implicit enumeration type inherits its condition from the > flat union. Good. > >> tag_member = QAPISchemaObjectTypeMember('type', typ, False) >> members = [tag_member] >> self._def_entity( >> QAPISchemaObjectType(name, info, doc, base, members, >> QAPISchemaObjectTypeVariants(tag_name, >> tag_member, >> - variants))) >> + variants)), >> + expr.get('if')) > > Covers union types. > > Third use of expr.get('if') in this function. Please put it in a > variable. > > Actually, do that for *all* uses of expr[X] and expr.get(X) in class > QAPISchema, because that's how the existing code works. done > >> >> def _def_alternate_type(self, expr, info, doc): >> name = expr['alternate'] >> @@ -1633,7 +1667,8 @@ class QAPISchema(object): >> QAPISchemaAlternateType(name, info, doc, >> QAPISchemaObjectTypeVariants(None, >> tag_member, >> - variants))) >> + variants)), >> + expr.get('if')) > > Covers alternate types. > >> >> def _def_command(self, expr, info, doc): >> name = expr['command'] >> @@ -1644,12 +1679,14 @@ class QAPISchema(object): >> boxed = expr.get('boxed', False) >> if isinstance(data, OrderedDict): >> data = self._make_implicit_object_type( >> - name, info, doc, 'arg', self._make_members(data, info)) >> + name, info, doc, 'arg', self._make_members(data, info), >> + expr.get('if')) > > A command's implicit argument type inherits its condition from the > command. Good. > >> if isinstance(rets, list): >> assert len(rets) == 1 >> rets = self._make_array_type(rets[0], info) >> self._def_entity(QAPISchemaCommand(name, info, doc, data, rets, >> - gen, success_response, boxed)) >> + gen, success_response, boxed), >> + expr.get('if')) > > Covers commands. > >> >> def _def_event(self, expr, info, doc): >> name = expr['event'] >> @@ -1657,8 +1694,10 @@ class QAPISchema(object): >> boxed = expr.get('boxed', False) >> if isinstance(data, OrderedDict): >> data = self._make_implicit_object_type( >> - name, info, doc, 'arg', self._make_members(data, info)) >> - self._def_entity(QAPISchemaEvent(name, info, doc, data, boxed)) >> + name, info, doc, 'arg', self._make_members(data, info), >> + expr.get('if')) > > An event's implicit argument type inherits its condition from the > command. Good. > >> + self._def_entity(QAPISchemaEvent(name, info, doc, data, boxed), >> + expr.get('if')) > > Covers events. You got them all. Good. > >> >> def _def_exprs(self): >> for expr_elem in self.exprs: >> @@ -1848,6 +1887,54 @@ def guardend(name): >> name=guardname(name)) >> >> >> +def gen_if(ifcond, func=''): >> + if not ifcond: >> + return '' >> + if isinstance(ifcond, str): >> + ifcond = [ifcond] >> + ret = '\n' >> + for ifc in ifcond: >> + ret += mcgen('#if %(ifcond)s /* %(func)s */\n', ifcond=ifc, >> func=func) >> + ret += '\n' >> + return ret > > Please use mcgen() like the existing code: > mcgen() does indent and fails with pre-processor lines. I added a comment > ret += mcgen(''' > #if %(ifcond)s /* %(func)s */ > ''', ifcond=ifc, func=func) > > With the default value of @func, we get a useless, ugly comment /* */. > If this can happen, please suppress the comment. Else, drop @func's > default value. > > Lists appear to be conjunctions. What for? I added some doc in qapi-code-gen.txt > >> + >> + >> +def gen_endif(ifcond, func=''): >> + if not ifcond: >> + return '' >> + if isinstance(ifcond, str): >> + ifcond = [ifcond] >> + ret = '\n' >> + for ifc in reversed(ifcond): >> + ret += mcgen('#endif /* %(ifcond)s %(func)s */\n', >> + ifcond=ifc, func=func) >> + ret += '\n' >> + return ret > > Likewise. > >> + >> + >> +# wrap a method to add #if / #endif to generated code >> +# self must have 'if_members' listing the attributes to wrap >> +# the last argument of the wrapped function must be the 'ifcond' > > Start your sentences with a capital letter, and end them with a period, > please. ok > >> +def if_wrap(func): > > Blank line, please. ok > >> + def func_wrapper(self, *args, **kwargs): >> + funcname = self.__class__.__name__ + '.' + func.__name__ >> + ifcond = args[-1] >> + save = {} >> + for mem in self.if_members: >> + save[mem] = getattr(self, mem) >> + func(self, *args, **kwargs) >> + for mem, val in save.items(): >> + newval = getattr(self, mem) >> + if newval != val: >> + assert newval.startswith(val) >> + newval = newval[len(val):] >> + val += gen_if(ifcond, funcname) > > Emitting comments pointing to the QAPI schema into the generated code is > often helpful. But this one points to QAPI generator code. Why is that > useful? That was mostly useful during development, removed > >> + val += newval >> + val += gen_endif(ifcond, funcname) >> + setattr(self, mem, val) >> + >> + return func_wrapper >> + > > pep8 again: > > scripts/qapi.py:1955:1: E302 expected 2 blank lines, found 1 > > Peeking ahead: this function is used as a decorator. Let's help the > reader and mention that in the function comment, or by naming the > function suitably. ifcond_decorator? done > > Messing with the wrapped method's class's attributes is naughty. Worse, > it's hard to understand. What alternatives have you considered? Well, I started writing the code that checked if members got code added, I had to put some enter()/leave() code everywhere. Then I realize this could easily be the job for a decorator. I think the end result is pretty neat. If you have a better idea, can you do it in a follow-up? > >> def gen_enum_lookup(name, values, prefix=None): >> ret = mcgen(''' >> >> diff --git a/scripts/qapi-commands.py b/scripts/qapi-commands.py >> index 974d0a4a80..19b1bb9b88 100644 >> --- a/scripts/qapi-commands.py >> +++ b/scripts/qapi-commands.py >> @@ -228,6 +228,7 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor): >> self.defn = None >> self._regy = None >> self._visited_ret_types = None >> + self.if_members = ['decl', 'defn', '_regy'] >> >> def visit_begin(self, schema): >> self.decl = '' >> @@ -240,8 +241,9 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor): >> self._regy = None >> self._visited_ret_types = None >> >> + @if_wrap >> def visit_command(self, name, info, arg_type, ret_type, >> - gen, success_response, boxed): >> + gen, success_response, boxed, ifcond): >> if not gen: >> return >> self.decl += gen_command_decl(name, arg_type, boxed, ret_type) >> diff --git a/scripts/qapi-event.py b/scripts/qapi-event.py >> index bcbef1035f..cad9ece790 100644 >> --- a/scripts/qapi-event.py >> +++ b/scripts/qapi-event.py >> @@ -152,6 +152,7 @@ class QAPISchemaGenEventVisitor(QAPISchemaVisitor): >> self.decl = None >> self.defn = None >> self._event_names = None >> + self.if_members = ['decl', 'defn'] >> >> def visit_begin(self, schema): >> self.decl = '' >> @@ -163,7 +164,8 @@ class QAPISchemaGenEventVisitor(QAPISchemaVisitor): >> self.defn += gen_enum_lookup(event_enum_name, self._event_names) >> self._event_names = None >> >> - def visit_event(self, name, info, arg_type, boxed): >> + @if_wrap >> + def visit_event(self, name, info, arg_type, boxed, ifcond): >> self.decl += gen_event_send_decl(name, arg_type, boxed) >> self.defn += gen_event_send(name, arg_type, boxed) >> self._event_names.append(name) >> diff --git a/scripts/qapi-introspect.py b/scripts/qapi-introspect.py >> index fc72cdb66d..ecfb0f2752 100644 >> --- a/scripts/qapi-introspect.py >> +++ b/scripts/qapi-introspect.py >> @@ -12,7 +12,7 @@ >> from qapi import * >> >> >> -def to_qlit(obj, level=0, first_indent=True): >> +def to_qlit(obj, level=0, first_indent=True, suffix=''): >> def indent(level): >> return level * 4 * ' ' >> ret = '' >> @@ -20,14 +20,20 @@ def to_qlit(obj, level=0, first_indent=True): >> ret += indent(level) >> if obj is None: >> ret += 'QLIT_QNULL' >> + elif isinstance(obj, tuple): >> + obj, ifcond = obj >> + ret += gen_if(ifcond) >> + ret += to_qlit(obj, level, False) + suffix > > Keyword argument instead of bare False, please. ok > >> + ret += gen_endif(ifcond) >> + suffix = '' > > New case tuple, for generating conditionals. Okay. > >> elif isinstance(obj, str): >> ret += 'QLIT_QSTR(' + '"' + obj.replace('"', r'\"') + '"' + ')' >> elif isinstance(obj, list): >> - elts = [to_qlit(elt, level + 1) >> + elts = [to_qlit(elt, level + 1, True, ",") > > Make that > > elts = [to_qlit(elt, level + 1, suffix=",") ok > >> for elt in obj] >> elts.append(indent(level + 1) + "{ }") >> ret += 'QLIT_QLIST(((QLitObject[]) {\n' >> - ret += ',\n'.join(elts) + '\n' >> + ret += '\n'.join(elts) + '\n' >> ret += indent(level) + '}))' >> elif isinstance(obj, dict): >> elts = [ indent(level + 1) + '{ "%s", %s }' % >> @@ -39,7 +45,7 @@ def to_qlit(obj, level=0, first_indent=True): >> ret += indent(level) + '}))' >> else: >> assert False # not implemented >> - return ret >> + return ret + suffix > > This is getting really hard to review --- my brain is about to overflow > and shut down for the day. Can you split off some preparatory work? > The introduction of suffix here, perhaps? ok > >> >> >> class QAPISchemaGenIntrospectVisitor(QAPISchemaVisitor): >> @@ -113,12 +119,12 @@ const QLitObject %(c_name)s = %(c_string)s; >> return '[' + self._use_type(typ.element_type) + ']' >> return self._name(typ.name) >> >> - def _gen_qlit(self, name, mtype, obj): >> + def _gen_qlit(self, name, mtype, obj, ifcond): >> if mtype not in ('command', 'event', 'builtin', 'array'): >> name = self._name(name) >> obj['name'] = name >> obj['meta-type'] = mtype >> - self._qlits.append(obj) >> + self._qlits.append((obj, ifcond)) >> >> def _gen_member(self, member): >> ret = {'name': member.name, 'type': self._use_type(member.type)} >> @@ -134,38 +140,40 @@ const QLitObject %(c_name)s = %(c_string)s; >> return {'case': variant.name, 'type': self._use_type(variant.type)} >> >> def visit_builtin_type(self, name, info, json_type): >> - self._gen_qlit(name, 'builtin', {'json-type': json_type}) >> + self._gen_qlit(name, 'builtin', {'json-type': json_type}, None) >> >> - def visit_enum_type(self, name, info, values, prefix): >> - self._gen_qlit(name, 'enum', {'values': values}) >> + def visit_enum_type(self, name, info, values, prefix, ifcond): >> + self._gen_qlit(name, 'enum', {'values': values}, ifcond) >> >> - def visit_array_type(self, name, info, element_type): >> + def visit_array_type(self, name, info, element_type, ifcond): >> element = self._use_type(element_type) >> - self._gen_qlit('[' + element + ']', 'array', {'element-type': >> element}) >> + self._gen_qlit('[' + element + ']', 'array', {'element-type': >> element}, >> + ifcond) >> >> - def visit_object_type_flat(self, name, info, members, variants): >> + def visit_object_type_flat(self, name, info, members, variants, ifcond): >> obj = {'members': [self._gen_member(m) for m in members]} >> if variants: >> obj.update(self._gen_variants(variants.tag_member.name, >> variants.variants)) >> - self._gen_qlit(name, 'object', obj) >> + self._gen_qlit(name, 'object', obj, ifcond) >> >> - def visit_alternate_type(self, name, info, variants): >> + def visit_alternate_type(self, name, info, variants, ifcond): >> self._gen_qlit(name, 'alternate', >> {'members': [{'type': self._use_type(m.type)} >> - for m in variants.variants]}) >> + for m in variants.variants]}, ifcond) >> >> def visit_command(self, name, info, arg_type, ret_type, >> - gen, success_response, boxed): >> + gen, success_response, boxed, ifcond): >> arg_type = arg_type or self._schema.the_empty_object_type >> ret_type = ret_type or self._schema.the_empty_object_type >> self._gen_qlit(name, 'command', >> {'arg-type': self._use_type(arg_type), >> - 'ret-type': self._use_type(ret_type)}) >> + 'ret-type': self._use_type(ret_type)}, ifcond) >> >> - def visit_event(self, name, info, arg_type, boxed): >> + def visit_event(self, name, info, arg_type, boxed, ifcond): >> arg_type = arg_type or self._schema.the_empty_object_type >> - self._gen_qlit(name, 'event', {'arg-type': >> self._use_type(arg_type)}) >> + self._gen_qlit(name, 'event', {'arg-type': >> self._use_type(arg_type)}, >> + ifcond) >> >> # Debugging aid: unmask QAPI schema's type names >> # We normally mask them, because they're not QMP wire ABI > > Out of review brainpower for today. Hope to resume tomorrow. > > [...] > -- Marc-André Lureau