If I understand you correctly, you’re saying that because the Callable
structural type doesn’t include a __get__
method in its definition, we should assume that no __get__
method is present on an object that conforms to this structural type. Is my understanding correct? If so, that doesn’t sound right to me. If we treat it like other structural types, then Callable
says nothing about either the presence or absence of a __get__
method. That means we need to use other information and rules to determine the binding behaviors for an attribute that has a Callable
type annotation, which is what mypy and pyright are doing.
I’m open to changing pyright’s behavior if it more accurately models the runtime behavior and we can get consensus from the community (including the mypy maintainers). However, we’d need to take into account the impact of such a change. I suspect there are many stubs, libraries, and source bases that rely on mypy’s and pyright’s current interpretation. Have you done any experiments to see what the fallout would be if we were to make a change here? My educated guess is that it would be a really painful change for users to swallow.
Here’s another idea. What if we added a few new special forms like Method
, ClassMethod
and StaticMethod
? These would be subtypes of Callable
but they would also include a __get__
method as appropriate. Like Callable
, these would be structural types, as opposed to the the nominal types defined in types.pyi
such as MethodDescriptorType
and ClassMethodDescriptorType
. They’d support the same generic parameterization as Callable
. Perhaps that’s roughly what you had in mind when you said “splitting Callable
into separate incompatible types”? If we were to do this, we could keep the current (mypy and pyright) behavior for Callable
but implement the improved behavior for these new subtypes.
Another thing to consider is how any such change would affect typed decorators — those that use either TypeVars with a Callable
upper bound or Callable
with a ParamSpec
. I mentioned above that pyright uses flags internally to track whether a function type is an regular function, an instance method, a class method, or a static method, and it uses these flags to inform the binding behavior. These flags are also captured and propagated by the constraint solver, so if a callable object is passed as an argument to a generic decorator function, the return result of the decorator retains these flags. I think mypy does something similar.
Code sample in pyright playground
In addition to these flags, pyright also captures the docstring of the pre-decorated function so when you hover over references to the post-decorated function, you’ll still see the docstring in your editor. Are you doing something similar in ty and pyrefly? If not, you may find that existing typed decorators don’t work in a compatible way.