typing: add type hints to argument checking functions in cmdutil
authorMatt Harbison <matt_harbison@yahoo.com>
Tue, 14 Feb 2023 12:40:59 -0500
changeset 50145 ce60c8d4ac87
parent 50144 a8d71a6ba205
child 50146 1cffc156f7cd
typing: add type hints to argument checking functions in cmdutil These might be surprising, since they can take strings instead of bytes. The way `AnyStr` works is that it must be all bytes or all str for any given invocation. The wildcard here will be the `opts` that get passed in- if the type is unknown and defaults to `Any`, there's no enforcement that the dict key type matches the additional args. But a lot of uses should be using `**opts` from the command method, which has a str key. The uses of these methods in this module are now typed because their internals force a specific type, and it can't just be inferred from the caller.
mercurial/cmdutil.py
--- a/mercurial/cmdutil.py	Tue Feb 14 15:45:26 2023 -0500
+++ b/mercurial/cmdutil.py	Tue Feb 14 12:40:59 2023 -0500
@@ -11,6 +11,15 @@
 import os
 import re
 
+from typing import (
+    Any,
+    AnyStr,
+    Dict,
+    Iterable,
+    Optional,
+    cast,
+)
+
 from .i18n import _
 from .node import (
     hex,
@@ -64,14 +73,10 @@
 )
 
 if pycompat.TYPE_CHECKING:
-    from typing import (
-        Any,
-        Dict,
+    from . import (
+        ui as uimod,
     )
 
-    for t in (Any, Dict):
-        assert t
-
 stringio = util.stringio
 
 # templates of common command options
@@ -268,13 +273,16 @@
 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
 
 
-def check_at_most_one_arg(opts, *args):
+def check_at_most_one_arg(
+    opts: Dict[AnyStr, Any],
+    *args: AnyStr,
+) -> Optional[AnyStr]:
     """abort if more than one of the arguments are in opts
 
     Returns the unique argument or None if none of them were specified.
     """
 
-    def to_display(name):
+    def to_display(name: AnyStr) -> bytes:
         return pycompat.sysbytes(name).replace(b'_', b'-')
 
     previous = None
@@ -289,7 +297,11 @@
     return previous
 
 
-def check_incompatible_arguments(opts, first, others):
+def check_incompatible_arguments(
+    opts: Dict[AnyStr, Any],
+    first: AnyStr,
+    others: Iterable[AnyStr],
+) -> None:
     """abort if the first argument is given along with any of the others
 
     Unlike check_at_most_one_arg(), `others` are not mutually exclusive
@@ -299,7 +311,7 @@
         check_at_most_one_arg(opts, first, other)
 
 
-def resolve_commit_options(ui, opts):
+def resolve_commit_options(ui: "uimod.ui", opts: Dict[str, Any]) -> bool:
     """modify commit options dict to handle related options
 
     The return value indicates that ``rewrite.update-timestamp`` is the reason
@@ -326,7 +338,7 @@
     return datemaydiffer
 
 
-def check_note_size(opts):
+def check_note_size(opts: Dict[str, Any]) -> None:
     """make sure note is of valid format"""
 
     note = opts.get('note')
@@ -1114,12 +1126,12 @@
         ctx.sub(s).bailifchanged(hint=hint)
 
 
-def logmessage(ui, opts):
+def logmessage(ui: "uimod.ui", opts: Dict[bytes, Any]) -> Optional[bytes]:
     """get the log message according to -m and -l option"""
 
     check_at_most_one_arg(opts, b'message', b'logfile')
 
-    message = opts.get(b'message')
+    message = cast(Optional[bytes], opts.get(b'message'))
     logfile = opts.get(b'logfile')
 
     if not message and logfile:
@@ -1464,7 +1476,7 @@
     return openstorage(repo, cmd, file_, opts, returnrevlog=True)
 
 
-def copy(ui, repo, pats, opts, rename=False):
+def copy(ui, repo, pats, opts: Dict[bytes, Any], rename=False):
     check_incompatible_arguments(opts, b'forget', [b'dry_run'])
 
     # called with the repo lock held
@@ -2777,7 +2789,7 @@
                 basefm,
                 fntemplate,
                 subprefix,
-                **pycompat.strkwargs(opts)
+                **pycompat.strkwargs(opts),
             ):
                 err = 0
         except error.RepoLookupError:
@@ -2931,7 +2943,7 @@
         return f not in ctx2.manifest()
 
 
-def amend(ui, repo, old, extra, pats, opts):
+def amend(ui, repo, old, extra, pats, opts: Dict[str, Any]):
     # avoid cycle context -> subrepo -> cmdutil
     from . import context