impl_validation.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include "toolchain/check/impl_validation.h"
  5. #include <utility>
  6. #include "llvm/ADT/ArrayRef.h"
  7. #include "llvm/ADT/STLExtras.h"
  8. #include "llvm/ADT/SmallVector.h"
  9. #include "toolchain/base/kind_switch.h"
  10. #include "toolchain/check/diagnostic_helpers.h"
  11. #include "toolchain/check/impl_lookup.h"
  12. #include "toolchain/check/import_ref.h"
  13. #include "toolchain/check/type_structure.h"
  14. #include "toolchain/sem_ir/entity_with_params_base.h"
  15. #include "toolchain/sem_ir/ids.h"
  16. #include "toolchain/sem_ir/type_iterator.h"
  17. #include "toolchain/sem_ir/typed_insts.h"
  18. namespace Carbon::Check {
  19. namespace {
  20. // All information about a `SemIR::Impl` needed for validation.
  21. struct ImplInfo {
  22. SemIR::ImplId impl_id;
  23. bool is_final;
  24. SemIR::InstId witness_id;
  25. SemIR::TypeInstId self_id;
  26. SemIR::InstId latest_decl_id;
  27. SemIR::SpecificInterface interface;
  28. // Whether the `impl` decl was imported or from the local file.
  29. bool is_local;
  30. // If imported, the IR from which the `impl` decl was imported.
  31. SemIR::ImportIRId ir_id;
  32. std::optional<TypeStructure> type_structure;
  33. };
  34. } // namespace
  35. static auto GetIRId(Context& context, SemIR::InstId owning_inst_id)
  36. -> SemIR::ImportIRId {
  37. if (!owning_inst_id.has_value()) {
  38. return SemIR::ImportIRId::None;
  39. }
  40. return GetCanonicalImportIRInst(context, owning_inst_id).ir_id();
  41. }
  42. // Returns if `owning_inst_id` is from the current file. This does not count an
  43. // api and impl file as the same file.
  44. static auto IsSameFile(Context& context, SemIR::InstId owning_inst_id) -> bool {
  45. if (!owning_inst_id.has_value()) {
  46. return false;
  47. }
  48. auto ir_id = GetCanonicalImportIRInst(context, owning_inst_id).ir_id();
  49. return !ir_id.has_value();
  50. }
  51. // Returns if `owning_inst_id` is from the current library. This does count api
  52. // and impl files as the same library.
  53. static auto IsSameLibrary(Context& context, SemIR::InstId owning_inst_id)
  54. -> bool {
  55. if (!owning_inst_id.has_value()) {
  56. return false;
  57. }
  58. auto ir_id = GetCanonicalImportIRInst(context, owning_inst_id).ir_id();
  59. if (!ir_id.has_value()) {
  60. return true;
  61. }
  62. if (const auto* api =
  63. context.import_irs().Get(SemIR::ImportIRId::ApiForImpl).sem_ir) {
  64. auto& ir = context.import_irs().Get(ir_id);
  65. return ir.sem_ir == api;
  66. }
  67. return false;
  68. }
  69. static auto GetImplInfo(Context& context, SemIR::ImplId impl_id) -> ImplInfo {
  70. const auto& impl = context.impls().Get(impl_id);
  71. auto ir_id = GetIRId(context, impl.first_owning_decl_id);
  72. return {.impl_id = impl_id,
  73. .is_final = impl.is_final,
  74. .witness_id = impl.witness_id,
  75. .self_id = impl.self_id,
  76. .latest_decl_id = impl.latest_decl_id(),
  77. .interface = impl.interface,
  78. .is_local = !ir_id.has_value(),
  79. .ir_id = ir_id,
  80. .type_structure =
  81. BuildTypeStructure(context, impl.self_id, impl.interface)};
  82. }
  83. // A final impl must be in the same file as either its root self type or the
  84. // interface in its constraint.
  85. //
  86. // Returns true if an error was diagnosed.
  87. static auto DiagnoseFinalImplNotInSameFileAsRootSelfTypeOrInterface(
  88. Context& context, const ImplInfo& impl, SemIR::ImportIRId interface_ir_id)
  89. -> bool {
  90. bool self_type_same_file = false;
  91. auto type_iter = SemIR::TypeIterator(&context.sem_ir());
  92. type_iter.Add(impl.self_id);
  93. auto step = type_iter.Next();
  94. using Step = SemIR::TypeIterator::Step;
  95. CARBON_KIND_SWITCH(step.any) {
  96. case CARBON_KIND(Step::ClassStart start): {
  97. auto inst_id = context.classes().Get(start.class_id).definition_id;
  98. if (IsSameFile(context, inst_id)) {
  99. self_type_same_file = true;
  100. }
  101. break;
  102. }
  103. case CARBON_KIND(Step::ClassStartOnly start): {
  104. auto inst_id = context.classes().Get(start.class_id).definition_id;
  105. if (IsSameFile(context, inst_id)) {
  106. self_type_same_file = true;
  107. }
  108. break;
  109. }
  110. case CARBON_KIND(Step::Done _): {
  111. CARBON_FATAL("self type is empty?");
  112. }
  113. default:
  114. break;
  115. }
  116. bool interface_same_file = !interface_ir_id.has_value();
  117. if (!self_type_same_file && !interface_same_file) {
  118. CARBON_DIAGNOSTIC(FinalImplInvalidFile, Error,
  119. "`final impl` found in file that does not contain "
  120. "the root self type nor the interface definition");
  121. context.emitter().Emit(impl.latest_decl_id, FinalImplInvalidFile);
  122. return true;
  123. }
  124. return false;
  125. }
  126. static auto DiagnoseOrphanImpl(Context& context, const ImplInfo& impl,
  127. SemIR::ImportIRId interface_ir_id) -> bool {
  128. // If the interface is defined in this file, then the impl is not an orphan.
  129. if (!interface_ir_id.has_value()) {
  130. return true;
  131. }
  132. // Look for a class in the self type, or the interface specific, that is from
  133. // this file to show the impl is not an orphan.
  134. auto type_iter = SemIR::TypeIterator(&context.sem_ir());
  135. type_iter.Add(impl.self_id);
  136. type_iter.Add(impl.interface);
  137. for (auto done = false; !done;) {
  138. auto step = type_iter.Next();
  139. using Step = SemIR::TypeIterator::Step;
  140. CARBON_KIND_SWITCH(step.any) {
  141. case CARBON_KIND(Step::ClassStart start): {
  142. auto inst_id = context.classes().Get(start.class_id).definition_id;
  143. if (IsSameLibrary(context, inst_id)) {
  144. return true;
  145. }
  146. break;
  147. }
  148. case CARBON_KIND(Step::ClassStartOnly start): {
  149. auto inst_id = context.classes().Get(start.class_id).definition_id;
  150. if (IsSameLibrary(context, inst_id)) {
  151. return true;
  152. }
  153. break;
  154. }
  155. case CARBON_KIND(Step::ConcreteType type): {
  156. // These are found in a specific when a `GenericClass`, a
  157. // `GenericInterface` or a `GenericNamedConstraint` appears. The generic
  158. // instruction itself is evaluated to a callable StructValue in the
  159. // type, but the specific also contains the callable's type which is one
  160. // of these.
  161. CARBON_KIND_SWITCH(context.types().GetAsInst(type.type_id)) {
  162. case CARBON_KIND(SemIR::GenericClassType class_type): {
  163. auto class_id = class_type.class_id;
  164. auto inst_id = context.classes().Get(class_id).definition_id;
  165. if (IsSameLibrary(context, inst_id)) {
  166. return true;
  167. }
  168. break;
  169. }
  170. case CARBON_KIND(SemIR::GenericInterfaceType interface_type): {
  171. auto interface_id = interface_type.interface_id;
  172. auto inst_id = context.interfaces().Get(interface_id).definition_id;
  173. if (IsSameLibrary(context, inst_id)) {
  174. return true;
  175. }
  176. break;
  177. }
  178. case CARBON_KIND(SemIR::GenericNamedConstraintType constraint_type): {
  179. auto constraint_id = constraint_type.named_constraint_id;
  180. auto inst_id =
  181. context.named_constraints().Get(constraint_id).definition_id;
  182. if (IsSameLibrary(context, inst_id)) {
  183. return true;
  184. }
  185. break;
  186. }
  187. default:
  188. break;
  189. }
  190. break;
  191. }
  192. case CARBON_KIND(Step::Done _): {
  193. done = true;
  194. break;
  195. }
  196. default:
  197. break;
  198. }
  199. }
  200. CARBON_DIAGNOSTIC(ImplIsOrphan, Error,
  201. "orphan `impl` found; something in the self-type or "
  202. "constraint must be defined in the same file");
  203. context.emitter().Emit(impl.latest_decl_id, ImplIsOrphan);
  204. return false;
  205. }
  206. // The type structure each non-final `impl` must differ from all other non-final
  207. // `impl` for the same interface visible from the file.
  208. //
  209. // Returns true if an error was diagnosed.
  210. static auto DiagnoseNonFinalImplsWithSameTypeStructure(Context& context,
  211. const ImplInfo& impl_a,
  212. const ImplInfo& impl_b)
  213. -> bool {
  214. if (impl_a.type_structure == impl_b.type_structure) {
  215. CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructure, Error,
  216. "found non-final `impl` with the same type "
  217. "structure as another non-final `impl`");
  218. auto builder = context.emitter().Build(impl_b.latest_decl_id,
  219. ImplNonFinalSameTypeStructure);
  220. CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructureNote, Note,
  221. "other `impl` here");
  222. builder.Note(impl_a.latest_decl_id, ImplNonFinalSameTypeStructureNote);
  223. builder.Emit();
  224. return true;
  225. }
  226. return false;
  227. }
  228. // An impl's self type and constraint can not match (as a lookup query)
  229. // against any final impl, or it would never be used instead of that
  230. // final impl.
  231. //
  232. // Returns true if an error was diagnosed.
  233. static auto DiagnoseUnmatchableNonFinalImplWithFinalImpl(Context& context,
  234. const ImplInfo& impl_a,
  235. const ImplInfo& impl_b)
  236. -> bool {
  237. auto diagnose_unmatchable_impl = [&](const ImplInfo& query_impl,
  238. const ImplInfo& final_impl) -> bool {
  239. if (LookupMatchesImpl(context, SemIR::LocId(query_impl.latest_decl_id),
  240. context.constant_values().Get(query_impl.self_id),
  241. query_impl.interface, final_impl.impl_id)) {
  242. CARBON_DIAGNOSTIC(ImplFinalOverlapsNonFinal, Error,
  243. "`impl` will never be used");
  244. auto builder = context.emitter().Build(query_impl.latest_decl_id,
  245. ImplFinalOverlapsNonFinal);
  246. CARBON_DIAGNOSTIC(
  247. ImplFinalOverlapsNonFinalNote, Note,
  248. "`final impl` declared here would always be used instead");
  249. builder.Note(final_impl.latest_decl_id, ImplFinalOverlapsNonFinalNote);
  250. builder.Emit();
  251. return true;
  252. }
  253. return false;
  254. };
  255. CARBON_CHECK(impl_a.is_final || impl_b.is_final);
  256. if (impl_b.is_final) {
  257. return diagnose_unmatchable_impl(impl_a, impl_b);
  258. } else {
  259. return diagnose_unmatchable_impl(impl_b, impl_a);
  260. }
  261. }
  262. // Final impls that overlap in their type structure must be in the
  263. // same file.
  264. //
  265. // Returns true if an error was diagnosed.
  266. static auto DiagnoseFinalImplsOverlapInDifferentFiles(Context& context,
  267. const ImplInfo& impl_a,
  268. const ImplInfo& impl_b)
  269. -> bool {
  270. if (impl_a.ir_id != impl_b.ir_id) {
  271. CARBON_DIAGNOSTIC(
  272. FinalImplOverlapsDifferentFile, Error,
  273. "`final impl` overlaps with `final impl` from another file");
  274. CARBON_DIAGNOSTIC(FinalImplOverlapsDifferentFileNote, Note,
  275. "imported `final impl` here");
  276. if (impl_a.is_local) {
  277. auto builder = context.emitter().Build(impl_a.latest_decl_id,
  278. FinalImplOverlapsDifferentFile);
  279. builder.Note(impl_b.latest_decl_id, FinalImplOverlapsDifferentFileNote);
  280. builder.Emit();
  281. } else {
  282. auto builder = context.emitter().Build(impl_b.latest_decl_id,
  283. FinalImplOverlapsDifferentFile);
  284. builder.Note(impl_a.latest_decl_id, FinalImplOverlapsDifferentFileNote);
  285. builder.Emit();
  286. }
  287. return true;
  288. }
  289. return false;
  290. }
  291. // Two final impls in the same file can not overlap in their type
  292. // structure if they are not in the same match_first block.
  293. //
  294. // TODO: Support for match_first needed here when they exist in the
  295. // toolchain.
  296. //
  297. // Returns true if an error was diagnosed.
  298. static auto DiagnoseFinalImplsOverlapOutsideMatchFirst(Context& context,
  299. const ImplInfo& impl_a,
  300. const ImplInfo& impl_b)
  301. -> bool {
  302. if (impl_a.is_local && impl_b.is_local) {
  303. CARBON_DIAGNOSTIC(FinalImplOverlapsSameFile, Error,
  304. "`final impl` overlaps with `final impl` from same file "
  305. "outside a `match_first` block");
  306. auto builder = context.emitter().Build(impl_b.latest_decl_id,
  307. FinalImplOverlapsSameFile);
  308. CARBON_DIAGNOSTIC(FinalImplOverlapsSameFileNote, Note,
  309. "other `final impl` here");
  310. builder.Note(impl_a.latest_decl_id, FinalImplOverlapsSameFileNote);
  311. builder.Emit();
  312. return true;
  313. }
  314. return false;
  315. }
  316. static auto ValidateImplsForInterface(Context& context,
  317. llvm::ArrayRef<ImplInfo> impls) -> void {
  318. // All `impl`s we look at here have the same `InterfaceId` (though different
  319. // `SpecificId`s in their `SpecificInterface`s). So we can grab the
  320. // `ImportIRId` for the interface a single time up front.
  321. auto interface_decl_id = context.interfaces()
  322. .Get(impls[0].interface.interface_id)
  323. .first_owning_decl_id;
  324. auto interface_ir_id = GetIRId(context, interface_decl_id);
  325. for (const auto& impl : impls) {
  326. if (impl.is_final && impl.is_local) {
  327. // =======================================================================
  328. /// Rules for an individual final impl.
  329. // =======================================================================
  330. DiagnoseFinalImplNotInSameFileAsRootSelfTypeOrInterface(context, impl,
  331. interface_ir_id);
  332. } else if (impl.is_local) {
  333. DiagnoseOrphanImpl(context, impl, interface_ir_id);
  334. }
  335. }
  336. // TODO: We should revisit this and look for a way to do these checks in less
  337. // than quadratic time. From @zygoloid: Possibly by converting the set of
  338. // impls into a decision tree.
  339. //
  340. // For each impl, we compare it pair-wise which each impl found before it, so
  341. // that diagnostics are attached to the later impl, as the earlier impl on its
  342. // own does not generate a diagnostic.
  343. size_t num_impls = impls.size();
  344. for (auto [split_point, impl_b] : llvm::drop_begin(llvm::enumerate(impls))) {
  345. // Prevent diagnosing the same error multiple times for the same `impl_b`
  346. // against different impls before it. But still ensure we do give one of
  347. // each diagnostic when they are different errors.
  348. bool did_diagnose_non_final_impls_with_same_type_structure = false;
  349. bool did_diagnose_unmatchable_non_final_impl_with_final_impl = false;
  350. bool did_diagnose_final_impls_overlap_in_different_files = false;
  351. bool did_diagnose_final_impls_overlap_outside_match_first = false;
  352. auto impls_before = llvm::drop_end(impls, num_impls - split_point);
  353. for (const auto& impl_a : impls_before) {
  354. // Don't diagnose structures that contain errors.
  355. if (!impl_a.type_structure || !impl_b.type_structure) {
  356. continue;
  357. }
  358. // Only enforce rules when at least one of the impls was written in this
  359. // file.
  360. if (!impl_a.is_local && !impl_b.is_local) {
  361. continue;
  362. }
  363. if (!impl_a.is_final && !impl_b.is_final) {
  364. // =====================================================================
  365. // Rules between two non-final impls.
  366. // =====================================================================
  367. if (!did_diagnose_non_final_impls_with_same_type_structure) {
  368. // Two impls in separate files will need to have some different
  369. // concrete element in their type structure, as enforced by the orphan
  370. // rule. So we don't need to check against non-local impls.
  371. if (impl_a.is_local && impl_b.is_local) {
  372. if (DiagnoseNonFinalImplsWithSameTypeStructure(context, impl_a,
  373. impl_b)) {
  374. // The same final `impl_a` may overlap with multiple `impl_b`s,
  375. // and we want to diagnose each `impl_b`.
  376. did_diagnose_non_final_impls_with_same_type_structure = true;
  377. }
  378. }
  379. }
  380. } else if (!impl_a.is_final || !impl_b.is_final) {
  381. // =====================================================================
  382. // Rules between final impl and non-final impl.
  383. // =====================================================================
  384. if (!did_diagnose_unmatchable_non_final_impl_with_final_impl) {
  385. if (DiagnoseUnmatchableNonFinalImplWithFinalImpl(context, impl_a,
  386. impl_b)) {
  387. did_diagnose_unmatchable_non_final_impl_with_final_impl = true;
  388. }
  389. }
  390. } else if (impl_a.type_structure->CompareStructure(
  391. TypeStructure::CompareTest::HasOverlap,
  392. *impl_b.type_structure)) {
  393. // =====================================================================
  394. // Rules between two overlapping final impls.
  395. // =====================================================================
  396. CARBON_CHECK(impl_a.is_final && impl_b.is_final);
  397. if (!did_diagnose_final_impls_overlap_in_different_files) {
  398. if (DiagnoseFinalImplsOverlapInDifferentFiles(context, impl_a,
  399. impl_b)) {
  400. did_diagnose_final_impls_overlap_in_different_files = true;
  401. }
  402. }
  403. if (!did_diagnose_final_impls_overlap_outside_match_first) {
  404. if (DiagnoseFinalImplsOverlapOutsideMatchFirst(context, impl_a,
  405. impl_b)) {
  406. did_diagnose_final_impls_overlap_outside_match_first = true;
  407. }
  408. }
  409. }
  410. }
  411. }
  412. }
  413. // For each `impl` seen in this file, ensure that we import every available
  414. // `final impl` for the same interface, so that we can to check for
  415. // diagnostics about the relationship between them and the `impl`s in this
  416. // file.
  417. static auto ImportFinalImplsWithImplInFile(Context& context) -> void {
  418. struct InterfaceToImport {
  419. SemIR::ImportIRId ir_id;
  420. SemIR::InterfaceId interface_id;
  421. constexpr auto operator==(const InterfaceToImport& rhs) const
  422. -> bool = default;
  423. constexpr auto operator<=>(const InterfaceToImport& rhs) const -> auto {
  424. if (ir_id != rhs.ir_id) {
  425. return ir_id.index <=> rhs.ir_id.index;
  426. }
  427. return interface_id.index <=> rhs.interface_id.index;
  428. }
  429. };
  430. llvm::SmallVector<InterfaceToImport> interfaces_to_import;
  431. for (const auto& impl : context.impls().values()) {
  432. if (impl.witness_id == SemIR::ErrorInst::InstId) {
  433. continue;
  434. }
  435. auto impl_import_ir_id = GetIRId(context, impl.first_owning_decl_id);
  436. if (impl_import_ir_id.has_value()) {
  437. // Only import `impl`s of interfaces for which there is a local `impl` of
  438. // that that interface.
  439. continue;
  440. }
  441. auto interface_id = impl.interface.interface_id;
  442. const auto& interface = context.interfaces().Get(interface_id);
  443. if (!interface.first_owning_decl_id.has_value()) {
  444. continue;
  445. }
  446. const auto& import_ir_inst =
  447. GetCanonicalImportIRInst(context, interface.first_owning_decl_id);
  448. if (!import_ir_inst.ir_id().has_value()) {
  449. continue;
  450. }
  451. interfaces_to_import.push_back(
  452. {.ir_id = import_ir_inst.ir_id(),
  453. .interface_id =
  454. context.import_irs()
  455. .Get(import_ir_inst.ir_id())
  456. .sem_ir->insts()
  457. .GetAs<SemIR::InterfaceDecl>(import_ir_inst.inst_id())
  458. .interface_id});
  459. }
  460. llvm::sort(interfaces_to_import);
  461. llvm::unique(interfaces_to_import);
  462. struct ImplToImport {
  463. SemIR::ImportIRId ir_id;
  464. SemIR::ImplId import_impl_id;
  465. constexpr auto operator==(const ImplToImport& rhs) const -> bool = default;
  466. constexpr auto operator<=>(const ImplToImport& rhs) const -> auto {
  467. if (ir_id != rhs.ir_id) {
  468. return ir_id.index <=> rhs.ir_id.index;
  469. }
  470. return import_impl_id.index <=> rhs.import_impl_id.index;
  471. }
  472. };
  473. llvm::SmallVector<ImplToImport> impls_to_import;
  474. for (auto [ir_id, interface_id] : interfaces_to_import) {
  475. const SemIR::File& sem_ir = *context.import_irs().Get(ir_id).sem_ir;
  476. for (auto [impl_id, impl] : sem_ir.impls().enumerate()) {
  477. if (impl.is_final && impl.interface.interface_id == interface_id) {
  478. impls_to_import.push_back({.ir_id = ir_id, .import_impl_id = impl_id});
  479. }
  480. }
  481. }
  482. llvm::sort(impls_to_import);
  483. llvm::unique(impls_to_import);
  484. for (auto [ir_id, import_impl_id] : impls_to_import) {
  485. ImportImpl(context, ir_id, import_impl_id);
  486. }
  487. }
  488. auto ValidateImplsInFile(Context& context) -> void {
  489. ImportFinalImplsWithImplInFile(context);
  490. // Collect all of the impls sorted into contiguous segments by their
  491. // interface. We only need to compare impls within each such segment. We don't
  492. // keep impls with an Error in them, as they may be missing other values
  493. // needed to check the diagnostics and they already have a diagnostic printed
  494. // about them anyhow. We also verify the impl has an `InterfaceId` since it
  495. // can be missing, in which case a diagnostic would have been generated
  496. // already as well.
  497. llvm::SmallVector<ImplInfo> impl_ids_by_interface(llvm::map_range(
  498. llvm::make_filter_range(
  499. context.impls().enumerate(),
  500. [](std::pair<SemIR::ImplId, const SemIR::Impl&> pair) {
  501. return pair.second.witness_id != SemIR::ErrorInst::InstId &&
  502. pair.second.interface.interface_id.has_value();
  503. }),
  504. [&](std::pair<SemIR::ImplId, const SemIR::Impl&> pair) {
  505. return GetImplInfo(context, pair.first);
  506. }));
  507. llvm::stable_sort(impl_ids_by_interface, [](const ImplInfo& lhs,
  508. const ImplInfo& rhs) {
  509. return lhs.interface.interface_id.index < rhs.interface.interface_id.index;
  510. });
  511. const auto* it = impl_ids_by_interface.begin();
  512. while (it != impl_ids_by_interface.end()) {
  513. const auto* segment_begin = it;
  514. auto begin_interface_id = segment_begin->interface.interface_id;
  515. do {
  516. ++it;
  517. } while (it != impl_ids_by_interface.end() &&
  518. it->interface.interface_id == begin_interface_id);
  519. const auto* segment_end = it;
  520. ValidateImplsForInterface(context, {segment_begin, segment_end});
  521. }
  522. }
  523. } // namespace Carbon::Check