소스 검색

Let variables (#1095)

* Modify parser and AST nodes to include let statement

* Implement let variables

* Remove redundant code in fail_match_choice test

* Add comment for has_value_category()

* Apply suggestions from code review

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>

* clang-format changes

* Update comments for new BindingPattern methods

* Implement nested vars in patterns

* Apply suggestions from @geoffromer's code review

Co-authored-by: Geoff Romer <gromer@google.com>

* Implement changes from code review.

* Remove maybe_var_pattern grammar rule

Co-authored-by: Jon Meow <46229924+jonmeow@users.noreply.github.com>
Co-authored-by: Geoff Romer <gromer@google.com>
Darshal Shetty 4 년 전
부모
커밋
20b272446f

+ 1 - 0
executable_semantics/ast/ast_rtti.txt

@@ -5,6 +5,7 @@
 root class AstNode;
 abstract class Pattern : AstNode;
   class AutoPattern : Pattern;
+  class VarPattern : Pattern;
   class BindingPattern : Pattern;
   class TuplePattern : Pattern;
   class AlternativePattern : Pattern;

+ 6 - 2
executable_semantics/ast/declaration.h

@@ -251,10 +251,12 @@ class VariableDeclaration : public Declaration {
  public:
   VariableDeclaration(SourceLocation source_loc,
                       Nonnull<BindingPattern*> binding,
-                      std::optional<Nonnull<Expression*>> initializer)
+                      std::optional<Nonnull<Expression*>> initializer,
+                      ValueCategory value_category)
       : Declaration(AstNodeKind::VariableDeclaration, source_loc),
         binding_(binding),
-        initializer_(initializer) {}
+        initializer_(initializer),
+        value_category_(value_category) {}
 
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromVariableDeclaration(node->kind());
@@ -264,6 +266,7 @@ class VariableDeclaration : public Declaration {
   auto binding() -> BindingPattern& { return *binding_; }
   auto initializer() const -> const Expression& { return **initializer_; }
   auto initializer() -> Expression& { return **initializer_; }
+  auto value_category() const -> ValueCategory { return value_category_; }
 
   bool has_initializer() const { return initializer_.has_value(); }
 
@@ -273,6 +276,7 @@ class VariableDeclaration : public Declaration {
   // missing name.
   Nonnull<BindingPattern*> binding_;
   std::optional<Nonnull<Expression*>> initializer_;
+  ValueCategory value_category_;
 };
 
 class InterfaceDeclaration : public Declaration {

+ 6 - 0
executable_semantics/ast/pattern.cpp

@@ -48,6 +48,9 @@ void Pattern::Print(llvm::raw_ostream& out) const {
     case PatternKind::ExpressionPattern:
       out << cast<ExpressionPattern>(*this).expression();
       break;
+    case PatternKind::VarPattern:
+      out << "var" << cast<VarPattern>(*this).pattern();
+      break;
   }
 }
 
@@ -71,6 +74,9 @@ static void GetBindingsImpl(
     case PatternKind::AutoPattern:
     case PatternKind::ExpressionPattern:
       return;
+    case PatternKind::VarPattern:
+      GetBindingsImpl(cast<VarPattern>(pattern).pattern(), bindings);
+      return;
   }
 }
 

+ 41 - 3
executable_semantics/ast/pattern.h

@@ -101,6 +101,24 @@ class AutoPattern : public Pattern {
   }
 };
 
+class VarPattern : public Pattern {
+ public:
+  explicit VarPattern(SourceLocation source_loc, Nonnull<Pattern*> pattern)
+      : Pattern(AstNodeKind::VarPattern, source_loc), pattern_(pattern) {}
+
+  static auto classof(const AstNode* node) -> bool {
+    return InheritsFromVarPattern(node->kind());
+  }
+
+  auto pattern() const -> const Pattern& { return *pattern_; }
+  auto pattern() -> Pattern& { return *pattern_; }
+
+  auto value_category() const -> ValueCategory { return ValueCategory::Var; }
+
+ private:
+  Nonnull<Pattern*> pattern_;
+};
+
 // A pattern that matches a value of a specified type, and optionally binds
 // a name to it.
 class BindingPattern : public Pattern {
@@ -108,10 +126,12 @@ class BindingPattern : public Pattern {
   using ImplementsCarbonValueNode = void;
 
   BindingPattern(SourceLocation source_loc, std::string name,
-                 Nonnull<Pattern*> type)
+                 Nonnull<Pattern*> type,
+                 std::optional<ValueCategory> value_category)
       : Pattern(AstNodeKind::BindingPattern, source_loc),
         name_(std::move(name)),
-        type_(type) {}
+        type_(type),
+        value_category_(value_category) {}
 
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromBindingPattern(node->kind());
@@ -126,7 +146,24 @@ class BindingPattern : public Pattern {
   auto type() const -> const Pattern& { return *type_; }
   auto type() -> Pattern& { return *type_; }
 
-  auto value_category() const -> ValueCategory { return ValueCategory::Var; }
+  // Returns the value category of this pattern. Can only be called after
+  // typechecking.
+  auto value_category() const -> ValueCategory {
+    return value_category_.value();
+  }
+
+  // Returns whether the value category has been set. Should only be called
+  // during typechecking.
+  auto has_value_category() const -> bool {
+    return value_category_.has_value();
+  }
+
+  // Sets the value category of the variable being bound. Can only be called
+  // once during typechecking
+  void set_value_category(ValueCategory vc) {
+    CHECK(!value_category_.has_value());
+    value_category_ = vc;
+  }
 
   auto constant_value() const -> std::optional<Nonnull<const Value*>> {
     return std::nullopt;
@@ -135,6 +172,7 @@ class BindingPattern : public Pattern {
  private:
   std::string name_;
   Nonnull<Pattern*> type_;
+  std::optional<ValueCategory> value_category_;
 };
 
 // A pattern that matches a tuple value field-wise.

+ 5 - 2
executable_semantics/ast/statement.h

@@ -108,10 +108,11 @@ class Assign : public Statement {
 class VariableDefinition : public Statement {
  public:
   VariableDefinition(SourceLocation source_loc, Nonnull<Pattern*> pattern,
-                     Nonnull<Expression*> init)
+                     Nonnull<Expression*> init, ValueCategory value_category)
       : Statement(AstNodeKind::VariableDefinition, source_loc),
         pattern_(pattern),
-        init_(init) {}
+        init_(init),
+        value_category_(value_category) {}
 
   static auto classof(const AstNode* node) -> bool {
     return InheritsFromVariableDefinition(node->kind());
@@ -121,10 +122,12 @@ class VariableDefinition : public Statement {
   auto pattern() -> Pattern& { return *pattern_; }
   auto init() const -> const Expression& { return *init_; }
   auto init() -> Expression& { return *init_; }
+  auto value_category() const -> ValueCategory { return value_category_; }
 
  private:
   Nonnull<Pattern*> pattern_;
   Nonnull<Expression*> init_;
+  ValueCategory value_category_;
 };
 
 class If : public Statement {

+ 7 - 0
executable_semantics/interpreter/interpreter.cpp

@@ -760,6 +760,13 @@ void Interpreter::StepPattern() {
       } else {
         return todo_.FinishAction(act.results()[0]);
       }
+    case PatternKind::VarPattern:
+      if (act.pos() == 0) {
+        return todo_.Spawn(std::make_unique<PatternAction>(
+            &cast<VarPattern>(pattern).pattern()));
+      } else {
+        return todo_.FinishAction(act.results()[0]);
+      }
   }
 }
 

+ 3 - 0
executable_semantics/interpreter/resolve_names.cpp

@@ -180,6 +180,9 @@ static void ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) {
       break;
     case PatternKind::AutoPattern:
       break;
+    case PatternKind::VarPattern:
+      ResolveNames(cast<VarPattern>(pattern).pattern(), enclosing_scope);
+      break;
   }
 }
 

+ 28 - 9
executable_semantics/interpreter/type_checker.cpp

@@ -823,7 +823,7 @@ void TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
 
 void TypeChecker::TypeCheckPattern(
     Nonnull<Pattern*> p, std::optional<Nonnull<const Value*>> expected,
-    const ImplScope& impl_scope) {
+    const ImplScope& impl_scope, ValueCategory enclosing_value_category) {
   if (trace_) {
     llvm::outs() << "checking pattern " << *p;
     if (expected) {
@@ -844,7 +844,8 @@ void TypeChecker::TypeCheckPattern(
         FATAL_COMPILATION_ERROR(binding.type().source_loc())
             << "The type of a binding pattern cannot contain bindings.";
       }
-      TypeCheckPattern(&binding.type(), std::nullopt, impl_scope);
+      TypeCheckPattern(&binding.type(), std::nullopt, impl_scope,
+                       enclosing_value_category);
       Nonnull<const Value*> type =
           InterpPattern(&binding.type(), arena_, trace_);
       if (expected) {
@@ -863,6 +864,10 @@ void TypeChecker::TypeCheckPattern(
       ExpectIsConcreteType(binding.source_loc(), type);
       binding.set_static_type(type);
       SetValue(&binding, InterpPattern(&binding, arena_, trace_));
+
+      if (!binding.has_value_category()) {
+        binding.set_value_category(enclosing_value_category);
+      }
       return;
     }
     case PatternKind::TuplePattern: {
@@ -882,7 +887,8 @@ void TypeChecker::TypeCheckPattern(
         if (expected) {
           expected_field_type = cast<TupleValue>(**expected).elements()[i];
         }
-        TypeCheckPattern(field, expected_field_type, impl_scope);
+        TypeCheckPattern(field, expected_field_type, impl_scope,
+                         enclosing_value_category);
         field_types.push_back(&field->static_type());
       }
       tuple.set_static_type(arena_->New<TupleValue>(std::move(field_types)));
@@ -912,7 +918,8 @@ void TypeChecker::TypeCheckPattern(
             << "'" << alternative.alternative_name()
             << "' is not an alternative of " << choice_type;
       }
-      TypeCheckPattern(&alternative.arguments(), *parameter_types, impl_scope);
+      TypeCheckPattern(&alternative.arguments(), *parameter_types, impl_scope,
+                       enclosing_value_category);
       alternative.set_static_type(&choice_type);
       SetValue(&alternative, InterpPattern(&alternative, arena_, trace_));
       return;
@@ -924,6 +931,14 @@ void TypeChecker::TypeCheckPattern(
       SetValue(p, InterpPattern(p, arena_, trace_));
       return;
     }
+    case PatternKind::VarPattern:
+      auto& let_var_pattern = cast<VarPattern>(*p);
+
+      TypeCheckPattern(&let_var_pattern.pattern(), expected, impl_scope,
+                       let_var_pattern.value_category());
+      let_var_pattern.set_static_type(&let_var_pattern.pattern().static_type());
+      SetValue(&let_var_pattern,
+               InterpPattern(&let_var_pattern, arena_, trace_));
   }
 }
 
@@ -939,7 +954,7 @@ void TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
       std::vector<Match::Clause> new_clauses;
       for (auto& clause : match.clauses()) {
         TypeCheckPattern(&clause.pattern(), &match.expression().static_type(),
-                         impl_scope);
+                         impl_scope, ValueCategory::Let);
         TypeCheckStmt(&clause.statement(), impl_scope);
       }
       return;
@@ -967,7 +982,8 @@ void TypeChecker::TypeCheckStmt(Nonnull<Statement*> s,
       auto& var = cast<VariableDefinition>(*s);
       TypeCheckExp(&var.init(), impl_scope);
       const Value& rhs_ty = var.init().static_type();
-      TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope);
+      TypeCheckPattern(&var.pattern(), &rhs_ty, impl_scope,
+                       var.value_category());
       return;
     }
     case StatementKind::Assign: {
@@ -1117,10 +1133,12 @@ void TypeChecker::DeclareFunctionDeclaration(Nonnull<FunctionDeclaration*> f,
   }
   // Type check the receiver pattern
   if (f->is_method()) {
-    TypeCheckPattern(&f->me_pattern(), std::nullopt, impl_scope);
+    TypeCheckPattern(&f->me_pattern(), std::nullopt, impl_scope,
+                     ValueCategory::Let);
   }
   // Type check the parameter pattern
-  TypeCheckPattern(&f->param_pattern(), std::nullopt, impl_scope);
+  TypeCheckPattern(&f->param_pattern(), std::nullopt, impl_scope,
+                   ValueCategory::Let);
 
   // Create the impl_bindings
   std::vector<Nonnull<const ImplBinding*>> impl_bindings;
@@ -1431,7 +1449,8 @@ void TypeChecker::DeclareDeclaration(Nonnull<Declaration*> d,
       }
       Expression& type =
           cast<ExpressionPattern>(var.binding().type()).expression();
-      TypeCheckPattern(&var.binding(), std::nullopt, impl_scope);
+      TypeCheckPattern(&var.binding(), std::nullopt, impl_scope,
+                       var.value_category());
       Nonnull<const Value*> declared_type = InterpExp(&type, arena_, trace_);
       var.set_static_type(declared_type);
       break;

+ 2 - 1
executable_semantics/interpreter/type_checker.h

@@ -50,7 +50,8 @@ class TypeChecker {
   // nullopt.
   void TypeCheckPattern(Nonnull<Pattern*> p,
                         std::optional<Nonnull<const Value*>> expected,
-                        const ImplScope& impl_scope);
+                        const ImplScope& impl_scope,
+                        ValueCategory enclosing_value_category);
 
   // Equivalent to TypeCheckExp, but operates on the AST rooted at `s`.
   //

+ 2 - 0
executable_semantics/syntax/lexer.lpp

@@ -72,6 +72,7 @@ INTERFACE            "interface"
 LEFT_CURLY_BRACE     "{"
 LEFT_PARENTHESIS     "("
 LEFT_SQUARE_BRACKET  "["
+LET                  "let"
 LIBRARY              "library"
 MATCH                "match"
 MINUS                "-"
@@ -173,6 +174,7 @@ string_literal        \"([^\\\"\n\v\f\r]|\\.)*\"
 {LEFT_CURLY_BRACE}    { return SIMPLE_TOKEN(LEFT_CURLY_BRACE);    }
 {LEFT_PARENTHESIS}    { return SIMPLE_TOKEN(LEFT_PARENTHESIS);    }
 {LEFT_SQUARE_BRACKET} { return SIMPLE_TOKEN(LEFT_SQUARE_BRACKET); }
+{LET}                 { return SIMPLE_TOKEN(LET);                 }
 {LIBRARY}             { return SIMPLE_TOKEN(LIBRARY);             }
 {MATCH}               { return SIMPLE_TOKEN(MATCH);               }
 {MINUS}               { return SIMPLE_TOKEN(MINUS);               }

+ 33 - 6
executable_semantics/syntax/parser.ypp

@@ -72,6 +72,7 @@
   #include "executable_semantics/ast/expression.h"
   #include "executable_semantics/ast/paren_contents.h"
   #include "executable_semantics/ast/pattern.h"
+  #include "executable_semantics/ast/value_category.h"
   #include "executable_semantics/common/arena.h"
   #include "executable_semantics/common/nonnull.h"
   #include "executable_semantics/syntax/bison_wrap.h"
@@ -202,6 +203,7 @@
   LEFT_CURLY_BRACE
   LEFT_PARENTHESIS
   LEFT_SQUARE_BRACKET
+  LET
   LIBRARY
   MATCH
   MINUS
@@ -573,11 +575,16 @@ non_expression_pattern:
   AUTO
     { $$ = arena->New<AutoPattern>(context.source_loc()); }
 | binding_lhs COLON pattern
-    { $$ = arena->New<BindingPattern>(context.source_loc(), $1, $3); }
+    {
+      $$ = arena->New<BindingPattern>(context.source_loc(), $1, $3,
+                                      std::nullopt);
+    }
 | paren_pattern
     { $$ = $1; }
 | postfix_expression tuple_pattern
     { $$ = arena->New<AlternativePattern>(context.source_loc(), $1, $2); }
+| VAR non_expression_pattern
+    { $$ = arena->New<VarPattern>(context.source_loc(), $2); }
 ;
 binding_lhs:
   identifier { $$ = $1; }
@@ -642,7 +649,8 @@ clause:
     {
       $$ = Match::Clause(arena->New<BindingPattern>(
                              context.source_loc(), std::string(AnonymousName),
-                             arena->New<AutoPattern>(context.source_loc())),
+                             arena->New<AutoPattern>(context.source_loc()),
+                             ValueCategory::Let),
                          $3);
     }
 ;
@@ -659,7 +667,15 @@ statement:
   statement_expression EQUAL expression SEMICOLON
     { $$ = arena->New<Assign>(context.source_loc(), $1, $3); }
 | VAR pattern EQUAL expression SEMICOLON
-    { $$ = arena->New<VariableDefinition>(context.source_loc(), $2, $4); }
+    {
+      $$ = arena->New<VariableDefinition>(context.source_loc(), $2, $4,
+                                          ValueCategory::Var);
+    }
+| LET pattern EQUAL expression SEMICOLON
+    {
+      $$ = arena->New<VariableDefinition>(context.source_loc(), $2, $4,
+                                          ValueCategory::Let);
+    }
 | statement_expression SEMICOLON
     { $$ = arena->New<ExpressionStatement>(context.source_loc(), $1); }
 | if_statement
@@ -798,7 +814,10 @@ function_declaration:
     }
 ;
 variable_declaration: identifier COLON pattern
-    { $$ = arena->New<BindingPattern>(context.source_loc(), $1, $3); }
+    {
+      $$ = arena->New<BindingPattern>(context.source_loc(), $1, $3,
+                                      std::nullopt);
+    }
 ;
 alternative:
   identifier tuple
@@ -837,10 +856,18 @@ declaration:
 | VAR variable_declaration SEMICOLON
     {
       $$ = arena->New<VariableDeclaration>(context.source_loc(), $2,
-                                           std::nullopt);
+                                           std::nullopt, ValueCategory::Var);
     }
 | VAR variable_declaration EQUAL expression SEMICOLON
-    { $$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4); }
+    {
+      $$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4,
+                                           ValueCategory::Var);
+    }
+| LET variable_declaration EQUAL expression SEMICOLON
+    {
+      $$ = arena->New<VariableDeclaration>(context.source_loc(), $2, $4,
+                                           ValueCategory::Let);
+    }
 | INTERFACE identifier LEFT_CURLY_BRACE declaration_list RIGHT_CURLY_BRACE
     {
       auto ty_ty = arena -> New<TypeTypeLiteral>(context.source_loc());

+ 23 - 0
executable_semantics/testdata/let/fail_function_args.carbon

@@ -0,0 +1,23 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_function_args.carbon:16: Cannot assign to rvalue 'y'
+
+package ExecutableSemanticsTest api;
+
+fn f((var x: i32, y: i32)) -> i32 {
+  x = 0;
+  y = 0;
+  return x - 1;
+}
+
+fn Main() -> i32 {
+  var t: auto = (1, 2);
+  return f(t);
+}

+ 19 - 0
executable_semantics/testdata/let/fail_global_assign.carbon

@@ -0,0 +1,19 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_global_assign.carbon:17: Cannot assign to rvalue 'x'
+
+package ExecutableSemanticsTest api;
+
+let x: i32 = 10;
+
+fn Main() -> i32 {
+  x = 0;
+  return 0;
+}

+ 18 - 0
executable_semantics/testdata/let/fail_local_assign.carbon

@@ -0,0 +1,18 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_local_assign.carbon:16: Cannot assign to rvalue 'x'
+
+package ExecutableSemanticsTest api;
+
+fn Main() -> i32 {
+  let x: auto = 10;
+  x = 0;
+  return 0;
+}

+ 33 - 0
executable_semantics/testdata/let/fail_match_choice.carbon

@@ -0,0 +1,33 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_match_choice.carbon:29: Cannot assign to rvalue 'x'
+
+package ExecutableSemanticsTest api;
+
+choice Ints {
+  None,
+  One(i32),
+  Two(i32,i32)
+}
+
+fn Main() -> i32 {
+  var x: auto = Ints.None();
+  var n: auto = 0;
+  match (x) {
+    case Ints.One(var x: auto) =>
+      x = 2;
+    case Ints.None() =>
+      n = n - 1;
+    case Ints.Two(x: auto, y: auto) => {
+      x = 0;
+    }
+  }
+  return 0;
+}

+ 32 - 0
executable_semantics/testdata/let/fail_method_args.carbon

@@ -0,0 +1,32 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_method_args.carbon:21: Cannot assign to rvalue 'x'
+
+package ExecutableSemanticsTest api;
+
+class Point {
+
+  fn Origin() -> Point {
+    return {.x = 0, .y = 0};
+  }
+
+  fn SetX[me: Point](x :i32) {
+    x = 10;
+  }
+
+  var x: i32;
+  var y: i32;
+}
+
+fn Main() -> i32 {
+  var p: Point = Point.Origin();
+  p.SetX(42);
+  return 0;
+}

+ 19 - 0
executable_semantics/testdata/let/fail_tuple_pattern_let_context.carbon

@@ -0,0 +1,19 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_tuple_pattern_let_context.carbon:17: Cannot assign to rvalue 'c'
+
+package ExecutableSemanticsTest api;
+
+fn Main() -> i32 {
+  let (var a: auto, b: auto, c:auto, d: auto) = (1, 2, 3, 4);
+  a = 0;
+  c = 0; // should fail
+  return 0;
+}

+ 17 - 0
executable_semantics/testdata/let/fail_tuple_pattern_let_context_nested.carbon

@@ -0,0 +1,17 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_tuple_pattern_let_context_nested.carbon:15: syntax error, unexpected LET
+
+package ExecutableSemanticsTest api;
+
+fn Main() -> i32 {
+  let (var a: auto, b: auto, c:auto, var (d: auto, let e:auto)) = (1, 2, 3, (4, 5));
+  return 0;
+}

+ 17 - 0
executable_semantics/testdata/let/fail_tuple_pattern_let_in_var.carbon

@@ -0,0 +1,17 @@
+// 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
+//
+// RUN: %{not} %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: COMPILATION ERROR: {{.*}}/executable_semantics/testdata/let/fail_tuple_pattern_let_in_var.carbon:15: syntax error, unexpected LET
+
+package ExecutableSemanticsTest api;
+
+fn Main() -> i32 {
+  var (var a: auto, b: auto, let c:auto, d: auto) = (1, 2, 3, 4);
+  return 0;
+}

+ 28 - 0
executable_semantics/testdata/let/nested_tuple_pattern.carbon

@@ -0,0 +1,28 @@
+// 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
+//
+// RUN: %{executable_semantics} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{executable_semantics} --trace %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{executable_semantics} %s
+// CHECK: result: 0
+
+package ExecutableSemanticsTest api;
+
+choice Ints {
+  None,
+  One(i32),
+  Two(i32,i32)
+}
+
+fn Main() -> i32 {
+  let (var Ints.Two(a1: auto, var a2: auto), ((b: auto, var c:auto), var (d: auto, e:auto))) = (Ints.Two(1, 10), ((2, 3), (4, 5)));
+  a1 = 0;
+  a2 = 0;
+  c = 0;
+  d = 0;
+  e = 0;
+  return 0;
+}