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