xref: /openbmc/qemu/scripts/qapi/schema.py (revision 9bda6c7d1108c13be67e615b50f5d1b61fa3177e)
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 and self.info.defn_meta)
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        return self.lookup_entity(name, QAPISchemaType)
993
994    def resolve_type(self, name, info, what):
995        typ = self.lookup_type(name)
996        if not typ:
997            if callable(what):
998                what = what(info)
999            raise QAPISemError(
1000                info, "%s uses unknown type '%s'" % (what, name))
1001        return typ
1002
1003    def _module_name(self, fname: str) -> str:
1004        if QAPISchemaModule.is_system_module(fname):
1005            return fname
1006        return os.path.relpath(fname, self._schema_dir)
1007
1008    def _make_module(self, fname):
1009        name = self._module_name(fname)
1010        if name not in self._module_dict:
1011            self._module_dict[name] = QAPISchemaModule(name)
1012        return self._module_dict[name]
1013
1014    def module_by_fname(self, fname):
1015        name = self._module_name(fname)
1016        return self._module_dict[name]
1017
1018    def _def_include(self, expr: QAPIExpression):
1019        include = expr['include']
1020        assert expr.doc is None
1021        self._def_entity(
1022            QAPISchemaInclude(self._make_module(include), expr.info))
1023
1024    def _def_builtin_type(self, name, json_type, c_type):
1025        self._def_definition(QAPISchemaBuiltinType(name, json_type, c_type))
1026        # Instantiating only the arrays that are actually used would
1027        # be nice, but we can't as long as their generated code
1028        # (qapi-builtin-types.[ch]) may be shared by some other
1029        # schema.
1030        self._make_array_type(name, None)
1031
1032    def _def_predefineds(self):
1033        for t in [('str',    'string',  'char' + POINTER_SUFFIX),
1034                  ('number', 'number',  'double'),
1035                  ('int',    'int',     'int64_t'),
1036                  ('int8',   'int',     'int8_t'),
1037                  ('int16',  'int',     'int16_t'),
1038                  ('int32',  'int',     'int32_t'),
1039                  ('int64',  'int',     'int64_t'),
1040                  ('uint8',  'int',     'uint8_t'),
1041                  ('uint16', 'int',     'uint16_t'),
1042                  ('uint32', 'int',     'uint32_t'),
1043                  ('uint64', 'int',     'uint64_t'),
1044                  ('size',   'int',     'uint64_t'),
1045                  ('bool',   'boolean', 'bool'),
1046                  ('any',    'value',   'QObject' + POINTER_SUFFIX),
1047                  ('null',   'null',    'QNull' + POINTER_SUFFIX)]:
1048            self._def_builtin_type(*t)
1049        self.the_empty_object_type = QAPISchemaObjectType(
1050            'q_empty', None, None, None, None, None, [], None)
1051        self._def_definition(self.the_empty_object_type)
1052
1053        qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1054                  'qbool']
1055        qtype_values = self._make_enum_members(
1056            [{'name': n} for n in qtypes], None)
1057
1058        self._def_definition(QAPISchemaEnumType(
1059            'QType', None, None, None, None, qtype_values, 'QTYPE'))
1060
1061    def _make_features(self, features, info):
1062        if features is None:
1063            return []
1064        return [QAPISchemaFeature(f['name'], info,
1065                                  QAPISchemaIfCond(f.get('if')))
1066                for f in features]
1067
1068    def _make_enum_member(self, name, ifcond, features, info):
1069        return QAPISchemaEnumMember(name, info,
1070                                    QAPISchemaIfCond(ifcond),
1071                                    self._make_features(features, info))
1072
1073    def _make_enum_members(self, values, info):
1074        return [self._make_enum_member(v['name'], v.get('if'),
1075                                       v.get('features'), info)
1076                for v in values]
1077
1078    def _make_array_type(self, element_type, info):
1079        name = element_type + 'List'    # reserved by check_defn_name_str()
1080        if not self.lookup_type(name):
1081            self._def_definition(QAPISchemaArrayType(
1082                name, info, element_type))
1083        return name
1084
1085    def _make_implicit_object_type(self, name, info, ifcond, role, members):
1086        if not members:
1087            return None
1088        # See also QAPISchemaObjectTypeMember.describe()
1089        name = 'q_obj_%s-%s' % (name, role)
1090        typ = self.lookup_entity(name, QAPISchemaObjectType)
1091        if typ:
1092            # The implicit object type has multiple users.  This can
1093            # only be a duplicate definition, which will be flagged
1094            # later.
1095            pass
1096        else:
1097            self._def_definition(QAPISchemaObjectType(
1098                name, info, None, ifcond, None, None, members, None))
1099        return name
1100
1101    def _def_enum_type(self, expr: QAPIExpression):
1102        name = expr['enum']
1103        data = expr['data']
1104        prefix = expr.get('prefix')
1105        ifcond = QAPISchemaIfCond(expr.get('if'))
1106        info = expr.info
1107        features = self._make_features(expr.get('features'), info)
1108        self._def_definition(QAPISchemaEnumType(
1109            name, info, expr.doc, ifcond, features,
1110            self._make_enum_members(data, info), prefix))
1111
1112    def _make_member(self, name, typ, ifcond, features, info):
1113        optional = False
1114        if name.startswith('*'):
1115            name = name[1:]
1116            optional = True
1117        if isinstance(typ, list):
1118            assert len(typ) == 1
1119            typ = self._make_array_type(typ[0], info)
1120        return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1121                                          self._make_features(features, info))
1122
1123    def _make_members(self, data, info):
1124        return [self._make_member(key, value['type'],
1125                                  QAPISchemaIfCond(value.get('if')),
1126                                  value.get('features'), info)
1127                for (key, value) in data.items()]
1128
1129    def _def_struct_type(self, expr: QAPIExpression):
1130        name = expr['struct']
1131        base = expr.get('base')
1132        data = expr['data']
1133        info = expr.info
1134        ifcond = QAPISchemaIfCond(expr.get('if'))
1135        features = self._make_features(expr.get('features'), info)
1136        self._def_definition(QAPISchemaObjectType(
1137            name, info, expr.doc, ifcond, features, base,
1138            self._make_members(data, info),
1139            None))
1140
1141    def _make_variant(self, case, typ, ifcond, info):
1142        if isinstance(typ, list):
1143            assert len(typ) == 1
1144            typ = self._make_array_type(typ[0], info)
1145        return QAPISchemaVariant(case, info, typ, ifcond)
1146
1147    def _def_union_type(self, expr: QAPIExpression):
1148        name = expr['union']
1149        base = expr['base']
1150        tag_name = expr['discriminator']
1151        data = expr['data']
1152        assert isinstance(data, dict)
1153        info = expr.info
1154        ifcond = QAPISchemaIfCond(expr.get('if'))
1155        features = self._make_features(expr.get('features'), info)
1156        if isinstance(base, dict):
1157            base = self._make_implicit_object_type(
1158                name, info, ifcond,
1159                'base', self._make_members(base, info))
1160        variants = [
1161            self._make_variant(key, value['type'],
1162                               QAPISchemaIfCond(value.get('if')),
1163                               info)
1164            for (key, value) in data.items()]
1165        members: List[QAPISchemaObjectTypeMember] = []
1166        self._def_definition(
1167            QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1168                                 base, members,
1169                                 QAPISchemaVariants(
1170                                     tag_name, info, None, variants)))
1171
1172    def _def_alternate_type(self, expr: QAPIExpression):
1173        name = expr['alternate']
1174        data = expr['data']
1175        assert isinstance(data, dict)
1176        ifcond = QAPISchemaIfCond(expr.get('if'))
1177        info = expr.info
1178        features = self._make_features(expr.get('features'), info)
1179        variants = [
1180            self._make_variant(key, value['type'],
1181                               QAPISchemaIfCond(value.get('if')),
1182                               info)
1183            for (key, value) in data.items()]
1184        tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1185        self._def_definition(
1186            QAPISchemaAlternateType(
1187                name, info, expr.doc, ifcond, features,
1188                QAPISchemaVariants(None, info, tag_member, variants)))
1189
1190    def _def_command(self, expr: QAPIExpression):
1191        name = expr['command']
1192        data = expr.get('data')
1193        rets = expr.get('returns')
1194        gen = expr.get('gen', True)
1195        success_response = expr.get('success-response', True)
1196        boxed = expr.get('boxed', False)
1197        allow_oob = expr.get('allow-oob', False)
1198        allow_preconfig = expr.get('allow-preconfig', False)
1199        coroutine = expr.get('coroutine', False)
1200        ifcond = QAPISchemaIfCond(expr.get('if'))
1201        info = expr.info
1202        features = self._make_features(expr.get('features'), info)
1203        if isinstance(data, OrderedDict):
1204            data = self._make_implicit_object_type(
1205                name, info, ifcond,
1206                'arg', self._make_members(data, info))
1207        if isinstance(rets, list):
1208            assert len(rets) == 1
1209            rets = self._make_array_type(rets[0], info)
1210        self._def_definition(
1211            QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
1212                              rets, gen, success_response, boxed, allow_oob,
1213                              allow_preconfig, coroutine))
1214
1215    def _def_event(self, expr: QAPIExpression):
1216        name = expr['event']
1217        data = expr.get('data')
1218        boxed = expr.get('boxed', False)
1219        ifcond = QAPISchemaIfCond(expr.get('if'))
1220        info = expr.info
1221        features = self._make_features(expr.get('features'), info)
1222        if isinstance(data, OrderedDict):
1223            data = self._make_implicit_object_type(
1224                name, info, ifcond,
1225                'arg', self._make_members(data, info))
1226        self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
1227                                             features, data, boxed))
1228
1229    def _def_exprs(self, exprs):
1230        for expr in exprs:
1231            if 'enum' in expr:
1232                self._def_enum_type(expr)
1233            elif 'struct' in expr:
1234                self._def_struct_type(expr)
1235            elif 'union' in expr:
1236                self._def_union_type(expr)
1237            elif 'alternate' in expr:
1238                self._def_alternate_type(expr)
1239            elif 'command' in expr:
1240                self._def_command(expr)
1241            elif 'event' in expr:
1242                self._def_event(expr)
1243            elif 'include' in expr:
1244                self._def_include(expr)
1245            else:
1246                assert False
1247
1248    def check(self):
1249        for ent in self._entity_list:
1250            ent.check(self)
1251            ent.connect_doc()
1252        for ent in self._entity_list:
1253            ent.set_module(self)
1254        for doc in self.docs:
1255            doc.check()
1256
1257    def visit(self, visitor):
1258        visitor.visit_begin(self)
1259        for mod in self._module_dict.values():
1260            mod.visit(visitor)
1261        visitor.visit_end()
1262