Skip to content

Commit bc107c6

Browse files
committed
[ty] Improve assignability/subtyping between two protocol types
1 parent f7652c4 commit bc107c6

File tree

12 files changed

+879
-329
lines changed

12 files changed

+879
-329
lines changed

crates/ty_python_semantic/resources/mdtest/call/overloads.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,11 +1093,7 @@ from typing_extensions import LiteralString
10931093

10941094
def f(a: Foo, b: list[str], c: list[LiteralString], e):
10951095
reveal_type(e) # revealed: Unknown
1096-
1097-
# TODO: we should select the second overload here and reveal `str`
1098-
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
1099-
reveal_type(a.join(b)) # revealed: LiteralString
1100-
1096+
reveal_type(a.join(b)) # revealed: str
11011097
reveal_type(a.join(c)) # revealed: LiteralString
11021098

11031099
# since both overloads match and they have return types that are not equivalent,

crates/ty_python_semantic/resources/mdtest/diagnostics/same_names.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ from typing import Protocol
176176
import proto_a
177177
import proto_b
178178

179-
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
180179
def _(drawable_b: proto_b.Drawable):
180+
# error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
181181
drawable: proto_a.Drawable = drawable_b
182182
```
183183

crates/ty_python_semantic/resources/mdtest/loops/for.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,7 @@ class StrIterator:
247247

248248
def f(x: IntIterator | StrIterator):
249249
for a in x:
250-
# TODO: this should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
251-
reveal_type(a) # revealed: int
250+
reveal_type(a) # revealed: int | str
252251
```
253252

254253
Most real-world iterable types use `Iterator` as the return annotation of their `__iter__` methods:
@@ -260,14 +259,11 @@ def g(
260259
c: Literal["foo", b"bar"],
261260
):
262261
for x in a:
263-
# TODO: should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
264-
reveal_type(x) # revealed: int
262+
reveal_type(x) # revealed: int | str
265263
for y in b:
266-
# TODO: should be `str | int` (https://github.com/astral-sh/ty/issues/1089)
267-
reveal_type(y) # revealed: str
264+
reveal_type(y) # revealed: str | int
268265
for z in c:
269-
# TODO: should be `LiteralString | int` (https://github.com/astral-sh/ty/issues/1089)
270-
reveal_type(z) # revealed: LiteralString
266+
reveal_type(z) # revealed: LiteralString | int
271267
```
272268

273269
## Union type as iterable where one union element has no `__iter__` method

crates/ty_python_semantic/resources/mdtest/protocols.md

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -617,11 +617,10 @@ static_assert(is_assignable_to(Foo, HasX))
617617
static_assert(not is_subtype_of(Foo, HasXY))
618618
static_assert(not is_assignable_to(Foo, HasXY))
619619

620-
# TODO: these should pass
621-
static_assert(not is_subtype_of(HasXIntSub, HasX)) # error: [static-assert-error]
622-
static_assert(not is_assignable_to(HasXIntSub, HasX)) # error: [static-assert-error]
623-
static_assert(not is_subtype_of(HasX, HasXIntSub)) # error: [static-assert-error]
624-
static_assert(not is_assignable_to(HasX, HasXIntSub)) # error: [static-assert-error]
620+
static_assert(not is_subtype_of(HasXIntSub, HasX))
621+
static_assert(not is_assignable_to(HasXIntSub, HasX))
622+
static_assert(not is_subtype_of(HasX, HasXIntSub))
623+
static_assert(not is_assignable_to(HasX, HasXIntSub))
625624

626625
class FooSub(Foo): ...
627626

@@ -2286,10 +2285,9 @@ class MethodPUnrelated(Protocol):
22862285

22872286
static_assert(is_subtype_of(MethodPSub, MethodPSuper))
22882287

2289-
# TODO: these should pass
2290-
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper)) # error: [static-assert-error]
2291-
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated)) # error: [static-assert-error]
2292-
static_assert(not is_assignable_to(MethodPSuper, MethodPSub)) # error: [static-assert-error]
2288+
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper))
2289+
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated))
2290+
static_assert(not is_assignable_to(MethodPSuper, MethodPSub))
22932291
```
22942292

22952293
## Subtyping between protocols with method members and protocols with non-method members
@@ -2348,8 +2346,7 @@ And for the same reason, they are never assignable to attribute members (which a
23482346
class Attribute(Protocol):
23492347
f: Callable[[], bool]
23502348

2351-
# TODO: should pass
2352-
static_assert(not is_assignable_to(Method, Attribute)) # error: [static-assert-error]
2349+
static_assert(not is_assignable_to(Method, Attribute))
23532350
```
23542351

23552352
Protocols with attribute members, meanwhile, cannot be assigned to protocols with method members,
@@ -2358,9 +2355,8 @@ this is not true for attribute members. The same principle also applies for prot
23582355
members
23592356

23602357
```py
2361-
# TODO: this should pass
2362-
static_assert(not is_assignable_to(PropertyBool, Method)) # error: [static-assert-error]
2363-
static_assert(not is_assignable_to(Attribute, Method)) # error: [static-assert-error]
2358+
static_assert(not is_assignable_to(PropertyBool, Method))
2359+
static_assert(not is_assignable_to(Attribute, Method))
23642360
```
23652361

23662362
But an exception to this rule is if an attribute member is marked as `ClassVar`, as this guarantees
@@ -2695,9 +2691,8 @@ class RecursiveNonFullyStatic(Protocol):
26952691
parent: RecursiveNonFullyStatic
26962692
x: Any
26972693

2698-
# TODO: these should pass, once we take into account types of members
2699-
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic)) # error: [static-assert-error]
2700-
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic)) # error: [static-assert-error]
2694+
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
2695+
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
27012696

27022697
static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveNonFullyStatic))
27032698
static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
@@ -2715,9 +2710,7 @@ class RecursiveOptionalParent(Protocol):
27152710
static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
27162711

27172712
# Due to invariance of mutable attribute members, neither is assignable to the other
2718-
#
2719-
# TODO: should pass
2720-
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
2713+
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
27212714
static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
27222715

27232716
class Other(Protocol):

0 commit comments

Comments
 (0)