mercurial/pycompat.py
changeset 49807 c5a06cc37401
parent 49803 55d45d0de4e7
child 50101 b900f40c343e
--- a/mercurial/pycompat.py	Wed Dec 14 22:27:22 2022 -0500
+++ b/mercurial/pycompat.py	Thu Dec 15 01:05:27 2022 -0500
@@ -29,12 +29,22 @@
 import xmlrpc.client as xmlrpclib
 
 from typing import (
+    Any,
+    AnyStr,
+    BinaryIO,
+    Dict,
     Iterable,
     Iterator,
     List,
+    Mapping,
+    NoReturn,
     Optional,
+    Sequence,
+    Tuple,
     Type,
     TypeVar,
+    cast,
+    overload,
 )
 
 ispy3 = sys.version_info[0] >= 3
@@ -46,6 +56,8 @@
 
     TYPE_CHECKING = typing.TYPE_CHECKING
 
+_GetOptResult = Tuple[List[Tuple[bytes, bytes]], List[bytes]]
+_T0 = TypeVar('_T0')
 _Tbytestr = TypeVar('_Tbytestr', bound='bytestr')
 
 
@@ -56,7 +68,7 @@
 FileNotFoundError = builtins.FileNotFoundError
 
 
-def identity(a):
+def identity(a: _T0) -> _T0:
     return a
 
 
@@ -250,6 +262,17 @@
     return map(bytechr, s)
 
 
+if TYPE_CHECKING:
+
+    @overload
+    def maybebytestr(s: bytes) -> bytestr:
+        ...
+
+    @overload
+    def maybebytestr(s: _T0) -> _T0:
+        ...
+
+
 def maybebytestr(s):
     """Promote bytes to bytestr"""
     if isinstance(s, bytes):
@@ -257,7 +280,7 @@
     return s
 
 
-def sysbytes(s):
+def sysbytes(s: AnyStr) -> bytes:
     """Convert an internal str (e.g. keyword, __doc__) back to bytes
 
     This never raises UnicodeEncodeError, but only ASCII characters
@@ -268,7 +291,7 @@
     return s.encode('utf-8')
 
 
-def sysstr(s):
+def sysstr(s: AnyStr) -> str:
     """Return a keyword str to be passed to Python functions such as
     getattr() and str.encode()
 
@@ -281,26 +304,26 @@
     return s.decode('latin-1')
 
 
-def strurl(url):
+def strurl(url: AnyStr) -> str:
     """Converts a bytes url back to str"""
     if isinstance(url, bytes):
         return url.decode('ascii')
     return url
 
 
-def bytesurl(url):
+def bytesurl(url: AnyStr) -> 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: BaseException, tb) -> NoReturn:
     """Raise exception with the given traceback"""
     raise exc.with_traceback(tb)
 
 
-def getdoc(obj):
+def getdoc(obj: object) -> Optional[bytes]:
     """Get docstring as bytes; may be None so gettext() won't confuse it
     with _('')"""
     doc = builtins.getattr(obj, '__doc__', None)
@@ -326,14 +349,22 @@
 unicode = str
 
 
-def open(name, mode=b'r', buffering=-1, encoding=None):
+def open(
+    name,
+    mode: AnyStr = b'r',
+    buffering: int = -1,
+    encoding: Optional[str] = None,
+) -> Any:
+    # TODO: assert binary mode, and cast result to BinaryIO?
     return builtins.open(name, sysstr(mode), buffering, encoding)
 
 
 safehasattr = _wrapattrfunc(builtins.hasattr)
 
 
-def _getoptbwrapper(orig, args, shortlist, namelist):
+def _getoptbwrapper(
+    orig, args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes]
+) -> _GetOptResult:
     """
     Takes bytes arguments, converts them to unicode, pass them to
     getopt.getopt(), convert the returned values back to bytes and then
@@ -349,7 +380,7 @@
     return opts, args
 
 
-def strkwargs(dic):
+def strkwargs(dic: Mapping[bytes, _T0]) -> Dict[str, _T0]:
     """
     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
@@ -359,7 +390,7 @@
     return dic
 
 
-def byteskwargs(dic):
+def byteskwargs(dic: Mapping[str, _T0]) -> Dict[bytes, _T0]:
     """
     Converts keys of python dictionaries to bytes as they were converted to
     str to pass that dictonary as a keyword argument on Python 3.
@@ -369,7 +400,9 @@
 
 
 # TODO: handle shlex.shlex().
-def shlexsplit(s, comments=False, posix=True):
+def shlexsplit(
+    s: bytes, comments: bool = False, posix: bool = 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
@@ -392,38 +425,51 @@
 iswindows: bool = osname == b'nt'
 
 
-def getoptb(args, shortlist, namelist):
+def getoptb(
+    args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes]
+) -> _GetOptResult:
     return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
 
 
-def gnugetoptb(args, shortlist, namelist):
+def gnugetoptb(
+    args: Sequence[bytes], shortlist: bytes, namelist: Sequence[bytes]
+) -> _GetOptResult:
     return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
 
 
-def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
+def mkdtemp(
+    suffix: bytes = b'', prefix: bytes = b'tmp', dir: Optional[bytes] = None
+) -> bytes:
     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: bytes = b'', prefix: bytes = b'tmp', dir: Optional[bytes] = None
+) -> Tuple[int, bytes]:
     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: Optional[bytes] = None, *args, **kwargs) -> BinaryIO:
     if mode is None:
         mode = 'w+b'
     else:
         mode = sysstr(mode)
     assert 'b' in mode
-    return tempfile.TemporaryFile(mode, *args, **kwargs)
+    return cast(BinaryIO, tempfile.TemporaryFile(mode, *args, **kwargs))
 
 
 # NamedTemporaryFile does not support an "encoding=" argument on python2.
 # This wrapper file are always open in byte mode.
 def namedtempfile(
-    mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None, delete=True
+    mode: bytes = b'w+b',
+    bufsize: int = -1,
+    suffix: bytes = b'',
+    prefix: bytes = b'tmp',
+    dir: Optional[bytes] = None,
+    delete: bool = True,
 ):
     mode = sysstr(mode)
     assert 'b' in mode