xref: /qemu/scripts/qapi/schema.py (revision 32a1bb21c6f4d569427099e4e495f1d07d017fdb)
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# pylint: disable=too-many-lines
16
17# TODO catching name collisions in generated code would be nice
18
19from __future__ import annotations
20
21from abc import ABC, abstractmethod
22from collections import OrderedDict
23import os
24import re
25from typing import (
26    Any,
27    Callable,
28    Dict,
29    List,
30    Optional,
31    Union,
32    ValuesView,
33    cast,
34)
35
36from .common import (
37    POINTER_SUFFIX,
38    c_name,
39    cgen_ifcond,
40    docgen_ifcond,
41    gen_endif,
42    gen_if,
43)
44from .error import QAPIError, QAPISemError, QAPISourceError
45from .expr import check_exprs
46from .parser import QAPIDoc, QAPIExpression, QAPISchemaParser
47from .source import QAPISourceInfo
48
49
50class QAPISchemaIfCond:
51    def __init__(
52        self,
53        ifcond: Optional[Union[str, Dict[str, object]]] = None,
54    ) -> None:
55        self.ifcond = ifcond
56
57    def _cgen(self) -> str:
58        return cgen_ifcond(self.ifcond)
59
60    def gen_if(self) -> str:
61        return gen_if(self._cgen())
62
63    def gen_endif(self) -> str:
64        return gen_endif(self._cgen())
65
66    def docgen(self) -> str:
67        return docgen_ifcond(self.ifcond)
68
69    def is_present(self) -> bool:
70        return bool(self.ifcond)
71
72
73class QAPISchemaEntity:
74    """
75    A schema entity.
76
77    This is either a directive, such as include, or a definition.
78    The latter uses sub-class `QAPISchemaDefinition`.
79    """
80    def __init__(self, info: Optional[QAPISourceInfo]):
81        self._module: Optional[QAPISchemaModule] = None
82        # For explicitly defined entities, info points to the (explicit)
83        # definition.  For builtins (and their arrays), info is None.
84        # For implicitly defined entities, info points to a place that
85        # triggered the implicit definition (there may be more than one
86        # such place).
87        self.info = info
88        self._checked = False
89
90    def __repr__(self) -> str:
91        return "<%s at 0x%x>" % (type(self).__name__, id(self))
92
93    def check(self, schema: QAPISchema) -> None:
94        # pylint: disable=unused-argument
95        self._checked = True
96
97    def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None:
98        pass
99
100    def _set_module(
101        self, schema: QAPISchema, info: Optional[QAPISourceInfo]
102    ) -> None:
103        assert self._checked
104        fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
105        self._module = schema.module_by_fname(fname)
106        self._module.add_entity(self)
107
108    def set_module(self, schema: QAPISchema) -> None:
109        self._set_module(schema, self.info)
110
111    def visit(self, visitor: QAPISchemaVisitor) -> None:
112        # pylint: disable=unused-argument
113        assert self._checked
114
115
116class QAPISchemaDefinition(QAPISchemaEntity):
117    meta: str
118
119    def __init__(
120        self,
121        name: str,
122        info: Optional[QAPISourceInfo],
123        doc: Optional[QAPIDoc],
124        ifcond: Optional[QAPISchemaIfCond] = None,
125        features: Optional[List[QAPISchemaFeature]] = None,
126    ):
127        super().__init__(info)
128        for f in features or []:
129            f.set_defined_in(name)
130        self.name = name
131        self.doc = doc
132        self._ifcond = ifcond or QAPISchemaIfCond()
133        self.features = features or []
134
135    def __repr__(self) -> str:
136        return "<%s:%s at 0x%x>" % (type(self).__name__, self.name,
137                                    id(self))
138
139    def c_name(self) -> str:
140        return c_name(self.name)
141
142    def check(self, schema: QAPISchema) -> None:
143        assert not self._checked
144        super().check(schema)
145        seen: Dict[str, QAPISchemaMember] = {}
146        for f in self.features:
147            f.check_clash(self.info, seen)
148
149    def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None:
150        super().connect_doc(doc)
151        doc = doc or self.doc
152        if doc:
153            for f in self.features:
154                doc.connect_feature(f)
155
156    @property
157    def ifcond(self) -> QAPISchemaIfCond:
158        assert self._checked
159        return self._ifcond
160
161    def is_implicit(self) -> bool:
162        return not self.info
163
164    def describe(self) -> str:
165        return "%s '%s'" % (self.meta, self.name)
166
167
168class QAPISchemaVisitor:
169    def visit_begin(self, schema: QAPISchema) -> None:
170        pass
171
172    def visit_end(self) -> None:
173        pass
174
175    def visit_module(self, name: str) -> None:
176        pass
177
178    def visit_needed(self, entity: QAPISchemaEntity) -> bool:
179        # pylint: disable=unused-argument
180        # Default to visiting everything
181        return True
182
183    def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None:
184        pass
185
186    def visit_builtin_type(
187        self, name: str, info: Optional[QAPISourceInfo], json_type: str
188    ) -> None:
189        pass
190
191    def visit_enum_type(
192        self,
193        name: str,
194        info: Optional[QAPISourceInfo],
195        ifcond: QAPISchemaIfCond,
196        features: List[QAPISchemaFeature],
197        members: List[QAPISchemaEnumMember],
198        prefix: Optional[str],
199    ) -> None:
200        pass
201
202    def visit_array_type(
203        self,
204        name: str,
205        info: Optional[QAPISourceInfo],
206        ifcond: QAPISchemaIfCond,
207        element_type: QAPISchemaType,
208    ) -> None:
209        pass
210
211    def visit_object_type(
212        self,
213        name: str,
214        info: Optional[QAPISourceInfo],
215        ifcond: QAPISchemaIfCond,
216        features: List[QAPISchemaFeature],
217        base: Optional[QAPISchemaObjectType],
218        members: List[QAPISchemaObjectTypeMember],
219        branches: Optional[QAPISchemaBranches],
220    ) -> None:
221        pass
222
223    def visit_object_type_flat(
224        self,
225        name: str,
226        info: Optional[QAPISourceInfo],
227        ifcond: QAPISchemaIfCond,
228        features: List[QAPISchemaFeature],
229        members: List[QAPISchemaObjectTypeMember],
230        branches: Optional[QAPISchemaBranches],
231    ) -> None:
232        pass
233
234    def visit_alternate_type(
235        self,
236        name: str,
237        info: Optional[QAPISourceInfo],
238        ifcond: QAPISchemaIfCond,
239        features: List[QAPISchemaFeature],
240        alternatives: QAPISchemaAlternatives,
241    ) -> None:
242        pass
243
244    def visit_command(
245        self,
246        name: str,
247        info: Optional[QAPISourceInfo],
248        ifcond: QAPISchemaIfCond,
249        features: List[QAPISchemaFeature],
250        arg_type: Optional[QAPISchemaObjectType],
251        ret_type: Optional[QAPISchemaType],
252        gen: bool,
253        success_response: bool,
254        boxed: bool,
255        allow_oob: bool,
256        allow_preconfig: bool,
257        coroutine: bool,
258    ) -> None:
259        pass
260
261    def visit_event(
262        self,
263        name: str,
264        info: Optional[QAPISourceInfo],
265        ifcond: QAPISchemaIfCond,
266        features: List[QAPISchemaFeature],
267        arg_type: Optional[QAPISchemaObjectType],
268        boxed: bool,
269    ) -> None:
270        pass
271
272
273class QAPISchemaModule:
274
275    BUILTIN_MODULE_NAME = './builtin'
276
277    def __init__(self, name: str):
278        self.name = name
279        self._entity_list: List[QAPISchemaEntity] = []
280
281    @staticmethod
282    def is_system_module(name: str) -> bool:
283        """
284        System modules are internally defined modules.
285
286        Their names start with the "./" prefix.
287        """
288        return name.startswith('./')
289
290    @classmethod
291    def is_user_module(cls, name: str) -> bool:
292        """
293        User modules are those defined by the user in qapi JSON files.
294
295        They do not start with the "./" prefix.
296        """
297        return not cls.is_system_module(name)
298
299    @classmethod
300    def is_builtin_module(cls, name: str) -> bool:
301        """
302        The built-in module is a single System module for the built-in types.
303
304        It is always "./builtin".
305        """
306        return name == cls.BUILTIN_MODULE_NAME
307
308    def add_entity(self, ent: QAPISchemaEntity) -> None:
309        self._entity_list.append(ent)
310
311    def visit(self, visitor: QAPISchemaVisitor) -> None:
312        visitor.visit_module(self.name)
313        for entity in self._entity_list:
314            if visitor.visit_needed(entity):
315                entity.visit(visitor)
316
317
318class QAPISchemaInclude(QAPISchemaEntity):
319    def __init__(self, sub_module: QAPISchemaModule, info: QAPISourceInfo):
320        super().__init__(info)
321        self._sub_module = sub_module
322
323    def visit(self, visitor: QAPISchemaVisitor) -> None:
324        super().visit(visitor)
325        visitor.visit_include(self._sub_module.name, self.info)
326
327
328class QAPISchemaType(QAPISchemaDefinition, ABC):
329    # Return the C type for common use.
330    # For the types we commonly box, this is a pointer type.
331    @abstractmethod
332    def c_type(self) -> str:
333        pass
334
335    # Return the C type to be used in a parameter list.
336    def c_param_type(self) -> str:
337        return self.c_type()
338
339    # Return the C type to be used where we suppress boxing.
340    def c_unboxed_type(self) -> str:
341        return self.c_type()
342
343    @abstractmethod
344    def json_type(self) -> str:
345        pass
346
347    def alternate_qtype(self) -> Optional[str]:
348        json2qtype = {
349            'null':    'QTYPE_QNULL',
350            'string':  'QTYPE_QSTRING',
351            'number':  'QTYPE_QNUM',
352            'int':     'QTYPE_QNUM',
353            'boolean': 'QTYPE_QBOOL',
354            'array':   'QTYPE_QLIST',
355            'object':  'QTYPE_QDICT'
356        }
357        return json2qtype.get(self.json_type())
358
359    def doc_type(self) -> Optional[str]:
360        if self.is_implicit():
361            return None
362        return self.name
363
364    def need_has_if_optional(self) -> bool:
365        # When FOO is a pointer, has_FOO == !!FOO, i.e. has_FOO is redundant.
366        # Except for arrays; see QAPISchemaArrayType.need_has_if_optional().
367        return not self.c_type().endswith(POINTER_SUFFIX)
368
369    def check(self, schema: QAPISchema) -> None:
370        super().check(schema)
371        for feat in self.features:
372            if feat.is_special():
373                raise QAPISemError(
374                    self.info,
375                    f"feature '{feat.name}' is not supported for types")
376
377    def describe(self) -> str:
378        return "%s type '%s'" % (self.meta, self.name)
379
380
381class QAPISchemaBuiltinType(QAPISchemaType):
382    meta = 'built-in'
383
384    def __init__(self, name: str, json_type: str, c_type: str):
385        super().__init__(name, None, None)
386        assert json_type in ('string', 'number', 'int', 'boolean', 'null',
387                             'value')
388        self._json_type_name = json_type
389        self._c_type_name = c_type
390
391    def c_name(self) -> str:
392        return self.name
393
394    def c_type(self) -> str:
395        return self._c_type_name
396
397    def c_param_type(self) -> str:
398        if self.name == 'str':
399            return 'const ' + self._c_type_name
400        return self._c_type_name
401
402    def json_type(self) -> str:
403        return self._json_type_name
404
405    def doc_type(self) -> str:
406        return self.json_type()
407
408    def visit(self, visitor: QAPISchemaVisitor) -> None:
409        super().visit(visitor)
410        visitor.visit_builtin_type(self.name, self.info, self.json_type())
411
412
413class QAPISchemaEnumType(QAPISchemaType):
414    meta = 'enum'
415
416    def __init__(
417        self,
418        name: str,
419        info: Optional[QAPISourceInfo],
420        doc: Optional[QAPIDoc],
421        ifcond: Optional[QAPISchemaIfCond],
422        features: Optional[List[QAPISchemaFeature]],
423        members: List[QAPISchemaEnumMember],
424        prefix: Optional[str],
425    ):
426        super().__init__(name, info, doc, ifcond, features)
427        for m in members:
428            m.set_defined_in(name)
429        self.members = members
430        self.prefix = prefix
431
432    def check(self, schema: QAPISchema) -> None:
433        super().check(schema)
434        seen: Dict[str, QAPISchemaMember] = {}
435        for m in self.members:
436            m.check_clash(self.info, seen)
437
438    def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None:
439        super().connect_doc(doc)
440        doc = doc or self.doc
441        for m in self.members:
442            m.connect_doc(doc)
443
444    def is_implicit(self) -> bool:
445        # See QAPISchema._def_predefineds()
446        return self.name == 'QType'
447
448    def c_type(self) -> str:
449        return c_name(self.name)
450
451    def member_names(self) -> List[str]:
452        return [m.name for m in self.members]
453
454    def json_type(self) -> str:
455        return 'string'
456
457    def visit(self, visitor: QAPISchemaVisitor) -> None:
458        super().visit(visitor)
459        visitor.visit_enum_type(
460            self.name, self.info, self.ifcond, self.features,
461            self.members, self.prefix)
462
463
464class QAPISchemaArrayType(QAPISchemaType):
465    meta = 'array'
466
467    def __init__(
468        self, name: str, info: Optional[QAPISourceInfo], element_type: str
469    ):
470        super().__init__(name, info, None)
471        self._element_type_name = element_type
472        self.element_type: QAPISchemaType
473
474    def need_has_if_optional(self) -> bool:
475        # When FOO is an array, we still need has_FOO to distinguish
476        # absent (!has_FOO) from present and empty (has_FOO && !FOO).
477        return True
478
479    def check(self, schema: QAPISchema) -> None:
480        super().check(schema)
481        self.element_type = schema.resolve_type(
482            self._element_type_name, self.info,
483            self.info.defn_meta if self.info else None)
484        assert not isinstance(self.element_type, QAPISchemaArrayType)
485
486    def set_module(self, schema: QAPISchema) -> None:
487        self._set_module(schema, self.element_type.info)
488
489    @property
490    def ifcond(self) -> QAPISchemaIfCond:
491        assert self._checked
492        return self.element_type.ifcond
493
494    def is_implicit(self) -> bool:
495        return True
496
497    def c_type(self) -> str:
498        return c_name(self.name) + POINTER_SUFFIX
499
500    def json_type(self) -> str:
501        return 'array'
502
503    def doc_type(self) -> Optional[str]:
504        elt_doc_type = self.element_type.doc_type()
505        if not elt_doc_type:
506            return None
507        return 'array of ' + elt_doc_type
508
509    def visit(self, visitor: QAPISchemaVisitor) -> None:
510        super().visit(visitor)
511        visitor.visit_array_type(self.name, self.info, self.ifcond,
512                                 self.element_type)
513
514    def describe(self) -> str:
515        return "%s type ['%s']" % (self.meta, self._element_type_name)
516
517
518class QAPISchemaObjectType(QAPISchemaType):
519    def __init__(
520        self,
521        name: str,
522        info: Optional[QAPISourceInfo],
523        doc: Optional[QAPIDoc],
524        ifcond: Optional[QAPISchemaIfCond],
525        features: Optional[List[QAPISchemaFeature]],
526        base: Optional[str],
527        local_members: List[QAPISchemaObjectTypeMember],
528        branches: Optional[QAPISchemaBranches],
529    ):
530        # struct has local_members, optional base, and no branches
531        # union has base, branches, and no local_members
532        super().__init__(name, info, doc, ifcond, features)
533        self.meta = 'union' if branches else 'struct'
534        for m in local_members:
535            m.set_defined_in(name)
536        if branches is not None:
537            branches.set_defined_in(name)
538        self._base_name = base
539        self.base = None
540        self.local_members = local_members
541        self.branches = branches
542        self.members: List[QAPISchemaObjectTypeMember]
543        self._check_complete = False
544
545    def check(self, schema: QAPISchema) -> None:
546        # This calls another type T's .check() exactly when the C
547        # struct emitted by gen_object() contains that T's C struct
548        # (pointers don't count).
549        if self._check_complete:
550            # A previous .check() completed: nothing to do
551            return
552        if self._checked:
553            # Recursed: C struct contains itself
554            raise QAPISemError(self.info,
555                               "object %s contains itself" % self.name)
556
557        super().check(schema)
558        assert self._checked and not self._check_complete
559
560        seen = OrderedDict()
561        if self._base_name:
562            self.base = schema.resolve_type(self._base_name, self.info,
563                                            "'base'")
564            if (not isinstance(self.base, QAPISchemaObjectType)
565                    or self.base.branches):
566                raise QAPISemError(
567                    self.info,
568                    "'base' requires a struct type, %s isn't"
569                    % self.base.describe())
570            self.base.check(schema)
571            self.base.check_clash(self.info, seen)
572        for m in self.local_members:
573            m.check(schema)
574            m.check_clash(self.info, seen)
575
576        # self.check_clash() works in terms of the supertype, but
577        # self.members is declared List[QAPISchemaObjectTypeMember].
578        # Cast down to the subtype.
579        members = cast(List[QAPISchemaObjectTypeMember], list(seen.values()))
580
581        if self.branches:
582            self.branches.check(schema, seen)
583            self.branches.check_clash(self.info, seen)
584
585        self.members = members
586        self._check_complete = True  # mark completed
587
588    # Check that the members of this type do not cause duplicate JSON members,
589    # and update seen to track the members seen so far. Report any errors
590    # on behalf of info, which is not necessarily self.info
591    def check_clash(
592        self,
593        info: Optional[QAPISourceInfo],
594        seen: Dict[str, QAPISchemaMember],
595    ) -> None:
596        assert self._checked
597        for m in self.members:
598            m.check_clash(info, seen)
599        if self.branches:
600            self.branches.check_clash(info, seen)
601
602    def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None:
603        super().connect_doc(doc)
604        doc = doc or self.doc
605        if self.base and self.base.is_implicit():
606            self.base.connect_doc(doc)
607        for m in self.local_members:
608            m.connect_doc(doc)
609
610    def is_implicit(self) -> bool:
611        # See QAPISchema._make_implicit_object_type(), as well as
612        # _def_predefineds()
613        return self.name.startswith('q_')
614
615    def is_empty(self) -> bool:
616        return not self.members and not self.branches
617
618    def has_conditional_members(self) -> bool:
619        return any(m.ifcond.is_present() for m in self.members)
620
621    def c_name(self) -> str:
622        assert self.name != 'q_empty'
623        return super().c_name()
624
625    def c_type(self) -> str:
626        assert not self.is_implicit()
627        return c_name(self.name) + POINTER_SUFFIX
628
629    def c_unboxed_type(self) -> str:
630        return c_name(self.name)
631
632    def json_type(self) -> str:
633        return 'object'
634
635    def visit(self, visitor: QAPISchemaVisitor) -> None:
636        super().visit(visitor)
637        visitor.visit_object_type(
638            self.name, self.info, self.ifcond, self.features,
639            self.base, self.local_members, self.branches)
640        visitor.visit_object_type_flat(
641            self.name, self.info, self.ifcond, self.features,
642            self.members, self.branches)
643
644
645class QAPISchemaAlternateType(QAPISchemaType):
646    meta = 'alternate'
647
648    def __init__(
649        self,
650        name: str,
651        info: QAPISourceInfo,
652        doc: Optional[QAPIDoc],
653        ifcond: Optional[QAPISchemaIfCond],
654        features: List[QAPISchemaFeature],
655        alternatives: QAPISchemaAlternatives,
656    ):
657        super().__init__(name, info, doc, ifcond, features)
658        assert alternatives.tag_member
659        alternatives.set_defined_in(name)
660        alternatives.tag_member.set_defined_in(self.name)
661        self.alternatives = alternatives
662
663    def check(self, schema: QAPISchema) -> None:
664        super().check(schema)
665        self.alternatives.tag_member.check(schema)
666        # Not calling self.alternatives.check_clash(), because there's
667        # nothing to clash with
668        self.alternatives.check(schema, {})
669        # Alternate branch names have no relation to the tag enum values;
670        # so we have to check for potential name collisions ourselves.
671        seen: Dict[str, QAPISchemaMember] = {}
672        types_seen: Dict[str, str] = {}
673        for v in self.alternatives.variants:
674            v.check_clash(self.info, seen)
675            qtype = v.type.alternate_qtype()
676            if not qtype:
677                raise QAPISemError(
678                    self.info,
679                    "%s cannot use %s"
680                    % (v.describe(self.info), v.type.describe()))
681            conflicting = set([qtype])
682            if qtype == 'QTYPE_QSTRING':
683                if isinstance(v.type, QAPISchemaEnumType):
684                    for m in v.type.members:
685                        if m.name in ['on', 'off']:
686                            conflicting.add('QTYPE_QBOOL')
687                        if re.match(r'[-+0-9.]', m.name):
688                            # lazy, could be tightened
689                            conflicting.add('QTYPE_QNUM')
690                else:
691                    conflicting.add('QTYPE_QNUM')
692                    conflicting.add('QTYPE_QBOOL')
693            for qt in conflicting:
694                if qt in types_seen:
695                    raise QAPISemError(
696                        self.info,
697                        "%s can't be distinguished from '%s'"
698                        % (v.describe(self.info), types_seen[qt]))
699                types_seen[qt] = v.name
700
701    def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None:
702        super().connect_doc(doc)
703        doc = doc or self.doc
704        for v in self.alternatives.variants:
705            v.connect_doc(doc)
706
707    def c_type(self) -> str:
708        return c_name(self.name) + POINTER_SUFFIX
709
710    def json_type(self) -> str:
711        return 'value'
712
713    def visit(self, visitor: QAPISchemaVisitor) -> None:
714        super().visit(visitor)
715        visitor.visit_alternate_type(
716            self.name, self.info, self.ifcond, self.features,
717            self.alternatives)
718
719
720class QAPISchemaVariants:
721    def __init__(
722        self,
723        info: QAPISourceInfo,
724        variants: List[QAPISchemaVariant],
725    ):
726        self.info = info
727        self.tag_member: QAPISchemaObjectTypeMember
728        self.variants = variants
729
730    def set_defined_in(self, name: str) -> None:
731        for v in self.variants:
732            v.set_defined_in(name)
733
734    # pylint: disable=unused-argument
735    def check(
736            self, schema: QAPISchema, seen: Dict[str, QAPISchemaMember]
737    ) -> None:
738        for v in self.variants:
739            v.check(schema)
740
741
742class QAPISchemaBranches(QAPISchemaVariants):
743    def __init__(self,
744                 info: QAPISourceInfo,
745                 variants: List[QAPISchemaVariant],
746                 tag_name: str):
747        super().__init__(info, variants)
748        self._tag_name = tag_name
749
750    def check(
751            self, schema: QAPISchema, seen: Dict[str, QAPISchemaMember]
752    ) -> None:
753        # We need to narrow the member type:
754        tag_member = seen.get(c_name(self._tag_name))
755        assert (tag_member is None
756                or isinstance(tag_member, QAPISchemaObjectTypeMember))
757
758        base = "'base'"
759        # Pointing to the base type when not implicit would be
760        # nice, but we don't know it here
761        if not tag_member or self._tag_name != tag_member.name:
762            raise QAPISemError(
763                self.info,
764                "discriminator '%s' is not a member of %s"
765                % (self._tag_name, base))
766        self.tag_member = tag_member
767        # Here we do:
768        assert tag_member.defined_in
769        base_type = schema.lookup_type(tag_member.defined_in)
770        assert base_type
771        if not base_type.is_implicit():
772            base = "base type '%s'" % tag_member.defined_in
773        if not isinstance(tag_member.type, QAPISchemaEnumType):
774            raise QAPISemError(
775                self.info,
776                "discriminator member '%s' of %s must be of enum type"
777                % (self._tag_name, base))
778        if tag_member.optional:
779            raise QAPISemError(
780                self.info,
781                "discriminator member '%s' of %s must not be optional"
782                % (self._tag_name, base))
783        if tag_member.ifcond.is_present():
784            raise QAPISemError(
785                self.info,
786                "discriminator member '%s' of %s must not be conditional"
787                % (self._tag_name, base))
788        # branches that are not explicitly covered get an empty type
789        assert tag_member.defined_in
790        cases = {v.name for v in self.variants}
791        for m in tag_member.type.members:
792            if m.name not in cases:
793                v = QAPISchemaVariant(m.name, self.info,
794                                      'q_empty', m.ifcond)
795                v.set_defined_in(tag_member.defined_in)
796                self.variants.append(v)
797        if not self.variants:
798            raise QAPISemError(self.info, "union has no branches")
799        for v in self.variants:
800            v.check(schema)
801            # Union names must match enum values; alternate names are
802            # checked separately. Use 'seen' to tell the two apart.
803            if seen:
804                if v.name not in tag_member.type.member_names():
805                    raise QAPISemError(
806                        self.info,
807                        "branch '%s' is not a value of %s"
808                        % (v.name, tag_member.type.describe()))
809                if not isinstance(v.type, QAPISchemaObjectType):
810                    raise QAPISemError(
811                        self.info,
812                        "%s cannot use %s"
813                        % (v.describe(self.info), v.type.describe()))
814                v.type.check(schema)
815
816    def check_clash(
817        self,
818        info: Optional[QAPISourceInfo],
819        seen: Dict[str, QAPISchemaMember],
820    ) -> None:
821        for v in self.variants:
822            # Reset seen map for each variant, since qapi names from one
823            # branch do not affect another branch.
824            #
825            # v.type's typing is enforced in check() above.
826            assert isinstance(v.type, QAPISchemaObjectType)
827            v.type.check_clash(info, dict(seen))
828
829
830class QAPISchemaAlternatives(QAPISchemaVariants):
831    def __init__(self,
832                 info: QAPISourceInfo,
833                 variants: List[QAPISchemaVariant],
834                 tag_member: QAPISchemaObjectTypeMember):
835        super().__init__(info, variants)
836        self.tag_member = tag_member
837
838    def check(
839            self, schema: QAPISchema, seen: Dict[str, QAPISchemaMember]
840    ) -> None:
841        super().check(schema, seen)
842        assert isinstance(self.tag_member.type, QAPISchemaEnumType)
843        assert not self.tag_member.optional
844        assert not self.tag_member.ifcond.is_present()
845
846
847class QAPISchemaMember:
848    """ Represents object members, enum members and features """
849    role = 'member'
850
851    def __init__(
852        self,
853        name: str,
854        info: Optional[QAPISourceInfo],
855        ifcond: Optional[QAPISchemaIfCond] = None,
856    ):
857        self.name = name
858        self.info = info
859        self.ifcond = ifcond or QAPISchemaIfCond()
860        self.defined_in: Optional[str] = None
861
862    def set_defined_in(self, name: str) -> None:
863        assert not self.defined_in
864        self.defined_in = name
865
866    def check_clash(
867        self,
868        info: Optional[QAPISourceInfo],
869        seen: Dict[str, QAPISchemaMember],
870    ) -> None:
871        cname = c_name(self.name)
872        if cname in seen:
873            raise QAPISemError(
874                info,
875                "%s collides with %s"
876                % (self.describe(info), seen[cname].describe(info)))
877        seen[cname] = self
878
879    def connect_doc(self, doc: Optional[QAPIDoc]) -> None:
880        if doc:
881            doc.connect_member(self)
882
883    def describe(self, info: Optional[QAPISourceInfo]) -> str:
884        role = self.role
885        meta = 'type'
886        defined_in = self.defined_in
887        assert defined_in
888
889        if defined_in.startswith('q_obj_'):
890            # See QAPISchema._make_implicit_object_type() - reverse the
891            # mapping there to create a nice human-readable description
892            defined_in = defined_in[6:]
893            if defined_in.endswith('-arg'):
894                # Implicit type created for a command's dict 'data'
895                assert role == 'member'
896                role = 'parameter'
897                meta = 'command'
898                defined_in = defined_in[:-4]
899            elif defined_in.endswith('-base'):
900                # Implicit type created for a union's dict 'base'
901                role = 'base ' + role
902                defined_in = defined_in[:-5]
903            else:
904                assert False
905
906        assert info is not None
907        if defined_in != info.defn_name:
908            return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in)
909        return "%s '%s'" % (role, self.name)
910
911
912class QAPISchemaEnumMember(QAPISchemaMember):
913    role = 'value'
914
915    def __init__(
916        self,
917        name: str,
918        info: Optional[QAPISourceInfo],
919        ifcond: Optional[QAPISchemaIfCond] = None,
920        features: Optional[List[QAPISchemaFeature]] = None,
921    ):
922        super().__init__(name, info, ifcond)
923        for f in features or []:
924            f.set_defined_in(name)
925        self.features = features or []
926
927    def connect_doc(self, doc: Optional[QAPIDoc]) -> None:
928        super().connect_doc(doc)
929        if doc:
930            for f in self.features:
931                doc.connect_feature(f)
932
933
934class QAPISchemaFeature(QAPISchemaMember):
935    role = 'feature'
936
937    # Features which are standardized across all schemas
938    SPECIAL_NAMES = ['deprecated', 'unstable']
939
940    def is_special(self) -> bool:
941        return self.name in QAPISchemaFeature.SPECIAL_NAMES
942
943
944class QAPISchemaObjectTypeMember(QAPISchemaMember):
945    def __init__(
946        self,
947        name: str,
948        info: QAPISourceInfo,
949        typ: str,
950        optional: bool,
951        ifcond: Optional[QAPISchemaIfCond] = None,
952        features: Optional[List[QAPISchemaFeature]] = None,
953    ):
954        super().__init__(name, info, ifcond)
955        for f in features or []:
956            f.set_defined_in(name)
957        self._type_name = typ
958        self.type: QAPISchemaType  # set during check()
959        self.optional = optional
960        self.features = features or []
961
962    def need_has(self) -> bool:
963        return self.optional and self.type.need_has_if_optional()
964
965    def check(self, schema: QAPISchema) -> None:
966        assert self.defined_in
967        self.type = schema.resolve_type(self._type_name, self.info,
968                                        self.describe)
969        seen: Dict[str, QAPISchemaMember] = {}
970        for f in self.features:
971            f.check_clash(self.info, seen)
972
973    def connect_doc(self, doc: Optional[QAPIDoc]) -> None:
974        super().connect_doc(doc)
975        if doc:
976            for f in self.features:
977                doc.connect_feature(f)
978
979
980class QAPISchemaVariant(QAPISchemaObjectTypeMember):
981    role = 'branch'
982
983    def __init__(
984        self,
985        name: str,
986        info: QAPISourceInfo,
987        typ: str,
988        ifcond: QAPISchemaIfCond,
989    ):
990        super().__init__(name, info, typ, False, ifcond)
991
992
993class QAPISchemaCommand(QAPISchemaDefinition):
994    meta = 'command'
995
996    def __init__(
997        self,
998        name: str,
999        info: QAPISourceInfo,
1000        doc: Optional[QAPIDoc],
1001        ifcond: QAPISchemaIfCond,
1002        features: List[QAPISchemaFeature],
1003        arg_type: Optional[str],
1004        ret_type: Optional[str],
1005        gen: bool,
1006        success_response: bool,
1007        boxed: bool,
1008        allow_oob: bool,
1009        allow_preconfig: bool,
1010        coroutine: bool,
1011    ):
1012        super().__init__(name, info, doc, ifcond, features)
1013        self._arg_type_name = arg_type
1014        self.arg_type: Optional[QAPISchemaObjectType] = None
1015        self._ret_type_name = ret_type
1016        self.ret_type: Optional[QAPISchemaType] = None
1017        self.gen = gen
1018        self.success_response = success_response
1019        self.boxed = boxed
1020        self.allow_oob = allow_oob
1021        self.allow_preconfig = allow_preconfig
1022        self.coroutine = coroutine
1023
1024    def check(self, schema: QAPISchema) -> None:
1025        assert self.info is not None
1026        super().check(schema)
1027        if self._arg_type_name:
1028            arg_type = schema.resolve_type(
1029                self._arg_type_name, self.info, "command's 'data'")
1030            if not isinstance(arg_type, QAPISchemaObjectType):
1031                raise QAPISemError(
1032                    self.info,
1033                    "command's 'data' cannot take %s"
1034                    % arg_type.describe())
1035            self.arg_type = arg_type
1036            if self.arg_type.branches and not self.boxed:
1037                raise QAPISemError(
1038                    self.info,
1039                    "command's 'data' can take %s only with 'boxed': true"
1040                    % self.arg_type.describe())
1041            self.arg_type.check(schema)
1042            if self.arg_type.has_conditional_members() and not self.boxed:
1043                raise QAPISemError(
1044                    self.info,
1045                    "conditional command arguments require 'boxed': true")
1046        if self._ret_type_name:
1047            self.ret_type = schema.resolve_type(
1048                self._ret_type_name, self.info, "command's 'returns'")
1049            if self.name not in self.info.pragma.command_returns_exceptions:
1050                typ = self.ret_type
1051                if isinstance(typ, QAPISchemaArrayType):
1052                    typ = typ.element_type
1053                if not isinstance(typ, QAPISchemaObjectType):
1054                    raise QAPISemError(
1055                        self.info,
1056                        "command's 'returns' cannot take %s"
1057                        % self.ret_type.describe())
1058
1059    def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None:
1060        super().connect_doc(doc)
1061        doc = doc or self.doc
1062        if doc:
1063            if self.arg_type and self.arg_type.is_implicit():
1064                self.arg_type.connect_doc(doc)
1065
1066    def visit(self, visitor: QAPISchemaVisitor) -> None:
1067        super().visit(visitor)
1068        visitor.visit_command(
1069            self.name, self.info, self.ifcond, self.features,
1070            self.arg_type, self.ret_type, self.gen, self.success_response,
1071            self.boxed, self.allow_oob, self.allow_preconfig,
1072            self.coroutine)
1073
1074
1075class QAPISchemaEvent(QAPISchemaDefinition):
1076    meta = 'event'
1077
1078    def __init__(
1079        self,
1080        name: str,
1081        info: QAPISourceInfo,
1082        doc: Optional[QAPIDoc],
1083        ifcond: QAPISchemaIfCond,
1084        features: List[QAPISchemaFeature],
1085        arg_type: Optional[str],
1086        boxed: bool,
1087    ):
1088        super().__init__(name, info, doc, ifcond, features)
1089        self._arg_type_name = arg_type
1090        self.arg_type: Optional[QAPISchemaObjectType] = None
1091        self.boxed = boxed
1092
1093    def check(self, schema: QAPISchema) -> None:
1094        super().check(schema)
1095        if self._arg_type_name:
1096            typ = schema.resolve_type(
1097                self._arg_type_name, self.info, "event's 'data'")
1098            if not isinstance(typ, QAPISchemaObjectType):
1099                raise QAPISemError(
1100                    self.info,
1101                    "event's 'data' cannot take %s"
1102                    % typ.describe())
1103            self.arg_type = typ
1104            if self.arg_type.branches and not self.boxed:
1105                raise QAPISemError(
1106                    self.info,
1107                    "event's 'data' can take %s only with 'boxed': true"
1108                    % self.arg_type.describe())
1109            self.arg_type.check(schema)
1110            if self.arg_type.has_conditional_members() and not self.boxed:
1111                raise QAPISemError(
1112                    self.info,
1113                    "conditional event arguments require 'boxed': true")
1114
1115    def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None:
1116        super().connect_doc(doc)
1117        doc = doc or self.doc
1118        if doc:
1119            if self.arg_type and self.arg_type.is_implicit():
1120                self.arg_type.connect_doc(doc)
1121
1122    def visit(self, visitor: QAPISchemaVisitor) -> None:
1123        super().visit(visitor)
1124        visitor.visit_event(
1125            self.name, self.info, self.ifcond, self.features,
1126            self.arg_type, self.boxed)
1127
1128
1129class QAPISchema:
1130    def __init__(self, fname: str):
1131        self.fname = fname
1132
1133        try:
1134            parser = QAPISchemaParser(fname)
1135        except OSError as err:
1136            raise QAPIError(
1137                f"can't read schema file '{fname}': {err.strerror}"
1138            ) from err
1139
1140        exprs = check_exprs(parser.exprs)
1141        self.docs = parser.docs
1142        self._entity_list: List[QAPISchemaEntity] = []
1143        self._entity_dict: Dict[str, QAPISchemaDefinition] = {}
1144        self._module_dict: Dict[str, QAPISchemaModule] = OrderedDict()
1145        # NB, values in the dict will identify the first encountered
1146        # usage of a named feature only
1147        self._feature_dict: Dict[str, QAPISchemaFeature] = OrderedDict()
1148
1149        # All schemas get the names defined in the QapiSpecialFeature enum.
1150        # Rely on dict iteration order matching insertion order so that
1151        # the special names are emitted first when generating code.
1152        for f in QAPISchemaFeature.SPECIAL_NAMES:
1153            self._feature_dict[f] = QAPISchemaFeature(f, None)
1154
1155        self._schema_dir = os.path.dirname(fname)
1156        self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
1157        self._make_module(fname)
1158        self._predefining = True
1159        self._def_predefineds()
1160        self._predefining = False
1161        self._def_exprs(exprs)
1162        self.check()
1163
1164    def features(self) -> ValuesView[QAPISchemaFeature]:
1165        return self._feature_dict.values()
1166
1167    def _def_entity(self, ent: QAPISchemaEntity) -> None:
1168        self._entity_list.append(ent)
1169
1170    def _def_definition(self, defn: QAPISchemaDefinition) -> None:
1171        # Only the predefined types are allowed to not have info
1172        assert defn.info or self._predefining
1173        self._def_entity(defn)
1174        # TODO reject names that differ only in '_' vs. '.'  vs. '-',
1175        # because they're liable to clash in generated C.
1176        other_defn = self._entity_dict.get(defn.name)
1177        if other_defn:
1178            if other_defn.info:
1179                where = QAPISourceError(other_defn.info, "previous definition")
1180                raise QAPISemError(
1181                    defn.info,
1182                    "'%s' is already defined\n%s" % (defn.name, where))
1183            raise QAPISemError(
1184                defn.info, "%s is already defined" % other_defn.describe())
1185        self._entity_dict[defn.name] = defn
1186
1187    def lookup_entity(self, name: str) -> Optional[QAPISchemaEntity]:
1188        return self._entity_dict.get(name)
1189
1190    def lookup_type(self, name: str) -> Optional[QAPISchemaType]:
1191        typ = self.lookup_entity(name)
1192        if isinstance(typ, QAPISchemaType):
1193            return typ
1194        return None
1195
1196    def resolve_type(
1197        self,
1198        name: str,
1199        info: Optional[QAPISourceInfo],
1200        what: Union[None, str, Callable[[QAPISourceInfo], str]],
1201    ) -> QAPISchemaType:
1202        typ = self.lookup_type(name)
1203        if not typ:
1204            assert info and what  # built-in types must not fail lookup
1205            if callable(what):
1206                what = what(info)
1207            raise QAPISemError(
1208                info, "%s uses unknown type '%s'" % (what, name))
1209        return typ
1210
1211    def _module_name(self, fname: str) -> str:
1212        if QAPISchemaModule.is_system_module(fname):
1213            return fname
1214        return os.path.relpath(fname, self._schema_dir)
1215
1216    def _make_module(self, fname: str) -> QAPISchemaModule:
1217        name = self._module_name(fname)
1218        if name not in self._module_dict:
1219            self._module_dict[name] = QAPISchemaModule(name)
1220        return self._module_dict[name]
1221
1222    def module_by_fname(self, fname: str) -> QAPISchemaModule:
1223        name = self._module_name(fname)
1224        return self._module_dict[name]
1225
1226    def _def_include(self, expr: QAPIExpression) -> None:
1227        include = expr['include']
1228        assert expr.doc is None
1229        self._def_entity(
1230            QAPISchemaInclude(self._make_module(include), expr.info))
1231
1232    def _def_builtin_type(
1233        self, name: str, json_type: str, c_type: str
1234    ) -> None:
1235        self._def_definition(QAPISchemaBuiltinType(name, json_type, c_type))
1236        # Instantiating only the arrays that are actually used would
1237        # be nice, but we can't as long as their generated code
1238        # (qapi-builtin-types.[ch]) may be shared by some other
1239        # schema.
1240        self._make_array_type(name, None)
1241
1242    def _def_predefineds(self) -> None:
1243        for t in [('str',    'string',  'char' + POINTER_SUFFIX),
1244                  ('number', 'number',  'double'),
1245                  ('int',    'int',     'int64_t'),
1246                  ('int8',   'int',     'int8_t'),
1247                  ('int16',  'int',     'int16_t'),
1248                  ('int32',  'int',     'int32_t'),
1249                  ('int64',  'int',     'int64_t'),
1250                  ('uint8',  'int',     'uint8_t'),
1251                  ('uint16', 'int',     'uint16_t'),
1252                  ('uint32', 'int',     'uint32_t'),
1253                  ('uint64', 'int',     'uint64_t'),
1254                  ('size',   'int',     'uint64_t'),
1255                  ('bool',   'boolean', 'bool'),
1256                  ('any',    'value',   'QObject' + POINTER_SUFFIX),
1257                  ('null',   'null',    'QNull' + POINTER_SUFFIX)]:
1258            self._def_builtin_type(*t)
1259        self.the_empty_object_type = QAPISchemaObjectType(
1260            'q_empty', None, None, None, None, None, [], None)
1261        self._def_definition(self.the_empty_object_type)
1262
1263        qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1264                  'qbool']
1265        qtype_values = self._make_enum_members(
1266            [{'name': n} for n in qtypes], None)
1267
1268        self._def_definition(QAPISchemaEnumType(
1269            'QType', None, None, None, None, qtype_values, None))
1270
1271    def _make_features(
1272        self,
1273        features: Optional[List[Dict[str, Any]]],
1274        info: Optional[QAPISourceInfo],
1275    ) -> List[QAPISchemaFeature]:
1276        if features is None:
1277            return []
1278
1279        for f in features:
1280            feat = QAPISchemaFeature(f['name'], info)
1281            if feat.name not in self._feature_dict:
1282                self._feature_dict[feat.name] = feat
1283
1284        return [QAPISchemaFeature(f['name'], info,
1285                                  QAPISchemaIfCond(f.get('if')))
1286                for f in features]
1287
1288    def _make_enum_member(
1289        self,
1290        name: str,
1291        ifcond: Optional[Union[str, Dict[str, Any]]],
1292        features: Optional[List[Dict[str, Any]]],
1293        info: Optional[QAPISourceInfo],
1294    ) -> QAPISchemaEnumMember:
1295        return QAPISchemaEnumMember(name, info,
1296                                    QAPISchemaIfCond(ifcond),
1297                                    self._make_features(features, info))
1298
1299    def _make_enum_members(
1300        self, values: List[Dict[str, Any]], info: Optional[QAPISourceInfo]
1301    ) -> List[QAPISchemaEnumMember]:
1302        return [self._make_enum_member(v['name'], v.get('if'),
1303                                       v.get('features'), info)
1304                for v in values]
1305
1306    def _make_array_type(
1307        self, element_type: str, info: Optional[QAPISourceInfo]
1308    ) -> str:
1309        name = element_type + 'List'    # reserved by check_defn_name_str()
1310        if not self.lookup_type(name):
1311            self._def_definition(QAPISchemaArrayType(
1312                name, info, element_type))
1313        return name
1314
1315    def _make_implicit_object_type(
1316        self,
1317        name: str,
1318        info: QAPISourceInfo,
1319        ifcond: QAPISchemaIfCond,
1320        role: str,
1321        members: List[QAPISchemaObjectTypeMember],
1322    ) -> Optional[str]:
1323        if not members:
1324            return None
1325        # See also QAPISchemaObjectTypeMember.describe()
1326        name = 'q_obj_%s-%s' % (name, role)
1327        typ = self.lookup_entity(name)
1328        if typ:
1329            assert isinstance(typ, QAPISchemaObjectType)
1330            # The implicit object type has multiple users.  This can
1331            # only be a duplicate definition, which will be flagged
1332            # later.
1333        else:
1334            self._def_definition(QAPISchemaObjectType(
1335                name, info, None, ifcond, None, None, members, None))
1336        return name
1337
1338    def _def_enum_type(self, expr: QAPIExpression) -> None:
1339        name = expr['enum']
1340        data = expr['data']
1341        prefix = expr.get('prefix')
1342        ifcond = QAPISchemaIfCond(expr.get('if'))
1343        info = expr.info
1344        features = self._make_features(expr.get('features'), info)
1345        self._def_definition(QAPISchemaEnumType(
1346            name, info, expr.doc, ifcond, features,
1347            self._make_enum_members(data, info), prefix))
1348
1349    def _make_member(
1350        self,
1351        name: str,
1352        typ: Union[List[str], str],
1353        ifcond: QAPISchemaIfCond,
1354        features: Optional[List[Dict[str, Any]]],
1355        info: QAPISourceInfo,
1356    ) -> QAPISchemaObjectTypeMember:
1357        optional = False
1358        if name.startswith('*'):
1359            name = name[1:]
1360            optional = True
1361        if isinstance(typ, list):
1362            assert len(typ) == 1
1363            typ = self._make_array_type(typ[0], info)
1364        return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1365                                          self._make_features(features, info))
1366
1367    def _make_members(
1368        self,
1369        data: Dict[str, Any],
1370        info: QAPISourceInfo,
1371    ) -> List[QAPISchemaObjectTypeMember]:
1372        return [self._make_member(key, value['type'],
1373                                  QAPISchemaIfCond(value.get('if')),
1374                                  value.get('features'), info)
1375                for (key, value) in data.items()]
1376
1377    def _def_struct_type(self, expr: QAPIExpression) -> None:
1378        name = expr['struct']
1379        base = expr.get('base')
1380        data = expr['data']
1381        info = expr.info
1382        ifcond = QAPISchemaIfCond(expr.get('if'))
1383        features = self._make_features(expr.get('features'), info)
1384        self._def_definition(QAPISchemaObjectType(
1385            name, info, expr.doc, ifcond, features, base,
1386            self._make_members(data, info),
1387            None))
1388
1389    def _make_variant(
1390        self,
1391        case: str,
1392        typ: str,
1393        ifcond: QAPISchemaIfCond,
1394        info: QAPISourceInfo,
1395    ) -> QAPISchemaVariant:
1396        if isinstance(typ, list):
1397            assert len(typ) == 1
1398            typ = self._make_array_type(typ[0], info)
1399        return QAPISchemaVariant(case, info, typ, ifcond)
1400
1401    def _def_union_type(self, expr: QAPIExpression) -> None:
1402        name = expr['union']
1403        base = expr['base']
1404        tag_name = expr['discriminator']
1405        data = expr['data']
1406        assert isinstance(data, dict)
1407        info = expr.info
1408        ifcond = QAPISchemaIfCond(expr.get('if'))
1409        features = self._make_features(expr.get('features'), info)
1410        if isinstance(base, dict):
1411            base = self._make_implicit_object_type(
1412                name, info, ifcond,
1413                'base', self._make_members(base, info))
1414        variants = [
1415            self._make_variant(key, value['type'],
1416                               QAPISchemaIfCond(value.get('if')),
1417                               info)
1418            for (key, value) in data.items()]
1419        members: List[QAPISchemaObjectTypeMember] = []
1420        self._def_definition(
1421            QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1422                                 base, members,
1423                                 QAPISchemaBranches(
1424                                     info, variants, tag_name)))
1425
1426    def _def_alternate_type(self, expr: QAPIExpression) -> None:
1427        name = expr['alternate']
1428        data = expr['data']
1429        assert isinstance(data, dict)
1430        ifcond = QAPISchemaIfCond(expr.get('if'))
1431        info = expr.info
1432        features = self._make_features(expr.get('features'), info)
1433        variants = [
1434            self._make_variant(key, value['type'],
1435                               QAPISchemaIfCond(value.get('if')),
1436                               info)
1437            for (key, value) in data.items()]
1438        tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1439        self._def_definition(
1440            QAPISchemaAlternateType(
1441                name, info, expr.doc, ifcond, features,
1442                QAPISchemaAlternatives(info, variants, tag_member)))
1443
1444    def _def_command(self, expr: QAPIExpression) -> None:
1445        name = expr['command']
1446        data = expr.get('data')
1447        rets = expr.get('returns')
1448        gen = expr.get('gen', True)
1449        success_response = expr.get('success-response', True)
1450        boxed = expr.get('boxed', False)
1451        allow_oob = expr.get('allow-oob', False)
1452        allow_preconfig = expr.get('allow-preconfig', False)
1453        coroutine = expr.get('coroutine', False)
1454        ifcond = QAPISchemaIfCond(expr.get('if'))
1455        info = expr.info
1456        features = self._make_features(expr.get('features'), info)
1457        if isinstance(data, OrderedDict):
1458            data = self._make_implicit_object_type(
1459                name, info, ifcond,
1460                'arg', self._make_members(data, info))
1461        if isinstance(rets, list):
1462            assert len(rets) == 1
1463            rets = self._make_array_type(rets[0], info)
1464        self._def_definition(
1465            QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
1466                              rets, gen, success_response, boxed, allow_oob,
1467                              allow_preconfig, coroutine))
1468
1469    def _def_event(self, expr: QAPIExpression) -> None:
1470        name = expr['event']
1471        data = expr.get('data')
1472        boxed = expr.get('boxed', False)
1473        ifcond = QAPISchemaIfCond(expr.get('if'))
1474        info = expr.info
1475        features = self._make_features(expr.get('features'), info)
1476        if isinstance(data, OrderedDict):
1477            data = self._make_implicit_object_type(
1478                name, info, ifcond,
1479                'arg', self._make_members(data, info))
1480        self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
1481                                             features, data, boxed))
1482
1483    def _def_exprs(self, exprs: List[QAPIExpression]) -> None:
1484        for expr in exprs:
1485            if 'enum' in expr:
1486                self._def_enum_type(expr)
1487            elif 'struct' in expr:
1488                self._def_struct_type(expr)
1489            elif 'union' in expr:
1490                self._def_union_type(expr)
1491            elif 'alternate' in expr:
1492                self._def_alternate_type(expr)
1493            elif 'command' in expr:
1494                self._def_command(expr)
1495            elif 'event' in expr:
1496                self._def_event(expr)
1497            elif 'include' in expr:
1498                self._def_include(expr)
1499            else:
1500                assert False
1501
1502    def check(self) -> None:
1503        for ent in self._entity_list:
1504            ent.check(self)
1505            ent.connect_doc()
1506        for ent in self._entity_list:
1507            ent.set_module(self)
1508        for doc in self.docs:
1509            doc.check()
1510
1511        features = list(self._feature_dict.values())
1512        if len(features) > 64:
1513            raise QAPISemError(
1514                features[64].info,
1515                "Maximum of 64 schema features is permitted")
1516
1517    def visit(self, visitor: QAPISchemaVisitor) -> None:
1518        visitor.visit_begin(self)
1519        for mod in self._module_dict.values():
1520            mod.visit(visitor)
1521        visitor.visit_end()
1522