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 super().__init
__(info
)
127 for f
in features
or []:
128 f
.set_defined_in(name
)
131 self
._ifcond
= ifcond
or QAPISchemaIfCond()
132 self
.features
= features
or []
134 def __repr__(self
) -> str:
135 return "<%s:%s at 0x%x>" % (type(self
).__name
__, self
.name
,
138 def c_name(self
) -> str:
139 return c_name(self
.name
)
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
)
148 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
149 super().connect_doc(doc
)
150 doc
= doc
or self
.doc
152 for f
in self
.features
:
153 doc
.connect_feature(f
)
156 def ifcond(self
) -> QAPISchemaIfCond
:
160 def is_implicit(self
) -> bool:
163 def describe(self
) -> str:
164 return "%s '%s'" % (self
.meta
, self
.name
)
167 class QAPISchemaVisitor
:
168 def visit_begin(self
, schema
: QAPISchema
) -> None:
171 def visit_end(self
) -> None:
174 def visit_module(self
, name
: str) -> None:
177 def visit_needed(self
, entity
: QAPISchemaEntity
) -> bool:
178 # pylint: disable=unused-argument
179 # Default to visiting everything
182 def visit_include(self
, name
: str, info
: Optional
[QAPISourceInfo
]) -> None:
185 def visit_builtin_type(
186 self
, name
: str, info
: Optional
[QAPISourceInfo
], json_type
: str
193 info
: Optional
[QAPISourceInfo
],
194 ifcond
: QAPISchemaIfCond
,
195 features
: List
[QAPISchemaFeature
],
196 members
: List
[QAPISchemaEnumMember
],
197 prefix
: Optional
[str],
201 def visit_array_type(
204 info
: Optional
[QAPISourceInfo
],
205 ifcond
: QAPISchemaIfCond
,
206 element_type
: QAPISchemaType
,
210 def visit_object_type(
213 info
: Optional
[QAPISourceInfo
],
214 ifcond
: QAPISchemaIfCond
,
215 features
: List
[QAPISchemaFeature
],
216 base
: Optional
[QAPISchemaObjectType
],
217 members
: List
[QAPISchemaObjectTypeMember
],
218 branches
: Optional
[QAPISchemaBranches
],
222 def visit_object_type_flat(
225 info
: Optional
[QAPISourceInfo
],
226 ifcond
: QAPISchemaIfCond
,
227 features
: List
[QAPISchemaFeature
],
228 members
: List
[QAPISchemaObjectTypeMember
],
229 branches
: Optional
[QAPISchemaBranches
],
233 def visit_alternate_type(
236 info
: Optional
[QAPISourceInfo
],
237 ifcond
: QAPISchemaIfCond
,
238 features
: List
[QAPISchemaFeature
],
239 alternatives
: QAPISchemaAlternatives
,
246 info
: Optional
[QAPISourceInfo
],
247 ifcond
: QAPISchemaIfCond
,
248 features
: List
[QAPISchemaFeature
],
249 arg_type
: Optional
[QAPISchemaObjectType
],
250 ret_type
: Optional
[QAPISchemaType
],
252 success_response
: bool,
255 allow_preconfig
: bool,
263 info
: Optional
[QAPISourceInfo
],
264 ifcond
: QAPISchemaIfCond
,
265 features
: List
[QAPISchemaFeature
],
266 arg_type
: Optional
[QAPISchemaObjectType
],
272 class QAPISchemaModule
:
274 BUILTIN_MODULE_NAME
= './builtin'
276 def __init__(self
, name
: str):
278 self
._entity
_list
: List
[QAPISchemaEntity
] = []
281 def is_system_module(name
: str) -> bool:
283 System modules are internally defined modules.
285 Their names start with the "./" prefix.
287 return name
.startswith('./')
290 def is_user_module(cls
, name
: str) -> bool:
292 User modules are those defined by the user in qapi JSON files.
294 They do not start with the "./" prefix.
296 return not cls
.is_system_module(name
)
299 def is_builtin_module(cls
, name
: str) -> bool:
301 The built-in module is a single System module for the built-in types.
303 It is always "./builtin".
305 return name
== cls
.BUILTIN_MODULE_NAME
307 def add_entity(self
, ent
: QAPISchemaEntity
) -> None:
308 self
._entity
_list
.append(ent
)
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
)
317 class QAPISchemaInclude(QAPISchemaEntity
):
318 def __init__(self
, sub_module
: QAPISchemaModule
, info
: QAPISourceInfo
):
319 super().__init
__(info
)
320 self
._sub
_module
= sub_module
322 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
323 super().visit(visitor
)
324 visitor
.visit_include(self
._sub
_module
.name
, self
.info
)
327 class QAPISchemaType(QAPISchemaDefinition
, ABC
):
328 # Return the C type for common use.
329 # For the types we commonly box, this is a pointer type.
331 def c_type(self
) -> str:
334 # Return the C type to be used in a parameter list.
335 def c_param_type(self
) -> str:
338 # Return the C type to be used where we suppress boxing.
339 def c_unboxed_type(self
) -> str:
343 def json_type(self
) -> str:
346 def alternate_qtype(self
) -> Optional
[str]:
348 'null': 'QTYPE_QNULL',
349 'string': 'QTYPE_QSTRING',
350 'number': 'QTYPE_QNUM',
352 'boolean': 'QTYPE_QBOOL',
353 'array': 'QTYPE_QLIST',
354 'object': 'QTYPE_QDICT'
356 return json2qtype
.get(self
.json_type())
358 def doc_type(self
) -> Optional
[str]:
359 if self
.is_implicit():
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
)
368 def check(self
, schema
: QAPISchema
) -> None:
369 super().check(schema
)
370 for feat
in self
.features
:
371 if feat
.is_special():
374 f
"feature '{feat.name}' is not supported for types")
376 def describe(self
) -> str:
377 return "%s type '%s'" % (self
.meta
, self
.name
)
380 class QAPISchemaBuiltinType(QAPISchemaType
):
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',
387 self
._json
_type
_name
= json_type
388 self
._c
_type
_name
= c_type
390 def c_name(self
) -> str:
393 def c_type(self
) -> str:
394 return self
._c
_type
_name
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
401 def json_type(self
) -> str:
402 return self
._json
_type
_name
404 def doc_type(self
) -> str:
405 return self
.json_type()
407 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
408 super().visit(visitor
)
409 visitor
.visit_builtin_type(self
.name
, self
.info
, self
.json_type())
412 class QAPISchemaEnumType(QAPISchemaType
):
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],
425 super().__init
__(name
, info
, doc
, ifcond
, features
)
427 m
.set_defined_in(name
)
428 self
.members
= members
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
)
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
:
443 def is_implicit(self
) -> bool:
444 # See QAPISchema._def_predefineds()
445 return self
.name
== 'QType'
447 def c_type(self
) -> str:
448 return c_name(self
.name
)
450 def member_names(self
) -> List
[str]:
451 return [m
.name
for m
in self
.members
]
453 def json_type(self
) -> str:
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
)
463 class QAPISchemaArrayType(QAPISchemaType
):
467 self
, name
: str, info
: Optional
[QAPISourceInfo
], element_type
: str
469 super().__init
__(name
, info
, None)
470 self
._element
_type
_name
= element_type
471 self
.element_type
: QAPISchemaType
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).
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
)
485 def set_module(self
, schema
: QAPISchema
) -> None:
486 self
._set
_module
(schema
, self
.element_type
.info
)
489 def ifcond(self
) -> QAPISchemaIfCond
:
491 return self
.element_type
.ifcond
493 def is_implicit(self
) -> bool:
496 def c_type(self
) -> str:
497 return c_name(self
.name
) + POINTER_SUFFIX
499 def json_type(self
) -> str:
502 def doc_type(self
) -> Optional
[str]:
503 elt_doc_type
= self
.element_type
.doc_type()
506 return 'array of ' + elt_doc_type
508 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
509 super().visit(visitor
)
510 visitor
.visit_array_type(self
.name
, self
.info
, self
.ifcond
,
513 def describe(self
) -> str:
514 return "%s type ['%s']" % (self
.meta
, self
._element
_type
_name
)
517 class QAPISchemaObjectType(QAPISchemaType
):
521 info
: Optional
[QAPISourceInfo
],
522 doc
: Optional
[QAPIDoc
],
523 ifcond
: Optional
[QAPISchemaIfCond
],
524 features
: Optional
[List
[QAPISchemaFeature
]],
526 local_members
: List
[QAPISchemaObjectTypeMember
],
527 branches
: Optional
[QAPISchemaBranches
],
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
539 self
.local_members
= local_members
540 self
.branches
= branches
541 self
.members
: List
[QAPISchemaObjectTypeMember
]
542 self
._check
_complete
= False
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
552 # Recursed: C struct contains itself
553 raise QAPISemError(self
.info
,
554 "object %s contains itself" % self
.name
)
556 super().check(schema
)
557 assert self
._checked
and not self
._check
_complete
561 self
.base
= schema
.resolve_type(self
._base
_name
, self
.info
,
563 if (not isinstance(self
.base
, QAPISchemaObjectType
)
564 or self
.base
.branches
):
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
:
573 m
.check_clash(self
.info
, seen
)
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()))
581 self
.branches
.check(schema
, seen
)
582 self
.branches
.check_clash(self
.info
, seen
)
584 self
.members
= members
585 self
._check
_complete
= True # mark completed
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
592 info
: Optional
[QAPISourceInfo
],
593 seen
: Dict
[str, QAPISchemaMember
],
596 for m
in self
.members
:
597 m
.check_clash(info
, seen
)
599 self
.branches
.check_clash(info
, seen
)
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
:
609 def is_implicit(self
) -> bool:
610 # See QAPISchema._make_implicit_object_type(), as well as
612 return self
.name
.startswith('q_')
614 def is_empty(self
) -> bool:
615 return not self
.members
and not self
.branches
617 def has_conditional_members(self
) -> bool:
618 return any(m
.ifcond
.is_present() for m
in self
.members
)
620 def c_name(self
) -> str:
621 assert self
.name
!= 'q_empty'
622 return super().c_name()
624 def c_type(self
) -> str:
625 assert not self
.is_implicit()
626 return c_name(self
.name
) + POINTER_SUFFIX
628 def c_unboxed_type(self
) -> str:
629 return c_name(self
.name
)
631 def json_type(self
) -> str:
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
)
644 class QAPISchemaAlternateType(QAPISchemaType
):
650 info
: QAPISourceInfo
,
651 doc
: Optional
[QAPIDoc
],
652 ifcond
: Optional
[QAPISchemaIfCond
],
653 features
: List
[QAPISchemaFeature
],
654 alternatives
: QAPISchemaAlternatives
,
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
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()
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')
690 conflicting
.add('QTYPE_QNUM')
691 conflicting
.add('QTYPE_QBOOL')
692 for qt
in conflicting
:
696 "%s can't be distinguished from '%s'"
697 % (v
.describe(self
.info
), types_seen
[qt
]))
698 types_seen
[qt
] = v
.name
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
:
706 def c_type(self
) -> str:
707 return c_name(self
.name
) + POINTER_SUFFIX
709 def json_type(self
) -> str:
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
,
719 class QAPISchemaVariants
:
722 info
: QAPISourceInfo
,
723 variants
: List
[QAPISchemaVariant
],
726 self
.tag_member
: QAPISchemaObjectTypeMember
727 self
.variants
= variants
729 def set_defined_in(self
, name
: str) -> None:
730 for v
in self
.variants
:
731 v
.set_defined_in(name
)
733 # pylint: disable=unused-argument
735 self
, schema
: QAPISchema
, seen
: Dict
[str, QAPISchemaMember
]
737 for v
in self
.variants
:
741 class QAPISchemaBranches(QAPISchemaVariants
):
743 info
: QAPISourceInfo
,
744 variants
: List
[QAPISchemaVariant
],
746 super().__init
__(info
, variants
)
747 self
._tag
_name
= tag_name
750 self
, schema
: QAPISchema
, seen
: Dict
[str, QAPISchemaMember
]
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
))
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
:
763 "discriminator '%s' is not a member of %s"
764 % (self
._tag
_name
, base
))
765 self
.tag_member
= tag_member
767 assert tag_member
.defined_in
768 base_type
= schema
.lookup_type(tag_member
.defined_in
)
770 if not base_type
.is_implicit():
771 base
= "base type '%s'" % tag_member
.defined_in
772 if not isinstance(tag_member
.type, QAPISchemaEnumType
):
775 "discriminator member '%s' of %s must be of enum type"
776 % (self
._tag
_name
, base
))
777 if tag_member
.optional
:
780 "discriminator member '%s' of %s must not be optional"
781 % (self
._tag
_name
, base
))
782 if tag_member
.ifcond
.is_present():
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
,
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
:
800 # Union names must match enum values; alternate names are
801 # checked separately. Use 'seen' to tell the two apart.
803 if v
.name
not in tag_member
.type.member_names():
806 "branch '%s' is not a value of %s"
807 % (v
.name
, tag_member
.type.describe()))
808 if not isinstance(v
.type, QAPISchemaObjectType
):
812 % (v
.describe(self
.info
), v
.type.describe()))
817 info
: Optional
[QAPISourceInfo
],
818 seen
: Dict
[str, QAPISchemaMember
],
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.
824 # v.type's typing is enforced in check() above.
825 assert isinstance(v
.type, QAPISchemaObjectType
)
826 v
.type.check_clash(info
, dict(seen
))
829 class QAPISchemaAlternatives(QAPISchemaVariants
):
831 info
: QAPISourceInfo
,
832 variants
: List
[QAPISchemaVariant
],
833 tag_member
: QAPISchemaObjectTypeMember
):
834 super().__init
__(info
, variants
)
835 self
.tag_member
= tag_member
838 self
, schema
: QAPISchema
, seen
: Dict
[str, QAPISchemaMember
]
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()
846 class QAPISchemaMember
:
847 """ Represents object members, enum members and features """
853 info
: Optional
[QAPISourceInfo
],
854 ifcond
: Optional
[QAPISchemaIfCond
] = None,
858 self
.ifcond
= ifcond
or QAPISchemaIfCond()
859 self
.defined_in
: Optional
[str] = None
861 def set_defined_in(self
, name
: str) -> None:
862 assert not self
.defined_in
863 self
.defined_in
= name
867 info
: Optional
[QAPISourceInfo
],
868 seen
: Dict
[str, QAPISchemaMember
],
870 cname
= c_name(self
.name
)
874 "%s collides with %s"
875 % (self
.describe(info
), seen
[cname
].describe(info
)))
878 def connect_doc(self
, doc
: Optional
[QAPIDoc
]) -> None:
880 doc
.connect_member(self
)
882 def describe(self
, info
: Optional
[QAPISourceInfo
]) -> str:
885 defined_in
= self
.defined_in
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'
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]
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
)
911 class QAPISchemaEnumMember(QAPISchemaMember
):
917 info
: Optional
[QAPISourceInfo
],
918 ifcond
: Optional
[QAPISchemaIfCond
] = None,
919 features
: Optional
[List
[QAPISchemaFeature
]] = None,
921 super().__init
__(name
, info
, ifcond
)
922 for f
in features
or []:
923 f
.set_defined_in(name
)
924 self
.features
= features
or []
926 def connect_doc(self
, doc
: Optional
[QAPIDoc
]) -> None:
927 super().connect_doc(doc
)
929 for f
in self
.features
:
930 doc
.connect_feature(f
)
933 class QAPISchemaFeature(QAPISchemaMember
):
936 def is_special(self
) -> bool:
937 return self
.name
in ('deprecated', 'unstable')
940 class QAPISchemaObjectTypeMember(QAPISchemaMember
):
944 info
: QAPISourceInfo
,
947 ifcond
: Optional
[QAPISchemaIfCond
] = None,
948 features
: Optional
[List
[QAPISchemaFeature
]] = None,
950 super().__init
__(name
, info
, ifcond
)
951 for f
in features
or []:
952 f
.set_defined_in(name
)
953 self
._type
_name
= typ
954 self
.type: QAPISchemaType
# set during check()
955 self
.optional
= optional
956 self
.features
= features
or []
958 def need_has(self
) -> bool:
959 return self
.optional
and self
.type.need_has_if_optional()
961 def check(self
, schema
: QAPISchema
) -> None:
962 assert self
.defined_in
963 self
.type = schema
.resolve_type(self
._type
_name
, self
.info
,
965 seen
: Dict
[str, QAPISchemaMember
] = {}
966 for f
in self
.features
:
967 f
.check_clash(self
.info
, seen
)
969 def connect_doc(self
, doc
: Optional
[QAPIDoc
]) -> None:
970 super().connect_doc(doc
)
972 for f
in self
.features
:
973 doc
.connect_feature(f
)
976 class QAPISchemaVariant(QAPISchemaObjectTypeMember
):
982 info
: QAPISourceInfo
,
984 ifcond
: QAPISchemaIfCond
,
986 super().__init
__(name
, info
, typ
, False, ifcond
)
989 class QAPISchemaCommand(QAPISchemaDefinition
):
995 info
: QAPISourceInfo
,
996 doc
: Optional
[QAPIDoc
],
997 ifcond
: QAPISchemaIfCond
,
998 features
: List
[QAPISchemaFeature
],
999 arg_type
: Optional
[str],
1000 ret_type
: Optional
[str],
1002 success_response
: bool,
1005 allow_preconfig
: bool,
1008 super().__init
__(name
, info
, doc
, ifcond
, features
)
1009 self
._arg
_type
_name
= arg_type
1010 self
.arg_type
: Optional
[QAPISchemaObjectType
] = None
1011 self
._ret
_type
_name
= ret_type
1012 self
.ret_type
: Optional
[QAPISchemaType
] = None
1014 self
.success_response
= success_response
1016 self
.allow_oob
= allow_oob
1017 self
.allow_preconfig
= allow_preconfig
1018 self
.coroutine
= coroutine
1020 def check(self
, schema
: QAPISchema
) -> None:
1021 assert self
.info
is not None
1022 super().check(schema
)
1023 if self
._arg
_type
_name
:
1024 arg_type
= schema
.resolve_type(
1025 self
._arg
_type
_name
, self
.info
, "command's 'data'")
1026 if not isinstance(arg_type
, QAPISchemaObjectType
):
1029 "command's 'data' cannot take %s"
1030 % arg_type
.describe())
1031 self
.arg_type
= arg_type
1032 if self
.arg_type
.branches
and not self
.boxed
:
1035 "command's 'data' can take %s only with 'boxed': true"
1036 % self
.arg_type
.describe())
1037 self
.arg_type
.check(schema
)
1038 if self
.arg_type
.has_conditional_members() and not self
.boxed
:
1041 "conditional command arguments require 'boxed': true")
1042 if self
._ret
_type
_name
:
1043 self
.ret_type
= schema
.resolve_type(
1044 self
._ret
_type
_name
, self
.info
, "command's 'returns'")
1045 if self
.name
not in self
.info
.pragma
.command_returns_exceptions
:
1047 if isinstance(typ
, QAPISchemaArrayType
):
1048 typ
= typ
.element_type
1049 if not isinstance(typ
, QAPISchemaObjectType
):
1052 "command's 'returns' cannot take %s"
1053 % self
.ret_type
.describe())
1055 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
1056 super().connect_doc(doc
)
1057 doc
= doc
or self
.doc
1059 if self
.arg_type
and self
.arg_type
.is_implicit():
1060 self
.arg_type
.connect_doc(doc
)
1062 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
1063 super().visit(visitor
)
1064 visitor
.visit_command(
1065 self
.name
, self
.info
, self
.ifcond
, self
.features
,
1066 self
.arg_type
, self
.ret_type
, self
.gen
, self
.success_response
,
1067 self
.boxed
, self
.allow_oob
, self
.allow_preconfig
,
1071 class QAPISchemaEvent(QAPISchemaDefinition
):
1077 info
: QAPISourceInfo
,
1078 doc
: Optional
[QAPIDoc
],
1079 ifcond
: QAPISchemaIfCond
,
1080 features
: List
[QAPISchemaFeature
],
1081 arg_type
: Optional
[str],
1084 super().__init
__(name
, info
, doc
, ifcond
, features
)
1085 self
._arg
_type
_name
= arg_type
1086 self
.arg_type
: Optional
[QAPISchemaObjectType
] = None
1089 def check(self
, schema
: QAPISchema
) -> None:
1090 super().check(schema
)
1091 if self
._arg
_type
_name
:
1092 typ
= schema
.resolve_type(
1093 self
._arg
_type
_name
, self
.info
, "event's 'data'")
1094 if not isinstance(typ
, QAPISchemaObjectType
):
1097 "event's 'data' cannot take %s"
1100 if self
.arg_type
.branches
and not self
.boxed
:
1103 "event's 'data' can take %s only with 'boxed': true"
1104 % self
.arg_type
.describe())
1105 self
.arg_type
.check(schema
)
1106 if self
.arg_type
.has_conditional_members() and not self
.boxed
:
1109 "conditional event arguments require 'boxed': true")
1111 def connect_doc(self
, doc
: Optional
[QAPIDoc
] = None) -> None:
1112 super().connect_doc(doc
)
1113 doc
= doc
or self
.doc
1115 if self
.arg_type
and self
.arg_type
.is_implicit():
1116 self
.arg_type
.connect_doc(doc
)
1118 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
1119 super().visit(visitor
)
1120 visitor
.visit_event(
1121 self
.name
, self
.info
, self
.ifcond
, self
.features
,
1122 self
.arg_type
, self
.boxed
)
1126 def __init__(self
, fname
: str):
1130 parser
= QAPISchemaParser(fname
)
1131 except OSError as err
:
1133 f
"can't read schema file '{fname}': {err.strerror}"
1136 exprs
= check_exprs(parser
.exprs
)
1137 self
.docs
= parser
.docs
1138 self
._entity
_list
: List
[QAPISchemaEntity
] = []
1139 self
._entity
_dict
: Dict
[str, QAPISchemaDefinition
] = {}
1140 self
._module
_dict
: Dict
[str, QAPISchemaModule
] = OrderedDict()
1141 self
._schema
_dir
= os
.path
.dirname(fname
)
1142 self
._make
_module
(QAPISchemaModule
.BUILTIN_MODULE_NAME
)
1143 self
._make
_module
(fname
)
1144 self
._predefining
= True
1145 self
._def
_predefineds
()
1146 self
._predefining
= False
1147 self
._def
_exprs
(exprs
)
1150 def _def_entity(self
, ent
: QAPISchemaEntity
) -> None:
1151 self
._entity
_list
.append(ent
)
1153 def _def_definition(self
, defn
: QAPISchemaDefinition
) -> None:
1154 # Only the predefined types are allowed to not have info
1155 assert defn
.info
or self
._predefining
1156 self
._def
_entity
(defn
)
1157 # TODO reject names that differ only in '_' vs. '.' vs. '-',
1158 # because they're liable to clash in generated C.
1159 other_defn
= self
._entity
_dict
.get(defn
.name
)
1162 where
= QAPISourceError(other_defn
.info
, "previous definition")
1165 "'%s' is already defined\n%s" % (defn
.name
, where
))
1167 defn
.info
, "%s is already defined" % other_defn
.describe())
1168 self
._entity
_dict
[defn
.name
] = defn
1170 def lookup_entity(self
, name
: str) -> Optional
[QAPISchemaEntity
]:
1171 return self
._entity
_dict
.get(name
)
1173 def lookup_type(self
, name
: str) -> Optional
[QAPISchemaType
]:
1174 typ
= self
.lookup_entity(name
)
1175 if isinstance(typ
, QAPISchemaType
):
1182 info
: Optional
[QAPISourceInfo
],
1183 what
: Union
[None, str, Callable
[[QAPISourceInfo
], str]],
1184 ) -> QAPISchemaType
:
1185 typ
= self
.lookup_type(name
)
1187 assert info
and what
# built-in types must not fail lookup
1191 info
, "%s uses unknown type '%s'" % (what
, name
))
1194 def _module_name(self
, fname
: str) -> str:
1195 if QAPISchemaModule
.is_system_module(fname
):
1197 return os
.path
.relpath(fname
, self
._schema
_dir
)
1199 def _make_module(self
, fname
: str) -> QAPISchemaModule
:
1200 name
= self
._module
_name
(fname
)
1201 if name
not in self
._module
_dict
:
1202 self
._module
_dict
[name
] = QAPISchemaModule(name
)
1203 return self
._module
_dict
[name
]
1205 def module_by_fname(self
, fname
: str) -> QAPISchemaModule
:
1206 name
= self
._module
_name
(fname
)
1207 return self
._module
_dict
[name
]
1209 def _def_include(self
, expr
: QAPIExpression
) -> None:
1210 include
= expr
['include']
1211 assert expr
.doc
is None
1213 QAPISchemaInclude(self
._make
_module
(include
), expr
.info
))
1215 def _def_builtin_type(
1216 self
, name
: str, json_type
: str, c_type
: str
1218 self
._def
_definition
(QAPISchemaBuiltinType(name
, json_type
, c_type
))
1219 # Instantiating only the arrays that are actually used would
1220 # be nice, but we can't as long as their generated code
1221 # (qapi-builtin-types.[ch]) may be shared by some other
1223 self
._make
_array
_type
(name
, None)
1225 def _def_predefineds(self
) -> None:
1226 for t
in [('str', 'string', 'char' + POINTER_SUFFIX
),
1227 ('number', 'number', 'double'),
1228 ('int', 'int', 'int64_t'),
1229 ('int8', 'int', 'int8_t'),
1230 ('int16', 'int', 'int16_t'),
1231 ('int32', 'int', 'int32_t'),
1232 ('int64', 'int', 'int64_t'),
1233 ('uint8', 'int', 'uint8_t'),
1234 ('uint16', 'int', 'uint16_t'),
1235 ('uint32', 'int', 'uint32_t'),
1236 ('uint64', 'int', 'uint64_t'),
1237 ('size', 'int', 'uint64_t'),
1238 ('bool', 'boolean', 'bool'),
1239 ('any', 'value', 'QObject' + POINTER_SUFFIX
),
1240 ('null', 'null', 'QNull' + POINTER_SUFFIX
)]:
1241 self
._def
_builtin
_type
(*t
)
1242 self
.the_empty_object_type
= QAPISchemaObjectType(
1243 'q_empty', None, None, None, None, None, [], None)
1244 self
._def
_definition
(self
.the_empty_object_type
)
1246 qtypes
= ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
1248 qtype_values
= self
._make
_enum
_members
(
1249 [{'name': n
} for n
in qtypes
], None)
1251 self
._def
_definition
(QAPISchemaEnumType(
1252 'QType', None, None, None, None, qtype_values
, 'QTYPE'))
1256 features
: Optional
[List
[Dict
[str, Any
]]],
1257 info
: Optional
[QAPISourceInfo
],
1258 ) -> List
[QAPISchemaFeature
]:
1259 if features
is None:
1261 return [QAPISchemaFeature(f
['name'], info
,
1262 QAPISchemaIfCond(f
.get('if')))
1265 def _make_enum_member(
1268 ifcond
: Optional
[Union
[str, Dict
[str, Any
]]],
1269 features
: Optional
[List
[Dict
[str, Any
]]],
1270 info
: Optional
[QAPISourceInfo
],
1271 ) -> QAPISchemaEnumMember
:
1272 return QAPISchemaEnumMember(name
, info
,
1273 QAPISchemaIfCond(ifcond
),
1274 self
._make
_features
(features
, info
))
1276 def _make_enum_members(
1277 self
, values
: List
[Dict
[str, Any
]], info
: Optional
[QAPISourceInfo
]
1278 ) -> List
[QAPISchemaEnumMember
]:
1279 return [self
._make
_enum
_member
(v
['name'], v
.get('if'),
1280 v
.get('features'), info
)
1283 def _make_array_type(
1284 self
, element_type
: str, info
: Optional
[QAPISourceInfo
]
1286 name
= element_type
+ 'List' # reserved by check_defn_name_str()
1287 if not self
.lookup_type(name
):
1288 self
._def
_definition
(QAPISchemaArrayType(
1289 name
, info
, element_type
))
1292 def _make_implicit_object_type(
1295 info
: QAPISourceInfo
,
1296 ifcond
: QAPISchemaIfCond
,
1298 members
: List
[QAPISchemaObjectTypeMember
],
1302 # See also QAPISchemaObjectTypeMember.describe()
1303 name
= 'q_obj_%s-%s' % (name
, role
)
1304 typ
= self
.lookup_entity(name
)
1306 assert isinstance(typ
, QAPISchemaObjectType
)
1307 # The implicit object type has multiple users. This can
1308 # only be a duplicate definition, which will be flagged
1311 self
._def
_definition
(QAPISchemaObjectType(
1312 name
, info
, None, ifcond
, None, None, members
, None))
1315 def _def_enum_type(self
, expr
: QAPIExpression
) -> None:
1318 prefix
= expr
.get('prefix')
1319 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1321 features
= self
._make
_features
(expr
.get('features'), info
)
1322 self
._def
_definition
(QAPISchemaEnumType(
1323 name
, info
, expr
.doc
, ifcond
, features
,
1324 self
._make
_enum
_members
(data
, info
), prefix
))
1329 typ
: Union
[List
[str], str],
1330 ifcond
: QAPISchemaIfCond
,
1331 features
: Optional
[List
[Dict
[str, Any
]]],
1332 info
: QAPISourceInfo
,
1333 ) -> QAPISchemaObjectTypeMember
:
1335 if name
.startswith('*'):
1338 if isinstance(typ
, list):
1339 assert len(typ
) == 1
1340 typ
= self
._make
_array
_type
(typ
[0], info
)
1341 return QAPISchemaObjectTypeMember(name
, info
, typ
, optional
, ifcond
,
1342 self
._make
_features
(features
, info
))
1346 data
: Dict
[str, Any
],
1347 info
: QAPISourceInfo
,
1348 ) -> List
[QAPISchemaObjectTypeMember
]:
1349 return [self
._make
_member
(key
, value
['type'],
1350 QAPISchemaIfCond(value
.get('if')),
1351 value
.get('features'), info
)
1352 for (key
, value
) in data
.items()]
1354 def _def_struct_type(self
, expr
: QAPIExpression
) -> None:
1355 name
= expr
['struct']
1356 base
= expr
.get('base')
1359 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1360 features
= self
._make
_features
(expr
.get('features'), info
)
1361 self
._def
_definition
(QAPISchemaObjectType(
1362 name
, info
, expr
.doc
, ifcond
, features
, base
,
1363 self
._make
_members
(data
, info
),
1370 ifcond
: QAPISchemaIfCond
,
1371 info
: QAPISourceInfo
,
1372 ) -> QAPISchemaVariant
:
1373 if isinstance(typ
, list):
1374 assert len(typ
) == 1
1375 typ
= self
._make
_array
_type
(typ
[0], info
)
1376 return QAPISchemaVariant(case
, info
, typ
, ifcond
)
1378 def _def_union_type(self
, expr
: QAPIExpression
) -> None:
1379 name
= expr
['union']
1381 tag_name
= expr
['discriminator']
1383 assert isinstance(data
, dict)
1385 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1386 features
= self
._make
_features
(expr
.get('features'), info
)
1387 if isinstance(base
, dict):
1388 base
= self
._make
_implicit
_object
_type
(
1390 'base', self
._make
_members
(base
, info
))
1392 self
._make
_variant
(key
, value
['type'],
1393 QAPISchemaIfCond(value
.get('if')),
1395 for (key
, value
) in data
.items()]
1396 members
: List
[QAPISchemaObjectTypeMember
] = []
1397 self
._def
_definition
(
1398 QAPISchemaObjectType(name
, info
, expr
.doc
, ifcond
, features
,
1401 info
, variants
, tag_name
)))
1403 def _def_alternate_type(self
, expr
: QAPIExpression
) -> None:
1404 name
= expr
['alternate']
1406 assert isinstance(data
, dict)
1407 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1409 features
= self
._make
_features
(expr
.get('features'), info
)
1411 self
._make
_variant
(key
, value
['type'],
1412 QAPISchemaIfCond(value
.get('if')),
1414 for (key
, value
) in data
.items()]
1415 tag_member
= QAPISchemaObjectTypeMember('type', info
, 'QType', False)
1416 self
._def
_definition
(
1417 QAPISchemaAlternateType(
1418 name
, info
, expr
.doc
, ifcond
, features
,
1419 QAPISchemaAlternatives(info
, variants
, tag_member
)))
1421 def _def_command(self
, expr
: QAPIExpression
) -> None:
1422 name
= expr
['command']
1423 data
= expr
.get('data')
1424 rets
= expr
.get('returns')
1425 gen
= expr
.get('gen', True)
1426 success_response
= expr
.get('success-response', True)
1427 boxed
= expr
.get('boxed', False)
1428 allow_oob
= expr
.get('allow-oob', False)
1429 allow_preconfig
= expr
.get('allow-preconfig', False)
1430 coroutine
= expr
.get('coroutine', False)
1431 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1433 features
= self
._make
_features
(expr
.get('features'), info
)
1434 if isinstance(data
, OrderedDict
):
1435 data
= self
._make
_implicit
_object
_type
(
1437 'arg', self
._make
_members
(data
, info
))
1438 if isinstance(rets
, list):
1439 assert len(rets
) == 1
1440 rets
= self
._make
_array
_type
(rets
[0], info
)
1441 self
._def
_definition
(
1442 QAPISchemaCommand(name
, info
, expr
.doc
, ifcond
, features
, data
,
1443 rets
, gen
, success_response
, boxed
, allow_oob
,
1444 allow_preconfig
, coroutine
))
1446 def _def_event(self
, expr
: QAPIExpression
) -> None:
1447 name
= expr
['event']
1448 data
= expr
.get('data')
1449 boxed
= expr
.get('boxed', False)
1450 ifcond
= QAPISchemaIfCond(expr
.get('if'))
1452 features
= self
._make
_features
(expr
.get('features'), info
)
1453 if isinstance(data
, OrderedDict
):
1454 data
= self
._make
_implicit
_object
_type
(
1456 'arg', self
._make
_members
(data
, info
))
1457 self
._def
_definition
(QAPISchemaEvent(name
, info
, expr
.doc
, ifcond
,
1458 features
, data
, boxed
))
1460 def _def_exprs(self
, exprs
: List
[QAPIExpression
]) -> None:
1463 self
._def
_enum
_type
(expr
)
1464 elif 'struct' in expr
:
1465 self
._def
_struct
_type
(expr
)
1466 elif 'union' in expr
:
1467 self
._def
_union
_type
(expr
)
1468 elif 'alternate' in expr
:
1469 self
._def
_alternate
_type
(expr
)
1470 elif 'command' in expr
:
1471 self
._def
_command
(expr
)
1472 elif 'event' in expr
:
1473 self
._def
_event
(expr
)
1474 elif 'include' in expr
:
1475 self
._def
_include
(expr
)
1479 def check(self
) -> None:
1480 for ent
in self
._entity
_list
:
1483 for ent
in self
._entity
_list
:
1484 ent
.set_module(self
)
1485 for doc
in self
.docs
:
1488 def visit(self
, visitor
: QAPISchemaVisitor
) -> None:
1489 visitor
.visit_begin(self
)
1490 for mod
in self
._module
_dict
.values():