changeset 49810:a9faacdc5943

typing: add type hints to mercurial/win32.py These are the low level functions that are imported by the mercurial.windows module, which is in turn imported by mercurial.utils as the platform module. Pretty straightforward, but pytype inferred very little of it, likely because of the heavy ctypes usage. It also seems to trigger a pytype bug in procutil, now that it has an idea of the underlying function type, so disable that warning to maintain a working test.
author Matt Harbison <matt_harbison@yahoo.com>
date Thu, 15 Dec 2022 18:02:55 -0500
parents 7a80a614c9e5
children 0a91aba258e0
files mercurial/posix.py mercurial/utils/procutil.py mercurial/win32.py
diffstat 3 files changed, 37 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/posix.py	Thu Dec 15 15:46:25 2022 -0500
+++ b/mercurial/posix.py	Thu Dec 15 18:02:55 2022 -0500
@@ -625,7 +625,7 @@
     return pycompat.rapply(pycompat.fsencode, list(grp.getgrnam(name).gr_mem))
 
 
-def spawndetached(args):
+def spawndetached(args) -> int:
     return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), args[0], args)
 
 
--- a/mercurial/utils/procutil.py	Thu Dec 15 15:46:25 2022 -0500
+++ b/mercurial/utils/procutil.py	Thu Dec 15 18:02:55 2022 -0500
@@ -585,7 +585,7 @@
     return _gethgcmd()
 
 
-def rundetached(args, condfn):
+def rundetached(args, condfn) -> int:
     """Execute the argument list in a detached process.
 
     condfn is a callable which is called repeatedly and should return
@@ -621,6 +621,12 @@
         if prevhandler is not None:
             signal.signal(signal.SIGCHLD, prevhandler)
 
+        # pytype seems to get confused by not having a return in the finally
+        # block, and thinks the return value should be Optional[int] here.  It
+        # appears to be https://github.com/google/pytype/issues/938, without
+        # the `with` clause.
+        pass  # pytype: disable=bad-return-type
+
 
 @contextlib.contextmanager
 def uninterruptible(warn):
--- a/mercurial/win32.py	Thu Dec 15 15:46:25 2022 -0500
+++ b/mercurial/win32.py	Thu Dec 15 18:02:55 2022 -0500
@@ -14,6 +14,13 @@
 import random
 import subprocess
 
+from typing import (
+    List,
+    NoReturn,
+    Optional,
+    Tuple,
+)
+
 from . import (
     encoding,
     pycompat,
@@ -356,7 +363,7 @@
 _kernel32.PeekNamedPipe.restype = _BOOL
 
 
-def _raiseoserror(name):
+def _raiseoserror(name: bytes) -> NoReturn:
     # Force the code to a signed int to avoid an 'int too large' error.
     # See https://bugs.python.org/issue28474
     code = _kernel32.GetLastError()
@@ -368,7 +375,7 @@
     )
 
 
-def _getfileinfo(name):
+def _getfileinfo(name: bytes) -> _BY_HANDLE_FILE_INFORMATION:
     fh = _kernel32.CreateFileA(
         name,
         0,
@@ -389,7 +396,7 @@
         _kernel32.CloseHandle(fh)
 
 
-def checkcertificatechain(cert, build=True):
+def checkcertificatechain(cert: bytes, build: bool = True) -> bool:
     """Tests the given certificate to see if there is a complete chain to a
     trusted root certificate.  As a side effect, missing certificates are
     downloaded and installed unless ``build=False``.  True is returned if a
@@ -439,7 +446,7 @@
         _crypt32.CertFreeCertificateContext(certctx)
 
 
-def oslink(src, dst):
+def oslink(src: bytes, dst: bytes) -> None:
     try:
         if not _kernel32.CreateHardLinkA(dst, src, None):
             _raiseoserror(src)
@@ -447,12 +454,12 @@
         _raiseoserror(src)
 
 
-def nlinks(name):
+def nlinks(name: bytes) -> int:
     '''return number of hardlinks for the given file'''
     return _getfileinfo(name).nNumberOfLinks
 
 
-def samefile(path1, path2):
+def samefile(path1: bytes, path2: bytes) -> bool:
     '''Returns whether path1 and path2 refer to the same file or directory.'''
     res1 = _getfileinfo(path1)
     res2 = _getfileinfo(path2)
@@ -463,14 +470,14 @@
     )
 
 
-def samedevice(path1, path2):
+def samedevice(path1: bytes, path2: bytes) -> bool:
     '''Returns whether path1 and path2 are on the same device.'''
     res1 = _getfileinfo(path1)
     res2 = _getfileinfo(path2)
     return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber
 
 
-def peekpipe(pipe):
+def peekpipe(pipe) -> int:
     handle = msvcrt.get_osfhandle(pipe.fileno())  # pytype: disable=module-attr
     avail = _DWORD()
 
@@ -485,14 +492,14 @@
     return avail.value
 
 
-def lasterrorwaspipeerror(err):
+def lasterrorwaspipeerror(err) -> bool:
     if err.errno != errno.EINVAL:
         return False
     err = _kernel32.GetLastError()
     return err == _ERROR_BROKEN_PIPE or err == _ERROR_NO_DATA
 
 
-def testpid(pid):
+def testpid(pid: int) -> bool:
     """return True if pid is still running or unable to
     determine, False otherwise"""
     h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid)
@@ -506,7 +513,7 @@
     return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER
 
 
-def executablepath():
+def executablepath() -> bytes:
     '''return full path of hg.exe'''
     size = 600
     buf = ctypes.create_string_buffer(size + 1)
@@ -520,7 +527,7 @@
     return buf.value
 
 
-def getvolumename(path):
+def getvolumename(path: bytes) -> Optional[bytes]:
     """Get the mount point of the filesystem from a directory or file
     (best-effort)
 
@@ -541,7 +548,7 @@
     return buf.value
 
 
-def getfstype(path):
+def getfstype(path: bytes) -> Optional[bytes]:
     """Get the filesystem type name from a directory or file (best-effort)
 
     Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
@@ -572,7 +579,7 @@
     return name.value
 
 
-def getuser():
+def getuser() -> bytes:
     '''return name of current user'''
     size = _DWORD(300)
     buf = ctypes.create_string_buffer(size.value + 1)
@@ -581,10 +588,10 @@
     return buf.value
 
 
-_signalhandler = []
+_signalhandler: List[_SIGNAL_HANDLER] = []
 
 
-def setsignalhandler():
+def setsignalhandler() -> None:
     """Register a termination handler for console events including
     CTRL+C. python signal handlers do not work well with socket
     operations.
@@ -601,7 +608,7 @@
         raise ctypes.WinError()  # pytype: disable=module-attr
 
 
-def hidewindow():
+def hidewindow() -> None:
     def callback(hwnd, pid):
         wpid = _DWORD()
         _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid))
@@ -614,7 +621,7 @@
     _user32.EnumWindows(_WNDENUMPROC(callback), pid)
 
 
-def termsize():
+def termsize() -> Tuple[int, int]:
     # cmd.exe does not handle CR like a unix console, the CR is
     # counted in the line length. On 80 columns consoles, if 80
     # characters are written, the following CR won't apply on the
@@ -635,7 +642,7 @@
     return width, height
 
 
-def enablevtmode():
+def enablevtmode() -> bool:
     """Enable virtual terminal mode for the associated console.  Return True if
     enabled, else False."""
 
@@ -661,7 +668,7 @@
     return True
 
 
-def spawndetached(args):
+def spawndetached(args: List[bytes]) -> int:
     # No standard library function really spawns a fully detached
     # process under win32 because they allocate pipes or other objects
     # to handle standard streams communications. Passing these objects
@@ -703,7 +710,7 @@
     return pi.dwProcessId
 
 
-def unlink(f):
+def unlink(f: bytes) -> None:
     '''try to implement POSIX' unlink semantics on Windows'''
 
     if os.path.isdir(f):
@@ -758,7 +765,7 @@
             pass
 
 
-def makedir(path, notindexed):
+def makedir(path: bytes, notindexed: bool) -> None:
     os.mkdir(path)
     if notindexed:
         _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)