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