Skip to content

Commit e4da8a3

Browse files
committed
[red-knot] Infer lambda expression
1 parent c970b79 commit e4da8a3

File tree

4 files changed

+170
-14
lines changed

4 files changed

+170
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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: () -> @Todo(lambda return type)
9+
10+
# error: [unresolved-reference]
11+
reveal_type(lambda: a) # revealed: () -> @Todo(lambda return type)
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) -> @Todo(lambda return type)
21+
reveal_type(lambda a, b: a + b) # revealed: (a, b) -> @Todo(lambda return type)
22+
```
23+
24+
But, it can have default values:
25+
26+
```py
27+
reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> @Todo(lambda return type)
28+
reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> @Todo(lambda return type)
29+
```
30+
31+
And, positional-only parameters:
32+
33+
```py
34+
reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> @Todo(lambda return type)
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) -> @Todo(lambda return type)
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) -> @Todo(lambda return type)
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) -> @Todo(lambda return type)
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) -> @Todo(lambda return type)
61+
reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None)
62+
```
63+
64+
## Nested `lambda` expressions
65+
66+
Here, a `lambda` expression is used as the default value for a parameter in another `lambda`
67+
expression.
68+
69+
```py
70+
reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> @Todo(lambda return type)) -> @Todo(lambda return type)
71+
```

crates/red_knot_python_semantic/src/semantic_model.rs

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ impl HasType for ast::ExprRef<'_> {
5555
let scope = file_scope.to_scope_id(model.db, model.file);
5656

5757
let expression_id = self.scoped_expression_id(model.db, scope);
58+
// println!("EXPR: {:?}", self);
5859
infer_scope_types(model.db, scope).expression_type(expression_id)
5960
}
6061
}

crates/red_knot_python_semantic/src/types/infer.rs

+90-14
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,14 @@ fn infer_definition_types_cycle_recovery<'db>(
119119
_cycle: &salsa::Cycle,
120120
input: Definition<'db>,
121121
) -> TypeInference<'db> {
122-
tracing::trace!("infer_definition_types_cycle_recovery");
122+
let file = input.file(db);
123+
let _span = tracing::trace_span!(
124+
"infer_definition_types_cycle_recovery",
125+
range = ?input.kind(db).target_range(),
126+
file = %file.path(db)
127+
)
128+
.entered();
129+
123130
TypeInference::cycle_fallback(input.scope(db), todo_type!("cycle recovery"))
124131
}
125132

@@ -317,7 +324,7 @@ impl<'db> TypeInference<'db> {
317324
#[track_caller]
318325
pub(crate) fn expression_type(&self, expression: ScopedExpressionId) -> Type<'db> {
319326
self.try_expression_type(expression).expect(
320-
"expression should belong to this TypeInference region and
327+
"expression should belong to this TypeInference region and \
321328
TypeInferenceBuilder should have inferred a type for it",
322329
)
323330
}
@@ -1361,7 +1368,9 @@ impl<'db> TypeInferenceBuilder<'db> {
13611368
///
13621369
/// The annotated type is implicitly wrapped in a homogeneous tuple.
13631370
///
1364-
/// See `infer_parameter_definition` doc comment for some relevant observations about scopes.
1371+
/// See [`infer_parameter_definition`] doc comment for some relevant observations about scopes.
1372+
///
1373+
/// [`infer_parameter_definition`]: Self::infer_parameter_definition
13651374
fn infer_variadic_positional_parameter_definition(
13661375
&mut self,
13671376
parameter: &ast::Parameter,
@@ -1390,7 +1399,9 @@ impl<'db> TypeInferenceBuilder<'db> {
13901399
///
13911400
/// The annotated type is implicitly wrapped in a string-keyed dictionary.
13921401
///
1393-
/// See `infer_parameter_definition` doc comment for some relevant observations about scopes.
1402+
/// See [`infer_parameter_definition`] doc comment for some relevant observations about scopes.
1403+
///
1404+
/// [`infer_parameter_definition`]: Self::infer_parameter_definition
13941405
fn infer_variadic_keyword_parameter_definition(
13951406
&mut self,
13961407
parameter: &ast::Parameter,
@@ -3283,18 +3294,83 @@ impl<'db> TypeInferenceBuilder<'db> {
32833294
body: _,
32843295
} = lambda_expression;
32853296

3286-
if let Some(parameters) = parameters {
3287-
for default in parameters
3288-
.iter_non_variadic_params()
3289-
.filter_map(|param| param.default.as_deref())
3290-
{
3291-
self.infer_expression(default);
3292-
}
3297+
let parameters = if let Some(parameters) = parameters {
3298+
let positional_only = parameters
3299+
.posonlyargs
3300+
.iter()
3301+
.map(|parameter| {
3302+
Parameter::new(
3303+
Some(parameter.name().id.clone()),
3304+
None,
3305+
ParameterKind::PositionalOnly {
3306+
default_ty: parameter
3307+
.default()
3308+
.map(|default| self.infer_expression(default)),
3309+
},
3310+
)
3311+
})
3312+
.collect::<Vec<_>>();
3313+
let positional_or_keyword = parameters
3314+
.args
3315+
.iter()
3316+
.map(|parameter| {
3317+
Parameter::new(
3318+
Some(parameter.name().id.clone()),
3319+
None,
3320+
ParameterKind::PositionalOrKeyword {
3321+
default_ty: parameter
3322+
.default()
3323+
.map(|default| self.infer_expression(default)),
3324+
},
3325+
)
3326+
})
3327+
.collect::<Vec<_>>();
3328+
let variadic = parameters.vararg.as_ref().map(|parameter| {
3329+
Parameter::new(
3330+
Some(parameter.name.id.clone()),
3331+
None,
3332+
ParameterKind::Variadic,
3333+
)
3334+
});
3335+
let keyword_only = parameters
3336+
.kwonlyargs
3337+
.iter()
3338+
.map(|parameter| {
3339+
Parameter::new(
3340+
Some(parameter.name().id.clone()),
3341+
None,
3342+
ParameterKind::KeywordOnly {
3343+
default_ty: parameter
3344+
.default()
3345+
.map(|default| self.infer_expression(default)),
3346+
},
3347+
)
3348+
})
3349+
.collect::<Vec<_>>();
3350+
let keyword_variadic = parameters.kwarg.as_ref().map(|parameter| {
3351+
Parameter::new(
3352+
Some(parameter.name.id.clone()),
3353+
None,
3354+
ParameterKind::KeywordVariadic,
3355+
)
3356+
});
32933357

3294-
self.infer_parameters(parameters);
3295-
}
3358+
Parameters::new(
3359+
positional_only
3360+
.into_iter()
3361+
.chain(positional_or_keyword)
3362+
.chain(variadic)
3363+
.chain(keyword_only)
3364+
.chain(keyword_variadic),
3365+
)
3366+
} else {
3367+
Parameters::empty()
3368+
};
32963369

3297-
todo_type!("typing.Callable type")
3370+
Type::Callable(CallableType::General(GeneralCallableType::new(
3371+
self.db(),
3372+
Signature::new(parameters, Some(todo_type!("lambda return type"))),
3373+
)))
32983374
}
32993375

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

crates/red_knot_python_semantic/src/types/signatures.rs

+8
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)