xref: /openbmc/qemu/scripts/qapi/schema.py (revision d150be3d54e4df04948b7205f7ccdde5a84f0684)
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            self.arg_type = schema.resolve_type(
847                self._arg_type_name, self.info, "command's 'data'")
848            if not isinstance(self.arg_type, QAPISchemaObjectType):
849                raise QAPISemError(
850                    self.info,
851                    "command's 'data' cannot take %s"
852                    % self.arg_type.describe())
853            if self.arg_type.variants and not self.boxed:
854                raise QAPISemError(
855                    self.info,
856                    "command's 'data' can take %s only with 'boxed': true"
857                    % self.arg_type.describe())
858            self.arg_type.check(schema)
859            if self.arg_type.has_conditional_members() and not self.boxed:
860                raise QAPISemError(
861                    self.info,
862                    "conditional command arguments require 'boxed': true")
863        if self._ret_type_name:
864            self.ret_type = schema.resolve_type(
865                self._ret_type_name, self.info, "command's 'returns'")
866            if self.name not in self.info.pragma.command_returns_exceptions:
867                typ = self.ret_type
868                if isinstance(typ, QAPISchemaArrayType):
869                    typ = self.ret_type.element_type
870                    assert typ
871                if not isinstance(typ, QAPISchemaObjectType):
872                    raise QAPISemError(
873                        self.info,
874                        "command's 'returns' cannot take %s"
875                        % self.ret_type.describe())
876
877    def connect_doc(self, doc=None):
878        super().connect_doc(doc)
879        doc = doc or self.doc
880        if doc:
881            if self.arg_type and self.arg_type.is_implicit():
882                self.arg_type.connect_doc(doc)
883
884    def visit(self, visitor):
885        super().visit(visitor)
886        visitor.visit_command(
887            self.name, self.info, self.ifcond, self.features,
888            self.arg_type, self.ret_type, self.gen, self.success_response,
889            self.boxed, self.allow_oob, self.allow_preconfig,
890            self.coroutine)
891
892
893class QAPISchemaEvent(QAPISchemaDefinition):
894    meta = 'event'
895
896    def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
897        super().__init__(name, info, doc, ifcond, features)
898        assert not arg_type or isinstance(arg_type, str)
899        self._arg_type_name = arg_type
900        self.arg_type = None
901        self.boxed = boxed
902
903    def check(self, schema):
904        super().check(schema)
905        if self._arg_type_name:
906            self.arg_type = schema.resolve_type(
907                self._arg_type_name, self.info, "event's 'data'")
908            if not isinstance(self.arg_type, QAPISchemaObjectType):
909                raise QAPISemError(
910                    self.info,
911                    "event's 'data' cannot take %s"
912                    % self.arg_type.describe())
913            if self.arg_type.variants and not self.boxed:
914                raise QAPISemError(
915                    self.info,
916                    "event's 'data' can take %s only with 'boxed': true"
917                    % self.arg_type.describe())
918            self.arg_type.check(schema)
919            if self.arg_type.has_conditional_members() and not self.boxed:
920                raise QAPISemError(
921                    self.info,
922                    "conditional event arguments require 'boxed': true")
923
924    def connect_doc(self, doc=None):
925        super().connect_doc(doc)
926        doc = doc or self.doc
927        if doc:
928            if self.arg_type and self.arg_type.is_implicit():
929                self.arg_type.connect_doc(doc)
930
931    def visit(self, visitor):
932        super().visit(visitor)
933        visitor.visit_event(
934            self.name, self.info, self.ifcond, self.features,
935            self.arg_type, self.boxed)
936
937
938class QAPISchema:
939    def __init__(self, fname):
940        self.fname = fname
941
942        try:
943            parser = QAPISchemaParser(fname)
944        except OSError as err:
945            raise QAPIError(
946                f"can't read schema file '{fname}': {err.strerror}"
947            ) from err
948
949        exprs = check_exprs(parser.exprs)
950        self.docs = parser.docs
951        self._entity_list = []
952        self._entity_dict = {}
953        self._module_dict = OrderedDict()
954        self._schema_dir = os.path.dirname(fname)
955        self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
956        self._make_module(fname)
957        self._predefining = True
958        self._def_predefineds()
959        self._predefining = False
960        self._def_exprs(exprs)
961        self.check()
962
963    def _def_entity(self, ent):
964        self._entity_list.append(ent)
965
966    def _def_definition(self, defn):
967        # Only the predefined types are allowed to not have info
968        assert defn.info or self._predefining
969        self._def_entity(defn)
970        # TODO reject names that differ only in '_' vs. '.'  vs. '-',
971        # because they're liable to clash in generated C.
972        other_defn = self._entity_dict.get(defn.name)
973        if other_defn:
974            if other_defn.info:
975                where = QAPISourceError(other_defn.info, "previous definition")
976                raise QAPISemError(
977                    defn.info,
978                    "'%s' is already defined\n%s" % (defn.name, where))
979            raise QAPISemError(
980                defn.info, "%s is already defined" % other_defn.describe())
981        self._entity_dict[defn.name] = defn
982
983    def lookup_entity(self, name, typ=None):
984        ent = self._entity_dict.get(name)
985        if typ and not isinstance(ent, typ):
986            return None
987        return ent
988
989    def lookup_type(self, name):
990        return self.lookup_entity(name, QAPISchemaType)
991
992    def resolve_type(self, name, info, what):
993        typ = self.lookup_type(name)
994        if not typ:
995            if callable(what):
996                what = what(info)
997            raise QAPISemError(
998                info, "%s uses unknown type '%s'" % (what, name))
999        return typ
1000
1001    def _module_name(self, fname: str) -> str:
1002        if QAPISchemaModule.is_system_module(fname):
1003            return fname
1004        return os.path.relpath(fname, self._schema_dir)
1005
1006    def _make_module(self, fname):
1007        name = self._module_name(fname)
1008        if name not in self._module_dict:
1009            self._module_dict[name] = QAPISchemaModule(name)
1010        return self._module_dict[name]
1011
1012    def module_by_fname(self, fname):
1013        name = self._module_name(fname)
1014        return self._module_dict[name]
1015
1016    def _def_include(self, expr: QAPIExpression):
1017        include = expr['include']
1018        assert expr.doc is None
1019        self._def_entity(
1020            QAPISchemaInclude(self._make_module(include), expr.info))
1021
1022    def _def_builtin_type(self, name, json_type, c_type):
1023        self._def_definition(QAPISchemaBuiltinType(name, json_type, c_type))
1024        # Instantiating only the arrays that are actually used would
1025        # be nice, but we can't as long as their generated code
1026        # (qapi-builtin-types.[ch]) may be shared by some other
1027        # schema.
1028        self._make_array_type(name, None)
1029
1030    def _def_predefineds(self):
1031        for t in [('str',    'string',  'char' + POINTER_SUFFIX),
1032                  ('number', 'number',  'double'),
1033                  ('int',    'int',     'int64_t'),
1034                  ('int8',   'int',     'int8_t'),
1035                  ('int16',  'int',     'int16_t'),
1036                  ('int32',  'int',     'int32_t'),
1037                  ('int64',  'int',     'int64_t'),
1038                  ('uint8',  'int',     'uint8_t'),
1039                  ('uint16', 'int',     'uint16_t'),
1040                  ('uint32', 'int',     'uint32_t'),
1041                  ('uint64', 'int',     'uint64_t'),
1042                  ('size',   'int',     'uint64_t'),
1043                  ('bool',   'boolean', 'bool'),
1044                  ('any',    'value',   'QObject' + POINTER_SUFFIX),
1045                  ('null',   'null',    'QNull' + POINTER_SUFFIX)]:
1046            self._def_builtin_type(*t)
1047        self.the_empty_object_type = QAPISchemaObjectType(
1048            'q_empty', None, None, None, None, None, [], None)
1049        self._def_definition(self.the_empty_object_type)
1050
1051        qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1052                  'qbool']
1053        qtype_values = self._make_enum_members(
1054            [{'name': n} for n in qtypes], None)
1055
1056        self._def_definition(QAPISchemaEnumType(
1057            'QType', None, None, None, None, qtype_values, 'QTYPE'))
1058
1059    def _make_features(self, features, info):
1060        if features is None:
1061            return []
1062        return [QAPISchemaFeature(f['name'], info,
1063                                  QAPISchemaIfCond(f.get('if')))
1064                for f in features]
1065
1066    def _make_enum_member(self, name, ifcond, features, info):
1067        return QAPISchemaEnumMember(name, info,
1068                                    QAPISchemaIfCond(ifcond),
1069                                    self._make_features(features, info))
1070
1071    def _make_enum_members(self, values, info):
1072        return [self._make_enum_member(v['name'], v.get('if'),
1073                                       v.get('features'), info)
1074                for v in values]
1075
1076    def _make_array_type(self, element_type, info):
1077        name = element_type + 'List'    # reserved by check_defn_name_str()
1078        if not self.lookup_type(name):
1079            self._def_definition(QAPISchemaArrayType(
1080                name, info, element_type))
1081        return name
1082
1083    def _make_implicit_object_type(self, name, info, ifcond, role, members):
1084        if not members:
1085            return None
1086        # See also QAPISchemaObjectTypeMember.describe()
1087        name = 'q_obj_%s-%s' % (name, role)
1088        typ = self.lookup_entity(name, QAPISchemaObjectType)
1089        if typ:
1090            # The implicit object type has multiple users.  This can
1091            # only be a duplicate definition, which will be flagged
1092            # later.
1093            pass
1094        else:
1095            self._def_definition(QAPISchemaObjectType(
1096                name, info, None, ifcond, None, None, members, None))
1097        return name
1098
1099    def _def_enum_type(self, expr: QAPIExpression):
1100        name = expr['enum']
1101        data = expr['data']
1102        prefix = expr.get('prefix')
1103        ifcond = QAPISchemaIfCond(expr.get('if'))
1104        info = expr.info
1105        features = self._make_features(expr.get('features'), info)
1106        self._def_definition(QAPISchemaEnumType(
1107            name, info, expr.doc, ifcond, features,
1108            self._make_enum_members(data, info), prefix))
1109
1110    def _make_member(self, name, typ, ifcond, features, info):
1111        optional = False
1112        if name.startswith('*'):
1113            name = name[1:]
1114            optional = True
1115        if isinstance(typ, list):
1116            assert len(typ) == 1
1117            typ = self._make_array_type(typ[0], info)
1118        return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1119                                          self._make_features(features, info))
1120
1121    def _make_members(self, data, info):
1122        return [self._make_member(key, value['type'],
1123                                  QAPISchemaIfCond(value.get('if')),
1124                                  value.get('features'), info)
1125                for (key, value) in data.items()]
1126
1127    def _def_struct_type(self, expr: QAPIExpression):
1128        name = expr['struct']
1129        base = expr.get('base')
1130        data = expr['data']
1131        info = expr.info
1132        ifcond = QAPISchemaIfCond(expr.get('if'))
1133        features = self._make_features(expr.get('features'), info)
1134        self._def_definition(QAPISchemaObjectType(
1135            name, info, expr.doc, ifcond, features, base,
1136            self._make_members(data, info),
1137            None))
1138
1139    def _make_variant(self, case, typ, ifcond, info):
1140        if isinstance(typ, list):
1141            assert len(typ) == 1
1142            typ = self._make_array_type(typ[0], info)
1143        return QAPISchemaVariant(case, info, typ, ifcond)
1144
1145    def _def_union_type(self, expr: QAPIExpression):
1146        name = expr['union']
1147        base = expr['base']
1148        tag_name = expr['discriminator']
1149        data = expr['data']
1150        assert isinstance(data, dict)
1151        info = expr.info
1152        ifcond = QAPISchemaIfCond(expr.get('if'))
1153        features = self._make_features(expr.get('features'), info)
1154        if isinstance(base, dict):
1155            base = self._make_implicit_object_type(
1156                name, info, ifcond,
1157                'base', self._make_members(base, info))
1158        variants = [
1159            self._make_variant(key, value['type'],
1160                               QAPISchemaIfCond(value.get('if')),
1161                               info)
1162            for (key, value) in data.items()]
1163        members: List[QAPISchemaObjectTypeMember] = []
1164        self._def_definition(
1165            QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1166                                 base, members,
1167                                 QAPISchemaVariants(
1168                                     tag_name, info, None, variants)))
1169
1170    def _def_alternate_type(self, expr: QAPIExpression):
1171        name = expr['alternate']
1172        data = expr['data']
1173        assert isinstance(data, dict)
1174        ifcond = QAPISchemaIfCond(expr.get('if'))
1175        info = expr.info
1176        features = self._make_features(expr.get('features'), info)
1177        variants = [
1178            self._make_variant(key, value['type'],
1179                               QAPISchemaIfCond(value.get('if')),
1180                               info)
1181            for (key, value) in data.items()]
1182        tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1183        self._def_definition(
1184            QAPISchemaAlternateType(
1185                name, info, expr.doc, ifcond, features,
1186                QAPISchemaVariants(None, info, tag_member, variants)))
1187
1188    def _def_command(self, expr: QAPIExpression):
1189        name = expr['command']
1190        data = expr.get('data')
1191        rets = expr.get('returns')
1192        gen = expr.get('gen', True)
1193        success_response = expr.get('success-response', True)
1194        boxed = expr.get('boxed', False)
1195        allow_oob = expr.get('allow-oob', False)
1196        allow_preconfig = expr.get('allow-preconfig', False)
1197        coroutine = expr.get('coroutine', False)
1198        ifcond = QAPISchemaIfCond(expr.get('if'))
1199        info = expr.info
1200        features = self._make_features(expr.get('features'), info)
1201        if isinstance(data, OrderedDict):
1202            data = self._make_implicit_object_type(
1203                name, info, ifcond,
1204                'arg', self._make_members(data, info))
1205        if isinstance(rets, list):
1206            assert len(rets) == 1
1207            rets = self._make_array_type(rets[0], info)
1208        self._def_definition(
1209            QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
1210                              rets, gen, success_response, boxed, allow_oob,
1211                              allow_preconfig, coroutine))
1212
1213    def _def_event(self, expr: QAPIExpression):
1214        name = expr['event']
1215        data = expr.get('data')
1216        boxed = expr.get('boxed', False)
1217        ifcond = QAPISchemaIfCond(expr.get('if'))
1218        info = expr.info
1219        features = self._make_features(expr.get('features'), info)
1220        if isinstance(data, OrderedDict):
1221            data = self._make_implicit_object_type(
1222                name, info, ifcond,
1223                'arg', self._make_members(data, info))
1224        self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
1225                                             features, data, boxed))
1226
1227    def _def_exprs(self, exprs):
1228        for expr in exprs:
1229            if 'enum' in expr:
1230                self._def_enum_type(expr)
1231            elif 'struct' in expr:
1232                self._def_struct_type(expr)
1233            elif 'union' in expr:
1234                self._def_union_type(expr)
1235            elif 'alternate' in expr:
1236                self._def_alternate_type(expr)
1237            elif 'command' in expr:
1238                self._def_command(expr)
1239            elif 'event' in expr:
1240                self._def_event(expr)
1241            elif 'include' in expr:
1242                self._def_include(expr)
1243            else:
1244                assert False
1245
1246    def check(self):
1247        for ent in self._entity_list:
1248            ent.check(self)
1249            ent.connect_doc()
1250        for ent in self._entity_list:
1251            ent.set_module(self)
1252        for doc in self.docs:
1253            doc.check()
1254
1255    def visit(self, visitor):
1256        visitor.visit_begin(self)
1257        for mod in self._module_dict.values():
1258            mod.visit(visitor)
1259        visitor.visit_end()
1260