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