Explorar el Código

[parse] Implement initial parsing support for Lambda expressions (#6583)

This adds the necessary parser infrastructure to recognize and parse
lambda expressions in Carbon.

Key changes:
- Added  and  Parse Node Kinds.
- Updated  to use  to accommodate the growing number of node kinds.
- Implemented parser states and handlers for lambda syntax ( or ).
- Added  structure to .
- Added diagnostics for missing lambda bodies.
- Added a stub in  phase to defer semantic analysis using .
- Added parser tests for lambdas.
Burak Emir hace 3 meses
padre
commit
80639a02f0

+ 5 - 0
toolchain/check/handle_function.cpp

@@ -688,4 +688,9 @@ auto HandleParseNode(Context& context,
   return true;
 }
 
+auto HandleParseNode(Context& context, Parse::FunctionTerseDefinitionId node_id)
+    -> bool {
+  return context.TODO(node_id, "HandleFunctionTerseDefinition");
+}
+
 }  // namespace Carbon::Check

+ 24 - 0
toolchain/check/handle_lambda.cpp

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/check/context.h"
+#include "toolchain/check/handle.h"
+
+namespace Carbon::Check {
+
+auto HandleParseNode(Context& context, Parse::LambdaIntroducerId node_id)
+    -> bool {
+  return context.TODO(node_id, "HandleLambdaIntroducer");
+}
+
+auto HandleParseNode(Context& context, Parse::LambdaId node_id) -> bool {
+  return context.TODO(node_id, "HandleLambda");
+}
+
+auto HandleParseNode(Context& context, Parse::TerseBodyArrowId node_id)
+    -> bool {
+  return context.TODO(node_id, "HandleTerseBodyArrow");
+}
+
+}  // namespace Carbon::Check

+ 2 - 0
toolchain/check/node_stack.h

@@ -462,6 +462,7 @@ class NodeStack {
       case Parse::NodeKind::ImplicitParamListStart:
       case Parse::NodeKind::ImplIntroducer:
       case Parse::NodeKind::InterfaceIntroducer:
+      case Parse::NodeKind::LambdaIntroducer:
       case Parse::NodeKind::LetInitializer:
       case Parse::NodeKind::LetIntroducer:
       case Parse::NodeKind::NamedConstraintIntroducer:
@@ -534,6 +535,7 @@ class NodeStack {
       case Parse::NodeKind::StructLiteralComma:
       case Parse::NodeKind::StructFieldDesignator:
       case Parse::NodeKind::StructTypeLiteralComma:
+      case Parse::NodeKind::TerseBodyArrow:
       case Parse::NodeKind::TupleLiteralComma:
       case Parse::NodeKind::WhileCondition:
         return Id::Kind::Invalid;

+ 9 - 4
toolchain/check/testdata/function/declaration/fail_todo_no_params.carbon

@@ -53,13 +53,13 @@ fn A[] -> ();
 
 // TODO: We don't have parsing support for this yet.
 library "[[@TEST_NAME]]";
-// CHECK:STDERR: fail_todo_arrow_body.carbon:[[@LINE+8]]:6: error: `fn` declarations must either end with a `;` or have a `{ ... }` block for a definition [ExpectedDeclSemiOrDefinition]
+// CHECK:STDERR: fail_todo_arrow_body.carbon:[[@LINE+8]]:1: error: semantics TODO: `function with positional parameters` [SemanticsTodo]
 // CHECK:STDERR: fn A => 0;
-// CHECK:STDERR:      ^~
+// CHECK:STDERR: ^~~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_todo_arrow_body.carbon:[[@LINE+4]]:1: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+// CHECK:STDERR: fail_todo_arrow_body.carbon:[[@LINE+4]]:6: error: semantics TODO: `HandleTerseBodyArrow` [SemanticsTodo]
 // CHECK:STDERR: fn A => 0;
-// CHECK:STDERR: ^~~~~~~~~~
+// CHECK:STDERR:      ^~
 // CHECK:STDERR:
 fn A => 0;
 
@@ -176,6 +176,11 @@ fn A {
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_arrow_body.carbon
 // CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @A;
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_invalid_file_generic_regression_test.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 2 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -95,6 +95,8 @@ CARBON_DIAGNOSTIC_KIND(BinaryOperatorRequiresWhitespace)
 CARBON_DIAGNOSTIC_KIND(ExpectedArrayComma)
 CARBON_DIAGNOSTIC_KIND(ExpectedCloseSymbol)
 CARBON_DIAGNOSTIC_KIND(ExpectedCodeBlock)
+CARBON_DIAGNOSTIC_KIND(ExpectedLambdaBody)
+CARBON_DIAGNOSTIC_KIND(ExpectedLambdaBodyAfterReturnType)
 CARBON_DIAGNOSTIC_KIND(ExpectedExpr)
 CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterPeriodOrArrow)
 CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierOrSelfAfterPeriod)

+ 2 - 1
toolchain/lex/token_kind.def

@@ -155,7 +155,8 @@ CARBON_DECL_INTRODUCER_TOKEN(Choice,      "choice")
 CARBON_DECL_INTRODUCER_TOKEN(Class,       "class")
 CARBON_DECL_INTRODUCER_TOKEN(Constraint,  "constraint")
 CARBON_DECL_INTRODUCER_TOKEN(Export,      "export")
-CARBON_DECL_INTRODUCER_TOKEN(Fn,          "fn")
+CARBON_TOKEN_WITH_VIRTUAL_NODE(
+  CARBON_DECL_INTRODUCER_TOKEN(Fn,          "fn"))
 CARBON_DECL_INTRODUCER_TOKEN(Impl,        "impl")
 CARBON_DECL_INTRODUCER_TOKEN(Import,      "import")
 CARBON_DECL_INTRODUCER_TOKEN(Interface,   "interface")

+ 13 - 0
toolchain/parse/context.cpp

@@ -470,6 +470,19 @@ auto Context::AddFunctionDefinition(Lex::TokenIndex token, bool has_error)
   }
 }
 
+auto Context::AddFunctionTerseDefinition(Lex::TokenIndex token, bool has_error)
+    -> void {
+  auto definition_id =
+      AddNode<NodeKind::FunctionTerseDefinition>(token, has_error);
+  if (ParsingInDeferredDefinitionScope(*this)) {
+    auto definition_index = deferred_definition_stack_.pop_back_val();
+    auto& definition = tree_->deferred_definitions_.Get(definition_index);
+    definition.definition_id = definition_id;
+    definition.next_definition_index =
+        DeferredDefinitionIndex(tree_->deferred_definitions().size());
+  }
+}
+
 auto Context::PrintForStackDump(llvm::raw_ostream& output) const -> void {
   output << "Parser stack:\n";
   for (auto [i, entry] : llvm::enumerate(state_stack_)) {

+ 4 - 0
toolchain/parse/context.h

@@ -406,6 +406,10 @@ class Context {
   // Adds a function definition node, and ends tracking a deferred definition if
   // necessary.
   auto AddFunctionDefinition(Lex::TokenIndex token, bool has_error) -> void;
+  // Adds a function terse definition node, and ends tracking a deferred
+  // definition if necessary.
+  auto AddFunctionTerseDefinition(Lex::TokenIndex token, bool has_error)
+      -> void;
 
   // Prints information for a stack dump.
   auto PrintForStackDump(llvm::raw_ostream& output) const -> void;

+ 5 - 0
toolchain/parse/handle_expr.cpp

@@ -79,6 +79,11 @@ auto HandleExprInPostfix(Context& context) -> void {
       context.PushState(state);
       break;
     }
+    case Lex::TokenKind::Fn: {
+      context.PushState(state);
+      context.PushState(StateKind::LambdaIntroducer);
+      break;
+    }
     case Lex::TokenKind::False: {
       context.AddLeafNode(NodeKind::BoolLiteralFalse, context.Consume());
       context.PushState(state);

+ 19 - 0
toolchain/parse/handle_function.cpp

@@ -51,6 +51,13 @@ auto HandleFunctionSignatureFinish(Context& context) -> void {
       context.PushState(StateKind::StatementScopeLoop);
       break;
     }
+    case Lex::TokenKind::EqualGreater: {
+      context.AddFunctionDefinitionStart(context.Consume(), state.has_error);
+      context.AddLeafNode(NodeKind::TerseBodyArrow, *(context.position() - 1));
+      context.PushState(state, StateKind::FunctionTerseBodyFinish);
+      context.PushStateForExpr(PrecedenceGroup::ForTopLevelExpr());
+      break;
+    }
     case Lex::TokenKind::Equal: {
       context.AddNode(NodeKind::BuiltinFunctionDefinitionStart,
                       context.Consume(), state.has_error);
@@ -95,4 +102,16 @@ auto HandleFunctionDefinitionFinish(Context& context) -> void {
   context.AddFunctionDefinition(context.Consume(), state.has_error);
 }
 
+auto HandleFunctionTerseBodyFinish(Context& context) -> void {
+  auto state = context.PopState();
+
+  auto semi = context.ConsumeIf(Lex::TokenKind::Semi);
+  if (!semi && !state.has_error) {
+    context.DiagnoseExpectedDeclSemi(Lex::TokenKind::Fn);
+    state.has_error = true;
+  }
+  context.AddFunctionTerseDefinition(semi ? *semi : *(context.position() - 1),
+                                     state.has_error);
+}
+
 }  // namespace Carbon::Parse

+ 100 - 0
toolchain/parse/handle_lambda.cpp

@@ -0,0 +1,100 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/parse/context.h"
+#include "toolchain/parse/handle.h"
+
+namespace Carbon::Parse {
+
+auto HandleLambdaIntroducer(Context& context) -> void {
+  auto state = context.PopState();
+  context.AddLeafNode(NodeKind::LambdaIntroducer, context.Consume());
+  context.PushState(state, StateKind::LambdaAfterIntroducer);
+}
+
+auto HandleLambdaAfterIntroducer(Context& context) -> void {
+  auto state = context.PopState();
+
+  if (context.PositionIs(Lex::TokenKind::OpenSquareBracket)) {
+    context.PushState(state, StateKind::LambdaAfterImplicitParams);
+    context.PushState(StateKind::PatternListAsImplicit);
+  } else if (context.PositionIs(Lex::TokenKind::OpenParen)) {
+    context.PushState(state, StateKind::LambdaAfterParams);
+    context.PushState(StateKind::PatternListAsExplicit);
+  } else {
+    // No implicit or explicit params.
+    context.PushState(state, StateKind::LambdaAfterParams);
+  }
+}
+
+auto HandleLambdaAfterImplicitParams(Context& context) -> void {
+  auto state = context.PopState();
+
+  if (context.PositionIs(Lex::TokenKind::OpenParen)) {
+    context.PushState(state, StateKind::LambdaAfterParams);
+    context.PushState(StateKind::PatternListAsExplicit);
+  } else {
+    // No explicit params after implicit params.
+    context.PushState(state, StateKind::LambdaAfterParams);
+  }
+}
+
+auto HandleLambdaAfterParams(Context& context) -> void {
+  auto state = context.PopState();
+
+  if (context.PositionIs(Lex::TokenKind::MinusGreater)) {
+    // Has return type.
+    context.PushState(state, StateKind::LambdaBody);
+    context.PushState(StateKind::FunctionReturnTypeFinish);
+    context.ConsumeAndDiscard();
+    context.PushStateForExpr(PrecedenceGroup::ForType());
+  } else if (context.PositionIs(Lex::TokenKind::EqualGreater)) {
+    // Terse body `=> expr`
+    context.AddLeafNode(NodeKind::TerseBodyArrow, context.Consume());
+    context.PushState(state, StateKind::LambdaBodyFinish);
+    context.PushStateForExpr(PrecedenceGroup::ForTopLevelExpr());
+  } else if (context.PositionIs(Lex::TokenKind::OpenCurlyBrace)) {
+    // Block body `{ ... }`
+    context.PushState(state, StateKind::LambdaBodyFinish);
+    context.PushState(StateKind::CodeBlock);
+  } else {
+    CARBON_DIAGNOSTIC(ExpectedLambdaBody, Error,
+                      "expected `->`, `=>`, or `{{`");
+    context.emitter().Emit(*context.position(), ExpectedLambdaBody);
+    state.has_error = true;
+    context.ReturnErrorOnState();
+  }
+}
+
+auto HandleLambdaBody(Context& context) -> void {
+  auto state = context.PopState();
+
+  // We arrive here after parsing return type.
+  // So we look for `=>` or `{`.
+
+  if (context.PositionIs(Lex::TokenKind::EqualGreater)) {
+    // Terse body `=> expr`
+    context.AddLeafNode(NodeKind::TerseBodyArrow, context.Consume());
+    context.PushState(state, StateKind::LambdaBodyFinish);
+    context.PushStateForExpr(PrecedenceGroup::ForTopLevelExpr());
+  } else if (context.PositionIs(Lex::TokenKind::OpenCurlyBrace)) {
+    // Block body `{ ... }`
+    context.PushState(state, StateKind::LambdaBodyFinish);
+    context.PushState(StateKind::CodeBlock);
+  } else {
+    CARBON_DIAGNOSTIC(ExpectedLambdaBodyAfterReturnType, Error,
+                      "expected `=>` or `{{` after return type");
+    context.emitter().Emit(*context.position(),
+                           ExpectedLambdaBodyAfterReturnType);
+    state.has_error = true;
+    context.ReturnErrorOnState();
+  }
+}
+
+auto HandleLambdaBodyFinish(Context& context) -> void {
+  auto state = context.PopState();
+  context.AddNode(NodeKind::Lambda, state.token, state.has_error);
+}
+
+}  // namespace Carbon::Parse

+ 3 - 0
toolchain/parse/node_ids.h

@@ -160,6 +160,9 @@ using AnyClassDeclId =
                 ChoiceDefinitionStartId>;
 using AnyFunctionDeclId = NodeIdOneOf<FunctionDeclId, FunctionDefinitionStartId,
                                       BuiltinFunctionDefinitionStartId>;
+using AnyFunctionDefinitionId =
+    NodeIdOneOf<FunctionDefinitionId, FunctionTerseDefinitionId,
+                BuiltinFunctionDefinitionId>;
 using AnyImplDeclId = NodeIdOneOf<ImplDeclId, ImplDefinitionStartId>;
 using AnyInterfaceDeclId =
     NodeIdOneOf<InterfaceDeclId, InterfaceDefinitionStartId>;

+ 4 - 0
toolchain/parse/node_kind.def

@@ -143,10 +143,14 @@ CARBON_PARSE_NODE_KIND(CodeBlockStart)
 CARBON_PARSE_NODE_KIND(CodeBlock)
 
 CARBON_PARSE_NODE_KIND(FunctionIntroducer)
+CARBON_PARSE_NODE_KIND(LambdaIntroducer)
 CARBON_PARSE_NODE_KIND(ReturnType)
 CARBON_PARSE_NODE_KIND(FunctionDefinitionStart)
 CARBON_PARSE_NODE_KIND(FunctionDefinition)
+CARBON_PARSE_NODE_KIND(FunctionTerseDefinition)
 CARBON_PARSE_NODE_KIND(FunctionDecl)
+CARBON_PARSE_NODE_KIND(Lambda)
+CARBON_PARSE_NODE_KIND(TerseBodyArrow)
 CARBON_PARSE_NODE_KIND(BuiltinFunctionDefinitionStart)
 CARBON_PARSE_NODE_KIND(BuiltinName)
 CARBON_PARSE_NODE_KIND(BuiltinFunctionDefinition)

+ 3 - 3
toolchain/parse/node_kind.h

@@ -14,7 +14,7 @@
 
 namespace Carbon::Parse {
 
-CARBON_DEFINE_RAW_ENUM_CLASS(NodeKind, uint8_t) {
+CARBON_DEFINE_RAW_ENUM_CLASS(NodeKind, uint16_t) {
 #define CARBON_PARSE_NODE_KIND(Name) CARBON_RAW_ENUM_ENUMERATOR(Name)
 #include "toolchain/parse/node_kind.def"
 };
@@ -91,8 +91,8 @@ static_assert(
     "a `constexpr` context despite being declared as merely `const`. We use it "
     "in a static assert here to ensure that.");
 
-// We expect the parse node kind to fit compactly into 8 bits.
-static_assert(sizeof(NodeKind) == 1, "Kind objects include padding!");
+// We expect the parse node kind to fit compactly into 16 bits.
+static_assert(sizeof(NodeKind) == 2, "Kind objects include padding!");
 
 // Optional arguments that can be supplied when defining a node kind. At least
 // one of `bracketed_by` and `child_count` is required.

+ 84 - 0
toolchain/parse/state.def

@@ -678,6 +678,83 @@ CARBON_PARSE_STATE(ExprStatementFinish)
 //   2. FunctionAfterParams
 CARBON_PARSE_STATE(FunctionIntroducer)
 
+// Handles a lambda's introducer.
+//
+// fn ...
+//   ^
+//   1. LambdaIntroducer
+//   2. LambdaAfterIntroducer
+CARBON_PARSE_STATE(LambdaIntroducer)
+
+// Handles processing of a lambda after the introducer.
+//
+// fn [ ... ] ...
+//    ^
+//   1. PatternListAsImplicit
+//   2. LambdaAfterImplicitParams
+//
+// fn ( ... ) ...
+//    ^
+//   1. PatternListAsExplicit
+//   2. LambdaAfterParams
+//
+// fn ...
+//   ^
+//   1. LambdaAfterParams
+CARBON_PARSE_STATE(LambdaAfterIntroducer)
+
+// Handles processing of a lambda after implicit parameters.
+//
+// fn [ ... ] ( ... ) ...
+//            ^
+//   1. PatternListAsExplicit
+//   2. LambdaAfterParams
+//
+// fn [ ... ] ...
+//           ^
+//   1. LambdaAfterParams
+CARBON_PARSE_STATE(LambdaAfterImplicitParams)
+
+// Handles processing of a lambda after optional parameters.
+//
+// fn ... -> ...
+//        ^
+//   1. FunctionReturnTypeFinish  (Reused)
+//   2. LambdaBody
+//
+// fn ... => ...
+//        ^
+//   1. Expr
+//   2. LambdaBodyFinish
+//
+// fn ... { ... }
+//        ^
+//   1. StatementScopeLoop
+//   2. LambdaBodyFinish
+CARBON_PARSE_STATE(LambdaAfterParams)
+
+// Handles processing of a lambda body.
+//
+// fn ... -> ... { ... }
+//               ^
+//   1. StatementScopeLoop
+//   2. LambdaBodyFinish
+//
+// fn ... -> ... => ...
+//               ^
+//   1. Expr
+//   2. LambdaBodyFinish
+CARBON_PARSE_STATE(LambdaBody)
+
+// Finishes a lambda expression.
+//
+// fn ... }
+//         ^
+// fn ... => expr
+//               ^
+//   (state done)
+CARBON_PARSE_STATE(LambdaBodyFinish)
+
 // Handles processing of a function's syntax after `)`, primarily the
 // possibility a `->` return type is there. Always enqueues signature finish
 // handling.
@@ -726,6 +803,13 @@ CARBON_PARSE_STATE(FunctionSignatureFinish)
 //   (state done)
 CARBON_PARSE_STATE(FunctionDefinitionFinish)
 
+// Finishes a function with a terse body.
+//
+// fn ... => expr ;
+//                ^
+//   (state done)
+CARBON_PARSE_STATE(FunctionTerseBodyFinish)
+
 // Handles `export <name>`.
 //
 // export Name;

+ 43 - 0
toolchain/parse/testdata/function/terse.carbon

@@ -0,0 +1,43 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/function/terse.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/function/terse.carbon
+
+fn F(x: i32) => x;
+
+fn G(x: i32) -> i32 => x;
+
+// CHECK:STDOUT: - filename: terse.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'F'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '=>', subtree_size: 8},
+// CHECK:STDOUT:       {kind: 'TerseBodyArrow', text: '=>'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameExpr', text: 'x'},
+// CHECK:STDOUT:     {kind: 'FunctionTerseDefinition', text: ';', subtree_size: 11},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'G'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '=>', subtree_size: 10},
+// CHECK:STDOUT:       {kind: 'TerseBodyArrow', text: '=>'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameExpr', text: 'x'},
+// CHECK:STDOUT:     {kind: 'FunctionTerseDefinition', text: ';', subtree_size: 13},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 135 - 0
toolchain/parse/testdata/lambda/lambda.carbon

@@ -0,0 +1,135 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/lambda/lambda.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/lambda/lambda.carbon
+
+// --- basic.carbon
+
+var a: auto = fn [T:! type] (x: T) -> T { return x; };
+var b: auto = fn (x: i32) => x;
+var c: auto = fn { };
+
+// --- fail_expected_body.carbon
+
+fn F() {
+  // CHECK:STDERR: fail_expected_body.carbon:[[@LINE+4]]:20: error: expected `->`, `=>`, or `{` [ExpectedLambdaBody]
+  // CHECK:STDERR:   var x: auto = fn ;
+  // CHECK:STDERR:                    ^
+  // CHECK:STDERR:
+  var x: auto = fn ;
+}
+
+// --- fail_expected_body_after_return_type.carbon
+
+fn G() {
+  // CHECK:STDERR: fail_expected_body_after_return_type.carbon:[[@LINE+4]]:27: error: expected `=>` or `{` after return type [ExpectedLambdaBodyAfterReturnType]
+  // CHECK:STDERR:   var x: auto = fn -> i32 ;
+  // CHECK:STDERR:                           ^
+  // CHECK:STDERR:
+  var x: auto = fn -> i32 ;
+}
+
+// CHECK:STDOUT: - filename: basic.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'a'},
+// CHECK:STDOUT:           {kind: 'AutoTypeLiteral', text: 'auto'},
+// 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: 'ImplicitParamListStart', text: '['},
+// CHECK:STDOUT:               {kind: 'IdentifierNameNotBeforeParams', text: 'T'},
+// CHECK:STDOUT:             {kind: 'CompileTimeBindingPatternStart', text: ':!', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'TypeTypeLiteral', text: 'type'},
+// CHECK:STDOUT:           {kind: 'CompileTimeBindingPattern', text: ':!', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'ImplicitParamList', text: ']', subtree_size: 6},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'T'},
+// CHECK:STDOUT:           {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'IdentifierNameExpr', text: 'T'},
+// CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'CodeBlockStart', text: '{'},
+// CHECK:STDOUT:             {kind: 'ReturnStatementStart', text: 'return'},
+// CHECK:STDOUT:             {kind: 'IdentifierNameExpr', text: 'x'},
+// CHECK:STDOUT:           {kind: 'ReturnStatement', text: ';', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'CodeBlock', text: '}', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'Lambda', text: 'fn', subtree_size: 20},
+// CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 27},
+// CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'b'},
+// CHECK:STDOUT:           {kind: 'AutoTypeLiteral', text: 'auto'},
+// 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: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'LetBindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'TerseBodyArrow', text: '=>'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameExpr', text: 'x'},
+// CHECK:STDOUT:       {kind: 'Lambda', text: 'fn', subtree_size: 9},
+// CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 16},
+// CHECK:STDOUT:       {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'c'},
+// CHECK:STDOUT:           {kind: 'AutoTypeLiteral', text: 'auto'},
+// 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: 'CodeBlockStart', text: '{'},
+// CHECK:STDOUT:         {kind: 'CodeBlock', text: '}', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'Lambda', text: 'fn', subtree_size: 4},
+// CHECK:STDOUT:     {kind: 'VariableDecl', text: ';', subtree_size: 11},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_expected_body.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'F'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'AutoTypeLiteral', text: 'auto'},
+// 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: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_expected_body_after_return_type.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierNameBeforeParams', text: 'G'},
+// CHECK:STDOUT:           {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ExplicitParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'AutoTypeLiteral', text: 'auto'},
+// 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: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:         {kind: 'ReturnType', text: '->', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', has_error: yes, subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 16},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 3 - 4
toolchain/parse/tree.h

@@ -40,7 +40,7 @@ struct DeferredDefinition {
   // The node that starts the function definition.
   FunctionDefinitionStartId start_id;
   // The function definition node.
-  FunctionDefinitionId definition_id = NodeId::None;
+  AnyFunctionDefinitionId definition_id = NodeId::None;
   // The index of the next method that is not nested within this one.
   DeferredDefinitionIndex next_definition_index = DeferredDefinitionIndex::None;
 };
@@ -211,9 +211,8 @@ class Tree : public Printable<Tree> {
     }
 
    private:
-    // The kind of this node. Note that this is only a single byte.
+    // The kind of this node.
     NodeKind kind_;
-    static_assert(sizeof(kind_) == 1, "TokenKind must pack to 8 bits");
 
     // Whether this node is or contains a parse error.
     //
@@ -234,7 +233,7 @@ class Tree : public Printable<Tree> {
     unsigned token_index_ : Lex::TokenIndex::Bits;
   };
 
-  static_assert(sizeof(NodeImpl) == 4,
+  static_assert(sizeof(NodeImpl) == 8,
                 "Unexpected size of node implementation!");
 
   // Sets the kind of a node. This is intended to allow putting the tree into a

+ 35 - 2
toolchain/parse/typed_nodes.h

@@ -469,8 +469,8 @@ struct FunctionSignature {
 using FunctionDecl = FunctionSignature<NodeKind::FunctionDecl,
                                        Lex::SemiTokenIndex, NodeCategory::Decl>;
 using FunctionDefinitionStart =
-    FunctionSignature<NodeKind::FunctionDefinitionStart,
-                      Lex::OpenCurlyBraceTokenIndex, NodeCategory::None>;
+    FunctionSignature<NodeKind::FunctionDefinitionStart, Lex::TokenIndex,
+                      NodeCategory::None>;
 
 // A function definition: `fn F() -> i32 { ... }`.
 struct FunctionDefinition {
@@ -483,6 +483,18 @@ struct FunctionDefinition {
   Lex::CloseCurlyBraceTokenIndex token;
 };
 
+// A terse function definition: `fn F() -> i32 => expr;`.
+struct FunctionTerseDefinition {
+  static constexpr auto Kind = NodeKind::FunctionTerseDefinition.Define(
+      {.category = NodeCategory::Decl,
+       .bracketed_by = FunctionDefinitionStart::Kind});
+
+  FunctionDefinitionStartId signature;
+  TerseBodyArrowId arrow;
+  AnyExprId body;
+  Lex::SemiTokenIndex token;
+};
+
 using BuiltinFunctionDefinitionStart =
     FunctionSignature<NodeKind::BuiltinFunctionDefinitionStart,
                       Lex::EqualTokenIndex, NodeCategory::None>;
@@ -651,6 +663,9 @@ struct VariablePattern {
 using CodeBlockStart =
     LeafNode<NodeKind::CodeBlockStart, Lex::OpenCurlyBraceTokenIndex>;
 
+using TerseBodyArrow =
+    LeafNode<NodeKind::TerseBodyArrow, Lex::EqualGreaterTokenIndex>;
+
 // A code block: `{ statement; statement; ... }`.
 struct CodeBlock {
   static constexpr auto Kind =
@@ -661,6 +676,24 @@ struct CodeBlock {
   Lex::CloseCurlyBraceTokenIndex token;
 };
 
+using LambdaIntroducer =
+    LeafNode<NodeKind::LambdaIntroducer, Lex::FnTokenIndex>;
+
+struct Lambda {
+  static constexpr auto Kind = NodeKind::Lambda.Define(
+      {.category = NodeCategory::Expr, .bracketed_by = LambdaIntroducer::Kind});
+
+  LambdaIntroducerId introducer;
+  std::optional<ImplicitParamListId> implicit_params;
+  std::optional<ExplicitParamListId> explicit_params;
+  std::optional<ReturnTypeId> return_type;
+  std::optional<TerseBodyArrowId> arrow;
+  NodeId body;
+  // Use a generic token index because the token might be `}` or part of an
+  // expression.
+  Lex::TokenIndex token;
+};
+
 // An expression statement: `F(x);`.
 struct ExprStatement {
   static constexpr auto Kind = NodeKind::ExprStatement.Define(