mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-31 15:47:41 +00:00
[ty] Callable types are disjoint from non-callable @final
nominal instance types (#18368)
## Summary Resolves [#513](https://github.com/astral-sh/ty/issues/513). Callable types are now considered to be disjoint from nominal instance types where: * The class is `@final`, and * Its `__call__` either does not exist or is not assignable to `(...) -> Unknown`. ## Test Plan Markdown tests. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
695de4f27f
commit
9b0dfc505f
2 changed files with 68 additions and 0 deletions
|
@ -439,3 +439,51 @@ static_assert(is_disjoint_from(Callable[[], None], Literal[b""]))
|
|||
static_assert(is_disjoint_from(Callable[[], None], Literal[1]))
|
||||
static_assert(is_disjoint_from(Callable[[], None], Literal[True]))
|
||||
```
|
||||
|
||||
A callable type is disjoint from nominal instance types where the classes are final and whose
|
||||
`__call__` is not callable.
|
||||
|
||||
```py
|
||||
from ty_extensions import CallableTypeOf, is_disjoint_from, static_assert
|
||||
from typing_extensions import Any, Callable, final
|
||||
|
||||
@final
|
||||
class C: ...
|
||||
|
||||
static_assert(is_disjoint_from(bool, Callable[..., Any]))
|
||||
static_assert(is_disjoint_from(C, Callable[..., Any]))
|
||||
static_assert(is_disjoint_from(bool | C, Callable[..., Any]))
|
||||
|
||||
static_assert(not is_disjoint_from(str, Callable[..., Any]))
|
||||
static_assert(not is_disjoint_from(bool | str, Callable[..., Any]))
|
||||
|
||||
def bound_with_valid_type():
|
||||
@final
|
||||
class D:
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
static_assert(not is_disjoint_from(D, Callable[..., Any]))
|
||||
|
||||
def possibly_unbound_with_valid_type(flag: bool):
|
||||
@final
|
||||
class E:
|
||||
if flag:
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> Any: ...
|
||||
|
||||
static_assert(not is_disjoint_from(E, Callable[..., Any]))
|
||||
|
||||
def bound_with_invalid_type():
|
||||
@final
|
||||
class F:
|
||||
__call__: int = 1
|
||||
|
||||
static_assert(is_disjoint_from(F, Callable[..., Any]))
|
||||
|
||||
def possibly_unbound_with_invalid_type(flag: bool):
|
||||
@final
|
||||
class G:
|
||||
if flag:
|
||||
__call__: int = 1
|
||||
|
||||
static_assert(is_disjoint_from(G, Callable[..., Any]))
|
||||
```
|
||||
|
|
|
@ -2177,6 +2177,26 @@ impl<'db> Type<'db> {
|
|||
true
|
||||
}
|
||||
|
||||
(
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
Type::NominalInstance(instance),
|
||||
)
|
||||
| (
|
||||
Type::NominalInstance(instance),
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
) if instance.class.is_final(db) => {
|
||||
let member = self.member_lookup_with_policy(
|
||||
db,
|
||||
Name::new_static("__call__"),
|
||||
MemberLookupPolicy::NO_INSTANCE_FALLBACK,
|
||||
);
|
||||
match member.symbol {
|
||||
// TODO: ideally this would check disjointness of the `__call__` signature and the callable signature
|
||||
Symbol::Type(ty, _) => !ty.is_assignable_to(db, CallableType::unknown(db)),
|
||||
Symbol::Unbound => true,
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
_,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue