diff --git a/toolchain/check/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 505bea2b2b2e..4249ce30bf3a 100644 --- a/toolchain/check/handle_impl.cpp +++ b/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 { - 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(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( - 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(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(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 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( - 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); - } - } + 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), + }; } - // 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); - } - } - - 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 { diff --git a/toolchain/check/impl.cpp b/toolchain/check/impl.cpp index f6deef94d20b..e5130f98a5a2 100644 --- a/toolchain/check/impl.cpp +++ b/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(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(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 { + 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(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( + 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 extend_impl) + -> std::pair { + 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(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( + 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 diff --git a/toolchain/check/impl.h b/toolchain/check/impl.h index 586948916e49..4d50ffa8b61a 100644 --- a/toolchain/check/impl.h +++ b/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 extend_impl) + -> std::pair; + } // namespace Carbon::Check #endif // CARBON_TOOLCHAIN_CHECK_IMPL_H_