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

John Snow <jsnow@redhat.com> writes:

> 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: ...

.resolve_type()'s type remains the same.

> 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.

.resolve_type() doesn't before the patch.

> 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.

Talking about .resolve_type() so much suggests you're changing it.
You're not.

Here's my own summary of the change, just to make sure I got it:

1. Split .lookup_entity() into .get_entity() and .get_typed_entity().

   schema.lookup_entity(name) and schema.lookup_entity(name, None)
   become schema.get_entity(name).

   schema.lookup_entity(name, typ) where typ is not None becomes
   schema.get_typed_entity().

2. Tighten .get_typed_entity()'s type from Optional[QAPISchemaEntity] to
   Optional[_EntityType], where Entity is argument @typ.

3. Change .lookup_type()'s behavior for "not found" from "return None"
   to "throw KeyError if doesn't exist, throw TypeError if exists, but
   not a type".

Correct?

> 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.

.get_entity()'s single caller being QAPIDocDirective.run(), and its
other single caller being QAPISchema._make_implicit_object_type() ;-P

> 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 = QAPISchemaGenRSTVisitor(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))

@vis is a QAPISchemaGenRSTVisitor, and vis.symbol is

    def symbol(self, doc, entity):
        [...]
        self._cur_doc = doc
        entity.visit(self)
        self._cur_doc = None

When you add type hints to qapidoc.py, parameter @entity will be
QAPISchemaEntity.  Type error since .get_entity() returns
Optional[QAPISchemaEntity].  I'm fine with addressing that when adding
types to qapidoc.py.

>                  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)

Readability improvement here, due to tighter typing of .lookup_type():
it now returns QAPISchemaType instead of Optional[QAPISchemaType].

Before, lookup failure results in AssertionError.  Afterwards, it
results in KeyError or TypeError.  Fine.  Is it worth mentioning in the
commit message?  Genuine question!

> 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

Same change of errors as above.

>              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
> +)

Oh, the fanciness!

> +
> +
>  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

This is at best a wash for readability.

When something throws KeyError or TypeError unexpectedly, we
misinterpret the programming error as a semantic error in the schema.

>  
>      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



      reply	other threads:[~2023-11-28 12:08 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 ` [PATCH 19/19] qapi/schema: refactor entity lookup helpers John Snow
2023-11-28 12:06   ` Markus Armbruster [this message]

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=877cm2xesd.fsf@pond.sub.org \
    --to=armbru@redhat.com \
    --cc=jsnow@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.