qapi/schema: adjust type narrowing for mypy's benefit
[qemu/kevin.git] / scripts / qapi / schema.py
blobe44802369dd892091fb9235133dba05adec81cec
1 # -*- coding: utf-8 -*-
3 # QAPI schema internal representation
5 # Copyright (c) 2015-2019 Red Hat Inc.
7 # Authors:
8 # Markus Armbruster <armbru@redhat.com>
9 # Eric Blake <eblake@redhat.com>
10 # Marc-André Lureau <marcandre.lureau@redhat.com>
12 # This work is licensed under the terms of the GNU GPL, version 2.
13 # See the COPYING file in the top-level directory.
15 # pylint: disable=too-many-lines
17 # TODO catching name collisions in generated code would be nice
19 from abc import ABC, abstractmethod
20 from collections import OrderedDict
21 import os
22 import re
23 from typing import List, Optional
25 from .common import (
26 POINTER_SUFFIX,
27 c_name,
28 cgen_ifcond,
29 docgen_ifcond,
30 gen_endif,
31 gen_if,
33 from .error import QAPIError, QAPISemError, QAPISourceError
34 from .expr import check_exprs
35 from .parser import QAPIExpression, QAPISchemaParser
38 class QAPISchemaIfCond:
39 def __init__(self, ifcond=None):
40 self.ifcond = ifcond
42 def _cgen(self):
43 return cgen_ifcond(self.ifcond)
45 def gen_if(self):
46 return gen_if(self._cgen())
48 def gen_endif(self):
49 return gen_endif(self._cgen())
51 def docgen(self):
52 return docgen_ifcond(self.ifcond)
54 def is_present(self):
55 return bool(self.ifcond)
58 class QAPISchemaEntity:
59 """
60 A schema entity.
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
75 def __repr__(self):
76 return "<%s at 0x%x>" % (type(self).__name__, id(self))
78 def check(self, schema):
79 # pylint: disable=unused-argument
80 self._checked = True
82 def connect_doc(self, doc=None):
83 pass
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)
91 def set_module(self, schema):
92 self._set_module(schema, self.info)
94 def visit(self, visitor):
95 # pylint: disable=unused-argument
96 assert self._checked
99 class QAPISchemaDefinition(QAPISchemaEntity):
100 meta: Optional[str] = None
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 []
113 def __repr__(self):
114 return "<%s:%s at 0x%x>" % (type(self).__name__, self.name,
115 id(self))
117 def c_name(self):
118 return c_name(self.name)
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)
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)
134 @property
135 def ifcond(self):
136 assert self._checked
137 return self._ifcond
139 def is_implicit(self):
140 return not self.info
142 def describe(self):
143 assert self.meta
144 return "%s '%s'" % (self.meta, self.name)
147 class QAPISchemaVisitor:
148 def visit_begin(self, schema):
149 pass
151 def visit_end(self):
152 pass
154 def visit_module(self, name):
155 pass
157 def visit_needed(self, entity):
158 # pylint: disable=unused-argument
159 # Default to visiting everything
160 return True
162 def visit_include(self, name, info):
163 pass
165 def visit_builtin_type(self, name, info, json_type):
166 pass
168 def visit_enum_type(self, name, info, ifcond, features, members, prefix):
169 pass
171 def visit_array_type(self, name, info, ifcond, element_type):
172 pass
174 def visit_object_type(self, name, info, ifcond, features,
175 base, members, variants):
176 pass
178 def visit_object_type_flat(self, name, info, ifcond, features,
179 members, variants):
180 pass
182 def visit_alternate_type(self, name, info, ifcond, features, variants):
183 pass
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
190 def visit_event(self, name, info, ifcond, features, arg_type, boxed):
191 pass
194 class QAPISchemaModule:
196 BUILTIN_MODULE_NAME = './builtin'
198 def __init__(self, name):
199 self.name = name
200 self._entity_list = []
202 @staticmethod
203 def is_system_module(name: str) -> bool:
205 System modules are internally defined modules.
207 Their names start with the "./" prefix.
209 return name.startswith('./')
211 @classmethod
212 def is_user_module(cls, name: str) -> bool:
214 User modules are those defined by the user in qapi JSON files.
216 They do not start with the "./" prefix.
218 return not cls.is_system_module(name)
220 @classmethod
221 def is_builtin_module(cls, name: str) -> bool:
223 The built-in module is a single System module for the built-in types.
225 It is always "./builtin".
227 return name == cls.BUILTIN_MODULE_NAME
229 def add_entity(self, ent):
230 self._entity_list.append(ent)
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)
239 class QAPISchemaInclude(QAPISchemaEntity):
240 def __init__(self, sub_module, info):
241 super().__init__(info)
242 self._sub_module = sub_module
244 def visit(self, visitor):
245 super().visit(visitor)
246 visitor.visit_include(self._sub_module.name, self.info)
249 class 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
256 # Return the C type to be used in a parameter list.
257 def c_param_type(self):
258 return self.c_type()
260 # Return the C type to be used where we suppress boxing.
261 def c_unboxed_type(self):
262 return self.c_type()
264 @abstractmethod
265 def json_type(self):
266 pass
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'
278 return json2qtype.get(self.json_type())
280 def doc_type(self):
281 if self.is_implicit():
282 return None
283 return self.name
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)
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")
298 def describe(self):
299 assert self.meta
300 return "%s type '%s'" % (self.meta, self.name)
303 class QAPISchemaBuiltinType(QAPISchemaType):
304 meta = 'built-in'
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
314 def c_name(self):
315 return self.name
317 def c_type(self):
318 return self._c_type_name
320 def c_param_type(self):
321 if self.name == 'str':
322 return 'const ' + self._c_type_name
323 return self._c_type_name
325 def json_type(self):
326 return self._json_type_name
328 def doc_type(self):
329 return self.json_type()
331 def visit(self, visitor):
332 super().visit(visitor)
333 visitor.visit_builtin_type(self.name, self.info, self.json_type())
336 class QAPISchemaEnumType(QAPISchemaType):
337 meta = 'enum'
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
348 def check(self, schema):
349 super().check(schema)
350 seen = {}
351 for m in self.members:
352 m.check_clash(self.info, seen)
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)
360 def is_implicit(self):
361 # See QAPISchema._def_predefineds()
362 return self.name == 'QType'
364 def c_type(self):
365 return c_name(self.name)
367 def member_names(self):
368 return [m.name for m in self.members]
370 def json_type(self):
371 return 'string'
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)
380 class QAPISchemaArrayType(QAPISchemaType):
381 meta = 'array'
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
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
394 def check(self, schema):
395 super().check(schema)
396 self.element_type = schema.resolve_type(
397 self._element_type_name, self.info,
398 self.info and self.info.defn_meta)
399 assert not isinstance(self.element_type, QAPISchemaArrayType)
401 def set_module(self, schema):
402 self._set_module(schema, self.element_type.info)
404 @property
405 def ifcond(self):
406 assert self._checked
407 return self.element_type.ifcond
409 def is_implicit(self):
410 return True
412 def c_type(self):
413 return c_name(self.name) + POINTER_SUFFIX
415 def json_type(self):
416 return 'array'
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
424 def visit(self, visitor):
425 super().visit(visitor)
426 visitor.visit_array_type(self.name, self.info, self.ifcond,
427 self.element_type)
429 def describe(self):
430 assert self.meta
431 return "%s type ['%s']" % (self.meta, self._element_type_name)
434 class 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
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)
466 super().check(schema)
467 assert self._checked and self.members is None
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()
486 if self.variants:
487 self.variants.check(schema, seen)
488 self.variants.check_clash(self.info, seen)
490 self.members = members # mark completed
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)
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)
510 def is_implicit(self):
511 # See QAPISchema._make_implicit_object_type(), as well as
512 # _def_predefineds()
513 return self.name.startswith('q_')
515 def is_empty(self):
516 assert self.members is not None
517 return not self.members and not self.variants
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)
523 def c_name(self):
524 assert self.name != 'q_empty'
525 return super().c_name()
527 def c_type(self):
528 assert not self.is_implicit()
529 return c_name(self.name) + POINTER_SUFFIX
531 def c_unboxed_type(self):
532 return c_name(self.name)
534 def json_type(self):
535 return 'object'
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)
547 class QAPISchemaAlternateType(QAPISchemaType):
548 meta = 'alternate'
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
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
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)
602 def c_type(self):
603 return c_name(self.name) + POINTER_SUFFIX
605 def json_type(self):
606 return 'value'
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)
614 class 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
629 def set_defined_in(self, name):
630 for v in self.variants:
631 v.set_defined_in(name)
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)
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))
703 class QAPISchemaMember:
704 """ Represents object members, enum members and features """
705 role = 'member'
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
714 def set_defined_in(self, name):
715 assert not self.defined_in
716 self.defined_in = name
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
727 def connect_doc(self, doc):
728 if doc:
729 doc.connect_member(self)
731 def describe(self, info):
732 role = self.role
733 meta = 'type'
734 defined_in = self.defined_in
735 assert defined_in
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
754 if defined_in != info.defn_name:
755 return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in)
756 return "%s '%s'" % (role, self.name)
759 class QAPISchemaEnumMember(QAPISchemaMember):
760 role = 'value'
762 def __init__(self, name, info, ifcond=None, features=None):
763 super().__init__(name, info, ifcond)
764 for f in features or []:
765 assert isinstance(f, QAPISchemaFeature)
766 f.set_defined_in(name)
767 self.features = features or []
769 def connect_doc(self, doc):
770 super().connect_doc(doc)
771 if doc:
772 for f in self.features:
773 doc.connect_feature(f)
776 class QAPISchemaFeature(QAPISchemaMember):
777 role = 'feature'
779 def is_special(self):
780 return self.name in ('deprecated', 'unstable')
783 class QAPISchemaObjectTypeMember(QAPISchemaMember):
784 def __init__(self, name, info, typ, optional, ifcond=None, features=None):
785 super().__init__(name, info, ifcond)
786 assert isinstance(typ, str)
787 assert isinstance(optional, bool)
788 for f in features or []:
789 assert isinstance(f, QAPISchemaFeature)
790 f.set_defined_in(name)
791 self._type_name = typ
792 self.type: QAPISchemaType # set during check()
793 self.optional = optional
794 self.features = features or []
796 def need_has(self):
797 assert self.type
798 return self.optional and self.type.need_has_if_optional()
800 def check(self, schema):
801 assert self.defined_in
802 self.type = schema.resolve_type(self._type_name, self.info,
803 self.describe)
804 seen = {}
805 for f in self.features:
806 f.check_clash(self.info, seen)
808 def connect_doc(self, doc):
809 super().connect_doc(doc)
810 if doc:
811 for f in self.features:
812 doc.connect_feature(f)
815 class QAPISchemaVariant(QAPISchemaObjectTypeMember):
816 role = 'branch'
818 def __init__(self, name, info, typ, ifcond=None):
819 super().__init__(name, info, typ, False, ifcond)
822 class QAPISchemaCommand(QAPISchemaDefinition):
823 meta = 'command'
825 def __init__(self, name, info, doc, ifcond, features,
826 arg_type, ret_type,
827 gen, success_response, boxed, allow_oob, allow_preconfig,
828 coroutine):
829 super().__init__(name, info, doc, ifcond, features)
830 assert not arg_type or isinstance(arg_type, str)
831 assert not ret_type or isinstance(ret_type, str)
832 self._arg_type_name = arg_type
833 self.arg_type = None
834 self._ret_type_name = ret_type
835 self.ret_type = None
836 self.gen = gen
837 self.success_response = success_response
838 self.boxed = boxed
839 self.allow_oob = allow_oob
840 self.allow_preconfig = allow_preconfig
841 self.coroutine = coroutine
843 def check(self, schema):
844 super().check(schema)
845 if self._arg_type_name:
846 arg_type = schema.resolve_type(
847 self._arg_type_name, self.info, "command's 'data'")
848 if not isinstance(arg_type, QAPISchemaObjectType):
849 raise QAPISemError(
850 self.info,
851 "command's 'data' cannot take %s"
852 % arg_type.describe())
853 self.arg_type = arg_type
854 if self.arg_type.variants and not self.boxed:
855 raise QAPISemError(
856 self.info,
857 "command's 'data' can take %s only with 'boxed': true"
858 % self.arg_type.describe())
859 self.arg_type.check(schema)
860 if self.arg_type.has_conditional_members() and not self.boxed:
861 raise QAPISemError(
862 self.info,
863 "conditional command arguments require 'boxed': true")
864 if self._ret_type_name:
865 self.ret_type = schema.resolve_type(
866 self._ret_type_name, self.info, "command's 'returns'")
867 if self.name not in self.info.pragma.command_returns_exceptions:
868 typ = self.ret_type
869 if isinstance(typ, QAPISchemaArrayType):
870 assert typ
871 typ = typ.element_type
872 if not isinstance(typ, QAPISchemaObjectType):
873 raise QAPISemError(
874 self.info,
875 "command's 'returns' cannot take %s"
876 % self.ret_type.describe())
878 def connect_doc(self, doc=None):
879 super().connect_doc(doc)
880 doc = doc or self.doc
881 if doc:
882 if self.arg_type and self.arg_type.is_implicit():
883 self.arg_type.connect_doc(doc)
885 def visit(self, visitor):
886 super().visit(visitor)
887 visitor.visit_command(
888 self.name, self.info, self.ifcond, self.features,
889 self.arg_type, self.ret_type, self.gen, self.success_response,
890 self.boxed, self.allow_oob, self.allow_preconfig,
891 self.coroutine)
894 class QAPISchemaEvent(QAPISchemaDefinition):
895 meta = 'event'
897 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
898 super().__init__(name, info, doc, ifcond, features)
899 assert not arg_type or isinstance(arg_type, str)
900 self._arg_type_name = arg_type
901 self.arg_type = None
902 self.boxed = boxed
904 def check(self, schema):
905 super().check(schema)
906 if self._arg_type_name:
907 typ = schema.resolve_type(
908 self._arg_type_name, self.info, "event's 'data'")
909 if not isinstance(typ, QAPISchemaObjectType):
910 raise QAPISemError(
911 self.info,
912 "event's 'data' cannot take %s"
913 % typ.describe())
914 self.arg_type = typ
915 if self.arg_type.variants and not self.boxed:
916 raise QAPISemError(
917 self.info,
918 "event's 'data' can take %s only with 'boxed': true"
919 % self.arg_type.describe())
920 self.arg_type.check(schema)
921 if self.arg_type.has_conditional_members() and not self.boxed:
922 raise QAPISemError(
923 self.info,
924 "conditional event arguments require 'boxed': true")
926 def connect_doc(self, doc=None):
927 super().connect_doc(doc)
928 doc = doc or self.doc
929 if doc:
930 if self.arg_type and self.arg_type.is_implicit():
931 self.arg_type.connect_doc(doc)
933 def visit(self, visitor):
934 super().visit(visitor)
935 visitor.visit_event(
936 self.name, self.info, self.ifcond, self.features,
937 self.arg_type, self.boxed)
940 class QAPISchema:
941 def __init__(self, fname):
942 self.fname = fname
944 try:
945 parser = QAPISchemaParser(fname)
946 except OSError as err:
947 raise QAPIError(
948 f"can't read schema file '{fname}': {err.strerror}"
949 ) from err
951 exprs = check_exprs(parser.exprs)
952 self.docs = parser.docs
953 self._entity_list = []
954 self._entity_dict = {}
955 self._module_dict = OrderedDict()
956 self._schema_dir = os.path.dirname(fname)
957 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
958 self._make_module(fname)
959 self._predefining = True
960 self._def_predefineds()
961 self._predefining = False
962 self._def_exprs(exprs)
963 self.check()
965 def _def_entity(self, ent):
966 self._entity_list.append(ent)
968 def _def_definition(self, defn):
969 # Only the predefined types are allowed to not have info
970 assert defn.info or self._predefining
971 self._def_entity(defn)
972 # TODO reject names that differ only in '_' vs. '.' vs. '-',
973 # because they're liable to clash in generated C.
974 other_defn = self._entity_dict.get(defn.name)
975 if other_defn:
976 if other_defn.info:
977 where = QAPISourceError(other_defn.info, "previous definition")
978 raise QAPISemError(
979 defn.info,
980 "'%s' is already defined\n%s" % (defn.name, where))
981 raise QAPISemError(
982 defn.info, "%s is already defined" % other_defn.describe())
983 self._entity_dict[defn.name] = defn
985 def lookup_entity(self, name, typ=None):
986 ent = self._entity_dict.get(name)
987 if typ and not isinstance(ent, typ):
988 return None
989 return ent
991 def lookup_type(self, name):
992 return self.lookup_entity(name, QAPISchemaType)
994 def resolve_type(self, name, info, what):
995 typ = self.lookup_type(name)
996 if not typ:
997 if callable(what):
998 what = what(info)
999 raise QAPISemError(
1000 info, "%s uses unknown type '%s'" % (what, name))
1001 return typ
1003 def _module_name(self, fname: str) -> str:
1004 if QAPISchemaModule.is_system_module(fname):
1005 return fname
1006 return os.path.relpath(fname, self._schema_dir)
1008 def _make_module(self, fname):
1009 name = self._module_name(fname)
1010 if name not in self._module_dict:
1011 self._module_dict[name] = QAPISchemaModule(name)
1012 return self._module_dict[name]
1014 def module_by_fname(self, fname):
1015 name = self._module_name(fname)
1016 return self._module_dict[name]
1018 def _def_include(self, expr: QAPIExpression):
1019 include = expr['include']
1020 assert expr.doc is None
1021 self._def_entity(
1022 QAPISchemaInclude(self._make_module(include), expr.info))
1024 def _def_builtin_type(self, name, json_type, c_type):
1025 self._def_definition(QAPISchemaBuiltinType(name, json_type, c_type))
1026 # Instantiating only the arrays that are actually used would
1027 # be nice, but we can't as long as their generated code
1028 # (qapi-builtin-types.[ch]) may be shared by some other
1029 # schema.
1030 self._make_array_type(name, None)
1032 def _def_predefineds(self):
1033 for t in [('str', 'string', 'char' + POINTER_SUFFIX),
1034 ('number', 'number', 'double'),
1035 ('int', 'int', 'int64_t'),
1036 ('int8', 'int', 'int8_t'),
1037 ('int16', 'int', 'int16_t'),
1038 ('int32', 'int', 'int32_t'),
1039 ('int64', 'int', 'int64_t'),
1040 ('uint8', 'int', 'uint8_t'),
1041 ('uint16', 'int', 'uint16_t'),
1042 ('uint32', 'int', 'uint32_t'),
1043 ('uint64', 'int', 'uint64_t'),
1044 ('size', 'int', 'uint64_t'),
1045 ('bool', 'boolean', 'bool'),
1046 ('any', 'value', 'QObject' + POINTER_SUFFIX),
1047 ('null', 'null', 'QNull' + POINTER_SUFFIX)]:
1048 self._def_builtin_type(*t)
1049 self.the_empty_object_type = QAPISchemaObjectType(
1050 'q_empty', None, None, None, None, None, [], None)
1051 self._def_definition(self.the_empty_object_type)
1053 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1054 'qbool']
1055 qtype_values = self._make_enum_members(
1056 [{'name': n} for n in qtypes], None)
1058 self._def_definition(QAPISchemaEnumType(
1059 'QType', None, None, None, None, qtype_values, 'QTYPE'))
1061 def _make_features(self, features, info):
1062 if features is None:
1063 return []
1064 return [QAPISchemaFeature(f['name'], info,
1065 QAPISchemaIfCond(f.get('if')))
1066 for f in features]
1068 def _make_enum_member(self, name, ifcond, features, info):
1069 return QAPISchemaEnumMember(name, info,
1070 QAPISchemaIfCond(ifcond),
1071 self._make_features(features, info))
1073 def _make_enum_members(self, values, info):
1074 return [self._make_enum_member(v['name'], v.get('if'),
1075 v.get('features'), info)
1076 for v in values]
1078 def _make_array_type(self, element_type, info):
1079 name = element_type + 'List' # reserved by check_defn_name_str()
1080 if not self.lookup_type(name):
1081 self._def_definition(QAPISchemaArrayType(
1082 name, info, element_type))
1083 return name
1085 def _make_implicit_object_type(self, name, info, ifcond, role, members):
1086 if not members:
1087 return None
1088 # See also QAPISchemaObjectTypeMember.describe()
1089 name = 'q_obj_%s-%s' % (name, role)
1090 typ = self.lookup_entity(name, QAPISchemaObjectType)
1091 if typ:
1092 # The implicit object type has multiple users. This can
1093 # only be a duplicate definition, which will be flagged
1094 # later.
1095 pass
1096 else:
1097 self._def_definition(QAPISchemaObjectType(
1098 name, info, None, ifcond, None, None, members, None))
1099 return name
1101 def _def_enum_type(self, expr: QAPIExpression):
1102 name = expr['enum']
1103 data = expr['data']
1104 prefix = expr.get('prefix')
1105 ifcond = QAPISchemaIfCond(expr.get('if'))
1106 info = expr.info
1107 features = self._make_features(expr.get('features'), info)
1108 self._def_definition(QAPISchemaEnumType(
1109 name, info, expr.doc, ifcond, features,
1110 self._make_enum_members(data, info), prefix))
1112 def _make_member(self, name, typ, ifcond, features, info):
1113 optional = False
1114 if name.startswith('*'):
1115 name = name[1:]
1116 optional = True
1117 if isinstance(typ, list):
1118 assert len(typ) == 1
1119 typ = self._make_array_type(typ[0], info)
1120 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1121 self._make_features(features, info))
1123 def _make_members(self, data, info):
1124 return [self._make_member(key, value['type'],
1125 QAPISchemaIfCond(value.get('if')),
1126 value.get('features'), info)
1127 for (key, value) in data.items()]
1129 def _def_struct_type(self, expr: QAPIExpression):
1130 name = expr['struct']
1131 base = expr.get('base')
1132 data = expr['data']
1133 info = expr.info
1134 ifcond = QAPISchemaIfCond(expr.get('if'))
1135 features = self._make_features(expr.get('features'), info)
1136 self._def_definition(QAPISchemaObjectType(
1137 name, info, expr.doc, ifcond, features, base,
1138 self._make_members(data, info),
1139 None))
1141 def _make_variant(self, case, typ, ifcond, info):
1142 if isinstance(typ, list):
1143 assert len(typ) == 1
1144 typ = self._make_array_type(typ[0], info)
1145 return QAPISchemaVariant(case, info, typ, ifcond)
1147 def _def_union_type(self, expr: QAPIExpression):
1148 name = expr['union']
1149 base = expr['base']
1150 tag_name = expr['discriminator']
1151 data = expr['data']
1152 assert isinstance(data, dict)
1153 info = expr.info
1154 ifcond = QAPISchemaIfCond(expr.get('if'))
1155 features = self._make_features(expr.get('features'), info)
1156 if isinstance(base, dict):
1157 base = self._make_implicit_object_type(
1158 name, info, ifcond,
1159 'base', self._make_members(base, info))
1160 variants = [
1161 self._make_variant(key, value['type'],
1162 QAPISchemaIfCond(value.get('if')),
1163 info)
1164 for (key, value) in data.items()]
1165 members: List[QAPISchemaObjectTypeMember] = []
1166 self._def_definition(
1167 QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1168 base, members,
1169 QAPISchemaVariants(
1170 tag_name, info, None, variants)))
1172 def _def_alternate_type(self, expr: QAPIExpression):
1173 name = expr['alternate']
1174 data = expr['data']
1175 assert isinstance(data, dict)
1176 ifcond = QAPISchemaIfCond(expr.get('if'))
1177 info = expr.info
1178 features = self._make_features(expr.get('features'), info)
1179 variants = [
1180 self._make_variant(key, value['type'],
1181 QAPISchemaIfCond(value.get('if')),
1182 info)
1183 for (key, value) in data.items()]
1184 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1185 self._def_definition(
1186 QAPISchemaAlternateType(
1187 name, info, expr.doc, ifcond, features,
1188 QAPISchemaVariants(None, info, tag_member, variants)))
1190 def _def_command(self, expr: QAPIExpression):
1191 name = expr['command']
1192 data = expr.get('data')
1193 rets = expr.get('returns')
1194 gen = expr.get('gen', True)
1195 success_response = expr.get('success-response', True)
1196 boxed = expr.get('boxed', False)
1197 allow_oob = expr.get('allow-oob', False)
1198 allow_preconfig = expr.get('allow-preconfig', False)
1199 coroutine = expr.get('coroutine', False)
1200 ifcond = QAPISchemaIfCond(expr.get('if'))
1201 info = expr.info
1202 features = self._make_features(expr.get('features'), info)
1203 if isinstance(data, OrderedDict):
1204 data = self._make_implicit_object_type(
1205 name, info, ifcond,
1206 'arg', self._make_members(data, info))
1207 if isinstance(rets, list):
1208 assert len(rets) == 1
1209 rets = self._make_array_type(rets[0], info)
1210 self._def_definition(
1211 QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
1212 rets, gen, success_response, boxed, allow_oob,
1213 allow_preconfig, coroutine))
1215 def _def_event(self, expr: QAPIExpression):
1216 name = expr['event']
1217 data = expr.get('data')
1218 boxed = expr.get('boxed', False)
1219 ifcond = QAPISchemaIfCond(expr.get('if'))
1220 info = expr.info
1221 features = self._make_features(expr.get('features'), info)
1222 if isinstance(data, OrderedDict):
1223 data = self._make_implicit_object_type(
1224 name, info, ifcond,
1225 'arg', self._make_members(data, info))
1226 self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
1227 features, data, boxed))
1229 def _def_exprs(self, exprs):
1230 for expr in exprs:
1231 if 'enum' in expr:
1232 self._def_enum_type(expr)
1233 elif 'struct' in expr:
1234 self._def_struct_type(expr)
1235 elif 'union' in expr:
1236 self._def_union_type(expr)
1237 elif 'alternate' in expr:
1238 self._def_alternate_type(expr)
1239 elif 'command' in expr:
1240 self._def_command(expr)
1241 elif 'event' in expr:
1242 self._def_event(expr)
1243 elif 'include' in expr:
1244 self._def_include(expr)
1245 else:
1246 assert False
1248 def check(self):
1249 for ent in self._entity_list:
1250 ent.check(self)
1251 ent.connect_doc()
1252 for ent in self._entity_list:
1253 ent.set_module(self)
1254 for doc in self.docs:
1255 doc.check()
1257 def visit(self, visitor):
1258 visitor.visit_begin(self)
1259 for mod in self._module_dict.values():
1260 mod.visit(visitor)
1261 visitor.visit_end()