diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index e11047c70251..e5620df4d097 100644 --- a/toolchain/check/handle_function.cpp +++ b/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 diff --git a/toolchain/check/handle_lambda.cpp b/toolchain/check/handle_lambda.cpp new file mode 100644 index 000000000000..0170ccf22ba9 --- /dev/null +++ b/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 diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index bc8f95946d3a..901562558b27 100644 --- a/toolchain/check/node_stack.h +++ b/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; diff --git a/toolchain/check/testdata/function/declaration/fail_todo_no_params.carbon b/toolchain/check/testdata/function/declaration/fail_todo_no_params.carbon index 21cb3e15d4a2..2d50415f960f 100644 --- a/toolchain/check/testdata/function/declaration/fail_todo_no_params.carbon +++ b/toolchain/check/testdata/function/declaration/fail_todo_no_params.carbon @@ -53,14 +53,14 @@ 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: fail_todo_arrow_body.carbon:[[@LINE+4]]:6: error: semantics TODO: `HandleTerseBodyArrow` [SemanticsTodo] // CHECK:STDERR: fn A => 0; // 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: fn A => 0; -// CHECK:STDERR: ^~~~~~~~~~ -// CHECK:STDERR: fn A => 0; // --- fail_invalid_file_generic_regression_test.carbon @@ -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 { diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index dd0b08311fd4..3e5f0a4b9a14 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/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) diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index 3b7637b55cf3..856ab70a75c0 100644 --- a/toolchain/lex/token_kind.def +++ b/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") diff --git a/toolchain/parse/context.cpp b/toolchain/parse/context.cpp index be33fad796ed..b61009dd40b0 100644 --- a/toolchain/parse/context.cpp +++ b/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(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_)) { diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index b28facad0ee8..c2bfa62ba26f 100644 --- a/toolchain/parse/context.h +++ b/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; diff --git a/toolchain/parse/handle_expr.cpp b/toolchain/parse/handle_expr.cpp index eef12852b8d4..2113ddfaa891 100644 --- a/toolchain/parse/handle_expr.cpp +++ b/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); diff --git a/toolchain/parse/handle_function.cpp b/toolchain/parse/handle_function.cpp index 5170c9c9da20..2e32a924003c 100644 --- a/toolchain/parse/handle_function.cpp +++ b/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 diff --git a/toolchain/parse/handle_lambda.cpp b/toolchain/parse/handle_lambda.cpp new file mode 100644 index 000000000000..b479d69dca3d --- /dev/null +++ b/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 diff --git a/toolchain/parse/node_ids.h b/toolchain/parse/node_ids.h index cedea5866a77..e7cda777afd8 100644 --- a/toolchain/parse/node_ids.h +++ b/toolchain/parse/node_ids.h @@ -160,6 +160,9 @@ using AnyClassDeclId = ChoiceDefinitionStartId>; using AnyFunctionDeclId = NodeIdOneOf; +using AnyFunctionDefinitionId = + NodeIdOneOf; using AnyImplDeclId = NodeIdOneOf; using AnyInterfaceDeclId = NodeIdOneOf; diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 10cd23e869af..6cf4bbbdbaa6 100644 --- a/toolchain/parse/node_kind.def +++ b/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) diff --git a/toolchain/parse/node_kind.h b/toolchain/parse/node_kind.h index 682ed9ce68a4..524c1df81038 100644 --- a/toolchain/parse/node_kind.h +++ b/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. diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index c1534adfa553..7ec22d9c4f21 100644 --- a/toolchain/parse/state.def +++ b/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 `. // // export Name; diff --git a/toolchain/parse/testdata/function/terse.carbon b/toolchain/parse/testdata/function/terse.carbon new file mode 100644 index 000000000000..bd8fdbab24d5 --- /dev/null +++ b/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: ] diff --git a/toolchain/parse/testdata/lambda/lambda.carbon b/toolchain/parse/testdata/lambda/lambda.carbon new file mode 100644 index 000000000000..6f2c23856704 --- /dev/null +++ b/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: ] diff --git a/toolchain/parse/tree.h b/toolchain/parse/tree.h index 89f965892bab..f47921da93dc 100644 --- a/toolchain/parse/tree.h +++ b/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 { } 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 { 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 diff --git a/toolchain/parse/typed_nodes.h b/toolchain/parse/typed_nodes.h index 32da26da4f7f..833dab355bf6 100644 --- a/toolchain/parse/typed_nodes.h +++ b/toolchain/parse/typed_nodes.h @@ -469,8 +469,8 @@ struct FunctionSignature { using FunctionDecl = FunctionSignature; using FunctionDefinitionStart = - FunctionSignature; + FunctionSignature; // 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; @@ -651,6 +663,9 @@ struct VariablePattern { using CodeBlockStart = LeafNode; +using TerseBodyArrow = + LeafNode; + // A code block: `{ statement; statement; ... }`. struct CodeBlock { static constexpr auto Kind = @@ -661,6 +676,24 @@ struct CodeBlock { Lex::CloseCurlyBraceTokenIndex token; }; +using LambdaIntroducer = + LeafNode; + +struct Lambda { + static constexpr auto Kind = NodeKind::Lambda.Define( + {.category = NodeCategory::Expr, .bracketed_by = LambdaIntroducer::Kind}); + + LambdaIntroducerId introducer; + std::optional implicit_params; + std::optional explicit_params; + std::optional return_type; + std::optional 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(