changeset 49888:8147abc05794

pytype: stop excluding mercurial/ui.py ui.extractchoices() is perhaps making assumptions that it shouldn't about the pattern always matching, but presumably we have test coverage for that. PyCharm flags the updated classes with a warning "Class xxx must implement all abstract methods", and suggests adding `abc.ABC` to the superclasses. I'm not sure why, unless it doesn't recognize the `__getattr__()` delegation pattern. Additionally, we can't unconditionally subclass `typing.BinaryIO` because that defeats the `__getattr__` delegation to the wrapped object at runtime. Instead, it has to only subclass during the type checking phase[1]. In any event, this fixes: File "/mnt/c/Users/Matt/hg/mercurial/ui.py", line 1518, in _runpager: Function subprocess.Popen.__new__ was called with the wrong arguments [wrong-arg-types] Expected: (cls, args, bufsize, executable, stdin, stdout: Optional[Union[IO, int]] = ..., ...) Actually passed: (cls, args, bufsize, stdin, stdout: Union[mercurial.utils.procutil.WriteAllWrapper, mercurial.windows.winstdout], ...) File "/mnt/c/Users/Matt/hg/mercurial/ui.py", line 1798, in extractchoices: No attribute 'group' on None [attribute-error] In Optional[Match[bytes]] File "/mnt/c/Users/Matt/hg/mercurial/ui.py", line 1799, in extractchoices: No attribute 'group' on None [attribute-error] In Optional[Match[bytes]] [1] https://stackoverflow.com/q/71365594
author Matt Harbison <matt_harbison@yahoo.com>
date Fri, 25 Nov 2022 18:39:47 -0500
parents e1953a34c110
children 25fe689a4a64
files contrib/check-pytype.sh mercurial/typelib.py mercurial/ui.py mercurial/utils/procutil.py mercurial/windows.py
diffstat 5 files changed, 45 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/check-pytype.sh	Wed Dec 07 20:12:23 2022 +0100
+++ b/contrib/check-pytype.sh	Fri Nov 25 18:39:47 2022 -0500
@@ -31,7 +31,6 @@
 # mercurial/pure/parsers.py     # [attribute-error]
 # mercurial/repoview.py         # [attribute-error]
 # mercurial/testing/storage.py  # tons of [attribute-error]
-# mercurial/ui.py               # [attribute-error], [wrong-arg-types]
 # mercurial/unionrepo.py        # ui, svfs, unfiltered [attribute-error]
 # mercurial/win32.py            # [not-callable]
 # mercurial/wireprotoframing.py # [unsupported-operands], [attribute-error], [import-error]
@@ -64,7 +63,6 @@
     -x mercurial/repoview.py \
     -x mercurial/testing/storage.py \
     -x mercurial/thirdparty \
-    -x mercurial/ui.py \
     -x mercurial/unionrepo.py \
     -x mercurial/win32.py \
     -x mercurial/wireprotoframing.py \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/typelib.py	Fri Nov 25 18:39:47 2022 -0500
@@ -0,0 +1,28 @@
+# typelib.py - type hint aliases and support
+#
+# Copyright 2022 Matt Harbison <matt_harbison@yahoo.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import typing
+
+# Note: this is slightly different from pycompat.TYPE_CHECKING, as using
+# pycompat causes the BinaryIO_Proxy type to be resolved to ``object`` when
+# used as the base class during a pytype run.
+TYPE_CHECKING = typing.TYPE_CHECKING
+
+
+# The BinaryIO class provides empty methods, which at runtime means that
+# ``__getattr__`` on the proxy classes won't get called for the methods that
+# should delegate to the internal object.  So to avoid runtime changes because
+# of the required typing inheritance, just use BinaryIO when typechecking, and
+# ``object`` otherwise.
+if TYPE_CHECKING:
+    from typing import (
+        BinaryIO,
+    )
+
+    BinaryIO_Proxy = BinaryIO
+else:
+    BinaryIO_Proxy = object
--- a/mercurial/ui.py	Wed Dec 07 20:12:23 2022 +0100
+++ b/mercurial/ui.py	Fri Nov 25 18:39:47 2022 -0500
@@ -1795,6 +1795,9 @@
         # choices containing spaces, ASCII, or basically anything
         # except an ampersand followed by a character.
         m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
+
+        assert m is not None  # help pytype
+
         msg = m.group(1)
         choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
 
--- a/mercurial/utils/procutil.py	Wed Dec 07 20:12:23 2022 +0100
+++ b/mercurial/utils/procutil.py	Fri Nov 25 18:39:47 2022 -0500
@@ -18,6 +18,10 @@
 import threading
 import time
 
+from typing import (
+    BinaryIO,
+)
+
 from ..i18n import _
 from ..pycompat import (
     getattr,
@@ -29,6 +33,7 @@
     error,
     policy,
     pycompat,
+    typelib,
 )
 
 # Import like this to keep import-checker happy
@@ -118,8 +123,8 @@
     return stream
 
 
-class WriteAllWrapper:
-    def __init__(self, orig):
+class WriteAllWrapper(typelib.BinaryIO_Proxy):
+    def __init__(self, orig: BinaryIO):
         self.orig = orig
 
     def __getattr__(self, attr):
--- a/mercurial/windows.py	Wed Dec 07 20:12:23 2022 +0100
+++ b/mercurial/windows.py	Fri Nov 25 18:39:47 2022 -0500
@@ -16,6 +16,10 @@
 import sys
 import winreg  # pytype: disable=import-error
 
+from typing import (
+    BinaryIO,
+)
+
 from .i18n import _
 from .pycompat import getattr
 from . import (
@@ -23,6 +27,7 @@
     error,
     policy,
     pycompat,
+    typelib,
     win32,
 )
 
@@ -208,7 +213,7 @@
     return encoding.unitolocal(pw)
 
 
-class winstdout:
+class winstdout(typelib.BinaryIO_Proxy):
     """Some files on Windows misbehave.
 
     When writing to a broken pipe, EINVAL instead of EPIPE may be raised.
@@ -217,7 +222,7 @@
     error may happen. Python 3 already works around that.
     """
 
-    def __init__(self, fp):
+    def __init__(self, fp: BinaryIO):
         self.fp = fp
 
     def __getattr__(self, key):