Prechádzať zdrojové kódy

Add error for virtual member function without self (#5005)

This tripped over a lowering crash when a member function with self was
declared-but-not-defined, so that's why some test cases were updated to
have (empty) function definitions.

I'll follow-up with/look into a fix for the
self-declared-but-not-defined cases separately.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
David Blaikie 1 rok pred
rodič
commit
f97f1a3e11

+ 2 - 2
toolchain/check/function.cpp

@@ -14,7 +14,7 @@ auto CheckFunctionTypeMatches(Context& context,
                               const SemIR::Function& new_function,
                               const SemIR::Function& new_function,
                               const SemIR::Function& prev_function,
                               const SemIR::Function& prev_function,
                               SemIR::SpecificId prev_specific_id,
                               SemIR::SpecificId prev_specific_id,
-                              bool check_syntax) -> bool {
+                              bool check_syntax, bool check_self) -> bool {
   // TODO: When check_syntax is false, the functions should be allowed to have
   // TODO: When check_syntax is false, the functions should be allowed to have
   // different signatures as long as we can synthesize a suitable thunk. i.e.,
   // different signatures as long as we can synthesize a suitable thunk. i.e.,
   // when there's an implicit conversion from the original parameter types to
   // when there's an implicit conversion from the original parameter types to
@@ -23,7 +23,7 @@ auto CheckFunctionTypeMatches(Context& context,
   // Also, build that thunk.
   // Also, build that thunk.
   if (!CheckRedeclParamsMatch(context, DeclParams(new_function),
   if (!CheckRedeclParamsMatch(context, DeclParams(new_function),
                               DeclParams(prev_function), prev_specific_id,
                               DeclParams(prev_function), prev_specific_id,
-                              check_syntax)) {
+                              /*diagnose=*/true, check_syntax, check_self)) {
     return false;
     return false;
   }
   }
 
 

+ 19 - 5
toolchain/check/function.h

@@ -29,11 +29,25 @@ struct SuspendedFunction {
 // Checks that `new_function` has the same parameter types and return type as
 // Checks that `new_function` has the same parameter types and return type as
 // `prev_function`, or if `prev_function_id` is specified, a specific version of
 // `prev_function`, or if `prev_function_id` is specified, a specific version of
 // `prev_function`. Prints a suitable diagnostic and returns false if not.
 // `prev_function`. Prints a suitable diagnostic and returns false if not.
-auto CheckFunctionTypeMatches(
-    Context& context, const SemIR::Function& new_function,
-    const SemIR::Function& prev_function,
-    SemIR::SpecificId prev_specific_id = SemIR::SpecificId::None,
-    bool check_syntax = true) -> bool;
+//
+// `check_syntax` is false if the redeclaration can be called via a thunk with
+// implicit conversions from the original declaration.
+// `check_self` is false if the self declaration does not have to match (for
+// instance in impls of virtual functions).
+auto CheckFunctionTypeMatches(Context& context,
+                              const SemIR::Function& new_function,
+                              const SemIR::Function& prev_function,
+                              SemIR::SpecificId prev_specific_id,
+                              bool check_syntax, bool check_self) -> bool;
+
+inline auto CheckFunctionTypeMatches(Context& context,
+                                     const SemIR::Function& new_function,
+                                     const SemIR::Function& prev_function)
+    -> bool {
+  return CheckFunctionTypeMatches(context, new_function, prev_function,
+                                  SemIR::SpecificId::None,
+                                  /*check_syntax=*/true, /*check_self=*/true);
+}
 
 
 // Checks that the return type of the specified function is complete, issuing an
 // Checks that the return type of the specified function is complete, issuing an
 // error if not. This computes the return slot usage for the function if
 // error if not. This computes the return slot usage for the function if

+ 2 - 1
toolchain/check/handle_class.cpp

@@ -736,7 +736,8 @@ static auto CheckCompleteClassType(Context& context, Parse::NodeId node_id,
             // `SpecificId::None`.
             // `SpecificId::None`.
             CheckFunctionTypeMatches(context, override_fn, fn,
             CheckFunctionTypeMatches(context, override_fn, fn,
                                      SemIR::SpecificId::None,
                                      SemIR::SpecificId::None,
-                                     /*check_syntax=*/false);
+                                     /*check_syntax=*/false,
+                                     /*check_self=*/false);
             fn_decl_id = override_fn_decl_id;
             fn_decl_id = override_fn_decl_id;
           }
           }
         }
         }

+ 5 - 0
toolchain/check/handle_function.cpp

@@ -278,6 +278,11 @@ static auto BuildFunctionDecl(Context& context,
     self_param_id = *i;
     self_param_id = *i;
   }
   }
 
 
+  if (virtual_modifier != SemIR::Function::VirtualModifier::None &&
+      !self_param_id.has_value()) {
+    CARBON_DIAGNOSTIC(VirtualWithoutSelf, Error, "virtual class function");
+    context.emitter().Build(node_id, VirtualWithoutSelf).Emit();
+  }
   // Build the function entity. This will be merged into an existing function if
   // Build the function entity. This will be merged into an existing function if
   // there is one, or otherwise added to the function store.
   // there is one, or otherwise added to the function store.
   auto function_info =
   auto function_info =

+ 2 - 1
toolchain/check/handle_impl.cpp

@@ -254,7 +254,8 @@ static auto MergeImplRedecl(Context& context, SemIR::Impl& new_impl,
   // `impl`. Keep looking for a prior declaration without issuing a diagnostic.
   // `impl`. Keep looking for a prior declaration without issuing a diagnostic.
   if (!CheckRedeclParamsMatch(context, DeclParams(new_impl),
   if (!CheckRedeclParamsMatch(context, DeclParams(new_impl),
                               DeclParams(prev_impl), SemIR::SpecificId::None,
                               DeclParams(prev_impl), SemIR::SpecificId::None,
-                              /*check_syntax=*/true, /*diagnose=*/false)) {
+                              /*diagnose=*/false, /*check_syntax=*/true,
+                              /*check_self=*/true)) {
     // NOLINTNEXTLINE(readability-simplify-boolean-expr)
     // NOLINTNEXTLINE(readability-simplify-boolean-expr)
     return false;
     return false;
   }
   }

+ 2 - 2
toolchain/check/impl.cpp

@@ -72,8 +72,8 @@ static auto CheckAssociatedFunctionImplementation(
   if (!CheckFunctionTypeMatches(
   if (!CheckFunctionTypeMatches(
           context, context.functions().Get(impl_function_decl->function_id),
           context, context.functions().Get(impl_function_decl->function_id),
           context.functions().Get(interface_function_type.function_id),
           context.functions().Get(interface_function_type.function_id),
-          interface_function_specific_id,
-          /*check_syntax=*/false)) {
+          interface_function_specific_id, /*check_syntax=*/false,
+          /*check_self=*/true)) {
     return SemIR::ErrorInst::SingletonInstId;
     return SemIR::ErrorInst::SingletonInstId;
   }
   }
   return impl_decl_id;
   return impl_decl_id;

+ 45 - 33
toolchain/check/merge.cpp

@@ -201,8 +201,8 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param,
                              int32_t param_index,
                              int32_t param_index,
                              SemIR::InstId new_param_pattern_id,
                              SemIR::InstId new_param_pattern_id,
                              SemIR::InstId prev_param_pattern_id,
                              SemIR::InstId prev_param_pattern_id,
-                             SemIR::SpecificId prev_specific_id, bool diagnose)
-    -> bool {
+                             SemIR::SpecificId prev_specific_id, bool diagnose,
+                             bool check_self) -> bool {
   // TODO: Consider differentiating between type and name mistakes. For now,
   // TODO: Consider differentiating between type and name mistakes. For now,
   // taking the simpler approach because I also think we may want to refactor
   // taking the simpler approach because I also think we may want to refactor
   // params.
   // params.
@@ -231,25 +231,6 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param,
     return false;
     return false;
   }
   }
 
 
-  auto prev_param_type_id = SemIR::GetTypeInSpecific(
-      context.sem_ir(), prev_specific_id, prev_param_pattern.type_id());
-  if (!context.types().AreEqualAcrossDeclarations(new_param_pattern.type_id(),
-                                                  prev_param_type_id)) {
-    if (!diagnose) {
-      return false;
-    }
-    CARBON_DIAGNOSTIC(RedeclParamDiffersType, Error,
-                      "type {3} of {0:implicit |}parameter {1} in "
-                      "redeclaration differs from previous parameter type {2}",
-                      BoolAsSelect, int32_t, SemIR::TypeId, SemIR::TypeId);
-    context.emitter()
-        .Build(new_param_pattern_id, RedeclParamDiffersType, is_implicit_param,
-               param_index + 1, prev_param_type_id, new_param_pattern.type_id())
-        .Note(prev_param_pattern_id, RedeclParamPrevious, is_implicit_param)
-        .Emit();
-    return false;
-  }
-
   if (new_param_pattern.Is<SemIR::AddrPattern>()) {
   if (new_param_pattern.Is<SemIR::AddrPattern>()) {
     new_param_pattern = context.insts().Get(
     new_param_pattern = context.insts().Get(
         new_param_pattern.As<SemIR::AddrPattern>().inner_id);
         new_param_pattern.As<SemIR::AddrPattern>().inner_id);
@@ -270,11 +251,40 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param,
     return false;
     return false;
   }
   }
 
 
-  auto new_entity_name = context.entity_names().Get(
-      new_param_pattern.As<SemIR::AnyBindingPattern>().entity_name_id);
-  auto prev_entity_name = context.entity_names().Get(
-      prev_param_pattern.As<SemIR::AnyBindingPattern>().entity_name_id);
-  if (new_entity_name.name_id != prev_entity_name.name_id) {
+  auto new_name_id =
+      context.entity_names()
+          .Get(new_param_pattern.As<SemIR::AnyBindingPattern>().entity_name_id)
+          .name_id;
+  auto prev_name_id =
+      context.entity_names()
+          .Get(prev_param_pattern.As<SemIR::AnyBindingPattern>().entity_name_id)
+          .name_id;
+
+  if (!check_self && new_name_id == SemIR::NameId::SelfValue &&
+      prev_name_id == SemIR::NameId::SelfValue) {
+    return true;
+  }
+
+  auto prev_param_type_id = SemIR::GetTypeInSpecific(
+      context.sem_ir(), prev_specific_id, prev_param_pattern.type_id());
+  if (!context.types().AreEqualAcrossDeclarations(new_param_pattern.type_id(),
+                                                  prev_param_type_id)) {
+    if (!diagnose) {
+      return false;
+    }
+    CARBON_DIAGNOSTIC(RedeclParamDiffersType, Error,
+                      "type {3} of {0:implicit |}parameter {1} in "
+                      "redeclaration differs from previous parameter type {2}",
+                      BoolAsSelect, int32_t, SemIR::TypeId, SemIR::TypeId);
+    context.emitter()
+        .Build(new_param_pattern_id, RedeclParamDiffersType, is_implicit_param,
+               param_index + 1, prev_param_type_id, new_param_pattern.type_id())
+        .Note(prev_param_pattern_id, RedeclParamPrevious, is_implicit_param)
+        .Emit();
+    return false;
+  }
+
+  if (new_name_id != prev_name_id) {
     emit_diagnostic();
     emit_diagnostic();
     return false;
     return false;
   }
   }
@@ -288,8 +298,8 @@ static auto CheckRedeclParams(Context& context, SemIRLoc new_decl_loc,
                               SemIRLoc prev_decl_loc,
                               SemIRLoc prev_decl_loc,
                               SemIR::InstBlockId prev_param_patterns_id,
                               SemIR::InstBlockId prev_param_patterns_id,
                               bool is_implicit_param,
                               bool is_implicit_param,
-                              SemIR::SpecificId prev_specific_id, bool diagnose)
-    -> bool {
+                              SemIR::SpecificId prev_specific_id, bool diagnose,
+                              bool check_self) -> bool {
   // This will often occur for empty params.
   // This will often occur for empty params.
   if (new_param_patterns_id == prev_param_patterns_id) {
   if (new_param_patterns_id == prev_param_patterns_id) {
     return true;
     return true;
@@ -347,7 +357,7 @@ static auto CheckRedeclParams(Context& context, SemIRLoc new_decl_loc,
        llvm::enumerate(new_param_pattern_ids, prev_param_pattern_ids)) {
        llvm::enumerate(new_param_pattern_ids, prev_param_pattern_ids)) {
     if (!CheckRedeclParam(context, is_implicit_param, index,
     if (!CheckRedeclParam(context, is_implicit_param, index,
                           new_param_pattern_id, prev_param_pattern_id,
                           new_param_pattern_id, prev_param_pattern_id,
-                          prev_specific_id, diagnose)) {
+                          prev_specific_id, diagnose, check_self)) {
       return false;
       return false;
     }
     }
   }
   }
@@ -458,8 +468,8 @@ static auto CheckRedeclParamSyntax(Context& context,
 
 
 auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
 auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
                             const DeclParams& prev_entity,
                             const DeclParams& prev_entity,
-                            SemIR::SpecificId prev_specific_id,
-                            bool check_syntax, bool diagnose) -> bool {
+                            SemIR::SpecificId prev_specific_id, bool diagnose,
+                            bool check_syntax, bool check_self) -> bool {
   if (EntityHasParamError(context, new_entity) ||
   if (EntityHasParamError(context, new_entity) ||
       EntityHasParamError(context, prev_entity)) {
       EntityHasParamError(context, prev_entity)) {
     return false;
     return false;
@@ -467,13 +477,15 @@ auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
   if (!CheckRedeclParams(
   if (!CheckRedeclParams(
           context, new_entity.loc, new_entity.implicit_param_patterns_id,
           context, new_entity.loc, new_entity.implicit_param_patterns_id,
           prev_entity.loc, prev_entity.implicit_param_patterns_id,
           prev_entity.loc, prev_entity.implicit_param_patterns_id,
-          /*is_implicit_param=*/true, prev_specific_id, diagnose)) {
+          /*is_implicit_param=*/true, prev_specific_id, diagnose, check_self)) {
     return false;
     return false;
   }
   }
+  // Don't forward `check_self` here because it's extra cost, and `self` is only
+  // allowed in implicit params.
   if (!CheckRedeclParams(context, new_entity.loc, new_entity.param_patterns_id,
   if (!CheckRedeclParams(context, new_entity.loc, new_entity.param_patterns_id,
                          prev_entity.loc, prev_entity.param_patterns_id,
                          prev_entity.loc, prev_entity.param_patterns_id,
                          /*is_implicit_param=*/false, prev_specific_id,
                          /*is_implicit_param=*/false, prev_specific_id,
-                         diagnose)) {
+                         diagnose, /*check_self=*/true)) {
     return false;
     return false;
   }
   }
   if (check_syntax &&
   if (check_syntax &&

+ 12 - 5
toolchain/check/merge.h

@@ -96,11 +96,18 @@ struct DeclParams {
 // Checks that the parameters in a redeclaration of an entity match the
 // Checks that the parameters in a redeclaration of an entity match the
 // parameters in the prior declaration. If not, produces a diagnostic if
 // parameters in the prior declaration. If not, produces a diagnostic if
 // `diagnose` is true, and returns false.
 // `diagnose` is true, and returns false.
-auto CheckRedeclParamsMatch(
-    Context& context, const DeclParams& new_entity,
-    const DeclParams& prev_entity,
-    SemIR::SpecificId prev_specific_id = SemIR::SpecificId::None,
-    bool check_syntax = true, bool diagnose = true) -> bool;
+auto CheckRedeclParamsMatch(Context& context, const DeclParams& new_entity,
+                            const DeclParams& prev_entity,
+                            SemIR::SpecificId prev_specific_id, bool diagnose,
+                            bool check_syntax, bool check_self) -> bool;
+
+inline auto CheckRedeclParamsMatch(Context& context,
+                                   const DeclParams& new_entity,
+                                   const DeclParams& prev_entity) -> bool {
+  return CheckRedeclParamsMatch(context, new_entity, prev_entity,
+                                SemIR::SpecificId::None, /*diagnose=*/true,
+                                /*check_syntax=*/true, /*check_self=*/true);
+}
 
 
 }  // namespace Carbon::Check
 }  // namespace Carbon::Check
 
 

+ 12 - 5
toolchain/check/testdata/class/adapter/fail_adapt_with_base.carbon

@@ -9,13 +9,13 @@
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/adapter/fail_adapt_with_base.carbon
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/adapter/fail_adapt_with_base.carbon
 class Simple {};
 class Simple {};
 base class AdaptWithVirtual {
 base class AdaptWithVirtual {
-  virtual fn F();
+  virtual fn F[self: Self]();
   // CHECK:STDERR: fail_adapt_with_base.carbon:[[@LINE+7]]:3: error: adapter with virtual function [AdaptWithVirtual]
   // CHECK:STDERR: fail_adapt_with_base.carbon:[[@LINE+7]]:3: error: adapter with virtual function [AdaptWithVirtual]
   // CHECK:STDERR:   adapt Simple;
   // CHECK:STDERR:   adapt Simple;
   // CHECK:STDERR:   ^~~~~~~~~~~~~
   // CHECK:STDERR:   ^~~~~~~~~~~~~
   // CHECK:STDERR: fail_adapt_with_base.carbon:[[@LINE-4]]:3: note: first virtual function declaration is here [AdaptWithVirtualHere]
   // CHECK:STDERR: fail_adapt_with_base.carbon:[[@LINE-4]]:3: note: first virtual function declaration is here [AdaptWithVirtualHere]
-  // CHECK:STDERR:   virtual fn F();
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~
+  // CHECK:STDERR:   virtual fn F[self: Self]();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
   adapt Simple;
   adapt Simple;
 }
 }
@@ -58,7 +58,14 @@ base class AdaptWithVirtual {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @AdaptWithVirtual {
 // CHECK:STDOUT: class @AdaptWithVirtual {
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %self.patt: %AdaptWithVirtual = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %AdaptWithVirtual = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %AdaptWithVirtual = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%AdaptWithVirtual [concrete = constants.%AdaptWithVirtual]
+// CHECK:STDOUT:     %self: %AdaptWithVirtual = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Simple.ref: type = name_ref Simple, file.%Simple.decl [concrete = constants.%Simple]
 // CHECK:STDOUT:   %Simple.ref: type = name_ref Simple, file.%Simple.decl [concrete = constants.%Simple]
 // CHECK:STDOUT:   adapt_decl %Simple.ref [concrete]
 // CHECK:STDOUT:   adapt_decl %Simple.ref [concrete]
 // CHECK:STDOUT:   complete_type_witness = <error>
 // CHECK:STDOUT:   complete_type_witness = <error>
@@ -69,5 +76,5 @@ base class AdaptWithVirtual {
 // CHECK:STDOUT:   .Simple = <poisoned>
 // CHECK:STDOUT:   .Simple = <poisoned>
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F();
+// CHECK:STDOUT: virtual fn @F[%self.param_patt: %AdaptWithVirtual]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:

+ 37 - 16
toolchain/check/testdata/class/fail_modifiers.carbon

@@ -83,18 +83,18 @@ abstract protected class WrongOrder { }
 abstract base class AbstractAndBase {}
 abstract base class AbstractAndBase {}
 
 
 abstract class AbstractWithDefinition {
 abstract class AbstractWithDefinition {
-  // CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:19: error: definition of `abstract` function [DefinedAbstractFunction]
-  // CHECK:STDERR:   abstract fn F() {}
-  // CHECK:STDERR:                   ^
+  // CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:31: error: definition of `abstract` function [DefinedAbstractFunction]
+  // CHECK:STDERR:   abstract fn F[self: Self]() {}
+  // CHECK:STDERR:                               ^
   // CHECK:STDERR:
   // CHECK:STDERR:
-  abstract fn F() {}
-  abstract fn G();
+  abstract fn F[self: Self]() {}
+  abstract fn G[self: Self]();
 }
 }
-// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:31: error: definition of `abstract` function [DefinedAbstractFunction]
-// CHECK:STDERR: fn AbstractWithDefinition.G() {
-// CHECK:STDERR:                               ^
+// CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:43: error: definition of `abstract` function [DefinedAbstractFunction]
+// CHECK:STDERR: fn AbstractWithDefinition.G[self: Self]() {
+// CHECK:STDERR:                                           ^
 // CHECK:STDERR:
 // CHECK:STDERR:
-fn AbstractWithDefinition.G() {
+fn AbstractWithDefinition.G[self: Self]() {
 }
 }
 
 
 // CHECK:STDOUT: --- fail_modifiers.carbon
 // CHECK:STDOUT: --- fail_modifiers.carbon
@@ -115,9 +115,9 @@ fn AbstractWithDefinition.G() {
 // CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
 // CHECK:STDOUT:   %F: %F.type = struct_value () [concrete]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
 // CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
 // CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
-// CHECK:STDOUT:   %ptr: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
 // CHECK:STDOUT:   %.9a5: <vtable> = vtable (%F, %G) [concrete]
 // CHECK:STDOUT:   %.9a5: <vtable> = vtable (%F, %G) [concrete]
-// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr} [concrete]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
 // CHECK:STDOUT:   %complete_type.513: <witness> = complete_type_witness %struct_type.vptr [concrete]
 // CHECK:STDOUT:   %complete_type.513: <witness> = complete_type_witness %struct_type.vptr [concrete]
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -151,7 +151,14 @@ fn AbstractWithDefinition.G() {
 // CHECK:STDOUT:   %WrongOrder.decl: type = class_decl @WrongOrder [concrete = constants.%WrongOrder] {} {}
 // CHECK:STDOUT:   %WrongOrder.decl: type = class_decl @WrongOrder [concrete = constants.%WrongOrder] {} {}
 // CHECK:STDOUT:   %AbstractAndBase.decl: type = class_decl @AbstractAndBase [concrete = constants.%AbstractAndBase] {} {}
 // CHECK:STDOUT:   %AbstractAndBase.decl: type = class_decl @AbstractAndBase [concrete = constants.%AbstractAndBase] {} {}
 // CHECK:STDOUT:   %AbstractWithDefinition.decl: type = class_decl @AbstractWithDefinition [concrete = constants.%AbstractWithDefinition] {} {}
 // CHECK:STDOUT:   %AbstractWithDefinition.decl: type = class_decl @AbstractWithDefinition [concrete = constants.%AbstractWithDefinition] {} {}
-// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {}
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %self.patt: %AbstractWithDefinition = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %AbstractWithDefinition = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param.loc97: %AbstractWithDefinition = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref.loc97: type = name_ref Self, constants.%AbstractWithDefinition [concrete = constants.%AbstractWithDefinition]
+// CHECK:STDOUT:     %self.loc97: %AbstractWithDefinition = bind_name self, %self.param.loc97
+// CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @DuplicatePrivate;
 // CHECK:STDOUT: class @DuplicatePrivate;
@@ -195,8 +202,22 @@ fn AbstractWithDefinition.G() {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @AbstractWithDefinition {
 // CHECK:STDOUT: class @AbstractWithDefinition {
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
-// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %self.patt: %AbstractWithDefinition = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %AbstractWithDefinition = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %AbstractWithDefinition = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%AbstractWithDefinition [concrete = constants.%AbstractWithDefinition]
+// CHECK:STDOUT:     %self: %AbstractWithDefinition = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {
+// CHECK:STDOUT:     %self.patt: %AbstractWithDefinition = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %AbstractWithDefinition = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param.loc91: %AbstractWithDefinition = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref.loc91: type = name_ref Self, constants.%AbstractWithDefinition [concrete = constants.%AbstractWithDefinition]
+// CHECK:STDOUT:     %self.loc91: %AbstractWithDefinition = bind_name self, %self.param.loc91
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc92: <vtable> = vtable (%F.decl, %G.decl) [concrete = constants.%.9a5]
 // CHECK:STDOUT:   %.loc92: <vtable> = vtable (%F.decl, %G.decl) [concrete = constants.%.9a5]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -207,12 +228,12 @@ fn AbstractWithDefinition.G() {
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT:   .G = %G.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: abstract fn @F() {
+// CHECK:STDOUT: abstract fn @F[%self.param_patt: %AbstractWithDefinition]() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: abstract fn @G() {
+// CHECK:STDOUT: abstract fn @G[%self.param_patt: %AbstractWithDefinition]() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT:   return
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }

+ 464 - 95
toolchain/check/testdata/class/virtual_modifiers.carbon

@@ -13,13 +13,13 @@
 package Modifiers;
 package Modifiers;
 
 
 base class Base {
 base class Base {
-  virtual fn H();
+  virtual fn H[self: Self]();
 }
 }
 
 
 abstract class Abstract {
 abstract class Abstract {
-  abstract fn J();
+  abstract fn J[self: Self]();
 
 
-  virtual fn K();
+  virtual fn K[self: Self]();
 }
 }
 
 
 // --- override_import.carbon
 // --- override_import.carbon
@@ -30,7 +30,7 @@ import Modifiers;
 
 
 class Derived {
 class Derived {
   extend base: Modifiers.Base;
   extend base: Modifiers.Base;
-  impl fn H();
+  impl fn H[self: Self]();
 }
 }
 
 
 // --- todo_fail_later_base.carbon
 // --- todo_fail_later_base.carbon
@@ -40,7 +40,7 @@ library "[[@TEST_NAME]]";
 import Modifiers;
 import Modifiers;
 
 
 base class Derived {
 base class Derived {
-  virtual fn F();
+  virtual fn F[self: Self]();
   extend base: Modifiers.Base;
   extend base: Modifiers.Base;
 }
 }
 
 
@@ -59,12 +59,12 @@ fn F() {
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
 abstract class A1 {
 abstract class A1 {
-  virtual fn F();
+  virtual fn F[self: Self]();
 }
 }
 
 
 abstract class A2 {
 abstract class A2 {
   extend base: A1;
   extend base: A1;
-  impl fn F();
+  impl fn F[self: Self]();
 }
 }
 
 
 // --- impl_base.carbon
 // --- impl_base.carbon
@@ -72,17 +72,17 @@ abstract class A2 {
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
 base class B1 {
 base class B1 {
-  virtual fn F();
+  virtual fn F[self: Self]();
 }
 }
 
 
 base class B2 {
 base class B2 {
   extend base: B1;
   extend base: B1;
-  impl fn F();
+  impl fn F[self: Self]();
 }
 }
 
 
 class C {
 class C {
   extend base: B2;
   extend base: B2;
-  impl fn F();
+  impl fn F[self: Self]();
 }
 }
 
 
 // --- fail_modifiers.carbon
 // --- fail_modifiers.carbon
@@ -91,10 +91,10 @@ library "[[@TEST_NAME]]";
 
 
 class C {
 class C {
   // CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:3: error: impl without base class [ImplWithoutBase]
   // CHECK:STDERR: fail_modifiers.carbon:[[@LINE+4]]:3: error: impl without base class [ImplWithoutBase]
-  // CHECK:STDERR:   impl fn F();
-  // CHECK:STDERR:   ^~~~~~~~~~~~
+  // CHECK:STDERR:   impl fn F[self: Self]();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
-  impl fn F();
+  impl fn F[self: Self]();
 }
 }
 
 
 // --- init_members.carbon
 // --- init_members.carbon
@@ -105,7 +105,7 @@ base class Base {
   var m1: i32;
   var m1: i32;
   var m2: i32;
   var m2: i32;
 
 
-  virtual fn F();
+  virtual fn F[self: Self]();
 }
 }
 
 
 fn F() {
 fn F() {
@@ -127,7 +127,7 @@ base class Base {
 
 
 class Derived {
 class Derived {
   extend base: Base;
   extend base: Base;
-  impl fn F();
+  impl fn F[self: Self]();
 }
 }
 
 
 // --- abstract_impl.carbon
 // --- abstract_impl.carbon
@@ -135,7 +135,7 @@ class Derived {
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
 abstract class AbstractBase {
 abstract class AbstractBase {
-  abstract fn F();
+  abstract fn F[self: Self]();
 }
 }
 
 
 abstract class AbstractIntermediate {
 abstract class AbstractIntermediate {
@@ -144,7 +144,7 @@ abstract class AbstractIntermediate {
 
 
 class Derived {
 class Derived {
   extend base: AbstractIntermediate;
   extend base: AbstractIntermediate;
-  impl fn F();
+  impl fn F[self: Self]();
 }
 }
 
 
 // --- virtual_impl.carbon
 // --- virtual_impl.carbon
@@ -152,7 +152,7 @@ class Derived {
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
 base class VirtualBase {
 base class VirtualBase {
-  virtual fn F();
+  virtual fn F[self: Self]();
 }
 }
 
 
 base class VirtualIntermediate {
 base class VirtualIntermediate {
@@ -161,7 +161,7 @@ base class VirtualIntermediate {
 
 
 class Derived {
 class Derived {
   extend base: VirtualIntermediate;
   extend base: VirtualIntermediate;
-  impl fn F();
+  impl fn F[self: Self]();
 }
 }
 
 
 // --- fail_impl_mismatch.carbon
 // --- fail_impl_mismatch.carbon
@@ -169,19 +169,19 @@ class Derived {
 library "[[@TEST_NAME]]";
 library "[[@TEST_NAME]]";
 
 
 base class Base {
 base class Base {
-  virtual fn F();
+  virtual fn F[self: Self]();
 }
 }
 
 
 class Derived {
 class Derived {
   extend base: Base;
   extend base: Base;
   // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+7]]:3: error: redeclaration differs because of parameter count of 1 [RedeclParamCountDiffers]
   // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE+7]]:3: error: redeclaration differs because of parameter count of 1 [RedeclParamCountDiffers]
-  // CHECK:STDERR:   impl fn F(v: i32);
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:   impl fn F[self: Self](v: i32);
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE-8]]:3: note: previously declared with parameter count of 0 [RedeclParamCountPrevious]
   // CHECK:STDERR: fail_impl_mismatch.carbon:[[@LINE-8]]:3: note: previously declared with parameter count of 0 [RedeclParamCountPrevious]
-  // CHECK:STDERR:   virtual fn F();
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~
+  // CHECK:STDERR:   virtual fn F[self: Self]();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
-  impl fn F(v: i32);
+  impl fn F[self: Self](v: i32);
 }
 }
 
 
 // --- fail_todo_impl_conversion.carbon
 // --- fail_todo_impl_conversion.carbon
@@ -201,19 +201,19 @@ impl T2 as Core.ImplicitAs(T1) {
 }
 }
 
 
 base class Base {
 base class Base {
-  virtual fn F() -> T1;
+  virtual fn F[self: Self]() -> T1;
 }
 }
 
 
 class Derived {
 class Derived {
   extend base: Base;
   extend base: Base;
   // CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE+7]]:3: error: function redeclaration differs because return type is `T2` [FunctionRedeclReturnTypeDiffers]
   // CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE+7]]:3: error: function redeclaration differs because return type is `T2` [FunctionRedeclReturnTypeDiffers]
-  // CHECK:STDERR:   impl fn F() -> T2;
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:   impl fn F[self: Self]() -> T2;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE-8]]:3: note: previously declared with return type `T1` [FunctionRedeclReturnTypePrevious]
   // CHECK:STDERR: fail_todo_impl_conversion.carbon:[[@LINE-8]]:3: note: previously declared with return type `T1` [FunctionRedeclReturnTypePrevious]
-  // CHECK:STDERR:   virtual fn F() -> T1;
-  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~
+  // CHECK:STDERR:   virtual fn F[self: Self]() -> T1;
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
-  impl fn F() -> T2;
+  impl fn F[self: Self]() -> T2;
 }
 }
 
 
 // --- fail_todo_impl_generic_base.carbon
 // --- fail_todo_impl_generic_base.carbon
@@ -224,19 +224,65 @@ class T1 {
 }
 }
 
 
 base class Base(T:! type) {
 base class Base(T:! type) {
-  virtual fn F(t: T);
+  virtual fn F[self: Self](t: T);
 }
 }
 
 
 class Derived {
 class Derived {
   extend base: Base(T1);
   extend base: Base(T1);
-  // CHECK:STDERR: fail_todo_impl_generic_base.carbon:[[@LINE+7]]:13: error: type `T1` of parameter 1 in redeclaration differs from previous parameter type `T` [RedeclParamDiffersType]
-  // CHECK:STDERR:   impl fn F(t: T1);
-  // CHECK:STDERR:             ^~~~~
-  // CHECK:STDERR: fail_todo_impl_generic_base.carbon:[[@LINE-8]]:16: note: previous declaration's corresponding parameter here [RedeclParamPrevious]
-  // CHECK:STDERR:   virtual fn F(t: T);
-  // CHECK:STDERR:                ^~~~
+  // CHECK:STDERR: fail_todo_impl_generic_base.carbon:[[@LINE+7]]:25: error: type `T1` of parameter 1 in redeclaration differs from previous parameter type `T` [RedeclParamDiffersType]
+  // CHECK:STDERR:   impl fn F[self: Self](t: T1);
+  // CHECK:STDERR:                         ^~~~~
+  // CHECK:STDERR: fail_todo_impl_generic_base.carbon:[[@LINE-8]]:28: note: previous declaration's corresponding parameter here [RedeclParamPrevious]
+  // CHECK:STDERR:   virtual fn F[self: Self](t: T);
+  // CHECK:STDERR:                            ^~~~
   // CHECK:STDERR:
   // CHECK:STDERR:
-  impl fn F(t: T1);
+  impl fn F[self: Self](t: T1);
+}
+
+// --- fail_virtual_without_self.carbon
+
+library "[[@TEST_NAME]]";
+
+abstract class T1 {
+  // CHECK:STDERR: fail_virtual_without_self.carbon:[[@LINE+4]]:3: error: virtual class function [VirtualWithoutSelf]
+  // CHECK:STDERR:   virtual fn F();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  virtual fn F();
+  // CHECK:STDERR: fail_virtual_without_self.carbon:[[@LINE+4]]:3: error: virtual class function [VirtualWithoutSelf]
+  // CHECK:STDERR:   abstract fn G();
+  // CHECK:STDERR:   ^~~~~~~~~~~~~~~~
+  // CHECK:STDERR:
+  abstract fn G();
+}
+
+class T2 {
+  extend base: T1;
+  // CHECK:STDERR: fail_virtual_without_self.carbon:[[@LINE+4]]:3: error: virtual class function [VirtualWithoutSelf]
+  // CHECK:STDERR:   impl fn F();
+  // CHECK:STDERR:   ^~~~~~~~~~~~
+  // CHECK:STDERR:
+  impl fn F();
+}
+
+// --- fail_addr_self_mismatch.carbon
+
+library "[[@TEST_NAME]]";
+
+base class T1 {
+  virtual fn F1[self: Self*]();
+}
+
+class T2 {
+  extend base: T1;
+  // CHECK:STDERR: fail_addr_self_mismatch.carbon:[[@LINE+7]]:14: error: redeclaration differs at implicit parameter 1 [RedeclParamDiffers]
+  // CHECK:STDERR:   impl fn F1[addr self: Self*]();
+  // CHECK:STDERR:              ^~~~~~~~~~~~~~~~
+  // CHECK:STDERR: fail_addr_self_mismatch.carbon:[[@LINE-8]]:17: note: previous declaration's corresponding implicit parameter here [RedeclParamPrevious]
+  // CHECK:STDERR:   virtual fn F1[self: Self*]();
+  // CHECK:STDERR:                 ^~~~~~~~~~~
+  // CHECK:STDERR:
+  impl fn F1[addr self: Self*]();
 }
 }
 
 
 // CHECK:STDOUT: --- modifiers.carbon
 // CHECK:STDOUT: --- modifiers.carbon
@@ -276,7 +322,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT: class @Base {
-// CHECK:STDOUT:   %H.decl: %H.type = fn_decl @H [concrete = constants.%H] {} {}
+// CHECK:STDOUT:   %H.decl: %H.type = fn_decl @H [concrete = constants.%H] {
+// CHECK:STDOUT:     %self.patt: %Base = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Base = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Base = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Base [concrete = constants.%Base]
+// CHECK:STDOUT:     %self: %Base = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%H.decl) [concrete = constants.%.c3d]
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%H.decl) [concrete = constants.%.c3d]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -287,8 +340,22 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Abstract {
 // CHECK:STDOUT: class @Abstract {
-// CHECK:STDOUT:   %J.decl: %J.type = fn_decl @J [concrete = constants.%J] {} {}
-// CHECK:STDOUT:   %K.decl: %K.type = fn_decl @K [concrete = constants.%K] {} {}
+// CHECK:STDOUT:   %J.decl: %J.type = fn_decl @J [concrete = constants.%J] {
+// CHECK:STDOUT:     %self.patt: %Abstract = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Abstract = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Abstract = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Abstract [concrete = constants.%Abstract]
+// CHECK:STDOUT:     %self: %Abstract = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %K.decl: %K.type = fn_decl @K [concrete = constants.%K] {
+// CHECK:STDOUT:     %self.patt: %Abstract = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Abstract = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Abstract = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Abstract [concrete = constants.%Abstract]
+// CHECK:STDOUT:     %self: %Abstract = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc12: <vtable> = vtable (%J.decl, %K.decl) [concrete = constants.%.2b2]
 // CHECK:STDOUT:   %.loc12: <vtable> = vtable (%J.decl, %K.decl) [concrete = constants.%.2b2]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -299,11 +366,11 @@ class Derived {
 // CHECK:STDOUT:   .K = %K.decl
 // CHECK:STDOUT:   .K = %K.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @H();
+// CHECK:STDOUT: virtual fn @H[%self.param_patt: %Base]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: abstract fn @J();
+// CHECK:STDOUT: abstract fn @J[%self.param_patt: %Abstract]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @K();
+// CHECK:STDOUT: virtual fn @K[%self.param_patt: %Abstract]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- override_import.carbon
 // CHECK:STDOUT: --- override_import.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -333,7 +400,7 @@ class Derived {
 // CHECK:STDOUT:   %Modifiers.Base: type = import_ref Modifiers//default, Base, loaded [concrete = constants.%Base]
 // CHECK:STDOUT:   %Modifiers.Base: type = import_ref Modifiers//default, Base, loaded [concrete = constants.%Base]
 // CHECK:STDOUT:   %Modifiers.import_ref.05e: <witness> = import_ref Modifiers//default, loc6_1, loaded [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %Modifiers.import_ref.05e: <witness> = import_ref Modifiers//default, loc6_1, loaded [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %Modifiers.import_ref.1f3 = import_ref Modifiers//default, inst16 [no loc], unloaded
 // CHECK:STDOUT:   %Modifiers.import_ref.1f3 = import_ref Modifiers//default, inst16 [no loc], unloaded
-// CHECK:STDOUT:   %Modifiers.import_ref.2cc = import_ref Modifiers//default, loc5_17, unloaded
+// CHECK:STDOUT:   %Modifiers.import_ref.2cc = import_ref Modifiers//default, loc5_29, unloaded
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {
@@ -351,7 +418,14 @@ class Derived {
 // CHECK:STDOUT:   %Modifiers.ref: <namespace> = name_ref Modifiers, imports.%Modifiers [concrete = imports.%Modifiers]
 // CHECK:STDOUT:   %Modifiers.ref: <namespace> = name_ref Modifiers, imports.%Modifiers [concrete = imports.%Modifiers]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, imports.%Modifiers.Base [concrete = constants.%Base]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, imports.%Modifiers.Base [concrete = constants.%Base]
 // CHECK:STDOUT:   %.loc7: %Derived.elem = base_decl %Base.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc7: %Derived.elem = base_decl %Base.ref, element0 [concrete]
-// CHECK:STDOUT:   %H.decl: %H.type.dba = fn_decl @H.1 [concrete = constants.%H.bce] {} {}
+// CHECK:STDOUT:   %H.decl: %H.type.dba = fn_decl @H.1 [concrete = constants.%H.bce] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc9: <vtable> = vtable (%H.decl) [concrete = constants.%.dce]
 // CHECK:STDOUT:   %.loc9: <vtable> = vtable (%H.decl) [concrete = constants.%.dce]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.0e2]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.0e2]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -372,9 +446,9 @@ class Derived {
 // CHECK:STDOUT:   .H = imports.%Modifiers.import_ref.2cc
 // CHECK:STDOUT:   .H = imports.%Modifiers.import_ref.2cc
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @H.1();
+// CHECK:STDOUT: impl fn @H.1[%self.param_patt: %Derived]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @H.2() [from "modifiers.carbon"];
+// CHECK:STDOUT: fn @H.2[%self.param_patt: %Base]() [from "modifiers.carbon"];
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- todo_fail_later_base.carbon
 // CHECK:STDOUT: --- todo_fail_later_base.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -406,7 +480,7 @@ class Derived {
 // CHECK:STDOUT:   %Modifiers.Base: type = import_ref Modifiers//default, Base, loaded [concrete = constants.%Base]
 // CHECK:STDOUT:   %Modifiers.Base: type = import_ref Modifiers//default, Base, loaded [concrete = constants.%Base]
 // CHECK:STDOUT:   %Modifiers.import_ref.05e: <witness> = import_ref Modifiers//default, loc6_1, loaded [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %Modifiers.import_ref.05e: <witness> = import_ref Modifiers//default, loc6_1, loaded [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %Modifiers.import_ref.1f3 = import_ref Modifiers//default, inst16 [no loc], unloaded
 // CHECK:STDOUT:   %Modifiers.import_ref.1f3 = import_ref Modifiers//default, inst16 [no loc], unloaded
-// CHECK:STDOUT:   %Modifiers.import_ref.2cc = import_ref Modifiers//default, loc5_17, unloaded
+// CHECK:STDOUT:   %Modifiers.import_ref.2cc = import_ref Modifiers//default, loc5_29, unloaded
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {
@@ -421,7 +495,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT: class @Derived {
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %Modifiers.ref: <namespace> = name_ref Modifiers, imports.%Modifiers [concrete = imports.%Modifiers]
 // CHECK:STDOUT:   %Modifiers.ref: <namespace> = name_ref Modifiers, imports.%Modifiers [concrete = imports.%Modifiers]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, imports.%Modifiers.Base [concrete = constants.%Base]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, imports.%Modifiers.Base [concrete = constants.%Base]
 // CHECK:STDOUT:   %.loc8: %Derived.elem = base_decl %Base.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc8: %Derived.elem = base_decl %Base.ref, element0 [concrete]
@@ -445,9 +526,9 @@ class Derived {
 // CHECK:STDOUT:   .H = imports.%Modifiers.import_ref.2cc
 // CHECK:STDOUT:   .H = imports.%Modifiers.import_ref.2cc
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F();
+// CHECK:STDOUT: virtual fn @F[%self.param_patt: %Derived]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: fn @H() [from "modifiers.carbon"];
+// CHECK:STDOUT: fn @H[%self.param_patt: %Base]() [from "modifiers.carbon"];
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- init.carbon
 // CHECK:STDOUT: --- init.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -473,7 +554,7 @@ class Derived {
 // CHECK:STDOUT:   %Modifiers.Base: type = import_ref Modifiers//default, Base, loaded [concrete = constants.%Base]
 // CHECK:STDOUT:   %Modifiers.Base: type = import_ref Modifiers//default, Base, loaded [concrete = constants.%Base]
 // CHECK:STDOUT:   %Modifiers.import_ref.05e: <witness> = import_ref Modifiers//default, loc6_1, loaded [concrete = constants.%complete_type]
 // CHECK:STDOUT:   %Modifiers.import_ref.05e: <witness> = import_ref Modifiers//default, loc6_1, loaded [concrete = constants.%complete_type]
 // CHECK:STDOUT:   %Modifiers.import_ref.1f3 = import_ref Modifiers//default, inst16 [no loc], unloaded
 // CHECK:STDOUT:   %Modifiers.import_ref.1f3 = import_ref Modifiers//default, inst16 [no loc], unloaded
-// CHECK:STDOUT:   %Modifiers.import_ref.2cc = import_ref Modifiers//default, loc5_17, unloaded
+// CHECK:STDOUT:   %Modifiers.import_ref.2cc = import_ref Modifiers//default, loc5_29, unloaded
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT: file {
@@ -555,7 +636,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @A1 {
 // CHECK:STDOUT: class @A1 {
-// CHECK:STDOUT:   %F.decl: %F.type.13a = fn_decl @F.1 [concrete = constants.%F.df5] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.13a = fn_decl @F.1 [concrete = constants.%F.df5] {
+// CHECK:STDOUT:     %self.patt: %A1 = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %A1 = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %A1 = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%A1 [concrete = constants.%A1]
+// CHECK:STDOUT:     %self: %A1 = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.593]
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.593]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -568,7 +656,14 @@ class Derived {
 // CHECK:STDOUT: class @A2 {
 // CHECK:STDOUT: class @A2 {
 // CHECK:STDOUT:   %A1.ref: type = name_ref A1, file.%A1.decl [concrete = constants.%A1]
 // CHECK:STDOUT:   %A1.ref: type = name_ref A1, file.%A1.decl [concrete = constants.%A1]
 // CHECK:STDOUT:   %.loc9: %A2.elem = base_decl %A1.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc9: %A2.elem = base_decl %A1.ref, element0 [concrete]
-// CHECK:STDOUT:   %F.decl: %F.type.4ae = fn_decl @F.2 [concrete = constants.%F.1d5] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.4ae = fn_decl @F.2 [concrete = constants.%F.1d5] {
+// CHECK:STDOUT:     %self.patt: %A2 = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %A2 = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %A2 = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%A2 [concrete = constants.%A2]
+// CHECK:STDOUT:     %self: %A2 = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc11: <vtable> = vtable (%F.decl) [concrete = constants.%.943]
 // CHECK:STDOUT:   %.loc11: <vtable> = vtable (%F.decl) [concrete = constants.%.943]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.a6f]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.a6f]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -581,9 +676,9 @@ class Derived {
 // CHECK:STDOUT:   extend %A1.ref
 // CHECK:STDOUT:   extend %A1.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F.1();
+// CHECK:STDOUT: virtual fn @F.1[%self.param_patt: %A1]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2();
+// CHECK:STDOUT: impl fn @F.2[%self.param_patt: %A2]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- impl_base.carbon
 // CHECK:STDOUT: --- impl_base.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -632,7 +727,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @B1 {
 // CHECK:STDOUT: class @B1 {
-// CHECK:STDOUT:   %F.decl: %F.type.e4c = fn_decl @F.1 [concrete = constants.%F.8f5] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.e4c = fn_decl @F.1 [concrete = constants.%F.8f5] {
+// CHECK:STDOUT:     %self.patt: %B1 = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %B1 = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %B1 = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%B1 [concrete = constants.%B1]
+// CHECK:STDOUT:     %self: %B1 = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.bc5]
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.bc5]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -645,7 +747,14 @@ class Derived {
 // CHECK:STDOUT: class @B2 {
 // CHECK:STDOUT: class @B2 {
 // CHECK:STDOUT:   %B1.ref: type = name_ref B1, file.%B1.decl [concrete = constants.%B1]
 // CHECK:STDOUT:   %B1.ref: type = name_ref B1, file.%B1.decl [concrete = constants.%B1]
 // CHECK:STDOUT:   %.loc9: %B2.elem = base_decl %B1.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc9: %B2.elem = base_decl %B1.ref, element0 [concrete]
-// CHECK:STDOUT:   %F.decl: %F.type.b26 = fn_decl @F.2 [concrete = constants.%F.d48] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.b26 = fn_decl @F.2 [concrete = constants.%F.d48] {
+// CHECK:STDOUT:     %self.patt: %B2 = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %B2 = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %B2 = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%B2 [concrete = constants.%B2]
+// CHECK:STDOUT:     %self: %B2 = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc11: <vtable> = vtable (%F.decl) [concrete = constants.%.579]
 // CHECK:STDOUT:   %.loc11: <vtable> = vtable (%F.decl) [concrete = constants.%.579]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.508 [concrete = constants.%complete_type.5ac]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.508 [concrete = constants.%complete_type.5ac]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -661,7 +770,14 @@ class Derived {
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT:   %B2.ref: type = name_ref B2, file.%B2.decl [concrete = constants.%B2]
 // CHECK:STDOUT:   %B2.ref: type = name_ref B2, file.%B2.decl [concrete = constants.%B2]
 // CHECK:STDOUT:   %.loc14: %C.elem = base_decl %B2.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc14: %C.elem = base_decl %B2.ref, element0 [concrete]
-// CHECK:STDOUT:   %F.decl: %F.type.c29 = fn_decl @F.3 [concrete = constants.%F.437] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.c29 = fn_decl @F.3 [concrete = constants.%F.437] {
+// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %C = value_param runtime_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:   %.loc16: <vtable> = vtable (%F.decl) [concrete = constants.%.5f6]
 // CHECK:STDOUT:   %.loc16: <vtable> = vtable (%F.decl) [concrete = constants.%.5f6]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.421 [concrete = constants.%complete_type.066]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.421 [concrete = constants.%complete_type.066]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -674,11 +790,11 @@ class Derived {
 // CHECK:STDOUT:   extend %B2.ref
 // CHECK:STDOUT:   extend %B2.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F.1();
+// CHECK:STDOUT: virtual fn @F.1[%self.param_patt: %B1]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2();
+// CHECK:STDOUT: impl fn @F.2[%self.param_patt: %B2]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.3();
+// CHECK:STDOUT: impl fn @F.3[%self.param_patt: %C]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_modifiers.carbon
 // CHECK:STDOUT: --- fail_modifiers.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -709,7 +825,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @C {
 // CHECK:STDOUT: class @C {
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %self.patt: %C = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %C = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %C = value_param runtime_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:   %.loc10: <vtable> = vtable () [concrete = constants.%.f2b]
 // CHECK:STDOUT:   %.loc10: <vtable> = vtable () [concrete = constants.%.f2b]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -719,7 +842,7 @@ class Derived {
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F();
+// CHECK:STDOUT: impl fn @F[%self.param_patt: %C]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- init_members.carbon
 // CHECK:STDOUT: --- init_members.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -790,7 +913,14 @@ class Derived {
 // CHECK:STDOUT:     %.loc6_3: %Base.elem = var_pattern %.loc6_9
 // CHECK:STDOUT:     %.loc6_3: %Base.elem = var_pattern %.loc6_9
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.var.loc6: ref %Base.elem = var <none>
 // CHECK:STDOUT:   %.var.loc6: ref %Base.elem = var <none>
-// CHECK:STDOUT:   %F.decl: %F.type.7c6 = fn_decl @F.1 [concrete = constants.%F.d17] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.7c6 = fn_decl @F.1 [concrete = constants.%F.d17] {
+// CHECK:STDOUT:     %self.patt: %Base = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Base = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Base = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Base [concrete = constants.%Base]
+// CHECK:STDOUT:     %self: %Base = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc9: <vtable> = vtable (%F.decl) [concrete = constants.%.5ee]
 // CHECK:STDOUT:   %.loc9: <vtable> = vtable (%F.decl) [concrete = constants.%.5ee]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr.m1.m2 [concrete = constants.%complete_type.cf7]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr.m1.m2 [concrete = constants.%complete_type.cf7]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -802,7 +932,7 @@ class Derived {
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT:   .F = %F.decl
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F.1();
+// CHECK:STDOUT: virtual fn @F.1[%self.param_patt: %Base]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: fn @F.2() {
 // CHECK:STDOUT: !entry:
 // CHECK:STDOUT: !entry:
@@ -933,7 +1063,14 @@ class Derived {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [concrete = constants.%Base]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [concrete = constants.%Base]
 // CHECK:STDOUT:   %.loc8: %Derived.elem = base_decl %Base.ref, element1 [concrete]
 // CHECK:STDOUT:   %.loc8: %Derived.elem = base_decl %Base.ref, element1 [concrete]
-// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc10: <vtable> = vtable () [concrete = constants.%.f2b]
 // CHECK:STDOUT:   %.loc10: <vtable> = vtable () [concrete = constants.%.f2b]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr.base [concrete = constants.%complete_type.336]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr.base [concrete = constants.%complete_type.336]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -946,7 +1083,7 @@ class Derived {
 // CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F();
+// CHECK:STDOUT: impl fn @F[%self.param_patt: %Derived]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- abstract_impl.carbon
 // CHECK:STDOUT: --- abstract_impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -992,7 +1129,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @AbstractBase {
 // CHECK:STDOUT: class @AbstractBase {
-// CHECK:STDOUT:   %F.decl: %F.type.85b = fn_decl @F.1 [concrete = constants.%F.6e9] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.85b = fn_decl @F.1 [concrete = constants.%F.6e9] {
+// CHECK:STDOUT:     %self.patt: %AbstractBase = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %AbstractBase = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %AbstractBase = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%AbstractBase [concrete = constants.%AbstractBase]
+// CHECK:STDOUT:     %self: %AbstractBase = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.6ec]
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.6ec]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -1019,7 +1163,14 @@ class Derived {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %AbstractIntermediate.ref: type = name_ref AbstractIntermediate, file.%AbstractIntermediate.decl [concrete = constants.%AbstractIntermediate]
 // CHECK:STDOUT:   %AbstractIntermediate.ref: type = name_ref AbstractIntermediate, file.%AbstractIntermediate.decl [concrete = constants.%AbstractIntermediate]
 // CHECK:STDOUT:   %.loc13: %Derived.elem = base_decl %AbstractIntermediate.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc13: %Derived.elem = base_decl %AbstractIntermediate.ref, element0 [concrete]
-// CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc15: <vtable> = vtable (%F.decl) [concrete = constants.%.88d]
 // CHECK:STDOUT:   %.loc15: <vtable> = vtable (%F.decl) [concrete = constants.%.88d]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.da5 [concrete = constants.%complete_type.f8c]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.da5 [concrete = constants.%complete_type.f8c]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -1032,9 +1183,9 @@ class Derived {
 // CHECK:STDOUT:   extend %AbstractIntermediate.ref
 // CHECK:STDOUT:   extend %AbstractIntermediate.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: abstract fn @F.1();
+// CHECK:STDOUT: abstract fn @F.1[%self.param_patt: %AbstractBase]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2();
+// CHECK:STDOUT: impl fn @F.2[%self.param_patt: %Derived]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- virtual_impl.carbon
 // CHECK:STDOUT: --- virtual_impl.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -1080,7 +1231,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @VirtualBase {
 // CHECK:STDOUT: class @VirtualBase {
-// CHECK:STDOUT:   %F.decl: %F.type.e62 = fn_decl @F.1 [concrete = constants.%F.3e7] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.e62 = fn_decl @F.1 [concrete = constants.%F.3e7] {
+// CHECK:STDOUT:     %self.patt: %VirtualBase = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %VirtualBase = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %VirtualBase = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%VirtualBase [concrete = constants.%VirtualBase]
+// CHECK:STDOUT:     %self: %VirtualBase = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.def]
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.def]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -1107,7 +1265,14 @@ class Derived {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT: class @Derived {
 // CHECK:STDOUT:   %VirtualIntermediate.ref: type = name_ref VirtualIntermediate, file.%VirtualIntermediate.decl [concrete = constants.%VirtualIntermediate]
 // CHECK:STDOUT:   %VirtualIntermediate.ref: type = name_ref VirtualIntermediate, file.%VirtualIntermediate.decl [concrete = constants.%VirtualIntermediate]
 // CHECK:STDOUT:   %.loc13: %Derived.elem = base_decl %VirtualIntermediate.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc13: %Derived.elem = base_decl %VirtualIntermediate.ref, element0 [concrete]
-// CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc15: <vtable> = vtable (%F.decl) [concrete = constants.%.88d]
 // CHECK:STDOUT:   %.loc15: <vtable> = vtable (%F.decl) [concrete = constants.%.88d]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.43c [concrete = constants.%complete_type.fa6]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base.43c [concrete = constants.%complete_type.fa6]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -1120,9 +1285,9 @@ class Derived {
 // CHECK:STDOUT:   extend %VirtualIntermediate.ref
 // CHECK:STDOUT:   extend %VirtualIntermediate.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F.1();
+// CHECK:STDOUT: virtual fn @F.1[%self.param_patt: %VirtualBase]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2();
+// CHECK:STDOUT: impl fn @F.2[%self.param_patt: %Derived]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_impl_mismatch.carbon
 // CHECK:STDOUT: --- fail_impl_mismatch.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -1165,7 +1330,14 @@ class Derived {
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT: class @Base {
-// CHECK:STDOUT:   %F.decl: %F.type.7c6 = fn_decl @F.1 [concrete = constants.%F.d17] {} {}
+// CHECK:STDOUT:   %F.decl: %F.type.7c6 = fn_decl @F.1 [concrete = constants.%F.d17] {
+// CHECK:STDOUT:     %self.patt: %Base = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Base = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %Base = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Base [concrete = constants.%Base]
+// CHECK:STDOUT:     %self: %Base = bind_name self, %self.param
+// CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.5ee]
 // CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F.decl) [concrete = constants.%.5ee]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
 // CHECK:STDOUT:   complete_type_witness = %complete_type
 // CHECK:STDOUT:   complete_type_witness = %complete_type
@@ -1179,10 +1351,15 @@ class Derived {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [concrete = constants.%Base]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [concrete = constants.%Base]
 // CHECK:STDOUT:   %.loc9: %Derived.elem = base_decl %Base.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc9: %Derived.elem = base_decl %Base.ref, element0 [concrete]
 // CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
 // CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
 // CHECK:STDOUT:     %v.patt: %i32 = binding_pattern v
 // CHECK:STDOUT:     %v.patt: %i32 = binding_pattern v
-// CHECK:STDOUT:     %v.param_patt: %i32 = value_param_pattern %v.patt, runtime_param0
+// CHECK:STDOUT:     %v.param_patt: %i32 = value_param_pattern %v.patt, runtime_param1
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %v.param: %i32 = value_param runtime_param0
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:     %v.param: %i32 = value_param runtime_param1
 // CHECK:STDOUT:     %.loc17: type = splice_block %i32 [concrete = constants.%i32] {
 // CHECK:STDOUT:     %.loc17: type = splice_block %i32 [concrete = constants.%i32] {
 // CHECK:STDOUT:       %int_32: Core.IntLiteral = int_value 32 [concrete = constants.%int_32]
 // 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:       %i32: type = class_type @Int, @Int(constants.%int_32) [concrete = constants.%i32]
@@ -1201,9 +1378,9 @@ class Derived {
 // CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT:   extend %Base.ref
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F.1();
+// CHECK:STDOUT: virtual fn @F.1[%self.param_patt: %Base]();
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2(%v.param_patt: %i32);
+// CHECK:STDOUT: impl fn @F.2[%self.param_patt: %Derived](%v.param_patt: %i32);
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_impl_conversion.carbon
 // CHECK:STDOUT: --- fail_todo_impl_conversion.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -1306,11 +1483,16 @@ class Derived {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT: class @Base {
 // CHECK:STDOUT:   %F.decl: %F.type.7c6 = fn_decl @F.1 [concrete = constants.%F.d17] {
 // CHECK:STDOUT:   %F.decl: %F.type.7c6 = fn_decl @F.1 [concrete = constants.%F.d17] {
+// CHECK:STDOUT:     %self.patt: %Base = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Base = value_param_pattern %self.patt, runtime_param0
 // CHECK:STDOUT:     %return.patt: %T1 = return_slot_pattern
 // CHECK:STDOUT:     %return.patt: %T1 = return_slot_pattern
-// CHECK:STDOUT:     %return.param_patt: %T1 = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:     %return.param_patt: %T1 = out_param_pattern %return.patt, runtime_param1
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %T1.ref: type = name_ref T1, file.%T1.decl [concrete = constants.%T1]
 // CHECK:STDOUT:     %T1.ref: type = name_ref T1, file.%T1.decl [concrete = constants.%T1]
-// CHECK:STDOUT:     %return.param: ref %T1 = out_param runtime_param0
+// CHECK:STDOUT:     %self.param: %Base = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Base [concrete = constants.%Base]
+// CHECK:STDOUT:     %self: %Base = bind_name self, %self.param
+// CHECK:STDOUT:     %return.param: ref %T1 = out_param runtime_param1
 // CHECK:STDOUT:     %return: ref %T1 = return_slot %return.param
 // CHECK:STDOUT:     %return: ref %T1 = return_slot %return.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc18: <vtable> = vtable (%F.decl) [concrete = constants.%.5ee]
 // CHECK:STDOUT:   %.loc18: <vtable> = vtable (%F.decl) [concrete = constants.%.5ee]
@@ -1328,11 +1510,16 @@ class Derived {
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [concrete = constants.%Base]
 // CHECK:STDOUT:   %Base.ref: type = name_ref Base, file.%Base.decl [concrete = constants.%Base]
 // CHECK:STDOUT:   %.loc21: %Derived.elem = base_decl %Base.ref, element0 [concrete]
 // CHECK:STDOUT:   %.loc21: %Derived.elem = base_decl %Base.ref, element0 [concrete]
 // CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
 // CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
 // CHECK:STDOUT:     %return.patt: %T2 = return_slot_pattern
 // CHECK:STDOUT:     %return.patt: %T2 = return_slot_pattern
-// CHECK:STDOUT:     %return.param_patt: %T2 = out_param_pattern %return.patt, runtime_param0
+// CHECK:STDOUT:     %return.param_patt: %T2 = out_param_pattern %return.patt, runtime_param1
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:     %T2.ref: type = name_ref T2, file.%T2.decl [concrete = constants.%T2]
 // CHECK:STDOUT:     %T2.ref: type = name_ref T2, file.%T2.decl [concrete = constants.%T2]
-// CHECK:STDOUT:     %return.param: ref %T2 = out_param runtime_param0
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:     %return.param: ref %T2 = out_param runtime_param1
 // CHECK:STDOUT:     %return: ref %T2 = return_slot %return.param
 // CHECK:STDOUT:     %return: ref %T2 = return_slot %return.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   %.loc30: <vtable> = vtable (%F.decl) [concrete = constants.%.88d]
 // CHECK:STDOUT:   %.loc30: <vtable> = vtable (%F.decl) [concrete = constants.%.88d]
@@ -1356,9 +1543,9 @@ class Derived {
 // CHECK:STDOUT:   return %.loc12_14 to %return
 // CHECK:STDOUT:   return %.loc12_14 to %return
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: virtual fn @F.1() -> %T1;
+// CHECK:STDOUT: virtual fn @F.1[%self.param_patt: %Base]() -> %T1;
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2() -> %T2;
+// CHECK:STDOUT: impl fn @F.2[%self.param_patt: %Derived]() -> %T2;
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_todo_impl_generic_base.carbon
 // CHECK:STDOUT: --- fail_todo_impl_generic_base.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -1435,10 +1622,18 @@ class Derived {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT:   class {
 // CHECK:STDOUT:   class {
 // CHECK:STDOUT:     %F.decl: @Base.%F.type (%F.type.f17) = fn_decl @F.1 [symbolic = @Base.%F (constants.%F.e26)] {
 // CHECK:STDOUT:     %F.decl: @Base.%F.type (%F.type.f17) = fn_decl @F.1 [symbolic = @Base.%F (constants.%F.e26)] {
+// CHECK:STDOUT:       %self.patt: @F.1.%Base (%Base.370) = binding_pattern self
+// CHECK:STDOUT:       %self.param_patt: @F.1.%Base (%Base.370) = value_param_pattern %self.patt, runtime_param0
 // CHECK:STDOUT:       %t.patt: @F.1.%T (%T) = binding_pattern t
 // CHECK:STDOUT:       %t.patt: @F.1.%T (%T) = binding_pattern t
-// CHECK:STDOUT:       %t.param_patt: @F.1.%T (%T) = value_param_pattern %t.patt, runtime_param0
+// CHECK:STDOUT:       %t.param_patt: @F.1.%T (%T) = value_param_pattern %t.patt, runtime_param1
 // CHECK:STDOUT:     } {
 // CHECK:STDOUT:     } {
-// CHECK:STDOUT:       %t.param: @F.1.%T (%T) = value_param runtime_param0
+// CHECK:STDOUT:       %self.param: @F.1.%Base (%Base.370) = value_param runtime_param0
+// CHECK:STDOUT:       %.loc8_22.1: type = splice_block %Self.ref [symbolic = %Base (constants.%Base.370)] {
+// CHECK:STDOUT:         %.loc8_22.2: type = specific_constant constants.%Base.370, @Base(constants.%T) [symbolic = %Base (constants.%Base.370)]
+// CHECK:STDOUT:         %Self.ref: type = name_ref Self, %.loc8_22.2 [symbolic = %Base (constants.%Base.370)]
+// CHECK:STDOUT:       }
+// CHECK:STDOUT:       %self: @F.1.%Base (%Base.370) = bind_name self, %self.param
+// CHECK:STDOUT:       %t.param: @F.1.%T (%T) = value_param runtime_param1
 // CHECK:STDOUT:       %T.ref: type = name_ref T, @Base.%T.loc7_17.1 [symbolic = %T (constants.%T)]
 // CHECK:STDOUT:       %T.ref: type = name_ref T, @Base.%T.loc7_17.1 [symbolic = %T (constants.%T)]
 // CHECK:STDOUT:       %t: @F.1.%T (%T) = bind_name t, %t.param
 // CHECK:STDOUT:       %t: @F.1.%T (%T) = bind_name t, %t.param
 // CHECK:STDOUT:     }
 // CHECK:STDOUT:     }
@@ -1460,10 +1655,15 @@ class Derived {
 // CHECK:STDOUT:   %Base: type = class_type @Base, @Base(constants.%T1) [concrete = constants.%Base.ea5]
 // CHECK:STDOUT:   %Base: type = class_type @Base, @Base(constants.%T1) [concrete = constants.%Base.ea5]
 // CHECK:STDOUT:   %.loc12: %Derived.elem = base_decl %Base, element0 [concrete]
 // CHECK:STDOUT:   %.loc12: %Derived.elem = base_decl %Base, element0 [concrete]
 // CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
 // CHECK:STDOUT:   %F.decl: %F.type.5da = fn_decl @F.2 [concrete = constants.%F.fa3] {
+// CHECK:STDOUT:     %self.patt: %Derived = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %Derived = value_param_pattern %self.patt, runtime_param0
 // CHECK:STDOUT:     %t.patt: %T1 = binding_pattern t
 // CHECK:STDOUT:     %t.patt: %T1 = binding_pattern t
-// CHECK:STDOUT:     %t.param_patt: %T1 = value_param_pattern %t.patt, runtime_param0
+// CHECK:STDOUT:     %t.param_patt: %T1 = value_param_pattern %t.patt, runtime_param1
 // CHECK:STDOUT:   } {
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %t.param: %T1 = value_param runtime_param0
+// CHECK:STDOUT:     %self.param: %Derived = value_param runtime_param0
+// CHECK:STDOUT:     %Self.ref: type = name_ref Self, constants.%Derived [concrete = constants.%Derived]
+// CHECK:STDOUT:     %self: %Derived = bind_name self, %self.param
+// CHECK:STDOUT:     %t.param: %T1 = value_param runtime_param1
 // CHECK:STDOUT:     %T1.ref: type = name_ref T1, file.%T1.decl [concrete = constants.%T1]
 // CHECK:STDOUT:     %T1.ref: type = name_ref T1, file.%T1.decl [concrete = constants.%T1]
 // CHECK:STDOUT:     %t: %T1 = bind_name t, %t.param
 // CHECK:STDOUT:     %t: %T1 = bind_name t, %t.param
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   }
@@ -1482,11 +1682,12 @@ class Derived {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: generic virtual fn @F.1(@Base.%T.loc7_17.1: type) {
 // CHECK:STDOUT: generic virtual fn @F.1(@Base.%T.loc7_17.1: type) {
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
 // CHECK:STDOUT:   %T: type = bind_symbolic_name T, 0 [symbolic = %T (constants.%T)]
+// CHECK:STDOUT:   %Base: type = class_type @Base, @Base(%T) [symbolic = %Base (constants.%Base.370)]
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT:   virtual fn(%t.param_patt: @F.1.%T (%T));
+// CHECK:STDOUT:   virtual fn[%self.param_patt: @F.1.%Base (%Base.370)](%t.param_patt: @F.1.%T (%T));
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: impl fn @F.2(%t.param_patt: %T1);
+// CHECK:STDOUT: impl fn @F.2[%self.param_patt: %Derived](%t.param_patt: %T1);
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Base(constants.%T) {
 // CHECK:STDOUT: specific @Base(constants.%T) {
 // CHECK:STDOUT:   %T.loc7_17.2 => constants.%T
 // CHECK:STDOUT:   %T.loc7_17.2 => constants.%T
@@ -1495,8 +1696,11 @@ class Derived {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @F.1(constants.%T) {
 // CHECK:STDOUT: specific @F.1(constants.%T) {
 // CHECK:STDOUT:   %T => constants.%T
 // CHECK:STDOUT:   %T => constants.%T
+// CHECK:STDOUT:   %Base => constants.%Base.370
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @Base(@F.1.%T) {}
+// CHECK:STDOUT:
 // CHECK:STDOUT: specific @Base(%T.loc7_17.2) {}
 // CHECK:STDOUT: specific @Base(%T.loc7_17.2) {}
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: specific @Base(constants.%T1) {
 // CHECK:STDOUT: specific @Base(constants.%T1) {
@@ -1509,3 +1713,168 @@ class Derived {
 // CHECK:STDOUT:   %.loc9_1.2 => constants.%.611
 // CHECK:STDOUT:   %.loc9_1.2 => constants.%.611
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_virtual_without_self.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T1: type = class_type @T1 [concrete]
+// CHECK:STDOUT:   %F.type.ba7: type = fn_type @F.1 [concrete]
+// CHECK:STDOUT:   %F.1a5: %F.type.ba7 = struct_value () [concrete]
+// CHECK:STDOUT:   %G.type: type = fn_type @G [concrete]
+// CHECK:STDOUT:   %G: %G.type = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %.e62: <vtable> = vtable (%F.1a5, %G) [concrete]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
+// CHECK:STDOUT:   %complete_type.513: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT:   %T2: type = class_type @T2 [concrete]
+// CHECK:STDOUT:   %T2.elem: type = unbound_element_type %T2, %T1 [concrete]
+// CHECK:STDOUT:   %F.type.834: type = fn_type @F.2 [concrete]
+// CHECK:STDOUT:   %F.a48: %F.type.834 = struct_value () [concrete]
+// CHECK:STDOUT:   %.025: <vtable> = vtable (%F.a48, %G) [concrete]
+// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %T1} [concrete]
+// CHECK:STDOUT:   %complete_type.e14: <witness> = complete_type_witness %struct_type.base [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .T1 = %T1.decl
+// CHECK:STDOUT:     .T2 = %T2.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %T1.decl: type = class_decl @T1 [concrete = constants.%T1] {} {}
+// CHECK:STDOUT:   %T2.decl: type = class_decl @T2 [concrete = constants.%T2] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @T1 {
+// CHECK:STDOUT:   %F.decl: %F.type.ba7 = fn_decl @F.1 [concrete = constants.%F.1a5] {} {}
+// CHECK:STDOUT:   %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {}
+// CHECK:STDOUT:   %.loc15: <vtable> = vtable (%F.decl, %G.decl) [concrete = constants.%.e62]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%T1
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT:   .G = %G.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @T2 {
+// CHECK:STDOUT:   %T1.ref: type = name_ref T1, file.%T1.decl [concrete = constants.%T1]
+// CHECK:STDOUT:   %.loc18: %T2.elem = base_decl %T1.ref, element0 [concrete]
+// CHECK:STDOUT:   %F.decl: %F.type.834 = fn_decl @F.2 [concrete = constants.%F.a48] {} {}
+// CHECK:STDOUT:   %.loc24: <vtable> = vtable (%F.decl, constants.%G) [concrete = constants.%.025]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.e14]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%T2
+// CHECK:STDOUT:   .T1 = <poisoned>
+// CHECK:STDOUT:   .base = %.loc18
+// CHECK:STDOUT:   .F = %F.decl
+// CHECK:STDOUT:   extend %T1.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: virtual fn @F.1();
+// CHECK:STDOUT:
+// CHECK:STDOUT: abstract fn @G();
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl fn @F.2();
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_addr_self_mismatch.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %T1: type = class_type @T1 [concrete]
+// CHECK:STDOUT:   %ptr.87b: type = ptr_type %T1 [concrete]
+// CHECK:STDOUT:   %F1.type.b96: type = fn_type @F1.1 [concrete]
+// CHECK:STDOUT:   %F1.765: %F1.type.b96 = struct_value () [concrete]
+// CHECK:STDOUT:   %ptr.454: type = ptr_type <vtable> [concrete]
+// CHECK:STDOUT:   %.278: <vtable> = vtable (%F1.765) [concrete]
+// CHECK:STDOUT:   %struct_type.vptr: type = struct_type {.<vptr>: %ptr.454} [concrete]
+// CHECK:STDOUT:   %complete_type.513: <witness> = complete_type_witness %struct_type.vptr [concrete]
+// CHECK:STDOUT:   %T2: type = class_type @T2 [concrete]
+// CHECK:STDOUT:   %T2.elem: type = unbound_element_type %T2, %T1 [concrete]
+// CHECK:STDOUT:   %ptr.63e: type = ptr_type %T2 [concrete]
+// CHECK:STDOUT:   %F1.type.b0d: type = fn_type @F1.2 [concrete]
+// CHECK:STDOUT:   %F1.0ce: %F1.type.b0d = struct_value () [concrete]
+// CHECK:STDOUT:   %.20a: <vtable> = vtable (%F1.0ce) [concrete]
+// CHECK:STDOUT:   %struct_type.base: type = struct_type {.base: %T1} [concrete]
+// CHECK:STDOUT:   %complete_type.e14: <witness> = complete_type_witness %struct_type.base [concrete]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: imports {
+// CHECK:STDOUT:   %Core: <namespace> = namespace file.%Core.import, [concrete] {
+// CHECK:STDOUT:     import Core//prelude
+// CHECK:STDOUT:     import Core//prelude/...
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [concrete] {
+// CHECK:STDOUT:     .Core = imports.%Core
+// CHECK:STDOUT:     .T1 = %T1.decl
+// CHECK:STDOUT:     .T2 = %T2.decl
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Core.import = import Core
+// CHECK:STDOUT:   %T1.decl: type = class_decl @T1 [concrete = constants.%T1] {} {}
+// CHECK:STDOUT:   %T2.decl: type = class_decl @T2 [concrete = constants.%T2] {} {}
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @T1 {
+// CHECK:STDOUT:   %F1.decl: %F1.type.b96 = fn_decl @F1.1 [concrete = constants.%F1.765] {
+// CHECK:STDOUT:     %self.patt: %ptr.87b = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %ptr.87b = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %ptr.87b = value_param runtime_param0
+// CHECK:STDOUT:     %.loc5: type = splice_block %ptr [concrete = constants.%ptr.87b] {
+// CHECK:STDOUT:       %Self.ref: type = name_ref Self, constants.%T1 [concrete = constants.%T1]
+// CHECK:STDOUT:       %ptr: type = ptr_type %T1 [concrete = constants.%ptr.87b]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %self: %ptr.87b = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc6: <vtable> = vtable (%F1.decl) [concrete = constants.%.278]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.vptr [concrete = constants.%complete_type.513]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%T1
+// CHECK:STDOUT:   .F1 = %F1.decl
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: class @T2 {
+// CHECK:STDOUT:   %T1.ref: type = name_ref T1, file.%T1.decl [concrete = constants.%T1]
+// CHECK:STDOUT:   %.loc9: %T2.elem = base_decl %T1.ref, element0 [concrete]
+// CHECK:STDOUT:   %F1.decl: %F1.type.b0d = fn_decl @F1.2 [concrete = constants.%F1.0ce] {
+// CHECK:STDOUT:     %self.patt: %ptr.63e = binding_pattern self
+// CHECK:STDOUT:     %self.param_patt: %ptr.63e = value_param_pattern %self.patt, runtime_param0
+// CHECK:STDOUT:     %.loc17_14: auto = addr_pattern %self.param_patt
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %self.param: %ptr.63e = value_param runtime_param0
+// CHECK:STDOUT:     %.loc17_29: type = splice_block %ptr [concrete = constants.%ptr.63e] {
+// CHECK:STDOUT:       %Self.ref: type = name_ref Self, constants.%T2 [concrete = constants.%T2]
+// CHECK:STDOUT:       %ptr: type = ptr_type %T2 [concrete = constants.%ptr.63e]
+// CHECK:STDOUT:     }
+// CHECK:STDOUT:     %self: %ptr.63e = bind_name self, %self.param
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %.loc18: <vtable> = vtable (%F1.decl) [concrete = constants.%.20a]
+// CHECK:STDOUT:   %complete_type: <witness> = complete_type_witness %struct_type.base [concrete = constants.%complete_type.e14]
+// CHECK:STDOUT:   complete_type_witness = %complete_type
+// CHECK:STDOUT:
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = constants.%T2
+// CHECK:STDOUT:   .T1 = <poisoned>
+// CHECK:STDOUT:   .base = %.loc9
+// CHECK:STDOUT:   .F1 = %F1.decl
+// CHECK:STDOUT:   extend %T1.ref
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: virtual fn @F1.1[%self.param_patt: %ptr.87b]();
+// CHECK:STDOUT:
+// CHECK:STDOUT: impl fn @F1.2[addr %self.param_patt: %ptr.63e]();
+// CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -256,6 +256,7 @@ CARBON_DIAGNOSTIC_KIND(ClassSpecificDeclPrevious)
 CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition)
 CARBON_DIAGNOSTIC_KIND(ClassIncompleteWithinDefinition)
 CARBON_DIAGNOSTIC_KIND(ExpectedSymbolicBindingInFieldDecl)
 CARBON_DIAGNOSTIC_KIND(ExpectedSymbolicBindingInFieldDecl)
 CARBON_DIAGNOSTIC_KIND(ImplWithoutBase)
 CARBON_DIAGNOSTIC_KIND(ImplWithoutBase)
+CARBON_DIAGNOSTIC_KIND(VirtualWithoutSelf)
 
 
 // Deduction.
 // Deduction.
 CARBON_DIAGNOSTIC_KIND(DeductionIncomplete)
 CARBON_DIAGNOSTIC_KIND(DeductionIncomplete)

+ 45 - 28
toolchain/lower/testdata/class/virtual.carbon

@@ -17,12 +17,12 @@ base class Base {
 
 
 base class Intermediate {
 base class Intermediate {
   extend base: Base;
   extend base: Base;
-  virtual fn Fn();
+  virtual fn Fn[self: Self]() { }
 }
 }
 
 
 class Derived {
 class Derived {
   extend base: Intermediate;
   extend base: Intermediate;
-  impl fn Fn();
+  impl fn Fn[self: Self]() { }
 }
 }
 
 
 // --- create.carbon
 // --- create.carbon
@@ -47,7 +47,7 @@ package MemberInit;
 
 
 base class Base {
 base class Base {
   var m: i32;
   var m: i32;
-  virtual fn Fn();
+  virtual fn Fn[self: Self]() { }
 }
 }
 
 
 fn Fn() {
 fn Fn() {
@@ -62,9 +62,15 @@ fn Fn() {
 // CHECK:STDOUT: ; ModuleID = 'classes.carbon'
 // CHECK:STDOUT: ; ModuleID = 'classes.carbon'
 // CHECK:STDOUT: source_filename = "classes.carbon"
 // CHECK:STDOUT: source_filename = "classes.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes()
+// CHECK:STDOUT: define void @_CFn.Intermediate.Classes(ptr %self) !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !7
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CFn.Derived.Classes()
+// CHECK:STDOUT: define void @_CFn.Derived.Classes(ptr %self) !dbg !8 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !9
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!2}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!2}
@@ -73,6 +79,12 @@ fn Fn() {
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !3 = !DIFile(filename: "classes.carbon", directory: "")
 // CHECK:STDOUT: !3 = !DIFile(filename: "classes.carbon", directory: "")
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Fn", linkageName: "_CFn.Intermediate.Classes", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
+// CHECK:STDOUT: !6 = !{}
+// CHECK:STDOUT: !7 = !DILocation(line: 9, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "Fn", linkageName: "_CFn.Derived.Classes", scope: null, file: !3, line: 14, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 14, column: 3, scope: !8)
 // CHECK:STDOUT: ; ModuleID = 'create.carbon'
 // CHECK:STDOUT: ; ModuleID = 'create.carbon'
 // CHECK:STDOUT: source_filename = "create.carbon"
 // CHECK:STDOUT: source_filename = "create.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT:
@@ -89,11 +101,11 @@ fn Fn() {
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @_CUse.Create(ptr %v) !dbg !9 {
 // CHECK:STDOUT: define void @_CUse.Create(ptr %v) !dbg !9 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   call void @_CFn.Intermediate.Classes(), !dbg !10
+// CHECK:STDOUT:   call void @_CFn.Intermediate.Classes(ptr %v), !dbg !10
 // CHECK:STDOUT:   ret void, !dbg !11
 // CHECK:STDOUT:   ret void, !dbg !11
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes()
+// CHECK:STDOUT: declare void @_CFn.Intermediate.Classes(ptr)
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
 // CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
 // CHECK:STDOUT: declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0
@@ -121,23 +133,26 @@ fn Fn() {
 // CHECK:STDOUT: ; ModuleID = 'member_init.carbon'
 // CHECK:STDOUT: ; ModuleID = 'member_init.carbon'
 // CHECK:STDOUT: source_filename = "member_init.carbon"
 // CHECK:STDOUT: source_filename = "member_init.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: declare void @_CFn.Base.MemberInit()
+// CHECK:STDOUT: define void @_CFn.Base.MemberInit(ptr %self) !dbg !4 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   ret void, !dbg !7
+// CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
-// CHECK:STDOUT: define void @_CFn.MemberInit() !dbg !4 {
+// CHECK:STDOUT: define void @_CFn.MemberInit() !dbg !8 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT: entry:
-// CHECK:STDOUT:   %i.var = alloca i32, align 4, !dbg !7
-// CHECK:STDOUT:   %v.var = alloca { ptr, i32 }, align 8, !dbg !7
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 4, ptr %i.var), !dbg !7
-// CHECK:STDOUT:   store i32 3, ptr %i.var, align 4, !dbg !7
-// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 16, ptr %v.var), !dbg !7
-// CHECK:STDOUT:   %.loc12_24.2.vptr = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 0, !dbg !8
-// CHECK:STDOUT:   store ptr null, ptr %.loc12_24.2.vptr, align 8, !dbg !8
-// CHECK:STDOUT:   %.loc12_23 = load i32, ptr %i.var, align 4, !dbg !9
-// CHECK:STDOUT:   %.loc12_24.5.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !8
-// CHECK:STDOUT:   store i32 %.loc12_23, ptr %.loc12_24.5.m, align 4, !dbg !8
-// CHECK:STDOUT:   %.loc15_4.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !10
-// CHECK:STDOUT:   store i32 5, ptr %.loc15_4.m, align 4, !dbg !10
-// CHECK:STDOUT:   ret void, !dbg !11
+// CHECK:STDOUT:   %i.var = alloca i32, align 4, !dbg !9
+// CHECK:STDOUT:   %v.var = alloca { ptr, i32 }, align 8, !dbg !9
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 4, ptr %i.var), !dbg !9
+// CHECK:STDOUT:   store i32 3, ptr %i.var, align 4, !dbg !9
+// CHECK:STDOUT:   call void @llvm.lifetime.start.p0(i64 16, ptr %v.var), !dbg !9
+// CHECK:STDOUT:   %.loc12_24.2.vptr = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 0, !dbg !10
+// CHECK:STDOUT:   store ptr null, ptr %.loc12_24.2.vptr, align 8, !dbg !10
+// CHECK:STDOUT:   %.loc12_23 = load i32, ptr %i.var, align 4, !dbg !11
+// CHECK:STDOUT:   %.loc12_24.5.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !10
+// CHECK:STDOUT:   store i32 %.loc12_23, ptr %.loc12_24.5.m, align 4, !dbg !10
+// CHECK:STDOUT:   %.loc15_4.m = getelementptr inbounds nuw { ptr, i32 }, ptr %v.var, i32 0, i32 1, !dbg !12
+// CHECK:STDOUT:   store i32 5, ptr %.loc15_4.m, align 4, !dbg !12
+// CHECK:STDOUT:   ret void, !dbg !13
 // CHECK:STDOUT: }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT:
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
 // CHECK:STDOUT: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
@@ -155,11 +170,13 @@ fn Fn() {
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
 // CHECK:STDOUT: !3 = !DIFile(filename: "member_init.carbon", directory: "")
 // CHECK:STDOUT: !3 = !DIFile(filename: "member_init.carbon", directory: "")
-// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Fn", linkageName: "_CFn.MemberInit", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "Fn", linkageName: "_CFn.Base.MemberInit", scope: null, file: !3, line: 6, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{}
 // CHECK:STDOUT: !6 = !{}
-// CHECK:STDOUT: !7 = !DILocation(line: 10, column: 3, scope: !4)
-// CHECK:STDOUT: !8 = !DILocation(line: 12, column: 17, scope: !4)
-// CHECK:STDOUT: !9 = !DILocation(line: 12, column: 23, scope: !4)
-// CHECK:STDOUT: !10 = !DILocation(line: 15, column: 3, scope: !4)
-// CHECK:STDOUT: !11 = !DILocation(line: 9, column: 1, scope: !4)
+// CHECK:STDOUT: !7 = !DILocation(line: 6, column: 3, scope: !4)
+// CHECK:STDOUT: !8 = distinct !DISubprogram(name: "Fn", linkageName: "_CFn.MemberInit", scope: null, file: !3, line: 9, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !9 = !DILocation(line: 10, column: 3, scope: !8)
+// CHECK:STDOUT: !10 = !DILocation(line: 12, column: 17, scope: !8)
+// CHECK:STDOUT: !11 = !DILocation(line: 12, column: 23, scope: !8)
+// CHECK:STDOUT: !12 = !DILocation(line: 15, column: 3, scope: !8)
+// CHECK:STDOUT: !13 = !DILocation(line: 9, column: 1, scope: !8)