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