소스 검색

Add support for `var` patterns (#5069)

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
Geoff Romer 1 년 전
부모
커밋
6d4f2567a7

+ 6 - 4
toolchain/check/handle_binding_pattern.cpp

@@ -15,6 +15,7 @@
 #include "toolchain/diagnostics/format_providers.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
+#include "toolchain/sem_ir/pattern.h"
 
 namespace Carbon::Check {
 
@@ -193,8 +194,6 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
     case FullPatternStack::Kind::ExplicitParamList: {
       // Parameters can have incomplete types in a function declaration, but not
       // in a function definition. We don't know which kind we have here.
-      // TODO: A tuple pattern can appear in other places than function
-      // parameters.
       bool had_error = false;
       switch (introducer.kind) {
         case Lex::TokenKind::Fn: {
@@ -250,6 +249,10 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
       } else {
         result_inst_id = make_binding_pattern();
         if (node_kind == Parse::NodeKind::LetBindingPattern) {
+          // A value binding pattern in a function signature is a `Call`
+          // parameter, but a variable binding pattern is not (instead the
+          // enclosing `var` pattern is), and a symbolic binding pattern is not
+          // (because it's not passed to the `Call` inst).
           result_inst_id = AddPatternInst<SemIR::ValueParamPattern>(
               context, node_id,
               {.type_id = context.insts().Get(result_inst_id).type_id(),
@@ -347,8 +350,7 @@ auto HandleParseNode(Context& context,
 
 auto HandleParseNode(Context& context, Parse::AddrId node_id) -> bool {
   auto param_pattern_id = context.node_stack().PopPattern();
-  if (SemIR::Function::GetNameFromPatternId(
-          context.sem_ir(), param_pattern_id) == SemIR::NameId::SelfValue) {
+  if (SemIR::IsSelfPattern(context.sem_ir(), param_pattern_id)) {
     auto pointer_type = context.types().TryGetAs<SemIR::PointerType>(
         context.insts().Get(param_pattern_id).type_id());
     if (pointer_type) {

+ 6 - 7
toolchain/check/handle_function.cpp

@@ -26,6 +26,7 @@
 #include "toolchain/sem_ir/entry_point.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/pattern.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
@@ -267,13 +268,11 @@ static auto BuildFunctionDecl(Context& context,
   auto self_param_id = SemIR::InstId::None;
   auto implicit_param_patterns =
       context.inst_blocks().GetOrEmpty(name.implicit_param_patterns_id);
-  if (const auto* i =
-          llvm::find_if(implicit_param_patterns,
-                        [&](auto implicit_param_id) {
-                          return SemIR::Function::GetNameFromPatternId(
-                                     context.sem_ir(), implicit_param_id) ==
-                                 SemIR::NameId::SelfValue;
-                        });
+  if (const auto* i = llvm::find_if(implicit_param_patterns,
+                                    [&](auto implicit_param_id) {
+                                      return SemIR::IsSelfPattern(
+                                          context.sem_ir(), implicit_param_id);
+                                    });
       i != implicit_param_patterns.end()) {
     self_param_id = *i;
   }

+ 27 - 16
toolchain/check/handle_let_and_var.cpp

@@ -21,6 +21,7 @@
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/name_scope.h"
+#include "toolchain/sem_ir/pattern.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
@@ -102,10 +103,10 @@ auto HandleParseNode(Context& context, Parse::VariableIntroducerId node_id)
   return HandleIntroducer<Lex::TokenKind::Var>(context, node_id);
 }
 
-// Returns a VarStorage inst for the given pattern. If the pattern
+// Returns a VarStorage inst for the given `var` pattern. If the pattern
 // is the body of a returned var, this reuses the return slot, and otherwise it
 // adds a new inst.
-static auto GetOrAddStorage(Context& context, SemIR::InstId pattern_id)
+static auto GetOrAddStorage(Context& context, SemIR::InstId var_pattern_id)
     -> SemIR::InstId {
   if (context.decl_introducer_state_stack().innermost().modifier_set.HasAnyOf(
           KeywordModifierSet::Returned)) {
@@ -116,29 +117,39 @@ static auto GetOrAddStorage(Context& context, SemIR::InstId pattern_id)
       return GetCurrentReturnSlot(context);
     }
   }
-  auto pattern = context.insts().GetWithLocId(pattern_id);
-  auto subpattern =
-      context.insts().Get(pattern.inst.As<SemIR::VarPattern>().subpattern_id);
-
-  // Try to populate name_id on a best-effort basis.
-  auto name_id = SemIR::NameId::None;
-  if (auto binding_pattern = subpattern.TryAs<SemIR::BindingPattern>()) {
-    name_id =
-        context.entity_names().Get(binding_pattern->entity_name_id).name_id;
-  }
+  auto pattern = context.insts().GetWithLocId(var_pattern_id);
+
   return AddInst(
       context,
       SemIR::LocIdAndInst::UncheckedLoc(
-          pattern.loc_id, SemIR::VarStorage{.type_id = pattern.inst.type_id(),
-                                            .pretty_name_id = name_id}));
+          pattern.loc_id,
+          SemIR::VarStorage{
+              .type_id = pattern.inst.type_id(),
+              .pretty_name_id = SemIR::GetPrettyNameFromPatternId(
+                  context.sem_ir(),
+                  pattern.inst.As<SemIR::VarPattern>().subpattern_id)}));
 }
 
 auto HandleParseNode(Context& context, Parse::VariablePatternId node_id)
     -> bool {
-  auto subpattern_id = SemIR::InstId::None;
-  subpattern_id = context.node_stack().PopPattern();
+  auto subpattern_id = context.node_stack().PopPattern();
   auto type_id = context.insts().Get(subpattern_id).type_id();
 
+  // In a parameter list, a `var` pattern is always a single `Call` parameter,
+  // even if it contains multiple binding patterns.
+  switch (context.full_pattern_stack().CurrentKind()) {
+    case FullPatternStack::Kind::ExplicitParamList:
+    case FullPatternStack::Kind::ImplicitParamList:
+      subpattern_id = AddPatternInst<SemIR::RefParamPattern>(
+          context, node_id,
+          {.type_id = type_id,
+           .subpattern_id = subpattern_id,
+           .index = SemIR::CallParamIndex::None});
+      break;
+    case FullPatternStack::Kind::NameBindingDecl:
+      break;
+  }
+
   auto pattern_id = AddPatternInst<SemIR::VarPattern>(
       context, node_id, {.type_id = type_id, .subpattern_id = subpattern_id});
   context.node_stack().Push(node_id, pattern_id);

+ 97 - 36
toolchain/check/pattern_match.cpp

@@ -15,25 +15,10 @@
 #include "toolchain/check/subpattern.h"
 #include "toolchain/check/type.h"
 #include "toolchain/diagnostics/format_providers.h"
+#include "toolchain/sem_ir/pattern.h"
 
 namespace Carbon::Check {
 
-// Returns a best-effort name for the given ParamPattern, suitable for use in
-// IR pretty-printing.
-template <typename ParamPattern>
-static auto GetPrettyName(Context& context, ParamPattern param_pattern)
-    -> SemIR::NameId {
-  if (context.insts().Is<SemIR::ReturnSlotPattern>(
-          param_pattern.subpattern_id)) {
-    return SemIR::NameId::ReturnSlot;
-  }
-  if (auto binding_pattern = context.insts().TryGetAs<SemIR::AnyBindingPattern>(
-          param_pattern.subpattern_id)) {
-    return context.entity_names().Get(binding_pattern->entity_name_id).name_id;
-  }
-  return SemIR::NameId::None;
-}
-
 namespace {
 
 // Selects between the different kinds of pattern matching.
@@ -110,6 +95,9 @@ class MatchContext {
   auto DoEmitPatternMatch(Context& context,
                           SemIR::ValueParamPattern param_pattern,
                           SemIR::LocId pattern_loc_id, WorkItem entry) -> void;
+  auto DoEmitPatternMatch(Context& context,
+                          SemIR::RefParamPattern param_pattern,
+                          SemIR::LocId pattern_loc_id, WorkItem entry) -> void;
   auto DoEmitPatternMatch(Context& context,
                           SemIR::OutParamPattern param_pattern,
                           SemIR::LocId pattern_loc_id, WorkItem entry) -> void;
@@ -274,13 +262,12 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
                                       SemIR::ValueParamPattern param_pattern,
                                       SemIR::LocId pattern_loc_id,
                                       WorkItem entry) -> void {
-  CARBON_CHECK(
-      param_pattern.index.index < 0 ||
-          static_cast<size_t>(param_pattern.index.index) == results_.size(),
-      "Parameters out of order; expecting {0} but got {1}", results_.size(),
-      param_pattern.index.index);
   switch (kind_) {
     case MatchKind::Caller: {
+      CARBON_CHECK(
+          static_cast<size_t>(param_pattern.index.index) == results_.size(),
+          "Parameters out of order; expecting {0} but got {1}", results_.size(),
+          param_pattern.index.index);
       CARBON_CHECK(entry.scrutinee_id.has_value());
       if (entry.scrutinee_id == SemIR::ErrorInst::SingletonInstId) {
         results_.push_back(SemIR::ErrorInst::SingletonInstId);
@@ -296,15 +283,15 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
       break;
     }
     case MatchKind::Callee: {
-      if (!param_pattern.index.has_value()) {
-        param_pattern.index = NextRuntimeIndex();
-        ReplaceInstBeforeConstantUse(context, entry.pattern_id, param_pattern);
-      }
+      CARBON_CHECK(!param_pattern.index.has_value());
+      param_pattern.index = NextRuntimeIndex();
+      ReplaceInstBeforeConstantUse(context, entry.pattern_id, param_pattern);
       auto param_id = AddInst<SemIR::ValueParam>(
           context, pattern_loc_id,
           {.type_id = param_pattern.type_id,
            .index = param_pattern.index,
-           .pretty_name_id = GetPrettyName(context, param_pattern)});
+           .pretty_name_id = SemIR::GetPrettyNameFromPatternId(
+               context.sem_ir(), entry.pattern_id)});
       AddWork({.pattern_id = param_pattern.subpattern_id,
                .scrutinee_id = param_id});
       results_.push_back(param_id);
@@ -316,12 +303,57 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
   }
 }
 
+auto MatchContext::DoEmitPatternMatch(Context& context,
+                                      SemIR::RefParamPattern param_pattern,
+                                      SemIR::LocId pattern_loc_id,
+                                      WorkItem entry) -> void {
+  switch (kind_) {
+    case MatchKind::Caller: {
+      CARBON_CHECK(
+          static_cast<size_t>(param_pattern.index.index) == results_.size(),
+          "Parameters out of order; expecting {0} but got {1}", results_.size(),
+          param_pattern.index.index);
+      CARBON_CHECK(entry.scrutinee_id.has_value());
+      auto expr_category =
+          SemIR::GetExprCategory(context.sem_ir(), entry.scrutinee_id);
+      CARBON_CHECK(expr_category == SemIR::ExprCategory::EphemeralRef ||
+                   expr_category == SemIR::ExprCategory::DurableRef);
+      results_.push_back(entry.scrutinee_id);
+      // Do not traverse farther, because the caller side of the pattern
+      // ends here.
+      break;
+    }
+    case MatchKind::Callee: {
+      CARBON_CHECK(!param_pattern.index.has_value());
+      param_pattern.index = NextRuntimeIndex();
+      ReplaceInstBeforeConstantUse(context, entry.pattern_id, param_pattern);
+      auto param_id = AddInst<SemIR::RefParam>(
+          context, pattern_loc_id,
+          {.type_id = param_pattern.type_id,
+           .index = param_pattern.index,
+           .pretty_name_id = SemIR::GetPrettyNameFromPatternId(
+               context.sem_ir(), entry.pattern_id)});
+      AddWork({.pattern_id = param_pattern.subpattern_id,
+               .scrutinee_id = param_id});
+      results_.push_back(param_id);
+      break;
+    }
+    case MatchKind::Local: {
+      CARBON_FATAL("Found RefParamPattern during local pattern match");
+    }
+  }
+}
+
 auto MatchContext::DoEmitPatternMatch(Context& context,
                                       SemIR::OutParamPattern param_pattern,
                                       SemIR::LocId pattern_loc_id,
                                       WorkItem entry) -> void {
   switch (kind_) {
     case MatchKind::Caller: {
+      CARBON_CHECK(
+          static_cast<size_t>(param_pattern.index.index) == results_.size(),
+          "Parameters out of order; expecting {0} but got {1}", results_.size(),
+          param_pattern.index.index);
       CARBON_CHECK(entry.scrutinee_id.has_value());
       CARBON_CHECK(context.insts().Get(entry.scrutinee_id).type_id() ==
                    SemIR::GetTypeInSpecific(context.sem_ir(),
@@ -334,16 +366,16 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
     }
     case MatchKind::Callee: {
       // TODO: Consider ways to address near-duplication with the
-      // ValueParamPattern case.
-      if (!param_pattern.index.has_value()) {
-        param_pattern.index = NextRuntimeIndex();
-        ReplaceInstBeforeConstantUse(context, entry.pattern_id, param_pattern);
-      }
+      // other ParamPattern cases.
+      CARBON_CHECK(!param_pattern.index.has_value());
+      param_pattern.index = NextRuntimeIndex();
+      ReplaceInstBeforeConstantUse(context, entry.pattern_id, param_pattern);
       auto param_id = AddInst<SemIR::OutParam>(
           context, pattern_loc_id,
           {.type_id = param_pattern.type_id,
            .index = param_pattern.index,
-           .pretty_name_id = GetPrettyName(context, param_pattern)});
+           .pretty_name_id = SemIR::GetPrettyNameFromPatternId(
+               context.sem_ir(), entry.pattern_id)});
       AddWork({.pattern_id = param_pattern.subpattern_id,
                .scrutinee_id = param_id});
       results_.push_back(param_id);
@@ -375,7 +407,31 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
                                       SemIR::VarPattern var_pattern,
                                       SemIR::LocId pattern_loc_id,
                                       WorkItem entry) -> void {
-  auto var_id = context.var_storage_map().Lookup(entry.pattern_id).value();
+  auto storage_id = SemIR::InstId::None;
+  switch (kind_) {
+    case MatchKind::Callee: {
+      // We're emitting pattern-match IR for the callee, but we're still on
+      // the caller side of the pattern, so we traverse without emitting any
+      // insts.
+      AddWork({.pattern_id = var_pattern.subpattern_id,
+               .scrutinee_id = SemIR::InstId::None});
+      return;
+    }
+    case MatchKind::Local: {
+      // In a `var`/`let` declaration, the `VarStorage` inst is created before
+      // we start pattern matching.
+      auto lookup_result = context.var_storage_map().Lookup(entry.pattern_id);
+      CARBON_CHECK(lookup_result);
+      storage_id = lookup_result.value();
+      break;
+    }
+    case MatchKind::Caller: {
+      storage_id = AddInst<SemIR::TemporaryStorage>(
+          context, pattern_loc_id, {.type_id = var_pattern.type_id});
+      CARBON_CHECK(entry.scrutinee_id.has_value());
+      break;
+    }
+  }
   // TODO: Find a more efficient way to put these insts in the global_init
   // block (or drop the distinction between the global_init block and the
   // file scope?)
@@ -384,13 +440,14 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
   }
   if (entry.scrutinee_id.has_value()) {
     auto init_id =
-        Initialize(context, pattern_loc_id, var_id, entry.scrutinee_id);
+        Initialize(context, pattern_loc_id, storage_id, entry.scrutinee_id);
     // TODO: Consider using different instruction kinds for assignment
     // versus initialization.
     AddInst<SemIR::Assign>(context, pattern_loc_id,
-                           {.lhs_id = var_id, .rhs_id = init_id});
+                           {.lhs_id = storage_id, .rhs_id = init_id});
   }
-  AddWork({.pattern_id = var_pattern.subpattern_id, .scrutinee_id = var_id});
+  AddWork(
+      {.pattern_id = var_pattern.subpattern_id, .scrutinee_id = storage_id});
   if (context.scope_stack().PeekIndex() == ScopeIndex::Package) {
     context.global_init().Suspend();
   }
@@ -488,6 +545,10 @@ auto MatchContext::EmitPatternMatch(Context& context,
       DoEmitPatternMatch(context, param_pattern, pattern.loc_id, entry);
       break;
     }
+    case CARBON_KIND(SemIR::RefParamPattern param_pattern): {
+      DoEmitPatternMatch(context, param_pattern, pattern.loc_id, entry);
+      break;
+    }
     case CARBON_KIND(SemIR::OutParamPattern param_pattern): {
       DoEmitPatternMatch(context, param_pattern, pattern.loc_id, entry);
       break;

+ 2 - 2
toolchain/check/testdata/class/no_prelude/comp_time_field.carbon

@@ -35,13 +35,13 @@ class Class {
   // CHECK:STDERR:   var C:! type = Class;
   // CHECK:STDERR:       ^~~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_var.carbon:[[@LINE+4]]:16: error: `var` declaration cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
+  // CHECK:STDERR: fail_var.carbon:[[@LINE+4]]:16: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
   // CHECK:STDERR:   var C:! type = Class;
   // CHECK:STDERR:                ^
   // CHECK:STDERR:
   var C:! type = Class;
 
-  // CHECK:STDERR: fail_var.carbon:[[@LINE+4]]:25: error: `var` declaration cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
+  // CHECK:STDERR: fail_var.carbon:[[@LINE+4]]:25: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
   // CHECK:STDERR:   var template D:! type = Class;
   // CHECK:STDERR:                         ^
   // CHECK:STDERR:

+ 1 - 1
toolchain/check/testdata/function/declaration/no_prelude/fail_todo_no_params.carbon

@@ -58,7 +58,7 @@ library "[[@TEST_NAME]]";
 // CHECK:STDERR: var x:! () = ();
 // CHECK:STDERR:     ^~~~~~
 // CHECK:STDERR:
-// CHECK:STDERR: fail_invalid_file_generic_regression_test.carbon:[[@LINE+4]]:12: error: `var` declaration cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
+// CHECK:STDERR: fail_invalid_file_generic_regression_test.carbon:[[@LINE+4]]:12: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
 // CHECK:STDERR: var x:! () = ();
 // CHECK:STDERR:            ^
 // CHECK:STDERR:

+ 8 - 8
toolchain/check/testdata/interface/no_prelude/fail_assoc_const_not_binding.carbon

@@ -37,13 +37,13 @@ interface I {
 library "[[@TEST_NAME]]";
 
 interface I {
-  // CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+8]]:7: error: expected name in binding pattern [ExpectedBindingPattern]
+  // CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+8]]:11: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
   // CHECK:STDERR:   let var T:! type;
-  // CHECK:STDERR:       ^~~
+  // CHECK:STDERR:           ^~~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+4]]:7: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR: fail_var_pattern.carbon:[[@LINE+4]]:19: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
   // CHECK:STDERR:   let var T:! type;
-  // CHECK:STDERR:       ^~~
+  // CHECK:STDERR:                   ^
   // CHECK:STDERR:
   let var T:! type;
 }
@@ -53,13 +53,13 @@ interface I {
 library "[[@TEST_NAME]]";
 
 interface I {
-  // CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+8]]:15: error: expected name in binding pattern [ExpectedBindingPattern]
+  // CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+8]]:19: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
   // CHECK:STDERR:   default let var T:! type = {};
-  // CHECK:STDERR:               ^~~
+  // CHECK:STDERR:                   ^~~~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+4]]:15: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR: fail_var_pattern_with_default.carbon:[[@LINE+4]]:28: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
   // CHECK:STDERR:   default let var T:! type = {};
-  // CHECK:STDERR:               ^~~
+  // CHECK:STDERR:                            ^
   // CHECK:STDERR:
   default let var T:! type = {};
 }

+ 1 - 1
toolchain/check/testdata/var/no_prelude/fail_generic.carbon

@@ -13,7 +13,7 @@ fn Main() {
   // CHECK:STDERR:   var x:! () = ();
   // CHECK:STDERR:       ^~~~~~
   // CHECK:STDERR:
-  // CHECK:STDERR: fail_generic.carbon:[[@LINE+4]]:14: error: `var` declaration cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
+  // CHECK:STDERR: fail_generic.carbon:[[@LINE+4]]:14: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
   // CHECK:STDERR:   var x:! () = ();
   // CHECK:STDERR:              ^
   // CHECK:STDERR:

+ 372 - 0
toolchain/check/testdata/var/no_prelude/var_pattern.carbon

@@ -0,0 +1,372 @@
+// 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/check/testdata/var/no_prelude/var_pattern.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/var/no_prelude/var_pattern.carbon
+
+// --- basic.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F() {
+  let var x: () = ();
+}
+
+// --- tuple.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F() {
+  let (x: (), var y: ()) = ((), ());
+}
+
+// --- function.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F(x: (), var y: ());
+
+fn G() {
+  var v: () = ();
+  F(v, ());
+}
+
+// --- fail_nested.carbon
+
+library "[[@TEST_NAME]]";
+
+fn F() {
+  // CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:27: error: `var` nested within another `var` [NestedVar]
+  // CHECK:STDERR:   let (x: (), var (y: (), var z: ())) = ((), ((), ()));
+  // CHECK:STDERR:                           ^~~
+  // CHECK:STDERR:
+  let (x: (), var (y: (), var z: ())) = ((), ((), ()));
+}
+
+// --- fail_compile_time.carbon
+
+library "[[@TEST_NAME]]";
+
+fn f() {
+  // CHECK:STDERR: fail_compile_time.carbon:[[@LINE+8]]:7: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+  // CHECK:STDERR:   var T:! type;
+  // CHECK:STDERR:       ^~~~~~~~
+  // CHECK:STDERR:
+  // CHECK:STDERR: fail_compile_time.carbon:[[@LINE+4]]:15: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
+  // CHECK:STDERR:   var T:! type;
+  // CHECK:STDERR:               ^
+  // CHECK:STDERR:
+  var T:! type;
+  let (x: (), T:! type) = ((), ());
+}
+
+// --- fail_implicit.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_implicit.carbon:[[@LINE+4]]:10: error: implicit parameters of functions must be constant or `self` [ImplictParamMustBeConstant]
+// CHECK:STDERR: fn F[var u: ()]();
+// CHECK:STDERR:          ^~~~~
+// CHECK:STDERR:
+fn F[var u: ()]();
+
+// CHECK:STDERR: fail_implicit.carbon:[[@LINE+8]]:10: error: semantics TODO: `handle invalid parse trees in `check`` [SemanticsTodo]
+// CHECK:STDERR: fn G[var T:! type]();
+// CHECK:STDERR:          ^~~~~~~~
+// CHECK:STDERR:
+// CHECK:STDERR: fail_implicit.carbon:[[@LINE+4]]:18: error: `var` pattern cannot declare a compile-time binding [CompileTimeBindingInVarDecl]
+// CHECK:STDERR: fn G[var T:! type]();
+// CHECK:STDERR:                  ^
+// CHECK:STDERR:
+fn G[var T:! type]();
+
+// --- var_self.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  fn F[var self: Self]();
+}
+
+// CHECK:STDOUT: --- basic.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
+// CHECK:STDOUT:     %.loc5_7.1: %empty_tuple.type = var_pattern %x.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x.var: ref %empty_tuple.type = var x
+// CHECK:STDOUT:   %.loc5_20.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %.loc5_20.2: init %empty_tuple.type = tuple_init () to %x.var [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc5_7.2: init %empty_tuple.type = converted %.loc5_20.1, %.loc5_20.2 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   assign %x.var, %.loc5_7.2
+// CHECK:STDOUT:   %.loc5_15.1: type = splice_block %.loc5_15.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc5_15.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc5_15.3: type = converted %.loc5_15.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %x: ref %empty_tuple.type = bind_name x, %x.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- tuple.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %tuple.type: type = tuple_type (%empty_tuple.type, %empty_tuple.type) [concrete]
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
+// CHECK:STDOUT:     %y.patt: %empty_tuple.type = binding_pattern y
+// CHECK:STDOUT:     %.loc5_15.1: %empty_tuple.type = var_pattern %y.patt
+// CHECK:STDOUT:     %.loc5_24: %tuple.type = tuple_pattern (%x.patt, %.loc5_15.1)
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %y.var: ref %empty_tuple.type = var y
+// CHECK:STDOUT:   %.loc5_30.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %.loc5_34.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %.loc5_35: %tuple.type = tuple_literal (%.loc5_30.1, %.loc5_34.1)
+// CHECK:STDOUT:   %.loc5_12.1: type = splice_block %.loc5_12.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc5_12.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc5_12.3: type = converted %.loc5_12.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc5_30.2: %empty_tuple.type = converted %.loc5_30.1, %empty_tuple [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %x: %empty_tuple.type = bind_name x, %.loc5_30.2
+// CHECK:STDOUT:   %.loc5_34.2: init %empty_tuple.type = tuple_init () to %y.var [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc5_15.2: init %empty_tuple.type = converted %.loc5_34.1, %.loc5_34.2 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   assign %y.var, %.loc5_15.2
+// CHECK:STDOUT:   %.loc5_23.1: type = splice_block %.loc5_23.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc5_23.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc5_23.3: type = converted %.loc5_23.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %y: ref %empty_tuple.type = bind_name y, %y.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- function.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %empty_tuple.type = value_param_pattern %x.patt, call_param0
+// CHECK:STDOUT:     %y.patt: %empty_tuple.type = binding_pattern y
+// CHECK:STDOUT:     %y.param_patt: %empty_tuple.type = ref_param_pattern %y.patt, call_param1
+// CHECK:STDOUT:     %.loc4_13: %empty_tuple.type = var_pattern %y.param_patt
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %x.param: %empty_tuple.type = value_param call_param0
+// CHECK:STDOUT:     %.loc4_10.1: type = splice_block %.loc4_10.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:       %.loc4_10.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:       %.loc4_10.3: type = converted %.loc4_10.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %x: %empty_tuple.type = bind_name x, %x.param
+// CHECK:STDOUT:     %y.param: ref %empty_tuple.type = ref_param call_param1
+// CHECK:STDOUT:     %.loc4_21.1: type = splice_block %.loc4_21.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:       %.loc4_21.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:       %.loc4_21.3: type = converted %.loc4_21.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %y: ref %empty_tuple.type = bind_name y, %y.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F(%x.param_patt: %empty_tuple.type, %.loc4_13: %empty_tuple.type);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %v.patt: %empty_tuple.type = binding_pattern v
+// CHECK:STDOUT:     %.loc7_3.1: %empty_tuple.type = var_pattern %v.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %v.var: ref %empty_tuple.type = var v
+// CHECK:STDOUT:   %.loc7_16.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %.loc7_16.2: init %empty_tuple.type = tuple_init () to %v.var [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc7_3.2: init %empty_tuple.type = converted %.loc7_16.1, %.loc7_16.2 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   assign %v.var, %.loc7_3.2
+// CHECK:STDOUT:   %.loc7_11.1: type = splice_block %.loc7_11.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc7_11.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc7_11.3: type = converted %.loc7_11.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %v: ref %empty_tuple.type = bind_name v, %v.var
+// CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:   %v.ref: ref %empty_tuple.type = name_ref v, %v
+// CHECK:STDOUT:   %.loc8_9.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %tuple: %empty_tuple.type = tuple_value () [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc8_5: %empty_tuple.type = converted %v.ref, %tuple [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc4_13.1: ref %empty_tuple.type = temporary_storage
+// CHECK:STDOUT:   %.loc8_9.2: init %empty_tuple.type = tuple_init () to %.loc4_13.1 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc4_13.2: init %empty_tuple.type = converted %.loc8_9.1, %.loc8_9.2 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   assign %.loc4_13.1, %.loc4_13.2
+// CHECK:STDOUT:   %F.call: init %empty_tuple.type = call %F.ref(%.loc8_5, %.loc4_13.1)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_nested.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %tuple.type.bcd: type = tuple_type (%empty_tuple.type, %empty_tuple.type) [concrete]
+// CHECK:STDOUT:   %tuple.type.a21: type = tuple_type (%empty_tuple.type, %tuple.type.bcd) [concrete]
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
+// CHECK:STDOUT:   %tuple: %tuple.type.bcd = tuple_value (%empty_tuple, %empty_tuple) [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
+// CHECK:STDOUT:     %y.patt: %empty_tuple.type = binding_pattern y
+// CHECK:STDOUT:     %z.patt: %empty_tuple.type = binding_pattern z
+// CHECK:STDOUT:     %.loc9_27.1: %empty_tuple.type = var_pattern %z.patt
+// CHECK:STDOUT:     %.loc9_36: %tuple.type.bcd = tuple_pattern (%y.patt, %.loc9_27.1)
+// CHECK:STDOUT:     %.loc9_15.1: %tuple.type.bcd = var_pattern %.loc9_36
+// CHECK:STDOUT:     %.loc9_37: %tuple.type.a21 = tuple_pattern (%x.patt, %.loc9_15.1)
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %z.var: ref %empty_tuple.type = var z
+// CHECK:STDOUT:   %.var: ref %tuple.type.bcd = var <none>
+// CHECK:STDOUT:   %.loc9_43.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %.loc9_48.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %.loc9_52.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:   %.loc9_53.1: %tuple.type.bcd = tuple_literal (%.loc9_48.1, %.loc9_52.1)
+// CHECK:STDOUT:   %.loc9_54: %tuple.type.a21 = tuple_literal (%.loc9_43.1, %.loc9_53.1)
+// CHECK:STDOUT:   %.loc9_12.1: type = splice_block %.loc9_12.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc9_12.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc9_12.3: type = converted %.loc9_12.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc9_43.2: %empty_tuple.type = converted %.loc9_43.1, %empty_tuple [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %x: %empty_tuple.type = bind_name x, %.loc9_43.2
+// CHECK:STDOUT:   %tuple.elem0.loc9_53: ref %empty_tuple.type = tuple_access %.var, element0
+// CHECK:STDOUT:   %.loc9_48.2: init %empty_tuple.type = tuple_init () to %tuple.elem0.loc9_53 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc9_53.2: init %empty_tuple.type = converted %.loc9_48.1, %.loc9_48.2 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %tuple.elem1.loc9_53: ref %empty_tuple.type = tuple_access %.var, element1
+// CHECK:STDOUT:   %.loc9_52.2: init %empty_tuple.type = tuple_init () to %tuple.elem1.loc9_53 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc9_53.3: init %empty_tuple.type = converted %.loc9_52.1, %.loc9_52.2 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc9_53.4: init %tuple.type.bcd = tuple_init (%.loc9_53.2, %.loc9_53.3) to %.var [concrete = constants.%tuple]
+// CHECK:STDOUT:   %.loc9_15.2: init %tuple.type.bcd = converted %.loc9_53.1, %.loc9_53.4 [concrete = constants.%tuple]
+// CHECK:STDOUT:   assign %.var, %.loc9_15.2
+// CHECK:STDOUT:   %tuple.elem0.loc9_15: ref %empty_tuple.type = tuple_access %.var, element0
+// CHECK:STDOUT:   %tuple.elem1.loc9_15: ref %empty_tuple.type = tuple_access %.var, element1
+// CHECK:STDOUT:   %.loc9_24.1: type = splice_block %.loc9_24.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc9_24.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc9_24.3: type = converted %.loc9_24.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %y: ref %empty_tuple.type = bind_name y, %tuple.elem0.loc9_15
+// CHECK:STDOUT:   %.loc9_15.3: init %empty_tuple.type = tuple_init () to %z.var [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   %.loc9_27.2: init %empty_tuple.type = converted %tuple.elem1.loc9_15, %.loc9_15.3 [concrete = constants.%empty_tuple]
+// CHECK:STDOUT:   assign %z.var, %.loc9_27.2
+// CHECK:STDOUT:   %.loc9_35.1: type = splice_block %.loc9_35.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:     %.loc9_35.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc9_35.3: type = converted %.loc9_35.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %z: ref %empty_tuple.type = bind_name z, %z.var
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_compile_time.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @f();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_implicit.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F[%.loc8: <error>]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- var_self.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %C = ref_param_pattern %self.patt, call_param0
+// CHECK:STDOUT:     %.loc5: %C = var_pattern %self.param_patt
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: ref %C = ref_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
+// CHECK:STDOUT:     %self: ref %C = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%C
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F[%.loc5: %C]();
+// CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -92,6 +92,7 @@ CARBON_DIAGNOSTIC_KIND(ExpectedVarAfterReturned)
 CARBON_DIAGNOSTIC_KIND(ExpectedVariableDecl)
 CARBON_DIAGNOSTIC_KIND(ExpectedChoiceDefinition)
 CARBON_DIAGNOSTIC_KIND(ExpectedChoiceAlternativeName)
+CARBON_DIAGNOSTIC_KIND(NestedVar)
 CARBON_DIAGNOSTIC_KIND(OperatorRequiresParentheses)
 CARBON_DIAGNOSTIC_KIND(StatementOperatorAsSubExpr)
 CARBON_DIAGNOSTIC_KIND(UnaryOperatorRequiresParentheses)

+ 4 - 2
toolchain/lower/file_context.cpp

@@ -21,6 +21,7 @@
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/inst_kind.h"
+#include "toolchain/sem_ir/pattern.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Lower {
@@ -220,9 +221,10 @@ auto FileContext::BuildFunctionTypeInfo(const SemIR::Function& function,
     return GetType(SemIR::GetTypeInSpecific(sem_ir(), specific_id, type_id));
   };
 
+  // TODO: expose the `Call` parameter patterns in `Function`, and use them here
+  // instead of reconstructing them via the syntactic parameter lists.
   auto implicit_param_patterns =
       sem_ir().inst_blocks().GetOrEmpty(function.implicit_param_patterns_id);
-  // TODO: Include parameters corresponding to positional parameters.
   auto param_patterns =
       sem_ir().inst_blocks().GetOrEmpty(function.param_patterns_id);
 
@@ -334,7 +336,7 @@ auto FileContext::BuildFunctionDecl(SemIR::FunctionId function_id,
       arg.addAttr(llvm::Attribute::getWithStructRetType(
           llvm_context(), function_type_info.return_type));
     } else {
-      name_id = SemIR::Function::GetNameFromPatternId(sem_ir(), inst_id);
+      name_id = SemIR::GetPrettyNameFromPatternId(sem_ir(), inst_id);
     }
     arg.setName(sem_ir().names().GetIRBaseName(name_id));
   }

+ 5 - 0
toolchain/lower/handle.cpp

@@ -210,6 +210,11 @@ auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
   // Parameters are lowered by `BuildFunctionDefinition`.
 }
 
+auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
+                SemIR::RefParam /*inst*/) -> void {
+  // Parameters are lowered by `BuildFunctionDefinition`.
+}
+
 auto HandleInst(FunctionContext& /*context*/, SemIR::InstId /*inst_id*/,
                 SemIR::ValueParam /*inst*/) -> void {
   // Parameters are lowered by `BuildFunctionDefinition`.

+ 2 - 3
toolchain/parse/handle_binding_pattern.cpp

@@ -92,9 +92,8 @@ static auto HandleBindingPatternFinish(Context& context, bool is_compile_time)
   if (state.in_var_pattern) {
     node_kind = NodeKind::VarBindingPattern;
     if (is_compile_time) {
-      CARBON_DIAGNOSTIC(
-          CompileTimeBindingInVarDecl, Error,
-          "`var` declaration cannot declare a compile-time binding");
+      CARBON_DIAGNOSTIC(CompileTimeBindingInVarDecl, Error,
+                        "`var` pattern cannot declare a compile-time binding");
       context.emitter().Emit(*context.position(), CompileTimeBindingInVarDecl);
       state.has_error = true;
     }

+ 11 - 5
toolchain/parse/handle_pattern.cpp

@@ -9,11 +9,17 @@ namespace Carbon::Parse {
 
 auto HandlePattern(Context& context) -> void {
   auto state = context.PopState();
-  if (context.PositionKind() == Lex::TokenKind::OpenParen) {
-    context.PushStateForPattern(State::PatternListAsTuple,
-                                state.in_var_pattern);
-  } else {
-    context.PushStateForPattern(State::BindingPattern, state.in_var_pattern);
+  switch (context.PositionKind()) {
+    case Lex::TokenKind::OpenParen:
+      context.PushStateForPattern(State::PatternListAsTuple,
+                                  state.in_var_pattern);
+      break;
+    case Lex::TokenKind::Var:
+      context.PushStateForPattern(State::VariablePattern, state.in_var_pattern);
+      break;
+    default:
+      context.PushStateForPattern(State::BindingPattern, state.in_var_pattern);
+      break;
   }
 }
 

+ 24 - 0
toolchain/parse/handle_var.cpp

@@ -117,4 +117,28 @@ auto HandleVarFinishAsFor(Context& context) -> void {
   context.AddNode(NodeKind::ForIn, end_token, state.has_error);
 }
 
+auto HandleVariablePattern(Context& context) -> void {
+  auto state = context.PopState();
+  if (state.in_var_pattern) {
+    CARBON_DIAGNOSTIC(NestedVar, Error, "`var` nested within another `var`");
+    context.emitter().Emit(*context.position(), NestedVar);
+    state.has_error = true;
+  }
+  context.PushState(State::FinishVariablePattern);
+  context.ConsumeChecked(Lex::TokenKind::Var);
+
+  context.PushStateForPattern(State::Pattern, /*in_var_pattern=*/true);
+}
+
+auto HandleFinishVariablePattern(Context& context) -> void {
+  auto state = context.PopState();
+  context.AddNode(NodeKind::VariablePattern, state.token, state.has_error);
+
+  // Propagate errors to the parent state so that they can take different
+  // actions on invalid patterns.
+  if (state.has_error) {
+    context.ReturnErrorOnState();
+  }
+}
+
 }  // namespace Carbon::Parse

+ 20 - 0
toolchain/parse/state.def

@@ -980,6 +980,11 @@ CARBON_PARSE_STATE(ParenExprFinish)
 // ^
 //   1. PatternListAsTuple
 //
+//  var ...
+// ^
+//
+//   1. VariablePattern
+//
 //  ...
 // ^
 //   1. BindingPattern
@@ -1027,6 +1032,21 @@ CARBON_PARSE_STATE(BindingPatternAddr)
 //   (state done)
 CARBON_PARSE_STATE_VARIANTS2(BindingPatternFinish, Generic, Regular)
 
+// Handles `var` in a pattern context.
+//
+// var ...
+// ^~~
+//   1. Pattern
+//   2. FinishVariablePattern
+CARBON_PARSE_STATE(VariablePattern)
+
+// Finishes `var` in a pattern context.
+//
+// var ...
+//        ^
+//   (state done)
+CARBON_PARSE_STATE(FinishVariablePattern)
+
 // Handles a single statement. While typically within a statement block, this
 // can also be used for error recovery where we expect a statement block and
 // are missing braces.

+ 132 - 0
toolchain/parse/testdata/var/var_pattern.carbon

@@ -0,0 +1,132 @@
+// 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/var/var_pattern.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/parse/testdata/var/var_pattern.carbon
+
+// --- basic.carbon
+
+let var x: () = ();
+
+// --- tuple.carbon
+
+let (x: (), var y: ()) = ((), ());
+
+// --- function.carbon
+
+fn F(x: (), var y: ());
+
+// --- fail_nested.carbon
+
+// CHECK:STDERR: fail_nested.carbon:[[@LINE+4]]:25: error: `var` nested within another `var` [NestedVar]
+// CHECK:STDERR: let (x: (), var (y: (), var z: ())) = ((), ((), ()));
+// CHECK:STDERR:                         ^~~
+// CHECK:STDERR:
+let (x: (), var (y: (), var z: ())) = ((), ((), ()));
+
+// CHECK:STDOUT: - filename: basic.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'LetIntroducer', text: 'let'},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'VarBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:       {kind: 'VariablePattern', text: 'var', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'LetInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:       {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:     {kind: 'LetDecl', text: ';', subtree_size: 10},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: tuple.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'LetIntroducer', text: 'let'},
+// CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'LetBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'PatternListComma', text: ','},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'y'},
+// CHECK:STDOUT:               {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:             {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'VarBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'VariablePattern', text: 'var', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 12},
+// CHECK:STDOUT:       {kind: 'LetInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'TupleLiteralComma', text: ','},
+// CHECK:STDOUT:           {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:       {kind: 'TupleLiteral', text: ')', subtree_size: 7},
+// CHECK:STDOUT:     {kind: 'LetDecl', text: ';', subtree_size: 22},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: function.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'FunctionIntroducer', text: 'fn'},
+// CHECK:STDOUT:       {kind: 'IdentifierNameBeforeParams', text: 'F'},
+// CHECK:STDOUT:         {kind: 'ExplicitParamListStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'LetBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'PatternListComma', text: ','},
+// CHECK:STDOUT:             {kind: 'IdentifierNameNotBeforeParams', text: 'y'},
+// CHECK:STDOUT:               {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:             {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'VarBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'VariablePattern', text: 'var', subtree_size: 5},
+// CHECK:STDOUT:       {kind: 'ExplicitParamList', text: ')', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'FunctionDecl', text: ';', subtree_size: 15},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]
+// CHECK:STDOUT: - filename: fail_nested.carbon
+// CHECK:STDOUT:   parse_tree: [
+// CHECK:STDOUT:     {kind: 'FileStart', text: ''},
+// CHECK:STDOUT:       {kind: 'LetIntroducer', text: 'let'},
+// CHECK:STDOUT:         {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:           {kind: 'IdentifierNameNotBeforeParams', text: 'x'},
+// CHECK:STDOUT:             {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'LetBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:         {kind: 'PatternListComma', text: ','},
+// CHECK:STDOUT:             {kind: 'TuplePatternStart', text: '('},
+// CHECK:STDOUT:               {kind: 'IdentifierNameNotBeforeParams', text: 'y'},
+// CHECK:STDOUT:                 {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:               {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:             {kind: 'VarBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:             {kind: 'PatternListComma', text: ','},
+// CHECK:STDOUT:                 {kind: 'IdentifierNameNotBeforeParams', text: 'z'},
+// CHECK:STDOUT:                   {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:                 {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:               {kind: 'VarBindingPattern', text: ':', subtree_size: 4},
+// CHECK:STDOUT:             {kind: 'VariablePattern', text: 'var', subtree_size: 5},
+// CHECK:STDOUT:           {kind: 'TuplePattern', text: ')', subtree_size: 12},
+// CHECK:STDOUT:         {kind: 'VariablePattern', text: 'var', subtree_size: 13},
+// CHECK:STDOUT:       {kind: 'TuplePattern', text: ')', subtree_size: 20},
+// CHECK:STDOUT:       {kind: 'LetInitializer', text: '='},
+// CHECK:STDOUT:         {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:         {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'TupleLiteralComma', text: ','},
+// CHECK:STDOUT:           {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:             {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:           {kind: 'TupleLiteralComma', text: ','},
+// CHECK:STDOUT:             {kind: 'TupleLiteralStart', text: '('},
+// CHECK:STDOUT:           {kind: 'TupleLiteral', text: ')', subtree_size: 2},
+// CHECK:STDOUT:         {kind: 'TupleLiteral', text: ')', subtree_size: 7},
+// CHECK:STDOUT:       {kind: 'TupleLiteral', text: ')', subtree_size: 12},
+// CHECK:STDOUT:     {kind: 'LetDecl', text: ';', subtree_size: 35},
+// CHECK:STDOUT:     {kind: 'FileEnd', text: ''},
+// CHECK:STDOUT:   ]

+ 2 - 0
toolchain/sem_ir/BUILD

@@ -77,6 +77,7 @@ cc_library(
         "impl.cpp",
         "name.cpp",
         "name_scope.cpp",
+        "pattern.cpp",
         "type.cpp",
         "type_info.cpp",
     ],
@@ -98,6 +99,7 @@ cc_library(
         "interface.h",
         "name.h",
         "name_scope.h",
+        "pattern.h",
         "struct_type_field.h",
         "type.h",
         "type_info.h",

+ 2 - 3
toolchain/sem_ir/entity_with_params_base.h

@@ -108,9 +108,8 @@ struct EntityWithParamsBase {
   // instruction in the entity's pattern block that depends on all other
   // pattern insts pertaining to that parameter.
   InstBlockId implicit_param_patterns_id;
-  // A block containing, for each explicit parameter, a reference to the
-  // instruction in the entity's pattern block that depends on all other
-  // pattern insts pertaining to that parameter.
+  // A block containing, for each element of the explicit parameter list tuple
+  // pattern, a reference to the root pattern inst for that element.
   InstBlockId param_patterns_id;
 
   // If this entity is a function, this block consists of references to the

+ 2 - 0
toolchain/sem_ir/file.cpp

@@ -192,6 +192,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       case NameBindingDecl::Kind:
       case Namespace::Kind:
       case OutParamPattern::Kind:
+      case RefParamPattern::Kind:
       case RequirementEquivalent::Kind:
       case RequirementImpls::Kind:
       case RequirementRewrite::Kind:
@@ -368,6 +369,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
         return ExprCategory::EphemeralRef;
 
       case OutParam::Kind:
+      case RefParam::Kind:
         // TODO: Consider introducing a separate category for OutParam:
         // unlike other DurableRefs, it permits initialization.
         return ExprCategory::DurableRef;

+ 6 - 0
toolchain/sem_ir/formatter.cpp

@@ -1013,6 +1013,12 @@ class FormatterImpl {
     // pretty-printing.
   }
 
+  auto FormatInstRhs(RefParam inst) -> void {
+    FormatArgs(inst.index);
+    // Omit pretty_name because it's an implementation detail of
+    // pretty-printing.
+  }
+
   auto FormatInstRhs(OutParam inst) -> void {
     FormatArgs(inst.index);
     // Omit pretty_name because it's an implementation detail of

+ 0 - 26
toolchain/sem_ir/function.cpp

@@ -88,32 +88,6 @@ auto Function::GetParamPatternInfoFromPatternId(const File& sem_ir,
            .entity_name_id = binding_pattern.entity_name_id}};
 }
 
-auto Function::GetNameFromPatternId(const File& sem_ir, InstId pattern_id)
-    -> SemIR::NameId {
-  auto inst_id = pattern_id;
-  auto inst = sem_ir.insts().Get(inst_id);
-
-  if (auto addr_pattern = inst.TryAs<SemIR::AddrPattern>()) {
-    inst_id = addr_pattern->inner_id;
-    inst = sem_ir.insts().Get(inst_id);
-  }
-
-  if (inst_id == SemIR::ErrorInst::SingletonInstId) {
-    return SemIR::NameId::None;
-  }
-
-  if (auto param_pattern_inst = inst.TryAs<SemIR::AnyParamPattern>()) {
-    inst_id = param_pattern_inst->subpattern_id;
-    inst = sem_ir.insts().Get(inst_id);
-  }
-
-  if (inst.Is<ReturnSlotPattern>()) {
-    return SemIR::NameId::ReturnSlot;
-  }
-  auto binding_pattern = inst.As<AnyBindingPattern>();
-  return sem_ir.entity_names().Get(binding_pattern.entity_name_id).name_id;
-}
-
 auto Function::GetDeclaredReturnType(const File& file,
                                      SpecificId specific_id) const -> TypeId {
   if (!return_slot_pattern_id.has_value()) {

+ 0 - 5
toolchain/sem_ir/function.h

@@ -94,11 +94,6 @@ struct Function : public EntityWithParamsBase,
                                                InstId param_pattern_id)
       -> std::optional<ParamPatternInfo>;
 
-  // Gets the name from the name binding instruction, or `None` if this pattern
-  // has been replaced with BuiltinErrorInst.
-  static auto GetNameFromPatternId(const File& sem_ir, InstId param_pattern_id)
-      -> SemIR::NameId;
-
   // Gets the declared return type for a specific version of this function, or
   // the canonical return type for the original declaration no specific is
   // specified.  Returns `None` if no return type was specified, in which

+ 2 - 0
toolchain/sem_ir/inst_kind.def

@@ -87,6 +87,8 @@ CARBON_SEM_IR_INST_KIND(NamespaceType)
 CARBON_SEM_IR_INST_KIND(OutParam)
 CARBON_SEM_IR_INST_KIND(OutParamPattern)
 CARBON_SEM_IR_INST_KIND(PointerType)
+CARBON_SEM_IR_INST_KIND(RefParam)
+CARBON_SEM_IR_INST_KIND(RefParamPattern)
 CARBON_SEM_IR_INST_KIND(RequireCompleteType)
 CARBON_SEM_IR_INST_KIND(RequirementEquivalent)
 CARBON_SEM_IR_INST_KIND(RequirementImpls)

+ 5 - 3
toolchain/sem_ir/inst_namer.cpp

@@ -19,6 +19,7 @@
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/inst_kind.h"
+#include "toolchain/sem_ir/pattern.h"
 #include "toolchain/sem_ir/type_info.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -790,15 +791,16 @@ auto InstNamer::CollectNamesInBlock(ScopeId top_scope_id,
         continue;
       }
       case OutParam::Kind:
+      case RefParam::Kind:
       case ValueParam::Kind: {
         add_inst_name_id(untyped_inst.As<AnyParam>().pretty_name_id, ".param");
         continue;
       }
       case OutParamPattern::Kind:
+      case RefParamPattern::Kind:
       case ValueParamPattern::Kind: {
-        add_inst_name_id(
-            SemIR::Function::GetNameFromPatternId(*sem_ir_, inst_id),
-            ".param_patt");
+        add_inst_name_id(SemIR::GetPrettyNameFromPatternId(*sem_ir_, inst_id),
+                         ".param_patt");
         continue;
       }
       case PointerType::Kind: {

+ 47 - 0
toolchain/sem_ir/pattern.cpp

@@ -0,0 +1,47 @@
+// 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/sem_ir/pattern.h"
+
+namespace Carbon::SemIR {
+
+auto IsSelfPattern(const File& sem_ir, InstId pattern_id) -> bool {
+  // Note that the public contract of GetPrettyNameFromPatternId does not
+  // guarantee that this is correct; we're relying on knowledge of the
+  // implementation details.
+  return GetPrettyNameFromPatternId(sem_ir, pattern_id) == NameId::SelfValue;
+}
+
+auto GetPrettyNameFromPatternId(const File& sem_ir, InstId pattern_id)
+    -> NameId {
+  auto inst_id = pattern_id;
+  auto inst = sem_ir.insts().Get(inst_id);
+
+  if (auto var_pattern = inst.TryAs<VarPattern>()) {
+    inst_id = var_pattern->subpattern_id;
+    inst = sem_ir.insts().Get(inst_id);
+  }
+
+  if (auto addr_pattern = inst.TryAs<AddrPattern>()) {
+    inst_id = addr_pattern->inner_id;
+    inst = sem_ir.insts().Get(inst_id);
+  }
+
+  if (auto param_pattern_inst = inst.TryAs<AnyParamPattern>()) {
+    inst_id = param_pattern_inst->subpattern_id;
+    inst = sem_ir.insts().Get(inst_id);
+  }
+
+  if (inst.Is<ReturnSlotPattern>()) {
+    return NameId::ReturnSlot;
+  }
+
+  if (auto binding_pattern = inst.TryAs<AnyBindingPattern>()) {
+    return sem_ir.entity_names().Get(binding_pattern->entity_name_id).name_id;
+  }
+
+  return NameId::None;
+}
+
+}  // namespace Carbon::SemIR

+ 30 - 0
toolchain/sem_ir/pattern.h

@@ -0,0 +1,30 @@
+// 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
+
+#ifndef CARBON_TOOLCHAIN_SEM_IR_PATTERN_H_
+#define CARBON_TOOLCHAIN_SEM_IR_PATTERN_H_
+
+#include "toolchain/sem_ir/file.h"
+#include "toolchain/sem_ir/ids.h"
+
+namespace Carbon::SemIR {
+
+// Returns true if `pattern_id` is a `self` parameter pattern, such as
+// `self: Foo` or `addr self: Self*`.
+auto IsSelfPattern(const File& sem_ir, InstId pattern_id) -> bool;
+
+// If `pattern_id` is a declaration of a single name, this returns that name,
+// and otherwise returns `None`. This tries to "see through" wrappers like
+// `AddrPattern` and `*ParamPattern`, so this may return the same name for
+// different insts if one is an ancestor of the other (or if they represent
+// separate declarations of the same name).
+//
+// This should only be used for decorative purposes such as SemIR
+// pretty-printing or LLVM parameter naming.
+auto GetPrettyNameFromPatternId(const File& sem_ir, InstId pattern_id)
+    -> SemIR::NameId;
+
+}  // namespace Carbon::SemIR
+
+#endif  // CARBON_TOOLCHAIN_SEM_IR_PATTERN_H_

+ 2 - 0
toolchain/sem_ir/stringify_type.cpp

@@ -599,6 +599,8 @@ auto StringifyTypeExpr(const SemIR::File& sem_ir, InstId outer_inst_id)
       case NameBindingDecl::Kind:
       case OutParam::Kind:
       case OutParamPattern::Kind:
+      case RefParam::Kind:
+      case RefParamPattern::Kind:
       case RequireCompleteType::Kind:
       case RequirementEquivalent::Kind:
       case RequirementImpls::Kind:

+ 27 - 3
toolchain/sem_ir/typed_insts.h

@@ -1040,10 +1040,9 @@ struct NamespaceType {
   TypeId type_id;
 };
 
-// A `Call` parameter for a function or other parameterized block. The sub-kinds
-// differ only in their expression category.
+// A `Call` parameter for a function or other parameterized block.
 struct AnyParam {
-  static constexpr InstKind Kinds[] = {InstKind::OutParam,
+  static constexpr InstKind Kinds[] = {InstKind::OutParam, InstKind::RefParam,
                                        InstKind::ValueParam};
 
   InstKind kind;
@@ -1067,6 +1066,17 @@ struct OutParam {
   NameId pretty_name_id;
 };
 
+// A by-reference `Call` parameter. See AnyParam for member documentation.
+struct RefParam {
+  // TODO: Make Parse::NodeId more specific.
+  static constexpr auto Kind = InstKind::RefParam.Define<Parse::NodeId>(
+      {.ir_name = "ref_param", .constant_kind = InstConstantKind::Never});
+
+  TypeId type_id;
+  CallParamIndex index;
+  NameId pretty_name_id;
+};
+
 // A by-value `Call` parameter. See AnyParam for member documentation.
 struct ValueParam {
   // TODO: Make Parse::NodeId more specific.
@@ -1083,6 +1093,7 @@ struct ValueParam {
 // of the corresponding parameter inst.
 struct AnyParamPattern {
   static constexpr InstKind Kinds[] = {InstKind::OutParamPattern,
+                                       InstKind::RefParamPattern,
                                        InstKind::ValueParamPattern};
 
   InstKind kind;
@@ -1104,6 +1115,19 @@ struct OutParamPattern {
   CallParamIndex index;
 };
 
+// A pattern that represents a by-reference `Call` parameter.
+struct RefParamPattern {
+  // TODO: Make Parse::NodeId more specific.
+  static constexpr auto Kind = InstKind::RefParamPattern.Define<Parse::NodeId>(
+      {.ir_name = "ref_param_pattern",
+       .constant_kind = InstConstantKind::Never,
+       .is_lowered = false});
+
+  TypeId type_id;
+  InstId subpattern_id;
+  CallParamIndex index;
+};
+
 // A pattern that represents a by-value `Call` parameter.
 struct ValueParamPattern {
   // TODO: Make Parse::NodeId more specific.