Skip to content

Commit 3d132d6

Browse files
committed
[red-knot] Infer lambda expression
1 parent 871bddb commit 3d132d6

File tree

3 files changed

+146
-11
lines changed

3 files changed

+146
-11
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# `lambda` expression
2+
3+
## No parameters
4+
5+
`lambda` expressions can be defined without any parameters.
6+
7+
```py
8+
reveal_type(lambda: 1) # revealed: () -> Literal[1]
9+
10+
# error: [unresolved-reference]
11+
reveal_type(lambda: a) # revealed: () -> Unknown
12+
```
13+
14+
## With parameters
15+
16+
Unlike parameters in function definition, the parameters in a `lambda` expression cannot be
17+
annotated.
18+
19+
```py
20+
reveal_type(lambda a: a) # revealed: (a) -> Unknown
21+
reveal_type(lambda a, b: a + b) # revealed: (a, b) -> Unknown
22+
```
23+
24+
But, it can have default values:
25+
26+
```py
27+
reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> Unknown | Literal[1]
28+
reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> Unknown
29+
```
30+
31+
And, positional-only parameters:
32+
33+
```py
34+
reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> Unknown
35+
```
36+
37+
And, keyword-only parameters:
38+
39+
```py
40+
reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> Unknown | Literal[2]
41+
```
42+
43+
And, variadic parameter:
44+
45+
```py
46+
# TODO: should be `tuple[Unknown, ...]` (needs generics)
47+
reveal_type(lambda *args: args) # revealed: (*args) -> tuple
48+
```
49+
50+
And, keyword-varidic parameter:
51+
52+
```py
53+
# TODO: should be `dict[str, Unknown]` (needs generics)
54+
reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> dict
55+
```
56+
57+
Mixing all of them together:
58+
59+
```py
60+
# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> None
61+
reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None)
62+
```

crates/red_knot_python_semantic/src/types/infer.rs

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3354,21 +3354,86 @@ impl<'db> TypeInferenceBuilder<'db> {
33543354
let ast::ExprLambda {
33553355
range: _,
33563356
parameters,
3357-
body: _,
3357+
body,
33583358
} = lambda_expression;
33593359

3360-
if let Some(parameters) = parameters {
3361-
for default in parameters
3362-
.iter_non_variadic_params()
3363-
.filter_map(|param| param.default.as_deref())
3364-
{
3365-
self.infer_expression(default);
3366-
}
3360+
let parameters = if let Some(parameters) = parameters {
3361+
let positional_only = parameters
3362+
.posonlyargs
3363+
.iter()
3364+
.map(|parameter| {
3365+
Parameter::new(
3366+
Some(parameter.name().id.clone()),
3367+
None,
3368+
ParameterKind::PositionalOnly {
3369+
default_ty: parameter
3370+
.default()
3371+
.map(|default| self.infer_expression(default)),
3372+
},
3373+
)
3374+
})
3375+
.collect::<Vec<_>>();
3376+
let positional_or_keyword = parameters
3377+
.args
3378+
.iter()
3379+
.map(|parameter| {
3380+
Parameter::new(
3381+
Some(parameter.name().id.clone()),
3382+
None,
3383+
ParameterKind::PositionalOrKeyword {
3384+
default_ty: parameter
3385+
.default()
3386+
.map(|default| self.infer_expression(default)),
3387+
},
3388+
)
3389+
})
3390+
.collect::<Vec<_>>();
3391+
let variadic = parameters.vararg.as_ref().map(|parameter| {
3392+
Parameter::new(
3393+
Some(parameter.name.id.clone()),
3394+
None,
3395+
ParameterKind::Variadic,
3396+
)
3397+
});
3398+
let keyword_only = parameters
3399+
.kwonlyargs
3400+
.iter()
3401+
.map(|parameter| {
3402+
Parameter::new(
3403+
Some(parameter.name().id.clone()),
3404+
None,
3405+
ParameterKind::KeywordOnly {
3406+
default_ty: parameter
3407+
.default()
3408+
.map(|default| self.infer_expression(default)),
3409+
},
3410+
)
3411+
})
3412+
.collect::<Vec<_>>();
3413+
let keyword_variadic = parameters.kwarg.as_ref().map(|parameter| {
3414+
Parameter::new(
3415+
Some(parameter.name.id.clone()),
3416+
None,
3417+
ParameterKind::KeywordVariadic,
3418+
)
3419+
});
33673420

3368-
self.infer_parameters(parameters);
3369-
}
3421+
Parameters::new(
3422+
positional_only
3423+
.into_iter()
3424+
.chain(positional_or_keyword)
3425+
.chain(variadic)
3426+
.chain(keyword_only)
3427+
.chain(keyword_variadic),
3428+
)
3429+
} else {
3430+
Parameters::empty()
3431+
};
33703432

3371-
todo_type!("typing.Callable type")
3433+
Type::Callable(CallableType::General(GeneralCallableType::new(
3434+
self.db(),
3435+
Signature::new(parameters, Some(self.file_expression_type(&**body))),
3436+
)))
33723437
}
33733438

33743439
fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> {

crates/red_knot_python_semantic/src/types/signatures.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ impl<'db> Parameters<'db> {
9494
}
9595
}
9696

97+
/// Create an empty parameter list.
98+
pub(crate) fn empty() -> Self {
99+
Self {
100+
value: Vec::new(),
101+
is_gradual: false,
102+
}
103+
}
104+
97105
pub(crate) fn as_slice(&self) -> &[Parameter<'db>] {
98106
self.value.as_slice()
99107
}

0 commit comments

Comments
 (0)