diagnostic_loc_converter.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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<AbsoluteNodeId> absolute_node_ids =
  103. GetAbsoluteNodeId(sem_ir_, loc_id);
  104. auto final_node_id = absolute_node_ids.pop_back_val();
  105. // Convert the final location.
  106. LocAndImports result = {.loc = ConvertImpl(final_node_id, token_only)};
  107. // Convert the import locations.
  108. for (const auto& absolute_node_id : absolute_node_ids) {
  109. if (!absolute_node_id.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({.loc = ConvertImpl(absolute_node_id, false).loc});
  115. }
  116. // Convert the C++ import locations.
  117. if (final_node_id.check_ir_id() == CheckIRId::Cpp) {
  118. const SemIR::CppFile* cpp_file = sem_ir_->cpp_file();
  119. CARBON_CHECK(cpp_file, "Converting C++ location before C++ file is set");
  120. // Collect the location backtrace that Clang would use for an error here.
  121. ClangImportCollector(cpp_file->lang_options(),
  122. cpp_file->diagnostic_options(), &result.imports)
  123. .emitDiagnostic(
  124. clang::FullSourceLoc(sem_ir_->clang_source_locs().Get(
  125. final_node_id.clang_source_loc_id()),
  126. cpp_file->source_manager()),
  127. clang::DiagnosticsEngine::Error, "", {}, {});
  128. }
  129. return result;
  130. }
  131. auto DiagnosticLocConverter::Convert(LocId loc_id, bool token_only) const
  132. -> Diagnostics::ConvertedLoc {
  133. llvm::SmallVector<AbsoluteNodeId> absolute_node_ids =
  134. GetAbsoluteNodeId(sem_ir_, loc_id);
  135. return ConvertImpl(absolute_node_ids.back(), token_only);
  136. }
  137. auto DiagnosticLocConverter::ConvertImpl(AbsoluteNodeId absolute_node_id,
  138. bool token_only) const
  139. -> Diagnostics::ConvertedLoc {
  140. if (absolute_node_id.check_ir_id() == CheckIRId::Cpp) {
  141. return ConvertImpl(absolute_node_id.clang_source_loc_id());
  142. }
  143. return ConvertImpl(absolute_node_id.check_ir_id(), absolute_node_id.node_id(),
  144. token_only);
  145. }
  146. auto DiagnosticLocConverter::ConvertImpl(CheckIRId check_ir_id,
  147. Parse::NodeId node_id,
  148. bool token_only) const
  149. -> Diagnostics::ConvertedLoc {
  150. CARBON_CHECK(check_ir_id != CheckIRId::Cpp);
  151. const auto& tree_and_subtrees =
  152. tree_and_subtrees_getters_->Get(check_ir_id)();
  153. return tree_and_subtrees.NodeToDiagnosticLoc(node_id, token_only);
  154. }
  155. auto DiagnosticLocConverter::ConvertImpl(
  156. ClangSourceLocId clang_source_loc_id) const -> Diagnostics::ConvertedLoc {
  157. clang::SourceLocation clang_loc =
  158. sem_ir_->clang_source_locs().Get(clang_source_loc_id);
  159. CARBON_CHECK(sem_ir_->cpp_file());
  160. const auto& src_mgr = sem_ir_->cpp_file()->source_manager();
  161. clang::PresumedLoc presumed_loc = src_mgr.getPresumedLoc(clang_loc);
  162. if (presumed_loc.isInvalid()) {
  163. return Diagnostics::ConvertedLoc();
  164. }
  165. unsigned offset = src_mgr.getDecomposedLoc(clang_loc).second;
  166. return Diagnostics::ConvertedLoc{
  167. .loc = ConvertPresumedLocToDiagnosticsLoc(
  168. clang::FullSourceLoc(clang_loc, src_mgr), presumed_loc),
  169. .last_byte_offset = static_cast<int32_t>(offset)};
  170. }
  171. } // namespace Carbon::SemIR