Эх сурвалжийг харах

Support destruction of storage (#5171)

What this does:

- Adds tracking where storage is allocated.
- Determines if that storage supports destruction and, if so, records
the `destroy` function for it.
- Calls any found `destroy` functions when going out-of-scope.

What this does not do:

- Precise scope tracking of temporaries. We currently don't define
temporary scopes, which would probably be the solution.
- Destruction for anything but a `class` with `fn destroy`, in an
implicit return. That excludes:
- Classes with members that need destruction, particularly in the
absence of `fn destroy`.
  - Structs, tuples, and arrays.
  - Explicit returns, break, continue, nested scopes.

Noting the exclusions in particular, I think those will need work to
support, but this should set the right framework.

The cleanup block concept stems from clang and trying to share code
across cleanups, from discussion with chandlerc. Note in this
implementation I try to find `destroy` functions early on: that's so
that, when destruction is present on multiple paths, particularly
non-shared paths, we only bind the `destroy` method once.

Implementation-wise, I'll note this adds a `has_cleanup` flag to
`TemporaryStorage` and `VarStorage`. There are several related options,
but this felt similar to other information we're trying to track on
instructions. My goal with this is to mitigate the chance of accidental
calls where the storage may not be tracked for destruction. Alternatives
I considered were to not add the flag (I was worried about heightened
risk of errors), or to just add a concept for the relevant `requires`
(which just felt inconsistent).

Cleanup logic ends up in control_flow in this change because I thought
it was a reasonably consistent place for the cleanup block concept and
its pretty direct control flow interactions.
Jon Ross-Perkins 1 жил өмнө
parent
commit
a5df8ad736

+ 1 - 1
toolchain/check/action.cpp

@@ -136,7 +136,7 @@ static auto RefineOperands(Context& context, SemIR::LocId loc_id,
                            SemIR::Inst action) -> SemIR::Inst {
   auto [arg0_kind, arg1_kind] = action.ArgKinds();
   auto arg0 = RefineOperand(context, loc_id, arg0_kind, action.arg0());
-  auto arg1 = RefineOperand(context, loc_id, arg0_kind, action.arg1());
+  auto arg1 = RefineOperand(context, loc_id, arg1_kind, action.arg1());
   action.SetArgs(arg0, arg1);
   return action;
 }

+ 2 - 0
toolchain/check/action.h

@@ -15,6 +15,8 @@ namespace Carbon::Check {
 // Performs a member access action. Defined in member_access.cpp.
 auto PerformAction(Context& context, SemIR::LocId loc_id,
                    SemIR::AccessMemberAction action) -> SemIR::InstId;
+auto PerformAction(Context& context, SemIR::LocId loc_id,
+                   SemIR::AccessOptionalMemberAction action) -> SemIR::InstId;
 
 // Performs a conversion action. Defined in convert.cpp.
 auto PerformAction(Context& context, SemIR::LocId loc_id,

+ 2 - 1
toolchain/check/call.cpp

@@ -6,6 +6,7 @@
 
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/deduce.h"
 #include "toolchain/check/facet_type.h"
@@ -233,7 +234,7 @@ auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id,
     case SemIR::InitRepr::InPlace:
       // Tentatively put storage for a temporary in the function's return slot.
       // This will be replaced if necessary when we perform initialization.
-      return_slot_arg_id = AddInst<SemIR::TemporaryStorage>(
+      return_slot_arg_id = AddInstWithCleanup<SemIR::TemporaryStorage>(
           context, loc_id, {.type_id = return_info.type_id});
       break;
     case SemIR::InitRepr::None:

+ 59 - 0
toolchain/check/control_flow.cpp

@@ -4,7 +4,12 @@
 
 #include "toolchain/check/control_flow.h"
 
+#include "toolchain/base/kind_switch.h"
+#include "toolchain/check/call.h"
 #include "toolchain/check/inst.h"
+#include "toolchain/check/member_access.h"
+#include "toolchain/check/name_lookup.h"
+#include "toolchain/sem_ir/ids.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
 namespace Carbon::Check {
@@ -130,4 +135,58 @@ auto IsCurrentPositionReachable(Context& context) -> bool {
          SemIR::TerminatorKind::Terminator;
 }
 
+auto MaybeAddCleanupForInst(Context& context, SemIR::TypeId type_id,
+                            SemIR::InstId inst_id) -> void {
+  if (!context.scope_stack().PeekIsLexicalScope()) {
+    // Cleanup can only occur in lexical scopes.
+    return;
+  }
+
+  // TODO: Add destruction of members of ArrayType, StructType, and
+  // TupleType. Includes refactoring MaybeAddCleanupForInst to remain
+  // non-recursive.
+
+  auto type_inst = context.types().GetAsInst(type_id);
+  CARBON_KIND_SWITCH(type_inst) {
+    case SemIR::ClassType::Kind: {
+      // TODO: Figure out what destruction of classes with destroyable members
+      // should look like (maybe an implicit `fn destroy` added by
+      // `handle_class.cpp`?).
+      auto destroy_id =
+          PerformMemberAccess(context, SemIR::LocId::None, inst_id,
+                              SemIR::NameId::Destroy, /*required=*/false);
+      if (destroy_id.has_value()) {
+        context.scope_stack().destroy_id_stack().AppendToTop(destroy_id);
+      }
+      break;
+    }
+
+    default:
+      // Not interesting storage for destruction.
+      return;
+  }
+}
+
+// Common support for cleanup blocks.
+static auto AddCleanupBlock(Context& context) -> void {
+  auto destroy_ids = context.scope_stack().destroy_id_stack().PeekArray();
+
+  // If there's nothing to destroy, add the final instruction to the current
+  // block.
+  if (destroy_ids.empty()) {
+    return;
+  }
+
+  for (auto destroy_id : llvm::reverse(destroy_ids)) {
+    PerformCall(context, SemIR::LocId::None, destroy_id, {});
+  }
+}
+
+auto AddReturnCleanupBlock(
+    Context& context,
+    typename decltype(SemIR::Return::Kind)::TypedNodeId node_id) -> void {
+  AddCleanupBlock(context);
+  AddInst(context, node_id, SemIR::Return{});
+}
+
 }  // namespace Carbon::Check

+ 59 - 0
toolchain/check/control_flow.h

@@ -6,6 +6,8 @@
 #define CARBON_TOOLCHAIN_CHECK_CONTROL_FLOW_H_
 
 #include "toolchain/check/context.h"
+#include "toolchain/check/inst.h"
+#include "toolchain/parse/typed_nodes.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
@@ -62,6 +64,63 @@ auto SetBlockArgResultBeforeConstantUse(Context& context,
 // Returns whether the current position in the current block is reachable.
 auto IsCurrentPositionReachable(Context& context) -> bool;
 
+// Determines whether the instruction requires cleanup and, if so, adds it for
+// cleanup blocks. Note for example that a class may not need destruction when
+// neither it nor its members have `destroy` functions.
+auto MaybeAddCleanupForInst(Context& context, SemIR::TypeId type_id,
+                            SemIR::InstId inst_id) -> void;
+
+// Adds an instruction that has cleanup associated.
+template <typename InstT, typename LocT>
+  requires(InstT::Kind.has_cleanup())
+auto AddInstWithCleanup(Context& context, LocT loc, InstT inst)
+    -> SemIR::InstId {
+  auto inst_id = AddInst(context, SemIR::LocIdAndInst(loc, inst));
+  MaybeAddCleanupForInst(context, inst.type_id, inst_id);
+  return inst_id;
+}
+
+// Adds an instruction that has cleanup associated.
+template <typename InstT, typename LocT>
+  requires(InstT::Kind.has_cleanup())
+auto AddInstWithCleanupInNoBlock(Context& context, LocT loc, InstT inst)
+    -> SemIR::InstId {
+  auto inst_id = AddInstInNoBlock(context, SemIR::LocIdAndInst(loc, inst));
+  MaybeAddCleanupForInst(context, inst.type_id, inst_id);
+  return inst_id;
+}
+
+// Cleanup blocks are an effort to share cleanup instructions across equivalent
+// scope-ending instructions (for example, all `return;` instructions are
+// equivalent). Structurally, they should first run non-shared cleanup, then
+// either dispatch to a cleanup block that includes shared cleanup, or invoke
+// the control flow instruction.
+//
+// For example:
+//
+//   fn F() {
+//     var a: C;
+//     if (...) {
+//       // Cleanup block 1: destroy a, return
+//       return;
+//     }
+//
+//     var b: C;
+//     if (...) {
+//       // Cleanup block 2: destroy b, reuse cleanup block 1.
+//       return;
+//     }
+//
+//     DoSomethingMore();
+//     // Cleanup block 3: reuse cleanup block 2.
+//   }
+//
+// TODO: Add support for `return;`, `return <expr>;`, `break;`,and `continue;`;
+// also, add reuse (described above but not done).
+auto AddReturnCleanupBlock(
+    Context& context,
+    typename decltype(SemIR::Return::Kind)::TypedNodeId node_id) -> void;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_CONTROL_FLOW_H_

+ 6 - 4
toolchain/check/convert.cpp

@@ -13,6 +13,7 @@
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/action.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/control_flow.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/impl_lookup.h"
@@ -134,7 +135,7 @@ static auto FinalizeTemporary(Context& context, SemIR::InstId init_id,
   // initialize a temporary, rather than two separate instructions.
   auto init = sem_ir.insts().Get(init_id);
   auto loc_id = sem_ir.insts().GetLocId(init_id);
-  auto temporary_id = AddInst<SemIR::TemporaryStorage>(
+  auto temporary_id = AddInstWithCleanup<SemIR::TemporaryStorage>(
       context, loc_id, {.type_id = init.type_id()});
   return AddInst<SemIR::Temporary>(context, loc_id,
                                    {.type_id = init.type_id(),
@@ -293,8 +294,9 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
   // destination for the array initialization if we weren't given one.
   SemIR::InstId return_slot_arg_id = target.init_id;
   if (!target.init_id.has_value()) {
-    return_slot_arg_id = target_block->AddInst<SemIR::TemporaryStorage>(
-        value_loc_id, {.type_id = target.type_id});
+    return_slot_arg_id =
+        target_block->AddInstWithCleanup<SemIR::TemporaryStorage>(
+            value_loc_id, {.type_id = target.type_id});
   }
 
   // Initialize each element of the array from the corresponding element of the
@@ -619,7 +621,7 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
   if (need_temporary) {
     target.kind = ConversionTarget::Initializer;
     target.init_block = &target_block;
-    target.init_id = target_block.AddInst<SemIR::TemporaryStorage>(
+    target.init_id = target_block.AddInstWithCleanup<SemIR::TemporaryStorage>(
         context.insts().GetLocId(value_id), {.type_id = target.type_id});
   }
 

+ 2 - 1
toolchain/check/handle_function.cpp

@@ -29,6 +29,7 @@
 #include "toolchain/sem_ir/entry_point.h"
 #include "toolchain/sem_ir/function.h"
 #include "toolchain/sem_ir/ids.h"
+#include "toolchain/sem_ir/inst.h"
 #include "toolchain/sem_ir/pattern.h"
 #include "toolchain/sem_ir/typed_insts.h"
 
@@ -641,7 +642,7 @@ auto HandleParseNode(Context& context, Parse::FunctionDefinitionId node_id)
           "missing `return` at end of function with declared return type");
       context.emitter().Emit(TokenOnly(node_id), MissingReturnStatement);
     } else {
-      AddInst<SemIR::Return>(context, node_id, {});
+      AddReturnCleanupBlock(context, node_id);
     }
   }
 

+ 8 - 6
toolchain/check/handle_let_and_var.cpp

@@ -3,6 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 #include "toolchain/check/context.h"
+#include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/decl_introducer_state.h"
 #include "toolchain/check/generic.h"
@@ -119,12 +120,13 @@ static auto GetOrAddStorage(Context& context, SemIR::InstId var_pattern_id)
   }
   auto pattern = context.insts().GetWithLocId(var_pattern_id);
 
-  return AddInst(context, 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)});
+  return AddInstWithCleanup(
+      context, 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)

+ 21 - 2
toolchain/check/inst.h

@@ -16,15 +16,20 @@ auto AddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
     -> SemIR::InstId;
 
 // Convenience for AddInst with typed nodes.
+//
+// As a safety check, prevent use with storage insts (see `AddInstWithCleanup`).
 template <typename InstT, typename LocT>
+  requires(!InstT::Kind.has_cleanup())
 auto AddInst(Context& context, LocT loc, InstT inst) -> SemIR::InstId {
   return AddInst(context, SemIR::LocIdAndInst(loc, inst));
 }
 
 // Pushes a parse tree node onto the stack, storing the SemIR::Inst as the
 // result.
+//
+// As a safety check, prevent use with storage insts (see `AddInstWithCleanup`).
 template <typename InstT>
-  requires(SemIR::Internal::HasNodeId<InstT>)
+  requires(SemIR::Internal::HasNodeId<InstT> && !InstT::Kind.has_cleanup())
 auto AddInstAndPush(Context& context,
                     typename decltype(InstT::Kind)::TypedNodeId node_id,
                     InstT inst) -> void {
@@ -37,7 +42,10 @@ auto AddInstInNoBlock(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
     -> SemIR::InstId;
 
 // Convenience for AddInstInNoBlock with typed nodes.
+//
+// As a safety check, prevent use with storage insts (see `AddInstWithCleanup`).
 template <typename InstT, typename LocT>
+  requires(!InstT::Kind.has_cleanup())
 auto AddInstInNoBlock(Context& context, LocT loc, InstT inst) -> SemIR::InstId {
   return AddInstInNoBlock(context, SemIR::LocIdAndInst(loc, inst));
 }
@@ -48,7 +56,10 @@ auto GetOrAddInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
     -> SemIR::InstId;
 
 // Convenience for GetOrAddInst with typed nodes.
+//
+// As a safety check, prevent use with storage insts (see `AddInstWithCleanup`).
 template <typename InstT, typename LocT>
+  requires(!InstT::Kind.has_cleanup())
 auto GetOrAddInst(Context& context, LocT loc, InstT inst) -> SemIR::InstId {
   return GetOrAddInst(context, SemIR::LocIdAndInst(loc, inst));
 }
@@ -76,8 +87,10 @@ auto AddPatternInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
     -> SemIR::InstId;
 
 // Convenience for AddPatternInst with typed nodes.
+//
+// As a safety check, prevent use with storage insts (see `AddInstWithCleanup`).
 template <typename InstT>
-  requires(SemIR::Internal::HasNodeId<InstT>)
+  requires(SemIR::Internal::HasNodeId<InstT> && !InstT::Kind.has_cleanup())
 auto AddPatternInst(Context& context,
                     typename decltype(InstT::Kind)::TypedNodeId node_id,
                     InstT inst) -> SemIR::InstId {
@@ -91,7 +104,10 @@ auto AddPlaceholderInst(Context& context, SemIR::LocIdAndInst loc_id_and_inst)
     -> SemIR::InstId;
 
 // Convenience for AddPlaceholderInst with typed nodes.
+//
+// As a safety check, prevent use with storage insts (see `AddInstWithCleanup`).
 template <typename InstT, typename LocT>
+  requires(!InstT::Kind.has_cleanup())
 auto AddPlaceholderInst(Context& context, LocT loc, InstT inst)
     -> SemIR::InstId {
   return AddPlaceholderInst(context, SemIR::LocIdAndInst(loc, inst));
@@ -105,7 +121,10 @@ auto AddPlaceholderInstInNoBlock(Context& context,
     -> SemIR::InstId;
 
 // Convenience for AddPlaceholderInstInNoBlock with typed nodes.
+//
+// As a safety check, prevent use with storage insts (see `AddInstWithCleanup`).
 template <typename InstT, typename LocT>
+  requires(!InstT::Kind.has_cleanup())
 auto AddPlaceholderInstInNoBlock(Context& context, LocT loc, InstT inst)
     -> SemIR::InstId {
   return AddPlaceholderInstInNoBlock(context, SemIR::LocIdAndInst(loc, inst));

+ 58 - 37
toolchain/check/member_access.cpp

@@ -245,16 +245,15 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
                                     SemIR::NameId name_id,
                                     SemIR::ConstantId name_scope_const_id,
                                     llvm::ArrayRef<LookupScope> lookup_scopes,
-                                    bool lookup_in_type_of_base)
+                                    bool lookup_in_type_of_base, bool required)
     -> SemIR::InstId {
   AccessInfo access_info = {
       .constant_id = name_scope_const_id,
       .highest_allowed_access =
           GetHighestAllowedAccess(context, loc_id, name_scope_const_id),
   };
-  LookupResult result =
-      LookupQualifiedName(context, loc_id, name_id, lookup_scopes,
-                          /*required=*/true, access_info);
+  LookupResult result = LookupQualifiedName(
+      context, loc_id, name_id, lookup_scopes, required, access_info);
 
   if (!result.scope_result.is_found()) {
     return SemIR::ErrorInst::SingletonInstId;
@@ -458,24 +457,31 @@ static auto ValidateTupleIndex(Context& context, SemIR::LocId loc_id,
 }
 
 auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,
-                         SemIR::InstId base_id, SemIR::NameId name_id)
-    -> SemIR::InstId {
+                         SemIR::InstId base_id, SemIR::NameId name_id,
+                         bool required) -> SemIR::InstId {
   // TODO: Member access for dependent member names is supposed to perform a
   // lookup in both the template definition context and the template
   // instantiation context, and reject if both succeed but find different
   // things.
-  return HandleAction<SemIR::AccessMemberAction>(
-      context, loc_id,
-      {.type_id = SemIR::InstType::SingletonTypeId,
-       .base_id = base_id,
-       .name_id = name_id});
+  if (required) {
+    return HandleAction<SemIR::AccessMemberAction>(
+        context, loc_id,
+        {.type_id = SemIR::InstType::SingletonTypeId,
+         .base_id = base_id,
+         .name_id = name_id});
+  } else {
+    return HandleAction<SemIR::AccessOptionalMemberAction>(
+        context, loc_id,
+        {.type_id = SemIR::InstType::SingletonTypeId,
+         .base_id = base_id,
+         .name_id = name_id});
+  }
 }
 
-auto PerformAction(Context& context, SemIR::LocId loc_id,
-                   SemIR::AccessMemberAction action) -> SemIR::InstId {
-  SemIR::InstId base_id = action.base_id;
-  SemIR::NameId name_id = action.name_id;
-
+// Common logic for `AccessMemberAction` and `AccessOptionalMemberAction`.
+static auto PerformActionHelper(Context& context, SemIR::LocId loc_id,
+                                SemIR::InstId base_id, SemIR::NameId name_id,
+                                bool required) -> SemIR::InstId {
   // If the base is a name scope, such as a class or namespace, perform lookup
   // into that scope.
   if (auto base_const_id = context.constant_values().Get(base_id);
@@ -483,23 +489,22 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
     llvm::SmallVector<LookupScope> lookup_scopes;
     if (AppendLookupScopesForConstant(context, loc_id, base_const_id,
                                       &lookup_scopes)) {
-      return LookupMemberNameInScope(context, loc_id, base_id, name_id,
-                                     base_const_id, lookup_scopes,
-                                     /*lookup_in_type_of_base=*/false);
+      return LookupMemberNameInScope(
+          context, loc_id, base_id, name_id, base_const_id, lookup_scopes,
+          /*lookup_in_type_of_base=*/false, /*required=*/required);
     }
   }
 
   // If the base isn't a scope, it must have a complete type.
   auto base_type_id = context.insts().Get(base_id).type_id();
-  if (!RequireCompleteType(
-          context, base_type_id, context.insts().GetLocId(base_id), [&] {
-            CARBON_DIAGNOSTIC(
-                IncompleteTypeInMemberAccess, Error,
-                "member access into object of incomplete type {0}",
-                TypeOfInstId);
-            return context.emitter().Build(
-                base_id, IncompleteTypeInMemberAccess, base_id);
-          })) {
+  auto base_loc_id = context.insts().GetLocId(base_id);
+  if (!RequireCompleteType(context, base_type_id, base_loc_id, [&] {
+        CARBON_DIAGNOSTIC(IncompleteTypeInMemberAccess, Error,
+                          "member access into object of incomplete type {0}",
+                          TypeOfInstId);
+        return context.emitter().Build(base_id, IncompleteTypeInMemberAccess,
+                                       base_id);
+      })) {
     return SemIR::ErrorInst::SingletonInstId;
   }
 
@@ -528,12 +533,16 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
                .index = SemIR::ElementIndex(i)});
         }
       }
-      CARBON_DIAGNOSTIC(QualifiedExprNameNotFound, Error,
-                        "type {0} does not have a member `{1}`", TypeOfInstId,
-                        SemIR::NameId);
-      context.emitter().Emit(loc_id, QualifiedExprNameNotFound, base_id,
-                             name_id);
-      return SemIR::ErrorInst::SingletonInstId;
+      if (required) {
+        CARBON_DIAGNOSTIC(QualifiedExprNameNotFound, Error,
+                          "type {0} does not have a member `{1}`", TypeOfInstId,
+                          SemIR::NameId);
+        context.emitter().Emit(loc_id, QualifiedExprNameNotFound, base_id,
+                               name_id);
+        return SemIR::ErrorInst::SingletonInstId;
+      } else {
+        return SemIR::InstId::None;
+      }
     }
 
     if (base_type_id != SemIR::ErrorInst::SingletonTypeId) {
@@ -546,9 +555,9 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
   }
 
   // Perform lookup into the base type.
-  auto member_id = LookupMemberNameInScope(context, loc_id, base_id, name_id,
-                                           base_type_const_id, lookup_scopes,
-                                           /*lookup_in_type_of_base=*/true);
+  auto member_id = LookupMemberNameInScope(
+      context, loc_id, base_id, name_id, base_type_const_id, lookup_scopes,
+      /*lookup_in_type_of_base=*/true, /*required=*/required);
 
   // For name lookup into a facet, never perform instance binding.
   // TODO: According to the design, this should be a "lookup in base" lookup,
@@ -564,6 +573,18 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
   return member_id;
 }
 
+auto PerformAction(Context& context, SemIR::LocId loc_id,
+                   SemIR::AccessMemberAction action) -> SemIR::InstId {
+  return PerformActionHelper(context, loc_id, action.base_id, action.name_id,
+                             /*required=*/true);
+}
+
+auto PerformAction(Context& context, SemIR::LocId loc_id,
+                   SemIR::AccessOptionalMemberAction action) -> SemIR::InstId {
+  return PerformActionHelper(context, loc_id, action.base_id, action.name_id,
+                             /*required=*/false);
+}
+
 // Logic shared by GetAssociatedValue() and PerformCompoundMemberAccess().
 static auto GetAssociatedValueImpl(Context& context, SemIR::LocId loc_id,
                                    SemIR::InstId base_id,

+ 5 - 3
toolchain/check/member_access.h

@@ -11,10 +11,12 @@
 namespace Carbon::Check {
 
 // Creates SemIR to perform a member access with base expression `base_id` and
-// member name `name_id`. Returns the result of the access.
+// member name `name_id`. When `required`, failing to find the name is a
+// diagnosed error; otherwise, `None` is returned. Returns the result of the
+// access.
 auto PerformMemberAccess(Context& context, SemIR::LocId loc_id,
-                         SemIR::InstId base_id, SemIR::NameId name_id)
-    -> SemIR::InstId;
+                         SemIR::InstId base_id, SemIR::NameId name_id,
+                         bool required = true) -> SemIR::InstId;
 
 // Creates SemIR to perform a compound member access with base expression
 // `base_id` and member name expression `member_expr_id`. Returns the result of

+ 3 - 2
toolchain/check/pattern_match.cpp

@@ -11,6 +11,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/control_flow.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/subpattern.h"
 #include "toolchain/check/type.h"
@@ -257,7 +258,7 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
           AddrSelfIsNonRef);
       // Add fake reference expression to preserve invariants.
       auto scrutinee = context.insts().GetWithLocId(entry.scrutinee_id);
-      scrutinee_ref_id = AddInst<SemIR::TemporaryStorage>(
+      scrutinee_ref_id = AddInstWithCleanup<SemIR::TemporaryStorage>(
           context, scrutinee.loc_id, {.type_id = scrutinee.inst.type_id()});
   }
   auto scrutinee_ref = context.insts().Get(scrutinee_ref_id);
@@ -436,7 +437,7 @@ auto MatchContext::DoEmitPatternMatch(Context& context,
       break;
     }
     case MatchKind::Caller: {
-      storage_id = AddInst<SemIR::TemporaryStorage>(
+      storage_id = AddInstWithCleanup<SemIR::TemporaryStorage>(
           context, pattern_loc_id, {.type_id = var_pattern.type_id});
       CARBON_CHECK(entry.scrutinee_id.has_value());
       break;

+ 7 - 0
toolchain/check/pending_block.h

@@ -49,6 +49,13 @@ class PendingBlock {
     return inst_id;
   }
 
+  template <typename InstT, typename LocT>
+  auto AddInstWithCleanup(LocT loc_id, InstT inst) -> SemIR::InstId {
+    auto inst_id = AddInstWithCleanupInNoBlock(context_, loc_id, inst);
+    insts_.push_back(inst_id);
+    return inst_id;
+  }
+
   // Insert the pending block of code at the current position.
   auto InsertHere() -> void {
     for (auto id : insts_) {

+ 27 - 9
toolchain/check/scope_stack.cpp

@@ -10,7 +10,16 @@
 namespace Carbon::Check {
 
 auto ScopeStack::VerifyOnFinish() const -> void {
+  CARBON_CHECK(return_scope_stack_.empty(), "{0}", return_scope_stack_.size());
+  CARBON_CHECK(break_continue_stack_.empty(), "{0}",
+               break_continue_stack_.size());
   CARBON_CHECK(scope_stack_.empty(), "{0}", scope_stack_.size());
+  CARBON_CHECK(destroy_id_stack_.empty(), "{0}",
+               destroy_id_stack_.all_values_size());
+  CARBON_CHECK(non_lexical_scope_stack_.empty(), "{0}",
+               non_lexical_scope_stack_.size());
+  CARBON_CHECK(compile_time_binding_stack_.empty(), "{0}",
+               compile_time_binding_stack_.all_values_size());
   full_pattern_stack_.VerifyOnFinish();
 }
 
@@ -46,16 +55,18 @@ auto ScopeStack::Push(SemIR::InstId scope_inst_id, SemIR::NameScopeId scope_id,
            compile_time_binding_stack_.all_values_size()),
        .lexical_lookup_has_load_error =
            LexicalLookupHasLoadError() || lexical_lookup_has_load_error});
-  if (scope_id.has_value()) {
-    non_lexical_scope_stack_.push_back({.scope_index = next_scope_index_,
-                                        .name_scope_id = scope_id,
-                                        .specific_id = enclosing_specific_id});
-  } else {
+  if (scope_stack_.back().is_lexical_scope()) {
     // For lexical lookups, unqualified lookup doesn't know how to find the
     // associated specific, so if we start adding lexical scopes associated with
     // specifics, we'll need to somehow track them in lookup.
     CARBON_CHECK(!specific_id.has_value(),
                  "Lexical scope should not have an associated specific.");
+
+    destroy_id_stack_.PushArray();
+  } else {
+    non_lexical_scope_stack_.push_back({.scope_index = next_scope_index_,
+                                        .name_scope_id = scope_id,
+                                        .specific_id = enclosing_specific_id});
   }
 
   // TODO: Handle this case more gracefully.
@@ -76,7 +87,9 @@ auto ScopeStack::Pop() -> void {
     lexical_results.pop_back();
   });
 
-  if (scope.scope_id.has_value()) {
+  if (scope.is_lexical_scope()) {
+    destroy_id_stack_.PopArray();
+  } else {
     CARBON_CHECK(non_lexical_scope_stack_.back().scope_index == scope.index);
     non_lexical_scope_stack_.pop_back();
   }
@@ -203,10 +216,13 @@ auto ScopeStack::Suspend() -> SuspendedScope {
   CARBON_CHECK(!scope_stack_.empty(), "No scope to suspend");
   SuspendedScope result = {.entry = scope_stack_.pop_back_val(),
                            .suspended_items = {}};
-  if (result.entry.scope_id.has_value()) {
+  if (result.entry.is_lexical_scope()) {
+    CARBON_CHECK(destroy_id_stack_.PeekArray().empty(),
+                 "Missing support to suspend scopes with destructed storage");
+    destroy_id_stack_.PopArray();
+  } else {
     non_lexical_scope_stack_.pop_back();
   }
-
   auto peek_compile_time_bindings = compile_time_binding_stack_.PeekArray();
   result.suspended_items.reserve(result.entry.num_names +
                                  peek_compile_time_bindings.size());
@@ -247,7 +263,9 @@ auto ScopeStack::Restore(SuspendedScope scope) -> void {
 
   VerifyNextCompileTimeBindIndex("Restore", scope.entry);
 
-  if (scope.entry.scope_id.has_value()) {
+  if (scope.entry.is_lexical_scope()) {
+    destroy_id_stack_.PushArray();
+  } else {
     non_lexical_scope_stack_.push_back(
         {.scope_index = scope.entry.index,
          .name_scope_id = scope.entry.scope_id,

+ 13 - 2
toolchain/check/scope_stack.h

@@ -98,6 +98,9 @@ class ScopeStack {
     return Peek().specific_id;
   }
 
+  // Returns true if current scope is lexical.
+  auto PeekIsLexicalScope() const -> bool { return Peek().is_lexical_scope(); }
+
   // Returns the current scope, if it is of the specified kind. Otherwise,
   // returns nullopt.
   template <typename InstT>
@@ -165,6 +168,10 @@ class ScopeStack {
     return break_continue_stack_;
   }
 
+  auto destroy_id_stack() -> ArrayStack<SemIR::InstId>& {
+    return destroy_id_stack_;
+  }
+
   auto compile_time_bindings_stack() -> ArrayStack<SemIR::InstId>& {
     return compile_time_binding_stack_;
   }
@@ -174,6 +181,8 @@ class ScopeStack {
  private:
   // An entry in scope_stack_.
   struct ScopeStackEntry {
+    auto is_lexical_scope() const -> bool { return !scope_id.has_value(); }
+
     // The sequential index of this scope entry within the file.
     ScopeIndex index;
 
@@ -208,8 +217,6 @@ class ScopeStack {
     // Names which are registered with lexical_lookup_, and will need to be
     // unregistered when the scope ends.
     Set<SemIR::NameId> names = {};
-
-    // TODO: This likely needs to track things which need to be destructed.
   };
 
   auto Peek() const -> const ScopeStackEntry& { return scope_stack_.back(); }
@@ -239,6 +246,10 @@ class ScopeStack {
   // A stack for scope context.
   llvm::SmallVector<ScopeStackEntry> scope_stack_;
 
+  // A stack of `destroy` functions to call. This only has entries for lexical
+  // scopes, because non-lexical scopes don't have destruction on scope exit.
+  ArrayStack<SemIR::InstId> destroy_id_stack_;
+
   // Information about non-lexical scopes. This is a subset of the entries and
   // the information in scope_stack_.
   llvm::SmallVector<NonLexicalScope> non_lexical_scope_stack_;

+ 0 - 734
toolchain/check/testdata/class/no_prelude/destroy.carbon

@@ -1,734 +0,0 @@
-// 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/class/no_prelude/destroy.carbon
-// TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/no_prelude/destroy.carbon
-
-// --- self.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  fn destroy[self: Self]();
-}
-
-// --- addr_self.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  fn destroy[addr self: Self*]();
-}
-
-// --- explicit_return.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  fn destroy[self: Self]() -> ();
-}
-
-// --- fail_class_function.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  // CHECK:STDERR: fail_class_function.carbon:[[@LINE+4]]:3: error: missing implicit `self` parameter [DestroyFunctionMissingSelf]
-  // CHECK:STDERR:   fn destroy();
-  // CHECK:STDERR:   ^~~~~~~~~~~~~
-  // CHECK:STDERR:
-  fn destroy();
-}
-
-// --- fail_extra_implicit_params_second.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  // CHECK:STDERR: fail_extra_implicit_params_second.carbon:[[@LINE+4]]:26: error: unexpected implicit parameter [DestroyFunctionUnexpectedImplicitParam]
-  // CHECK:STDERR:   fn destroy[self: Self, T:! type]();
-  // CHECK:STDERR:                          ^
-  // CHECK:STDERR:
-  fn destroy[self: Self, T:! type]();
-}
-
-// --- fail_extra_implicit_params_first.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  // CHECK:STDERR: fail_extra_implicit_params_first.carbon:[[@LINE+4]]:14: error: unexpected implicit parameter [DestroyFunctionUnexpectedImplicitParam]
-  // CHECK:STDERR:   fn destroy[T:! type, self: Self]();
-  // CHECK:STDERR:              ^
-  // CHECK:STDERR:
-  fn destroy[T:! type, self: Self]();
-}
-
-// --- fail_positional_params.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  // CHECK:STDERR: fail_positional_params.carbon:[[@LINE+4]]:3: error: missing empty explicit parameter list [DestroyFunctionPositionalParams]
-  // CHECK:STDERR:   fn destroy[self: Self];
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~
-  // CHECK:STDERR:
-  fn destroy[self: Self];
-}
-
-// --- fail_explicit_params.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  // CHECK:STDERR: fail_explicit_params.carbon:[[@LINE+4]]:26: error: unexpected parameter [DestroyFunctionNonEmptyExplicitParams]
-  // CHECK:STDERR:   fn destroy[self: Self](x: ());
-  // CHECK:STDERR:                          ^~~~~
-  // CHECK:STDERR:
-  fn destroy[self: Self](x: ());
-}
-
-// --- fail_return_type.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  // CHECK:STDERR: fail_return_type.carbon:[[@LINE+4]]:28: error: incorrect return type; must be unspecified or `()` [DestroyFunctionIncorrectReturnType]
-  // CHECK:STDERR:   fn destroy[self: Self]() -> {};
-  // CHECK:STDERR:                            ^~~~~
-  // CHECK:STDERR:
-  fn destroy[self: Self]() -> {};
-}
-
-// --- fail_out_of_line_missing_params.carbon
-
-library "[[@TEST_NAME]]";
-
-
-class C {
-  fn destroy[self: Self]();
-}
-
-// CHECK:STDERR: fail_out_of_line_missing_params.carbon:[[@LINE+7]]:1: error: redeclaration differs because of missing implicit parameter list [RedeclParamListDiffers]
-// CHECK:STDERR: fn C.destroy {}
-// CHECK:STDERR: ^~~~~~~~~~~~~~
-// CHECK:STDERR: fail_out_of_line_missing_params.carbon:[[@LINE-6]]:3: note: previously declared with implicit parameter list [RedeclParamListPrevious]
-// CHECK:STDERR:   fn destroy[self: Self]();
-// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~
-// CHECK:STDERR:
-fn C.destroy {}
-
-// --- fail_destroy_in_file_scope.carbon
-
-library "[[@TEST_NAME]]";
-
-// CHECK:STDERR: fail_destroy_in_file_scope.carbon:[[@LINE+4]]:1: error: declaring `fn destroy` in non-class scope [DestroyFunctionOutsideClass]
-// CHECK:STDERR: fn destroy[self: ()]();
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~
-// CHECK:STDERR:
-fn destroy[self: ()]();
-
-// --- fail_destroy_in_namespace_scope.carbon
-
-library "[[@TEST_NAME]]";
-
-namespace NS;
-
-// CHECK:STDERR: fail_destroy_in_namespace_scope.carbon:[[@LINE+4]]:1: error: declaring `fn destroy` in non-class scope [DestroyFunctionOutsideClass]
-// CHECK:STDERR: fn NS.destroy();
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~
-// CHECK:STDERR:
-fn NS.destroy();
-
-// --- fail_invalid_qualifier_with_params.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  fn destroy[self: Self]();
-}
-
-// CHECK:STDERR: fail_invalid_qualifier_with_params.carbon:[[@LINE+7]]:6: error: name qualifiers are only allowed for entities that provide a scope [QualifiedNameInNonScope]
-// CHECK:STDERR: fn C.destroy[self: Self]().Foo() {}
-// CHECK:STDERR:      ^~~~~~~
-// CHECK:STDERR: fail_invalid_qualifier_with_params.carbon:[[@LINE-6]]:3: note: referenced non-scope entity declared here [QualifiedNameNonScopeEntity]
-// CHECK:STDERR:   fn destroy[self: Self]();
-// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~
-// CHECK:STDERR:
-fn C.destroy[self: Self]().Foo() {}
-
-// --- fail_invalid_qualifier_no_params.carbon
-
-library "[[@TEST_NAME]]";
-
-class C {
-  fn destroy[self: Self]();
-}
-
-// CHECK:STDERR: fail_invalid_qualifier_no_params.carbon:[[@LINE+7]]:6: error: name qualifiers are only allowed for entities that provide a scope [QualifiedNameInNonScope]
-// CHECK:STDERR: fn C.destroy.Foo() {}
-// CHECK:STDERR:      ^~~~~~~
-// CHECK:STDERR: fail_invalid_qualifier_no_params.carbon:[[@LINE-6]]:3: note: referenced non-scope entity declared here [QualifiedNameNonScopeEntity]
-// CHECK:STDERR:   fn destroy[self: Self]();
-// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~
-// CHECK:STDERR:
-fn C.destroy.Foo() {}
-
-// CHECK:STDOUT: --- self.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- addr_self.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %ptr: type = ptr_type %C [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %ptr = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %ptr = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:     %.loc5_14: auto = addr_pattern %self.param_patt
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %ptr = value_param call_param0
-// CHECK:STDOUT:     %.loc5_29: type = splice_block %ptr [concrete = constants.%ptr] {
-// CHECK:STDOUT:       %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:       %ptr: type = ptr_type %C [concrete = constants.%ptr]
-// CHECK:STDOUT:     }
-// CHECK:STDOUT:     %self: %ptr = 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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[addr %self.param_patt: %ptr]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- explicit_return.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:     %return.patt: %empty_tuple.type = return_slot_pattern
-// CHECK:STDOUT:     %return.param_patt: %empty_tuple.type = out_param_pattern %return.patt, call_param1
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %.loc5_32.1: %empty_tuple.type = tuple_literal ()
-// CHECK:STDOUT:     %.loc5_32.2: type = converted %.loc5_32.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %C = bind_name self, %self.param
-// CHECK:STDOUT:     %return.param: ref %empty_tuple.type = out_param call_param1
-// CHECK:STDOUT:     %return: ref %empty_tuple.type = return_slot %return.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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]() -> %empty_tuple.type;
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_class_function.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {} {}
-// 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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy();
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_extra_implicit_params_second.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
-// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:     %T.patt.loc9_26.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc9_26.2 (constants.%T.patt)]
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %C = bind_name self, %self.param
-// CHECK:STDOUT:     %T.loc9_26.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_26.1 (constants.%T)]
-// 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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: generic fn @destroy(%T.loc9_26.2: type) {
-// CHECK:STDOUT:   %T.loc9_26.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_26.1 (constants.%T)]
-// CHECK:STDOUT:   %T.patt.loc9_26.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc9_26.2 (constants.%T.patt)]
-// CHECK:STDOUT:
-// CHECK:STDOUT:   fn[%self.param_patt: %C, %T.patt.loc9_26.1: type]();
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: specific @destroy(constants.%T) {
-// CHECK:STDOUT:   %T.loc9_26.1 => constants.%T
-// CHECK:STDOUT:   %T.patt.loc9_26.2 => constants.%T
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_extra_implicit_params_first.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
-// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0 [symbolic]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %T.patt.loc9_14.1: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc9_14.2 (constants.%T.patt)]
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %T.loc9_14.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_14.1 (constants.%T)]
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: generic fn @destroy(%T.loc9_14.2: type) {
-// CHECK:STDOUT:   %T.loc9_14.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc9_14.1 (constants.%T)]
-// CHECK:STDOUT:   %T.patt.loc9_14.2: type = symbolic_binding_pattern T, 0 [symbolic = %T.patt.loc9_14.2 (constants.%T.patt)]
-// CHECK:STDOUT:
-// CHECK:STDOUT:   fn[%T.patt.loc9_14.1: type, %self.param_patt: %C]();
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: specific @destroy(constants.%T) {
-// CHECK:STDOUT:   %T.loc9_14.1 => constants.%T
-// CHECK:STDOUT:   %T.patt.loc9_14.2 => constants.%T
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_positional_params.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_explicit_params.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
-// CHECK:STDOUT:     %x.param_patt: %empty_tuple.type = value_param_pattern %x.patt, call_param1
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %C = bind_name self, %self.param
-// CHECK:STDOUT:     %x.param: %empty_tuple.type = value_param call_param1
-// CHECK:STDOUT:     %.loc9_30.1: type = splice_block %.loc9_30.3 [concrete = constants.%empty_tuple.type] {
-// CHECK:STDOUT:       %.loc9_30.2: %empty_tuple.type = tuple_literal ()
-// CHECK:STDOUT:       %.loc9_30.3: type = converted %.loc9_30.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:   }
-// 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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_return_type.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.type = struct_value () [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:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:     %return.patt: %empty_struct_type = return_slot_pattern
-// CHECK:STDOUT:     %return.param_patt: %empty_struct_type = out_param_pattern %return.patt, call_param1
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %.loc9_32.1: %empty_struct_type = struct_literal ()
-// CHECK:STDOUT:     %.loc9_32.2: type = converted %.loc9_32.1, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %C = bind_name self, %self.param
-// CHECK:STDOUT:     %return.param: ref %empty_struct_type = out_param call_param1
-// CHECK:STDOUT:     %return: ref %empty_struct_type = return_slot %return.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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]() -> %empty_struct_type;
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_out_of_line_missing_params.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %destroy.type.bd30b6.1: type = fn_type @destroy.1 [concrete]
-// CHECK:STDOUT:   %destroy.357925.1: %destroy.type.bd30b6.1 = 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:   %destroy.type.bd30b6.2: type = fn_type @destroy.2 [concrete]
-// CHECK:STDOUT:   %destroy.357925.2: %destroy.type.bd30b6.2 = struct_value () [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:   %destroy.decl: %destroy.type.bd30b6.2 = fn_decl @destroy.2 [concrete = constants.%destroy.357925.2] {} {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   %destroy.decl: %destroy.type.bd30b6.1 = fn_decl @destroy.1 [concrete = constants.%destroy.357925.1] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy.1[%self.param_patt: %C]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy.2() {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   return
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_destroy_in_file_scope.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.type = struct_value () [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .destroy = %destroy.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %empty_tuple.type = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %empty_tuple.type = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %empty_tuple.type = value_param call_param0
-// CHECK:STDOUT:     %.loc8_19.1: type = splice_block %.loc8_19.3 [concrete = constants.%empty_tuple.type] {
-// CHECK:STDOUT:       %.loc8_19.2: %empty_tuple.type = tuple_literal ()
-// CHECK:STDOUT:       %.loc8_19.3: type = converted %.loc8_19.2, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
-// CHECK:STDOUT:     }
-// CHECK:STDOUT:     %self: %empty_tuple.type = bind_name self, %self.param
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %empty_tuple.type]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_destroy_in_namespace_scope.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.type = struct_value () [concrete]
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: file {
-// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .NS = %NS
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %NS: <namespace> = namespace [concrete] {
-// CHECK:STDOUT:     .destroy = %destroy.decl
-// CHECK:STDOUT:   }
-// CHECK:STDOUT:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {} {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy();
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_invalid_qualifier_with_params.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %Foo.type: type = fn_type @Foo [concrete]
-// CHECK:STDOUT:   %Foo: %Foo.type = struct_value () [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:   %Foo.decl: %Foo.type = fn_decl @Foo [concrete = constants.%Foo] {} {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %C = bind_name self, %self.param
-// CHECK:STDOUT:   }
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @Foo() {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   return
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: --- fail_invalid_qualifier_no_params.carbon
-// CHECK:STDOUT:
-// CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %C: type = class_type @C [concrete]
-// CHECK:STDOUT:   %destroy.type: type = fn_type @destroy [concrete]
-// CHECK:STDOUT:   %destroy: %destroy.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:   %Foo.type: type = fn_type @Foo [concrete]
-// CHECK:STDOUT:   %Foo: %Foo.type = struct_value () [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:   %Foo.decl: %Foo.type = fn_decl @Foo [concrete = constants.%Foo] {} {}
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   %destroy.decl: %destroy.type = fn_decl @destroy [concrete = constants.%destroy] {
-// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
-// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, call_param0
-// CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %self.param: %C = value_param call_param0
-// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
-// CHECK:STDOUT:     %self: %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:   .destroy = %destroy.decl
-// CHECK:STDOUT: }
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @destroy[%self.param_patt: %C]();
-// CHECK:STDOUT:
-// CHECK:STDOUT: fn @Foo() {
-// CHECK:STDOUT: !entry:
-// CHECK:STDOUT:   return
-// CHECK:STDOUT: }
-// CHECK:STDOUT:

+ 850 - 0
toolchain/check/testdata/class/no_prelude/destroy_calls.carbon

@@ -0,0 +1,850 @@
+// 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/class/no_prelude/destroy_calls.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/no_prelude/destroy_calls.carbon
+
+// --- types.carbon
+
+library "[[@TEST_NAME]]";
+
+class NoAddr {
+  fn Make() -> NoAddr;
+  fn destroy[self: Self]();
+}
+
+class ExplicitReturn {
+  fn Make() -> ExplicitReturn;
+  fn destroy[self: Self]() -> ();
+}
+
+class WithAddr {
+  fn Make() -> WithAddr;
+  fn destroy[addr self: Self*]();
+}
+
+// --- implicit_return.carbon
+
+library "[[@TEST_NAME]]";
+import library "types";
+
+fn F() {
+  var no_addr: NoAddr;
+  var explicit_return: ExplicitReturn;
+  var with_addr: WithAddr;
+}
+
+// --- nested_scope.carbon
+
+library "[[@TEST_NAME]]";
+import library "types";
+
+fn F() {
+  var no_addr: NoAddr;
+  var explicit_return: ExplicitReturn;
+  var with_addr: WithAddr;
+  if (true) {
+    // TODO: Destroy in nested scope.
+    var in_scope: NoAddr;
+  }
+}
+
+// --- temp.carbon
+
+library "[[@TEST_NAME]]";
+import library "types";
+
+fn F() {
+  // TODO: The scoping of these destroy calls is incorrect. Maybe we need to
+  // establish statement scopes?
+  NoAddr.Make();
+  ExplicitReturn.Make();
+  WithAddr.Make();
+}
+
+// --- fail_recovery.carbon
+
+library "[[@TEST_NAME]]";
+
+class NoSelf {
+  // CHECK:STDERR: fail_recovery.carbon:[[@LINE+4]]:3: error: missing implicit `self` parameter [DestroyFunctionMissingSelf]
+  // CHECK:STDERR:   fn destroy();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~
+  // CHECK:STDERR:
+  fn destroy();
+}
+
+class Args {
+  // CHECK:STDERR: fail_recovery.carbon:[[@LINE+4]]:26: error: unexpected parameter [DestroyFunctionNonEmptyExplicitParams]
+  // CHECK:STDERR:   fn destroy[self: Self](x: ());
+  // CHECK:STDERR:                          ^~~~~
+  // CHECK:STDERR:
+  fn destroy[self: Self](x: ());
+}
+
+fn F() {
+  var a: NoSelf;
+  var b: Args;
+}
+
+// --- fail_not_breaking_generics.carbon
+// CHECK:STDERR: fail_not_breaking_generics.carbon: error: value of type `<dependent type>` is not callable [CallToNonCallable]
+// CHECK:STDERR:
+
+class C(template T:! type) {}
+
+fn F(template T:! type) {
+  var v: C(T);
+}
+
+fn G() { F({}); }
+
+// CHECK:STDOUT: --- types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %NoAddr: type = class_type @NoAddr [concrete]
+// CHECK:STDOUT:   %Make.type.bc9: type = fn_type @Make.1 [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %Make.1be: %Make.type.bc9 = struct_value () [concrete]
+// CHECK:STDOUT:   %destroy.type.bc5: type = fn_type @destroy.1 [concrete]
+// CHECK:STDOUT:   %destroy.60f: %destroy.type.bc5 = 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:   %ExplicitReturn: type = class_type @ExplicitReturn [concrete]
+// CHECK:STDOUT:   %Make.type.378: type = fn_type @Make.2 [concrete]
+// CHECK:STDOUT:   %Make.960: %Make.type.378 = struct_value () [concrete]
+// CHECK:STDOUT:   %destroy.type.dfa: type = fn_type @destroy.2 [concrete]
+// CHECK:STDOUT:   %destroy.539: %destroy.type.dfa = struct_value () [concrete]
+// CHECK:STDOUT:   %WithAddr: type = class_type @WithAddr [concrete]
+// CHECK:STDOUT:   %Make.type.e14: type = fn_type @Make.3 [concrete]
+// CHECK:STDOUT:   %Make.b0a: %Make.type.e14 = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr: type = ptr_type %WithAddr [concrete]
+// CHECK:STDOUT:   %destroy.type.02f: type = fn_type @destroy.3 [concrete]
+// CHECK:STDOUT:   %destroy.8d0: %destroy.type.02f = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .NoAddr = %NoAddr.decl
+// CHECK:STDOUT:     .ExplicitReturn = %ExplicitReturn.decl
+// CHECK:STDOUT:     .WithAddr = %WithAddr.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %NoAddr.decl: type = class_decl @NoAddr [concrete = constants.%NoAddr] {} {}
+// CHECK:STDOUT:   %ExplicitReturn.decl: type = class_decl @ExplicitReturn [concrete = constants.%ExplicitReturn] {} {}
+// CHECK:STDOUT:   %WithAddr.decl: type = class_decl @WithAddr [concrete = constants.%WithAddr] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NoAddr {
+// CHECK:STDOUT:   %Make.decl: %Make.type.bc9 = fn_decl @Make.1 [concrete = constants.%Make.1be] {
+// CHECK:STDOUT:     %return.patt: %NoAddr = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %NoAddr = out_param_pattern %return.patt, call_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %NoAddr.ref: type = name_ref NoAddr, file.%NoAddr.decl [concrete = constants.%NoAddr]
+// CHECK:STDOUT:     %return.param: ref %NoAddr = out_param call_param0
+// CHECK:STDOUT:     %return: ref %NoAddr = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %destroy.decl: %destroy.type.bc5 = fn_decl @destroy.1 [concrete = constants.%destroy.60f] {
+// CHECK:STDOUT:     %self.patt: %NoAddr = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %NoAddr = value_param_pattern %self.patt, call_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %NoAddr = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%NoAddr [concrete = constants.%NoAddr]
+// CHECK:STDOUT:     %self: %NoAddr = 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.%NoAddr
+// CHECK:STDOUT:   .NoAddr = <poisoned>
+// CHECK:STDOUT:   .Make = %Make.decl
+// CHECK:STDOUT:   .destroy = %destroy.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @ExplicitReturn {
+// CHECK:STDOUT:   %Make.decl: %Make.type.378 = fn_decl @Make.2 [concrete = constants.%Make.960] {
+// CHECK:STDOUT:     %return.patt: %ExplicitReturn = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %ExplicitReturn = out_param_pattern %return.patt, call_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %ExplicitReturn.ref: type = name_ref ExplicitReturn, file.%ExplicitReturn.decl [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:     %return.param: ref %ExplicitReturn = out_param call_param0
+// CHECK:STDOUT:     %return: ref %ExplicitReturn = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %destroy.decl: %destroy.type.dfa = fn_decl @destroy.2 [concrete = constants.%destroy.539] {
+// CHECK:STDOUT:     %self.patt: %ExplicitReturn = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %ExplicitReturn = value_param_pattern %self.patt, call_param0
+// CHECK:STDOUT:     %return.patt: %empty_tuple.type = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %empty_tuple.type = out_param_pattern %return.patt, call_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %.loc11_32.1: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:     %.loc11_32.2: type = converted %.loc11_32.1, constants.%empty_tuple.type [concrete = constants.%empty_tuple.type]
+// CHECK:STDOUT:     %self.param: %ExplicitReturn = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%ExplicitReturn [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:     %self: %ExplicitReturn = bind_name self, %self.param
+// CHECK:STDOUT:     %return.param: ref %empty_tuple.type = out_param call_param1
+// CHECK:STDOUT:     %return: ref %empty_tuple.type = return_slot %return.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.%ExplicitReturn
+// CHECK:STDOUT:   .ExplicitReturn = <poisoned>
+// CHECK:STDOUT:   .Make = %Make.decl
+// CHECK:STDOUT:   .destroy = %destroy.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @WithAddr {
+// CHECK:STDOUT:   %Make.decl: %Make.type.e14 = fn_decl @Make.3 [concrete = constants.%Make.b0a] {
+// CHECK:STDOUT:     %return.patt: %WithAddr = return_slot_pattern
+// CHECK:STDOUT:     %return.param_patt: %WithAddr = out_param_pattern %return.patt, call_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %WithAddr.ref: type = name_ref WithAddr, file.%WithAddr.decl [concrete = constants.%WithAddr]
+// CHECK:STDOUT:     %return.param: ref %WithAddr = out_param call_param0
+// CHECK:STDOUT:     %return: ref %WithAddr = return_slot %return.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %destroy.decl: %destroy.type.02f = fn_decl @destroy.3 [concrete = constants.%destroy.8d0] {
+// CHECK:STDOUT:     %self.patt: %ptr = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %ptr = value_param_pattern %self.patt, call_param0
+// CHECK:STDOUT:     %.loc16_14: auto = addr_pattern %self.param_patt
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %ptr = value_param call_param0
+// CHECK:STDOUT:     %.loc16_29: type = splice_block %ptr [concrete = constants.%ptr] {
+// CHECK:STDOUT:       %Self.ref: type = name_ref Self, constants.%WithAddr [concrete = constants.%WithAddr]
+// CHECK:STDOUT:       %ptr: type = ptr_type %WithAddr [concrete = constants.%ptr]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %self: %ptr = 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.%WithAddr
+// CHECK:STDOUT:   .WithAddr = <poisoned>
+// CHECK:STDOUT:   .Make = %Make.decl
+// CHECK:STDOUT:   .destroy = %destroy.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make.1() -> %NoAddr;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.1[%self.param_patt: %NoAddr]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make.2() -> %ExplicitReturn;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.2[%self.param_patt: %ExplicitReturn]() -> %empty_tuple.type;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make.3() -> %WithAddr;
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.3[addr %self.param_patt: %ptr]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- implicit_return.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:   %NoAddr: type = class_type @NoAddr [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %destroy.type.bc5: type = fn_type @destroy.1 [concrete]
+// CHECK:STDOUT:   %destroy.60f: %destroy.type.bc5 = struct_value () [concrete]
+// CHECK:STDOUT:   %ExplicitReturn: type = class_type @ExplicitReturn [concrete]
+// CHECK:STDOUT:   %destroy.type.dfa: type = fn_type @destroy.2 [concrete]
+// CHECK:STDOUT:   %destroy.539: %destroy.type.dfa = struct_value () [concrete]
+// CHECK:STDOUT:   %WithAddr: type = class_type @WithAddr [concrete]
+// CHECK:STDOUT:   %destroy.type.02f: type = fn_type @destroy.3 [concrete]
+// CHECK:STDOUT:   %destroy.8d0: %destroy.type.02f = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.b4e: type = ptr_type %WithAddr [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.NoAddr: type = import_ref Main//types, NoAddr, loaded [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %Main.ExplicitReturn: type = import_ref Main//types, ExplicitReturn, loaded [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:   %Main.WithAddr: type = import_ref Main//types, WithAddr, loaded [concrete = constants.%WithAddr]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.1: <witness> = import_ref Main//types, loc7_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.f42 = import_ref Main//types, inst15 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.b5b = import_ref Main//types, loc5_22, unloaded
+// CHECK:STDOUT:   %Main.import_ref.7fe: %destroy.type.bc5 = import_ref Main//types, loc6_27, loaded [concrete = constants.%destroy.60f]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.2: <witness> = import_ref Main//types, loc12_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.ee7 = import_ref Main//types, inst37 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.b14 = import_ref Main//types, loc10_30, unloaded
+// CHECK:STDOUT:   %Main.import_ref.83a: %destroy.type.dfa = import_ref Main//types, loc11_33, loaded [concrete = constants.%destroy.539]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.3: <witness> = import_ref Main//types, loc17_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.95d = import_ref Main//types, inst62 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.1cb = import_ref Main//types, loc15_24, unloaded
+// CHECK:STDOUT:   %Main.import_ref.54e: %destroy.type.02f = import_ref Main//types, loc16_33, loaded [concrete = constants.%destroy.8d0]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .NoAddr = imports.%Main.NoAddr
+// CHECK:STDOUT:     .ExplicitReturn = imports.%Main.ExplicitReturn
+// CHECK:STDOUT:     .WithAddr = imports.%Main.WithAddr
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %default.import = import <none>
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NoAddr [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.1
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.f42
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.b5b
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.7fe
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @ExplicitReturn [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.ee7
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.b14
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.83a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @WithAddr [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.3
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.95d
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.1cb
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.54e
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %no_addr.patt: %NoAddr = binding_pattern no_addr
+// CHECK:STDOUT:     %.loc6_3.1: %NoAddr = var_pattern %no_addr.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %no_addr.var: ref %NoAddr = var no_addr
+// CHECK:STDOUT:   %destroy.ref.1: %destroy.type.bc5 = name_ref destroy, imports.%Main.import_ref.7fe [concrete = constants.%destroy.60f]
+// CHECK:STDOUT:   %destroy.bound.1: <bound method> = bound_method %no_addr.var, %destroy.ref.1
+// CHECK:STDOUT:   %NoAddr.ref: type = name_ref NoAddr, imports.%Main.NoAddr [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %no_addr: ref %NoAddr = bind_name no_addr, %no_addr.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %explicit_return.patt: %ExplicitReturn = binding_pattern explicit_return
+// CHECK:STDOUT:     %.loc7_3.1: %ExplicitReturn = var_pattern %explicit_return.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %explicit_return.var: ref %ExplicitReturn = var explicit_return
+// CHECK:STDOUT:   %destroy.ref.2: %destroy.type.dfa = name_ref destroy, imports.%Main.import_ref.83a [concrete = constants.%destroy.539]
+// CHECK:STDOUT:   %destroy.bound.2: <bound method> = bound_method %explicit_return.var, %destroy.ref.2
+// CHECK:STDOUT:   %ExplicitReturn.ref: type = name_ref ExplicitReturn, imports.%Main.ExplicitReturn [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:   %explicit_return: ref %ExplicitReturn = bind_name explicit_return, %explicit_return.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %with_addr.patt: %WithAddr = binding_pattern with_addr
+// CHECK:STDOUT:     %.loc8: %WithAddr = var_pattern %with_addr.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %with_addr.var: ref %WithAddr = var with_addr
+// CHECK:STDOUT:   %destroy.ref.3: %destroy.type.02f = name_ref destroy, imports.%Main.import_ref.54e [concrete = constants.%destroy.8d0]
+// CHECK:STDOUT:   %destroy.bound.3: <bound method> = bound_method %with_addr.var, %destroy.ref.3
+// CHECK:STDOUT:   %WithAddr.ref: type = name_ref WithAddr, imports.%Main.WithAddr [concrete = constants.%WithAddr]
+// CHECK:STDOUT:   %with_addr: ref %WithAddr = bind_name with_addr, %with_addr.var
+// CHECK:STDOUT:   %addr: %ptr.b4e = addr_of %with_addr.var
+// CHECK:STDOUT:   %destroy.call.1: init %empty_tuple.type = call %destroy.bound.3(%addr)
+// CHECK:STDOUT:   %.loc7_3.2: %ExplicitReturn = bind_value %explicit_return.var
+// CHECK:STDOUT:   %destroy.call.2: init %empty_tuple.type = call %destroy.bound.2(%.loc7_3.2)
+// CHECK:STDOUT:   %.loc6_3.2: %NoAddr = bind_value %no_addr.var
+// CHECK:STDOUT:   %destroy.call.3: init %empty_tuple.type = call %destroy.bound.1(%.loc6_3.2)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.1[%self.param_patt: %NoAddr]() [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.2[%self.param_patt: %ExplicitReturn]() -> %empty_tuple.type [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.3[addr <unexpected>.inst82: %ptr.b4e]() [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- nested_scope.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:   %NoAddr: type = class_type @NoAddr [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %destroy.type.bc5: type = fn_type @destroy.1 [concrete]
+// CHECK:STDOUT:   %destroy.60f: %destroy.type.bc5 = struct_value () [concrete]
+// CHECK:STDOUT:   %ExplicitReturn: type = class_type @ExplicitReturn [concrete]
+// CHECK:STDOUT:   %destroy.type.dfa: type = fn_type @destroy.2 [concrete]
+// CHECK:STDOUT:   %destroy.539: %destroy.type.dfa = struct_value () [concrete]
+// CHECK:STDOUT:   %WithAddr: type = class_type @WithAddr [concrete]
+// CHECK:STDOUT:   %destroy.type.02f: type = fn_type @destroy.3 [concrete]
+// CHECK:STDOUT:   %destroy.8d0: %destroy.type.02f = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.b4e: type = ptr_type %WithAddr [concrete]
+// CHECK:STDOUT:   %true: bool = bool_literal true [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.NoAddr: type = import_ref Main//types, NoAddr, loaded [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %Main.ExplicitReturn: type = import_ref Main//types, ExplicitReturn, loaded [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:   %Main.WithAddr: type = import_ref Main//types, WithAddr, loaded [concrete = constants.%WithAddr]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.1: <witness> = import_ref Main//types, loc7_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.f42 = import_ref Main//types, inst15 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.b5b = import_ref Main//types, loc5_22, unloaded
+// CHECK:STDOUT:   %Main.import_ref.7fe: %destroy.type.bc5 = import_ref Main//types, loc6_27, loaded [concrete = constants.%destroy.60f]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.2: <witness> = import_ref Main//types, loc12_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.ee7 = import_ref Main//types, inst37 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.b14 = import_ref Main//types, loc10_30, unloaded
+// CHECK:STDOUT:   %Main.import_ref.83a: %destroy.type.dfa = import_ref Main//types, loc11_33, loaded [concrete = constants.%destroy.539]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.3: <witness> = import_ref Main//types, loc17_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.95d = import_ref Main//types, inst62 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.1cb = import_ref Main//types, loc15_24, unloaded
+// CHECK:STDOUT:   %Main.import_ref.54e: %destroy.type.02f = import_ref Main//types, loc16_33, loaded [concrete = constants.%destroy.8d0]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .NoAddr = imports.%Main.NoAddr
+// CHECK:STDOUT:     .ExplicitReturn = imports.%Main.ExplicitReturn
+// CHECK:STDOUT:     .WithAddr = imports.%Main.WithAddr
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %default.import = import <none>
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NoAddr [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.1
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.f42
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.b5b
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.7fe
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @ExplicitReturn [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.ee7
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.b14
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.83a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @WithAddr [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.3
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.95d
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.1cb
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.54e
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %no_addr.patt: %NoAddr = binding_pattern no_addr
+// CHECK:STDOUT:     %.loc6_3.1: %NoAddr = var_pattern %no_addr.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %no_addr.var: ref %NoAddr = var no_addr
+// CHECK:STDOUT:   %destroy.ref.1: %destroy.type.bc5 = name_ref destroy, imports.%Main.import_ref.7fe [concrete = constants.%destroy.60f]
+// CHECK:STDOUT:   %destroy.bound.1: <bound method> = bound_method %no_addr.var, %destroy.ref.1
+// CHECK:STDOUT:   %NoAddr.ref.loc6: type = name_ref NoAddr, imports.%Main.NoAddr [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %no_addr: ref %NoAddr = bind_name no_addr, %no_addr.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %explicit_return.patt: %ExplicitReturn = binding_pattern explicit_return
+// CHECK:STDOUT:     %.loc7_3.1: %ExplicitReturn = var_pattern %explicit_return.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %explicit_return.var: ref %ExplicitReturn = var explicit_return
+// CHECK:STDOUT:   %destroy.ref.2: %destroy.type.dfa = name_ref destroy, imports.%Main.import_ref.83a [concrete = constants.%destroy.539]
+// CHECK:STDOUT:   %destroy.bound.2: <bound method> = bound_method %explicit_return.var, %destroy.ref.2
+// CHECK:STDOUT:   %ExplicitReturn.ref: type = name_ref ExplicitReturn, imports.%Main.ExplicitReturn [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:   %explicit_return: ref %ExplicitReturn = bind_name explicit_return, %explicit_return.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %with_addr.patt: %WithAddr = binding_pattern with_addr
+// CHECK:STDOUT:     %.loc8: %WithAddr = var_pattern %with_addr.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %with_addr.var: ref %WithAddr = var with_addr
+// CHECK:STDOUT:   %destroy.ref.3: %destroy.type.02f = name_ref destroy, imports.%Main.import_ref.54e [concrete = constants.%destroy.8d0]
+// CHECK:STDOUT:   %destroy.bound.3: <bound method> = bound_method %with_addr.var, %destroy.ref.3
+// CHECK:STDOUT:   %WithAddr.ref: type = name_ref WithAddr, imports.%Main.WithAddr [concrete = constants.%WithAddr]
+// CHECK:STDOUT:   %with_addr: ref %WithAddr = bind_name with_addr, %with_addr.var
+// CHECK:STDOUT:   %true: bool = bool_literal true [concrete = constants.%true]
+// CHECK:STDOUT:   if %true br !if.then else br !if.else
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.then:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %in_scope.patt: %NoAddr = binding_pattern in_scope
+// CHECK:STDOUT:     %.loc11: %NoAddr = var_pattern %in_scope.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %in_scope.var: ref %NoAddr = var in_scope
+// CHECK:STDOUT:   %destroy.ref.4: %destroy.type.bc5 = name_ref destroy, imports.%Main.import_ref.7fe [concrete = constants.%destroy.60f]
+// CHECK:STDOUT:   %destroy.bound.4: <bound method> = bound_method %in_scope.var, %destroy.ref.4
+// CHECK:STDOUT:   %NoAddr.ref.loc11: type = name_ref NoAddr, imports.%Main.NoAddr [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %in_scope: ref %NoAddr = bind_name in_scope, %in_scope.var
+// CHECK:STDOUT:   br !if.else
+// CHECK:STDOUT:
+// CHECK:STDOUT: !if.else:
+// CHECK:STDOUT:   %addr: %ptr.b4e = addr_of %with_addr.var
+// CHECK:STDOUT:   %destroy.call.1: init %empty_tuple.type = call %destroy.bound.3(%addr)
+// CHECK:STDOUT:   %.loc7_3.2: %ExplicitReturn = bind_value %explicit_return.var
+// CHECK:STDOUT:   %destroy.call.2: init %empty_tuple.type = call %destroy.bound.2(%.loc7_3.2)
+// CHECK:STDOUT:   %.loc6_3.2: %NoAddr = bind_value %no_addr.var
+// CHECK:STDOUT:   %destroy.call.3: init %empty_tuple.type = call %destroy.bound.1(%.loc6_3.2)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.1[%self.param_patt: %NoAddr]() [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.2[%self.param_patt: %ExplicitReturn]() -> %empty_tuple.type [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.3[addr <unexpected>.inst82: %ptr.b4e]() [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- temp.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:   %NoAddr: type = class_type @NoAddr [concrete]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %Make.type.bc9: type = fn_type @Make.1 [concrete]
+// CHECK:STDOUT:   %Make.1be: %Make.type.bc9 = struct_value () [concrete]
+// CHECK:STDOUT:   %destroy.type.bc5: type = fn_type @destroy.1 [concrete]
+// CHECK:STDOUT:   %destroy.60f: %destroy.type.bc5 = struct_value () [concrete]
+// CHECK:STDOUT:   %ExplicitReturn: type = class_type @ExplicitReturn [concrete]
+// CHECK:STDOUT:   %Make.type.378: type = fn_type @Make.2 [concrete]
+// CHECK:STDOUT:   %Make.960: %Make.type.378 = struct_value () [concrete]
+// CHECK:STDOUT:   %destroy.type.dfa: type = fn_type @destroy.2 [concrete]
+// CHECK:STDOUT:   %destroy.539: %destroy.type.dfa = struct_value () [concrete]
+// CHECK:STDOUT:   %WithAddr: type = class_type @WithAddr [concrete]
+// CHECK:STDOUT:   %Make.type.e14: type = fn_type @Make.3 [concrete]
+// CHECK:STDOUT:   %Make.b0a: %Make.type.e14 = struct_value () [concrete]
+// CHECK:STDOUT:   %destroy.type.02f: type = fn_type @destroy.3 [concrete]
+// CHECK:STDOUT:   %destroy.8d0: %destroy.type.02f = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.b4e: type = ptr_type %WithAddr [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Main.NoAddr: type = import_ref Main//types, NoAddr, loaded [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %Main.ExplicitReturn: type = import_ref Main//types, ExplicitReturn, loaded [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:   %Main.WithAddr: type = import_ref Main//types, WithAddr, loaded [concrete = constants.%WithAddr]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.1: <witness> = import_ref Main//types, loc7_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.f42 = import_ref Main//types, inst15 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.784: %Make.type.bc9 = import_ref Main//types, loc5_22, loaded [concrete = constants.%Make.1be]
+// CHECK:STDOUT:   %Main.import_ref.7fe: %destroy.type.bc5 = import_ref Main//types, loc6_27, loaded [concrete = constants.%destroy.60f]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.2: <witness> = import_ref Main//types, loc12_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.ee7 = import_ref Main//types, inst37 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.8e0: %Make.type.378 = import_ref Main//types, loc10_30, loaded [concrete = constants.%Make.960]
+// CHECK:STDOUT:   %Main.import_ref.83a: %destroy.type.dfa = import_ref Main//types, loc11_33, loaded [concrete = constants.%destroy.539]
+// CHECK:STDOUT:   %Main.import_ref.8f24d3.3: <witness> = import_ref Main//types, loc17_1, loaded [concrete = constants.%complete_type]
+// CHECK:STDOUT:   %Main.import_ref.95d = import_ref Main//types, inst62 [no loc], unloaded
+// CHECK:STDOUT:   %Main.import_ref.974: %Make.type.e14 = import_ref Main//types, loc15_24, loaded [concrete = constants.%Make.b0a]
+// CHECK:STDOUT:   %Main.import_ref.54e: %destroy.type.02f = import_ref Main//types, loc16_33, loaded [concrete = constants.%destroy.8d0]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .NoAddr = imports.%Main.NoAddr
+// CHECK:STDOUT:     .ExplicitReturn = imports.%Main.ExplicitReturn
+// CHECK:STDOUT:     .WithAddr = imports.%Main.WithAddr
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %default.import = import <none>
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NoAddr [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.1
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.f42
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.784
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.7fe
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @ExplicitReturn [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.2
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.ee7
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.8e0
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.83a
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @WithAddr [from "types.carbon"] {
+// CHECK:STDOUT:   complete_type_witness = imports.%Main.import_ref.8f24d3.3
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = imports.%Main.import_ref.95d
+// CHECK:STDOUT:   .Make = imports.%Main.import_ref.974
+// CHECK:STDOUT:   .destroy = imports.%Main.import_ref.54e
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %NoAddr.ref: type = name_ref NoAddr, imports.%Main.NoAddr [concrete = constants.%NoAddr]
+// CHECK:STDOUT:   %Make.ref.loc8: %Make.type.bc9 = name_ref Make, imports.%Main.import_ref.784 [concrete = constants.%Make.1be]
+// CHECK:STDOUT:   %.loc8_15.1: ref %NoAddr = temporary_storage
+// CHECK:STDOUT:   %destroy.ref.1: %destroy.type.bc5 = name_ref destroy, imports.%Main.import_ref.7fe [concrete = constants.%destroy.60f]
+// CHECK:STDOUT:   %destroy.bound.1: <bound method> = bound_method %.loc8_15.1, %destroy.ref.1
+// CHECK:STDOUT:   %Make.call.loc8: init %NoAddr = call %Make.ref.loc8() to %.loc8_15.1
+// CHECK:STDOUT:   %.loc8_15.2: ref %NoAddr = temporary %.loc8_15.1, %Make.call.loc8
+// CHECK:STDOUT:   %ExplicitReturn.ref: type = name_ref ExplicitReturn, imports.%Main.ExplicitReturn [concrete = constants.%ExplicitReturn]
+// CHECK:STDOUT:   %Make.ref.loc9: %Make.type.378 = name_ref Make, imports.%Main.import_ref.8e0 [concrete = constants.%Make.960]
+// CHECK:STDOUT:   %.loc9_23.1: ref %ExplicitReturn = temporary_storage
+// CHECK:STDOUT:   %destroy.ref.2: %destroy.type.dfa = name_ref destroy, imports.%Main.import_ref.83a [concrete = constants.%destroy.539]
+// CHECK:STDOUT:   %destroy.bound.2: <bound method> = bound_method %.loc9_23.1, %destroy.ref.2
+// CHECK:STDOUT:   %Make.call.loc9: init %ExplicitReturn = call %Make.ref.loc9() to %.loc9_23.1
+// CHECK:STDOUT:   %.loc9_23.2: ref %ExplicitReturn = temporary %.loc9_23.1, %Make.call.loc9
+// CHECK:STDOUT:   %WithAddr.ref: type = name_ref WithAddr, imports.%Main.WithAddr [concrete = constants.%WithAddr]
+// CHECK:STDOUT:   %Make.ref.loc10: %Make.type.e14 = name_ref Make, imports.%Main.import_ref.974 [concrete = constants.%Make.b0a]
+// CHECK:STDOUT:   %.loc10_17.1: ref %WithAddr = temporary_storage
+// CHECK:STDOUT:   %destroy.ref.3: %destroy.type.02f = name_ref destroy, imports.%Main.import_ref.54e [concrete = constants.%destroy.8d0]
+// CHECK:STDOUT:   %destroy.bound.3: <bound method> = bound_method %.loc10_17.1, %destroy.ref.3
+// CHECK:STDOUT:   %Make.call.loc10: init %WithAddr = call %Make.ref.loc10() to %.loc10_17.1
+// CHECK:STDOUT:   %.loc10_17.2: ref %WithAddr = temporary %.loc10_17.1, %Make.call.loc10
+// CHECK:STDOUT:   %addr: %ptr.b4e = addr_of %.loc10_17.1
+// CHECK:STDOUT:   %destroy.call.1: init %empty_tuple.type = call %destroy.bound.3(%addr)
+// CHECK:STDOUT:   %.loc9_23.3: %ExplicitReturn = bind_value %.loc9_23.1
+// CHECK:STDOUT:   %destroy.call.2: init %empty_tuple.type = call %destroy.bound.2(%.loc9_23.3)
+// CHECK:STDOUT:   %.loc8_15.3: %NoAddr = bind_value %.loc8_15.1
+// CHECK:STDOUT:   %destroy.call.3: init %empty_tuple.type = call %destroy.bound.1(%.loc8_15.3)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make.1() -> %NoAddr [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.1[%self.param_patt: %NoAddr]() [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make.2() -> %ExplicitReturn [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.2[%self.param_patt: %ExplicitReturn]() -> %empty_tuple.type [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Make.3() -> %WithAddr [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.3[addr <unexpected>.inst92: %ptr.b4e]() [from "types.carbon"];
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_recovery.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %NoSelf: type = class_type @NoSelf [concrete]
+// CHECK:STDOUT:   %destroy.type.d69: type = fn_type @destroy.1 [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %destroy.ab7: %destroy.type.d69 = 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:   %Args: type = class_type @Args [concrete]
+// CHECK:STDOUT:   %destroy.type.ba0: type = fn_type @destroy.2 [concrete]
+// CHECK:STDOUT:   %destroy.723: %destroy.type.ba0 = struct_value () [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .NoSelf = %NoSelf.decl
+// CHECK:STDOUT:     .Args = %Args.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %NoSelf.decl: type = class_decl @NoSelf [concrete = constants.%NoSelf] {} {}
+// CHECK:STDOUT:   %Args.decl: type = class_decl @Args [concrete = constants.%Args] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @NoSelf {
+// CHECK:STDOUT:   %destroy.decl: %destroy.type.d69 = fn_decl @destroy.1 [concrete = constants.%destroy.ab7] {} {}
+// 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.%NoSelf
+// CHECK:STDOUT:   .destroy = %destroy.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @Args {
+// CHECK:STDOUT:   %destroy.decl: %destroy.type.ba0 = fn_decl @destroy.2 [concrete = constants.%destroy.723] {
+// CHECK:STDOUT:     %self.patt: %Args = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Args = value_param_pattern %self.patt, call_param0
+// CHECK:STDOUT:     %x.patt: %empty_tuple.type = binding_pattern x
+// CHECK:STDOUT:     %x.param_patt: %empty_tuple.type = value_param_pattern %x.patt, call_param1
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Args = value_param call_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Args [concrete = constants.%Args]
+// CHECK:STDOUT:     %self: %Args = bind_name self, %self.param
+// CHECK:STDOUT:     %x.param: %empty_tuple.type = value_param call_param1
+// CHECK:STDOUT:     %.loc17_30.1: type = splice_block %.loc17_30.3 [concrete = constants.%empty_tuple.type] {
+// CHECK:STDOUT:       %.loc17_30.2: %empty_tuple.type = tuple_literal ()
+// CHECK:STDOUT:       %.loc17_30.3: type = converted %.loc17_30.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:   }
+// 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.%Args
+// CHECK:STDOUT:   .destroy = %destroy.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @destroy.2[%self.param_patt: %Args]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %a.patt: %NoSelf = binding_pattern a
+// CHECK:STDOUT:     %.loc21: %NoSelf = var_pattern %a.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %a.var: ref %NoSelf = var a
+// CHECK:STDOUT:   %destroy.ref.1: %destroy.type.d69 = name_ref destroy, @NoSelf.%destroy.decl [concrete = constants.%destroy.ab7]
+// CHECK:STDOUT:   %NoSelf.ref: type = name_ref NoSelf, file.%NoSelf.decl [concrete = constants.%NoSelf]
+// CHECK:STDOUT:   %a: ref %NoSelf = bind_name a, %a.var
+// CHECK:STDOUT:   name_binding_decl {
+// CHECK:STDOUT:     %b.patt: %Args = binding_pattern b
+// CHECK:STDOUT:     %.loc22_3.1: %Args = var_pattern %b.patt
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %b.var: ref %Args = var b
+// CHECK:STDOUT:   %destroy.ref.2: %destroy.type.ba0 = name_ref destroy, @Args.%destroy.decl [concrete = constants.%destroy.723]
+// CHECK:STDOUT:   %destroy.bound: <bound method> = bound_method %b.var, %destroy.ref.2
+// CHECK:STDOUT:   %Args.ref: type = name_ref Args, file.%Args.decl [concrete = constants.%Args]
+// CHECK:STDOUT:   %b: ref %Args = bind_name b, %b.var
+// CHECK:STDOUT:   %.loc22_3.2: %Args = bind_value %b.var
+// CHECK:STDOUT:   %destroy.call.1: init %empty_tuple.type = call %destroy.bound(%.loc22_3.2)
+// CHECK:STDOUT:   %destroy.call.2: init %empty_tuple.type = call %destroy.ref.1()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_not_breaking_generics.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0, template [template]
+// CHECK:STDOUT:   %T.patt: type = symbolic_binding_pattern T, 0, template [template]
+// CHECK:STDOUT:   %C.type: type = generic_class_type @C [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %C.generic: %C.type = struct_value () [concrete]
+// CHECK:STDOUT:   %C.f2e: type = class_type @C, @C(%T) [template]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
+// CHECK:STDOUT:   %F.type: type = fn_type @F [concrete]
+// CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type %C.f2e [template]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %F.specific_fn: <specific function> = specific_function %F, @F(%empty_struct_type) [concrete]
+// CHECK:STDOUT:   %C.7a7: type = class_type @C, @C(%empty_struct_type) [concrete]
+// CHECK:STDOUT:   %inst.as_compatible: <instruction> = inst_value [concrete] {
+// CHECK:STDOUT:     %.6f0: ref %C.7a7 = as_compatible @F.%v.var
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %inst.splice_block: <instruction> = inst_value [concrete] {
+// CHECK:STDOUT:     %.432: <error> = splice_block <error> [concrete = <error>] {}
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:     .F = %F.decl
+// CHECK:STDOUT:     .G = %G.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %C.decl: %C.type = class_decl @C [concrete = constants.%C.generic] {
+// CHECK:STDOUT:     %T.patt.loc4_18.1: type = symbolic_binding_pattern T, 0, template [template = %T.patt.loc4_18.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc4_18.1: type = bind_symbolic_name T, 0, template [template = %T.loc4_18.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %T.patt.loc6_15.1: type = symbolic_binding_pattern T, 0, template [template = %T.patt.loc6_15.2 (constants.%T.patt)]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc6_15.1: type = bind_symbolic_name T, 0, template [template = %T.loc6_15.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic class @C(%T.loc4_18.1: type) {
+// CHECK:STDOUT:   %T.loc4_18.2: type = bind_symbolic_name T, 0, template [template = %T.loc4_18.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc4_18.2: type = symbolic_binding_pattern T, 0, template [template = %T.patt.loc4_18.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:
+// CHECK:STDOUT:   class {
+// 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.f2e
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F(%T.loc6_15.1: type) {
+// CHECK:STDOUT:   %T.loc6_15.2: type = bind_symbolic_name T, 0, template [template = %T.loc6_15.2 (constants.%T)]
+// CHECK:STDOUT:   %T.patt.loc6_15.2: type = symbolic_binding_pattern T, 0, template [template = %T.patt.loc6_15.2 (constants.%T.patt)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %C.loc7_13.2: type = class_type @C, @C(%T.loc6_15.2) [template = %C.loc7_13.2 (constants.%C.f2e)]
+// CHECK:STDOUT:   %require_complete: <witness> = require_complete_type @F.%C.loc7_13.2 (%C.f2e) [template = %require_complete (constants.%require_complete)]
+// CHECK:STDOUT:   %.3: <instruction> = refine_type_action %v.var, @F.%C.loc7_13.2 (%C.f2e) [template]
+// CHECK:STDOUT:   %.4: <instruction> = access_optional_member_action %.1, destroy [template]
+// CHECK:STDOUT:   %.5: type = type_of_inst %.4 [template]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn(%T.patt.loc6_15.1: type) {
+// CHECK:STDOUT:   !entry:
+// CHECK:STDOUT:     name_binding_decl {
+// CHECK:STDOUT:       %v.patt: @F.%C.loc7_13.2 (%C.f2e) = binding_pattern v
+// CHECK:STDOUT:       %.loc7_3: @F.%C.loc7_13.2 (%C.f2e) = var_pattern %v.patt
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %v.var: ref @F.%C.loc7_13.2 (%C.f2e) = var v
+// CHECK:STDOUT:     %.1: @F.%C.loc7_13.2 (%C.f2e) = splice_inst %.3
+// CHECK:STDOUT:     %.2: @F.%.5 (@F.%.5) = splice_inst %.4
+// CHECK:STDOUT:     %.loc7_13: type = splice_block %C.loc7_13.1 [template = %C.loc7_13.2 (constants.%C.f2e)] {
+// CHECK:STDOUT:       %C.ref: %C.type = name_ref C, file.%C.decl [concrete = constants.%C.generic]
+// CHECK:STDOUT:       %T.ref: type = name_ref T, %T.loc6_15.1 [template = %T.loc6_15.2 (constants.%T)]
+// CHECK:STDOUT:       %C.loc7_13.1: type = class_type @C, @C(constants.%T) [template = %C.loc7_13.2 (constants.%C.f2e)]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %v: ref @F.%C.loc7_13.2 (%C.f2e) = bind_name v, %v.var
+// CHECK:STDOUT:     return
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @G() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F]
+// CHECK:STDOUT:   %.loc10_13: %empty_struct_type = struct_literal ()
+// CHECK:STDOUT:   %.loc10_14: type = converted %.loc10_13, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
+// CHECK:STDOUT:   %F.specific_fn: <specific function> = specific_function %F.ref, @F(constants.%empty_struct_type) [concrete = constants.%F.specific_fn]
+// CHECK:STDOUT:   %F.call: init %empty_tuple.type = call %F.specific_fn()
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_18.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc4_18.2 => constants.%T
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%T) {
+// CHECK:STDOUT:   %T.loc6_15.2 => constants.%T
+// CHECK:STDOUT:   %T.patt.loc6_15.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(@F.%T.loc6_15.2) {}
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F(constants.%empty_struct_type) {
+// CHECK:STDOUT:   %T.loc6_15.2 => constants.%empty_struct_type
+// CHECK:STDOUT:   %T.patt.loc6_15.2 => constants.%empty_struct_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %C.loc7_13.2 => constants.%C.7a7
+// CHECK:STDOUT:   %require_complete => constants.%complete_type
+// CHECK:STDOUT:   %.3 => constants.%inst.as_compatible
+// CHECK:STDOUT:   %.4 => constants.%inst.splice_block
+// CHECK:STDOUT:   %.5 => <error>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @C(constants.%empty_struct_type) {
+// CHECK:STDOUT:   %T.loc4_18.2 => constants.%empty_struct_type
+// CHECK:STDOUT:   %T.patt.loc4_18.2 => constants.%empty_struct_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 181 - 0
toolchain/check/testdata/class/no_prelude/destroy_decl.carbon

@@ -0,0 +1,181 @@
+// 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
+//
+// EXTRA-ARGS: --no-dump-sem-ir
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/no_prelude/destroy_decl.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/no_prelude/destroy_decl.carbon
+
+// --- self.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  fn destroy[self: Self]();
+}
+
+// --- addr_self.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  fn destroy[addr self: Self*]();
+}
+
+// --- explicit_return.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  fn destroy[self: Self]() -> ();
+}
+
+// --- fail_class_function.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_class_function.carbon:[[@LINE+4]]:3: error: missing implicit `self` parameter [DestroyFunctionMissingSelf]
+  // CHECK:STDERR:   fn destroy();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~
+  // CHECK:STDERR:
+  fn destroy();
+}
+
+// --- fail_extra_implicit_params_second.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_extra_implicit_params_second.carbon:[[@LINE+4]]:26: error: unexpected implicit parameter [DestroyFunctionUnexpectedImplicitParam]
+  // CHECK:STDERR:   fn destroy[self: Self, T:! type]();
+  // CHECK:STDERR:                          ^
+  // CHECK:STDERR:
+  fn destroy[self: Self, T:! type]();
+}
+
+// --- fail_extra_implicit_params_first.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_extra_implicit_params_first.carbon:[[@LINE+4]]:14: error: unexpected implicit parameter [DestroyFunctionUnexpectedImplicitParam]
+  // CHECK:STDERR:   fn destroy[T:! type, self: Self]();
+  // CHECK:STDERR:              ^
+  // CHECK:STDERR:
+  fn destroy[T:! type, self: Self]();
+}
+
+// --- fail_positional_params.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_positional_params.carbon:[[@LINE+4]]:3: error: missing empty explicit parameter list [DestroyFunctionPositionalParams]
+  // CHECK:STDERR:   fn destroy[self: Self];
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  fn destroy[self: Self];
+}
+
+// --- fail_explicit_params.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_explicit_params.carbon:[[@LINE+4]]:26: error: unexpected parameter [DestroyFunctionNonEmptyExplicitParams]
+  // CHECK:STDERR:   fn destroy[self: Self](x: ());
+  // CHECK:STDERR:                          ^~~~~
+  // CHECK:STDERR:
+  fn destroy[self: Self](x: ());
+}
+
+// --- fail_return_type.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  // CHECK:STDERR: fail_return_type.carbon:[[@LINE+4]]:28: error: incorrect return type; must be unspecified or `()` [DestroyFunctionIncorrectReturnType]
+  // CHECK:STDERR:   fn destroy[self: Self]() -> {};
+  // CHECK:STDERR:                            ^~~~~
+  // CHECK:STDERR:
+  fn destroy[self: Self]() -> {};
+}
+
+// --- fail_out_of_line_missing_params.carbon
+
+library "[[@TEST_NAME]]";
+
+
+class C {
+  fn destroy[self: Self]();
+}
+
+// CHECK:STDERR: fail_out_of_line_missing_params.carbon:[[@LINE+7]]:1: error: redeclaration differs because of missing implicit parameter list [RedeclParamListDiffers]
+// CHECK:STDERR: fn C.destroy {}
+// CHECK:STDERR: ^~~~~~~~~~~~~~
+// CHECK:STDERR: fail_out_of_line_missing_params.carbon:[[@LINE-6]]:3: note: previously declared with implicit parameter list [RedeclParamListPrevious]
+// CHECK:STDERR:   fn destroy[self: Self]();
+// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn C.destroy {}
+
+// --- fail_destroy_in_file_scope.carbon
+
+library "[[@TEST_NAME]]";
+
+// CHECK:STDERR: fail_destroy_in_file_scope.carbon:[[@LINE+4]]:1: error: declaring `fn destroy` in non-class scope [DestroyFunctionOutsideClass]
+// CHECK:STDERR: fn destroy[self: ()]();
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn destroy[self: ()]();
+
+// --- fail_destroy_in_namespace_scope.carbon
+
+library "[[@TEST_NAME]]";
+
+namespace NS;
+
+// CHECK:STDERR: fail_destroy_in_namespace_scope.carbon:[[@LINE+4]]:1: error: declaring `fn destroy` in non-class scope [DestroyFunctionOutsideClass]
+// CHECK:STDERR: fn NS.destroy();
+// CHECK:STDERR: ^~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn NS.destroy();
+
+// --- fail_invalid_qualifier_with_params.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  fn destroy[self: Self]();
+}
+
+// CHECK:STDERR: fail_invalid_qualifier_with_params.carbon:[[@LINE+7]]:6: error: name qualifiers are only allowed for entities that provide a scope [QualifiedNameInNonScope]
+// CHECK:STDERR: fn C.destroy[self: Self]().Foo() {}
+// CHECK:STDERR:      ^~~~~~~
+// CHECK:STDERR: fail_invalid_qualifier_with_params.carbon:[[@LINE-6]]:3: note: referenced non-scope entity declared here [QualifiedNameNonScopeEntity]
+// CHECK:STDERR:   fn destroy[self: Self]();
+// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn C.destroy[self: Self]().Foo() {}
+
+// --- fail_invalid_qualifier_no_params.carbon
+
+library "[[@TEST_NAME]]";
+
+class C {
+  fn destroy[self: Self]();
+}
+
+// CHECK:STDERR: fail_invalid_qualifier_no_params.carbon:[[@LINE+7]]:6: error: name qualifiers are only allowed for entities that provide a scope [QualifiedNameInNonScope]
+// CHECK:STDERR: fn C.destroy.Foo() {}
+// CHECK:STDERR:      ^~~~~~~
+// CHECK:STDERR: fail_invalid_qualifier_no_params.carbon:[[@LINE-6]]:3: note: referenced non-scope entity declared here [QualifiedNameNonScopeEntity]
+// CHECK:STDERR:   fn destroy[self: Self]();
+// CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~
+// CHECK:STDERR:
+fn C.destroy.Foo() {}

+ 1 - 0
toolchain/sem_ir/file.cpp

@@ -242,6 +242,7 @@ auto GetExprCategory(const File& file, InstId inst_id) -> ExprCategory {
       }
 
       case AccessMemberAction::Kind:
+      case AccessOptionalMemberAction::Kind:
       case AddrOf::Kind:
       case ArrayType::Kind:
       case AssociatedConstantDecl::Kind:

+ 1 - 0
toolchain/sem_ir/inst_kind.def

@@ -19,6 +19,7 @@
 // For each instruction kind declared here there is a matching definition in
 // `typed_insts.h`.
 CARBON_SEM_IR_INST_KIND(AccessMemberAction)
+CARBON_SEM_IR_INST_KIND(AccessOptionalMemberAction)
 CARBON_SEM_IR_INST_KIND(AdaptDecl)
 CARBON_SEM_IR_INST_KIND(AddrOf)
 CARBON_SEM_IR_INST_KIND(AddrPattern)

+ 5 - 0
toolchain/sem_ir/inst_kind.h

@@ -122,6 +122,7 @@ class InstKind : public CARBON_ENUM_BASE(InstKind) {
     TerminatorKind terminator_kind = TerminatorKind::NotTerminator;
     bool is_lowered = true;
     bool deduce_through = false;
+    bool has_cleanup = false;
   };
 
   // Provides a definition for this instruction kind. Should only be called
@@ -222,6 +223,10 @@ class InstKind::Definition : public InstKind {
   // conclude `A` == `B`.
   constexpr auto deduce_through() const -> bool { return info_.deduce_through; }
 
+  // Returns true if this instruction has scoped cleanup associated, typically a
+  // destructor.
+  constexpr auto has_cleanup() const -> bool { return info_.has_cleanup; }
+
  private:
   friend class InstKind;
 

+ 19 - 2
toolchain/sem_ir/typed_insts.h

@@ -61,6 +61,20 @@ struct AccessMemberAction {
   NameId name_id;
 };
 
+// An action that performs member access which should fail silently. For
+// example, `base.destroy`.
+struct AccessOptionalMemberAction {
+  static constexpr auto Kind =
+      InstKind::AccessOptionalMemberAction.Define<Parse::NodeId>(
+          {.ir_name = "access_optional_member_action",
+           .constant_kind = InstConstantKind::InstAction,
+           .is_lowered = false});
+
+  TypeId type_id;
+  MetaInstId base_id;
+  NameId name_id;
+};
+
 // Common representation for declarations describing the foundation type of a
 // class -- either its adapted type or its base class.
 struct AnyFoundationDecl {
@@ -1576,7 +1590,8 @@ struct TemporaryStorage {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind = InstKind::TemporaryStorage.Define<Parse::NodeId>(
       {.ir_name = "temporary_storage",
-       .constant_kind = InstConstantKind::Never});
+       .constant_kind = InstConstantKind::Never,
+       .has_cleanup = true});
 
   TypeId type_id;
 };
@@ -1750,7 +1765,9 @@ struct VarPattern {
 struct VarStorage {
   // TODO: Make Parse::NodeId more specific.
   static constexpr auto Kind = InstKind::VarStorage.Define<Parse::NodeId>(
-      {.ir_name = "var", .constant_kind = InstConstantKind::Never});
+      {.ir_name = "var",
+       .constant_kind = InstConstantKind::Never,
+       .has_cleanup = true});
 
   TypeId type_id;