]> git.proxmox.com Git - mirror_qemu.git/blame - scripts/qapi/schema.py
qapi: Extend -compat to set policy for unstable interfaces
[mirror_qemu.git] / scripts / qapi / schema.py
CommitLineData
e6c42b96
MA
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# TODO catching name collisions in generated code would be nice
16
67fea575 17from collections import OrderedDict
e6c42b96
MA
18import os
19import re
7e09d788 20from typing import Optional
e6c42b96 21
d806f89f
MAL
22from .common import (
23 POINTER_SUFFIX,
24 c_name,
25 cgen_ifcond,
26 docgen_ifcond,
1889e57a
MA
27 gen_endif,
28 gen_if,
d806f89f 29)
3404e574 30from .error import QAPIError, QAPISemError, QAPISourceError
7137a960
JS
31from .expr import check_exprs
32from .parser import QAPISchemaParser
e6c42b96
MA
33
34
f17539c8
MAL
35class QAPISchemaIfCond:
36 def __init__(self, ifcond=None):
e46c930c 37 self.ifcond = ifcond
f17539c8 38
1889e57a 39 def _cgen(self):
6cc2e481
MAL
40 return cgen_ifcond(self.ifcond)
41
1889e57a
MA
42 def gen_if(self):
43 return gen_if(self._cgen())
44
45 def gen_endif(self):
46 return gen_endif(self._cgen())
47
d806f89f
MAL
48 def docgen(self):
49 return docgen_ifcond(self.ifcond)
50
33aa3267
MAL
51 def is_present(self):
52 return bool(self.ifcond)
53
f17539c8 54
baa310f1 55class QAPISchemaEntity:
7e09d788 56 meta: Optional[str] = None
e6c42b96 57
82b52f6b 58 def __init__(self, name: str, info, doc, ifcond=None, features=None):
e6c42b96 59 assert name is None or isinstance(name, str)
b3cdff10
MA
60 for f in features or []:
61 assert isinstance(f, QAPISchemaFeature)
62 f.set_defined_in(name)
e6c42b96
MA
63 self.name = name
64 self._module = None
65 # For explicitly defined entities, info points to the (explicit)
66 # definition. For builtins (and their arrays), info is None.
67 # For implicitly defined entities, info points to a place that
68 # triggered the implicit definition (there may be more than one
69 # such place).
70 self.info = info
71 self.doc = doc
f17539c8 72 self._ifcond = ifcond or QAPISchemaIfCond()
b3cdff10 73 self.features = features or []
e6c42b96
MA
74 self._checked = False
75
76 def c_name(self):
77 return c_name(self.name)
78
79 def check(self, schema):
80 assert not self._checked
b3cdff10
MA
81 seen = {}
82 for f in self.features:
83 f.check_clash(self.info, seen)
e6c42b96
MA
84 self._checked = True
85
7faefad1 86 def connect_doc(self, doc=None):
e4405b30
MA
87 doc = doc or self.doc
88 if doc:
89 for f in self.features:
90 doc.connect_feature(f)
ee1e6a1f
MA
91
92 def check_doc(self):
1192a486
MA
93 if self.doc:
94 self.doc.check()
ee1e6a1f 95
a9f1dd7e
MA
96 def _set_module(self, schema, info):
97 assert self._checked
39b2d838 98 fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
e2bbc4ea 99 self._module = schema.module_by_fname(fname)
3e7fb581 100 self._module.add_entity(self)
a9f1dd7e
MA
101
102 def set_module(self, schema):
103 self._set_module(schema, self.info)
104
e6c42b96
MA
105 @property
106 def ifcond(self):
107 assert self._checked
108 return self._ifcond
109
e6c42b96
MA
110 def is_implicit(self):
111 return not self.info
112
113 def visit(self, visitor):
114 assert self._checked
115
116 def describe(self):
117 assert self.meta
118 return "%s '%s'" % (self.meta, self.name)
119
120
baa310f1 121class QAPISchemaVisitor:
e6c42b96
MA
122 def visit_begin(self, schema):
123 pass
124
125 def visit_end(self):
126 pass
127
8ec0e1a4 128 def visit_module(self, name):
e6c42b96
MA
129 pass
130
131 def visit_needed(self, entity):
132 # Default to visiting everything
133 return True
134
8ec0e1a4 135 def visit_include(self, name, info):
e6c42b96
MA
136 pass
137
138 def visit_builtin_type(self, name, info, json_type):
139 pass
140
013b4efc 141 def visit_enum_type(self, name, info, ifcond, features, members, prefix):
e6c42b96
MA
142 pass
143
144 def visit_array_type(self, name, info, ifcond, element_type):
145 pass
146
7b3bc9e2
MA
147 def visit_object_type(self, name, info, ifcond, features,
148 base, members, variants):
e6c42b96
MA
149 pass
150
7b3bc9e2
MA
151 def visit_object_type_flat(self, name, info, ifcond, features,
152 members, variants):
e6c42b96
MA
153 pass
154
013b4efc 155 def visit_alternate_type(self, name, info, ifcond, features, variants):
e6c42b96
MA
156 pass
157
7b3bc9e2
MA
158 def visit_command(self, name, info, ifcond, features,
159 arg_type, ret_type, gen, success_response, boxed,
04f22362 160 allow_oob, allow_preconfig, coroutine):
e6c42b96
MA
161 pass
162
013b4efc 163 def visit_event(self, name, info, ifcond, features, arg_type, boxed):
e6c42b96
MA
164 pass
165
166
baa310f1 167class QAPISchemaModule:
39b2d838
JS
168
169 BUILTIN_MODULE_NAME = './builtin'
170
a9f1dd7e
MA
171 def __init__(self, name):
172 self.name = name
3e7fb581
MA
173 self._entity_list = []
174
98967c24 175 @staticmethod
e2bbc4ea 176 def is_system_module(name: str) -> bool:
98967c24
JS
177 """
178 System modules are internally defined modules.
179
180 Their names start with the "./" prefix.
181 """
e2bbc4ea 182 return name.startswith('./')
98967c24
JS
183
184 @classmethod
e2bbc4ea 185 def is_user_module(cls, name: str) -> bool:
98967c24
JS
186 """
187 User modules are those defined by the user in qapi JSON files.
188
189 They do not start with the "./" prefix.
190 """
191 return not cls.is_system_module(name)
192
39b2d838
JS
193 @classmethod
194 def is_builtin_module(cls, name: str) -> bool:
98967c24
JS
195 """
196 The built-in module is a single System module for the built-in types.
197
e2bbc4ea 198 It is always "./builtin".
98967c24 199 """
39b2d838 200 return name == cls.BUILTIN_MODULE_NAME
98967c24 201
3e7fb581
MA
202 def add_entity(self, ent):
203 self._entity_list.append(ent)
204
205 def visit(self, visitor):
206 visitor.visit_module(self.name)
207 for entity in self._entity_list:
208 if visitor.visit_needed(entity):
209 entity.visit(visitor)
a9f1dd7e 210
e6c42b96 211
a9f1dd7e
MA
212class QAPISchemaInclude(QAPISchemaEntity):
213 def __init__(self, sub_module, info):
2cae67bc 214 super().__init__(None, info, None)
a9f1dd7e 215 self._sub_module = sub_module
e6c42b96
MA
216
217 def visit(self, visitor):
2cae67bc 218 super().visit(visitor)
a9f1dd7e 219 visitor.visit_include(self._sub_module.name, self.info)
e6c42b96
MA
220
221
222class QAPISchemaType(QAPISchemaEntity):
223 # Return the C type for common use.
224 # For the types we commonly box, this is a pointer type.
225 def c_type(self):
226 pass
227
228 # Return the C type to be used in a parameter list.
229 def c_param_type(self):
230 return self.c_type()
231
232 # Return the C type to be used where we suppress boxing.
233 def c_unboxed_type(self):
234 return self.c_type()
235
236 def json_type(self):
237 pass
238
239 def alternate_qtype(self):
240 json2qtype = {
241 'null': 'QTYPE_QNULL',
242 'string': 'QTYPE_QSTRING',
243 'number': 'QTYPE_QNUM',
244 'int': 'QTYPE_QNUM',
245 'boolean': 'QTYPE_QBOOL',
246 'object': 'QTYPE_QDICT'
247 }
248 return json2qtype.get(self.json_type())
249
250 def doc_type(self):
251 if self.is_implicit():
252 return None
253 return self.name
254
f965e8fe
MA
255 def check(self, schema):
256 QAPISchemaEntity.check(self, schema)
57df0dff
MA
257 for feat in self.features:
258 if feat.is_special():
259 raise QAPISemError(
260 self.info,
261 f"feature '{feat.name}' is not supported for types")
f965e8fe 262
e6c42b96
MA
263 def describe(self):
264 assert self.meta
265 return "%s type '%s'" % (self.meta, self.name)
266
267
268class QAPISchemaBuiltinType(QAPISchemaType):
269 meta = 'built-in'
270
271 def __init__(self, name, json_type, c_type):
2cae67bc 272 super().__init__(name, None, None)
e6c42b96
MA
273 assert not c_type or isinstance(c_type, str)
274 assert json_type in ('string', 'number', 'int', 'boolean', 'null',
275 'value')
276 self._json_type_name = json_type
277 self._c_type_name = c_type
278
279 def c_name(self):
280 return self.name
281
282 def c_type(self):
283 return self._c_type_name
284
285 def c_param_type(self):
286 if self.name == 'str':
287 return 'const ' + self._c_type_name
288 return self._c_type_name
289
290 def json_type(self):
291 return self._json_type_name
292
293 def doc_type(self):
294 return self.json_type()
295
296 def visit(self, visitor):
2cae67bc 297 super().visit(visitor)
e6c42b96
MA
298 visitor.visit_builtin_type(self.name, self.info, self.json_type())
299
300
301class QAPISchemaEnumType(QAPISchemaType):
302 meta = 'enum'
303
013b4efc
MA
304 def __init__(self, name, info, doc, ifcond, features, members, prefix):
305 super().__init__(name, info, doc, ifcond, features)
e6c42b96
MA
306 for m in members:
307 assert isinstance(m, QAPISchemaEnumMember)
308 m.set_defined_in(name)
309 assert prefix is None or isinstance(prefix, str)
310 self.members = members
311 self.prefix = prefix
312
313 def check(self, schema):
2cae67bc 314 super().check(schema)
e6c42b96
MA
315 seen = {}
316 for m in self.members:
317 m.check_clash(self.info, seen)
ee1e6a1f 318
7faefad1 319 def connect_doc(self, doc=None):
e4405b30 320 super().connect_doc(doc)
7faefad1 321 doc = doc or self.doc
645178c0
MA
322 for m in self.members:
323 m.connect_doc(doc)
e6c42b96
MA
324
325 def is_implicit(self):
4e99f4b1
MA
326 # See QAPISchema._def_predefineds()
327 return self.name == 'QType'
e6c42b96
MA
328
329 def c_type(self):
330 return c_name(self.name)
331
332 def member_names(self):
333 return [m.name for m in self.members]
334
335 def json_type(self):
336 return 'string'
337
338 def visit(self, visitor):
2cae67bc 339 super().visit(visitor)
013b4efc
MA
340 visitor.visit_enum_type(
341 self.name, self.info, self.ifcond, self.features,
342 self.members, self.prefix)
e6c42b96
MA
343
344
345class QAPISchemaArrayType(QAPISchemaType):
346 meta = 'array'
347
348 def __init__(self, name, info, element_type):
013b4efc 349 super().__init__(name, info, None)
e6c42b96
MA
350 assert isinstance(element_type, str)
351 self._element_type_name = element_type
352 self.element_type = None
353
354 def check(self, schema):
2cae67bc 355 super().check(schema)
e6c42b96
MA
356 self.element_type = schema.resolve_type(
357 self._element_type_name, self.info,
358 self.info and self.info.defn_meta)
359 assert not isinstance(self.element_type, QAPISchemaArrayType)
360
a9f1dd7e
MA
361 def set_module(self, schema):
362 self._set_module(schema, self.element_type.info)
363
e6c42b96
MA
364 @property
365 def ifcond(self):
366 assert self._checked
367 return self.element_type.ifcond
368
e6c42b96
MA
369 def is_implicit(self):
370 return True
371
372 def c_type(self):
a7aa64a6 373 return c_name(self.name) + POINTER_SUFFIX
e6c42b96
MA
374
375 def json_type(self):
376 return 'array'
377
378 def doc_type(self):
379 elt_doc_type = self.element_type.doc_type()
380 if not elt_doc_type:
381 return None
382 return 'array of ' + elt_doc_type
383
384 def visit(self, visitor):
2cae67bc 385 super().visit(visitor)
e6c42b96
MA
386 visitor.visit_array_type(self.name, self.info, self.ifcond,
387 self.element_type)
388
389 def describe(self):
390 assert self.meta
391 return "%s type ['%s']" % (self.meta, self._element_type_name)
392
393
394class QAPISchemaObjectType(QAPISchemaType):
013b4efc
MA
395 def __init__(self, name, info, doc, ifcond, features,
396 base, local_members, variants):
e6c42b96 397 # struct has local_members, optional base, and no variants
4e99f4b1 398 # union has base, variants, and no local_members
2cae67bc 399 super().__init__(name, info, doc, ifcond, features)
e6c42b96
MA
400 self.meta = 'union' if variants else 'struct'
401 assert base is None or isinstance(base, str)
402 for m in local_members:
403 assert isinstance(m, QAPISchemaObjectTypeMember)
404 m.set_defined_in(name)
405 if variants is not None:
5858fd1a 406 assert isinstance(variants, QAPISchemaVariants)
e6c42b96 407 variants.set_defined_in(name)
e6c42b96
MA
408 self._base_name = base
409 self.base = None
410 self.local_members = local_members
411 self.variants = variants
412 self.members = None
e6c42b96
MA
413
414 def check(self, schema):
415 # This calls another type T's .check() exactly when the C
416 # struct emitted by gen_object() contains that T's C struct
417 # (pointers don't count).
418 if self.members is not None:
419 # A previous .check() completed: nothing to do
420 return
421 if self._checked:
422 # Recursed: C struct contains itself
423 raise QAPISemError(self.info,
424 "object %s contains itself" % self.name)
425
2cae67bc 426 super().check(schema)
e6c42b96
MA
427 assert self._checked and self.members is None
428
429 seen = OrderedDict()
430 if self._base_name:
431 self.base = schema.resolve_type(self._base_name, self.info,
432 "'base'")
433 if (not isinstance(self.base, QAPISchemaObjectType)
434 or self.base.variants):
435 raise QAPISemError(
436 self.info,
437 "'base' requires a struct type, %s isn't"
438 % self.base.describe())
439 self.base.check(schema)
440 self.base.check_clash(self.info, seen)
441 for m in self.local_members:
442 m.check(schema)
443 m.check_clash(self.info, seen)
e6c42b96
MA
444 members = seen.values()
445
446 if self.variants:
447 self.variants.check(schema, seen)
448 self.variants.check_clash(self.info, seen)
449
e6c42b96
MA
450 self.members = members # mark completed
451
452 # Check that the members of this type do not cause duplicate JSON members,
453 # and update seen to track the members seen so far. Report any errors
454 # on behalf of info, which is not necessarily self.info
455 def check_clash(self, info, seen):
456 assert self._checked
457 assert not self.variants # not implemented
458 for m in self.members:
459 m.check_clash(info, seen)
460
7faefad1 461 def connect_doc(self, doc=None):
e4405b30 462 super().connect_doc(doc)
7faefad1 463 doc = doc or self.doc
645178c0
MA
464 if self.base and self.base.is_implicit():
465 self.base.connect_doc(doc)
466 for m in self.local_members:
467 m.connect_doc(doc)
ee1e6a1f 468
e6c42b96
MA
469 def is_implicit(self):
470 # See QAPISchema._make_implicit_object_type(), as well as
471 # _def_predefineds()
472 return self.name.startswith('q_')
473
474 def is_empty(self):
475 assert self.members is not None
476 return not self.members and not self.variants
477
478 def c_name(self):
479 assert self.name != 'q_empty'
2cae67bc 480 return super().c_name()
e6c42b96
MA
481
482 def c_type(self):
483 assert not self.is_implicit()
a7aa64a6 484 return c_name(self.name) + POINTER_SUFFIX
e6c42b96
MA
485
486 def c_unboxed_type(self):
487 return c_name(self.name)
488
489 def json_type(self):
490 return 'object'
491
492 def visit(self, visitor):
2cae67bc 493 super().visit(visitor)
7b3bc9e2
MA
494 visitor.visit_object_type(
495 self.name, self.info, self.ifcond, self.features,
496 self.base, self.local_members, self.variants)
497 visitor.visit_object_type_flat(
498 self.name, self.info, self.ifcond, self.features,
499 self.members, self.variants)
e6c42b96
MA
500
501
226b5be6
MA
502class QAPISchemaAlternateType(QAPISchemaType):
503 meta = 'alternate'
e6c42b96 504
226b5be6
MA
505 def __init__(self, name, info, doc, ifcond, features, variants):
506 super().__init__(name, info, doc, ifcond, features)
5858fd1a 507 assert isinstance(variants, QAPISchemaVariants)
226b5be6
MA
508 assert variants.tag_member
509 variants.set_defined_in(name)
510 variants.tag_member.set_defined_in(self.name)
511 self.variants = variants
e6c42b96 512
226b5be6
MA
513 def check(self, schema):
514 super().check(schema)
515 self.variants.tag_member.check(schema)
516 # Not calling self.variants.check_clash(), because there's nothing
517 # to clash with
518 self.variants.check(schema, {})
519 # Alternate branch names have no relation to the tag enum values;
520 # so we have to check for potential name collisions ourselves.
521 seen = {}
522 types_seen = {}
523 for v in self.variants.variants:
524 v.check_clash(self.info, seen)
525 qtype = v.type.alternate_qtype()
526 if not qtype:
527 raise QAPISemError(
528 self.info,
529 "%s cannot use %s"
530 % (v.describe(self.info), v.type.describe()))
531 conflicting = set([qtype])
532 if qtype == 'QTYPE_QSTRING':
533 if isinstance(v.type, QAPISchemaEnumType):
534 for m in v.type.members:
535 if m.name in ['on', 'off']:
536 conflicting.add('QTYPE_QBOOL')
537 if re.match(r'[-+0-9.]', m.name):
538 # lazy, could be tightened
539 conflicting.add('QTYPE_QNUM')
540 else:
541 conflicting.add('QTYPE_QNUM')
542 conflicting.add('QTYPE_QBOOL')
543 for qt in conflicting:
544 if qt in types_seen:
545 raise QAPISemError(
546 self.info,
547 "%s can't be distinguished from '%s'"
548 % (v.describe(self.info), types_seen[qt]))
549 types_seen[qt] = v.name
e6c42b96 550
226b5be6
MA
551 def connect_doc(self, doc=None):
552 super().connect_doc(doc)
553 doc = doc or self.doc
645178c0
MA
554 for v in self.variants.variants:
555 v.connect_doc(doc)
e6c42b96 556
226b5be6 557 def c_type(self):
a7aa64a6 558 return c_name(self.name) + POINTER_SUFFIX
e6c42b96 559
226b5be6
MA
560 def json_type(self):
561 return 'value'
e6c42b96 562
226b5be6
MA
563 def visit(self, visitor):
564 super().visit(visitor)
565 visitor.visit_alternate_type(
566 self.name, self.info, self.ifcond, self.features, self.variants)
e6c42b96
MA
567
568
5858fd1a 569class QAPISchemaVariants:
e6c42b96 570 def __init__(self, tag_name, info, tag_member, variants):
4e99f4b1
MA
571 # Unions pass tag_name but not tag_member.
572 # Alternates pass tag_member but not tag_name.
573 # After check(), tag_member is always set.
e6c42b96
MA
574 assert bool(tag_member) != bool(tag_name)
575 assert (isinstance(tag_name, str) or
576 isinstance(tag_member, QAPISchemaObjectTypeMember))
577 for v in variants:
5858fd1a 578 assert isinstance(v, QAPISchemaVariant)
e6c42b96
MA
579 self._tag_name = tag_name
580 self.info = info
581 self.tag_member = tag_member
582 self.variants = variants
583
584 def set_defined_in(self, name):
585 for v in self.variants:
586 v.set_defined_in(name)
587
588 def check(self, schema, seen):
4e99f4b1 589 if self._tag_name: # union
e6c42b96
MA
590 self.tag_member = seen.get(c_name(self._tag_name))
591 base = "'base'"
592 # Pointing to the base type when not implicit would be
593 # nice, but we don't know it here
594 if not self.tag_member or self._tag_name != self.tag_member.name:
595 raise QAPISemError(
596 self.info,
597 "discriminator '%s' is not a member of %s"
598 % (self._tag_name, base))
599 # Here we do:
600 base_type = schema.lookup_type(self.tag_member.defined_in)
601 assert base_type
602 if not base_type.is_implicit():
603 base = "base type '%s'" % self.tag_member.defined_in
604 if not isinstance(self.tag_member.type, QAPISchemaEnumType):
605 raise QAPISemError(
606 self.info,
607 "discriminator member '%s' of %s must be of enum type"
608 % (self._tag_name, base))
609 if self.tag_member.optional:
610 raise QAPISemError(
611 self.info,
612 "discriminator member '%s' of %s must not be optional"
613 % (self._tag_name, base))
33aa3267 614 if self.tag_member.ifcond.is_present():
e6c42b96
MA
615 raise QAPISemError(
616 self.info,
617 "discriminator member '%s' of %s must not be conditional"
618 % (self._tag_name, base))
4e99f4b1 619 else: # alternate
e6c42b96
MA
620 assert isinstance(self.tag_member.type, QAPISchemaEnumType)
621 assert not self.tag_member.optional
33aa3267 622 assert not self.tag_member.ifcond.is_present()
4e99f4b1 623 if self._tag_name: # union
e6c42b96 624 # branches that are not explicitly covered get an empty type
8ec0e1a4 625 cases = {v.name for v in self.variants}
e6c42b96
MA
626 for m in self.tag_member.type.members:
627 if m.name not in cases:
5858fd1a
MA
628 v = QAPISchemaVariant(m.name, self.info,
629 'q_empty', m.ifcond)
e6c42b96
MA
630 v.set_defined_in(self.tag_member.defined_in)
631 self.variants.append(v)
632 if not self.variants:
633 raise QAPISemError(self.info, "union has no branches")
634 for v in self.variants:
635 v.check(schema)
636 # Union names must match enum values; alternate names are
637 # checked separately. Use 'seen' to tell the two apart.
638 if seen:
639 if v.name not in self.tag_member.type.member_names():
640 raise QAPISemError(
641 self.info,
642 "branch '%s' is not a value of %s"
643 % (v.name, self.tag_member.type.describe()))
644 if (not isinstance(v.type, QAPISchemaObjectType)
645 or v.type.variants):
646 raise QAPISemError(
647 self.info,
648 "%s cannot use %s"
649 % (v.describe(self.info), v.type.describe()))
650 v.type.check(schema)
651
652 def check_clash(self, info, seen):
653 for v in self.variants:
654 # Reset seen map for each variant, since qapi names from one
655 # branch do not affect another branch
656 v.type.check_clash(info, dict(seen))
657
658
226b5be6
MA
659class QAPISchemaMember:
660 """ Represents object members, enum members and features """
661 role = 'member'
e6c42b96 662
226b5be6
MA
663 def __init__(self, name, info, ifcond=None):
664 assert isinstance(name, str)
665 self.name = name
666 self.info = info
f17539c8 667 self.ifcond = ifcond or QAPISchemaIfCond()
226b5be6 668 self.defined_in = None
e6c42b96 669
226b5be6
MA
670 def set_defined_in(self, name):
671 assert not self.defined_in
672 self.defined_in = name
e6c42b96 673
226b5be6
MA
674 def check_clash(self, info, seen):
675 cname = c_name(self.name)
676 if cname in seen:
677 raise QAPISemError(
678 info,
679 "%s collides with %s"
680 % (self.describe(info), seen[cname].describe(info)))
681 seen[cname] = self
e6c42b96 682
645178c0
MA
683 def connect_doc(self, doc):
684 if doc:
685 doc.connect_member(self)
686
226b5be6
MA
687 def describe(self, info):
688 role = self.role
689 defined_in = self.defined_in
690 assert defined_in
e6c42b96 691
226b5be6
MA
692 if defined_in.startswith('q_obj_'):
693 # See QAPISchema._make_implicit_object_type() - reverse the
694 # mapping there to create a nice human-readable description
695 defined_in = defined_in[6:]
696 if defined_in.endswith('-arg'):
697 # Implicit type created for a command's dict 'data'
698 assert role == 'member'
699 role = 'parameter'
700 elif defined_in.endswith('-base'):
4e99f4b1 701 # Implicit type created for a union's dict 'base'
226b5be6
MA
702 role = 'base ' + role
703 else:
226b5be6 704 assert False
226b5be6
MA
705 elif defined_in != info.defn_name:
706 return "%s '%s' of type '%s'" % (role, self.name, defined_in)
707 return "%s '%s'" % (role, self.name)
ee1e6a1f 708
ee1e6a1f 709
226b5be6
MA
710class QAPISchemaEnumMember(QAPISchemaMember):
711 role = 'value'
e6c42b96 712
b6c18755
MA
713 def __init__(self, name, info, ifcond=None, features=None):
714 super().__init__(name, info, ifcond)
715 for f in features or []:
716 assert isinstance(f, QAPISchemaFeature)
717 f.set_defined_in(name)
718 self.features = features or []
719
720 def connect_doc(self, doc):
721 super().connect_doc(doc)
722 if doc:
723 for f in self.features:
724 doc.connect_feature(f)
725
e6c42b96 726
226b5be6
MA
727class QAPISchemaFeature(QAPISchemaMember):
728 role = 'feature'
729
c67db1ed 730 def is_special(self):
57df0dff 731 return self.name in ('deprecated', 'unstable')
c67db1ed 732
226b5be6
MA
733
734class QAPISchemaObjectTypeMember(QAPISchemaMember):
84ab0086 735 def __init__(self, name, info, typ, optional, ifcond=None, features=None):
226b5be6
MA
736 super().__init__(name, info, ifcond)
737 assert isinstance(typ, str)
738 assert isinstance(optional, bool)
84ab0086
MA
739 for f in features or []:
740 assert isinstance(f, QAPISchemaFeature)
741 f.set_defined_in(name)
226b5be6
MA
742 self._type_name = typ
743 self.type = None
744 self.optional = optional
84ab0086 745 self.features = features or []
226b5be6
MA
746
747 def check(self, schema):
748 assert self.defined_in
749 self.type = schema.resolve_type(self._type_name, self.info,
750 self.describe)
84ab0086
MA
751 seen = {}
752 for f in self.features:
753 f.check_clash(self.info, seen)
754
755 def connect_doc(self, doc):
756 super().connect_doc(doc)
757 if doc:
758 for f in self.features:
759 doc.connect_feature(f)
226b5be6
MA
760
761
5858fd1a 762class QAPISchemaVariant(QAPISchemaObjectTypeMember):
226b5be6
MA
763 role = 'branch'
764
765 def __init__(self, name, info, typ, ifcond=None):
766 super().__init__(name, info, typ, False, ifcond)
e6c42b96
MA
767
768
769class QAPISchemaCommand(QAPISchemaEntity):
770 meta = 'command'
771
013b4efc
MA
772 def __init__(self, name, info, doc, ifcond, features,
773 arg_type, ret_type,
04f22362
KW
774 gen, success_response, boxed, allow_oob, allow_preconfig,
775 coroutine):
2cae67bc 776 super().__init__(name, info, doc, ifcond, features)
e6c42b96
MA
777 assert not arg_type or isinstance(arg_type, str)
778 assert not ret_type or isinstance(ret_type, str)
779 self._arg_type_name = arg_type
780 self.arg_type = None
781 self._ret_type_name = ret_type
782 self.ret_type = None
783 self.gen = gen
784 self.success_response = success_response
785 self.boxed = boxed
786 self.allow_oob = allow_oob
787 self.allow_preconfig = allow_preconfig
04f22362 788 self.coroutine = coroutine
e6c42b96
MA
789
790 def check(self, schema):
2cae67bc 791 super().check(schema)
e6c42b96
MA
792 if self._arg_type_name:
793 self.arg_type = schema.resolve_type(
794 self._arg_type_name, self.info, "command's 'data'")
795 if not isinstance(self.arg_type, QAPISchemaObjectType):
796 raise QAPISemError(
797 self.info,
798 "command's 'data' cannot take %s"
799 % self.arg_type.describe())
800 if self.arg_type.variants and not self.boxed:
801 raise QAPISemError(
802 self.info,
803 "command's 'data' can take %s only with 'boxed': true"
804 % self.arg_type.describe())
805 if self._ret_type_name:
806 self.ret_type = schema.resolve_type(
807 self._ret_type_name, self.info, "command's 'returns'")
b86df374 808 if self.name not in self.info.pragma.command_returns_exceptions:
7e9c1707
MA
809 typ = self.ret_type
810 if isinstance(typ, QAPISchemaArrayType):
811 typ = self.ret_type.element_type
812 assert typ
813 if not isinstance(typ, QAPISchemaObjectType):
e6c42b96
MA
814 raise QAPISemError(
815 self.info,
816 "command's 'returns' cannot take %s"
817 % self.ret_type.describe())
818
bf83f04e 819 def connect_doc(self, doc=None):
e4405b30 820 super().connect_doc(doc)
bf83f04e
MA
821 doc = doc or self.doc
822 if doc:
823 if self.arg_type and self.arg_type.is_implicit():
824 self.arg_type.connect_doc(doc)
825
e6c42b96 826 def visit(self, visitor):
2cae67bc 827 super().visit(visitor)
7b3bc9e2
MA
828 visitor.visit_command(
829 self.name, self.info, self.ifcond, self.features,
830 self.arg_type, self.ret_type, self.gen, self.success_response,
04f22362
KW
831 self.boxed, self.allow_oob, self.allow_preconfig,
832 self.coroutine)
e6c42b96
MA
833
834
835class QAPISchemaEvent(QAPISchemaEntity):
836 meta = 'event'
837
013b4efc
MA
838 def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
839 super().__init__(name, info, doc, ifcond, features)
e6c42b96
MA
840 assert not arg_type or isinstance(arg_type, str)
841 self._arg_type_name = arg_type
842 self.arg_type = None
843 self.boxed = boxed
844
845 def check(self, schema):
2cae67bc 846 super().check(schema)
e6c42b96
MA
847 if self._arg_type_name:
848 self.arg_type = schema.resolve_type(
849 self._arg_type_name, self.info, "event's 'data'")
850 if not isinstance(self.arg_type, QAPISchemaObjectType):
851 raise QAPISemError(
852 self.info,
853 "event's 'data' cannot take %s"
854 % self.arg_type.describe())
855 if self.arg_type.variants and not self.boxed:
856 raise QAPISemError(
857 self.info,
858 "event's 'data' can take %s only with 'boxed': true"
859 % self.arg_type.describe())
860
bf83f04e 861 def connect_doc(self, doc=None):
e4405b30 862 super().connect_doc(doc)
bf83f04e
MA
863 doc = doc or self.doc
864 if doc:
865 if self.arg_type and self.arg_type.is_implicit():
866 self.arg_type.connect_doc(doc)
867
e6c42b96 868 def visit(self, visitor):
2cae67bc 869 super().visit(visitor)
013b4efc
MA
870 visitor.visit_event(
871 self.name, self.info, self.ifcond, self.features,
872 self.arg_type, self.boxed)
e6c42b96
MA
873
874
baa310f1 875class QAPISchema:
e6c42b96
MA
876 def __init__(self, fname):
877 self.fname = fname
3404e574
JS
878
879 try:
880 parser = QAPISchemaParser(fname)
881 except OSError as err:
882 raise QAPIError(
883 f"can't read schema file '{fname}': {err.strerror}"
884 ) from err
885
e6c42b96
MA
886 exprs = check_exprs(parser.exprs)
887 self.docs = parser.docs
888 self._entity_list = []
889 self._entity_dict = {}
43d1455c 890 self._module_dict = OrderedDict()
a9f1dd7e 891 self._schema_dir = os.path.dirname(fname)
39b2d838 892 self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
a9f1dd7e 893 self._make_module(fname)
e6c42b96
MA
894 self._predefining = True
895 self._def_predefineds()
896 self._predefining = False
897 self._def_exprs(exprs)
898 self.check()
899
900 def _def_entity(self, ent):
901 # Only the predefined types are allowed to not have info
902 assert ent.info or self._predefining
903 self._entity_list.append(ent)
904 if ent.name is None:
905 return
906 # TODO reject names that differ only in '_' vs. '.' vs. '-',
907 # because they're liable to clash in generated C.
908 other_ent = self._entity_dict.get(ent.name)
909 if other_ent:
910 if other_ent.info:
86cc2ff6 911 where = QAPISourceError(other_ent.info, "previous definition")
e6c42b96
MA
912 raise QAPISemError(
913 ent.info,
914 "'%s' is already defined\n%s" % (ent.name, where))
915 raise QAPISemError(
916 ent.info, "%s is already defined" % other_ent.describe())
917 self._entity_dict[ent.name] = ent
918
919 def lookup_entity(self, name, typ=None):
920 ent = self._entity_dict.get(name)
921 if typ and not isinstance(ent, typ):
922 return None
923 return ent
924
925 def lookup_type(self, name):
926 return self.lookup_entity(name, QAPISchemaType)
927
928 def resolve_type(self, name, info, what):
929 typ = self.lookup_type(name)
930 if not typ:
931 if callable(what):
932 what = what(info)
933 raise QAPISemError(
934 info, "%s uses unknown type '%s'" % (what, name))
935 return typ
936
e2bbc4ea 937 def _module_name(self, fname: str) -> str:
98967c24
JS
938 if QAPISchemaModule.is_system_module(fname):
939 return fname
a9f1dd7e
MA
940 return os.path.relpath(fname, self._schema_dir)
941
942 def _make_module(self, fname):
943 name = self._module_name(fname)
8ec0e1a4 944 if name not in self._module_dict:
a9f1dd7e
MA
945 self._module_dict[name] = QAPISchemaModule(name)
946 return self._module_dict[name]
947
948 def module_by_fname(self, fname):
949 name = self._module_name(fname)
a9f1dd7e
MA
950 return self._module_dict[name]
951
e6c42b96
MA
952 def _def_include(self, expr, info, doc):
953 include = expr['include']
954 assert doc is None
a9f1dd7e 955 self._def_entity(QAPISchemaInclude(self._make_module(include), info))
e6c42b96
MA
956
957 def _def_builtin_type(self, name, json_type, c_type):
958 self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
959 # Instantiating only the arrays that are actually used would
960 # be nice, but we can't as long as their generated code
961 # (qapi-builtin-types.[ch]) may be shared by some other
962 # schema.
963 self._make_array_type(name, None)
964
965 def _def_predefineds(self):
a7aa64a6 966 for t in [('str', 'string', 'char' + POINTER_SUFFIX),
e6c42b96
MA
967 ('number', 'number', 'double'),
968 ('int', 'int', 'int64_t'),
969 ('int8', 'int', 'int8_t'),
970 ('int16', 'int', 'int16_t'),
971 ('int32', 'int', 'int32_t'),
972 ('int64', 'int', 'int64_t'),
973 ('uint8', 'int', 'uint8_t'),
974 ('uint16', 'int', 'uint16_t'),
975 ('uint32', 'int', 'uint32_t'),
976 ('uint64', 'int', 'uint64_t'),
977 ('size', 'int', 'uint64_t'),
978 ('bool', 'boolean', 'bool'),
a7aa64a6
JS
979 ('any', 'value', 'QObject' + POINTER_SUFFIX),
980 ('null', 'null', 'QNull' + POINTER_SUFFIX)]:
e6c42b96
MA
981 self._def_builtin_type(*t)
982 self.the_empty_object_type = QAPISchemaObjectType(
013b4efc 983 'q_empty', None, None, None, None, None, [], None)
e6c42b96
MA
984 self._def_entity(self.the_empty_object_type)
985
986 qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
987 'qbool']
988 qtype_values = self._make_enum_members(
989 [{'name': n} for n in qtypes], None)
990
013b4efc 991 self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
e6c42b96
MA
992 qtype_values, 'QTYPE'))
993
ed30f58d
MA
994 def _make_features(self, features, info):
995 if features is None:
996 return []
f17539c8
MAL
997 return [QAPISchemaFeature(f['name'], info,
998 QAPISchemaIfCond(f.get('if')))
e6c42b96
MA
999 for f in features]
1000
b6c18755
MA
1001 def _make_enum_member(self, name, ifcond, features, info):
1002 return QAPISchemaEnumMember(name, info,
1003 QAPISchemaIfCond(ifcond),
1004 self._make_features(features, info))
1005
e6c42b96 1006 def _make_enum_members(self, values, info):
b6c18755
MA
1007 return [self._make_enum_member(v['name'], v.get('if'),
1008 v.get('features'), info)
e6c42b96
MA
1009 for v in values]
1010
e6c42b96
MA
1011 def _make_array_type(self, element_type, info):
1012 name = element_type + 'List' # reserved by check_defn_name_str()
1013 if not self.lookup_type(name):
1014 self._def_entity(QAPISchemaArrayType(name, info, element_type))
1015 return name
1016
a710e1c8 1017 def _make_implicit_object_type(self, name, info, ifcond, role, members):
e6c42b96
MA
1018 if not members:
1019 return None
1020 # See also QAPISchemaObjectTypeMember.describe()
1021 name = 'q_obj_%s-%s' % (name, role)
1022 typ = self.lookup_entity(name, QAPISchemaObjectType)
1023 if typ:
4e99f4b1
MA
1024 # The implicit object type has multiple users. This can
1025 # only be a duplicate definition, which will be flagged
1026 # later.
b32abbb2 1027 pass
e6c42b96 1028 else:
013b4efc
MA
1029 self._def_entity(QAPISchemaObjectType(
1030 name, info, None, ifcond, None, None, members, None))
e6c42b96
MA
1031 return name
1032
1033 def _def_enum_type(self, expr, info, doc):
1034 name = expr['enum']
1035 data = expr['data']
1036 prefix = expr.get('prefix')
f17539c8 1037 ifcond = QAPISchemaIfCond(expr.get('if'))
ed30f58d 1038 features = self._make_features(expr.get('features'), info)
e6c42b96 1039 self._def_entity(QAPISchemaEnumType(
013b4efc 1040 name, info, doc, ifcond, features,
e6c42b96
MA
1041 self._make_enum_members(data, info), prefix))
1042
84ab0086 1043 def _make_member(self, name, typ, ifcond, features, info):
e6c42b96
MA
1044 optional = False
1045 if name.startswith('*'):
1046 name = name[1:]
1047 optional = True
1048 if isinstance(typ, list):
1049 assert len(typ) == 1
1050 typ = self._make_array_type(typ[0], info)
84ab0086
MA
1051 return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
1052 self._make_features(features, info))
e6c42b96
MA
1053
1054 def _make_members(self, data, info):
f17539c8
MAL
1055 return [self._make_member(key, value['type'],
1056 QAPISchemaIfCond(value.get('if')),
84ab0086 1057 value.get('features'), info)
e6c42b96
MA
1058 for (key, value) in data.items()]
1059
1060 def _def_struct_type(self, expr, info, doc):
1061 name = expr['struct']
1062 base = expr.get('base')
1063 data = expr['data']
f17539c8 1064 ifcond = QAPISchemaIfCond(expr.get('if'))
ed30f58d 1065 features = self._make_features(expr.get('features'), info)
e6c42b96 1066 self._def_entity(QAPISchemaObjectType(
013b4efc 1067 name, info, doc, ifcond, features, base,
e6c42b96 1068 self._make_members(data, info),
013b4efc 1069 None))
e6c42b96
MA
1070
1071 def _make_variant(self, case, typ, ifcond, info):
5858fd1a 1072 return QAPISchemaVariant(case, info, typ, ifcond)
e6c42b96 1073
e6c42b96
MA
1074 def _def_union_type(self, expr, info, doc):
1075 name = expr['union']
4e99f4b1
MA
1076 base = expr['base']
1077 tag_name = expr['discriminator']
e6c42b96 1078 data = expr['data']
f17539c8 1079 ifcond = QAPISchemaIfCond(expr.get('if'))
ed30f58d 1080 features = self._make_features(expr.get('features'), info)
e6c42b96
MA
1081 if isinstance(base, dict):
1082 base = self._make_implicit_object_type(
a710e1c8 1083 name, info, ifcond,
e6c42b96 1084 'base', self._make_members(base, info))
4e99f4b1
MA
1085 variants = [
1086 self._make_variant(key, value['type'],
1087 QAPISchemaIfCond(value.get('if')),
1088 info)
1089 for (key, value) in data.items()]
1090 members = []
e6c42b96 1091 self._def_entity(
013b4efc
MA
1092 QAPISchemaObjectType(name, info, doc, ifcond, features,
1093 base, members,
5858fd1a 1094 QAPISchemaVariants(
4e99f4b1 1095 tag_name, info, None, variants)))
e6c42b96
MA
1096
1097 def _def_alternate_type(self, expr, info, doc):
1098 name = expr['alternate']
1099 data = expr['data']
f17539c8 1100 ifcond = QAPISchemaIfCond(expr.get('if'))
ed30f58d 1101 features = self._make_features(expr.get('features'), info)
f17539c8
MAL
1102 variants = [
1103 self._make_variant(key, value['type'],
1104 QAPISchemaIfCond(value.get('if')),
1105 info)
1106 for (key, value) in data.items()]
e6c42b96
MA
1107 tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
1108 self._def_entity(
013b4efc 1109 QAPISchemaAlternateType(name, info, doc, ifcond, features,
5858fd1a 1110 QAPISchemaVariants(
e6c42b96
MA
1111 None, info, tag_member, variants)))
1112
1113 def _def_command(self, expr, info, doc):
1114 name = expr['command']
1115 data = expr.get('data')
1116 rets = expr.get('returns')
1117 gen = expr.get('gen', True)
1118 success_response = expr.get('success-response', True)
1119 boxed = expr.get('boxed', False)
1120 allow_oob = expr.get('allow-oob', False)
1121 allow_preconfig = expr.get('allow-preconfig', False)
04f22362 1122 coroutine = expr.get('coroutine', False)
f17539c8 1123 ifcond = QAPISchemaIfCond(expr.get('if'))
ed30f58d 1124 features = self._make_features(expr.get('features'), info)
e6c42b96
MA
1125 if isinstance(data, OrderedDict):
1126 data = self._make_implicit_object_type(
013b4efc
MA
1127 name, info, ifcond,
1128 'arg', self._make_members(data, info))
e6c42b96
MA
1129 if isinstance(rets, list):
1130 assert len(rets) == 1
1131 rets = self._make_array_type(rets[0], info)
013b4efc
MA
1132 self._def_entity(QAPISchemaCommand(name, info, doc, ifcond, features,
1133 data, rets,
e6c42b96 1134 gen, success_response,
04f22362
KW
1135 boxed, allow_oob, allow_preconfig,
1136 coroutine))
e6c42b96
MA
1137
1138 def _def_event(self, expr, info, doc):
1139 name = expr['event']
1140 data = expr.get('data')
1141 boxed = expr.get('boxed', False)
f17539c8 1142 ifcond = QAPISchemaIfCond(expr.get('if'))
ed30f58d 1143 features = self._make_features(expr.get('features'), info)
e6c42b96
MA
1144 if isinstance(data, OrderedDict):
1145 data = self._make_implicit_object_type(
013b4efc
MA
1146 name, info, ifcond,
1147 'arg', self._make_members(data, info))
1148 self._def_entity(QAPISchemaEvent(name, info, doc, ifcond, features,
1149 data, boxed))
e6c42b96
MA
1150
1151 def _def_exprs(self, exprs):
1152 for expr_elem in exprs:
1153 expr = expr_elem['expr']
1154 info = expr_elem['info']
1155 doc = expr_elem.get('doc')
1156 if 'enum' in expr:
1157 self._def_enum_type(expr, info, doc)
1158 elif 'struct' in expr:
1159 self._def_struct_type(expr, info, doc)
1160 elif 'union' in expr:
1161 self._def_union_type(expr, info, doc)
1162 elif 'alternate' in expr:
1163 self._def_alternate_type(expr, info, doc)
1164 elif 'command' in expr:
1165 self._def_command(expr, info, doc)
1166 elif 'event' in expr:
1167 self._def_event(expr, info, doc)
1168 elif 'include' in expr:
1169 self._def_include(expr, info, doc)
1170 else:
1171 assert False
1172
1173 def check(self):
1174 for ent in self._entity_list:
1175 ent.check(self)
ee1e6a1f
MA
1176 ent.connect_doc()
1177 ent.check_doc()
a9f1dd7e
MA
1178 for ent in self._entity_list:
1179 ent.set_module(self)
e6c42b96
MA
1180
1181 def visit(self, visitor):
1182 visitor.visit_begin(self)
3e7fb581
MA
1183 for mod in self._module_dict.values():
1184 mod.visit(visitor)
e6c42b96 1185 visitor.visit_end()