diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 4a38939a0390..61587469b5a6 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1574,14 +1574,14 @@ def apply_decorator_to_funcitem( ): return func if decorator.fullname == "builtins.classmethod": - if func.arguments[0].variable.name not in ("_cls", "cls", "mcs", "metacls"): - raise StubtestFailure( - f"unexpected class parameter name {func.arguments[0].variable.name!r} " - f"in {dec.fullname}" - ) + if not func.arguments or func.arguments[0].kind not in (nodes.ARG_POS, nodes.ARG_OPT): + # Nothing to drop, e.g. `def f(*args)`. inspect.signature of the runtime + # classmethod keeps the star argument too, so the two already line up. + return func # FuncItem is written so that copy.copy() actually works, even when compiled ret = copy.copy(func) - # Remove the cls argument, since it's not present in inspect.signature of classmethods + # Remove the cls argument, since it's not present in inspect.signature of classmethods. + # It can be given any name, so don't look at the name to decide. ret.arguments = ret.arguments[1:] return ret # Just give up on any other decorators. After excluding properties, we don't run into diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 2b54a150892b..24ce57942966 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -682,6 +682,33 @@ def __new__(cls, *args, **kwargs): pass """, error=None, ) + # The first parameter of a classmethod can be called anything, see #16583 + yield Case( + stub=""" + class GoodOddClsName: + @classmethod + def f(GoodOddClsName, number: int) -> None: ... + """, + runtime=""" + class GoodOddClsName: + @classmethod + def f(GoodOddClsName, number): pass + """, + error=None, + ) + yield Case( + stub=""" + class GoodStarArgsCls: + @classmethod + def f(*args: int) -> None: ... + """, + runtime=""" + class GoodStarArgsCls: + @classmethod + def f(*args): pass + """, + error=None, + ) @collect_cases def test_arg_mismatch(self) -> Iterator[Case]: