Improve backtrace for lowering crashes. (#5651)

Factor out the logic for mapping from a `LocId` into a diagnostic
location from check into sem_ir so it can be reused by lowering. Include
the function and instruction being lowered in the pretty stack trace.
Example stack trace:

```carbon
2.      filename: examples/sieve.carbon
3.      core/prelude/types/int.carbon:213:3: lowering function Core.Op(Core.IntLiteral as Core.ImplicitAs(i32))
            fn Op[addr self: Self*](other: Self) = "int.sadd_assign";
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4.      core/prelude/operators/arithmetic.carbon:22:27: lowering call
            fn Op[addr self: Self*](other: Other);
                                    ^~~~~~~~~~~~
```
This commit is contained in:
Richard Smith
2025-06-16 23:21:22 +00:00
committed by GitHub
parent ac56057f08
commit 519e633147
16 changed files with 276 additions and 149 deletions
+1
View File
@@ -285,6 +285,7 @@ cc_library(
"//toolchain/lex:token_index",
"//toolchain/parse:tree",
"//toolchain/sem_ir:absolute_node_id",
"//toolchain/sem_ir:diagnostic_loc_converter",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:stringify",
"//toolchain/sem_ir:typed_insts",
+1 -1
View File
@@ -377,7 +377,7 @@ auto CheckUnit::ProcessNodeIds() -> bool {
const auto& tree = tree_and_subtrees_getter_();
auto converted = tree.NodeToDiagnosticLoc(node_id, /*token_only=*/false);
converted.loc.FormatLocation(output);
output << "checking " << context_.parse_tree().node_kind(node_id) << "\n";
output << "Checking " << context_.parse_tree().node_kind(node_id) << "\n";
// Crash output has a tab indent; try to indent slightly past that.
converted.loc.FormatSnippet(output, /*indent=*/10);
});
+6 -75
View File
@@ -19,8 +19,12 @@ namespace Carbon::Check {
auto DiagnosticEmitter::ConvertLoc(LocIdForDiagnostics loc_id,
ContextFnT context_fn) const
-> Diagnostics::ConvertedLoc {
auto converted =
ConvertLocImpl(loc_id.loc_id(), loc_id.is_token_only(), context_fn);
auto [imports, converted] = loc_converter_.ConvertWithImports(
loc_id.loc_id(), loc_id.is_token_only());
for (const auto& import : imports) {
CARBON_DIAGNOSTIC(InImport, LocationInfo, "in import");
context_fn(import.loc, InImport);
}
// Use the token when possible, but -1 is the default value.
auto last_offset = -1;
@@ -40,73 +44,6 @@ auto DiagnosticEmitter::ConvertLoc(LocIdForDiagnostics loc_id,
return converted;
}
auto DiagnosticEmitter::ConvertLocImpl(SemIR::LocId loc_id, bool is_token_only,
ContextFnT context_fn) const
-> Diagnostics::ConvertedLoc {
llvm::SmallVector<SemIR::AbsoluteNodeId> absolute_node_ids =
SemIR::GetAbsoluteNodeId(sem_ir_, loc_id);
auto final_node_id = absolute_node_ids.pop_back_val();
for (const auto& absolute_node_id : absolute_node_ids) {
if (!absolute_node_id.node_id().has_value()) {
// TODO: Add an "In implicit import of prelude." note for the case where
// we don't have a location.
continue;
}
// TODO: Include the name of the imported library in the diagnostic.
auto diag_loc =
ConvertLocInFile(absolute_node_id, is_token_only, context_fn);
AddInImport(diag_loc.loc, context_fn);
}
return ConvertLocInFile(final_node_id, is_token_only, context_fn);
}
auto DiagnosticEmitter::ConvertLocInFile(SemIR::AbsoluteNodeId absolute_node_id,
bool token_only,
ContextFnT context_fn) const
-> Diagnostics::ConvertedLoc {
if (absolute_node_id.check_ir_id() == SemIR::CheckIRId::Cpp) {
// Special handling of Clang source locations.
CARBON_CHECK(sem_ir_->import_cpps().size() > 0);
// TODO: Use information on the specific C++ import extract from Clang error
// message and propagated here instead of using first C++ import
// arbitrarily.
Parse::NodeId import_node_id =
sem_ir_->import_cpps().values().begin()->node_id;
AddInImport(ConvertLocInCarbonFile(sem_ir_->check_ir_id(), import_node_id,
/*token_only=*/false)
.loc,
context_fn);
clang::SourceLocation clang_loc = sem_ir_->clang_source_locs().Get(
absolute_node_id.clang_source_loc_id());
CARBON_CHECK(sem_ir_->cpp_ast());
clang::PresumedLoc presumed_loc =
sem_ir_->cpp_ast()->getSourceManager().getPresumedLoc(clang_loc);
return Diagnostics::ConvertedLoc{
.loc = {.filename = presumed_loc.getFilename(),
.line_number = static_cast<int32_t>(presumed_loc.getLine())},
// TODO: Set `last_byte_offset` based on the `import Cpp` location.
.last_byte_offset = 0};
}
return ConvertLocInCarbonFile(absolute_node_id.check_ir_id(),
absolute_node_id.node_id(), token_only);
}
auto DiagnosticEmitter::ConvertLocInCarbonFile(SemIR::CheckIRId check_ir_id,
Parse::NodeId node_id,
bool token_only) const
-> Diagnostics::ConvertedLoc {
CARBON_CHECK(check_ir_id != SemIR::CheckIRId::Cpp);
const auto& tree_and_subtrees =
tree_and_subtrees_getters_[check_ir_id.index]();
return tree_and_subtrees.NodeToDiagnosticLoc(node_id, token_only);
}
auto DiagnosticEmitter::ConvertArg(llvm::Any arg) const -> llvm::Any {
if (auto* library_name_id = llvm::any_cast<SemIR::LibraryNameId>(&arg)) {
std::string library_name;
@@ -178,10 +115,4 @@ auto DiagnosticEmitter::ConvertArg(llvm::Any arg) const -> llvm::Any {
return DiagnosticEmitterBase::ConvertArg(arg);
}
auto DiagnosticEmitter::AddInImport(Diagnostics::Loc loc, ContextFnT context_fn)
-> void {
CARBON_DIAGNOSTIC(InImport, LocationInfo, "in import");
context_fn(loc, InImport);
}
} // namespace Carbon::Check
+6 -28
View File
@@ -9,10 +9,7 @@
#include "toolchain/check/diagnostic_helpers.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
#include "toolchain/lex/token_index.h"
#include "toolchain/parse/tree_and_subtrees.h"
#include "toolchain/sem_ir/absolute_node_id.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/diagnostic_loc_converter.h"
namespace Carbon::Check {
@@ -24,8 +21,8 @@ class DiagnosticEmitter : public DiagnosticEmitterBase {
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
const SemIR::File* sem_ir)
: DiagnosticEmitterBase(consumer),
tree_and_subtrees_getters_(tree_and_subtrees_getters),
sem_ir_(sem_ir) {}
sem_ir_(sem_ir),
loc_converter_(tree_and_subtrees_getters, sem_ir) {}
// If a byte offset is past the current last byte offset, advances forward.
// Earlier offsets are ignored.
@@ -47,31 +44,12 @@ class DiagnosticEmitter : public DiagnosticEmitterBase {
-> Diagnostics::ConvertedLoc override;
private:
// Implements `ConvertLoc`, but without `last_token_` applied.
auto ConvertLocImpl(SemIR::LocId loc_id, bool is_token_only,
ContextFnT context_fn) const -> Diagnostics::ConvertedLoc;
// Converts an `absolute_node_id` in either a Carbon file or C++ import to a
// diagnostic location.
auto ConvertLocInFile(SemIR::AbsoluteNodeId absolute_node_id, bool token_only,
ContextFnT context_fn) const
-> Diagnostics::ConvertedLoc;
// Converts a `node_id` corresponding to a specific sem_ir to a diagnostic
// location.
auto ConvertLocInCarbonFile(SemIR::CheckIRId check_ir_id,
Parse::NodeId node_id, bool token_only) const
-> Diagnostics::ConvertedLoc;
// Adds `in import` note.
static auto AddInImport(Diagnostics::Loc loc, ContextFnT context_fn) -> void;
// Converters for each SemIR.
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters_;
// The current SemIR being processed.
const SemIR::File* sem_ir_;
// Converter for locations.
SemIR::DiagnosticLocConverter loc_converter_;
// The last token encountered during processing.
Lex::TokenIndex last_token_ = Lex::TokenIndex::None;
};
+6 -8
View File
@@ -708,13 +708,11 @@ auto CompilationUnit::RunLower() -> void {
// TODO: Consider disabling instruction naming by default if we're not
// producing textual LLVM IR.
SemIR::InstNamer inst_namer(&*sem_ir_);
std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>> subtrees;
if (options_->include_debug_info) {
subtrees = cache_->tree_and_subtrees_getters();
}
module_ = Lower::LowerToLLVM(*llvm_context_, driver_env_->fs, subtrees,
input_filename_, *sem_ir_, &inst_namer,
vlog_stream_);
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> subtrees =
cache_->tree_and_subtrees_getters();
module_ = Lower::LowerToLLVM(
*llvm_context_, driver_env_->fs, options_->include_debug_info, subtrees,
input_filename_, *sem_ir_, &inst_namer, vlog_stream_);
});
if (vlog_stream_) {
CARBON_VLOG("*** llvm::Module ***\n");
@@ -846,7 +844,7 @@ auto CompilationUnit::LogCall(llvm::StringLiteral logging_label,
llvm::StringLiteral timing_label,
llvm::function_ref<auto()->void> fn) -> void {
PrettyStackTraceFunction trace_file([&](llvm::raw_ostream& out) {
out << "filename: " << input_filename_ << "\n";
out << "Filename: " << input_filename_ << "\n";
});
CARBON_VLOG("*** {0}: {1} ***\n", logging_label, input_filename_);
Timings::ScopedTiming timing(timings_ ? &*timings_ : nullptr, timing_label);
+3
View File
@@ -53,12 +53,15 @@ cc_library(
"//common:vlog",
"//toolchain/base:fixed_size_value_store",
"//toolchain/base:kind_switch",
"//toolchain/base:pretty_stack_trace_function",
"//toolchain/parse:tree",
"//toolchain/sem_ir:absolute_node_id",
"//toolchain/sem_ir:diagnostic_loc_converter",
"//toolchain/sem_ir:entry_point",
"//toolchain/sem_ir:expr_info",
"//toolchain/sem_ir:file",
"//toolchain/sem_ir:inst_namer",
"//toolchain/sem_ir:stringify",
"//toolchain/sem_ir:typed_insts",
"@llvm-project//clang:ast",
"@llvm-project//clang:basic",
+8 -10
View File
@@ -13,21 +13,20 @@
namespace Carbon::Lower {
Context::Context(llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
tree_and_subtrees_getters_for_debug_info,
llvm::StringRef module_name, llvm::raw_ostream* vlog_stream)
Context::Context(
llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, bool want_debug_info,
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
llvm::StringRef module_name, llvm::raw_ostream* vlog_stream)
: llvm_context_(&llvm_context),
llvm_module_(std::make_unique<llvm::Module>(module_name, llvm_context)),
file_system_(std::move(fs)),
di_builder_(*llvm_module_),
di_compile_unit_(
tree_and_subtrees_getters_for_debug_info
want_debug_info
? BuildDICompileUnit(module_name, *llvm_module_, di_builder_)
: nullptr),
tree_and_subtrees_getters_for_debug_info_(
tree_and_subtrees_getters_for_debug_info),
tree_and_subtrees_getters_(tree_and_subtrees_getters),
vlog_stream_(vlog_stream) {}
auto Context::GetFileContext(const SemIR::File* file,
@@ -79,8 +78,7 @@ auto Context::BuildDICompileUnit(llvm::StringRef module_name,
auto Context::GetLocForDI(SemIR::AbsoluteNodeId abs_node_id) -> LocForDI {
const auto& tree_and_subtrees =
(*tree_and_subtrees_getters_for_debug_info_)[abs_node_id.check_ir_id()
.index]();
tree_and_subtrees_getters()[abs_node_id.check_ir_id().index]();
const auto& tokens = tree_and_subtrees.tree().tokens();
if (abs_node_id.node_id().has_value()) {
+11 -8
View File
@@ -41,11 +41,11 @@ class Context {
SemIR::SpecificId specific_id;
};
explicit Context(llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
tree_and_subtrees_getters_for_debug_info,
llvm::StringRef module_name, llvm::raw_ostream* vlog_stream);
explicit Context(
llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, bool want_debug_info,
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
llvm::StringRef module_name, llvm::raw_ostream* vlog_stream);
// Gets or creates the `FileContext` for a given SemIR file. If an
// `inst_namer` is specified the first time this is called for a file, it will
@@ -95,6 +95,10 @@ class Context {
}
auto di_builder() -> llvm::DIBuilder& { return di_builder_; }
auto di_compile_unit() -> llvm::DICompileUnit* { return di_compile_unit_; }
auto tree_and_subtrees_getters()
-> llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> {
return tree_and_subtrees_getters_;
}
auto printf_int_format_string() -> llvm::Value* {
return printf_int_format_string_;
@@ -128,9 +132,8 @@ class Context {
// The DICompileUnit, if any - null implies debug info is not being emitted.
llvm::DICompileUnit* di_compile_unit_;
// The trees are only provided when debug info should be emitted.
std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
tree_and_subtrees_getters_for_debug_info_;
// Parse trees. Used for debug information and crash diagnostics.
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters_;
// The optional vlog stream.
llvm::raw_ostream* vlog_stream_;
+23
View File
@@ -19,10 +19,12 @@
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/base/pretty_stack_trace_function.h"
#include "toolchain/lower/constant.h"
#include "toolchain/lower/function_context.h"
#include "toolchain/lower/mangler.h"
#include "toolchain/sem_ir/absolute_node_id.h"
#include "toolchain/sem_ir/diagnostic_loc_converter.h"
#include "toolchain/sem_ir/entry_point.h"
#include "toolchain/sem_ir/expr_info.h"
#include "toolchain/sem_ir/file.h"
@@ -33,6 +35,7 @@
#include "toolchain/sem_ir/inst_categories.h"
#include "toolchain/sem_ir/inst_kind.h"
#include "toolchain/sem_ir/pattern.h"
#include "toolchain/sem_ir/stringify.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Lower {
@@ -757,6 +760,26 @@ auto FileContext::BuildFunctionBody(SemIR::FunctionId function_id,
FileContext& definition_context,
const SemIR::Function& definition_function)
-> void {
// On crash, report the function we were lowering.
PrettyStackTraceFunction stack_trace_entry([&](llvm::raw_ostream& output) {
SemIR::DiagnosticLocConverter converter(
context().tree_and_subtrees_getters(), &sem_ir());
auto converted =
converter.Convert(SemIR::LocId(declaration_function.definition_id),
/*token_only=*/false);
converted.loc.FormatLocation(output);
output << "Lowering function ";
if (specific_id.has_value()) {
output << SemIR::StringifySpecific(sem_ir(), specific_id);
} else {
output << SemIR::StringifyConstantInst(
sem_ir(), declaration_function.definition_id);
}
output << "\n";
// Crash output has a tab indent; try to indent slightly past that.
converted.loc.FormatSnippet(output, /*indent=*/10);
});
// Note that `definition_function` is potentially from a different SemIR::File
// than the one that this file context represents. Any lowering done for
// values derived from `definition_function` should use `definition_context`
+20
View File
@@ -6,6 +6,8 @@
#include "common/vlog.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/base/pretty_stack_trace_function.h"
#include "toolchain/sem_ir/diagnostic_loc_converter.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/generic.h"
@@ -55,7 +57,25 @@ auto FunctionContext::TryToReuseBlock(SemIR::InstBlockId block_id,
}
auto FunctionContext::LowerBlockContents(SemIR::InstBlockId block_id) -> void {
auto inst_id_for_stack_trace = SemIR::InstId::None;
// On crash, report the instruction we were lowering.
PrettyStackTraceFunction stack_trace_entry([&](llvm::raw_ostream& output) {
SemIR::DiagnosticLocConverter converter(
file_context_->context().tree_and_subtrees_getters(), &sem_ir());
auto converted = converter.Convert(SemIR::LocId(inst_id_for_stack_trace),
/*token_only=*/false);
converted.loc.FormatLocation(output);
// TODO: Format SemIR for the instruction we were lowering?
output << "Lowering "
<< sem_ir().insts().Get(inst_id_for_stack_trace).kind().ir_name()
<< "\n";
// Crash output has a tab indent; try to indent slightly past that.
converted.loc.FormatSnippet(output, /*indent=*/10);
});
for (auto inst_id : sem_ir().inst_blocks().Get(block_id)) {
inst_id_for_stack_trace = inst_id;
LowerInst(inst_id);
}
}
+8 -10
View File
@@ -12,17 +12,15 @@
namespace Carbon::Lower {
auto LowerToLLVM(llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
tree_and_subtrees_getters_for_debug_info,
llvm::StringRef module_name, const SemIR::File& sem_ir,
const SemIR::InstNamer* inst_namer,
llvm::raw_ostream* vlog_stream)
auto LowerToLLVM(
llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, bool want_debug_info,
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
llvm::StringRef module_name, const SemIR::File& sem_ir,
const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream)
-> std::unique_ptr<llvm::Module> {
Context context(llvm_context, std::move(fs),
tree_and_subtrees_getters_for_debug_info, module_name,
vlog_stream);
Context context(llvm_context, std::move(fs), want_debug_info,
tree_and_subtrees_getters, module_name, vlog_stream);
context.GetFileContext(&sem_ir, inst_namer).LowerDefinitions();
return std::move(context).Finalize();
}
+6 -7
View File
@@ -15,13 +15,12 @@
namespace Carbon::Lower {
// Lowers SemIR to LLVM IR.
auto LowerToLLVM(llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
std::optional<llvm::ArrayRef<Parse::GetTreeAndSubtreesFn>>
tree_and_subtrees_getters_for_debug_info,
llvm::StringRef module_name, const SemIR::File& sem_ir,
const SemIR::InstNamer* inst_namer,
llvm::raw_ostream* vlog_stream)
auto LowerToLLVM(
llvm::LLVMContext& llvm_context,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs, bool want_debug_info,
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
llvm::StringRef module_name, const SemIR::File& sem_ir,
const SemIR::InstNamer* inst_namer, llvm::raw_ostream* vlog_stream)
-> std::unique_ptr<llvm::Module>;
} // namespace Carbon::Lower
+14
View File
@@ -228,6 +228,20 @@ cc_library(
],
)
cc_library(
name = "diagnostic_loc_converter",
srcs = ["diagnostic_loc_converter.cpp"],
hdrs = ["diagnostic_loc_converter.h"],
deps = [
":absolute_node_id",
":file",
":typed_insts",
"//toolchain/diagnostics:diagnostic_emitter",
"//toolchain/parse:tree",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "dump",
srcs = ["dump.cpp"],
+9 -2
View File
@@ -8,14 +8,21 @@
namespace Carbon::SemIR {
// Notes an import on the diagnostic. For `Cpp` imports, returns true. Otherwise
// updates cursors to point at the imported IR and returns false.
// Follows an imported instruction location to find the sequence of import
// locations and the ultimately imported location.
static auto FollowImportRef(
llvm::SmallVector<AbsoluteNodeId>& absolute_node_ids,
const File*& cursor_ir, InstId& cursor_inst_id,
ImportIRInstId import_ir_inst_id) -> bool {
auto import_ir_inst = cursor_ir->import_ir_insts().Get(import_ir_inst_id);
if (import_ir_inst.ir_id() == ImportIRId::Cpp) {
CARBON_CHECK(cursor_ir->import_cpps().size() > 0);
// TODO: Decompose the Clang source location to determine which C++ import
// made this location available, and use the location of that import instead
// of arbitrarily using the first C++ import.
absolute_node_ids.push_back(
AbsoluteNodeId(cursor_ir->check_ir_id(),
cursor_ir->import_cpps().values().begin()->node_id));
absolute_node_ids.push_back(
AbsoluteNodeId(import_ir_inst.clang_source_loc_id()));
return true;
@@ -0,0 +1,77 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/sem_ir/diagnostic_loc_converter.h"
namespace Carbon::SemIR {
auto DiagnosticLocConverter::ConvertWithImports(LocId loc_id,
bool token_only) const
-> LocAndImports {
llvm::SmallVector<SemIR::AbsoluteNodeId> absolute_node_ids =
SemIR::GetAbsoluteNodeId(sem_ir_, loc_id);
auto final_node_id = absolute_node_ids.pop_back_val();
// Convert the final location.
LocAndImports result = {.loc = Convert(final_node_id, token_only)};
// Convert the import locations.
for (const auto& absolute_node_id : absolute_node_ids) {
if (!absolute_node_id.node_id().has_value()) {
// TODO: Add an `ImportLoc` pointing at the prelude for the case where
// we don't have a location.
continue;
}
result.imports.push_back({.loc = Convert(absolute_node_id, false).loc});
}
return result;
}
auto DiagnosticLocConverter::Convert(LocId loc_id, bool token_only) const
-> Diagnostics::ConvertedLoc {
llvm::SmallVector<SemIR::AbsoluteNodeId> absolute_node_ids =
SemIR::GetAbsoluteNodeId(sem_ir_, loc_id);
return Convert(absolute_node_ids.back(), token_only);
}
auto DiagnosticLocConverter::Convert(SemIR::AbsoluteNodeId absolute_node_id,
bool token_only) const
-> Diagnostics::ConvertedLoc {
if (absolute_node_id.check_ir_id() == SemIR::CheckIRId::Cpp) {
return Convert(absolute_node_id.clang_source_loc_id());
}
return Convert(absolute_node_id.check_ir_id(), absolute_node_id.node_id(),
token_only);
}
auto DiagnosticLocConverter::Convert(SemIR::CheckIRId check_ir_id,
Parse::NodeId node_id,
bool token_only) const
-> Diagnostics::ConvertedLoc {
CARBON_CHECK(check_ir_id != SemIR::CheckIRId::Cpp);
const auto& tree_and_subtrees =
tree_and_subtrees_getters_[check_ir_id.index]();
return tree_and_subtrees.NodeToDiagnosticLoc(node_id, token_only);
}
auto DiagnosticLocConverter::Convert(ClangSourceLocId clang_source_loc_id) const
-> Diagnostics::ConvertedLoc {
clang::SourceLocation clang_loc =
sem_ir_->clang_source_locs().Get(clang_source_loc_id);
CARBON_CHECK(sem_ir_->cpp_ast());
clang::PresumedLoc presumed_loc =
sem_ir_->cpp_ast()->getSourceManager().getPresumedLoc(clang_loc);
unsigned offset =
sem_ir_->cpp_ast()->getSourceManager().getDecomposedLoc(clang_loc).second;
return Diagnostics::ConvertedLoc{
.loc = {.filename = presumed_loc.getFilename(),
.line_number = static_cast<int32_t>(presumed_loc.getLine())},
.last_byte_offset = static_cast<int32_t>(offset)};
}
} // namespace Carbon::SemIR
@@ -0,0 +1,77 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_TOOLCHAIN_SEM_IR_DIAGNOSTIC_LOC_CONVERTER_H_
#define CARBON_TOOLCHAIN_SEM_IR_DIAGNOSTIC_LOC_CONVERTER_H_
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "toolchain/diagnostics/diagnostic_emitter.h"
#include "toolchain/parse/tree_and_subtrees.h"
#include "toolchain/sem_ir/absolute_node_id.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::SemIR {
// Converter from compact location information into a diagnostic location
// describing a filename, line location, and potentially a sequence of imports.
// Such diagnostics locations are used to render user-facing diagnostics and
// also locations for stack trace in crash diagnostics.
class DiagnosticLocConverter {
public:
// Information about an import within which the location was found.
struct ImportLoc {
Diagnostics::Loc loc;
// TODO: Include the name of the imported library in this information so it
// can be included in the diagnostic.
};
// Information about a location that has been converted from a LocId to a
// diagnostic location.
struct LocAndImports {
llvm::SmallVector<ImportLoc> imports;
Diagnostics::ConvertedLoc loc;
};
// `sem_ir` must not be null.
explicit DiagnosticLocConverter(
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters,
const File* sem_ir)
: tree_and_subtrees_getters_(tree_and_subtrees_getters),
sem_ir_(sem_ir) {}
// Converts the given location into a sequence of import locations and a final
// diagnostic location.
auto ConvertWithImports(LocId loc_id, bool token_only) const -> LocAndImports;
// Converts the given location into a diagnostic location.
auto Convert(LocId loc_id, bool token_only) const
-> Diagnostics::ConvertedLoc;
// Converts an `absolute_node_id` in either a Carbon file or C++ import to a
// diagnostic location.
auto Convert(AbsoluteNodeId absolute_node_id, bool token_only) const
-> Diagnostics::ConvertedLoc;
// Converts a `node_id` corresponding to a specific check IR to a diagnostic
// location.
auto Convert(CheckIRId check_ir_id, Parse::NodeId node_id,
bool token_only) const -> Diagnostics::ConvertedLoc;
// Converts a location pointing into C++ code to a diagnostic location.
auto Convert(ClangSourceLocId clang_source_loc_id) const
-> Diagnostics::ConvertedLoc;
private:
// Converters for each SemIR.
llvm::ArrayRef<Parse::GetTreeAndSubtreesFn> tree_and_subtrees_getters_;
// The current SemIR being processed.
const File* sem_ir_;
};
} // namespace Carbon::SemIR
#endif // CARBON_TOOLCHAIN_SEM_IR_DIAGNOSTIC_LOC_CONVERTER_H_