Include entities don't have names, but we generally expect "entities" to
have names. Reclassify all entities with names as *definitions*, leaving
the nameless include entities as QAPISchemaEntity instances.
This is primarily to help simplify typing around expectations of what
callers expect for properties of an "entity".
Suggested-by: Markus Armbruster <armbru@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
---
scripts/qapi/schema.py | 117 ++++++++++++++++++++++++-----------------
1 file changed, 70 insertions(+), 47 deletions(-)
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index b7830672e57..e39ed972a80 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -55,14 +55,14 @@ def is_present(self):
class QAPISchemaEntity:
- meta: Optional[str] = None
+ """
+ QAPISchemaEntity represents all schema entities.
- def __init__(self, name: str, info, doc, ifcond=None, features=None):
- assert name is None or isinstance(name, str)
- for f in features or []:
- assert isinstance(f, QAPISchemaFeature)
- f.set_defined_in(name)
- self.name = name
+ Notably, this includes both named and un-named entities, such as
+ include directives. Entities with names are represented by the
+ more specific sub-class `QAPISchemaDefinition` instead.
+ """
+ def __init__(self, info):
self._module = None
# For explicitly defined entities, info points to the (explicit)
# definition. For builtins (and their arrays), info is None.
@@ -70,14 +70,50 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
# triggered the implicit definition (there may be more than one
# such place).
self.info = info
+ self._checked = False
+
+ def __repr__(self):
+ return "<%s at 0x%x>" % (type(self).__name__, id(self))
+
+ def check(self, schema):
+ # pylint: disable=unused-argument
+ self._checked = True
+
+ def connect_doc(self, doc=None):
+ pass
+
+ def check_doc(self):
+ pass
+
+ def _set_module(self, schema, info):
+ assert self._checked
+ fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
+ self._module = schema.module_by_fname(fname)
+ self._module.add_entity(self)
+
+ def set_module(self, schema):
+ self._set_module(schema, self.info)
+
+ def visit(self, visitor):
+ # pylint: disable=unused-argument
+ assert self._checked
+
+
+class QAPISchemaDefinition(QAPISchemaEntity):
+ meta: Optional[str] = None
+
+ def __init__(self, name: str, info, doc, ifcond=None, features=None):
+ assert isinstance(name, str)
+ super().__init__(info)
+ for f in features or []:
+ assert isinstance(f, QAPISchemaFeature)
+ f.set_defined_in(name)
+ self.name = name
self.doc = doc
self._ifcond = ifcond or QAPISchemaIfCond()
self.features = features or []
- self._checked = False
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))
@@ -102,15 +138,6 @@ def check_doc(self):
if self.doc:
self.doc.check()
- def _set_module(self, schema, info):
- assert self._checked
- fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
- self._module = schema.module_by_fname(fname)
- self._module.add_entity(self)
-
- def set_module(self, schema):
- self._set_module(schema, self.info)
-
@property
def ifcond(self):
assert self._checked
@@ -119,10 +146,6 @@ def ifcond(self):
def is_implicit(self):
return not self.info
- def visit(self, visitor):
- # pylint: disable=unused-argument
- assert self._checked
-
def describe(self):
assert self.meta
return "%s '%s'" % (self.meta, self.name)
@@ -222,7 +245,7 @@ def visit(self, visitor):
class QAPISchemaInclude(QAPISchemaEntity):
def __init__(self, sub_module, info):
- super().__init__(None, info, None)
+ super().__init__(info)
self._sub_module = sub_module
def visit(self, visitor):
@@ -230,7 +253,7 @@ def visit(self, visitor):
visitor.visit_include(self._sub_module.name, self.info)
-class QAPISchemaType(QAPISchemaEntity):
+class QAPISchemaType(QAPISchemaDefinition):
# Return the C type for common use.
# For the types we commonly box, this is a pointer type.
def c_type(self):
@@ -801,7 +824,7 @@ def __init__(self, name, info, typ, ifcond=None):
super().__init__(name, info, typ, False, ifcond)
-class QAPISchemaCommand(QAPISchemaEntity):
+class QAPISchemaCommand(QAPISchemaDefinition):
meta = 'command'
def __init__(self, name, info, doc, ifcond, features,
@@ -872,7 +895,7 @@ def visit(self, visitor):
self.coroutine)
-class QAPISchemaEvent(QAPISchemaEntity):
+class QAPISchemaEvent(QAPISchemaDefinition):
meta = 'event'
def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
@@ -943,11 +966,12 @@ def __init__(self, fname):
self.check()
def _def_entity(self, ent):
+ self._entity_list.append(ent)
+
+ def _def_definition(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
+ self._def_entity(ent)
# 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)
@@ -1001,7 +1025,7 @@ def _def_include(self, expr: QAPIExpression):
QAPISchemaInclude(self._make_module(include), expr.info))
def _def_builtin_type(self, name, json_type, c_type):
- self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
+ self._def_definition(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
@@ -1027,15 +1051,15 @@ def _def_predefineds(self):
self._def_builtin_type(*t)
self.the_empty_object_type = QAPISchemaObjectType(
'q_empty', None, None, None, None, None, [], None)
- self._def_entity(self.the_empty_object_type)
+ self._def_definition(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)
- self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
- qtype_values, 'QTYPE'))
+ self._def_definition(QAPISchemaEnumType('QType', None, None, None,
+ None, qtype_values, 'QTYPE'))
def _make_features(self, features, info):
if features is None:
@@ -1057,7 +1081,7 @@ def _make_enum_members(self, values, info):
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))
+ self._def_definition(QAPISchemaArrayType(name, info, element_type))
return name
def _make_implicit_object_type(self, name, info, ifcond, role, members):
@@ -1072,7 +1096,7 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
# later.
pass
else:
- self._def_entity(QAPISchemaObjectType(
+ self._def_definition(QAPISchemaObjectType(
name, info, None, ifcond, None, None, members, None))
return name
@@ -1083,7 +1107,7 @@ def _def_enum_type(self, expr: QAPIExpression):
ifcond = QAPISchemaIfCond(expr.get('if'))
info = expr.info
features = self._make_features(expr.get('features'), info)
- self._def_entity(QAPISchemaEnumType(
+ self._def_definition(QAPISchemaEnumType(
name, info, expr.doc, ifcond, features,
self._make_enum_members(data, info), prefix))
@@ -1111,7 +1135,7 @@ def _def_struct_type(self, expr: QAPIExpression):
info = expr.info
ifcond = QAPISchemaIfCond(expr.get('if'))
features = self._make_features(expr.get('features'), info)
- self._def_entity(QAPISchemaObjectType(
+ self._def_definition(QAPISchemaObjectType(
name, info, expr.doc, ifcond, features, base,
self._make_members(data, info),
None))
@@ -1141,7 +1165,7 @@ def _def_union_type(self, expr: QAPIExpression):
info)
for (key, value) in data.items()]
members: List[QAPISchemaObjectTypeMember] = []
- self._def_entity(
+ self._def_definition(
QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
base, members,
QAPISchemaVariants(
@@ -1160,7 +1184,7 @@ def _def_alternate_type(self, expr: QAPIExpression):
info)
for (key, value) in data.items()]
tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
- self._def_entity(
+ self._def_definition(
QAPISchemaAlternateType(
name, info, expr.doc, ifcond, features,
QAPISchemaVariants(None, info, tag_member, variants)))
@@ -1185,11 +1209,10 @@ def _def_command(self, expr: QAPIExpression):
if isinstance(rets, list):
assert len(rets) == 1
rets = self._make_array_type(rets[0], info)
- self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
- features, data, rets,
- gen, success_response,
- boxed, allow_oob, allow_preconfig,
- coroutine))
+ self._def_definition(
+ QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
+ rets, gen, success_response, boxed, allow_oob,
+ allow_preconfig, coroutine))
def _def_event(self, expr: QAPIExpression):
name = expr['event']
@@ -1202,8 +1225,8 @@ def _def_event(self, expr: QAPIExpression):
data = self._make_implicit_object_type(
name, info, ifcond,
'arg', self._make_members(data, info))
- self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
- features, data, boxed))
+ self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
+ features, data, boxed))
def _def_exprs(self, exprs):
for expr in exprs:
--
2.43.0
John Snow <jsnow@redhat.com> writes:
> Include entities don't have names, but we generally expect "entities" to
> have names. Reclassify all entities with names as *definitions*, leaving
> the nameless include entities as QAPISchemaEntity instances.
>
> This is primarily to help simplify typing around expectations of what
> callers expect for properties of an "entity".
>
> Suggested-by: Markus Armbruster <armbru@redhat.com>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
> scripts/qapi/schema.py | 117 ++++++++++++++++++++++++-----------------
> 1 file changed, 70 insertions(+), 47 deletions(-)
>
> diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> index b7830672e57..e39ed972a80 100644
> --- a/scripts/qapi/schema.py
> +++ b/scripts/qapi/schema.py
> @@ -55,14 +55,14 @@ def is_present(self):
>
>
> class QAPISchemaEntity:
> - meta: Optional[str] = None
> + """
> + QAPISchemaEntity represents all schema entities.
>
> - def __init__(self, name: str, info, doc, ifcond=None, features=None):
> - assert name is None or isinstance(name, str)
> - for f in features or []:
> - assert isinstance(f, QAPISchemaFeature)
> - f.set_defined_in(name)
> - self.name = name
> + Notably, this includes both named and un-named entities, such as
> + include directives. Entities with names are represented by the
> + more specific sub-class `QAPISchemaDefinition` instead.
> + """
Hmm. What about:
"""
A schema entity.
This is either a directive, such as include, or a definition.
The latter use sub-class `QAPISchemaDefinition`.
"""
> + def __init__(self, info):
> self._module = None
> # For explicitly defined entities, info points to the (explicit)
> # definition. For builtins (and their arrays), info is None.
> @@ -70,14 +70,50 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
> # triggered the implicit definition (there may be more than one
> # such place).
> self.info = info
> + self._checked = False
> +
> + def __repr__(self):
> + return "<%s at 0x%x>" % (type(self).__name__, id(self))
> +
> + def check(self, schema):
> + # pylint: disable=unused-argument
> + self._checked = True
> +
> + def connect_doc(self, doc=None):
> + pass
> +
> + def check_doc(self):
> + pass
> +
> + def _set_module(self, schema, info):
> + assert self._checked
> + fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
> + self._module = schema.module_by_fname(fname)
> + self._module.add_entity(self)
> +
> + def set_module(self, schema):
> + self._set_module(schema, self.info)
> +
> + def visit(self, visitor):
> + # pylint: disable=unused-argument
> + assert self._checked
> +
> +
> +class QAPISchemaDefinition(QAPISchemaEntity):
> + meta: Optional[str] = None
> +
> + def __init__(self, name: str, info, doc, ifcond=None, features=None):
> + assert isinstance(name, str)
> + super().__init__(info)
> + for f in features or []:
> + assert isinstance(f, QAPISchemaFeature)
> + f.set_defined_in(name)
> + self.name = name
> self.doc = doc
> self._ifcond = ifcond or QAPISchemaIfCond()
> self.features = features or []
> - self._checked = False
>
> 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))
>
> @@ -102,15 +138,6 @@ def check_doc(self):
def check(self, schema):
# pylint: disable=unused-argument
assert not self._checked
seen = {}
for f in self.features:
f.check_clash(self.info, seen)
self._checked = True
def connect_doc(self, doc=None):
doc = doc or self.doc
if doc:
for f in self.features:
doc.connect_feature(f)
def check_doc(self):
> if self.doc:
> self.doc.check()
No super().FOO()?
>
> - def _set_module(self, schema, info):
> - assert self._checked
> - fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
> - self._module = schema.module_by_fname(fname)
> - self._module.add_entity(self)
> -
> - def set_module(self, schema):
> - self._set_module(schema, self.info)
> -
> @property
> def ifcond(self):
> assert self._checked
> @@ -119,10 +146,6 @@ def ifcond(self):
> def is_implicit(self):
> return not self.info
>
> - def visit(self, visitor):
> - # pylint: disable=unused-argument
> - assert self._checked
> -
> def describe(self):
> assert self.meta
> return "%s '%s'" % (self.meta, self.name)
> @@ -222,7 +245,7 @@ def visit(self, visitor):
>
> class QAPISchemaInclude(QAPISchemaEntity):
> def __init__(self, sub_module, info):
> - super().__init__(None, info, None)
> + super().__init__(info)
> self._sub_module = sub_module
>
> def visit(self, visitor):
> @@ -230,7 +253,7 @@ def visit(self, visitor):
> visitor.visit_include(self._sub_module.name, self.info)
>
>
> -class QAPISchemaType(QAPISchemaEntity):
> +class QAPISchemaType(QAPISchemaDefinition):
> # Return the C type for common use.
> # For the types we commonly box, this is a pointer type.
> def c_type(self):
> @@ -801,7 +824,7 @@ def __init__(self, name, info, typ, ifcond=None):
> super().__init__(name, info, typ, False, ifcond)
>
>
> -class QAPISchemaCommand(QAPISchemaEntity):
> +class QAPISchemaCommand(QAPISchemaDefinition):
> meta = 'command'
>
> def __init__(self, name, info, doc, ifcond, features,
> @@ -872,7 +895,7 @@ def visit(self, visitor):
> self.coroutine)
>
>
> -class QAPISchemaEvent(QAPISchemaEntity):
> +class QAPISchemaEvent(QAPISchemaDefinition):
> meta = 'event'
>
> def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
> @@ -943,11 +966,12 @@ def __init__(self, fname):
> self.check()
>
> def _def_entity(self, ent):
> + self._entity_list.append(ent)
> +
> + def _def_definition(self, ent):
Name the argument @defn instead of @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
> + self._def_entity(ent)
> # 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)
> @@ -1001,7 +1025,7 @@ def _def_include(self, expr: QAPIExpression):
> QAPISchemaInclude(self._make_module(include), expr.info))
>
> def _def_builtin_type(self, name, json_type, c_type):
> - self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
> + self._def_definition(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
> @@ -1027,15 +1051,15 @@ def _def_predefineds(self):
> self._def_builtin_type(*t)
> self.the_empty_object_type = QAPISchemaObjectType(
> 'q_empty', None, None, None, None, None, [], None)
> - self._def_entity(self.the_empty_object_type)
> + self._def_definition(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)
>
> - self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
> - qtype_values, 'QTYPE'))
> + self._def_definition(QAPISchemaEnumType('QType', None, None, None,
> + None, qtype_values, 'QTYPE'))
The long identifiers squeeze the (also long) argument list against the
right margin. What about:
self._def_definition(QAPISchemaEnumType(
'QType', None, None, None, None, qtype_values, 'QTYPE'))
or
self._def_definition(
QAPISchemaEnumType('QType', None, None, None, None,
qtype_values, 'QTYPE'))
We already use the former style elsewhere, visible below.
You add one in the latter style in the second to last hunk.
Pick one style and stick ot it?
>
> def _make_features(self, features, info):
> if features is None:
> @@ -1057,7 +1081,7 @@ def _make_enum_members(self, values, info):
> 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))
> + self._def_definition(QAPISchemaArrayType(name, info, element_type))
self._def_definition(QAPISchemaArrayType(
name, info, element_type))
or
self._def_definition(
QAPISchemaArrayType(name, info, element_type))
> return name
>
> def _make_implicit_object_type(self, name, info, ifcond, role, members):
> @@ -1072,7 +1096,7 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
> # later.
> pass
> else:
> - self._def_entity(QAPISchemaObjectType(
> + self._def_definition(QAPISchemaObjectType(
> name, info, None, ifcond, None, None, members, None))
> return name
>
> @@ -1083,7 +1107,7 @@ def _def_enum_type(self, expr: QAPIExpression):
> ifcond = QAPISchemaIfCond(expr.get('if'))
> info = expr.info
> features = self._make_features(expr.get('features'), info)
> - self._def_entity(QAPISchemaEnumType(
> + self._def_definition(QAPISchemaEnumType(
> name, info, expr.doc, ifcond, features,
> self._make_enum_members(data, info), prefix))
>
> @@ -1111,7 +1135,7 @@ def _def_struct_type(self, expr: QAPIExpression):
> info = expr.info
> ifcond = QAPISchemaIfCond(expr.get('if'))
> features = self._make_features(expr.get('features'), info)
> - self._def_entity(QAPISchemaObjectType(
> + self._def_definition(QAPISchemaObjectType(
> name, info, expr.doc, ifcond, features, base,
> self._make_members(data, info),
> None))
> @@ -1141,7 +1165,7 @@ def _def_union_type(self, expr: QAPIExpression):
> info)
> for (key, value) in data.items()]
> members: List[QAPISchemaObjectTypeMember] = []
> - self._def_entity(
> + self._def_definition(
> QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
> base, members,
> QAPISchemaVariants(
> @@ -1160,7 +1184,7 @@ def _def_alternate_type(self, expr: QAPIExpression):
> info)
> for (key, value) in data.items()]
> tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
> - self._def_entity(
> + self._def_definition(
> QAPISchemaAlternateType(
> name, info, expr.doc, ifcond, features,
> QAPISchemaVariants(None, info, tag_member, variants)))
> @@ -1185,11 +1209,10 @@ def _def_command(self, expr: QAPIExpression):
> if isinstance(rets, list):
> assert len(rets) == 1
> rets = self._make_array_type(rets[0], info)
> - self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
> - features, data, rets,
> - gen, success_response,
> - boxed, allow_oob, allow_preconfig,
> - coroutine))
> + self._def_definition(
> + QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
> + rets, gen, success_response, boxed, allow_oob,
> + allow_preconfig, coroutine))
>
> def _def_event(self, expr: QAPIExpression):
> name = expr['event']
> @@ -1202,8 +1225,8 @@ def _def_event(self, expr: QAPIExpression):
> data = self._make_implicit_object_type(
> name, info, ifcond,
> 'arg', self._make_members(data, info))
> - self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
> - features, data, boxed))
> + self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
> + features, data, boxed))
>
> def _def_exprs(self, exprs):
> for expr in exprs:
Slightly more code, but with slightly fewer conditionals. Feels a bit
cleaner.
On Mon, Jan 15, 2024 at 8:16 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> John Snow <jsnow@redhat.com> writes:
>
> > Include entities don't have names, but we generally expect "entities" to
> > have names. Reclassify all entities with names as *definitions*, leaving
> > the nameless include entities as QAPISchemaEntity instances.
> >
> > This is primarily to help simplify typing around expectations of what
> > callers expect for properties of an "entity".
> >
> > Suggested-by: Markus Armbruster <armbru@redhat.com>
> > Signed-off-by: John Snow <jsnow@redhat.com>
> > ---
> > scripts/qapi/schema.py | 117 ++++++++++++++++++++++++-----------------
> > 1 file changed, 70 insertions(+), 47 deletions(-)
> >
> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> > index b7830672e57..e39ed972a80 100644
> > --- a/scripts/qapi/schema.py
> > +++ b/scripts/qapi/schema.py
> > @@ -55,14 +55,14 @@ def is_present(self):
> >
> >
> > class QAPISchemaEntity:
> > - meta: Optional[str] = None
> > + """
> > + QAPISchemaEntity represents all schema entities.
> >
> > - def __init__(self, name: str, info, doc, ifcond=None, features=None):
> > - assert name is None or isinstance(name, str)
> > - for f in features or []:
> > - assert isinstance(f, QAPISchemaFeature)
> > - f.set_defined_in(name)
> > - self.name = name
> > + Notably, this includes both named and un-named entities, such as
> > + include directives. Entities with names are represented by the
> > + more specific sub-class `QAPISchemaDefinition` instead.
> > + """
>
> Hmm. What about:
>
> """
> A schema entity.
>
> This is either a directive, such as include, or a definition.
> The latter use sub-class `QAPISchemaDefinition`.
> """
>
Sure. Key point was just the cookie crumb to the sub-class.
> > + def __init__(self, info):
> > self._module = None
> > # For explicitly defined entities, info points to the (explicit)
> > # definition. For builtins (and their arrays), info is None.
> > @@ -70,14 +70,50 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
> > # triggered the implicit definition (there may be more than one
> > # such place).
> > self.info = info
> > + self._checked = False
> > +
> > + def __repr__(self):
> > + return "<%s at 0x%x>" % (type(self).__name__, id(self))
> > +
> > + def check(self, schema):
> > + # pylint: disable=unused-argument
> > + self._checked = True
> > +
> > + def connect_doc(self, doc=None):
> > + pass
> > +
> > + def check_doc(self):
> > + pass
> > +
> > + def _set_module(self, schema, info):
> > + assert self._checked
> > + fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
> > + self._module = schema.module_by_fname(fname)
> > + self._module.add_entity(self)
> > +
> > + def set_module(self, schema):
> > + self._set_module(schema, self.info)
> > +
> > + def visit(self, visitor):
> > + # pylint: disable=unused-argument
> > + assert self._checked
> > +
> > +
> > +class QAPISchemaDefinition(QAPISchemaEntity):
> > + meta: Optional[str] = None
> > +
> > + def __init__(self, name: str, info, doc, ifcond=None, features=None):
> > + assert isinstance(name, str)
> > + super().__init__(info)
> > + for f in features or []:
> > + assert isinstance(f, QAPISchemaFeature)
> > + f.set_defined_in(name)
> > + self.name = name
> > self.doc = doc
> > self._ifcond = ifcond or QAPISchemaIfCond()
> > self.features = features or []
> > - self._checked = False
> >
> > 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))
> >
> > @@ -102,15 +138,6 @@ def check_doc(self):
> def check(self, schema):
> # pylint: disable=unused-argument
> assert not self._checked
> seen = {}
> for f in self.features:
> f.check_clash(self.info, seen)
> self._checked = True
>
> def connect_doc(self, doc=None):
> doc = doc or self.doc
> if doc:
> for f in self.features:
> doc.connect_feature(f)
>
> def check_doc(self):
> > if self.doc:
> > self.doc.check()
>
> No super().FOO()?
>
Ah, just an oversight. It worked out because the super method doesn't
do anything anyway. check() and connect_doc() should also use the
super call, probably.
> >
> > - def _set_module(self, schema, info):
> > - assert self._checked
> > - fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
> > - self._module = schema.module_by_fname(fname)
> > - self._module.add_entity(self)
> > -
> > - def set_module(self, schema):
> > - self._set_module(schema, self.info)
> > -
> > @property
> > def ifcond(self):
> > assert self._checked
> > @@ -119,10 +146,6 @@ def ifcond(self):
> > def is_implicit(self):
> > return not self.info
> >
> > - def visit(self, visitor):
> > - # pylint: disable=unused-argument
> > - assert self._checked
> > -
> > def describe(self):
> > assert self.meta
> > return "%s '%s'" % (self.meta, self.name)
> > @@ -222,7 +245,7 @@ def visit(self, visitor):
> >
> > class QAPISchemaInclude(QAPISchemaEntity):
> > def __init__(self, sub_module, info):
> > - super().__init__(None, info, None)
> > + super().__init__(info)
> > self._sub_module = sub_module
> >
> > def visit(self, visitor):
> > @@ -230,7 +253,7 @@ def visit(self, visitor):
> > visitor.visit_include(self._sub_module.name, self.info)
> >
> >
> > -class QAPISchemaType(QAPISchemaEntity):
> > +class QAPISchemaType(QAPISchemaDefinition):
> > # Return the C type for common use.
> > # For the types we commonly box, this is a pointer type.
> > def c_type(self):
> > @@ -801,7 +824,7 @@ def __init__(self, name, info, typ, ifcond=None):
> > super().__init__(name, info, typ, False, ifcond)
> >
> >
> > -class QAPISchemaCommand(QAPISchemaEntity):
> > +class QAPISchemaCommand(QAPISchemaDefinition):
> > meta = 'command'
> >
> > def __init__(self, name, info, doc, ifcond, features,
> > @@ -872,7 +895,7 @@ def visit(self, visitor):
> > self.coroutine)
> >
> >
> > -class QAPISchemaEvent(QAPISchemaEntity):
> > +class QAPISchemaEvent(QAPISchemaDefinition):
> > meta = 'event'
> >
> > def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
> > @@ -943,11 +966,12 @@ def __init__(self, fname):
> > self.check()
> >
> > def _def_entity(self, ent):
> > + self._entity_list.append(ent)
> > +
> > + def _def_definition(self, ent):
>
> Name the argument @defn instead of @ent?
>
OK. (Was aiming for less diffstat, but yes.)
> > # 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
> > + self._def_entity(ent)
> > # 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)
> > @@ -1001,7 +1025,7 @@ def _def_include(self, expr: QAPIExpression):
> > QAPISchemaInclude(self._make_module(include), expr.info))
> >
> > def _def_builtin_type(self, name, json_type, c_type):
> > - self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
> > + self._def_definition(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
> > @@ -1027,15 +1051,15 @@ def _def_predefineds(self):
> > self._def_builtin_type(*t)
> > self.the_empty_object_type = QAPISchemaObjectType(
> > 'q_empty', None, None, None, None, None, [], None)
> > - self._def_entity(self.the_empty_object_type)
> > + self._def_definition(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)
> >
> > - self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
> > - qtype_values, 'QTYPE'))
> > + self._def_definition(QAPISchemaEnumType('QType', None, None, None,
> > + None, qtype_values, 'QTYPE'))
>
> The long identifiers squeeze the (also long) argument list against the
> right margin. What about:
>
> self._def_definition(QAPISchemaEnumType(
> 'QType', None, None, None, None, qtype_values, 'QTYPE'))
This is fine to my eyes.
>
> or
>
> self._def_definition(
> QAPISchemaEnumType('QType', None, None, None, None,
> qtype_values, 'QTYPE'))
>
> We already use the former style elsewhere, visible below.
>
> You add one in the latter style in the second to last hunk.
>
> Pick one style and stick ot it?
Yeah. I might try to run the black formatter in the end and just stick
to that, if you don't mind a bit of churn in exchange for having it be
a bit more mindless. It would be a big hassle to run it at the
beginning of the series now, though... but I'll fix this instance for
now.
>
> >
> > def _make_features(self, features, info):
> > if features is None:
> > @@ -1057,7 +1081,7 @@ def _make_enum_members(self, values, info):
> > 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))
> > + self._def_definition(QAPISchemaArrayType(name, info, element_type))
>
> self._def_definition(QAPISchemaArrayType(
> name, info, element_type))
>
> or
>
> self._def_definition(
> QAPISchemaArrayType(name, info, element_type))
>
OK. (79 columns too long for ya?)
> > return name
> >
> > def _make_implicit_object_type(self, name, info, ifcond, role, members):
> > @@ -1072,7 +1096,7 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
> > # later.
> > pass
> > else:
> > - self._def_entity(QAPISchemaObjectType(
> > + self._def_definition(QAPISchemaObjectType(
> > name, info, None, ifcond, None, None, members, None))
> > return name
> >
> > @@ -1083,7 +1107,7 @@ def _def_enum_type(self, expr: QAPIExpression):
> > ifcond = QAPISchemaIfCond(expr.get('if'))
> > info = expr.info
> > features = self._make_features(expr.get('features'), info)
> > - self._def_entity(QAPISchemaEnumType(
> > + self._def_definition(QAPISchemaEnumType(
> > name, info, expr.doc, ifcond, features,
> > self._make_enum_members(data, info), prefix))
> >
> > @@ -1111,7 +1135,7 @@ def _def_struct_type(self, expr: QAPIExpression):
> > info = expr.info
> > ifcond = QAPISchemaIfCond(expr.get('if'))
> > features = self._make_features(expr.get('features'), info)
> > - self._def_entity(QAPISchemaObjectType(
> > + self._def_definition(QAPISchemaObjectType(
> > name, info, expr.doc, ifcond, features, base,
> > self._make_members(data, info),
> > None))
> > @@ -1141,7 +1165,7 @@ def _def_union_type(self, expr: QAPIExpression):
> > info)
> > for (key, value) in data.items()]
> > members: List[QAPISchemaObjectTypeMember] = []
> > - self._def_entity(
> > + self._def_definition(
> > QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
> > base, members,
> > QAPISchemaVariants(
> > @@ -1160,7 +1184,7 @@ def _def_alternate_type(self, expr: QAPIExpression):
> > info)
> > for (key, value) in data.items()]
> > tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
> > - self._def_entity(
> > + self._def_definition(
> > QAPISchemaAlternateType(
> > name, info, expr.doc, ifcond, features,
> > QAPISchemaVariants(None, info, tag_member, variants)))
> > @@ -1185,11 +1209,10 @@ def _def_command(self, expr: QAPIExpression):
> > if isinstance(rets, list):
> > assert len(rets) == 1
> > rets = self._make_array_type(rets[0], info)
> > - self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
> > - features, data, rets,
> > - gen, success_response,
> > - boxed, allow_oob, allow_preconfig,
> > - coroutine))
> > + self._def_definition(
> > + QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
> > + rets, gen, success_response, boxed, allow_oob,
> > + allow_preconfig, coroutine))
> >
> > def _def_event(self, expr: QAPIExpression):
> > name = expr['event']
> > @@ -1202,8 +1225,8 @@ def _def_event(self, expr: QAPIExpression):
> > data = self._make_implicit_object_type(
> > name, info, ifcond,
> > 'arg', self._make_members(data, info))
> > - self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
> > - features, data, boxed))
> > + self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
> > + features, data, boxed))
> >
> > def _def_exprs(self, exprs):
> > for expr in exprs:
>
> Slightly more code, but with slightly fewer conditionals. Feels a bit
> cleaner.
>
Probably a few more asserts and things that can come out, too. It's
nicer for static types at the expense of more OO boilerplate.
John Snow <jsnow@redhat.com> writes:
> On Mon, Jan 15, 2024 at 8:16 AM Markus Armbruster <armbru@redhat.com> wrote:
>>
>> John Snow <jsnow@redhat.com> writes:
>>
>> > Include entities don't have names, but we generally expect "entities" to
>> > have names. Reclassify all entities with names as *definitions*, leaving
>> > the nameless include entities as QAPISchemaEntity instances.
>> >
>> > This is primarily to help simplify typing around expectations of what
>> > callers expect for properties of an "entity".
>> >
>> > Suggested-by: Markus Armbruster <armbru@redhat.com>
>> > Signed-off-by: John Snow <jsnow@redhat.com>
>> > ---
>> > scripts/qapi/schema.py | 117 ++++++++++++++++++++++++-----------------
>> > 1 file changed, 70 insertions(+), 47 deletions(-)
>> >
>> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
>> > index b7830672e57..e39ed972a80 100644
>> > --- a/scripts/qapi/schema.py
>> > +++ b/scripts/qapi/schema.py
>> > @@ -55,14 +55,14 @@ def is_present(self):
>> >
>> >
>> > class QAPISchemaEntity:
>> > - meta: Optional[str] = None
>> > + """
>> > + QAPISchemaEntity represents all schema entities.
>> >
>> > - def __init__(self, name: str, info, doc, ifcond=None, features=None):
>> > - assert name is None or isinstance(name, str)
>> > - for f in features or []:
>> > - assert isinstance(f, QAPISchemaFeature)
>> > - f.set_defined_in(name)
>> > - self.name = name
>> > + Notably, this includes both named and un-named entities, such as
>> > + include directives. Entities with names are represented by the
>> > + more specific sub-class `QAPISchemaDefinition` instead.
>> > + """
>>
>> Hmm. What about:
>>
>> """
>> A schema entity.
>>
>> This is either a directive, such as include, or a definition.
>> The latter use sub-class `QAPISchemaDefinition`.
Or is it "uses"? Not a native speaker...
>> """
>>
>
> Sure. Key point was just the cookie crumb to the sub-class.
>
>> > + def __init__(self, info):
>> > self._module = None
>> > # For explicitly defined entities, info points to the (explicit)
>> > # definition. For builtins (and their arrays), info is None.
>> > @@ -70,14 +70,50 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
>> > # triggered the implicit definition (there may be more than one
>> > # such place).
>> > self.info = info
>> > + self._checked = False
>> > +
>> > + def __repr__(self):
>> > + return "<%s at 0x%x>" % (type(self).__name__, id(self))
>> > +
>> > + def check(self, schema):
>> > + # pylint: disable=unused-argument
>> > + self._checked = True
>> > +
>> > + def connect_doc(self, doc=None):
>> > + pass
>> > +
>> > + def check_doc(self):
>> > + pass
>> > +
>> > + def _set_module(self, schema, info):
>> > + assert self._checked
>> > + fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
>> > + self._module = schema.module_by_fname(fname)
>> > + self._module.add_entity(self)
>> > +
>> > + def set_module(self, schema):
>> > + self._set_module(schema, self.info)
>> > +
>> > + def visit(self, visitor):
>> > + # pylint: disable=unused-argument
>> > + assert self._checked
>> > +
>> > +
>> > +class QAPISchemaDefinition(QAPISchemaEntity):
>> > + meta: Optional[str] = None
>> > +
>> > + def __init__(self, name: str, info, doc, ifcond=None, features=None):
>> > + assert isinstance(name, str)
>> > + super().__init__(info)
>> > + for f in features or []:
>> > + assert isinstance(f, QAPISchemaFeature)
>> > + f.set_defined_in(name)
>> > + self.name = name
>> > self.doc = doc
>> > self._ifcond = ifcond or QAPISchemaIfCond()
>> > self.features = features or []
>> > - self._checked = False
>> >
>> > 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))
>> >
>> > @@ -102,15 +138,6 @@ def check_doc(self):
>> def check(self, schema):
>> # pylint: disable=unused-argument
>> assert not self._checked
>> seen = {}
>> for f in self.features:
>> f.check_clash(self.info, seen)
>> self._checked = True
>>
>> def connect_doc(self, doc=None):
>> doc = doc or self.doc
>> if doc:
>> for f in self.features:
>> doc.connect_feature(f)
>>
>> def check_doc(self):
>> > if self.doc:
>> > self.doc.check()
>>
>> No super().FOO()?
>
> Ah, just an oversight. It worked out because the super method doesn't
> do anything anyway. check() and connect_doc() should also use the
> super call, probably.
Yes, please; it's a good habit.
>> >
>> > - def _set_module(self, schema, info):
>> > - assert self._checked
>> > - fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
>> > - self._module = schema.module_by_fname(fname)
>> > - self._module.add_entity(self)
>> > -
>> > - def set_module(self, schema):
>> > - self._set_module(schema, self.info)
>> > -
>> > @property
>> > def ifcond(self):
>> > assert self._checked
>> > @@ -119,10 +146,6 @@ def ifcond(self):
>> > def is_implicit(self):
>> > return not self.info
>> >
>> > - def visit(self, visitor):
>> > - # pylint: disable=unused-argument
>> > - assert self._checked
>> > -
>> > def describe(self):
>> > assert self.meta
>> > return "%s '%s'" % (self.meta, self.name)
>> > @@ -222,7 +245,7 @@ def visit(self, visitor):
>> >
>> > class QAPISchemaInclude(QAPISchemaEntity):
>> > def __init__(self, sub_module, info):
>> > - super().__init__(None, info, None)
>> > + super().__init__(info)
>> > self._sub_module = sub_module
>> >
>> > def visit(self, visitor):
>> > @@ -230,7 +253,7 @@ def visit(self, visitor):
>> > visitor.visit_include(self._sub_module.name, self.info)
>> >
>> >
>> > -class QAPISchemaType(QAPISchemaEntity):
>> > +class QAPISchemaType(QAPISchemaDefinition):
>> > # Return the C type for common use.
>> > # For the types we commonly box, this is a pointer type.
>> > def c_type(self):
>> > @@ -801,7 +824,7 @@ def __init__(self, name, info, typ, ifcond=None):
>> > super().__init__(name, info, typ, False, ifcond)
>> >
>> >
>> > -class QAPISchemaCommand(QAPISchemaEntity):
>> > +class QAPISchemaCommand(QAPISchemaDefinition):
>> > meta = 'command'
>> >
>> > def __init__(self, name, info, doc, ifcond, features,
>> > @@ -872,7 +895,7 @@ def visit(self, visitor):
>> > self.coroutine)
>> >
>> >
>> > -class QAPISchemaEvent(QAPISchemaEntity):
>> > +class QAPISchemaEvent(QAPISchemaDefinition):
>> > meta = 'event'
>> >
>> > def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
>> > @@ -943,11 +966,12 @@ def __init__(self, fname):
>> > self.check()
>> >
>> > def _def_entity(self, ent):
>> > + self._entity_list.append(ent)
>> > +
>> > + def _def_definition(self, ent):
>>
>> Name the argument @defn instead of @ent?
>
> OK. (Was aiming for less diffstat, but yes.)
Yes, the churn from the rename is annoying. More annoying than the now
odd name? Not sure.
>> > # 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
>> > + self._def_entity(ent)
>> > # 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)
>> > @@ -1001,7 +1025,7 @@ def _def_include(self, expr: QAPIExpression):
>> > QAPISchemaInclude(self._make_module(include), expr.info))
>> >
>> > def _def_builtin_type(self, name, json_type, c_type):
>> > - self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
>> > + self._def_definition(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
>> > @@ -1027,15 +1051,15 @@ def _def_predefineds(self):
>> > self._def_builtin_type(*t)
>> > self.the_empty_object_type = QAPISchemaObjectType(
>> > 'q_empty', None, None, None, None, None, [], None)
>> > - self._def_entity(self.the_empty_object_type)
>> > + self._def_definition(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)
>> >
>> > - self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
>> > - qtype_values, 'QTYPE'))
>> > + self._def_definition(QAPISchemaEnumType('QType', None, None, None,
>> > + None, qtype_values, 'QTYPE'))
>>
>> The long identifiers squeeze the (also long) argument list against the
>> right margin. What about:
>>
>> self._def_definition(QAPISchemaEnumType(
>> 'QType', None, None, None, None, qtype_values, 'QTYPE'))
>
> This is fine to my eyes.
>
>>
>> or
>>
>> self._def_definition(
>> QAPISchemaEnumType('QType', None, None, None, None,
>> qtype_values, 'QTYPE'))
>>
>> We already use the former style elsewhere, visible below.
>>
>> You add one in the latter style in the second to last hunk.
>>
>> Pick one style and stick ot it?
>
> Yeah. I might try to run the black formatter in the end and just stick
> to that, if you don't mind a bit of churn in exchange for having it be
> a bit more mindless. It would be a big hassle to run it at the
> beginning of the series now, though... but I'll fix this instance for
> now.
I gave black a quick try a few months ago: the churn is massive.
Not sure it's worth it.
>> > def _make_features(self, features, info):
>> > if features is None:
>> > @@ -1057,7 +1081,7 @@ def _make_enum_members(self, values, info):
>> > 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))
>> > + self._def_definition(QAPISchemaArrayType(name, info, element_type))
>>
>> self._def_definition(QAPISchemaArrayType(
>> name, info, element_type))
>>
>> or
>>
>> self._def_definition(
>> QAPISchemaArrayType(name, info, element_type))
>>
>
> OK. (79 columns too long for ya?)
I generally aim for 70, accept 75 without thought, and longer when the
alternative looks worse. Deeply indented lines get a bit of extra
leeway.
>> > return name
>> >
>> > def _make_implicit_object_type(self, name, info, ifcond, role, members):
>> > @@ -1072,7 +1096,7 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
>> > # later.
>> > pass
>> > else:
>> > - self._def_entity(QAPISchemaObjectType(
>> > + self._def_definition(QAPISchemaObjectType(
>> > name, info, None, ifcond, None, None, members, None))
>> > return name
>> >
>> > @@ -1083,7 +1107,7 @@ def _def_enum_type(self, expr: QAPIExpression):
>> > ifcond = QAPISchemaIfCond(expr.get('if'))
>> > info = expr.info
>> > features = self._make_features(expr.get('features'), info)
>> > - self._def_entity(QAPISchemaEnumType(
>> > + self._def_definition(QAPISchemaEnumType(
>> > name, info, expr.doc, ifcond, features,
>> > self._make_enum_members(data, info), prefix))
>> >
>> > @@ -1111,7 +1135,7 @@ def _def_struct_type(self, expr: QAPIExpression):
>> > info = expr.info
>> > ifcond = QAPISchemaIfCond(expr.get('if'))
>> > features = self._make_features(expr.get('features'), info)
>> > - self._def_entity(QAPISchemaObjectType(
>> > + self._def_definition(QAPISchemaObjectType(
>> > name, info, expr.doc, ifcond, features, base,
>> > self._make_members(data, info),
>> > None))
>> > @@ -1141,7 +1165,7 @@ def _def_union_type(self, expr: QAPIExpression):
>> > info)
>> > for (key, value) in data.items()]
>> > members: List[QAPISchemaObjectTypeMember] = []
>> > - self._def_entity(
>> > + self._def_definition(
>> > QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
>> > base, members,
>> > QAPISchemaVariants(
>> > @@ -1160,7 +1184,7 @@ def _def_alternate_type(self, expr: QAPIExpression):
>> > info)
>> > for (key, value) in data.items()]
>> > tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
>> > - self._def_entity(
>> > + self._def_definition(
>> > QAPISchemaAlternateType(
>> > name, info, expr.doc, ifcond, features,
>> > QAPISchemaVariants(None, info, tag_member, variants)))
>> > @@ -1185,11 +1209,10 @@ def _def_command(self, expr: QAPIExpression):
>> > if isinstance(rets, list):
>> > assert len(rets) == 1
>> > rets = self._make_array_type(rets[0], info)
>> > - self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
>> > - features, data, rets,
>> > - gen, success_response,
>> > - boxed, allow_oob, allow_preconfig,
>> > - coroutine))
>> > + self._def_definition(
>> > + QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
>> > + rets, gen, success_response, boxed, allow_oob,
>> > + allow_preconfig, coroutine))
>> >
>> > def _def_event(self, expr: QAPIExpression):
>> > name = expr['event']
>> > @@ -1202,8 +1225,8 @@ def _def_event(self, expr: QAPIExpression):
>> > data = self._make_implicit_object_type(
>> > name, info, ifcond,
>> > 'arg', self._make_members(data, info))
>> > - self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
>> > - features, data, boxed))
>> > + self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
>> > + features, data, boxed))
>> >
>> > def _def_exprs(self, exprs):
>> > for expr in exprs:
>>
>> Slightly more code, but with slightly fewer conditionals. Feels a bit
>> cleaner.
>>
>
> Probably a few more asserts and things that can come out, too. It's
> nicer for static types at the expense of more OO boilerplate.
Let's do it.
On Tue, Jan 16, 2024 at 2:22 AM Markus Armbruster <armbru@redhat.com> wrote:
>
> John Snow <jsnow@redhat.com> writes:
>
> > On Mon, Jan 15, 2024 at 8:16 AM Markus Armbruster <armbru@redhat.com> wrote:
> >>
> >> John Snow <jsnow@redhat.com> writes:
> >>
> >> > Include entities don't have names, but we generally expect "entities" to
> >> > have names. Reclassify all entities with names as *definitions*, leaving
> >> > the nameless include entities as QAPISchemaEntity instances.
> >> >
> >> > This is primarily to help simplify typing around expectations of what
> >> > callers expect for properties of an "entity".
> >> >
> >> > Suggested-by: Markus Armbruster <armbru@redhat.com>
> >> > Signed-off-by: John Snow <jsnow@redhat.com>
> >> > ---
> >> > scripts/qapi/schema.py | 117 ++++++++++++++++++++++++-----------------
> >> > 1 file changed, 70 insertions(+), 47 deletions(-)
> >> >
> >> > diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
> >> > index b7830672e57..e39ed972a80 100644
> >> > --- a/scripts/qapi/schema.py
> >> > +++ b/scripts/qapi/schema.py
> >> > @@ -55,14 +55,14 @@ def is_present(self):
> >> >
> >> >
> >> > class QAPISchemaEntity:
> >> > - meta: Optional[str] = None
> >> > + """
> >> > + QAPISchemaEntity represents all schema entities.
> >> >
> >> > - def __init__(self, name: str, info, doc, ifcond=None, features=None):
> >> > - assert name is None or isinstance(name, str)
> >> > - for f in features or []:
> >> > - assert isinstance(f, QAPISchemaFeature)
> >> > - f.set_defined_in(name)
> >> > - self.name = name
> >> > + Notably, this includes both named and un-named entities, such as
> >> > + include directives. Entities with names are represented by the
> >> > + more specific sub-class `QAPISchemaDefinition` instead.
> >> > + """
> >>
> >> Hmm. What about:
> >>
> >> """
> >> A schema entity.
> >>
> >> This is either a directive, such as include, or a definition.
> >> The latter use sub-class `QAPISchemaDefinition`.
>
> Or is it "uses"? Not a native speaker...
American English: collective nouns are treated as singular ("Congress
has passed a law")
British English: collective nouns are treated as plural ("Parliament
have enacted a new regulation")
>
> >> """
> >>
> >
> > Sure. Key point was just the cookie crumb to the sub-class.
> >
> >> > + def __init__(self, info):
> >> > self._module = None
> >> > # For explicitly defined entities, info points to the (explicit)
> >> > # definition. For builtins (and their arrays), info is None.
> >> > @@ -70,14 +70,50 @@ def __init__(self, name: str, info, doc, ifcond=None, features=None):
> >> > # triggered the implicit definition (there may be more than one
> >> > # such place).
> >> > self.info = info
> >> > + self._checked = False
> >> > +
> >> > + def __repr__(self):
> >> > + return "<%s at 0x%x>" % (type(self).__name__, id(self))
> >> > +
> >> > + def check(self, schema):
> >> > + # pylint: disable=unused-argument
> >> > + self._checked = True
> >> > +
> >> > + def connect_doc(self, doc=None):
> >> > + pass
> >> > +
> >> > + def check_doc(self):
> >> > + pass
> >> > +
> >> > + def _set_module(self, schema, info):
> >> > + assert self._checked
> >> > + fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
> >> > + self._module = schema.module_by_fname(fname)
> >> > + self._module.add_entity(self)
> >> > +
> >> > + def set_module(self, schema):
> >> > + self._set_module(schema, self.info)
> >> > +
> >> > + def visit(self, visitor):
> >> > + # pylint: disable=unused-argument
> >> > + assert self._checked
> >> > +
> >> > +
> >> > +class QAPISchemaDefinition(QAPISchemaEntity):
> >> > + meta: Optional[str] = None
> >> > +
> >> > + def __init__(self, name: str, info, doc, ifcond=None, features=None):
> >> > + assert isinstance(name, str)
> >> > + super().__init__(info)
> >> > + for f in features or []:
> >> > + assert isinstance(f, QAPISchemaFeature)
> >> > + f.set_defined_in(name)
> >> > + self.name = name
> >> > self.doc = doc
> >> > self._ifcond = ifcond or QAPISchemaIfCond()
> >> > self.features = features or []
> >> > - self._checked = False
> >> >
> >> > 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))
> >> >
> >> > @@ -102,15 +138,6 @@ def check_doc(self):
> >> def check(self, schema):
> >> # pylint: disable=unused-argument
> >> assert not self._checked
> >> seen = {}
> >> for f in self.features:
> >> f.check_clash(self.info, seen)
> >> self._checked = True
> >>
> >> def connect_doc(self, doc=None):
> >> doc = doc or self.doc
> >> if doc:
> >> for f in self.features:
> >> doc.connect_feature(f)
> >>
> >> def check_doc(self):
> >> > if self.doc:
> >> > self.doc.check()
> >>
> >> No super().FOO()?
> >
> > Ah, just an oversight. It worked out because the super method doesn't
> > do anything anyway. check() and connect_doc() should also use the
> > super call, probably.
>
> Yes, please; it's a good habit.
>
> >> >
> >> > - def _set_module(self, schema, info):
> >> > - assert self._checked
> >> > - fname = info.fname if info else QAPISchemaModule.BUILTIN_MODULE_NAME
> >> > - self._module = schema.module_by_fname(fname)
> >> > - self._module.add_entity(self)
> >> > -
> >> > - def set_module(self, schema):
> >> > - self._set_module(schema, self.info)
> >> > -
> >> > @property
> >> > def ifcond(self):
> >> > assert self._checked
> >> > @@ -119,10 +146,6 @@ def ifcond(self):
> >> > def is_implicit(self):
> >> > return not self.info
> >> >
> >> > - def visit(self, visitor):
> >> > - # pylint: disable=unused-argument
> >> > - assert self._checked
> >> > -
> >> > def describe(self):
> >> > assert self.meta
> >> > return "%s '%s'" % (self.meta, self.name)
> >> > @@ -222,7 +245,7 @@ def visit(self, visitor):
> >> >
> >> > class QAPISchemaInclude(QAPISchemaEntity):
> >> > def __init__(self, sub_module, info):
> >> > - super().__init__(None, info, None)
> >> > + super().__init__(info)
> >> > self._sub_module = sub_module
> >> >
> >> > def visit(self, visitor):
> >> > @@ -230,7 +253,7 @@ def visit(self, visitor):
> >> > visitor.visit_include(self._sub_module.name, self.info)
> >> >
> >> >
> >> > -class QAPISchemaType(QAPISchemaEntity):
> >> > +class QAPISchemaType(QAPISchemaDefinition):
> >> > # Return the C type for common use.
> >> > # For the types we commonly box, this is a pointer type.
> >> > def c_type(self):
> >> > @@ -801,7 +824,7 @@ def __init__(self, name, info, typ, ifcond=None):
> >> > super().__init__(name, info, typ, False, ifcond)
> >> >
> >> >
> >> > -class QAPISchemaCommand(QAPISchemaEntity):
> >> > +class QAPISchemaCommand(QAPISchemaDefinition):
> >> > meta = 'command'
> >> >
> >> > def __init__(self, name, info, doc, ifcond, features,
> >> > @@ -872,7 +895,7 @@ def visit(self, visitor):
> >> > self.coroutine)
> >> >
> >> >
> >> > -class QAPISchemaEvent(QAPISchemaEntity):
> >> > +class QAPISchemaEvent(QAPISchemaDefinition):
> >> > meta = 'event'
> >> >
> >> > def __init__(self, name, info, doc, ifcond, features, arg_type, boxed):
> >> > @@ -943,11 +966,12 @@ def __init__(self, fname):
> >> > self.check()
> >> >
> >> > def _def_entity(self, ent):
> >> > + self._entity_list.append(ent)
> >> > +
> >> > + def _def_definition(self, ent):
> >>
> >> Name the argument @defn instead of @ent?
> >
> > OK. (Was aiming for less diffstat, but yes.)
>
> Yes, the churn from the rename is annoying. More annoying than the now
> odd name? Not sure.
>
> >> > # 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
> >> > + self._def_entity(ent)
> >> > # 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)
> >> > @@ -1001,7 +1025,7 @@ def _def_include(self, expr: QAPIExpression):
> >> > QAPISchemaInclude(self._make_module(include), expr.info))
> >> >
> >> > def _def_builtin_type(self, name, json_type, c_type):
> >> > - self._def_entity(QAPISchemaBuiltinType(name, json_type, c_type))
> >> > + self._def_definition(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
> >> > @@ -1027,15 +1051,15 @@ def _def_predefineds(self):
> >> > self._def_builtin_type(*t)
> >> > self.the_empty_object_type = QAPISchemaObjectType(
> >> > 'q_empty', None, None, None, None, None, [], None)
> >> > - self._def_entity(self.the_empty_object_type)
> >> > + self._def_definition(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)
> >> >
> >> > - self._def_entity(QAPISchemaEnumType('QType', None, None, None, None,
> >> > - qtype_values, 'QTYPE'))
> >> > + self._def_definition(QAPISchemaEnumType('QType', None, None, None,
> >> > + None, qtype_values, 'QTYPE'))
> >>
> >> The long identifiers squeeze the (also long) argument list against the
> >> right margin. What about:
> >>
> >> self._def_definition(QAPISchemaEnumType(
> >> 'QType', None, None, None, None, qtype_values, 'QTYPE'))
> >
> > This is fine to my eyes.
> >
> >>
> >> or
> >>
> >> self._def_definition(
> >> QAPISchemaEnumType('QType', None, None, None, None,
> >> qtype_values, 'QTYPE'))
> >>
> >> We already use the former style elsewhere, visible below.
> >>
> >> You add one in the latter style in the second to last hunk.
> >>
> >> Pick one style and stick ot it?
> >
> > Yeah. I might try to run the black formatter in the end and just stick
> > to that, if you don't mind a bit of churn in exchange for having it be
> > a bit more mindless. It would be a big hassle to run it at the
> > beginning of the series now, though... but I'll fix this instance for
> > now.
>
> I gave black a quick try a few months ago: the churn is massive.
> Not sure it's worth it.
>
You can reduce the churn:
black --line-length=79 --skip-string-normalization schema.py
It still churns a lot, but it's a lot less. I like not having to think
about the formatting, but we can worry about that after this series.
> >> > def _make_features(self, features, info):
> >> > if features is None:
> >> > @@ -1057,7 +1081,7 @@ def _make_enum_members(self, values, info):
> >> > 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))
> >> > + self._def_definition(QAPISchemaArrayType(name, info, element_type))
> >>
> >> self._def_definition(QAPISchemaArrayType(
> >> name, info, element_type))
> >>
> >> or
> >>
> >> self._def_definition(
> >> QAPISchemaArrayType(name, info, element_type))
> >>
> >
> > OK. (79 columns too long for ya?)
>
> I generally aim for 70, accept 75 without thought, and longer when the
> alternative looks worse. Deeply indented lines get a bit of extra
> leeway.
>
> >> > return name
> >> >
> >> > def _make_implicit_object_type(self, name, info, ifcond, role, members):
> >> > @@ -1072,7 +1096,7 @@ def _make_implicit_object_type(self, name, info, ifcond, role, members):
> >> > # later.
> >> > pass
> >> > else:
> >> > - self._def_entity(QAPISchemaObjectType(
> >> > + self._def_definition(QAPISchemaObjectType(
> >> > name, info, None, ifcond, None, None, members, None))
> >> > return name
> >> >
> >> > @@ -1083,7 +1107,7 @@ def _def_enum_type(self, expr: QAPIExpression):
> >> > ifcond = QAPISchemaIfCond(expr.get('if'))
> >> > info = expr.info
> >> > features = self._make_features(expr.get('features'), info)
> >> > - self._def_entity(QAPISchemaEnumType(
> >> > + self._def_definition(QAPISchemaEnumType(
> >> > name, info, expr.doc, ifcond, features,
> >> > self._make_enum_members(data, info), prefix))
> >> >
> >> > @@ -1111,7 +1135,7 @@ def _def_struct_type(self, expr: QAPIExpression):
> >> > info = expr.info
> >> > ifcond = QAPISchemaIfCond(expr.get('if'))
> >> > features = self._make_features(expr.get('features'), info)
> >> > - self._def_entity(QAPISchemaObjectType(
> >> > + self._def_definition(QAPISchemaObjectType(
> >> > name, info, expr.doc, ifcond, features, base,
> >> > self._make_members(data, info),
> >> > None))
> >> > @@ -1141,7 +1165,7 @@ def _def_union_type(self, expr: QAPIExpression):
> >> > info)
> >> > for (key, value) in data.items()]
> >> > members: List[QAPISchemaObjectTypeMember] = []
> >> > - self._def_entity(
> >> > + self._def_definition(
> >> > QAPISchemaObjectType(name, info, expr.doc, ifcond, features,
> >> > base, members,
> >> > QAPISchemaVariants(
> >> > @@ -1160,7 +1184,7 @@ def _def_alternate_type(self, expr: QAPIExpression):
> >> > info)
> >> > for (key, value) in data.items()]
> >> > tag_member = QAPISchemaObjectTypeMember('type', info, 'QType', False)
> >> > - self._def_entity(
> >> > + self._def_definition(
> >> > QAPISchemaAlternateType(
> >> > name, info, expr.doc, ifcond, features,
> >> > QAPISchemaVariants(None, info, tag_member, variants)))
> >> > @@ -1185,11 +1209,10 @@ def _def_command(self, expr: QAPIExpression):
> >> > if isinstance(rets, list):
> >> > assert len(rets) == 1
> >> > rets = self._make_array_type(rets[0], info)
> >> > - self._def_entity(QAPISchemaCommand(name, info, expr.doc, ifcond,
> >> > - features, data, rets,
> >> > - gen, success_response,
> >> > - boxed, allow_oob, allow_preconfig,
> >> > - coroutine))
> >> > + self._def_definition(
> >> > + QAPISchemaCommand(name, info, expr.doc, ifcond, features, data,
> >> > + rets, gen, success_response, boxed, allow_oob,
> >> > + allow_preconfig, coroutine))
> >> >
> >> > def _def_event(self, expr: QAPIExpression):
> >> > name = expr['event']
> >> > @@ -1202,8 +1225,8 @@ def _def_event(self, expr: QAPIExpression):
> >> > data = self._make_implicit_object_type(
> >> > name, info, ifcond,
> >> > 'arg', self._make_members(data, info))
> >> > - self._def_entity(QAPISchemaEvent(name, info, expr.doc, ifcond,
> >> > - features, data, boxed))
> >> > + self._def_definition(QAPISchemaEvent(name, info, expr.doc, ifcond,
> >> > + features, data, boxed))
> >> >
> >> > def _def_exprs(self, exprs):
> >> > for expr in exprs:
> >>
> >> Slightly more code, but with slightly fewer conditionals. Feels a bit
> >> cleaner.
> >>
> >
> > Probably a few more asserts and things that can come out, too. It's
> > nicer for static types at the expense of more OO boilerplate.
>
> Let's do it.
>
© 2016 - 2026 Red Hat, Inc.