diff --git a/tests/test_type_eval.py b/tests/test_type_eval.py index 0963ab5..8a57b53 100644 --- a/tests/test_type_eval.py +++ b/tests/test_type_eval.py @@ -226,6 +226,23 @@ def test_type_getattr_union_5(): assert d == int | list[float] | TB +def test_getmembertype_classmethod_01(): + class C: + @classmethod + def f(cls, x: int) -> int: ... + @classmethod + def g(cls: type[C], x: int) -> int: ... + @classmethod + def h(cls: type[Self], x: int) -> int: ... + + d = eval_typing(GetMemberType[C, Literal["f"]]) + assert d == classmethod[C, tuple[Param[Literal["x"], int]], int] + d = eval_typing(GetMemberType[C, Literal["g"]]) + assert d == classmethod[C, tuple[Param[Literal["x"], int]], int] + d = eval_typing(GetMemberType[C, Literal["h"]]) + assert d == classmethod[Self, tuple[Param[Literal["x"], int]], int] + + def test_type_strings_1(): d = eval_typing(Uppercase[Literal["foo"]]) assert d == Literal["FOO"] @@ -1634,7 +1651,7 @@ def test_new_protocol_with_methods_02(): ], Member[ Literal["class_method"], - classmethod[type[Self], tuple[Param[Literal["x"], int]], int], + classmethod[Self, tuple[Param[Literal["x"], int]], int], Literal["ClassVar"], ], Member[ diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 38a5587..c2f3c6c 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -599,9 +599,6 @@ def _callable_type_to_method(name, typ, ctx): # For __init_subclass__ generic on cls: T, keep type[T] cls_typ = type[cls] # type: ignore[name-defined] else: - # An annoying thing to know is that for a member classmethod of C, - # cls *should* be type[C], but if it was not explicitly annotated, - # it will be C. cls_typ = type[typing.Self] # type: ignore[name-defined] cls_param = Param[typing.Literal["cls"], cls_typ, quals] typ = typing.Callable[[cls_param] + list(typing.get_args(params)), ret] @@ -658,7 +655,15 @@ def _ann(x): if ann is empty: ann = receiver_type else: - specified_receiver = ann + if ( + isinstance(func, classmethod) + and typing.get_origin(ann) is type + and (receiver_args := typing.get_args(ann)) + ): + # The annotation for cls in a classmethod should be type[C] + specified_receiver = receiver_args[0] + else: + specified_receiver = ann quals = [] if p.kind == inspect.Parameter.VAR_POSITIONAL: