mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 18:58:04 +00:00
[red-knot] Add basic subtyping between class literal and callable (#17469)
## Summary This covers step 1 from https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable Part of #17343 ## Test Plan Update is_subtype_of.md and is_assignable_to.md --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
21561000b1
commit
53ffe7143f
2 changed files with 62 additions and 0 deletions
|
@ -1125,6 +1125,47 @@ def f(fn: Callable[[int], int]) -> None: ...
|
|||
f(a)
|
||||
```
|
||||
|
||||
### Class literals
|
||||
|
||||
#### Classes with metaclasses
|
||||
|
||||
```py
|
||||
from typing import Callable, overload
|
||||
from typing_extensions import Self
|
||||
from knot_extensions import TypeOf, static_assert, is_subtype_of
|
||||
|
||||
class MetaWithReturn(type):
|
||||
def __call__(cls) -> "A":
|
||||
return super().__call__()
|
||||
|
||||
class A(metaclass=MetaWithReturn): ...
|
||||
|
||||
static_assert(is_subtype_of(TypeOf[A], Callable[[], A]))
|
||||
static_assert(not is_subtype_of(TypeOf[A], Callable[[object], A]))
|
||||
|
||||
class MetaWithDifferentReturn(type):
|
||||
def __call__(cls) -> int:
|
||||
return super().__call__()
|
||||
|
||||
class B(metaclass=MetaWithDifferentReturn): ...
|
||||
|
||||
static_assert(is_subtype_of(TypeOf[B], Callable[[], int]))
|
||||
static_assert(not is_subtype_of(TypeOf[B], Callable[[], B]))
|
||||
|
||||
class MetaWithOverloadReturn(type):
|
||||
@overload
|
||||
def __call__(cls, x: int) -> int: ...
|
||||
@overload
|
||||
def __call__(cls) -> str: ...
|
||||
def __call__(cls, x: int | None = None) -> str | int:
|
||||
return super().__call__()
|
||||
|
||||
class C(metaclass=MetaWithOverloadReturn): ...
|
||||
|
||||
static_assert(is_subtype_of(TypeOf[C], Callable[[int], int]))
|
||||
static_assert(is_subtype_of(TypeOf[C], Callable[[], str]))
|
||||
```
|
||||
|
||||
### Bound methods
|
||||
|
||||
```py
|
||||
|
|
|
@ -1134,6 +1134,27 @@ impl<'db> Type<'db> {
|
|||
self_subclass_ty.is_subtype_of(db, target_subclass_ty)
|
||||
}
|
||||
|
||||
(Type::ClassLiteral(_), Type::Callable(_)) => {
|
||||
let metaclass_call_symbol = self
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
"__call__".into(),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK
|
||||
| MemberLookupPolicy::META_CLASS_NO_TYPE_FALLBACK,
|
||||
)
|
||||
.symbol;
|
||||
|
||||
if let Symbol::Type(Type::BoundMethod(new_function), _) = metaclass_call_symbol {
|
||||
// TODO: this intentionally diverges from step 1 in
|
||||
// https://typing.python.org/en/latest/spec/constructors.html#converting-a-constructor-to-callable
|
||||
// by always respecting the signature of the metaclass `__call__`, rather than
|
||||
// using a heuristic which makes unwarranted assumptions to sometimes ignore it.
|
||||
let new_function = new_function.into_callable_type(db);
|
||||
return new_function.is_subtype_of(db, target);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// `Literal[str]` is a subtype of `type` because the `str` class object is an instance of its metaclass `type`.
|
||||
// `Literal[abc.ABC]` is a subtype of `abc.ABCMeta` because the `abc.ABC` class object
|
||||
// is an instance of its metaclass `abc.ABCMeta`.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue