xref: /openbmc/qemu/scripts/qapi/schema.py (revision 8c91329ff09cec94dac22b459cc9914a7f49c54a)
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
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 = None
453
454    def check(self, schema):
455        # This calls another type T's .check() exactly when the C
456        # struct emitted by gen_object() contains that T's C struct
457        # (pointers don't count).
458        if self.members is not None:
459            # A previous .check() completed: nothing to do
460            return
461        if self._checked:
462            # Recursed: C struct contains itself
463            raise QAPISemError(self.info,
464                               "object %s contains itself" % self.name)
465
466        super().check(schema)
467        assert self._checked and self.members is None
468
469        seen = OrderedDict()
470        if self._base_name:
471            self.base = schema.resolve_type(self._base_name, self.info,
472                                            "'base'")
473            if (not isinstance(self.base, QAPISchemaObjectType)
474                    or self.base.variants):
475                raise QAPISemError(
476                    self.info,
477                    "'base' requires a struct type, %s isn't"
478                    % self.base.describe())
479            self.base.check(schema)
480            self.base.check_clash(self.info, seen)
481        for m in self.local_members:
482            m.check(schema)
483            m.check_clash(self.info, seen)
484        members = seen.values()
485
486        if self.variants:
487            self.variants.check(schema, seen)
488            self.variants.check_clash(self.info, seen)
489
490        self.members = members  # mark completed
491
492    # Check that the members of this type do not cause duplicate JSON members,
493    # and update seen to track the members seen so far. Report any errors
494    # on behalf of info, which is not necessarily self.info
495    def check_clash(self, info, seen):
496        assert self._checked
497        for m in self.members:
498            m.check_clash(info, seen)
499        if self.variants:
500            self.variants.check_clash(info, seen)
501
502    def connect_doc(self, doc=None):
503        super().connect_doc(doc)
504        doc = doc or self.doc
505        if self.base and self.base.is_implicit():
506            self.base.connect_doc(doc)
507        for m in self.local_members:
508            m.connect_doc(doc)
509
510    def is_implicit(self):
511        # See QAPISchema._make_implicit_object_type(), as well as
512        # _def_predefineds()
513        return self.name.startswith('q_')
514
515    def is_empty(self):
516        assert self.members is not None
517        return not self.members and not self.variants
518
519    def has_conditional_members(self):
520        assert self.members is not None
521        return any(m.ifcond.is_present() for m in self.members)
522
523    def c_name(self):
524        assert self.name != 'q_empty'
525        return super().c_name()
526
527    def c_type(self):
528        assert not self.is_implicit()
529        return c_name(self.name) + POINTER_SUFFIX
530
531    def c_unboxed_type(self):
532        return c_name(self.name)
533
534    def json_type(self):
535        return 'object'
536
537    def visit(self, visitor):
538        super().visit(visitor)
539        visitor.visit_object_type(
540            self.name, self.info, self.ifcond, self.features,
541            self.base, self.local_members, self.variants)
542        visitor.visit_object_type_flat(
543            self.name, self.info, self.ifcond, self.features,
544            self.members, self.variants)
545
546
547class QAPISchemaAlternateType(QAPISchemaType):
548    meta = 'alternate'
549
550    def __init__(self, name, info, doc, ifcond, features, variants):
551        super().__init__(name, info, doc, ifcond, features)
552        assert isinstance(variants, QAPISchemaVariants)
553        assert variants.tag_member
554        variants.set_defined_in(name)
555        variants.tag_member.set_defined_in(self.name)
556        self.variants = variants
557
558    def check(self, schema):
559        super().check(schema)
560        self.variants.tag_member.check(schema)
561        # Not calling self.variants.check_clash(), because there's nothing
562        # to clash with
563        self.variants.check(schema, {})
564        # Alternate branch names have no relation to the tag enum values;
565        # so we have to check for potential name collisions ourselves.
566        seen = {}
567        types_seen = {}
568        for v in self.variants.variants:
569            v.check_clash(self.info, seen)
570            qtype = v.type.alternate_qtype()
571            if not qtype:
572                raise QAPISemError(
573                    self.info,
574                    "%s cannot use %s"
575                    % (v.describe(self.info), v.type.describe()))
576            conflicting = set([qtype])
577            if qtype == 'QTYPE_QSTRING':
578                if isinstance(v.type, QAPISchemaEnumType):
579                    for m in v.type.members:
580                        if m.name in ['on', 'off']:
581                            conflicting.add('QTYPE_QBOOL')
582                        if re.match(r'[-+0-9.]', m.name):
583                            # lazy, could be tightened
584                            conflicting.add('QTYPE_QNUM')
585                else:
586                    conflicting.add('QTYPE_QNUM')
587                    conflicting.add('QTYPE_QBOOL')
588            for qt in conflicting:
589                if qt in types_seen:
590                    raise QAPISemError(
591                        self.info,
592                        "%s can't be distinguished from '%s'"
593                        % (v.describe(self.info), types_seen[qt]))
594                types_seen[qt] = v.name
595
596    def connect_doc(self, doc=None):
597        super().connect_doc(doc)
598        doc = doc or self.doc
599        for v in self.variants.variants:
600            v.connect_doc(doc)
601
602    def c_type(self):
603        return c_name(self.name) + POINTER_SUFFIX
604
605    def json_type(self):
606        return 'value'
607
608    def visit(self, visitor):
609        super().visit(visitor)
610        visitor.visit_alternate_type(
611            self.name, self.info, self.ifcond, self.features, self.variants)
612
613
614class QAPISchemaVariants:
615    def __init__(self, tag_name, info, tag_member, variants):
616        # Unions pass tag_name but not tag_member.
617        # Alternates pass tag_member but not tag_name.
618        # After check(), tag_member is always set.
619        assert bool(tag_member) != bool(tag_name)
620        assert (isinstance(tag_name, str) or
621                isinstance(tag_member, QAPISchemaObjectTypeMember))
622        for v in variants:
623            assert isinstance(v, QAPISchemaVariant)
624        self._tag_name = tag_name
625        self.info = info
626        self.tag_member = tag_member
627        self.variants = variants
628
629    def set_defined_in(self, name):
630        for v in self.variants:
631            v.set_defined_in(name)
632
633    def check(self, schema, seen):
634        if self._tag_name:      # union
635            self.tag_member = seen.get(c_name(self._tag_name))
636            base = "'base'"
637            # Pointing to the base type when not implicit would be
638            # nice, but we don't know it here
639            if not self.tag_member or self._tag_name != self.tag_member.name:
640                raise QAPISemError(
641                    self.info,
642                    "discriminator '%s' is not a member of %s"
643                    % (self._tag_name, base))
644            # Here we do:
645            base_type = schema.lookup_type(self.tag_member.defined_in)
646            assert base_type
647            if not base_type.is_implicit():
648                base = "base type '%s'" % self.tag_member.defined_in
649            if not isinstance(self.tag_member.type, QAPISchemaEnumType):
650                raise QAPISemError(
651                    self.info,
652                    "discriminator member '%s' of %s must be of enum type"
653                    % (self._tag_name, base))
654            if self.tag_member.optional:
655                raise QAPISemError(
656                    self.info,
657                    "discriminator member '%s' of %s must not be optional"
658                    % (self._tag_name, base))
659            if self.tag_member.ifcond.is_present():
660                raise QAPISemError(
661                    self.info,
662                    "discriminator member '%s' of %s must not be conditional"
663                    % (self._tag_name, base))
664        else:                   # alternate
665            assert isinstance(self.tag_member.type, QAPISchemaEnumType)
666            assert not self.tag_member.optional
667            assert not self.tag_member.ifcond.is_present()
668        if self._tag_name:      # union
669            # branches that are not explicitly covered get an empty type
670            cases = {v.name for v in self.variants}
671            for m in self.tag_member.type.members:
672                if m.name not in cases:
673                    v = QAPISchemaVariant(m.name, self.info,
674                                          'q_empty', m.ifcond)
675                    v.set_defined_in(self.tag_member.defined_in)
676                    self.variants.append(v)
677        if not self.variants:
678            raise QAPISemError(self.info, "union has no branches")
679        for v in self.variants:
680            v.check(schema)
681            # Union names must match enum values; alternate names are
682            # checked separately. Use 'seen' to tell the two apart.
683            if seen:
684                if v.name not in self.tag_member.type.member_names():
685                    raise QAPISemError(
686                        self.info,
687                        "branch '%s' is not a value of %s"
688                        % (v.name, self.tag_member.type.describe()))
689                if not isinstance(v.type, QAPISchemaObjectType):
690                    raise QAPISemError(
691                        self.info,
692                        "%s cannot use %s"
693                        % (v.describe(self.info), v.type.describe()))
694                v.type.check(schema)
695
696    def check_clash(self, info, seen):
697        for v in self.variants:
698            # Reset seen map for each variant, since qapi names from one
699            # branch do not affect another branch
700            v.type.check_clash(info, dict(seen))
701
702
703class QAPISchemaMember:
704    """ Represents object members, enum members and features """
705    role = 'member'
706
707    def __init__(self, name, info, ifcond=None):
708        assert isinstance(name, str)
709        self.name = name
710        self.info = info
711        self.ifcond = ifcond or QAPISchemaIfCond()
712        self.defined_in = None
713
714    def set_defined_in(self, name):
715        assert not self.defined_in
716        self.defined_in = name
717
718    def check_clash(self, info, seen):
719        cname = c_name(self.name)
720        if cname in seen:
721            raise QAPISemError(
722                info,
723                "%s collides with %s"
724                % (self.describe(info), seen[cname].describe(info)))
725        seen[cname] = self
726
727    def connect_doc(self, doc):
728        if doc:
729            doc.connect_member(self)
730
731    def describe(self, info):
732        role = self.role
733        meta = 'type'
734        defined_in = self.defined_in
735        assert defined_in
736
737        if defined_in.startswith('q_obj_'):
738            # See QAPISchema._make_implicit_object_type() - reverse the
739            # mapping there to create a nice human-readable description
740            defined_in = defined_in[6:]
741            if defined_in.endswith('-arg'):
742                # Implicit type created for a command's dict 'data'
743                assert role == 'member'
744                role = 'parameter'
745                meta = 'command'
746                defined_in = defined_in[:-4]
747            elif defined_in.endswith('-base'):
748                # Implicit type created for a union's dict 'base'
749                role = 'base ' + role
750                defined_in = defined_in[:-5]
751            else:
752                assert False
753
754        if defined_in != info.defn_name:
755            return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in)
756        return "%s '%s'" % (role, self.name)
757
758
759class QAPISchemaEnumMember(QAPISchemaMember):
760    role = 'value'
761
762    def __init__(self, name, info, ifcond=None, features=None):
763        super().__init__(name, info, ifcond)
764        for f in features or []:
765            assert isinstance(f, QAPISchemaFeature)
766            f.set_defined_in(name)
767        self.features = features or []
768
769    def connect_doc(self, doc):
770        super().connect_doc(doc)
771        if doc:
772            for f in self.features:
773                doc.connect_feature(f)
774
775
776class QAPISchemaFeature(QAPISchemaMember):
777    role = 'feature'
778
779    def is_special(self):
780        return self.name in ('deprecated', 'unstable')
781
782
783class QAPISchemaObjectTypeMember(QAPISchemaMember):
784    def __init__(self, name, info, typ, optional, ifcond=None, features=None):
785        super().__init__(name, info, ifcond)
786        assert isinstance(typ, str)
787        assert isinstance(optional, bool)
788        for f in features or []:
789            assert isinstance(f, QAPISchemaFeature)
790            f.set_defined_in(name)
791        self._type_name = typ
792        self.type: QAPISchemaType  # set during check()
793        self.optional = optional
794        self.features = features or []
795
796    def need_has(self):
797        assert self.type
798        return self.optional and self.type.need_has_if_optional()
799
800    def check(self, schema):
801        assert self.defined_in
802        self.type = schema.resolve_type(self._type_name, self.info,
803                                        self.describe)
804        seen = {}
805        for f in self.features:
806            f.check_clash(self.info, seen)
807
808    def connect_doc(self, doc):
809        super().connect_doc(doc)
810        if doc:
811            for f in self.features:
812                doc.connect_feature(f)
813
814
815class QAPISchemaVariant(QAPISchemaObjectTypeMember):
816    role = 'branch'
817
818    def __init__(self, name, info, typ, ifcond=None):
819        super().__init__(name, info, typ, False, ifcond)
820
821
822class QAPISchemaCommand(QAPISchemaDefinition):
823    meta = 'command'
824
825    def __init__(self, name, info, doc, ifcond, features,
826                 arg_type, ret_type,
827                 gen, success_response, boxed, allow_oob, allow_preconfig,
828                 coroutine):
829        super().__init__(name, info, doc, ifcond, features)
830        assert not arg_type or isinstance(arg_type, str)
831        assert not ret_type or isinstance(ret_type, str)
832        self._arg_type_name = arg_type
833        self.arg_type = None
834        self._ret_type_name = ret_type
835        self.ret_type = None
836        self.gen = gen
837        self.success_response = success_response
838        self.boxed = boxed
839        self.allow_oob = allow_oob
840        self.allow_preconfig = allow_preconfig
841        self.coroutine = coroutine
842
843    def check(self, schema):
844        super().check(schema)
845        if self._arg_type_name:
846            arg_type = schema.resolve_type(
847                self._arg_type_name, self.info, "command's 'data'")
848            if not isinstance(arg_type, QAPISchemaObjectType):
849                raise QAPISemError(
850                    self.info,
851                    "command's 'data' cannot take %s"
852                    % arg_type.describe())
853            self.arg_type = arg_type
854            if self.arg_type.variants and not self.boxed:
855                raise QAPISemError(
856                    self.info,
857                    "command's 'data' can take %s only with 'boxed': true"
858                    % self.arg_type.describe())
859            self.arg_type.check(schema)
860            if self.arg_type.has_conditional_members() and not self.boxed:
861                raise QAPISemError(
862                    self.info,
863                    "conditional command arguments require 'boxed': true")
864        if self._ret_type_name:
865            self.ret_type = schema.resolve_type(
866                self._ret_type_name, self.info, "command's 'returns'")
867            if self.name not in self.info.pragma.command_returns_exceptions:
868                typ = self.ret_type
869                if isinstance(typ, QAPISchemaArrayType):
870                    assert typ
871                    typ = typ.element_type
872                if not isinstance(typ, QAPISchemaObjectType):
873                    raise QAPISemError(
874                        self.info,
875                        "command's 'returns' cannot take %s"
876                        % self.ret_type.describe())
877
878    def connect_doc(self, doc=None):
879        super().connect_doc(doc)
880        doc = doc or self.doc
881        if doc:
882            if self.arg_type and self.arg_type.is_implicit():
883                self.arg_type.connect_doc(doc)
884
885    def visit(self, visitor):
886        super().visit(visitor)
887        visitor.visit_command(
888            self.name, self.info, self.ifcond, self.features,
889            self.arg_type, self.ret_type, self.gen, self.success_response,
890            self.boxed, self.allow_oob, self.allow_preconfig,
891            self.coroutine)
892
893
894class QAPISchemaEvent(QAPISchemaDefinition):
895    meta = 'event'
896
897    def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
898        super().__init__(name, info, doc, ifcond, features)
899        assert not arg_type or isinstance(arg_type, str)
900        self._arg_type_name = arg_type
901        self.arg_type = None
902        self.boxed = boxed
903
904    def check(self, schema):
905        super().check(schema)
906        if self._arg_type_name:
907            typ = schema.resolve_type(
908                self._arg_type_name, self.info, "event's 'data'")
909            if not isinstance(typ, QAPISchemaObjectType):
910                raise QAPISemError(
911                    self.info,
912                    "event's 'data' cannot take %s"
913                    % typ.describe())
914            self.arg_type = typ
915            if self.arg_type.variants and not self.boxed:
916                raise QAPISemError(
917                    self.info,
918                    "event's 'data' can take %s only with 'boxed': true"
919                    % self.arg_type.describe())
920            self.arg_type.check(schema)
921            if self.arg_type.has_conditional_members() and not self.boxed:
922                raise QAPISemError(
923                    self.info,
924                    "conditional event arguments require 'boxed': true")
925
926    def connect_doc(self, doc=None):
927        super().connect_doc(doc)
928        doc = doc or self.doc
929        if doc:
930            if self.arg_type and self.arg_type.is_implicit():
931                self.arg_type.connect_doc(doc)
932
933    def visit(self, visitor):
934        super().visit(visitor)
935        visitor.visit_event(
936            self.name, self.info, self.ifcond, self.features,
937            self.arg_type, self.boxed)
938
939
940class QAPISchema:
941    def __init__(self, fname):
942        self.fname = fname
943
944        try:
945            parser = QAPISchemaParser(fname)
946        except OSError as err:
947            raise QAPIError(
948                f"can't read schema file '{fname}': {err.strerror}"
949            ) from err
950
951        exprs = check_exprs(parser.exprs)
952        self.docs = parser.docs
953        self._entity_list = []
954        self._entity_dict = {}
955        self._module_dict = OrderedDict()
956        self._schema_dir = os.path.dirname(fname)
957        self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
958        self._make_module(fname)
959        self._predefining = True
960        self._def_predefineds()
961        self._predefining = False
962        self._def_exprs(exprs)
963        self.check()
964
965    def _def_entity(self, ent):
966        self._entity_list.append(ent)
967
968    def _def_definition(self, defn):
969        # Only the predefined types are allowed to not have info
970        assert defn.info or self._predefining
971        self._def_entity(defn)
972        # TODO reject names that differ only in '_' vs. '.'  vs. '-',
973        # because they're liable to clash in generated C.
974        other_defn = self._entity_dict.get(defn.name)
975        if other_defn:
976            if other_defn.info:
977                where = QAPISourceError(other_defn.info, "previous definition")
978                raise QAPISemError(
979                    defn.info,
980                    "'%s' is already defined\n%s" % (defn.name, where))
981            raise QAPISemError(
982                defn.info, "%s is already defined" % other_defn.describe())
983        self._entity_dict[defn.name] = defn
984
985    def lookup_entity(self, name, typ=None):
986        ent = self._entity_dict.get(name)
987        if typ and not isinstance(ent, typ):
988            return None
989        return ent
990
991    def lookup_type(self, name):
992        typ = self.lookup_entity(name, QAPISchemaType)
993        assert typ is None or isinstance(typ, QAPISchemaType)
994        return typ
995
996    def resolve_type(self, name, info, what):
997        typ = self.lookup_type(name)
998        if not typ:
999            assert info and what  # built-in types must not fail lookup
1000            if callable(what):
1001                what = what(info)
1002            raise QAPISemError(
1003                info, "%s uses unknown type '%s'" % (what, name))
1004        return typ
1005
1006    def _module_name(self, fname: str) -> str:
1007        if QAPISchemaModule.is_system_module(fname):
1008            return fname
1009        return os.path.relpath(fname, self._schema_dir)
1010
1011    def _make_module(self, fname):
1012        name = self._module_name(fname)
1013        if name not in self._module_dict:
1014            self._module_dict[name] = QAPISchemaModule(name)
1015        return self._module_dict[name]
1016
1017    def module_by_fname(self, fname):
1018        name = self._module_name(fname)
1019        return self._module_dict[name]
1020
1021    def _def_include(self, expr: QAPIExpression):
1022        include = expr['include']
1023        assert expr.doc is None
1024        self._def_entity(
1025            QAPISchemaInclude(self._make_module(include), expr.info))
1026
1027    def _def_builtin_type(self, name, json_type, c_type):
1028        self._def_definition(QAPISchemaBuiltinType(name, json_type, c_type))
1029        # Instantiating only the arrays that are actually used would
1030        # be nice, but we can't as long as their generated code
1031        # (qapi-builtin-types.[ch]) may be shared by some other
1032        # schema.
1033        self._make_array_type(name, None)
1034
1035    def _def_predefineds(self):
1036        for t in [('str',    'string',  'char' + POINTER_SUFFIX),
1037                  ('number', 'number',  'double'),
1038                  ('int',    'int',     'int64_t'),
1039                  ('int8',   'int',     'int8_t'),
1040                  ('int16',  'int',     'int16_t'),
1041                  ('int32',  'int',     'int32_t'),
1042                  ('int64',  'int',     'int64_t'),
1043                  ('uint8',  'int',     'uint8_t'),
1044                  ('uint16', 'int',     'uint16_t'),
1045                  ('uint32', 'int',     'uint32_t'),
1046                  ('uint64', 'int',     'uint64_t'),
1047                  ('size',   'int',     'uint64_t'),
1048                  ('bool',   'boolean', 'bool'),
1049                  ('any',    'value',   'QObject' + POINTER_SUFFIX),
1050                  ('null',   'null',    'QNull' + POINTER_SUFFIX)]:
1051            self._def_builtin_type(*t)
1052        self.the_empty_object_type = QAPISchemaObjectType(
1053            'q_empty', None, None, None, None, None, [], None)
1054        self._def_definition(self.the_empty_object_type)
1055
1056        qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1057                  'qbool']
1058        qtype_values = self._make_enum_members(
1059            [{'name': n} for n in qtypes], None)
1060
1061        self._def_definition(QAPISchemaEnumType(
1062            'QType', None, None, None, None, qtype_values, 'QTYPE'))
1063
1064    def _make_features(self, features, info):
1065        if features is None:
1066            return []
1067        return [QAPISchemaFeature(f['name'], info,
1068                                  QAPISchemaIfCond(f.get('if')))
1069                for f in features]
1070
1071    def _make_enum_member(self, name, ifcond, features, info):
1072        return QAPISchemaEnumMember(name, info,
1073                                    QAPISchemaIfCond(ifcond),
1074                                    self._make_features(features, info))
1075
1076    def _make_enum_members(self, values, info):
1077        return [self._make_enum_member(v['name'], v.get('if'),
1078                                       v.get('features'), info)
1079                for v in values]
1080
1081    def _make_array_type(self, element_type, info):
1082        name = element_type + 'List'    # reserved by check_defn_name_str()
1083        if not self.lookup_type(name):
1084            self._def_definition(QAPISchemaArrayType(
1085                name, info, element_type))
1086        return name
1087
1088    def _make_implicit_object_type(self, name, info, ifcond, role, members):
1089        if not members:
1090            return None
1091        # See also QAPISchemaObjectTypeMember.describe()
1092        name = 'q_obj_%s-%s' % (name, role)
1093        typ = self.lookup_entity(name, QAPISchemaObjectType)
1094        if typ:
1095            # The implicit object type has multiple users.  This can
1096            # only be a duplicate definition, which will be flagged
1097            # later.
1098            pass
1099        else:
1100            self._def_definition(QAPISchemaObjectType(
1101                name, info, None, ifcond, None, None, members, None))
1102        return name
1103
1104    def _def_enum_type(self, expr: QAPIExpression):
1105        name = expr['enum']
1106        data = expr['data']
1107        prefix = expr.get('prefix')
1108        ifcond = QAPISchemaIfCond(expr.get('if'))
1109        info = expr.info
1110        features = self._make_features(expr.get('features'), info)
1111        self._def_definition(QAPISchemaEnumType(
1112            name, info, expr.doc, ifcond, features,
1113            self._make_enum_members(data, info), prefix))
1114
1115    def _make_member(self, name, typ, ifcond, features, info):
1116        optional = False
1117        if name.startswith('*'):
1118            name = name[1:]
1119            optional = True
1120        if isinstance(typ, list):
1121            assert len(typ) == 1
1122            typ = self._make_array_type(typ[0], info)
1123        return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1124                                          self._make_features(features, info))
1125
1126    def _make_members(self, data, info):
1127        return [self._make_member(key, value['type'],
1128                                  QAPISchemaIfCond(value.get('if')),
1129                                  value.get('features'), info)
1130                for (key, value) in data.items()]
1131
1132    def _def_struct_type(self, expr: QAPIExpression):
1133        name = expr['struct']
1134        base = expr.get('base')
1135        data = expr['data']
1136        info = expr.info
1137        ifcond = QAPISchemaIfCond(expr.get('if'))
1138        features = self._make_features(expr.get('features'), info)
1139        self._def_definition(QAPISchemaObjectType(
1140            name, info, expr.doc, ifcond, features, base,
1141            self._make_members(data, info),
1142            None))
1143
1144    def _make_variant(self, case, typ, ifcond, info):
1145        if isinstance(typ, list):
1146            assert len(typ) == 1
1147            typ = self._make_array_type(typ[0], info)
1148        return QAPISchemaVariant(case, info, typ, ifcond)
1149
1150    def _def_union_type(self, expr: QAPIExpression):
1151        name = expr['union']
1152        base = expr['base']
1153        tag_name = expr['discriminator']
1154        data = expr['data']
1155        assert isinstance(data, dict)
1156        info = expr.info
1157        ifcond = QAPISchemaIfCond(expr.get('if'))
1158        features = self._make_features(expr.get('features'), info)
1159        if isinstance(base, dict):
1160            base = self._make_implicit_object_type(
1161                name, info, ifcond,
1162                'base', self._make_members(base, info))
1163        variants = [
1164            self._make_variant(key, value['type'],
1165                               QAPISchemaIfCond(value.get('if')),
1166                               info)
1167            for (key, value) in data.items()]
1168        members: List[QAPISchemaObjectTypeMember] = []
1169        self._def_definition(
1170            QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1171                                 base, members,
1172                                 QAPISchemaVariants(
1173                                     tag_name, info, None, variants)))
1174
1175    def _def_alternate_type(self, expr: QAPIExpression):
1176        name = expr['alternate']
1177        data = expr['data']
1178        assert isinstance(data, dict)
1179        ifcond = QAPISchemaIfCond(expr.get('if'))
1180        info = expr.info
1181        features = self._make_features(expr.get('features'), info)
1182        variants = [
1183            self._make_variant(key, value['type'],
1184                               QAPISchemaIfCond(value.get('if')),
1185                               info)
1186            for (key, value) in data.items()]
1187        tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1188        self._def_definition(
1189            QAPISchemaAlternateType(
1190                name, info, expr.doc, ifcond, features,
1191                QAPISchemaVariants(None, info, tag_member, variants)))
1192
1193    def _def_command(self, expr: QAPIExpression):
1194        name = expr['command']
1195        data = expr.get('data')
1196        rets = expr.get('returns')
1197        gen = expr.get('gen', True)
1198        success_response = expr.get('success-response', True)
1199        boxed = expr.get('boxed', False)
1200        allow_oob = expr.get('allow-oob', False)
1201        allow_preconfig = expr.get('allow-preconfig', False)
1202        coroutine = expr.get('coroutine', False)
1203        ifcond = QAPISchemaIfCond(expr.get('if'))
1204        info = expr.info
1205        features = self._make_features(expr.get('features'), info)
1206        if isinstance(data, OrderedDict):
1207            data = self._make_implicit_object_type(
1208                name, info, ifcond,
1209                'arg', self._make_members(data, info))
1210        if isinstance(rets, list):
1211            assert len(rets) == 1
1212            rets = self._make_array_type(rets[0], info)
1213        self._def_definition(
1214            QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
1215                              rets, gen, success_response, boxed, allow_oob,
1216                              allow_preconfig, coroutine))
1217
1218    def _def_event(self, expr: QAPIExpression):
1219        name = expr['event']
1220        data = expr.get('data')
1221        boxed = expr.get('boxed', False)
1222        ifcond = QAPISchemaIfCond(expr.get('if'))
1223        info = expr.info
1224        features = self._make_features(expr.get('features'), info)
1225        if isinstance(data, OrderedDict):
1226            data = self._make_implicit_object_type(
1227                name, info, ifcond,
1228                'arg', self._make_members(data, info))
1229        self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
1230                                             features, data, boxed))
1231
1232    def _def_exprs(self, exprs):
1233        for expr in exprs:
1234            if 'enum' in expr:
1235                self._def_enum_type(expr)
1236            elif 'struct' in expr:
1237                self._def_struct_type(expr)
1238            elif 'union' in expr:
1239                self._def_union_type(expr)
1240            elif 'alternate' in expr:
1241                self._def_alternate_type(expr)
1242            elif 'command' in expr:
1243                self._def_command(expr)
1244            elif 'event' in expr:
1245                self._def_event(expr)
1246            elif 'include' in expr:
1247                self._def_include(expr)
1248            else:
1249                assert False
1250
1251    def check(self):
1252        for ent in self._entity_list:
1253            ent.check(self)
1254            ent.connect_doc()
1255        for ent in self._entity_list:
1256            ent.set_module(self)
1257        for doc in self.docs:
1258            doc.check()
1259
1260    def visit(self, visitor):
1261        visitor.visit_begin(self)
1262        for mod in self._module_dict.values():
1263            mod.visit(visitor)
1264        visitor.visit_end()
1265