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