From 9b1fba438deaa8494183c7f3bb680cb7816f57ec Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 22 Feb 2026 15:54:07 -0800 Subject: [PATCH 1/3] stubtest: attempt to resolve decorators from their type Fixes #19689 --- mypy/stubtest.py | 40 +++++++++++++++++++++++++++++-- mypy/test/teststubtest.py | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index a6780984c1f54..503c12cd641e3 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -12,7 +12,6 @@ import enum import functools import importlib -import importlib.machinery import inspect import os import pkgutil @@ -35,11 +34,11 @@ import mypy.build import mypy.checkexpr -import mypy.checkmember import mypy.erasetype import mypy.modulefinder import mypy.nodes import mypy.state +import mypy.subtypes import mypy.types import mypy.version from mypy import nodes @@ -1540,11 +1539,48 @@ def apply_decorator_to_funcitem( for decorator in dec.original_decorators: resulting_func = apply_decorator_to_funcitem(decorator, func) if resulting_func is None: + # We couldn't figure out how to apply the decorator by transforming nodes, so try to + # reconstitute a FuncDef from the resulting type of the decorator + # This is worse because e.g. we lose the values of defaults + dec_type = mypy.types.get_proper_type(dec.type) + callable_type = None + if isinstance(dec_type, mypy.types.Instance): + callable_type = mypy.subtypes.find_member( + "__call__", dec_type, dec_type, is_operator=True + ) + elif isinstance(dec_type, mypy.types.CallableType): + callable_type = dec_type + + callable_type = mypy.types.get_proper_type(callable_type) + if isinstance(callable_type, mypy.types.CallableType): + return _resolve_funcitem_from_callable_type(callable_type) return None + func = resulting_func return func +def _resolve_funcitem_from_callable_type(typ: mypy.types.CallableType) -> nodes.FuncDef: + args: list[nodes.Argument] = [] + + for i, (arg_type, arg_kind, arg_name) in enumerate( + zip(typ.arg_types, typ.arg_kinds, typ.arg_names, strict=True) + ): + var_name = arg_name if arg_name is not None else f"__arg{i}" + var = nodes.Var(var_name, arg_type) + pos_only = arg_name is None and arg_kind == nodes.ARG_POS + args.append( + nodes.Argument( + variable=var, + type_annotation=arg_type, + initializer=None, # CallableType doesn't store the values of defaults + kind=arg_kind, + pos_only=pos_only, + ) + ) + return nodes.FuncDef(name=typ.name or "", arguments=args, body=nodes.Block([]), typ=typ) + + @verify.register(nodes.Decorator) def verify_decorator( stub: nodes.Decorator, runtime: MaybeMissing[Any], object_path: list[str] diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 45cc46c401082..b93891111f5b6 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -900,6 +900,56 @@ def f(a, *args): ... error=None, ) + @collect_cases + def test_decorated_overload(self) -> Iterator[Case]: + yield Case( + stub=""" + from typing import overload + + class _dec1: + def __init__(self, func: object) -> None: ... + def __call__(self, x: str) -> str: ... + + @overload + def good1(x: int) -> int: ... + @overload + @_dec1 + def good1(unrelated: int, whatever: str) -> str: ... + """, + runtime="def good1(x): ...", + error=None, + ) + yield Case( + stub=""" + class _dec2: + def __init__(self, func: object) -> None: ... + def __call__(self, x: str, y: int) -> str: ... + + @overload + def good2(x: int) -> str: ... + @overload + @_dec2 + def good2(unrelated: int, whatever: str) -> str: ... + """, + runtime="def good2(x, y=...): ...", + error=None, + ) + yield Case( + stub=""" + class _dec3: + def __init__(self, func: object) -> None: ... + def __call__(self, x: str, y: int) -> str: ... + + @overload + def bad(x: int) -> str: ... + @overload + @_dec3 + def bad(unrelated: int, whatever: str) -> str: ... + """, + runtime="def bad(x): ...", + error="bad", + ) + @collect_cases def test_property(self) -> Iterator[Case]: yield Case( From 637e5026e4a1a241b5fa565910c8ef88f9e1dd8a Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 22 Feb 2026 16:18:29 -0800 Subject: [PATCH 2/3] vararg varkwarg --- mypy/stubtest.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 503c12cd641e3..208efa206eb38 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1560,9 +1560,18 @@ def apply_decorator_to_funcitem( return func -def _resolve_funcitem_from_callable_type(typ: mypy.types.CallableType) -> nodes.FuncDef: - args: list[nodes.Argument] = [] +def _resolve_funcitem_from_callable_type(typ: mypy.types.CallableType) -> nodes.FuncDef | None: + if ( + typ.arg_kinds == [nodes.ARG_STAR, nodes.ARG_STAR2] + and (var_arg := typ.var_arg()) is not None + and isinstance(mypy.types.get_proper_type(var_arg.typ), mypy.types.AnyType) + and (var_kwarg := typ.kw_arg()) is not None + and isinstance(mypy.types.get_proper_type(var_kwarg.typ), mypy.types.AnyType) + ): + # There isn't a FuncDef we can invent corresponding to a Callable[..., T] + return None + args: list[nodes.Argument] = [] for i, (arg_type, arg_kind, arg_name) in enumerate( zip(typ.arg_types, typ.arg_kinds, typ.arg_names, strict=True) ): From 5a06d0800ace7bab624f04816c7829083fd11fc7 Mon Sep 17 00:00:00 2001 From: Shantanu Jain Date: Sun, 22 Feb 2026 16:30:12 -0800 Subject: [PATCH 3/3] classmethod --- mypy/stubtest.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 208efa206eb38..5f5768fa07ef3 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -1521,7 +1521,7 @@ def apply_decorator_to_funcitem( ): return func if decorator.fullname == "builtins.classmethod": - if func.arguments[0].variable.name not in ("cls", "mcs", "metacls"): + 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}" @@ -1553,14 +1553,16 @@ def apply_decorator_to_funcitem( callable_type = mypy.types.get_proper_type(callable_type) if isinstance(callable_type, mypy.types.CallableType): - return _resolve_funcitem_from_callable_type(callable_type) + return _resolve_funcitem_from_callable_type(dec, callable_type) return None func = resulting_func return func -def _resolve_funcitem_from_callable_type(typ: mypy.types.CallableType) -> nodes.FuncDef | None: +def _resolve_funcitem_from_callable_type( + dec: nodes.Decorator, typ: mypy.types.CallableType +) -> nodes.FuncDef | None: if ( typ.arg_kinds == [nodes.ARG_STAR, nodes.ARG_STAR2] and (var_arg := typ.var_arg()) is not None @@ -1587,7 +1589,18 @@ def _resolve_funcitem_from_callable_type(typ: mypy.types.CallableType) -> nodes. pos_only=pos_only, ) ) - return nodes.FuncDef(name=typ.name or "", arguments=args, body=nodes.Block([]), typ=typ) + + if dec.func.is_class: + if not args: + return None + # Munge classmethods, similar to logic in _resolve_funcitem_from_decorator + if args[0].variable.name not in ("_cls", "cls", "mcs", "metacls"): + return None + args.pop(0) + + ret = nodes.FuncDef(name=typ.name or "", arguments=args, body=nodes.Block([]), typ=typ) + ret.is_class = dec.func.is_class + return ret @verify.register(nodes.Decorator)