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.
--- 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:
--- 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)
--- 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