qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

schema.py (41640B)


      1 # -*- coding: utf-8 -*-
      2 #
      3 # QAPI schema internal representation
      4 #
      5 # Copyright (c) 2015-2019 Red Hat Inc.
      6 #
      7 # Authors:
      8 #  Markus Armbruster <armbru@redhat.com>
      9 #  Eric Blake <eblake@redhat.com>
     10 #  Marc-André Lureau <marcandre.lureau@redhat.com>
     11 #
     12 # This work is licensed under the terms of the GNU GPL, version 2.
     13 # See the COPYING file in the top-level directory.
     14 
     15 # TODO catching name collisions in generated code would be nice
     16 
     17 from collections import OrderedDict
     18 import os
     19 import re
     20 from typing import Optional
     21 
     22 from .common import (
     23     POINTER_SUFFIX,
     24     c_name,
     25     cgen_ifcond,
     26     docgen_ifcond,
     27     gen_endif,
     28     gen_if,
     29 )
     30 from .error import QAPIError, QAPISemError, QAPISourceError
     31 from .expr import check_exprs
     32 from .parser import QAPISchemaParser
     33 
     34 
     35 class QAPISchemaIfCond:
     36     def __init__(self, ifcond=None):
     37         self.ifcond = ifcond
     38 
     39     def _cgen(self):
     40         return cgen_ifcond(self.ifcond)
     41 
     42     def gen_if(self):
     43         return gen_if(self._cgen())
     44 
     45     def gen_endif(self):
     46         return gen_endif(self._cgen())
     47 
     48     def docgen(self):
     49         return docgen_ifcond(self.ifcond)
     50 
     51     def is_present(self):
     52         return bool(self.ifcond)
     53 
     54 
     55 class QAPISchemaEntity:
     56     meta: Optional[str] = None
     57 
     58     def __init__(self, name: str, info, doc, ifcond=None, features=None):
     59         assert name is None or isinstance(name, str)
     60         for f in features or []:
     61             assert isinstance(f, QAPISchemaFeature)
     62             f.set_defined_in(name)
     63         self.name = name
     64         self._module = None
     65         # For explicitly defined entities, info points to the (explicit)
     66         # definition.  For builtins (and their arrays), info is None.
     67         # For implicitly defined entities, info points to a place that
     68         # triggered the implicit definition (there may be more than one
     69         # such place).
     70         self.info = info
     71         self.doc = doc
     72         self._ifcond = ifcond or QAPISchemaIfCond()
     73         self.features = features or []
     74         self._checked = False
     75 
     76     def c_name(self):
     77         return c_name(self.name)
     78 
     79     def check(self, schema):
     80         assert not self._checked
     81         seen = {}
     82         for f in self.features:
     83             f.check_clash(self.info, seen)
     84         self._checked = True
     85 
     86     def connect_doc(self, doc=None):
     87         doc = doc or self.doc
     88         if doc:
     89             for f in self.features:
     90                 doc.connect_feature(f)
     91 
     92     def check_doc(self):
     93         if self.doc:
     94             self.doc.check()
     95 
     96     def _set_module(self, schema, info):
     97         assert self._checked
     98         fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
     99         self._module = schema.module_by_fname(fname)
    100         self._module.add_entity(self)
    101 
    102     def set_module(self, schema):
    103         self._set_module(schema, self.info)
    104 
    105     @property
    106     def ifcond(self):
    107         assert self._checked
    108         return self._ifcond
    109 
    110     def is_implicit(self):
    111         return not self.info
    112 
    113     def visit(self, visitor):
    114         assert self._checked
    115 
    116     def describe(self):
    117         assert self.meta
    118         return "%s '%s'" % (self.meta, self.name)
    119 
    120 
    121 class QAPISchemaVisitor:
    122     def visit_begin(self, schema):
    123         pass
    124 
    125     def visit_end(self):
    126         pass
    127 
    128     def visit_module(self, name):
    129         pass
    130 
    131     def visit_needed(self, entity):
    132         # Default to visiting everything
    133         return True
    134 
    135     def visit_include(self, name, info):
    136         pass
    137 
    138     def visit_builtin_type(self, name, info, json_type):
    139         pass
    140 
    141     def visit_enum_type(self, name, info, ifcond, features, members, prefix):
    142         pass
    143 
    144     def visit_array_type(self, name, info, ifcond, element_type):
    145         pass
    146 
    147     def visit_object_type(self, name, info, ifcond, features,
    148                           base, members, variants):
    149         pass
    150 
    151     def visit_object_type_flat(self, name, info, ifcond, features,
    152                                members, variants):
    153         pass
    154 
    155     def visit_alternate_type(self, name, info, ifcond, features, variants):
    156         pass
    157 
    158     def visit_command(self, name, info, ifcond, features,
    159                       arg_type, ret_type, gen, success_response, boxed,
    160                       allow_oob, allow_preconfig, coroutine):
    161         pass
    162 
    163     def visit_event(self, name, info, ifcond, features, arg_type, boxed):
    164         pass
    165 
    166 
    167 class QAPISchemaModule:
    168 
    169     BUILTIN_MODULE_NAME = './builtin'
    170 
    171     def __init__(self, name):
    172         self.name = name
    173         self._entity_list = []
    174 
    175     @staticmethod
    176     def is_system_module(name: str) -> bool:
    177         """
    178         System modules are internally defined modules.
    179 
    180         Their names start with the "./" prefix.
    181         """
    182         return name.startswith('./')
    183 
    184     @classmethod
    185     def is_user_module(cls, name: str) -> bool:
    186         """
    187         User modules are those defined by the user in qapi JSON files.
    188 
    189         They do not start with the "./" prefix.
    190         """
    191         return not cls.is_system_module(name)
    192 
    193     @classmethod
    194     def is_builtin_module(cls, name: str) -> bool:
    195         """
    196         The built-in module is a single System module for the built-in types.
    197 
    198         It is always "./builtin".
    199         """
    200         return name == cls.BUILTIN_MODULE_NAME
    201 
    202     def add_entity(self, ent):
    203         self._entity_list.append(ent)
    204 
    205     def visit(self, visitor):
    206         visitor.visit_module(self.name)
    207         for entity in self._entity_list:
    208             if visitor.visit_needed(entity):
    209                 entity.visit(visitor)
    210 
    211 
    212 class QAPISchemaInclude(QAPISchemaEntity):
    213     def __init__(self, sub_module, info):
    214         super().__init__(None, info, None)
    215         self._sub_module = sub_module
    216 
    217     def visit(self, visitor):
    218         super().visit(visitor)
    219         visitor.visit_include(self._sub_module.name, self.info)
    220 
    221 
    222 class QAPISchemaType(QAPISchemaEntity):
    223     # Return the C type for common use.
    224     # For the types we commonly box, this is a pointer type.
    225     def c_type(self):
    226         pass
    227 
    228     # Return the C type to be used in a parameter list.
    229     def c_param_type(self):
    230         return self.c_type()
    231 
    232     # Return the C type to be used where we suppress boxing.
    233     def c_unboxed_type(self):
    234         return self.c_type()
    235 
    236     def json_type(self):
    237         pass
    238 
    239     def alternate_qtype(self):
    240         json2qtype = {
    241             'null':    'QTYPE_QNULL',
    242             'string':  'QTYPE_QSTRING',
    243             'number':  'QTYPE_QNUM',
    244             'int':     'QTYPE_QNUM',
    245             'boolean': 'QTYPE_QBOOL',
    246             'array':   'QTYPE_QLIST',
    247             'object':  'QTYPE_QDICT'
    248         }
    249         return json2qtype.get(self.json_type())
    250 
    251     def doc_type(self):
    252         if self.is_implicit():
    253             return None
    254         return self.name
    255 
    256     def check(self, schema):
    257         QAPISchemaEntity.check(self, schema)
    258         for feat in self.features:
    259             if feat.is_special():
    260                 raise QAPISemError(
    261                     self.info,
    262                     f"feature '{feat.name}' is not supported for types")
    263 
    264     def describe(self):
    265         assert self.meta
    266         return "%s type '%s'" % (self.meta, self.name)
    267 
    268 
    269 class QAPISchemaBuiltinType(QAPISchemaType):
    270     meta = 'built-in'
    271 
    272     def __init__(self, name, json_type, c_type):
    273         super().__init__(name, None, None)
    274         assert not c_type or isinstance(c_type, str)
    275         assert json_type in ('string', 'number', 'int', 'boolean', 'null',
    276                              'value')
    277         self._json_type_name = json_type
    278         self._c_type_name = c_type
    279 
    280     def c_name(self):
    281         return self.name
    282 
    283     def c_type(self):
    284         return self._c_type_name
    285 
    286     def c_param_type(self):
    287         if self.name == 'str':
    288             return 'const ' + self._c_type_name
    289         return self._c_type_name
    290 
    291     def json_type(self):
    292         return self._json_type_name
    293 
    294     def doc_type(self):
    295         return self.json_type()
    296 
    297     def visit(self, visitor):
    298         super().visit(visitor)
    299         visitor.visit_builtin_type(self.name, self.info, self.json_type())
    300 
    301 
    302 class QAPISchemaEnumType(QAPISchemaType):
    303     meta = 'enum'
    304 
    305     def __init__(self, name, info, doc, ifcond, features, members, prefix):
    306         super().__init__(name, info, doc, ifcond, features)
    307         for m in members:
    308             assert isinstance(m, QAPISchemaEnumMember)
    309             m.set_defined_in(name)
    310         assert prefix is None or isinstance(prefix, str)
    311         self.members = members
    312         self.prefix = prefix
    313 
    314     def check(self, schema):
    315         super().check(schema)
    316         seen = {}
    317         for m in self.members:
    318             m.check_clash(self.info, seen)
    319 
    320     def connect_doc(self, doc=None):
    321         super().connect_doc(doc)
    322         doc = doc or self.doc
    323         for m in self.members:
    324             m.connect_doc(doc)
    325 
    326     def is_implicit(self):
    327         # See QAPISchema._def_predefineds()
    328         return self.name == 'QType'
    329 
    330     def c_type(self):
    331         return c_name(self.name)
    332 
    333     def member_names(self):
    334         return [m.name for m in self.members]
    335 
    336     def json_type(self):
    337         return 'string'
    338 
    339     def visit(self, visitor):
    340         super().visit(visitor)
    341         visitor.visit_enum_type(
    342             self.name, self.info, self.ifcond, self.features,
    343             self.members, self.prefix)
    344 
    345 
    346 class QAPISchemaArrayType(QAPISchemaType):
    347     meta = 'array'
    348 
    349     def __init__(self, name, info, element_type):
    350         super().__init__(name, info, None)
    351         assert isinstance(element_type, str)
    352         self._element_type_name = element_type
    353         self.element_type = None
    354 
    355     def check(self, schema):
    356         super().check(schema)
    357         self.element_type = schema.resolve_type(
    358             self._element_type_name, self.info,
    359             self.info and self.info.defn_meta)
    360         assert not isinstance(self.element_type, QAPISchemaArrayType)
    361 
    362     def set_module(self, schema):
    363         self._set_module(schema, self.element_type.info)
    364 
    365     @property
    366     def ifcond(self):
    367         assert self._checked
    368         return self.element_type.ifcond
    369 
    370     def is_implicit(self):
    371         return True
    372 
    373     def c_type(self):
    374         return c_name(self.name) + POINTER_SUFFIX
    375 
    376     def json_type(self):
    377         return 'array'
    378 
    379     def doc_type(self):
    380         elt_doc_type = self.element_type.doc_type()
    381         if not elt_doc_type:
    382             return None
    383         return 'array of ' + elt_doc_type
    384 
    385     def visit(self, visitor):
    386         super().visit(visitor)
    387         visitor.visit_array_type(self.name, self.info, self.ifcond,
    388                                  self.element_type)
    389 
    390     def describe(self):
    391         assert self.meta
    392         return "%s type ['%s']" % (self.meta, self._element_type_name)
    393 
    394 
    395 class QAPISchemaObjectType(QAPISchemaType):
    396     def __init__(self, name, info, doc, ifcond, features,
    397                  base, local_members, variants):
    398         # struct has local_members, optional base, and no variants
    399         # union has base, variants, and no local_members
    400         super().__init__(name, info, doc, ifcond, features)
    401         self.meta = 'union' if variants else 'struct'
    402         assert base is None or isinstance(base, str)
    403         for m in local_members:
    404             assert isinstance(m, QAPISchemaObjectTypeMember)
    405             m.set_defined_in(name)
    406         if variants is not None:
    407             assert isinstance(variants, QAPISchemaVariants)
    408             variants.set_defined_in(name)
    409         self._base_name = base
    410         self.base = None
    411         self.local_members = local_members
    412         self.variants = variants
    413         self.members = None
    414 
    415     def check(self, schema):
    416         # This calls another type T's .check() exactly when the C
    417         # struct emitted by gen_object() contains that T's C struct
    418         # (pointers don't count).
    419         if self.members is not None:
    420             # A previous .check() completed: nothing to do
    421             return
    422         if self._checked:
    423             # Recursed: C struct contains itself
    424             raise QAPISemError(self.info,
    425                                "object %s contains itself" % self.name)
    426 
    427         super().check(schema)
    428         assert self._checked and self.members is None
    429 
    430         seen = OrderedDict()
    431         if self._base_name:
    432             self.base = schema.resolve_type(self._base_name, self.info,
    433                                             "'base'")
    434             if (not isinstance(self.base, QAPISchemaObjectType)
    435                     or self.base.variants):
    436                 raise QAPISemError(
    437                     self.info,
    438                     "'base' requires a struct type, %s isn't"
    439                     % self.base.describe())
    440             self.base.check(schema)
    441             self.base.check_clash(self.info, seen)
    442         for m in self.local_members:
    443             m.check(schema)
    444             m.check_clash(self.info, seen)
    445         members = seen.values()
    446 
    447         if self.variants:
    448             self.variants.check(schema, seen)
    449             self.variants.check_clash(self.info, seen)
    450 
    451         self.members = members  # mark completed
    452 
    453     # Check that the members of this type do not cause duplicate JSON members,
    454     # and update seen to track the members seen so far. Report any errors
    455     # on behalf of info, which is not necessarily self.info
    456     def check_clash(self, info, seen):
    457         assert self._checked
    458         assert not self.variants       # not implemented
    459         for m in self.members:
    460             m.check_clash(info, seen)
    461 
    462     def connect_doc(self, doc=None):
    463         super().connect_doc(doc)
    464         doc = doc or self.doc
    465         if self.base and self.base.is_implicit():
    466             self.base.connect_doc(doc)
    467         for m in self.local_members:
    468             m.connect_doc(doc)
    469 
    470     def is_implicit(self):
    471         # See QAPISchema._make_implicit_object_type(), as well as
    472         # _def_predefineds()
    473         return self.name.startswith('q_')
    474 
    475     def is_empty(self):
    476         assert self.members is not None
    477         return not self.members and not self.variants
    478 
    479     def c_name(self):
    480         assert self.name != 'q_empty'
    481         return super().c_name()
    482 
    483     def c_type(self):
    484         assert not self.is_implicit()
    485         return c_name(self.name) + POINTER_SUFFIX
    486 
    487     def c_unboxed_type(self):
    488         return c_name(self.name)
    489 
    490     def json_type(self):
    491         return 'object'
    492 
    493     def visit(self, visitor):
    494         super().visit(visitor)
    495         visitor.visit_object_type(
    496             self.name, self.info, self.ifcond, self.features,
    497             self.base, self.local_members, self.variants)
    498         visitor.visit_object_type_flat(
    499             self.name, self.info, self.ifcond, self.features,
    500             self.members, self.variants)
    501 
    502 
    503 class QAPISchemaAlternateType(QAPISchemaType):
    504     meta = 'alternate'
    505 
    506     def __init__(self, name, info, doc, ifcond, features, variants):
    507         super().__init__(name, info, doc, ifcond, features)
    508         assert isinstance(variants, QAPISchemaVariants)
    509         assert variants.tag_member
    510         variants.set_defined_in(name)
    511         variants.tag_member.set_defined_in(self.name)
    512         self.variants = variants
    513 
    514     def check(self, schema):
    515         super().check(schema)
    516         self.variants.tag_member.check(schema)
    517         # Not calling self.variants.check_clash(), because there's nothing
    518         # to clash with
    519         self.variants.check(schema, {})
    520         # Alternate branch names have no relation to the tag enum values;
    521         # so we have to check for potential name collisions ourselves.
    522         seen = {}
    523         types_seen = {}
    524         for v in self.variants.variants:
    525             v.check_clash(self.info, seen)
    526             qtype = v.type.alternate_qtype()
    527             if not qtype:
    528                 raise QAPISemError(
    529                     self.info,
    530                     "%s cannot use %s"
    531                     % (v.describe(self.info), v.type.describe()))
    532             conflicting = set([qtype])
    533             if qtype == 'QTYPE_QSTRING':
    534                 if isinstance(v.type, QAPISchemaEnumType):
    535                     for m in v.type.members:
    536                         if m.name in ['on', 'off']:
    537                             conflicting.add('QTYPE_QBOOL')
    538                         if re.match(r'[-+0-9.]', m.name):
    539                             # lazy, could be tightened
    540                             conflicting.add('QTYPE_QNUM')
    541                 else:
    542                     conflicting.add('QTYPE_QNUM')
    543                     conflicting.add('QTYPE_QBOOL')
    544             for qt in conflicting:
    545                 if qt in types_seen:
    546                     raise QAPISemError(
    547                         self.info,
    548                         "%s can't be distinguished from '%s'"
    549                         % (v.describe(self.info), types_seen[qt]))
    550                 types_seen[qt] = v.name
    551 
    552     def connect_doc(self, doc=None):
    553         super().connect_doc(doc)
    554         doc = doc or self.doc
    555         for v in self.variants.variants:
    556             v.connect_doc(doc)
    557 
    558     def c_type(self):
    559         return c_name(self.name) + POINTER_SUFFIX
    560 
    561     def json_type(self):
    562         return 'value'
    563 
    564     def visit(self, visitor):
    565         super().visit(visitor)
    566         visitor.visit_alternate_type(
    567             self.name, self.info, self.ifcond, self.features, self.variants)
    568 
    569 
    570 class QAPISchemaVariants:
    571     def __init__(self, tag_name, info, tag_member, variants):
    572         # Unions pass tag_name but not tag_member.
    573         # Alternates pass tag_member but not tag_name.
    574         # After check(), tag_member is always set.
    575         assert bool(tag_member) != bool(tag_name)
    576         assert (isinstance(tag_name, str) or
    577                 isinstance(tag_member, QAPISchemaObjectTypeMember))
    578         for v in variants:
    579             assert isinstance(v, QAPISchemaVariant)
    580         self._tag_name = tag_name
    581         self.info = info
    582         self.tag_member = tag_member
    583         self.variants = variants
    584 
    585     def set_defined_in(self, name):
    586         for v in self.variants:
    587             v.set_defined_in(name)
    588 
    589     def check(self, schema, seen):
    590         if self._tag_name:      # union
    591             self.tag_member = seen.get(c_name(self._tag_name))
    592             base = "'base'"
    593             # Pointing to the base type when not implicit would be
    594             # nice, but we don't know it here
    595             if not self.tag_member or self._tag_name != self.tag_member.name:
    596                 raise QAPISemError(
    597                     self.info,
    598                     "discriminator '%s' is not a member of %s"
    599                     % (self._tag_name, base))
    600             # Here we do:
    601             base_type = schema.lookup_type(self.tag_member.defined_in)
    602             assert base_type
    603             if not base_type.is_implicit():
    604                 base = "base type '%s'" % self.tag_member.defined_in
    605             if not isinstance(self.tag_member.type, QAPISchemaEnumType):
    606                 raise QAPISemError(
    607                     self.info,
    608                     "discriminator member '%s' of %s must be of enum type"
    609                     % (self._tag_name, base))
    610             if self.tag_member.optional:
    611                 raise QAPISemError(
    612                     self.info,
    613                     "discriminator member '%s' of %s must not be optional"
    614                     % (self._tag_name, base))
    615             if self.tag_member.ifcond.is_present():
    616                 raise QAPISemError(
    617                     self.info,
    618                     "discriminator member '%s' of %s must not be conditional"
    619                     % (self._tag_name, base))
    620         else:                   # alternate
    621             assert isinstance(self.tag_member.type, QAPISchemaEnumType)
    622             assert not self.tag_member.optional
    623             assert not self.tag_member.ifcond.is_present()
    624         if self._tag_name:      # union
    625             # branches that are not explicitly covered get an empty type
    626             cases = {v.name for v in self.variants}
    627             for m in self.tag_member.type.members:
    628                 if m.name not in cases:
    629                     v = QAPISchemaVariant(m.name, self.info,
    630                                           'q_empty', m.ifcond)
    631                     v.set_defined_in(self.tag_member.defined_in)
    632                     self.variants.append(v)
    633         if not self.variants:
    634             raise QAPISemError(self.info, "union has no branches")
    635         for v in self.variants:
    636             v.check(schema)
    637             # Union names must match enum values; alternate names are
    638             # checked separately. Use 'seen' to tell the two apart.
    639             if seen:
    640                 if v.name not in self.tag_member.type.member_names():
    641                     raise QAPISemError(
    642                         self.info,
    643                         "branch '%s' is not a value of %s"
    644                         % (v.name, self.tag_member.type.describe()))
    645                 if (not isinstance(v.type, QAPISchemaObjectType)
    646                         or v.type.variants):
    647                     raise QAPISemError(
    648                         self.info,
    649                         "%s cannot use %s"
    650                         % (v.describe(self.info), v.type.describe()))
    651                 v.type.check(schema)
    652 
    653     def check_clash(self, info, seen):
    654         for v in self.variants:
    655             # Reset seen map for each variant, since qapi names from one
    656             # branch do not affect another branch
    657             v.type.check_clash(info, dict(seen))
    658 
    659 
    660 class QAPISchemaMember:
    661     """ Represents object members, enum members and features """
    662     role = 'member'
    663 
    664     def __init__(self, name, info, ifcond=None):
    665         assert isinstance(name, str)
    666         self.name = name
    667         self.info = info
    668         self.ifcond = ifcond or QAPISchemaIfCond()
    669         self.defined_in = None
    670 
    671     def set_defined_in(self, name):
    672         assert not self.defined_in
    673         self.defined_in = name
    674 
    675     def check_clash(self, info, seen):
    676         cname = c_name(self.name)
    677         if cname in seen:
    678             raise QAPISemError(
    679                 info,
    680                 "%s collides with %s"
    681                 % (self.describe(info), seen[cname].describe(info)))
    682         seen[cname] = self
    683 
    684     def connect_doc(self, doc):
    685         if doc:
    686             doc.connect_member(self)
    687 
    688     def describe(self, info):
    689         role = self.role
    690         defined_in = self.defined_in
    691         assert defined_in
    692 
    693         if defined_in.startswith('q_obj_'):
    694             # See QAPISchema._make_implicit_object_type() - reverse the
    695             # mapping there to create a nice human-readable description
    696             defined_in = defined_in[6:]
    697             if defined_in.endswith('-arg'):
    698                 # Implicit type created for a command's dict 'data'
    699                 assert role == 'member'
    700                 role = 'parameter'
    701             elif defined_in.endswith('-base'):
    702                 # Implicit type created for a union's dict 'base'
    703                 role = 'base ' + role
    704             else:
    705                 assert False
    706         elif defined_in != info.defn_name:
    707             return "%s '%s' of type '%s'" % (role, self.name, defined_in)
    708         return "%s '%s'" % (role, self.name)
    709 
    710 
    711 class QAPISchemaEnumMember(QAPISchemaMember):
    712     role = 'value'
    713 
    714     def __init__(self, name, info, ifcond=None, features=None):
    715         super().__init__(name, info, ifcond)
    716         for f in features or []:
    717             assert isinstance(f, QAPISchemaFeature)
    718             f.set_defined_in(name)
    719         self.features = features or []
    720 
    721     def connect_doc(self, doc):
    722         super().connect_doc(doc)
    723         if doc:
    724             for f in self.features:
    725                 doc.connect_feature(f)
    726 
    727 
    728 class QAPISchemaFeature(QAPISchemaMember):
    729     role = 'feature'
    730 
    731     def is_special(self):
    732         return self.name in ('deprecated', 'unstable')
    733 
    734 
    735 class QAPISchemaObjectTypeMember(QAPISchemaMember):
    736     def __init__(self, name, info, typ, optional, ifcond=None, features=None):
    737         super().__init__(name, info, ifcond)
    738         assert isinstance(typ, str)
    739         assert isinstance(optional, bool)
    740         for f in features or []:
    741             assert isinstance(f, QAPISchemaFeature)
    742             f.set_defined_in(name)
    743         self._type_name = typ
    744         self.type = None
    745         self.optional = optional
    746         self.features = features or []
    747 
    748     def check(self, schema):
    749         assert self.defined_in
    750         self.type = schema.resolve_type(self._type_name, self.info,
    751                                         self.describe)
    752         seen = {}
    753         for f in self.features:
    754             f.check_clash(self.info, seen)
    755 
    756     def connect_doc(self, doc):
    757         super().connect_doc(doc)
    758         if doc:
    759             for f in self.features:
    760                 doc.connect_feature(f)
    761 
    762 
    763 class QAPISchemaVariant(QAPISchemaObjectTypeMember):
    764     role = 'branch'
    765 
    766     def __init__(self, name, info, typ, ifcond=None):
    767         super().__init__(name, info, typ, False, ifcond)
    768 
    769 
    770 class QAPISchemaCommand(QAPISchemaEntity):
    771     meta = 'command'
    772 
    773     def __init__(self, name, info, doc, ifcond, features,
    774                  arg_type, ret_type,
    775                  gen, success_response, boxed, allow_oob, allow_preconfig,
    776                  coroutine):
    777         super().__init__(name, info, doc, ifcond, features)
    778         assert not arg_type or isinstance(arg_type, str)
    779         assert not ret_type or isinstance(ret_type, str)
    780         self._arg_type_name = arg_type
    781         self.arg_type = None
    782         self._ret_type_name = ret_type
    783         self.ret_type = None
    784         self.gen = gen
    785         self.success_response = success_response
    786         self.boxed = boxed
    787         self.allow_oob = allow_oob
    788         self.allow_preconfig = allow_preconfig
    789         self.coroutine = coroutine
    790 
    791     def check(self, schema):
    792         super().check(schema)
    793         if self._arg_type_name:
    794             self.arg_type = schema.resolve_type(
    795                 self._arg_type_name, self.info, "command's 'data'")
    796             if not isinstance(self.arg_type, QAPISchemaObjectType):
    797                 raise QAPISemError(
    798                     self.info,
    799                     "command's 'data' cannot take %s"
    800                     % self.arg_type.describe())
    801             if self.arg_type.variants and not self.boxed:
    802                 raise QAPISemError(
    803                     self.info,
    804                     "command's 'data' can take %s only with 'boxed': true"
    805                     % self.arg_type.describe())
    806         if self._ret_type_name:
    807             self.ret_type = schema.resolve_type(
    808                 self._ret_type_name, self.info, "command's 'returns'")
    809             if self.name not in self.info.pragma.command_returns_exceptions:
    810                 typ = self.ret_type
    811                 if isinstance(typ, QAPISchemaArrayType):
    812                     typ = self.ret_type.element_type
    813                     assert typ
    814                 if not isinstance(typ, QAPISchemaObjectType):
    815                     raise QAPISemError(
    816                         self.info,
    817                         "command's 'returns' cannot take %s"
    818                         % self.ret_type.describe())
    819 
    820     def connect_doc(self, doc=None):
    821         super().connect_doc(doc)
    822         doc = doc or self.doc
    823         if doc:
    824             if self.arg_type and self.arg_type.is_implicit():
    825                 self.arg_type.connect_doc(doc)
    826 
    827     def visit(self, visitor):
    828         super().visit(visitor)
    829         visitor.visit_command(
    830             self.name, self.info, self.ifcond, self.features,
    831             self.arg_type, self.ret_type, self.gen, self.success_response,
    832             self.boxed, self.allow_oob, self.allow_preconfig,
    833             self.coroutine)
    834 
    835 
    836 class QAPISchemaEvent(QAPISchemaEntity):
    837     meta = 'event'
    838 
    839     def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
    840         super().__init__(name, info, doc, ifcond, features)
    841         assert not arg_type or isinstance(arg_type, str)
    842         self._arg_type_name = arg_type
    843         self.arg_type = None
    844         self.boxed = boxed
    845 
    846     def check(self, schema):
    847         super().check(schema)
    848         if self._arg_type_name:
    849             self.arg_type = schema.resolve_type(
    850                 self._arg_type_name, self.info, "event's 'data'")
    851             if not isinstance(self.arg_type, QAPISchemaObjectType):
    852                 raise QAPISemError(
    853                     self.info,
    854                     "event's 'data' cannot take %s"
    855                     % self.arg_type.describe())
    856             if self.arg_type.variants and not self.boxed:
    857                 raise QAPISemError(
    858                     self.info,
    859                     "event's 'data' can take %s only with 'boxed': true"
    860                     % self.arg_type.describe())
    861 
    862     def connect_doc(self, doc=None):
    863         super().connect_doc(doc)
    864         doc = doc or self.doc
    865         if doc:
    866             if self.arg_type and self.arg_type.is_implicit():
    867                 self.arg_type.connect_doc(doc)
    868 
    869     def visit(self, visitor):
    870         super().visit(visitor)
    871         visitor.visit_event(
    872             self.name, self.info, self.ifcond, self.features,
    873             self.arg_type, self.boxed)
    874 
    875 
    876 class QAPISchema:
    877     def __init__(self, fname):
    878         self.fname = fname
    879 
    880         try:
    881             parser = QAPISchemaParser(fname)
    882         except OSError as err:
    883             raise QAPIError(
    884                 f"can't read schema file '{fname}': {err.strerror}"
    885             ) from err
    886 
    887         exprs = check_exprs(parser.exprs)
    888         self.docs = parser.docs
    889         self._entity_list = []
    890         self._entity_dict = {}
    891         self._module_dict = OrderedDict()
    892         self._schema_dir = os.path.dirname(fname)
    893         self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
    894         self._make_module(fname)
    895         self._predefining = True
    896         self._def_predefineds()
    897         self._predefining = False
    898         self._def_exprs(exprs)
    899         self.check()
    900 
    901     def _def_entity(self, ent):
    902         # Only the predefined types are allowed to not have info
    903         assert ent.info or self._predefining
    904         self._entity_list.append(ent)
    905         if ent.name is None:
    906             return
    907         # TODO reject names that differ only in '_' vs. '.'  vs. '-',
    908         # because they're liable to clash in generated C.
    909         other_ent = self._entity_dict.get(ent.name)
    910         if other_ent:
    911             if other_ent.info:
    912                 where = QAPISourceError(other_ent.info, "previous definition")
    913                 raise QAPISemError(
    914                     ent.info,
    915                     "'%s' is already defined\n%s" % (ent.name, where))
    916             raise QAPISemError(
    917                 ent.info, "%s is already defined" % other_ent.describe())
    918         self._entity_dict[ent.name] = ent
    919 
    920     def lookup_entity(self, name, typ=None):
    921         ent = self._entity_dict.get(name)
    922         if typ and not isinstance(ent, typ):
    923             return None
    924         return ent
    925 
    926     def lookup_type(self, name):
    927         return self.lookup_entity(name, QAPISchemaType)
    928 
    929     def resolve_type(self, name, info, what):
    930         typ = self.lookup_type(name)
    931         if not typ:
    932             if callable(what):
    933                 what = what(info)
    934             raise QAPISemError(
    935                 info, "%s uses unknown type '%s'" % (what, name))
    936         return typ
    937 
    938     def _module_name(self, fname: str) -> str:
    939         if QAPISchemaModule.is_system_module(fname):
    940             return fname
    941         return os.path.relpath(fname, self._schema_dir)
    942 
    943     def _make_module(self, fname):
    944         name = self._module_name(fname)
    945         if name not in self._module_dict:
    946             self._module_dict[name] = QAPISchemaModule(name)
    947         return self._module_dict[name]
    948 
    949     def module_by_fname(self, fname):
    950         name = self._module_name(fname)
    951         return self._module_dict[name]
    952 
    953     def _def_include(self, expr, info, doc):
    954         include = expr['include']
    955         assert doc is None
    956         self._def_entity(QAPISchemaInclude(self._make_module(include), info))
    957 
    958     def _def_builtin_type(self, name, json_type, c_type):
    959         self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
    960         # Instantiating only the arrays that are actually used would
    961         # be nice, but we can't as long as their generated code
    962         # (qapi-builtin-types.[ch]) may be shared by some other
    963         # schema.
    964         self._make_array_type(name, None)
    965 
    966     def _def_predefineds(self):
    967         for t in [('str',    'string',  'char' + POINTER_SUFFIX),
    968                   ('number', 'number',  'double'),
    969                   ('int',    'int',     'int64_t'),
    970                   ('int8',   'int',     'int8_t'),
    971                   ('int16',  'int',     'int16_t'),
    972                   ('int32',  'int',     'int32_t'),
    973                   ('int64',  'int',     'int64_t'),
    974                   ('uint8',  'int',     'uint8_t'),
    975                   ('uint16', 'int',     'uint16_t'),
    976                   ('uint32', 'int',     'uint32_t'),
    977                   ('uint64', 'int',     'uint64_t'),
    978                   ('size',   'int',     'uint64_t'),
    979                   ('bool',   'boolean', 'bool'),
    980                   ('any',    'value',   'QObject' + POINTER_SUFFIX),
    981                   ('null',   'null',    'QNull' + POINTER_SUFFIX)]:
    982             self._def_builtin_type(*t)
    983         self.the_empty_object_type = QAPISchemaObjectType(
    984             'q_empty', None, None, None, None, None, [], None)
    985         self._def_entity(self.the_empty_object_type)
    986 
    987         qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
    988                   'qbool']
    989         qtype_values = self._make_enum_members(
    990             [{'name': n} for n in qtypes], None)
    991 
    992         self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
    993                                             qtype_values, 'QTYPE'))
    994 
    995     def _make_features(self, features, info):
    996         if features is None:
    997             return []
    998         return [QAPISchemaFeature(f['name'], info,
    999                                   QAPISchemaIfCond(f.get('if')))
   1000                 for f in features]
   1001 
   1002     def _make_enum_member(self, name, ifcond, features, info):
   1003         return QAPISchemaEnumMember(name, info,
   1004                                     QAPISchemaIfCond(ifcond),
   1005                                     self._make_features(features, info))
   1006 
   1007     def _make_enum_members(self, values, info):
   1008         return [self._make_enum_member(v['name'], v.get('if'),
   1009                                        v.get('features'), info)
   1010                 for v in values]
   1011 
   1012     def _make_array_type(self, element_type, info):
   1013         name = element_type + 'List'    # reserved by check_defn_name_str()
   1014         if not self.lookup_type(name):
   1015             self._def_entity(QAPISchemaArrayType(name, info, element_type))
   1016         return name
   1017 
   1018     def _make_implicit_object_type(self, name, info, ifcond, role, members):
   1019         if not members:
   1020             return None
   1021         # See also QAPISchemaObjectTypeMember.describe()
   1022         name = 'q_obj_%s-%s' % (name, role)
   1023         typ = self.lookup_entity(name, QAPISchemaObjectType)
   1024         if typ:
   1025             # The implicit object type has multiple users.  This can
   1026             # only be a duplicate definition, which will be flagged
   1027             # later.
   1028             pass
   1029         else:
   1030             self._def_entity(QAPISchemaObjectType(
   1031                 name, info, None, ifcond, None, None, members, None))
   1032         return name
   1033 
   1034     def _def_enum_type(self, expr, info, doc):
   1035         name = expr['enum']
   1036         data = expr['data']
   1037         prefix = expr.get('prefix')
   1038         ifcond = QAPISchemaIfCond(expr.get('if'))
   1039         features = self._make_features(expr.get('features'), info)
   1040         self._def_entity(QAPISchemaEnumType(
   1041             name, info, doc, ifcond, features,
   1042             self._make_enum_members(data, info), prefix))
   1043 
   1044     def _make_member(self, name, typ, ifcond, features, info):
   1045         optional = False
   1046         if name.startswith('*'):
   1047             name = name[1:]
   1048             optional = True
   1049         if isinstance(typ, list):
   1050             assert len(typ) == 1
   1051             typ = self._make_array_type(typ[0], info)
   1052         return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
   1053                                           self._make_features(features, info))
   1054 
   1055     def _make_members(self, data, info):
   1056         return [self._make_member(key, value['type'],
   1057                                   QAPISchemaIfCond(value.get('if')),
   1058                                   value.get('features'), info)
   1059                 for (key, value) in data.items()]
   1060 
   1061     def _def_struct_type(self, expr, info, doc):
   1062         name = expr['struct']
   1063         base = expr.get('base')
   1064         data = expr['data']
   1065         ifcond = QAPISchemaIfCond(expr.get('if'))
   1066         features = self._make_features(expr.get('features'), info)
   1067         self._def_entity(QAPISchemaObjectType(
   1068             name, info, doc, ifcond, features, base,
   1069             self._make_members(data, info),
   1070             None))
   1071 
   1072     def _make_variant(self, case, typ, ifcond, info):
   1073         if isinstance(typ, list):
   1074             assert len(typ) == 1
   1075             typ = self._make_array_type(typ[0], info)
   1076         return QAPISchemaVariant(case, info, typ, ifcond)
   1077 
   1078     def _def_union_type(self, expr, info, doc):
   1079         name = expr['union']
   1080         base = expr['base']
   1081         tag_name = expr['discriminator']
   1082         data = expr['data']
   1083         ifcond = QAPISchemaIfCond(expr.get('if'))
   1084         features = self._make_features(expr.get('features'), info)
   1085         if isinstance(base, dict):
   1086             base = self._make_implicit_object_type(
   1087                 name, info, ifcond,
   1088                 'base', self._make_members(base, info))
   1089         variants = [
   1090             self._make_variant(key, value['type'],
   1091                                QAPISchemaIfCond(value.get('if')),
   1092                                info)
   1093             for (key, value) in data.items()]
   1094         members = []
   1095         self._def_entity(
   1096             QAPISchemaObjectType(name, info, doc, ifcond, features,
   1097                                  base, members,
   1098                                  QAPISchemaVariants(
   1099                                      tag_name, info, None, variants)))
   1100 
   1101     def _def_alternate_type(self, expr, info, doc):
   1102         name = expr['alternate']
   1103         data = expr['data']
   1104         ifcond = QAPISchemaIfCond(expr.get('if'))
   1105         features = self._make_features(expr.get('features'), info)
   1106         variants = [
   1107             self._make_variant(key, value['type'],
   1108                                QAPISchemaIfCond(value.get('if')),
   1109                                info)
   1110             for (key, value) in data.items()]
   1111         tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
   1112         self._def_entity(
   1113             QAPISchemaAlternateType(name, info, doc, ifcond, features,
   1114                                     QAPISchemaVariants(
   1115                                         None, info, tag_member, variants)))
   1116 
   1117     def _def_command(self, expr, info, doc):
   1118         name = expr['command']
   1119         data = expr.get('data')
   1120         rets = expr.get('returns')
   1121         gen = expr.get('gen', True)
   1122         success_response = expr.get('success-response', True)
   1123         boxed = expr.get('boxed', False)
   1124         allow_oob = expr.get('allow-oob', False)
   1125         allow_preconfig = expr.get('allow-preconfig', False)
   1126         coroutine = expr.get('coroutine', False)
   1127         ifcond = QAPISchemaIfCond(expr.get('if'))
   1128         features = self._make_features(expr.get('features'), info)
   1129         if isinstance(data, OrderedDict):
   1130             data = self._make_implicit_object_type(
   1131                 name, info, ifcond,
   1132                 'arg', self._make_members(data, info))
   1133         if isinstance(rets, list):
   1134             assert len(rets) == 1
   1135             rets = self._make_array_type(rets[0], info)
   1136         self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, features,
   1137                                            data, rets,
   1138                                            gen, success_response,
   1139                                            boxed, allow_oob, allow_preconfig,
   1140                                            coroutine))
   1141 
   1142     def _def_event(self, expr, info, doc):
   1143         name = expr['event']
   1144         data = expr.get('data')
   1145         boxed = expr.get('boxed', False)
   1146         ifcond = QAPISchemaIfCond(expr.get('if'))
   1147         features = self._make_features(expr.get('features'), info)
   1148         if isinstance(data, OrderedDict):
   1149             data = self._make_implicit_object_type(
   1150                 name, info, ifcond,
   1151                 'arg', self._make_members(data, info))
   1152         self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, features,
   1153                                          data, boxed))
   1154 
   1155     def _def_exprs(self, exprs):
   1156         for expr_elem in exprs:
   1157             expr = expr_elem['expr']
   1158             info = expr_elem['info']
   1159             doc = expr_elem.get('doc')
   1160             if 'enum' in expr:
   1161                 self._def_enum_type(expr, info, doc)
   1162             elif 'struct' in expr:
   1163                 self._def_struct_type(expr, info, doc)
   1164             elif 'union' in expr:
   1165                 self._def_union_type(expr, info, doc)
   1166             elif 'alternate' in expr:
   1167                 self._def_alternate_type(expr, info, doc)
   1168             elif 'command' in expr:
   1169                 self._def_command(expr, info, doc)
   1170             elif 'event' in expr:
   1171                 self._def_event(expr, info, doc)
   1172             elif 'include' in expr:
   1173                 self._def_include(expr, info, doc)
   1174             else:
   1175                 assert False
   1176 
   1177     def check(self):
   1178         for ent in self._entity_list:
   1179             ent.check(self)
   1180             ent.connect_doc()
   1181             ent.check_doc()
   1182         for ent in self._entity_list:
   1183             ent.set_module(self)
   1184 
   1185     def visit(self, visitor):
   1186         visitor.visit_begin(self)
   1187         for mod in self._module_dict.values():
   1188             mod.visit(visitor)
   1189         visitor.visit_end()