Преглед на файлове

Parse support for tuple patterns in `var` and `let` (#3448)

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Geoff Romer преди 2 години
родител
ревизия
39750b9925
променени са 27 файла, в които са добавени 181 реда и са изтрити 104 реда
  1. 1 0
      toolchain/check/handle_binding_pattern.cpp
  2. 3 0
      toolchain/check/handle_let.cpp
  3. 11 3
      toolchain/check/handle_variable.cpp
  4. 1 3
      toolchain/diagnostics/diagnostic_kind.def
  5. 0 10
      toolchain/parse/context.cpp
  6. 0 13
      toolchain/parse/context.h
  7. 14 48
      toolchain/parse/handle_binding_pattern.cpp
  8. 1 1
      toolchain/parse/handle_let.cpp
  9. 2 4
      toolchain/parse/handle_param.cpp
  10. 18 0
      toolchain/parse/handle_pattern.cpp
  11. 1 1
      toolchain/parse/handle_var.cpp
  12. 4 2
      toolchain/parse/node_kind.def
  13. 36 6
      toolchain/parse/state.def
  14. 1 1
      toolchain/parse/testdata/basics/fail_paren_match_regression.carbon
  15. 1 1
      toolchain/parse/testdata/function/declaration/fail_missing_implicit_close.carbon
  16. 1 1
      toolchain/parse/testdata/function/declaration/fail_with_identifier_as_param.carbon
  17. 2 2
      toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon
  18. 1 1
      toolchain/parse/testdata/let/fail_bad_name.carbon
  19. 1 1
      toolchain/parse/testdata/let/fail_empty.carbon
  20. 1 1
      toolchain/parse/testdata/let/fail_missing_type.carbon
  21. 1 1
      toolchain/parse/testdata/let/fail_no_semi.carbon
  22. 38 0
      toolchain/parse/testdata/let/let_tuple.carbon
  23. 1 1
      toolchain/parse/testdata/package_expr/fail_in_name.carbon
  24. 1 1
      toolchain/parse/testdata/var/fail_bad_name.carbon
  25. 1 1
      toolchain/parse/testdata/var/fail_empty.carbon
  26. 1 1
      toolchain/parse/testdata/var/fail_no_semi.carbon
  27. 38 0
      toolchain/parse/testdata/var/var_tuple.carbon

+ 1 - 0
toolchain/check/handle_binding_pattern.cpp

@@ -150,6 +150,7 @@ auto HandleBindingPattern(Context& context, Parse::NodeId parse_node) -> bool {
 }
 
 auto HandleTemplate(Context& context, Parse::NodeId parse_node) -> bool {
+  // TODO: diagnose if this occurs in a `var` context.
   return context.TODO(parse_node, "HandleTemplate");
 }
 

+ 3 - 0
toolchain/check/handle_let.cpp

@@ -11,6 +11,9 @@ namespace Carbon::Check {
 
 auto HandleLetDecl(Context& context, Parse::NodeId parse_node) -> bool {
   auto value_id = context.node_stack().PopExpr();
+  if (context.node_stack().PeekIs<Parse::NodeKind::ParamList>()) {
+    return context.TODO(parse_node, "tuple pattern in let");
+  }
   SemIR::InstId pattern_id =
       context.node_stack().Pop<Parse::NodeKind::BindingPattern>();
   context.node_stack()

+ 11 - 3
toolchain/check/handle_variable.cpp

@@ -34,15 +34,23 @@ auto HandleVariableInitializer(Context& context, Parse::NodeId parse_node)
 auto HandleVariableDecl(Context& context, Parse::NodeId parse_node) -> bool {
   // Handle the optional initializer.
   auto init_id = SemIR::InstId::Invalid;
-  bool has_init =
-      context.parse_tree().node_kind(context.node_stack().PeekParseNode()) !=
-      Parse::NodeKind::BindingPattern;
+  Parse::NodeKind next_kind =
+      context.parse_tree().node_kind(context.node_stack().PeekParseNode());
+  if (next_kind == Parse::NodeKind::ParamList) {
+    return context.TODO(parse_node, "tuple pattern in var");
+  }
+  // TODO: find a more robust way to determine if there was an initializer.
+  bool has_init = next_kind != Parse::NodeKind::BindingPattern;
   if (has_init) {
     init_id = context.node_stack().PopExpr();
     context.node_stack()
         .PopAndDiscardSoloParseNode<Parse::NodeKind::VariableInitializer>();
   }
 
+  if (context.node_stack().PeekIs<Parse::NodeKind::ParamList>()) {
+    return context.TODO(parse_node, "tuple pattern in var");
+  }
+
   // Extract the name binding.
   auto value_id = context.node_stack().Pop<Parse::NodeKind::BindingPattern>();
   if (auto bind_name = context.insts().Get(value_id).TryAs<SemIR::BindName>()) {

+ 1 - 3
toolchain/diagnostics/diagnostic_kind.def

@@ -64,15 +64,13 @@ CARBON_DIAGNOSTIC_KIND(ExpectedCloseSymbol)
 CARBON_DIAGNOSTIC_KIND(ExpectedCodeBlock)
 CARBON_DIAGNOSTIC_KIND(ExpectedExpr)
 CARBON_DIAGNOSTIC_KIND(ExpectedIdentifierAfterDotOrArrow)
-CARBON_DIAGNOSTIC_KIND(ExpectedLetBindingName)
-CARBON_DIAGNOSTIC_KIND(ExpectedParamName)
+CARBON_DIAGNOSTIC_KIND(ExpectedBindingPattern)
 CARBON_DIAGNOSTIC_KIND(ExpectedParenAfter)
 CARBON_DIAGNOSTIC_KIND(ExpectedExprSemi)
 CARBON_DIAGNOSTIC_KIND(ExpectedStatementSemi)
 CARBON_DIAGNOSTIC_KIND(ExpectedStructLiteralField)
 CARBON_DIAGNOSTIC_KIND(ExpectedVarAfterReturned)
 CARBON_DIAGNOSTIC_KIND(ExpectedVariableDecl)
-CARBON_DIAGNOSTIC_KIND(ExpectedVariableName)
 CARBON_DIAGNOSTIC_KIND(OperatorRequiresParentheses)
 CARBON_DIAGNOSTIC_KIND(StatementOperatorAsSubExpr)
 CARBON_DIAGNOSTIC_KIND(UnaryOperatorRequiresParentheses)

+ 0 - 10
toolchain/parse/context.cpp

@@ -166,16 +166,6 @@ auto Context::ConsumeIf(Lex::TokenKind kind) -> std::optional<Lex::TokenIndex> {
   return Consume();
 }
 
-auto Context::ConsumeIfBindingPatternKeyword(Lex::TokenKind keyword_token,
-                                             State keyword_state,
-                                             int subtree_start) -> void {
-  if (auto token = ConsumeIf(keyword_token)) {
-    PushState(Context::StateStackEntry(
-        keyword_state, PrecedenceGroup::ForTopLevelExpr(),
-        PrecedenceGroup::ForTopLevelExpr(), *token, subtree_start));
-  }
-}
-
 auto Context::FindNextOf(std::initializer_list<Lex::TokenKind> desired_kinds)
     -> std::optional<Lex::TokenIndex> {
   auto new_position = position_;

+ 0 - 13
toolchain/parse/context.h

@@ -39,14 +39,6 @@ class Context {
   // Possible return values for FindListToken.
   enum class ListTokenKind : int8_t { Comma, Close, CommaClose };
 
-  // Supported kinds for HandleBindingPattern.
-  enum class BindingPatternKind : int8_t {
-    ImplicitParam,
-    Param,
-    Variable,
-    Let
-  };
-
   // Used for restricting ordering of `package` and `import` directives.
   enum class PackagingState : int8_t {
     FileStart,
@@ -295,11 +287,6 @@ class Context {
   // Propagates an error up the state stack, to the parent state.
   auto ReturnErrorOnState() -> void { state_stack_.back().has_error = true; }
 
-  // For HandleBindingPattern, tries to consume a wrapping keyword.
-  auto ConsumeIfBindingPatternKeyword(Lex::TokenKind keyword_token,
-                                      State keyword_state, int subtree_start)
-      -> void;
-
   // Emits a diagnostic for a declaration missing a semi.
   auto EmitExpectedDeclSemi(Lex::TokenKind expected_kind) -> void;
 

+ 14 - 48
toolchain/parse/handle_binding_pattern.cpp

@@ -6,46 +6,28 @@
 
 namespace Carbon::Parse {
 
-// Handles BindingPatternAs(ImplicitParam|FunctionParam|Variable|Let).
-static auto HandleBindingPattern(Context& context,
-                                 Context::BindingPatternKind pattern_kind)
-    -> void {
+auto HandleBindingPattern(Context& context) -> void {
   auto state = context.PopState();
 
   // Parameters may have keywords prefixing the pattern. They become the parent
   // for the full BindingPattern.
-  if (pattern_kind != Context::BindingPatternKind::Variable) {
-    context.ConsumeIfBindingPatternKeyword(Lex::TokenKind::Template,
-                                           State::BindingPatternTemplate,
-                                           state.subtree_start);
-    context.ConsumeIfBindingPatternKeyword(Lex::TokenKind::Addr,
-                                           State::BindingPatternAddress,
-                                           state.subtree_start);
+  if (auto token = context.ConsumeIf(Lex::TokenKind::Template)) {
+    context.PushState(Context::StateStackEntry(
+        State::BindingPatternTemplate, PrecedenceGroup::ForTopLevelExpr(),
+        PrecedenceGroup::ForTopLevelExpr(), *token, state.subtree_start));
+  }
+
+  if (auto token = context.ConsumeIf(Lex::TokenKind::Addr)) {
+    context.PushState(Context::StateStackEntry(
+        State::BindingPatternAddress, PrecedenceGroup::ForTopLevelExpr(),
+        PrecedenceGroup::ForTopLevelExpr(), *token, state.subtree_start));
   }
 
   // Handle an invalid pattern introducer for parameters and variables.
   auto on_error = [&]() {
-    switch (pattern_kind) {
-      case Context::BindingPatternKind::ImplicitParam:
-      case Context::BindingPatternKind::Param: {
-        CARBON_DIAGNOSTIC(ExpectedParamName, Error,
-                          "Expected parameter declaration.");
-        context.emitter().Emit(*context.position(), ExpectedParamName);
-        break;
-      }
-      case Context::BindingPatternKind::Variable: {
-        CARBON_DIAGNOSTIC(ExpectedVariableName, Error,
-                          "Expected pattern in `var` declaration.");
-        context.emitter().Emit(*context.position(), ExpectedVariableName);
-        break;
-      }
-      case Context::BindingPatternKind::Let: {
-        CARBON_DIAGNOSTIC(ExpectedLetBindingName, Error,
-                          "Expected pattern in `let` declaration.");
-        context.emitter().Emit(*context.position(), ExpectedLetBindingName);
-        break;
-      }
-    }
+    CARBON_DIAGNOSTIC(ExpectedBindingPattern, Error,
+                      "Expected binding pattern.");
+    context.emitter().Emit(*context.position(), ExpectedBindingPattern);
     // Add a placeholder for the type.
     context.AddLeafNode(NodeKind::InvalidParse, *context.position(),
                         /*has_error=*/true);
@@ -89,22 +71,6 @@ static auto HandleBindingPattern(Context& context,
   }
 }
 
-auto HandleBindingPatternAsImplicitParam(Context& context) -> void {
-  HandleBindingPattern(context, Context::BindingPatternKind::ImplicitParam);
-}
-
-auto HandleBindingPatternAsParam(Context& context) -> void {
-  HandleBindingPattern(context, Context::BindingPatternKind::Param);
-}
-
-auto HandleBindingPatternAsVariable(Context& context) -> void {
-  HandleBindingPattern(context, Context::BindingPatternKind::Variable);
-}
-
-auto HandleBindingPatternAsLet(Context& context) -> void {
-  HandleBindingPattern(context, Context::BindingPatternKind::Let);
-}
-
 // Handles BindingPatternFinishAs(Generic|Regular).
 static auto HandleBindingPatternFinish(Context& context, NodeKind node_kind)
     -> void {

+ 1 - 1
toolchain/parse/handle_let.cpp

@@ -16,7 +16,7 @@ auto HandleLet(Context& context) -> void {
   context.PushState(state);
 
   // This will start at the pattern.
-  context.PushState(State::BindingPatternAsLet);
+  context.PushState(State::Pattern);
 }
 
 auto HandleLetAfterPattern(Context& context) -> void {

+ 2 - 4
toolchain/parse/handle_param.cpp

@@ -16,13 +16,11 @@ static auto HandleParam(Context& context, State pattern_state,
 }
 
 auto HandleParamAsImplicit(Context& context) -> void {
-  HandleParam(context, State::BindingPatternAsImplicitParam,
-              State::ParamFinishAsImplicit);
+  HandleParam(context, State::BindingPattern, State::ParamFinishAsImplicit);
 }
 
 auto HandleParamAsRegular(Context& context) -> void {
-  HandleParam(context, State::BindingPatternAsParam,
-              State::ParamFinishAsRegular);
+  HandleParam(context, State::BindingPattern, State::ParamFinishAsRegular);
 }
 
 // Handles ParamFinishAs(Implicit|Regular).

+ 18 - 0
toolchain/parse/handle_pattern.cpp

@@ -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
+
+#include "toolchain/parse/context.h"
+
+namespace Carbon::Parse {
+
+auto HandlePattern(Context& context) -> void {
+  context.PopAndDiscardState();
+  if (context.PositionKind() == Lex::TokenKind::OpenParen) {
+    context.PushState(State::ParamListAsRegular);
+  } else {
+    context.PushState(State::BindingPattern);
+  }
+}
+
+}  // namespace Carbon::Parse

+ 1 - 1
toolchain/parse/handle_var.cpp

@@ -23,7 +23,7 @@ static auto HandleVar(Context& context, State finish_state,
     context.AddLeafNode(NodeKind::ReturnedModifier, returned_token);
   }
 
-  context.PushState(State::BindingPatternAsVariable);
+  context.PushState(State::Pattern);
 }
 
 auto HandleVarAsDecl(Context& context) -> void {

+ 4 - 2
toolchain/parse/node_kind.def

@@ -245,6 +245,8 @@ CARBON_PARSE_NODE_KIND_BRACKET(FunctionDecl, FunctionIntroducer,
 //
 // [Generic]PatternBinding and ParamListComma may repeat with ParamListComma
 // as a separator.
+//
+// TODO: rename ParamList to TuplePattern to reflect wider usage.
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ParamListStart, 0, CARBON_TOKEN(OpenParen))
 CARBON_PARSE_NODE_KIND_CHILD_COUNT(ImplicitParamListStart, 0,
                                    CARBON_TOKEN(OpenSquareBracket))
@@ -285,7 +287,7 @@ CARBON_PARSE_NODE_KIND_CHILD_COUNT(Template, 1, CARBON_TOKEN(Template))
 // `let`:
 //   LetIntroducer
 //   _repeated_ _external_: modifier
-//   _external_: BindingPattern
+//   _external_: BindingPattern or ParamList
 //   LetInitializer
 //   _external_: expression
 // LetDecl
@@ -301,7 +303,7 @@ CARBON_PARSE_NODE_KIND_BRACKET(LetDecl, LetIntroducer,
 //   VariableIntroducer
 //   _repeated_ _external_: modifier
 //   _optional_ ReturnedModifier
-//   _external_: BindingPattern
+//   _external_: BindingPattern or ParamList
 //     VariableInitializer
 //     _external_: expression
 //   _optional_

+ 36 - 6
toolchain/parse/state.def

@@ -649,7 +649,7 @@ CARBON_PARSE_STATE(Package)
 //
 //  ...
 // ^
-//   1. BindingPatternAs(ImplicitParam|Param)
+//   1. BindingPattern
 //   2. ParamFinishAs(Implicit|Regular)
 CARBON_PARSE_STATE_VARIANTS2(Param, Implicit, Regular)
 
@@ -671,6 +671,8 @@ CARBON_PARSE_STATE_VARIANTS2(ParamFinish, Implicit, Regular)
 
 // Handles processing of a parameter list `[` or `(`.
 //
+// TODO: Rename this to TuplePattern to reflect wider usage.
+//
 // ( )        (variant is Regular)
 // ^
 // [ ]        (variant is Implicit)
@@ -759,6 +761,17 @@ CARBON_PARSE_STATE_VARIANTS2(ParenExprParamFinish, Unknown, Tuple)
 //   (state done)
 CARBON_PARSE_STATE_VARIANTS2(ParenExprFinish, Normal, Tuple)
 
+// Handles processing of a pattern.
+//
+//  ( ... )
+// ^
+//   1. ParamListAsRegular
+//
+//  ...
+// ^
+//   1. BindingPattern
+CARBON_PARSE_STATE(Pattern)
+
 // Handles the initial part of a binding pattern, enqueuing type expression
 // processing.
 //
@@ -791,8 +804,7 @@ CARBON_PARSE_STATE_VARIANTS2(ParenExprFinish, Normal, Tuple)
 //  ???
 // ^
 //   1. BindingPatternFinishAsRegular
-CARBON_PARSE_STATE_VARIANTS4(BindingPattern, ImplicitParam, Param, Variable,
-                             Let)
+CARBON_PARSE_STATE(BindingPattern)
 
 // Handles `addr` in a binding pattern.
 //
@@ -1056,13 +1068,25 @@ CARBON_PARSE_STATE(BaseDecl)
 //
 // var ...             (variant is not Returned)
 //    ^
-//   1. BindingPatternAsVariable
+//   1. BindingPattern
+//   2. VarAfterPattern
+//   3. VarFinishAs(Decl|For)
+//
+// var (...)
+//    ^
+//   1. ParamListAsRegular
 //   2. VarAfterPattern
 //   3. VarFinishAs(Decl|For)
 //
 // returned var ...    (variant is Returned)
 // ^~~~~~~~~~~~
-//   1. BindingPatternAsVariable
+//   1. BindingPattern
+//   2. VarAfterPattern
+//   3. VarFinishAsDecl
+//
+// returned var ...    (variant is Returned)
+// ^~~~~~~~~~~~
+//   1. ParamListAsRegular
 //   2. VarAfterPattern
 //   3. VarFinishAsDecl
 //
@@ -1102,9 +1126,15 @@ CARBON_PARSE_STATE_VARIANTS2(VarFinish, Decl, For)
 
 // Handles the start of a `let`.
 //
+// let (...)
+// ^~~
+//   1. ParamListAsRegular
+//   2. LetAfterPattern
+//   3. LetFinish
+//
 // let ...
 //    ^
-//   1. BindingPatternAsLet
+//   1. BindingPattern
 //   2. LetAfterPattern
 //   3. LetFinish
 CARBON_PARSE_STATE(Let)

+ 1 - 1
toolchain/parse/testdata/basics/fail_paren_match_regression.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+6]]:5: ERROR: Expected pattern in `var` declaration.
+// CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+6]]:5: ERROR: Expected binding pattern.
 // CHECK:STDERR: var = (foo {})
 // CHECK:STDERR:     ^
 // CHECK:STDERR: fail_paren_match_regression.carbon:[[@LINE+3]]:12: ERROR: Expected `,` or `)`.

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_missing_implicit_close.carbon

@@ -8,7 +8,7 @@
 // CHECK:STDERR: fail_missing_implicit_close.carbon:[[@LINE+6]]:7: ERROR: Closing symbol does not match most recent opening symbol.
 // CHECK:STDERR: fn Div[();
 // CHECK:STDERR:       ^
-// CHECK:STDERR: fail_missing_implicit_close.carbon:[[@LINE+3]]:8: ERROR: Expected parameter declaration.
+// CHECK:STDERR: fail_missing_implicit_close.carbon:[[@LINE+3]]:8: ERROR: Expected binding pattern.
 // CHECK:STDERR: fn Div[();
 // CHECK:STDERR:        ^
 fn Div[();

+ 1 - 1
toolchain/parse/testdata/function/declaration/fail_with_identifier_as_param.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_with_identifier_as_param.carbon:[[@LINE+3]]:11: ERROR: Expected parameter declaration.
+// CHECK:STDERR: fail_with_identifier_as_param.carbon:[[@LINE+3]]:11: ERROR: Expected binding pattern.
 // CHECK:STDERR: fn foo(bar);
 // CHECK:STDERR:           ^
 fn foo(bar);

+ 2 - 2
toolchain/parse/testdata/generics/interface/fail_self_param_syntax.carbon

@@ -5,12 +5,12 @@
 // AUTOUPDATE
 
 interface Foo {
-  // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+3]]:13: ERROR: Expected parameter declaration.
+  // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+3]]:13: ERROR: Expected binding pattern.
   // CHECK:STDERR:   fn Sub[me Self](b: Self) -> Self;
   // CHECK:STDERR:             ^~~~
   fn Sub[me Self](b: Self) -> Self;
 
-  // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+3]]:10: ERROR: Expected parameter declaration.
+  // CHECK:STDERR: fail_self_param_syntax.carbon:[[@LINE+3]]:10: ERROR: Expected binding pattern.
   // CHECK:STDERR:   fn Mul[Self](b: Self) -> Self;
   // CHECK:STDERR:          ^~~~
   fn Mul[Self](b: Self) -> Self;

+ 1 - 1
toolchain/parse/testdata/let/fail_bad_name.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+3]]:5: ERROR: Expected pattern in `let` declaration.
+// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+3]]:5: ERROR: Expected binding pattern.
 // CHECK:STDERR: let ? = 4;
 // CHECK:STDERR:     ^
 let ? = 4;

+ 1 - 1
toolchain/parse/testdata/let/fail_empty.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_empty.carbon:[[@LINE+3]]:4: ERROR: Expected pattern in `let` declaration.
+// CHECK:STDERR: fail_empty.carbon:[[@LINE+3]]:4: ERROR: Expected binding pattern.
 // CHECK:STDERR: let;
 // CHECK:STDERR:    ^
 let;

+ 1 - 1
toolchain/parse/testdata/let/fail_missing_type.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_missing_type.carbon:[[@LINE+3]]:7: ERROR: Expected pattern in `let` declaration.
+// CHECK:STDERR: fail_missing_type.carbon:[[@LINE+3]]:7: ERROR: Expected binding pattern.
 // CHECK:STDERR: let a = 4;
 // CHECK:STDERR:       ^
 let a = 4;

+ 1 - 1
toolchain/parse/testdata/let/fail_no_semi.carbon

@@ -6,7 +6,7 @@
 
 let
 
-// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+15]]:21: ERROR: Expected pattern in `let` declaration.
+// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+15]]:21: ERROR: Expected binding pattern.
 // CHECK:STDERR: // CHECK:STDOUT:   ]
 // CHECK:STDERR:                     ^
 // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+12]]:21: ERROR: `let` declarations must end with a `;`.

+ 38 - 0
toolchain/parse/testdata/let/let_tuple.carbon

@@ -0,0 +1,38 @@
+// 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() {
+  let (s: String, i: i32) = ("hello", 0);
+}
+
+// CHECK:STDOUT: - filename: let_tuple.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'F'},
+// CHECK:STDOUT:           {kind: 'ParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'LetIntroducer', text: 'let'},
+// CHECK:STDOUT:           {kind: 'ParamListStart', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierName', text: 's'},
+// CHECK:STDOUT:             {kind: 'StringTypeLiteral', text: 'String'},
+// CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'ParamListComma', text: ','},
+// CHECK:STDOUT:             {kind: 'IdentifierName', text: 'i'},
+// CHECK:STDOUT:             {kind: 'IntTypeLiteral', text: 'i32'},
+// CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ParamList', text: ')', subtree_size: 9},
+// CHECK:STDOUT:         {kind: 'LetInitializer', text: '='},
+// CHECK:STDOUT:           {kind: 'ParenExprOrTupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'StringLiteral', text: '"hello"'},
+// CHECK:STDOUT:           {kind: 'TupleLiteralComma', text: ','},
+// CHECK:STDOUT:           {kind: 'IntLiteral', text: '0'},
+// CHECK:STDOUT:         {kind: 'TupleLiteral', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'LetDecl', text: ';', subtree_size: 17},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 23},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 1 - 1
toolchain/parse/testdata/package_expr/fail_in_name.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_in_name.carbon:[[@LINE+3]]:5: ERROR: Expected pattern in `var` declaration.
+// CHECK:STDERR: fail_in_name.carbon:[[@LINE+3]]:5: ERROR: Expected binding pattern.
 // CHECK:STDERR: var package.val: i32;
 // CHECK:STDERR:     ^~~~~~~
 var package.val: i32;

+ 1 - 1
toolchain/parse/testdata/var/fail_bad_name.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+3]]:5: ERROR: Expected pattern in `var` declaration.
+// CHECK:STDERR: fail_bad_name.carbon:[[@LINE+3]]:5: ERROR: Expected binding pattern.
 // CHECK:STDERR: var *;
 // CHECK:STDERR:     ^
 var *;

+ 1 - 1
toolchain/parse/testdata/var/fail_empty.carbon

@@ -4,7 +4,7 @@
 //
 // AUTOUPDATE
 
-// CHECK:STDERR: fail_empty.carbon:[[@LINE+3]]:4: ERROR: Expected pattern in `var` declaration.
+// CHECK:STDERR: fail_empty.carbon:[[@LINE+3]]:4: ERROR: Expected binding pattern.
 // CHECK:STDERR: var;
 // CHECK:STDERR:    ^
 var;

+ 1 - 1
toolchain/parse/testdata/var/fail_no_semi.carbon

@@ -6,7 +6,7 @@
 
 var
 
-// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+15]]:21: ERROR: Expected pattern in `var` declaration.
+// CHECK:STDERR: fail_no_semi.carbon:[[@LINE+15]]:21: ERROR: Expected binding pattern.
 // CHECK:STDERR: // CHECK:STDOUT:   ]
 // CHECK:STDERR:                     ^
 // CHECK:STDERR: fail_no_semi.carbon:[[@LINE+12]]:21: ERROR: `var` declarations must end with a `;`.

+ 38 - 0
toolchain/parse/testdata/var/var_tuple.carbon

@@ -0,0 +1,38 @@
+// 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() {
+  var (s: String, i: 32) = ("hello", 0);
+}
+
+// CHECK:STDOUT: - filename: var_tuple.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:         {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:         {kind: 'IdentifierName', text: 'F'},
+// CHECK:STDOUT:           {kind: 'ParamListStart', text: '('},
+// CHECK:STDOUT:         {kind: 'ParamList', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'FunctionDefinitionStart', text: '{', subtree_size: 5},
+// CHECK:STDOUT:         {kind: 'VariableIntroducer', text: 'var'},
+// CHECK:STDOUT:           {kind: 'ParamListStart', text: '('},
+// CHECK:STDOUT:             {kind: 'IdentifierName', text: 's'},
+// CHECK:STDOUT:             {kind: 'StringTypeLiteral', text: 'String'},
+// CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:           {kind: 'ParamListComma', text: ','},
+// CHECK:STDOUT:             {kind: 'IdentifierName', text: 'i'},
+// CHECK:STDOUT:             {kind: 'IntLiteral', text: '32'},
+// CHECK:STDOUT:           {kind: 'BindingPattern', text: ':', subtree_size: 3},
+// CHECK:STDOUT:         {kind: 'ParamList', text: ')', subtree_size: 9},
+// CHECK:STDOUT:         {kind: 'VariableInitializer', text: '='},
+// CHECK:STDOUT:           {kind: 'ParenExprOrTupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'StringLiteral', text: '"hello"'},
+// CHECK:STDOUT:           {kind: 'TupleLiteralComma', text: ','},
+// CHECK:STDOUT:           {kind: 'IntLiteral', text: '0'},
+// CHECK:STDOUT:         {kind: 'TupleLiteral', text: ')', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'VariableDecl', text: ';', subtree_size: 17},
+// CHECK:STDOUT:     {kind: 'FunctionDefinition', text: '}', subtree_size: 23},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]