From 181a592b8c7519e5fbd3614f0b8674721c2ba4d7 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Fri, 27 Mar 2026 17:06:06 -0700 Subject: [PATCH] Support for parsing expression patterns (#6977) When parsing a pattern, if we encounter something that isn't pattern syntax, try parsing as an expression instead. We only need one-token lookahead to distinguish pattern syntax from expression syntax. Track a precedence group through pattern parsing so that we can allow different kinds of expressions in a top-level pattern (such as the operand of `let`) and in a nested pattern (such as a subpattern of a tuple pattern or within grouping parens). For example, we do not allow `case if ...`, and for now I've chosen to also not allow logical or relational operators at the top level of a pattern, so `case 1 + 1` is OK, but `case 1 == 1` and `case true and false` require parentheses. This decision should be ratified or revisited by a design proposal. Very basic check support is also provided, only sufficient to form an `ExprPattern` instruction and nothing beyond that. For now, all pattern matching against an `ExprPattern` fails with a TODO error. To support that, I've switched from calling `BeginSubpattern` in the parent handler of a pattern and `EndSubpatternAs*` in the pattern handler itself to calling both functions in parent handlers, with `EndSubpattern` converting an expression into an expression pattern where needed. Depends on #6976. Assisted-by: Gemini via Google Antigravity --- toolchain/check/cpp/import.cpp | 9 +- toolchain/check/function.cpp | 6 +- toolchain/check/handle_binding_pattern.cpp | 6 +- toolchain/check/handle_let_and_var.cpp | 10 +- toolchain/check/handle_loop_statement.cpp | 1 + toolchain/check/handle_pattern_list.cpp | 12 +- toolchain/check/pattern.cpp | 45 +++++- toolchain/check/pattern.h | 34 +++-- toolchain/check/pattern_match.cpp | 23 +++ toolchain/check/region_stack.h | 3 + .../check/testdata/impl/error_recovery.carbon | 10 +- .../check/testdata/patterns/expression.carbon | 134 +++++++++++++++++ toolchain/diagnostics/kind.def | 2 + toolchain/docs/check/pattern_matching.md | 4 +- toolchain/parse/context.h | 4 +- toolchain/parse/handle_expr.cpp | 9 +- toolchain/parse/handle_let.cpp | 5 +- toolchain/parse/handle_match.cpp | 4 +- toolchain/parse/handle_pattern.cpp | 68 +++++++-- toolchain/parse/handle_pattern_list.cpp | 14 +- toolchain/parse/handle_statement.cpp | 4 +- toolchain/parse/handle_unused.cpp | 3 +- toolchain/parse/handle_var.cpp | 6 +- toolchain/parse/node_ids.h | 3 +- toolchain/parse/precedence.h | 8 + toolchain/parse/state.def | 20 ++- .../basics/fail_paren_match_regression.carbon | 12 +- .../testdata/for/fail_missing_cond.carbon | 30 ++-- .../testdata/for/fail_returned_var.carbon | 14 +- .../testdata/for/fail_square_brackets.carbon | 14 +- .../testdata/function/declaration.carbon | 16 +- .../testdata/generics/impl/fail_impl.carbon | 14 +- .../interface/fail_self_param_syntax.carbon | 24 +-- .../let/expression_pattern_precedence.carbon | 141 ++++++++++++++++++ .../parse/testdata/let/fail_bad_name.carbon | 8 +- .../parse/testdata/let/fail_empty.carbon | 8 +- .../testdata/let/fail_missing_name.carbon | 17 +-- .../parse/testdata/let/fail_no_semi.carbon | 10 +- ...issing_type.carbon => missing_type.carbon} | 16 +- .../match/fail_missing_case_pattern.carbon | 16 +- .../testdata/package_expr/fail_in_name.carbon | 27 +++- .../parse/testdata/var/fail_bad_name.carbon | 13 +- .../parse/testdata/var/fail_empty.carbon | 10 +- .../parse/testdata/var/fail_no_semi.carbon | 12 +- toolchain/sem_ir/formatter.cpp | 18 +++ toolchain/sem_ir/formatter.h | 1 + toolchain/sem_ir/inst_kind.def | 1 + toolchain/sem_ir/inst_namer.cpp | 8 + toolchain/sem_ir/typed_insts.h | 12 ++ 49 files changed, 679 insertions(+), 210 deletions(-) create mode 100644 toolchain/check/testdata/patterns/expression.carbon create mode 100644 toolchain/parse/testdata/let/expression_pattern_precedence.carbon rename toolchain/parse/testdata/let/{fail_missing_type.carbon => missing_type.carbon} (55%) diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index df1820da3351..3effeec65f79 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -1113,7 +1113,9 @@ static auto MakeImplicitParamPatternsBlockId( auto param_info = MapParameterType(context, loc_id, param_type); auto [type_inst_id, type_id] = param_info.type; SemIR::ExprRegionId type_expr_region_id = - EndSubpatternAsExpr(context, type_inst_id); + ConsumeSubpatternExpr(context, type_inst_id); + + EndEmptySubpattern(context); if (!type_id.has_value()) { context.TODO(loc_id, @@ -1163,7 +1165,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, ClangGetUnqualifiedTypePreserveNonNull(context, orig_param_type); // Mark the start of a region of insts, needed for the type expression - // created later with the call of `EndSubpatternAsExpr()`. + // created later with the call of `ConsumeSubpatternExpr()`. BeginSubpattern(context); auto param_info = MapParameterType(context, loc_id, param_type); auto [type_inst_id, type_id] = param_info.type; @@ -1171,7 +1173,8 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, // region that allows control flow in the type expression e.g. fn F(x: if C // then i32 else i64). SemIR::ExprRegionId type_expr_region_id = - EndSubpatternAsExpr(context, type_inst_id); + ConsumeSubpatternExpr(context, type_inst_id); + EndEmptySubpattern(context); if (!type_id.has_value()) { context.TODO(loc_id, llvm::formatv("Unsupported: parameter type: {0}", diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index 012e7b727ac1..0c67b6d26d12 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -126,8 +126,9 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id, context.full_pattern_stack().StartImplicitParamList(); BeginSubpattern(context); - auto self_type_region_id = EndSubpatternAsExpr( + auto self_type_region_id = ConsumeSubpatternExpr( context, context.types().GetTypeInstId(args.self_type_id)); + EndEmptySubpattern(context); insts.self_param_id = AddParamPattern( context, loc_id, SemIR::NameId::SelfValue, self_type_region_id, @@ -147,8 +148,9 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id, context.inst_block_stack().Push(); for (auto param_type_id : args.param_type_ids) { BeginSubpattern(context); - auto param_type_region_id = EndSubpatternAsExpr( + auto param_type_region_id = ConsumeSubpatternExpr( context, context.types().GetTypeInstId(param_type_id)); + EndEmptySubpattern(context); context.inst_block_stack().AddInstId(AddParamPattern( context, loc_id, SemIR::NameId::Underscore, param_type_region_id, diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index a704a71cddcd..b2f211c9d65b 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -155,7 +155,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id, } SemIR::ExprRegionId type_expr_region_id = - EndSubpatternAsExpr(context, type_expr.inst_id); + ConsumeSubpatternExpr(context, type_expr.inst_id); // The name in a generic binding may be wrapped in `template`. bool is_generic = node_kind == Parse::NodeKind::CompileTimeBindingPattern; @@ -453,7 +453,9 @@ auto HandleParseNode(Context& context, auto [cast_type_inst_id, cast_type_id] = ExprAsType(context, type_node, parsed_type_id); - EndSubpatternAsExpr(context, cast_type_inst_id); + auto region_id = ConsumeSubpatternExpr(context, cast_type_inst_id); + // TODO: Should we be tracking this somewhere? + (void)region_id; auto [name_node, name_id] = context.node_stack().PopNameWithNodeId(); diff --git a/toolchain/check/handle_let_and_var.cpp b/toolchain/check/handle_let_and_var.cpp index 329cae859a15..72d5e5e46e75 100644 --- a/toolchain/check/handle_let_and_var.cpp +++ b/toolchain/check/handle_let_and_var.cpp @@ -131,6 +131,7 @@ auto HandleParseNode(Context& context, Parse::VariablePatternId node_id) // Handle the end of the full-pattern of a let/var declaration (before the // start of the initializer, if any). static auto EndFullPattern(Context& context) -> void { + EndSubpattern(context, context.node_stack()); auto scope_id = context.scope_stack().PeekNameScopeId(); if (scope_id.has_value() && context.name_scopes().Get(scope_id).is_interface_definition()) { @@ -179,10 +180,13 @@ auto HandleParseNode(Context& context, Parse::LetInitializerId node_id) auto HandleParseNode(Context& context, Parse::AssociatedConstantInitializerId node_id) -> bool { + EndFullPattern(context); auto interface_decl = context.scope_stack().GetCurrentScopeAs(); EndAssociatedConstantDeclRegion(context, interface_decl.interface_id); - return HandleInitializer(context, node_id); + context.node_stack().Push(node_id); + StartPatternInitializer(context); + return true; } auto HandleParseNode(Context& context, Parse::VariableInitializerId node_id) @@ -252,6 +256,8 @@ static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo { context.node_stack().PopAndDiscardSoloNodeId(); EndPatternInitializer(context); } else { + EndFullPattern(context); + // For an associated constant declaration, handle the completed declaration // now. We will have done this at the `=` if there was an initializer. if constexpr (IntroducerNodeKind == @@ -262,8 +268,6 @@ static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo { EndAssociatedConstantDeclRegion(context, interface_decl.interface_id); } - EndFullPattern(context); - // A variable declaration without an explicit initializer is initialized by // calling `(T as Core.DefaultOrUnformed).Op()`. if constexpr (IntroducerNodeKind == Parse::NodeKind::VariableIntroducer) { diff --git a/toolchain/check/handle_loop_statement.cpp b/toolchain/check/handle_loop_statement.cpp index ab91e123a493..79625000d8d7 100644 --- a/toolchain/check/handle_loop_statement.cpp +++ b/toolchain/check/handle_loop_statement.cpp @@ -123,6 +123,7 @@ auto HandleParseNode(Context& context, Parse::ForHeaderStartId node_id) } auto HandleParseNode(Context& context, Parse::ForInId node_id) -> bool { + EndSubpattern(context, context.node_stack()); auto pattern_block_id = context.pattern_block_stack().Pop(); AddInst(context, node_id, {.pattern_block_id = pattern_block_id}); diff --git a/toolchain/check/handle_pattern_list.cpp b/toolchain/check/handle_pattern_list.cpp index 8c53a44ebef9..46492fea0a2d 100644 --- a/toolchain/check/handle_pattern_list.cpp +++ b/toolchain/check/handle_pattern_list.cpp @@ -43,7 +43,9 @@ static auto HandleParamListEnd(Context& context, Parse::NodeId node_id, if (context.node_stack().PeekIs(start_kind)) { // End the subpattern started by a trailing comma, or the opening delimiter // of an empty list. - EndSubpatternAsNonExpr(context); + EndEmptySubpattern(context); + } else { + EndSubpattern(context, context.node_stack()); } // Note the Start node remains on the stack, where the param list handler can // make use of it. @@ -67,7 +69,7 @@ auto HandleParseNode(Context& context, Parse::ExplicitParamListId node_id) } auto HandleParseNode(Context& context, Parse::ParenPatternId node_id) -> bool { - EndSubpatternAsNonExpr(context); + EndSubpattern(context, context.node_stack()); auto pattern_id = context.node_stack().PopPattern(); context.param_and_arg_refs_stack().PopAndDiscard(); context.node_stack() @@ -80,7 +82,9 @@ auto HandleParseNode(Context& context, Parse::TuplePatternId node_id) -> bool { if (context.node_stack().PeekIs(Parse::NodeKind::TuplePatternStart)) { // End the subpattern started by a trailing comma, or the opening delimiter // of an empty list. - EndSubpatternAsNonExpr(context); + EndEmptySubpattern(context); + } else { + EndSubpattern(context, context.node_stack()); } auto refs_id = context.param_and_arg_refs_stack().EndAndPop( Parse::NodeKind::TuplePatternStart); @@ -100,12 +104,12 @@ auto HandleParseNode(Context& context, Parse::TuplePatternId node_id) -> bool { node_id, AddPatternInst( context, node_id, {.type_id = type_id, .elements_id = refs_id})); - EndSubpatternAsNonExpr(context); return true; } auto HandleParseNode(Context& context, Parse::PatternListCommaId /*node_id*/) -> bool { + EndSubpattern(context, context.node_stack()); context.param_and_arg_refs_stack().ApplyComma(); BeginSubpattern(context); return true; diff --git a/toolchain/check/pattern.cpp b/toolchain/check/pattern.cpp index 6f1639e7eaf0..4fca17f1fdda 100644 --- a/toolchain/check/pattern.cpp +++ b/toolchain/check/pattern.cpp @@ -14,10 +14,12 @@ namespace Carbon::Check { auto BeginSubpattern(Context& context) -> void { context.inst_block_stack().Push(); + // TODO: This allocates an InstBlockId even in the case where the pattern has + // no associated expression. Find a way to avoid this. context.region_stack().PushRegion(context.inst_block_stack().PeekOrAdd()); } -auto EndSubpatternAsExpr(Context& context, SemIR::InstId result_id) +static auto PopSubpatternExpr(Context& context, SemIR::InstId result_id) -> SemIR::ExprRegionId { if (context.region_stack().PeekRegion().size() > 1) { // End the exit block with a branch to a successor block, whose contents @@ -39,14 +41,45 @@ auto EndSubpatternAsExpr(Context& context, SemIR::InstId result_id) .result_id = result_id}); } -auto EndSubpatternAsNonExpr(Context& context) -> void { - auto block_id = context.inst_block_stack().Pop(); - CARBON_CHECK(block_id == context.region_stack().PeekRegion().back()); - CARBON_CHECK(context.region_stack().PeekRegion().size() == 1); - CARBON_CHECK(context.inst_blocks().Get(block_id).empty()); +auto ConsumeSubpatternExpr(Context& context, SemIR::InstId result_id) + -> SemIR::ExprRegionId { + auto region_id = PopSubpatternExpr(context, result_id); + // Push an empty, unreachable region so that we can later detect the region + // has been consumed. + context.region_stack().PushUnreachableRegion(); + return region_id; +} + +auto EndEmptySubpattern(Context& context) -> void { + if (!context.region_stack().PeekRegion().empty()) { + CARBON_CHECK(context.inst_block_stack().PeekCurrentBlockContents().empty()); + auto block_id = context.inst_block_stack().Pop(); + CARBON_CHECK(block_id == context.region_stack().PeekRegion().back()); + CARBON_CHECK(context.region_stack().PeekRegion().size() == 1); + } context.region_stack().PopAndDiscardRegion(); } +auto EndSubpattern(Context& context, NodeStack& node_stack) -> void { + auto [node_id, maybe_expr_id] = + node_stack.PopWithNodeIdIf(); + if (maybe_expr_id) { + // We formed an expression, not a pattern, so convert it to an expression + // pattern now. + auto expr_region_id = PopSubpatternExpr(context, *maybe_expr_id); + auto pattern_type_id = + GetPatternType(context, context.insts().Get(*maybe_expr_id).type_id()); + node_stack.Push(node_id, AddPatternInst( + context, node_id, + {.type_id = pattern_type_id, + .expr_region_id = expr_region_id})); + } else { + // The expression region should have been consumed when forming the pattern + // instruction, so should now effectively be empty. + EndEmptySubpattern(context); + } +} + auto AddBindingEntityName(Context& context, SemIR::NameId name_id, SemIR::ConstantId form_id, bool is_unused, BindingPhase phase) -> SemIR::EntityNameId { diff --git a/toolchain/check/pattern.h b/toolchain/check/pattern.h index 9208be6aa501..9573b9ac65c0 100644 --- a/toolchain/check/pattern.h +++ b/toolchain/check/pattern.h @@ -10,22 +10,32 @@ namespace Carbon::Check { -// Marks the start of a region of insts in a pattern context that might -// represent an expression or a pattern. Typically this is called when -// handling a parse node that can immediately precede a subpattern (such -// as `let` or a `,` in a pattern list), and the handler for the subpattern -// node makes the matching `EndSubpatternAs*` call. +// Marks the start of a region of insts in a pattern context that might contain +// an expression. Typically this is called when handling a parse node that can +// immediately precede a subpattern (such as `let` or a `,` in a pattern list). +// `End[Empty]Subpattern` should be called later by the consumer of the +// subpattern. auto BeginSubpattern(Context& context) -> void; -// Ends a region started by BeginSubpattern (in stack order), treating it as -// an expression with the given result, and returns the ID of the region. The -// region will not yet have any control-flow edges into or out of it. -auto EndSubpatternAsExpr(Context& context, SemIR::InstId result_id) +// Consumes the expression in a region started by the most recent +// BeginSubpattern, and returns the ID of the region. The region will not yet +// have any control-flow edges into or out of it. +auto ConsumeSubpatternExpr(Context& context, SemIR::InstId result_id) -> SemIR::ExprRegionId; // Ends a region started by BeginSubpattern (in stack order), asserting that -// it had no expression content. -auto EndSubpatternAsNonExpr(Context& context) -> void; +// it either had no expression content or the expression has been consumed. +auto EndEmptySubpattern(Context& context) -> void; + +// Ends a region started by BeginSubpattern (in stack order). If the top of the +// node stack is an expression, the subpattern region is consumed and converted +// to an expression pattern, which replaces the expression on the node stack. +// Otherwise, the top of the node stack should be a pattern, in which case this +// asserts that the subpattern region is either empty or has been consumed. +// +// The node stack is passed explicitly as a reminder that this function affects +// the node stack, unlike the other *Subpattern functions. +auto EndSubpattern(Context& context, NodeStack& node_stack) -> void; // Information about a created binding pattern. struct BindingPatternInfo { @@ -33,8 +43,6 @@ struct BindingPatternInfo { SemIR::InstId bind_id; }; -// TODO: Add EndSubpatternAsPattern, when needed. - // The phase of a binding pattern. enum class BindingPhase { Template, Symbolic, Runtime }; diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index d79c94d18438..b5fb7ad60580 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -152,6 +152,8 @@ class MatchContext { SemIR::InstId scrutinee_id, WorkItem entry) -> void; auto DoPreWork(Context& context, SemIR::AnyParamPattern param_pattern, SemIR::InstId scrutinee_id, WorkItem entry) -> void; + auto DoPreWork(Context& context, SemIR::ExprPattern expr_pattern, + SemIR::InstId scrutinee_id, WorkItem entry) -> void; auto DoPreWork(Context& context, SemIR::ReturnSlotPattern return_slot_pattern, SemIR::InstId scrutinee_id, WorkItem entry) -> void; auto DoPreWork(Context& context, SemIR::VarPattern var_pattern, @@ -167,6 +169,8 @@ class MatchContext { WorkItem entry) -> void; auto DoPostWork(Context& context, SemIR::AnyParamPattern param_pattern, WorkItem entry) -> void; + auto DoPostWork(Context& context, SemIR::ExprPattern expr_pattern, + WorkItem entry) -> void; auto DoPostWork(Context& context, SemIR::ReturnSlotPattern return_slot_pattern, WorkItem entry) -> void; @@ -574,6 +578,17 @@ auto MatchContext::DoPostWork(Context& /*context*/, // would have to be done here. } +auto MatchContext::DoPreWork(Context& context, + SemIR::ExprPattern /*expr_pattern*/, + SemIR::InstId /*scrutinee_id*/, WorkItem entry) + -> void { + context.TODO(entry.pattern_id, "expression pattern"); +} + +auto MatchContext::DoPostWork(Context& /*context*/, + SemIR::ExprPattern /*expr_pattern*/, + WorkItem /*entry*/) -> void {} + auto MatchContext::DoPreWork(Context& /*context*/, SemIR::ReturnSlotPattern return_slot_pattern, SemIR::InstId scrutinee_id, WorkItem entry) @@ -803,6 +818,10 @@ auto MatchContext::Dispatch(Context& context, WorkItem entry) -> void { DoPreWork(context, any_param_pattern, work.scrutinee_id, entry); break; } + case CARBON_KIND(SemIR::ExprPattern expr_pattern): { + DoPreWork(context, expr_pattern, work.scrutinee_id, entry); + break; + } case CARBON_KIND(SemIR::ReturnSlotPattern return_slot_pattern): { DoPreWork(context, return_slot_pattern, work.scrutinee_id, entry); break; @@ -831,6 +850,10 @@ auto MatchContext::Dispatch(Context& context, WorkItem entry) -> void { DoPostWork(context, any_param_pattern, entry); break; } + case CARBON_KIND(SemIR::ExprPattern expr_pattern): { + DoPostWork(context, expr_pattern, entry); + break; + } case CARBON_KIND(SemIR::ReturnSlotPattern return_slot_pattern): { DoPostWork(context, return_slot_pattern, entry); break; diff --git a/toolchain/check/region_stack.h b/toolchain/check/region_stack.h index 0b6229a60746..1f8f88bb92b0 100644 --- a/toolchain/check/region_stack.h +++ b/toolchain/check/region_stack.h @@ -27,6 +27,9 @@ class RegionStack { stack_.AppendToTop(entry_block_id); } + // Mark the start of a new empty, unreachable region. + auto PushUnreachableRegion() -> void { stack_.PushArray(); } + // Add `block_id` to the most recently pushed single-entry region. To preserve // the single-entry property, `block_id` must not be directly reachable from // any block outside the region. To ensure the region's blocks are in lexical diff --git a/toolchain/check/testdata/impl/error_recovery.carbon b/toolchain/check/testdata/impl/error_recovery.carbon index ef54ba799013..4810fe413cb0 100644 --- a/toolchain/check/testdata/impl/error_recovery.carbon +++ b/toolchain/check/testdata/impl/error_recovery.carbon @@ -118,13 +118,17 @@ class C {}; //@dump-sem-ir-begin impl C as I { // This leaves the impl with a placeholder instruction in the witness table. - // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+8]]:11: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern] + // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+12]]:9: error: name `x` not found [NameNotFound] + // CHECK:STDERR: fn Op[x self: C]() {} + // CHECK:STDERR: ^ + // CHECK:STDERR: + // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+8]]:11: error: expected `,` or `]` [UnexpectedTokenAfterListElement] // CHECK:STDERR: fn Op[x self: C]() {} // CHECK:STDERR: ^~~~ // CHECK:STDERR: - // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+4]]:11: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] + // CHECK:STDERR: fail_invalid_fn_syntax_in_impl.carbon:[[@LINE+4]]:8: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo] // CHECK:STDERR: fn Op[x self: C]() {} - // CHECK:STDERR: ^~~~ + // CHECK:STDERR: ^~~~~~~~~~~ // CHECK:STDERR: fn Op[x self: C]() {} } diff --git a/toolchain/check/testdata/patterns/expression.carbon b/toolchain/check/testdata/patterns/expression.carbon new file mode 100644 index 000000000000..18ddbc38cbf2 --- /dev/null +++ b/toolchain/check/testdata/patterns/expression.carbon @@ -0,0 +1,134 @@ +// 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-FILE: toolchain/testing/testdata/min_prelude/none.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/patterns/expression.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/patterns/expression.carbon + +// --- fail_todo_let.carbon + +library "[[@TEST_NAME]]"; + +// TODO: We should reject this for using a refutable pattern in an irrefutable pattern context. +let a: () = (); +// CHECK:STDERR: fail_todo_let.carbon:[[@LINE+4]]:5: error: semantics TODO: `expression pattern` [SemanticsTodo] +// CHECK:STDERR: let a = (); +// CHECK:STDERR: ^ +// CHECK:STDERR: +let a = (); + +// --- fail_todo_in_tuple.carbon + +library "[[@TEST_NAME]]"; + +// TODO: We should reject this for using a refutable pattern in an irrefutable pattern context. +let a: () = (); +// CHECK:STDERR: fail_todo_in_tuple.carbon:[[@LINE+4]]:13: error: semantics TODO: `expression pattern` [SemanticsTodo] +// CHECK:STDERR: let (b: (), a, c: ()) = ((), (), ()); +// CHECK:STDERR: ^ +// CHECK:STDERR: +let (b: (), a, c: ()) = ((), (), ()); + +// --- fail_todo_in_function_param.carbon + +library "[[@TEST_NAME]]"; + +// TODO: We should reject this for using a refutable pattern in an irrefutable pattern context. +let a: () = (); +// CHECK:STDERR: fail_todo_in_function_param.carbon:[[@LINE+4]]:6: error: semantics TODO: `expression pattern` [SemanticsTodo] +// CHECK:STDERR: fn F(a) {} +// CHECK:STDERR: ^ +// CHECK:STDERR: +fn F(a) {} + +// --- fail_todo_parens.carbon + +library "[[@TEST_NAME]]"; + +let a: () = (); +// TODO: Replace this with a valid expression pattern once we support refutable +// pattern contexts. +// Note that this is a parenthesized zero-tuple expression pattern, not a +// one-tuple pattern containing a zero-tuple. +// CHECK:STDERR: fail_todo_parens.carbon:[[@LINE+4]]:6: error: semantics TODO: `expression pattern` [SemanticsTodo] +// CHECK:STDERR: let (a) = (); +// CHECK:STDERR: ^ +// CHECK:STDERR: +let (a) = (); + +// --- fail_todo_control_flow.carbon + +library "[[@TEST_NAME]]"; + +fn F() { + // TODO: Replace this with a valid expression pattern once we support refutable + // pattern contexts. + //@dump-sem-ir-begin + // CHECK:STDERR: fail_todo_control_flow.carbon:[[@LINE+8]]:8: error: semantics TODO: `expression pattern` [SemanticsTodo] + // CHECK:STDERR: let (if true then true else false, true and true) = (true, true); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_todo_control_flow.carbon:[[@LINE+4]]:38: error: semantics TODO: `expression pattern` [SemanticsTodo] + // CHECK:STDERR: let (if true then true else false, true and true) = (true, true); + // CHECK:STDERR: ^~~~~~~~~~~~~ + // CHECK:STDERR: + let (if true then true else false, true and true) = (true, true); + //@dump-sem-ir-end +} + +// CHECK:STDOUT: --- fail_todo_control_flow.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %true: bool = bool_literal true [concrete] +// CHECK:STDOUT: %false: bool = bool_literal false [concrete] +// CHECK:STDOUT: %pattern_type.831: type = pattern_type bool [concrete] +// CHECK:STDOUT: %tuple.type: type = tuple_type (bool, bool) [concrete] +// CHECK:STDOUT: %pattern_type.860: type = pattern_type %tuple.type [concrete] +// CHECK:STDOUT: %tuple: %tuple.type = tuple_value (%true, %true) [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: name_binding_decl { +// CHECK:STDOUT: %expr_patt.loc16_26: %pattern_type.831 = expr_pattern %.loc16_8 in [concrete] { +// CHECK:STDOUT: %true.loc16_11: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: if %true.loc16_11 br !if.expr.then else br !if.expr.else +// CHECK:STDOUT: +// CHECK:STDOUT: !if.expr.then: +// CHECK:STDOUT: %true.loc16_21: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: br !if.expr.result(%true.loc16_21) +// CHECK:STDOUT: +// CHECK:STDOUT: !if.expr.else: +// CHECK:STDOUT: %false.loc16_31: bool = bool_literal false [concrete = constants.%false] +// CHECK:STDOUT: br !if.expr.result(%false.loc16_31) +// CHECK:STDOUT: +// CHECK:STDOUT: !if.expr.result: +// CHECK:STDOUT: %.loc16_8: bool = block_arg !if.expr.result [concrete = constants.%true] +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %expr_patt.loc16_43: %pattern_type.831 = expr_pattern %.loc16_43 in [concrete] { +// CHECK:STDOUT: %true.loc16_38: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: %false.loc16_43: bool = bool_literal false [concrete = constants.%false] +// CHECK:STDOUT: if %true.loc16_38 br !and.rhs else br !.loc16(%false.loc16_43) +// CHECK:STDOUT: +// CHECK:STDOUT: !and.rhs: +// CHECK:STDOUT: %true.loc16_47: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: br !.loc16(%true.loc16_47) +// CHECK:STDOUT: +// CHECK:STDOUT: !.loc16: +// CHECK:STDOUT: %.loc16_43: bool = block_arg !.loc16 [concrete = constants.%true] +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: %.loc16_51: %pattern_type.860 = tuple_pattern (%expr_patt.loc16_26, %expr_patt.loc16_43) [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: %true.loc16_56: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: %true.loc16_62: bool = bool_literal true [concrete = constants.%true] +// CHECK:STDOUT: %.loc16_66: %tuple.type = tuple_literal (%true.loc16_56, %true.loc16_62) [concrete = constants.%tuple] +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index 5513a2b2a4f5..93fbcd5394bb 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -104,8 +104,10 @@ CARBON_DIAGNOSTIC_KIND(ExpectedCodeBlock) CARBON_DIAGNOSTIC_KIND(ExpectedLambdaBody) CARBON_DIAGNOSTIC_KIND(ExpectedLambdaBodyAfterReturnType) CARBON_DIAGNOSTIC_KIND(ExpectedExpr) +CARBON_DIAGNOSTIC_KIND(ExpectedPattern) CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterPeriodOrArrow) CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierOrSelfAfterPeriod) +CARBON_DIAGNOSTIC_KIND(ExpectedBindingName) CARBON_DIAGNOSTIC_KIND(ExpectedBindingPattern) CARBON_DIAGNOSTIC_KIND(ExpectedGenericBindingPatternAfterTemplate) CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter) diff --git a/toolchain/docs/check/pattern_matching.md b/toolchain/docs/check/pattern_matching.md index 7ea209aa204a..4b5226513b85 100644 --- a/toolchain/docs/check/pattern_matching.md +++ b/toolchain/docs/check/pattern_matching.md @@ -125,8 +125,8 @@ be one that emits non-pattern instructions. To handle these situations, we speculatively push an instruction block onto the (non-pattern) stack whenever we are about to begin handling a subpattern, and then pop it at the end of the subpattern, with different treatment depending on whether the subpattern turned -out to be a subexpression. This is handled by `BeginSubpattern`, -`EndSubpatternAsExpr`, and `EndSubpatternAsNonExpr`. +out to involve a subexpression. This is handled by `BeginSubpattern`, +`ConsumeSubpatternExpr`, `EndSubpattern`, and `EndEmptySubpattern`. One further complication here is that the type expression can contain control flow (such as an `if` expression). Consequently, we can't represent the type diff --git a/toolchain/parse/context.h b/toolchain/parse/context.h index e09d36efe87b..be6a238b2cec 100644 --- a/toolchain/parse/context.h +++ b/toolchain/parse/context.h @@ -332,10 +332,12 @@ class Context { // `in_unused_pattern` indicate whether that pattern is nested inside a `var` // or `unused` pattern. auto PushStateForPattern(StateKind kind, bool in_var_pattern, - bool in_unused_pattern) -> void { + bool in_unused_pattern, PrecedenceGroup precedence) + -> void { PushState({.kind = kind, .in_var_pattern = in_var_pattern, .in_unused_pattern = in_unused_pattern, + .ambient_precedence = precedence, .token = *position_, .subtree_start = tree_->size()}); } diff --git a/toolchain/parse/handle_expr.cpp b/toolchain/parse/handle_expr.cpp index d5c563cb0591..0039c552e285 100644 --- a/toolchain/parse/handle_expr.cpp +++ b/toolchain/parse/handle_expr.cpp @@ -246,7 +246,14 @@ auto HandleExprInPostfix(Context& context) -> void { // If not already diagnosed in the lexer, diagnose it here. if (token_kind != Lex::TokenKind::Error) { CARBON_DIAGNOSTIC(ExpectedExpr, Error, "expected expression"); - context.emitter().Emit(*context.position(), ExpectedExpr); + CARBON_DIAGNOSTIC(ExpectedPattern, Error, "expected pattern"); + llvm::SmallVector state_kinds( + llvm::map_range(llvm::ArrayRef(context.state_stack()).take_back(2), + [&](const Context::State& s) { return s.kind; })); + bool in_pattern = state_kinds == llvm::ArrayRef{StateKind::ExprPattern, + StateKind::ExprLoop}; + context.emitter().Emit(*context.position(), + in_pattern ? ExpectedPattern : ExpectedExpr); } // Add a node to keep the parse tree balanced. diff --git a/toolchain/parse/handle_let.cpp b/toolchain/parse/handle_let.cpp index 751a7033bcc1..d90c7111e8ba 100644 --- a/toolchain/parse/handle_let.cpp +++ b/toolchain/parse/handle_let.cpp @@ -17,8 +17,9 @@ auto HandleLet(Context& context) -> void { context.PushState(state, StateKind::LetFinishAsRegular); context.PushState(state, StateKind::LetAfterPatternAsRegular); - // This will start at the pattern. - context.PushState(StateKind::Pattern); + context.PushStateForPattern(StateKind::Pattern, /*in_var_pattern=*/false, + /*in_unused_pattern=*/false, + PrecedenceGroup::ForTopLevelPattern()); } auto HandleAssociatedConstant(Context& context) -> void { diff --git a/toolchain/parse/handle_match.cpp b/toolchain/parse/handle_match.cpp index 053546867d2d..6d41bd515971 100644 --- a/toolchain/parse/handle_match.cpp +++ b/toolchain/parse/handle_match.cpp @@ -142,7 +142,9 @@ auto HandleMatchCaseIntroducer(Context& context) -> void { context.AddLeafNode(NodeKind::MatchCaseIntroducer, context.Consume()); context.PushState(state, StateKind::MatchCaseAfterPattern); - context.PushState(StateKind::Pattern); + context.PushStateForPattern(StateKind::Pattern, /*in_var_pattern=*/false, + /*in_unused_pattern=*/false, + PrecedenceGroup::ForTopLevelPattern()); } auto HandleMatchCaseAfterPattern(Context& context) -> void { diff --git a/toolchain/parse/handle_pattern.cpp b/toolchain/parse/handle_pattern.cpp index 807a8f9113ec..8740e06d01ad 100644 --- a/toolchain/parse/handle_pattern.cpp +++ b/toolchain/parse/handle_pattern.cpp @@ -7,30 +7,80 @@ namespace Carbon::Parse { +static auto IsBindingPatternOperator(Lex::TokenKind kind) -> bool { + return kind == Lex::TokenKind::Colon || + kind == Lex::TokenKind::ColonExclaim || + kind == Lex::TokenKind::ColonQuestion; +} + auto HandlePattern(Context& context) -> void { auto state = context.PopState(); switch (context.PositionKind()) { case Lex::TokenKind::OpenParen: context.PushStateForPattern(StateKind::PatternListAsTuple, - state.in_var_pattern, - state.in_unused_pattern); + state.in_var_pattern, state.in_unused_pattern, + state.ambient_precedence); break; case Lex::TokenKind::Var: context.PushStateForPattern(StateKind::VariablePattern, - state.in_var_pattern, - state.in_unused_pattern); + state.in_var_pattern, state.in_unused_pattern, + state.ambient_precedence); break; case Lex::TokenKind::Unused: context.PushStateForPattern(StateKind::UnusedPattern, - state.in_var_pattern, - state.in_unused_pattern); + state.in_var_pattern, state.in_unused_pattern, + state.ambient_precedence); break; - default: + case Lex::TokenKind::Template: + case Lex::TokenKind::Ref: context.PushStateForPattern(StateKind::BindingPattern, - state.in_var_pattern, - state.in_unused_pattern); + state.in_var_pattern, state.in_unused_pattern, + state.ambient_precedence); + break; + case Lex::TokenKind::Identifier: + case Lex::TokenKind::SelfValueIdentifier: + case Lex::TokenKind::Underscore: { + if (IsBindingPatternOperator( + context.PositionKind(Lookahead::NextToken))) { + context.PushStateForPattern( + StateKind::BindingPattern, state.in_var_pattern, + state.in_unused_pattern, state.ambient_precedence); + break; + } + [[fallthrough]]; + } + default: + context.PushState(StateKind::ExprPattern); + context.PushStateForExpr(state.ambient_precedence); break; } } +auto HandleExprPattern(Context& context) -> void { + auto state = context.PopState(); + + // If we parsed an expression followed by a binding operator, we most likely + // have a malformed attempt to introduce a binding pattern that we interpreted + // as an expression pattern, so diagnose that here rather than diagnosing a + // missing `;` at an outer level. + if (IsBindingPatternOperator(context.PositionKind())) { + if (!state.has_error) { + CARBON_DIAGNOSTIC(ExpectedBindingName, Error, + "unexpected expression before {0} in binding pattern", + Lex::TokenKind); + // TODO: Underline the parsed expression. + context.emitter().Emit(*context.position(), ExpectedBindingName, + context.PositionKind()); + state.has_error = true; + } + context.Consume(); + // It'd be nice to skip the type expression here too, but we can't determine + // the end of it. + } + + if (state.has_error) { + context.ReturnErrorOnState(); + } +} + } // namespace Carbon::Parse diff --git a/toolchain/parse/handle_pattern_list.cpp b/toolchain/parse/handle_pattern_list.cpp index 6a7f44bbbecc..5f05174ae369 100644 --- a/toolchain/parse/handle_pattern_list.cpp +++ b/toolchain/parse/handle_pattern_list.cpp @@ -13,9 +13,11 @@ static auto HandlePatternListElement(Context& context, StateKind pattern_state, auto state = context.PopState(); context.PushStateForPattern(finish_state_kind, state.in_var_pattern, - state.in_unused_pattern); + state.in_unused_pattern, + state.ambient_precedence); context.PushStateForPattern(pattern_state, state.in_var_pattern, - state.in_unused_pattern); + state.in_unused_pattern, + state.ambient_precedence); } auto HandlePatternListElementAsTuple(Context& context) -> void { @@ -58,7 +60,8 @@ static auto HandlePatternListElementFinish(Context& context, if (list_token_kind == Context::ListTokenKind::Comma) { context.PushStateForPattern(param_state_kind, state.in_var_pattern, - state.in_unused_pattern); + state.in_unused_pattern, + state.ambient_precedence); } } @@ -90,12 +93,13 @@ static auto HandlePatternList(Context& context, NodeKind node_kind, context.PushStateForPattern( empty ? finish_state_empty : finish_state_nonempty, state.in_var_pattern, - state.in_unused_pattern); + state.in_unused_pattern, state.ambient_precedence); context.AddLeafNode(node_kind, open_token); if (!empty) { context.PushStateForPattern(param_state, state.in_var_pattern, - state.in_unused_pattern); + state.in_unused_pattern, + PrecedenceGroup::ForTopLevelExpr()); } } diff --git a/toolchain/parse/handle_statement.cpp b/toolchain/parse/handle_statement.cpp index 2b4559e45302..2785b635fe20 100644 --- a/toolchain/parse/handle_statement.cpp +++ b/toolchain/parse/handle_statement.cpp @@ -117,7 +117,9 @@ auto HandleStatementForHeader(Context& context) -> void { state.kind = StateKind::StatementForHeaderIn; context.PushState(state); - context.PushState(StateKind::Pattern); + context.PushStateForPattern(StateKind::Pattern, /*in_var_pattern=*/false, + /*in_unused_pattern=*/false, + PrecedenceGroup::ForTopLevelPattern()); } auto HandleStatementForHeaderIn(Context& context) -> void { diff --git a/toolchain/parse/handle_unused.cpp b/toolchain/parse/handle_unused.cpp index c49a1cb9cd61..79fe87227083 100644 --- a/toolchain/parse/handle_unused.cpp +++ b/toolchain/parse/handle_unused.cpp @@ -19,7 +19,8 @@ auto HandleUnusedPattern(Context& context) -> void { context.ConsumeChecked(Lex::TokenKind::Unused); context.PushStateForPattern(StateKind::Pattern, state.in_var_pattern, - /*in_unused_pattern=*/true); + /*in_unused_pattern=*/true, + state.ambient_precedence); } auto HandleFinishUnusedPattern(Context& context) -> void { diff --git a/toolchain/parse/handle_var.cpp b/toolchain/parse/handle_var.cpp index 9bcdea4cddcf..9af7012ccb09 100644 --- a/toolchain/parse/handle_var.cpp +++ b/toolchain/parse/handle_var.cpp @@ -26,7 +26,8 @@ static auto HandleVar(Context& context, StateKind finish_state_kind, } context.PushStateForPattern(StateKind::Pattern, /*in_var_pattern=*/true, - /*in_unused_pattern=*/false); + /*in_unused_pattern=*/false, + PrecedenceGroup::ForTopLevelPattern()); } auto HandleVarAsRegular(Context& context) -> void { @@ -144,7 +145,8 @@ auto HandleVariablePattern(Context& context) -> void { context.ConsumeChecked(Lex::TokenKind::Var); context.PushStateForPattern(StateKind::Pattern, /*in_var_pattern=*/true, - state.in_unused_pattern); + state.in_unused_pattern, + state.ambient_precedence); } auto HandleFinishVariablePattern(Context& context) -> void { diff --git a/toolchain/parse/node_ids.h b/toolchain/parse/node_ids.h index 4d7d6687af0e..5cda0473d7a7 100644 --- a/toolchain/parse/node_ids.h +++ b/toolchain/parse/node_ids.h @@ -106,7 +106,8 @@ using AnyMemberAccessId = NodeIdInCategory; using AnyModifierId = NodeIdInCategory; -using AnyPatternId = NodeIdInCategory; +using AnyPatternId = + NodeIdInCategory; using AnyStatementId = NodeIdInCategory; using AnyRequireImplsId = NodeIdInCategory; diff --git a/toolchain/parse/precedence.h b/toolchain/parse/precedence.h index e3ffd362af6a..8cd928bcaba4 100644 --- a/toolchain/parse/precedence.h +++ b/toolchain/parse/precedence.h @@ -66,6 +66,10 @@ class PrecedenceGroup { // after `where`, `require`, or `observe`. static auto ForRequirements() -> PrecedenceGroup; + // Get the precedence level for a pattern that is not nested within another + // (eg, tuple or struct) pattern. + static auto ForTopLevelPattern() -> PrecedenceGroup; + // Look up the operator information of the given prefix operator token, or // return std::nullopt if the given token is not a prefix operator. static auto ForLeading(Lex::TokenKind kind) -> std::optional; @@ -182,6 +186,10 @@ inline auto PrecedenceGroup::ForRequirements() -> PrecedenceGroup { return PrecedenceGroup(Where); } +inline auto PrecedenceGroup::ForTopLevelPattern() -> PrecedenceGroup { + return PrecedenceGroup(Relational); +} + } // namespace Carbon::Parse #endif // CARBON_TOOLCHAIN_PARSE_PRECEDENCE_H_ diff --git a/toolchain/parse/state.def b/toolchain/parse/state.def index 23f59ee169e2..999a0eb4da01 100644 --- a/toolchain/parse/state.def +++ b/toolchain/parse/state.def @@ -1145,15 +1145,31 @@ CARBON_PARSE_STATE(ParenExprFinish) // // 1. VariablePattern // -// ... +// unused ... +// ^ +// 1. UnusedPattern +// +// [identifier|self] [:|:!|:?] +// ^ +// template ... +// ^ +// ref ... // ^ // 1. BindingPattern // // ... // ^ -// 1. UnusedPattern +// 1. Expr +// 2. ExprPattern CARBON_PARSE_STATE(Pattern) +// Tracks that we are parsing an expression as an expression pattern. +// +// ... +// ^ +// (state done) +CARBON_PARSE_STATE(ExprPattern) + // Handles the initial part of a binding pattern, enqueuing type expression // processing. // diff --git a/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon b/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon index 38ee1ee8faa0..4986e81b9360 100644 --- a/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon +++ b/toolchain/parse/testdata/basics/fail_paren_match_regression.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/basics/fail_paren_match_regression.carbon -// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+8]]:5: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+8]]:5: error: expected pattern [ExpectedPattern] // CHECK:STDERR: var = (foo {}) // CHECK:STDERR: ^ // CHECK:STDERR: @@ -18,7 +18,7 @@ // CHECK:STDERR: var = (foo {}) -// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+18]]:21: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+16]]:21: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: // CHECK:STDOUT: ] // CHECK:STDERR: ^ // CHECK:STDERR: @@ -26,14 +26,12 @@ var = (foo {}) // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '=', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '=', has_error: yes}, -// CHECK:STDOUT: {kind: 'VarBindingPattern', text: '=', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '=', has_error: yes}, +// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 2}, // CHECK:STDOUT: {kind: 'VariableInitializer', text: '='}, // CHECK:STDOUT: {kind: 'ParenExprStart', text: '('}, // CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'foo'}, // CHECK:STDOUT: {kind: 'ParenExpr', text: ')', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'VariableDecl', text: ')', has_error: yes, subtree_size: 10}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ')', has_error: yes, subtree_size: 8}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/for/fail_missing_cond.carbon b/toolchain/parse/testdata/for/fail_missing_cond.carbon index 1d47161cfa37..7c613ec3f9a3 100644 --- a/toolchain/parse/testdata/for/fail_missing_cond.carbon +++ b/toolchain/parse/testdata/for/fail_missing_cond.carbon @@ -9,16 +9,20 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/for/fail_missing_cond.carbon fn F() { - // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+8]]:7: error: expected `(` after `for` [ExpectedParenAfter] - // CHECK:STDERR: for { - // CHECK:STDERR: ^ - // CHECK:STDERR: - // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+4]]:7: error: expected name in binding pattern [ExpectedBindingPattern] + // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+4]]:7: error: expected `(` after `for` [ExpectedParenAfter] // CHECK:STDERR: for { // CHECK:STDERR: ^ // CHECK:STDERR: for { } +// CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+16]]:1: error: expected `in` after loop pattern [ExpectedIn] +// CHECK:STDERR: } +// CHECK:STDERR: ^ +// CHECK:STDERR: +// CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+12]]:1: error: expected expression [ExpectedExpr] +// CHECK:STDERR: } +// CHECK:STDERR: ^ +// CHECK:STDERR: // CHECK:STDERR: fail_missing_cond.carbon:[[@LINE+8]]:1: error: expected braced code block [ExpectedCodeBlock] // CHECK:STDERR: } // CHECK:STDERR: ^ @@ -38,18 +42,16 @@ fn F() { // CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, // CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, // CHECK:STDOUT: {kind: 'ForHeaderStart', text: 'for', has_error: yes}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '{', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '{', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: '{', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'ForIn', text: 'for', has_error: yes, subtree_size: 4}, -// CHECK:STDOUT: {kind: 'StructLiteralStart', text: '{'}, -// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'ForHeader', text: 'for', has_error: yes, subtree_size: 8}, +// CHECK:STDOUT: {kind: 'StructLiteralStart', text: '{'}, +// CHECK:STDOUT: {kind: 'StructLiteral', text: '}', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ForIn', text: 'for', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '}', has_error: yes}, +// CHECK:STDOUT: {kind: 'ForHeader', text: 'for', has_error: yes, subtree_size: 6}, // CHECK:STDOUT: {kind: 'CodeBlockStart', text: '}', has_error: yes}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '}', has_error: yes}, // CHECK:STDOUT: {kind: 'ExprStatement', text: '}', has_error: yes, subtree_size: 2}, // CHECK:STDOUT: {kind: 'CodeBlock', text: '}', has_error: yes, subtree_size: 4}, -// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 13}, -// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 19}, +// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 11}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 17}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/for/fail_returned_var.carbon b/toolchain/parse/testdata/for/fail_returned_var.carbon index c30b70f9062f..129e357681f3 100644 --- a/toolchain/parse/testdata/for/fail_returned_var.carbon +++ b/toolchain/parse/testdata/for/fail_returned_var.carbon @@ -9,7 +9,7 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/for/fail_returned_var.carbon fn foo() -> i32 { - // CHECK:STDERR: fail_returned_var.carbon:[[@LINE+4]]:8: error: expected name in binding pattern [ExpectedBindingPattern] + // CHECK:STDERR: fail_returned_var.carbon:[[@LINE+4]]:8: error: expected pattern [ExpectedPattern] // CHECK:STDERR: for (returned var x: i32 in y) { // CHECK:STDERR: ^~~~~~~~ // CHECK:STDERR: @@ -29,17 +29,15 @@ fn foo() -> i32 { // CHECK:STDOUT: {kind: 'ReturnType', text: '->', subtree_size: 2}, // CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 7}, // CHECK:STDOUT: {kind: 'ForHeaderStart', text: '('}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'returned', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: 'returned', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: 'returned', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'ForIn', text: '(', has_error: yes, subtree_size: 4}, -// CHECK:STDOUT: {kind: 'ForHeader', text: ')', has_error: yes, subtree_size: 6}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: 'returned', has_error: yes}, +// CHECK:STDOUT: {kind: 'ForIn', text: '(', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'ForHeader', text: ')', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'CodeBlockStart', text: '{'}, // CHECK:STDOUT: {kind: 'ReturnStatementStart', text: 'return'}, // CHECK:STDOUT: {kind: 'ReturnVarModifier', text: 'var'}, // CHECK:STDOUT: {kind: 'ReturnStatement', text: ';', subtree_size: 3}, // CHECK:STDOUT: {kind: 'CodeBlock', text: '}', subtree_size: 5}, -// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 12}, -// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 20}, +// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/for/fail_square_brackets.carbon b/toolchain/parse/testdata/for/fail_square_brackets.carbon index 07c6fa986fe4..5b994a13a25b 100644 --- a/toolchain/parse/testdata/for/fail_square_brackets.carbon +++ b/toolchain/parse/testdata/for/fail_square_brackets.carbon @@ -13,7 +13,7 @@ fn F() { // CHECK:STDERR: for [] { // CHECK:STDERR: ^ // CHECK:STDERR: - // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+16]]:7: error: expected name in binding pattern [ExpectedBindingPattern] + // CHECK:STDERR: fail_square_brackets.carbon:[[@LINE+16]]:7: error: expected pattern [ExpectedPattern] // CHECK:STDERR: for [] { // CHECK:STDERR: ^ // CHECK:STDERR: @@ -42,17 +42,15 @@ fn F() { // CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, // CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, // CHECK:STDOUT: {kind: 'ForHeaderStart', text: 'for', has_error: yes}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '[', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '[', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: '[', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'ForIn', text: 'for', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '[', has_error: yes}, +// CHECK:STDOUT: {kind: 'ForIn', text: 'for', has_error: yes, subtree_size: 2}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '[', has_error: yes}, -// CHECK:STDOUT: {kind: 'ForHeader', text: 'for', has_error: yes, subtree_size: 7}, +// CHECK:STDOUT: {kind: 'ForHeader', text: 'for', has_error: yes, subtree_size: 5}, // CHECK:STDOUT: {kind: 'CodeBlockStart', text: '[', has_error: yes}, // CHECK:STDOUT: {kind: 'InvalidParse', text: '[', has_error: yes}, // CHECK:STDOUT: {kind: 'ExprStatement', text: '}', has_error: yes, subtree_size: 2}, // CHECK:STDOUT: {kind: 'CodeBlock', text: '[', has_error: yes, subtree_size: 4}, -// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 12}, -// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 18}, +// CHECK:STDOUT: {kind: 'ForStatement', text: 'for', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 16}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/function/declaration.carbon b/toolchain/parse/testdata/function/declaration.carbon index 317f8fc65688..49c7a918c2c9 100644 --- a/toolchain/parse/testdata/function/declaration.carbon +++ b/toolchain/parse/testdata/function/declaration.carbon @@ -146,12 +146,8 @@ fn F(); struct X { fn () } fn F(); -// --- fail_with_identifier_as_param.carbon +// --- with_identifier_as_param.carbon -// CHECK:STDERR: fail_with_identifier_as_param.carbon:[[@LINE+4]]:11: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern] -// CHECK:STDERR: fn foo(bar); -// CHECK:STDERR: ^ -// CHECK:STDERR: fn foo(bar); // --- fail_without_name_and_many_tokens_in_params.carbon @@ -426,17 +422,15 @@ fn ComplexReturnForm() ->? X.Y(Z); // CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] -// CHECK:STDOUT: - filename: fail_with_identifier_as_param.carbon +// CHECK:STDOUT: - filename: with_identifier_as_param.carbon // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'IdentifierNameMaybeBeforeSignature', text: 'foo'}, // CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'bar'}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: ')', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: 'bar', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', has_error: yes, subtree_size: 5}, -// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'bar'}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 6}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] // CHECK:STDOUT: - filename: fail_without_name_and_many_tokens_in_params.carbon diff --git a/toolchain/parse/testdata/generics/impl/fail_impl.carbon b/toolchain/parse/testdata/generics/impl/fail_impl.carbon index 71c1999d2746..ea5cb065221b 100644 --- a/toolchain/parse/testdata/generics/impl/fail_impl.carbon +++ b/toolchain/parse/testdata/generics/impl/fail_impl.carbon @@ -54,10 +54,6 @@ impl forall f32; // CHECK:STDERR: impl forall [] u32; -// CHECK:STDERR: fail_impl.carbon:[[@LINE+8]]:21: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern] -// CHECK:STDERR: impl forall [invalid] i8; -// CHECK:STDERR: ^ -// CHECK:STDERR: // CHECK:STDERR: fail_impl.carbon:[[@LINE+4]]:25: error: expected `as` in `impl` declaration [ImplExpectedAs] // CHECK:STDERR: impl forall [invalid] i8; // CHECK:STDERR: ^ @@ -96,7 +92,7 @@ impl; impl -// CHECK:STDERR: fail_impl.carbon:[[@LINE+92]]:21: error: expected expression [ExpectedExpr] +// CHECK:STDERR: fail_impl.carbon:[[@LINE+90]]:21: error: expected expression [ExpectedExpr] // CHECK:STDERR: // CHECK:STDOUT: ] // CHECK:STDERR: ^ // CHECK:STDERR: @@ -136,12 +132,10 @@ impl // CHECK:STDOUT: {kind: 'ImplIntroducer', text: 'impl'}, // CHECK:STDOUT: {kind: 'Forall', text: 'forall'}, // CHECK:STDOUT: {kind: 'ImplicitParamListStart', text: '['}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'invalid'}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: ']', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: 'invalid', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'ImplicitParamList', text: ']', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'invalid'}, +// CHECK:STDOUT: {kind: 'ImplicitParamList', text: ']', subtree_size: 3}, // CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i8'}, -// CHECK:STDOUT: {kind: 'ImplDecl', text: ';', has_error: yes, subtree_size: 9}, +// CHECK:STDOUT: {kind: 'ImplDecl', text: ';', has_error: yes, subtree_size: 7}, // CHECK:STDOUT: {kind: 'ImplIntroducer', text: 'impl'}, // CHECK:STDOUT: {kind: 'Forall', text: 'forall'}, // CHECK:STDOUT: {kind: 'InvalidParse', text: 'f16', has_error: yes}, diff --git a/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon b/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon index ef7579d8bef8..a29b5c7dd8c8 100644 --- a/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon +++ b/toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon @@ -9,16 +9,12 @@ // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon interface Foo { - // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+4]]:13: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern] + // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+4]]:13: error: expected `,` or `]` [UnexpectedTokenAfterListElement] // CHECK:STDERR: fn Sub[me Self](b: Self) -> Self; // CHECK:STDERR: ^~~~ // CHECK:STDERR: fn Sub[me Self](b: Self) -> Self; - // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+4]]:10: error: expected name in binding pattern [ExpectedBindingPattern] - // CHECK:STDERR: fn Mul[Self](b: Self) -> Self; - // CHECK:STDERR: ^~~~ - // CHECK:STDERR: fn Mul[Self](b: Self) -> Self; } @@ -31,10 +27,8 @@ interface Foo { // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'IdentifierNameMaybeBeforeSignature', text: 'Sub'}, // CHECK:STDOUT: {kind: 'ImplicitParamListStart', text: '['}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'me'}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: 'Self', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: 'me', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'ImplicitParamList', text: ']', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'me'}, +// CHECK:STDOUT: {kind: 'ImplicitParamList', text: ']', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, // CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'b'}, // CHECK:STDOUT: {kind: 'SelfTypeNameExpr', text: 'Self'}, @@ -42,14 +36,12 @@ interface Foo { // CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 5}, // CHECK:STDOUT: {kind: 'SelfTypeNameExpr', text: 'Self'}, // CHECK:STDOUT: {kind: 'ReturnType', text: '->', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 15}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 13}, // CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, // CHECK:STDOUT: {kind: 'IdentifierNameMaybeBeforeSignature', text: 'Mul'}, // CHECK:STDOUT: {kind: 'ImplicitParamListStart', text: '['}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'Self', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: 'Self', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: 'Self', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'ImplicitParamList', text: ']', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'SelfTypeNameExpr', text: 'Self'}, +// CHECK:STDOUT: {kind: 'ImplicitParamList', text: ']', subtree_size: 3}, // CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, // CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'b'}, // CHECK:STDOUT: {kind: 'SelfTypeNameExpr', text: 'Self'}, @@ -57,7 +49,7 @@ interface Foo { // CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 5}, // CHECK:STDOUT: {kind: 'SelfTypeNameExpr', text: 'Self'}, // CHECK:STDOUT: {kind: 'ReturnType', text: '->', subtree_size: 2}, -// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 15}, -// CHECK:STDOUT: {kind: 'InterfaceDefinition', text: '}', subtree_size: 34}, +// CHECK:STDOUT: {kind: 'FunctionDecl', text: ';', subtree_size: 13}, +// CHECK:STDOUT: {kind: 'InterfaceDefinition', text: '}', subtree_size: 30}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/expression_pattern_precedence.carbon b/toolchain/parse/testdata/let/expression_pattern_precedence.carbon new file mode 100644 index 000000000000..6557e44df8a5 --- /dev/null +++ b/toolchain/parse/testdata/let/expression_pattern_precedence.carbon @@ -0,0 +1,141 @@ +// 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/let/expression_pattern_precedence.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/expression_pattern_precedence.carbon + +// --- parens.carbon + +fn F() { + let (a == b) = c; + + let (a and b) = c; + + let (if true then 1 else 2) = 3; +} + +// --- fail_relational.carbon + +fn G() { + // CHECK:STDERR: fail_relational.carbon:[[@LINE+4]]:9: error: `let` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: let a == b = c; + // CHECK:STDERR: ^~ + // CHECK:STDERR: + let a == b = c; +} + +// --- fail_logical.carbon + +fn H() { + // CHECK:STDERR: fail_logical.carbon:[[@LINE+4]]:9: error: `let` declarations must end with a `;` [ExpectedDeclSemi] + // CHECK:STDERR: let a and b = c; + // CHECK:STDERR: ^~~ + // CHECK:STDERR: + let a and b = c; +} + +// --- fail_if.carbon + +fn I() { + // CHECK:STDERR: fail_if.carbon:[[@LINE+4]]:7: error: parentheses are required around this unary `if` operator [UnaryOperatorRequiresParentheses] + // CHECK:STDERR: let if true then 1 else 2 = 3; + // CHECK:STDERR: ^~ + // CHECK:STDERR: + let if true then 1 else 2 = 3; +} + +// CHECK:STDOUT: - filename: parens.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameMaybeBeforeSignature', 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: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'InfixOperatorEqualEqual', text: '==', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'ParenPattern', text: ')', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'ShortCircuitOperandAnd', text: 'and', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'b'}, +// CHECK:STDOUT: {kind: 'ShortCircuitOperatorAnd', text: 'and', subtree_size: 4}, +// CHECK:STDOUT: {kind: 'ParenPattern', text: ')', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'c'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'TuplePatternStart', text: '('}, +// CHECK:STDOUT: {kind: 'BoolLiteralTrue', text: 'true'}, +// CHECK:STDOUT: {kind: 'IfExprIf', text: 'if', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '1'}, +// CHECK:STDOUT: {kind: 'IfExprThen', text: 'then', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '2'}, +// CHECK:STDOUT: {kind: 'IfExprElse', text: 'else', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'ParenPattern', text: ')', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '3'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 12}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 37}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_relational.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameMaybeBeforeSignature', 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: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_logical.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameMaybeBeforeSignature', text: 'H'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 9}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] +// CHECK:STDOUT: - filename: fail_if.carbon +// CHECK:STDOUT: parse_tree: [ +// CHECK:STDOUT: {kind: 'FileStart', text: ''}, +// CHECK:STDOUT: {kind: 'FunctionIntroducer', text: 'fn'}, +// CHECK:STDOUT: {kind: 'IdentifierNameMaybeBeforeSignature', text: 'I'}, +// CHECK:STDOUT: {kind: 'ExplicitParamListStart', text: '('}, +// CHECK:STDOUT: {kind: 'ExplicitParamList', text: ')', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, +// CHECK:STDOUT: {kind: 'BoolLiteralTrue', text: 'true'}, +// CHECK:STDOUT: {kind: 'IfExprIf', text: 'if', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '1'}, +// CHECK:STDOUT: {kind: 'IfExprThen', text: 'then', subtree_size: 2}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '2'}, +// CHECK:STDOUT: {kind: 'IfExprElse', text: 'else', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, +// CHECK:STDOUT: {kind: 'IntLiteral', text: '3'}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 10}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 16}, +// CHECK:STDOUT: {kind: 'FileEnd', text: ''}, +// CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/fail_bad_name.carbon b/toolchain/parse/testdata/let/fail_bad_name.carbon index 5412fe9c0f9d..09e591598b16 100644 --- a/toolchain/parse/testdata/let/fail_bad_name.carbon +++ b/toolchain/parse/testdata/let/fail_bad_name.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/fail_bad_name.carbon -// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+4]]:5: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+4]]:5: error: expected pattern [ExpectedPattern] // CHECK:STDERR: let ? = 4; // CHECK:STDERR: ^ // CHECK:STDERR: @@ -18,11 +18,9 @@ let ? = 4; // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '?', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '?', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: '?', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '?', has_error: yes}, // CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, // CHECK:STDOUT: {kind: 'IntLiteral', text: '4'}, -// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 5}, // 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 index b0353c7c4002..2c23f087f3ac 100644 --- a/toolchain/parse/testdata/let/fail_empty.carbon +++ b/toolchain/parse/testdata/let/fail_empty.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/fail_empty.carbon -// CHECK:STDERR: fail_empty.carbon:[[@LINE+4]]:4: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_empty.carbon:[[@LINE+4]]:4: error: expected pattern [ExpectedPattern] // CHECK:STDERR: let; // CHECK:STDERR: ^ // CHECK:STDERR: @@ -18,9 +18,7 @@ let; // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: ';', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: ';', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 5}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 3}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/fail_missing_name.carbon b/toolchain/parse/testdata/let/fail_missing_name.carbon index 103e1d5675e9..dc8860495415 100644 --- a/toolchain/parse/testdata/let/fail_missing_name.carbon +++ b/toolchain/parse/testdata/let/fail_missing_name.carbon @@ -10,7 +10,7 @@ // --- fail_runtime_binding.carbon -// CHECK:STDERR: fail_runtime_binding.carbon:[[@LINE+4]]:5: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_runtime_binding.carbon:[[@LINE+4]]:5: error: expected pattern [ExpectedPattern] // CHECK:STDERR: let : i32 = 4; // CHECK:STDERR: ^ // CHECK:STDERR: @@ -18,7 +18,7 @@ let : i32 = 4; // --- fail_compiletime_binding.carbon -// CHECK:STDERR: fail_compiletime_binding.carbon:[[@LINE+4]]:5: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_compiletime_binding.carbon:[[@LINE+4]]:5: error: expected pattern [ExpectedPattern] // CHECK:STDERR: let :! bool = true; // CHECK:STDERR: ^~ // CHECK:STDERR: @@ -28,24 +28,19 @@ let :! bool = true; // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: ':', has_error: yes}, -// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: ':', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ':', has_error: yes}, // CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, // CHECK:STDOUT: {kind: 'IntLiteral', text: '4'}, -// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] // CHECK:STDOUT: - filename: fail_compiletime_binding.carbon // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: ':!', has_error: yes}, -// CHECK:STDOUT: {kind: 'CompileTimeBindingPatternStart', text: ':!', has_error: yes, subtree_size: 2}, -// CHECK:STDOUT: {kind: 'BoolTypeLiteral', text: 'bool'}, -// CHECK:STDOUT: {kind: 'CompileTimeBindingPattern', text: ':!', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ':!', has_error: yes}, // CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, // CHECK:STDOUT: {kind: 'BoolLiteralTrue', text: 'true'}, -// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 8}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/fail_no_semi.carbon b/toolchain/parse/testdata/let/fail_no_semi.carbon index 3969ef1a5221..34de0813b38e 100644 --- a/toolchain/parse/testdata/let/fail_no_semi.carbon +++ b/toolchain/parse/testdata/let/fail_no_semi.carbon @@ -10,11 +10,11 @@ let -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+17]]:21: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+15]]:21: error: expected pattern [ExpectedPattern] // CHECK:STDERR: // CHECK:STDOUT: ] // CHECK:STDERR: ^ // CHECK:STDERR: -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+13]]:21: error: `let` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+11]]:21: error: `let` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: // CHECK:STDOUT: ] // CHECK:STDERR: ^ // CHECK:STDERR: @@ -22,9 +22,7 @@ let // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: '', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'LetDecl', text: 'let', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '', has_error: yes}, +// CHECK:STDOUT: {kind: 'LetDecl', text: 'let', has_error: yes, subtree_size: 3}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/let/fail_missing_type.carbon b/toolchain/parse/testdata/let/missing_type.carbon similarity index 55% rename from toolchain/parse/testdata/let/fail_missing_type.carbon rename to toolchain/parse/testdata/let/missing_type.carbon index 05193207d443..5a99a53c455a 100644 --- a/toolchain/parse/testdata/let/fail_missing_type.carbon +++ b/toolchain/parse/testdata/let/missing_type.carbon @@ -4,25 +4,19 @@ // // AUTOUPDATE // TIP: To test this file alone, run: -// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/let/fail_missing_type.carbon +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/parse/testdata/let/missing_type.carbon // TIP: To dump output, run: -// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/fail_missing_type.carbon +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/let/missing_type.carbon -// CHECK:STDERR: fail_missing_type.carbon:[[@LINE+4]]:7: error: expected `:`, `:!`, or `:?` in binding pattern [ExpectedBindingPattern] -// CHECK:STDERR: let a = 4; -// CHECK:STDERR: ^ -// CHECK:STDERR: let a = 4; -// CHECK:STDOUT: - filename: fail_missing_type.carbon +// CHECK:STDOUT: - filename: missing_type.carbon // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'LetIntroducer', text: 'let'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'a'}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '=', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: 'a', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'IdentifierNameExpr', text: 'a'}, // CHECK:STDOUT: {kind: 'LetInitializer', text: '='}, // CHECK:STDOUT: {kind: 'IntLiteral', text: '4'}, -// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 7}, +// CHECK:STDOUT: {kind: 'LetDecl', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon b/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon index 036195cc6b7b..391d3e020130 100644 --- a/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon +++ b/toolchain/parse/testdata/match/fail_missing_case_pattern.carbon @@ -10,7 +10,7 @@ fn f() -> i32 { match (3) { - // CHECK:STDERR: fail_missing_case_pattern.carbon:[[@LINE+4]]:10: error: expected name in binding pattern [ExpectedBindingPattern] + // CHECK:STDERR: fail_missing_case_pattern.carbon:[[@LINE+4]]:10: error: expected pattern [ExpectedPattern] // CHECK:STDERR: case => { return 2; } // CHECK:STDERR: ^~ // CHECK:STDERR: @@ -35,16 +35,14 @@ fn f() -> i32 { // CHECK:STDOUT: {kind: 'MatchCondition', text: ')', subtree_size: 3}, // CHECK:STDOUT: {kind: 'MatchStatementStart', text: '{', subtree_size: 5}, // CHECK:STDOUT: {kind: 'MatchCaseIntroducer', text: 'case'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '=>', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '=>', has_error: yes}, -// CHECK:STDOUT: {kind: 'LetBindingPattern', text: '=>', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'MatchCase', text: '=>', has_error: yes, subtree_size: 5}, -// CHECK:STDOUT: {kind: 'MatchHandlerStart', text: '=>', has_error: yes, subtree_size: 6}, -// CHECK:STDOUT: {kind: 'MatchHandler', text: '=>', has_error: yes, subtree_size: 7}, -// CHECK:STDOUT: {kind: 'MatchStatement', text: '}', subtree_size: 13}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '=>', has_error: yes}, +// CHECK:STDOUT: {kind: 'MatchCase', text: '=>', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'MatchHandlerStart', text: '=>', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'MatchHandler', text: '=>', has_error: yes, subtree_size: 5}, +// CHECK:STDOUT: {kind: 'MatchStatement', text: '}', subtree_size: 11}, // CHECK:STDOUT: {kind: 'ReturnStatementStart', text: 'return'}, // CHECK:STDOUT: {kind: 'IntLiteral', text: '0'}, // CHECK:STDOUT: {kind: 'ReturnStatement', text: ';', subtree_size: 3}, -// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 24}, +// CHECK:STDOUT: {kind: 'FunctionDefinition', text: '}', subtree_size: 22}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/package_expr/fail_in_name.carbon b/toolchain/parse/testdata/package_expr/fail_in_name.carbon index d0419889f074..a70a87fac06b 100644 --- a/toolchain/parse/testdata/package_expr/fail_in_name.carbon +++ b/toolchain/parse/testdata/package_expr/fail_in_name.carbon @@ -8,9 +8,20 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/package_expr/fail_in_name.carbon -// CHECK:STDERR: fail_in_name.carbon:[[@LINE+4]]:5: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+4]]:14: error: unexpected expression before : in binding pattern [ExpectedBindingName] +// CHECK:STDERR: var package.n: i32; +// CHECK:STDERR: ^ +// CHECK:STDERR: +var package.n: i32; + +// `val` is a keyword, but during error recovery we treat it as an identifier. +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+8]]:13: error: expected identifier after `.` [ExpectedIdentifierAfterPeriodOrArrow] // CHECK:STDERR: var package.val: i32; -// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: ^~~ +// CHECK:STDERR: +// CHECK:STDERR: fail_in_name.carbon:[[@LINE+4]]:16: error: unexpected expression before : in binding pattern [ExpectedBindingName] +// CHECK:STDERR: var package.val: i32; +// CHECK:STDERR: ^ // CHECK:STDERR: var package.val: i32; @@ -31,9 +42,15 @@ class package.C { // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'package', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: 'package', has_error: yes}, -// CHECK:STDOUT: {kind: 'VarBindingPattern', text: 'package', has_error: yes, subtree_size: 3}, +// CHECK:STDOUT: {kind: 'PackageExpr', text: 'package'}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'n'}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 4}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, +// CHECK:STDOUT: {kind: 'PackageExpr', text: 'package'}, +// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'val', has_error: yes}, +// CHECK:STDOUT: {kind: 'MemberAccessExpr', text: '.', subtree_size: 3}, // CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, // CHECK:STDOUT: {kind: 'NamespaceStart', text: 'namespace'}, diff --git a/toolchain/parse/testdata/var/fail_bad_name.carbon b/toolchain/parse/testdata/var/fail_bad_name.carbon index 7c5e46742221..9acc2a081a6e 100644 --- a/toolchain/parse/testdata/var/fail_bad_name.carbon +++ b/toolchain/parse/testdata/var/fail_bad_name.carbon @@ -8,9 +8,9 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/var/fail_bad_name.carbon -// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+4]]:5: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+4]]:6: error: expected expression [ExpectedExpr] // CHECK:STDERR: var *; -// CHECK:STDERR: ^ +// CHECK:STDERR: ^ // CHECK:STDERR: var *; @@ -18,10 +18,9 @@ var *; // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '*', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '*', has_error: yes}, -// CHECK:STDOUT: {kind: 'VarBindingPattern', text: '*', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 4}, -// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'PrefixOperatorStar', text: '*', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', subtree_size: 3}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 5}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/var/fail_empty.carbon b/toolchain/parse/testdata/var/fail_empty.carbon index de3ada100095..f74b8b038fba 100644 --- a/toolchain/parse/testdata/var/fail_empty.carbon +++ b/toolchain/parse/testdata/var/fail_empty.carbon @@ -8,7 +8,7 @@ // TIP: To dump output, run: // TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/var/fail_empty.carbon -// CHECK:STDERR: fail_empty.carbon:[[@LINE+4]]:4: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_empty.carbon:[[@LINE+4]]:4: error: expected pattern [ExpectedPattern] // CHECK:STDERR: var; // CHECK:STDERR: ^ // CHECK:STDERR: @@ -18,10 +18,8 @@ var; // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: ';', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, -// CHECK:STDOUT: {kind: 'VarBindingPattern', text: ';', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 4}, -// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 6}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: ';', has_error: yes}, +// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 4}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/parse/testdata/var/fail_no_semi.carbon b/toolchain/parse/testdata/var/fail_no_semi.carbon index 70d811235c33..13dbdaaaf165 100644 --- a/toolchain/parse/testdata/var/fail_no_semi.carbon +++ b/toolchain/parse/testdata/var/fail_no_semi.carbon @@ -10,11 +10,11 @@ var -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+18]]:21: error: expected name in binding pattern [ExpectedBindingPattern] +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+16]]:21: error: expected pattern [ExpectedPattern] // CHECK:STDERR: // CHECK:STDOUT: ] // CHECK:STDERR: ^ // CHECK:STDERR: -// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+14]]:21: error: `var` declarations must end with a `;` [ExpectedDeclSemi] +// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+12]]:21: error: `var` declarations must end with a `;` [ExpectedDeclSemi] // CHECK:STDERR: // CHECK:STDOUT: ] // CHECK:STDERR: ^ // CHECK:STDERR: @@ -22,10 +22,8 @@ var // CHECK:STDOUT: parse_tree: [ // CHECK:STDOUT: {kind: 'FileStart', text: ''}, // CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'}, -// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: '', has_error: yes}, -// CHECK:STDOUT: {kind: 'InvalidParse', text: '', has_error: yes}, -// CHECK:STDOUT: {kind: 'VarBindingPattern', text: '', has_error: yes, subtree_size: 3}, -// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 4}, -// CHECK:STDOUT: {kind: 'VariableDecl', text: 'var', has_error: yes, subtree_size: 6}, +// CHECK:STDOUT: {kind: 'InvalidParse', text: '', has_error: yes}, +// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', has_error: yes, subtree_size: 2}, +// CHECK:STDOUT: {kind: 'VariableDecl', text: 'var', has_error: yes, subtree_size: 4}, // CHECK:STDOUT: {kind: 'FileEnd', text: ''}, // CHECK:STDOUT: ] diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index db741bfd946b..87dfb24eaea8 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -1551,6 +1551,24 @@ auto Formatter::FormatArg(AbsoluteInstBlockId id) -> void { FormatArg(static_cast(id)); } +auto Formatter::FormatArg(ExprRegionId id) -> void { + const auto& region = sem_ir_->expr_regions().Get(id); + + FormatArg(region.result_id); + out() << " in "; + OpenBrace(); + for (auto [i, block_id] : llvm::enumerate(region.block_ids)) { + if (i != 0) { + IndentLabel(); + FormatLabel(block_id); + out() << ":\n"; + } + + FormatCodeBlock(block_id); + } + CloseBrace(); +} + auto Formatter::FormatArg(RealId id) -> void { // TODO: Format with a `.` when the exponent is near zero. const auto& real = sem_ir_->reals().Get(id); diff --git a/toolchain/sem_ir/formatter.h b/toolchain/sem_ir/formatter.h index 1150cda7a650..71d286d0bc35 100644 --- a/toolchain/sem_ir/formatter.h +++ b/toolchain/sem_ir/formatter.h @@ -274,6 +274,7 @@ class Formatter { auto FormatArg(NameScopeId id) -> void; auto FormatArg(InstBlockId id) -> void; auto FormatArg(AbsoluteInstBlockId id) -> void; + auto FormatArg(ExprRegionId id) -> void; auto FormatArg(RealId id) -> void; auto FormatArg(StringLiteralValueId id) -> void; auto FormatArg(ConstantId id) -> void { FormatConstant(id); } diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 86b05d2dd359..07e2c13fdac6 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -61,6 +61,7 @@ CARBON_SEM_IR_INST_KIND(CustomWitness) CARBON_SEM_IR_INST_KIND(Deref) CARBON_SEM_IR_INST_KIND(ErrorInst) CARBON_SEM_IR_INST_KIND(ExportDecl) +CARBON_SEM_IR_INST_KIND(ExprPattern) CARBON_SEM_IR_INST_KIND(FacetAccessType) CARBON_SEM_IR_INST_KIND(FacetType) CARBON_SEM_IR_INST_KIND(FacetValue) diff --git a/toolchain/sem_ir/inst_namer.cpp b/toolchain/sem_ir/inst_namer.cpp index ec69febea583..d1d26acc6329 100644 --- a/toolchain/sem_ir/inst_namer.cpp +++ b/toolchain/sem_ir/inst_namer.cpp @@ -1009,6 +1009,14 @@ auto InstNamer::NamingContext::NameInst() -> void { AddInstName("custom_witness"); return; } + case CARBON_KIND(ExprPattern inst): { + for (auto block_id : + sem_ir().expr_regions().Get(inst.expr_region_id).block_ids) { + PushBlockId(scope_id_, block_id); + } + AddInstName("expr_patt"); + return; + } case CARBON_KIND(FacetAccessType inst): { auto name_id = SemIR::NameId::None; if (auto name = diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index 496ae2971247..74f7bc860675 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -630,6 +630,18 @@ struct ExportDecl { InstId value_id; }; +// Represents an expression pattern. +struct ExprPattern { + static constexpr auto Kind = InstKind::ExprPattern.Define( + {.ir_name = "expr_pattern", + .expr_category = ExprCategory::Pattern, + .constant_kind = InstConstantKind::AlwaysUnique, + .is_lowered = false}); + + TypeId type_id; + ExprRegionId expr_region_id; +}; + // Represents accessing the `type` field in a facet value, which is notionally a // pair of a type and a witness. struct FacetAccessType {