diagnostic_loc_converter.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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, clang::DiagnosticOptions& diag_opts,
  37. llvm::SmallVectorImpl<DiagnosticLocConverter::ImportLoc>* imports)
  38. : DiagnosticRenderer(lang_opts, diag_opts), imports_(imports) {}
  39. void emitDiagnosticMessage(clang::FullSourceLoc loc, clang::PresumedLoc ploc,
  40. clang::DiagnosticsEngine::Level /*level*/,
  41. llvm::StringRef message,
  42. llvm::ArrayRef<clang::CharSourceRange> /*ranges*/,
  43. clang::DiagOrStoredDiag /*info*/) override {
  44. if (!emitted_message_) {
  45. emitted_message_ = true;
  46. return;
  47. }
  48. // This is an "in macro expanded here" diagnostic that Clang emits after the
  49. // emitted diagnostic. We treat that as another form of context location.
  50. imports_->push_back(
  51. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  52. .kind = DiagnosticLocConverter::ImportLoc::CppMacroExpansion,
  53. .imported_name = message});
  54. }
  55. void emitDiagnosticLoc(
  56. clang::FullSourceLoc /*loc*/, clang::PresumedLoc /*ploc*/,
  57. clang::DiagnosticsEngine::Level /*level*/,
  58. llvm::ArrayRef<clang::CharSourceRange> /*ranges*/) override {}
  59. void emitCodeContext(
  60. clang::FullSourceLoc /*loc*/, clang::DiagnosticsEngine::Level /*level*/,
  61. llvm::SmallVectorImpl<clang::CharSourceRange>& /*ranges*/,
  62. llvm::ArrayRef<clang::FixItHint> /*hints*/) override {}
  63. void emitIncludeLocation(clang::FullSourceLoc loc,
  64. clang::PresumedLoc ploc) override {
  65. // TODO: If this location is for a `#include` in the generated C++ includes
  66. // buffer that corresponds to a carbon import, report it as being an Import
  67. // instead of a CppInclude.
  68. imports_->push_back(
  69. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  70. .kind = DiagnosticLocConverter::ImportLoc::CppInclude});
  71. }
  72. void emitImportLocation(clang::FullSourceLoc loc, clang::PresumedLoc ploc,
  73. llvm::StringRef module_name) override {
  74. imports_->push_back(
  75. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  76. .kind = DiagnosticLocConverter::ImportLoc::CppModuleImport,
  77. .imported_name = module_name});
  78. }
  79. void emitBuildingModuleLocation(clang::FullSourceLoc loc,
  80. clang::PresumedLoc ploc,
  81. llvm::StringRef module_name) override {
  82. imports_->push_back(
  83. {.loc = ConvertPresumedLocToDiagnosticsLoc(loc, ploc),
  84. .kind = DiagnosticLocConverter::ImportLoc::CppModuleImport,
  85. .imported_name = module_name});
  86. }
  87. private:
  88. llvm::SmallVectorImpl<DiagnosticLocConverter::ImportLoc>* imports_;
  89. // Whether we've emitted the primary diagnostic message or not. Any diagnostic
  90. // emitted after this is an "in macro expansion" note that we want to capture
  91. // as context.
  92. bool emitted_message_ = false;
  93. };
  94. } // namespace
  95. auto DiagnosticLocConverter::ConvertWithImports(LocId loc_id,
  96. bool token_only) const
  97. -> LocAndImports {
  98. llvm::SmallVector<AbsoluteNodeId> absolute_node_ids =
  99. GetAbsoluteNodeId(sem_ir_, loc_id);
  100. auto final_node_id = absolute_node_ids.pop_back_val();
  101. // Convert the final location.
  102. LocAndImports result = {.loc = ConvertImpl(final_node_id, token_only)};
  103. // Convert the import locations.
  104. for (const auto& absolute_node_id : absolute_node_ids) {
  105. if (!absolute_node_id.node_id().has_value()) {
  106. // TODO: Add an `ImportLoc` pointing at the prelude for the case where
  107. // we don't have a location.
  108. continue;
  109. }
  110. result.imports.push_back({.loc = ConvertImpl(absolute_node_id, false).loc});
  111. }
  112. // Convert the C++ import locations.
  113. if (final_node_id.check_ir_id() == CheckIRId::Cpp) {
  114. const clang::ASTUnit* ast = sem_ir_->clang_ast_unit();
  115. // Collect the location backtrace that Clang would use for an error here.
  116. ClangImportCollector(ast->getLangOpts(),
  117. ast->getDiagnostics().getDiagnosticOptions(),
  118. &result.imports)
  119. .emitDiagnostic(
  120. clang::FullSourceLoc(sem_ir_->clang_source_locs().Get(
  121. final_node_id.clang_source_loc_id()),
  122. ast->getSourceManager()),
  123. clang::DiagnosticsEngine::Error, "", {}, {});
  124. }
  125. return result;
  126. }
  127. auto DiagnosticLocConverter::Convert(LocId loc_id, bool token_only) const
  128. -> Diagnostics::ConvertedLoc {
  129. llvm::SmallVector<AbsoluteNodeId> absolute_node_ids =
  130. GetAbsoluteNodeId(sem_ir_, loc_id);
  131. return ConvertImpl(absolute_node_ids.back(), token_only);
  132. }
  133. auto DiagnosticLocConverter::ConvertImpl(AbsoluteNodeId absolute_node_id,
  134. bool token_only) const
  135. -> Diagnostics::ConvertedLoc {
  136. if (absolute_node_id.check_ir_id() == CheckIRId::Cpp) {
  137. return ConvertImpl(absolute_node_id.clang_source_loc_id());
  138. }
  139. return ConvertImpl(absolute_node_id.check_ir_id(), absolute_node_id.node_id(),
  140. token_only);
  141. }
  142. auto DiagnosticLocConverter::ConvertImpl(CheckIRId check_ir_id,
  143. Parse::NodeId node_id,
  144. bool token_only) const
  145. -> Diagnostics::ConvertedLoc {
  146. CARBON_CHECK(check_ir_id != CheckIRId::Cpp);
  147. const auto& tree_and_subtrees =
  148. tree_and_subtrees_getters_->Get(check_ir_id)();
  149. return tree_and_subtrees.NodeToDiagnosticLoc(node_id, token_only);
  150. }
  151. auto DiagnosticLocConverter::ConvertImpl(
  152. ClangSourceLocId clang_source_loc_id) const -> Diagnostics::ConvertedLoc {
  153. clang::SourceLocation clang_loc =
  154. sem_ir_->clang_source_locs().Get(clang_source_loc_id);
  155. CARBON_CHECK(sem_ir_->clang_ast_unit());
  156. const auto& src_mgr = sem_ir_->clang_ast_unit()->getSourceManager();
  157. clang::PresumedLoc presumed_loc = src_mgr.getPresumedLoc(clang_loc);
  158. if (presumed_loc.isInvalid()) {
  159. return Diagnostics::ConvertedLoc();
  160. }
  161. unsigned offset = src_mgr.getDecomposedLoc(clang_loc).second;
  162. return Diagnostics::ConvertedLoc{
  163. .loc = ConvertPresumedLocToDiagnosticsLoc(
  164. clang::FullSourceLoc(clang_loc, src_mgr), presumed_loc),
  165. .last_byte_offset = static_cast<int32_t>(offset)};
  166. }
  167. } // namespace Carbon::SemIR