Skip to content

[ty] Diagnose invalid descriptor __get__ calls - #27400

Merged
charliermarsh merged 41 commits into
mainfrom
charlie/invalid-descriptor-get-signature
Aug 6, 2026
Merged

charliermarsh merged 41 commits into
mainfrom
charlie/invalid-descriptor-get-signature

Conversation

@charliermarsh

@charliermarsh charliermarsh commented Jul 31, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR reports invalid-attribute-access when an attribute read implicitly calls a descriptor's __get__ method with incompatible arguments.

class Descriptor:
    def __get__(self):
        pass

class C:
    value = Descriptor()

C().value  # error: `__get__` receives the descriptor, instance, and owner

Previously, descriptor lookup recovered the inferred return type from a failed __get__ call and discarded the call error.

The core change here is straightforward, but it ends up being a significant diff because we need to preserve errors that we were previously discarding during attribute lookup, and that happens deep in the stack. Conceptually, we made this change:

// Before
member_lookup(...) -> PlaceAndQualifiers

// After
type MemberLookupResult = Result<PlaceAndQualifiers, MemberLookupError>;
member_lookup(...) -> MemberLookupResult

The lookup error retains the recovered member type to avoid cascading diagnostics, and is threaded through the various lookup sites.

Closes astral-sh/ty#4129.

@astral-sh-bot astral-sh-bot Bot added the ty The ty type checker label Jul 31, 2026
@astral-sh-bot

astral-sh-bot Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 96.96%. The percentage of expected errors that received a diagnostic held steady at 92.96%. The number of fully passing files held steady at 106/133.

@astral-sh-bot

astral-sh-bot Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Memory usage report

Summary

Project Old New Diff Outcome
prefect 667.81MB 667.92MB +0.02% (109.97kB) ⏫
sphinx 208.30MB 208.34MB +0.02% (37.21kB) ⏫
trio 94.65MB 94.66MB +0.01% (12.37kB) ⏫
flake8 40.42MB 40.42MB +0.00% (936.00B) ⏫

Significant changes

Click to expand detailed breakdown

prefect

Name Old New Diff Outcome
try_call_dunder_get_inner 1.19MB 1.29MB +9.04% (109.97kB) ⏫

sphinx

Name Old New Diff Outcome
try_call_dunder_get_inner 305.60kB 335.21kB +9.69% (29.61kB) ⏫
try_call_dunder_get_inner::interned_arguments 377.12kB 383.91kB +1.80% (6.78kB) ⏫
Type<'db>::class_member_with_policy_inner_ 3.33MB 3.34MB +0.02% (792.00B) ⏫
MemberLookupKey 3.42MB 3.43MB +0.02% (624.00B) ⏫
TypePair 2.71MB 2.71MB -0.01% (192.00B) ⬇️
IntersectionType 773.95kB 773.78kB -0.02% (168.00B) ⬇️
UnionType 804.64kB 804.53kB -0.01% (112.00B) ⬇️
is_redundant_with_impl 1.02MB 1.02MB -0.01% (104.00B) ⬇️

trio

Name Old New Diff Outcome
try_call_dunder_get_inner 101.34kB 110.95kB +9.49% (9.62kB) ⏫
DescriptorGetCallContext 0.00B 1.17kB +1.17kB (new) ⏫
MemberLookupError 0.00B 960.00B +960.00B (new) ⏫
member_lookup_with_policy_inner 1.45MB 1.45MB +0.02% (328.00B) ⏫
Type<'db>::class_member_with_policy_inner_ 1.17MB 1.17MB +0.02% (224.00B) ⏫
MemberLookupKey 1.15MB 1.15MB +0.01% (104.00B) ⏫

flake8

Name Old New Diff Outcome
try_call_dunder_get_inner 14.93kB 15.84kB +6.12% (936.00B) ⏫

@astral-sh-bot

astral-sh-bot Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-attribute-access 140 0 0
Total 140 0 0

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Raw diff (140 changes)
Tanjun (https://github.com/FasterSpeeding/Tanjun)
+ tanjun/commands/menu.py:176:60 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `(_MessageCallbackSigT@decorator & MenuCommand[Unknown, Unknown]) | (_MessageCallbackSigT@decorator & MessageCommand[Unknown]) | (_MessageCallbackSigT@decorator & SlashCommand[Unknown]) | ... omitted 3 union elements`: Argument type `MessageCommand[_MessageCallbackSigT@decorator]` does not satisfy upper bound `MessageCommand[_MessageCallbackSigT@MessageCommand]` of type variable `Self`
+ tanjun/commands/menu.py:303:57 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `(_UserCallbackSigT@decorator & MenuCommand[Unknown, Unknown]) | (_UserCallbackSigT@decorator & MessageCommand[Unknown]) | (_UserCallbackSigT@decorator & SlashCommand[Unknown]) | ... omitted 3 union elements`: Argument type `MessageCommand[_UserCallbackSigT@decorator]` does not satisfy upper bound `MessageCommand[_MessageCallbackSigT@MessageCommand]` of type variable `Self`
+ tanjun/commands/menu.py:522:57 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `(_UserCallbackSigT@__init__ & MenuCommand[Unknown, Unknown]) | (_UserCallbackSigT@__init__ & MessageCommand[Unknown]) | (_UserCallbackSigT@__init__ & SlashCommand[Unknown]) | ... omitted 9 union elements`: Argument type `MessageCommand[_UserCallbackSigT@__init__]` does not satisfy upper bound `MessageCommand[_MessageCallbackSigT@MessageCommand]` of type variable `Self`
+ tanjun/commands/message.py:109:60 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `MenuCommand[_MessageCallbackSigT@decorator, Any] | MessageCommand[_MessageCallbackSigT@decorator] | SlashCommand[_MessageCallbackSigT@decorator] | ... omitted 3 union elements`: Argument type `MenuCommand[_MessageCallbackSigT@decorator, Any]` does not satisfy upper bound `MenuCommand[_MenuCallbackSigT@MenuCommand, _MenuTypeT@MenuCommand]` of type variable `Self`
+ tanjun/commands/message.py:166:60 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `MenuCommand[_MessageCallbackSigT@decorator, Any] | MessageCommand[_MessageCallbackSigT@decorator] | SlashCommand[_MessageCallbackSigT@decorator] | ... omitted 3 union elements`: Argument type `MenuCommand[_MessageCallbackSigT@decorator, Any]` does not satisfy upper bound `MenuCommand[_MenuCallbackSigT@MenuCommand, _MenuTypeT@MenuCommand]` of type variable `Self`
+ tanjun/commands/message.py:236:60 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `MenuCommand[_MessageCallbackSigT@MessageCommand, Any] | MessageCommand[_MessageCallbackSigT@MessageCommand] | SlashCommand[_MessageCallbackSigT@MessageCommand] | ... omitted 3 union elements`: Argument type `MenuCommand[_MessageCallbackSigT@MessageCommand, Any]` does not satisfy upper bound `MenuCommand[_MenuCallbackSigT@MenuCommand, _MenuTypeT@MenuCommand]` of type variable `Self`
+ tanjun/commands/slash.py:381:58 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `(_SlashCallbackSigT@decorator & MenuCommand[Unknown, Unknown]) | (_SlashCallbackSigT@decorator & MessageCommand[Unknown]) | (_SlashCallbackSigT@decorator & SlashCommand[Unknown]) | ... omitted 3 union elements`: Argument type `MenuCommand[_SlashCallbackSigT@decorator, Any]` does not satisfy upper bound `MenuCommand[_MenuCallbackSigT@MenuCommand, _MenuTypeT@MenuCommand]` of type variable `Self`
+ tanjun/commands/slash.py:1644:58 error[invalid-attribute-access] Invalid access to descriptor attribute `callback` on type `(_SlashCallbackSigT@SlashCommand & MenuCommand[Unknown, Unknown]) | (_SlashCallbackSigT@SlashCommand & MessageCommand[Unknown]) | (_SlashCallbackSigT@SlashCommand & SlashCommand[Unknown]) | ... omitted 3 union elements`: Argument type `MenuCommand[_SlashCallbackSigT@SlashCommand, Any]` does not satisfy upper bound `MenuCommand[_MenuCallbackSigT@MenuCommand, _MenuTypeT@MenuCommand]` of type variable `Self`

asynq (https://github.com/quora/asynq)
+ asynq/tests/test_asynq_to_async.py:150:15 error[invalid-attribute-access] Invalid access to descriptor attribute `f` on type `A`: Expected `PureAsyncDecorator[Any, (Any, /, x)]`, found `AsyncDecorator[Any, (self, x)]`
+ asynq/tests/test_asynq_to_async.py:154:23 error[invalid-attribute-access] Invalid access to descriptor attribute `f` on type `A`: Expected `PureAsyncDecorator[Any, (Any, /, x)]`, found `AsyncDecorator[Any, (self, x)]`
+ asynq/tests/test_asynq_to_async.py:157:27 error[invalid-attribute-access] Invalid access to descriptor attribute `f` on type `A`: Expected `PureAsyncDecorator[Any, (Any, /, x)]`, found `AsyncDecorator[Any, (self, x)]`
+ asynq/tests/test_asynq_to_async.py:226:27 error[invalid-attribute-access] Invalid access to descriptor attribute `g` on type `Self@f`: Expected `PureAsyncDecorator[Any, (Any, /, x)]`, found `AsyncDecorator[Any, (self, x)]`
+ asynq/tests/test_asynq_to_async.py:229:12 error[invalid-attribute-access] Invalid access to descriptor attribute `f` on type `B`: Expected `PureAsyncDecorator[Any, (Any, /, x)]`, found `AsyncDecorator[Any, (self, x)]`
+ asynq/tests/test_asynq_to_async.py:230:24 error[invalid-attribute-access] Invalid access to descriptor attribute `f` on type `B`: Expected `PureAsyncDecorator[Any, (Any, /, x)]`, found `AsyncDecorator[Any, (self, x)]`
+ asynq/generator.py:127:16 error[invalid-attribute-access] Invalid access to descriptor attribute `send` on type `Self@next`: Expected `PureAsyncDecorator[Any, (Any, /, value)]`, found `AsyncDecorator[Any, (self, value)]`
+ asynq/generator.py:148:16 error[invalid-attribute-access] Invalid access to descriptor attribute `_send_inner` on type `Self@send`: Expected `PureAsyncDecorator[Any, (Any, /, first_task)]`, found `AsyncDecorator[Any, (self, first_task)]`
+ asynq/tests/test_decorators.py:49:20 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls_and_args` on type `Self@method`: Expected `PureAsyncDecorator[Any, (Any, /, number)]`, found `AsyncDecorator[Any, (cls, number)]`
+ asynq/tests/test_decorators.py:52:26 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls_and_args` on type `Self@method`: Expected `PureAsyncDecorator[Any, (Any, /, number)]`, found `AsyncDecorator[Any, (cls, number)]`
+ asynq/tests/test_decorators.py:59:30 error[invalid-attribute-access] Invalid access to descriptor attribute `async_proxy_classmethod` on type `Self@method`: Expected `PureAsyncDecorator[Any, (Any, /, number)]`, found `AsyncDecorator[Any, (cls, number)]`
+ asynq/tests/test_decorators.py:67:16 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls_and_args` on type `type[Self@async_proxy_classmethod]`: Expected `PureAsyncDecorator[Any, (Any, /, number)]`, found `AsyncDecorator[Any, (cls, number)]`
+ asynq/tests/test_decorators.py:73:16 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls_ac` on type `type[Self@get_cls_and_args]`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:74:23 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls_ac` on type `type[Self@get_cls_and_args] & Unknown`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:75:16 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls` on type `type[Self@get_cls_and_args] & Unknown`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `PureAsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:76:23 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls` on type `type[Self@get_cls_and_args] & Unknown`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `PureAsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:188:29 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls` on type `<class 'MyClass'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `PureAsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:189:33 error[invalid-attribute-access] Invalid access to descriptor attribute `get_cls_ac` on type `<class 'MyClass'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:227:19 error[invalid-attribute-access] Invalid access to descriptor attribute `method` on type `MyClass`: Expected `PureAsyncDecorator[Any, (Any, /, number)]`, found `AsyncDecorator[Any, (self, number)]`
+ asynq/tests/test_decorators.py:231:36 error[invalid-attribute-access] Invalid access to descriptor attribute `async_staticmethod` on type `<class 'MyClass'>`: Expected `PureAsyncDecorator[Any, (Any, /, *args: Unknown, **kwargs: Unknown)]`, found `AsyncDecorator[Any, ()]`
+ asynq/tests/test_decorators.py:232:37 error[invalid-attribute-access] Invalid access to descriptor attribute `async_staticmethod` on type `<class 'MyClass'>`: Expected `PureAsyncDecorator[Any, (Any, /, *args: Unknown, **kwargs: Unknown)]`, found `AsyncDecorator[Any, ()]`
+ asynq/tests/test_decorators.py:236:36 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'MyClass'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:237:35 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'MyClass'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_decorators.py:242:30 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `MyClass`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_decorators.py:243:31 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `MyClass`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_decorators.py:55:21 error[invalid-attribute-access] Invalid access to descriptor attribute `static` on type `Self@method`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `PureAsyncDecorator[Any, (number)]`
+ asynq/tests/test_decorators.py:57:21 error[invalid-attribute-access] Invalid access to descriptor attribute `static_ac` on type `Self@method`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (number)]`
+ asynq/tests/test_mock.py:62:17 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'Cls'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_mock.py:63:18 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'Cls'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_mock.py:67:12 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'Cls'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_mock.py:71:18 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'Cls'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_mock.py:77:17 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `Cls`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_mock.py:78:18 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `Cls`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_mock.py:82:12 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `Cls`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_mock.py:86:18 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `Cls`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_mock.py:200:34 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'Cls'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_mock.py:200:57 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `Cls`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_mock.py:208:38 error[invalid-attribute-access] Invalid access to descriptor attribute `async_classmethod` on type `<class 'Cls'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (cls)]`
+ asynq/tests/test_mock.py:208:61 error[invalid-attribute-access] Invalid access to descriptor attribute `async_method` on type `Cls`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_multiple_inheritance.py:43:15 error[invalid-attribute-access] Invalid access to descriptor attribute `method` on type `<super: <class 'Child'>, Self@method>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_multiple_inheritance.py:52:15 error[invalid-attribute-access] Invalid access to descriptor attribute `method` on type `Child`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_multiple_inheritance.py:44:15 error[invalid-attribute-access] Invalid access to descriptor attribute `method` on type `<class 'Parent2'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_tools.py:192:22 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_tools.py:193:22 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_tools.py:194:22 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_tools.py:195:22 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_tools.py:196:22 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_tools.py:197:22 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_tools.py:199:22 error[invalid-attribute-access] Invalid access to descriptor attribute `with_kwargs` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, x=1, y=2, z=3)]`, found `AsyncDecorator[Any, (self, x=1, y=2, z=3)]`
+ asynq/tests/test_tools.py:200:22 error[invalid-attribute-access] Invalid access to descriptor attribute `with_kwargs` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, x=1, y=2, z=3)]`, found `AsyncDecorator[Any, (self, x=1, y=2, z=3)]`
+ asynq/tests/test_tools.py:201:23 error[invalid-attribute-access] Invalid access to descriptor attribute `with_kwargs` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, x=1, y=2, z=3)]`, found `AsyncDecorator[Any, (self, x=1, y=2, z=3)]`
+ asynq/tests/test_tools.py:205:22 error[invalid-attribute-access] Invalid access to descriptor attribute `with_kwonly_arg` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, *, arg=1)]`, found `AsyncDecorator[Any, (self, *, arg=1)]`
+ asynq/tests/test_tools.py:214:9 error[invalid-attribute-access] Invalid access to descriptor attribute `raises_exception` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_tools.py:410:11 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_tools.py:498:11 error[invalid-attribute-access] Invalid access to descriptor attribute `increment_value_method` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, val=1)]`, found `AsyncDecorator[Any, (self, val=1)]`
+ asynq/tests/test_tools.py:498:47 error[invalid-attribute-access] Invalid access to descriptor attribute `increment_value_method` on type `AsyncObject`: Expected `PureAsyncDecorator[Any, (Any, /, val=1)]`, found `AsyncDecorator[Any, (self, val=1)]`
+ asynq/tests/test_tools.py:501:11 error[invalid-attribute-access] Invalid access to descriptor attribute `deduplicated_static_method` on type `<class 'AsyncObject'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (val=1)]`
+ asynq/tests/test_tools.py:501:59 error[invalid-attribute-access] Invalid access to descriptor attribute `deduplicated_static_method` on type `<class 'AsyncObject'>`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (val=1)]`
+ asynq/tests/test_tools.py:581:5 error[invalid-attribute-access] Invalid access to descriptor attribute `trigger` on type `AsyncEventHook`: Expected `PureAsyncDecorator[Any, (Any, /, *args: Any)]`, found `AsyncDecorator[Any, (self, *args: Any)]`
+ asynq/tests/test_tools.py:588:15 error[invalid-attribute-access] Invalid access to descriptor attribute `trigger` on type `AsyncEventHook`: Expected `PureAsyncDecorator[Any, (Any, /, *args: Any)]`, found `AsyncDecorator[Any, (self, *args: Any)]`
+ asynq/tests/test_tools.py:598:9 error[invalid-attribute-access] Invalid access to descriptor attribute `safe_trigger` on type `AsyncEventHook`: Expected `PureAsyncDecorator[Any, (Any, /, *args: Any)]`, found `AsyncDecorator[Any, (self, *args: Any)]`
+ asynq/tests/test_tools.py:606:9 error[invalid-attribute-access] Invalid access to descriptor attribute `safe_trigger` on type `AsyncEventHook`: Expected `PureAsyncDecorator[Any, (Any, /, *args: Any)]`, found `AsyncDecorator[Any, (self, *args: Any)]`
+ asynq/tests/test_tools.py:623:24 error[invalid-attribute-access] Invalid access to descriptor attribute `return_three` on type `Self@return_three_and_five`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_tools.py:623:51 error[invalid-attribute-access] Invalid access to descriptor attribute `return_five` on type `Self@return_three_and_five`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_tools.py:630:23 error[invalid-attribute-access] Invalid access to descriptor attribute `return_three_and_five` on type `DeduplicateClassWrapper`: Expected `PureAsyncDecorator[Any, (Any, /)]`, found `AsyncDecorator[Any, (self)]`
+ asynq/tests/test_tools.py:189:17 error[invalid-attribute-access] Invalid access to descriptor attribute `get_value` on type `type[AsyncObject]`: Expected `PureAsyncDecorator[Any, (Any, /, index)]`, found `AsyncDecorator[Any, (self, index)]`
+ asynq/tests/test_typing.py:42:27 error[invalid-attribute-access] Invalid access to descriptor attribute `method` on type `Obj`: Expected `PureAsyncDecorator[Any, (Any, /, x: str)]`, found `AsyncDecorator[Any, (self, x: str)]`
+ asynq/tests/test_typing.py:47:15 error[invalid-attribute-access] Invalid access to descriptor attribute `method` on type `Obj`: Expected `PureAsyncDecorator[Any, (Any, /, x: str)]`, found `AsyncDecorator[Any, (self, x: str)]`

core (https://github.com/home-assistant/core)
+ homeassistant/components/sonos/helpers.py:100:42 error[invalid-attribute-access] Invalid access to descriptor attribute `soco` on type `_T@soco_error`: Expected `SonosEntity`, found `_T@soco_error`

hydpy (https://github.com/hydpy-dev/hydpy)
+ hydpy/auxs/iuhtools.py:514:30 error[invalid-attribute-access] Invalid access to descriptor attribute `shape` on type `VectorFloatFlex@__call__`: Argument type `VectorFloatFlex@__call__` does not satisfy upper bound `ndarray[_ShapeT_co@ndarray, _DTypeT_co@ndarray]` of type variable `Self`

ibis (https://github.com/ibis-project/ibis)
+ ibis/backends/flink/utils.py:122:33 error[invalid-attribute-access] Invalid access to descriptor attribute `units` on type `Self@_convert_to_combined_units`: Expected `type[FlinkIntervalType]`, found `Self@_convert_to_combined_units`
+ ibis/backends/flink/utils.py:122:50 error[invalid-attribute-access] Invalid access to descriptor attribute `factors` on type `Self@_convert_to_combined_units`: Expected `type[FlinkIntervalType]`, found `Self@_convert_to_combined_units`
+ ibis/backends/flink/utils.py:128:31 error[invalid-attribute-access] Invalid access to descriptor attribute `units` on type `Self@_convert_to_combined_units`: Expected `type[FlinkIntervalType]`, found `Self@_convert_to_combined_units`

pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/algorithms.py:208:50 error[invalid-attribute-access] Invalid access to descriptor attribute `dtype` on type `ArrayLikeT@_reconstruct_data & ExtensionArray`: Argument type `ArrayLikeT@_reconstruct_data & ExtensionArray` does not satisfy upper bound `ndarray[_ShapeT_co@ndarray, _DTypeT_co@ndarray]` of type variable `Self`
+ pandas/core/arrays/interval.py:758:39 error[invalid-attribute-access] Invalid access to descriptor attribute `left` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:758:69 error[invalid-attribute-access] Invalid access to descriptor attribute `right` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:760:39 error[invalid-attribute-access] Invalid access to descriptor attribute `left` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:760:69 error[invalid-attribute-access] Invalid access to descriptor attribute `right` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:762:38 error[invalid-attribute-access] Invalid access to descriptor attribute `left` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:763:36 error[invalid-attribute-access] Invalid access to descriptor attribute `left` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:763:65 error[invalid-attribute-access] Invalid access to descriptor attribute `right` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:768:38 error[invalid-attribute-access] Invalid access to descriptor attribute `left` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:769:36 error[invalid-attribute-access] Invalid access to descriptor attribute `left` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/arrays/interval.py:769:65 error[invalid-attribute-access] Invalid access to descriptor attribute `right` on type `(Unknown & Top[Interval[Unknown]]) | (ExtensionArray & Top[Interval[Unknown]]) | Self@_cmp_method`: Expected `Bottom[Interval[Unknown]]`, found `ExtensionArray & Top[Interval[Unknown]]`
+ pandas/core/dtypes/cast.py:417:13 error[invalid-attribute-access] Invalid access to descriptor attribute `dtype` on type `NumpyIndexT@maybe_upcast_numeric_to_64bit`: Argument type `NumpyIndexT@maybe_upcast_numeric_to_64bit` does not satisfy upper bound `ndarray[_ShapeT_co@ndarray, _DTypeT_co@ndarray]` of type variable `Self`
+ pandas/core/dtypes/cast.py:778:43 error[invalid-attribute-access] Invalid access to descriptor attribute `left` on type `Top[Interval[Unknown]] & ~Period`: Expected `Bottom[Interval[Unknown]]`, found `Top[Interval[Unknown]] & ~Period`
+ pandas/core/dtypes/missing.py:651:19 error[invalid-attribute-access] Invalid access to descriptor attribute `dtype` on type `_ArrLikeT@remove_na_arraylike`: Expected `Series`, found `_ArrLikeT@remove_na_arraylike`

pwndbg (https://github.com/pwndbg/pwndbg)
+ pwndbg/aglib/heap/ptmalloc.py:1080:16 error[invalid-attribute-access] Invalid access to descriptor attribute `type` on type `TheValue@GlibcMemoryAllocator | None`: Expected `Value`, found `TheValue@GlibcMemoryAllocator`
+ pwndbg/aglib/heap/ptmalloc.py:1246:40 error[invalid-attribute-access] Invalid access to descriptor attribute `address` on type `TheValue@GlibcMemoryAllocator`: Expected `Value`, found `TheValue@GlibcMemoryAllocator`

python-htmlgen (https://github.com/srittau/python-htmlgen)
+ htmlgen/form.py:200:33 error[invalid-attribute-access] Invalid access to descriptor attribute `value` on type `Self@date`: Expected `Element`, found `Self@date`
+ htmlgen/form.py:360:16 error[invalid-attribute-access] Invalid access to descriptor attribute `value` on type `Self@label`: Expected `Element`, found `Self@label`

static-frame (https://github.com/static-frame/static-frame)
+ static_frame/core/node_iter.py:856:17 error[invalid-attribute-access] Invalid access to descriptor attribute `shape` on type `TContainerAny@IterNode`: Argument type `TContainerAny@IterNode` does not satisfy upper bound `Frame[TVIndex@Frame, TVColumns@Frame, TVDtypes@Frame]` of type variable `Self`
+ static_frame/core/node_values.py:149:27 error[invalid-attribute-access] Invalid access to descriptor attribute `index` on type `TVContainer_co@InterfaceValues & Frame[Any, Any, *tuple[Any, ...]]`: Argument type `TVContainer_co@InterfaceValues & Frame[Any, Any, *tuple[Any, ...]]` does not satisfy upper bound `Series[TVIndex@Series, TVDtype@Series]` of type variable `Self`
+ static_frame/core/node_values.py:151:26 error[invalid-attribute-access] Invalid access to descriptor attribute `name` on type `TVContainer_co@InterfaceValues & Frame[Any, Any, *tuple[Any, ...]]`: Expected `IndexBase`, found `TVContainer_co@InterfaceValues & Frame[Any, Any, *tuple[Any, ...]]`
+ static_frame/core/node_values.py:166:17 error[invalid-attribute-access] Invalid access to descriptor attribute `values` on type `TVContainer_co@InterfaceValues`: Argument type `TVContainer_co@InterfaceValues` does not satisfy upper bound `Frame[TVIndex@Frame, TVColumns@Frame, TVDtypes@Frame]` of type variable `Self`
+ static_frame/core/node_values.py:171:17 error[invalid-attribute-access] Invalid access to descriptor attribute `values` on type `TVContainer_co@InterfaceValues`: Argument type `TVContainer_co@InterfaceValues` does not satisfy upper bound `Frame[TVIndex@Frame, TVColumns@Frame, TVDtypes@Frame]` of type variable `Self`
+ static_frame/core/node_values.py:178:23 error[invalid-attribute-access] Invalid access to descriptor attribute `index` on type `TVContainer_co@InterfaceValues & Series[Any, Any]`: Argument type `TVContainer_co@InterfaceValues & Series[Any, Any]` does not satisfy upper bound `Frame[TVIndex@Frame, TVColumns@Frame, TVDtypes@Frame]` of type variable `Self`
+ static_frame/core/node_values.py:179:22 error[invalid-attribute-access] Invalid access to descriptor attribute `name` on type `TVContainer_co@InterfaceValues & Series[Any, Any]`: Argument type `TVContainer_co@InterfaceValues & Series[Any, Any]` does not satisfy upper bound `Frame[TVIndex@Frame, TVColumns@Frame, TVDtypes@Frame]` of type variable `Self`
+ static_frame/core/node_values.py:185:18 error[invalid-attribute-access] Invalid access to descriptor attribute `name` on type `TVContainer_co@InterfaceValues & ~Top[Series[Unknown, Unknown]]`: Argument type `TVContainer_co@InterfaceValues & ~Top[Series[Unknown, Unknown]]` does not satisfy upper bound `Frame[TVIndex@Frame, TVColumns@Frame, TVDtypes@Frame]` of type variable `Self`
+ static_frame/core/node_values.py:275:20 error[invalid-attribute-access] Invalid access to descriptor attribute `via_values` on type `TVContainer_co@func`: Argument type `TVContainer_co@func` does not satisfy upper bound `Frame[TVIndex@Frame, TVColumns@Frame, TVDtypes@Frame]` of type variable `Self`

werkzeug (https://github.com/pallets/werkzeug)
+ tests/test_local.py:191:12 error[invalid-attribute-access] Invalid access to descriptor attribute `__doc__` on type `<class 'LocalProxy'>`: Expected `LocalProxy[Any]`, found `None`
+ src/werkzeug/local.py:535:52 error[invalid-attribute-access] Invalid access to descriptor attribute `__doc__` on type `type[LocalProxy[Any]]`: Expected `LocalProxy[Any]`, found `None`

xarray (https://github.com/pydata/xarray)
+ xarray/computation/rolling.py:115:68 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@Rolling`: Expected `DataArray`, found `T_Xarray@Rolling`
+ xarray/computation/rolling.py:120:37 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@Rolling`: Expected `DataArray`, found `T_Xarray@Rolling`
+ xarray/computation/rolling.py:141:26 error[invalid-attribute-access] Invalid access to descriptor attribute `sizes` on type `T_Xarray@Rolling`: Expected `Dataset`, found `T_Xarray@Rolling`
+ xarray/computation/rolling.py:200:49 error[invalid-attribute-access] Invalid access to descriptor attribute `dtype` on type `T_Xarray@Rolling`: Expected `DataArray`, found `T_Xarray@Rolling`
+ xarray/computation/rolling.py:203:28 error[invalid-attribute-access] Invalid access to descriptor attribute `attrs` on type `T_Xarray@Rolling`: Expected `DataArray`, found `T_Xarray@Rolling`
+ xarray/computation/rolling.py:1088:74 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@Coarsen`: Expected `DataArray`, found `T_Xarray@Coarsen`
+ xarray/computation/rolling.py:1092:37 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@Coarsen`: Expected `DataArray`, found `T_Xarray@Coarsen`
+ xarray/computation/rolling.py:1098:44 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@Coarsen`: Expected `DataArray`, found `T_Xarray@Coarsen`
+ xarray/computation/rolling.py:1099:18 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@Coarsen`: Expected `DataArray`, found `T_Xarray@Coarsen`
+ xarray/computation/rolling.py:1217:51 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@Coarsen`: Expected `DataArray`, found `T_Xarray@Coarsen`
+ xarray/computation/rolling.py:1218:13 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@Coarsen`: Expected `DataArray`, found `T_Xarray@Coarsen`
+ xarray/computation/weighted.py:203:24 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@Weighted`: Expected `DataArray`, found `T_Xarray@Weighted`
+ xarray/core/coordinates.py:1212:14 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@assert_coordinate_consistent`: Expected `DataArray`, found `T_Xarray@assert_coordinate_consistent`
+ xarray/core/coordinates.py:1214:33 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@assert_coordinate_consistent`: Expected `DataArray`, found `T_Xarray@assert_coordinate_consistent`
+ xarray/core/dataset.py:4157:25 error[invalid-attribute-access] Invalid access to descriptor attribute `xindexes` on type `T_Xarray@interp_like`: Expected `DataArray`, found `T_Xarray@interp_like`
+ xarray/core/groupby.py:197:21 error[invalid-attribute-access] Invalid access to descriptor attribute `sizes` on type `T_Xarray@_DummyGroup`: Expected `Dataset`, found `T_Xarray@_DummyGroup`
+ xarray/core/groupby.py:943:12 error[invalid-attribute-access] Invalid access to descriptor attribute `chunks` on type `T_Xarray@GroupBy`: Expected `DataArray`, found `T_Xarray@GroupBy`
+ xarray/core/groupby.py:946:40 error[invalid-attribute-access] Invalid access to descriptor attribute `chunksizes` on type `T_Xarray@GroupBy`: Expected `DataArray`, found `T_Xarray@GroupBy`
+ xarray/core/groupby.py:962:28 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@GroupBy`: Expected `DataArray`, found `T_Xarray@GroupBy`
+ xarray/core/groupby.py:962:46 error[invalid-attribute-access] Invalid access to descriptor attribute `xindexes` on type `T_Xarray@GroupBy`: Expected `DataArray`, found `T_Xarray@GroupBy`
+ xarray/core/groupby.py:1047:32 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@GroupBy`: Expected `DataArray`, found `T_Xarray@GroupBy`
+ xarray/core/groupby.py:1055:58 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@GroupBy`: Expected `DataArray`, found `T_Xarray@GroupBy`
+ xarray/core/groupby.py:1111:37 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@GroupBy`: Expected `DataArray`, found `T_Xarray@GroupBy`
+ xarray/core/groupby.py:1203:43 error[invalid-attribute-access] Invalid access to descriptor attribute `sizes` on type `T_Xarray@GroupBy`: Expected `Dataset`, found `T_Xarray@GroupBy`
+ xarray/core/indexing.py:154:24 error[invalid-attribute-access] Invalid access to descriptor attribute `xindexes` on type `T_Xarray@group_indexers_by_index`: Expected `DataArray`, found `T_Xarray@group_indexers_by_index`
+ xarray/core/parallel.py:478:31 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@map_blocks`: Expected `DataArray`, found `T_Xarray@map_blocks`
+ xarray/core/parallel.py:492:32 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@map_blocks`: Expected `DataArray`, found `T_Xarray@map_blocks`
+ xarray/core/parallel.py:498:47 error[invalid-attribute-access] Invalid access to descriptor attribute `dims` on type `T_Xarray@map_blocks`: Expected `DataArray`, found `T_Xarray@map_blocks`
+ xarray/core/parallel.py:511:23 error[invalid-attribute-access] Invalid access to descriptor attribute `xindexes` on type `DataArray | Dataset | T_Xarray@map_blocks`: Expected `DataArray`, found `T_Xarray@map_blocks`
+ xarray/core/resample.py:127:21 error[invalid-attribute-access] Invalid access to descriptor attribute `coords` on type `T_Xarray@Resample`: Expected `DataArray`, found `T_Xarray@Resample`

Full report with detailed diff (timing results)

@charliermarsh
charliermarsh force-pushed the charlie/invalid-descriptor-get-signature branch from 12a0b7f to 9169951 Compare August 1, 2026 02:29
@charliermarsh

Copy link
Copy Markdown
Member Author

The asynq errors all appear to be of the form:

from collections.abc import Callable
from typing import Any, Concatenate, Generic, ParamSpec

P = ParamSpec("P")
Rest = ParamSpec("Rest")


class Descriptor(Generic[P]):
    def __get__(
        self: "Descriptor[Concatenate[Any, Rest]]",
        instance: object,
        owner: type[object],
    ) -> None:
        pass


def decorate(function: Callable[P, object]) -> Descriptor[P]:
    raise NotImplementedError


class C:
    @decorate
    def method(self, value: int) -> object:
        return value


C().method  # error: [invalid-attribute-access]

The decorator produces Descriptor[(self, value: int)], but __get__ says its self must be Descriptor[(Any, /, value: int)]. So the first parameter is named in the actual type but positional-only in the expected type due to Concatenate...

@charliermarsh charliermarsh changed the title [ty] Diagnose invalid descriptor __get__ calls [ty] Diagnose invalid descriptor __get__ calls Aug 1, 2026
@charliermarsh
charliermarsh force-pushed the charlie/invalid-descriptor-get-signature branch from 9169951 to 8d150ca Compare August 4, 2026 18:37
@charliermarsh
charliermarsh marked this pull request as ready for review August 4, 2026 21:05
@charliermarsh
charliermarsh requested review from a team as code owners August 4, 2026 21:05
@astral-sh-bot
astral-sh-bot Bot requested a review from dhruvmanila August 4, 2026 21:05
@MichaReiser

Copy link
Copy Markdown
Member

Nit: should the method be renamed to try_member_lookup?

@charliermarsh charliermarsh assigned sharkdp and unassigned sharkdp Aug 4, 2026
@charliermarsh
charliermarsh requested review from sharkdp and removed request for dhruvmanila August 4, 2026 21:21
@sharkdp

sharkdp commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Conceptually, we made this change:

// Before
member_lookup(...) -> PlaceAndQualifiers

// After
member_lookup(...) -> MemberLookupResult {
    member: PlaceAndQualifiers,
    descriptor_error: Option<DescriptorGetCallContext>,
}

We wanted to make this change for a while, see astral-sh/ty#194. This PR seems to implement a narrow version of that, in that it only seems to consider descriptor errors? I think, if we make this large architectural change anyway, it would be good to consider other attribute lookup errors as well. For example, the same mechanism could be used to solve astral-sh/ty#135 as well.

I also wonder why you chose to use a struct here for MemberLookupResult, as opposed to something like Result<PlaceAndQualifiers, MemberLookupError>?

@charliermarsh

Copy link
Copy Markdown
Member Author

We can use a Result, but the problem is we do want the recovered type to avoid cascading errors, like:

class Descriptor:
    def __get__(self) -> int:
        return 1


class C:
    value = Descriptor()


value = C().value  # error: invalid __get__ call
reveal_type(value)  # revealed: int
result = value + 1  # no additional error

We could include the type in the error variant though. Do you think that's better?

@charliermarsh

Copy link
Copy Markdown
Member Author

Let me try that, and also put up a second PR atop this that adds similar support for __getattr__ error tracking so that we have some confidence that this generalizes.

@AlexWaygood

Copy link
Copy Markdown
Member

We could include the type in the error variant though. Do you think that's better?

I believe that's the pattern we generally use elsewhere. IterationError has element_type and fallback_element_type methods; ContextManagerError has enter_type and fallback_enter_type methods; BoolError has a fallback_truthiness method; etc.

@charliermarsh
charliermarsh force-pushed the charlie/invalid-descriptor-get-signature branch from e91a674 to 90b6867 Compare August 5, 2026 12:43
@charliermarsh
charliermarsh marked this pull request as ready for review August 5, 2026 13:37
@charliermarsh

Copy link
Copy Markdown
Member Author

Ok, we now use a "real" result type.

@charliermarsh

Copy link
Copy Markdown
Member Author

#27502 extends the design for __getattr__.

@charliermarsh
charliermarsh force-pushed the charlie/invalid-descriptor-get-signature branch from 90b6867 to 1683568 Compare August 5, 2026 14:33
@charliermarsh
charliermarsh changed the base branch from main to charlie/property-getter-call-errors August 5, 2026 14:35
@charliermarsh
charliermarsh force-pushed the charlie/invalid-descriptor-get-signature branch from 1683568 to 05fbf9c Compare August 5, 2026 14:55
@charliermarsh
charliermarsh force-pushed the charlie/invalid-descriptor-get-signature branch from 04f4c95 to 9394b1c Compare August 6, 2026 11:49
@charliermarsh
charliermarsh merged commit 05160d5 into main Aug 6, 2026
65 checks passed
@charliermarsh
charliermarsh deleted the charlie/invalid-descriptor-get-signature branch August 6, 2026 12:39
charliermarsh added a commit that referenced this pull request Aug 6, 2026
## Summary

Previously, an invalid `__getattr__` method caused us to report that the
requested attribute was missing, even though Python actually invokes the
method and raises `TypeError`:

```py
class Example:
    def __getattr__(self) -> str:
        return "fallback"

Example().missing  # error: [invalid-attribute-access]
```

We now propagate failed implicit `__getattr__` calls through the
generalized member-lookup error introduced in #27400, so the diagnostic
identifies the invalid call and preserves the method's return type for
error recovery.

This applies to instance and metaclass attribute lookup, including
incompatible attribute-name parameters. Literal-restricted `__getattr__`
methods continue to treat unsupported names as missing attributes, and
already-defined attributes still bypass the fallback.

Closes astral-sh/ty#135.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty The ty type checker

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No error on invalid descriptor __get__ signature

4 participants