mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-06 10:54:49 +01:00
Add diagnostics for invalid impl declarations (#5420)
Outside of `match_first` this adds diagnostics for invalid non-final and final `impl` declarations in line with those being proposed in https://github.com/carbon-language/carbon-lang/pull/5337. - Two non-final `impl`s with the exact same type structure is invalid. - A `final impl` that matches the self/constraint of another `impl` as a query would always be preferred, making the second one invalid. - Two `final impl`s that overlap (have compatible type structures) in different files is invalid. - Two `final impl`s that overlap (have compatible type structures) in the same file is invalid outside of `match_first`. - A `final impl` in a different file from its root self type and interface is invalid. We add tests for all these scenarios as well as correct scenarios. The "compatible" test for two type structures was being done symmetrically, which is incorrect. We want it to test that a query type structure is the same _or more specific_ in a compatible way with an impl's type structure. This is corrected in the implementation, and the diagnostics now have to test both directions to get the desired output, as expected. --------- Co-authored-by: josh11b <15258583+josh11b@users.noreply.github.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
#include "toolchain/check/handle.h"
|
||||
#include "toolchain/check/impl.h"
|
||||
#include "toolchain/check/impl_lookup.h"
|
||||
#include "toolchain/check/impl_validation.h"
|
||||
#include "toolchain/check/import.h"
|
||||
#include "toolchain/check/import_cpp.h"
|
||||
#include "toolchain/check/import_ref.h"
|
||||
@@ -572,160 +573,13 @@ auto CheckUnit::CheckPoisonedConcreteImplLookupQueries() -> void {
|
||||
context_.inst_block_stack().PopAndDiscard();
|
||||
}
|
||||
|
||||
// Check for invalid overlap between impls, given the set of all impls for a
|
||||
// single interface.
|
||||
static auto CheckOverlappingImplsForInterface(
|
||||
Context& context,
|
||||
llvm::ArrayRef<std::pair<SemIR::ImplId, SemIR::SpecificInterface>>
|
||||
impls_and_interface) -> void {
|
||||
// Range over `SemIR::ImplId` only. It'd be nice to make this the function
|
||||
// parameter but we don't have a concept to express that outside the (banned)
|
||||
// std::ranges.
|
||||
auto impl_ids = llvm::map_range(
|
||||
impls_and_interface,
|
||||
[=](std::pair<SemIR::ImplId, SemIR::SpecificInterface> pair) {
|
||||
auto [impl_id, _] = pair;
|
||||
return impl_id;
|
||||
});
|
||||
|
||||
// Avoid holding a reference into the ImplStore, as the diagnostic checks
|
||||
// below can invalidate the reference. We copy out the part of the `Impl` we
|
||||
// need.
|
||||
struct ImplInfo {
|
||||
bool is_final;
|
||||
SemIR::InstId witness_id;
|
||||
SemIR::TypeInstId self_id;
|
||||
SemIR::InstId latest_decl_id;
|
||||
SemIR::SpecificInterface interface;
|
||||
};
|
||||
auto get_impl_info = [&](SemIR::ImplId impl_id) -> ImplInfo {
|
||||
const auto& impl = context.impls().Get(impl_id);
|
||||
return {.is_final = impl.is_final,
|
||||
.witness_id = impl.witness_id,
|
||||
.self_id = impl.self_id,
|
||||
.latest_decl_id = impl.latest_decl_id(),
|
||||
.interface = impl.interface};
|
||||
};
|
||||
|
||||
// TODO: We should revisit this and look for a way to do these checks in less
|
||||
// than quadratic time. From @zygoloid: Possibly by converting the set of
|
||||
// impls into a decision tree.
|
||||
for (auto [index_a, impl_a_id] : llvm::enumerate(impl_ids)) {
|
||||
auto impl_a = get_impl_info(impl_a_id);
|
||||
if (impl_a.witness_id == SemIR::ErrorInst::InstId) {
|
||||
continue;
|
||||
}
|
||||
auto type_structure =
|
||||
BuildTypeStructure(context, impl_a.self_id, impl_a.interface);
|
||||
|
||||
for (auto impl_b_id : llvm::drop_begin(impl_ids, index_a + 1)) {
|
||||
auto impl_b = get_impl_info(impl_b_id);
|
||||
if (impl_b.witness_id == SemIR::ErrorInst::InstId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The type structure each non-final `impl` must differ from all other
|
||||
// non-final `impl` for the same interface visible from the file.
|
||||
if (!impl_a.is_final && !impl_b.is_final) {
|
||||
auto type_structure2 =
|
||||
BuildTypeStructure(context, impl_b.self_id, impl_b.interface);
|
||||
if (type_structure == type_structure2) {
|
||||
CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructure, Error,
|
||||
"found non-final `impl` with the same type "
|
||||
"structure as another non-final `impl`");
|
||||
auto builder = context.emitter().Build(impl_b.latest_decl_id,
|
||||
ImplNonFinalSameTypeStructure);
|
||||
CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructureNote, Note,
|
||||
"other `impl` here");
|
||||
builder.Note(impl_a.latest_decl_id,
|
||||
ImplNonFinalSameTypeStructureNote);
|
||||
builder.Emit();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
CARBON_CHECK(impl_a.is_final || impl_b.is_final);
|
||||
|
||||
auto diagnose = [&](ImplInfo& query_impl, SemIR::ImplId final_impl_id,
|
||||
const ImplInfo& final_impl) -> bool {
|
||||
if (LookupMatchesImpl(
|
||||
context, SemIR::LocId(query_impl.latest_decl_id),
|
||||
context.constant_values().Get(query_impl.self_id),
|
||||
query_impl.interface, final_impl_id)) {
|
||||
CARBON_DIAGNOSTIC(ImplFinalOverlapsNonFinal, Error,
|
||||
"`impl` will never be used");
|
||||
auto builder = context.emitter().Build(query_impl.latest_decl_id,
|
||||
ImplFinalOverlapsNonFinal);
|
||||
CARBON_DIAGNOSTIC(
|
||||
ImplFinalOverlapsNonFinalNote, Note,
|
||||
"`final impl` declared here would always be used instead");
|
||||
builder.Note(final_impl.latest_decl_id,
|
||||
ImplFinalOverlapsNonFinalNote);
|
||||
builder.Emit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool did_diagnose = false;
|
||||
if (impl_a.is_final) {
|
||||
did_diagnose = diagnose(impl_b, impl_a_id, impl_a);
|
||||
}
|
||||
if (impl_b.is_final && !did_diagnose) {
|
||||
diagnose(impl_a, impl_b_id, impl_b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The self + constraint of a `impl` must not match against (be
|
||||
// fully subsumed by) any final `impl` visible from the file. Do a
|
||||
// final-only query for all non-final impls?
|
||||
}
|
||||
}
|
||||
|
||||
auto CheckUnit::CheckOverlappingImpls() -> void {
|
||||
// Collect all of the impls sorted into contiguous segments by their
|
||||
// interface. We only need to compare impls within each such segment.
|
||||
//
|
||||
// Don't hold Impl pointers here because the process of looking for
|
||||
// diagnostics may cause imports and may invalidate pointers into the
|
||||
// ImplStore.
|
||||
llvm::SmallVector<std::pair<SemIR::ImplId, SemIR::SpecificInterface>>
|
||||
impl_ids_by_interface(llvm::map_range(
|
||||
context_.impls().enumerate(),
|
||||
[](std::pair<SemIR::ImplId, const SemIR::Impl&> pair) {
|
||||
return std::make_pair(pair.first, pair.second.interface);
|
||||
}));
|
||||
llvm::stable_sort(
|
||||
impl_ids_by_interface,
|
||||
[](std::pair<SemIR::ImplId, SemIR::SpecificInterface> a,
|
||||
std::pair<SemIR::ImplId, const SemIR::SpecificInterface> b) {
|
||||
return a.second.interface_id.index < b.second.interface_id.index;
|
||||
});
|
||||
|
||||
const auto* it = impl_ids_by_interface.begin();
|
||||
while (it != impl_ids_by_interface.end()) {
|
||||
const auto* segment_begin = it;
|
||||
do {
|
||||
++it;
|
||||
} while (it != impl_ids_by_interface.end() &&
|
||||
it->second.interface_id == segment_begin->second.interface_id);
|
||||
const auto* segment_end = it;
|
||||
|
||||
if (std::distance(segment_begin, segment_end) == 1) {
|
||||
// Only 1 interface in the segment; nothing to overlap with.
|
||||
continue;
|
||||
}
|
||||
|
||||
CheckOverlappingImplsForInterface(
|
||||
context_, llvm::ArrayRef(segment_begin, segment_end));
|
||||
}
|
||||
}
|
||||
auto CheckUnit::CheckImpls() -> void { ValidateImplsInFile(context_); }
|
||||
|
||||
auto CheckUnit::FinishRun() -> void {
|
||||
CheckRequiredDeclarations();
|
||||
CheckRequiredDefinitions();
|
||||
CheckPoisonedConcreteImplLookupQueries();
|
||||
CheckOverlappingImpls();
|
||||
CheckImpls();
|
||||
|
||||
// Pop information for the file-level scope.
|
||||
context_.sem_ir().set_top_inst_block_id(context_.inst_block_stack().Pop());
|
||||
|
||||
Reference in New Issue
Block a user