qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Markus Armbruster <armbru@redhat.com>
To: John Snow <jsnow@redhat.com>
Cc: Michael Roth <michael.roth@amd.com>,
	Cleber Rosa <crosa@redhat.com>, Eric Blake <eblake@redhat.com>,
	qemu-devel@nongnu.org, Eduardo Habkost <ehabkost@redhat.com>
Subject: Re: [PATCH v3 09/13] qapi/parser: add import cycle workaround
Date: Thu, 30 Sep 2021 11:45:15 +0200	[thread overview]
Message-ID: <87o88aqtw4.fsf@dusky.pond.sub.org> (raw)
In-Reply-To: <20210929194428.1038496-10-jsnow@redhat.com> (John Snow's message of "Wed, 29 Sep 2021 15:44:24 -0400")

John Snow <jsnow@redhat.com> writes:

> There is a cycle that exists in the QAPI generator: [schema -> expr ->

"There is" or "there will be once we add strong type hints"?

> parser -> schema]. It exists because the QAPIDoc class needs the names
> of types defined by the schema module, but the schema module needs to
> import both expr.py/parser.py to do its actual parsing.
>
> Ultimately, the layering violation is that parser.py should not have any
> knowledge of specifics of the Schema. QAPIDoc performs double-duty here
> both as a parser *and* as a finalized object that is part of the schema.
>
> I see three paths here:
>
> (1) Just use the TYPE_CHECKING trick to eliminate the cycle which is only
>     present during static analysis.
>
> (2) Don't bother to annotate connect_member() et al, give them 'object'
>     or 'Any'. I don't particularly like this, because it diminishes the
>     usefulness of type hints for documentation purposes. Still, it's an
>     extremely quick fix.
>
> (3) Reimplement doc <--> definition correlation directly in schema.py,
>     integrating doc fields directly into QAPISchemaMember and relieving
>     the QAPIDoc class of the responsibility. Users of the information
>     would instead visit the members first and retrieve their
>     documentation instead of the inverse operation -- visiting the
>     documentation and retrieving their members.
>
> I prefer (3), but (1) is the easiest way to have my cake (strong type
> hints) and eat it too (Not have import cycles). Do (1) for now, but plan
> for (3). See also:
> https://mypy.readthedocs.io/en/latest/runtime_troubles.html#import-cycles
>
> Signed-off-by: John Snow <jsnow@redhat.com>
> ---
>  scripts/qapi/parser.py | 15 +++++++++++----
>  1 file changed, 11 insertions(+), 4 deletions(-)
>
> diff --git a/scripts/qapi/parser.py b/scripts/qapi/parser.py
> index 123fc2f099c..30b1d98df0b 100644
> --- a/scripts/qapi/parser.py
> +++ b/scripts/qapi/parser.py
> @@ -18,6 +18,7 @@
>  import os
>  import re
>  from typing import (
> +    TYPE_CHECKING,
>      Dict,
>      List,
>      Optional,
> @@ -30,6 +31,12 @@
>  from .source import QAPISourceInfo
>  
>  
> +if TYPE_CHECKING:
> +    # pylint: disable=cyclic-import
> +    # TODO: Remove cycle. [schema -> expr -> parser -> schema]
> +    from .schema import QAPISchemaFeature, QAPISchemaMember
> +
> +
>  # Return value alias for get_expr().
>  _ExprValue = Union[List[object], Dict[str, object], str, bool]
>  
> @@ -473,9 +480,9 @@ def append(self, line):
>      class ArgSection(Section):
>          def __init__(self, parser, name, indent=0):
>              super().__init__(parser, name, indent)
> -            self.member = None
> +            self.member: Optional['QAPISchemaMember'] = None
>  
> -        def connect(self, member):
> +        def connect(self, member: 'QAPISchemaMember') -> None:
>              self.member = member
>  
>      class NullSection(Section):
> @@ -750,14 +757,14 @@ def _append_freeform(self, line):
>                                   % match.group(1))
>          self._section.append(line)
>  
> -    def connect_member(self, member):
> +    def connect_member(self, member: 'QAPISchemaMember') -> None:
>          if member.name not in self.args:
>              # Undocumented TODO outlaw
>              self.args[member.name] = QAPIDoc.ArgSection(self._parser,
>                                                          member.name)
>          self.args[member.name].connect(member)
>  
> -    def connect_feature(self, feature):
> +    def connect_feature(self, feature: 'QAPISchemaFeature') -> None:
>          if feature.name not in self.features:
>              raise QAPISemError(feature.info,
>                                 "feature '%s' lacks documentation"

This adds just the type hints that cause the cycle.  I like that,
because it illustrates the cycle.  Would be nice if the commit message
mentioned this, perhaps

  I prefer (3), but (1) is the easiest way to have my cake (strong type
  hints) and eat it too (Not have import cycles). Do (1) for now, but plan
  for (3). Also add the type hints that cause the cycle right away to
  illustrate. See also:
  https://mypy.readthedocs.io/en/latest/runtime_troubles.html#import-cycles

Slightly nicer, I think, would be swapping this and the next patch.
Then that one's commit message needs to say something like "except for a
few problematic ones, which the next commit will add".  Worthwhile?  Up
to you.



  reply	other threads:[~2021-09-30  9:47 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-29 19:44 [PATCH v3 00/13] qapi: static typing conversion, pt5b John Snow
2021-09-29 19:44 ` [PATCH v3 01/13] qapi/pylintrc: ignore 'consider-using-f-string' warning John Snow
2021-09-29 19:44 ` [PATCH v3 02/13] qapi/gen: use dict.items() to iterate over _modules John Snow
2021-09-29 19:44 ` [PATCH v3 03/13] qapi/parser: fix unused check_args_section arguments John Snow
2021-09-29 19:44 ` [PATCH v3 04/13] qapi: Add spaces after symbol declaration for consistency John Snow
2021-09-29 19:44 ` [PATCH v3 05/13] qapi/parser: improve detection of '@symbol:' preface John Snow
2021-09-30  8:42   ` Markus Armbruster
2021-09-30 17:43     ` John Snow
2021-09-29 19:44 ` [PATCH v3 06/13] qapi/parser: remove FIXME comment from _append_body_line John Snow
2021-09-30  8:47   ` Markus Armbruster
2021-09-30 17:20     ` John Snow
2021-09-29 19:44 ` [PATCH v3 07/13] qapi/parser: Simplify _end_section() John Snow
2021-09-29 19:44 ` [PATCH v3 08/13] qapi/parser: Introduce NullSection John Snow
2021-09-30  9:34   ` Markus Armbruster
2021-09-30 16:59     ` John Snow
2021-09-29 19:44 ` [PATCH v3 09/13] qapi/parser: add import cycle workaround John Snow
2021-09-30  9:45   ` Markus Armbruster [this message]
2021-09-30 17:11     ` John Snow
2021-09-29 19:44 ` [PATCH v3 10/13] qapi/parser: add type hint annotations (QAPIDoc) John Snow
2021-09-29 19:44 ` [PATCH v3 11/13] qapi/parser: enable mypy checks John Snow
2021-09-29 19:44 ` [PATCH v3 12/13] qapi/parser: Silence too-few-public-methods warning John Snow
2021-09-29 19:44 ` [PATCH v3 13/13] qapi/parser: enable pylint checks John Snow
2021-09-30 10:08 ` [PATCH v3 00/13] qapi: static typing conversion, pt5b 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=87o88aqtw4.fsf@dusky.pond.sub.org \
    --to=armbru@redhat.com \
    --cc=crosa@redhat.com \
    --cc=eblake@redhat.com \
    --cc=ehabkost@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=michael.roth@amd.com \
    --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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).