From 380e2ba3b88b1fc949bfbc733b73eb1d46d5cbc2 Mon Sep 17 00:00:00 2001 From: STerliakov <terlya.stas@gmail.com> Date: Sun, 4 May 2025 16:21:22 +0200 Subject: [PATCH 1/2] Bind selftypes in decorated classmethods --- mypy/checkmember.py | 6 ++++- test-data/unit/check-selftype.test | 41 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 1a76372d4731..26cbed470659 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -1203,9 +1203,13 @@ def analyze_class_attribute_access( t = get_proper_type(t) if isinstance(t, FunctionLike) and is_classmethod: t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg) - result = add_class_tvars( + t = add_class_tvars( t, isuper, is_classmethod, is_staticmethod, mx.self_type, original_vars=original_vars ) + if is_decorated and not is_staticmethod: + t = expand_self_type_if_needed(t, mx, node.node.var, itype, is_class=is_classmethod) + + result = t # __set__ is not called on class objects. if not mx.is_lvalue: result = analyze_descriptor_access(result, mx) diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index ffa1a369e883..36a0fde31f19 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -2221,3 +2221,44 @@ class B: class C(A, B): # OK: both methods take Self pass [builtins fixtures/tuple.pyi] + +[case testSelfInFuncDecoratedClassmethod] +from collections.abc import Callable +from typing import Self, TypeVar + +T = TypeVar("T") + +def debug(make: Callable[[type[T]], T]) -> Callable[[type[T]], T]: + return make + +class Foo: + @classmethod + @debug + def make(cls) -> Self: + return cls() + +class Bar(Foo): ... + +reveal_type(Foo.make()) # N: Revealed type is "__main__.Foo" +reveal_type(Foo().make()) # N: Revealed type is "__main__.Foo" +reveal_type(Bar.make()) # N: Revealed type is "__main__.Bar" +reveal_type(Bar().make()) # N: Revealed type is "__main__.Bar" +[builtins fixtures/tuple.pyi] + +[case testSelfInClassDecoratedClassmethod] +from typing import Callable, Generic, TypeVar, Self + +T = TypeVar("T") + +class W(Generic[T]): + def __init__(self, fn: Callable[..., T]) -> None: ... + def __call__(self) -> T: ... + +class Check: + @W + def foo(self) -> Self: + ... + +reveal_type(Check.foo()) # N: Revealed type is "def () -> __main__.Check" +reveal_type(Check().foo()) # N: Revealed type is "__main__.Check" +[builtins fixtures/tuple.pyi] From 4442e4a52f3fbbc75f70f2788aaca622c518edb4 Mon Sep 17 00:00:00 2001 From: STerliakov <terlya.stas@gmail.com> Date: Sun, 4 May 2025 16:26:12 +0200 Subject: [PATCH 2/2] Fix typing --- mypy/checkmember.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 26cbed470659..0210f8518434 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -1207,7 +1207,9 @@ def analyze_class_attribute_access( t, isuper, is_classmethod, is_staticmethod, mx.self_type, original_vars=original_vars ) if is_decorated and not is_staticmethod: - t = expand_self_type_if_needed(t, mx, node.node.var, itype, is_class=is_classmethod) + t = expand_self_type_if_needed( + t, mx, cast(Decorator, node.node).var, itype, is_class=is_classmethod + ) result = t # __set__ is not called on class objects.