1 # -*- coding: utf-8 -*-
3 # QAPI schema internal representation
5 # Copyright (c) 2015-2019 Red Hat Inc.
8 # Markus Armbruster <armbru@redhat.com>
9 # Eric Blake <eblake@redhat.com>
10 # Marc-André Lureau <marcandre.lureau@redhat.com>
12 # This work is licensed under the terms of the GNU GPL, version 2.
13 # See the COPYING file in the top-level directory.
15 # pylint: disable=too-many-lines
17 # TODO catching name collisions in generated code would be nice
19 from __future__
import annotations
21 from abc
import ABC
, abstractmethod
22 from collections
import OrderedDict
43 from .error
import QAPIError
, QAPISemError
, QAPISourceError
44 from .expr
import check_exprs
45 from .parser
import QAPIDoc
, QAPIExpression
, QAPISchemaParser
46 from .source
import QAPISourceInfo
49 class QAPISchemaIfCond
:
52 ifcond
: Optional
[Union
[str, Dict
[str, object]]] = None,
56 def _cgen(self
) -> str:
57 return cgen_ifcond(self
.ifcond
)
59 def gen_if(self
) -> str:
60 return gen_if(self
._cgen
())
62 def gen_endif(self
) -> str:
63 return gen_endif(self
._cgen
())
65 def docgen(self
) -> str:
66 return docgen_ifcond(self
.ifcond
)
68 def is_present(self
) -> bool:
69 return bool(self
.ifcond
)
72 class QAPISchemaEntity
:
76 This is either a directive, such as include, or a definition.
77 The latter uses sub-class `QAPISchemaDefinition`.
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
89 def __repr__(self
) -> str:
90 return "<%s at 0x%x>" % (type(self
).__name
__, id(self
))
92 def check(self
, schema
: QAPISchema
) -> None:
93 # pylint: disable=unused-argument
96 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
100 self
, schema
: QAPISchema
, info
: Optional
[QAPISourceInfo
]
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
)
107 def set_module(self
, schema
: QAPISchema
) -> None:
108 self
._set
_module
(schema
, self
.info
)
110 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
111 # pylint: disable=unused-argument
115 class QAPISchemaDefinition(QAPISchemaEntity
):
121 info
: Optional
[QAPISourceInfo
],
122 doc
: Optional
[QAPIDoc
],
123 ifcond
: Optional
[QAPISchemaIfCond
] = None,
124 features
: Optional
[List
[QAPISchemaFeature
]] = None,
126 assert isinstance(name
, str)
127 super().__init
__(info
)
128 for f
in features
or []:
129 assert isinstance(f
, QAPISchemaFeature
)
130 f
.set_defined_in(name
)
133 self
._ifcond
= ifcond
or QAPISchemaIfCond()
134 self
.features
= features
or []
136 def __repr__(self
) -> str:
137 return "<%s:%s at 0x%x>" % (type(self
).__name
__, self
.name
,
140 def c_name(self
) -> str:
141 return c_name(self
.name
)
143 def check(self
, schema
: QAPISchema
) -> None:
144 assert not self
._checked
145 super().check(schema
)
146 seen
: Dict
[str, QAPISchemaMember
] = {}
147 for f
in self
.features
:
148 f
.check_clash(self
.info
, seen
)
150 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
151 super().connect_doc(doc
)
152 doc
= doc
or self
.doc
154 for f
in self
.features
:
155 doc
.connect_feature(f
)
158 def ifcond(self
) -> QAPISchemaIfCond
:
162 def is_implicit(self
) -> bool:
165 def describe(self
) -> str:
167 return "%s '%s'" % (self
.meta
, self
.name
)
170 class QAPISchemaVisitor
:
171 def visit_begin(self
, schema
: QAPISchema
) -> None:
174 def visit_end(self
) -> None:
177 def visit_module(self
, name
: str) -> None:
180 def visit_needed(self
, entity
: QAPISchemaEntity
) -> bool:
181 # pylint: disable=unused-argument
182 # Default to visiting everything
185 def visit_include(self
, name
: str, info
: Optional
[QAPISourceInfo
]) -> None:
188 def visit_builtin_type(
189 self
, name
: str, info
: Optional
[QAPISourceInfo
], json_type
: str
196 info
: Optional
[QAPISourceInfo
],
197 ifcond
: QAPISchemaIfCond
,
198 features
: List
[QAPISchemaFeature
],
199 members
: List
[QAPISchemaEnumMember
],
200 prefix
: Optional
[str],
204 def visit_array_type(
207 info
: Optional
[QAPISourceInfo
],
208 ifcond
: QAPISchemaIfCond
,
209 element_type
: QAPISchemaType
,
213 def visit_object_type(
216 info
: Optional
[QAPISourceInfo
],
217 ifcond
: QAPISchemaIfCond
,
218 features
: List
[QAPISchemaFeature
],
219 base
: Optional
[QAPISchemaObjectType
],
220 members
: List
[QAPISchemaObjectTypeMember
],
221 variants
: Optional
[QAPISchemaVariants
],
225 def visit_object_type_flat(
228 info
: Optional
[QAPISourceInfo
],
229 ifcond
: QAPISchemaIfCond
,
230 features
: List
[QAPISchemaFeature
],
231 members
: List
[QAPISchemaObjectTypeMember
],
232 variants
: Optional
[QAPISchemaVariants
],
236 def visit_alternate_type(
239 info
: Optional
[QAPISourceInfo
],
240 ifcond
: QAPISchemaIfCond
,
241 features
: List
[QAPISchemaFeature
],
242 variants
: QAPISchemaVariants
,
249 info
: Optional
[QAPISourceInfo
],
250 ifcond
: QAPISchemaIfCond
,
251 features
: List
[QAPISchemaFeature
],
252 arg_type
: Optional
[QAPISchemaObjectType
],
253 ret_type
: Optional
[QAPISchemaType
],
255 success_response
: bool,
258 allow_preconfig
: bool,
266 info
: Optional
[QAPISourceInfo
],
267 ifcond
: QAPISchemaIfCond
,
268 features
: List
[QAPISchemaFeature
],
269 arg_type
: Optional
[QAPISchemaObjectType
],
275 class QAPISchemaModule
:
277 BUILTIN_MODULE_NAME
= './builtin'
279 def __init__(self
, name
: str):
281 self
._entity
_list
: List
[QAPISchemaEntity
] = []
284 def is_system_module(name
: str) -> bool:
286 System modules are internally defined modules.
288 Their names start with the "./" prefix.
290 return name
.startswith('./')
293 def is_user_module(cls
, name
: str) -> bool:
295 User modules are those defined by the user in qapi JSON files.
297 They do not start with the "./" prefix.
299 return not cls
.is_system_module(name
)
302 def is_builtin_module(cls
, name
: str) -> bool:
304 The built-in module is a single System module for the built-in types.
306 It is always "./builtin".
308 return name
== cls
.BUILTIN_MODULE_NAME
310 def add_entity(self
, ent
: QAPISchemaEntity
) -> None:
311 self
._entity
_list
.append(ent
)
313 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
314 visitor
.visit_module(self
.name
)
315 for entity
in self
._entity
_list
:
316 if visitor
.visit_needed(entity
):
317 entity
.visit(visitor
)
320 class QAPISchemaInclude(QAPISchemaEntity
):
321 def __init__(self
, sub_module
: QAPISchemaModule
, info
: QAPISourceInfo
):
322 super().__init
__(info
)
323 self
._sub
_module
= sub_module
325 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
326 super().visit(visitor
)
327 visitor
.visit_include(self
._sub
_module
.name
, self
.info
)
330 class QAPISchemaType(QAPISchemaDefinition
, ABC
):
331 # Return the C type for common use.
332 # For the types we commonly box, this is a pointer type.
334 def c_type(self
) -> str:
337 # Return the C type to be used in a parameter list.
338 def c_param_type(self
) -> str:
341 # Return the C type to be used where we suppress boxing.
342 def c_unboxed_type(self
) -> str:
346 def json_type(self
) -> str:
349 def alternate_qtype(self
) -> Optional
[str]:
351 'null': 'QTYPE_QNULL',
352 'string': 'QTYPE_QSTRING',
353 'number': 'QTYPE_QNUM',
355 'boolean': 'QTYPE_QBOOL',
356 'array': 'QTYPE_QLIST',
357 'object': 'QTYPE_QDICT'
359 return json2qtype
.get(self
.json_type())
361 def doc_type(self
) -> Optional
[str]:
362 if self
.is_implicit():
366 def need_has_if_optional(self
) -> bool:
367 # When FOO is a pointer, has_FOO == !!FOO, i.e. has_FOO is redundant.
368 # Except for arrays; see QAPISchemaArrayType.need_has_if_optional().
369 return not self
.c_type().endswith(POINTER_SUFFIX
)
371 def check(self
, schema
: QAPISchema
) -> None:
372 super().check(schema
)
373 for feat
in self
.features
:
374 if feat
.is_special():
377 f
"feature '{feat.name}' is not supported for types")
379 def describe(self
) -> str:
381 return "%s type '%s'" % (self
.meta
, self
.name
)
384 class QAPISchemaBuiltinType(QAPISchemaType
):
387 def __init__(self
, name
: str, json_type
: str, c_type
: str):
388 super().__init
__(name
, None, None)
389 assert not c_type
or isinstance(c_type
, str)
390 assert json_type
in ('string', 'number', 'int', 'boolean', 'null',
392 self
._json
_type
_name
= json_type
393 self
._c
_type
_name
= c_type
395 def c_name(self
) -> str:
398 def c_type(self
) -> str:
399 return self
._c
_type
_name
401 def c_param_type(self
) -> str:
402 if self
.name
== 'str':
403 return 'const ' + self
._c
_type
_name
404 return self
._c
_type
_name
406 def json_type(self
) -> str:
407 return self
._json
_type
_name
409 def doc_type(self
) -> str:
410 return self
.json_type()
412 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
413 super().visit(visitor
)
414 visitor
.visit_builtin_type(self
.name
, self
.info
, self
.json_type())
417 class QAPISchemaEnumType(QAPISchemaType
):
423 info
: Optional
[QAPISourceInfo
],
424 doc
: Optional
[QAPIDoc
],
425 ifcond
: Optional
[QAPISchemaIfCond
],
426 features
: Optional
[List
[QAPISchemaFeature
]],
427 members
: List
[QAPISchemaEnumMember
],
428 prefix
: Optional
[str],
430 super().__init
__(name
, info
, doc
, ifcond
, features
)
432 assert isinstance(m
, QAPISchemaEnumMember
)
433 m
.set_defined_in(name
)
434 assert prefix
is None or isinstance(prefix
, str)
435 self
.members
= members
438 def check(self
, schema
: QAPISchema
) -> None:
439 super().check(schema
)
440 seen
: Dict
[str, QAPISchemaMember
] = {}
441 for m
in self
.members
:
442 m
.check_clash(self
.info
, seen
)
444 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
445 super().connect_doc(doc
)
446 doc
= doc
or self
.doc
447 for m
in self
.members
:
450 def is_implicit(self
) -> bool:
451 # See QAPISchema._def_predefineds()
452 return self
.name
== 'QType'
454 def c_type(self
) -> str:
455 return c_name(self
.name
)
457 def member_names(self
) -> List
[str]:
458 return [m
.name
for m
in self
.members
]
460 def json_type(self
) -> str:
463 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
464 super().visit(visitor
)
465 visitor
.visit_enum_type(
466 self
.name
, self
.info
, self
.ifcond
, self
.features
,
467 self
.members
, self
.prefix
)
470 class QAPISchemaArrayType(QAPISchemaType
):
474 self
, name
: str, info
: Optional
[QAPISourceInfo
], element_type
: str
476 super().__init
__(name
, info
, None)
477 assert isinstance(element_type
, str)
478 self
._element
_type
_name
= element_type
479 self
.element_type
: QAPISchemaType
481 def need_has_if_optional(self
) -> bool:
482 # When FOO is an array, we still need has_FOO to distinguish
483 # absent (!has_FOO) from present and empty (has_FOO && !FOO).
486 def check(self
, schema
: QAPISchema
) -> None:
487 super().check(schema
)
488 self
.element_type
= schema
.resolve_type(
489 self
._element
_type
_name
, self
.info
,
490 self
.info
.defn_meta
if self
.info
else None)
491 assert not isinstance(self
.element_type
, QAPISchemaArrayType
)
493 def set_module(self
, schema
: QAPISchema
) -> None:
494 self
._set
_module
(schema
, self
.element_type
.info
)
497 def ifcond(self
) -> QAPISchemaIfCond
:
499 return self
.element_type
.ifcond
501 def is_implicit(self
) -> bool:
504 def c_type(self
) -> str:
505 return c_name(self
.name
) + POINTER_SUFFIX
507 def json_type(self
) -> str:
510 def doc_type(self
) -> Optional
[str]:
511 elt_doc_type
= self
.element_type
.doc_type()
514 return 'array of ' + elt_doc_type
516 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
517 super().visit(visitor
)
518 visitor
.visit_array_type(self
.name
, self
.info
, self
.ifcond
,
521 def describe(self
) -> str:
523 return "%s type ['%s']" % (self
.meta
, self
._element
_type
_name
)
526 class QAPISchemaObjectType(QAPISchemaType
):
530 info
: Optional
[QAPISourceInfo
],
531 doc
: Optional
[QAPIDoc
],
532 ifcond
: Optional
[QAPISchemaIfCond
],
533 features
: Optional
[List
[QAPISchemaFeature
]],
535 local_members
: List
[QAPISchemaObjectTypeMember
],
536 variants
: Optional
[QAPISchemaVariants
],
538 # struct has local_members, optional base, and no variants
539 # union has base, variants, and no local_members
540 super().__init
__(name
, info
, doc
, ifcond
, features
)
541 self
.meta
= 'union' if variants
else 'struct'
542 assert base
is None or isinstance(base
, str)
543 for m
in local_members
:
544 assert isinstance(m
, QAPISchemaObjectTypeMember
)
545 m
.set_defined_in(name
)
546 if variants
is not None:
547 assert isinstance(variants
, QAPISchemaVariants
)
548 variants
.set_defined_in(name
)
549 self
._base
_name
= base
551 self
.local_members
= local_members
552 self
.variants
= variants
553 self
.members
: List
[QAPISchemaObjectTypeMember
]
554 self
._check
_complete
= False
556 def check(self
, schema
: QAPISchema
) -> None:
557 # This calls another type T's .check() exactly when the C
558 # struct emitted by gen_object() contains that T's C struct
559 # (pointers don't count).
560 if self
._check
_complete
:
561 # A previous .check() completed: nothing to do
564 # Recursed: C struct contains itself
565 raise QAPISemError(self
.info
,
566 "object %s contains itself" % self
.name
)
568 super().check(schema
)
569 assert self
._checked
and not self
._check
_complete
573 self
.base
= schema
.resolve_type(self
._base
_name
, self
.info
,
575 if (not isinstance(self
.base
, QAPISchemaObjectType
)
576 or self
.base
.variants
):
579 "'base' requires a struct type, %s isn't"
580 % self
.base
.describe())
581 self
.base
.check(schema
)
582 self
.base
.check_clash(self
.info
, seen
)
583 for m
in self
.local_members
:
585 m
.check_clash(self
.info
, seen
)
587 # self.check_clash() works in terms of the supertype, but
588 # self.members is declared List[QAPISchemaObjectTypeMember].
589 # Cast down to the subtype.
590 members
= cast(List
[QAPISchemaObjectTypeMember
], list(seen
.values()))
593 self
.variants
.check(schema
, seen
)
594 self
.variants
.check_clash(self
.info
, seen
)
596 self
.members
= members
597 self
._check
_complete
= True # mark completed
599 # Check that the members of this type do not cause duplicate JSON members,
600 # and update seen to track the members seen so far. Report any errors
601 # on behalf of info, which is not necessarily self.info
604 info
: Optional
[QAPISourceInfo
],
605 seen
: Dict
[str, QAPISchemaMember
],
608 for m
in self
.members
:
609 m
.check_clash(info
, seen
)
611 self
.variants
.check_clash(info
, seen
)
613 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
614 super().connect_doc(doc
)
615 doc
= doc
or self
.doc
616 if self
.base
and self
.base
.is_implicit():
617 self
.base
.connect_doc(doc
)
618 for m
in self
.local_members
:
621 def is_implicit(self
) -> bool:
622 # See QAPISchema._make_implicit_object_type(), as well as
624 return self
.name
.startswith('q_')
626 def is_empty(self
) -> bool:
627 return not self
.members
and not self
.variants
629 def has_conditional_members(self
) -> bool:
630 return any(m
.ifcond
.is_present() for m
in self
.members
)
632 def c_name(self
) -> str:
633 assert self
.name
!= 'q_empty'
634 return super().c_name()
636 def c_type(self
) -> str:
637 assert not self
.is_implicit()
638 return c_name(self
.name
) + POINTER_SUFFIX
640 def c_unboxed_type(self
) -> str:
641 return c_name(self
.name
)
643 def json_type(self
) -> str:
646 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
647 super().visit(visitor
)
648 visitor
.visit_object_type(
649 self
.name
, self
.info
, self
.ifcond
, self
.features
,
650 self
.base
, self
.local_members
, self
.variants
)
651 visitor
.visit_object_type_flat(
652 self
.name
, self
.info
, self
.ifcond
, self
.features
,
653 self
.members
, self
.variants
)
656 class QAPISchemaAlternateType(QAPISchemaType
):
662 info
: QAPISourceInfo
,
663 doc
: Optional
[QAPIDoc
],
664 ifcond
: Optional
[QAPISchemaIfCond
],
665 features
: List
[QAPISchemaFeature
],
666 variants
: QAPISchemaVariants
,
668 super().__init
__(name
, info
, doc
, ifcond
, features
)
669 assert isinstance(variants
, QAPISchemaVariants
)
670 assert variants
.tag_member
671 variants
.set_defined_in(name
)
672 variants
.tag_member
.set_defined_in(self
.name
)
673 self
.variants
= variants
675 def check(self
, schema
: QAPISchema
) -> None:
676 super().check(schema
)
677 self
.variants
.tag_member
.check(schema
)
678 # Not calling self.variants.check_clash(), because there's nothing
680 self
.variants
.check(schema
, {})
681 # Alternate branch names have no relation to the tag enum values;
682 # so we have to check for potential name collisions ourselves.
683 seen
: Dict
[str, QAPISchemaMember
] = {}
684 types_seen
: Dict
[str, str] = {}
685 for v
in self
.variants
.variants
:
686 v
.check_clash(self
.info
, seen
)
687 qtype
= v
.type.alternate_qtype()
692 % (v
.describe(self
.info
), v
.type.describe()))
693 conflicting
= set([qtype
])
694 if qtype
== 'QTYPE_QSTRING':
695 if isinstance(v
.type, QAPISchemaEnumType
):
696 for m
in v
.type.members
:
697 if m
.name
in ['on', 'off']:
698 conflicting
.add('QTYPE_QBOOL')
699 if re
.match(r
'[-+0-9.]', m
.name
):
700 # lazy, could be tightened
701 conflicting
.add('QTYPE_QNUM')
703 conflicting
.add('QTYPE_QNUM')
704 conflicting
.add('QTYPE_QBOOL')
705 for qt
in conflicting
:
709 "%s can't be distinguished from '%s'"
710 % (v
.describe(self
.info
), types_seen
[qt
]))
711 types_seen
[qt
] = v
.name
713 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
714 super().connect_doc(doc
)
715 doc
= doc
or self
.doc
716 for v
in self
.variants
.variants
:
719 def c_type(self
) -> str:
720 return c_name(self
.name
) + POINTER_SUFFIX
722 def json_type(self
) -> str:
725 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
726 super().visit(visitor
)
727 visitor
.visit_alternate_type(
728 self
.name
, self
.info
, self
.ifcond
, self
.features
, self
.variants
)
731 class QAPISchemaVariants
:
734 tag_name
: Optional
[str],
735 info
: QAPISourceInfo
,
736 tag_member
: Optional
[QAPISchemaObjectTypeMember
],
737 variants
: List
[QAPISchemaVariant
],
739 # Unions pass tag_name but not tag_member.
740 # Alternates pass tag_member but not tag_name.
741 # After check(), tag_member is always set.
742 assert bool(tag_member
) != bool(tag_name
)
743 assert (isinstance(tag_name
, str) or
744 isinstance(tag_member
, QAPISchemaObjectTypeMember
))
746 assert isinstance(v
, QAPISchemaVariant
)
747 self
._tag
_name
= tag_name
749 self
._tag
_member
= tag_member
750 self
.variants
= variants
753 def tag_member(self
) -> QAPISchemaObjectTypeMember
:
754 if self
._tag
_member
is None:
756 "QAPISchemaVariants has no tag_member property until "
757 "after check() has been run."
759 return self
._tag
_member
761 def set_defined_in(self
, name
: str) -> None:
762 for v
in self
.variants
:
763 v
.set_defined_in(name
)
766 self
, schema
: QAPISchema
, seen
: Dict
[str, QAPISchemaMember
]
768 if self
._tag
_name
: # union
769 # We need to narrow the member type:
770 tmp
= seen
.get(c_name(self
._tag
_name
))
771 assert tmp
is None or isinstance(tmp
, QAPISchemaObjectTypeMember
)
772 self
._tag
_member
= tmp
775 # Pointing to the base type when not implicit would be
776 # nice, but we don't know it here
777 if not self
._tag
_member
or self
._tag
_name
!= self
._tag
_member
.name
:
780 "discriminator '%s' is not a member of %s"
781 % (self
._tag
_name
, base
))
783 assert self
.tag_member
.defined_in
784 base_type
= schema
.lookup_type(self
.tag_member
.defined_in
)
786 if not base_type
.is_implicit():
787 base
= "base type '%s'" % self
.tag_member
.defined_in
788 if not isinstance(self
.tag_member
.type, QAPISchemaEnumType
):
791 "discriminator member '%s' of %s must be of enum type"
792 % (self
._tag
_name
, base
))
793 if self
.tag_member
.optional
:
796 "discriminator member '%s' of %s must not be optional"
797 % (self
._tag
_name
, base
))
798 if self
.tag_member
.ifcond
.is_present():
801 "discriminator member '%s' of %s must not be conditional"
802 % (self
._tag
_name
, base
))
804 assert self
._tag
_member
805 assert isinstance(self
.tag_member
.type, QAPISchemaEnumType
)
806 assert not self
.tag_member
.optional
807 assert not self
.tag_member
.ifcond
.is_present()
808 if self
._tag
_name
: # union
809 # branches that are not explicitly covered get an empty type
810 assert self
.tag_member
.defined_in
811 cases
= {v
.name
for v
in self
.variants
}
812 for m
in self
.tag_member
.type.members
:
813 if m
.name
not in cases
:
814 v
= QAPISchemaVariant(m
.name
, self
.info
,
816 v
.set_defined_in(self
.tag_member
.defined_in
)
817 self
.variants
.append(v
)
818 if not self
.variants
:
819 raise QAPISemError(self
.info
, "union has no branches")
820 for v
in self
.variants
:
822 # Union names must match enum values; alternate names are
823 # checked separately. Use 'seen' to tell the two apart.
825 if v
.name
not in self
.tag_member
.type.member_names():
828 "branch '%s' is not a value of %s"
829 % (v
.name
, self
.tag_member
.type.describe()))
830 if not isinstance(v
.type, QAPISchemaObjectType
):
834 % (v
.describe(self
.info
), v
.type.describe()))
839 info
: Optional
[QAPISourceInfo
],
840 seen
: Dict
[str, QAPISchemaMember
],
842 for v
in self
.variants
:
843 # Reset seen map for each variant, since qapi names from one
844 # branch do not affect another branch.
846 # v.type's typing is enforced in check() above.
847 assert isinstance(v
.type, QAPISchemaObjectType
)
848 v
.type.check_clash(info
, dict(seen
))
851 class QAPISchemaMember
:
852 """ Represents object members, enum members and features """
858 info
: Optional
[QAPISourceInfo
],
859 ifcond
: Optional
[QAPISchemaIfCond
] = None,
861 assert isinstance(name
, str)
864 self
.ifcond
= ifcond
or QAPISchemaIfCond()
865 self
.defined_in
: Optional
[str] = None
867 def set_defined_in(self
, name
: str) -> None:
868 assert not self
.defined_in
869 self
.defined_in
= name
873 info
: Optional
[QAPISourceInfo
],
874 seen
: Dict
[str, QAPISchemaMember
],
876 cname
= c_name(self
.name
)
880 "%s collides with %s"
881 % (self
.describe(info
), seen
[cname
].describe(info
)))
884 def connect_doc(self
, doc
: Optional
[QAPIDoc
]) -> None:
886 doc
.connect_member(self
)
888 def describe(self
, info
: Optional
[QAPISourceInfo
]) -> str:
891 defined_in
= self
.defined_in
894 if defined_in
.startswith('q_obj_'):
895 # See QAPISchema._make_implicit_object_type() - reverse the
896 # mapping there to create a nice human-readable description
897 defined_in
= defined_in
[6:]
898 if defined_in
.endswith('-arg'):
899 # Implicit type created for a command's dict 'data'
900 assert role
== 'member'
903 defined_in
= defined_in
[:-4]
904 elif defined_in
.endswith('-base'):
905 # Implicit type created for a union's dict 'base'
906 role
= 'base ' + role
907 defined_in
= defined_in
[:-5]
911 assert info
is not None
912 if defined_in
!= info
.defn_name
:
913 return "%s '%s' of %s '%s'" % (role
, self
.name
, meta
, defined_in
)
914 return "%s '%s'" % (role
, self
.name
)
917 class QAPISchemaEnumMember(QAPISchemaMember
):
923 info
: Optional
[QAPISourceInfo
],
924 ifcond
: Optional
[QAPISchemaIfCond
] = None,
925 features
: Optional
[List
[QAPISchemaFeature
]] = None,
927 super().__init
__(name
, info
, ifcond
)
928 for f
in features
or []:
929 assert isinstance(f
, QAPISchemaFeature
)
930 f
.set_defined_in(name
)
931 self
.features
= features
or []
933 def connect_doc(self
, doc
: Optional
[QAPIDoc
]) -> None:
934 super().connect_doc(doc
)
936 for f
in self
.features
:
937 doc
.connect_feature(f
)
940 class QAPISchemaFeature(QAPISchemaMember
):
943 def is_special(self
) -> bool:
944 return self
.name
in ('deprecated', 'unstable')
947 class QAPISchemaObjectTypeMember(QAPISchemaMember
):
951 info
: QAPISourceInfo
,
954 ifcond
: Optional
[QAPISchemaIfCond
] = None,
955 features
: Optional
[List
[QAPISchemaFeature
]] = None,
957 super().__init
__(name
, info
, ifcond
)
958 assert isinstance(typ
, str)
959 assert isinstance(optional
, bool)
960 for f
in features
or []:
961 assert isinstance(f
, QAPISchemaFeature
)
962 f
.set_defined_in(name
)
963 self
._type
_name
= typ
964 self
.type: QAPISchemaType
# set during check()
965 self
.optional
= optional
966 self
.features
= features
or []
968 def need_has(self
) -> bool:
970 return self
.optional
and self
.type.need_has_if_optional()
972 def check(self
, schema
: QAPISchema
) -> None:
973 assert self
.defined_in
974 self
.type = schema
.resolve_type(self
._type
_name
, self
.info
,
976 seen
: Dict
[str, QAPISchemaMember
] = {}
977 for f
in self
.features
:
978 f
.check_clash(self
.info
, seen
)
980 def connect_doc(self
, doc
: Optional
[QAPIDoc
]) -> None:
981 super().connect_doc(doc
)
983 for f
in self
.features
:
984 doc
.connect_feature(f
)
987 class QAPISchemaVariant(QAPISchemaObjectTypeMember
):
993 info
: QAPISourceInfo
,
995 ifcond
: QAPISchemaIfCond
,
997 super().__init
__(name
, info
, typ
, False, ifcond
)
1000 class QAPISchemaCommand(QAPISchemaDefinition
):
1006 info
: QAPISourceInfo
,
1007 doc
: Optional
[QAPIDoc
],
1008 ifcond
: QAPISchemaIfCond
,
1009 features
: List
[QAPISchemaFeature
],
1010 arg_type
: Optional
[str],
1011 ret_type
: Optional
[str],
1013 success_response
: bool,
1016 allow_preconfig
: bool,
1019 super().__init
__(name
, info
, doc
, ifcond
, features
)
1020 assert not arg_type
or isinstance(arg_type
, str)
1021 assert not ret_type
or isinstance(ret_type
, str)
1022 self
._arg
_type
_name
= arg_type
1023 self
.arg_type
: Optional
[QAPISchemaObjectType
] = None
1024 self
._ret
_type
_name
= ret_type
1025 self
.ret_type
: Optional
[QAPISchemaType
] = None
1027 self
.success_response
= success_response
1029 self
.allow_oob
= allow_oob
1030 self
.allow_preconfig
= allow_preconfig
1031 self
.coroutine
= coroutine
1033 def check(self
, schema
: QAPISchema
) -> None:
1034 assert self
.info
is not None
1035 super().check(schema
)
1036 if self
._arg
_type
_name
:
1037 arg_type
= schema
.resolve_type(
1038 self
._arg
_type
_name
, self
.info
, "command's 'data'")
1039 if not isinstance(arg_type
, QAPISchemaObjectType
):
1042 "command's 'data' cannot take %s"
1043 % arg_type
.describe())
1044 self
.arg_type
= arg_type
1045 if self
.arg_type
.variants
and not self
.boxed
:
1048 "command's 'data' can take %s only with 'boxed': true"
1049 % self
.arg_type
.describe())
1050 self
.arg_type
.check(schema
)
1051 if self
.arg_type
.has_conditional_members() and not self
.boxed
:
1054 "conditional command arguments require 'boxed': true")
1055 if self
._ret
_type
_name
:
1056 self
.ret_type
= schema
.resolve_type(
1057 self
._ret
_type
_name
, self
.info
, "command's 'returns'")
1058 if self
.name
not in self
.info
.pragma
.command_returns_exceptions
:
1060 if isinstance(typ
, QAPISchemaArrayType
):
1062 typ
= typ
.element_type
1063 if not isinstance(typ
, QAPISchemaObjectType
):
1066 "command's 'returns' cannot take %s"
1067 % self
.ret_type
.describe())
1069 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
1070 super().connect_doc(doc
)
1071 doc
= doc
or self
.doc
1073 if self
.arg_type
and self
.arg_type
.is_implicit():
1074 self
.arg_type
.connect_doc(doc
)
1076 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
1077 super().visit(visitor
)
1078 visitor
.visit_command(
1079 self
.name
, self
.info
, self
.ifcond
, self
.features
,
1080 self
.arg_type
, self
.ret_type
, self
.gen
, self
.success_response
,
1081 self
.boxed
, self
.allow_oob
, self
.allow_preconfig
,
1085 class QAPISchemaEvent(QAPISchemaDefinition
):
1091 info
: QAPISourceInfo
,
1092 doc
: Optional
[QAPIDoc
],
1093 ifcond
: QAPISchemaIfCond
,
1094 features
: List
[QAPISchemaFeature
],
1095 arg_type
: Optional
[str],
1098 super().__init
__(name
, info
, doc
, ifcond
, features
)
1099 assert not arg_type
or isinstance(arg_type
, str)
1100 self
._arg
_type
_name
= arg_type
1101 self
.arg_type
: Optional
[QAPISchemaObjectType
] = None
1104 def check(self
, schema
: QAPISchema
) -> None:
1105 super().check(schema
)
1106 if self
._arg
_type
_name
:
1107 typ
= schema
.resolve_type(
1108 self
._arg
_type
_name
, self
.info
, "event's 'data'")
1109 if not isinstance(typ
, QAPISchemaObjectType
):
1112 "event's 'data' cannot take %s"
1115 if self
.arg_type
.variants
and not self
.boxed
:
1118 "event's 'data' can take %s only with 'boxed': true"
1119 % self
.arg_type
.describe())
1120 self
.arg_type
.check(schema
)
1121 if self
.arg_type
.has_conditional_members() and not self
.boxed
:
1124 "conditional event arguments require 'boxed': true")
1126 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
1127 super().connect_doc(doc
)
1128 doc
= doc
or self
.doc
1130 if self
.arg_type
and self
.arg_type
.is_implicit():
1131 self
.arg_type
.connect_doc(doc
)
1133 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
1134 super().visit(visitor
)
1135 visitor
.visit_event(
1136 self
.name
, self
.info
, self
.ifcond
, self
.features
,
1137 self
.arg_type
, self
.boxed
)
1141 def __init__(self
, fname
: str):
1145 parser
= QAPISchemaParser(fname
)
1146 except OSError as err
:
1148 f
"can't read schema file '{fname}': {err.strerror}"
1151 exprs
= check_exprs(parser
.exprs
)
1152 self
.docs
= parser
.docs
1153 self
._entity
_list
: List
[QAPISchemaEntity
] = []
1154 self
._entity
_dict
: Dict
[str, QAPISchemaDefinition
] = {}
1155 self
._module
_dict
: Dict
[str, QAPISchemaModule
] = OrderedDict()
1156 self
._schema
_dir
= os
.path
.dirname(fname
)
1157 self
._make
_module
(QAPISchemaModule
.BUILTIN_MODULE_NAME
)
1158 self
._make
_module
(fname
)
1159 self
._predefining
= True
1160 self
._def
_predefineds
()
1161 self
._predefining
= False
1162 self
._def
_exprs
(exprs
)
1165 def _def_entity(self
, ent
: QAPISchemaEntity
) -> None:
1166 self
._entity
_list
.append(ent
)
1168 def _def_definition(self
, defn
: QAPISchemaDefinition
) -> None:
1169 # Only the predefined types are allowed to not have info
1170 assert defn
.info
or self
._predefining
1171 self
._def
_entity
(defn
)
1172 # TODO reject names that differ only in '_' vs. '.' vs. '-',
1173 # because they're liable to clash in generated C.
1174 other_defn
= self
._entity
_dict
.get(defn
.name
)
1177 where
= QAPISourceError(other_defn
.info
, "previous definition")
1180 "'%s' is already defined\n%s" % (defn
.name
, where
))
1182 defn
.info
, "%s is already defined" % other_defn
.describe())
1183 self
._entity
_dict
[defn
.name
] = defn
1188 typ
: Optional
[type] = None,
1189 ) -> Optional
[QAPISchemaDefinition
]:
1190 ent
= self
._entity
_dict
.get(name
)
1191 if typ
and not isinstance(ent
, typ
):
1195 def lookup_type(self
, name
: str) -> Optional
[QAPISchemaType
]:
1196 typ
= self
.lookup_entity(name
, QAPISchemaType
)
1197 assert typ
is None or isinstance(typ
, QAPISchemaType
)
1203 info
: Optional
[QAPISourceInfo
],
1204 what
: Union
[None, str, Callable
[[QAPISourceInfo
], str]],
1205 ) -> QAPISchemaType
:
1206 typ
= self
.lookup_type(name
)
1208 assert info
and what
# built-in types must not fail lookup
1212 info
, "%s uses unknown type '%s'" % (what
, name
))
1215 def _module_name(self
, fname
: str) -> str:
1216 if QAPISchemaModule
.is_system_module(fname
):
1218 return os
.path
.relpath(fname
, self
._schema
_dir
)
1220 def _make_module(self
, fname
: str) -> QAPISchemaModule
:
1221 name
= self
._module
_name
(fname
)
1222 if name
not in self
._module
_dict
:
1223 self
._module
_dict
[name
] = QAPISchemaModule(name
)
1224 return self
._module
_dict
[name
]
1226 def module_by_fname(self
, fname
: str) -> QAPISchemaModule
:
1227 name
= self
._module
_name
(fname
)
1228 return self
._module
_dict
[name
]
1230 def _def_include(self
, expr
: QAPIExpression
) -> None:
1231 include
= expr
['include']
1232 assert expr
.doc
is None
1234 QAPISchemaInclude(self
._make
_module
(include
), expr
.info
))
1236 def _def_builtin_type(
1237 self
, name
: str, json_type
: str, c_type
: str
1239 self
._def
_definition
(QAPISchemaBuiltinType(name
, json_type
, c_type
))
1240 # Instantiating only the arrays that are actually used would
1241 # be nice, but we can't as long as their generated code
1242 # (qapi-builtin-types.[ch]) may be shared by some other
1244 self
._make
_array
_type
(name
, None)
1246 def _def_predefineds(self
) -> None:
1247 for t
in [('str', 'string', 'char' + POINTER_SUFFIX
),
1248 ('number', 'number', 'double'),
1249 ('int', 'int', 'int64_t'),
1250 ('int8', 'int', 'int8_t'),
1251 ('int16', 'int', 'int16_t'),
1252 ('int32', 'int', 'int32_t'),
1253 ('int64', 'int', 'int64_t'),
1254 ('uint8', 'int', 'uint8_t'),
1255 ('uint16', 'int', 'uint16_t'),
1256 ('uint32', 'int', 'uint32_t'),
1257 ('uint64', 'int', 'uint64_t'),
1258 ('size', 'int', 'uint64_t'),
1259 ('bool', 'boolean', 'bool'),
1260 ('any', 'value', 'QObject' + POINTER_SUFFIX
),
1261 ('null', 'null', 'QNull' + POINTER_SUFFIX
)]:
1262 self
._def
_builtin
_type
(*t
)
1263 self
.the_empty_object_type
= QAPISchemaObjectType(
1264 'q_empty', None, None, None, None, None, [], None)
1265 self
._def
_definition
(self
.the_empty_object_type
)
1267 qtypes
= ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1269 qtype_values
= self
._make
_enum
_members
(
1270 [{'name': n
} for n
in qtypes
], None)
1272 self
._def
_definition
(QAPISchemaEnumType(
1273 'QType', None, None, None, None, qtype_values
, 'QTYPE'))
1277 features
: Optional
[List
[Dict
[str, Any
]]],
1278 info
: Optional
[QAPISourceInfo
],
1279 ) -> List
[QAPISchemaFeature
]:
1280 if features
is None:
1282 return [QAPISchemaFeature(f
['name'], info
,
1283 QAPISchemaIfCond(f
.get('if')))
1286 def _make_enum_member(
1289 ifcond
: Optional
[Union
[str, Dict
[str, Any
]]],
1290 features
: Optional
[List
[Dict
[str, Any
]]],
1291 info
: Optional
[QAPISourceInfo
],
1292 ) -> QAPISchemaEnumMember
:
1293 return QAPISchemaEnumMember(name
, info
,
1294 QAPISchemaIfCond(ifcond
),
1295 self
._make
_features
(features
, info
))
1297 def _make_enum_members(
1298 self
, values
: List
[Dict
[str, Any
]], info
: Optional
[QAPISourceInfo
]
1299 ) -> List
[QAPISchemaEnumMember
]:
1300 return [self
._make
_enum
_member
(v
['name'], v
.get('if'),
1301 v
.get('features'), info
)
1304 def _make_array_type(
1305 self
, element_type
: str, info
: Optional
[QAPISourceInfo
]
1307 name
= element_type
+ 'List' # reserved by check_defn_name_str()
1308 if not self
.lookup_type(name
):
1309 self
._def
_definition
(QAPISchemaArrayType(
1310 name
, info
, element_type
))
1313 def _make_implicit_object_type(
1316 info
: QAPISourceInfo
,
1317 ifcond
: QAPISchemaIfCond
,
1319 members
: List
[QAPISchemaObjectTypeMember
],
1323 # See also QAPISchemaObjectTypeMember.describe()
1324 name
= 'q_obj_%s-%s' % (name
, role
)
1325 typ
= self
.lookup_entity(name
, QAPISchemaObjectType
)
1327 # The implicit object type has multiple users. This can
1328 # only be a duplicate definition, which will be flagged
1332 self
._def
_definition
(QAPISchemaObjectType(
1333 name
, info
, None, ifcond
, None, None, members
, None))
1336 def _def_enum_type(self
, expr
: QAPIExpression
) -> None:
1339 prefix
= expr
.get('prefix')
1340 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1342 features
= self
._make
_features
(expr
.get('features'), info
)
1343 self
._def
_definition
(QAPISchemaEnumType(
1344 name
, info
, expr
.doc
, ifcond
, features
,
1345 self
._make
_enum
_members
(data
, info
), prefix
))
1350 typ
: Union
[List
[str], str],
1351 ifcond
: QAPISchemaIfCond
,
1352 features
: Optional
[List
[Dict
[str, Any
]]],
1353 info
: QAPISourceInfo
,
1354 ) -> QAPISchemaObjectTypeMember
:
1356 if name
.startswith('*'):
1359 if isinstance(typ
, list):
1360 assert len(typ
) == 1
1361 typ
= self
._make
_array
_type
(typ
[0], info
)
1362 return QAPISchemaObjectTypeMember(name
, info
, typ
, optional
, ifcond
,
1363 self
._make
_features
(features
, info
))
1367 data
: Dict
[str, Any
],
1368 info
: QAPISourceInfo
,
1369 ) -> List
[QAPISchemaObjectTypeMember
]:
1370 return [self
._make
_member
(key
, value
['type'],
1371 QAPISchemaIfCond(value
.get('if')),
1372 value
.get('features'), info
)
1373 for (key
, value
) in data
.items()]
1375 def _def_struct_type(self
, expr
: QAPIExpression
) -> None:
1376 name
= expr
['struct']
1377 base
= expr
.get('base')
1380 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1381 features
= self
._make
_features
(expr
.get('features'), info
)
1382 self
._def
_definition
(QAPISchemaObjectType(
1383 name
, info
, expr
.doc
, ifcond
, features
, base
,
1384 self
._make
_members
(data
, info
),
1391 ifcond
: QAPISchemaIfCond
,
1392 info
: QAPISourceInfo
,
1393 ) -> QAPISchemaVariant
:
1394 if isinstance(typ
, list):
1395 assert len(typ
) == 1
1396 typ
= self
._make
_array
_type
(typ
[0], info
)
1397 return QAPISchemaVariant(case
, info
, typ
, ifcond
)
1399 def _def_union_type(self
, expr
: QAPIExpression
) -> None:
1400 name
= expr
['union']
1402 tag_name
= expr
['discriminator']
1404 assert isinstance(data
, dict)
1406 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1407 features
= self
._make
_features
(expr
.get('features'), info
)
1408 if isinstance(base
, dict):
1409 base
= self
._make
_implicit
_object
_type
(
1411 'base', self
._make
_members
(base
, info
))
1413 self
._make
_variant
(key
, value
['type'],
1414 QAPISchemaIfCond(value
.get('if')),
1416 for (key
, value
) in data
.items()]
1417 members
: List
[QAPISchemaObjectTypeMember
] = []
1418 self
._def
_definition
(
1419 QAPISchemaObjectType(name
, info
, expr
.doc
, ifcond
, features
,
1422 tag_name
, info
, None, variants
)))
1424 def _def_alternate_type(self
, expr
: QAPIExpression
) -> None:
1425 name
= expr
['alternate']
1427 assert isinstance(data
, dict)
1428 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1430 features
= self
._make
_features
(expr
.get('features'), info
)
1432 self
._make
_variant
(key
, value
['type'],
1433 QAPISchemaIfCond(value
.get('if')),
1435 for (key
, value
) in data
.items()]
1436 tag_member
= QAPISchemaObjectTypeMember('type', info
, 'QType', False)
1437 self
._def
_definition
(
1438 QAPISchemaAlternateType(
1439 name
, info
, expr
.doc
, ifcond
, features
,
1440 QAPISchemaVariants(None, info
, tag_member
, variants
)))
1442 def _def_command(self
, expr
: QAPIExpression
) -> None:
1443 name
= expr
['command']
1444 data
= expr
.get('data')
1445 rets
= expr
.get('returns')
1446 gen
= expr
.get('gen', True)
1447 success_response
= expr
.get('success-response', True)
1448 boxed
= expr
.get('boxed', False)
1449 allow_oob
= expr
.get('allow-oob', False)
1450 allow_preconfig
= expr
.get('allow-preconfig', False)
1451 coroutine
= expr
.get('coroutine', False)
1452 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1454 features
= self
._make
_features
(expr
.get('features'), info
)
1455 if isinstance(data
, OrderedDict
):
1456 data
= self
._make
_implicit
_object
_type
(
1458 'arg', self
._make
_members
(data
, info
))
1459 if isinstance(rets
, list):
1460 assert len(rets
) == 1
1461 rets
= self
._make
_array
_type
(rets
[0], info
)
1462 self
._def
_definition
(
1463 QAPISchemaCommand(name
, info
, expr
.doc
, ifcond
, features
, data
,
1464 rets
, gen
, success_response
, boxed
, allow_oob
,
1465 allow_preconfig
, coroutine
))
1467 def _def_event(self
, expr
: QAPIExpression
) -> None:
1468 name
= expr
['event']
1469 data
= expr
.get('data')
1470 boxed
= expr
.get('boxed', False)
1471 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1473 features
= self
._make
_features
(expr
.get('features'), info
)
1474 if isinstance(data
, OrderedDict
):
1475 data
= self
._make
_implicit
_object
_type
(
1477 'arg', self
._make
_members
(data
, info
))
1478 self
._def
_definition
(QAPISchemaEvent(name
, info
, expr
.doc
, ifcond
,
1479 features
, data
, boxed
))
1481 def _def_exprs(self
, exprs
: List
[QAPIExpression
]) -> None:
1484 self
._def
_enum
_type
(expr
)
1485 elif 'struct' in expr
:
1486 self
._def
_struct
_type
(expr
)
1487 elif 'union' in expr
:
1488 self
._def
_union
_type
(expr
)
1489 elif 'alternate' in expr
:
1490 self
._def
_alternate
_type
(expr
)
1491 elif 'command' in expr
:
1492 self
._def
_command
(expr
)
1493 elif 'event' in expr
:
1494 self
._def
_event
(expr
)
1495 elif 'include' in expr
:
1496 self
._def
_include
(expr
)
1500 def check(self
) -> None:
1501 for ent
in self
._entity
_list
:
1504 for ent
in self
._entity
_list
:
1505 ent
.set_module(self
)
1506 for doc
in self
.docs
:
1509 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
1510 visitor
.visit_begin(self
)
1511 for mod
in self
._module
_dict
.values():