From 366cb1010b6c0d690685eb5d696254c1f8e3375c Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Mon, 29 Jun 2026 14:12:58 -0700 Subject: [PATCH] Recover an incomplete lambda as a complete expression. (#7435) A lambda introducer (`fn` in expression context) with no parameters and no body -- for example `(fn)`, where `)` immediately follows `fn` -- left `HandleLambdaAfterParams` calling `ReturnErrorOnState()` without ever emitting a `Lambda` node. The orphaned `LambdaIntroducer` leaf was then left where an expression was required, so `Parse` produced a tree that failed its own verification and aborted via `CARBON_FATAL`. Recover the way `HandleLambdaBody` already does for a missing body after a return type: emit a placeholder `InvalidParse` body and finish a complete `Lambda` node, so the lambda stays a valid expression and the surrounding construct (here a `ParenExpr`) extracts cleanly. Found by fuzzing. Assisted-by: Claude Code --- toolchain/parse/handle_lambda.cpp | 11 ++++++++++- toolchain/parse/testdata/lambda/lambda.carbon | 8 +++++--- toolchain/parse/tree_test.cpp | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/toolchain/parse/handle_lambda.cpp b/toolchain/parse/handle_lambda.cpp index d761e6762112..225b56577407 100644 --- a/toolchain/parse/handle_lambda.cpp +++ b/toolchain/parse/handle_lambda.cpp @@ -62,8 +62,17 @@ auto HandleLambdaAfterParams(Context& context) -> void { CARBON_DIAGNOSTIC(ExpectedLambdaBody, Error, "expected `->`, `=>`, or `{{`"); context.emitter().Emit(*context.position(), ExpectedLambdaBody); + + // Add a dummy node for the missing body without consuming the current + // token, then bundle everything into a complete lambda node. This keeps the + // lambda a valid expression for error recovery -- otherwise the orphaned + // `LambdaIntroducer` would be left where an expression is required, for + // example in `(fn)`. + context.AddLeafNode(NodeKind::InvalidParse, *context.position(), + /*has_error=*/true); + state.has_error = true; - context.ReturnErrorOnState(); + context.PushState(state, StateKind::LambdaBodyFinish); } } diff --git a/toolchain/parse/testdata/lambda/lambda.carbon b/toolchain/parse/testdata/lambda/lambda.carbon index 1032df9bae64..40ac309b3f46 100644 --- a/toolchain/parse/testdata/lambda/lambda.carbon +++ b/toolchain/parse/testdata/lambda/lambda.carbon @@ -155,9 +155,11 @@ fn G() { // CHECK:STDOUT: {kind: 'VarBindingPattern', text: ':', subtree_size: 3}, // CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', subtree_size: 4}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, -// CHECK:STDOUT: {kind: 'LambdaIntroducer', text: 'fn'}, -// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', has_error: yes, subtree_size: 8}, -// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 14}, +// CHECK:STDOUT: {kind: 'LambdaIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'Lambda', text: 'fn', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 16}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] // CHECK:STDOUT: - filename: fail_expected_body_after_return_type.carbon diff --git a/toolchain/parse/tree_test.cpp b/toolchain/parse/tree_test.cpp index b44aaa5ef665..b354ee69677f 100644 --- a/toolchain/parse/tree_test.cpp +++ b/toolchain/parse/tree_test.cpp @@ -178,5 +178,21 @@ TEST_F(TreeTest, HighRecursion) { EXPECT_FALSE(tree.has_errors()); } +TEST_F(TreeTest, IncompleteLambdaRecovers) { + // An incomplete lambda -- a `fn` introducer with no body, here `(fn)` -- must + // still parse to a structurally valid tree (a `Lambda` node with a + // placeholder body) rather than leaving an orphaned introducer where an + // expression is required, which would fail the parser's own tree + // verification. + Lex::TokenizedBuffer& tokens = + compile_helper_.GetTokenizedBuffer("var x: auto = (fn);"); + ASSERT_FALSE(tokens.has_errors()); + ::testing::NiceMock consumer; + Parse::ParseOptions options; + options.consumer = &consumer; + Tree tree = Parse(tokens, options); + EXPECT_TRUE(tree.has_errors()); +} + } // namespace } // namespace Carbon::Parse