impl_validation.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  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. namespace Carbon::Check {
  18. namespace {
  19. struct ImplAndInterface {
  20. SemIR::ImplId impl_id;
  21. SemIR::SpecificInterface interface;
  22. };
  23. // We avoid holding a reference into the ImplStore, as the act of verifying the
  24. // diagnostic checks can invalidate such a reference. We collect what we need to
  25. // know about the the `Impl` for validation into this struct.
  26. struct ImplInfo {
  27. bool is_final;
  28. SemIR::InstId witness_id;
  29. SemIR::TypeInstId self_id;
  30. SemIR::InstId latest_decl_id;
  31. SemIR::SpecificInterface interface;
  32. // Whether the `impl` decl was imported or from the local file.
  33. bool is_local;
  34. // If imported, the IR from which the `impl` decl was imported.
  35. SemIR::ImportIRId ir_id;
  36. TypeStructure type_structure;
  37. };
  38. } // namespace
  39. static auto GetIRId(Context& context, SemIR::InstId owning_inst_id)
  40. -> SemIR::ImportIRId {
  41. if (!owning_inst_id.has_value()) {
  42. return SemIR::ImportIRId::None;
  43. }
  44. return GetCanonicalImportIRInst(context, owning_inst_id).ir_id();
  45. }
  46. auto GetImplInfo(Context& context, SemIR::ImplId impl_id) -> ImplInfo {
  47. const auto& impl = context.impls().Get(impl_id);
  48. auto ir_id = GetIRId(context, impl.first_owning_decl_id);
  49. return {.is_final = impl.is_final,
  50. .witness_id = impl.witness_id,
  51. .self_id = impl.self_id,
  52. .latest_decl_id = impl.latest_decl_id(),
  53. .interface = impl.interface,
  54. .is_local = !ir_id.has_value(),
  55. .ir_id = ir_id,
  56. .type_structure =
  57. BuildTypeStructure(context, impl.self_id, impl.interface)};
  58. }
  59. // A final impl must be in the same file as either its root self type or the
  60. // interface in its constraint.
  61. //
  62. // Returns true if an error was diagnosed.
  63. static auto DiagnoseFinalImplNotInSameFileAsRootSelfTypeOrInterface(
  64. Context& context, const ImplInfo& impl, SemIR::ImportIRId interface_ir_id)
  65. -> bool {
  66. bool self_type_same_file = false;
  67. auto type_iter = SemIR::TypeIterator(&context.sem_ir());
  68. type_iter.Add(impl.self_id);
  69. auto step = type_iter.Next();
  70. using Step = SemIR::TypeIterator::Step;
  71. CARBON_KIND_SWITCH(step.any) {
  72. case CARBON_KIND(Step::ClassStart start): {
  73. auto inst_id = context.classes().Get(start.class_id).first_owning_decl_id;
  74. if (!GetIRId(context, inst_id).has_value()) {
  75. self_type_same_file = true;
  76. }
  77. break;
  78. }
  79. case CARBON_KIND(Step::ClassStartOnly start): {
  80. auto inst_id = context.classes().Get(start.class_id).first_owning_decl_id;
  81. if (!GetIRId(context, inst_id).has_value()) {
  82. self_type_same_file = true;
  83. }
  84. break;
  85. }
  86. case CARBON_KIND(Step::Done _):
  87. CARBON_FATAL("self type is empty?");
  88. default:
  89. break;
  90. }
  91. bool interface_same_file = !interface_ir_id.has_value();
  92. if (!self_type_same_file && !interface_same_file) {
  93. CARBON_DIAGNOSTIC(FinalImplInvalidFile, Error,
  94. "`final impl` found in file that does not contain "
  95. "the root self type nor the interface definition");
  96. context.emitter().Emit(impl.latest_decl_id, FinalImplInvalidFile);
  97. return true;
  98. }
  99. return false;
  100. }
  101. // The type structure each non-final `impl` must differ from all other non-final
  102. // `impl` for the same interface visible from the file.
  103. //
  104. // Returns true if an error was diagnosed.
  105. static auto DiagnoseNonFinalImplsWithSameTypeStructure(Context& context,
  106. const ImplInfo& impl_a,
  107. const ImplInfo& impl_b)
  108. -> bool {
  109. if (impl_a.type_structure == impl_b.type_structure) {
  110. CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructure, Error,
  111. "found non-final `impl` with the same type "
  112. "structure as another non-final `impl`");
  113. auto builder = context.emitter().Build(impl_b.latest_decl_id,
  114. ImplNonFinalSameTypeStructure);
  115. CARBON_DIAGNOSTIC(ImplNonFinalSameTypeStructureNote, Note,
  116. "other `impl` here");
  117. builder.Note(impl_a.latest_decl_id, ImplNonFinalSameTypeStructureNote);
  118. builder.Emit();
  119. return true;
  120. }
  121. return false;
  122. }
  123. // An impl's self type and constraint can not match (as a lookup query)
  124. // against any final impl, or it would never be used instead of that
  125. // final impl.
  126. //
  127. // Returns true if an error was diagnosed.
  128. static auto DiagnoseUnmatchableNonFinalImplWithFinalImpl(
  129. Context& context, SemIR::ImplId impl_a_id, const ImplInfo& impl_a,
  130. SemIR::ImplId impl_b_id, const ImplInfo& impl_b) -> bool {
  131. auto diagnose_unmatchable_impl = [&](const ImplInfo& query_impl,
  132. SemIR::ImplId final_impl_id,
  133. const ImplInfo& final_impl) -> bool {
  134. if (LookupMatchesImpl(context, SemIR::LocId(query_impl.latest_decl_id),
  135. context.constant_values().Get(query_impl.self_id),
  136. query_impl.interface, final_impl_id)) {
  137. CARBON_DIAGNOSTIC(ImplFinalOverlapsNonFinal, Error,
  138. "`impl` will never be used");
  139. auto builder = context.emitter().Build(query_impl.latest_decl_id,
  140. ImplFinalOverlapsNonFinal);
  141. CARBON_DIAGNOSTIC(
  142. ImplFinalOverlapsNonFinalNote, Note,
  143. "`final impl` declared here would always be used instead");
  144. builder.Note(final_impl.latest_decl_id, ImplFinalOverlapsNonFinalNote);
  145. builder.Emit();
  146. return true;
  147. }
  148. return false;
  149. };
  150. CARBON_CHECK(impl_a.is_final || impl_b.is_final);
  151. if (impl_b.is_final) {
  152. return diagnose_unmatchable_impl(impl_a, impl_b_id, impl_b);
  153. } else {
  154. return diagnose_unmatchable_impl(impl_b, impl_a_id, impl_a);
  155. }
  156. }
  157. // Final impls that overlap in their type structure must be in the
  158. // same file.
  159. //
  160. // Returns true if an error was diagnosed.
  161. static auto DiagnoseFinalImplsOverlapInDifferentFiles(Context& context,
  162. const ImplInfo& impl_a,
  163. const ImplInfo& impl_b)
  164. -> bool {
  165. if (impl_a.ir_id != impl_b.ir_id) {
  166. CARBON_DIAGNOSTIC(
  167. FinalImplOverlapsDifferentFile, Error,
  168. "`final impl` overlaps with `final impl` from another file");
  169. CARBON_DIAGNOSTIC(FinalImplOverlapsDifferentFileNote, Note,
  170. "imported `final impl` here");
  171. if (impl_a.is_local) {
  172. auto builder = context.emitter().Build(impl_a.latest_decl_id,
  173. FinalImplOverlapsDifferentFile);
  174. builder.Note(impl_b.latest_decl_id, FinalImplOverlapsDifferentFileNote);
  175. builder.Emit();
  176. } else {
  177. auto builder = context.emitter().Build(impl_b.latest_decl_id,
  178. FinalImplOverlapsDifferentFile);
  179. builder.Note(impl_a.latest_decl_id, FinalImplOverlapsDifferentFileNote);
  180. builder.Emit();
  181. }
  182. return true;
  183. }
  184. return false;
  185. }
  186. // Two final impls in the same file can not overlap in their type
  187. // structure if they are not in the same match_first block.
  188. //
  189. // TODO: Support for match_first needed here when they exist in the
  190. // toolchain.
  191. //
  192. // Returns true if an error was diagnosed.
  193. static auto DiagnoseFinalImplsOverlapOutsideMatchFirst(Context& context,
  194. const ImplInfo& impl_a,
  195. const ImplInfo& impl_b)
  196. -> bool {
  197. if (impl_a.is_local && impl_b.is_local) {
  198. CARBON_DIAGNOSTIC(FinalImplOverlapsSameFile, Error,
  199. "`final impl` overlaps with `final impl` from same file "
  200. "outside a `match_first` block");
  201. auto builder = context.emitter().Build(impl_b.latest_decl_id,
  202. FinalImplOverlapsSameFile);
  203. CARBON_DIAGNOSTIC(FinalImplOverlapsSameFileNote, Note,
  204. "other `final impl` here");
  205. builder.Note(impl_a.latest_decl_id, FinalImplOverlapsSameFileNote);
  206. builder.Emit();
  207. return true;
  208. }
  209. return false;
  210. }
  211. static auto ValidateImplsForInterface(
  212. Context& context, llvm::ArrayRef<ImplAndInterface> impls_and_interface)
  213. -> void {
  214. // Range over `SemIR::ImplId` only. Caller has ensured all of these are
  215. // for the same interface.
  216. auto impl_ids = llvm::map_range(impls_and_interface,
  217. [=](ImplAndInterface impl_and_interface) {
  218. return impl_and_interface.impl_id;
  219. });
  220. // All `impl`s we look at here have the same `InterfaceId` (though different
  221. // `SpecificId`s in their `SpecificInterface`s). So we can grab the
  222. // `ImportIRId` for the interface a single time up front.
  223. auto interface_decl_id =
  224. context.interfaces()
  225. .Get(impls_and_interface[0].interface.interface_id)
  226. .first_owning_decl_id;
  227. auto interface_ir_id = GetIRId(context, interface_decl_id);
  228. for (auto impl_id : impl_ids) {
  229. auto impl = GetImplInfo(context, impl_id);
  230. if (impl.is_final && impl.is_local) {
  231. // =======================================================================
  232. /// Rules for an individual final impl.
  233. // =======================================================================
  234. DiagnoseFinalImplNotInSameFileAsRootSelfTypeOrInterface(context, impl,
  235. interface_ir_id);
  236. }
  237. }
  238. // TODO: We should revisit this and look for a way to do these checks in less
  239. // than quadratic time. From @zygoloid: Possibly by converting the set of
  240. // impls into a decision tree.
  241. //
  242. // For each impl, we compare it pair-wise which each impl found before it, so
  243. // that diagnostics are attached to the later impl, as the earlier impl on its
  244. // own does not generate a diagnostic.
  245. size_t num_impl_ids = impls_and_interface.size();
  246. for (auto [split_point, impl_b_id] :
  247. llvm::drop_begin(llvm::enumerate(impl_ids))) {
  248. auto impl_b = GetImplInfo(context, impl_b_id);
  249. // Prevent diagnosing the same error multiple times for the same `impl_b`
  250. // against different impls before it. But still ensure we do give one of
  251. // each diagnostic when they are different errors.
  252. bool did_diagnose_non_final_impls_with_same_type_structure = false;
  253. bool did_diagnose_unmatchable_non_final_impl_with_final_impl = false;
  254. bool did_diagnose_final_impls_overlap_in_different_files = false;
  255. bool did_diagnose_final_impls_overlap_outside_match_first = false;
  256. auto impls_before = llvm::drop_end(impl_ids, num_impl_ids - split_point);
  257. for (auto impl_a_id : impls_before) {
  258. auto impl_a = GetImplInfo(context, impl_a_id);
  259. // Only enforce rules when at least one of the impls was written in this
  260. // file.
  261. if (!impl_a.is_local && !impl_b.is_local) {
  262. continue;
  263. }
  264. if (!impl_a.is_final && !impl_b.is_final) {
  265. // =====================================================================
  266. // Rules between two non-final impls.
  267. // =====================================================================
  268. if (!did_diagnose_non_final_impls_with_same_type_structure) {
  269. // Two impls in separate files will need to have some different
  270. // concrete element in their type structure, as enforced by the orphan
  271. // rule. So we don't need to check against non-local impls.
  272. if (impl_a.is_local && impl_b.is_local) {
  273. if (DiagnoseNonFinalImplsWithSameTypeStructure(context, impl_a,
  274. impl_b)) {
  275. // The same final `impl_a` may overlap with multiple `impl_b`s,
  276. // and we want to diagnose each `impl_b`.
  277. did_diagnose_non_final_impls_with_same_type_structure = true;
  278. }
  279. }
  280. }
  281. } else if (!impl_a.is_final || !impl_b.is_final) {
  282. // =====================================================================
  283. // Rules between final impl and non-final impl.
  284. // =====================================================================
  285. if (!did_diagnose_unmatchable_non_final_impl_with_final_impl) {
  286. if (DiagnoseUnmatchableNonFinalImplWithFinalImpl(
  287. context, impl_a_id, impl_a, impl_b_id, impl_b)) {
  288. did_diagnose_unmatchable_non_final_impl_with_final_impl = true;
  289. }
  290. }
  291. } else if (impl_a.type_structure.CompareStructure(
  292. TypeStructure::CompareTest::HasOverlap,
  293. impl_b.type_structure)) {
  294. // =====================================================================
  295. // Rules between two overlapping final impls.
  296. // =====================================================================
  297. CARBON_CHECK(impl_a.is_final && impl_b.is_final);
  298. if (!did_diagnose_final_impls_overlap_in_different_files) {
  299. if (DiagnoseFinalImplsOverlapInDifferentFiles(context, impl_a,
  300. impl_b)) {
  301. did_diagnose_final_impls_overlap_in_different_files = true;
  302. }
  303. }
  304. if (!did_diagnose_final_impls_overlap_outside_match_first) {
  305. if (DiagnoseFinalImplsOverlapOutsideMatchFirst(context, impl_a,
  306. impl_b)) {
  307. did_diagnose_final_impls_overlap_outside_match_first = true;
  308. }
  309. }
  310. }
  311. }
  312. }
  313. }
  314. // For each `impl` seen in this file, ensure that we import every available
  315. // `final impl` for the same interface, so that we can to check for
  316. // diagnostics about the relationship between them and the `impl`s in this
  317. // file.
  318. static auto ImportFinalImplsWithImplInFile(Context& context) -> void {
  319. struct InterfaceToImport {
  320. SemIR::ImportIRId ir_id;
  321. SemIR::InterfaceId interface_id;
  322. constexpr auto operator==(const InterfaceToImport& rhs) const
  323. -> bool = default;
  324. constexpr auto operator<=>(const InterfaceToImport& rhs) const -> auto {
  325. if (ir_id != rhs.ir_id) {
  326. return ir_id.index <=> rhs.ir_id.index;
  327. }
  328. return interface_id.index <=> rhs.interface_id.index;
  329. }
  330. };
  331. llvm::SmallVector<InterfaceToImport> interfaces_to_import;
  332. for (const auto& impl : context.impls().values()) {
  333. if (impl.witness_id == SemIR::ErrorInst::InstId) {
  334. continue;
  335. }
  336. auto impl_import_ir_id = GetIRId(context, impl.first_owning_decl_id);
  337. if (impl_import_ir_id.has_value()) {
  338. // Only import `impl`s of interfaces for which there is a local `impl` of
  339. // that that interface.
  340. continue;
  341. }
  342. auto interface_id = impl.interface.interface_id;
  343. const auto& interface = context.interfaces().Get(interface_id);
  344. auto import_ir_id = GetIRId(context, interface.first_owning_decl_id);
  345. if (!import_ir_id.has_value()) {
  346. continue;
  347. }
  348. interfaces_to_import.push_back(
  349. {.ir_id = import_ir_id, .interface_id = interface_id});
  350. }
  351. llvm::sort(interfaces_to_import);
  352. llvm::unique(interfaces_to_import);
  353. struct ImplToImport {
  354. SemIR::ImportIRId ir_id;
  355. SemIR::ImplId import_impl_id;
  356. constexpr auto operator==(const ImplToImport& rhs) const -> bool = default;
  357. constexpr auto operator<=>(const ImplToImport& rhs) const -> auto {
  358. if (ir_id != rhs.ir_id) {
  359. return ir_id.index <=> rhs.ir_id.index;
  360. }
  361. return import_impl_id.index <=> rhs.import_impl_id.index;
  362. }
  363. };
  364. llvm::SmallVector<ImplToImport> impls_to_import;
  365. for (auto [ir_id, interface_id] : interfaces_to_import) {
  366. const SemIR::File& sem_ir = *context.import_irs().Get(ir_id).sem_ir;
  367. for (auto [impl_id, impl] : sem_ir.impls().enumerate()) {
  368. if (impl.is_final && impl.interface.interface_id == interface_id) {
  369. impls_to_import.push_back({.ir_id = ir_id, .import_impl_id = impl_id});
  370. }
  371. }
  372. }
  373. llvm::sort(impls_to_import);
  374. llvm::unique(impls_to_import);
  375. for (auto [ir_id, import_impl_id] : impls_to_import) {
  376. ImportImpl(context, ir_id, import_impl_id);
  377. }
  378. }
  379. auto ValidateImplsInFile(Context& context) -> void {
  380. ImportFinalImplsWithImplInFile(context);
  381. // Collect all of the impls sorted into contiguous segments by their
  382. // interface. We only need to compare impls within each such segment. We don't
  383. // keep impls with an Error in them, as they may be missing other values
  384. // needed to check the diagnostics and they already have a diagnostic printed
  385. // about them anyhow. We also verify the impl has an `InterfaceId` since it
  386. // can be missing, in which case a diagnostic would have been generated
  387. // already as well.
  388. //
  389. // Don't hold Impl pointers here because the process of looking for
  390. // diagnostics may cause imports and may invalidate pointers into the
  391. // ImplStore.
  392. llvm::SmallVector<ImplAndInterface> impl_ids_by_interface(llvm::map_range(
  393. llvm::make_filter_range(
  394. context.impls().enumerate(),
  395. [](std::pair<SemIR::ImplId, const SemIR::Impl&> pair) {
  396. return pair.second.witness_id != SemIR::ErrorInst::InstId &&
  397. pair.second.interface.interface_id.has_value();
  398. }),
  399. [](std::pair<SemIR::ImplId, const SemIR::Impl&> pair) {
  400. return ImplAndInterface{.impl_id = pair.first,
  401. .interface = pair.second.interface};
  402. }));
  403. llvm::stable_sort(impl_ids_by_interface, [](const ImplAndInterface& lhs,
  404. const ImplAndInterface& rhs) {
  405. return lhs.interface.interface_id.index < rhs.interface.interface_id.index;
  406. });
  407. const auto* it = impl_ids_by_interface.begin();
  408. while (it != impl_ids_by_interface.end()) {
  409. const auto* segment_begin = it;
  410. auto begin_interface_id = segment_begin->interface.interface_id;
  411. do {
  412. ++it;
  413. } while (it != impl_ids_by_interface.end() &&
  414. it->interface.interface_id == begin_interface_id);
  415. const auto* segment_end = it;
  416. ValidateImplsForInterface(context, {segment_begin, segment_end});
  417. }
  418. }
  419. } // namespace Carbon::Check