# HG changeset patch # User Matt Harbison # Date 1676396459 18000 # Node ID ce60c8d4ac87ec2c0d44ed87ea272cd3de421210 # Parent a8d71a6ba2050c1689c6c4227b9ae8f55fa1f409 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. diff -r a8d71a6ba205 -r ce60c8d4ac87 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