Avoid cyclic lookup of an impl inside its own definition (#6629)

An interface A requiring another interface B means that an impl of A
must verify that the self-type also impls B. The instructions created
from this can involved a lookup that the self-type impls A, which end up
finding the impl being defined. This is not problematic of itself, but
it is problematic if these lookup instructions become part of the impl's
generic definition. When we find a specific of that `impl as A` during
impl lookup of A, and we resolve the specific definition, those lookup
instructions are replayed. Doing so does another lookup for `impl as A`,
which creates an infinitely recursive loop.

To break this loop we move the lookup instructions done to verify that
the self-type impls B outside of the definition of `impl as A`. This
prevents them from being specialized. But it doesn't prevent us from
diagnosing monomorphization errors properly. They just get diagnosed at
the use of that invalid specific, instead of inside the verification of
`impl as B` in the definition of `impl as A`.
This commit is contained in:
Dana Jansens
2026-01-20 19:28:46 +00:00
committed by GitHub
parent ee77aa4b67
commit 848eddc9dd
5 changed files with 91 additions and 9 deletions
+12 -4
View File
@@ -663,12 +663,21 @@ auto FinishImplWitness(Context& context, const SemIR::Impl& impl) -> void {
// TODO: Diagnose if any declarations in the impl are not in used_decl_ids.
}
auto CheckRequireDeclsSatisfied(Context& context, SemIR::Impl& impl) -> void {
auto CheckRequireDeclsSatisfied(Context& context, SemIR::LocId loc_id,
SemIR::Impl& impl) -> void {
if (impl.witness_id == SemIR::ErrorInst::InstId) {
return;
}
const auto& interface = context.interfaces().Get(impl.interface.interface_id);
if (!interface.is_complete()) {
// This will be diagnosed later. We check for required decls before starting
// the definition to avoid inserting these lookups into the definition, as
// the lookups can end up looking for the impl being defined, which creates
// a cycle.
return;
}
auto require_ids =
context.require_impls_blocks().Get(interface.require_impls_block_id);
if (require_ids.empty()) {
@@ -690,8 +699,7 @@ auto CheckRequireDeclsSatisfied(Context& context, SemIR::Impl& impl) -> void {
context, require_specific, require.facet_type_inst_id);
auto result =
LookupImplWitness(context, SemIR::LocId(impl.latest_decl_id()),
self_const_id, facet_type_const_id);
LookupImplWitness(context, loc_id, self_const_id, facet_type_const_id);
// TODO: If the facet type contains 2 interfaces, and one is not `impl`ed,
// it would be nice to diagnose which one was not `impl`ed, but that
// requires LookupImplWitness to return a partial result, or take a
@@ -708,7 +716,7 @@ auto CheckRequireDeclsSatisfied(Context& context, SemIR::Impl& impl) -> void {
SemIR::SpecificInterface, SemIR::TypeId,
SemIR::FacetTypeId);
context.emitter().Emit(
impl.latest_decl_id(), RequireImplsNotImplemented, impl.interface,
loc_id, RequireImplsNotImplemented, impl.interface,
context.types().GetTypeIdForTypeConstantId(self_const_id),
context.insts()
.GetAs<SemIR::FacetType>(facet_type_inst_id)