xref: /openbmc/qemu/scripts/qapi/schema.py (revision 583f4d6fdd96f0f25f39018a9b160654e3f9aa01)
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: Optional[QAPISchemaObjectTypeMember] = tag_member
631        self.variants = variants
632
633    @property
634    def tag_member(self) -> 'QAPISchemaObjectTypeMember':
635        if self._tag_member is None:
636            raise RuntimeError(
637                "QAPISchemaVariants has no tag_member property until "
638                "after check() has been run."
639            )
640        return self._tag_member
641
642    def set_defined_in(self, name):
643        for v in self.variants:
644            v.set_defined_in(name)
645
646    def check(self, schema, seen):
647        if self._tag_name:      # union
648            # We need to narrow the member type:
649            tmp = seen.get(c_name(self._tag_name))
650            assert tmp is None or isinstance(tmp, QAPISchemaObjectTypeMember)
651            self._tag_member = tmp
652
653            base = "'base'"
654            # Pointing to the base type when not implicit would be
655            # nice, but we don't know it here
656            if not self._tag_member or self._tag_name != self._tag_member.name:
657                raise QAPISemError(
658                    self.info,
659                    "discriminator '%s' is not a member of %s"
660                    % (self._tag_name, base))
661            # Here we do:
662            assert self.tag_member.defined_in
663            base_type = schema.lookup_type(self.tag_member.defined_in)
664            assert base_type
665            if not base_type.is_implicit():
666                base = "base type '%s'" % self.tag_member.defined_in
667            if not isinstance(self.tag_member.type, QAPISchemaEnumType):
668                raise QAPISemError(
669                    self.info,
670                    "discriminator member '%s' of %s must be of enum type"
671                    % (self._tag_name, base))
672            if self.tag_member.optional:
673                raise QAPISemError(
674                    self.info,
675                    "discriminator member '%s' of %s must not be optional"
676                    % (self._tag_name, base))
677            if self.tag_member.ifcond.is_present():
678                raise QAPISemError(
679                    self.info,
680                    "discriminator member '%s' of %s must not be conditional"
681                    % (self._tag_name, base))
682        else:                   # alternate
683            assert self._tag_member
684            assert isinstance(self.tag_member.type, QAPISchemaEnumType)
685            assert not self.tag_member.optional
686            assert not self.tag_member.ifcond.is_present()
687        if self._tag_name:      # union
688            # branches that are not explicitly covered get an empty type
689            assert self.tag_member.defined_in
690            cases = {v.name for v in self.variants}
691            for m in self.tag_member.type.members:
692                if m.name not in cases:
693                    v = QAPISchemaVariant(m.name, self.info,
694                                          'q_empty', m.ifcond)
695                    v.set_defined_in(self.tag_member.defined_in)
696                    self.variants.append(v)
697        if not self.variants:
698            raise QAPISemError(self.info, "union has no branches")
699        for v in self.variants:
700            v.check(schema)
701            # Union names must match enum values; alternate names are
702            # checked separately. Use 'seen' to tell the two apart.
703            if seen:
704                if v.name not in self.tag_member.type.member_names():
705                    raise QAPISemError(
706                        self.info,
707                        "branch '%s' is not a value of %s"
708                        % (v.name, self.tag_member.type.describe()))
709                if not isinstance(v.type, QAPISchemaObjectType):
710                    raise QAPISemError(
711                        self.info,
712                        "%s cannot use %s"
713                        % (v.describe(self.info), v.type.describe()))
714                v.type.check(schema)
715
716    def check_clash(self, info, seen):
717        for v in self.variants:
718            # Reset seen map for each variant, since qapi names from one
719            # branch do not affect another branch
720            v.type.check_clash(info, dict(seen))
721
722
723class QAPISchemaMember:
724    """ Represents object members, enum members and features """
725    role = 'member'
726
727    def __init__(self, name, info, ifcond=None):
728        assert isinstance(name, str)
729        self.name = name
730        self.info = info
731        self.ifcond = ifcond or QAPISchemaIfCond()
732        self.defined_in = None
733
734    def set_defined_in(self, name):
735        assert not self.defined_in
736        self.defined_in = name
737
738    def check_clash(self, info, seen):
739        cname = c_name(self.name)
740        if cname in seen:
741            raise QAPISemError(
742                info,
743                "%s collides with %s"
744                % (self.describe(info), seen[cname].describe(info)))
745        seen[cname] = self
746
747    def connect_doc(self, doc):
748        if doc:
749            doc.connect_member(self)
750
751    def describe(self, info):
752        role = self.role
753        meta = 'type'
754        defined_in = self.defined_in
755        assert defined_in
756
757        if defined_in.startswith('q_obj_'):
758            # See QAPISchema._make_implicit_object_type() - reverse the
759            # mapping there to create a nice human-readable description
760            defined_in = defined_in[6:]
761            if defined_in.endswith('-arg'):
762                # Implicit type created for a command's dict 'data'
763                assert role == 'member'
764                role = 'parameter'
765                meta = 'command'
766                defined_in = defined_in[:-4]
767            elif defined_in.endswith('-base'):
768                # Implicit type created for a union's dict 'base'
769                role = 'base ' + role
770                defined_in = defined_in[:-5]
771            else:
772                assert False
773
774        assert info is not None
775        if defined_in != info.defn_name:
776            return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in)
777        return "%s '%s'" % (role, self.name)
778
779
780class QAPISchemaEnumMember(QAPISchemaMember):
781    role = 'value'
782
783    def __init__(self, name, info, ifcond=None, features=None):
784        super().__init__(name, info, ifcond)
785        for f in features or []:
786            assert isinstance(f, QAPISchemaFeature)
787            f.set_defined_in(name)
788        self.features = features or []
789
790    def connect_doc(self, doc):
791        super().connect_doc(doc)
792        if doc:
793            for f in self.features:
794                doc.connect_feature(f)
795
796
797class QAPISchemaFeature(QAPISchemaMember):
798    role = 'feature'
799
800    def is_special(self):
801        return self.name in ('deprecated', 'unstable')
802
803
804class QAPISchemaObjectTypeMember(QAPISchemaMember):
805    def __init__(self, name, info, typ, optional, ifcond=None, features=None):
806        super().__init__(name, info, ifcond)
807        assert isinstance(typ, str)
808        assert isinstance(optional, bool)
809        for f in features or []:
810            assert isinstance(f, QAPISchemaFeature)
811            f.set_defined_in(name)
812        self._type_name = typ
813        self.type: QAPISchemaType  # set during check()
814        self.optional = optional
815        self.features = features or []
816
817    def need_has(self):
818        assert self.type
819        return self.optional and self.type.need_has_if_optional()
820
821    def check(self, schema):
822        assert self.defined_in
823        self.type = schema.resolve_type(self._type_name, self.info,
824                                        self.describe)
825        seen = {}
826        for f in self.features:
827            f.check_clash(self.info, seen)
828
829    def connect_doc(self, doc):
830        super().connect_doc(doc)
831        if doc:
832            for f in self.features:
833                doc.connect_feature(f)
834
835
836class QAPISchemaVariant(QAPISchemaObjectTypeMember):
837    role = 'branch'
838
839    def __init__(self, name, info, typ, ifcond=None):
840        super().__init__(name, info, typ, False, ifcond)
841
842
843class QAPISchemaCommand(QAPISchemaDefinition):
844    meta = 'command'
845
846    def __init__(self, name, info, doc, ifcond, features,
847                 arg_type, ret_type,
848                 gen, success_response, boxed, allow_oob, allow_preconfig,
849                 coroutine):
850        super().__init__(name, info, doc, ifcond, features)
851        assert not arg_type or isinstance(arg_type, str)
852        assert not ret_type or isinstance(ret_type, str)
853        self._arg_type_name = arg_type
854        self.arg_type = None
855        self._ret_type_name = ret_type
856        self.ret_type = None
857        self.gen = gen
858        self.success_response = success_response
859        self.boxed = boxed
860        self.allow_oob = allow_oob
861        self.allow_preconfig = allow_preconfig
862        self.coroutine = coroutine
863
864    def check(self, schema):
865        assert self.info is not None
866        super().check(schema)
867        if self._arg_type_name:
868            arg_type = schema.resolve_type(
869                self._arg_type_name, self.info, "command's 'data'")
870            if not isinstance(arg_type, QAPISchemaObjectType):
871                raise QAPISemError(
872                    self.info,
873                    "command's 'data' cannot take %s"
874                    % arg_type.describe())
875            self.arg_type = arg_type
876            if self.arg_type.variants and not self.boxed:
877                raise QAPISemError(
878                    self.info,
879                    "command's 'data' can take %s only with 'boxed': true"
880                    % self.arg_type.describe())
881            self.arg_type.check(schema)
882            if self.arg_type.has_conditional_members() and not self.boxed:
883                raise QAPISemError(
884                    self.info,
885                    "conditional command arguments require 'boxed': true")
886        if self._ret_type_name:
887            self.ret_type = schema.resolve_type(
888                self._ret_type_name, self.info, "command's 'returns'")
889            if self.name not in self.info.pragma.command_returns_exceptions:
890                typ = self.ret_type
891                if isinstance(typ, QAPISchemaArrayType):
892                    assert typ
893                    typ = typ.element_type
894                if not isinstance(typ, QAPISchemaObjectType):
895                    raise QAPISemError(
896                        self.info,
897                        "command's 'returns' cannot take %s"
898                        % self.ret_type.describe())
899
900    def connect_doc(self, doc=None):
901        super().connect_doc(doc)
902        doc = doc or self.doc
903        if doc:
904            if self.arg_type and self.arg_type.is_implicit():
905                self.arg_type.connect_doc(doc)
906
907    def visit(self, visitor):
908        super().visit(visitor)
909        visitor.visit_command(
910            self.name, self.info, self.ifcond, self.features,
911            self.arg_type, self.ret_type, self.gen, self.success_response,
912            self.boxed, self.allow_oob, self.allow_preconfig,
913            self.coroutine)
914
915
916class QAPISchemaEvent(QAPISchemaDefinition):
917    meta = 'event'
918
919    def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
920        super().__init__(name, info, doc, ifcond, features)
921        assert not arg_type or isinstance(arg_type, str)
922        self._arg_type_name = arg_type
923        self.arg_type = None
924        self.boxed = boxed
925
926    def check(self, schema):
927        super().check(schema)
928        if self._arg_type_name:
929            typ = schema.resolve_type(
930                self._arg_type_name, self.info, "event's 'data'")
931            if not isinstance(typ, QAPISchemaObjectType):
932                raise QAPISemError(
933                    self.info,
934                    "event's 'data' cannot take %s"
935                    % typ.describe())
936            self.arg_type = typ
937            if self.arg_type.variants and not self.boxed:
938                raise QAPISemError(
939                    self.info,
940                    "event's 'data' can take %s only with 'boxed': true"
941                    % self.arg_type.describe())
942            self.arg_type.check(schema)
943            if self.arg_type.has_conditional_members() and not self.boxed:
944                raise QAPISemError(
945                    self.info,
946                    "conditional event arguments require 'boxed': true")
947
948    def connect_doc(self, doc=None):
949        super().connect_doc(doc)
950        doc = doc or self.doc
951        if doc:
952            if self.arg_type and self.arg_type.is_implicit():
953                self.arg_type.connect_doc(doc)
954
955    def visit(self, visitor):
956        super().visit(visitor)
957        visitor.visit_event(
958            self.name, self.info, self.ifcond, self.features,
959            self.arg_type, self.boxed)
960
961
962class QAPISchema:
963    def __init__(self, fname):
964        self.fname = fname
965
966        try:
967            parser = QAPISchemaParser(fname)
968        except OSError as err:
969            raise QAPIError(
970                f"can't read schema file '{fname}': {err.strerror}"
971            ) from err
972
973        exprs = check_exprs(parser.exprs)
974        self.docs = parser.docs
975        self._entity_list = []
976        self._entity_dict = {}
977        self._module_dict = OrderedDict()
978        self._schema_dir = os.path.dirname(fname)
979        self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
980        self._make_module(fname)
981        self._predefining = True
982        self._def_predefineds()
983        self._predefining = False
984        self._def_exprs(exprs)
985        self.check()
986
987    def _def_entity(self, ent):
988        self._entity_list.append(ent)
989
990    def _def_definition(self, defn):
991        # Only the predefined types are allowed to not have info
992        assert defn.info or self._predefining
993        self._def_entity(defn)
994        # TODO reject names that differ only in '_' vs. '.'  vs. '-',
995        # because they're liable to clash in generated C.
996        other_defn = self._entity_dict.get(defn.name)
997        if other_defn:
998            if other_defn.info:
999                where = QAPISourceError(other_defn.info, "previous definition")
1000                raise QAPISemError(
1001                    defn.info,
1002                    "'%s' is already defined\n%s" % (defn.name, where))
1003            raise QAPISemError(
1004                defn.info, "%s is already defined" % other_defn.describe())
1005        self._entity_dict[defn.name] = defn
1006
1007    def lookup_entity(self, name, typ=None):
1008        ent = self._entity_dict.get(name)
1009        if typ and not isinstance(ent, typ):
1010            return None
1011        return ent
1012
1013    def lookup_type(self, name):
1014        typ = self.lookup_entity(name, QAPISchemaType)
1015        assert typ is None or isinstance(typ, QAPISchemaType)
1016        return typ
1017
1018    def resolve_type(self, name, info, what):
1019        typ = self.lookup_type(name)
1020        if not typ:
1021            assert info and what  # built-in types must not fail lookup
1022            if callable(what):
1023                what = what(info)
1024            raise QAPISemError(
1025                info, "%s uses unknown type '%s'" % (what, name))
1026        return typ
1027
1028    def _module_name(self, fname: str) -> str:
1029        if QAPISchemaModule.is_system_module(fname):
1030            return fname
1031        return os.path.relpath(fname, self._schema_dir)
1032
1033    def _make_module(self, fname):
1034        name = self._module_name(fname)
1035        if name not in self._module_dict:
1036            self._module_dict[name] = QAPISchemaModule(name)
1037        return self._module_dict[name]
1038
1039    def module_by_fname(self, fname):
1040        name = self._module_name(fname)
1041        return self._module_dict[name]
1042
1043    def _def_include(self, expr: QAPIExpression):
1044        include = expr['include']
1045        assert expr.doc is None
1046        self._def_entity(
1047            QAPISchemaInclude(self._make_module(include), expr.info))
1048
1049    def _def_builtin_type(self, name, json_type, c_type):
1050        self._def_definition(QAPISchemaBuiltinType(name, json_type, c_type))
1051        # Instantiating only the arrays that are actually used would
1052        # be nice, but we can't as long as their generated code
1053        # (qapi-builtin-types.[ch]) may be shared by some other
1054        # schema.
1055        self._make_array_type(name, None)
1056
1057    def _def_predefineds(self):
1058        for t in [('str',    'string',  'char' + POINTER_SUFFIX),
1059                  ('number', 'number',  'double'),
1060                  ('int',    'int',     'int64_t'),
1061                  ('int8',   'int',     'int8_t'),
1062                  ('int16',  'int',     'int16_t'),
1063                  ('int32',  'int',     'int32_t'),
1064                  ('int64',  'int',     'int64_t'),
1065                  ('uint8',  'int',     'uint8_t'),
1066                  ('uint16', 'int',     'uint16_t'),
1067                  ('uint32', 'int',     'uint32_t'),
1068                  ('uint64', 'int',     'uint64_t'),
1069                  ('size',   'int',     'uint64_t'),
1070                  ('bool',   'boolean', 'bool'),
1071                  ('any',    'value',   'QObject' + POINTER_SUFFIX),
1072                  ('null',   'null',    'QNull' + POINTER_SUFFIX)]:
1073            self._def_builtin_type(*t)
1074        self.the_empty_object_type = QAPISchemaObjectType(
1075            'q_empty', None, None, None, None, None, [], None)
1076        self._def_definition(self.the_empty_object_type)
1077
1078        qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1079                  'qbool']
1080        qtype_values = self._make_enum_members(
1081            [{'name': n} for n in qtypes], None)
1082
1083        self._def_definition(QAPISchemaEnumType(
1084            'QType', None, None, None, None, qtype_values, 'QTYPE'))
1085
1086    def _make_features(self, features, info):
1087        if features is None:
1088            return []
1089        return [QAPISchemaFeature(f['name'], info,
1090                                  QAPISchemaIfCond(f.get('if')))
1091                for f in features]
1092
1093    def _make_enum_member(self, name, ifcond, features, info):
1094        return QAPISchemaEnumMember(name, info,
1095                                    QAPISchemaIfCond(ifcond),
1096                                    self._make_features(features, info))
1097
1098    def _make_enum_members(self, values, info):
1099        return [self._make_enum_member(v['name'], v.get('if'),
1100                                       v.get('features'), info)
1101                for v in values]
1102
1103    def _make_array_type(self, element_type, info):
1104        name = element_type + 'List'    # reserved by check_defn_name_str()
1105        if not self.lookup_type(name):
1106            self._def_definition(QAPISchemaArrayType(
1107                name, info, element_type))
1108        return name
1109
1110    def _make_implicit_object_type(self, name, info, ifcond, role, members):
1111        if not members:
1112            return None
1113        # See also QAPISchemaObjectTypeMember.describe()
1114        name = 'q_obj_%s-%s' % (name, role)
1115        typ = self.lookup_entity(name, QAPISchemaObjectType)
1116        if typ:
1117            # The implicit object type has multiple users.  This can
1118            # only be a duplicate definition, which will be flagged
1119            # later.
1120            pass
1121        else:
1122            self._def_definition(QAPISchemaObjectType(
1123                name, info, None, ifcond, None, None, members, None))
1124        return name
1125
1126    def _def_enum_type(self, expr: QAPIExpression):
1127        name = expr['enum']
1128        data = expr['data']
1129        prefix = expr.get('prefix')
1130        ifcond = QAPISchemaIfCond(expr.get('if'))
1131        info = expr.info
1132        features = self._make_features(expr.get('features'), info)
1133        self._def_definition(QAPISchemaEnumType(
1134            name, info, expr.doc, ifcond, features,
1135            self._make_enum_members(data, info), prefix))
1136
1137    def _make_member(self, name, typ, ifcond, features, info):
1138        optional = False
1139        if name.startswith('*'):
1140            name = name[1:]
1141            optional = True
1142        if isinstance(typ, list):
1143            assert len(typ) == 1
1144            typ = self._make_array_type(typ[0], info)
1145        return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1146                                          self._make_features(features, info))
1147
1148    def _make_members(self, data, info):
1149        return [self._make_member(key, value['type'],
1150                                  QAPISchemaIfCond(value.get('if')),
1151                                  value.get('features'), info)
1152                for (key, value) in data.items()]
1153
1154    def _def_struct_type(self, expr: QAPIExpression):
1155        name = expr['struct']
1156        base = expr.get('base')
1157        data = expr['data']
1158        info = expr.info
1159        ifcond = QAPISchemaIfCond(expr.get('if'))
1160        features = self._make_features(expr.get('features'), info)
1161        self._def_definition(QAPISchemaObjectType(
1162            name, info, expr.doc, ifcond, features, base,
1163            self._make_members(data, info),
1164            None))
1165
1166    def _make_variant(self, case, typ, ifcond, info):
1167        if isinstance(typ, list):
1168            assert len(typ) == 1
1169            typ = self._make_array_type(typ[0], info)
1170        return QAPISchemaVariant(case, info, typ, ifcond)
1171
1172    def _def_union_type(self, expr: QAPIExpression):
1173        name = expr['union']
1174        base = expr['base']
1175        tag_name = expr['discriminator']
1176        data = expr['data']
1177        assert isinstance(data, dict)
1178        info = expr.info
1179        ifcond = QAPISchemaIfCond(expr.get('if'))
1180        features = self._make_features(expr.get('features'), info)
1181        if isinstance(base, dict):
1182            base = self._make_implicit_object_type(
1183                name, info, ifcond,
1184                'base', self._make_members(base, info))
1185        variants = [
1186            self._make_variant(key, value['type'],
1187                               QAPISchemaIfCond(value.get('if')),
1188                               info)
1189            for (key, value) in data.items()]
1190        members: List[QAPISchemaObjectTypeMember] = []
1191        self._def_definition(
1192            QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1193                                 base, members,
1194                                 QAPISchemaVariants(
1195                                     tag_name, info, None, variants)))
1196
1197    def _def_alternate_type(self, expr: QAPIExpression):
1198        name = expr['alternate']
1199        data = expr['data']
1200        assert isinstance(data, dict)
1201        ifcond = QAPISchemaIfCond(expr.get('if'))
1202        info = expr.info
1203        features = self._make_features(expr.get('features'), info)
1204        variants = [
1205            self._make_variant(key, value['type'],
1206                               QAPISchemaIfCond(value.get('if')),
1207                               info)
1208            for (key, value) in data.items()]
1209        tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1210        self._def_definition(
1211            QAPISchemaAlternateType(
1212                name, info, expr.doc, ifcond, features,
1213                QAPISchemaVariants(None, info, tag_member, variants)))
1214
1215    def _def_command(self, expr: QAPIExpression):
1216        name = expr['command']
1217        data = expr.get('data')
1218        rets = expr.get('returns')
1219        gen = expr.get('gen', True)
1220        success_response = expr.get('success-response', True)
1221        boxed = expr.get('boxed', False)
1222        allow_oob = expr.get('allow-oob', False)
1223        allow_preconfig = expr.get('allow-preconfig', False)
1224        coroutine = expr.get('coroutine', False)
1225        ifcond = QAPISchemaIfCond(expr.get('if'))
1226        info = expr.info
1227        features = self._make_features(expr.get('features'), info)
1228        if isinstance(data, OrderedDict):
1229            data = self._make_implicit_object_type(
1230                name, info, ifcond,
1231                'arg', self._make_members(data, info))
1232        if isinstance(rets, list):
1233            assert len(rets) == 1
1234            rets = self._make_array_type(rets[0], info)
1235        self._def_definition(
1236            QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
1237                              rets, gen, success_response, boxed, allow_oob,
1238                              allow_preconfig, coroutine))
1239
1240    def _def_event(self, expr: QAPIExpression):
1241        name = expr['event']
1242        data = expr.get('data')
1243        boxed = expr.get('boxed', False)
1244        ifcond = QAPISchemaIfCond(expr.get('if'))
1245        info = expr.info
1246        features = self._make_features(expr.get('features'), info)
1247        if isinstance(data, OrderedDict):
1248            data = self._make_implicit_object_type(
1249                name, info, ifcond,
1250                'arg', self._make_members(data, info))
1251        self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
1252                                             features, data, boxed))
1253
1254    def _def_exprs(self, exprs):
1255        for expr in exprs:
1256            if 'enum' in expr:
1257                self._def_enum_type(expr)
1258            elif 'struct' in expr:
1259                self._def_struct_type(expr)
1260            elif 'union' in expr:
1261                self._def_union_type(expr)
1262            elif 'alternate' in expr:
1263                self._def_alternate_type(expr)
1264            elif 'command' in expr:
1265                self._def_command(expr)
1266            elif 'event' in expr:
1267                self._def_event(expr)
1268            elif 'include' in expr:
1269                self._def_include(expr)
1270            else:
1271                assert False
1272
1273    def check(self):
1274        for ent in self._entity_list:
1275            ent.check(self)
1276            ent.connect_doc()
1277        for ent in self._entity_list:
1278            ent.set_module(self)
1279        for doc in self.docs:
1280            doc.check()
1281
1282    def visit(self, visitor):
1283        visitor.visit_begin(self)
1284        for mod in self._module_dict.values():
1285            mod.visit(visitor)
1286        visitor.visit_end()
1287