Skip to content

Commit 0016661

Browse files
committed
[ty] Synthetic function-like callables
1 parent cb9e669 commit 0016661

File tree

4 files changed

+80
-32
lines changed

4 files changed

+80
-32
lines changed

crates/ty_python_semantic/resources/mdtest/dataclasses.md

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@ Person(20, "Eve")
5656

5757
## Signature of `__init__`
5858

59-
TODO: All of the following tests are missing the `self` argument in the `__init__` signature.
60-
6159
Declarations in the class body are used to generate the signature of the `__init__` method. If the
6260
attributes are not just declarations, but also bindings, the type inferred from bindings is used as
6361
the default value.
@@ -71,7 +69,7 @@ class D:
7169
y: str = "default"
7270
z: int | None = 1 + 2
7371

74-
reveal_type(D.__init__) # revealed: (x: int, y: str = Literal["default"], z: int | None = Literal[3]) -> None
72+
reveal_type(D.__init__) # revealed: (self: D, x: int, y: str = Literal["default"], z: int | None = Literal[3]) -> None
7573
```
7674

7775
This also works if the declaration and binding are split:
@@ -82,7 +80,7 @@ class D:
8280
x: int | None
8381
x = None
8482

85-
reveal_type(D.__init__) # revealed: (x: int | None = None) -> None
83+
reveal_type(D.__init__) # revealed: (self: D, x: int | None = None) -> None
8684
```
8785

8886
Non-fully static types are handled correctly:
@@ -96,7 +94,7 @@ class C:
9694
y: int | Any
9795
z: tuple[int, Any]
9896

99-
reveal_type(C.__init__) # revealed: (x: Any, y: int | Any, z: tuple[int, Any]) -> None
97+
reveal_type(C.__init__) # revealed: (self: C, x: Any, y: int | Any, z: tuple[int, Any]) -> None
10098
```
10199

102100
Variables without annotations are ignored:
@@ -107,7 +105,7 @@ class D:
107105
x: int
108106
y = 1
109107

110-
reveal_type(D.__init__) # revealed: (x: int) -> None
108+
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
111109
```
112110

113111
If attributes without default values are declared after attributes with default values, a
@@ -132,7 +130,7 @@ class D:
132130
y: ClassVar[str] = "default"
133131
z: bool
134132

135-
reveal_type(D.__init__) # revealed: (x: int, z: bool) -> None
133+
reveal_type(D.__init__) # revealed: (self: D, x: int, z: bool) -> None
136134

137135
d = D(1, True)
138136
reveal_type(d.x) # revealed: int
@@ -150,7 +148,7 @@ class D:
150148
def y(self) -> str:
151149
return ""
152150

153-
reveal_type(D.__init__) # revealed: (x: int) -> None
151+
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
154152
```
155153

156154
And neither do nested class declarations:
@@ -163,7 +161,7 @@ class D:
163161
class Nested:
164162
y: str
165163

166-
reveal_type(D.__init__) # revealed: (x: int) -> None
164+
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
167165
```
168166

169167
But if there is a variable annotation with a function or class literal type, the signature of
@@ -181,7 +179,7 @@ class D:
181179
class_literal: TypeOf[SomeClass]
182180
class_subtype_of: type[SomeClass]
183181

184-
# revealed: (function_literal: def some_function() -> None, class_literal: <class 'SomeClass'>, class_subtype_of: type[SomeClass]) -> None
182+
# revealed: (self: D, function_literal: def some_function() -> None, class_literal: <class 'SomeClass'>, class_subtype_of: type[SomeClass]) -> None
185183
reveal_type(D.__init__)
186184
```
187185

@@ -194,7 +192,7 @@ from typing import Callable
194192
class D:
195193
c: Callable[[int], str]
196194

197-
reveal_type(D.__init__) # revealed: (c: (int, /) -> str) -> None
195+
reveal_type(D.__init__) # revealed: (self: D, c: (int, /) -> str) -> None
198196
```
199197

200198
Implicit instance attributes do not affect the signature of `__init__`:
@@ -209,7 +207,7 @@ class D:
209207

210208
reveal_type(D(1).y) # revealed: str
211209

212-
reveal_type(D.__init__) # revealed: (x: int) -> None
210+
reveal_type(D.__init__) # revealed: (self: D, x: int) -> None
213211
```
214212

215213
Annotating expressions does not lead to an entry in `__annotations__` at runtime, and so it wouldn't
@@ -222,7 +220,7 @@ class D:
222220
(x): int = 1
223221

224222
# TODO: should ideally not include a `x` parameter
225-
reveal_type(D.__init__) # revealed: (x: int = Literal[1]) -> None
223+
reveal_type(D.__init__) # revealed: (self: D, x: int = Literal[1]) -> None
226224
```
227225

228226
## `@dataclass` calls with arguments
@@ -471,7 +469,7 @@ class C(Base):
471469
z: int = 10
472470
x: int = 15
473471

474-
reveal_type(C.__init__) # revealed: (x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
472+
reveal_type(C.__init__) # revealed: (self: C, x: int = Literal[15], y: int = Literal[0], z: int = Literal[10]) -> None
475473
```
476474

477475
## Generic dataclasses
@@ -524,7 +522,7 @@ class UppercaseString:
524522
class C:
525523
upper: UppercaseString = UppercaseString()
526524

527-
reveal_type(C.__init__) # revealed: (upper: str = str) -> None
525+
reveal_type(C.__init__) # revealed: (self: C, upper: str = str) -> None
528526

529527
c = C("abc")
530528
reveal_type(c.upper) # revealed: str
@@ -570,7 +568,7 @@ class ConvertToLength:
570568
class C:
571569
converter: ConvertToLength = ConvertToLength()
572570

573-
reveal_type(C.__init__) # revealed: (converter: str = Literal[""]) -> None
571+
reveal_type(C.__init__) # revealed: (self: C, converter: str = Literal[""]) -> None
574572

575573
c = C("abc")
576574
reveal_type(c.converter) # revealed: int
@@ -609,7 +607,7 @@ class AcceptsStrAndInt:
609607
class C:
610608
field: AcceptsStrAndInt = AcceptsStrAndInt()
611609

612-
reveal_type(C.__init__) # revealed: (field: str | int = int) -> None
610+
reveal_type(C.__init__) # revealed: (self: C, field: str | int = int) -> None
613611
```
614612

615613
## `dataclasses.field`
@@ -670,7 +668,7 @@ import dataclasses
670668
class C:
671669
x: str
672670

673-
reveal_type(C.__init__) # revealed: (x: str) -> None
671+
reveal_type(C.__init__) # revealed: (self: C, x: str) -> None
674672
```
675673

676674
### Dataclass with custom `__init__` method
@@ -763,8 +761,7 @@ reveal_type(Person.__mro__) # revealed: tuple[<class 'Person'>, <class 'object'
763761
The generated methods have the following signatures:
764762

765763
```py
766-
# TODO: `self` is missing here
767-
reveal_type(Person.__init__) # revealed: (name: str, age: int | None = None) -> None
764+
reveal_type(Person.__init__) # revealed: (self: Person, name: str, age: int | None = None) -> None
768765

769766
reveal_type(Person.__repr__) # revealed: def __repr__(self) -> str
770767

crates/ty_python_semantic/src/types.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,6 +2746,21 @@ impl<'db> Type<'db> {
27462746
instance.display(db),
27472747
owner.display(db)
27482748
);
2749+
match self {
2750+
Type::Callable(callable) if callable.is_function_like(db) => {
2751+
// For "function-like" callables, model the the behavior of `FunctionType.__get__`:
2752+
if instance.is_none(db) {
2753+
return Some((self, AttributeKind::NormalOrNonDataDescriptor));
2754+
} else {
2755+
return Some((
2756+
Type::Callable(callable.bind_self(db)),
2757+
AttributeKind::NormalOrNonDataDescriptor,
2758+
));
2759+
}
2760+
}
2761+
_ => {}
2762+
};
2763+
27492764
let descr_get = self.class_member(db, "__get__".into()).symbol;
27502765

27512766
if let Symbol::Type(descr_get, descr_get_boundness) = descr_get {
@@ -6860,6 +6875,7 @@ impl<'db> FunctionType<'db> {
68606875
Type::Callable(CallableType::from_overloads(
68616876
db,
68626877
self.signature(db).overloads.iter().cloned(),
6878+
false, // TODO?
68636879
))
68646880
}
68656881

@@ -7486,6 +7502,7 @@ impl<'db> BoundMethodType<'db> {
74867502
.overloads
74877503
.iter()
74887504
.map(signatures::Signature::bind_self),
7505+
false,
74897506
))
74907507
}
74917508

@@ -7550,20 +7567,26 @@ impl<'db> BoundMethodType<'db> {
75507567
pub struct CallableType<'db> {
75517568
#[returns(deref)]
75527569
signatures: Box<[Signature<'db>]>,
7570+
is_function_like: bool,
75537571
}
75547572

75557573
impl<'db> CallableType<'db> {
75567574
/// Create a non-overloaded callable type with a single signature.
75577575
pub(crate) fn single(db: &'db dyn Db, signature: Signature<'db>) -> Self {
7558-
CallableType::new(db, vec![signature].into_boxed_slice())
7576+
CallableType::new(db, vec![signature].into_boxed_slice(), false)
7577+
}
7578+
7579+
/// Create a non-overloaded callable type with a single signature.
7580+
pub(crate) fn function_like(db: &'db dyn Db, signature: Signature<'db>) -> Self {
7581+
CallableType::new(db, vec![signature].into_boxed_slice(), true)
75597582
}
75607583

75617584
/// Create an overloaded callable type with multiple signatures.
75627585
///
75637586
/// # Panics
75647587
///
75657588
/// Panics if `overloads` is empty.
7566-
pub(crate) fn from_overloads<I>(db: &'db dyn Db, overloads: I) -> Self
7589+
pub(crate) fn from_overloads<I>(db: &'db dyn Db, overloads: I, is_function_like: bool) -> Self
75677590
where
75687591
I: IntoIterator<Item = Signature<'db>>,
75697592
{
@@ -7572,7 +7595,7 @@ impl<'db> CallableType<'db> {
75727595
!overloads.is_empty(),
75737596
"CallableType must have at least one signature"
75747597
);
7575-
CallableType::new(db, overloads)
7598+
CallableType::new(db, overloads, is_function_like)
75767599
}
75777600

75787601
/// Create a callable type which accepts any parameters and returns an `Unknown` type.
@@ -7583,6 +7606,16 @@ impl<'db> CallableType<'db> {
75837606
)
75847607
}
75857608

7609+
pub(crate) fn bind_self(self, db: &'db dyn Db) -> Self {
7610+
CallableType::from_overloads(
7611+
db,
7612+
self.signatures(db)
7613+
.iter()
7614+
.map(|signature| signature.bind_self()),
7615+
false,
7616+
)
7617+
}
7618+
75867619
/// Create a callable type which represents a fully-static "bottom" callable.
75877620
///
75887621
/// Specifically, this represents a callable type with a single signature:
@@ -7601,6 +7634,7 @@ impl<'db> CallableType<'db> {
76017634
self.signatures(db)
76027635
.iter()
76037636
.map(|signature| signature.normalized(db)),
7637+
self.is_function_like(db),
76047638
)
76057639
}
76067640

@@ -7610,6 +7644,7 @@ impl<'db> CallableType<'db> {
76107644
self.signatures(db)
76117645
.iter()
76127646
.map(|signature| signature.apply_type_mapping(db, type_mapping)),
7647+
self.is_function_like(db),
76137648
)
76147649
}
76157650

@@ -7745,6 +7780,7 @@ impl<'db> CallableType<'db> {
77457780
.iter()
77467781
.cloned()
77477782
.map(|signature| signature.replace_self_reference(db, class)),
7783+
self.is_function_like(db),
77487784
)
77497785
}
77507786
}

crates/ty_python_semantic/src/types/class.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,7 +1261,7 @@ impl<'db> ClassLiteral<'db> {
12611261
}
12621262

12631263
let signature = Signature::new(Parameters::new(parameters), Some(Type::none(db)));
1264-
Some(Type::Callable(CallableType::single(db, signature)))
1264+
Some(Type::Callable(CallableType::function_like(db, signature)))
12651265
};
12661266

12671267
match (field_policy, name) {
@@ -1275,7 +1275,13 @@ impl<'db> ClassLiteral<'db> {
12751275
return None;
12761276
}
12771277

1278-
signature_from_fields(vec![])
1278+
let self_parameter = Parameter::positional_or_keyword(Name::new_static("self"))
1279+
// TODO: could be `Self`.
1280+
.with_annotated_type(Type::instance(
1281+
db,
1282+
self.apply_optional_specialization(db, specialization),
1283+
));
1284+
signature_from_fields(vec![self_parameter])
12791285
}
12801286
(CodeGeneratorKind::NamedTuple, "__new__") => {
12811287
let cls_parameter = Parameter::positional_or_keyword(Name::new_static("cls"))
@@ -1288,16 +1294,24 @@ impl<'db> ClassLiteral<'db> {
12881294
}
12891295

12901296
let signature = Signature::new(
1291-
Parameters::new([Parameter::positional_or_keyword(Name::new_static("other"))
1292-
// TODO: could be `Self`.
1293-
.with_annotated_type(Type::instance(
1294-
db,
1295-
self.apply_optional_specialization(db, specialization),
1296-
))]),
1297+
Parameters::new([
1298+
Parameter::positional_or_keyword(Name::new_static("self"))
1299+
// TODO: could be `Self`.
1300+
.with_annotated_type(Type::instance(
1301+
db,
1302+
self.apply_optional_specialization(db, specialization),
1303+
)),
1304+
Parameter::positional_or_keyword(Name::new_static("other"))
1305+
// TODO: could be `Self`.
1306+
.with_annotated_type(Type::instance(
1307+
db,
1308+
self.apply_optional_specialization(db, specialization),
1309+
)),
1310+
]),
12971311
Some(KnownClass::Bool.to_instance(db)),
12981312
);
12991313

1300-
Some(Type::Callable(CallableType::single(db, signature)))
1314+
Some(Type::Callable(CallableType::function_like(db, signature)))
13011315
}
13021316
(CodeGeneratorKind::NamedTuple, name) if name != "__init__" => {
13031317
KnownClass::NamedTupleFallback

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8609,6 +8609,7 @@ impl<'db> TypeInferenceBuilder<'db> {
86098609
Type::Callable(CallableType::from_overloads(
86108610
db,
86118611
std::iter::once(signature).chain(signature_iter),
8612+
false,
86128613
))
86138614
}
86148615
},

0 commit comments

Comments
 (0)