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