Rename QAPISchemaUsedTypes to QAPISchemaTypeAnalysis and broaden type
collection: instead of discovering types transitively from commands and
events, register every non-implicit type upfront in visit_needed() and
let visit_end() resolve their dependencies.

This is needed so that QOM property types that are defined in the QAPI
schema but not referenced by any command or event still appear in
query-qmp-schema output, making them introspectable by management tools.

Commands and events still register their (often implicit) argument and
return types, which visit_needed() intentionally skips.

This makes the x86-64 schema grow from 244K to 265K.

Signed-off-by: Marc-André Lureau <[email protected]>
---
 scripts/qapi/backend.py         |  4 +--
 scripts/qapi/introspect.py      |  6 ++---
 scripts/qapi/schema_analysis.py | 55 +++++++++++++++++++++--------------------
 3 files changed, 33 insertions(+), 32 deletions(-)

diff --git a/scripts/qapi/backend.py b/scripts/qapi/backend.py
index 59329965890f..24717be48c3c 100644
--- a/scripts/qapi/backend.py
+++ b/scripts/qapi/backend.py
@@ -8,7 +8,7 @@
 from .features import gen_features
 from .introspect import gen_introspect
 from .schema import QAPISchema
-from .schema_analysis import QAPISchemaUsedTypes
+from .schema_analysis import QAPISchemaTypeAnalysis
 from .types import gen_types
 from .visit import gen_visit
 
@@ -58,7 +58,7 @@ def generate(self,
 
         :raise QAPIError: On failures.
         """
-        schema_types = QAPISchemaUsedTypes(unmask)
+        schema_types = QAPISchemaTypeAnalysis(unmask)
         schema.visit(schema_types)
         gen_types(schema, output_dir, prefix, builtins)
         gen_features(schema, output_dir, prefix)
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 9e76e3aa38a9..77c29425c42d 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -38,7 +38,7 @@
     QAPISchemaType,
     QAPISchemaVariant,
 )
-from .schema_analysis import QAPISchemaUsedTypes
+from .schema_analysis import QAPISchemaTypeAnalysis
 from .source import QAPISourceInfo
 
 
@@ -168,7 +168,7 @@ def to_c_string(string: str) -> str:
 
 class QAPISchemaGenIntrospectVisitor(QAPISchemaMonolithicCVisitor):
 
-    def __init__(self, prefix: str, schema_types: QAPISchemaUsedTypes):
+    def __init__(self, prefix: str, schema_types: QAPISchemaTypeAnalysis):
         super().__init__(
             prefix, 'qapi-introspect',
             ' * QAPI/QMP schema introspection', __doc__)
@@ -187,7 +187,7 @@ def visit_begin(self, schema: QAPISchema) -> None:
 
     def visit_end(self) -> None:
         # visit the types that are actually used
-        for typ in self._schema_types.used_types():
+        for typ in self._schema_types.types():
             typ.visit(self)
         # generate C
         name = c_name(self._prefix, protect=False) + 'qmp_schema_qlit'
diff --git a/scripts/qapi/schema_analysis.py b/scripts/qapi/schema_analysis.py
index 7e42abbc14e1..1d12306f61e2 100644
--- a/scripts/qapi/schema_analysis.py
+++ b/scripts/qapi/schema_analysis.py
@@ -35,37 +35,37 @@
 from .source import QAPISourceInfo
 
 
-class QAPISchemaUsedTypes(QAPISchemaVisitor):
-    """Collect the set of QMP-reachable types from a schema.
+class QAPISchemaTypeAnalysis(QAPISchemaVisitor):
+    """Analyze types from a QAPI schema.
 
-    Types are discovered transitively starting from commands and events.
-    Each type is also given a masked introspection name (an integer
-    string).
+    Every non-builtin, non-array type is given a masked introspection
+    name (an integer string).
     """
 
     def __init__(self, unmask: bool):
         self._unmask = unmask
         self._schema: Optional[QAPISchema] = None
         # Ordered list + set: insert during iteration + O(1) check
-        self._used_types: List[QAPISchemaType] = []
-        self._used_types_set: Set[QAPISchemaType] = set()
+        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._used_types = []
-        self._used_types_set = set()
+        self._types = []
+        self._types_set = set()
         self._name_map = {}
 
     def visit_end(self) -> None:
         assert self._schema is not None
-        # Discover transitively-used types; the list grows as
+        # Discover type dependencies; the list grows as
         # visiting each type registers the types it references.
-        for typ in self._used_types:
+        for typ in self._types:
             typ.visit(self)
-        # Assign stable masked names now that all types are known
+
+        # Assign masked names now that all introspected types are known.
         counter = 0
-        for typ in self._used_types:
+        for typ in self._types:
             if isinstance(typ, (QAPISchemaBuiltinType, QAPISchemaArrayType)):
                 continue
             self._name_map[typ.name] = (
@@ -73,8 +73,14 @@ def visit_end(self) -> None:
             counter += 1
 
     def visit_needed(self, entity: QAPISchemaEntity) -> bool:
-        # Skip types during main traversal; visit_end() handles them
-        return not isinstance(entity, QAPISchemaType)
+        # 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,
@@ -107,11 +113,6 @@ def visit_object_type_flat(
             for v in branches.variants:
                 self._register_type(v.type)
 
-    def visit_array_type(self, name: str, info: Optional[QAPISourceInfo],
-                         ifcond: QAPISchemaIfCond,
-                         element_type: QAPISchemaType) -> None:
-        self._register_type(element_type)
-
     def visit_alternate_type(
             self, name: str, info: Optional[QAPISourceInfo],
             ifcond: QAPISchemaIfCond,
@@ -121,11 +122,11 @@ def visit_alternate_type(
             self._register_type(m.type)
 
     def _register_type(self, typ: QAPISchemaType) -> None:
-        """Record a type as QMP-reachable (idempotent)."""
+        """Record a type for introspection (idempotent)."""
         typ = self._canonicalize_type(typ)
-        if typ not in self._used_types_set:
-            self._used_types.append(typ)
-            self._used_types_set.add(typ)
+        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)
 
@@ -156,9 +157,9 @@ 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._used_types_set
+        assert typ in self._types_set
         return self.masked_name(typ.name)
 
-    def used_types(self) -> Sequence[QAPISchemaType]:
+    def types(self) -> Sequence[QAPISchemaType]:
         """Return the types to include in QAPI introspection."""
-        return self._used_types
+        return self._types

-- 
2.55.0.543.g5ebe2ebe4ea8


Reply via email to