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