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


Reply via email to