[ty] Don't use implicit superclass annotation when converting a class constructor into a `Callable` (#22011)

This fixes a bug @zsol found running ty against pyx. His original repro
is:

```py
class Base:
    def __init__(self) -> None: pass

class A(Base):
    pass

def foo[T](callable: Callable[..., T]) -> T:
    return callable()

a: A = foo(A)
```

The call at the bottom would fail, since we would infer `() -> Base` as
the callable type of `A`, when it should be `() -> A`.

The issue was how we add implicit annotations to `self` parameters.
Typically, we turn it into `self: Self`. But in cases where we don't
need to introduce a full typevar, we turn it into `self: [the class
itself]` — in this case, `self: Base`. Then, when turning the class
constructor into a callable, we would see this non-`Self` annotation and
think that it was important and load-bearing.

The fix is that we skip all implicit annotations when determining
whether the `self` annotation should take precedence in the callable's
return type.
This commit is contained in:
Douglas Creager 2025-12-16 13:37:11 -05:00 committed by GitHub
parent c02bd11b93
commit 2214a46139
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 20 additions and 1 deletions

View File

@ -407,4 +407,22 @@ def f_okay(c: Callable[[], None]):
c.__qualname__ = "my_callable" # okay
```
## From a class
### Subclasses should return themselves, not superclass
```py
from ty_extensions import into_callable
class Base:
def __init__(self) -> None:
pass
class A(Base):
pass
# revealed: () -> A
reveal_type(into_callable(A))
```
[gradual form]: https://typing.python.org/en/latest/spec/glossary.html#term-gradual-form

View File

@ -1257,6 +1257,7 @@ impl<'db> ClassType<'db> {
let self_annotation = signature
.parameters()
.get_positional(0)
.filter(|parameter| !parameter.inferred_annotation)
.and_then(Parameter::annotated_type)
.filter(|ty| {
ty.as_typevar()

View File

@ -2167,7 +2167,7 @@ pub(crate) struct Parameter<'db> {
/// Does the type of this parameter come from an explicit annotation, or was it inferred from
/// the context, like `Self` for the `self` parameter of instance methods.
inferred_annotation: bool,
pub(crate) inferred_annotation: bool,
kind: ParameterKind<'db>,
pub(crate) form: ParameterForm,