Sfoglia il codice sorgente

Defer building thunks until the end of the enclosing definition. (#5403)

Instead of building the definition of a thunk immediately when we
generate the thunk declaration, wait until we reach the `}` of the
outermost class, interface, etc. -- at the same time when we would parse
the definition of the thunk if it were defined inline.

This fixes issues where we fail to define the thunk because it requires
an enclosing class to be complete, or its definition depends on
something declared later in the enclosing class.

Make the representation of a suspended function scope, and its
constituent suspended components, be move-only, and switch to passing it
around by rvalue reference instead of by value because it's expensive
both to move and especially to copy.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 1 anno fa
parent
commit
e060342411

+ 5 - 0
common/BUILD

@@ -331,6 +331,11 @@ sh_test(
     ],
 )
 
+cc_library(
+    name = "move_only",
+    hdrs = ["move_only.h"],
+)
+
 cc_library(
     name = "ostream",
     hdrs = ["ostream.h"],

+ 5 - 5
common/array_stack.h

@@ -66,17 +66,17 @@ class ArrayStack {
   auto PeekAllValues() const -> llvm::ArrayRef<ValueT> { return values_; }
 
   // Appends a value to the top array on the stack.
-  auto AppendToTop(ValueT value) -> void {
+  auto AppendToTop(const ValueT& value) -> void {
     CARBON_CHECK(!array_offsets_.empty(),
                  "Must call PushArray before AppendToTop.");
     values_.push_back(value);
   }
 
-  // Prepends a value to the top array on the stack.
-  auto PrependToTop(ValueT value) -> void {
+  // Appends a value to the top array on the stack.
+  auto AppendToTop(ValueT&& value) -> void {
     CARBON_CHECK(!array_offsets_.empty(),
-                 "Must call PushArray before PrependToTop.");
-    values_.insert(values_.begin() + array_offsets_.back(), value);
+                 "Must call PushArray before AppendToTop.");
+    values_.push_back(std::move(value));
   }
 
   // Adds multiple values to the top array on the stack.

+ 28 - 0
common/move_only.h

@@ -0,0 +1,28 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_COMMON_MOVE_ONLY_H_
+#define CARBON_COMMON_MOVE_ONLY_H_
+
+namespace Carbon {
+
+// A base class that indicates a type is move-only. Typically this can be
+// achieved by declaring the move constructor and move assignment yourself; this
+// type should be used only when doing that is not feasible, such as when
+// aggregate initialization is still desired.
+//
+// This class uses CRTP to ensure that each MoveOnly base class has a different
+// type. This is important to avoid the compiler adding extra padding to derived
+// classes to give multiple MoveOnly subobjects of the same type different
+// addresses.
+template <typename Derived>
+struct MoveOnly {
+  MoveOnly() = default;
+  MoveOnly(MoveOnly&&) noexcept = default;
+  auto operator=(MoveOnly&&) noexcept -> MoveOnly& = default;
+};
+
+}  // namespace Carbon
+
+#endif  // CARBON_COMMON_MOVE_ONLY_H_

+ 2 - 0
toolchain/check/BUILD

@@ -64,6 +64,7 @@ cc_library(
         "decl_introducer_state.h",
         "decl_name_stack.h",
         "deduce.h",
+        "deferred_definition_scope.h",
         "diagnostic_helpers.h",
         "eval.h",
         "eval_inst.h",
@@ -254,6 +255,7 @@ cc_library(
     deps = [
         "//common:array_stack",
         "//common:check",
+        "//common:move_only",
         "//common:ostream",
         "//common:set",
         "//common:vlog",

+ 8 - 0
toolchain/check/context.h

@@ -13,6 +13,7 @@
 #include "toolchain/base/value_store.h"
 #include "toolchain/check/decl_introducer_state.h"
 #include "toolchain/check/decl_name_stack.h"
+#include "toolchain/check/deferred_definition_scope.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/full_pattern_stack.h"
 #include "toolchain/check/generic_region_stack.h"
@@ -128,6 +129,10 @@ class Context {
     return scope_stack_.full_pattern_stack();
   }
 
+  auto deferred_definition_scope_stack() -> DeferredDefinitionScopeStack& {
+    return deferred_definition_scope_stack_;
+  }
+
   auto generic_region_stack() -> GenericRegionStack& {
     return generic_region_stack_;
   }
@@ -337,6 +342,9 @@ class Context {
   // The stack of scopes we are currently within.
   ScopeStack scope_stack_;
 
+  // The stack of non-nested deferred definition scopes we are currently within.
+  DeferredDefinitionScopeStack deferred_definition_scope_stack_;
+
   // The stack of generic regions we are currently within.
   GenericRegionStack generic_region_stack_;
 

+ 1 - 1
toolchain/check/decl_name_stack.cpp

@@ -108,7 +108,7 @@ auto DeclNameStack::Suspend() -> SuspendedName {
   return result;
 }
 
-auto DeclNameStack::Restore(SuspendedName sus) -> void {
+auto DeclNameStack::Restore(SuspendedName&& sus) -> void {
   // The parent state must be the same when a name is restored.
   CARBON_CHECK(context_->scope_stack().PeekIndex() ==
                    sus.name_context.initial_scope_index,

+ 4 - 2
toolchain/check/decl_name_stack.h

@@ -165,7 +165,9 @@ class DeclNameStack {
   // Information about a declaration name that has been temporarily removed from
   // the stack and will later be restored. Names can only be suspended once they
   // are finished.
-  struct SuspendedName {
+  //
+  // This type is large, so moves of this type should be avoided.
+  struct SuspendedName : public MoveOnly<SuspendedName> {
     // The declaration name information.
     NameContext name_context;
 
@@ -236,7 +238,7 @@ class DeclNameStack {
   auto Suspend() -> SuspendedName;
 
   // Restore a previously suspended name.
-  auto Restore(SuspendedName sus) -> void;
+  auto Restore(SuspendedName&& sus) -> void;
 
   // Adds a name to name lookup. Assumes duplicates are already handled.
   auto AddName(NameContext name_context, SemIR::InstId target_id,

+ 68 - 0
toolchain/check/deferred_definition_scope.h

@@ -0,0 +1,68 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_TOOLCHAIN_CHECK_DEFERRED_DEFINITION_SCOPE_H_
+#define CARBON_TOOLCHAIN_CHECK_DEFERRED_DEFINITION_SCOPE_H_
+
+#include "common/array_stack.h"
+#include "toolchain/check/scope_stack.h"
+#include "toolchain/sem_ir/inst.h"
+
+namespace Carbon::Check {
+
+// A thunk that has been declared but not yet defined.
+//
+// This type is large, so moves of this type should be avoided.
+struct PendingThunk : public MoveOnly<PendingThunk> {
+  SemIR::FunctionId signature_id;
+  SemIR::FunctionId function_id;
+  SemIR::InstId decl_id;
+  SemIR::InstId callee_id;
+  ScopeStack::SuspendedScope scope;
+};
+
+// A stack of the current non-nested deferred definition scopes. For example, in
+// this code:
+//
+// class A {
+//   class B {
+//     fn F() {
+//       class C {
+//         // ...
+//       }
+//     }
+//   }
+// }
+//
+// ... we have two non-nested deferred definition scopes: one for class A, and
+// one for class C. The scope for class B is nested, so is not tracked.
+//
+// At the end of each such scope, pending function definitions for functions
+// defined inline are processed. At this location, we also generate bodies for
+// thunks generated by checking.
+class DeferredDefinitionScopeStack {
+ public:
+  // Push a new scope.
+  auto Push() -> void { pending_thunks_.PushArray(); }
+
+  // Pop a scope.
+  auto Pop() -> void { pending_thunks_.PopArray(); }
+
+  // Add a pending thunk definition for the current scope.
+  auto AddPendingThunk(PendingThunk&& thunk) -> void {
+    pending_thunks_.AppendToTop(std::move(thunk));
+  }
+
+  // Peek the list of pending thunks in this scope.
+  auto PeekPendingThunks() -> llvm::MutableArrayRef<PendingThunk> {
+    return pending_thunks_.PeekArray();
+  }
+
+ private:
+  ArrayStack<PendingThunk> pending_thunks_;
+};
+
+}  // namespace Carbon::Check
+
+#endif  // CARBON_TOOLCHAIN_CHECK_DEFERRED_DEFINITION_SCOPE_H_

+ 17 - 10
toolchain/check/deferred_definition_worklist.cpp

@@ -33,7 +33,7 @@ auto DeferredDefinitionWorklist::SuspendFunctionAndPush(
 }
 
 auto DeferredDefinitionWorklist::PushEnterDeferredDefinitionScope(
-    Context& context) -> void {
+    Context& context) -> bool {
   bool nested = !entered_scopes_.empty() &&
                 entered_scopes_.back().scope_index ==
                     context.decl_name_stack().PeekInitialScopeIndex();
@@ -43,25 +43,30 @@ auto DeferredDefinitionWorklist::PushEnterDeferredDefinitionScope(
       .suspended_name = std::nullopt, .in_deferred_definition_scope = nested});
   CARBON_VLOG("{0}Push EnterDeferredDefinitionScope {1}\n", VlogPrefix,
               nested ? "(nested)" : "(non-nested)");
+  return !nested;
 }
 
 auto DeferredDefinitionWorklist::SuspendFinishedScopeAndPush(Context& context)
-    -> bool {
+    -> FinishedScopeKind {
   auto start_index = entered_scopes_.pop_back_val().worklist_start_index;
 
   // If we've not found any deferred definitions in this scope, clean up the
   // stack.
   if (start_index == worklist_.size() - 1) {
     context.decl_name_stack().PopScope();
-    worklist_.pop_back();
+    auto enter_scope =
+        get<EnterDeferredDefinitionScope>(worklist_.pop_back_val());
     CARBON_VLOG("{0}Pop EnterDeferredDefinitionScope (empty)\n", VlogPrefix);
-    return false;
+    return enter_scope.in_deferred_definition_scope
+               ? FinishedScopeKind::Nested
+               : FinishedScopeKind::NonNestedEmpty;
   }
 
   // If we're finishing a nested deferred definition scope, keep track of that
   // but don't type-check deferred definitions now.
-  auto& enter_scope = get<EnterDeferredDefinitionScope>(worklist_[start_index]);
-  if (enter_scope.in_deferred_definition_scope) {
+  if (auto& enter_scope =
+          get<EnterDeferredDefinitionScope>(worklist_[start_index]);
+      enter_scope.in_deferred_definition_scope) {
     // This is a nested deferred definition scope. Suspend the inner scope so we
     // can restore it when we come to type-check the deferred definitions.
     enter_scope.suspended_name = context.decl_name_stack().Suspend();
@@ -70,7 +75,7 @@ auto DeferredDefinitionWorklist::SuspendFinishedScopeAndPush(Context& context)
     worklist_.push_back(
         LeaveDeferredDefinitionScope{.in_deferred_definition_scope = true});
     CARBON_VLOG("{0}Push LeaveDeferredDefinitionScope (nested)\n", VlogPrefix);
-    return false;
+    return FinishedScopeKind::Nested;
   }
 
   // We're at the end of a non-nested deferred definition scope. Prepare to
@@ -95,10 +100,11 @@ auto DeferredDefinitionWorklist::SuspendFinishedScopeAndPush(Context& context)
   worklist_.pop_back();
   CARBON_VLOG("{0}Handle EnterDeferredDefinitionScope (non-nested)\n",
               VlogPrefix);
-  return true;
+  return FinishedScopeKind::NonNestedWithWork;
 }
 
-auto DeferredDefinitionWorklist::Pop() -> Task {
+auto DeferredDefinitionWorklist::Pop(
+    llvm::function_ref<auto(Task&&)->void> handle_fn) -> void {
   if (vlog_stream_) {
     VariantMatch(
         worklist_.back(),
@@ -118,7 +124,8 @@ auto DeferredDefinitionWorklist::Pop() -> Task {
         });
   }
 
-  return worklist_.pop_back_val();
+  handle_fn(std::move(worklist_.back()));
+  worklist_.pop_back();
 }
 
 }  // namespace Carbon::Check

+ 20 - 9
toolchain/check/deferred_definition_worklist.h

@@ -67,24 +67,35 @@ class DeferredDefinitionWorklist {
 
   explicit DeferredDefinitionWorklist(llvm::raw_ostream* vlog_stream);
 
-  // Suspend the current function definition and push a task onto the worklist
+  // Suspends the current function definition and push a task onto the worklist
   // to finish it later.
   auto SuspendFunctionAndPush(Context& context,
                               Parse::DeferredDefinitionIndex index,
                               Parse::FunctionDefinitionStartId node_id) -> void;
 
-  // Push a task to re-enter a function scope, so that functions defined within
-  // it are type-checked in the right context.
-  auto PushEnterDeferredDefinitionScope(Context& context) -> void;
+  // Pushes a task to re-enter a function scope, so that functions defined
+  // within it are type-checked in the right context. Returns whether a
+  // non-nested scope was entered.
+  auto PushEnterDeferredDefinitionScope(Context& context) -> bool;
+
+  // The kind of scope that we just finished.
+  enum class FinishedScopeKind {
+    // We finished a nested scope. No further action is taken at this point.
+    Nested,
+    // We finished a non-nested scope that has no further actions to perform.
+    NonNestedEmpty,
+    // We finished a non-nested scope that has further actions to perform.
+    NonNestedWithWork,
+  };
 
-  // Suspend the current deferred definition scope, which is finished but still
-  // on the decl_name_stack, and push a task to leave the scope when we're
+  // Suspends the current deferred definition scope, which is finished but still
+  // on the decl_name_stack, and pushes a task to leave the scope when we're
   // type-checking deferred definitions. Returns `true` if the current list of
   // deferred definitions should be type-checked immediately.
-  auto SuspendFinishedScopeAndPush(Context& context) -> bool;
+  auto SuspendFinishedScopeAndPush(Context& context) -> FinishedScopeKind;
 
-  // Pop the next task off the worklist.
-  auto Pop() -> Task;
+  // Pop and handle the next task on the worklist.
+  auto Pop(llvm::function_ref<auto(Task&&)->void> handle_fn) -> void;
 
   // CHECK that the work list has no further work.
   auto VerifyEmpty() {

+ 3 - 1
toolchain/check/function.h

@@ -16,7 +16,9 @@ namespace Carbon::Check {
 // State saved for a function definition that has been suspended after
 // processing its declaration and before processing its body. This is used for
 // inline method handling.
-struct SuspendedFunction {
+//
+// This type is large, so moves of this type should be avoided.
+struct SuspendedFunction : public MoveOnly<SuspendedFunction> {
   // The function that was declared.
   SemIR::FunctionId function_id;
   // The instruction ID of the FunctionDecl instruction.

+ 1 - 1
toolchain/check/handle.h

@@ -28,7 +28,7 @@ auto HandleFunctionDefinitionSuspend(Context& context,
 // Handle resuming the definition of a function, after a previous suspension.
 auto HandleFunctionDefinitionResume(Context& context,
                                     Parse::FunctionDefinitionStartId node_id,
-                                    SuspendedFunction suspended_fn) -> void;
+                                    SuspendedFunction&& suspended_fn) -> void;
 
 }  // namespace Carbon::Check
 

+ 2 - 2
toolchain/check/handle_function.cpp

@@ -594,8 +594,8 @@ auto HandleFunctionDefinitionSuspend(Context& context,
 
 auto HandleFunctionDefinitionResume(Context& context,
                                     Parse::FunctionDefinitionStartId node_id,
-                                    SuspendedFunction suspended_fn) -> void {
-  context.decl_name_stack().Restore(suspended_fn.saved_name_state);
+                                    SuspendedFunction&& suspended_fn) -> void {
+  context.decl_name_stack().Restore(std::move(suspended_fn.saved_name_state));
   HandleFunctionDefinitionAfterSignature(
       context, node_id, suspended_fn.function_id, suspended_fn.decl_id);
 }

+ 30 - 6
toolchain/check/node_id_traversal.cpp

@@ -8,7 +8,9 @@
 #include <utility>
 #include <variant>
 
+#include "toolchain/check/deferred_definition_worklist.h"
 #include "toolchain/check/handle.h"
+#include "toolchain/check/thunk.h"
 
 namespace Carbon::Check {
 
@@ -29,9 +31,13 @@ auto NodeIdTraversal::Next() -> std::optional<Parse::NodeId> {
     // should check, restore its suspended state, and add a corresponding
     // `Chunk` to the top of the chunk list.
     if (chunks_.back().checking_deferred_definitions) {
-      std::visit(
-          [&](auto&& task) { PerformTask(std::forward<decltype(task)>(task)); },
-          worklist_.Pop());
+      worklist_.Pop([&](DeferredDefinitionWorklist::Task&& task) {
+        std::visit(
+            [&](auto&& task) {
+              PerformTask(std::forward<decltype(task)>(task));
+            },
+            std::move(task));
+      });
       continue;
     }
 
@@ -111,15 +117,33 @@ auto NodeIdTraversal::Handle(Parse::NodeKind parse_kind) -> void {
   // When we reach the start of a deferred definition scope, add a task to the
   // worklist to check future skipped definitions in the new context.
   if (IsStartOfDeferredDefinitionScope(parse_kind)) {
-    worklist_.PushEnterDeferredDefinitionScope(*context_);
+    if (worklist_.PushEnterDeferredDefinitionScope(*context_)) {
+      // Track that we're within a new non-nested deferred definition scope.
+      context_->deferred_definition_scope_stack().Push();
+    }
   }
 
   // When we reach the end of a deferred definition scope, add a task to the
   // worklist to leave the scope. If this is not a nested scope, start
   // checking the deferred definitions now.
   if (IsEndOfDeferredDefinitionScope(parse_kind)) {
-    chunks_.back().checking_deferred_definitions =
-        worklist_.SuspendFinishedScopeAndPush(*context_);
+    auto scope_kind = worklist_.SuspendFinishedScopeAndPush(*context_);
+
+    // At the end of a non-nested scope, define any pending thunks and clean up
+    // the stack.
+    if (scope_kind != DeferredDefinitionWorklist::FinishedScopeKind::Nested) {
+      for (auto& thunk :
+           context_->deferred_definition_scope_stack().PeekPendingThunks()) {
+        BuildThunkDefinition(*context_, std::move(thunk));
+      }
+      context_->deferred_definition_scope_stack().Pop();
+    }
+
+    // If we have function definitions in this scope, process them next.
+    if (scope_kind ==
+        DeferredDefinitionWorklist::FinishedScopeKind::NonNestedWithWork) {
+      chunks_.back().checking_deferred_definitions = true;
+    }
   }
 }
 

+ 1 - 1
toolchain/check/scope_stack.cpp

@@ -286,7 +286,7 @@ auto ScopeStack::Suspend() -> SuspendedScope {
   return result;
 }
 
-auto ScopeStack::Restore(SuspendedScope scope) -> void {
+auto ScopeStack::Restore(SuspendedScope&& scope) -> void {
   compile_time_binding_stack_.PushArray();
   for (auto [index, inst_id] : scope.suspended_items) {
     if (index == SuspendedScope::ScopeItem::IndexForCompileTimeBinding) {

+ 5 - 3
toolchain/check/scope_stack.h

@@ -6,6 +6,7 @@
 #define CARBON_TOOLCHAIN_CHECK_SCOPE_STACK_H_
 
 #include "common/array_stack.h"
+#include "common/move_only.h"
 #include "common/set.h"
 #include "llvm/ADT/SmallVector.h"
 #include "toolchain/check/full_pattern_stack.h"
@@ -44,6 +45,7 @@ class ScopeStack {
   };
 
   // Information about a scope that has been temporarily removed from the stack.
+  // This type is large, so moves of this type should be avoided.
   struct SuspendedScope;
 
   // Pushes a scope for a declaration name's parameters.
@@ -168,7 +170,7 @@ class ScopeStack {
   auto Suspend() -> SuspendedScope;
 
   // Restores a suspended scope stack entry.
-  auto Restore(SuspendedScope scope) -> void;
+  auto Restore(SuspendedScope&& scope) -> void;
 
   // Runs verification that the processing cleanly finished.
   auto VerifyOnFinish() const -> void;
@@ -189,7 +191,7 @@ class ScopeStack {
 
  private:
   // An entry in scope_stack_.
-  struct ScopeStackEntry {
+  struct ScopeStackEntry : public MoveOnly<ScopeStackEntry> {
     auto is_lexical_scope() const -> bool { return !scope_id.has_value(); }
 
     // The sequential index of this scope entry within the file.
@@ -315,7 +317,7 @@ class ScopeStack {
   FullPatternStack full_pattern_stack_;
 };
 
-struct ScopeStack::SuspendedScope {
+struct ScopeStack::SuspendedScope : public MoveOnly<SuspendedScope> {
   // An item that was suspended within this scope. This represents either a
   // lexical lookup entry in this scope, or a compile time binding entry in this
   // scope.

+ 106 - 159
toolchain/check/testdata/impl/fail_impl_bad_assoc_fn.carbon

@@ -129,45 +129,45 @@ class FMissingImplicitParam {
 
 class FMissingReturnType {
   impl as J {
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+13]]:5: error: cannot implicitly convert expression of type `()` to `bool` [ConversionFailure]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+16]]:5: error: cannot implicitly convert expression of type `()` to `bool` [ConversionFailure]
     // CHECK:STDERR:     fn F[self: bool](b: bool);
     // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+10]]:5: note: type `()` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+13]]:5: note: type `()` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
     // CHECK:STDERR:     fn F[self: bool](b: bool);
     // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~
     // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-39]]:15: note: while building thunk to match the signature of this function [ThunkSignature]
     // CHECK:STDERR: interface J { fn F[self: bool](b: bool) -> bool; }
     // CHECK:STDERR:               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-43]]:32: error: forming value of incomplete type `FDifferentParamType` [IncompleteTypeInValueConversion]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-43]]:32: error: cannot implicitly convert expression of type `bool` to `FDifferentParamType` [ConversionFailure]
+    // CHECK:STDERR: interface J { fn F[self: bool](b: bool) -> bool; }
+    // CHECK:STDERR:                                ^~~~~~~
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-46]]:32: note: type `bool` does not implement interface `Core.ImplicitAs(FDifferentParamType)` [MissingImplInMemberAccessNote]
     // CHECK:STDERR: interface J { fn F[self: bool](b: bool) -> bool; }
     // CHECK:STDERR:                                ^~~~~~~
     fn F[self: bool](b: bool);
   }
 }
 
-// CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+3]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-// CHECK:STDERR: class FDifferentParamType {
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~
 class FDifferentParamType {
   impl as J {
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+10]]:22: note: initializing function parameter [InCallToFunctionParam]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+13]]:22: note: initializing function parameter [InCallToFunctionParam]
     // CHECK:STDERR:     fn F[self: bool](b: Self) -> bool;
     // CHECK:STDERR:                      ^~~~~~~
     // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-58]]:15: note: while building thunk to match the signature of this function [ThunkSignature]
     // CHECK:STDERR: interface J { fn F[self: bool](b: bool) -> bool; }
     // CHECK:STDERR:               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-62]]:20: error: forming value of incomplete type `FDifferentImplicitParamType` [IncompleteTypeInValueConversion]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-62]]:20: error: cannot implicitly convert expression of type `bool` to `FDifferentImplicitParamType` [ConversionFailure]
+    // CHECK:STDERR: interface J { fn F[self: bool](b: bool) -> bool; }
+    // CHECK:STDERR:                    ^~~~~~~~~~
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-65]]:20: note: type `bool` does not implement interface `Core.ImplicitAs(FDifferentImplicitParamType)` [MissingImplInMemberAccessNote]
     // CHECK:STDERR: interface J { fn F[self: bool](b: bool) -> bool; }
     // CHECK:STDERR:                    ^~~~~~~~~~
     fn F[self: bool](b: Self) -> bool;
   }
 }
 
-// CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+3]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-// CHECK:STDERR: class FDifferentImplicitParamType {
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 class FDifferentImplicitParamType {
   impl as J {
     // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+7]]:10: note: initializing function parameter [InCallToFunctionParam]
@@ -183,16 +183,13 @@ class FDifferentImplicitParamType {
 
 class FDifferentReturnType {
   impl as J {
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+13]]:5: error: function returns incomplete type `FDifferentReturnType` [IncompleteTypeInFunctionReturnType]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+10]]:5: error: cannot implicitly convert expression of type `FDifferentReturnType` to `bool` [ConversionFailure]
     // CHECK:STDERR:     fn F[self: bool](b: bool) -> Self;
     // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-5]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-    // CHECK:STDERR: class FDifferentReturnType {
-    // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+7]]:31: note: return type declared here [IncompleteReturnTypeHere]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+7]]:5: note: type `FDifferentReturnType` does not implement interface `Core.ImplicitAs(bool)` [MissingImplInMemberAccessNote]
     // CHECK:STDERR:     fn F[self: bool](b: bool) -> Self;
-    // CHECK:STDERR:                               ^~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-96]]:15: note: while building thunk to match the signature of this function [ThunkSignature]
+    // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-93]]:15: note: while building thunk to match the signature of this function [ThunkSignature]
     // CHECK:STDERR: interface J { fn F[self: bool](b: bool) -> bool; }
     // CHECK:STDERR:               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // CHECK:STDERR:
@@ -201,93 +198,37 @@ class FDifferentReturnType {
 }
 
 interface SelfNested {
-  // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+3]]:41: error: function returns incomplete type `array(SelfNestedBadParam, 4)` [IncompleteTypeInFunctionReturnType]
+  // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+6]]:8: error: cannot implicitly convert expression of type `SelfNestedBadParam` to `i32` [ConversionFailure]
+  // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
+  // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+3]]:8: note: type `SelfNestedBadParam` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote]
   // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-  // CHECK:STDERR:                                         ^~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
 }
 
-// CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+3]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-// CHECK:STDERR: class SelfNestedBadParam {
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
 class SelfNestedBadParam {
   impl as SelfNested {
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+43]]:5: note: while building thunk calling this function [ThunkCallee]
-    // CHECK:STDERR:     fn F(x: (SelfNestedBadParam*, {.x: i32, .y: i32})) -> array(SelfNestedBadParam, 4);
-    // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-12]]:8: error: parameter has incomplete type `(SelfNestedBadParam*, {.x: SelfNestedBadParam, .y: i32})` in function definition [IncompleteTypeInFunctionParam]
-    // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-    // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-9]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-    // CHECK:STDERR: class SelfNestedBadParam {
-    // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+33]]:5: note: while building thunk calling this function [ThunkCallee]
-    // CHECK:STDERR:     fn F(x: (SelfNestedBadParam*, {.x: i32, .y: i32})) -> array(SelfNestedBadParam, 4);
-    // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+29]]:5: error: function returns incomplete type `array(SelfNestedBadParam, 4)` [IncompleteTypeInFunctionReturnType]
-    // CHECK:STDERR:     fn F(x: (SelfNestedBadParam*, {.x: i32, .y: i32})) -> array(SelfNestedBadParam, 4);
-    // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-19]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-    // CHECK:STDERR: class SelfNestedBadParam {
-    // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+23]]:56: note: return type declared here [IncompleteReturnTypeHere]
-    // CHECK:STDERR:     fn F(x: (SelfNestedBadParam*, {.x: i32, .y: i32})) -> array(SelfNestedBadParam, 4);
-    // CHECK:STDERR:                                                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-31]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
-    // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-    // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-35]]:8: error: cannot implicitly convert expression of type `SelfNestedBadParam` to `i32` [ConversionFailure]
-    // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-    // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-38]]:8: note: type `SelfNestedBadParam` does not implement interface `Core.ImplicitAs(i32)` [MissingImplInMemberAccessNote]
-    // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-    // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+10]]:10: note: initializing function parameter [InCallToFunctionParam]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+7]]:10: note: initializing function parameter [InCallToFunctionParam]
     // CHECK:STDERR:     fn F(x: (SelfNestedBadParam*, {.x: i32, .y: i32})) -> array(SelfNestedBadParam, 4);
     // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-44]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-8]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
     // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
     // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-48]]:41: error: function returns incomplete type `array(SelfNestedBadReturnType, 4)` [IncompleteTypeInFunctionReturnType]
-    // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-    // CHECK:STDERR:                                         ^~~~~~~~~~~~~~~~~
     fn F(x: (SelfNestedBadParam*, {.x: i32, .y: i32})) -> array(SelfNestedBadParam, 4);
   }
 }
 
-// CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+3]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-// CHECK:STDERR: class SelfNestedBadReturnType {
-// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 class SelfNestedBadReturnType {
   impl as SelfNested {
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+27]]:5: note: while building thunk calling this function [ThunkCallee]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+10]]:5: error: cannot implicitly convert expression of type `array(SelfNestedBadParam, 4)` to `array(SelfNestedBadReturnType, 4)` [ConversionFailure]
     // CHECK:STDERR:     fn F(x: (SelfNestedBadReturnType*, {.x: SelfNestedBadReturnType, .y: i32})) -> array(SelfNestedBadParam, 4);
     // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-64]]:8: error: parameter has incomplete type `(SelfNestedBadReturnType*, {.x: SelfNestedBadReturnType, .y: i32})` in function definition [IncompleteTypeInFunctionParam]
-    // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-    // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-9]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-    // CHECK:STDERR: class SelfNestedBadReturnType {
-    // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+17]]:5: note: while building thunk calling this function [ThunkCallee]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+7]]:5: note: type `array(SelfNestedBadParam, 4)` does not implement interface `Core.ImplicitAs(array(SelfNestedBadReturnType, 4))` [MissingImplInMemberAccessNote]
     // CHECK:STDERR:     fn F(x: (SelfNestedBadReturnType*, {.x: SelfNestedBadReturnType, .y: i32})) -> array(SelfNestedBadParam, 4);
     // CHECK:STDERR:     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR:
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-74]]:8: error: forming value of incomplete type `(SelfNestedBadReturnType*, {.x: SelfNestedBadReturnType, .y: i32})` [IncompleteTypeInValueConversion]
-    // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
-    // CHECK:STDERR:        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-19]]:1: note: class is incomplete within its definition [ClassIncompleteWithinDefinition]
-    // CHECK:STDERR: class SelfNestedBadReturnType {
-    // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE+7]]:10: note: initializing function parameter [InCallToFunctionParam]
-    // CHECK:STDERR:     fn F(x: (SelfNestedBadReturnType*, {.x: SelfNestedBadReturnType, .y: i32})) -> array(SelfNestedBadParam, 4);
-    // CHECK:STDERR:          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-83]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
+    // CHECK:STDERR: fail_impl_bad_assoc_fn.carbon:[[@LINE-24]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
     // CHECK:STDERR:   fn F(x: (Self*, {.x: Self, .y: i32})) -> array(Self, 4);
     // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     // CHECK:STDERR:
@@ -747,28 +688,28 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.1a7: %Self.ref as %J.ref {
-// CHECK:STDOUT:   %F.decl.loc145_30.1: %F.type.123d7a.1 = fn_decl @F.12 [concrete = constants.%F.c7d02d.1] {
+// CHECK:STDOUT:   %F.decl.loc148_30.1: %F.type.123d7a.1 = fn_decl @F.12 [concrete = constants.%F.c7d02d.1] {
 // CHECK:STDOUT:     %self.patt: %pattern_type.831 = binding_pattern self [concrete]
 // CHECK:STDOUT:     %self.param_patt: %pattern_type.831 = value_param_pattern %self.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %b.patt: %pattern_type.831 = binding_pattern b [concrete]
 // CHECK:STDOUT:     %b.param_patt: %pattern_type.831 = value_param_pattern %b.patt, call_param1 [concrete]
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %self.param: bool = value_param call_param0
-// CHECK:STDOUT:     %.loc145_16.1: type = splice_block %.loc145_16.3 [concrete = bool] {
-// CHECK:STDOUT:       %bool.make_type.loc145_16: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:       %.loc145_16.2: type = value_of_initializer %bool.make_type.loc145_16 [concrete = bool]
-// CHECK:STDOUT:       %.loc145_16.3: type = converted %bool.make_type.loc145_16, %.loc145_16.2 [concrete = bool]
+// CHECK:STDOUT:     %.loc148_16.1: type = splice_block %.loc148_16.3 [concrete = bool] {
+// CHECK:STDOUT:       %bool.make_type.loc148_16: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:       %.loc148_16.2: type = value_of_initializer %bool.make_type.loc148_16 [concrete = bool]
+// CHECK:STDOUT:       %.loc148_16.3: type = converted %bool.make_type.loc148_16, %.loc148_16.2 [concrete = bool]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %self: bool = bind_name self, %self.param
 // CHECK:STDOUT:     %b.param: bool = value_param call_param1
-// CHECK:STDOUT:     %.loc145_25.1: type = splice_block %.loc145_25.3 [concrete = bool] {
-// CHECK:STDOUT:       %bool.make_type.loc145_25: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:       %.loc145_25.2: type = value_of_initializer %bool.make_type.loc145_25 [concrete = bool]
-// CHECK:STDOUT:       %.loc145_25.3: type = converted %bool.make_type.loc145_25, %.loc145_25.2 [concrete = bool]
+// CHECK:STDOUT:     %.loc148_25.1: type = splice_block %.loc148_25.3 [concrete = bool] {
+// CHECK:STDOUT:       %bool.make_type.loc148_25: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:       %.loc148_25.2: type = value_of_initializer %bool.make_type.loc148_25 [concrete = bool]
+// CHECK:STDOUT:       %.loc148_25.3: type = converted %bool.make_type.loc148_25, %.loc148_25.2 [concrete = bool]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %b: bool = bind_name b, %b.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl.loc145_30.2: %F.type.123d7a.2 = fn_decl @F.13 [concrete = constants.%F.c7d02d.2] {
+// CHECK:STDOUT:   %F.decl.loc148_30.2: %F.type.123d7a.2 = fn_decl @F.13 [concrete = constants.%F.c7d02d.2] {
 // CHECK:STDOUT:     %self.patt: %pattern_type.831 = binding_pattern self [concrete]
 // CHECK:STDOUT:     %self.param_patt: %pattern_type.831 = value_param_pattern %self.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %b.patt: %pattern_type.831 = binding_pattern b [concrete]
@@ -785,12 +726,12 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F.decl.loc145_30.1
+// CHECK:STDOUT:   .F = %F.decl.loc148_30.1
 // CHECK:STDOUT:   witness = @FMissingReturnType.%J.impl_witness
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.f2b: %Self.ref as %J.ref {
-// CHECK:STDOUT:   %F.decl.loc164_38.1: %F.type.6b537d.1 = fn_decl @F.14 [concrete = constants.%F.04313a.1] {
+// CHECK:STDOUT:   %F.decl.loc167_38.1: %F.type.6b537d.1 = fn_decl @F.14 [concrete = constants.%F.04313a.1] {
 // CHECK:STDOUT:     %self.patt: %pattern_type.831 = binding_pattern self [concrete]
 // CHECK:STDOUT:     %self.param_patt: %pattern_type.831 = value_param_pattern %self.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %b.patt: %pattern_type.b90 = binding_pattern b [concrete]
@@ -798,14 +739,14 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %return.patt: %pattern_type.831 = return_slot_pattern [concrete]
 // CHECK:STDOUT:     %return.param_patt: %pattern_type.831 = out_param_pattern %return.patt, call_param2 [concrete]
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %bool.make_type.loc164_34: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:     %.loc164_34.1: type = value_of_initializer %bool.make_type.loc164_34 [concrete = bool]
-// CHECK:STDOUT:     %.loc164_34.2: type = converted %bool.make_type.loc164_34, %.loc164_34.1 [concrete = bool]
+// CHECK:STDOUT:     %bool.make_type.loc167_34: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:     %.loc167_34.1: type = value_of_initializer %bool.make_type.loc167_34 [concrete = bool]
+// CHECK:STDOUT:     %.loc167_34.2: type = converted %bool.make_type.loc167_34, %.loc167_34.1 [concrete = bool]
 // CHECK:STDOUT:     %self.param: bool = value_param call_param0
-// CHECK:STDOUT:     %.loc164_16.1: type = splice_block %.loc164_16.3 [concrete = bool] {
-// CHECK:STDOUT:       %bool.make_type.loc164_16: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:       %.loc164_16.2: type = value_of_initializer %bool.make_type.loc164_16 [concrete = bool]
-// CHECK:STDOUT:       %.loc164_16.3: type = converted %bool.make_type.loc164_16, %.loc164_16.2 [concrete = bool]
+// CHECK:STDOUT:     %.loc167_16.1: type = splice_block %.loc167_16.3 [concrete = bool] {
+// CHECK:STDOUT:       %bool.make_type.loc167_16: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:       %.loc167_16.2: type = value_of_initializer %bool.make_type.loc167_16 [concrete = bool]
+// CHECK:STDOUT:       %.loc167_16.3: type = converted %bool.make_type.loc167_16, %.loc167_16.2 [concrete = bool]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %self: bool = bind_name self, %self.param
 // CHECK:STDOUT:     %b.param: %FDifferentParamType = value_param call_param1
@@ -814,7 +755,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %return.param: ref bool = out_param call_param2
 // CHECK:STDOUT:     %return: ref bool = return_slot %return.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl.loc164_38.2: %F.type.6b537d.2 = fn_decl @F.15 [concrete = constants.%F.04313a.2] {
+// CHECK:STDOUT:   %F.decl.loc167_38.2: %F.type.6b537d.2 = fn_decl @F.15 [concrete = constants.%F.04313a.2] {
 // CHECK:STDOUT:     %self.patt: %pattern_type.831 = binding_pattern self [concrete]
 // CHECK:STDOUT:     %self.param_patt: %pattern_type.831 = value_param_pattern %self.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %b.patt: %pattern_type.831 = binding_pattern b [concrete]
@@ -831,7 +772,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F.decl.loc164_38.1
+// CHECK:STDOUT:   .F = %F.decl.loc167_38.1
 // CHECK:STDOUT:   witness = @FDifferentParamType.%J.impl_witness
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -882,7 +823,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.fcc: %Self.ref as %J.ref {
-// CHECK:STDOUT:   %F.decl.loc199_38.1: %F.type.d3b58f.1 = fn_decl @F.18 [concrete = constants.%F.be86c9.1] {
+// CHECK:STDOUT:   %F.decl.loc196_38.1: %F.type.d3b58f.1 = fn_decl @F.18 [concrete = constants.%F.be86c9.1] {
 // CHECK:STDOUT:     %self.patt: %pattern_type.831 = binding_pattern self [concrete]
 // CHECK:STDOUT:     %self.param_patt: %pattern_type.831 = value_param_pattern %self.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %b.patt: %pattern_type.831 = binding_pattern b [concrete]
@@ -892,23 +833,23 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%FDifferentReturnType [concrete = constants.%FDifferentReturnType]
 // CHECK:STDOUT:     %self.param: bool = value_param call_param0
-// CHECK:STDOUT:     %.loc199_16.1: type = splice_block %.loc199_16.3 [concrete = bool] {
-// CHECK:STDOUT:       %bool.make_type.loc199_16: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:       %.loc199_16.2: type = value_of_initializer %bool.make_type.loc199_16 [concrete = bool]
-// CHECK:STDOUT:       %.loc199_16.3: type = converted %bool.make_type.loc199_16, %.loc199_16.2 [concrete = bool]
+// CHECK:STDOUT:     %.loc196_16.1: type = splice_block %.loc196_16.3 [concrete = bool] {
+// CHECK:STDOUT:       %bool.make_type.loc196_16: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:       %.loc196_16.2: type = value_of_initializer %bool.make_type.loc196_16 [concrete = bool]
+// CHECK:STDOUT:       %.loc196_16.3: type = converted %bool.make_type.loc196_16, %.loc196_16.2 [concrete = bool]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %self: bool = bind_name self, %self.param
 // CHECK:STDOUT:     %b.param: bool = value_param call_param1
-// CHECK:STDOUT:     %.loc199_25.1: type = splice_block %.loc199_25.3 [concrete = bool] {
-// CHECK:STDOUT:       %bool.make_type.loc199_25: init type = call constants.%Bool() [concrete = bool]
-// CHECK:STDOUT:       %.loc199_25.2: type = value_of_initializer %bool.make_type.loc199_25 [concrete = bool]
-// CHECK:STDOUT:       %.loc199_25.3: type = converted %bool.make_type.loc199_25, %.loc199_25.2 [concrete = bool]
+// CHECK:STDOUT:     %.loc196_25.1: type = splice_block %.loc196_25.3 [concrete = bool] {
+// CHECK:STDOUT:       %bool.make_type.loc196_25: init type = call constants.%Bool() [concrete = bool]
+// CHECK:STDOUT:       %.loc196_25.2: type = value_of_initializer %bool.make_type.loc196_25 [concrete = bool]
+// CHECK:STDOUT:       %.loc196_25.3: type = converted %bool.make_type.loc196_25, %.loc196_25.2 [concrete = bool]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %b: bool = bind_name b, %b.param
 // CHECK:STDOUT:     %return.param: ref %FDifferentReturnType = out_param call_param2
 // CHECK:STDOUT:     %return: ref %FDifferentReturnType = return_slot %return.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl.loc199_38.2: %F.type.d3b58f.2 = fn_decl @F.19 [concrete = constants.%F.be86c9.2] {
+// CHECK:STDOUT:   %F.decl.loc196_38.2: %F.type.d3b58f.2 = fn_decl @F.19 [concrete = constants.%F.be86c9.2] {
 // CHECK:STDOUT:     %self.patt: %pattern_type.831 = binding_pattern self [concrete]
 // CHECK:STDOUT:     %self.param_patt: %pattern_type.831 = value_param_pattern %self.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %b.patt: %pattern_type.831 = binding_pattern b [concrete]
@@ -925,37 +866,37 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
-// CHECK:STDOUT:   .F = %F.decl.loc199_38.1
+// CHECK:STDOUT:   .F = %F.decl.loc196_38.1
 // CHECK:STDOUT:   witness = @FDifferentReturnType.%J.impl_witness
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.6a5: %Self.ref as %SelfNested.ref {
-// CHECK:STDOUT:   %F.decl.loc258_87.1: %F.type.f90165.1 = fn_decl @F.21 [concrete = constants.%F.fa8d04.1] {
+// CHECK:STDOUT:   %F.decl.loc219_87.1: %F.type.f90165.1 = fn_decl @F.21 [concrete = constants.%F.fa8d04.1] {
 // CHECK:STDOUT:     %x.patt: %pattern_type.714 = binding_pattern x [concrete]
 // CHECK:STDOUT:     %x.param_patt: %pattern_type.714 = value_param_pattern %x.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %return.patt: %pattern_type.1f1 = return_slot_pattern [concrete]
 // CHECK:STDOUT:     %return.param_patt: %pattern_type.1f1 = out_param_pattern %return.patt, call_param1 [concrete]
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %SelfNestedBadParam.ref.loc258_65: type = name_ref SelfNestedBadParam, file.%SelfNestedBadParam.decl [concrete = constants.%SelfNestedBadParam]
+// CHECK:STDOUT:     %SelfNestedBadParam.ref.loc219_65: type = name_ref SelfNestedBadParam, file.%SelfNestedBadParam.decl [concrete = constants.%SelfNestedBadParam]
 // CHECK:STDOUT:     %int_4: Core.IntLiteral = int_value 4 [concrete = constants.%int_4]
-// CHECK:STDOUT:     %array_type: type = array_type %int_4, %SelfNestedBadParam.ref.loc258_65 [concrete = constants.%array_type.a41]
+// CHECK:STDOUT:     %array_type: type = array_type %int_4, %SelfNestedBadParam.ref.loc219_65 [concrete = constants.%array_type.a41]
 // CHECK:STDOUT:     %x.param: %tuple.type.a7d = value_param call_param0
-// CHECK:STDOUT:     %.loc258_53.1: type = splice_block %.loc258_53.3 [concrete = constants.%tuple.type.a7d] {
-// CHECK:STDOUT:       %SelfNestedBadParam.ref.loc258_14: type = name_ref SelfNestedBadParam, file.%SelfNestedBadParam.decl [concrete = constants.%SelfNestedBadParam]
-// CHECK:STDOUT:       %ptr: type = ptr_type %SelfNestedBadParam.ref.loc258_14 [concrete = constants.%ptr.4cd]
-// CHECK:STDOUT:       %int_32.loc258_40: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
-// CHECK:STDOUT:       %i32.loc258_40: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
-// CHECK:STDOUT:       %int_32.loc258_49: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
-// CHECK:STDOUT:       %i32.loc258_49: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:     %.loc219_53.1: type = splice_block %.loc219_53.3 [concrete = constants.%tuple.type.a7d] {
+// CHECK:STDOUT:       %SelfNestedBadParam.ref.loc219_14: type = name_ref SelfNestedBadParam, file.%SelfNestedBadParam.decl [concrete = constants.%SelfNestedBadParam]
+// CHECK:STDOUT:       %ptr: type = ptr_type %SelfNestedBadParam.ref.loc219_14 [concrete = constants.%ptr.4cd]
+// CHECK:STDOUT:       %int_32.loc219_40: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:       %i32.loc219_40: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
+// CHECK:STDOUT:       %int_32.loc219_49: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
+// CHECK:STDOUT:       %i32.loc219_49: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
 // CHECK:STDOUT:       %struct_type.x.y: type = struct_type {.x: %i32, .y: %i32} [concrete = constants.%struct_type.x.y.871]
-// CHECK:STDOUT:       %.loc258_53.2: %tuple.type.24b = tuple_literal (%ptr, %struct_type.x.y)
-// CHECK:STDOUT:       %.loc258_53.3: type = converted %.loc258_53.2, constants.%tuple.type.a7d [concrete = constants.%tuple.type.a7d]
+// CHECK:STDOUT:       %.loc219_53.2: %tuple.type.24b = tuple_literal (%ptr, %struct_type.x.y)
+// CHECK:STDOUT:       %.loc219_53.3: type = converted %.loc219_53.2, constants.%tuple.type.a7d [concrete = constants.%tuple.type.a7d]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %x: %tuple.type.a7d = bind_name x, %x.param
 // CHECK:STDOUT:     %return.param: ref %array_type.a41 = out_param call_param1
 // CHECK:STDOUT:     %return: ref %array_type.a41 = return_slot %return.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl.loc258_87.2: %F.type.f90165.2 = fn_decl @F.22 [concrete = constants.%F.fa8d04.2] {
+// CHECK:STDOUT:   %F.decl.loc219_87.2: %F.type.f90165.2 = fn_decl @F.22 [concrete = constants.%F.fa8d04.2] {
 // CHECK:STDOUT:     %x.patt: %pattern_type.a5c = binding_pattern x [concrete]
 // CHECK:STDOUT:     %x.param_patt: %pattern_type.a5c = value_param_pattern %x.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %return.patt: %pattern_type.1f1 = return_slot_pattern [concrete]
@@ -969,12 +910,12 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .SelfNestedBadParam = <poisoned>
-// CHECK:STDOUT:   .F = %F.decl.loc258_87.1
+// CHECK:STDOUT:   .F = %F.decl.loc219_87.1
 // CHECK:STDOUT:   witness = @SelfNestedBadParam.%SelfNested.impl_witness
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: impl @impl.bfc: %Self.ref as %SelfNested.ref {
-// CHECK:STDOUT:   %F.decl.loc294_112.1: %F.type.0e7d1d.1 = fn_decl @F.23 [concrete = constants.%F.0bc78a.1] {
+// CHECK:STDOUT:   %F.decl.loc235_112.1: %F.type.0e7d1d.1 = fn_decl @F.23 [concrete = constants.%F.0bc78a.1] {
 // CHECK:STDOUT:     %x.patt: %pattern_type.23f = binding_pattern x [concrete]
 // CHECK:STDOUT:     %x.param_patt: %pattern_type.23f = value_param_pattern %x.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %return.patt: %pattern_type.1f1 = return_slot_pattern [concrete]
@@ -984,21 +925,21 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %int_4: Core.IntLiteral = int_value 4 [concrete = constants.%int_4]
 // CHECK:STDOUT:     %array_type: type = array_type %int_4, %SelfNestedBadParam.ref [concrete = constants.%array_type.a41]
 // CHECK:STDOUT:     %x.param: %tuple.type.eb9 = value_param call_param0
-// CHECK:STDOUT:     %.loc294_78.1: type = splice_block %.loc294_78.3 [concrete = constants.%tuple.type.eb9] {
-// CHECK:STDOUT:       %SelfNestedBadReturnType.ref.loc294_14: type = name_ref SelfNestedBadReturnType, file.%SelfNestedBadReturnType.decl [concrete = constants.%SelfNestedBadReturnType]
-// CHECK:STDOUT:       %ptr: type = ptr_type %SelfNestedBadReturnType.ref.loc294_14 [concrete = constants.%ptr.612]
-// CHECK:STDOUT:       %SelfNestedBadReturnType.ref.loc294_45: type = name_ref SelfNestedBadReturnType, file.%SelfNestedBadReturnType.decl [concrete = constants.%SelfNestedBadReturnType]
+// CHECK:STDOUT:     %.loc235_78.1: type = splice_block %.loc235_78.3 [concrete = constants.%tuple.type.eb9] {
+// CHECK:STDOUT:       %SelfNestedBadReturnType.ref.loc235_14: type = name_ref SelfNestedBadReturnType, file.%SelfNestedBadReturnType.decl [concrete = constants.%SelfNestedBadReturnType]
+// CHECK:STDOUT:       %ptr: type = ptr_type %SelfNestedBadReturnType.ref.loc235_14 [concrete = constants.%ptr.612]
+// CHECK:STDOUT:       %SelfNestedBadReturnType.ref.loc235_45: type = name_ref SelfNestedBadReturnType, file.%SelfNestedBadReturnType.decl [concrete = constants.%SelfNestedBadReturnType]
 // CHECK:STDOUT:       %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
 // CHECK:STDOUT:       %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
 // CHECK:STDOUT:       %struct_type.x.y: type = struct_type {.x: %SelfNestedBadReturnType, .y: %i32} [concrete = constants.%struct_type.x.y.ac5]
-// CHECK:STDOUT:       %.loc294_78.2: %tuple.type.24b = tuple_literal (%ptr, %struct_type.x.y)
-// CHECK:STDOUT:       %.loc294_78.3: type = converted %.loc294_78.2, constants.%tuple.type.eb9 [concrete = constants.%tuple.type.eb9]
+// CHECK:STDOUT:       %.loc235_78.2: %tuple.type.24b = tuple_literal (%ptr, %struct_type.x.y)
+// CHECK:STDOUT:       %.loc235_78.3: type = converted %.loc235_78.2, constants.%tuple.type.eb9 [concrete = constants.%tuple.type.eb9]
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     %x: %tuple.type.eb9 = bind_name x, %x.param
 // CHECK:STDOUT:     %return.param: ref %array_type.a41 = out_param call_param1
 // CHECK:STDOUT:     %return: ref %array_type.a41 = return_slot %return.param
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %F.decl.loc294_112.2: %F.type.0e7d1d.2 = fn_decl @F.24 [concrete = constants.%F.0bc78a.2] {
+// CHECK:STDOUT:   %F.decl.loc235_112.2: %F.type.0e7d1d.2 = fn_decl @F.24 [concrete = constants.%F.0bc78a.2] {
 // CHECK:STDOUT:     %x.patt: %pattern_type.23f = binding_pattern x [concrete]
 // CHECK:STDOUT:     %x.param_patt: %pattern_type.23f = value_param_pattern %x.patt, call_param0 [concrete]
 // CHECK:STDOUT:     %return.patt: %pattern_type.f56 = return_slot_pattern [concrete]
@@ -1013,7 +954,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT: !members:
 // CHECK:STDOUT:   .SelfNestedBadReturnType = <poisoned>
 // CHECK:STDOUT:   .SelfNestedBadParam = <poisoned>
-// CHECK:STDOUT:   .F = %F.decl.loc294_112.1
+// CHECK:STDOUT:   .F = %F.decl.loc235_112.1
 // CHECK:STDOUT:   witness = @SelfNestedBadReturnType.%SelfNested.impl_witness
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -1153,7 +1094,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%FMissingReturnType [concrete = constants.%FMissingReturnType]
 // CHECK:STDOUT:     %J.ref: type = name_ref J, file.%J.decl [concrete = constants.%J.type]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %J.impl_witness_table = impl_witness_table (@impl.1a7.%F.decl.loc145_30.2), @impl.1a7 [concrete]
+// CHECK:STDOUT:   %J.impl_witness_table = impl_witness_table (@impl.1a7.%F.decl.loc148_30.2), @impl.1a7 [concrete]
 // CHECK:STDOUT:   %J.impl_witness: <witness> = impl_witness %J.impl_witness_table [concrete = constants.%J.impl_witness.e75]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
@@ -1169,7 +1110,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%FDifferentParamType [concrete = constants.%FDifferentParamType]
 // CHECK:STDOUT:     %J.ref: type = name_ref J, file.%J.decl [concrete = constants.%J.type]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %J.impl_witness_table = impl_witness_table (@impl.f2b.%F.decl.loc164_38.2), @impl.f2b [concrete]
+// CHECK:STDOUT:   %J.impl_witness_table = impl_witness_table (@impl.f2b.%F.decl.loc167_38.2), @impl.f2b [concrete]
 // CHECK:STDOUT:   %J.impl_witness: <witness> = impl_witness %J.impl_witness_table [concrete = constants.%J.impl_witness.cbe]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
@@ -1201,7 +1142,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%FDifferentReturnType [concrete = constants.%FDifferentReturnType]
 // CHECK:STDOUT:     %J.ref: type = name_ref J, file.%J.decl [concrete = constants.%J.type]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %J.impl_witness_table = impl_witness_table (@impl.fcc.%F.decl.loc199_38.2), @impl.fcc [concrete]
+// CHECK:STDOUT:   %J.impl_witness_table = impl_witness_table (@impl.fcc.%F.decl.loc196_38.2), @impl.fcc [concrete]
 // CHECK:STDOUT:   %J.impl_witness: <witness> = impl_witness %J.impl_witness_table [concrete = constants.%J.impl_witness.bc1]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
@@ -1217,7 +1158,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%SelfNestedBadParam [concrete = constants.%SelfNestedBadParam]
 // CHECK:STDOUT:     %SelfNested.ref: type = name_ref SelfNested, file.%SelfNested.decl [concrete = constants.%SelfNested.type]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %SelfNested.impl_witness_table = impl_witness_table (@impl.6a5.%F.decl.loc258_87.2), @impl.6a5 [concrete]
+// CHECK:STDOUT:   %SelfNested.impl_witness_table = impl_witness_table (@impl.6a5.%F.decl.loc219_87.2), @impl.6a5 [concrete]
 // CHECK:STDOUT:   %SelfNested.impl_witness: <witness> = impl_witness %SelfNested.impl_witness_table [concrete = constants.%SelfNested.impl_witness.e31]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
@@ -1234,7 +1175,7 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%SelfNestedBadReturnType [concrete = constants.%SelfNestedBadReturnType]
 // CHECK:STDOUT:     %SelfNested.ref: type = name_ref SelfNested, file.%SelfNested.decl [concrete = constants.%SelfNested.type]
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %SelfNested.impl_witness_table = impl_witness_table (@impl.bfc.%F.decl.loc294_112.2), @impl.bfc [concrete]
+// CHECK:STDOUT:   %SelfNested.impl_witness_table = impl_witness_table (@impl.bfc.%F.decl.loc235_112.2), @impl.bfc [concrete]
 // CHECK:STDOUT:   %SelfNested.impl_witness: <witness> = impl_witness %SelfNested.impl_witness_table [concrete = constants.%SelfNested.impl_witness.d5e]
 // CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete = constants.%complete_type.357]
@@ -1301,10 +1242,10 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT: fn @F.13(%self.param: bool, %b.param: bool) -> bool {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: bool = name_ref self, %self.param
-// CHECK:STDOUT:   %F.bound: <bound method> = bound_method %self.ref, @impl.1a7.%F.decl.loc145_30.1
+// CHECK:STDOUT:   %F.bound: <bound method> = bound_method %self.ref, @impl.1a7.%F.decl.loc148_30.1
 // CHECK:STDOUT:   %b.ref: bool = name_ref b, %b.param
 // CHECK:STDOUT:   %F.call: init %empty_tuple.type = call %F.bound(%self.ref, %b.ref)
-// CHECK:STDOUT:   %.loc145: bool = converted %F.call, <error> [concrete = <error>]
+// CHECK:STDOUT:   %.loc148: bool = converted %F.call, <error> [concrete = <error>]
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -1313,12 +1254,13 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT: fn @F.15(%self.param: bool, %b.param: bool) -> bool {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: bool = name_ref self, %self.param
-// CHECK:STDOUT:   %F.bound: <bound method> = bound_method %self.ref, @impl.f2b.%F.decl.loc164_38.1
+// CHECK:STDOUT:   %F.bound: <bound method> = bound_method %self.ref, @impl.f2b.%F.decl.loc167_38.1
 // CHECK:STDOUT:   %b.ref: bool = name_ref b, %b.param
+// CHECK:STDOUT:   %.loc99: %FDifferentParamType = converted %b.ref, <error> [concrete = <error>]
 // CHECK:STDOUT:   %F.call: init bool = call %F.bound(%self.ref, <error>)
-// CHECK:STDOUT:   %.loc164_38.1: bool = value_of_initializer %F.call
-// CHECK:STDOUT:   %.loc164_38.2: bool = converted %F.call, %.loc164_38.1
-// CHECK:STDOUT:   return %.loc164_38.2
+// CHECK:STDOUT:   %.loc167_38.1: bool = value_of_initializer %F.call
+// CHECK:STDOUT:   %.loc167_38.2: bool = converted %F.call, %.loc167_38.1
+// CHECK:STDOUT:   return %.loc167_38.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.16(%self.param: %FDifferentImplicitParamType, %b.param: bool) -> bool;
@@ -1328,20 +1270,23 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT:   %self.ref: bool = name_ref self, %self.param
 // CHECK:STDOUT:   %F.bound: <bound method> = bound_method %self.ref, @impl.db4.%F.decl.loc180_38.1
 // CHECK:STDOUT:   %b.ref: bool = name_ref b, %b.param
+// CHECK:STDOUT:   %.loc99: %FDifferentImplicitParamType = converted %self.ref, <error> [concrete = <error>]
 // CHECK:STDOUT:   %F.call: init bool = call %F.bound(<error>, %b.ref)
 // CHECK:STDOUT:   %.loc180_38.1: bool = value_of_initializer %F.call
 // CHECK:STDOUT:   %.loc180_38.2: bool = converted %F.call, %.loc180_38.1
 // CHECK:STDOUT:   return %.loc180_38.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.18(%self.param: bool, %b.param: bool) -> %FDifferentReturnType;
+// CHECK:STDOUT: fn @F.18(%self.param: bool, %b.param: bool) -> %return.param: %FDifferentReturnType;
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.19(%self.param: bool, %b.param: bool) -> bool {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %self.ref: bool = name_ref self, %self.param
-// CHECK:STDOUT:   %F.bound: <bound method> = bound_method %self.ref, @impl.fcc.%F.decl.loc199_38.1
+// CHECK:STDOUT:   %F.bound: <bound method> = bound_method %self.ref, @impl.fcc.%F.decl.loc196_38.1
 // CHECK:STDOUT:   %b.ref: bool = name_ref b, %b.param
-// CHECK:STDOUT:   %F.call: init <error> = call %F.bound(%self.ref, %b.ref)
+// CHECK:STDOUT:   %.loc196_38.1: ref %FDifferentReturnType = temporary_storage
+// CHECK:STDOUT:   %F.call: init %FDifferentReturnType = call %F.bound(%self.ref, %b.ref) to %.loc196_38.1
+// CHECK:STDOUT:   %.loc196_38.2: bool = converted %F.call, <error> [concrete = <error>]
 // CHECK:STDOUT:   return <error>
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -1363,22 +1308,24 @@ class SelfNestedBadReturnType {
 // CHECK:STDOUT: fn @F.22(%x.param: %tuple.type.9c9) -> %return.param: %array_type.a41 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %x.ref: %tuple.type.9c9 = name_ref x, %x.param
+// CHECK:STDOUT:   %.loc207_41: ref %array_type.a41 = splice_block %return {}
 // CHECK:STDOUT:   %tuple.elem0: %ptr.4cd = tuple_access %x.ref, element0
 // CHECK:STDOUT:   %tuple.elem1: %struct_type.x.y.a89 = tuple_access %x.ref, element1
 // CHECK:STDOUT:   %.loc207_9.1: %SelfNestedBadParam = struct_access %tuple.elem1, element0
 // CHECK:STDOUT:   %.loc207_9.2: %i32 = converted %.loc207_9.1, <error> [concrete = <error>]
-// CHECK:STDOUT:   %F.call: init <error> = call @impl.6a5.%F.decl.loc258_87.1(<error>)
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   %F.call: init %array_type.a41 = call @impl.6a5.%F.decl.loc219_87.1(<error>) to %.loc207_41
+// CHECK:STDOUT:   return %F.call to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.23(%x.param: %tuple.type.eb9) -> %return.param: %array_type.a41;
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @F.24(%x.param: %tuple.type.eb9) -> %array_type.126 {
+// CHECK:STDOUT: fn @F.24(%x.param: %tuple.type.eb9) -> %return.param: %array_type.126 {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   %x.ref: %tuple.type.eb9 = name_ref x, %x.param
-// CHECK:STDOUT:   %.loc294: ref %array_type.a41 = temporary_storage
-// CHECK:STDOUT:   %F.call: init %array_type.a41 = call @impl.bfc.%F.decl.loc294_112.1(<error>) to %.loc294
-// CHECK:STDOUT:   return <error>
+// CHECK:STDOUT:   %.loc235_112.1: ref %array_type.a41 = temporary_storage
+// CHECK:STDOUT:   %F.call: init %array_type.a41 = call @impl.bfc.%F.decl.loc235_112.1(%x.ref) to %.loc235_112.1
+// CHECK:STDOUT:   %.loc235_112.2: %array_type.126 = converted %F.call, <error> [concrete = <error>]
+// CHECK:STDOUT:   return <error> to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @F.1(constants.%Self.826) {}

+ 42 - 0
toolchain/check/testdata/impl/min_prelude/impl_thunk.carbon

@@ -0,0 +1,42 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// INCLUDE-FILE: toolchain/testing/min_prelude/convert.carbon
+// EXTRA-ARGS: --no-dump-sem-ir --custom-core
+//
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/impl/min_prelude/impl_thunk.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/impl/min_prelude/impl_thunk.carbon
+
+// --- convert_in_class.carbon
+
+library "[[@TEST_NAME]]";
+
+interface X(T:! type, U:! type) {
+  fn F(t: T) -> U;
+}
+
+class ConvertsToA {}
+class ConvertsToB {}
+
+// Check that we don't try to define a thunk for `A.B.(as X).F` until we reach
+// the end of `A`. If we tried earlier, we wouldn't find a conversion from
+// `ConvertsToA` to `A` or from `ConvertsToB` to `B`.
+class A {
+  class B {
+    impl as X(ConvertsToA, B) {
+      fn F(a: A) -> ConvertsToB;
+    }
+
+    impl ConvertsToB as Core.ImplicitAs(B) {
+      fn Convert[self: Self]() -> B { return {}; }
+    }
+  }
+
+  impl ConvertsToA as Core.ImplicitAs(A) {
+    fn Convert[self: Self]() -> A { return {}; }
+  }
+}

+ 206 - 1
toolchain/check/testdata/impl/no_prelude/impl_thunk.carbon

@@ -203,6 +203,42 @@ impl C as I {
   fn NoReturn() -> C;
 }
 
+// --- fail_param_type_incomplete.carbon
+
+library "[[@TEST_NAME]]";
+
+interface I(T:! type) {
+  // CHECK:STDERR: fail_param_type_incomplete.carbon:[[@LINE+3]]:8: error: parameter has incomplete type `B` in function definition [IncompleteTypeInFunctionParam]
+  // CHECK:STDERR:   fn F(a: T);
+  // CHECK:STDERR:        ^~~~
+  fn F(a: T);
+}
+
+// CHECK:STDERR: fail_param_type_incomplete.carbon:[[@LINE+3]]:1: note: class was forward declared here [ClassForwardDeclaredHere]
+// CHECK:STDERR: class B;
+// CHECK:STDERR: ^~~~~~~~
+class B;
+
+class C {
+  impl as I(B) {
+    // CHECK:STDERR: fail_param_type_incomplete.carbon:[[@LINE+14]]:5: note: while building thunk calling this function [ThunkCallee]
+    // CHECK:STDERR:     fn F(c: C);
+    // CHECK:STDERR:     ^~~~~~~~~~~
+    // CHECK:STDERR:
+    // CHECK:STDERR: fail_param_type_incomplete.carbon:[[@LINE-14]]:8: error: `Core.ImplicitAs` implicitly referenced here, but package `Core` not found [CoreNotFound]
+    // CHECK:STDERR:   fn F(a: T);
+    // CHECK:STDERR:        ^~~~
+    // CHECK:STDERR: fail_param_type_incomplete.carbon:[[@LINE+7]]:10: note: initializing function parameter [InCallToFunctionParam]
+    // CHECK:STDERR:     fn F(c: C);
+    // CHECK:STDERR:          ^~~~
+    // CHECK:STDERR: fail_param_type_incomplete.carbon:[[@LINE-20]]:3: note: while building thunk to match the signature of this function [ThunkSignature]
+    // CHECK:STDERR:   fn F(a: T);
+    // CHECK:STDERR:   ^~~~~~~~~~~
+    // CHECK:STDERR:
+    fn F(c: C);
+  }
+}
+
 // CHECK:STDOUT: --- no_thunk_param_name_differs.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
@@ -1546,9 +1582,9 @@ impl C as I {
 // CHECK:STDOUT:   %I.facet: %I.type = facet_value %empty_tuple.type, (%I.impl_witness) [concrete]
 // CHECK:STDOUT:   %HasReturn.type.c73908.2: type = fn_type @HasReturn.3 [concrete]
 // CHECK:STDOUT:   %HasReturn.424378.2: %HasReturn.type.c73908.2 = struct_value () [concrete]
-// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
 // CHECK:STDOUT:   %EmptyTupleReturn.type.9ea74f.2: type = fn_type @EmptyTupleReturn.3 [concrete]
 // CHECK:STDOUT:   %EmptyTupleReturn.282179.2: %EmptyTupleReturn.type.9ea74f.2 = struct_value () [concrete]
+// CHECK:STDOUT:   %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
@@ -1800,3 +1836,172 @@ impl C as I {
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @NoReturn.1(constants.%I.facet.be9) {}
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_param_type_incomplete.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic]
+// CHECK:STDOUT:   %pattern_type.98f: type = pattern_type type [concrete]
+// CHECK:STDOUT:   %I.type.dac: type = generic_interface_type @I [concrete]
+// CHECK:STDOUT:   %empty_tuple.type: type = tuple_type () [concrete]
+// CHECK:STDOUT:   %I.generic: %I.type.dac = struct_value () [concrete]
+// CHECK:STDOUT:   %I.type.325: type = facet_type <@I, @I(%T)> [symbolic]
+// CHECK:STDOUT:   %Self.209: %I.type.325 = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %pattern_type.7dc: type = pattern_type %T [symbolic]
+// CHECK:STDOUT:   %F.type.2ae: type = fn_type @F.1, @I(%T) [symbolic]
+// CHECK:STDOUT:   %F.bb2: %F.type.2ae = struct_value () [symbolic]
+// CHECK:STDOUT:   %I.assoc_type.1e5: type = assoc_entity_type @I, @I(%T) [symbolic]
+// CHECK:STDOUT:   %assoc0.8f0: %I.assoc_type.1e5 = assoc_entity element0, @I.%F.decl [symbolic]
+// CHECK:STDOUT:   %B: type = class_type @B [concrete]
+// CHECK:STDOUT:   %C: type = class_type @C [concrete]
+// CHECK:STDOUT:   %I.type.a79: type = facet_type <@I, @I(%B)> [concrete]
+// CHECK:STDOUT:   %Self.fe1: %I.type.a79 = bind_symbolic_name Self, 1 [symbolic]
+// CHECK:STDOUT:   %F.type.7f2: type = fn_type @F.1, @I(%B) [concrete]
+// CHECK:STDOUT:   %F.e0a: %F.type.7f2 = struct_value () [concrete]
+// CHECK:STDOUT:   %I.assoc_type.5ff: type = assoc_entity_type @I, @I(%B) [concrete]
+// CHECK:STDOUT:   %assoc0.470: %I.assoc_type.5ff = assoc_entity element0, @I.%F.decl [concrete]
+// CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness @C.%I.impl_witness_table [concrete]
+// CHECK:STDOUT:   %pattern_type.c48: type = pattern_type %C [concrete]
+// CHECK:STDOUT:   %F.type.17e183.1: type = fn_type @F.2 [concrete]
+// CHECK:STDOUT:   %F.4f1533.1: %F.type.17e183.1 = struct_value () [concrete]
+// CHECK:STDOUT:   %I.facet: %I.type.a79 = facet_value %C, (%I.impl_witness) [concrete]
+// CHECK:STDOUT:   %pattern_type.049: type = pattern_type %B [concrete]
+// CHECK:STDOUT:   %F.type.17e183.2: type = fn_type @F.3 [concrete]
+// CHECK:STDOUT:   %F.4f1533.2: %F.type.17e183.2 = 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:     .I = %I.decl
+// CHECK:STDOUT:     .B = %B.decl
+// CHECK:STDOUT:     .C = %C.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.decl: %I.type.dac = interface_decl @I [concrete = constants.%I.generic] {
+// CHECK:STDOUT:     %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %T.loc4_13.1: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_13.2 (constants.%T)]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %B.decl: type = class_decl @B [concrete = constants.%B] {} {}
+// CHECK:STDOUT:   %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic interface @I(%T.loc4_13.1: type) {
+// CHECK:STDOUT:   %T.loc4_13.2: type = bind_symbolic_name T, 0 [symbolic = %T.loc4_13.2 (constants.%T)]
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %I.type: type = facet_type <@I, @I(%T.loc4_13.2)> [symbolic = %I.type (constants.%I.type.325)]
+// CHECK:STDOUT:   %Self.2: @I.%I.type (%I.type.325) = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.209)]
+// CHECK:STDOUT:   %F.type: type = fn_type @F.1, @I(%T.loc4_13.2) [symbolic = %F.type (constants.%F.type.2ae)]
+// CHECK:STDOUT:   %F: @I.%F.type (%F.type.2ae) = struct_value () [symbolic = %F (constants.%F.bb2)]
+// CHECK:STDOUT:   %I.assoc_type: type = assoc_entity_type @I, @I(%T.loc4_13.2) [symbolic = %I.assoc_type (constants.%I.assoc_type.1e5)]
+// CHECK:STDOUT:   %assoc0.loc8_13.2: @I.%I.assoc_type (%I.assoc_type.1e5) = assoc_entity element0, %F.decl [symbolic = %assoc0.loc8_13.2 (constants.%assoc0.8f0)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   interface {
+// CHECK:STDOUT:     %Self.1: @I.%I.type (%I.type.325) = bind_symbolic_name Self, 1 [symbolic = %Self.2 (constants.%Self.209)]
+// CHECK:STDOUT:     %F.decl: @I.%F.type (%F.type.2ae) = fn_decl @F.1 [symbolic = @I.%F (constants.%F.bb2)] {
+// CHECK:STDOUT:       %a.patt: @F.1.%pattern_type (%pattern_type.7dc) = binding_pattern a [concrete]
+// CHECK:STDOUT:       %a.param_patt: @F.1.%pattern_type (%pattern_type.7dc) = value_param_pattern %a.patt, call_param0 [concrete]
+// CHECK:STDOUT:     } {
+// CHECK:STDOUT:       %a.param: @F.1.%T (%T) = value_param call_param0
+// CHECK:STDOUT:       %T.ref: type = name_ref T, @I.%T.loc4_13.1 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:       %a: @F.1.%T (%T) = bind_name a, %a.param
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %assoc0.loc8_13.1: @I.%I.assoc_type (%I.assoc_type.1e5) = assoc_entity element0, %F.decl [symbolic = %assoc0.loc8_13.2 (constants.%assoc0.8f0)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     .Self = %Self.1
+// CHECK:STDOUT:     .T = <poisoned>
+// CHECK:STDOUT:     .F = %assoc0.loc8_13.1
+// CHECK:STDOUT:     witness = (%F.decl)
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl @impl: %Self.ref as %I.type {
+// CHECK:STDOUT:   %F.decl.loc32_15.1: %F.type.17e183.1 = fn_decl @F.2 [concrete = constants.%F.4f1533.1] {
+// CHECK:STDOUT:     %c.patt: %pattern_type.c48 = binding_pattern c [concrete]
+// CHECK:STDOUT:     %c.param_patt: %pattern_type.c48 = value_param_pattern %c.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %c.param: %C = value_param call_param0
+// CHECK:STDOUT:     %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C]
+// CHECK:STDOUT:     %c: %C = bind_name c, %c.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %F.decl.loc32_15.2: %F.type.17e183.2 = fn_decl @F.3 [concrete = constants.%F.4f1533.2] {
+// CHECK:STDOUT:     %a.patt: %pattern_type.049 = binding_pattern a [concrete]
+// CHECK:STDOUT:     %a.param_patt: %pattern_type.049 = value_param_pattern %a.patt, call_param0 [concrete]
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %a.param: %B = value_param call_param0
+// CHECK:STDOUT:     %a: %B = bind_name a, %a.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .C = <poisoned>
+// CHECK:STDOUT:   .F = %F.decl.loc32_15.1
+// CHECK:STDOUT:   witness = @C.%I.impl_witness
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @B;
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @C {
+// CHECK:STDOUT:   impl_decl @impl [concrete] {} {
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%C [concrete = constants.%C]
+// CHECK:STDOUT:     %I.ref: %I.type.dac = name_ref I, file.%I.decl [concrete = constants.%I.generic]
+// CHECK:STDOUT:     %B.ref: type = name_ref B, file.%B.decl [concrete = constants.%B]
+// CHECK:STDOUT:     %I.type: type = facet_type <@I, @I(constants.%B)> [concrete = constants.%I.type.a79]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %I.impl_witness_table = impl_witness_table (@impl.%F.decl.loc32_15.2), @impl [concrete]
+// CHECK:STDOUT:   %I.impl_witness: <witness> = impl_witness %I.impl_witness_table [concrete = constants.%I.impl_witness]
+// CHECK:STDOUT:   %empty_struct_type: type = struct_type {} [concrete = constants.%empty_struct_type]
+// 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:   .I = <poisoned>
+// CHECK:STDOUT:   .B = <poisoned>
+// CHECK:STDOUT:   .C = <poisoned>
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic fn @F.1(@I.%T.loc4_13.1: type, @I.%Self.1: @I.%I.type (%I.type.325)) {
+// CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %pattern_type: type = pattern_type %T [symbolic = %pattern_type (constants.%pattern_type.7dc)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   fn(%a.param: @F.1.%T (%T));
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.2(%c.param: %C);
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @F.3(%a.param: %B) {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %a.ref: %B = name_ref a, %a.param
+// CHECK:STDOUT:   %.loc8: %C = converted %a.ref, <error> [concrete = <error>]
+// CHECK:STDOUT:   %F.call: init %empty_tuple.type = call @impl.%F.decl.loc32_15.1(<error>)
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(constants.%T) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%T
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%T, constants.%Self.209) {
+// CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.7dc
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @I(constants.%B) {
+// CHECK:STDOUT:   %T.loc4_13.2 => constants.%B
+// CHECK:STDOUT:
+// CHECK:STDOUT: !definition:
+// CHECK:STDOUT:   %I.type => constants.%I.type.a79
+// CHECK:STDOUT:   %Self.2 => constants.%Self.fe1
+// CHECK:STDOUT:   %F.type => constants.%F.type.7f2
+// CHECK:STDOUT:   %F => constants.%F.e0a
+// CHECK:STDOUT:   %I.assoc_type => constants.%I.assoc_type.5ff
+// CHECK:STDOUT:   %assoc0.loc8_13.2 => constants.%assoc0.470
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @F.1(constants.%B, constants.%I.facet) {
+// CHECK:STDOUT:   %T => constants.%B
+// CHECK:STDOUT:   %pattern_type => constants.%pattern_type.049
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 70 - 58
toolchain/check/thunk.cpp

@@ -6,6 +6,7 @@
 
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/call.h"
+#include "toolchain/check/deferred_definition_scope.h"
 #include "toolchain/check/diagnostic_helpers.h"
 #include "toolchain/check/function.h"
 #include "toolchain/check/generic.h"
@@ -216,6 +217,71 @@ static auto CloneFunctionDecl(Context& context, SemIR::LocId loc_id,
   return {function_decl.function_id, decl_id};
 }
 
+static auto HasDeclaredReturnType(Context& context,
+                                  SemIR::FunctionId function_id) -> bool {
+  return context.functions()
+      .Get(function_id)
+      .return_slot_pattern_id.has_value();
+}
+
+auto BuildThunk(Context& context, SemIR::FunctionId signature_id,
+                SemIR::SpecificId signature_specific_id,
+                SemIR::InstId callee_id) -> SemIR::InstId {
+  auto callee = SemIR::GetCalleeFunction(context.sem_ir(), callee_id);
+
+  // Check whether we can use the given function without a thunk.
+  // TODO: For virtual functions, we want different rules for checking `self`.
+  // TODO: This is too strict; for example, we should not compare parameter
+  // names here.
+  if (CheckFunctionTypeMatches(
+          context, context.functions().Get(callee.function_id),
+          context.functions().Get(signature_id), signature_specific_id,
+          /*check_syntax=*/false, /*check_self=*/true, /*diagnose=*/false)) {
+    return callee_id;
+  }
+
+  // From P3763:
+  //   If the function in the interface does not have a return type, the
+  //   program is invalid if the function in the impl specifies a return type.
+  //
+  // Call into the redeclaration checking logic to produce a suitable error.
+  //
+  // TODO: Consider a different rule: always use an explicit return type for the
+  // thunk, and always convert the result of the wrapped call to the return type
+  // of the thunk.
+  if (!HasDeclaredReturnType(context, signature_id) &&
+      HasDeclaredReturnType(context, callee.function_id)) {
+    bool success = CheckFunctionReturnTypeMatches(
+        context, context.functions().Get(callee.function_id),
+        context.functions().Get(signature_id), signature_specific_id);
+    CARBON_CHECK(!success, "Return type unexpectedly matches");
+    return SemIR::ErrorInst::InstId;
+  }
+
+  // Create a scope for the function's parameters and generic parameters.
+  context.scope_stack().PushForDeclName();
+
+  // We can't use the function directly. Build a thunk.
+  // TODO: Check for and diagnose obvious reasons why this will fail, such as
+  // arity mismatch, before trying to build the thunk.
+  auto [function_id, thunk_id] =
+      CloneFunctionDecl(context, SemIR::LocId(callee_id), signature_id,
+                        signature_specific_id, callee.function_id);
+
+  // Register the thunk to be defined when we reach the end of the enclosing
+  // deferred definition scope, for example an `impl` or `class` definition, as
+  // if the thunk's body were written inline in this location.
+  context.deferred_definition_scope_stack().AddPendingThunk({
+      .signature_id = signature_id,
+      .function_id = function_id,
+      .decl_id = thunk_id,
+      .callee_id = callee_id,
+      .scope = context.scope_stack().Suspend(),
+  });
+
+  return thunk_id;
+}
+
 // Build an expression that names the value matched by a pattern.
 static auto BuildPatternRef(Context& context, SemIR::FunctionId function_id,
                             SemIR::InstId pattern_id) -> SemIR::InstId {
@@ -285,13 +351,6 @@ static auto BuildThunkCall(Context& context, SemIR::FunctionId function_id,
   return PerformCall(context, loc_id, callee_id, args);
 }
 
-static auto HasDeclaredReturnType(Context& context,
-                                  SemIR::FunctionId function_id) -> bool {
-  return context.functions()
-      .Get(function_id)
-      .return_slot_pattern_id.has_value();
-}
-
 // Given a declaration of a thunk and the function that it should call, build
 // the thunk body.
 static auto BuildThunkDefinition(Context& context,
@@ -352,60 +411,13 @@ static auto BuildThunkDefinition(Context& context,
   FinishGenericDefinition(context, function.generic_id);
 }
 
-auto BuildThunk(Context& context, SemIR::FunctionId signature_id,
-                SemIR::SpecificId signature_specific_id,
-                SemIR::InstId callee_id) -> SemIR::InstId {
-  auto callee = SemIR::GetCalleeFunction(context.sem_ir(), callee_id);
+auto BuildThunkDefinition(Context& context, PendingThunk&& thunk) -> void {
+  context.scope_stack().Restore(std::move(thunk.scope));
 
-  // Check whether we can use the given function without a thunk.
-  // TODO: For virtual functions, we want different rules for checking `self`.
-  // TODO: This is too strict; for example, we should not compare parameter
-  // names here.
-  if (CheckFunctionTypeMatches(
-          context, context.functions().Get(callee.function_id),
-          context.functions().Get(signature_id), signature_specific_id,
-          /*check_syntax=*/false, /*check_self=*/true, /*diagnose=*/false)) {
-    return callee_id;
-  }
-
-  // From P3763:
-  //   If the function in the interface does not have a return type, the
-  //   program is invalid if the function in the impl specifies a return type.
-  //
-  // Call into the redeclaration checking logic to produce a suitable error.
-  //
-  // TODO: Consider a different rule: always use an explicit return type for the
-  // thunk, and always convert the result of the wrapped call to the return type
-  // of the thunk.
-  if (!HasDeclaredReturnType(context, signature_id) &&
-      HasDeclaredReturnType(context, callee.function_id)) {
-    bool success = CheckFunctionReturnTypeMatches(
-        context, context.functions().Get(callee.function_id),
-        context.functions().Get(signature_id), signature_specific_id);
-    CARBON_CHECK(!success, "Return type unexpectedly matches");
-    return SemIR::ErrorInst::InstId;
-  }
-
-  // Create a scope for the function's parameters and generic parameters.
-  context.scope_stack().PushForDeclName();
-
-  // We can't use the function directly. Build a thunk.
-  // TODO: Check for and diagnose obvious reasons why this will fail, such as
-  // arity mismatch, before trying to build the thunk.
-  auto [function_id, thunk_id] =
-      CloneFunctionDecl(context, SemIR::LocId(callee_id), signature_id,
-                        signature_specific_id, callee.function_id);
-
-  // Define the thunk.
-  // TODO: We should delay doing this until we get to the end of the enclosing
-  // deferred definition scope, if there is one. For example, an `impl` inside a
-  // `class` definition should have its thunks defined at the end of the class,
-  // like they would be if they were defined inline.
-  BuildThunkDefinition(context, signature_id, function_id, thunk_id, callee_id);
+  BuildThunkDefinition(context, thunk.signature_id, thunk.function_id,
+                       thunk.decl_id, thunk.callee_id);
 
   context.scope_stack().Pop();
-
-  return thunk_id;
 }
 
 }  // namespace Carbon::Check

+ 5 - 0
toolchain/check/thunk.h

@@ -6,6 +6,7 @@
 #define CARBON_TOOLCHAIN_CHECK_THUNK_H_
 
 #include "toolchain/check/context.h"
+#include "toolchain/check/deferred_definition_scope.h"
 #include "toolchain/sem_ir/ids.h"
 
 namespace Carbon::Check {
@@ -17,6 +18,10 @@ auto BuildThunk(Context& context, SemIR::FunctionId signature_id,
                 SemIR::SpecificId signature_specific_id,
                 SemIR::InstId callee_id) -> SemIR::InstId;
 
+// Builds the definition for a thunk whose definition was deferred until the end
+// of the enclosing scope.
+auto BuildThunkDefinition(Context& context, PendingThunk&& thunk) -> void;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_THUNK_H_