Skip to content

Commit 97058e8

Browse files
dcreagercarljm
andauthored
[ty] Infer function call typevars in both directions (#18155)
This primarily comes up with annotated `self` parameters in constructors: ```py class C[T]: def __init__(self: C[int]): ... ``` Here, we want infer a specialization of `{T = int}` for a call that hits this overload. Normally when inferring a specialization of a function call, typevars appear in the parameter annotations, and not in the argument types. In this case, this is reversed: we need to verify that the `self` argument (`C[T]`, as we have not yet completed specialization inference) is assignable to the parameter type `C[int]`. To do this, we simply look for a typevar/type in both directions when performing inference, and apply the inferred specialization to argument types as well as parameter types before verifying assignability. As a wrinkle, this exposed that we were not checking subtyping/assignability for function literals correctly. Our function literal representation includes an optional specialization that should be applied to the signature. Before, function literals were considered subtypes of (assignable to) each other only if they were identical Salsa objects. Two function literals with different specializations should still be considered subtypes of (assignable to) each other if those specializations result in the same function signature (typically because the function doesn't use the typevars in the specialization). Closes astral-sh/ty#370 Closes astral-sh/ty#100 Closes astral-sh/ty#258 --------- Co-authored-by: Carl Meyer <carl@astral.sh>
1 parent 569c94b commit 97058e8

File tree

8 files changed

+366
-53
lines changed

8 files changed

+366
-53
lines changed

crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,39 @@ reveal_type(C(1, True)) # revealed: C[Literal[1]]
341341
wrong_innards: C[int] = C("five", 1)
342342
```
343343

344+
### Some `__init__` overloads only apply to certain specializations
345+
346+
```py
347+
from typing import overload, Generic, TypeVar
348+
349+
T = TypeVar("T")
350+
351+
class C(Generic[T]):
352+
@overload
353+
def __init__(self: "C[str]", x: str) -> None: ...
354+
@overload
355+
def __init__(self: "C[bytes]", x: bytes) -> None: ...
356+
@overload
357+
def __init__(self, x: int) -> None: ...
358+
def __init__(self, x: str | bytes | int) -> None: ...
359+
360+
reveal_type(C("string")) # revealed: C[str]
361+
reveal_type(C(b"bytes")) # revealed: C[bytes]
362+
reveal_type(C(12)) # revealed: C[Unknown]
363+
364+
C[str]("string")
365+
C[str](b"bytes") # error: [no-matching-overload]
366+
C[str](12)
367+
368+
C[bytes]("string") # error: [no-matching-overload]
369+
C[bytes](b"bytes")
370+
C[bytes](12)
371+
372+
C[None]("string") # error: [no-matching-overload]
373+
C[None](b"bytes") # error: [no-matching-overload]
374+
C[None](12)
375+
```
376+
344377
## Generic subclass
345378

346379
When a generic subclass fills its superclass's type parameter with one of its own, the actual types

crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,37 @@ reveal_type(C(1, True)) # revealed: C[Literal[1]]
279279
wrong_innards: C[int] = C("five", 1)
280280
```
281281

282+
### Some `__init__` overloads only apply to certain specializations
283+
284+
```py
285+
from typing import overload
286+
287+
class C[T]:
288+
@overload
289+
def __init__(self: C[str], x: str) -> None: ...
290+
@overload
291+
def __init__(self: C[bytes], x: bytes) -> None: ...
292+
@overload
293+
def __init__(self, x: int) -> None: ...
294+
def __init__(self, x: str | bytes | int) -> None: ...
295+
296+
reveal_type(C("string")) # revealed: C[str]
297+
reveal_type(C(b"bytes")) # revealed: C[bytes]
298+
reveal_type(C(12)) # revealed: C[Unknown]
299+
300+
C[str]("string")
301+
C[str](b"bytes") # error: [no-matching-overload]
302+
C[str](12)
303+
304+
C[bytes]("string") # error: [no-matching-overload]
305+
C[bytes](b"bytes")
306+
C[bytes](12)
307+
308+
C[None]("string") # error: [no-matching-overload]
309+
C[None](b"bytes") # error: [no-matching-overload]
310+
C[None](12)
311+
```
312+
282313
## Generic subclass
283314

284315
When a generic subclass fills its superclass's type parameter with one of its own, the actual types

crates/ty_python_semantic/resources/mdtest/generics/scoping.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class C[T]:
102102
return "a"
103103

104104
reveal_type(getattr_static(C[int], "f")) # revealed: def f(self, x: int) -> str
105-
reveal_type(getattr_static(C[int], "f").__get__) # revealed: <method-wrapper `__get__` of `f[int]`>
105+
reveal_type(getattr_static(C[int], "f").__get__) # revealed: <method-wrapper `__get__` of `f`>
106106
reveal_type(getattr_static(C[int], "f").__get__(None, C[int])) # revealed: def f(self, x: int) -> str
107107
# revealed: bound method C[int].f(x: int) -> str
108108
reveal_type(getattr_static(C[int], "f").__get__(C[int](), C[int]))

0 commit comments

Comments
 (0)