Skip to content

Commit ced0e48

Browse files
committed
[ty] Improve assignability/subtyping between two protocol types
1 parent 801e6d4 commit ced0e48

File tree

13 files changed

+897
-333
lines changed

13 files changed

+897
-333
lines changed

crates/ruff_benchmark/benches/ty_walltime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLo
232232
max_dep_date: "2025-08-09",
233233
python_version: PythonVersion::PY311,
234234
},
235-
600,
235+
620,
236236
)
237237
});
238238

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: 15 additions & 23 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
@@ -2379,9 +2375,8 @@ static_assert(is_assignable_to(ClassVarAttribute, Method))
23792375
class ClassVarAttributeBad(Protocol):
23802376
f: ClassVar[Callable[[], str]]
23812377

2382-
# TODO: these should pass:
2383-
static_assert(not is_subtype_of(ClassVarAttributeBad, Method)) # error: [static-assert-error]
2384-
static_assert(not is_assignable_to(ClassVarAttributeBad, Method)) # error: [static-assert-error]
2378+
static_assert(not is_subtype_of(ClassVarAttributeBad, Method))
2379+
static_assert(not is_assignable_to(ClassVarAttributeBad, Method))
23852380
```
23862381

23872382
## Narrowing of protocols
@@ -2702,9 +2697,8 @@ class RecursiveNonFullyStatic(Protocol):
27022697
parent: RecursiveNonFullyStatic
27032698
x: Any
27042699

2705-
# TODO: these should pass, once we take into account types of members
2706-
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic)) # error: [static-assert-error]
2707-
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic)) # error: [static-assert-error]
2700+
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
2701+
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
27082702

27092703
static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveNonFullyStatic))
27102704
static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
@@ -2722,9 +2716,7 @@ class RecursiveOptionalParent(Protocol):
27222716
static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
27232717

27242718
# Due to invariance of mutable attribute members, neither is assignable to the other
2725-
#
2726-
# TODO: should pass
2727-
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
2719+
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
27282720
static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
27292721

27302722
class Other(Protocol):

0 commit comments

Comments
 (0)