2019-10-18 10:43:44 +03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
|
|
|
# QAPI schema internal representation
|
|
|
|
#
|
|
|
|
# Copyright (c) 2015-2019 Red Hat Inc.
|
|
|
|
#
|
|
|
|
# Authors:
|
|
|
|
# Markus Armbruster <armbru@redhat.com>
|
|
|
|
# Eric Blake <eblake@redhat.com>
|
|
|
|
# Marc-André Lureau <marcandre.lureau@redhat.com>
|
|
|
|
#
|
|
|
|
# This work is licensed under the terms of the GNU GPL, version 2.
|
|
|
|
# See the COPYING file in the top-level directory.
|
|
|
|
|
|
|
|
# TODO catching name collisions in generated code would be nice
|
|
|
|
|
2020-10-09 19:15:29 +03:00
|
|
|
from collections import OrderedDict
|
2019-10-18 10:43:44 +03:00
|
|
|
import os
|
|
|
|
import re
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
from typing import List, Optional
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2021-08-04 11:31:00 +03:00
|
|
|
from .common import (
|
|
|
|
POINTER_SUFFIX,
|
|
|
|
c_name,
|
|
|
|
cgen_ifcond,
|
|
|
|
docgen_ifcond,
|
2021-08-31 15:37:58 +03:00
|
|
|
gen_endif,
|
|
|
|
gen_if,
|
2021-08-04 11:31:00 +03:00
|
|
|
)
|
qapi/parser: Don't try to handle file errors
Fixes: f5d4361cda
Fixes: 52a474180a
Fixes: 46f49468c6
Remove the try/except block that handles file-opening errors in
QAPISchemaParser.__init__() and add one each to
QAPISchemaParser._include() and QAPISchema.__init__() respectively.
This simultaneously fixes the typing of info.fname (f5d4361cda), A
static typing violation in test-qapi (46f49468c6), and a regression of
an error message (52a474180a).
The short-ish version of what motivates this patch is:
- It's hard to write a good error message in the init method,
because we need to determine the context of our caller to do so.
It's easier to just let the caller write the message.
- We don't want to allow QAPISourceInfo(None, None, None) to exist. The
typing introduced by commit f5d4361cda types the 'fname' field as
(non-optional) str, which was premature until the removal of this
construct.
- Errors made using such an object are currently incorrect (since
52a474180a)
- It's not technically a semantic error if we cannot open the schema.
- There are various typing constraints that make mixing these two cases
undesirable for a single special case.
- test-qapi's code handling an fname of 'None' is now dead, drop it.
Additionally, Not all QAPIError objects have an 'info' field (since
46f49468), so deleting this stanza corrects a typing oversight in
test-qapi introduced by that commit.
Other considerations:
- open() is moved to a 'with' block to ensure file pointers are
cleaned up deterministically.
- Python 3.3 deprecated IOError and made it a synonym for OSError.
Avoid the misleading perception these exception handlers are
narrower than they really are.
The long version:
The error message here is incorrect (since commit 52a474180a):
> python3 qapi-gen.py 'fake.json'
qapi-gen.py: qapi-gen.py: can't read schema file 'fake.json': No such file or directory
In pursuing it, we find that QAPISourceInfo has a special accommodation
for when there's no filename. Meanwhile, the intent when QAPISourceInfo
was typed (f5d4361cda) was non-optional 'str'. This usage was
overlooked.
To remove this, I'd want to avoid having a "fake" QAPISourceInfo
object. I also don't want to explicitly begin accommodating
QAPISourceInfo itself being None, because we actually want to eventually
prove that this can never happen -- We don't want to confuse "The file
isn't open yet" with "This error stems from a definition that wasn't
defined in any file".
(An earlier series tried to create a dummy info object, but it was tough
to prove in review that it worked correctly without creating new
regressions. This patch avoids that distraction. We would like to first
prove that we never raise QAPISemError for any built-in object before we
add "special" info objects. We aren't ready to do that yet.)
So, which way out of the labyrinth?
Here's one way: Don't try to handle errors at a level with "mixed"
semantic contexts; i.e. don't mix inclusion errors (should report a
source line where the include was triggered) and command line errors
(where we specified a file we couldn't read).
Remove the error handling from the initializer of the parser. Pythonic!
Now it's the caller's job to figure out what to do about it. Handle the
error in QAPISchemaParser._include() instead, where we can write a
targeted error message where we are guaranteed to have an 'info' context
to report with.
The root level error can similarly move to QAPISchema.__init__(), where
we know we'll never have an info context to report with, so we use a
more abstract error type.
Now the error looks sensible again:
> python3 qapi-gen.py 'fake.json'
qapi-gen.py: can't read schema file 'fake.json': No such file or directory
With these error cases separated, QAPISourceInfo can be solidified as
never having placeholder arguments that violate our desired types. Clean
up test-qapi along similar lines.
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20210519183951.3946870-2-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2021-05-19 21:39:37 +03:00
|
|
|
from .error import QAPIError, QAPISemError, QAPISourceError
|
qapi: Prefer explicit relative imports
All of the QAPI include statements are changed to be package-aware, as
explicit relative imports.
A quirk of Python packages is that the name of the package exists only
*outside* of the package. This means that to a module inside of the qapi
folder, there is inherently no such thing as the "qapi" package. The
reason these imports work is because the "qapi" package exists in the
context of the caller -- the execution shim, where sys.path includes a
directory that has a 'qapi' folder in it.
When we write "from qapi import sibling", we are NOT referencing the folder
'qapi', but rather "any package named qapi in sys.path". If you should
so happen to have a 'qapi' package in your path, it will use *that*
package.
When we write "from .sibling import foo", we always reference explicitly
our sibling module; guaranteeing consistency in *where* we are importing
these modules from.
This can be useful when working with virtual environments and packages
in development mode. In development mode, a package is installed as a
series of symlinks that forwards to your same source files. The problem
arises because code quality checkers will follow "import qapi.x" to the
"installed" version instead of the sibling file and -- even though they
are the same file -- they have different module paths, and this causes
cyclic import problems, false positive type mismatch errors, and more.
It can also be useful when dealing with hierarchical packages, e.g. if
we allow qemu.core.qmp, qemu.qapi.parser, etc.
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Eduardo Habkost <ehabkost@redhat.com>
Reviewed-by: Cleber Rosa <crosa@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Message-Id: <20201009161558.107041-6-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2020-10-09 19:15:27 +03:00
|
|
|
from .expr import check_exprs
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
from .parser import QAPIExpression, QAPISchemaParser
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
2021-08-04 11:30:57 +03:00
|
|
|
class QAPISchemaIfCond:
|
|
|
|
def __init__(self, ifcond=None):
|
2021-08-31 15:37:59 +03:00
|
|
|
self.ifcond = ifcond
|
2021-08-04 11:30:57 +03:00
|
|
|
|
2021-08-31 15:37:58 +03:00
|
|
|
def _cgen(self):
|
2021-08-04 11:30:59 +03:00
|
|
|
return cgen_ifcond(self.ifcond)
|
|
|
|
|
2021-08-31 15:37:58 +03:00
|
|
|
def gen_if(self):
|
|
|
|
return gen_if(self._cgen())
|
|
|
|
|
|
|
|
def gen_endif(self):
|
|
|
|
return gen_endif(self._cgen())
|
|
|
|
|
2021-08-04 11:31:00 +03:00
|
|
|
def docgen(self):
|
|
|
|
return docgen_ifcond(self.ifcond)
|
|
|
|
|
2021-08-04 11:30:58 +03:00
|
|
|
def is_present(self):
|
|
|
|
return bool(self.ifcond)
|
|
|
|
|
2021-08-04 11:30:57 +03:00
|
|
|
|
2020-03-04 18:59:29 +03:00
|
|
|
class QAPISchemaEntity:
|
2020-10-09 19:15:40 +03:00
|
|
|
meta: Optional[str] = None
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2021-02-16 05:18:04 +03:00
|
|
|
def __init__(self, name: str, info, doc, ifcond=None, features=None):
|
2019-10-18 10:43:44 +03:00
|
|
|
assert name is None or isinstance(name, str)
|
2019-10-24 14:02:35 +03:00
|
|
|
for f in features or []:
|
|
|
|
assert isinstance(f, QAPISchemaFeature)
|
|
|
|
f.set_defined_in(name)
|
2019-10-18 10:43:44 +03:00
|
|
|
self.name = name
|
|
|
|
self._module = None
|
|
|
|
# For explicitly defined entities, info points to the (explicit)
|
|
|
|
# definition. For builtins (and their arrays), info is None.
|
|
|
|
# For implicitly defined entities, info points to a place that
|
|
|
|
# triggered the implicit definition (there may be more than one
|
|
|
|
# such place).
|
|
|
|
self.info = info
|
|
|
|
self.doc = doc
|
2021-08-04 11:30:57 +03:00
|
|
|
self._ifcond = ifcond or QAPISchemaIfCond()
|
2019-10-24 14:02:35 +03:00
|
|
|
self.features = features or []
|
2019-10-18 10:43:44 +03:00
|
|
|
self._checked = False
|
|
|
|
|
2023-10-18 15:05:00 +03:00
|
|
|
def __repr__(self):
|
|
|
|
if self.name is None:
|
|
|
|
return "<%s at 0x%x>" % (type(self).__name__, id(self))
|
|
|
|
return "<%s:%s at 0x%x>" % type(self).__name__, self.name, id(self)
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def c_name(self):
|
|
|
|
return c_name(self.name)
|
|
|
|
|
|
|
|
def check(self, schema):
|
|
|
|
assert not self._checked
|
2019-10-24 14:02:35 +03:00
|
|
|
seen = {}
|
|
|
|
for f in self.features:
|
|
|
|
f.check_clash(self.info, seen)
|
2019-10-18 10:43:44 +03:00
|
|
|
self._checked = True
|
|
|
|
|
qapi: Clean up doc comment checking for implicit union base
An object type's doc comment describes the type's members, less the
ones defined in a named base type. Cases:
* Struct: the members are defined in 'data' and inherited from 'base'.
Since the base type cannot be implicit, the doc comment describes
just 'data'.
* Simple union: the only member is the implicit tag member @type, and
the doc comment describes it.
* Flat union with implicit base type: the members are defined in
'base', and the doc comment describes it.
* Flat union with named base type: the members are inherited from
'base'. The doc comment describes no members.
Before we can check a doc comment with .check_doc(), we need
.connect_doc() connect each of its "argument sections" to the member
it documents.
For structs and simple unions, this is straightforward: the members in
question are in .local_members, and .connect_doc() connects them.
For flat unions with a named base type, it's trivial: .local_members
is empty, and .connect_doc() does nothing.
For flat unions with an implicit base type, it's tricky. We have
QAPISchema._make_implicit_object_type() forward the union's doc
comment to the implicit base type, so that the base type's
.connect_doc() connects the members. The union's .connect_doc() does
nothing, as .local_members is empty.
Dirt effect: we check the doc comment twice, once for the union type,
and once for the implicit base type.
This is needlessly brittle and hard to understand. Clean up as
follows. Make the union's .connect_doc() connect an implicit base's
members itself. Do not forward the union's doc comment to its
implicit base type.
Requires extending .connect_doc() so it can work with a doc comment
other than self.doc. Add an optional argument for that.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-11-armbru@redhat.com>
2019-10-24 14:02:28 +03:00
|
|
|
def connect_doc(self, doc=None):
|
2020-03-17 14:54:36 +03:00
|
|
|
doc = doc or self.doc
|
|
|
|
if doc:
|
|
|
|
for f in self.features:
|
|
|
|
doc.connect_feature(f)
|
2019-10-24 14:02:26 +03:00
|
|
|
|
|
|
|
def check_doc(self):
|
2019-10-24 14:02:31 +03:00
|
|
|
if self.doc:
|
|
|
|
self.doc.check()
|
2019-10-24 14:02:26 +03:00
|
|
|
|
2019-11-20 21:25:49 +03:00
|
|
|
def _set_module(self, schema, info):
|
|
|
|
assert self._checked
|
2021-02-01 22:37:41 +03:00
|
|
|
fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
|
2021-02-01 22:37:39 +03:00
|
|
|
self._module = schema.module_by_fname(fname)
|
2019-11-20 21:25:50 +03:00
|
|
|
self._module.add_entity(self)
|
2019-11-20 21:25:49 +03:00
|
|
|
|
|
|
|
def set_module(self, schema):
|
|
|
|
self._set_module(schema, self.info)
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
@property
|
|
|
|
def ifcond(self):
|
|
|
|
assert self._checked
|
|
|
|
return self._ifcond
|
|
|
|
|
|
|
|
def is_implicit(self):
|
|
|
|
return not self.info
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
|
|
|
assert self._checked
|
|
|
|
|
|
|
|
def describe(self):
|
|
|
|
assert self.meta
|
|
|
|
return "%s '%s'" % (self.meta, self.name)
|
|
|
|
|
|
|
|
|
2020-03-04 18:59:29 +03:00
|
|
|
class QAPISchemaVisitor:
|
2019-10-18 10:43:44 +03:00
|
|
|
def visit_begin(self, schema):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_end(self):
|
|
|
|
pass
|
|
|
|
|
2020-03-04 18:59:32 +03:00
|
|
|
def visit_module(self, name):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_needed(self, entity):
|
|
|
|
# Default to visiting everything
|
|
|
|
return True
|
|
|
|
|
2020-03-04 18:59:32 +03:00
|
|
|
def visit_include(self, name, info):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_builtin_type(self, name, info, json_type):
|
|
|
|
pass
|
|
|
|
|
2020-03-17 14:54:37 +03:00
|
|
|
def visit_enum_type(self, name, info, ifcond, features, members, prefix):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
|
|
|
def visit_array_type(self, name, info, ifcond, element_type):
|
|
|
|
pass
|
|
|
|
|
2020-03-17 14:54:38 +03:00
|
|
|
def visit_object_type(self, name, info, ifcond, features,
|
|
|
|
base, members, variants):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
2020-03-17 14:54:38 +03:00
|
|
|
def visit_object_type_flat(self, name, info, ifcond, features,
|
|
|
|
members, variants):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
2020-03-17 14:54:37 +03:00
|
|
|
def visit_alternate_type(self, name, info, ifcond, features, variants):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
2020-03-17 14:54:38 +03:00
|
|
|
def visit_command(self, name, info, ifcond, features,
|
|
|
|
arg_type, ret_type, gen, success_response, boxed,
|
2020-10-05 18:58:49 +03:00
|
|
|
allow_oob, allow_preconfig, coroutine):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
2020-03-17 14:54:37 +03:00
|
|
|
def visit_event(self, name, info, ifcond, features, arg_type, boxed):
|
2019-10-18 10:43:44 +03:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2020-03-04 18:59:29 +03:00
|
|
|
class QAPISchemaModule:
|
2021-02-01 22:37:41 +03:00
|
|
|
|
|
|
|
BUILTIN_MODULE_NAME = './builtin'
|
|
|
|
|
2019-11-20 21:25:49 +03:00
|
|
|
def __init__(self, name):
|
|
|
|
self.name = name
|
2019-11-20 21:25:50 +03:00
|
|
|
self._entity_list = []
|
|
|
|
|
2021-02-01 22:37:36 +03:00
|
|
|
@staticmethod
|
2021-02-01 22:37:39 +03:00
|
|
|
def is_system_module(name: str) -> bool:
|
2021-02-01 22:37:36 +03:00
|
|
|
"""
|
|
|
|
System modules are internally defined modules.
|
|
|
|
|
|
|
|
Their names start with the "./" prefix.
|
|
|
|
"""
|
2021-02-01 22:37:39 +03:00
|
|
|
return name.startswith('./')
|
2021-02-01 22:37:36 +03:00
|
|
|
|
|
|
|
@classmethod
|
2021-02-01 22:37:39 +03:00
|
|
|
def is_user_module(cls, name: str) -> bool:
|
2021-02-01 22:37:36 +03:00
|
|
|
"""
|
|
|
|
User modules are those defined by the user in qapi JSON files.
|
|
|
|
|
|
|
|
They do not start with the "./" prefix.
|
|
|
|
"""
|
|
|
|
return not cls.is_system_module(name)
|
|
|
|
|
2021-02-01 22:37:41 +03:00
|
|
|
@classmethod
|
|
|
|
def is_builtin_module(cls, name: str) -> bool:
|
2021-02-01 22:37:36 +03:00
|
|
|
"""
|
|
|
|
The built-in module is a single System module for the built-in types.
|
|
|
|
|
2021-02-01 22:37:39 +03:00
|
|
|
It is always "./builtin".
|
2021-02-01 22:37:36 +03:00
|
|
|
"""
|
2021-02-01 22:37:41 +03:00
|
|
|
return name == cls.BUILTIN_MODULE_NAME
|
2021-02-01 22:37:36 +03:00
|
|
|
|
2019-11-20 21:25:50 +03:00
|
|
|
def add_entity(self, ent):
|
|
|
|
self._entity_list.append(ent)
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
|
|
|
visitor.visit_module(self.name)
|
|
|
|
for entity in self._entity_list:
|
|
|
|
if visitor.visit_needed(entity):
|
|
|
|
entity.visit(visitor)
|
2019-11-20 21:25:49 +03:00
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2019-11-20 21:25:49 +03:00
|
|
|
class QAPISchemaInclude(QAPISchemaEntity):
|
|
|
|
def __init__(self, sub_module, info):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().__init__(None, info, None)
|
2019-11-20 21:25:49 +03:00
|
|
|
self._sub_module = sub_module
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def visit(self, visitor):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().visit(visitor)
|
2019-11-20 21:25:49 +03:00
|
|
|
visitor.visit_include(self._sub_module.name, self.info)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaType(QAPISchemaEntity):
|
|
|
|
# Return the C type for common use.
|
|
|
|
# For the types we commonly box, this is a pointer type.
|
|
|
|
def c_type(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Return the C type to be used in a parameter list.
|
|
|
|
def c_param_type(self):
|
|
|
|
return self.c_type()
|
|
|
|
|
|
|
|
# Return the C type to be used where we suppress boxing.
|
|
|
|
def c_unboxed_type(self):
|
|
|
|
return self.c_type()
|
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def alternate_qtype(self):
|
|
|
|
json2qtype = {
|
|
|
|
'null': 'QTYPE_QNULL',
|
|
|
|
'string': 'QTYPE_QSTRING',
|
|
|
|
'number': 'QTYPE_QNUM',
|
|
|
|
'int': 'QTYPE_QNUM',
|
|
|
|
'boolean': 'QTYPE_QBOOL',
|
2022-03-21 19:42:41 +03:00
|
|
|
'array': 'QTYPE_QLIST',
|
2019-10-18 10:43:44 +03:00
|
|
|
'object': 'QTYPE_QDICT'
|
|
|
|
}
|
|
|
|
return json2qtype.get(self.json_type())
|
|
|
|
|
|
|
|
def doc_type(self):
|
|
|
|
if self.is_implicit():
|
|
|
|
return None
|
|
|
|
return self.name
|
|
|
|
|
qapi: Start to elide redundant has_FOO in generated C
In QAPI, absent optional members are distinct from any present value.
We thus represent an optional schema member FOO as two C members: a
FOO with the member's type, and a bool has_FOO. Likewise for function
arguments.
However, has_FOO is actually redundant for a pointer-valued FOO, which
can be null only when has_FOO is false, i.e. has_FOO == !!FOO. Except
for arrays, where we a null FOO can also be a present empty array.
The redundant has_FOO are a nuisance to work with. Improve the
generator to elide them. Uses of has_FOO need to be replaced as
follows.
Tests of has_FOO become the equivalent comparison of FOO with null.
For brevity, this is commonly done by implicit conversion to bool.
Assignments to has_FOO get dropped.
Likewise for arguments to has_FOO parameters.
Beware: code may violate the invariant has_FOO == !!FOO before the
transformation, and get away with it. The above transformation can
then break things. Two cases:
* Absent: if code ignores FOO entirely when !has_FOO (except for
freeing it if necessary), even non-null / uninitialized FOO works.
Such code is known to exist.
* Present: if code ignores FOO entirely when has_FOO, even null FOO
works. Such code should not exist.
In both cases, replacing tests of has_FOO by FOO reverts their sense.
We have to fix the value of FOO then.
To facilitate review of the necessary updates to handwritten code, add
means to opt out of this change, and opt out for all QAPI schema
modules where the change requires updates to handwritten code. The
next few commits will remove these opt-outs in reviewable chunks, then
drop the means to opt out.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Message-Id: <20221104160712.3005652-5-armbru@redhat.com>
2022-11-04 19:06:46 +03:00
|
|
|
def need_has_if_optional(self):
|
|
|
|
# When FOO is a pointer, has_FOO == !!FOO, i.e. has_FOO is redundant.
|
|
|
|
# Except for arrays; see QAPISchemaArrayType.need_has_if_optional().
|
|
|
|
return not self.c_type().endswith(POINTER_SUFFIX)
|
|
|
|
|
2020-03-17 14:54:50 +03:00
|
|
|
def check(self, schema):
|
2023-03-16 10:13:13 +03:00
|
|
|
super().check(schema)
|
2021-10-28 13:25:20 +03:00
|
|
|
for feat in self.features:
|
|
|
|
if feat.is_special():
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
f"feature '{feat.name}' is not supported for types")
|
2020-03-17 14:54:50 +03:00
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def describe(self):
|
|
|
|
assert self.meta
|
|
|
|
return "%s type '%s'" % (self.meta, self.name)
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaBuiltinType(QAPISchemaType):
|
|
|
|
meta = 'built-in'
|
|
|
|
|
|
|
|
def __init__(self, name, json_type, c_type):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().__init__(name, None, None)
|
2019-10-18 10:43:44 +03:00
|
|
|
assert not c_type or isinstance(c_type, str)
|
|
|
|
assert json_type in ('string', 'number', 'int', 'boolean', 'null',
|
|
|
|
'value')
|
|
|
|
self._json_type_name = json_type
|
|
|
|
self._c_type_name = c_type
|
|
|
|
|
|
|
|
def c_name(self):
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
def c_type(self):
|
|
|
|
return self._c_type_name
|
|
|
|
|
|
|
|
def c_param_type(self):
|
|
|
|
if self.name == 'str':
|
|
|
|
return 'const ' + self._c_type_name
|
|
|
|
return self._c_type_name
|
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
return self._json_type_name
|
|
|
|
|
|
|
|
def doc_type(self):
|
|
|
|
return self.json_type()
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().visit(visitor)
|
2019-10-18 10:43:44 +03:00
|
|
|
visitor.visit_builtin_type(self.name, self.info, self.json_type())
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaEnumType(QAPISchemaType):
|
|
|
|
meta = 'enum'
|
|
|
|
|
2020-03-17 14:54:37 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, features, members, prefix):
|
|
|
|
super().__init__(name, info, doc, ifcond, features)
|
2019-10-18 10:43:44 +03:00
|
|
|
for m in members:
|
|
|
|
assert isinstance(m, QAPISchemaEnumMember)
|
|
|
|
m.set_defined_in(name)
|
|
|
|
assert prefix is None or isinstance(prefix, str)
|
|
|
|
self.members = members
|
|
|
|
self.prefix = prefix
|
|
|
|
|
|
|
|
def check(self, schema):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().check(schema)
|
2019-10-18 10:43:44 +03:00
|
|
|
seen = {}
|
|
|
|
for m in self.members:
|
|
|
|
m.check_clash(self.info, seen)
|
2019-10-24 14:02:26 +03:00
|
|
|
|
qapi: Clean up doc comment checking for implicit union base
An object type's doc comment describes the type's members, less the
ones defined in a named base type. Cases:
* Struct: the members are defined in 'data' and inherited from 'base'.
Since the base type cannot be implicit, the doc comment describes
just 'data'.
* Simple union: the only member is the implicit tag member @type, and
the doc comment describes it.
* Flat union with implicit base type: the members are defined in
'base', and the doc comment describes it.
* Flat union with named base type: the members are inherited from
'base'. The doc comment describes no members.
Before we can check a doc comment with .check_doc(), we need
.connect_doc() connect each of its "argument sections" to the member
it documents.
For structs and simple unions, this is straightforward: the members in
question are in .local_members, and .connect_doc() connects them.
For flat unions with a named base type, it's trivial: .local_members
is empty, and .connect_doc() does nothing.
For flat unions with an implicit base type, it's tricky. We have
QAPISchema._make_implicit_object_type() forward the union's doc
comment to the implicit base type, so that the base type's
.connect_doc() connects the members. The union's .connect_doc() does
nothing, as .local_members is empty.
Dirt effect: we check the doc comment twice, once for the union type,
and once for the implicit base type.
This is needlessly brittle and hard to understand. Clean up as
follows. Make the union's .connect_doc() connect an implicit base's
members itself. Do not forward the union's doc comment to its
implicit base type.
Requires extending .connect_doc() so it can work with a doc comment
other than self.doc. Add an optional argument for that.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-11-armbru@redhat.com>
2019-10-24 14:02:28 +03:00
|
|
|
def connect_doc(self, doc=None):
|
2020-03-17 14:54:36 +03:00
|
|
|
super().connect_doc(doc)
|
qapi: Clean up doc comment checking for implicit union base
An object type's doc comment describes the type's members, less the
ones defined in a named base type. Cases:
* Struct: the members are defined in 'data' and inherited from 'base'.
Since the base type cannot be implicit, the doc comment describes
just 'data'.
* Simple union: the only member is the implicit tag member @type, and
the doc comment describes it.
* Flat union with implicit base type: the members are defined in
'base', and the doc comment describes it.
* Flat union with named base type: the members are inherited from
'base'. The doc comment describes no members.
Before we can check a doc comment with .check_doc(), we need
.connect_doc() connect each of its "argument sections" to the member
it documents.
For structs and simple unions, this is straightforward: the members in
question are in .local_members, and .connect_doc() connects them.
For flat unions with a named base type, it's trivial: .local_members
is empty, and .connect_doc() does nothing.
For flat unions with an implicit base type, it's tricky. We have
QAPISchema._make_implicit_object_type() forward the union's doc
comment to the implicit base type, so that the base type's
.connect_doc() connects the members. The union's .connect_doc() does
nothing, as .local_members is empty.
Dirt effect: we check the doc comment twice, once for the union type,
and once for the implicit base type.
This is needlessly brittle and hard to understand. Clean up as
follows. Make the union's .connect_doc() connect an implicit base's
members itself. Do not forward the union's doc comment to its
implicit base type.
Requires extending .connect_doc() so it can work with a doc comment
other than self.doc. Add an optional argument for that.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-11-armbru@redhat.com>
2019-10-24 14:02:28 +03:00
|
|
|
doc = doc or self.doc
|
2020-03-17 14:54:44 +03:00
|
|
|
for m in self.members:
|
|
|
|
m.connect_doc(doc)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def is_implicit(self):
|
2021-09-17 17:31:32 +03:00
|
|
|
# See QAPISchema._def_predefineds()
|
|
|
|
return self.name == 'QType'
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def c_type(self):
|
|
|
|
return c_name(self.name)
|
|
|
|
|
|
|
|
def member_names(self):
|
|
|
|
return [m.name for m in self.members]
|
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
return 'string'
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().visit(visitor)
|
2020-03-17 14:54:37 +03:00
|
|
|
visitor.visit_enum_type(
|
|
|
|
self.name, self.info, self.ifcond, self.features,
|
|
|
|
self.members, self.prefix)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaArrayType(QAPISchemaType):
|
|
|
|
meta = 'array'
|
|
|
|
|
|
|
|
def __init__(self, name, info, element_type):
|
2020-03-17 14:54:37 +03:00
|
|
|
super().__init__(name, info, None)
|
2019-10-18 10:43:44 +03:00
|
|
|
assert isinstance(element_type, str)
|
|
|
|
self._element_type_name = element_type
|
|
|
|
self.element_type = None
|
|
|
|
|
qapi: Start to elide redundant has_FOO in generated C
In QAPI, absent optional members are distinct from any present value.
We thus represent an optional schema member FOO as two C members: a
FOO with the member's type, and a bool has_FOO. Likewise for function
arguments.
However, has_FOO is actually redundant for a pointer-valued FOO, which
can be null only when has_FOO is false, i.e. has_FOO == !!FOO. Except
for arrays, where we a null FOO can also be a present empty array.
The redundant has_FOO are a nuisance to work with. Improve the
generator to elide them. Uses of has_FOO need to be replaced as
follows.
Tests of has_FOO become the equivalent comparison of FOO with null.
For brevity, this is commonly done by implicit conversion to bool.
Assignments to has_FOO get dropped.
Likewise for arguments to has_FOO parameters.
Beware: code may violate the invariant has_FOO == !!FOO before the
transformation, and get away with it. The above transformation can
then break things. Two cases:
* Absent: if code ignores FOO entirely when !has_FOO (except for
freeing it if necessary), even non-null / uninitialized FOO works.
Such code is known to exist.
* Present: if code ignores FOO entirely when has_FOO, even null FOO
works. Such code should not exist.
In both cases, replacing tests of has_FOO by FOO reverts their sense.
We have to fix the value of FOO then.
To facilitate review of the necessary updates to handwritten code, add
means to opt out of this change, and opt out for all QAPI schema
modules where the change requires updates to handwritten code. The
next few commits will remove these opt-outs in reviewable chunks, then
drop the means to opt out.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Message-Id: <20221104160712.3005652-5-armbru@redhat.com>
2022-11-04 19:06:46 +03:00
|
|
|
def need_has_if_optional(self):
|
|
|
|
# When FOO is an array, we still need has_FOO to distinguish
|
|
|
|
# absent (!has_FOO) from present and empty (has_FOO && !FOO).
|
|
|
|
return True
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def check(self, schema):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().check(schema)
|
2019-10-18 10:43:44 +03:00
|
|
|
self.element_type = schema.resolve_type(
|
|
|
|
self._element_type_name, self.info,
|
|
|
|
self.info and self.info.defn_meta)
|
|
|
|
assert not isinstance(self.element_type, QAPISchemaArrayType)
|
|
|
|
|
2019-11-20 21:25:49 +03:00
|
|
|
def set_module(self, schema):
|
|
|
|
self._set_module(schema, self.element_type.info)
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
@property
|
|
|
|
def ifcond(self):
|
|
|
|
assert self._checked
|
|
|
|
return self.element_type.ifcond
|
|
|
|
|
|
|
|
def is_implicit(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def c_type(self):
|
2020-10-09 19:15:34 +03:00
|
|
|
return c_name(self.name) + POINTER_SUFFIX
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
return 'array'
|
|
|
|
|
|
|
|
def doc_type(self):
|
|
|
|
elt_doc_type = self.element_type.doc_type()
|
|
|
|
if not elt_doc_type:
|
|
|
|
return None
|
|
|
|
return 'array of ' + elt_doc_type
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().visit(visitor)
|
2019-10-18 10:43:44 +03:00
|
|
|
visitor.visit_array_type(self.name, self.info, self.ifcond,
|
|
|
|
self.element_type)
|
|
|
|
|
|
|
|
def describe(self):
|
|
|
|
assert self.meta
|
|
|
|
return "%s type ['%s']" % (self.meta, self._element_type_name)
|
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaObjectType(QAPISchemaType):
|
2020-03-17 14:54:37 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, features,
|
|
|
|
base, local_members, variants):
|
2019-10-18 10:43:44 +03:00
|
|
|
# struct has local_members, optional base, and no variants
|
2021-09-17 17:31:32 +03:00
|
|
|
# union has base, variants, and no local_members
|
2020-03-04 18:59:31 +03:00
|
|
|
super().__init__(name, info, doc, ifcond, features)
|
2019-10-18 10:43:44 +03:00
|
|
|
self.meta = 'union' if variants else 'struct'
|
|
|
|
assert base is None or isinstance(base, str)
|
|
|
|
for m in local_members:
|
|
|
|
assert isinstance(m, QAPISchemaObjectTypeMember)
|
|
|
|
m.set_defined_in(name)
|
|
|
|
if variants is not None:
|
2020-03-17 14:54:43 +03:00
|
|
|
assert isinstance(variants, QAPISchemaVariants)
|
2019-10-18 10:43:44 +03:00
|
|
|
variants.set_defined_in(name)
|
|
|
|
self._base_name = base
|
|
|
|
self.base = None
|
|
|
|
self.local_members = local_members
|
|
|
|
self.variants = variants
|
|
|
|
self.members = None
|
|
|
|
|
|
|
|
def check(self, schema):
|
|
|
|
# This calls another type T's .check() exactly when the C
|
|
|
|
# struct emitted by gen_object() contains that T's C struct
|
|
|
|
# (pointers don't count).
|
|
|
|
if self.members is not None:
|
|
|
|
# A previous .check() completed: nothing to do
|
|
|
|
return
|
|
|
|
if self._checked:
|
|
|
|
# Recursed: C struct contains itself
|
|
|
|
raise QAPISemError(self.info,
|
|
|
|
"object %s contains itself" % self.name)
|
|
|
|
|
2020-03-04 18:59:31 +03:00
|
|
|
super().check(schema)
|
2019-10-18 10:43:44 +03:00
|
|
|
assert self._checked and self.members is None
|
|
|
|
|
|
|
|
seen = OrderedDict()
|
|
|
|
if self._base_name:
|
|
|
|
self.base = schema.resolve_type(self._base_name, self.info,
|
|
|
|
"'base'")
|
|
|
|
if (not isinstance(self.base, QAPISchemaObjectType)
|
|
|
|
or self.base.variants):
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"'base' requires a struct type, %s isn't"
|
|
|
|
% self.base.describe())
|
|
|
|
self.base.check(schema)
|
|
|
|
self.base.check_clash(self.info, seen)
|
|
|
|
for m in self.local_members:
|
|
|
|
m.check(schema)
|
|
|
|
m.check_clash(self.info, seen)
|
|
|
|
members = seen.values()
|
|
|
|
|
|
|
|
if self.variants:
|
|
|
|
self.variants.check(schema, seen)
|
|
|
|
self.variants.check_clash(self.info, seen)
|
|
|
|
|
|
|
|
self.members = members # mark completed
|
|
|
|
|
|
|
|
# Check that the members of this type do not cause duplicate JSON members,
|
|
|
|
# and update seen to track the members seen so far. Report any errors
|
|
|
|
# on behalf of info, which is not necessarily self.info
|
|
|
|
def check_clash(self, info, seen):
|
|
|
|
assert self._checked
|
|
|
|
for m in self.members:
|
|
|
|
m.check_clash(info, seen)
|
2023-04-20 13:26:19 +03:00
|
|
|
if self.variants:
|
|
|
|
self.variants.check_clash(info, seen)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
qapi: Clean up doc comment checking for implicit union base
An object type's doc comment describes the type's members, less the
ones defined in a named base type. Cases:
* Struct: the members are defined in 'data' and inherited from 'base'.
Since the base type cannot be implicit, the doc comment describes
just 'data'.
* Simple union: the only member is the implicit tag member @type, and
the doc comment describes it.
* Flat union with implicit base type: the members are defined in
'base', and the doc comment describes it.
* Flat union with named base type: the members are inherited from
'base'. The doc comment describes no members.
Before we can check a doc comment with .check_doc(), we need
.connect_doc() connect each of its "argument sections" to the member
it documents.
For structs and simple unions, this is straightforward: the members in
question are in .local_members, and .connect_doc() connects them.
For flat unions with a named base type, it's trivial: .local_members
is empty, and .connect_doc() does nothing.
For flat unions with an implicit base type, it's tricky. We have
QAPISchema._make_implicit_object_type() forward the union's doc
comment to the implicit base type, so that the base type's
.connect_doc() connects the members. The union's .connect_doc() does
nothing, as .local_members is empty.
Dirt effect: we check the doc comment twice, once for the union type,
and once for the implicit base type.
This is needlessly brittle and hard to understand. Clean up as
follows. Make the union's .connect_doc() connect an implicit base's
members itself. Do not forward the union's doc comment to its
implicit base type.
Requires extending .connect_doc() so it can work with a doc comment
other than self.doc. Add an optional argument for that.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-11-armbru@redhat.com>
2019-10-24 14:02:28 +03:00
|
|
|
def connect_doc(self, doc=None):
|
2020-03-17 14:54:36 +03:00
|
|
|
super().connect_doc(doc)
|
qapi: Clean up doc comment checking for implicit union base
An object type's doc comment describes the type's members, less the
ones defined in a named base type. Cases:
* Struct: the members are defined in 'data' and inherited from 'base'.
Since the base type cannot be implicit, the doc comment describes
just 'data'.
* Simple union: the only member is the implicit tag member @type, and
the doc comment describes it.
* Flat union with implicit base type: the members are defined in
'base', and the doc comment describes it.
* Flat union with named base type: the members are inherited from
'base'. The doc comment describes no members.
Before we can check a doc comment with .check_doc(), we need
.connect_doc() connect each of its "argument sections" to the member
it documents.
For structs and simple unions, this is straightforward: the members in
question are in .local_members, and .connect_doc() connects them.
For flat unions with a named base type, it's trivial: .local_members
is empty, and .connect_doc() does nothing.
For flat unions with an implicit base type, it's tricky. We have
QAPISchema._make_implicit_object_type() forward the union's doc
comment to the implicit base type, so that the base type's
.connect_doc() connects the members. The union's .connect_doc() does
nothing, as .local_members is empty.
Dirt effect: we check the doc comment twice, once for the union type,
and once for the implicit base type.
This is needlessly brittle and hard to understand. Clean up as
follows. Make the union's .connect_doc() connect an implicit base's
members itself. Do not forward the union's doc comment to its
implicit base type.
Requires extending .connect_doc() so it can work with a doc comment
other than self.doc. Add an optional argument for that.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-11-armbru@redhat.com>
2019-10-24 14:02:28 +03:00
|
|
|
doc = doc or self.doc
|
2020-03-17 14:54:44 +03:00
|
|
|
if self.base and self.base.is_implicit():
|
|
|
|
self.base.connect_doc(doc)
|
|
|
|
for m in self.local_members:
|
|
|
|
m.connect_doc(doc)
|
2019-10-24 14:02:26 +03:00
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def is_implicit(self):
|
|
|
|
# See QAPISchema._make_implicit_object_type(), as well as
|
|
|
|
# _def_predefineds()
|
|
|
|
return self.name.startswith('q_')
|
|
|
|
|
|
|
|
def is_empty(self):
|
|
|
|
assert self.members is not None
|
|
|
|
return not self.members and not self.variants
|
|
|
|
|
2023-03-16 10:13:25 +03:00
|
|
|
def has_conditional_members(self):
|
|
|
|
assert self.members is not None
|
|
|
|
return any(m.ifcond.is_present() for m in self.members)
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def c_name(self):
|
|
|
|
assert self.name != 'q_empty'
|
2020-03-04 18:59:31 +03:00
|
|
|
return super().c_name()
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def c_type(self):
|
|
|
|
assert not self.is_implicit()
|
2020-10-09 19:15:34 +03:00
|
|
|
return c_name(self.name) + POINTER_SUFFIX
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def c_unboxed_type(self):
|
|
|
|
return c_name(self.name)
|
|
|
|
|
|
|
|
def json_type(self):
|
|
|
|
return 'object'
|
|
|
|
|
|
|
|
def visit(self, visitor):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().visit(visitor)
|
2020-03-17 14:54:38 +03:00
|
|
|
visitor.visit_object_type(
|
|
|
|
self.name, self.info, self.ifcond, self.features,
|
|
|
|
self.base, self.local_members, self.variants)
|
|
|
|
visitor.visit_object_type_flat(
|
|
|
|
self.name, self.info, self.ifcond, self.features,
|
|
|
|
self.members, self.variants)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
class QAPISchemaAlternateType(QAPISchemaType):
|
|
|
|
meta = 'alternate'
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, features, variants):
|
|
|
|
super().__init__(name, info, doc, ifcond, features)
|
2020-03-17 14:54:43 +03:00
|
|
|
assert isinstance(variants, QAPISchemaVariants)
|
2020-03-17 14:54:42 +03:00
|
|
|
assert variants.tag_member
|
|
|
|
variants.set_defined_in(name)
|
|
|
|
variants.tag_member.set_defined_in(self.name)
|
|
|
|
self.variants = variants
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def check(self, schema):
|
|
|
|
super().check(schema)
|
|
|
|
self.variants.tag_member.check(schema)
|
|
|
|
# Not calling self.variants.check_clash(), because there's nothing
|
|
|
|
# to clash with
|
|
|
|
self.variants.check(schema, {})
|
|
|
|
# Alternate branch names have no relation to the tag enum values;
|
|
|
|
# so we have to check for potential name collisions ourselves.
|
|
|
|
seen = {}
|
|
|
|
types_seen = {}
|
|
|
|
for v in self.variants.variants:
|
|
|
|
v.check_clash(self.info, seen)
|
|
|
|
qtype = v.type.alternate_qtype()
|
|
|
|
if not qtype:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"%s cannot use %s"
|
|
|
|
% (v.describe(self.info), v.type.describe()))
|
|
|
|
conflicting = set([qtype])
|
|
|
|
if qtype == 'QTYPE_QSTRING':
|
|
|
|
if isinstance(v.type, QAPISchemaEnumType):
|
|
|
|
for m in v.type.members:
|
|
|
|
if m.name in ['on', 'off']:
|
|
|
|
conflicting.add('QTYPE_QBOOL')
|
|
|
|
if re.match(r'[-+0-9.]', m.name):
|
|
|
|
# lazy, could be tightened
|
|
|
|
conflicting.add('QTYPE_QNUM')
|
|
|
|
else:
|
|
|
|
conflicting.add('QTYPE_QNUM')
|
|
|
|
conflicting.add('QTYPE_QBOOL')
|
|
|
|
for qt in conflicting:
|
|
|
|
if qt in types_seen:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"%s can't be distinguished from '%s'"
|
|
|
|
% (v.describe(self.info), types_seen[qt]))
|
|
|
|
types_seen[qt] = v.name
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def connect_doc(self, doc=None):
|
|
|
|
super().connect_doc(doc)
|
|
|
|
doc = doc or self.doc
|
2020-03-17 14:54:44 +03:00
|
|
|
for v in self.variants.variants:
|
|
|
|
v.connect_doc(doc)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def c_type(self):
|
2020-10-09 19:15:34 +03:00
|
|
|
return c_name(self.name) + POINTER_SUFFIX
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def json_type(self):
|
|
|
|
return 'value'
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def visit(self, visitor):
|
|
|
|
super().visit(visitor)
|
|
|
|
visitor.visit_alternate_type(
|
|
|
|
self.name, self.info, self.ifcond, self.features, self.variants)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
2020-03-17 14:54:43 +03:00
|
|
|
class QAPISchemaVariants:
|
2019-10-18 10:43:44 +03:00
|
|
|
def __init__(self, tag_name, info, tag_member, variants):
|
2021-09-17 17:31:32 +03:00
|
|
|
# Unions pass tag_name but not tag_member.
|
|
|
|
# Alternates pass tag_member but not tag_name.
|
|
|
|
# After check(), tag_member is always set.
|
2019-10-18 10:43:44 +03:00
|
|
|
assert bool(tag_member) != bool(tag_name)
|
|
|
|
assert (isinstance(tag_name, str) or
|
|
|
|
isinstance(tag_member, QAPISchemaObjectTypeMember))
|
|
|
|
for v in variants:
|
2020-03-17 14:54:43 +03:00
|
|
|
assert isinstance(v, QAPISchemaVariant)
|
2019-10-18 10:43:44 +03:00
|
|
|
self._tag_name = tag_name
|
|
|
|
self.info = info
|
|
|
|
self.tag_member = tag_member
|
|
|
|
self.variants = variants
|
|
|
|
|
|
|
|
def set_defined_in(self, name):
|
|
|
|
for v in self.variants:
|
|
|
|
v.set_defined_in(name)
|
|
|
|
|
|
|
|
def check(self, schema, seen):
|
2021-09-17 17:31:32 +03:00
|
|
|
if self._tag_name: # union
|
2019-10-18 10:43:44 +03:00
|
|
|
self.tag_member = seen.get(c_name(self._tag_name))
|
|
|
|
base = "'base'"
|
|
|
|
# Pointing to the base type when not implicit would be
|
|
|
|
# nice, but we don't know it here
|
|
|
|
if not self.tag_member or self._tag_name != self.tag_member.name:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"discriminator '%s' is not a member of %s"
|
|
|
|
% (self._tag_name, base))
|
|
|
|
# Here we do:
|
|
|
|
base_type = schema.lookup_type(self.tag_member.defined_in)
|
|
|
|
assert base_type
|
|
|
|
if not base_type.is_implicit():
|
|
|
|
base = "base type '%s'" % self.tag_member.defined_in
|
|
|
|
if not isinstance(self.tag_member.type, QAPISchemaEnumType):
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"discriminator member '%s' of %s must be of enum type"
|
|
|
|
% (self._tag_name, base))
|
|
|
|
if self.tag_member.optional:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"discriminator member '%s' of %s must not be optional"
|
|
|
|
% (self._tag_name, base))
|
2021-08-04 11:30:58 +03:00
|
|
|
if self.tag_member.ifcond.is_present():
|
2019-10-18 10:43:44 +03:00
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"discriminator member '%s' of %s must not be conditional"
|
|
|
|
% (self._tag_name, base))
|
2021-09-17 17:31:32 +03:00
|
|
|
else: # alternate
|
2019-10-18 10:43:44 +03:00
|
|
|
assert isinstance(self.tag_member.type, QAPISchemaEnumType)
|
|
|
|
assert not self.tag_member.optional
|
2021-08-04 11:30:58 +03:00
|
|
|
assert not self.tag_member.ifcond.is_present()
|
2021-09-17 17:31:32 +03:00
|
|
|
if self._tag_name: # union
|
2019-10-18 10:43:44 +03:00
|
|
|
# branches that are not explicitly covered get an empty type
|
2020-03-04 18:59:32 +03:00
|
|
|
cases = {v.name for v in self.variants}
|
2019-10-18 10:43:44 +03:00
|
|
|
for m in self.tag_member.type.members:
|
|
|
|
if m.name not in cases:
|
2020-03-17 14:54:43 +03:00
|
|
|
v = QAPISchemaVariant(m.name, self.info,
|
|
|
|
'q_empty', m.ifcond)
|
2019-10-18 10:43:44 +03:00
|
|
|
v.set_defined_in(self.tag_member.defined_in)
|
|
|
|
self.variants.append(v)
|
|
|
|
if not self.variants:
|
|
|
|
raise QAPISemError(self.info, "union has no branches")
|
|
|
|
for v in self.variants:
|
|
|
|
v.check(schema)
|
|
|
|
# Union names must match enum values; alternate names are
|
|
|
|
# checked separately. Use 'seen' to tell the two apart.
|
|
|
|
if seen:
|
|
|
|
if v.name not in self.tag_member.type.member_names():
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"branch '%s' is not a value of %s"
|
|
|
|
% (v.name, self.tag_member.type.describe()))
|
2023-04-20 13:26:19 +03:00
|
|
|
if not isinstance(v.type, QAPISchemaObjectType):
|
2019-10-18 10:43:44 +03:00
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"%s cannot use %s"
|
|
|
|
% (v.describe(self.info), v.type.describe()))
|
|
|
|
v.type.check(schema)
|
|
|
|
|
|
|
|
def check_clash(self, info, seen):
|
|
|
|
for v in self.variants:
|
|
|
|
# Reset seen map for each variant, since qapi names from one
|
|
|
|
# branch do not affect another branch
|
|
|
|
v.type.check_clash(info, dict(seen))
|
|
|
|
|
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
class QAPISchemaMember:
|
|
|
|
""" Represents object members, enum members and features """
|
|
|
|
role = 'member'
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def __init__(self, name, info, ifcond=None):
|
|
|
|
assert isinstance(name, str)
|
|
|
|
self.name = name
|
|
|
|
self.info = info
|
2021-08-04 11:30:57 +03:00
|
|
|
self.ifcond = ifcond or QAPISchemaIfCond()
|
2020-03-17 14:54:42 +03:00
|
|
|
self.defined_in = None
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def set_defined_in(self, name):
|
|
|
|
assert not self.defined_in
|
|
|
|
self.defined_in = name
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def check_clash(self, info, seen):
|
|
|
|
cname = c_name(self.name)
|
|
|
|
if cname in seen:
|
|
|
|
raise QAPISemError(
|
|
|
|
info,
|
|
|
|
"%s collides with %s"
|
|
|
|
% (self.describe(info), seen[cname].describe(info)))
|
|
|
|
seen[cname] = self
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:44 +03:00
|
|
|
def connect_doc(self, doc):
|
|
|
|
if doc:
|
|
|
|
doc.connect_member(self)
|
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def describe(self, info):
|
|
|
|
role = self.role
|
qapi: Improve specificity of type/member descriptions
Error messages describe object members, enumeration values, features,
and variants like ROLE 'NAME', where ROLE is "member", "value",
"feature", or "branch", respectively. When the member is defined in
another type, e.g. inherited from a base type, we add "of type
'TYPE'". Example: test case struct-base-clash-deep reports a member
of type 'Sub' clashing with a member of its base type 'Base' as
struct-base-clash-deep.json: In struct 'Sub':
struct-base-clash-deep.json:10: member 'name' collides with member 'name' of type 'Base'
Members of implicitly defined types need special treatment. We don't
want to add "of type 'TYPE'" for them, because their named are made up
and mean nothing to the user. Instead, we describe members of an
implicitly defined base type as "base member 'NAME'", and command and
event parameters as "parameter 'NAME'". Example: test case
union-bad-base reports member of a variant's type clashing with a
member of its implicitly defined base type as
union-bad-base.json: In union 'TestUnion':
union-bad-base.json:8: member 'string' of type 'TestTypeA' collides with base member 'string'
The next commit will permit unions as variant types. "base member
'NAME' would then be ambigious: is it the union's base, or is it the
union's variant's base? One of its test cases would report a clash
between two such bases as "base member 'type' collides with base
member 'type'". Confusing.
Refine the special treatment: add "of TYPE" even for implicitly
defined types, but massage TYPE and ROLE so they make sense for the
user.
Message-Id: <20230420102619.348173-3-berrange@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2023-04-25 16:10:28 +03:00
|
|
|
meta = 'type'
|
2020-03-17 14:54:42 +03:00
|
|
|
defined_in = self.defined_in
|
|
|
|
assert defined_in
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
if defined_in.startswith('q_obj_'):
|
|
|
|
# See QAPISchema._make_implicit_object_type() - reverse the
|
|
|
|
# mapping there to create a nice human-readable description
|
|
|
|
defined_in = defined_in[6:]
|
|
|
|
if defined_in.endswith('-arg'):
|
|
|
|
# Implicit type created for a command's dict 'data'
|
|
|
|
assert role == 'member'
|
|
|
|
role = 'parameter'
|
qapi: Improve specificity of type/member descriptions
Error messages describe object members, enumeration values, features,
and variants like ROLE 'NAME', where ROLE is "member", "value",
"feature", or "branch", respectively. When the member is defined in
another type, e.g. inherited from a base type, we add "of type
'TYPE'". Example: test case struct-base-clash-deep reports a member
of type 'Sub' clashing with a member of its base type 'Base' as
struct-base-clash-deep.json: In struct 'Sub':
struct-base-clash-deep.json:10: member 'name' collides with member 'name' of type 'Base'
Members of implicitly defined types need special treatment. We don't
want to add "of type 'TYPE'" for them, because their named are made up
and mean nothing to the user. Instead, we describe members of an
implicitly defined base type as "base member 'NAME'", and command and
event parameters as "parameter 'NAME'". Example: test case
union-bad-base reports member of a variant's type clashing with a
member of its implicitly defined base type as
union-bad-base.json: In union 'TestUnion':
union-bad-base.json:8: member 'string' of type 'TestTypeA' collides with base member 'string'
The next commit will permit unions as variant types. "base member
'NAME' would then be ambigious: is it the union's base, or is it the
union's variant's base? One of its test cases would report a clash
between two such bases as "base member 'type' collides with base
member 'type'". Confusing.
Refine the special treatment: add "of TYPE" even for implicitly
defined types, but massage TYPE and ROLE so they make sense for the
user.
Message-Id: <20230420102619.348173-3-berrange@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2023-04-25 16:10:28 +03:00
|
|
|
meta = 'command'
|
|
|
|
defined_in = defined_in[:-4]
|
2020-03-17 14:54:42 +03:00
|
|
|
elif defined_in.endswith('-base'):
|
2021-09-17 17:31:32 +03:00
|
|
|
# Implicit type created for a union's dict 'base'
|
2020-03-17 14:54:42 +03:00
|
|
|
role = 'base ' + role
|
qapi: Improve specificity of type/member descriptions
Error messages describe object members, enumeration values, features,
and variants like ROLE 'NAME', where ROLE is "member", "value",
"feature", or "branch", respectively. When the member is defined in
another type, e.g. inherited from a base type, we add "of type
'TYPE'". Example: test case struct-base-clash-deep reports a member
of type 'Sub' clashing with a member of its base type 'Base' as
struct-base-clash-deep.json: In struct 'Sub':
struct-base-clash-deep.json:10: member 'name' collides with member 'name' of type 'Base'
Members of implicitly defined types need special treatment. We don't
want to add "of type 'TYPE'" for them, because their named are made up
and mean nothing to the user. Instead, we describe members of an
implicitly defined base type as "base member 'NAME'", and command and
event parameters as "parameter 'NAME'". Example: test case
union-bad-base reports member of a variant's type clashing with a
member of its implicitly defined base type as
union-bad-base.json: In union 'TestUnion':
union-bad-base.json:8: member 'string' of type 'TestTypeA' collides with base member 'string'
The next commit will permit unions as variant types. "base member
'NAME' would then be ambigious: is it the union's base, or is it the
union's variant's base? One of its test cases would report a clash
between two such bases as "base member 'type' collides with base
member 'type'". Confusing.
Refine the special treatment: add "of TYPE" even for implicitly
defined types, but massage TYPE and ROLE so they make sense for the
user.
Message-Id: <20230420102619.348173-3-berrange@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2023-04-25 16:10:28 +03:00
|
|
|
defined_in = defined_in[:-5]
|
2020-03-17 14:54:42 +03:00
|
|
|
else:
|
|
|
|
assert False
|
qapi: Improve specificity of type/member descriptions
Error messages describe object members, enumeration values, features,
and variants like ROLE 'NAME', where ROLE is "member", "value",
"feature", or "branch", respectively. When the member is defined in
another type, e.g. inherited from a base type, we add "of type
'TYPE'". Example: test case struct-base-clash-deep reports a member
of type 'Sub' clashing with a member of its base type 'Base' as
struct-base-clash-deep.json: In struct 'Sub':
struct-base-clash-deep.json:10: member 'name' collides with member 'name' of type 'Base'
Members of implicitly defined types need special treatment. We don't
want to add "of type 'TYPE'" for them, because their named are made up
and mean nothing to the user. Instead, we describe members of an
implicitly defined base type as "base member 'NAME'", and command and
event parameters as "parameter 'NAME'". Example: test case
union-bad-base reports member of a variant's type clashing with a
member of its implicitly defined base type as
union-bad-base.json: In union 'TestUnion':
union-bad-base.json:8: member 'string' of type 'TestTypeA' collides with base member 'string'
The next commit will permit unions as variant types. "base member
'NAME' would then be ambigious: is it the union's base, or is it the
union's variant's base? One of its test cases would report a clash
between two such bases as "base member 'type' collides with base
member 'type'". Confusing.
Refine the special treatment: add "of TYPE" even for implicitly
defined types, but massage TYPE and ROLE so they make sense for the
user.
Message-Id: <20230420102619.348173-3-berrange@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2023-04-25 16:10:28 +03:00
|
|
|
|
|
|
|
if defined_in != info.defn_name:
|
|
|
|
return "%s '%s' of %s '%s'" % (role, self.name, meta, defined_in)
|
2020-03-17 14:54:42 +03:00
|
|
|
return "%s '%s'" % (role, self.name)
|
2019-10-24 14:02:26 +03:00
|
|
|
|
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
class QAPISchemaEnumMember(QAPISchemaMember):
|
|
|
|
role = 'value'
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2021-10-25 07:24:02 +03:00
|
|
|
def __init__(self, name, info, ifcond=None, features=None):
|
|
|
|
super().__init__(name, info, ifcond)
|
|
|
|
for f in features or []:
|
|
|
|
assert isinstance(f, QAPISchemaFeature)
|
|
|
|
f.set_defined_in(name)
|
|
|
|
self.features = features or []
|
|
|
|
|
|
|
|
def connect_doc(self, doc):
|
|
|
|
super().connect_doc(doc)
|
|
|
|
if doc:
|
|
|
|
for f in self.features:
|
|
|
|
doc.connect_feature(f)
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
class QAPISchemaFeature(QAPISchemaMember):
|
|
|
|
role = 'feature'
|
|
|
|
|
2021-10-28 13:25:15 +03:00
|
|
|
def is_special(self):
|
2021-10-28 13:25:20 +03:00
|
|
|
return self.name in ('deprecated', 'unstable')
|
2021-10-28 13:25:15 +03:00
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
|
|
|
|
class QAPISchemaObjectTypeMember(QAPISchemaMember):
|
2020-03-17 14:54:45 +03:00
|
|
|
def __init__(self, name, info, typ, optional, ifcond=None, features=None):
|
2020-03-17 14:54:42 +03:00
|
|
|
super().__init__(name, info, ifcond)
|
|
|
|
assert isinstance(typ, str)
|
|
|
|
assert isinstance(optional, bool)
|
2020-03-17 14:54:45 +03:00
|
|
|
for f in features or []:
|
|
|
|
assert isinstance(f, QAPISchemaFeature)
|
|
|
|
f.set_defined_in(name)
|
2020-03-17 14:54:42 +03:00
|
|
|
self._type_name = typ
|
|
|
|
self.type = None
|
|
|
|
self.optional = optional
|
2020-03-17 14:54:45 +03:00
|
|
|
self.features = features or []
|
2020-03-17 14:54:42 +03:00
|
|
|
|
qapi: Start to elide redundant has_FOO in generated C
In QAPI, absent optional members are distinct from any present value.
We thus represent an optional schema member FOO as two C members: a
FOO with the member's type, and a bool has_FOO. Likewise for function
arguments.
However, has_FOO is actually redundant for a pointer-valued FOO, which
can be null only when has_FOO is false, i.e. has_FOO == !!FOO. Except
for arrays, where we a null FOO can also be a present empty array.
The redundant has_FOO are a nuisance to work with. Improve the
generator to elide them. Uses of has_FOO need to be replaced as
follows.
Tests of has_FOO become the equivalent comparison of FOO with null.
For brevity, this is commonly done by implicit conversion to bool.
Assignments to has_FOO get dropped.
Likewise for arguments to has_FOO parameters.
Beware: code may violate the invariant has_FOO == !!FOO before the
transformation, and get away with it. The above transformation can
then break things. Two cases:
* Absent: if code ignores FOO entirely when !has_FOO (except for
freeing it if necessary), even non-null / uninitialized FOO works.
Such code is known to exist.
* Present: if code ignores FOO entirely when has_FOO, even null FOO
works. Such code should not exist.
In both cases, replacing tests of has_FOO by FOO reverts their sense.
We have to fix the value of FOO then.
To facilitate review of the necessary updates to handwritten code, add
means to opt out of this change, and opt out for all QAPI schema
modules where the change requires updates to handwritten code. The
next few commits will remove these opt-outs in reviewable chunks, then
drop the means to opt out.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Message-Id: <20221104160712.3005652-5-armbru@redhat.com>
2022-11-04 19:06:46 +03:00
|
|
|
def need_has(self):
|
|
|
|
assert self.type
|
|
|
|
return self.optional and self.type.need_has_if_optional()
|
|
|
|
|
2020-03-17 14:54:42 +03:00
|
|
|
def check(self, schema):
|
|
|
|
assert self.defined_in
|
|
|
|
self.type = schema.resolve_type(self._type_name, self.info,
|
|
|
|
self.describe)
|
2020-03-17 14:54:45 +03:00
|
|
|
seen = {}
|
|
|
|
for f in self.features:
|
|
|
|
f.check_clash(self.info, seen)
|
|
|
|
|
|
|
|
def connect_doc(self, doc):
|
|
|
|
super().connect_doc(doc)
|
|
|
|
if doc:
|
|
|
|
for f in self.features:
|
|
|
|
doc.connect_feature(f)
|
2020-03-17 14:54:42 +03:00
|
|
|
|
|
|
|
|
2020-03-17 14:54:43 +03:00
|
|
|
class QAPISchemaVariant(QAPISchemaObjectTypeMember):
|
2020-03-17 14:54:42 +03:00
|
|
|
role = 'branch'
|
|
|
|
|
|
|
|
def __init__(self, name, info, typ, ifcond=None):
|
|
|
|
super().__init__(name, info, typ, False, ifcond)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaCommand(QAPISchemaEntity):
|
|
|
|
meta = 'command'
|
|
|
|
|
2020-03-17 14:54:37 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, features,
|
|
|
|
arg_type, ret_type,
|
2020-10-05 18:58:49 +03:00
|
|
|
gen, success_response, boxed, allow_oob, allow_preconfig,
|
|
|
|
coroutine):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().__init__(name, info, doc, ifcond, features)
|
2019-10-18 10:43:44 +03:00
|
|
|
assert not arg_type or isinstance(arg_type, str)
|
|
|
|
assert not ret_type or isinstance(ret_type, str)
|
|
|
|
self._arg_type_name = arg_type
|
|
|
|
self.arg_type = None
|
|
|
|
self._ret_type_name = ret_type
|
|
|
|
self.ret_type = None
|
|
|
|
self.gen = gen
|
|
|
|
self.success_response = success_response
|
|
|
|
self.boxed = boxed
|
|
|
|
self.allow_oob = allow_oob
|
|
|
|
self.allow_preconfig = allow_preconfig
|
2020-10-05 18:58:49 +03:00
|
|
|
self.coroutine = coroutine
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def check(self, schema):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().check(schema)
|
2019-10-18 10:43:44 +03:00
|
|
|
if self._arg_type_name:
|
|
|
|
self.arg_type = schema.resolve_type(
|
|
|
|
self._arg_type_name, self.info, "command's 'data'")
|
|
|
|
if not isinstance(self.arg_type, QAPISchemaObjectType):
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"command's 'data' cannot take %s"
|
|
|
|
% self.arg_type.describe())
|
|
|
|
if self.arg_type.variants and not self.boxed:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"command's 'data' can take %s only with 'boxed': true"
|
|
|
|
% self.arg_type.describe())
|
2023-03-16 10:13:25 +03:00
|
|
|
self.arg_type.check(schema)
|
|
|
|
if self.arg_type.has_conditional_members() and not self.boxed:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"conditional command arguments require 'boxed': true")
|
2019-10-18 10:43:44 +03:00
|
|
|
if self._ret_type_name:
|
|
|
|
self.ret_type = schema.resolve_type(
|
|
|
|
self._ret_type_name, self.info, "command's 'returns'")
|
2021-03-23 12:40:16 +03:00
|
|
|
if self.name not in self.info.pragma.command_returns_exceptions:
|
2019-11-20 21:25:46 +03:00
|
|
|
typ = self.ret_type
|
|
|
|
if isinstance(typ, QAPISchemaArrayType):
|
|
|
|
typ = self.ret_type.element_type
|
|
|
|
assert typ
|
|
|
|
if not isinstance(typ, QAPISchemaObjectType):
|
2019-10-18 10:43:44 +03:00
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"command's 'returns' cannot take %s"
|
|
|
|
% self.ret_type.describe())
|
|
|
|
|
qapi: Fix doc comment checking for commands and events
When a command's 'data' is an object, its doc comment describes the
arguments defined there. When 'data' names a type, the doc comment
does not describe arguments. Instead, the doc generator inserts a
pointer to the named type.
An event's doc comment works the same.
We don't actually check doc comments for commands and events.
Instead, QAPISchema._def_command() forwards the doc comment to the
implicit argument type, where it gets checked. Works because the
check only cares for the implicit argument type's members.
Not only is this needlessly hard to understand, it actually falls
apart in two cases:
* When 'data' is empty, there is nothing to forward to, and the doc
comment remains unchecked. Demonstrated by test doc-bad-event-arg.
* When 'data' names a type, we can't forward, as the type has its own
doc comment. The command or event's doc comment remains unchecked.
Demonstrated by test doc-bad-boxed-command-arg.
The forwarding goes back to commit 069fb5b250 "qapi: Prepare for
requiring more complete documentation", put to use in commit
816a57cd6e "qapi: Fix detection of bogus member documentation". That
fix was incomplete.
To fix this, make QAPISchemaCommand and QAPISchemaEvent check doc
comments, and drop the forwarding of doc comments to implicit argument
types.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-12-armbru@redhat.com>
2019-10-24 14:02:29 +03:00
|
|
|
def connect_doc(self, doc=None):
|
2020-03-17 14:54:36 +03:00
|
|
|
super().connect_doc(doc)
|
qapi: Fix doc comment checking for commands and events
When a command's 'data' is an object, its doc comment describes the
arguments defined there. When 'data' names a type, the doc comment
does not describe arguments. Instead, the doc generator inserts a
pointer to the named type.
An event's doc comment works the same.
We don't actually check doc comments for commands and events.
Instead, QAPISchema._def_command() forwards the doc comment to the
implicit argument type, where it gets checked. Works because the
check only cares for the implicit argument type's members.
Not only is this needlessly hard to understand, it actually falls
apart in two cases:
* When 'data' is empty, there is nothing to forward to, and the doc
comment remains unchecked. Demonstrated by test doc-bad-event-arg.
* When 'data' names a type, we can't forward, as the type has its own
doc comment. The command or event's doc comment remains unchecked.
Demonstrated by test doc-bad-boxed-command-arg.
The forwarding goes back to commit 069fb5b250 "qapi: Prepare for
requiring more complete documentation", put to use in commit
816a57cd6e "qapi: Fix detection of bogus member documentation". That
fix was incomplete.
To fix this, make QAPISchemaCommand and QAPISchemaEvent check doc
comments, and drop the forwarding of doc comments to implicit argument
types.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-12-armbru@redhat.com>
2019-10-24 14:02:29 +03:00
|
|
|
doc = doc or self.doc
|
|
|
|
if doc:
|
|
|
|
if self.arg_type and self.arg_type.is_implicit():
|
|
|
|
self.arg_type.connect_doc(doc)
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def visit(self, visitor):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().visit(visitor)
|
2020-03-17 14:54:38 +03:00
|
|
|
visitor.visit_command(
|
|
|
|
self.name, self.info, self.ifcond, self.features,
|
|
|
|
self.arg_type, self.ret_type, self.gen, self.success_response,
|
2020-10-05 18:58:49 +03:00
|
|
|
self.boxed, self.allow_oob, self.allow_preconfig,
|
|
|
|
self.coroutine)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
class QAPISchemaEvent(QAPISchemaEntity):
|
|
|
|
meta = 'event'
|
|
|
|
|
2020-03-17 14:54:37 +03:00
|
|
|
def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
|
|
|
|
super().__init__(name, info, doc, ifcond, features)
|
2019-10-18 10:43:44 +03:00
|
|
|
assert not arg_type or isinstance(arg_type, str)
|
|
|
|
self._arg_type_name = arg_type
|
|
|
|
self.arg_type = None
|
|
|
|
self.boxed = boxed
|
|
|
|
|
|
|
|
def check(self, schema):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().check(schema)
|
2019-10-18 10:43:44 +03:00
|
|
|
if self._arg_type_name:
|
|
|
|
self.arg_type = schema.resolve_type(
|
|
|
|
self._arg_type_name, self.info, "event's 'data'")
|
|
|
|
if not isinstance(self.arg_type, QAPISchemaObjectType):
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"event's 'data' cannot take %s"
|
|
|
|
% self.arg_type.describe())
|
|
|
|
if self.arg_type.variants and not self.boxed:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"event's 'data' can take %s only with 'boxed': true"
|
|
|
|
% self.arg_type.describe())
|
2023-03-16 10:13:25 +03:00
|
|
|
self.arg_type.check(schema)
|
|
|
|
if self.arg_type.has_conditional_members() and not self.boxed:
|
|
|
|
raise QAPISemError(
|
|
|
|
self.info,
|
|
|
|
"conditional event arguments require 'boxed': true")
|
2019-10-18 10:43:44 +03:00
|
|
|
|
qapi: Fix doc comment checking for commands and events
When a command's 'data' is an object, its doc comment describes the
arguments defined there. When 'data' names a type, the doc comment
does not describe arguments. Instead, the doc generator inserts a
pointer to the named type.
An event's doc comment works the same.
We don't actually check doc comments for commands and events.
Instead, QAPISchema._def_command() forwards the doc comment to the
implicit argument type, where it gets checked. Works because the
check only cares for the implicit argument type's members.
Not only is this needlessly hard to understand, it actually falls
apart in two cases:
* When 'data' is empty, there is nothing to forward to, and the doc
comment remains unchecked. Demonstrated by test doc-bad-event-arg.
* When 'data' names a type, we can't forward, as the type has its own
doc comment. The command or event's doc comment remains unchecked.
Demonstrated by test doc-bad-boxed-command-arg.
The forwarding goes back to commit 069fb5b250 "qapi: Prepare for
requiring more complete documentation", put to use in commit
816a57cd6e "qapi: Fix detection of bogus member documentation". That
fix was incomplete.
To fix this, make QAPISchemaCommand and QAPISchemaEvent check doc
comments, and drop the forwarding of doc comments to implicit argument
types.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-12-armbru@redhat.com>
2019-10-24 14:02:29 +03:00
|
|
|
def connect_doc(self, doc=None):
|
2020-03-17 14:54:36 +03:00
|
|
|
super().connect_doc(doc)
|
qapi: Fix doc comment checking for commands and events
When a command's 'data' is an object, its doc comment describes the
arguments defined there. When 'data' names a type, the doc comment
does not describe arguments. Instead, the doc generator inserts a
pointer to the named type.
An event's doc comment works the same.
We don't actually check doc comments for commands and events.
Instead, QAPISchema._def_command() forwards the doc comment to the
implicit argument type, where it gets checked. Works because the
check only cares for the implicit argument type's members.
Not only is this needlessly hard to understand, it actually falls
apart in two cases:
* When 'data' is empty, there is nothing to forward to, and the doc
comment remains unchecked. Demonstrated by test doc-bad-event-arg.
* When 'data' names a type, we can't forward, as the type has its own
doc comment. The command or event's doc comment remains unchecked.
Demonstrated by test doc-bad-boxed-command-arg.
The forwarding goes back to commit 069fb5b250 "qapi: Prepare for
requiring more complete documentation", put to use in commit
816a57cd6e "qapi: Fix detection of bogus member documentation". That
fix was incomplete.
To fix this, make QAPISchemaCommand and QAPISchemaEvent check doc
comments, and drop the forwarding of doc comments to implicit argument
types.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20191024110237.30963-12-armbru@redhat.com>
2019-10-24 14:02:29 +03:00
|
|
|
doc = doc or self.doc
|
|
|
|
if doc:
|
|
|
|
if self.arg_type and self.arg_type.is_implicit():
|
|
|
|
self.arg_type.connect_doc(doc)
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def visit(self, visitor):
|
2020-03-04 18:59:31 +03:00
|
|
|
super().visit(visitor)
|
2020-03-17 14:54:37 +03:00
|
|
|
visitor.visit_event(
|
|
|
|
self.name, self.info, self.ifcond, self.features,
|
|
|
|
self.arg_type, self.boxed)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
|
2020-03-04 18:59:29 +03:00
|
|
|
class QAPISchema:
|
2019-10-18 10:43:44 +03:00
|
|
|
def __init__(self, fname):
|
|
|
|
self.fname = fname
|
qapi/parser: Don't try to handle file errors
Fixes: f5d4361cda
Fixes: 52a474180a
Fixes: 46f49468c6
Remove the try/except block that handles file-opening errors in
QAPISchemaParser.__init__() and add one each to
QAPISchemaParser._include() and QAPISchema.__init__() respectively.
This simultaneously fixes the typing of info.fname (f5d4361cda), A
static typing violation in test-qapi (46f49468c6), and a regression of
an error message (52a474180a).
The short-ish version of what motivates this patch is:
- It's hard to write a good error message in the init method,
because we need to determine the context of our caller to do so.
It's easier to just let the caller write the message.
- We don't want to allow QAPISourceInfo(None, None, None) to exist. The
typing introduced by commit f5d4361cda types the 'fname' field as
(non-optional) str, which was premature until the removal of this
construct.
- Errors made using such an object are currently incorrect (since
52a474180a)
- It's not technically a semantic error if we cannot open the schema.
- There are various typing constraints that make mixing these two cases
undesirable for a single special case.
- test-qapi's code handling an fname of 'None' is now dead, drop it.
Additionally, Not all QAPIError objects have an 'info' field (since
46f49468), so deleting this stanza corrects a typing oversight in
test-qapi introduced by that commit.
Other considerations:
- open() is moved to a 'with' block to ensure file pointers are
cleaned up deterministically.
- Python 3.3 deprecated IOError and made it a synonym for OSError.
Avoid the misleading perception these exception handlers are
narrower than they really are.
The long version:
The error message here is incorrect (since commit 52a474180a):
> python3 qapi-gen.py 'fake.json'
qapi-gen.py: qapi-gen.py: can't read schema file 'fake.json': No such file or directory
In pursuing it, we find that QAPISourceInfo has a special accommodation
for when there's no filename. Meanwhile, the intent when QAPISourceInfo
was typed (f5d4361cda) was non-optional 'str'. This usage was
overlooked.
To remove this, I'd want to avoid having a "fake" QAPISourceInfo
object. I also don't want to explicitly begin accommodating
QAPISourceInfo itself being None, because we actually want to eventually
prove that this can never happen -- We don't want to confuse "The file
isn't open yet" with "This error stems from a definition that wasn't
defined in any file".
(An earlier series tried to create a dummy info object, but it was tough
to prove in review that it worked correctly without creating new
regressions. This patch avoids that distraction. We would like to first
prove that we never raise QAPISemError for any built-in object before we
add "special" info objects. We aren't ready to do that yet.)
So, which way out of the labyrinth?
Here's one way: Don't try to handle errors at a level with "mixed"
semantic contexts; i.e. don't mix inclusion errors (should report a
source line where the include was triggered) and command line errors
(where we specified a file we couldn't read).
Remove the error handling from the initializer of the parser. Pythonic!
Now it's the caller's job to figure out what to do about it. Handle the
error in QAPISchemaParser._include() instead, where we can write a
targeted error message where we are guaranteed to have an 'info' context
to report with.
The root level error can similarly move to QAPISchema.__init__(), where
we know we'll never have an info context to report with, so we use a
more abstract error type.
Now the error looks sensible again:
> python3 qapi-gen.py 'fake.json'
qapi-gen.py: can't read schema file 'fake.json': No such file or directory
With these error cases separated, QAPISourceInfo can be solidified as
never having placeholder arguments that violate our desired types. Clean
up test-qapi along similar lines.
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20210519183951.3946870-2-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: Markus Armbruster <armbru@redhat.com>
2021-05-19 21:39:37 +03:00
|
|
|
|
|
|
|
try:
|
|
|
|
parser = QAPISchemaParser(fname)
|
|
|
|
except OSError as err:
|
|
|
|
raise QAPIError(
|
|
|
|
f"can't read schema file '{fname}': {err.strerror}"
|
|
|
|
) from err
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
exprs = check_exprs(parser.exprs)
|
|
|
|
self.docs = parser.docs
|
|
|
|
self._entity_list = []
|
|
|
|
self._entity_dict = {}
|
2020-01-16 23:25:58 +03:00
|
|
|
self._module_dict = OrderedDict()
|
2019-11-20 21:25:49 +03:00
|
|
|
self._schema_dir = os.path.dirname(fname)
|
2021-02-01 22:37:41 +03:00
|
|
|
self._make_module(QAPISchemaModule.BUILTIN_MODULE_NAME)
|
2019-11-20 21:25:49 +03:00
|
|
|
self._make_module(fname)
|
2019-10-18 10:43:44 +03:00
|
|
|
self._predefining = True
|
|
|
|
self._def_predefineds()
|
|
|
|
self._predefining = False
|
|
|
|
self._def_exprs(exprs)
|
|
|
|
self.check()
|
|
|
|
|
|
|
|
def _def_entity(self, ent):
|
|
|
|
# Only the predefined types are allowed to not have info
|
|
|
|
assert ent.info or self._predefining
|
|
|
|
self._entity_list.append(ent)
|
|
|
|
if ent.name is None:
|
|
|
|
return
|
|
|
|
# TODO reject names that differ only in '_' vs. '.' vs. '-',
|
|
|
|
# because they're liable to clash in generated C.
|
|
|
|
other_ent = self._entity_dict.get(ent.name)
|
|
|
|
if other_ent:
|
|
|
|
if other_ent.info:
|
2021-04-21 22:22:28 +03:00
|
|
|
where = QAPISourceError(other_ent.info, "previous definition")
|
2019-10-18 10:43:44 +03:00
|
|
|
raise QAPISemError(
|
|
|
|
ent.info,
|
|
|
|
"'%s' is already defined\n%s" % (ent.name, where))
|
|
|
|
raise QAPISemError(
|
|
|
|
ent.info, "%s is already defined" % other_ent.describe())
|
|
|
|
self._entity_dict[ent.name] = ent
|
|
|
|
|
|
|
|
def lookup_entity(self, name, typ=None):
|
|
|
|
ent = self._entity_dict.get(name)
|
|
|
|
if typ and not isinstance(ent, typ):
|
|
|
|
return None
|
|
|
|
return ent
|
|
|
|
|
|
|
|
def lookup_type(self, name):
|
|
|
|
return self.lookup_entity(name, QAPISchemaType)
|
|
|
|
|
|
|
|
def resolve_type(self, name, info, what):
|
|
|
|
typ = self.lookup_type(name)
|
|
|
|
if not typ:
|
|
|
|
if callable(what):
|
|
|
|
what = what(info)
|
|
|
|
raise QAPISemError(
|
|
|
|
info, "%s uses unknown type '%s'" % (what, name))
|
|
|
|
return typ
|
|
|
|
|
2021-02-01 22:37:39 +03:00
|
|
|
def _module_name(self, fname: str) -> str:
|
2021-02-01 22:37:36 +03:00
|
|
|
if QAPISchemaModule.is_system_module(fname):
|
|
|
|
return fname
|
2019-11-20 21:25:49 +03:00
|
|
|
return os.path.relpath(fname, self._schema_dir)
|
|
|
|
|
|
|
|
def _make_module(self, fname):
|
|
|
|
name = self._module_name(fname)
|
2020-03-04 18:59:32 +03:00
|
|
|
if name not in self._module_dict:
|
2019-11-20 21:25:49 +03:00
|
|
|
self._module_dict[name] = QAPISchemaModule(name)
|
|
|
|
return self._module_dict[name]
|
|
|
|
|
|
|
|
def module_by_fname(self, fname):
|
|
|
|
name = self._module_name(fname)
|
|
|
|
return self._module_dict[name]
|
|
|
|
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
def _def_include(self, expr: QAPIExpression):
|
2019-10-18 10:43:44 +03:00
|
|
|
include = expr['include']
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
assert expr.doc is None
|
|
|
|
self._def_entity(
|
|
|
|
QAPISchemaInclude(self._make_module(include), expr.info))
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def _def_builtin_type(self, name, json_type, c_type):
|
|
|
|
self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
|
|
|
|
# Instantiating only the arrays that are actually used would
|
|
|
|
# be nice, but we can't as long as their generated code
|
|
|
|
# (qapi-builtin-types.[ch]) may be shared by some other
|
|
|
|
# schema.
|
|
|
|
self._make_array_type(name, None)
|
|
|
|
|
|
|
|
def _def_predefineds(self):
|
2020-10-09 19:15:34 +03:00
|
|
|
for t in [('str', 'string', 'char' + POINTER_SUFFIX),
|
2019-10-18 10:43:44 +03:00
|
|
|
('number', 'number', 'double'),
|
|
|
|
('int', 'int', 'int64_t'),
|
|
|
|
('int8', 'int', 'int8_t'),
|
|
|
|
('int16', 'int', 'int16_t'),
|
|
|
|
('int32', 'int', 'int32_t'),
|
|
|
|
('int64', 'int', 'int64_t'),
|
|
|
|
('uint8', 'int', 'uint8_t'),
|
|
|
|
('uint16', 'int', 'uint16_t'),
|
|
|
|
('uint32', 'int', 'uint32_t'),
|
|
|
|
('uint64', 'int', 'uint64_t'),
|
|
|
|
('size', 'int', 'uint64_t'),
|
|
|
|
('bool', 'boolean', 'bool'),
|
2020-10-09 19:15:34 +03:00
|
|
|
('any', 'value', 'QObject' + POINTER_SUFFIX),
|
|
|
|
('null', 'null', 'QNull' + POINTER_SUFFIX)]:
|
2019-10-18 10:43:44 +03:00
|
|
|
self._def_builtin_type(*t)
|
|
|
|
self.the_empty_object_type = QAPISchemaObjectType(
|
2020-03-17 14:54:37 +03:00
|
|
|
'q_empty', None, None, None, None, None, [], None)
|
2019-10-18 10:43:44 +03:00
|
|
|
self._def_entity(self.the_empty_object_type)
|
|
|
|
|
|
|
|
qtypes = ['none', 'qnull', 'qnum', 'qstring', 'qdict', 'qlist',
|
|
|
|
'qbool']
|
|
|
|
qtype_values = self._make_enum_members(
|
|
|
|
[{'name': n} for n in qtypes], None)
|
|
|
|
|
2020-03-17 14:54:37 +03:00
|
|
|
self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
|
2019-10-18 10:43:44 +03:00
|
|
|
qtype_values, 'QTYPE'))
|
|
|
|
|
2020-03-17 14:54:41 +03:00
|
|
|
def _make_features(self, features, info):
|
|
|
|
if features is None:
|
|
|
|
return []
|
2021-08-04 11:30:57 +03:00
|
|
|
return [QAPISchemaFeature(f['name'], info,
|
|
|
|
QAPISchemaIfCond(f.get('if')))
|
2019-10-18 10:43:44 +03:00
|
|
|
for f in features]
|
|
|
|
|
2021-10-25 07:24:02 +03:00
|
|
|
def _make_enum_member(self, name, ifcond, features, info):
|
|
|
|
return QAPISchemaEnumMember(name, info,
|
|
|
|
QAPISchemaIfCond(ifcond),
|
|
|
|
self._make_features(features, info))
|
|
|
|
|
2019-10-18 10:43:44 +03:00
|
|
|
def _make_enum_members(self, values, info):
|
2021-10-25 07:24:02 +03:00
|
|
|
return [self._make_enum_member(v['name'], v.get('if'),
|
|
|
|
v.get('features'), info)
|
2019-10-18 10:43:44 +03:00
|
|
|
for v in values]
|
|
|
|
|
|
|
|
def _make_array_type(self, element_type, info):
|
|
|
|
name = element_type + 'List' # reserved by check_defn_name_str()
|
|
|
|
if not self.lookup_type(name):
|
|
|
|
self._def_entity(QAPISchemaArrayType(name, info, element_type))
|
|
|
|
return name
|
|
|
|
|
2019-10-24 14:02:30 +03:00
|
|
|
def _make_implicit_object_type(self, name, info, ifcond, role, members):
|
2019-10-18 10:43:44 +03:00
|
|
|
if not members:
|
|
|
|
return None
|
|
|
|
# See also QAPISchemaObjectTypeMember.describe()
|
|
|
|
name = 'q_obj_%s-%s' % (name, role)
|
|
|
|
typ = self.lookup_entity(name, QAPISchemaObjectType)
|
|
|
|
if typ:
|
2021-09-17 17:31:32 +03:00
|
|
|
# The implicit object type has multiple users. This can
|
|
|
|
# only be a duplicate definition, which will be flagged
|
|
|
|
# later.
|
2021-08-06 15:05:10 +03:00
|
|
|
pass
|
2019-10-18 10:43:44 +03:00
|
|
|
else:
|
2020-03-17 14:54:37 +03:00
|
|
|
self._def_entity(QAPISchemaObjectType(
|
|
|
|
name, info, None, ifcond, None, None, members, None))
|
2019-10-18 10:43:44 +03:00
|
|
|
return name
|
|
|
|
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
def _def_enum_type(self, expr: QAPIExpression):
|
2019-10-18 10:43:44 +03:00
|
|
|
name = expr['enum']
|
|
|
|
data = expr['data']
|
|
|
|
prefix = expr.get('prefix')
|
2021-08-04 11:30:57 +03:00
|
|
|
ifcond = QAPISchemaIfCond(expr.get('if'))
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
info = expr.info
|
2020-03-17 14:54:41 +03:00
|
|
|
features = self._make_features(expr.get('features'), info)
|
2019-10-18 10:43:44 +03:00
|
|
|
self._def_entity(QAPISchemaEnumType(
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
name, info, expr.doc, ifcond, features,
|
2019-10-18 10:43:44 +03:00
|
|
|
self._make_enum_members(data, info), prefix))
|
|
|
|
|
2020-03-17 14:54:45 +03:00
|
|
|
def _make_member(self, name, typ, ifcond, features, info):
|
2019-10-18 10:43:44 +03:00
|
|
|
optional = False
|
|
|
|
if name.startswith('*'):
|
|
|
|
name = name[1:]
|
|
|
|
optional = True
|
|
|
|
if isinstance(typ, list):
|
|
|
|
assert len(typ) == 1
|
|
|
|
typ = self._make_array_type(typ[0], info)
|
2020-03-17 14:54:45 +03:00
|
|
|
return QAPISchemaObjectTypeMember(name, info, typ, optional, ifcond,
|
|
|
|
self._make_features(features, info))
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def _make_members(self, data, info):
|
2021-08-04 11:30:57 +03:00
|
|
|
return [self._make_member(key, value['type'],
|
|
|
|
QAPISchemaIfCond(value.get('if')),
|
2020-03-17 14:54:45 +03:00
|
|
|
value.get('features'), info)
|
2019-10-18 10:43:44 +03:00
|
|
|
for (key, value) in data.items()]
|
|
|
|
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
def _def_struct_type(self, expr: QAPIExpression):
|
2019-10-18 10:43:44 +03:00
|
|
|
name = expr['struct']
|
|
|
|
base = expr.get('base')
|
|
|
|
data = expr['data']
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
info = expr.info
|
2021-08-04 11:30:57 +03:00
|
|
|
ifcond = QAPISchemaIfCond(expr.get('if'))
|
2020-03-17 14:54:41 +03:00
|
|
|
features = self._make_features(expr.get('features'), info)
|
2019-10-18 10:43:44 +03:00
|
|
|
self._def_entity(QAPISchemaObjectType(
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
name, info, expr.doc, ifcond, features, base,
|
2019-10-18 10:43:44 +03:00
|
|
|
self._make_members(data, info),
|
2020-03-17 14:54:37 +03:00
|
|
|
None))
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def _make_variant(self, case, typ, ifcond, info):
|
2022-03-21 19:42:41 +03:00
|
|
|
if isinstance(typ, list):
|
|
|
|
assert len(typ) == 1
|
|
|
|
typ = self._make_array_type(typ[0], info)
|
2020-03-17 14:54:43 +03:00
|
|
|
return QAPISchemaVariant(case, info, typ, ifcond)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
def _def_union_type(self, expr: QAPIExpression):
|
2019-10-18 10:43:44 +03:00
|
|
|
name = expr['union']
|
2021-09-17 17:31:32 +03:00
|
|
|
base = expr['base']
|
|
|
|
tag_name = expr['discriminator']
|
2019-10-18 10:43:44 +03:00
|
|
|
data = expr['data']
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
assert isinstance(data, dict)
|
|
|
|
info = expr.info
|
2021-08-04 11:30:57 +03:00
|
|
|
ifcond = QAPISchemaIfCond(expr.get('if'))
|
2020-03-17 14:54:41 +03:00
|
|
|
features = self._make_features(expr.get('features'), info)
|
2019-10-18 10:43:44 +03:00
|
|
|
if isinstance(base, dict):
|
|
|
|
base = self._make_implicit_object_type(
|
2019-10-24 14:02:30 +03:00
|
|
|
name, info, ifcond,
|
2019-10-18 10:43:44 +03:00
|
|
|
'base', self._make_members(base, info))
|
2021-09-17 17:31:32 +03:00
|
|
|
variants = [
|
|
|
|
self._make_variant(key, value['type'],
|
|
|
|
QAPISchemaIfCond(value.get('if')),
|
|
|
|
info)
|
|
|
|
for (key, value) in data.items()]
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
members: List[QAPISchemaObjectTypeMember] = []
|
2019-10-18 10:43:44 +03:00
|
|
|
self._def_entity(
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
|
2020-03-17 14:54:37 +03:00
|
|
|
base, members,
|
2020-03-17 14:54:43 +03:00
|
|
|
QAPISchemaVariants(
|
2021-09-17 17:31:32 +03:00
|
|
|
tag_name, info, None, variants)))
|
2019-10-18 10:43:44 +03:00
|
|
|
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
def _def_alternate_type(self, expr: QAPIExpression):
|
2019-10-18 10:43:44 +03:00
|
|
|
name = expr['alternate']
|
|
|
|
data = expr['data']
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
assert isinstance(data, dict)
|
2021-08-04 11:30:57 +03:00
|
|
|
ifcond = QAPISchemaIfCond(expr.get('if'))
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
info = expr.info
|
2020-03-17 14:54:41 +03:00
|
|
|
features = self._make_features(expr.get('features'), info)
|
2021-08-04 11:30:57 +03:00
|
|
|
variants = [
|
|
|
|
self._make_variant(key, value['type'],
|
|
|
|
QAPISchemaIfCond(value.get('if')),
|
|
|
|
info)
|
|
|
|
for (key, value) in data.items()]
|
2019-10-18 10:43:44 +03:00
|
|
|
tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
|
|
|
|
self._def_entity(
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
QAPISchemaAlternateType(
|
|
|
|
name, info, expr.doc, ifcond, features,
|
|
|
|
QAPISchemaVariants(None, info, tag_member, variants)))
|
2019-10-18 10:43:44 +03:00
|
|
|
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
def _def_command(self, expr: QAPIExpression):
|
2019-10-18 10:43:44 +03:00
|
|
|
name = expr['command']
|
|
|
|
data = expr.get('data')
|
|
|
|
rets = expr.get('returns')
|
|
|
|
gen = expr.get('gen', True)
|
|
|
|
success_response = expr.get('success-response', True)
|
|
|
|
boxed = expr.get('boxed', False)
|
|
|
|
allow_oob = expr.get('allow-oob', False)
|
|
|
|
allow_preconfig = expr.get('allow-preconfig', False)
|
2020-10-05 18:58:49 +03:00
|
|
|
coroutine = expr.get('coroutine', False)
|
2021-08-04 11:30:57 +03:00
|
|
|
ifcond = QAPISchemaIfCond(expr.get('if'))
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
info = expr.info
|
2020-03-17 14:54:41 +03:00
|
|
|
features = self._make_features(expr.get('features'), info)
|
2019-10-18 10:43:44 +03:00
|
|
|
if isinstance(data, OrderedDict):
|
|
|
|
data = self._make_implicit_object_type(
|
2020-03-17 14:54:37 +03:00
|
|
|
name, info, ifcond,
|
|
|
|
'arg', self._make_members(data, info))
|
2019-10-18 10:43:44 +03:00
|
|
|
if isinstance(rets, list):
|
|
|
|
assert len(rets) == 1
|
|
|
|
rets = self._make_array_type(rets[0], info)
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
|
|
|
|
features, data, rets,
|
2019-10-18 10:43:44 +03:00
|
|
|
gen, success_response,
|
2020-10-05 18:58:49 +03:00
|
|
|
boxed, allow_oob, allow_preconfig,
|
|
|
|
coroutine))
|
2019-10-18 10:43:44 +03:00
|
|
|
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
def _def_event(self, expr: QAPIExpression):
|
2019-10-18 10:43:44 +03:00
|
|
|
name = expr['event']
|
|
|
|
data = expr.get('data')
|
|
|
|
boxed = expr.get('boxed', False)
|
2021-08-04 11:30:57 +03:00
|
|
|
ifcond = QAPISchemaIfCond(expr.get('if'))
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
info = expr.info
|
2020-03-17 14:54:41 +03:00
|
|
|
features = self._make_features(expr.get('features'), info)
|
2019-10-18 10:43:44 +03:00
|
|
|
if isinstance(data, OrderedDict):
|
|
|
|
data = self._make_implicit_object_type(
|
2020-03-17 14:54:37 +03:00
|
|
|
name, info, ifcond,
|
|
|
|
'arg', self._make_members(data, info))
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
|
|
|
|
features, data, boxed))
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def _def_exprs(self, exprs):
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
for expr in exprs:
|
2019-10-18 10:43:44 +03:00
|
|
|
if 'enum' in expr:
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_enum_type(expr)
|
2019-10-18 10:43:44 +03:00
|
|
|
elif 'struct' in expr:
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_struct_type(expr)
|
2019-10-18 10:43:44 +03:00
|
|
|
elif 'union' in expr:
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_union_type(expr)
|
2019-10-18 10:43:44 +03:00
|
|
|
elif 'alternate' in expr:
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_alternate_type(expr)
|
2019-10-18 10:43:44 +03:00
|
|
|
elif 'command' in expr:
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_command(expr)
|
2019-10-18 10:43:44 +03:00
|
|
|
elif 'event' in expr:
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_event(expr)
|
2019-10-18 10:43:44 +03:00
|
|
|
elif 'include' in expr:
|
qapi/parser: add QAPIExpression type
This patch creates a new type, QAPIExpression, which represents a parsed
expression complete with QAPIDoc and QAPISourceInfo.
This patch turns parser.exprs into a list of QAPIExpression instead,
and adjusts expr.py to match.
This allows the types we specify in parser.py to be "remembered" all the
way through expr.py and into schema.py. Several assertions around
packing and unpacking this data can be removed as a result.
It also corrects a harmless typing error. Before the patch,
check_exprs() allegedly takes a List[_JSONObject]. It actually takes
a list of dicts of the form
{'expr': E, 'info': I, 'doc': D}
where E is of type _ExprValue, I is of type QAPISourceInfo, and D is
of type QAPIDoc. Key 'doc' is optional. This is not a _JSONObject!
Passes type checking anyway, because _JSONObject is Dict[str, object].
Signed-off-by: John Snow <jsnow@redhat.com>
Message-Id: <20230215000011.1725012-5-jsnow@redhat.com>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
[Commit message amended to point out the typing fix]
2023-02-15 03:00:09 +03:00
|
|
|
self._def_include(expr)
|
2019-10-18 10:43:44 +03:00
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
|
|
|
|
def check(self):
|
|
|
|
for ent in self._entity_list:
|
|
|
|
ent.check(self)
|
2019-10-24 14:02:26 +03:00
|
|
|
ent.connect_doc()
|
|
|
|
ent.check_doc()
|
2019-11-20 21:25:49 +03:00
|
|
|
for ent in self._entity_list:
|
|
|
|
ent.set_module(self)
|
2019-10-18 10:43:44 +03:00
|
|
|
|
|
|
|
def visit(self, visitor):
|
|
|
|
visitor.visit_begin(self)
|
2019-11-20 21:25:50 +03:00
|
|
|
for mod in self._module_dict.values():
|
|
|
|
mod.visit(visitor)
|
2019-10-18 10:43:44 +03:00
|
|
|
visitor.visit_end()
|