Patchwork D12366: pycompat: add return type annotations

login
register
mail settings
Submitter phabricator
Date March 10, 2022, 1:08 a.m.
Message ID <differential-rev-PHID-DREV-qanx6r4lal222ek2bzle-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/50708/
State New
Headers show

Comments

phabricator - March 10, 2022, 1:08 a.m.
indygreg created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  Now that we're using Python 3.6+, we can start using inline type
  annotations.
  
  I figured a good a place as any to start would be pycompat. So this
  commit defines type annotations throughout the file.
  
  Because we can't use deferred annotations parsing (since this feature
  requires Python 3.7), complex types relying on symbols from the `typing`
  module use quoted expressions. This achieves some of the benefit of
  deferred parsing without compromising the ability for type checkers to
  infer things.
  
  When annotating `open()` as part of this change, pytype started
  complaining about a type mismatch in `profiling.py`. This turned out
  to be a false positive. So the error was suppressed.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D12366

AFFECTED FILES
  mercurial/profiling.py
  mercurial/pycompat.py

CHANGE DETAILS




To: indygreg, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py
--- a/mercurial/pycompat.py
+++ b/mercurial/pycompat.py
@@ -36,6 +36,16 @@ 
 
 if not globals():  # hide this from non-pytype users
     import typing
+    from typing import (
+        Any,
+        AnyStr,
+        Dict,
+        IO,
+        Iterator,
+        List,
+        NoReturn,
+        Tuple,
+    )
 
     TYPE_CHECKING = typing.TYPE_CHECKING
 
@@ -112,15 +122,15 @@ 
     sysexecutable = os.fsencode(sysexecutable)
 
 
-def maplist(*args):
+def maplist(*args) -> 'List[Any]':
     return list(map(*args))
 
 
-def rangelist(*args):
+def rangelist(*args) -> 'List[int]':
     return list(range(*args))
 
 
-def ziplist(*args):
+def ziplist(*args) -> 'List[Any]':
     return list(zip(*args))
 
 
@@ -226,20 +236,20 @@ 
             s = str(s).encode('ascii')
         return bytes.__new__(cls, s)
 
-    def __getitem__(self, key):
+    def __getitem__(self, key) -> bytes:
         s = bytes.__getitem__(self, key)
         if not isinstance(s, bytes):
             s = bytechr(s)
         return s
 
-    def __iter__(self):
+    def __iter__(self) -> 'Iterator[bytes]':
         return iterbytestr(bytes.__iter__(self))
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return bytes.__repr__(self)[1:]  # drop b''
 
 
-def iterbytestr(s):
+def iterbytestr(s) -> 'Iterator[bytes]':
     """Iterate bytes as if it were a str object of Python 2"""
     return map(bytechr, s)
 
@@ -251,7 +261,7 @@ 
     return s
 
 
-def sysbytes(s):
+def sysbytes(s) -> bytes:
     """Convert an internal str (e.g. keyword, __doc__) back to bytes
 
     This never raises UnicodeEncodeError, but only ASCII characters
@@ -262,7 +272,7 @@ 
     return s.encode('utf-8')
 
 
-def sysstr(s):
+def sysstr(s) -> str:
     """Return a keyword str to be passed to Python functions such as
     getattr() and str.encode()
 
@@ -275,26 +285,26 @@ 
     return s.decode('latin-1')
 
 
-def strurl(url):
+def strurl(url) -> str:
     """Converts a bytes url back to str"""
     if isinstance(url, bytes):
         return url.decode('ascii')
     return url
 
 
-def bytesurl(url):
+def bytesurl(url) -> bytes:
     """Converts a str url to bytes by encoding in ascii"""
     if isinstance(url, str):
         return url.encode('ascii')
     return url
 
 
-def raisewithtb(exc, tb):
+def raisewithtb(exc, tb) -> 'NoReturn':
     """Raise exception with the given traceback"""
     raise exc.with_traceback(tb)
 
 
-def getdoc(obj):
+def getdoc(obj) -> bytes:
     """Get docstring as bytes; may be None so gettext() won't confuse it
     with _('')"""
     doc = getattr(obj, '__doc__', None)
@@ -320,14 +330,16 @@ 
 unicode = str
 
 
-def open(name, mode=b'r', buffering=-1, encoding=None):
+def open(name, mode=b'r', buffering=-1, encoding=None) -> 'IO[AnyStr]':
     return builtins.open(name, sysstr(mode), buffering, encoding)
 
 
 safehasattr = _wrapattrfunc(builtins.hasattr)
 
 
-def _getoptbwrapper(orig, args, shortlist, namelist):
+def _getoptbwrapper(
+    orig, args, shortlist, namelist
+) -> 'Tuple[List[Tuple[bytes, bytes]], List[bytes]]':
     """
     Takes bytes arguments, converts them to unicode, pass them to
     getopt.getopt(), convert the returned values back to bytes and then
@@ -343,7 +355,7 @@ 
     return opts, args
 
 
-def strkwargs(dic):
+def strkwargs(dic) -> 'Dict[str, Any]':
     """
     Converts the keys of a python dictonary to str i.e. unicodes so that
     they can be passed as keyword arguments as dictionaries with bytes keys
@@ -353,7 +365,7 @@ 
     return dic
 
 
-def byteskwargs(dic):
+def byteskwargs(dic) -> 'Dict[bytes, Any]':
     """
     Converts keys of python dictionaries to bytes as they were converted to
     str to pass that dictonary as a keyword argument on Python 3.
@@ -363,7 +375,7 @@ 
 
 
 # TODO: handle shlex.shlex().
-def shlexsplit(s, comments=False, posix=True):
+def shlexsplit(s, comments=False, posix=True) -> 'List[bytes]':
     """
     Takes bytes argument, convert it to str i.e. unicodes, pass that into
     shlex.split(), convert the returned value to bytes and return that for
@@ -394,18 +406,18 @@ 
     return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
 
 
-def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
+def mkdtemp(suffix=b'', prefix=b'tmp', dir=None) -> 'AnyStr':
     return tempfile.mkdtemp(suffix, prefix, dir)
 
 
 # text=True is not supported; use util.from/tonativeeol() instead
-def mkstemp(suffix=b'', prefix=b'tmp', dir=None):
+def mkstemp(suffix=b'', prefix=b'tmp', dir=None) -> 'Tuple[int, AnyStr]':
     return tempfile.mkstemp(suffix, prefix, dir)
 
 
 # TemporaryFile does not support an "encoding=" argument on python2.
 # This wrapper file are always open in byte mode.
-def unnamedtempfile(mode=None, *args, **kwargs):
+def unnamedtempfile(mode=None, *args, **kwargs) -> 'IO[AnyStr]':
     if mode is None:
         mode = 'w+b'
     else:
diff --git a/mercurial/profiling.py b/mercurial/profiling.py
--- a/mercurial/profiling.py
+++ b/mercurial/profiling.py
@@ -272,7 +272,12 @@ 
                 exception_type, exception_value, traceback
             )
             if self._output == b'blackbox':
+                # We always use io.BytesIO if self._output == b"blackbox". But
+                # pytype doesn't realize this and thinks we're attempting to
+                # access .getvalue() on an IO type.
+                # pytype: disable=attribute-error
                 val = b'Profile:\n%s' % self._fp.getvalue()
+                # pytype: enable=attribute-error
                 # ui.log treats the input as a format string,
                 # so we need to escape any % signs.
                 val = val.replace(b'%', b'%%')