mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Add a Todo-type branch for type[P]
where P
is a protocol class (#19947)
This commit is contained in:
parent
24f6d2dc13
commit
e6dcdd29f2
5 changed files with 87 additions and 16 deletions
|
@ -355,7 +355,9 @@ And as a corollary, `type[MyProtocol]` can also be called:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def f(x: type[MyProtocol]):
|
def f(x: type[MyProtocol]):
|
||||||
reveal_type(x()) # revealed: MyProtocol
|
# TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
||||||
|
# (which doesn't work well with snapshots)
|
||||||
|
x()
|
||||||
```
|
```
|
||||||
|
|
||||||
## Members of a protocol
|
## Members of a protocol
|
||||||
|
@ -1931,7 +1933,7 @@ def _(r: Recursive):
|
||||||
reveal_type(r.t) # revealed: tuple[int, tuple[str, Recursive]]
|
reveal_type(r.t) # revealed: tuple[int, tuple[str, Recursive]]
|
||||||
reveal_type(r.callable1) # revealed: (int, /) -> Recursive
|
reveal_type(r.callable1) # revealed: (int, /) -> Recursive
|
||||||
reveal_type(r.callable2) # revealed: (Recursive, /) -> int
|
reveal_type(r.callable2) # revealed: (Recursive, /) -> int
|
||||||
reveal_type(r.subtype_of) # revealed: type[Recursive]
|
reveal_type(r.subtype_of) # revealed: @Todo(type[T] for protocols)
|
||||||
reveal_type(r.generic) # revealed: GenericC[Recursive]
|
reveal_type(r.generic) # revealed: GenericC[Recursive]
|
||||||
reveal_type(r.method(r)) # revealed: Recursive
|
reveal_type(r.method(r)) # revealed: Recursive
|
||||||
reveal_type(r.nested) # revealed: Recursive | ((Recursive, tuple[Recursive, Recursive], /) -> Recursive)
|
reveal_type(r.nested) # revealed: Recursive | ((Recursive, tuple[Recursive, Recursive], /) -> Recursive)
|
||||||
|
@ -2069,6 +2071,62 @@ def f(value: Iterator):
|
||||||
cast(Iterator, value) # error: [redundant-cast]
|
cast(Iterator, value) # error: [redundant-cast]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Meta-protocols
|
||||||
|
|
||||||
|
Where `P` is a protocol type, a class object `N` can be said to inhabit the type `type[P]` if:
|
||||||
|
|
||||||
|
- All `ClassVar` members on `P` exist on the class object `N`
|
||||||
|
- All method members on `P` exist on the class object `N`
|
||||||
|
- Instantiating `N` creates an object that would satisfy the protocol `P`
|
||||||
|
|
||||||
|
Currently meta-protocols are not fully supported by ty, but we try to keep false positives to a
|
||||||
|
minimum in the meantime.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Protocol, ClassVar
|
||||||
|
from ty_extensions import static_assert, is_assignable_to, TypeOf, is_subtype_of
|
||||||
|
|
||||||
|
class Foo(Protocol):
|
||||||
|
x: int
|
||||||
|
y: ClassVar[str]
|
||||||
|
def method(self) -> bytes: ...
|
||||||
|
|
||||||
|
def _(f: type[Foo]):
|
||||||
|
reveal_type(f) # revealed: type[@Todo(type[T] for protocols)]
|
||||||
|
|
||||||
|
# TODO: we should emit `unresolved-attribute` here: although we would accept this for a
|
||||||
|
# nominal class, we would see any class `N` as inhabiting `Foo` if it had an implicit
|
||||||
|
# instance attribute `x`, and implicit instance attributes are rarely bound on the class
|
||||||
|
# object.
|
||||||
|
reveal_type(f.x) # revealed: @Todo(type[T] for protocols)
|
||||||
|
|
||||||
|
# TODO: should be `str`
|
||||||
|
reveal_type(f.y) # revealed: @Todo(type[T] for protocols)
|
||||||
|
f.y = "foo" # fine
|
||||||
|
|
||||||
|
# TODO: should be `Callable[[Foo], bytes]`
|
||||||
|
reveal_type(f.method) # revealed: @Todo(type[T] for protocols)
|
||||||
|
|
||||||
|
class Bar: ...
|
||||||
|
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(not is_assignable_to(type[Bar], type[Foo])) # error: [static-assert-error]
|
||||||
|
static_assert(not is_assignable_to(TypeOf[Bar], type[Foo])) # error: [static-assert-error]
|
||||||
|
|
||||||
|
class Baz:
|
||||||
|
x: int
|
||||||
|
y: ClassVar[str] = "foo"
|
||||||
|
def method(self) -> bytes:
|
||||||
|
return b"foo"
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(type[Baz], type[Foo]))
|
||||||
|
static_assert(is_assignable_to(TypeOf[Baz], type[Foo]))
|
||||||
|
|
||||||
|
# TODO: these should pass
|
||||||
|
static_assert(is_subtype_of(type[Baz], type[Foo])) # error: [static-assert-error]
|
||||||
|
static_assert(is_subtype_of(TypeOf[Baz], type[Foo])) # error: [static-assert-error]
|
||||||
|
```
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
Add tests for:
|
Add tests for:
|
||||||
|
|
|
@ -36,7 +36,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
|
||||||
22 |
|
22 |
|
||||||
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||||
24 | def f(x: type[MyProtocol]):
|
24 | def f(x: type[MyProtocol]):
|
||||||
25 | reveal_type(x()) # revealed: MyProtocol
|
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
||||||
|
26 | # (which doesn't work well with snapshots)
|
||||||
|
27 | x()
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
@ -161,19 +163,7 @@ info[revealed-type]: Revealed type
|
||||||
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfGenericProtocol[int]`
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfGenericProtocol[int]`
|
||||||
24 | def f(x: type[MyProtocol]):
|
24 | def f(x: type[MyProtocol]):
|
||||||
25 | reveal_type(x()) # revealed: MyProtocol
|
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
||||||
|
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
info[revealed-type]: Revealed type
|
|
||||||
--> src/mdtest_snippet.py:25:17
|
|
||||||
|
|
|
||||||
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
|
|
||||||
24 | def f(x: type[MyProtocol]):
|
|
||||||
25 | reveal_type(x()) # revealed: MyProtocol
|
|
||||||
| ^^^ `MyProtocol`
|
|
||||||
|
|
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -849,6 +849,18 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn into_dynamic(self) -> Option<DynamicType> {
|
||||||
|
match self {
|
||||||
|
Type::Dynamic(dynamic_type) => Some(dynamic_type),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn expect_dynamic(self) -> DynamicType {
|
||||||
|
self.into_dynamic()
|
||||||
|
.expect("Expected a Type::Dynamic variant")
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) fn expect_class_literal(self) -> ClassLiteral<'db> {
|
pub(crate) fn expect_class_literal(self) -> ClassLiteral<'db> {
|
||||||
self.into_class_literal()
|
self.into_class_literal()
|
||||||
|
|
|
@ -10198,6 +10198,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
Type::ClassLiteral(class_literal) => {
|
Type::ClassLiteral(class_literal) => {
|
||||||
if class_literal.is_known(self.db(), KnownClass::Any) {
|
if class_literal.is_known(self.db(), KnownClass::Any) {
|
||||||
SubclassOfType::subclass_of_any()
|
SubclassOfType::subclass_of_any()
|
||||||
|
} else if class_literal.is_protocol(self.db()) {
|
||||||
|
SubclassOfType::from(
|
||||||
|
self.db(),
|
||||||
|
todo_type!("type[T] for protocols").expect_dynamic(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
SubclassOfType::from(
|
SubclassOfType::from(
|
||||||
self.db(),
|
self.db(),
|
||||||
|
|
|
@ -285,6 +285,12 @@ impl<'db> From<ClassType<'db>> for SubclassOfInner<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<DynamicType> for SubclassOfInner<'_> {
|
||||||
|
fn from(value: DynamicType) -> Self {
|
||||||
|
SubclassOfInner::Dynamic(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'db> From<SubclassOfInner<'db>> for Type<'db> {
|
impl<'db> From<SubclassOfInner<'db>> for Type<'db> {
|
||||||
fn from(value: SubclassOfInner<'db>) -> Self {
|
fn from(value: SubclassOfInner<'db>) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue