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