All of lore.kernel.org
 help / color / mirror / Atom feed
From: John Snow <jsnow@redhat.com>
To: qemu-devel@nongnu.org
Cc: Peter Maydell <peter.maydell@linaro.org>,
	Michael Roth <michael.roth@amd.com>,
	Markus Armbruster <armbru@redhat.com>,
	John Snow <jsnow@redhat.com>
Subject: [PATCH 19/19] qapi/schema: refactor entity lookup helpers
Date: Wed, 15 Nov 2023 20:43:50 -0500	[thread overview]
Message-ID: <20231116014350.653792-20-jsnow@redhat.com> (raw)
In-Reply-To: <20231116014350.653792-1-jsnow@redhat.com>

This is not a clear win, but I was confused and I couldn't help myself.

Before:

lookup_entity(self, name: str, typ: Optional[type] = None
              ) -> Optional[QAPISchemaEntity]: ...

lookup_type(self, name: str) -> Optional[QAPISchemaType]: ...

resolve_type(self, name: str, info: Optional[QAPISourceInfo],
             what: Union[str, Callable[[Optional[QAPISourceInfo]], str]]
             ) -> QAPISchemaType: ...

After:

get_entity(self, name: str) -> Optional[QAPISchemaEntity]: ...
get_typed_entity(self, name: str, typ: Type[_EntityType]
                 ) -> Optional[_EntityType]: ...
lookup_type(self, name: str) -> QAPISchemaType: ...
resolve_type(self, name: str, info: Optional[QAPISourceInfo],
             what: Union[str, Callable[[Optional[QAPISourceInfo]], str]]
             ) -> QAPISchemaType: ...

In essence, any function that can return a None value becomes "get ..."
to encourage association with the dict.get() function which has the same
behavior. Any function named "lookup" or "resolve" by contrast is no
longer allowed to return a None value.

This means that any callers to resolve_type or lookup_type don't have to
check that the function worked, they can just assume it did.

Callers to resolve_type will be greeted with a QAPISemError if something
has gone wrong, as they have in the past. Callers to lookup_type will be
greeted with a KeyError if the entity does not exist, or a TypeError if
it does, but is the wrong type.

get_entity and get_typed_entity remain for any callers who are
specifically interested in the negative case. These functions have only
a single caller each.

Signed-off-by: John Snow <jsnow@redhat.com>
---
 docs/sphinx/qapidoc.py     |  2 +-
 scripts/qapi/introspect.py |  8 ++----
 scripts/qapi/schema.py     | 52 ++++++++++++++++++++++++--------------
 3 files changed, 36 insertions(+), 26 deletions(-)

diff --git a/docs/sphinx/qapidoc.py b/docs/sphinx/qapidoc.py
index 8f3b9997a15..96deadbf7fc 100644
--- a/docs/sphinx/qapidoc.py
+++ b/docs/sphinx/qapidoc.py
@@ -508,7 +508,7 @@ def run(self):
             vis.visit_begin(schema)
             for doc in schema.docs:
                 if doc.symbol:
-                    vis.symbol(doc, schema.lookup_entity(doc.symbol))
+                    vis.symbol(doc, schema.get_entity(doc.symbol))
                 else:
                     vis.freeform(doc)
             return vis.get_document_nodes()
diff --git a/scripts/qapi/introspect.py b/scripts/qapi/introspect.py
index 42981bce163..67c7d89aae0 100644
--- a/scripts/qapi/introspect.py
+++ b/scripts/qapi/introspect.py
@@ -227,14 +227,10 @@ def _use_type(self, typ: QAPISchemaType) -> str:
 
         # Map the various integer types to plain int
         if typ.json_type() == 'int':
-            tmp = self._schema.lookup_type('int')
-            assert tmp is not None
-            typ = tmp
+            typ = self._schema.lookup_type('int')
         elif (isinstance(typ, QAPISchemaArrayType) and
               typ.element_type.json_type() == 'int'):
-            tmp = self._schema.lookup_type('intList')
-            assert tmp is not None
-            typ = tmp
+            typ = self._schema.lookup_type('intList')
         # Add type to work queue if new
         if typ not in self._used_types:
             self._used_types.append(typ)
diff --git a/scripts/qapi/schema.py b/scripts/qapi/schema.py
index b5f377e68b8..5813136e78b 100644
--- a/scripts/qapi/schema.py
+++ b/scripts/qapi/schema.py
@@ -26,6 +26,8 @@
     Dict,
     List,
     Optional,
+    Type,
+    TypeVar,
     Union,
     cast,
 )
@@ -767,7 +769,6 @@ def check(
             # Here we do:
             assert self.tag_member.defined_in
             base_type = schema.lookup_type(self.tag_member.defined_in)
-            assert base_type
             if not base_type.is_implicit():
                 base = "base type '%s'" % self.tag_member.defined_in
             if not isinstance(self.tag_member.type, QAPISchemaEnumType):
@@ -1111,6 +1112,12 @@ def visit(self, visitor: QAPISchemaVisitor) -> None:
             self.arg_type, self.boxed)
 
 
+# Used for type-dependent type lookup return values.
+_EntityType = TypeVar(   # pylint: disable=invalid-name
+    '_EntityType', bound=QAPISchemaEntity
+)
+
+
 class QAPISchema:
     def __init__(self, fname: str):
         self.fname = fname
@@ -1155,22 +1162,28 @@ def _def_entity(self, ent: QAPISchemaEntity) -> None:
                 ent.info, "%s is already defined" % other_ent.describe())
         self._entity_dict[ent.name] = ent
 
-    def lookup_entity(
+    def get_entity(self, name: str) -> Optional[QAPISchemaEntity]:
+        return self._entity_dict.get(name)
+
+    def get_typed_entity(
         self,
         name: str,
-        typ: Optional[type] = None,
-    ) -> Optional[QAPISchemaEntity]:
-        ent = self._entity_dict.get(name)
-        if typ and not isinstance(ent, typ):
-            return None
+        typ: Type[_EntityType]
+    ) -> Optional[_EntityType]:
+        ent = self.get_entity(name)
+        if ent is not None and not isinstance(ent, typ):
+            etype = type(ent).__name__
+            ttype = typ.__name__
+            raise TypeError(
+                f"Entity '{name}' is of type '{etype}', not '{ttype}'."
+            )
         return ent
 
-    def lookup_type(self, name: str) -> Optional[QAPISchemaType]:
-        typ = self.lookup_entity(name, QAPISchemaType)
-        if typ is None:
-            return None
-        assert isinstance(typ, QAPISchemaType)
-        return typ
+    def lookup_type(self, name: str) -> QAPISchemaType:
+        ent = self.get_typed_entity(name, QAPISchemaType)
+        if ent is None:
+            raise KeyError(f"Entity '{name}' is not defined.")
+        return ent
 
     def resolve_type(
         self,
@@ -1178,13 +1191,14 @@ def resolve_type(
         info: Optional[QAPISourceInfo],
         what: Union[str, Callable[[Optional[QAPISourceInfo]], str]],
     ) -> QAPISchemaType:
-        typ = self.lookup_type(name)
-        if not typ:
+        try:
+            return self.lookup_type(name)
+        except (KeyError, TypeError) as err:
             if callable(what):
                 what = what(info)
             raise QAPISemError(
-                info, "%s uses unknown type '%s'" % (what, name))
-        return typ
+                info, "%s uses unknown type '%s'" % (what, name)
+            ) from err
 
     def _module_name(self, fname: str) -> str:
         if QAPISchemaModule.is_system_module(fname):
@@ -1279,7 +1293,7 @@ def _make_array_type(
         self, element_type: str, info: Optional[QAPISourceInfo]
     ) -> str:
         name = element_type + 'List'    # reserved by check_defn_name_str()
-        if not self.lookup_type(name):
+        if not self.get_entity(name):
             self._def_entity(QAPISchemaArrayType(name, info, element_type))
         return name
 
@@ -1295,7 +1309,7 @@ def _make_implicit_object_type(
             return None
         # See also QAPISchemaObjectTypeMember.describe()
         name = 'q_obj_%s-%s' % (name, role)
-        typ = self.lookup_entity(name, QAPISchemaObjectType)
+        typ = self.get_typed_entity(name, QAPISchemaObjectType)
         if typ:
             # The implicit object type has multiple users.  This can
             # only be a duplicate definition, which will be flagged
-- 
2.41.0



  parent reply	other threads:[~2023-11-16  1:48 UTC|newest]

Thread overview: 76+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-16  1:43 [PATCH 00/19] qapi: statically type schema.py John Snow
2023-11-16  1:43 ` [PATCH 01/19] qapi/schema: fix QAPISchemaEntity.__repr__() John Snow
2023-11-16  7:01   ` Philippe Mathieu-Daudé
2023-11-16  1:43 ` [PATCH 02/19] qapi/schema: add pylint suppressions John Snow
2023-11-21 12:23   ` Markus Armbruster
2023-11-16  1:43 ` [PATCH 03/19] qapi/schema: name QAPISchemaInclude entities John Snow
2023-11-21 13:33   ` Markus Armbruster
2023-11-21 16:22     ` John Snow
2023-11-22  9:37       ` Markus Armbruster
2023-12-13  0:45         ` John Snow
2023-11-16  1:43 ` [PATCH 04/19] qapi/schema: declare type for QAPISchemaObjectTypeMember.type John Snow
2023-11-16  1:43 ` [PATCH 05/19] qapi/schema: make c_type() and json_type() abstract methods John Snow
2023-11-16  7:03   ` Philippe Mathieu-Daudé
2023-11-21 13:36   ` Markus Armbruster
2023-11-21 13:43     ` Daniel P. Berrangé
2023-11-21 16:28       ` John Snow
2023-11-21 16:34         ` Daniel P. Berrangé
2023-11-22  9:50           ` Markus Armbruster
2023-11-22  9:54             ` Daniel P. Berrangé
2023-11-16  1:43 ` [PATCH 06/19] qapi/schema: adjust type narrowing for mypy's benefit John Snow
2023-11-16  7:04   ` Philippe Mathieu-Daudé
2023-11-21 14:09   ` Markus Armbruster
2023-11-21 16:36     ` John Snow
2023-11-22 12:00       ` Markus Armbruster
2023-11-22 18:12         ` John Snow
2023-11-23 11:00           ` Markus Armbruster
2023-11-16  1:43 ` [PATCH 07/19] qapi/introspect: assert schema.lookup_type did not fail John Snow
2023-11-21 14:17   ` Markus Armbruster
2023-11-21 16:41     ` John Snow
2023-11-22  9:52       ` Markus Armbruster
2023-11-16  1:43 ` [PATCH 08/19] qapi/schema: add static typing and assertions to lookup_type() John Snow
2023-11-21 14:21   ` Markus Armbruster
2023-11-21 16:46     ` John Snow
2023-11-22 12:09       ` Markus Armbruster
2023-11-22 15:55         ` John Snow
2023-11-23 11:04           ` Markus Armbruster
2023-11-16  1:43 ` [PATCH 09/19] qapi/schema: assert info is present when necessary John Snow
2023-11-16  7:05   ` Philippe Mathieu-Daudé
2023-11-16  1:43 ` [PATCH 10/19] qapi/schema: make QAPISchemaArrayType.element_type non-Optional John Snow
2023-11-21 14:27   ` Markus Armbruster
2023-11-21 16:51     ` John Snow
2023-11-16  1:43 ` [PATCH 11/19] qapi/schema: fix QAPISchemaArrayType.check's call to resolve_type John Snow
2023-11-22 12:59   ` Markus Armbruster
2023-11-22 15:58     ` John Snow
2023-11-23 13:03       ` Markus Armbruster
2024-01-10 19:33         ` John Snow
2024-01-11  9:33           ` Markus Armbruster
2024-01-11 22:24             ` John Snow
2023-11-16  1:43 ` [PATCH 12/19] qapi/schema: split "checked" field into "checking" and "checked" John Snow
2023-11-22 14:02   ` Markus Armbruster
2024-01-10 20:21     ` John Snow
2024-01-11  9:24       ` Markus Armbruster
2023-11-16  1:43 ` [PATCH 13/19] qapi/schema: fix typing for QAPISchemaVariants.tag_member John Snow
2023-11-22 14:05   ` Markus Armbruster
2023-11-22 16:02     ` John Snow
2024-01-10  1:47       ` John Snow
2024-01-10  7:52         ` Markus Armbruster
2024-01-10  8:35           ` John Snow
2024-01-17  8:19             ` Markus Armbruster
2024-01-17 10:32               ` Markus Armbruster
2024-01-17 10:53                 ` Markus Armbruster
2024-02-01 20:54                   ` John Snow
2023-11-16  1:43 ` [PATCH 14/19] qapi/schema: assert QAPISchemaVariants are QAPISchemaObjectType John Snow
2023-11-23 13:51   ` Markus Armbruster
2024-01-10  0:42     ` John Snow
2023-11-16  1:43 ` [PATCH 15/19] qapi/parser: demote QAPIExpression to Dict[str, Any] John Snow
2023-11-23 14:12   ` Markus Armbruster
2024-01-10  0:14     ` John Snow
2024-01-10  7:58       ` Markus Armbruster
2023-11-16  1:43 ` [PATCH 16/19] qapi/schema: add type hints John Snow
2023-11-24 15:02   ` Markus Armbruster
2023-11-16  1:43 ` [PATCH 17/19] qapi/schema: turn on mypy strictness John Snow
2023-11-16  1:43 ` [PATCH 18/19] qapi/schema: remove unnecessary asserts John Snow
2023-11-28  9:22   ` Markus Armbruster
2023-11-16  1:43 ` John Snow [this message]
2023-11-28 12:06   ` [PATCH 19/19] qapi/schema: refactor entity lookup helpers Markus Armbruster

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231116014350.653792-20-jsnow@redhat.com \
    --to=jsnow@redhat.com \
    --cc=armbru@redhat.com \
    --cc=michael.roth@amd.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.