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