python / python/mypy

Overloaded `__getattr__` resolves attributes to the overloaded function

Open
#15,692 2 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

Bug Report

If __getattr__ is overloaded, all attribute accesses resolves as if they were getting __getattr__ itself. This is most obvious if you expect it to return a callable - mypy will complain that the args to the callable don't match the signatures of __getattr__. This does not happen if you call __getattr__ manually, only via regular attribute access.

To Reproduce

class A:
    @overload
    def __getattr__(self, name: str) -> Callable[..., Any]: ...
    @overload
    def __getattr__(self, name: int) -> int: ...
    def __getattr__(self, name: Any) -> Any:
        pass

reveal_type(A().__getattr__)
reveal_type(A().xyz)
reveal_type(A().__getattr__("xyz"))
reveal_type(A().__getattr__(1))

A().xyz(1, 2, 3)
main.py:7: error: Invalid signature "Callable[[A, int], int]" for "__getattr__"  [misc]
main.py:11: note: Revealed type is "Overload(def (builtins.str) -> def (*Any, **Any) -> Any, def (builtins.int) -> builtins.int)"
main.py:12: note: Revealed type is "Overload(def (builtins.str) -> def (*Any, **Any) -> Any, def (builtins.int) -> builtins.int)"
main.py:13: note: Revealed type is "def (*Any, **Any) -> Any"
main.py:14: note: Revealed type is "builtins.int"
main.py:16: error: No overload variant of "__getattr__" of "A" matches argument types "int", "int", "int"  [call-overload]
main.py:16: note: Possible overload variants:
main.py:16: note:     def __getattr__(self, str, /) -> Callable[..., Any]
main.py:16: note:     def __getattr__(self, int, /) -> int

The first error is expected - __getattr__ doesn't normally take ints, I'm just using them to make types clear. In practice, subclasses of str, or literals as in #8203 might make more sense, though they currently all error the same way.

This behavior is the same as accessing, but not calling, an overloaded function elsewhere.

class B:
    @overload
    def overloaded(self, name: str) -> Callable[..., Any]: ...
    @overload
    def overloaded(self, name: int) -> int: ...
    def overloaded(self, name: Any) -> Any:
        pass

reveal_type(B().overloaded)
main.py:26: note: Revealed type is "Overload(def (name: builtins.str) -> def (*Any, **Any) -> Any, def (name: builtins.int) -> builtins.int)"

Expected Behavior

The second revealed type, of A().xyz, should worst case resolve to Callable[..., Any] | int - i.e. when overloaded, attribute access should resolve to the union of the overloads' return values, in the same way other overloads with unknown args do.

x: str | int
reveal_type(B().overloaded(x))
main.py:29: note: Revealed type is "Union[def (*Any, **Any) -> Any, builtins.int]"

Additionally, I think it should be possible to deduce that __getattr__ gets called with a string in normal attribute access, meaning the type of the result can be narrowed, down to just Callable[..., Any] in this case.

Your Environment
Python 3.11, Mypy 1.4.1, default settings

https://mypy-play.net/?mypy=1.4.1&python=3.11&gist=0e629e0ec4d8a4a4a69f8ed979bdd04d

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the provided Python snippets with mypy and compare the revealed types for overloaded getattr and ordinary overloaded methods. Trace the type-checking path for attribute access and overload resolution. Done means ordinary access returns the appropriate overload result, ideally narrowed for a string attribute name, without the erroneous call-overload diagnostic.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.