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