diff --git a/toolchain/check/handle_let.cpp b/toolchain/check/handle_let.cpp new file mode 100644 index 000000000000..25fc8be22489 --- /dev/null +++ b/toolchain/check/handle_let.cpp @@ -0,0 +1,50 @@ +// 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/convert.h" +#include "toolchain/sem_ir/node.h" + +namespace Carbon::Check { + +auto HandleLetDeclaration(Context& context, Parse::Node parse_node) -> bool { + auto value_id = context.node_stack().PopExpression(); + SemIR::NodeId pattern_id = + context.node_stack().Pop(); + context.node_stack() + .PopAndDiscardSoloParseNode(); + + // Convert the value to match the type of the pattern. + auto pattern = context.semantics_ir().GetNode(pattern_id); + value_id = + ConvertToValueOfType(context, parse_node, value_id, pattern.type_id()); + + // Update the binding with its value and add it to the current block, after + // the computation of the value. + auto [name_id, absent_value_id] = pattern.GetAsBindName(); + CARBON_CHECK(!absent_value_id.is_valid()) + << "Binding should not already have a value!"; + context.semantics_ir().ReplaceNode( + pattern_id, + SemIR::Node::BindName::Make(pattern.parse_node(), pattern.type_id(), + name_id, value_id)); + context.node_block_stack().AddNodeId(pattern_id); + + // Add the name of the binding to the current scope. + context.AddNameToLookup(pattern.parse_node(), name_id, pattern_id); + return true; +} + +auto HandleLetIntroducer(Context& context, Parse::Node parse_node) -> bool { + // Push a bracketing node to establish the pattern context. + context.node_stack().Push(parse_node); + return true; +} + +auto HandleLetInitializer(Context& /*context*/, Parse::Node /*parse_node*/) + -> bool { + return true; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/handle_pattern_binding.cpp b/toolchain/check/handle_pattern_binding.cpp index 90044c9baa8a..9596b88e6726 100644 --- a/toolchain/check/handle_pattern_binding.cpp +++ b/toolchain/check/handle_pattern_binding.cpp @@ -28,6 +28,7 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool { // Allocate a node of the appropriate kind, linked to the name for error // locations. + // TODO: Each of these cases should create a `BindName` node. switch (auto context_parse_node_kind = context.parse_tree().node_kind( context.node_stack().PeekParseNode())) { case Parse::NodeKind::VariableIntroducer: @@ -38,6 +39,16 @@ auto HandlePatternBinding(Context& context, Parse::Node parse_node) -> bool { context.AddNodeAndPush(parse_node, SemIR::Node::Parameter::Make( name_node, cast_type_id, name_id)); break; + case Parse::NodeKind::LetIntroducer: + // Create the node, but don't add it to a block until after we've formed + // its initializer. + // TODO: For general pattern parsing, we'll need to create a block to hold + // the `let` pattern before we see the initializer. + context.node_stack().Push( + parse_node, + context.semantics_ir().AddNodeInNoBlock(SemIR::Node::BindName::Make( + name_node, cast_type_id, name_id, SemIR::NodeId::Invalid))); + break; default: CARBON_FATAL() << "Found a pattern binding in unexpected context " << context_parse_node_kind; diff --git a/toolchain/check/node_stack.h b/toolchain/check/node_stack.h index 4f31b806ea55..eaa28fbeb9d2 100644 --- a/toolchain/check/node_stack.h +++ b/toolchain/check/node_stack.h @@ -273,6 +273,7 @@ class NodeStack { case Parse::NodeKind::CodeBlockStart: case Parse::NodeKind::FunctionIntroducer: case Parse::NodeKind::IfStatementElse: + case Parse::NodeKind::LetIntroducer: case Parse::NodeKind::ParameterListStart: case Parse::NodeKind::ParenExpressionOrTupleLiteralStart: case Parse::NodeKind::QualifiedDeclaration: diff --git a/toolchain/check/testdata/let/convert.carbon b/toolchain/check/testdata/let/convert.carbon new file mode 100644 index 000000000000..d99236d6bd7c --- /dev/null +++ b/toolchain/check/testdata/let/convert.carbon @@ -0,0 +1,50 @@ +// 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 + +fn F() -> i32 { + var v: (i32, i32, i32) = (1, 2, 3); + // Convert from object representation to value representation. + let w: (i32, i32, i32) = v; + return w[1]; +} + +// CHECK:STDOUT: file "convert.carbon" { +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc8_24.1: type = tuple_type (type, type, type) +// CHECK:STDOUT: %.loc8_24.2: (type, type, type) = tuple_literal (i32, i32, i32) +// CHECK:STDOUT: %.loc8_24.3: type = tuple_type (i32, i32, i32) +// CHECK:STDOUT: %v: ref (i32, i32, i32) = var "v" +// CHECK:STDOUT: %.loc8_29: i32 = int_literal 1 +// CHECK:STDOUT: %.loc8_32: i32 = int_literal 2 +// CHECK:STDOUT: %.loc8_35: i32 = int_literal 3 +// CHECK:STDOUT: %.loc8_36.1: (i32, i32, i32) = tuple_literal (%.loc8_29, %.loc8_32, %.loc8_35) +// CHECK:STDOUT: %.loc8_36.2: ref i32 = tuple_access %v, member0 +// CHECK:STDOUT: %.loc8_36.3: init i32 = initialize_from %.loc8_29 to %.loc8_36.2 +// CHECK:STDOUT: %.loc8_36.4: ref i32 = tuple_access %v, member1 +// CHECK:STDOUT: %.loc8_36.5: init i32 = initialize_from %.loc8_32 to %.loc8_36.4 +// CHECK:STDOUT: %.loc8_36.6: ref i32 = tuple_access %v, member2 +// CHECK:STDOUT: %.loc8_36.7: init i32 = initialize_from %.loc8_35 to %.loc8_36.6 +// CHECK:STDOUT: %.loc8_36.8: init (i32, i32, i32) = tuple_init %.loc8_36.1, (%.loc8_36.3, %.loc8_36.5, %.loc8_36.7) +// CHECK:STDOUT: assign %v, %.loc8_36.8 +// CHECK:STDOUT: %.loc10_24: (type, type, type) = tuple_literal (i32, i32, i32) +// CHECK:STDOUT: %v.ref: ref (i32, i32, i32) = name_reference "v", %v +// CHECK:STDOUT: %.loc10_28.1: ref i32 = tuple_access %v.ref, member0 +// CHECK:STDOUT: %.loc10_28.2: i32 = bind_value %.loc10_28.1 +// CHECK:STDOUT: %.loc10_28.3: ref i32 = tuple_access %v.ref, member1 +// CHECK:STDOUT: %.loc10_28.4: i32 = bind_value %.loc10_28.3 +// CHECK:STDOUT: %.loc10_28.5: ref i32 = tuple_access %v.ref, member2 +// CHECK:STDOUT: %.loc10_28.6: i32 = bind_value %.loc10_28.5 +// CHECK:STDOUT: %.loc10_28.7: (i32, i32, i32) = tuple_value %v.ref, (%.loc10_28.2, %.loc10_28.4, %.loc10_28.6) +// CHECK:STDOUT: %w: (i32, i32, i32) = bind_name "w", %.loc10_28.7 +// CHECK:STDOUT: %w.ref: (i32, i32, i32) = name_reference "w", %w +// CHECK:STDOUT: %.loc11_12: i32 = int_literal 1 +// CHECK:STDOUT: %.loc11_13: i32 = tuple_index %w.ref, %.loc11_12 +// CHECK:STDOUT: return %.loc11_13 +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/let/fail_duplicate_decl.carbon b/toolchain/check/testdata/let/fail_duplicate_decl.carbon new file mode 100644 index 000000000000..95f4656a6c86 --- /dev/null +++ b/toolchain/check/testdata/let/fail_duplicate_decl.carbon @@ -0,0 +1,26 @@ +// 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 + +fn F(a: i32) { + // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE+6]]:7: ERROR: Duplicate name being declared in the same scope. + // CHECK:STDERR: let a: i32 = 1; + // CHECK:STDERR: ^ + // CHECK:STDERR: fail_duplicate_decl.carbon:[[@LINE-4]]:6: Name is previously declared here. + // CHECK:STDERR: fn F(a: i32) { + // CHECK:STDERR: ^ + let a: i32 = 1; +} + +// CHECK:STDOUT: file "fail_duplicate_decl.carbon" { +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%a.loc7: i32) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %.loc14: i32 = int_literal 1 +// CHECK:STDOUT: %a.loc14: i32 = bind_name "a", %.loc14 +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/let/fail_use_in_init.carbon b/toolchain/check/testdata/let/fail_use_in_init.carbon new file mode 100644 index 000000000000..5e38640e63b0 --- /dev/null +++ b/toolchain/check/testdata/let/fail_use_in_init.carbon @@ -0,0 +1,23 @@ +// 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 + +fn F() { + // CHECK:STDERR: fail_use_in_init.carbon:[[@LINE+3]]:16: ERROR: Name `a` not found. + // CHECK:STDERR: let a: i32 = a; + // CHECK:STDERR: ^ + let a: i32 = a; +} + +// CHECK:STDOUT: file "fail_use_in_init.carbon" { +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %a.ref: = name_reference "a", +// CHECK:STDOUT: %a: i32 = bind_name "a", +// CHECK:STDOUT: return +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/let/global.carbon b/toolchain/check/testdata/let/global.carbon new file mode 100644 index 000000000000..1c2a80cbd638 --- /dev/null +++ b/toolchain/check/testdata/let/global.carbon @@ -0,0 +1,21 @@ +// 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 + +let n: i32 = 1; + +fn F() -> i32 { return n; } + +// CHECK:STDOUT: file "global.carbon" { +// CHECK:STDOUT: %.loc7: i32 = int_literal 1 +// CHECK:STDOUT: %n: i32 = bind_name "n", %.loc7 +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %n.ref: i32 = name_reference "n", package.%n +// CHECK:STDOUT: return %n.ref +// CHECK:STDOUT: } diff --git a/toolchain/check/testdata/let/local.carbon b/toolchain/check/testdata/let/local.carbon new file mode 100644 index 000000000000..8044fe7e2df2 --- /dev/null +++ b/toolchain/check/testdata/let/local.carbon @@ -0,0 +1,22 @@ +// 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 + +fn F(a: i32) -> i32 { + let b: i32 = a; + return b; +} + +// CHECK:STDOUT: file "local.carbon" { +// CHECK:STDOUT: %F = fn_decl @F +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%a: i32) -> i32 { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %a.ref: i32 = name_reference "a", %a +// CHECK:STDOUT: %b: i32 = bind_name "b", %a.ref +// CHECK:STDOUT: %b.ref: i32 = name_reference "b", %b +// CHECK:STDOUT: return %b.ref +// CHECK:STDOUT: } diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 12db6964e72f..e5d5b10bfd87 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/toolchain/diagnostics/diagnostic_kind.def @@ -64,6 +64,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedCloseSymbol) CARBON_DIAGNOSTIC_KIND(ExpectedCodeBlock) CARBON_DIAGNOSTIC_KIND(ExpectedExpression) CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterDotOrArrow) +CARBON_DIAGNOSTIC_KIND(ExpectedLetBindingName) CARBON_DIAGNOSTIC_KIND(ExpectedParameterName) CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter) CARBON_DIAGNOSTIC_KIND(ExpectedExpressionSemi) @@ -97,6 +98,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedElseAfterIf) CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationName) CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationSemi) CARBON_DIAGNOSTIC_KIND(ExpectedDeclarationSemiOrDefinition) +CARBON_DIAGNOSTIC_KIND(ExpectedInitializerAfterLet) CARBON_DIAGNOSTIC_KIND(MethodImplNotAllowed) CARBON_DIAGNOSTIC_KIND(ParametersRequiredByIntroducer) CARBON_DIAGNOSTIC_KIND(ParametersRequiredByDeduced) diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 3464caed15aa..f4c38d1f99bb 100644 --- a/toolchain/lower/handle.cpp +++ b/toolchain/lower/handle.cpp @@ -60,9 +60,10 @@ auto HandleBinaryOperatorAdd(FunctionContext& /*context*/, CARBON_FATAL() << "TODO: Add support: " << node; } -auto HandleBindName(FunctionContext& /*context*/, SemIR::NodeId /*node_id*/, - SemIR::Node /*node*/) -> void { - // Probably need to do something here, but not necessary for now. +auto HandleBindName(FunctionContext& context, SemIR::NodeId node_id, + SemIR::Node node) -> void { + auto [name_id, value_id] = node.GetAsBindName(); + context.SetLocal(node_id, context.GetLocal(value_id)); } auto HandleBlockArg(FunctionContext& context, SemIR::NodeId node_id, diff --git a/toolchain/lower/testdata/let/local.carbon b/toolchain/lower/testdata/let/local.carbon new file mode 100644 index 000000000000..3843072a3e9d --- /dev/null +++ b/toolchain/lower/testdata/let/local.carbon @@ -0,0 +1,18 @@ +// 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 + +fn Run() -> i32 { + let n: i32 = 1; + let m: i32 = n; + return m; +} + +// CHECK:STDOUT: ; ModuleID = 'local.carbon' +// CHECK:STDOUT: source_filename = "local.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @main() { +// CHECK:STDOUT: ret i32 1 +// CHECK:STDOUT: } diff --git a/toolchain/lower/testdata/let/tuple.carbon b/toolchain/lower/testdata/let/tuple.carbon new file mode 100644 index 000000000000..5facd2e9e893 --- /dev/null +++ b/toolchain/lower/testdata/let/tuple.carbon @@ -0,0 +1,61 @@ +// 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 + +fn F() -> i32 { + var a: (i32, i32, i32) = (1, 2, 3); + var b: (i32, i32) = (4, 5); + let c: ((i32, i32, i32), (i32, i32)) = (a, b); + return c[1][1]; +} + +// CHECK:STDOUT: ; ModuleID = 'tuple.carbon' +// CHECK:STDOUT: source_filename = "tuple.carbon" +// CHECK:STDOUT: +// CHECK:STDOUT: define i32 @F() { +// CHECK:STDOUT: %a = alloca { i32, i32, i32 }, align 8 +// CHECK:STDOUT: %tuple.elem = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0 +// CHECK:STDOUT: store i32 1, ptr %tuple.elem, align 4 +// CHECK:STDOUT: %tuple.elem1 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 1 +// CHECK:STDOUT: store i32 2, ptr %tuple.elem1, align 4 +// CHECK:STDOUT: %tuple.elem2 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 2 +// CHECK:STDOUT: store i32 3, ptr %tuple.elem2, align 4 +// CHECK:STDOUT: %b = alloca { i32, i32 }, align 8 +// CHECK:STDOUT: %tuple.elem3 = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 0 +// CHECK:STDOUT: store i32 4, ptr %tuple.elem3, align 4 +// CHECK:STDOUT: %tuple.elem4 = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 1 +// CHECK:STDOUT: store i32 5, ptr %tuple.elem4, align 4 +// CHECK:STDOUT: %tuple.elem5 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 0 +// CHECK:STDOUT: %1 = load i32, ptr %tuple.elem5, align 4 +// CHECK:STDOUT: %tuple.elem6 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 1 +// CHECK:STDOUT: %2 = load i32, ptr %tuple.elem6, align 4 +// CHECK:STDOUT: %tuple.elem7 = getelementptr inbounds { i32, i32, i32 }, ptr %a, i32 0, i32 2 +// CHECK:STDOUT: %3 = load i32, ptr %tuple.elem7, align 4 +// CHECK:STDOUT: %tuple = alloca { i32, i32, i32 }, align 8 +// CHECK:STDOUT: %4 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple, i32 0, i32 0 +// CHECK:STDOUT: store i32 %1, ptr %4, align 4 +// CHECK:STDOUT: %5 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple, i32 0, i32 1 +// CHECK:STDOUT: store i32 %2, ptr %5, align 4 +// CHECK:STDOUT: %6 = getelementptr inbounds { i32, i32, i32 }, ptr %tuple, i32 0, i32 2 +// CHECK:STDOUT: store i32 %3, ptr %6, align 4 +// CHECK:STDOUT: %tuple.elem8 = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 0 +// CHECK:STDOUT: %7 = load i32, ptr %tuple.elem8, align 4 +// CHECK:STDOUT: %tuple.elem9 = getelementptr inbounds { i32, i32 }, ptr %b, i32 0, i32 1 +// CHECK:STDOUT: %8 = load i32, ptr %tuple.elem9, align 4 +// CHECK:STDOUT: %tuple10 = alloca { i32, i32 }, align 8 +// CHECK:STDOUT: %9 = getelementptr inbounds { i32, i32 }, ptr %tuple10, i32 0, i32 0 +// CHECK:STDOUT: store i32 %7, ptr %9, align 4 +// CHECK:STDOUT: %10 = getelementptr inbounds { i32, i32 }, ptr %tuple10, i32 0, i32 1 +// CHECK:STDOUT: store i32 %8, ptr %10, align 4 +// CHECK:STDOUT: %tuple11 = alloca { { i32, i32, i32 }, { i32, i32 } }, align 8 +// CHECK:STDOUT: %11 = getelementptr inbounds { { i32, i32, i32 }, { i32, i32 } }, ptr %tuple11, i32 0, i32 0 +// CHECK:STDOUT: store ptr %tuple, ptr %11, align 8 +// CHECK:STDOUT: %12 = getelementptr inbounds { { i32, i32, i32 }, { i32, i32 } }, ptr %tuple11, i32 0, i32 1 +// CHECK:STDOUT: store ptr %tuple10, ptr %12, align 8 +// CHECK:STDOUT: %tuple.index = getelementptr inbounds { { i32, i32, i32 }, { i32, i32 } }, ptr %tuple11, i32 0, i32 1 +// CHECK:STDOUT: %tuple.index12 = getelementptr inbounds { i32, i32 }, ptr %tuple.index, i32 0, i32 1 +// CHECK:STDOUT: %tuple.index.load = load i32, ptr %tuple.index12, align 4 +// CHECK:STDOUT: ret i32 %tuple.index.load +// CHECK:STDOUT: } diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index f07ff20aea83..008c401e8d08 100644 --- a/toolchain/parse/context.h +++ b/toolchain/parse/context.h @@ -29,7 +29,12 @@ class Context { enum class ListTokenKind : int8_t { Comma, Close, CommaClose }; // Supported kinds for HandlePattern. - enum class PatternKind : int8_t { DeducedParameter, Parameter, Variable }; + enum class PatternKind : int8_t { + DeducedParameter, + Parameter, + Variable, + Let + }; // Supported return values for GetDeclarationContext. enum class DeclarationContext : int8_t { diff --git a/toolchain/parse/handle_declaration_scope_loop.cpp b/toolchain/parse/handle_declaration_scope_loop.cpp index 87bfc2b3b4cb..d4ce74c9fb24 100644 --- a/toolchain/parse/handle_declaration_scope_loop.cpp +++ b/toolchain/parse/handle_declaration_scope_loop.cpp @@ -57,6 +57,10 @@ auto HandleDeclarationScopeLoop(Context& context) -> void { context.PushState(State::VarAsSemicolon); break; } + case Lex::TokenKind::Let: { + context.PushState(State::Let); + break; + } default: { HandleUnrecognizedDeclaration(context); break; diff --git a/toolchain/parse/handle_let.cpp b/toolchain/parse/handle_let.cpp new file mode 100644 index 000000000000..9c68b9bc7739 --- /dev/null +++ b/toolchain/parse/handle_let.cpp @@ -0,0 +1,61 @@ +// 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" + +namespace Carbon::Parse { + +auto HandleLet(Context& context) -> void { + context.PopAndDiscardState(); + + // These will start at the `let`. + context.PushState(State::LetFinish); + context.PushState(State::LetAfterPattern); + + context.AddLeafNode(NodeKind::LetIntroducer, context.Consume()); + + // This will start at the pattern. + context.PushState(State::PatternAsLet); +} + +auto HandleLetAfterPattern(Context& context) -> void { + auto state = context.PopState(); + + if (state.has_error) { + if (auto after_pattern = + context.FindNextOf({Lex::TokenKind::Equal, Lex::TokenKind::Semi})) { + context.SkipTo(*after_pattern); + } + } + + if (auto equals = context.ConsumeIf(Lex::TokenKind::Equal)) { + context.AddLeafNode(NodeKind::LetInitializer, *equals); + context.PushState(State::Expression); + } else if (!state.has_error) { + CARBON_DIAGNOSTIC( + ExpectedInitializerAfterLet, Error, + "Expected `=`; `let` declaration must have an initializer."); + context.emitter().Emit(*context.position(), ExpectedInitializerAfterLet); + context.ReturnErrorOnState(); + } +} + +auto HandleLetFinish(Context& context) -> void { + auto state = context.PopState(); + + auto end_token = state.token; + if (context.PositionIs(Lex::TokenKind::Semi)) { + end_token = context.Consume(); + } else { + context.EmitExpectedDeclarationSemi(Lex::TokenKind::Let); + state.has_error = true; + if (auto semi_token = context.SkipPastLikelyEnd(state.token)) { + end_token = *semi_token; + } + } + context.AddNode(NodeKind::LetDeclaration, end_token, state.subtree_start, + state.has_error); +} + +} // namespace Carbon::Parse diff --git a/toolchain/parse/handle_pattern.cpp b/toolchain/parse/handle_pattern.cpp index f7f4a4f290b8..d09125d15388 100644 --- a/toolchain/parse/handle_pattern.cpp +++ b/toolchain/parse/handle_pattern.cpp @@ -6,7 +6,7 @@ namespace Carbon::Parse { -// Handles PatternAs(DeducedParameter|FunctionParameter|Variable). +// Handles PatternAs(DeducedParameter|FunctionParameter|Variable|Let). static auto HandlePattern(Context& context, Context::PatternKind pattern_kind) -> void { auto state = context.PopState(); @@ -36,6 +36,12 @@ static auto HandlePattern(Context& context, Context::PatternKind pattern_kind) context.emitter().Emit(*context.position(), ExpectedVariableName); break; } + case Context::PatternKind::Let: { + CARBON_DIAGNOSTIC(ExpectedLetBindingName, Error, + "Expected pattern in `let` declaration."); + context.emitter().Emit(*context.position(), ExpectedLetBindingName); + break; + } } // Add a placeholder for the type. context.AddLeafNode(NodeKind::InvalidParse, *context.position(), @@ -90,6 +96,10 @@ auto HandlePatternAsVariable(Context& context) -> void { HandlePattern(context, Context::PatternKind::Variable); } +auto HandlePatternAsLet(Context& context) -> void { + HandlePattern(context, Context::PatternKind::Let); +} + // Handles PatternFinishAs(Generic|Regular). static auto HandlePatternFinish(Context& context, NodeKind node_kind) -> void { auto state = context.PopState(); diff --git a/toolchain/parse/handle_statement.cpp b/toolchain/parse/handle_statement.cpp index 3e70f49805aa..709bfd9b5714 100644 --- a/toolchain/parse/handle_statement.cpp +++ b/toolchain/parse/handle_statement.cpp @@ -30,6 +30,10 @@ auto HandleStatement(Context& context) -> void { context.PushState(State::StatementIf); break; } + case Lex::TokenKind::Let: { + context.PushState(State::Let); + break; + } case Lex::TokenKind::Return: { context.PushState(State::StatementReturn); break; diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index fbcea53915b7..e1748120aed0 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -142,6 +142,16 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(GenericPatternBinding, 2) CARBON_PARSE_NODE_KIND_CHILD_COUNT(Address, 1) CARBON_PARSE_NODE_KIND_CHILD_COUNT(Template, 1) +// `let`: +// LetIntroducer +// _external_: PatternBinding +// LetInitializer +// _external_: expression +// LetDeclaration +CARBON_PARSE_NODE_KIND_CHILD_COUNT(LetIntroducer, 0) +CARBON_PARSE_NODE_KIND_CHILD_COUNT(LetInitializer, 0) +CARBON_PARSE_NODE_KIND_BRACKET(LetDeclaration, LetIntroducer) + // `var`: // VariableIntroducer // _external_: PatternBinding diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 2cd759f2329e..71ba51a4a5cb 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -38,6 +38,9 @@ #define CARBON_PARSE_STATE_VARIANTS3(State, Variant1, Variant2, Variant3) \ CARBON_PARSE_STATE_VARIANT(State, Variant1) \ CARBON_PARSE_STATE_VARIANTS2(State, Variant2, Variant3) +#define CARBON_PARSE_STATE_VARIANTS4(State, Variant1, Variant2, Variant3, Variant4) \ + CARBON_PARSE_STATE_VARIANT(State, Variant1) \ + CARBON_PARSE_STATE_VARIANTS3(State, Variant2, Variant3, Variant4) // Handles an index expression `a[0]`. // @@ -209,8 +212,6 @@ CARBON_PARSE_STATE(DeclarationNameAndParamsAfterDeduced) // Handles processing of a declaration scope. Things like fn, class, interface, // and so on. // -// If `EndOfFile`: -// (state done) // If `Class`: // 1. TypeIntroducerAsClass // 2. DeclarationScopeLoop @@ -224,15 +225,18 @@ CARBON_PARSE_STATE(DeclarationNameAndParamsAfterDeduced) // 1. TypeIntroducerAsInterface // 2. DeclarationScopeLoop // If `Namespace`: -// 2. Namespace -// 3. DeclarationScopeLoop +// 1. Namespace +// 2. DeclarationScopeLoop // If `Semi`: // 1. DeclarationScopeLoop // If `Var`: -// 1. Var +// 1. VarAsSemicolon +// 2. DeclarationScopeLoop +// If `Let`: +// 1. Let // 2. DeclarationScopeLoop // Else: -// 1. DeclarationScopeLoop +// (state done) CARBON_PARSE_STATE(DeclarationScopeLoop) // Handles periods. Only does one `.` segment; the source is @@ -513,14 +517,14 @@ CARBON_PARSE_STATE_VARIANTS2(ParenExpressionParameterFinish, Unknown, Tuple) CARBON_PARSE_STATE_VARIANTS2(ParenExpressionFinish, Normal, Tuple) // Handles pattern parsing for a pattern, enqueuing type expression processing. -// This covers parameter and `var` support. +// This covers parameter, `let`, and `var` support. // // If valid: // 1. Expression // 2. PatternFinishAs(Generic|Regular) // Else: // 1. PatternFinish -CARBON_PARSE_STATE_VARIANTS3(Pattern, DeducedParameter, Parameter, Variable) +CARBON_PARSE_STATE_VARIANTS4(Pattern, DeducedParameter, Parameter, Variable, Let) // Handles `addr` in a pattern. // @@ -739,4 +743,26 @@ CARBON_PARSE_STATE(VarAfterPattern) // (state done) CARBON_PARSE_STATE_VARIANTS2(VarFinish, Semicolon, For) +// Handles the start of a `let`. +// +// Always: +// 1. PatternAsLet +// 2. LetAfterPattern +// 3. LetFinish +CARBON_PARSE_STATE(Let) + +// Handles `let` after the pattern, followed by an initializer. +// +// If `Equal`: +// 1. Expression +// Else: +// (state done) +CARBON_PARSE_STATE(LetAfterPattern) + +// Handles `let` parsing at the end. +// +// Always: +// (state done) +CARBON_PARSE_STATE(LetFinish) + #undef CARBON_PARSE_STATE diff --git a/toolchain/parse/testdata/let/fail_bad_name.carbon b/toolchain/parse/testdata/let/fail_bad_name.carbon new file mode 100644 index 000000000000..afa9b09707b9 --- /dev/null +++ b/toolchain/parse/testdata/let/fail_bad_name.carbon @@ -0,0 +1,23 @@ +// 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 + +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+3]]:5: ERROR: Expected pattern in `let` declaration. +// CHECK:STDERR: let ? = 4; +// CHECK:STDERR: ^ +let ? = 4; + +// CHECK:STDOUT: - filename: fail_bad_name.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'Name', text: '?', has_error: yes}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '?', has_error: yes}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: '?', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'Literal', text: '4'}, +// CHECK:STDOUT: {kind: 'LetDeclaration', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/fail_empty.carbon b/toolchain/parse/testdata/let/fail_empty.carbon new file mode 100644 index 000000000000..faf53472526c --- /dev/null +++ b/toolchain/parse/testdata/let/fail_empty.carbon @@ -0,0 +1,21 @@ +// 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 + +// CHECK:STDERR: fail_empty.carbon:[[@LINE+3]]:4: ERROR: Expected pattern in `let` declaration. +// CHECK:STDERR: let; +// CHECK:STDERR: ^ +let; + +// CHECK:STDOUT: - filename: fail_empty.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'Name', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetDeclaration', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/fail_missing_type.carbon b/toolchain/parse/testdata/let/fail_missing_type.carbon new file mode 100644 index 000000000000..067391e17b6c --- /dev/null +++ b/toolchain/parse/testdata/let/fail_missing_type.carbon @@ -0,0 +1,23 @@ +// 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 + +// CHECK:STDERR: fail_missing_type.carbon:[[@LINE+3]]:7: ERROR: Expected pattern in `let` declaration. +// CHECK:STDERR: let a = 4; +// CHECK:STDERR: ^ +let a = 4; + +// CHECK:STDOUT: - filename: fail_missing_type.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'Name', text: 'a'}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '=', has_error: yes}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: 'a', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'Literal', text: '4'}, +// CHECK:STDOUT: {kind: 'LetDeclaration', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/fail_missing_value.carbon b/toolchain/parse/testdata/let/fail_missing_value.carbon new file mode 100644 index 000000000000..49d292eec0af --- /dev/null +++ b/toolchain/parse/testdata/let/fail_missing_value.carbon @@ -0,0 +1,39 @@ +// 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 + +// CHECK:STDERR: fail_missing_value.carbon:[[@LINE+3]]:11: ERROR: Expected `=`; `let` declaration must have an initializer. +// CHECK:STDERR: let a: i32; +// CHECK:STDERR: ^ +let a: i32; + +fn F() { + // CHECK:STDERR: fail_missing_value.carbon:[[@LINE+3]]:13: ERROR: Expected `=`; `let` declaration must have an initializer. + // CHECK:STDERR: let b: i32; + // CHECK:STDERR: ^ + let b: i32; +} + +// CHECK:STDOUT: - filename: fail_missing_value.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'Name', text: 'a'}, +// CHECK:STDOUT: {kind: 'Literal', text: 'i32'}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetDeclaration', text: ';', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'Name', text: 'b'}, +// CHECK:STDOUT: {kind: 'Literal', text: 'i32'}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetDeclaration', text: ';', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/let.carbon b/toolchain/parse/testdata/let/let.carbon new file mode 100644 index 000000000000..390124861b9d --- /dev/null +++ b/toolchain/parse/testdata/let/let.carbon @@ -0,0 +1,36 @@ +// 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 + +let v: i32 = 0; +fn F() { + let s: String = "hello"; +} + +// CHECK:STDOUT: - filename: let.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'Name', text: 'v'}, +// CHECK:STDOUT: {kind: 'Literal', text: 'i32'}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'Literal', text: '0'}, +// CHECK:STDOUT: {kind: 'LetDeclaration', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'Name', text: 'F'}, +// CHECK:STDOUT: {kind: 'ParameterListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ParameterList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'Name', text: 's'}, +// CHECK:STDOUT: {kind: 'Literal', text: 'String'}, +// CHECK:STDOUT: {kind: 'PatternBinding', text: ':', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'Literal', text: '"hello"'}, +// CHECK:STDOUT: {kind: 'LetDeclaration', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 13}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/sem_ir/file.cpp b/toolchain/sem_ir/file.cpp index a4b6c7ebcd43..4f377e7f61b4 100644 --- a/toolchain/sem_ir/file.cpp +++ b/toolchain/sem_ir/file.cpp @@ -198,6 +198,7 @@ static auto GetTypePrecedence(NodeKind kind) -> int { case NodeKind::ArrayInit: case NodeKind::Assign: case NodeKind::BinaryOperatorAdd: + case NodeKind::BindName: case NodeKind::BindValue: case NodeKind::BlockArg: case NodeKind::BoolLiteral: @@ -366,6 +367,7 @@ auto File::StringifyType(TypeId type_id, bool in_type_context) const case NodeKind::ArrayInit: case NodeKind::Assign: case NodeKind::BinaryOperatorAdd: + case NodeKind::BindName: case NodeKind::BindValue: case NodeKind::BlockArg: case NodeKind::BoolLiteral: @@ -482,6 +484,12 @@ auto GetExpressionCategory(const File& file, NodeId node_id) case NodeKind::UnaryOperatorNot: return ExpressionCategory::Value; + case NodeKind::BindName: { + auto [name_id, value_id] = node.GetAsBindName(); + node_id = value_id; + continue; + } + case NodeKind::ArrayIndex: { auto [base_id, index_id] = node.GetAsArrayIndex(); node_id = base_id; @@ -549,6 +557,7 @@ auto GetValueRepresentation(const File& file, TypeId type_id) case NodeKind::ArrayInit: case NodeKind::Assign: case NodeKind::BinaryOperatorAdd: + case NodeKind::BindName: case NodeKind::BindValue: case NodeKind::BlockArg: case NodeKind::BoolLiteral: diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index f325443a5d3d..f2eeec858342 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -369,6 +369,11 @@ class NodeNamer { CollectNamesInBlock(scope_idx, block_id); break; } + case NodeKind::BindName: { + auto [name_id, value_id] = node.GetAsBindName(); + add_node_name_id(name_id); + continue; + } case NodeKind::FunctionDeclaration: { add_node_name_id( semantics_ir_.GetFunction(node.GetAsFunctionDeclaration()) diff --git a/toolchain/sem_ir/node.h b/toolchain/sem_ir/node.h index 8d95e0c413e4..5fef8edd311e 100644 --- a/toolchain/sem_ir/node.h +++ b/toolchain/sem_ir/node.h @@ -322,6 +322,9 @@ class Node : public Printable { using BinaryOperatorAdd = Node::Factory; + using BindName = + Factory; + using BindValue = Factory; using BlockArg = Factory; diff --git a/toolchain/sem_ir/node_kind.def b/toolchain/sem_ir/node_kind.def index e2ab0cb2d847..d59040bc0b38 100644 --- a/toolchain/sem_ir/node_kind.def +++ b/toolchain/sem_ir/node_kind.def @@ -51,6 +51,7 @@ CARBON_SEMANTICS_NODE_KIND_IMPL(ArrayInit, "array_init", Typed, NotTerminator) CARBON_SEMANTICS_NODE_KIND_IMPL(ArrayType, "array_type", Typed, NotTerminator) CARBON_SEMANTICS_NODE_KIND_IMPL(Assign, "assign", None, NotTerminator) CARBON_SEMANTICS_NODE_KIND_IMPL(BinaryOperatorAdd, "add", Typed, NotTerminator) +CARBON_SEMANTICS_NODE_KIND_IMPL(BindName, "bind_name", Typed, NotTerminator) CARBON_SEMANTICS_NODE_KIND_IMPL(BindValue, "bind_value", Typed, NotTerminator) CARBON_SEMANTICS_NODE_KIND_IMPL(BlockArg, "block_arg", Typed, NotTerminator) CARBON_SEMANTICS_NODE_KIND_IMPL(BoolLiteral, "bool_literal", Typed,