# HG changeset patch # User Matt Harbison # Date 1671000693 18000 # Node ID 55d45d0de4e723220b33f992452fc1dc65719f4c # Parent f3f33980f19bb994469e2a0940c887dddc90c288 typing: add type hints to pycompat.bytestr The problem with leaving pytype to its own devices here was that for functions that returned a bytestr, pytype inferred `Union[bytes, int]`. It now accepts that it can be treated as plain bytes. I wasn't able to figure out the arg type for `__getitem__`- `SupportsIndex` (which PyCharm indicated is how the superclass function is typed) got flagged: File "/mnt/c/Users/Matt/hg/mercurial/pycompat.py", line 236, in __getitem__: unsupported operand type(s) for item retrieval: bytestr and SupportsIndex [unsupported-operands] Function __getitem__ on bytestr expects int But some caller got flagged when I marked it as `int`. There's some minor spillover problems elsewhere- pytype doesn't seem to recognize that `bytes.startswith()` can optionally take a 3rd and 4th arg, so those few places have the warning disabled. It also flags where the tar API is being abused, but that would be a tricky refactor (and would require typing extensions until py3.7 is dropped), so disable those too. diff -r f3f33980f19b -r 55d45d0de4e7 mercurial/archival.py --- a/mercurial/archival.py Wed Dec 14 01:38:52 2022 -0500 +++ b/mercurial/archival.py Wed Dec 14 01:51:33 2022 -0500 @@ -154,9 +154,14 @@ ) self.fileobj = gzfileobj return ( + # taropen() wants Literal['a', 'r', 'w', 'x'] for the mode, + # but Literal[] is only available in 3.8+ without the + # typing_extensions backport. + # pytype: disable=wrong-arg-types tarfile.TarFile.taropen( # pytype: disable=attribute-error name, pycompat.sysstr(mode), gzfileobj ) + # pytype: enable=wrong-arg-types ) else: try: diff -r f3f33980f19b -r 55d45d0de4e7 mercurial/pycompat.py --- a/mercurial/pycompat.py Wed Dec 14 01:38:52 2022 -0500 +++ b/mercurial/pycompat.py Wed Dec 14 01:51:33 2022 -0500 @@ -29,8 +29,12 @@ import xmlrpc.client as xmlrpclib from typing import ( + Iterable, + Iterator, List, Optional, + Type, + TypeVar, ) ispy3 = sys.version_info[0] >= 3 @@ -42,6 +46,8 @@ TYPE_CHECKING = typing.TYPE_CHECKING +_Tbytestr = TypeVar('_Tbytestr', bound='bytestr') + def future_set_exception_info(f, exc_info): f.set_exception(exc_info[0]) @@ -212,10 +218,10 @@ # https://github.com/google/pytype/issues/500 if TYPE_CHECKING: - def __init__(self, s=b''): + def __init__(self, s: object = b'') -> None: pass - def __new__(cls, s=b''): + def __new__(cls: Type[_Tbytestr], s: object = b'') -> _Tbytestr: if isinstance(s, bytestr): return s if not isinstance( @@ -226,20 +232,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: Iterable[int]) -> Iterator[bytes]: """Iterate bytes as if it were a str object of Python 2""" return map(bytechr, s) diff -r f3f33980f19b -r 55d45d0de4e7 mercurial/templater.py --- a/mercurial/templater.py Wed Dec 14 01:38:52 2022 -0500 +++ b/mercurial/templater.py Wed Dec 14 01:51:33 2022 -0500 @@ -177,10 +177,17 @@ quote = program[pos : pos + 2] s = pos = pos + 2 while pos < end: # find closing escaped quote + # pycompat.bytestr (and bytes) both have .startswith() that + # takes an optional start and an optional end, but pytype thinks + # it only takes 2 args. + + # pytype: disable=wrong-arg-count if program.startswith(b'\\\\\\', pos, end): pos += 4 # skip over double escaped characters continue if program.startswith(quote, pos, end): + # pytype: enable=wrong-arg-count + # interpret as if it were a part of an outer string data = parser.unescapestr(program[s:pos]) if token == b'template': @@ -300,7 +307,14 @@ return parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, b'}')) + + # pycompat.bytestr (and bytes) both have .startswith() that + # takes an optional start and an optional end, but pytype thinks + # it only takes 2 args. + + # pytype: disable=wrong-arg-count if not tmpl.startswith(b'}', pos): + # pytype: enable=wrong-arg-count raise error.ParseError(_(b"invalid token"), pos) yield (b'template', parseres, n) pos += 1