Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding __name__ attribute to a callable Protocol doesn't work with function objects and breaks inference with Protocol objects #12976

Closed
glyph opened this issue Jun 12, 2022 · 1 comment
Labels
bug mypy got something wrong topic-calls Function calls, *args, **kwargs, defaults topic-protocols

Comments

@glyph
Copy link

glyph commented Jun 12, 2022

Bug Report

For context, I'm lightly abusing Mypy's model of the world to pass Protocol objects around at runtime; hopefully by the time things are fixed so that Protocols no longer look instantiatable we'll also have #9773 or something to allow for more runtime dynamism using them ;)

However, the bug in question actually shows up with function objects as well, albeit with a slightly different way.

Consider the following program:

from typing import Protocol, TypeVar, Generic, Callable, ParamSpec
from dataclasses import dataclass

Tco = TypeVar("Tco", covariant=True)
T = TypeVar("T")
T2 = TypeVar("T2")
P = ParamSpec("P")

class NamedCallable(Protocol[Tco]):
    __name__: str
    def __call__(self) -> Tco:
        ...

@dataclass
class G(Generic[T, T2, P]):
    x: NamedCallable[T]
    y: Callable[P, T2]


class SomeProtocol(Protocol):
    def method(self) -> None:
        ...

def f(x: str) -> int:
    ...

def actual_function() -> SomeProtocol:
    ...

reveal_type(f)
reveal_type(actual_function)
reveal_type(SomeProtocol)
g = G(SomeProtocol, f)
g2 = G(actual_function, f)
reveal_type(g)
reveal_type(g2)

I get the following output:

fkljsadf.py:31: note: Revealed type is "def (x: builtins.str) -> builtins.int"
fkljsadf.py:32: note: Revealed type is "def () -> fkljsadf.SomeProtocol"
fkljsadf.py:33: note: Revealed type is "def () -> fkljsadf.SomeProtocol"
fkljsadf.py:35: error: Need type annotation for "g2"
fkljsadf.py:35: error: Argument 1 to "G" has incompatible type "Callable[[], SomeProtocol]"; expected "NamedCallable[<nothing>]"
fkljsadf.py:36: note: Revealed type is "fkljsadf.G[Any, builtins.int, [x: builtins.str]]"
fkljsadf.py:37: note: Revealed type is "fkljsadf.G[Any, builtins.int, [x: builtins.str]]"

If I comment out the __name__ attribute of NamedCallable, the errors go away and I instead get this:

fkljsadf.py:31: note: Revealed type is "def (x: builtins.str) -> builtins.int"
fkljsadf.py:32: note: Revealed type is "def () -> fkljsadf.SomeProtocol"
fkljsadf.py:33: note: Revealed type is "def () -> fkljsadf.SomeProtocol"
fkljsadf.py:36: note: Revealed type is "fkljsadf.G[fkljsadf.SomeProtocol, builtins.int, [x: builtins.str]]"
fkljsadf.py:37: note: Revealed type is "fkljsadf.G[fkljsadf.SomeProtocol, builtins.int, [x: builtins.str]]"

Given that the revealed type is def () -> SomeProtocol for both the protocol object itself and the function, I am surprised that g silently degrades to Any but g2 requests an explicit annotation. It is also odd that the __name__ attribute, which is present on both of these objects at runtime, seems to be an impediment here.

So there area a few bugs here:

  • if actual_function and SomeProtocol are in fact different objects internally to Mypy (one thinking it has a __name__ and the other doesn't) perhaps the string they show in reveal_type ought to be visibly distinct?
  • these objects do have __name__ attributes and it seems like they ought to be inferred
  • why falling back to Any here with no warning in the Protocol case?
  • Protocols aren't actually functions that return themselves (but please don't fix this one yet unless we have some other way to pass these around 😉).

Your Environment

  • Mypy version used: mypy 0.961 (compiled: yes)
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: Python 3.10.4 (python.org)
  • Operating system and version:
ProductName:	macOS
ProductVersion:	12.4
BuildVersion:	21F79
@glyph glyph added the bug mypy got something wrong label Jun 12, 2022
@AlexWaygood AlexWaygood added topic-protocols topic-calls Function calls, *args, **kwargs, defaults labels Jun 12, 2022
@ilevkivskyi
Copy link
Member

ilevkivskyi commented Nov 16, 2022

#14084 fixed all issues here except silent fallback to Any, that has same root cause as
#10482: in typeshed type.__call__ is annotated as returning (*Any, **Any) -> Any, making all class objects implementing arbitrary callback protocols. Mypy uses metaclass as a fallback for protocol implementation if implementation is a class object, so we will need to either tighten definition in typeshed, or ignore metaclass __call__ if constructor didn't match. This will be tracked in the other issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-calls Function calls, *args, **kwargs, defaults topic-protocols
Projects
None yet
Development

No branches or pull requests

3 participants