Skip to content

Commit b6f5dc4

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

File tree

13 files changed

+906
-333
lines changed

13 files changed

+906
-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+
630,
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
@@ -1210,11 +1210,7 @@ from typing_extensions import LiteralString
12101210

12111211
def f(a: Foo, b: list[str], c: list[LiteralString], e):
12121212
reveal_type(e) # revealed: Unknown
1213-
1214-
# TODO: we should select the second overload here and reveal `str`
1215-
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
1216-
reveal_type(a.join(b)) # revealed: LiteralString
1217-
1213+
reveal_type(a.join(b)) # revealed: str
12181214
reveal_type(a.join(c)) # revealed: LiteralString
12191215

12201216
# 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
@@ -205,8 +205,8 @@ from typing import Protocol
205205
import proto_a
206206
import proto_b
207207

208-
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
209208
def _(drawable_b: proto_b.Drawable):
209+
# error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
210210
drawable: proto_a.Drawable = drawable_b
211211
```
212212

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

@@ -2291,10 +2290,9 @@ class MethodPUnrelated(Protocol):
22912290

22922291
static_assert(is_subtype_of(MethodPSub, MethodPSuper))
22932292

2294-
# TODO: these should pass
2295-
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper)) # error: [static-assert-error]
2296-
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated)) # error: [static-assert-error]
2297-
static_assert(not is_assignable_to(MethodPSuper, MethodPSub)) # error: [static-assert-error]
2293+
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper))
2294+
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated))
2295+
static_assert(not is_assignable_to(MethodPSuper, MethodPSub))
22982296
```
22992297

23002298
## Subtyping between protocols with method members and protocols with non-method members
@@ -2353,8 +2351,7 @@ And for the same reason, they are never assignable to attribute members (which a
23532351
class Attribute(Protocol):
23542352
f: Callable[[], bool]
23552353

2356-
# TODO: should pass
2357-
static_assert(not is_assignable_to(Method, Attribute)) # error: [static-assert-error]
2354+
static_assert(not is_assignable_to(Method, Attribute))
23582355
```
23592356

23602357
Protocols with attribute members, meanwhile, cannot be assigned to protocols with method members,
@@ -2363,9 +2360,8 @@ this is not true for attribute members. The same principle also applies for prot
23632360
members
23642361

23652362
```py
2366-
# TODO: this should pass
2367-
static_assert(not is_assignable_to(PropertyBool, Method)) # error: [static-assert-error]
2368-
static_assert(not is_assignable_to(Attribute, Method)) # error: [static-assert-error]
2363+
static_assert(not is_assignable_to(PropertyBool, Method))
2364+
static_assert(not is_assignable_to(Attribute, Method))
23692365
```
23702366

23712367
But an exception to this rule is if an attribute member is marked as `ClassVar`, as this guarantees
@@ -2384,9 +2380,8 @@ static_assert(is_assignable_to(ClassVarAttribute, Method))
23842380
class ClassVarAttributeBad(Protocol):
23852381
f: ClassVar[Callable[[], str]]
23862382

2387-
# TODO: these should pass:
2388-
static_assert(not is_subtype_of(ClassVarAttributeBad, Method)) # error: [static-assert-error]
2389-
static_assert(not is_assignable_to(ClassVarAttributeBad, Method)) # error: [static-assert-error]
2383+
static_assert(not is_subtype_of(ClassVarAttributeBad, Method))
2384+
static_assert(not is_assignable_to(ClassVarAttributeBad, Method))
23902385
```
23912386

23922387
## Narrowing of protocols
@@ -2707,9 +2702,8 @@ class RecursiveNonFullyStatic(Protocol):
27072702
parent: RecursiveNonFullyStatic
27082703
x: Any
27092704

2710-
# TODO: these should pass, once we take into account types of members
2711-
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic)) # error: [static-assert-error]
2712-
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic)) # error: [static-assert-error]
2705+
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
2706+
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
27132707

27142708
static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveNonFullyStatic))
27152709
static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
@@ -2727,9 +2721,7 @@ class RecursiveOptionalParent(Protocol):
27272721
static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
27282722

27292723
# Due to invariance of mutable attribute members, neither is assignable to the other
2730-
#
2731-
# TODO: should pass
2732-
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
2724+
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
27332725
static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
27342726

27352727
class Other(Protocol):

0 commit comments

Comments
 (0)