changeset 52521:48cdbd4d5443

typing: align the signatures of `repository.imanifestdict` overrides This is the same procedure as 048c11993d6a, where we bounce around to the various subclasses and steal their type hints to keep everything happy when the interface is explicitly subclassed.
author Matt Harbison <matt_harbison@yahoo.com>
date Wed, 23 Oct 2024 15:56:48 -0400
parents c855943e334b
children 3913f9509476
files hgext/git/manifest.py mercurial/interfaces/repository.py
diffstat 2 files changed, 86 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/git/manifest.py	Thu Oct 24 22:24:46 2024 -0400
+++ b/hgext/git/manifest.py	Wed Oct 23 15:56:48 2024 -0400
@@ -1,5 +1,13 @@
 from __future__ import annotations
 
+import typing
+
+from typing import (
+    Any,
+    Iterator,
+    Set,
+)
+
 from mercurial import (
     match as matchmod,
     pathutil,
@@ -12,6 +20,10 @@
 )
 from . import gitutil
 
+if typing.TYPE_CHECKING:
+    from typing import (
+        ByteString,  # TODO: change to Buffer for 3.14
+    )
 
 pygit2 = gitutil.get_pygit2()
 
@@ -71,16 +83,16 @@
             raise ValueError('unsupported mode %s' % oct(ent.filemode))
         return ent.id.raw, flags
 
-    def __getitem__(self, path):
+    def __getitem__(self, path: bytes) -> bytes:
         return self._resolve_entry(path)[0]
 
-    def find(self, path):
+    def find(self, path: bytes) -> tuple[bytes, bytes]:
         return self._resolve_entry(path)
 
-    def __len__(self):
+    def __len__(self) -> int:
         return len(list(self.walk(matchmod.always())))
 
-    def __nonzero__(self):
+    def __nonzero__(self) -> bool:
         try:
             next(iter(self))
             return True
@@ -89,30 +101,30 @@
 
     __bool__ = __nonzero__
 
-    def __contains__(self, path):
+    def __contains__(self, path: bytes) -> bool:
         try:
             self._resolve_entry(path)
             return True
         except KeyError:
             return False
 
-    def iterkeys(self):
+    def iterkeys(self) -> Iterator[bytes]:
         return self.walk(matchmod.always())
 
-    def keys(self):
+    def keys(self) -> list[bytes]:
         return list(self.iterkeys())
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[bytes]:
         return self.iterkeys()
 
-    def __setitem__(self, path, node):
+    def __setitem__(self, path: bytes, node: bytes) -> None:
         self._pending_changes[path] = node, self.flags(path)
 
-    def __delitem__(self, path):
+    def __delitem__(self, path: bytes) -> None:
         # TODO: should probably KeyError for already-deleted  files?
         self._pending_changes[path] = None
 
-    def filesnotin(self, other, match=None):
+    def filesnotin(self, other, match=None) -> Set[bytes]:
         if match is not None:
             match = matchmod.badmatch(match, lambda path, msg: None)
             sm2 = set(other.walk(match))
@@ -123,10 +135,18 @@
     def _dirs(self):
         return pathutil.dirs(self)
 
-    def hasdir(self, dir):
+    def hasdir(self, dir: bytes) -> bool:
         return dir in self._dirs
 
-    def diff(self, other, match=lambda x: True, clean=False):
+    def diff(
+        self,
+        other: Any,  # TODO: 'manifestdict' or (better) equivalent interface
+        match: Any = lambda x: True,  # TODO: Optional[matchmod.basematcher] = None,
+        clean: bool = False,
+    ) -> dict[
+        bytes,
+        tuple[tuple[bytes | None, bytes], tuple[bytes | None, bytes]] | None,
+    ]:
         """Finds changes between the current manifest and m2.
 
         The result is returned as a dict with filename as key and
@@ -200,42 +220,43 @@
 
         return result
 
-    def setflag(self, path, flag):
+    def setflag(self, path: bytes, flag: bytes) -> None:
         node, unused_flag = self._resolve_entry(path)
         self._pending_changes[path] = node, flag
 
-    def get(self, path, default=None):
+    def get(self, path: bytes, default=None) -> bytes | None:
         try:
             return self._resolve_entry(path)[0]
         except KeyError:
             return default
 
-    def flags(self, path):
+    def flags(self, path: bytes) -> bytes:
         try:
             return self._resolve_entry(path)[1]
         except KeyError:
             return b''
 
-    def copy(self):
+    def copy(self) -> 'gittreemanifest':
         return gittreemanifest(
             self._git_repo, self._tree, dict(self._pending_changes)
         )
 
-    def items(self):
+    def items(self) -> Iterator[tuple[bytes, bytes]]:
         for f in self:
             # TODO: build a proper iterator version of this
             yield f, self[f]
 
-    def iteritems(self):
+    def iteritems(self) -> Iterator[tuple[bytes, bytes]]:
         return self.items()
 
-    def iterentries(self):
+    def iterentries(self) -> Iterator[tuple[bytes, bytes, bytes]]:
         for f in self:
             # TODO: build a proper iterator version of this
             yield f, *self._resolve_entry(f)
 
-    def text(self):
-        assert False  # TODO can this method move out of the manifest iface?
+    def text(self) -> ByteString:
+        # TODO can this method move out of the manifest iface?
+        raise NotImplementedError
 
     def _walkonetree(self, tree, match, subdir):
         for te in tree:
@@ -249,7 +270,7 @@
             elif match(realname):
                 yield pycompat.fsencode(realname)
 
-    def walk(self, match):
+    def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
         # TODO: this is a very lazy way to merge in the pending
         # changes. There is absolutely room for optimization here by
         # being clever about walking over the sets...
--- a/mercurial/interfaces/repository.py	Thu Oct 24 22:24:46 2024 -0400
+++ b/mercurial/interfaces/repository.py	Wed Oct 23 15:56:48 2024 -0400
@@ -19,15 +19,22 @@
     Iterator,
     Mapping,
     Protocol,
+    Set,
 )
 
 from ..i18n import _
 from .. import error
 
 if typing.TYPE_CHECKING:
+    from typing import (
+        ByteString,  # TODO: change to Buffer for 3.14
+    )
+
     # Almost all mercurial modules are only imported in the type checking phase
     # to avoid circular imports
     from .. import (
+        match as matchmod,
+        pathutil,
         util,
     )
     from ..utils import (
@@ -1052,7 +1059,7 @@
     consists of a binary node and extra flags affecting that entry.
     """
 
-    def __getitem__(self, path):
+    def __getitem__(self, key: bytes) -> bytes:
         """Returns the binary node value for a path in the manifest.
 
         Raises ``KeyError`` if the path does not exist in the manifest.
@@ -1060,7 +1067,7 @@
         Equivalent to ``self.find(path)[0]``.
         """
 
-    def find(self, path):
+    def find(self, path: bytes) -> tuple[bytes, bytes]:
         """Returns the entry for a path in the manifest.
 
         Returns a 2-tuple of (node, flags).
@@ -1068,46 +1075,46 @@
         Raises ``KeyError`` if the path does not exist in the manifest.
         """
 
-    def __len__(self):
+    def __len__(self) -> int:
         """Return the number of entries in the manifest."""
 
-    def __nonzero__(self):
+    def __nonzero__(self) -> bool:
         """Returns True if the manifest has entries, False otherwise."""
 
     __bool__ = __nonzero__
 
-    def set(self, path, node, flags):
+    def set(self, path: bytes, node: bytes, flags: bytes) -> None:
         """Define the node value and flags for a path in the manifest.
 
         Equivalent to __setitem__ followed by setflag, but can be more efficient.
         """
 
-    def __setitem__(self, path, node):
+    def __setitem__(self, path: bytes, node: bytes) -> None:
         """Define the node value for a path in the manifest.
 
         If the path is already in the manifest, its flags will be copied to
         the new entry.
         """
 
-    def __contains__(self, path):
+    def __contains__(self, path: bytes) -> bool:
         """Whether a path exists in the manifest."""
 
-    def __delitem__(self, path):
+    def __delitem__(self, path: bytes) -> None:
         """Remove a path from the manifest.
 
         Raises ``KeyError`` if the path is not in the manifest.
         """
 
-    def __iter__(self):
+    def __iter__(self) -> Iterator[bytes]:
         """Iterate over paths in the manifest."""
 
-    def iterkeys(self):
+    def iterkeys(self) -> Iterator[bytes]:
         """Iterate over paths in the manifest."""
 
-    def keys(self):
+    def keys(self) -> list[bytes]:
         """Obtain a list of paths in the manifest."""
 
-    def filesnotin(self, other, match=None):
+    def filesnotin(self, other, match=None) -> Set[bytes]:
         """Obtain the set of paths in this manifest but not in another.
 
         ``match`` is an optional matcher function to be applied to both
@@ -1116,20 +1123,28 @@
         Returns a set of paths.
         """
 
-    def dirs(self):
+    def dirs(self) -> pathutil.dirs:
         """Returns an object implementing the ``idirs`` interface."""
 
-    def hasdir(self, dir):
+    def hasdir(self, dir: bytes) -> bool:
         """Returns a bool indicating if a directory is in this manifest."""
 
-    def walk(self, match):
+    def walk(self, match: matchmod.basematcher) -> Iterator[bytes]:
         """Generator of paths in manifest satisfying a matcher.
 
         If the matcher has explicit files listed and they don't exist in
         the manifest, ``match.bad()`` is called for each missing file.
         """
 
-    def diff(self, other, match=None, clean=False):
+    def diff(
+        self,
+        other: Any,  # TODO: 'manifestdict' or (better) equivalent interface
+        match: matchmod.basematcher | None = None,
+        clean: bool = False,
+    ) -> dict[
+        bytes,
+        tuple[tuple[bytes | None, bytes], tuple[bytes | None, bytes]] | None,
+    ]:
         """Find differences between this manifest and another.
 
         This manifest is compared to ``other``.
@@ -1146,41 +1161,43 @@
         are the same for the other manifest.
         """
 
-    def setflag(self, path, flag):
+    def setflag(self, path: bytes, flag: bytes) -> None:
         """Set the flag value for a given path.
 
         Raises ``KeyError`` if the path is not already in the manifest.
         """
 
-    def get(self, path, default=None):
+    def get(self, path: bytes, default=None) -> bytes | None:
         """Obtain the node value for a path or a default value if missing."""
 
-    def flags(self, path):
+    def flags(self, path: bytes) -> bytes:
         """Return the flags value for a path (default: empty bytestring)."""
 
-    def copy(self):
+    def copy(self) -> 'imanifestdict':
         """Return a copy of this manifest."""
 
-    def items(self):
+    def items(self) -> Iterator[tuple[bytes, bytes]]:
         """Returns an iterable of (path, node) for items in this manifest."""
 
-    def iteritems(self):
+    def iteritems(self) -> Iterator[tuple[bytes, bytes]]:
         """Identical to items()."""
 
-    def iterentries(self):
+    def iterentries(self) -> Iterator[tuple[bytes, bytes, bytes]]:
         """Returns an iterable of (path, node, flags) for this manifest.
 
         Similar to ``iteritems()`` except items are a 3-tuple and include
         flags.
         """
 
-    def text(self):
+    def text(self) -> ByteString:
         """Obtain the raw data representation for this manifest.
 
         Result is used to create a manifest revision.
         """
 
-    def fastdelta(self, base, changes):
+    def fastdelta(
+        self, base: ByteString, changes: Iterable[tuple[bytes, bool]]
+    ) -> tuple[ByteString, ByteString]:
         """Obtain a delta between this manifest and another given changes.
 
         ``base`` in the raw data representation for another manifest.