diagnostic_loc_converter.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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/sem_ir/diagnostic_loc_converter.h"
  5. #include "clang/Frontend/DiagnosticRenderer.h"
  6. namespace Carbon::SemIR {
  7. static auto ConvertPresumedLocToDiagnosticsLoc(clang::FullSourceLoc loc,
  8. clang::PresumedLoc presumed_loc)
  9. -> Diagnostics::Loc {
  10. llvm::StringRef line;
  11. // Ask the Clang SourceManager for the contents of the line containing this
  12. // location.
  13. // TODO: If this location is in our generated header, use the source text from
  14. // the presumed location (the Carbon source file) as the snippet instead.
  15. bool loc_invalid = false;
  16. const auto& src_mgr = loc.getManager();
  17. auto [file_id, offset] = src_mgr.getDecomposedSpellingLoc(loc);
  18. auto loc_line = src_mgr.getLineNumber(file_id, offset, &loc_invalid);
  19. if (!loc_invalid) {
  20. auto start_of_line = src_mgr.translateLineCol(file_id, loc_line, 1);
  21. line = src_mgr.getCharacterData(start_of_line, &loc_invalid);
  22. line = line.take_until([](char c) { return c == '\n'; });
  23. }
  24. return {.filename = presumed_loc.getFilename(),
  25. .line = loc_invalid ? "" : line,
  26. .line_number = static_cast<int32_t>(presumed_loc.getLine()),
  27. .column_number = static_cast<int32_t>(presumed_loc.getColumn()),
  28. .length = loc_invalid ? -1 : 1};
  29. }
  30. namespace {
  31. // A diagnostics "renderer" that renders the diagnostic into an array of
  32. // importing contexts based on the C++ include stack.
  33. class ClangImportCollector : public clang::DiagnosticRenderer {
  34. public:
  35. explicit ClangImportCollector(
  36. const clang::LangOptions& lang_opts,
  37. const clang::DiagnosticOptions& diag_opts,
  38. llvm::SmallVectorImpl<DiagnosticLocConverter::ImportLoc>* imports)
  39. : DiagnosticRenderer(lang_opts,
  40. // Work around lack of const-correctness in Clang.
  41. const_cast<clang::DiagnosticOptions&>(diag_opts)),
  42. imports_(imports) {}
  43. void emitDiagnosticMessage(clang::FullSourceLoc loc, clang::PresumedLoc ploc,
  44. clang::DiagnosticsEngine::Level /*level*/,
  45. llvm::StringRef message,
  46. llvm::ArrayRef<clang::CharSourceRange> /*ranges*/,
  47. clang::DiagOrStoredDiag /*info*/) override {
  48. if (!emitted_message_) {
  49. emitted_message_ = true;
  50. return;
  51. }
  52. // This is an "in macro expanded here" diagnostic that Clang emits after the
  53. // emitted diagnostic. We treat that as another form of context location.
  54. imports_->push_back(
  55. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  56. .kind = DiagnosticLocConverter::ImportLoc::CppMacroExpansion,
  57. .imported_name = message});
  58. }
  59. void emitDiagnosticLoc(
  60. clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/,
  61. clang::DiagnosticsEngine::Level /*level*/,
  62. llvm::ArrayRef<clang::CharSourceRange> /*ranges*/) override {}
  63. void emitCodeContext(
  64. clang::FullSourceLoc /*loc*/, clang::DiagnosticsEngine::Level /*level*/,
  65. llvm::SmallVectorImpl<clang::CharSourceRange>& /*ranges*/,
  66. llvm::ArrayRef<clang::FixItHint> /*hints*/) override {}
  67. void emitIncludeLocation(clang::FullSourceLoc loc,
  68. clang::PresumedLoc ploc) override {
  69. // TODO: If this location is for a `#include` in the generated C++ includes
  70. // buffer that corresponds to a carbon import, report it as being an Import
  71. // instead of a CppInclude.
  72. imports_->push_back(
  73. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  74. .kind = DiagnosticLocConverter::ImportLoc::CppInclude});
  75. }
  76. void emitImportLocation(clang::FullSourceLoc loc, clang::PresumedLoc ploc,
  77. llvm::StringRef module_name) override {
  78. imports_->push_back(
  79. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  80. .kind = DiagnosticLocConverter::ImportLoc::CppModuleImport,
  81. .imported_name = module_name});
  82. }
  83. void emitBuildingModuleLocation(clang::FullSourceLoc loc,
  84. clang::PresumedLoc ploc,
  85. llvm::StringRef module_name) override {
  86. imports_->push_back(
  87. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  88. .kind = DiagnosticLocConverter::ImportLoc::CppModuleImport,
  89. .imported_name = module_name});
  90. }
  91. private:
  92. llvm::SmallVectorImpl<DiagnosticLocConverter::ImportLoc>* imports_;
  93. // Whether we've emitted the primary diagnostic message or not. Any diagnostic
  94. // emitted after this is an "in macro expansion" note that we want to capture
  95. // as context.
  96. bool emitted_message_ = false;
  97. };
  98. } // namespace
  99. auto DiagnosticLocConverter::ConvertWithImports(LocId loc_id,
  100. bool token_only) const
  101. -> LocAndImports {
  102. llvm::SmallVector<AbsoluteNodeRef> absolute_node_refs =
  103. GetAbsoluteNodeRef(sem_ir_, loc_id);
  104. auto final_node = absolute_node_refs.pop_back_val();
  105. // Convert the final location.
  106. LocAndImports result = {.loc = ConvertImpl(final_node, token_only)};
  107. // Convert the import locations.
  108. for (const auto& absolute_node_ref : absolute_node_refs) {
  109. if (!absolute_node_ref.node_id().has_value()) {
  110. // TODO: Add an `ImportLoc` pointing at the prelude for the case where
  111. // we don't have a location.
  112. continue;
  113. }
  114. result.imports.push_back(
  115. {.loc = ConvertImpl(absolute_node_ref, false).loc});
  116. }
  117. // Convert the C++ import locations.
  118. if (final_node.is_cpp()) {
  119. const File* file = final_node.file();
  120. CARBON_CHECK(file->cpp_file(),
  121. "Converting C++ location before C++ file is set");
  122. // Collect the location backtrace that Clang would use for an error here.
  123. ClangImportCollector(file->cpp_file()->lang_options(),
  124. file->cpp_file()->diagnostic_options(),
  125. &result.imports)
  126. .emitDiagnostic(
  127. clang::FullSourceLoc(
  128. file->clang_source_locs().Get(final_node.clang_source_loc_id()),
  129. file->cpp_file()->source_manager()),
  130. clang::DiagnosticsEngine::Error, "", {}, {});
  131. }
  132. return result;
  133. }
  134. auto DiagnosticLocConverter::Convert(LocId loc_id, bool token_only) const
  135. -> Diagnostics::ConvertedLoc {
  136. llvm::SmallVector<AbsoluteNodeRef> absolute_node_refs =
  137. GetAbsoluteNodeRef(sem_ir_, loc_id);
  138. return ConvertImpl(absolute_node_refs.back(), token_only);
  139. }
  140. auto DiagnosticLocConverter::ConvertImpl(AbsoluteNodeRef absolute_node_ref,
  141. bool token_only) const
  142. -> Diagnostics::ConvertedLoc {
  143. if (absolute_node_ref.is_cpp()) {
  144. return ConvertImpl(absolute_node_ref.file(),
  145. absolute_node_ref.clang_source_loc_id());
  146. }
  147. return ConvertImpl(absolute_node_ref.check_ir_id(),
  148. absolute_node_ref.node_id(), token_only);
  149. }
  150. auto DiagnosticLocConverter::ConvertImpl(CheckIRId check_ir_id,
  151. Parse::NodeId node_id,
  152. bool token_only) const
  153. -> Diagnostics::ConvertedLoc {
  154. const auto& tree_and_subtrees =
  155. tree_and_subtrees_getters_->Get(check_ir_id)();
  156. return tree_and_subtrees.NodeToDiagnosticLoc(node_id, token_only);
  157. }
  158. auto DiagnosticLocConverter::ConvertImpl(
  159. const File* file, ClangSourceLocId clang_source_loc_id) const
  160. -> Diagnostics::ConvertedLoc {
  161. clang::SourceLocation clang_loc =
  162. file->clang_source_locs().Get(clang_source_loc_id);
  163. CARBON_CHECK(file->cpp_file());
  164. const auto& src_mgr = file->cpp_file()->source_manager();
  165. clang::PresumedLoc presumed_loc = src_mgr.getPresumedLoc(clang_loc);
  166. if (presumed_loc.isInvalid()) {
  167. return Diagnostics::ConvertedLoc();
  168. }
  169. unsigned offset = src_mgr.getDecomposedLoc(clang_loc).second;
  170. return Diagnostics::ConvertedLoc{
  171. .loc = ConvertPresumedLocToDiagnosticsLoc(
  172. clang::FullSourceLoc(clang_loc, src_mgr), presumed_loc),
  173. .last_byte_offset = static_cast<int32_t>(offset)};
  174. }
  175. } // namespace Carbon::SemIR