mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00
[ty] Synthetic function-like callables (#18242)
## Summary We create `Callable` types for synthesized functions like the `__init__` method of a dataclass. These generated functions are real functions though, with descriptor-like behavior. That is, they can bind `self` when accessed on an instance. This was modeled incorrectly so far. ## Test Plan Updated tests
This commit is contained in:
parent
48c425c15b
commit
bbcd7e0196
4 changed files with 167 additions and 32 deletions
|
@ -56,8 +56,6 @@ Person(20, "Eve")
|
||||||
|
|
||||||
## Signature of `__init__`
|
## Signature of `__init__`
|
||||||
|
|
||||||
TODO: All of the following tests are missing the `self` argument in the `__init__` signature.
|
|
||||||
|
|
||||||
Declarations in the class body are used to generate the signature of the `__init__` method. If the
|
Declarations in the class body are used to generate the signature of the `__init__` method. If the
|
||||||
attributes are not just declarations, but also bindings, the type inferred from bindings is used as
|
attributes are not just declarations, but also bindings, the type inferred from bindings is used as
|
||||||
the default value.
|
the default value.
|
||||||
|
@ -71,7 +69,7 @@ class D:
|
||||||
y: str = "default"
|
y: str = "default"
|
||||||
z: int | None = 1 + 2
|
z: int | None = 1 + 2
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (x: int, y: str = Literal["default"], z: int | None = Literal[3]) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int, y: str = Literal["default"], z: int | None = Literal[3]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
This also works if the declaration and binding are split:
|
This also works if the declaration and binding are split:
|
||||||
|
@ -82,7 +80,7 @@ class D:
|
||||||
x: int | None
|
x: int | None
|
||||||
x = None
|
x = None
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (x: int | None = None) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int | None = None) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Non-fully static types are handled correctly:
|
Non-fully static types are handled correctly:
|
||||||
|
@ -96,7 +94,7 @@ class C:
|
||||||
y: int | Any
|
y: int | Any
|
||||||
z: tuple[int, Any]
|
z: tuple[int, Any]
|
||||||
|
|
||||||
reveal_type(C.__init__) # revealed: (x: Any, y: int | Any, z: tuple[int, Any]) -> None
|
reveal_type(C.__init__) # revealed: (self: C, x: Any, y: int | Any, z: tuple[int, Any]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Variables without annotations are ignored:
|
Variables without annotations are ignored:
|
||||||
|
@ -107,7 +105,7 @@ class D:
|
||||||
x: int
|
x: int
|
||||||
y = 1
|
y = 1
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (x: int) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
If attributes without default values are declared after attributes with default values, a
|
If attributes without default values are declared after attributes with default values, a
|
||||||
|
@ -132,7 +130,7 @@ class D:
|
||||||
y: ClassVar[str] = "default"
|
y: ClassVar[str] = "default"
|
||||||
z: bool
|
z: bool
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (x: int, z: bool) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int, z: bool) -> None
|
||||||
|
|
||||||
d = D(1, True)
|
d = D(1, True)
|
||||||
reveal_type(d.x) # revealed: int
|
reveal_type(d.x) # revealed: int
|
||||||
|
@ -150,7 +148,7 @@ class D:
|
||||||
def y(self) -> str:
|
def y(self) -> str:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (x: int) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
And neither do nested class declarations:
|
And neither do nested class declarations:
|
||||||
|
@ -163,7 +161,7 @@ class D:
|
||||||
class Nested:
|
class Nested:
|
||||||
y: str
|
y: str
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (x: int) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
But if there is a variable annotation with a function or class literal type, the signature of
|
But if there is a variable annotation with a function or class literal type, the signature of
|
||||||
|
@ -181,7 +179,7 @@ class D:
|
||||||
class_literal: TypeOf[SomeClass]
|
class_literal: TypeOf[SomeClass]
|
||||||
class_subtype_of: type[SomeClass]
|
class_subtype_of: type[SomeClass]
|
||||||
|
|
||||||
# revealed: (function_literal: def some_function() -> None, class_literal: <class 'SomeClass'>, class_subtype_of: type[SomeClass]) -> None
|
# revealed: (self: D, function_literal: def some_function() -> None, class_literal: <class 'SomeClass'>, class_subtype_of: type[SomeClass]) -> None
|
||||||
reveal_type(D.__init__)
|
reveal_type(D.__init__)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -194,7 +192,7 @@ from typing import Callable
|
||||||
class D:
|
class D:
|
||||||
c: Callable[[int], str]
|
c: Callable[[int], str]
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (c: (int, /) -> str) -> None
|
reveal_type(D.__init__) # revealed: (self: D, c: (int, /) -> str) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Implicit instance attributes do not affect the signature of `__init__`:
|
Implicit instance attributes do not affect the signature of `__init__`:
|
||||||
|
@ -209,7 +207,7 @@ class D:
|
||||||
|
|
||||||
reveal_type(D(1).y) # revealed: str
|
reveal_type(D(1).y) # revealed: str
|
||||||
|
|
||||||
reveal_type(D.__init__) # revealed: (x: int) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
Annotating expressions does not lead to an entry in `__annotations__` at runtime, and so it wouldn't
|
Annotating expressions does not lead to an entry in `__annotations__` at runtime, and so it wouldn't
|
||||||
|
@ -222,7 +220,7 @@ class D:
|
||||||
(x): int = 1
|
(x): int = 1
|
||||||
|
|
||||||
# TODO: should ideally not include a `x` parameter
|
# TODO: should ideally not include a `x` parameter
|
||||||
reveal_type(D.__init__) # revealed: (x: int = Literal[1]) -> None
|
reveal_type(D.__init__) # revealed: (self: D, x: int = Literal[1]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
## `@dataclass` calls with arguments
|
## `@dataclass` calls with arguments
|
||||||
|
@ -529,7 +527,7 @@ class C(Base):
|
||||||
z: int = 10
|
z: int = 10
|
||||||
x: int = 15
|
x: int = 15
|
||||||
|
|
||||||
reveal_type(C.__init__) # revealed: (x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
|
reveal_type(C.__init__) # revealed: (self: C, x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
## Generic dataclasses
|
## Generic dataclasses
|
||||||
|
@ -582,7 +580,7 @@ class UppercaseString:
|
||||||
class C:
|
class C:
|
||||||
upper: UppercaseString = UppercaseString()
|
upper: UppercaseString = UppercaseString()
|
||||||
|
|
||||||
reveal_type(C.__init__) # revealed: (upper: str = str) -> None
|
reveal_type(C.__init__) # revealed: (self: C, upper: str = str) -> None
|
||||||
|
|
||||||
c = C("abc")
|
c = C("abc")
|
||||||
reveal_type(c.upper) # revealed: str
|
reveal_type(c.upper) # revealed: str
|
||||||
|
@ -628,7 +626,7 @@ class ConvertToLength:
|
||||||
class C:
|
class C:
|
||||||
converter: ConvertToLength = ConvertToLength()
|
converter: ConvertToLength = ConvertToLength()
|
||||||
|
|
||||||
reveal_type(C.__init__) # revealed: (converter: str = Literal[""]) -> None
|
reveal_type(C.__init__) # revealed: (self: C, converter: str = Literal[""]) -> None
|
||||||
|
|
||||||
c = C("abc")
|
c = C("abc")
|
||||||
reveal_type(c.converter) # revealed: int
|
reveal_type(c.converter) # revealed: int
|
||||||
|
@ -667,7 +665,7 @@ class AcceptsStrAndInt:
|
||||||
class C:
|
class C:
|
||||||
field: AcceptsStrAndInt = AcceptsStrAndInt()
|
field: AcceptsStrAndInt = AcceptsStrAndInt()
|
||||||
|
|
||||||
reveal_type(C.__init__) # revealed: (field: str | int = int) -> None
|
reveal_type(C.__init__) # revealed: (self: C, field: str | int = int) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
## `dataclasses.field`
|
## `dataclasses.field`
|
||||||
|
@ -728,7 +726,7 @@ import dataclasses
|
||||||
class C:
|
class C:
|
||||||
x: str
|
x: str
|
||||||
|
|
||||||
reveal_type(C.__init__) # revealed: (x: str) -> None
|
reveal_type(C.__init__) # revealed: (self: C, x: str) -> None
|
||||||
```
|
```
|
||||||
|
|
||||||
### Dataclass with custom `__init__` method
|
### Dataclass with custom `__init__` method
|
||||||
|
@ -821,10 +819,57 @@ reveal_type(Person.__mro__) # revealed: tuple[<class 'Person'>, <class 'object'
|
||||||
The generated methods have the following signatures:
|
The generated methods have the following signatures:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# TODO: `self` is missing here
|
reveal_type(Person.__init__) # revealed: (self: Person, name: str, age: int | None = None) -> None
|
||||||
reveal_type(Person.__init__) # revealed: (name: str, age: int | None = None) -> None
|
|
||||||
|
|
||||||
reveal_type(Person.__repr__) # revealed: def __repr__(self) -> str
|
reveal_type(Person.__repr__) # revealed: def __repr__(self) -> str
|
||||||
|
|
||||||
reveal_type(Person.__eq__) # revealed: def __eq__(self, value: object, /) -> bool
|
reveal_type(Person.__eq__) # revealed: def __eq__(self, value: object, /) -> bool
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Function-like behavior of synthesized methods
|
||||||
|
|
||||||
|
Here, we make sure that the synthesized methods of dataclasses behave like proper functions.
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.12"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable
|
||||||
|
from types import FunctionType
|
||||||
|
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
x: int
|
||||||
|
|
||||||
|
reveal_type(C.__init__) # revealed: (self: C, x: int) -> None
|
||||||
|
reveal_type(type(C.__init__)) # revealed: <class 'FunctionType'>
|
||||||
|
|
||||||
|
# We can access attributes that are defined on functions:
|
||||||
|
reveal_type(type(C.__init__).__code__) # revealed: CodeType
|
||||||
|
reveal_type(C.__init__.__code__) # revealed: CodeType
|
||||||
|
|
||||||
|
def equivalent_signature(self: C, x: int) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
type DunderInitType = TypeOf[C.__init__]
|
||||||
|
type EquivalentPureCallableType = Callable[[C, int], None]
|
||||||
|
type EquivalentFunctionLikeCallableType = CallableTypeOf[equivalent_signature]
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(DunderInitType, EquivalentPureCallableType))
|
||||||
|
static_assert(is_assignable_to(DunderInitType, EquivalentPureCallableType))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(EquivalentPureCallableType, DunderInitType))
|
||||||
|
static_assert(not is_assignable_to(EquivalentPureCallableType, DunderInitType))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(DunderInitType, EquivalentFunctionLikeCallableType))
|
||||||
|
static_assert(is_assignable_to(DunderInitType, EquivalentFunctionLikeCallableType))
|
||||||
|
|
||||||
|
static_assert(not is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
|
||||||
|
static_assert(not is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(DunderInitType, FunctionType))
|
||||||
|
```
|
||||||
|
|
|
@ -1235,6 +1235,14 @@ impl<'db> Type<'db> {
|
||||||
) => (self.literal_fallback_instance(db))
|
) => (self.literal_fallback_instance(db))
|
||||||
.is_some_and(|instance| instance.is_subtype_of(db, target)),
|
.is_some_and(|instance| instance.is_subtype_of(db, target)),
|
||||||
|
|
||||||
|
// Function-like callables are subtypes of `FunctionType`
|
||||||
|
(Type::Callable(callable), Type::NominalInstance(target))
|
||||||
|
if callable.is_function_like(db)
|
||||||
|
&& target.class.is_known(db, KnownClass::FunctionType) =>
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
|
(Type::FunctionLiteral(self_function_literal), Type::Callable(_)) => {
|
||||||
self_function_literal
|
self_function_literal
|
||||||
.into_callable_type(db)
|
.into_callable_type(db)
|
||||||
|
@ -2747,6 +2755,26 @@ impl<'db> Type<'db> {
|
||||||
instance.display(db),
|
instance.display(db),
|
||||||
owner.display(db)
|
owner.display(db)
|
||||||
);
|
);
|
||||||
|
match self {
|
||||||
|
Type::Callable(callable) if callable.is_function_like(db) => {
|
||||||
|
// For "function-like" callables, model the the behavior of `FunctionType.__get__`.
|
||||||
|
//
|
||||||
|
// It is a shortcut to model this in `try_call_dunder_get`. If we want to be really precise,
|
||||||
|
// we should instead return a new method-wrapper type variant for the synthesized `__get__`
|
||||||
|
// method of these synthesized functions. The method-wrapper would then be returned from
|
||||||
|
// `find_name_in_mro` when called on function-like `Callable`s. This would allow us to
|
||||||
|
// correctly model the behavior of *explicit* `SomeDataclass.__init__.__get__` calls.
|
||||||
|
return if instance.is_none(db) {
|
||||||
|
Some((self, AttributeKind::NormalOrNonDataDescriptor))
|
||||||
|
} else {
|
||||||
|
Some((
|
||||||
|
Type::Callable(callable.bind_self(db)),
|
||||||
|
AttributeKind::NormalOrNonDataDescriptor,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
let descr_get = self.class_member(db, "__get__".into()).symbol;
|
let descr_get = self.class_member(db, "__get__".into()).symbol;
|
||||||
|
|
||||||
|
@ -3080,6 +3108,11 @@ impl<'db> Type<'db> {
|
||||||
Type::Callable(_) | Type::DataclassTransformer(_) if name_str == "__call__" => {
|
Type::Callable(_) | Type::DataclassTransformer(_) if name_str == "__call__" => {
|
||||||
Symbol::bound(self).into()
|
Symbol::bound(self).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Type::Callable(callable) if callable.is_function_like(db) => KnownClass::FunctionType
|
||||||
|
.to_instance(db)
|
||||||
|
.member_lookup_with_policy(db, name, policy),
|
||||||
|
|
||||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Object
|
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Object
|
||||||
.to_instance(db)
|
.to_instance(db)
|
||||||
.member_lookup_with_policy(db, name, policy),
|
.member_lookup_with_policy(db, name, policy),
|
||||||
|
@ -5139,6 +5172,9 @@ impl<'db> Type<'db> {
|
||||||
Type::MethodWrapper(_) => KnownClass::MethodWrapperType.to_class_literal(db),
|
Type::MethodWrapper(_) => KnownClass::MethodWrapperType.to_class_literal(db),
|
||||||
Type::WrapperDescriptor(_) => KnownClass::WrapperDescriptorType.to_class_literal(db),
|
Type::WrapperDescriptor(_) => KnownClass::WrapperDescriptorType.to_class_literal(db),
|
||||||
Type::DataclassDecorator(_) => KnownClass::FunctionType.to_class_literal(db),
|
Type::DataclassDecorator(_) => KnownClass::FunctionType.to_class_literal(db),
|
||||||
|
Type::Callable(callable) if callable.is_function_like(db) => {
|
||||||
|
KnownClass::FunctionType.to_class_literal(db)
|
||||||
|
}
|
||||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
||||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||||
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
Type::Tuple(_) => KnownClass::Tuple.to_class_literal(db),
|
||||||
|
@ -6936,6 +6972,7 @@ impl<'db> FunctionType<'db> {
|
||||||
Type::Callable(CallableType::from_overloads(
|
Type::Callable(CallableType::from_overloads(
|
||||||
db,
|
db,
|
||||||
self.signature(db).overloads.iter().cloned(),
|
self.signature(db).overloads.iter().cloned(),
|
||||||
|
false,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7562,6 +7599,7 @@ impl<'db> BoundMethodType<'db> {
|
||||||
.overloads
|
.overloads
|
||||||
.iter()
|
.iter()
|
||||||
.map(signatures::Signature::bind_self),
|
.map(signatures::Signature::bind_self),
|
||||||
|
false,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7626,12 +7664,23 @@ impl<'db> BoundMethodType<'db> {
|
||||||
pub struct CallableType<'db> {
|
pub struct CallableType<'db> {
|
||||||
#[returns(deref)]
|
#[returns(deref)]
|
||||||
signatures: Box<[Signature<'db>]>,
|
signatures: Box<[Signature<'db>]>,
|
||||||
|
/// We use `CallableType` to represent function-like objects, like the synthesized methods
|
||||||
|
/// of dataclasses or NamedTuples. These callables act like real functions when accessed
|
||||||
|
/// as attributes on instances, i.e. they bind `self`.
|
||||||
|
is_function_like: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> CallableType<'db> {
|
impl<'db> CallableType<'db> {
|
||||||
/// Create a non-overloaded callable type with a single signature.
|
/// Create a non-overloaded callable type with a single signature.
|
||||||
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Self {
|
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Self {
|
||||||
CallableType::new(db, vec![signature].into_boxed_slice())
|
CallableType::new(db, vec![signature].into_boxed_slice(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a non-overloaded, function-like callable type with a single signature.
|
||||||
|
///
|
||||||
|
/// A function-like callable will bind `self` when accessed as an attribute on an instance.
|
||||||
|
pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Self {
|
||||||
|
CallableType::new(db, vec![signature].into_boxed_slice(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an overloaded callable type with multiple signatures.
|
/// Create an overloaded callable type with multiple signatures.
|
||||||
|
@ -7639,7 +7688,7 @@ impl<'db> CallableType<'db> {
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if `overloads` is empty.
|
/// Panics if `overloads` is empty.
|
||||||
pub(crate) fn from_overloads<I>(db: &'db dyn Db, overloads: I) -> Self
|
pub(crate) fn from_overloads<I>(db: &'db dyn Db, overloads: I, is_function_like: bool) -> Self
|
||||||
where
|
where
|
||||||
I: IntoIterator<Item = Signature<'db>>,
|
I: IntoIterator<Item = Signature<'db>>,
|
||||||
{
|
{
|
||||||
|
@ -7648,7 +7697,7 @@ impl<'db> CallableType<'db> {
|
||||||
!overloads.is_empty(),
|
!overloads.is_empty(),
|
||||||
"CallableType must have at least one signature"
|
"CallableType must have at least one signature"
|
||||||
);
|
);
|
||||||
CallableType::new(db, overloads)
|
CallableType::new(db, overloads, is_function_like)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a callable type which accepts any parameters and returns an `Unknown` type.
|
/// Create a callable type which accepts any parameters and returns an `Unknown` type.
|
||||||
|
@ -7659,6 +7708,14 @@ impl<'db> CallableType<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_self(self, db: &'db dyn Db) -> Self {
|
||||||
|
CallableType::from_overloads(
|
||||||
|
db,
|
||||||
|
self.signatures(db).iter().map(Signature::bind_self),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a callable type which represents a fully-static "bottom" callable.
|
/// Create a callable type which represents a fully-static "bottom" callable.
|
||||||
///
|
///
|
||||||
/// Specifically, this represents a callable type with a single signature:
|
/// Specifically, this represents a callable type with a single signature:
|
||||||
|
@ -7677,6 +7734,7 @@ impl<'db> CallableType<'db> {
|
||||||
self.signatures(db)
|
self.signatures(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|signature| signature.normalized(db)),
|
.map(|signature| signature.normalized(db)),
|
||||||
|
self.is_function_like(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7686,6 +7744,7 @@ impl<'db> CallableType<'db> {
|
||||||
self.signatures(db)
|
self.signatures(db)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|signature| signature.apply_type_mapping(db, type_mapping)),
|
.map(|signature| signature.apply_type_mapping(db, type_mapping)),
|
||||||
|
self.is_function_like(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7734,6 +7793,13 @@ impl<'db> CallableType<'db> {
|
||||||
where
|
where
|
||||||
F: Fn(&Signature<'db>, &Signature<'db>) -> bool,
|
F: Fn(&Signature<'db>, &Signature<'db>) -> bool,
|
||||||
{
|
{
|
||||||
|
let self_is_function_like = self.is_function_like(db);
|
||||||
|
let other_is_function_like = other.is_function_like(db);
|
||||||
|
|
||||||
|
if !self_is_function_like && other_is_function_like {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
match (self.signatures(db), other.signatures(db)) {
|
match (self.signatures(db), other.signatures(db)) {
|
||||||
([self_signature], [other_signature]) => {
|
([self_signature], [other_signature]) => {
|
||||||
// Base case: both callable types contain a single signature.
|
// Base case: both callable types contain a single signature.
|
||||||
|
@ -7776,6 +7842,10 @@ impl<'db> CallableType<'db> {
|
||||||
///
|
///
|
||||||
/// See [`Type::is_equivalent_to`] for more details.
|
/// See [`Type::is_equivalent_to`] for more details.
|
||||||
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
if self.is_function_like(db) != other.is_function_like(db) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
match (self.signatures(db), other.signatures(db)) {
|
match (self.signatures(db), other.signatures(db)) {
|
||||||
([self_signature], [other_signature]) => {
|
([self_signature], [other_signature]) => {
|
||||||
// Common case: both callable types contain a single signature, use the custom
|
// Common case: both callable types contain a single signature, use the custom
|
||||||
|
@ -7802,6 +7872,10 @@ impl<'db> CallableType<'db> {
|
||||||
///
|
///
|
||||||
/// See [`Type::is_gradual_equivalent_to`] for more details.
|
/// See [`Type::is_gradual_equivalent_to`] for more details.
|
||||||
fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||||
|
if self.is_function_like(db) != other.is_function_like(db) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
match (self.signatures(db), other.signatures(db)) {
|
match (self.signatures(db), other.signatures(db)) {
|
||||||
([self_signature], [other_signature]) => {
|
([self_signature], [other_signature]) => {
|
||||||
self_signature.is_gradual_equivalent_to(db, other_signature)
|
self_signature.is_gradual_equivalent_to(db, other_signature)
|
||||||
|
@ -7821,6 +7895,7 @@ impl<'db> CallableType<'db> {
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.map(|signature| signature.replace_self_reference(db, class)),
|
.map(|signature| signature.replace_self_reference(db, class)),
|
||||||
|
self.is_function_like(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1267,7 +1267,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db)));
|
let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db)));
|
||||||
Some(Type::Callable(CallableType::single(db, signature)))
|
Some(Type::Callable(CallableType::function_like(db, signature)))
|
||||||
};
|
};
|
||||||
|
|
||||||
match (field_policy, name) {
|
match (field_policy, name) {
|
||||||
|
@ -1281,7 +1281,13 @@ impl<'db> ClassLiteral<'db> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
signature_from_fields(vec![])
|
let self_parameter = Parameter::positional_or_keyword(Name::new_static("self"))
|
||||||
|
// TODO: could be `Self`.
|
||||||
|
.with_annotated_type(Type::instance(
|
||||||
|
db,
|
||||||
|
self.apply_optional_specialization(db, specialization),
|
||||||
|
));
|
||||||
|
signature_from_fields(vec![self_parameter])
|
||||||
}
|
}
|
||||||
(CodeGeneratorKind::NamedTuple, "__new__") => {
|
(CodeGeneratorKind::NamedTuple, "__new__") => {
|
||||||
let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls"))
|
let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls"))
|
||||||
|
@ -1294,16 +1300,24 @@ impl<'db> ClassLiteral<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let signature = Signature::new(
|
let signature = Signature::new(
|
||||||
Parameters::new([Parameter::positional_or_keyword(Name::new_static("other"))
|
Parameters::new([
|
||||||
|
Parameter::positional_or_keyword(Name::new_static("self"))
|
||||||
// TODO: could be `Self`.
|
// TODO: could be `Self`.
|
||||||
.with_annotated_type(Type::instance(
|
.with_annotated_type(Type::instance(
|
||||||
db,
|
db,
|
||||||
self.apply_optional_specialization(db, specialization),
|
self.apply_optional_specialization(db, specialization),
|
||||||
))]),
|
)),
|
||||||
|
Parameter::positional_or_keyword(Name::new_static("other"))
|
||||||
|
// TODO: could be `Self`.
|
||||||
|
.with_annotated_type(Type::instance(
|
||||||
|
db,
|
||||||
|
self.apply_optional_specialization(db, specialization),
|
||||||
|
)),
|
||||||
|
]),
|
||||||
Some(KnownClass::Bool.to_instance(db)),
|
Some(KnownClass::Bool.to_instance(db)),
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(Type::Callable(CallableType::single(db, signature)))
|
Some(Type::Callable(CallableType::function_like(db, signature)))
|
||||||
}
|
}
|
||||||
(CodeGeneratorKind::NamedTuple, name) if name != "__init__" => {
|
(CodeGeneratorKind::NamedTuple, name) if name != "__init__" => {
|
||||||
KnownClass::NamedTupleFallback
|
KnownClass::NamedTupleFallback
|
||||||
|
|
|
@ -8737,6 +8737,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
||||||
Type::Callable(CallableType::from_overloads(
|
Type::Callable(CallableType::from_overloads(
|
||||||
db,
|
db,
|
||||||
std::iter::once(signature).chain(signature_iter),
|
std::iter::once(signature).chain(signature_iter),
|
||||||
|
false,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue