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 22from collections import OrderedDict 23import os 24import re 25from typing import ( 26 Any, 27 Callable, 28 Dict, 29 List, 30 Optional, 31 Union, 32 ValuesView, 33 cast, 34) 35 36from .common import ( 37 POINTER_SUFFIX, 38 c_name, 39 cgen_ifcond, 40 docgen_ifcond, 41 gen_endif, 42 gen_if, 43) 44from .error import QAPIError, QAPISemError, QAPISourceError 45from .expr import check_exprs 46from .parser import QAPIDoc, QAPIExpression, QAPISchemaParser 47from .source import QAPISourceInfo 48 49 50class QAPISchemaIfCond: 51 def __init__( 52 self, 53 ifcond: Optional[Union[str, Dict[str, object]]] = None, 54 ) -> None: 55 self.ifcond = ifcond 56 57 def _cgen(self) -> str: 58 return cgen_ifcond(self.ifcond) 59 60 def gen_if(self) -> str: 61 return gen_if(self._cgen()) 62 63 def gen_endif(self) -> str: 64 return gen_endif(self._cgen()) 65 66 def docgen(self) -> str: 67 return docgen_ifcond(self.ifcond) 68 69 def is_present(self) -> bool: 70 return bool(self.ifcond) 71 72 73class QAPISchemaEntity: 74 """ 75 A schema entity. 76 77 This is either a directive, such as include, or a definition. 78 The latter uses sub-class `QAPISchemaDefinition`. 79 """ 80 def __init__(self, info: Optional[QAPISourceInfo]): 81 self._module: Optional[QAPISchemaModule] = None 82 # For explicitly defined entities, info points to the (explicit) 83 # definition. For builtins (and their arrays), info is None. 84 # For implicitly defined entities, info points to a place that 85 # triggered the implicit definition (there may be more than one 86 # such place). 87 self.info = info 88 self._checked = False 89 90 def __repr__(self) -> str: 91 return "<%s at 0x%x>" % (type(self).__name__, id(self)) 92 93 def check(self, schema: QAPISchema) -> None: 94 # pylint: disable=unused-argument 95 self._checked = True 96 97 def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None: 98 pass 99 100 def _set_module( 101 self, schema: QAPISchema, info: Optional[QAPISourceInfo] 102 ) -> None: 103 assert self._checked 104 fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME 105 self._module = schema.module_by_fname(fname) 106 self._module.add_entity(self) 107 108 def set_module(self, schema: QAPISchema) -> None: 109 self._set_module(schema, self.info) 110 111 def visit(self, visitor: QAPISchemaVisitor) -> None: 112 # pylint: disable=unused-argument 113 assert self._checked 114 115 116class QAPISchemaDefinition(QAPISchemaEntity): 117 meta: str 118 119 def __init__( 120 self, 121 name: str, 122 info: Optional[QAPISourceInfo], 123 doc: Optional[QAPIDoc], 124 ifcond: Optional[QAPISchemaIfCond] = None, 125 features: Optional[List[QAPISchemaFeature]] = None, 126 ): 127 super().__init__(info) 128 for f in features or []: 129 f.set_defined_in(name) 130 self.name = name 131 self.doc = doc 132 self._ifcond = ifcond or QAPISchemaIfCond() 133 self.features = features or [] 134 135 def __repr__(self) -> str: 136 return "<%s:%s at 0x%x>" % (type(self).__name__, self.name, 137 id(self)) 138 139 def c_name(self) -> str: 140 return c_name(self.name) 141 142 def check(self, schema: QAPISchema) -> None: 143 assert not self._checked 144 super().check(schema) 145 seen: Dict[str, QAPISchemaMember] = {} 146 for f in self.features: 147 f.check_clash(self.info, seen) 148 149 def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None: 150 super().connect_doc(doc) 151 doc = doc or self.doc 152 if doc: 153 for f in self.features: 154 doc.connect_feature(f) 155 156 @property 157 def ifcond(self) -> QAPISchemaIfCond: 158 assert self._checked 159 return self._ifcond 160 161 def is_implicit(self) -> bool: 162 return not self.info 163 164 def describe(self) -> str: 165 return "%s '%s'" % (self.meta, self.name) 166 167 168class QAPISchemaVisitor: 169 def visit_begin(self, schema: QAPISchema) -> None: 170 pass 171 172 def visit_end(self) -> None: 173 pass 174 175 def visit_module(self, name: str) -> None: 176 pass 177 178 def visit_needed(self, entity: QAPISchemaEntity) -> bool: 179 # pylint: disable=unused-argument 180 # Default to visiting everything 181 return True 182 183 def visit_include(self, name: str, info: Optional[QAPISourceInfo]) -> None: 184 pass 185 186 def visit_builtin_type( 187 self, name: str, info: Optional[QAPISourceInfo], json_type: str 188 ) -> None: 189 pass 190 191 def visit_enum_type( 192 self, 193 name: str, 194 info: Optional[QAPISourceInfo], 195 ifcond: QAPISchemaIfCond, 196 features: List[QAPISchemaFeature], 197 members: List[QAPISchemaEnumMember], 198 prefix: Optional[str], 199 ) -> None: 200 pass 201 202 def visit_array_type( 203 self, 204 name: str, 205 info: Optional[QAPISourceInfo], 206 ifcond: QAPISchemaIfCond, 207 element_type: QAPISchemaType, 208 ) -> None: 209 pass 210 211 def visit_object_type( 212 self, 213 name: str, 214 info: Optional[QAPISourceInfo], 215 ifcond: QAPISchemaIfCond, 216 features: List[QAPISchemaFeature], 217 base: Optional[QAPISchemaObjectType], 218 members: List[QAPISchemaObjectTypeMember], 219 branches: Optional[QAPISchemaBranches], 220 ) -> None: 221 pass 222 223 def visit_object_type_flat( 224 self, 225 name: str, 226 info: Optional[QAPISourceInfo], 227 ifcond: QAPISchemaIfCond, 228 features: List[QAPISchemaFeature], 229 members: List[QAPISchemaObjectTypeMember], 230 branches: Optional[QAPISchemaBranches], 231 ) -> None: 232 pass 233 234 def visit_alternate_type( 235 self, 236 name: str, 237 info: Optional[QAPISourceInfo], 238 ifcond: QAPISchemaIfCond, 239 features: List[QAPISchemaFeature], 240 alternatives: QAPISchemaAlternatives, 241 ) -> None: 242 pass 243 244 def visit_command( 245 self, 246 name: str, 247 info: Optional[QAPISourceInfo], 248 ifcond: QAPISchemaIfCond, 249 features: List[QAPISchemaFeature], 250 arg_type: Optional[QAPISchemaObjectType], 251 ret_type: Optional[QAPISchemaType], 252 gen: bool, 253 success_response: bool, 254 boxed: bool, 255 allow_oob: bool, 256 allow_preconfig: bool, 257 coroutine: bool, 258 ) -> None: 259 pass 260 261 def visit_event( 262 self, 263 name: str, 264 info: Optional[QAPISourceInfo], 265 ifcond: QAPISchemaIfCond, 266 features: List[QAPISchemaFeature], 267 arg_type: Optional[QAPISchemaObjectType], 268 boxed: bool, 269 ) -> None: 270 pass 271 272 273class QAPISchemaModule: 274 275 BUILTIN_MODULE_NAME = './builtin' 276 277 def __init__(self, name: str): 278 self.name = name 279 self._entity_list: List[QAPISchemaEntity] = [] 280 281 @staticmethod 282 def is_system_module(name: str) -> bool: 283 """ 284 System modules are internally defined modules. 285 286 Their names start with the "./" prefix. 287 """ 288 return name.startswith('./') 289 290 @classmethod 291 def is_user_module(cls, name: str) -> bool: 292 """ 293 User modules are those defined by the user in qapi JSON files. 294 295 They do not start with the "./" prefix. 296 """ 297 return not cls.is_system_module(name) 298 299 @classmethod 300 def is_builtin_module(cls, name: str) -> bool: 301 """ 302 The built-in module is a single System module for the built-in types. 303 304 It is always "./builtin". 305 """ 306 return name == cls.BUILTIN_MODULE_NAME 307 308 def add_entity(self, ent: QAPISchemaEntity) -> None: 309 self._entity_list.append(ent) 310 311 def visit(self, visitor: QAPISchemaVisitor) -> None: 312 visitor.visit_module(self.name) 313 for entity in self._entity_list: 314 if visitor.visit_needed(entity): 315 entity.visit(visitor) 316 317 318class QAPISchemaInclude(QAPISchemaEntity): 319 def __init__(self, sub_module: QAPISchemaModule, info: QAPISourceInfo): 320 super().__init__(info) 321 self._sub_module = sub_module 322 323 def visit(self, visitor: QAPISchemaVisitor) -> None: 324 super().visit(visitor) 325 visitor.visit_include(self._sub_module.name, self.info) 326 327 328class QAPISchemaType(QAPISchemaDefinition, ABC): 329 # Return the C type for common use. 330 # For the types we commonly box, this is a pointer type. 331 @abstractmethod 332 def c_type(self) -> str: 333 pass 334 335 # Return the C type to be used in a parameter list. 336 def c_param_type(self) -> str: 337 return self.c_type() 338 339 # Return the C type to be used where we suppress boxing. 340 def c_unboxed_type(self) -> str: 341 return self.c_type() 342 343 @abstractmethod 344 def json_type(self) -> str: 345 pass 346 347 def alternate_qtype(self) -> Optional[str]: 348 json2qtype = { 349 'null': 'QTYPE_QNULL', 350 'string': 'QTYPE_QSTRING', 351 'number': 'QTYPE_QNUM', 352 'int': 'QTYPE_QNUM', 353 'boolean': 'QTYPE_QBOOL', 354 'array': 'QTYPE_QLIST', 355 'object': 'QTYPE_QDICT' 356 } 357 return json2qtype.get(self.json_type()) 358 359 def doc_type(self) -> Optional[str]: 360 if self.is_implicit(): 361 return None 362 return self.name 363 364 def need_has_if_optional(self) -> bool: 365 # When FOO is a pointer, has_FOO == !!FOO, i.e. has_FOO is redundant. 366 # Except for arrays; see QAPISchemaArrayType.need_has_if_optional(). 367 return not self.c_type().endswith(POINTER_SUFFIX) 368 369 def check(self, schema: QAPISchema) -> None: 370 super().check(schema) 371 for feat in self.features: 372 if feat.is_special(): 373 raise QAPISemError( 374 self.info, 375 f"feature '{feat.name}' is not supported for types") 376 377 def describe(self) -> str: 378 return "%s type '%s'" % (self.meta, self.name) 379 380 381class QAPISchemaBuiltinType(QAPISchemaType): 382 meta = 'built-in' 383 384 def __init__(self, name: str, json_type: str, c_type: str): 385 super().__init__(name, None, None) 386 assert json_type in ('string', 'number', 'int', 'boolean', 'null', 387 'value') 388 self._json_type_name = json_type 389 self._c_type_name = c_type 390 391 def c_name(self) -> str: 392 return self.name 393 394 def c_type(self) -> str: 395 return self._c_type_name 396 397 def c_param_type(self) -> str: 398 if self.name == 'str': 399 return 'const ' + self._c_type_name 400 return self._c_type_name 401 402 def json_type(self) -> str: 403 return self._json_type_name 404 405 def doc_type(self) -> str: 406 return self.json_type() 407 408 def visit(self, visitor: QAPISchemaVisitor) -> None: 409 super().visit(visitor) 410 visitor.visit_builtin_type(self.name, self.info, self.json_type()) 411 412 413class QAPISchemaEnumType(QAPISchemaType): 414 meta = 'enum' 415 416 def __init__( 417 self, 418 name: str, 419 info: Optional[QAPISourceInfo], 420 doc: Optional[QAPIDoc], 421 ifcond: Optional[QAPISchemaIfCond], 422 features: Optional[List[QAPISchemaFeature]], 423 members: List[QAPISchemaEnumMember], 424 prefix: Optional[str], 425 ): 426 super().__init__(name, info, doc, ifcond, features) 427 for m in members: 428 m.set_defined_in(name) 429 self.members = members 430 self.prefix = prefix 431 432 def check(self, schema: QAPISchema) -> None: 433 super().check(schema) 434 seen: Dict[str, QAPISchemaMember] = {} 435 for m in self.members: 436 m.check_clash(self.info, seen) 437 438 def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None: 439 super().connect_doc(doc) 440 doc = doc or self.doc 441 for m in self.members: 442 m.connect_doc(doc) 443 444 def is_implicit(self) -> bool: 445 # See QAPISchema._def_predefineds() 446 return self.name == 'QType' 447 448 def c_type(self) -> str: 449 return c_name(self.name) 450 451 def member_names(self) -> List[str]: 452 return [m.name for m in self.members] 453 454 def json_type(self) -> str: 455 return 'string' 456 457 def visit(self, visitor: QAPISchemaVisitor) -> None: 458 super().visit(visitor) 459 visitor.visit_enum_type( 460 self.name, self.info, self.ifcond, self.features, 461 self.members, self.prefix) 462 463 464class QAPISchemaArrayType(QAPISchemaType): 465 meta = 'array' 466 467 def __init__( 468 self, name: str, info: Optional[QAPISourceInfo], element_type: str 469 ): 470 super().__init__(name, info, None) 471 self._element_type_name = element_type 472 self.element_type: QAPISchemaType 473 474 def need_has_if_optional(self) -> bool: 475 # When FOO is an array, we still need has_FOO to distinguish 476 # absent (!has_FOO) from present and empty (has_FOO && !FOO). 477 return True 478 479 def check(self, schema: QAPISchema) -> None: 480 super().check(schema) 481 self.element_type = schema.resolve_type( 482 self._element_type_name, self.info, 483 self.info.defn_meta if self.info else None) 484 assert not isinstance(self.element_type, QAPISchemaArrayType) 485 486 def set_module(self, schema: QAPISchema) -> None: 487 self._set_module(schema, self.element_type.info) 488 489 @property 490 def ifcond(self) -> QAPISchemaIfCond: 491 assert self._checked 492 return self.element_type.ifcond 493 494 def is_implicit(self) -> bool: 495 return True 496 497 def c_type(self) -> str: 498 return c_name(self.name) + POINTER_SUFFIX 499 500 def json_type(self) -> str: 501 return 'array' 502 503 def doc_type(self) -> Optional[str]: 504 elt_doc_type = self.element_type.doc_type() 505 if not elt_doc_type: 506 return None 507 return 'array of ' + elt_doc_type 508 509 def visit(self, visitor: QAPISchemaVisitor) -> None: 510 super().visit(visitor) 511 visitor.visit_array_type(self.name, self.info, self.ifcond, 512 self.element_type) 513 514 def describe(self) -> str: 515 return "%s type ['%s']" % (self.meta, self._element_type_name) 516 517 518class QAPISchemaObjectType(QAPISchemaType): 519 def __init__( 520 self, 521 name: str, 522 info: Optional[QAPISourceInfo], 523 doc: Optional[QAPIDoc], 524 ifcond: Optional[QAPISchemaIfCond], 525 features: Optional[List[QAPISchemaFeature]], 526 base: Optional[str], 527 local_members: List[QAPISchemaObjectTypeMember], 528 branches: Optional[QAPISchemaBranches], 529 ): 530 # struct has local_members, optional base, and no branches 531 # union has base, branches, and no local_members 532 super().__init__(name, info, doc, ifcond, features) 533 self.meta = 'union' if branches else 'struct' 534 for m in local_members: 535 m.set_defined_in(name) 536 if branches is not None: 537 branches.set_defined_in(name) 538 self._base_name = base 539 self.base = None 540 self.local_members = local_members 541 self.branches = branches 542 self.members: List[QAPISchemaObjectTypeMember] 543 self._check_complete = False 544 545 def check(self, schema: QAPISchema) -> None: 546 # This calls another type T's .check() exactly when the C 547 # struct emitted by gen_object() contains that T's C struct 548 # (pointers don't count). 549 if self._check_complete: 550 # A previous .check() completed: nothing to do 551 return 552 if self._checked: 553 # Recursed: C struct contains itself 554 raise QAPISemError(self.info, 555 "object %s contains itself" % self.name) 556 557 super().check(schema) 558 assert self._checked and not self._check_complete 559 560 seen = OrderedDict() 561 if self._base_name: 562 self.base = schema.resolve_type(self._base_name, self.info, 563 "'base'") 564 if (not isinstance(self.base, QAPISchemaObjectType) 565 or self.base.branches): 566 raise QAPISemError( 567 self.info, 568 "'base' requires a struct type, %s isn't" 569 % self.base.describe()) 570 self.base.check(schema) 571 self.base.check_clash(self.info, seen) 572 for m in self.local_members: 573 m.check(schema) 574 m.check_clash(self.info, seen) 575 576 # self.check_clash() works in terms of the supertype, but 577 # self.members is declared List[QAPISchemaObjectTypeMember]. 578 # Cast down to the subtype. 579 members = cast(List[QAPISchemaObjectTypeMember], list(seen.values())) 580 581 if self.branches: 582 self.branches.check(schema, seen) 583 self.branches.check_clash(self.info, seen) 584 585 self.members = members 586 self._check_complete = True # mark completed 587 588 # Check that the members of this type do not cause duplicate JSON members, 589 # and update seen to track the members seen so far. Report any errors 590 # on behalf of info, which is not necessarily self.info 591 def check_clash( 592 self, 593 info: Optional[QAPISourceInfo], 594 seen: Dict[str, QAPISchemaMember], 595 ) -> None: 596 assert self._checked 597 for m in self.members: 598 m.check_clash(info, seen) 599 if self.branches: 600 self.branches.check_clash(info, seen) 601 602 def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None: 603 super().connect_doc(doc) 604 doc = doc or self.doc 605 if self.base and self.base.is_implicit(): 606 self.base.connect_doc(doc) 607 for m in self.local_members: 608 m.connect_doc(doc) 609 610 def is_implicit(self) -> bool: 611 # See QAPISchema._make_implicit_object_type(), as well as 612 # _def_predefineds() 613 return self.name.startswith('q_') 614 615 def is_empty(self) -> bool: 616 return not self.members and not self.branches 617 618 def has_conditional_members(self) -> bool: 619 return any(m.ifcond.is_present() for m in self.members) 620 621 def c_name(self) -> str: 622 assert self.name != 'q_empty' 623 return super().c_name() 624 625 def c_type(self) -> str: 626 assert not self.is_implicit() 627 return c_name(self.name) + POINTER_SUFFIX 628 629 def c_unboxed_type(self) -> str: 630 return c_name(self.name) 631 632 def json_type(self) -> str: 633 return 'object' 634 635 def visit(self, visitor: QAPISchemaVisitor) -> None: 636 super().visit(visitor) 637 visitor.visit_object_type( 638 self.name, self.info, self.ifcond, self.features, 639 self.base, self.local_members, self.branches) 640 visitor.visit_object_type_flat( 641 self.name, self.info, self.ifcond, self.features, 642 self.members, self.branches) 643 644 645class QAPISchemaAlternateType(QAPISchemaType): 646 meta = 'alternate' 647 648 def __init__( 649 self, 650 name: str, 651 info: QAPISourceInfo, 652 doc: Optional[QAPIDoc], 653 ifcond: Optional[QAPISchemaIfCond], 654 features: List[QAPISchemaFeature], 655 alternatives: QAPISchemaAlternatives, 656 ): 657 super().__init__(name, info, doc, ifcond, features) 658 assert alternatives.tag_member 659 alternatives.set_defined_in(name) 660 alternatives.tag_member.set_defined_in(self.name) 661 self.alternatives = alternatives 662 663 def check(self, schema: QAPISchema) -> None: 664 super().check(schema) 665 self.alternatives.tag_member.check(schema) 666 # Not calling self.alternatives.check_clash(), because there's 667 # nothing to clash with 668 self.alternatives.check(schema, {}) 669 # Alternate branch names have no relation to the tag enum values; 670 # so we have to check for potential name collisions ourselves. 671 seen: Dict[str, QAPISchemaMember] = {} 672 types_seen: Dict[str, str] = {} 673 for v in self.alternatives.variants: 674 v.check_clash(self.info, seen) 675 qtype = v.type.alternate_qtype() 676 if not qtype: 677 raise QAPISemError( 678 self.info, 679 "%s cannot use %s" 680 % (v.describe(self.info), v.type.describe())) 681 conflicting = set([qtype]) 682 if qtype == 'QTYPE_QSTRING': 683 if isinstance(v.type, QAPISchemaEnumType): 684 for m in v.type.members: 685 if m.name in ['on', 'off']: 686 conflicting.add('QTYPE_QBOOL') 687 if re.match(r'[-+0-9.]', m.name): 688 # lazy, could be tightened 689 conflicting.add('QTYPE_QNUM') 690 else: 691 conflicting.add('QTYPE_QNUM') 692 conflicting.add('QTYPE_QBOOL') 693 for qt in conflicting: 694 if qt in types_seen: 695 raise QAPISemError( 696 self.info, 697 "%s can't be distinguished from '%s'" 698 % (v.describe(self.info), types_seen[qt])) 699 types_seen[qt] = v.name 700 701 def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None: 702 super().connect_doc(doc) 703 doc = doc or self.doc 704 for v in self.alternatives.variants: 705 v.connect_doc(doc) 706 707 def c_type(self) -> str: 708 return c_name(self.name) + POINTER_SUFFIX 709 710 def json_type(self) -> str: 711 return 'value' 712 713 def visit(self, visitor: QAPISchemaVisitor) -> None: 714 super().visit(visitor) 715 visitor.visit_alternate_type( 716 self.name, self.info, self.ifcond, self.features, 717 self.alternatives) 718 719 720class QAPISchemaVariants: 721 def __init__( 722 self, 723 info: QAPISourceInfo, 724 variants: List[QAPISchemaVariant], 725 ): 726 self.info = info 727 self.tag_member: QAPISchemaObjectTypeMember 728 self.variants = variants 729 730 def set_defined_in(self, name: str) -> None: 731 for v in self.variants: 732 v.set_defined_in(name) 733 734 # pylint: disable=unused-argument 735 def check( 736 self, schema: QAPISchema, seen: Dict[str, QAPISchemaMember] 737 ) -> None: 738 for v in self.variants: 739 v.check(schema) 740 741 742class QAPISchemaBranches(QAPISchemaVariants): 743 def __init__(self, 744 info: QAPISourceInfo, 745 variants: List[QAPISchemaVariant], 746 tag_name: str): 747 super().__init__(info, variants) 748 self._tag_name = tag_name 749 750 def check( 751 self, schema: QAPISchema, seen: Dict[str, QAPISchemaMember] 752 ) -> None: 753 # We need to narrow the member type: 754 tag_member = seen.get(c_name(self._tag_name)) 755 assert (tag_member is None 756 or isinstance(tag_member, QAPISchemaObjectTypeMember)) 757 758 base = "'base'" 759 # Pointing to the base type when not implicit would be 760 # nice, but we don't know it here 761 if not tag_member or self._tag_name != tag_member.name: 762 raise QAPISemError( 763 self.info, 764 "discriminator '%s' is not a member of %s" 765 % (self._tag_name, base)) 766 self.tag_member = tag_member 767 # Here we do: 768 assert tag_member.defined_in 769 base_type = schema.lookup_type(tag_member.defined_in) 770 assert base_type 771 if not base_type.is_implicit(): 772 base = "base type '%s'" % tag_member.defined_in 773 if not isinstance(tag_member.type, QAPISchemaEnumType): 774 raise QAPISemError( 775 self.info, 776 "discriminator member '%s' of %s must be of enum type" 777 % (self._tag_name, base)) 778 if tag_member.optional: 779 raise QAPISemError( 780 self.info, 781 "discriminator member '%s' of %s must not be optional" 782 % (self._tag_name, base)) 783 if tag_member.ifcond.is_present(): 784 raise QAPISemError( 785 self.info, 786 "discriminator member '%s' of %s must not be conditional" 787 % (self._tag_name, base)) 788 # branches that are not explicitly covered get an empty type 789 assert tag_member.defined_in 790 cases = {v.name for v in self.variants} 791 for m in tag_member.type.members: 792 if m.name not in cases: 793 v = QAPISchemaVariant(m.name, self.info, 794 'q_empty', m.ifcond) 795 v.set_defined_in(tag_member.defined_in) 796 self.variants.append(v) 797 if not self.variants: 798 raise QAPISemError(self.info, "union has no branches") 799 for v in self.variants: 800 v.check(schema) 801 # Union names must match enum values; alternate names are 802 # checked separately. Use 'seen' to tell the two apart. 803 if seen: 804 if v.name not in tag_member.type.member_names(): 805 raise QAPISemError( 806 self.info, 807 "branch '%s' is not a value of %s" 808 % (v.name, tag_member.type.describe())) 809 if not isinstance(v.type, QAPISchemaObjectType): 810 raise QAPISemError( 811 self.info, 812 "%s cannot use %s" 813 % (v.describe(self.info), v.type.describe())) 814 v.type.check(schema) 815 816 def check_clash( 817 self, 818 info: Optional[QAPISourceInfo], 819 seen: Dict[str, QAPISchemaMember], 820 ) -> None: 821 for v in self.variants: 822 # Reset seen map for each variant, since qapi names from one 823 # branch do not affect another branch. 824 # 825 # v.type's typing is enforced in check() above. 826 assert isinstance(v.type, QAPISchemaObjectType) 827 v.type.check_clash(info, dict(seen)) 828 829 830class QAPISchemaAlternatives(QAPISchemaVariants): 831 def __init__(self, 832 info: QAPISourceInfo, 833 variants: List[QAPISchemaVariant], 834 tag_member: QAPISchemaObjectTypeMember): 835 super().__init__(info, variants) 836 self.tag_member = tag_member 837 838 def check( 839 self, schema: QAPISchema, seen: Dict[str, QAPISchemaMember] 840 ) -> None: 841 super().check(schema, seen) 842 assert isinstance(self.tag_member.type, QAPISchemaEnumType) 843 assert not self.tag_member.optional 844 assert not self.tag_member.ifcond.is_present() 845 846 847class QAPISchemaMember: 848 """ Represents object members, enum members and features """ 849 role = 'member' 850 851 def __init__( 852 self, 853 name: str, 854 info: Optional[QAPISourceInfo], 855 ifcond: Optional[QAPISchemaIfCond] = None, 856 ): 857 self.name = name 858 self.info = info 859 self.ifcond = ifcond or QAPISchemaIfCond() 860 self.defined_in: Optional[str] = None 861 862 def set_defined_in(self, name: str) -> None: 863 assert not self.defined_in 864 self.defined_in = name 865 866 def check_clash( 867 self, 868 info: Optional[QAPISourceInfo], 869 seen: Dict[str, QAPISchemaMember], 870 ) -> None: 871 cname = c_name(self.name) 872 if cname in seen: 873 raise QAPISemError( 874 info, 875 "%s collides with %s" 876 % (self.describe(info), seen[cname].describe(info))) 877 seen[cname] = self 878 879 def connect_doc(self, doc: Optional[QAPIDoc]) -> None: 880 if doc: 881 doc.connect_member(self) 882 883 def describe(self, info: Optional[QAPISourceInfo]) -> str: 884 role = self.role 885 meta = 'type' 886 defined_in = self.defined_in 887 assert defined_in 888 889 if defined_in.startswith('q_obj_'): 890 # See QAPISchema._make_implicit_object_type() - reverse the 891 # mapping there to create a nice human-readable description 892 defined_in = defined_in[6:] 893 if defined_in.endswith('-arg'): 894 # Implicit type created for a command's dict 'data' 895 assert role == 'member' 896 role = 'parameter' 897 meta = 'command' 898 defined_in = defined_in[:-4] 899 elif defined_in.endswith('-base'): 900 # Implicit type created for a union's dict 'base' 901 role = 'base ' + role 902 defined_in = defined_in[:-5] 903 else: 904 assert False 905 906 assert info is not None 907 if defined_in != info.defn_name: 908 return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in) 909 return "%s '%s'" % (role, self.name) 910 911 912class QAPISchemaEnumMember(QAPISchemaMember): 913 role = 'value' 914 915 def __init__( 916 self, 917 name: str, 918 info: Optional[QAPISourceInfo], 919 ifcond: Optional[QAPISchemaIfCond] = None, 920 features: Optional[List[QAPISchemaFeature]] = None, 921 ): 922 super().__init__(name, info, ifcond) 923 for f in features or []: 924 f.set_defined_in(name) 925 self.features = features or [] 926 927 def connect_doc(self, doc: Optional[QAPIDoc]) -> None: 928 super().connect_doc(doc) 929 if doc: 930 for f in self.features: 931 doc.connect_feature(f) 932 933 934class QAPISchemaFeature(QAPISchemaMember): 935 role = 'feature' 936 937 # Features which are standardized across all schemas 938 SPECIAL_NAMES = ['deprecated', 'unstable'] 939 940 def is_special(self) -> bool: 941 return self.name in QAPISchemaFeature.SPECIAL_NAMES 942 943 944class QAPISchemaObjectTypeMember(QAPISchemaMember): 945 def __init__( 946 self, 947 name: str, 948 info: QAPISourceInfo, 949 typ: str, 950 optional: bool, 951 ifcond: Optional[QAPISchemaIfCond] = None, 952 features: Optional[List[QAPISchemaFeature]] = None, 953 ): 954 super().__init__(name, info, ifcond) 955 for f in features or []: 956 f.set_defined_in(name) 957 self._type_name = typ 958 self.type: QAPISchemaType # set during check() 959 self.optional = optional 960 self.features = features or [] 961 962 def need_has(self) -> bool: 963 return self.optional and self.type.need_has_if_optional() 964 965 def check(self, schema: QAPISchema) -> None: 966 assert self.defined_in 967 self.type = schema.resolve_type(self._type_name, self.info, 968 self.describe) 969 seen: Dict[str, QAPISchemaMember] = {} 970 for f in self.features: 971 f.check_clash(self.info, seen) 972 973 def connect_doc(self, doc: Optional[QAPIDoc]) -> None: 974 super().connect_doc(doc) 975 if doc: 976 for f in self.features: 977 doc.connect_feature(f) 978 979 980class QAPISchemaVariant(QAPISchemaObjectTypeMember): 981 role = 'branch' 982 983 def __init__( 984 self, 985 name: str, 986 info: QAPISourceInfo, 987 typ: str, 988 ifcond: QAPISchemaIfCond, 989 ): 990 super().__init__(name, info, typ, False, ifcond) 991 992 993class QAPISchemaCommand(QAPISchemaDefinition): 994 meta = 'command' 995 996 def __init__( 997 self, 998 name: str, 999 info: QAPISourceInfo, 1000 doc: Optional[QAPIDoc], 1001 ifcond: QAPISchemaIfCond, 1002 features: List[QAPISchemaFeature], 1003 arg_type: Optional[str], 1004 ret_type: Optional[str], 1005 gen: bool, 1006 success_response: bool, 1007 boxed: bool, 1008 allow_oob: bool, 1009 allow_preconfig: bool, 1010 coroutine: bool, 1011 ): 1012 super().__init__(name, info, doc, ifcond, features) 1013 self._arg_type_name = arg_type 1014 self.arg_type: Optional[QAPISchemaObjectType] = None 1015 self._ret_type_name = ret_type 1016 self.ret_type: Optional[QAPISchemaType] = None 1017 self.gen = gen 1018 self.success_response = success_response 1019 self.boxed = boxed 1020 self.allow_oob = allow_oob 1021 self.allow_preconfig = allow_preconfig 1022 self.coroutine = coroutine 1023 1024 def check(self, schema: QAPISchema) -> None: 1025 assert self.info is not None 1026 super().check(schema) 1027 if self._arg_type_name: 1028 arg_type = schema.resolve_type( 1029 self._arg_type_name, self.info, "command's 'data'") 1030 if not isinstance(arg_type, QAPISchemaObjectType): 1031 raise QAPISemError( 1032 self.info, 1033 "command's 'data' cannot take %s" 1034 % arg_type.describe()) 1035 self.arg_type = arg_type 1036 if self.arg_type.branches and not self.boxed: 1037 raise QAPISemError( 1038 self.info, 1039 "command's 'data' can take %s only with 'boxed': true" 1040 % self.arg_type.describe()) 1041 self.arg_type.check(schema) 1042 if self.arg_type.has_conditional_members() and not self.boxed: 1043 raise QAPISemError( 1044 self.info, 1045 "conditional command arguments require 'boxed': true") 1046 if self._ret_type_name: 1047 self.ret_type = schema.resolve_type( 1048 self._ret_type_name, self.info, "command's 'returns'") 1049 if self.name not in self.info.pragma.command_returns_exceptions: 1050 typ = self.ret_type 1051 if isinstance(typ, QAPISchemaArrayType): 1052 typ = typ.element_type 1053 if not isinstance(typ, QAPISchemaObjectType): 1054 raise QAPISemError( 1055 self.info, 1056 "command's 'returns' cannot take %s" 1057 % self.ret_type.describe()) 1058 1059 def connect_doc(self, doc: Optional[QAPIDoc] = None) -> None: 1060 super().connect_doc(doc) 1061 doc = doc or self.doc 1062 if doc: 1063 if self.arg_type and self.arg_type.is_implicit(): 1064 self.arg_type.connect_doc(doc) 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 1075class 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 1129class 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] = OrderedDict() 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] = OrderedDict() 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, OrderedDict): 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, OrderedDict): 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() 1522