# HG changeset patch # User Matt Harbison # Date 2022-12-15 06:05:27 # Node ID c5a06cc37401fac90835274848490283f5924d1b # Parent 9eb69fa5a7834975cb3fa9c8c748578c0cff7f66 typing: add type hints to most mercurial/pycompat.py functions The `rapply` methods are left out because it's not `rapply(f, xs: _T0) -> _T0` as I first thought- it's used somewhere to walk a collection and convert between bytes and str. Also, the `open()` call is partially untyped because I'm not sure what its purpose is at this point- both the name and mode can be either bytes or str as it is currently constituted. It might make sense to assert that the file is being opened in binary mode (like `namedtempfile()`) and cast the result to `BinaryIO`, but that shouldn't be smuggled in with these other changes. The return is currently typed as `Any` because something suddenly got smarter and a few uses in util.py (like readfile()) suddenly think it returns `IO[str]` instead of `IO[bytes]` (BinaryIO), and it flags the type mismatch there. diff --git a/mercurial/pycompat.py b/mercurial/pycompat.py --- a/mercurial/pycompat.py +++ b/mercurial/pycompat.py @@ -29,12 +29,22 @@ import tempfile 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 @@ if not globals(): # hide this from non- 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 @@ def future_set_exception_info(f, exc_inf FileNotFoundError = builtins.FileNotFoundError -def identity(a): +def identity(a: _T0) -> _T0: return a @@ -250,6 +262,17 @@ def iterbytestr(s: Iterable[int]) -> Ite 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 @@ def maybebytestr(s): 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 @@ def sysbytes(s): 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 @@ def sysstr(s): 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 @@ xrange = builtins.range 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 @@ def _getoptbwrapper(orig, args, shortlis 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 @@ def strkwargs(dic): 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 @@ def byteskwargs(dic): # 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 @@ isposix: bool = osname == b'posix' 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