From 744d6fd3cd72ca1edbc1f9ba43a4a86a317643aa Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Sat, 27 May 2023 20:21:24 +0100 Subject: [PATCH] Allow no whitespace between lambda keyword and params in certain cases Python accepts code where `lambda` follows a `*`, so this PR relaxes validation rules for Lambdas. Raised in #930. --- libcst/_nodes/expression.py | 20 +++++++++++ libcst/_nodes/tests/test_lambda.py | 56 +++++++++++++++++------------- 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/libcst/_nodes/expression.py b/libcst/_nodes/expression.py index be0589bb4..a011e5f41 100644 --- a/libcst/_nodes/expression.py +++ b/libcst/_nodes/expression.py @@ -1983,6 +1983,25 @@ def _visit_and_replace_children(self, visitor: CSTVisitorT) -> "Parameters": star_kwarg=visit_optional(self, "star_kwarg", self.star_kwarg, visitor), ) + def _safe_to_join_with_lambda(self) -> bool: + """ + Determine if Parameters need a space after the `lambda` keyword. Returns True + iff it's safe to omit the space between `lambda` and these Parameters. + + See also `BaseExpression._safe_to_use_with_word_operator`. + + For example: `lambda*_: pass` + """ + if len(self.posonly_params) != 0: + return False + + # posonly_ind can't appear if above condition is false + + if len(self.params) > 0 and self.params[0].star not in {"*", "**"}: + return False + + return True + def _codegen_impl(self, state: CodegenState) -> None: # noqa: C901 # Compute the star existence first so we can ask about whether # each element is the last in the list or not. @@ -2115,6 +2134,7 @@ def _validate(self) -> None: if ( isinstance(whitespace_after_lambda, BaseParenthesizableWhitespace) and whitespace_after_lambda.empty + and not self.params._safe_to_join_with_lambda() ): raise CSTValidationError( "Must have at least one space after lambda when specifying params" diff --git a/libcst/_nodes/tests/test_lambda.py b/libcst/_nodes/tests/test_lambda.py index f956ee03e..dbe5d51bf 100644 --- a/libcst/_nodes/tests/test_lambda.py +++ b/libcst/_nodes/tests/test_lambda.py @@ -303,30 +303,6 @@ def test_valid( ), "at least one space after lambda", ), - ( - lambda: cst.Lambda( - cst.Parameters(star_arg=cst.Param(cst.Name("arg"))), - cst.Integer("5"), - whitespace_after_lambda=cst.SimpleWhitespace(""), - ), - "at least one space after lambda", - ), - ( - lambda: cst.Lambda( - cst.Parameters(kwonly_params=(cst.Param(cst.Name("arg")),)), - cst.Integer("5"), - whitespace_after_lambda=cst.SimpleWhitespace(""), - ), - "at least one space after lambda", - ), - ( - lambda: cst.Lambda( - cst.Parameters(star_kwarg=cst.Param(cst.Name("arg"))), - cst.Integer("5"), - whitespace_after_lambda=cst.SimpleWhitespace(""), - ), - "at least one space after lambda", - ), ( lambda: cst.Lambda( cst.Parameters( @@ -944,6 +920,38 @@ class LambdaParserTest(CSTNodeTest): ), "( lambda : 5 )", ), + # No space between lambda and params + ( + cst.Lambda( + cst.Parameters(star_arg=cst.Param(cst.Name("args"), star="*")), + cst.Integer("5"), + whitespace_after_lambda=cst.SimpleWhitespace(""), + ), + "lambda*args: 5", + ), + ( + cst.Lambda( + cst.Parameters(star_kwarg=cst.Param(cst.Name("kwargs"), star="**")), + cst.Integer("5"), + whitespace_after_lambda=cst.SimpleWhitespace(""), + ), + "lambda**kwargs: 5", + ), + ( + cst.Lambda( + cst.Parameters( + star_arg=cst.ParamStar( + comma=cst.Comma( + cst.SimpleWhitespace(""), cst.SimpleWhitespace("") + ) + ), + kwonly_params=[cst.Param(cst.Name("args"), star="")], + ), + cst.Integer("5"), + whitespace_after_lambda=cst.SimpleWhitespace(""), + ), + "lambda*,args: 5", + ), ) ) def test_valid(