Bladeren bron

Factor out an impl declaration helper function (#5851)

In trying to have types implicitly define `impl Self as Destroy`, I'm
wanting to use standard impl declaration support. For example, this
should produce more consistent errors if someone writes code that would
conflict with the generated impl. I'm also concerned, with the
complexity involved, that I'd get something wrong if I tried to write a
divergent implementation.

I'm only factoring out the start of the declaration. Right now the
finishing portion seems much simpler and lower risk to duplicate; I may
also factor it out separately. But either way, I think `StartImplDecl`
here is high churn risk due to its size (`CheckConstraintIsInterface` I
also expect to be used).
Jon Ross-Perkins 9 maanden geleden
bovenliggende
commit
ef748ab36d
3 gewijzigde bestanden met toevoegingen van 404 en 345 verwijderingen
  1. 14 345
      toolchain/check/handle_impl.cpp
  2. 360 0
      toolchain/check/impl.cpp
  3. 30 0
      toolchain/check/impl.h

+ 14 - 345
toolchain/check/handle_impl.cpp

@@ -8,12 +8,10 @@
 #include "toolchain/check/context.h"
 #include "toolchain/check/convert.h"
 #include "toolchain/check/decl_name_stack.h"
-#include "toolchain/check/deduce.h"
 #include "toolchain/check/generic.h"
 #include "toolchain/check/handle.h"
 #include "toolchain/check/impl.h"
 #include "toolchain/check/inst.h"
-#include "toolchain/check/merge.h"
 #include "toolchain/check/modifiers.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/pattern_match.h"
@@ -69,38 +67,11 @@ auto HandleParseNode(Context& context, Parse::TypeImplAsId node_id) -> bool {
   return true;
 }
 
-// If the specified name scope corresponds to a class, returns the corresponding
-// class declaration.
-// TODO: Should this be somewhere more central?
-static auto TryAsClassScope(Context& context, SemIR::NameScopeId scope_id)
-    -> std::optional<SemIR::ClassDecl> {
-  if (!scope_id.has_value()) {
-    return std::nullopt;
-  }
-  auto& scope = context.name_scopes().Get(scope_id);
-  if (!scope.inst_id().has_value()) {
-    return std::nullopt;
-  }
-  return context.insts().TryGetAs<SemIR::ClassDecl>(scope.inst_id());
-}
-
-static auto GetDefaultSelfType(Context& context) -> SemIR::TypeId {
-  auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();
-
-  if (auto class_decl = TryAsClassScope(context, parent_scope_id)) {
-    return context.classes().Get(class_decl->class_id).self_type_id;
-  }
-
-  // TODO: This is also valid in a mixin.
-
-  return SemIR::TypeId::None;
-}
-
 auto HandleParseNode(Context& context, Parse::DefaultSelfImplAsId node_id)
     -> bool {
   auto self_inst_id = SemIR::TypeInstId::None;
 
-  if (auto self_type_id = GetDefaultSelfType(context);
+  if (auto self_type_id = GetImplDefaultSelfType(context);
       self_type_id.has_value()) {
     // Build the implicit access to the enclosing `Self`.
     // TODO: Consider calling `HandleNameAsExpr` to build this implicit `Self`
@@ -126,92 +97,6 @@ auto HandleParseNode(Context& context, Parse::DefaultSelfImplAsId node_id)
   return true;
 }
 
-static auto DiagnoseExtendImplOutsideClass(Context& context,
-                                           Parse::AnyImplDeclId node_id)
-    -> void {
-  CARBON_DIAGNOSTIC(ExtendImplOutsideClass, Error,
-                    "`extend impl` can only be used in a class");
-  context.emitter().Emit(node_id, ExtendImplOutsideClass);
-}
-
-// Process an `extend impl` declaration by extending the impl scope with the
-// `impl`'s scope.
-static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
-                       Parse::AnyImplDeclId node_id, SemIR::ImplId impl_id,
-                       Parse::NodeId self_type_node, SemIR::TypeId self_type_id,
-                       Parse::NodeId params_node,
-                       SemIR::TypeInstId constraint_type_inst_id,
-                       SemIR::TypeId constraint_type_id) -> bool {
-  auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();
-  if (!parent_scope_id.has_value()) {
-    DiagnoseExtendImplOutsideClass(context, node_id);
-    return false;
-  }
-  // TODO: This is also valid in a mixin.
-  if (!TryAsClassScope(context, parent_scope_id)) {
-    DiagnoseExtendImplOutsideClass(context, node_id);
-    return false;
-  }
-
-  auto& parent_scope = context.name_scopes().Get(parent_scope_id);
-
-  if (params_node.has_value()) {
-    CARBON_DIAGNOSTIC(ExtendImplForall, Error,
-                      "cannot `extend` a parameterized `impl`");
-    context.emitter().Emit(extend_node, ExtendImplForall);
-    parent_scope.set_has_error();
-    return false;
-  }
-
-  if (context.parse_tree().node_kind(self_type_node) ==
-      Parse::NodeKind::TypeImplAs) {
-    CARBON_DIAGNOSTIC(ExtendImplSelfAs, Error,
-                      "cannot `extend` an `impl` with an explicit self type");
-    auto diag = context.emitter().Build(extend_node, ExtendImplSelfAs);
-
-    // If the explicit self type is not the default, just bail out.
-    if (self_type_id != GetDefaultSelfType(context)) {
-      diag.Emit();
-      parent_scope.set_has_error();
-      return false;
-    }
-
-    // The explicit self type is the same as the default self type, so suggest
-    // removing it and recover as if it were not present.
-    if (auto self_as =
-            context.parse_tree_and_subtrees().ExtractAs<Parse::TypeImplAs>(
-                self_type_node)) {
-      CARBON_DIAGNOSTIC(ExtendImplSelfAsDefault, Note,
-                        "remove the explicit `Self` type here");
-      diag.Note(self_as->type_expr, ExtendImplSelfAsDefault);
-    }
-    diag.Emit();
-  }
-
-  const auto& impl = context.impls().Get(impl_id);
-  if (impl.witness_id == SemIR::ErrorInst::InstId) {
-    parent_scope.set_has_error();
-  } else {
-    bool is_complete = RequireCompleteType(
-        context, constraint_type_id, SemIR::LocId(constraint_type_inst_id),
-        [&] {
-          CARBON_DIAGNOSTIC(ExtendImplAsIncomplete, Error,
-                            "`extend impl as` incomplete facet type {0}",
-                            InstIdAsType);
-          return context.emitter().Build(impl.latest_decl_id(),
-                                         ExtendImplAsIncomplete,
-                                         constraint_type_inst_id);
-        });
-    if (!is_complete) {
-      parent_scope.set_has_error();
-      return false;
-    }
-  }
-
-  parent_scope.AddExtendedScope(constraint_type_inst_id);
-  return true;
-}
-
 // Pops the parameters of an `impl`, forming a `NameComponent` with no
 // associated name that describes them.
 static auto PopImplIntroducerAndParamsAsNameComponent(
@@ -262,134 +147,6 @@ static auto PopImplIntroducerAndParamsAsNameComponent(
           .pattern_block_id = pattern_block_id};
 }
 
-static auto MergeImplRedecl(Context& context, SemIR::Impl& new_impl,
-                            SemIR::ImplId prev_impl_id) -> bool {
-  auto& prev_impl = context.impls().Get(prev_impl_id);
-
-  // If the parameters aren't the same, then this is not a redeclaration of this
-  // `impl`. Keep looking for a prior declaration without issuing a diagnostic.
-  if (!CheckRedeclParamsMatch(context, DeclParams(new_impl),
-                              DeclParams(prev_impl), SemIR::SpecificId::None,
-                              /*diagnose=*/false, /*check_syntax=*/true,
-                              /*check_self=*/true)) {
-    // NOLINTNEXTLINE(readability-simplify-boolean-expr)
-    return false;
-  }
-  return true;
-}
-
-static auto IsValidImplRedecl(Context& context, SemIR::Impl& new_impl,
-                              SemIR::ImplId prev_impl_id) -> bool {
-  auto& prev_impl = context.impls().Get(prev_impl_id);
-
-  // TODO: Following #3763, disallow redeclarations in different scopes.
-
-  // Following #4672, disallowing defining non-extern declarations in another
-  // file.
-  if (auto import_ref =
-          context.insts().TryGetAs<SemIR::AnyImportRef>(prev_impl.self_id)) {
-    // TODO: Handle extern.
-    CARBON_DIAGNOSTIC(RedeclImportedImpl, Error,
-                      "redeclaration of imported impl");
-    // TODO: Note imported declaration
-    context.emitter().Emit(new_impl.latest_decl_id(), RedeclImportedImpl);
-    return false;
-  }
-
-  if (prev_impl.has_definition_started()) {
-    // Impls aren't merged in order to avoid generic region lookup into a
-    // mismatching table.
-    CARBON_DIAGNOSTIC(ImplRedefinition, Error,
-                      "redefinition of `impl {0} as {1}`", InstIdAsRawType,
-                      InstIdAsRawType);
-    CARBON_DIAGNOSTIC(ImplPreviousDefinition, Note,
-                      "previous definition was here");
-    context.emitter()
-        .Build(new_impl.latest_decl_id(), ImplRedefinition, new_impl.self_id,
-               new_impl.constraint_id)
-        .Note(prev_impl.definition_id, ImplPreviousDefinition)
-        .Emit();
-    return false;
-  }
-
-  // TODO: Only allow redeclaration in a match_first/impl_priority block.
-
-  return true;
-}
-
-// Checks that the constraint specified for the impl is valid and identified.
-// Returns the interface that the impl implements. On error, issues a diagnostic
-// and returns `None`.
-static auto CheckConstraintIsInterface(Context& context,
-                                       SemIR::InstId impl_decl_id,
-                                       SemIR::TypeInstId constraint_id)
-    -> SemIR::SpecificInterface {
-  auto facet_type_id = context.types().GetTypeIdForTypeInstId(constraint_id);
-  if (facet_type_id == SemIR::ErrorInst::TypeId) {
-    return SemIR::SpecificInterface::None;
-  }
-  auto facet_type = context.types().TryGetAs<SemIR::FacetType>(facet_type_id);
-  if (!facet_type) {
-    CARBON_DIAGNOSTIC(ImplAsNonFacetType, Error, "impl as non-facet type {0}",
-                      InstIdAsType);
-    context.emitter().Emit(impl_decl_id, ImplAsNonFacetType, constraint_id);
-    return SemIR::SpecificInterface::None;
-  }
-
-  auto identified_id = RequireIdentifiedFacetType(context, *facet_type);
-  const auto& identified = context.identified_facet_types().Get(identified_id);
-  if (!identified.is_valid_impl_as_target()) {
-    CARBON_DIAGNOSTIC(ImplOfNotOneInterface, Error,
-                      "impl as {0} interfaces, expected 1", int);
-    context.emitter().Emit(impl_decl_id, ImplOfNotOneInterface,
-                           identified.num_interfaces_to_impl());
-    return SemIR::SpecificInterface::None;
-  }
-  return identified.impl_as_target_interface();
-}
-
-static auto DiagnoseUnusedGenericBinding(Context& context,
-                                         Parse::NodeId node_id,
-                                         const NameComponent& name,
-                                         SemIR::ImplId impl_id) -> void {
-  auto deduced_specific_id = SemIR::SpecificId::None;
-
-  auto& impl = context.impls().Get(impl_id);
-  if (!impl.generic_id.has_value() ||
-      impl.witness_id == SemIR::ErrorInst::InstId) {
-    return;
-  }
-
-  // TODO: Deduce has side effects in the semir by generating `Converted`
-  // instructions which we will not use here. We should stop generating
-  // those when deducing for impl lookup, but for now we discard them by
-  // pushing an InstBlock on the stack and dropping it right after.
-  context.inst_block_stack().Push();
-  deduced_specific_id = DeduceImplArguments(
-      context, node_id, impl, context.constant_values().Get(impl.self_id),
-      impl.interface.specific_id);
-  context.inst_block_stack().PopAndDiscard();
-
-  if (deduced_specific_id.has_value()) {
-    // Deduction succeeded, all bindings were used.
-    return;
-  }
-
-  CARBON_DIAGNOSTIC(ImplUnusedBinding, Error,
-                    "`impl` with unused generic binding");
-  // TODO: This location may be incorrect, the binding may be inherited
-  // from an outer declaration. It would be nice to get the particular
-  // binding that was undeducible back from DeduceImplArguments here and
-  // use that.
-  auto loc = name.implicit_params_loc_id.has_value()
-                 ? name.implicit_params_loc_id
-                 : node_id;
-  context.emitter().Emit(loc, ImplUnusedBinding);
-  // Don't try to match the impl at all, save us work and possible future
-  // diagnostics.
-  FillImplWitnessWithErrors(context, context.impls().Get(impl_id));
-}
-
 // Build an ImplDecl describing the signature of an impl. This handles the
 // common logic shared by impl forward declarations and impl definitions.
 static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
@@ -423,9 +180,10 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
   // TODO: Check for an orphan `impl`.
 
   // Add the impl declaration.
-  SemIR::ImplDecl impl_decl = {.impl_id = SemIR::ImplId::None,
-                               .decl_block_id = decl_block_id};
-  auto impl_decl_id = AddPlaceholderInst(context, node_id, impl_decl);
+  auto impl_decl_id =
+      AddPlaceholderInst(context, node_id,
+                         SemIR::ImplDecl{.impl_id = SemIR::ImplId::None,
+                                         .decl_block_id = decl_block_id});
 
   SemIR::Impl impl_info = {name_context.MakeEntityWithParamsBase(
                                name, impl_decl_id,
@@ -435,108 +193,19 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
                             .interface = CheckConstraintIsInterface(
                                 context, impl_decl_id, constraint_type_inst_id),
                             .is_final = is_final}};
-  // Add the impl declaration.
-  auto lookup_bucket_ref = context.impls().GetOrAddLookupBucket(impl_info);
-  // TODO: Detect two impl declarations with the same self type and interface,
-  // and issue an error if they don't match.
-  for (auto prev_impl_id : lookup_bucket_ref) {
-    if (MergeImplRedecl(context, impl_info, prev_impl_id)) {
-      if (IsValidImplRedecl(context, impl_info, prev_impl_id)) {
-        impl_decl.impl_id = prev_impl_id;
-      } else {
-        // IsValidImplRedecl() has issued a diagnostic, avoid generating more
-        // diagnostics for this declaration.
-        impl_info.witness_id = SemIR::ErrorInst::InstId;
-      }
-      break;
-    }
-  }
-
-  // Create a new impl if this isn't a valid redeclaration.
-  if (!impl_decl.impl_id.has_value()) {
-    impl_info.generic_id = BuildGeneric(context, impl_decl_id);
-    if (impl_info.witness_id != SemIR::ErrorInst::InstId) {
-      if (impl_info.interface.interface_id.has_value()) {
-        impl_info.witness_id =
-            ImplWitnessForDeclaration(context, impl_info, is_definition);
-      } else {
-        impl_info.witness_id = SemIR::ErrorInst::InstId;
-        // TODO: We might also want to mark that the name scope for the impl has
-        // an error -- at least once we start making name lookups within the
-        // impl also look into the facet (eg, so you can name associated
-        // constants from within the impl).
-      }
-    }
-    FinishGenericDecl(context, SemIR::LocId(impl_decl_id),
-                      impl_info.generic_id);
-    // From here on, use the `Impl` from the `ImplStore` instead of `impl_info`
-    // in order to make and see any changes to the `Impl`.
-    impl_decl.impl_id = context.impls().Add(impl_info);
-    lookup_bucket_ref.push_back(impl_decl.impl_id);
-
-    AssignImplIdInWitness(context, impl_decl.impl_id, impl_info.witness_id);
-
-    // Looking to see if there are any generic bindings on the `impl`
-    // declaration that are not deducible. If so, and the `impl` does not
-    // actually use all its generic bindings, and will never be matched. This
-    // should be diagnossed to the user.
-    bool has_error_in_implicit_pattern = false;
-    if (name.implicit_param_patterns_id.has_value()) {
-      for (auto inst_id :
-           context.inst_blocks().Get(name.implicit_param_patterns_id)) {
-        if (inst_id == SemIR::ErrorInst::InstId) {
-          has_error_in_implicit_pattern = true;
-          break;
-        }
-      }
-    }
-
-    if (!has_error_in_implicit_pattern) {
-      DiagnoseUnusedGenericBinding(context, node_id, name, impl_decl.impl_id);
-    }
-  } else {
-    auto& stored_impl_info = context.impls().Get(impl_decl.impl_id);
-    FinishGenericRedecl(context, stored_impl_info.generic_id);
-  }
 
-  // Write the impl ID into the ImplDecl.
-  ReplaceInstBeforeConstantUse(context, impl_decl_id, impl_decl);
-
-  // For an `extend impl` declaration, mark the impl as extending this `impl`.
+  std::optional<ExtendImplDecl> extend_impl;
   if (introducer.modifier_set.HasAnyOf(KeywordModifierSet::Extend)) {
-    auto& stored_impl_info = context.impls().Get(impl_decl.impl_id);
-    auto self_type_id =
-        context.types().GetTypeIdForTypeInstId(stored_impl_info.self_id);
-    if (self_type_id != SemIR::ErrorInst::TypeId) {
-      auto extend_node = introducer.modifier_node_id(ModifierOrder::Extend);
-      if (stored_impl_info.generic_id.has_value()) {
-        constraint_type_inst_id = AddTypeInst<SemIR::SpecificConstant>(
-            context, SemIR::LocId(constraint_type_inst_id),
-            {.type_id = SemIR::TypeType::TypeId,
-             .inst_id = constraint_type_inst_id,
-             .specific_id = context.generics().GetSelfSpecific(
-                 stored_impl_info.generic_id)});
-      }
-      if (!ExtendImpl(context, extend_node, node_id, impl_decl.impl_id,
-                      self_type_node, self_type_id, name.implicit_params_loc_id,
-                      constraint_type_inst_id, constraint_type_id)) {
-        // Don't allow the invalid impl to be used.
-        FillImplWitnessWithErrors(context, stored_impl_info);
-      }
-    }
-  }
-
-  // Impl definitions are required in the same file as the declaration. We skip
-  // this requirement if we've already issued an invalid redeclaration error, or
-  // there is an error that would prevent the impl from being legal to define.
-  if (!is_definition) {
-    auto& stored_impl_info = context.impls().Get(impl_decl.impl_id);
-    if (stored_impl_info.witness_id != SemIR::ErrorInst::InstId) {
-      context.definitions_required_by_decl().push_back(impl_decl_id);
-    }
+    extend_impl = ExtendImplDecl{
+        .self_type_node_id = self_type_node,
+        .constraint_type_id = constraint_type_id,
+        .extend_node_id = introducer.modifier_node_id(ModifierOrder::Extend),
+    };
   }
 
-  return {impl_decl.impl_id, impl_decl_id};
+  return StartImplDecl(context, SemIR::LocId(node_id),
+                       name.implicit_params_loc_id, impl_info, is_definition,
+                       extend_impl);
 }
 
 auto HandleParseNode(Context& context, Parse::ImplDeclId node_id) -> bool {

+ 360 - 0
toolchain/check/impl.cpp

@@ -6,6 +6,7 @@
 
 #include "toolchain/base/kind_switch.h"
 #include "toolchain/check/context.h"
+#include "toolchain/check/deduce.h"
 #include "toolchain/check/eval.h"
 #include "toolchain/check/facet_type.h"
 #include "toolchain/check/function.h"
@@ -13,6 +14,7 @@
 #include "toolchain/check/import_ref.h"
 #include "toolchain/check/inst.h"
 #include "toolchain/check/interface.h"
+#include "toolchain/check/merge.h"
 #include "toolchain/check/name_lookup.h"
 #include "toolchain/check/thunk.h"
 #include "toolchain/check/type.h"
@@ -279,4 +281,362 @@ auto IsImplEffectivelyFinal(Context& context, const SemIR::Impl& impl) -> bool {
           context.constant_values().Get(impl.constraint_id).is_concrete());
 }
 
+auto CheckConstraintIsInterface(Context& context, SemIR::InstId impl_decl_id,
+                                SemIR::TypeInstId constraint_id)
+    -> SemIR::SpecificInterface {
+  auto facet_type_id = context.types().GetTypeIdForTypeInstId(constraint_id);
+  if (facet_type_id == SemIR::ErrorInst::TypeId) {
+    return SemIR::SpecificInterface::None;
+  }
+  auto facet_type = context.types().TryGetAs<SemIR::FacetType>(facet_type_id);
+  if (!facet_type) {
+    CARBON_DIAGNOSTIC(ImplAsNonFacetType, Error, "impl as non-facet type {0}",
+                      InstIdAsType);
+    context.emitter().Emit(impl_decl_id, ImplAsNonFacetType, constraint_id);
+    return SemIR::SpecificInterface::None;
+  }
+
+  auto identified_id = RequireIdentifiedFacetType(context, *facet_type);
+  const auto& identified = context.identified_facet_types().Get(identified_id);
+  if (!identified.is_valid_impl_as_target()) {
+    CARBON_DIAGNOSTIC(ImplOfNotOneInterface, Error,
+                      "impl as {0} interfaces, expected 1", int);
+    context.emitter().Emit(impl_decl_id, ImplOfNotOneInterface,
+                           identified.num_interfaces_to_impl());
+    return SemIR::SpecificInterface::None;
+  }
+  return identified.impl_as_target_interface();
+}
+
+// Returns true if impl redeclaration parameters match.
+static auto CheckImplRedeclParamsMatch(Context& context, SemIR::Impl& new_impl,
+                                       SemIR::ImplId prev_impl_id) -> bool {
+  auto& prev_impl = context.impls().Get(prev_impl_id);
+
+  // If the parameters aren't the same, then this is not a redeclaration of this
+  // `impl`. Keep looking for a prior declaration without issuing a diagnostic.
+  if (!CheckRedeclParamsMatch(context, DeclParams(new_impl),
+                              DeclParams(prev_impl), SemIR::SpecificId::None,
+                              /*diagnose=*/false, /*check_syntax=*/true,
+                              /*check_self=*/true)) {
+    // NOLINTNEXTLINE(readability-simplify-boolean-expr)
+    return false;
+  }
+  return true;
+}
+
+// Returns whether an impl can be redeclared. For example, defined impls
+// cannot be redeclared.
+static auto IsValidImplRedecl(Context& context, SemIR::Impl& new_impl,
+                              SemIR::ImplId prev_impl_id) -> bool {
+  auto& prev_impl = context.impls().Get(prev_impl_id);
+
+  // TODO: Following #3763, disallow redeclarations in different scopes.
+
+  // Following #4672, disallowing defining non-extern declarations in another
+  // file.
+  if (auto import_ref =
+          context.insts().TryGetAs<SemIR::AnyImportRef>(prev_impl.self_id)) {
+    // TODO: Handle extern.
+    CARBON_DIAGNOSTIC(RedeclImportedImpl, Error,
+                      "redeclaration of imported impl");
+    // TODO: Note imported declaration
+    context.emitter().Emit(new_impl.latest_decl_id(), RedeclImportedImpl);
+    return false;
+  }
+
+  if (prev_impl.has_definition_started()) {
+    // Impls aren't merged in order to avoid generic region lookup into a
+    // mismatching table.
+    CARBON_DIAGNOSTIC(ImplRedefinition, Error,
+                      "redefinition of `impl {0} as {1}`", InstIdAsRawType,
+                      InstIdAsRawType);
+    CARBON_DIAGNOSTIC(ImplPreviousDefinition, Note,
+                      "previous definition was here");
+    context.emitter()
+        .Build(new_impl.latest_decl_id(), ImplRedefinition, new_impl.self_id,
+               new_impl.constraint_id)
+        .Note(prev_impl.definition_id, ImplPreviousDefinition)
+        .Emit();
+    return false;
+  }
+
+  // TODO: Only allow redeclaration in a match_first/impl_priority block.
+
+  return true;
+}
+
+static auto DiagnoseExtendImplOutsideClass(Context& context,
+                                           SemIR::LocId loc_id) -> void {
+  CARBON_DIAGNOSTIC(ExtendImplOutsideClass, Error,
+                    "`extend impl` can only be used in a class");
+  context.emitter().Emit(loc_id, ExtendImplOutsideClass);
+}
+
+// If the specified name scope corresponds to a class, returns the corresponding
+// class declaration.
+// TODO: Should this be somewhere more central?
+static auto TryAsClassScope(Context& context, SemIR::NameScopeId scope_id)
+    -> std::optional<SemIR::ClassDecl> {
+  if (!scope_id.has_value()) {
+    return std::nullopt;
+  }
+  auto& scope = context.name_scopes().Get(scope_id);
+  if (!scope.inst_id().has_value()) {
+    return std::nullopt;
+  }
+  return context.insts().TryGetAs<SemIR::ClassDecl>(scope.inst_id());
+}
+
+auto GetImplDefaultSelfType(Context& context) -> SemIR::TypeId {
+  auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();
+
+  if (auto class_decl = TryAsClassScope(context, parent_scope_id)) {
+    return context.classes().Get(class_decl->class_id).self_type_id;
+  }
+
+  // TODO: This is also valid in a mixin.
+
+  return SemIR::TypeId::None;
+}
+
+// Process an `extend impl` declaration by extending the impl scope with the
+// `impl`'s scope.
+static auto ExtendImpl(Context& context, Parse::NodeId extend_node,
+                       SemIR::LocId loc_id, SemIR::ImplId impl_id,
+                       Parse::NodeId self_type_node_id,
+                       SemIR::TypeId self_type_id,
+                       SemIR::LocId implicit_params_loc_id,
+                       SemIR::TypeInstId constraint_type_inst_id,
+                       SemIR::TypeId constraint_type_id) -> bool {
+  auto parent_scope_id = context.decl_name_stack().PeekParentScopeId();
+  if (!parent_scope_id.has_value()) {
+    DiagnoseExtendImplOutsideClass(context, loc_id);
+    return false;
+  }
+  // TODO: This is also valid in a mixin.
+  if (!TryAsClassScope(context, parent_scope_id)) {
+    DiagnoseExtendImplOutsideClass(context, loc_id);
+    return false;
+  }
+
+  auto& parent_scope = context.name_scopes().Get(parent_scope_id);
+
+  if (implicit_params_loc_id.has_value()) {
+    CARBON_DIAGNOSTIC(ExtendImplForall, Error,
+                      "cannot `extend` a parameterized `impl`");
+    context.emitter().Emit(extend_node, ExtendImplForall);
+    parent_scope.set_has_error();
+    return false;
+  }
+
+  const auto& impl = context.impls().Get(impl_id);
+
+  if (context.parse_tree().node_kind(self_type_node_id) ==
+      Parse::NodeKind::TypeImplAs) {
+    CARBON_DIAGNOSTIC(ExtendImplSelfAs, Error,
+                      "cannot `extend` an `impl` with an explicit self type");
+    auto diag = context.emitter().Build(extend_node, ExtendImplSelfAs);
+
+    // If the explicit self type is not the default, just bail out.
+    if (self_type_id != GetImplDefaultSelfType(context)) {
+      diag.Emit();
+      parent_scope.set_has_error();
+      return false;
+    }
+
+    // The explicit self type is the same as the default self type, so suggest
+    // removing it and recover as if it were not present.
+    if (auto self_as =
+            context.parse_tree_and_subtrees().ExtractAs<Parse::TypeImplAs>(
+                self_type_node_id)) {
+      CARBON_DIAGNOSTIC(ExtendImplSelfAsDefault, Note,
+                        "remove the explicit `Self` type here");
+      diag.Note(self_as->type_expr, ExtendImplSelfAsDefault);
+    }
+    diag.Emit();
+  }
+
+  if (impl.witness_id == SemIR::ErrorInst::InstId) {
+    parent_scope.set_has_error();
+  } else {
+    bool is_complete = RequireCompleteType(
+        context, constraint_type_id, SemIR::LocId(constraint_type_inst_id),
+        [&] {
+          CARBON_DIAGNOSTIC(ExtendImplAsIncomplete, Error,
+                            "`extend impl as` incomplete facet type {0}",
+                            InstIdAsType);
+          return context.emitter().Build(impl.latest_decl_id(),
+                                         ExtendImplAsIncomplete,
+                                         constraint_type_inst_id);
+        });
+    if (!is_complete) {
+      parent_scope.set_has_error();
+      return false;
+    }
+  }
+
+  parent_scope.AddExtendedScope(constraint_type_inst_id);
+  return true;
+}
+
+// Diagnoses when an impl has an unused binding.
+static auto DiagnoseUnusedGenericBinding(Context& context, SemIR::LocId loc_id,
+                                         SemIR::LocId implicit_params_loc_id,
+                                         SemIR::ImplId impl_id) -> void {
+  auto deduced_specific_id = SemIR::SpecificId::None;
+
+  auto& impl = context.impls().Get(impl_id);
+  if (!impl.generic_id.has_value() ||
+      impl.witness_id == SemIR::ErrorInst::InstId) {
+    return;
+  }
+
+  // TODO: Deduce has side effects in the semir by generating `Converted`
+  // instructions which we will not use here. We should stop generating
+  // those when deducing for impl lookup, but for now we discard them by
+  // pushing an InstBlock on the stack and dropping it right after.
+  context.inst_block_stack().Push();
+  deduced_specific_id = DeduceImplArguments(
+      context, loc_id, impl, context.constant_values().Get(impl.self_id),
+      impl.interface.specific_id);
+  context.inst_block_stack().PopAndDiscard();
+
+  if (deduced_specific_id.has_value()) {
+    // Deduction succeeded, all bindings were used.
+    return;
+  }
+
+  CARBON_DIAGNOSTIC(ImplUnusedBinding, Error,
+                    "`impl` with unused generic binding");
+  // TODO: This location may be incorrect, the binding may be inherited
+  // from an outer declaration. It would be nice to get the particular
+  // binding that was undeducible back from DeduceImplArguments here and
+  // use that.
+  auto diag_loc_id =
+      implicit_params_loc_id.has_value() ? implicit_params_loc_id : loc_id;
+  context.emitter().Emit(diag_loc_id, ImplUnusedBinding);
+  // Don't try to match the impl at all, save us work and possible future
+  // diagnostics.
+  FillImplWitnessWithErrors(context, context.impls().Get(impl_id));
+}
+
+auto StartImplDecl(Context& context, SemIR::LocId loc_id,
+                   SemIR::LocId implicit_params_loc_id, SemIR::Impl impl,
+                   bool is_definition,
+                   std::optional<ExtendImplDecl> extend_impl)
+    -> std::pair<SemIR::ImplId, SemIR::InstId> {
+  auto impl_id = SemIR::ImplId::None;
+
+  // Add the impl declaration.
+  auto lookup_bucket_ref = context.impls().GetOrAddLookupBucket(impl);
+  // TODO: Detect two impl declarations with the same self type and interface,
+  // and issue an error if they don't match.
+  for (auto prev_impl_id : lookup_bucket_ref) {
+    if (CheckImplRedeclParamsMatch(context, impl, prev_impl_id)) {
+      if (IsValidImplRedecl(context, impl, prev_impl_id)) {
+        impl_id = prev_impl_id;
+      } else {
+        // IsValidImplRedecl() has issued a diagnostic, avoid generating more
+        // diagnostics for this declaration.
+        impl.witness_id = SemIR::ErrorInst::InstId;
+      }
+      break;
+    }
+  }
+
+  // Create a new impl if this isn't a valid redeclaration.
+  if (!impl_id.has_value()) {
+    impl.generic_id = BuildGeneric(context, impl.latest_decl_id());
+    if (impl.witness_id != SemIR::ErrorInst::InstId) {
+      if (impl.interface.interface_id.has_value()) {
+        impl.witness_id =
+            ImplWitnessForDeclaration(context, impl, is_definition);
+      } else {
+        impl.witness_id = SemIR::ErrorInst::InstId;
+        // TODO: We might also want to mark that the name scope for the impl has
+        // an error -- at least once we start making name lookups within the
+        // impl also look into the facet (eg, so you can name associated
+        // constants from within the impl).
+      }
+    }
+    FinishGenericDecl(context, SemIR::LocId(impl.latest_decl_id()),
+                      impl.generic_id);
+    // From here on, use the `Impl` from the `ImplStore` instead of `impl`
+    // in order to make and see any changes to the `Impl`.
+    impl_id = context.impls().Add(impl);
+    lookup_bucket_ref.push_back(impl_id);
+
+    AssignImplIdInWitness(context, impl_id, impl.witness_id);
+
+    // Looking to see if there are any generic bindings on the `impl`
+    // declaration that are not deducible. If so, and the `impl` does not
+    // actually use all its generic bindings, and will never be matched. This
+    // should be diagnossed to the user.
+    bool has_error_in_implicit_pattern = false;
+    if (impl.implicit_param_patterns_id.has_value()) {
+      for (auto inst_id :
+           context.inst_blocks().Get(impl.implicit_param_patterns_id)) {
+        if (inst_id == SemIR::ErrorInst::InstId) {
+          has_error_in_implicit_pattern = true;
+          break;
+        }
+      }
+    }
+
+    if (!has_error_in_implicit_pattern) {
+      DiagnoseUnusedGenericBinding(context, loc_id, implicit_params_loc_id,
+                                   impl_id);
+    }
+  } else {
+    auto& stored_impl = context.impls().Get(impl_id);
+    FinishGenericRedecl(context, stored_impl.generic_id);
+  }
+
+  // Write the impl ID into the ImplDecl.
+  auto impl_decl =
+      context.insts().GetAs<SemIR::ImplDecl>(impl.first_owning_decl_id);
+  CARBON_CHECK(!impl_decl.impl_id.has_value());
+  impl_decl.impl_id = impl_id;
+  ReplaceInstBeforeConstantUse(context, impl.first_owning_decl_id, impl_decl);
+
+  // For an `extend impl` declaration, mark the impl as extending this `impl`.
+  if (extend_impl) {
+    auto& stored_impl_info = context.impls().Get(impl_decl.impl_id);
+    auto self_type_id =
+        context.types().GetTypeIdForTypeInstId(stored_impl_info.self_id);
+    if (self_type_id != SemIR::ErrorInst::TypeId) {
+      auto constraint_id = impl.constraint_id;
+      if (stored_impl_info.generic_id.has_value()) {
+        constraint_id = AddTypeInst<SemIR::SpecificConstant>(
+            context, SemIR::LocId(constraint_id),
+            {.type_id = SemIR::TypeType::TypeId,
+             .inst_id = constraint_id,
+             .specific_id = context.generics().GetSelfSpecific(
+                 stored_impl_info.generic_id)});
+      }
+      if (!ExtendImpl(context, extend_impl->extend_node_id, loc_id,
+                      impl_decl.impl_id, extend_impl->self_type_node_id,
+                      self_type_id, implicit_params_loc_id, constraint_id,
+                      extend_impl->constraint_type_id)) {
+        // Don't allow the invalid impl to be used.
+        FillImplWitnessWithErrors(context, stored_impl_info);
+      }
+    }
+  }
+
+  // Impl definitions are required in the same file as the declaration. We skip
+  // this requirement if we've already issued an invalid redeclaration error, or
+  // there is an error that would prevent the impl from being legal to define.
+  if (!is_definition) {
+    auto& stored_impl = context.impls().Get(impl_id);
+    if (stored_impl.witness_id != SemIR::ErrorInst::InstId) {
+      context.definitions_required_by_decl().push_back(
+          stored_impl.latest_decl_id());
+    }
+  }
+
+  return {impl_id, impl.latest_decl_id()};
+}
+
 }  // namespace Carbon::Check

+ 30 - 0
toolchain/check/impl.h

@@ -35,6 +35,36 @@ auto AssignImplIdInWitness(Context& context, SemIR::ImplId impl_id,
 // being concrete.
 auto IsImplEffectivelyFinal(Context& context, const SemIR::Impl& impl) -> bool;
 
+// Checks that the constraint specified for the impl is valid and identified.
+// Returns the interface that the impl implements. On error, issues a diagnostic
+// and returns `None`.
+auto CheckConstraintIsInterface(Context& context, SemIR::InstId impl_decl_id,
+                                SemIR::TypeInstId constraint_id)
+    -> SemIR::SpecificInterface;
+
+// Returns the implicit `Self` type for an `impl` when it's in a `class`
+// declaration.
+auto GetImplDefaultSelfType(Context& context) -> SemIR::TypeId;
+
+// For `StartImplDecl`, additional details for an `extend impl` declaration.
+struct ExtendImplDecl {
+  Parse::NodeId self_type_node_id;
+  SemIR::TypeId constraint_type_id;
+  Parse::NodeId extend_node_id;
+};
+
+// Starts an impl declaration. The caller is responsible for ensuring a generic
+// declaration has been started. This returns the produced `ImplId` and
+// `ImplDecl`'s `InstId`.
+//
+// The `impl` should be constructed with a placeholder `ImplDecl` which this
+// will add the `ImplId` to.
+auto StartImplDecl(Context& context, SemIR::LocId loc_id,
+                   SemIR::LocId implicit_params_loc_id, SemIR::Impl impl,
+                   bool is_definition,
+                   std::optional<ExtendImplDecl> extend_impl)
+    -> std::pair<SemIR::ImplId, SemIR::InstId>;
+
 }  // namespace Carbon::Check
 
 #endif  // CARBON_TOOLCHAIN_CHECK_IMPL_H_