From cf768d1d262b9d86bc14167ab8c6d0611ede6b4f Mon Sep 17 00:00:00 2001 From: iliyasone Date: Wed, 27 May 2026 01:57:17 +0300 Subject: [PATCH] Skip method evaluation when collecting Attrs Attrs should collect class attributes without resolving ordinary method annotations. Thread an include_methods flag into get_local_defns and disable method processing from the attrs_only path. Add a regression test covering a method annotation that references a TYPE_CHECKING-only name. --- tests/test_attrs_type_checking.py | 26 ++++++++++++++++++++++++++ typemap/type_eval/_apply_generic.py | 4 +++- typemap/type_eval/_eval_operators.py | 4 +++- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 tests/test_attrs_type_checking.py diff --git a/tests/test_attrs_type_checking.py b/tests/test_attrs_type_checking.py new file mode 100644 index 0000000..c5fbf70 --- /dev/null +++ b/tests/test_attrs_type_checking.py @@ -0,0 +1,26 @@ +from typing import TYPE_CHECKING, Literal + +from typemap.type_eval import eval_typing +import typemap_extensions as typing + +if TYPE_CHECKING: + + class HiddenNamespace: + pass + + +class Base: + def rebuild(self, ns: HiddenNamespace | None = None) -> bool | None: + return None + + +class Model(Base): + name: str + + +def test_attrs_does_not_evaluate_method_annotations() -> None: + attrs = eval_typing(typing.Attrs[Model]) + + assert len(attrs.__args__) == 1 + assert eval_typing(attrs.__args__[0].name) == Literal["name"] + assert eval_typing(attrs.__args__[0].type) is str diff --git a/typemap/type_eval/_apply_generic.py b/typemap/type_eval/_apply_generic.py index fc43df1..0c6b365 100644 --- a/typemap/type_eval/_apply_generic.py +++ b/typemap/type_eval/_apply_generic.py @@ -323,6 +323,8 @@ def _resolved_function_signature(func, args): def get_local_defns( boxed: Boxed, + *, + include_methods: bool = True, ) -> tuple[ dict[str, Any], dict[ @@ -343,7 +345,7 @@ def get_local_defns( stuff = inspect.unwrap(orig) - if isinstance(stuff, types.FunctionType): + if include_methods and isinstance(stuff, types.FunctionType): local_fn: Any = None # TODO: This annos_ok thing is a hack because processing diff --git a/typemap/type_eval/_eval_operators.py b/typemap/type_eval/_eval_operators.py index 2b45997..8449ccf 100644 --- a/typemap/type_eval/_eval_operators.py +++ b/typemap/type_eval/_eval_operators.py @@ -121,7 +121,9 @@ def get_annotated_type_hints(cls, *, ctx, attrs_only=False, **kwargs): for abox in reversed(box.mro): acls = abox.alias_type() - annos, _ = _apply_generic.get_local_defns(abox) + annos, _ = _apply_generic.get_local_defns( + abox, include_methods=not attrs_only + ) for k, ty in annos.items(): quals = set()