diff --git a/toolchain/check/check.cpp b/toolchain/check/check.cpp index 14728ebb9a22..1fb42f89e5ae 100644 --- a/toolchain/check/check.cpp +++ b/toolchain/check/check.cpp @@ -706,12 +706,24 @@ auto NodeIdTraversal::Next() -> std::optional { // for example if an unrecoverable state is encountered. // NOLINTNEXTLINE(readability-function-size) static auto ProcessNodeIds(Context& context, llvm::raw_ostream* vlog_stream, - ErrorTrackingDiagnosticConsumer& err_tracker) - -> bool { + ErrorTrackingDiagnosticConsumer& err_tracker, + Parse::NodeLocConverter* converter) -> bool { NodeIdTraversal traversal(context, vlog_stream); + Parse::NodeId node_id = Parse::NodeId::Invalid; + + // On crash, report which token we were handling. + PrettyStackTraceFunction node_dumper([&](llvm::raw_ostream& output) { + auto loc = converter->ConvertLoc( + node_id, [](DiagnosticLoc, const Internal::DiagnosticBase<>&) {}); + loc.FormatLocation(output); + output << ": Check::Handle" << context.parse_tree().node_kind(node_id) + << "\n"; + loc.FormatSnippet(output); + }); + while (auto maybe_node_id = traversal.Next()) { - auto node_id = *maybe_node_id; + node_id = *maybe_node_id; auto parse_kind = context.parse_tree().node_kind(node_id); switch (parse_kind) { @@ -762,7 +774,8 @@ static auto CheckParseTree( // TODO: Do this selectively when we see an impl query. ImportImpls(context); - if (!ProcessNodeIds(context, vlog_stream, unit_info.err_tracker)) { + if (!ProcessNodeIds(context, vlog_stream, unit_info.err_tracker, + &unit_info.converter)) { context.sem_ir().set_has_errors(true); return; } diff --git a/toolchain/diagnostics/BUILD b/toolchain/diagnostics/BUILD index 08e2e94d15a8..32ee2f31ea58 100644 --- a/toolchain/diagnostics/BUILD +++ b/toolchain/diagnostics/BUILD @@ -13,7 +13,10 @@ filegroup( cc_library( name = "diagnostic_emitter", - srcs = ["diagnostic_consumer.cpp"], + srcs = [ + "diagnostic.cpp", + "diagnostic_consumer.cpp", + ], hdrs = [ "diagnostic.h", "diagnostic_consumer.h", diff --git a/toolchain/diagnostics/diagnostic.cpp b/toolchain/diagnostics/diagnostic.cpp new file mode 100644 index 000000000000..1eaadc5e3a9e --- /dev/null +++ b/toolchain/diagnostics/diagnostic.cpp @@ -0,0 +1,45 @@ +// 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/diagnostics/diagnostic.h" + +#include +#include + +namespace Carbon { + +auto DiagnosticLoc::FormatLocation(llvm::raw_ostream& out) const -> void { + out << filename; + if (line_number > 0) { + out << ":" << line_number; + if (column_number > 0) { + out << ":" << column_number; + } + } +} + +auto DiagnosticLoc::FormatSnippet(llvm::raw_ostream& out) const -> void { + if (column_number <= 0) { + return; + } + + // column_number is 1-based. + int32_t column = column_number - 1; + + out << line << "\n"; + out.indent(column); + out << "^"; + // We want to ensure that we don't underline past the end of the line in + // case of a multiline token. + // TODO: revisit this once we can reference multiple ranges on multiple + // lines in a single diagnostic message. + int underline_length = + std::min(length, static_cast(line.size()) - column); + for (int i = 1; i < underline_length; ++i) { + out << '~'; + } + out << '\n'; +} + +} // namespace Carbon diff --git a/toolchain/diagnostics/diagnostic.h b/toolchain/diagnostics/diagnostic.h index 11d35adf462c..8f06a0a3a657 100644 --- a/toolchain/diagnostics/diagnostic.h +++ b/toolchain/diagnostics/diagnostic.h @@ -46,6 +46,14 @@ enum class DiagnosticLevel : int8_t { // is required to be less than SourceBuffer that it refers to due to the // contained filename and line references. struct DiagnosticLoc { + // Write the filename, line number, and column number corresponding to this + // location to the given stream. + auto FormatLocation(llvm::raw_ostream& out) const -> void; + + // Write the source snippet corresponding to this location to the given + // stream. + auto FormatSnippet(llvm::raw_ostream& out) const -> void; + // Name of the file or buffer that this diagnostic refers to. llvm::StringRef filename; // A reference to the line of the error. diff --git a/toolchain/diagnostics/diagnostic_consumer.cpp b/toolchain/diagnostics/diagnostic_consumer.cpp index c8bed5136eaf..26e94af01967 100644 --- a/toolchain/diagnostics/diagnostic_consumer.cpp +++ b/toolchain/diagnostics/diagnostic_consumer.cpp @@ -17,35 +17,13 @@ auto StreamDiagnosticConsumer::HandleDiagnostic(Diagnostic diagnostic) -> void { } for (const auto& message : diagnostic.messages) { - *stream_ << message.loc.filename; - if (message.loc.line_number > 0) { - *stream_ << ":" << message.loc.line_number; - if (message.loc.column_number > 0) { - *stream_ << ":" << message.loc.column_number; - } - } + message.loc.FormatLocation(*stream_); *stream_ << ": "; if (message.level == DiagnosticLevel::Error) { *stream_ << "ERROR: "; } *stream_ << message.format_fn(message) << "\n"; - if (message.loc.column_number > 0) { - *stream_ << message.loc.line << "\n"; - stream_->indent(message.loc.column_number - 1); - *stream_ << "^"; - int underline_length = std::max(0, message.loc.length - 1); - // We want to ensure that we don't underline past the end of the line in - // case of a multiline token. - // TODO: revisit this once we can reference multiple ranges on multiple - // lines in a single diagnostic message. - underline_length = std::min( - underline_length, static_cast(message.loc.line.size()) - - message.loc.column_number); - for (int i = 0; i < underline_length; ++i) { - *stream_ << "~"; - } - *stream_ << "\n"; - } + message.loc.FormatSnippet(*stream_); } }