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