qapi: Require boxed for conditional command and event arguments
[qemu/armbru.git] / scripts / qapi / schema.py
blob8f31f8832f37c15584c907c79030c2c2d2aa3871
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 assert not self.variants # not implemented
469 for m in self.members:
470 m.check_clash(info, seen)
472 def connect_doc(self, doc=None):
473 super().connect_doc(doc)
474 doc = doc or self.doc
475 if self.base and self.base.is_implicit():
476 self.base.connect_doc(doc)
477 for m in self.local_members:
478 m.connect_doc(doc)
480 def is_implicit(self):
481 # See QAPISchema._make_implicit_object_type(), as well as
482 # _def_predefineds()
483 return self.name.startswith('q_')
485 def is_empty(self):
486 assert self.members is not None
487 return not self.members and not self.variants
489 def has_conditional_members(self):
490 assert self.members is not None
491 return any(m.ifcond.is_present() for m in self.members)
493 def c_name(self):
494 assert self.name != 'q_empty'
495 return super().c_name()
497 def c_type(self):
498 assert not self.is_implicit()
499 return c_name(self.name) + POINTER_SUFFIX
501 def c_unboxed_type(self):
502 return c_name(self.name)
504 def json_type(self):
505 return 'object'
507 def visit(self, visitor):
508 super().visit(visitor)
509 visitor.visit_object_type(
510 self.name, self.info, self.ifcond, self.features,
511 self.base, self.local_members, self.variants)
512 visitor.visit_object_type_flat(
513 self.name, self.info, self.ifcond, self.features,
514 self.members, self.variants)
517 class QAPISchemaAlternateType(QAPISchemaType):
518 meta = 'alternate'
520 def __init__(self, name, info, doc, ifcond, features, variants):
521 super().__init__(name, info, doc, ifcond, features)
522 assert isinstance(variants, QAPISchemaVariants)
523 assert variants.tag_member
524 variants.set_defined_in(name)
525 variants.tag_member.set_defined_in(self.name)
526 self.variants = variants
528 def check(self, schema):
529 super().check(schema)
530 self.variants.tag_member.check(schema)
531 # Not calling self.variants.check_clash(), because there's nothing
532 # to clash with
533 self.variants.check(schema, {})
534 # Alternate branch names have no relation to the tag enum values;
535 # so we have to check for potential name collisions ourselves.
536 seen = {}
537 types_seen = {}
538 for v in self.variants.variants:
539 v.check_clash(self.info, seen)
540 qtype = v.type.alternate_qtype()
541 if not qtype:
542 raise QAPISemError(
543 self.info,
544 "%s cannot use %s"
545 % (v.describe(self.info), v.type.describe()))
546 conflicting = set([qtype])
547 if qtype == 'QTYPE_QSTRING':
548 if isinstance(v.type, QAPISchemaEnumType):
549 for m in v.type.members:
550 if m.name in ['on', 'off']:
551 conflicting.add('QTYPE_QBOOL')
552 if re.match(r'[-+0-9.]', m.name):
553 # lazy, could be tightened
554 conflicting.add('QTYPE_QNUM')
555 else:
556 conflicting.add('QTYPE_QNUM')
557 conflicting.add('QTYPE_QBOOL')
558 for qt in conflicting:
559 if qt in types_seen:
560 raise QAPISemError(
561 self.info,
562 "%s can't be distinguished from '%s'"
563 % (v.describe(self.info), types_seen[qt]))
564 types_seen[qt] = v.name
566 def connect_doc(self, doc=None):
567 super().connect_doc(doc)
568 doc = doc or self.doc
569 for v in self.variants.variants:
570 v.connect_doc(doc)
572 def c_type(self):
573 return c_name(self.name) + POINTER_SUFFIX
575 def json_type(self):
576 return 'value'
578 def visit(self, visitor):
579 super().visit(visitor)
580 visitor.visit_alternate_type(
581 self.name, self.info, self.ifcond, self.features, self.variants)
584 class QAPISchemaVariants:
585 def __init__(self, tag_name, info, tag_member, variants):
586 # Unions pass tag_name but not tag_member.
587 # Alternates pass tag_member but not tag_name.
588 # After check(), tag_member is always set.
589 assert bool(tag_member) != bool(tag_name)
590 assert (isinstance(tag_name, str) or
591 isinstance(tag_member, QAPISchemaObjectTypeMember))
592 for v in variants:
593 assert isinstance(v, QAPISchemaVariant)
594 self._tag_name = tag_name
595 self.info = info
596 self.tag_member = tag_member
597 self.variants = variants
599 def set_defined_in(self, name):
600 for v in self.variants:
601 v.set_defined_in(name)
603 def check(self, schema, seen):
604 if self._tag_name: # union
605 self.tag_member = seen.get(c_name(self._tag_name))
606 base = "'base'"
607 # Pointing to the base type when not implicit would be
608 # nice, but we don't know it here
609 if not self.tag_member or self._tag_name != self.tag_member.name:
610 raise QAPISemError(
611 self.info,
612 "discriminator '%s' is not a member of %s"
613 % (self._tag_name, base))
614 # Here we do:
615 base_type = schema.lookup_type(self.tag_member.defined_in)
616 assert base_type
617 if not base_type.is_implicit():
618 base = "base type '%s'" % self.tag_member.defined_in
619 if not isinstance(self.tag_member.type, QAPISchemaEnumType):
620 raise QAPISemError(
621 self.info,
622 "discriminator member '%s' of %s must be of enum type"
623 % (self._tag_name, base))
624 if self.tag_member.optional:
625 raise QAPISemError(
626 self.info,
627 "discriminator member '%s' of %s must not be optional"
628 % (self._tag_name, base))
629 if self.tag_member.ifcond.is_present():
630 raise QAPISemError(
631 self.info,
632 "discriminator member '%s' of %s must not be conditional"
633 % (self._tag_name, base))
634 else: # alternate
635 assert isinstance(self.tag_member.type, QAPISchemaEnumType)
636 assert not self.tag_member.optional
637 assert not self.tag_member.ifcond.is_present()
638 if self._tag_name: # union
639 # branches that are not explicitly covered get an empty type
640 cases = {v.name for v in self.variants}
641 for m in self.tag_member.type.members:
642 if m.name not in cases:
643 v = QAPISchemaVariant(m.name, self.info,
644 'q_empty', m.ifcond)
645 v.set_defined_in(self.tag_member.defined_in)
646 self.variants.append(v)
647 if not self.variants:
648 raise QAPISemError(self.info, "union has no branches")
649 for v in self.variants:
650 v.check(schema)
651 # Union names must match enum values; alternate names are
652 # checked separately. Use 'seen' to tell the two apart.
653 if seen:
654 if v.name not in self.tag_member.type.member_names():
655 raise QAPISemError(
656 self.info,
657 "branch '%s' is not a value of %s"
658 % (v.name, self.tag_member.type.describe()))
659 if (not isinstance(v.type, QAPISchemaObjectType)
660 or v.type.variants):
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 defined_in = self.defined_in
705 assert defined_in
707 if defined_in.startswith('q_obj_'):
708 # See QAPISchema._make_implicit_object_type() - reverse the
709 # mapping there to create a nice human-readable description
710 defined_in = defined_in[6:]
711 if defined_in.endswith('-arg'):
712 # Implicit type created for a command's dict 'data'
713 assert role == 'member'
714 role = 'parameter'
715 elif defined_in.endswith('-base'):
716 # Implicit type created for a union's dict 'base'
717 role = 'base ' + role
718 else:
719 assert False
720 elif defined_in != info.defn_name:
721 return "%s '%s' of type '%s'" % (role, self.name, defined_in)
722 return "%s '%s'" % (role, self.name)
725 class QAPISchemaEnumMember(QAPISchemaMember):
726 role = 'value'
728 def __init__(self, name, info, ifcond=None, features=None):
729 super().__init__(name, info, ifcond)
730 for f in features or []:
731 assert isinstance(f, QAPISchemaFeature)
732 f.set_defined_in(name)
733 self.features = features or []
735 def connect_doc(self, doc):
736 super().connect_doc(doc)
737 if doc:
738 for f in self.features:
739 doc.connect_feature(f)
742 class QAPISchemaFeature(QAPISchemaMember):
743 role = 'feature'
745 def is_special(self):
746 return self.name in ('deprecated', 'unstable')
749 class QAPISchemaObjectTypeMember(QAPISchemaMember):
750 def __init__(self, name, info, typ, optional, ifcond=None, features=None):
751 super().__init__(name, info, ifcond)
752 assert isinstance(typ, str)
753 assert isinstance(optional, bool)
754 for f in features or []:
755 assert isinstance(f, QAPISchemaFeature)
756 f.set_defined_in(name)
757 self._type_name = typ
758 self.type = None
759 self.optional = optional
760 self.features = features or []
762 def need_has(self):
763 assert self.type
764 return self.optional and self.type.need_has_if_optional()
766 def check(self, schema):
767 assert self.defined_in
768 self.type = schema.resolve_type(self._type_name, self.info,
769 self.describe)
770 seen = {}
771 for f in self.features:
772 f.check_clash(self.info, seen)
774 def connect_doc(self, doc):
775 super().connect_doc(doc)
776 if doc:
777 for f in self.features:
778 doc.connect_feature(f)
781 class QAPISchemaVariant(QAPISchemaObjectTypeMember):
782 role = 'branch'
784 def __init__(self, name, info, typ, ifcond=None):
785 super().__init__(name, info, typ, False, ifcond)
788 class QAPISchemaCommand(QAPISchemaEntity):
789 meta = 'command'
791 def __init__(self, name, info, doc, ifcond, features,
792 arg_type, ret_type,
793 gen, success_response, boxed, allow_oob, allow_preconfig,
794 coroutine):
795 super().__init__(name, info, doc, ifcond, features)
796 assert not arg_type or isinstance(arg_type, str)
797 assert not ret_type or isinstance(ret_type, str)
798 self._arg_type_name = arg_type
799 self.arg_type = None
800 self._ret_type_name = ret_type
801 self.ret_type = None
802 self.gen = gen
803 self.success_response = success_response
804 self.boxed = boxed
805 self.allow_oob = allow_oob
806 self.allow_preconfig = allow_preconfig
807 self.coroutine = coroutine
809 def check(self, schema):
810 super().check(schema)
811 if self._arg_type_name:
812 self.arg_type = schema.resolve_type(
813 self._arg_type_name, self.info, "command's 'data'")
814 if not isinstance(self.arg_type, QAPISchemaObjectType):
815 raise QAPISemError(
816 self.info,
817 "command's 'data' cannot take %s"
818 % self.arg_type.describe())
819 if self.arg_type.variants and not self.boxed:
820 raise QAPISemError(
821 self.info,
822 "command's 'data' can take %s only with 'boxed': true"
823 % self.arg_type.describe())
824 self.arg_type.check(schema)
825 if self.arg_type.has_conditional_members() and not self.boxed:
826 raise QAPISemError(
827 self.info,
828 "conditional command arguments require 'boxed': true")
829 if self._ret_type_name:
830 self.ret_type = schema.resolve_type(
831 self._ret_type_name, self.info, "command's 'returns'")
832 if self.name not in self.info.pragma.command_returns_exceptions:
833 typ = self.ret_type
834 if isinstance(typ, QAPISchemaArrayType):
835 typ = self.ret_type.element_type
836 assert typ
837 if not isinstance(typ, QAPISchemaObjectType):
838 raise QAPISemError(
839 self.info,
840 "command's 'returns' cannot take %s"
841 % self.ret_type.describe())
843 def connect_doc(self, doc=None):
844 super().connect_doc(doc)
845 doc = doc or self.doc
846 if doc:
847 if self.arg_type and self.arg_type.is_implicit():
848 self.arg_type.connect_doc(doc)
850 def visit(self, visitor):
851 super().visit(visitor)
852 visitor.visit_command(
853 self.name, self.info, self.ifcond, self.features,
854 self.arg_type, self.ret_type, self.gen, self.success_response,
855 self.boxed, self.allow_oob, self.allow_preconfig,
856 self.coroutine)
859 class QAPISchemaEvent(QAPISchemaEntity):
860 meta = 'event'
862 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
863 super().__init__(name, info, doc, ifcond, features)
864 assert not arg_type or isinstance(arg_type, str)
865 self._arg_type_name = arg_type
866 self.arg_type = None
867 self.boxed = boxed
869 def check(self, schema):
870 super().check(schema)
871 if self._arg_type_name:
872 self.arg_type = schema.resolve_type(
873 self._arg_type_name, self.info, "event's 'data'")
874 if not isinstance(self.arg_type, QAPISchemaObjectType):
875 raise QAPISemError(
876 self.info,
877 "event's 'data' cannot take %s"
878 % self.arg_type.describe())
879 if self.arg_type.variants and not self.boxed:
880 raise QAPISemError(
881 self.info,
882 "event's 'data' can take %s only with 'boxed': true"
883 % self.arg_type.describe())
884 self.arg_type.check(schema)
885 if self.arg_type.has_conditional_members() and not self.boxed:
886 raise QAPISemError(
887 self.info,
888 "conditional event arguments require 'boxed': true")
890 def connect_doc(self, doc=None):
891 super().connect_doc(doc)
892 doc = doc or self.doc
893 if doc:
894 if self.arg_type and self.arg_type.is_implicit():
895 self.arg_type.connect_doc(doc)
897 def visit(self, visitor):
898 super().visit(visitor)
899 visitor.visit_event(
900 self.name, self.info, self.ifcond, self.features,
901 self.arg_type, self.boxed)
904 class QAPISchema:
905 def __init__(self, fname):
906 self.fname = fname
908 try:
909 parser = QAPISchemaParser(fname)
910 except OSError as err:
911 raise QAPIError(
912 f"can't read schema file '{fname}': {err.strerror}"
913 ) from err
915 exprs = check_exprs(parser.exprs)
916 self.docs = parser.docs
917 self._entity_list = []
918 self._entity_dict = {}
919 self._module_dict = OrderedDict()
920 self._schema_dir = os.path.dirname(fname)
921 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
922 self._make_module(fname)
923 self._predefining = True
924 self._def_predefineds()
925 self._predefining = False
926 self._def_exprs(exprs)
927 self.check()
929 def _def_entity(self, ent):
930 # Only the predefined types are allowed to not have info
931 assert ent.info or self._predefining
932 self._entity_list.append(ent)
933 if ent.name is None:
934 return
935 # TODO reject names that differ only in '_' vs. '.' vs. '-',
936 # because they're liable to clash in generated C.
937 other_ent = self._entity_dict.get(ent.name)
938 if other_ent:
939 if other_ent.info:
940 where = QAPISourceError(other_ent.info, "previous definition")
941 raise QAPISemError(
942 ent.info,
943 "'%s' is already defined\n%s" % (ent.name, where))
944 raise QAPISemError(
945 ent.info, "%s is already defined" % other_ent.describe())
946 self._entity_dict[ent.name] = ent
948 def lookup_entity(self, name, typ=None):
949 ent = self._entity_dict.get(name)
950 if typ and not isinstance(ent, typ):
951 return None
952 return ent
954 def lookup_type(self, name):
955 return self.lookup_entity(name, QAPISchemaType)
957 def resolve_type(self, name, info, what):
958 typ = self.lookup_type(name)
959 if not typ:
960 if callable(what):
961 what = what(info)
962 raise QAPISemError(
963 info, "%s uses unknown type '%s'" % (what, name))
964 return typ
966 def _module_name(self, fname: str) -> str:
967 if QAPISchemaModule.is_system_module(fname):
968 return fname
969 return os.path.relpath(fname, self._schema_dir)
971 def _make_module(self, fname):
972 name = self._module_name(fname)
973 if name not in self._module_dict:
974 self._module_dict[name] = QAPISchemaModule(name)
975 return self._module_dict[name]
977 def module_by_fname(self, fname):
978 name = self._module_name(fname)
979 return self._module_dict[name]
981 def _def_include(self, expr: QAPIExpression):
982 include = expr['include']
983 assert expr.doc is None
984 self._def_entity(
985 QAPISchemaInclude(self._make_module(include), expr.info))
987 def _def_builtin_type(self, name, json_type, c_type):
988 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
989 # Instantiating only the arrays that are actually used would
990 # be nice, but we can't as long as their generated code
991 # (qapi-builtin-types.[ch]) may be shared by some other
992 # schema.
993 self._make_array_type(name, None)
995 def _def_predefineds(self):
996 for t in [('str', 'string', 'char' + POINTER_SUFFIX),
997 ('number', 'number', 'double'),
998 ('int', 'int', 'int64_t'),
999 ('int8', 'int', 'int8_t'),
1000 ('int16', 'int', 'int16_t'),
1001 ('int32', 'int', 'int32_t'),
1002 ('int64', 'int', 'int64_t'),
1003 ('uint8', 'int', 'uint8_t'),
1004 ('uint16', 'int', 'uint16_t'),
1005 ('uint32', 'int', 'uint32_t'),
1006 ('uint64', 'int', 'uint64_t'),
1007 ('size', 'int', 'uint64_t'),
1008 ('bool', 'boolean', 'bool'),
1009 ('any', 'value', 'QObject' + POINTER_SUFFIX),
1010 ('null', 'null', 'QNull' + POINTER_SUFFIX)]:
1011 self._def_builtin_type(*t)
1012 self.the_empty_object_type = QAPISchemaObjectType(
1013 'q_empty', None, None, None, None, None, [], None)
1014 self._def_entity(self.the_empty_object_type)
1016 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1017 'qbool']
1018 qtype_values = self._make_enum_members(
1019 [{'name': n} for n in qtypes], None)
1021 self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
1022 qtype_values, 'QTYPE'))
1024 def _make_features(self, features, info):
1025 if features is None:
1026 return []
1027 return [QAPISchemaFeature(f['name'], info,
1028 QAPISchemaIfCond(f.get('if')))
1029 for f in features]
1031 def _make_enum_member(self, name, ifcond, features, info):
1032 return QAPISchemaEnumMember(name, info,
1033 QAPISchemaIfCond(ifcond),
1034 self._make_features(features, info))
1036 def _make_enum_members(self, values, info):
1037 return [self._make_enum_member(v['name'], v.get('if'),
1038 v.get('features'), info)
1039 for v in values]
1041 def _make_array_type(self, element_type, info):
1042 name = element_type + 'List' # reserved by check_defn_name_str()
1043 if not self.lookup_type(name):
1044 self._def_entity(QAPISchemaArrayType(name, info, element_type))
1045 return name
1047 def _make_implicit_object_type(self, name, info, ifcond, role, members):
1048 if not members:
1049 return None
1050 # See also QAPISchemaObjectTypeMember.describe()
1051 name = 'q_obj_%s-%s' % (name, role)
1052 typ = self.lookup_entity(name, QAPISchemaObjectType)
1053 if typ:
1054 # The implicit object type has multiple users. This can
1055 # only be a duplicate definition, which will be flagged
1056 # later.
1057 pass
1058 else:
1059 self._def_entity(QAPISchemaObjectType(
1060 name, info, None, ifcond, None, None, members, None))
1061 return name
1063 def _def_enum_type(self, expr: QAPIExpression):
1064 name = expr['enum']
1065 data = expr['data']
1066 prefix = expr.get('prefix')
1067 ifcond = QAPISchemaIfCond(expr.get('if'))
1068 info = expr.info
1069 features = self._make_features(expr.get('features'), info)
1070 self._def_entity(QAPISchemaEnumType(
1071 name, info, expr.doc, ifcond, features,
1072 self._make_enum_members(data, info), prefix))
1074 def _make_member(self, name, typ, ifcond, features, info):
1075 optional = False
1076 if name.startswith('*'):
1077 name = name[1:]
1078 optional = True
1079 if isinstance(typ, list):
1080 assert len(typ) == 1
1081 typ = self._make_array_type(typ[0], info)
1082 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1083 self._make_features(features, info))
1085 def _make_members(self, data, info):
1086 return [self._make_member(key, value['type'],
1087 QAPISchemaIfCond(value.get('if')),
1088 value.get('features'), info)
1089 for (key, value) in data.items()]
1091 def _def_struct_type(self, expr: QAPIExpression):
1092 name = expr['struct']
1093 base = expr.get('base')
1094 data = expr['data']
1095 info = expr.info
1096 ifcond = QAPISchemaIfCond(expr.get('if'))
1097 features = self._make_features(expr.get('features'), info)
1098 self._def_entity(QAPISchemaObjectType(
1099 name, info, expr.doc, ifcond, features, base,
1100 self._make_members(data, info),
1101 None))
1103 def _make_variant(self, case, typ, ifcond, info):
1104 if isinstance(typ, list):
1105 assert len(typ) == 1
1106 typ = self._make_array_type(typ[0], info)
1107 return QAPISchemaVariant(case, info, typ, ifcond)
1109 def _def_union_type(self, expr: QAPIExpression):
1110 name = expr['union']
1111 base = expr['base']
1112 tag_name = expr['discriminator']
1113 data = expr['data']
1114 assert isinstance(data, dict)
1115 info = expr.info
1116 ifcond = QAPISchemaIfCond(expr.get('if'))
1117 features = self._make_features(expr.get('features'), info)
1118 if isinstance(base, dict):
1119 base = self._make_implicit_object_type(
1120 name, info, ifcond,
1121 'base', self._make_members(base, info))
1122 variants = [
1123 self._make_variant(key, value['type'],
1124 QAPISchemaIfCond(value.get('if')),
1125 info)
1126 for (key, value) in data.items()]
1127 members: List[QAPISchemaObjectTypeMember] = []
1128 self._def_entity(
1129 QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
1130 base, members,
1131 QAPISchemaVariants(
1132 tag_name, info, None, variants)))
1134 def _def_alternate_type(self, expr: QAPIExpression):
1135 name = expr['alternate']
1136 data = expr['data']
1137 assert isinstance(data, dict)
1138 ifcond = QAPISchemaIfCond(expr.get('if'))
1139 info = expr.info
1140 features = self._make_features(expr.get('features'), info)
1141 variants = [
1142 self._make_variant(key, value['type'],
1143 QAPISchemaIfCond(value.get('if')),
1144 info)
1145 for (key, value) in data.items()]
1146 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1147 self._def_entity(
1148 QAPISchemaAlternateType(
1149 name, info, expr.doc, ifcond, features,
1150 QAPISchemaVariants(None, info, tag_member, variants)))
1152 def _def_command(self, expr: QAPIExpression):
1153 name = expr['command']
1154 data = expr.get('data')
1155 rets = expr.get('returns')
1156 gen = expr.get('gen', True)
1157 success_response = expr.get('success-response', True)
1158 boxed = expr.get('boxed', False)
1159 allow_oob = expr.get('allow-oob', False)
1160 allow_preconfig = expr.get('allow-preconfig', False)
1161 coroutine = expr.get('coroutine', False)
1162 ifcond = QAPISchemaIfCond(expr.get('if'))
1163 info = expr.info
1164 features = self._make_features(expr.get('features'), info)
1165 if isinstance(data, OrderedDict):
1166 data = self._make_implicit_object_type(
1167 name, info, ifcond,
1168 'arg', self._make_members(data, info))
1169 if isinstance(rets, list):
1170 assert len(rets) == 1
1171 rets = self._make_array_type(rets[0], info)
1172 self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
1173 features, data, rets,
1174 gen, success_response,
1175 boxed, allow_oob, allow_preconfig,
1176 coroutine))
1178 def _def_event(self, expr: QAPIExpression):
1179 name = expr['event']
1180 data = expr.get('data')
1181 boxed = expr.get('boxed', False)
1182 ifcond = QAPISchemaIfCond(expr.get('if'))
1183 info = expr.info
1184 features = self._make_features(expr.get('features'), info)
1185 if isinstance(data, OrderedDict):
1186 data = self._make_implicit_object_type(
1187 name, info, ifcond,
1188 'arg', self._make_members(data, info))
1189 self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
1190 features, data, boxed))
1192 def _def_exprs(self, exprs):
1193 for expr in exprs:
1194 if 'enum' in expr:
1195 self._def_enum_type(expr)
1196 elif 'struct' in expr:
1197 self._def_struct_type(expr)
1198 elif 'union' in expr:
1199 self._def_union_type(expr)
1200 elif 'alternate' in expr:
1201 self._def_alternate_type(expr)
1202 elif 'command' in expr:
1203 self._def_command(expr)
1204 elif 'event' in expr:
1205 self._def_event(expr)
1206 elif 'include' in expr:
1207 self._def_include(expr)
1208 else:
1209 assert False
1211 def check(self):
1212 for ent in self._entity_list:
1213 ent.check(self)
1214 ent.connect_doc()
1215 ent.check_doc()
1216 for ent in self._entity_list:
1217 ent.set_module(self)
1219 def visit(self, visitor):
1220 visitor.visit_begin(self)
1221 for mod in self._module_dict.values():
1222 mod.visit(visitor)
1223 visitor.visit_end()