aboutsummaryrefslogtreecommitdiff
path: root/scripts/qapi/common.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/qapi/common.py')
-rw-r--r--scripts/qapi/common.py380
1 files changed, 174 insertions, 206 deletions
diff --git a/scripts/qapi/common.py b/scripts/qapi/common.py
index d61bfdc526..b00caacca3 100644
--- a/scripts/qapi/common.py
+++ b/scripts/qapi/common.py
@@ -103,11 +103,11 @@ class QAPISemError(QAPIError):
class QAPIDoc(object):
"""
- A documentation comment block, either expression or free-form
+ A documentation comment block, either definition or free-form
- Expression documentation blocks consist of
+ Definition documentation blocks consist of
- * a body section: one line naming the expression, followed by an
+ * a body section: one line naming the definition, followed by an
overview (any number of lines)
* argument sections: a description of each argument (for commands
@@ -200,9 +200,9 @@ class QAPIDoc(object):
Process a line of documentation text in the body section.
If this a symbol line and it is the section's first line, this
- is an expression documentation block for that symbol.
+ is a definition documentation block for that symbol.
- If it's an expression documentation block, another symbol line
+ If it's a definition documentation block, another symbol line
begins the argument section for the argument named by it, and
a section tag begins an additional section. Start that
section and append the line to it.
@@ -214,13 +214,13 @@ class QAPIDoc(object):
# recognized, and get silently treated as ordinary text
if not self.symbol and not self.body.text and line.startswith('@'):
if not line.endswith(':'):
- raise QAPIParseError(self._parser, "Line should end with :")
+ raise QAPIParseError(self._parser, "Line should end with ':'")
self.symbol = line[1:-1]
# FIXME invalid names other than the empty string aren't flagged
if not self.symbol:
raise QAPIParseError(self._parser, "Invalid name")
elif self.symbol:
- # This is an expression documentation block
+ # This is a definition documentation block
if name.startswith('@') and name.endswith(':'):
self._append_line = self._append_args_line
self._append_args_line(line)
@@ -428,7 +428,7 @@ class QAPISchemaParser(object):
pragma = expr['pragma']
if not isinstance(pragma, dict):
raise QAPISemError(
- info, "Value of 'pragma' must be a dictionary")
+ info, "Value of 'pragma' must be an object")
for name, value in pragma.items():
self._pragma(name, value, info)
else:
@@ -437,7 +437,7 @@ class QAPISchemaParser(object):
if cur_doc:
if not cur_doc.symbol:
raise QAPISemError(
- cur_doc.info, "Expression documentation required")
+ cur_doc.info, "Definition documentation required")
expr_elem['doc'] = cur_doc
self.exprs.append(expr_elem)
cur_doc = None
@@ -470,7 +470,7 @@ class QAPISchemaParser(object):
else:
fobj = open(incl_fname, 'r')
except IOError as e:
- raise QAPISemError(info, '%s: %s' % (e.strerror, incl_fname))
+ raise QAPISemError(info, "%s: %s" % (e.strerror, incl_fname))
return QAPISchemaParser(fobj, previously_included, info)
def _pragma(self, name, value, info):
@@ -515,57 +515,31 @@ class QAPISchemaParser(object):
elif self.tok in '{}:,[]':
return
elif self.tok == "'":
+ # Note: we accept only printable ASCII
string = ''
esc = False
while True:
ch = self.src[self.cursor]
self.cursor += 1
if ch == '\n':
- raise QAPIParseError(self, 'Missing terminating "\'"')
+ raise QAPIParseError(self, "Missing terminating \"'\"")
if esc:
- if ch == 'b':
- string += '\b'
- elif ch == 'f':
- string += '\f'
- elif ch == 'n':
- string += '\n'
- elif ch == 'r':
- string += '\r'
- elif ch == 't':
- string += '\t'
- elif ch == 'u':
- value = 0
- for _ in range(0, 4):
- ch = self.src[self.cursor]
- self.cursor += 1
- if ch not in '0123456789abcdefABCDEF':
- raise QAPIParseError(self,
- '\\u escape needs 4 '
- 'hex digits')
- value = (value << 4) + int(ch, 16)
- # If Python 2 and 3 didn't disagree so much on
- # how to handle Unicode, then we could allow
- # Unicode string defaults. But most of QAPI is
- # ASCII-only, so we aren't losing much for now.
- if not value or value > 0x7f:
- raise QAPIParseError(self,
- 'For now, \\u escape '
- 'only supports non-zero '
- 'values up to \\u007f')
- string += chr(value)
- elif ch in '\\/\'"':
- string += ch
- else:
+ # Note: we recognize only \\ because we have
+ # no use for funny characters in strings
+ if ch != '\\':
raise QAPIParseError(self,
"Unknown escape \\%s" % ch)
esc = False
elif ch == '\\':
esc = True
+ continue
elif ch == "'":
self.val = string
return
- else:
- string += ch
+ if ord(ch) < 32 or ord(ch) >= 127:
+ raise QAPIParseError(
+ self, "Funny character in string")
+ string += ch
elif self.src.startswith('true', self.pos):
self.val = True
self.cursor += 3
@@ -574,10 +548,6 @@ class QAPISchemaParser(object):
self.val = False
self.cursor += 4
return
- elif self.src.startswith('null', self.pos):
- self.val = None
- self.cursor += 3
- return
elif self.tok == '\n':
if self.cursor == len(self.src):
self.tok = None
@@ -585,7 +555,11 @@ class QAPISchemaParser(object):
self.line += 1
self.line_pos = self.cursor
elif not self.tok.isspace():
- raise QAPIParseError(self, 'Stray "%s"' % self.tok)
+ # Show up to next structural, whitespace or quote
+ # character
+ match = re.match('[^[\\]{}:,\\s\'"]+',
+ self.src[self.cursor-1:])
+ raise QAPIParseError(self, "Stray '%s'" % match.group(0))
def get_members(self):
expr = OrderedDict()
@@ -593,24 +567,24 @@ class QAPISchemaParser(object):
self.accept()
return expr
if self.tok != "'":
- raise QAPIParseError(self, 'Expected string or "}"')
+ raise QAPIParseError(self, "Expected string or '}'")
while True:
key = self.val
self.accept()
if self.tok != ':':
- raise QAPIParseError(self, 'Expected ":"')
+ raise QAPIParseError(self, "Expected ':'")
self.accept()
if key in expr:
- raise QAPIParseError(self, 'Duplicate key "%s"' % key)
+ raise QAPIParseError(self, "Duplicate key '%s'" % key)
expr[key] = self.get_expr(True)
if self.tok == '}':
self.accept()
return expr
if self.tok != ',':
- raise QAPIParseError(self, 'Expected "," or "}"')
+ raise QAPIParseError(self, "Expected ',' or '}'")
self.accept()
if self.tok != "'":
- raise QAPIParseError(self, 'Expected string')
+ raise QAPIParseError(self, "Expected string")
def get_values(self):
expr = []
@@ -618,20 +592,20 @@ class QAPISchemaParser(object):
self.accept()
return expr
if self.tok not in "{['tfn":
- raise QAPIParseError(self, 'Expected "{", "[", "]", string, '
- 'boolean or "null"')
+ raise QAPIParseError(
+ self, "Expected '{', '[', ']', string, boolean or 'null'")
while True:
expr.append(self.get_expr(True))
if self.tok == ']':
self.accept()
return expr
if self.tok != ',':
- raise QAPIParseError(self, 'Expected "," or "]"')
+ raise QAPIParseError(self, "Expected ',' or ']'")
self.accept()
def get_expr(self, nested):
if self.tok != '{' and not nested:
- raise QAPIParseError(self, 'Expected "{"')
+ raise QAPIParseError(self, "Expected '{'")
if self.tok == '{':
self.accept()
expr = self.get_members()
@@ -642,8 +616,8 @@ class QAPISchemaParser(object):
expr = self.val
self.accept()
else:
- raise QAPIParseError(self, 'Expected "{", "[", string, '
- 'boolean or "null"')
+ raise QAPIParseError(
+ self, "Expected '{', '[', string, boolean or 'null'")
return expr
def get_doc(self, info):
@@ -698,26 +672,6 @@ def find_alternate_member_qtype(qapi_type):
return None
-# Return the discriminator enum define if discriminator is specified as an
-# enum type, otherwise return None.
-def discriminator_find_enum_define(expr):
- base = expr.get('base')
- discriminator = expr.get('discriminator')
-
- if not (discriminator and base):
- return None
-
- base_members = find_base_members(base)
- if not base_members:
- return None
-
- discriminator_value = base_members.get(discriminator)
- if not discriminator_value:
- return None
-
- return enum_types.get(discriminator_value['type'])
-
-
# Names must be letters, numbers, -, and _. They must start with letter,
# except for downstream extensions which must start with __RFQDN_.
# Dots are only valid in the downstream extension prefix.
@@ -748,7 +702,7 @@ def check_name(info, source, name, allow_optional=False,
raise QAPISemError(info, "%s uses invalid name '%s'" % (source, name))
-def add_name(name, info, meta, implicit=False):
+def add_name(name, info, meta):
global all_names
check_name(info, "'%s'" % meta, name)
# FIXME should reject names that differ only in '_' vs. '.'
@@ -756,7 +710,7 @@ def add_name(name, info, meta, implicit=False):
if name in all_names:
raise QAPISemError(info, "%s '%s' is already defined"
% (all_names[name], name))
- if not implicit and (name.endswith('Kind') or name.endswith('List')):
+ if name.endswith('Kind') or name.endswith('List'):
raise QAPISemError(info, "%s '%s' should not end in '%s'"
% (meta, name, name[-4:]))
all_names[name] = meta
@@ -768,8 +722,9 @@ def check_if(expr, info):
if not isinstance(ifcond, str):
raise QAPISemError(
info, "'if' condition must be a string or a list of strings")
- if ifcond == '':
- raise QAPISemError(info, "'if' condition '' makes no sense")
+ if ifcond.strip() == '':
+ raise QAPISemError(info, "'if' condition '%s' makes no sense"
+ % ifcond)
ifcond = expr.get('if')
if ifcond is None:
@@ -783,9 +738,8 @@ def check_if(expr, info):
check_if_str(ifcond, info)
-def check_type(info, source, value, allow_array=False,
- allow_dict=False, allow_optional=False,
- allow_metas=[]):
+def check_type(info, source, value,
+ allow_array=False, allow_dict=False, allow_metas=[]):
global all_names
if value is None:
@@ -816,12 +770,12 @@ def check_type(info, source, value, allow_array=False,
if not isinstance(value, OrderedDict):
raise QAPISemError(info,
- "%s should be a dictionary or type name" % source)
+ "%s should be an object or type name" % source)
# value is a dictionary, check that each member is okay
for (key, arg) in value.items():
check_name(info, "Member of %s" % source, key,
- allow_optional=allow_optional)
+ allow_optional=True)
if c_name(key, False) == 'u' or c_name(key, False).startswith('has_'):
raise QAPISemError(info, "Member of %s uses reserved name '%s'"
% (source, key))
@@ -829,6 +783,8 @@ def check_type(info, source, value, allow_array=False,
# an optional argument.
check_known_keys(info, "member '%s' of %s" % (key, source),
arg, ['type'], ['if'])
+ check_if(arg, info)
+ normalize_if(arg)
check_type(info, "Member '%s' of %s" % (key, source),
arg['type'], allow_array=True,
allow_metas=['built-in', 'union', 'alternate', 'struct',
@@ -841,16 +797,16 @@ def check_command(expr, info):
args_meta = ['struct']
if boxed:
- args_meta += ['union', 'alternate']
+ args_meta += ['union']
check_type(info, "'data' for command '%s'" % name,
- expr.get('data'), allow_dict=not boxed, allow_optional=True,
+ expr.get('data'), allow_dict=not boxed,
allow_metas=args_meta)
returns_meta = ['union', 'struct']
if name in returns_whitelist:
returns_meta += ['built-in', 'alternate', 'enum']
check_type(info, "'returns' for command '%s'" % name,
expr.get('returns'), allow_array=True,
- allow_optional=True, allow_metas=returns_meta)
+ allow_metas=returns_meta)
def check_event(expr, info):
@@ -859,9 +815,9 @@ def check_event(expr, info):
meta = ['struct']
if boxed:
- meta += ['union', 'alternate']
+ meta += ['union']
check_type(info, "'data' for event '%s'" % name,
- expr.get('data'), allow_dict=not boxed, allow_optional=True,
+ expr.get('data'), allow_dict=not boxed,
allow_metas=meta)
@@ -879,7 +835,7 @@ def check_union(expr, info):
# With no discriminator it is a simple union.
if discriminator is None:
- enum_define = None
+ enum_values = members.keys()
allow_metas = ['built-in', 'union', 'alternate', 'struct', 'enum']
if base is not None:
raise QAPISemError(info, "Simple union '%s' must not have a base" %
@@ -889,7 +845,7 @@ def check_union(expr, info):
else:
# The object must have a string or dictionary 'base'.
check_type(info, "'base' for union '%s'" % name,
- base, allow_dict=True, allow_optional=True,
+ base, allow_dict=True,
allow_metas=['struct'])
if not base:
raise QAPISemError(info, "Flat union '%s' must have a base"
@@ -904,29 +860,32 @@ def check_union(expr, info):
discriminator_value = base_members.get(discriminator)
if not discriminator_value:
raise QAPISemError(info,
- "Discriminator '%s' is not a member of base "
- "struct '%s'"
- % (discriminator, base))
+ "Discriminator '%s' is not a member of 'base'"
+ % discriminator)
if discriminator_value.get('if'):
- raise QAPISemError(info, 'The discriminator %s.%s for union %s '
- 'must not be conditional' %
- (base, discriminator, name))
+ raise QAPISemError(
+ info,
+ "The discriminator '%s' for union %s must not be conditional"
+ % (discriminator, name))
enum_define = enum_types.get(discriminator_value['type'])
- allow_metas = ['struct']
# Do not allow string discriminator
if not enum_define:
raise QAPISemError(info,
"Discriminator '%s' must be of enumeration "
"type" % discriminator)
+ enum_values = enum_get_names(enum_define)
+ allow_metas = ['struct']
+
+ if (len(enum_values) == 0):
+ raise QAPISemError(info, "Union '%s' has no branches" % name)
- # Check every branch; don't allow an empty union
- if len(members) == 0:
- raise QAPISemError(info, "Union '%s' cannot have empty 'data'" % name)
for (key, value) in members.items():
check_name(info, "Member of union '%s'" % name, key)
check_known_keys(info, "member '%s' of union '%s'" % (key, name),
value, ['type'], ['if'])
+ check_if(value, info)
+ normalize_if(value)
# Each value must name a known type
check_type(info, "Member '%s' of union '%s'" % (key, name),
value['type'],
@@ -934,8 +893,8 @@ def check_union(expr, info):
# If the discriminator names an enum type, then all members
# of 'data' must also be members of the enum type.
- if enum_define:
- if key not in enum_get_names(enum_define):
+ if discriminator is not None:
+ if key not in enum_values:
raise QAPISemError(info,
"Discriminator value '%s' is not found in "
"enum '%s'"
@@ -947,16 +906,16 @@ def check_alternate(expr, info):
members = expr['data']
types_seen = {}
- # Check every branch; require at least two branches
- if len(members) < 2:
+ if len(members) == 0:
raise QAPISemError(info,
- "Alternate '%s' should have at least two branches "
- "in 'data'" % name)
+ "Alternate '%s' cannot have empty 'data'" % name)
for (key, value) in members.items():
check_name(info, "Member of alternate '%s'" % name, key)
check_known_keys(info,
"member '%s' of alternate '%s'" % (key, name),
value, ['type'], ['if'])
+ check_if(value, info)
+ normalize_if(value)
typ = value['type']
# Ensure alternates have no type conflicts.
@@ -999,9 +958,10 @@ def check_enum(expr, info):
"Enum '%s' requires a string for 'prefix'" % name)
for member in members:
- source = "dictionary member of enum '%s'" % name
- check_known_keys(info, source, member, ['name'], ['if'])
+ check_known_keys(info, "member of enum '%s'" % name, member,
+ ['name'], ['if'])
check_if(member, info)
+ normalize_if(member)
check_name(info, "Member of enum '%s'" % name, member['name'],
enum_member=True)
@@ -1012,7 +972,7 @@ def check_struct(expr, info):
features = expr.get('features')
check_type(info, "'data' for struct '%s'" % name, members,
- allow_dict=True, allow_optional=True)
+ allow_dict=True)
check_type(info, "'base' for struct '%s'" % name, expr.get('base'),
allow_metas=['struct'])
@@ -1027,36 +987,35 @@ def check_struct(expr, info):
['name'], ['if'])
check_if(f, info)
+ normalize_if(f)
check_name(info, "Feature of struct %s" % name, f['name'])
-def check_known_keys(info, source, keys, required, optional):
+def check_known_keys(info, source, value, required, optional):
def pprint(elems):
return ', '.join("'" + e + "'" for e in sorted(elems))
- missing = set(required) - set(keys)
+ missing = set(required) - set(value)
if missing:
raise QAPISemError(info, "Key%s %s %s missing from %s"
% ('s' if len(missing) > 1 else '', pprint(missing),
'are' if len(missing) > 1 else 'is', source))
allowed = set(required + optional)
- unknown = set(keys) - allowed
+ unknown = set(value) - allowed
if unknown:
raise QAPISemError(info, "Unknown key%s %s in %s\nValid keys are %s."
% ('s' if len(unknown) > 1 else '', pprint(unknown),
source, pprint(allowed)))
-def check_keys(expr_elem, meta, required, optional=[]):
- expr = expr_elem['expr']
- info = expr_elem['info']
+def check_keys(expr, info, meta, required, optional=[]):
name = expr[meta]
if not isinstance(name, str):
raise QAPISemError(info, "'%s' key must have a string value" % meta)
required = required + [meta]
source = "%s '%s'" % (meta, name)
- check_known_keys(info, source, expr.keys(), required, optional)
+ check_known_keys(info, source, expr, required, optional)
for (key, value) in expr.items():
if key in ['gen', 'success-response'] and value is not False:
raise QAPISemError(info,
@@ -1091,6 +1050,12 @@ def normalize_features(features):
for f in features]
+def normalize_if(expr):
+ ifcond = expr.get('if')
+ if isinstance(ifcond, str):
+ expr['if'] = [ifcond]
+
+
def check_exprs(exprs):
global all_names
@@ -1109,65 +1074,50 @@ def check_exprs(exprs):
if not doc and doc_required:
raise QAPISemError(info,
- "Expression missing documentation comment")
+ "Definition missing documentation comment")
if 'enum' in expr:
meta = 'enum'
- check_keys(expr_elem, 'enum', ['data'], ['if', 'prefix'])
+ check_keys(expr, info, 'enum', ['data'], ['if', 'prefix'])
normalize_enum(expr)
enum_types[expr[meta]] = expr
elif 'union' in expr:
meta = 'union'
- check_keys(expr_elem, 'union', ['data'],
+ check_keys(expr, info, 'union', ['data'],
['base', 'discriminator', 'if'])
normalize_members(expr.get('base'))
normalize_members(expr['data'])
union_types[expr[meta]] = expr
elif 'alternate' in expr:
meta = 'alternate'
- check_keys(expr_elem, 'alternate', ['data'], ['if'])
+ check_keys(expr, info, 'alternate', ['data'], ['if'])
normalize_members(expr['data'])
elif 'struct' in expr:
meta = 'struct'
- check_keys(expr_elem, 'struct', ['data'],
+ check_keys(expr, info, 'struct', ['data'],
['base', 'if', 'features'])
normalize_members(expr['data'])
normalize_features(expr.get('features'))
struct_types[expr[meta]] = expr
elif 'command' in expr:
meta = 'command'
- check_keys(expr_elem, 'command', [],
+ check_keys(expr, info, 'command', [],
['data', 'returns', 'gen', 'success-response',
'boxed', 'allow-oob', 'allow-preconfig', 'if'])
normalize_members(expr.get('data'))
elif 'event' in expr:
meta = 'event'
- check_keys(expr_elem, 'event', [], ['data', 'boxed', 'if'])
+ check_keys(expr, info, 'event', [], ['data', 'boxed', 'if'])
normalize_members(expr.get('data'))
else:
- raise QAPISemError(expr_elem['info'],
- "Expression is missing metatype")
+ raise QAPISemError(info, "Expression is missing metatype")
+ normalize_if(expr)
name = expr[meta]
add_name(name, info, meta)
if doc and doc.symbol != name:
raise QAPISemError(info, "Definition of '%s' follows documentation"
" for '%s'" % (name, doc.symbol))
- # Try again for hidden UnionKind enum
- for expr_elem in exprs:
- expr = expr_elem['expr']
-
- if 'include' in expr:
- continue
- if 'union' in expr and not discriminator_find_enum_define(expr):
- name = '%sKind' % expr['union']
- elif 'alternate' in expr:
- name = '%sKind' % expr['alternate']
- else:
- continue
- enum_types[name] = {'enum': name}
- add_name(name, info, 'enum', implicit=True)
-
# Validate that exprs make sense
for expr_elem in exprs:
expr = expr_elem['expr']
@@ -1201,19 +1151,11 @@ def check_exprs(exprs):
# Schema compiler frontend
#
-def listify_cond(ifcond):
- if not ifcond:
- return []
- if not isinstance(ifcond, list):
- return [ifcond]
- return ifcond
-
-
class QAPISchemaEntity(object):
def __init__(self, name, info, doc, ifcond=None):
assert name is None or isinstance(name, str)
self.name = name
- self.module = None
+ 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
@@ -1221,28 +1163,34 @@ class QAPISchemaEntity(object):
# such place).
self.info = info
self.doc = doc
- self._ifcond = ifcond # self.ifcond is set only after .check()
+ self._ifcond = ifcond or []
+ self._checked = False
def c_name(self):
return c_name(self.name)
def check(self, schema):
- if isinstance(self._ifcond, QAPISchemaType):
- # inherit the condition from a type
- typ = self._ifcond
- typ.check(schema)
- self.ifcond = typ.ifcond
- else:
- self.ifcond = listify_cond(self._ifcond)
+ assert not self._checked
if self.info:
- self.module = os.path.relpath(self.info['file'],
- os.path.dirname(schema.fname))
+ self._module = os.path.relpath(self.info['file'],
+ os.path.dirname(schema.fname))
+ self._checked = True
+
+ @property
+ def ifcond(self):
+ assert self._checked
+ return self._ifcond
+
+ @property
+ def module(self):
+ assert self._checked
+ return self._module
def is_implicit(self):
return not self.info
def visit(self, visitor):
- pass
+ assert self._checked
class QAPISchemaVisitor(object):
@@ -1297,6 +1245,7 @@ class QAPISchemaInclude(QAPISchemaEntity):
self.fname = fname
def visit(self, visitor):
+ QAPISchemaEntity.visit(self, visitor)
visitor.visit_include(self.fname, self.info)
@@ -1361,6 +1310,7 @@ class QAPISchemaBuiltinType(QAPISchemaType):
return self.json_type()
def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
visitor.visit_builtin_type(self.name, self.info, self.json_type())
@@ -1368,7 +1318,7 @@ class QAPISchemaEnumType(QAPISchemaType):
def __init__(self, name, info, doc, ifcond, members, prefix):
QAPISchemaType.__init__(self, name, info, doc, ifcond)
for m in members:
- assert isinstance(m, QAPISchemaMember)
+ assert isinstance(m, QAPISchemaEnumMember)
m.set_owner(name)
assert prefix is None or isinstance(prefix, str)
self.members = members
@@ -1396,6 +1346,7 @@ class QAPISchemaEnumType(QAPISchemaType):
return 'string'
def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
visitor.visit_enum_type(self.name, self.info, self.ifcond,
self.members, self.prefix)
@@ -1411,9 +1362,16 @@ class QAPISchemaArrayType(QAPISchemaType):
QAPISchemaType.check(self, schema)
self.element_type = schema.lookup_type(self._element_type_name)
assert self.element_type
- self.element_type.check(schema)
- self.module = self.element_type.module
- self.ifcond = self.element_type.ifcond
+
+ @property
+ def ifcond(self):
+ assert self._checked
+ return self.element_type.ifcond
+
+ @property
+ def module(self):
+ assert self._checked
+ return self.element_type.module
def is_implicit(self):
return True
@@ -1431,6 +1389,7 @@ class QAPISchemaArrayType(QAPISchemaType):
return 'array of ' + elt_doc_type
def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
visitor.visit_array_type(self.name, self.info, self.ifcond,
self.element_type)
@@ -1460,13 +1419,20 @@ class QAPISchemaObjectType(QAPISchemaType):
self.features = features
def check(self, schema):
- QAPISchemaType.check(self, schema)
- if self.members is False: # check for cycles
+ # 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)
- if self.members:
- return
- self.members = False # mark as being checked
+
+ QAPISchemaType.check(self, schema)
+ assert self._checked and self.members is None
+
seen = OrderedDict()
if self._base_name:
self.base = schema.lookup_type(self._base_name)
@@ -1478,10 +1444,11 @@ class QAPISchemaObjectType(QAPISchemaType):
m.check_clash(self.info, seen)
if self.doc:
self.doc.connect_member(m)
- self.members = seen.values()
+ members = seen.values()
+
if self.variants:
self.variants.check(schema, seen)
- assert self.variants.tag_member in self.members
+ assert self.variants.tag_member in members
self.variants.check_clash(self.info, seen)
# Features are in a name space separate from members
@@ -1492,14 +1459,26 @@ class QAPISchemaObjectType(QAPISchemaType):
if self.doc:
self.doc.check()
+ 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
assert not self.variants # not implemented
for m in self.members:
m.check_clash(info, seen)
+ @property
+ def ifcond(self):
+ assert self._checked
+ if isinstance(self._ifcond, QAPISchemaType):
+ # Simple union wrapper type inherits from wrapped type;
+ # see _make_implicit_object_type()
+ return self._ifcond.ifcond
+ return self._ifcond
+
def is_implicit(self):
# See QAPISchema._make_implicit_object_type(), as well as
# _def_predefineds()
@@ -1524,6 +1503,7 @@ class QAPISchemaObjectType(QAPISchemaType):
return 'object'
def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
visitor.visit_object_type(self.name, self.info, self.ifcond,
self.base, self.local_members, self.variants,
self.features)
@@ -1539,7 +1519,7 @@ class QAPISchemaMember(object):
def __init__(self, name, ifcond=None):
assert isinstance(name, str)
self.name = name
- self.ifcond = listify_cond(ifcond)
+ self.ifcond = ifcond or []
self.owner = None
def set_owner(self, name):
@@ -1579,6 +1559,10 @@ class QAPISchemaMember(object):
return "'%s' %s" % (self.name, self._pretty_owner())
+class QAPISchemaEnumMember(QAPISchemaMember):
+ role = 'value'
+
+
class QAPISchemaFeature(QAPISchemaMember):
role = 'feature'
@@ -1607,7 +1591,6 @@ class QAPISchemaObjectTypeVariants(object):
assert bool(tag_member) != bool(tag_name)
assert (isinstance(tag_name, str) or
isinstance(tag_member, QAPISchemaObjectTypeMember))
- assert len(variants) > 0
for v in variants:
assert isinstance(v, QAPISchemaObjectTypeVariant)
self._tag_name = tag_name
@@ -1688,12 +1671,10 @@ class QAPISchemaAlternateType(QAPISchemaType):
return 'value'
def visit(self, visitor):
+ QAPISchemaType.visit(self, visitor)
visitor.visit_alternate_type(self.name, self.info, self.ifcond,
self.variants)
- def is_empty(self):
- return False
-
class QAPISchemaCommand(QAPISchemaEntity):
def __init__(self, name, info, doc, ifcond, arg_type, ret_type,
@@ -1715,16 +1696,8 @@ class QAPISchemaCommand(QAPISchemaEntity):
QAPISchemaEntity.check(self, schema)
if self._arg_type_name:
self.arg_type = schema.lookup_type(self._arg_type_name)
- assert (isinstance(self.arg_type, QAPISchemaObjectType) or
- isinstance(self.arg_type, QAPISchemaAlternateType))
- self.arg_type.check(schema)
- if self.boxed:
- if self.arg_type.is_empty():
- raise QAPISemError(self.info,
- "Cannot use 'boxed' with empty type")
- else:
- assert not isinstance(self.arg_type, QAPISchemaAlternateType)
- assert not self.arg_type.variants
+ assert isinstance(self.arg_type, QAPISchemaObjectType)
+ assert not self.arg_type.variants or self.boxed
elif self.boxed:
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
if self._ret_type_name:
@@ -1732,6 +1705,7 @@ class QAPISchemaCommand(QAPISchemaEntity):
assert isinstance(self.ret_type, QAPISchemaType)
def visit(self, visitor):
+ QAPISchemaEntity.visit(self, visitor)
visitor.visit_command(self.name, self.info, self.ifcond,
self.arg_type, self.ret_type,
self.gen, self.success_response,
@@ -1751,20 +1725,13 @@ class QAPISchemaEvent(QAPISchemaEntity):
QAPISchemaEntity.check(self, schema)
if self._arg_type_name:
self.arg_type = schema.lookup_type(self._arg_type_name)
- assert (isinstance(self.arg_type, QAPISchemaObjectType) or
- isinstance(self.arg_type, QAPISchemaAlternateType))
- self.arg_type.check(schema)
- if self.boxed:
- if self.arg_type.is_empty():
- raise QAPISemError(self.info,
- "Cannot use 'boxed' with empty type")
- else:
- assert not isinstance(self.arg_type, QAPISchemaAlternateType)
- assert not self.arg_type.variants
+ assert isinstance(self.arg_type, QAPISchemaObjectType)
+ assert not self.arg_type.variants or self.boxed
elif self.boxed:
raise QAPISemError(self.info, "Use of 'boxed' requires 'data'")
def visit(self, visitor):
+ QAPISchemaEntity.visit(self, visitor)
visitor.visit_event(self.name, self.info, self.ifcond,
self.arg_type, self.boxed)
@@ -1853,7 +1820,8 @@ class QAPISchema(object):
return [QAPISchemaFeature(f['name'], f.get('if')) for f in features]
def _make_enum_members(self, values):
- return [QAPISchemaMember(v['name'], v.get('if')) for v in values]
+ return [QAPISchemaEnumMember(v['name'], v.get('if'))
+ for v in values]
def _make_implicit_enum_type(self, name, info, ifcond, values):
# See also QAPISchemaObjectTypeMember._pretty_owner()
@@ -2269,7 +2237,7 @@ const QEnumLookup %(c_name)s_lookup = {
def gen_enum(name, members, prefix=None):
# append automatically generated _MAX value
- enum_members = members + [QAPISchemaMember('_MAX')]
+ enum_members = members + [QAPISchemaEnumMember('_MAX')]
ret = mcgen('''