From a69f6240cc3ca1c873f1f165ed32cdfac37536ce Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala Date: Tue, 18 Mar 2025 22:48:10 +0530 Subject: [PATCH] [red-knot] Infer `lambda` return type as `Unknown` (#16695) ## Summary Part of #15382 This PR infers the return type `lambda` expression as `Unknown`. In the future, it would be more useful to infer the expression type considering the surrounding context (#16696). ## Test Plan Update existing test cases from `@todo` to the (verified) return type. --- .../resources/mdtest/expression/lambda.md | 24 +++++++++---------- .../src/types/infer.rs | 5 +++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/expression/lambda.md b/crates/red_knot_python_semantic/resources/mdtest/expression/lambda.md index eaf9d5f08d..f247101851 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/expression/lambda.md +++ b/crates/red_knot_python_semantic/resources/mdtest/expression/lambda.md @@ -5,10 +5,10 @@ `lambda` expressions can be defined without any parameters. ```py -reveal_type(lambda: 1) # revealed: () -> @Todo(lambda return type) +reveal_type(lambda: 1) # revealed: () -> Unknown # error: [unresolved-reference] -reveal_type(lambda: a) # revealed: () -> @Todo(lambda return type) +reveal_type(lambda: a) # revealed: () -> Unknown ``` ## With parameters @@ -17,45 +17,45 @@ Unlike parameters in function definition, the parameters in a `lambda` expressio annotated. ```py -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) +reveal_type(lambda a: a) # revealed: (a) -> Unknown +reveal_type(lambda a, b: a + b) # revealed: (a, b) -> Unknown ``` But, it can have default values: ```py -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) +reveal_type(lambda a=1: a) # revealed: (a=Literal[1]) -> Unknown +reveal_type(lambda a, b=2: a) # revealed: (a, b=Literal[2]) -> Unknown ``` And, positional-only parameters: ```py -reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> @Todo(lambda return type) +reveal_type(lambda a, b, /, c: c) # revealed: (a, b, /, c) -> Unknown ``` And, keyword-only parameters: ```py -reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> @Todo(lambda return type) +reveal_type(lambda a, *, b=2, c: b) # revealed: (a, *, b=Literal[2], c) -> Unknown ``` And, variadic parameter: ```py -reveal_type(lambda *args: args) # revealed: (*args) -> @Todo(lambda return type) +reveal_type(lambda *args: args) # revealed: (*args) -> Unknown ``` And, keyword-varidic parameter: ```py -reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> @Todo(lambda return type) +reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> Unknown ``` Mixing all of them together: ```py -# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> @Todo(lambda return type) +# revealed: (a, b, /, c=Literal[True], *args, *, d=Literal["default"], e=Literal[5], **kwargs) -> Unknown reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None) ``` @@ -96,5 +96,5 @@ Here, a `lambda` expression is used as the default value for a parameter in anot expression. ```py -reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> @Todo(lambda return type)) -> @Todo(lambda return type) +reveal_type(lambda a=lambda x, y: 0: 2) # revealed: (a=(x, y) -> Unknown) -> Unknown ``` diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index ac060ead79..df1fc5c473 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -3840,9 +3840,12 @@ impl<'db> TypeInferenceBuilder<'db> { Parameters::empty() }; + // TODO: Useful inference of a lambda's return type will require a different approach, + // which does the inference of the body expression based on arguments at each call site, + // rather than eagerly computing a return type without knowing the argument types. Type::Callable(CallableType::General(GeneralCallableType::new( self.db(), - Signature::new(parameters, Some(todo_type!("lambda return type"))), + Signature::new(parameters, Some(Type::unknown())), ))) }