mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00

## Summary Part of https://github.com/astral-sh/ruff/issues/15382 This PR adds support for inferring the `lambda` expression and return the `CallableType`. Currently, this is only limited to inferring the parameters and a todo type for the return type. For posterity, I tried using the `file_expression_type` to infer the return type of lambda but it would always lead to cycle. The main reason is that in `infer_parameter_definition`, the default expression is being inferred using `file_expression_type`, which is correct, but it then Take the following source code as an example: ```py lambda x=1: x ``` Here's how the code will flow: * `infer_scope_types` for the global scope * `infer_lambda_expression` * `infer_expression` for the default value `1` * `file_expression_type` for the return type using the body expression. This is because the body creates it's own scope * `infer_scope_types` (lambda body scope) * `infer_name_load` for the symbol `x` whose visible binding is the lambda parameter `x` * `infer_parameter_definition` for parameter `x` * `file_expression_type` for the default value `1` * `infer_scope_types` for the global scope because of the default expression This will then reach to `infer_definition` for the parameter `x` again which then creates the cycle. ## Test Plan Add tests around `lambda` expression inference.
2.4 KiB
2.4 KiB
lambda
expression
No parameters
lambda
expressions can be defined without any parameters.
reveal_type(lambda: 1) # revealed: () -> @Todo(lambda return type)
# error: [unresolved-reference]
reveal_type(lambda: a) # revealed: () -> @Todo(lambda return type)
With parameters
Unlike parameters in function definition, the parameters in a lambda
expression cannot be
annotated.
reveal_type(lambda a: a) # revealed: (a) -> @Todo(lambda return type)
reveal_type(lambda a, b: a + b) # revealed: (a, b) -> @Todo(lambda return type)
But, it can have default values:
reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> @Todo(lambda return type)
reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> @Todo(lambda return type)
And, positional-only parameters:
reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> @Todo(lambda return type)
And, keyword-only parameters:
reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> @Todo(lambda return type)
And, variadic parameter:
reveal_type(lambda *args: args) # revealed: (*args) -> @Todo(lambda return type)
And, keyword-varidic parameter:
reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> @Todo(lambda return type)
Mixing all of them together:
# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> @Todo(lambda return type)
reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None)
Parameter type
In addition to correctly inferring the lambda
expression, the parameters should also be inferred
correctly.
Using a parameter with no default value:
lambda x: reveal_type(x) # revealed: Unknown
Using a parameter with default value:
lambda x=1: reveal_type(x) # revealed: Unknown | Literal[1]
Using a variadic paramter:
# TODO: should be `tuple[Unknown, ...]` (needs generics)
lambda *args: reveal_type(args) # revealed: tuple
Using a keyword-varidic parameter:
# TODO: should be `dict[str, Unknown]` (needs generics)
lambda **kwargs: reveal_type(kwargs) # revealed: dict
Nested lambda
expressions
Here, a lambda
expression is used as the default value for a parameter in another lambda
expression.
reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> @Todo(lambda return type)) -> @Todo(lambda return type)