Skip to content

Commit eaab5d1

Browse files
committed
Handle function-like callables in subtyping, assignability
1 parent c4f93df commit eaab5d1

File tree

2 files changed

+40
-9
lines changed

2 files changed

+40
-9
lines changed

crates/ty_python_semantic/resources/mdtest/dataclasses.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ python-version = "3.12"
839839
from dataclasses import dataclass
840840
from typing import Callable
841841
from types import FunctionType
842-
from ty_extensions import TypeOf, static_assert, is_subtype_of, is_assignable_to
842+
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to
843843

844844
@dataclass
845845
class C:
@@ -852,16 +852,24 @@ reveal_type(type(C.__init__)) # revealed: <class 'FunctionType'>
852852
reveal_type(type(C.__init__).__code__) # revealed: CodeType
853853
reveal_type(C.__init__.__code__) # revealed: CodeType
854854

855+
def equivalent_signature(self: C, x: int) -> None:
856+
pass
857+
855858
type DunderInitType = TypeOf[C.__init__]
856-
type EquivalentCallableType = Callable[[C, int], None]
859+
type EquivalentPureCallableType = Callable[[C, int], None]
860+
type EquivalentFunctionLikeCallableType = CallableTypeOf[equivalent_signature]
861+
862+
static_assert(is_subtype_of(DunderInitType, EquivalentPureCallableType))
863+
static_assert(is_assignable_to(DunderInitType, EquivalentPureCallableType))
864+
865+
static_assert(not is_subtype_of(EquivalentPureCallableType, DunderInitType))
866+
static_assert(not is_assignable_to(EquivalentPureCallableType, DunderInitType))
857867

858-
static_assert(is_subtype_of(DunderInitType, EquivalentCallableType))
859-
static_assert(is_assignable_to(DunderInitType, EquivalentCallableType))
868+
static_assert(is_subtype_of(DunderInitType, EquivalentFunctionLikeCallableType))
869+
static_assert(is_assignable_to(DunderInitType, EquivalentFunctionLikeCallableType))
860870

861-
static_assert(not is_subtype_of(EquivalentCallableType, DunderInitType))
862-
static_assert(not is_assignable_to(EquivalentCallableType, DunderInitType))
871+
static_assert(not is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
872+
static_assert(not is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
863873

864-
# TODO:
865-
# error: [static-assert-error]
866874
static_assert(is_subtype_of(DunderInitType, FunctionType))
867875
```

crates/ty_python_semantic/src/types.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,14 @@ impl<'db> Type<'db> {
12581258
) => (self.literal_fallback_instance(db))
12591259
.is_some_and(|instance| instance.is_subtype_of(db, target)),
12601260

1261+
// Function-like callables are subtypes of `FunctionType`
1262+
(Type::Callable(callable), Type::NominalInstance(target))
1263+
if callable.is_function_like(db)
1264+
&& target.class.is_known(db, KnownClass::FunctionType) =>
1265+
{
1266+
true
1267+
}
1268+
12611269
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
12621270
self_function_literal
12631271
.into_callable_type(db)
@@ -6933,7 +6941,7 @@ impl<'db> FunctionType<'db> {
69336941
Type::Callable(CallableType::from_overloads(
69346942
db,
69356943
self.signature(db).overloads.iter().cloned(),
6936-
true,
6944+
false,
69376945
))
69386946
}
69396947

@@ -7754,6 +7762,13 @@ impl<'db> CallableType<'db> {
77547762
where
77557763
F: Fn(&Signature<'db>, &Signature<'db>) -> bool,
77567764
{
7765+
let self_is_function_like = self.is_function_like(db);
7766+
let other_is_function_like = other.is_function_like(db);
7767+
7768+
if !self_is_function_like && other_is_function_like {
7769+
return false;
7770+
}
7771+
77577772
match (self.signatures(db), other.signatures(db)) {
77587773
([self_signature], [other_signature]) => {
77597774
// Base case: both callable types contain a single signature.
@@ -7796,6 +7811,10 @@ impl<'db> CallableType<'db> {
77967811
///
77977812
/// See [`Type::is_equivalent_to`] for more details.
77987813
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
7814+
if self.is_function_like(db) != other.is_function_like(db) {
7815+
return false;
7816+
}
7817+
77997818
match (self.signatures(db), other.signatures(db)) {
78007819
([self_signature], [other_signature]) => {
78017820
// Common case: both callable types contain a single signature, use the custom
@@ -7822,6 +7841,10 @@ impl<'db> CallableType<'db> {
78227841
///
78237842
/// See [`Type::is_gradual_equivalent_to`] for more details.
78247843
fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
7844+
if self.is_function_like(db) != other.is_function_like(db) {
7845+
return false;
7846+
}
7847+
78257848
match (self.signatures(db), other.signatures(db)) {
78267849
([self_signature], [other_signature]) => {
78277850
self_signature.is_gradual_equivalent_to(db, other_signature)

0 commit comments

Comments
 (0)