With all explicit types exposed, introspection no longer needs a reachability walk. Mark the internal QType enum and inline union bases as non-introspectable where they are created, then collect eligible types and assign names in a single schema pass.
Remove dependency traversal. Implicit argument objects and arrays remain included. Signed-off-by: Marc-André Lureau <[email protected]> --- scripts/qapi/introspect.py | 4 +- scripts/qapi/schema.py | 16 ++++-- scripts/qapi/schema_analysis.py | 110 ++++++---------------------------------- 3 files changed, 29 insertions(+), 101 deletions(-) diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py index e3749d38ced5..99eac5484528 100644 --- a/scripts/qapi/introspect.py +++ b/scripts/qapi/introspect.py @@ -202,7 +202,7 @@ def visit_begin(self, schema: QAPISchema) -> None: self._schema = schema def visit_end(self) -> None: - # visit the types that are actually used + # Visit all introspectable types for typ in self._schema_types.types(): typ.visit(self) # generate C @@ -222,7 +222,7 @@ def visit_end(self) -> None: self._trees = [] def visit_needed(self, entity: QAPISchemaEntity) -> bool: - # Ignore types on first pass; visit_end() will pick up used types + # Ignore types on first pass; visit_end() emits introspectable types return not isinstance(entity, QAPISchemaType) @staticmethod diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py index 8d88b40de2e1..10c5299bcf6d 100644 --- a/scripts/qapi/schema.py +++ b/scripts/qapi/schema.py @@ -323,6 +323,9 @@ def visit(self, visitor: QAPISchemaVisitor) -> None: class QAPISchemaType(QAPISchemaDefinition, ABC): + # Internal types without a separate wire representation opt out. + introspectable: bool = True + # Return the C type for common use. # For the types we commonly box, this is a pointer type. @abstractmethod @@ -1265,8 +1268,10 @@ def _def_predefineds(self) -> None: qtype_values = self._make_enum_members( [{'name': n} for n in qtypes], None) - self._def_definition(QAPISchemaEnumType( - 'QType', None, None, None, None, qtype_values, None)) + qtype = QAPISchemaEnumType( + 'QType', None, None, None, None, qtype_values, None) + qtype.introspectable = False + self._def_definition(qtype) def _make_features( self, @@ -1331,8 +1336,11 @@ def _make_implicit_object_type( # only be a duplicate definition, which will be flagged # later. else: - self._def_definition(QAPISchemaObjectType( - name, info, None, ifcond, None, None, members, None)) + typ = QAPISchemaObjectType( + name, info, None, ifcond, None, None, members, None) + # Inline union bases are flattened into the containing union. + typ.introspectable = role != 'base' + self._def_definition(typ) return name def _def_enum_type(self, expr: QAPIExpression) -> None: diff --git a/scripts/qapi/schema_analysis.py b/scripts/qapi/schema_analysis.py index 1aad2dc80380..03804f60e0e0 100644 --- a/scripts/qapi/schema_analysis.py +++ b/scripts/qapi/schema_analysis.py @@ -10,129 +10,50 @@ Marc-André Lureau <[email protected]> """ -from typing import ( - Dict, - List, - Optional, - Sequence, - Set, -) +from typing import Dict, List, Sequence from .schema import ( QAPISchema, - QAPISchemaAlternatives, QAPISchemaArrayType, - QAPISchemaBranches, QAPISchemaBuiltinType, QAPISchemaEntity, - QAPISchemaFeature, - QAPISchemaIfCond, - QAPISchemaObjectType, - QAPISchemaObjectTypeMember, QAPISchemaType, QAPISchemaVisitor, ) -from .source import QAPISourceInfo class QAPISchemaTypeAnalysis(QAPISchemaVisitor): """Analyze types from a QAPI schema. - Every non-builtin, non-array type is given a masked introspection - name (an integer string). + Every included non-builtin, non-array type is assigned an introspection + name. Names are masked as integer strings unless unmasking is requested. """ def __init__(self, unmask: bool): self._unmask = unmask - self._schema: Optional[QAPISchema] = None - # Ordered list + set: insert during iteration + O(1) check self._types: List[QAPISchemaType] = [] - self._types_set: Set[QAPISchemaType] = set() self._name_map: Dict[str, str] = {} def visit_begin(self, schema: QAPISchema) -> None: - self._schema = schema self._types = [] - self._types_set = set() self._name_map = {} - def visit_end(self) -> None: - assert self._schema is not None - # Discover type dependencies; the list grows as - # visiting each type registers the types it references. - for typ in self._types: - typ.visit(self) - - # Assign masked names now that all introspected types are known. - counter = 0 - for typ in self._types: - if isinstance(typ, (QAPISchemaBuiltinType, QAPISchemaArrayType)): - continue - self._name_map[typ.name] = ( - typ.name if self._unmask else str(counter)) - counter += 1 - def visit_needed(self, entity: QAPISchemaEntity) -> bool: - # Side effect: register all introspectable types now, so that - # visit_end() can traverse them to discover type dependencies. - if isinstance(entity, QAPISchemaType): - if (not entity.is_implicit() or - isinstance(entity, QAPISchemaArrayType)): - self._register_type(entity) - return False - return True - - def visit_command(self, name: str, info: Optional[QAPISourceInfo], - ifcond: QAPISchemaIfCond, - features: List[QAPISchemaFeature], - arg_type: Optional[QAPISchemaObjectType], - ret_type: Optional[QAPISchemaType], gen: bool, - success_response: bool, boxed: bool, allow_oob: bool, - allow_preconfig: bool, coroutine: bool) -> None: - assert self._schema is not None - self._register_type(arg_type or self._schema.the_empty_object_type) - self._register_type(ret_type or self._schema.the_empty_object_type) - - def visit_event(self, name: str, info: Optional[QAPISourceInfo], - ifcond: QAPISchemaIfCond, - features: List[QAPISchemaFeature], - arg_type: Optional[QAPISchemaObjectType], - boxed: bool) -> None: - assert self._schema is not None - self._register_type(arg_type or self._schema.the_empty_object_type) - - def visit_object_type_flat( - self, name: str, info: Optional[QAPISourceInfo], - ifcond: QAPISchemaIfCond, - features: List[QAPISchemaFeature], - members: List[QAPISchemaObjectTypeMember], - branches: Optional[QAPISchemaBranches]) -> None: - for m in members: - self._register_type(m.type) - if branches: - for v in branches.variants: - self._register_type(v.type) - - def visit_alternate_type( - self, name: str, info: Optional[QAPISourceInfo], - ifcond: QAPISchemaIfCond, - features: List[QAPISchemaFeature], - alternatives: QAPISchemaAlternatives) -> None: - for m in alternatives.variants: - self._register_type(m.type) - - def _register_type(self, typ: QAPISchemaType) -> None: - """Record a type for introspection (idempotent).""" - if typ not in self._types_set: - self._types.append(typ) - self._types_set.add(typ) - if isinstance(typ, QAPISchemaArrayType): - self._register_type(typ.element_type) + if isinstance(entity, QAPISchemaType) and entity.introspectable: + self._types.append(entity) + if not isinstance(entity, (QAPISchemaBuiltinType, + QAPISchemaArrayType)): + self._name_map[entity.name] = ( + entity.name if self._unmask else str(len(self._name_map))) + return False def masked_name(self, name: str) -> str: - """Return the masked name for a non-builtin, non-array type.""" + """Return a non-builtin, non-array type's assigned introspection name. + + Return the original name when unmasking is requested. + """ assert name in self._name_map, \ - f"type '{name}' was not registered or is builtin/array" + f"type '{name}' has no assigned introspection name" return self._name_map[name] def introspection_name(self, typ: QAPISchemaType) -> str: @@ -141,7 +62,6 @@ def introspection_name(self, typ: QAPISchemaType) -> str: return typ.name if isinstance(typ, QAPISchemaArrayType): return '[' + self.introspection_name(typ.element_type) + ']' - assert typ in self._types_set return self.masked_name(typ.name) def types(self) -> Sequence[QAPISchemaType]: -- 2.55.0.543.g5ebe2ebe4ea8
