Files
carbon-lang/toolchain/source/source_buffer.cpp
T
Jon Ross-Perkins 8f685b6953 Change how diagnostics are ordered (#4778)
This change deliberately breaks away from the line/column ordering, and
instead focuses on a last byte offset corresponding to the final token
processed as part of producing the message. Where that's equal, this
maintains stable ordering in order to reflect the order that diagnostics
were produced.

The intent of this approach is that lex, parse, and check diagnostics
are interleaved based on where they are produced, but that
subexpressions still have diagnostics emitted prior to containing
expressions. In particular, the prior line/column sort essentially
sorted on the _start_ of where a diagnostic was associated, and this is
closer to sorting based on the _end_. As a consequence, something like
`F(1 2)` will have the error for `1 2` emitted _before_ a diagnostic for
`F(1 2)` not matching parameters, instead of _after_.

In check, we track the last handled node. This provides a
last_byte_offset _separate_ from where a diagnostic is associated. The
intent is that this creates an ordering of diagnostics which may be
associated with earlier code, to cause the diagnostics to be emitted
later. An example consequence of this is the change in ordering of
modifier diagnostics: we are diagnosing those from the same place, but
they have the same last_byte_offset, so we print them out in the order
produced.

I've added similar tracking to parse, but cannot identify any test which
is affected by it (note the separate commit, I thought about this late).
I'm not sure whether we have good out-of-order errors we could produce
for this.

A significant number of tests have reordered diagnostics as a
consequence of this change, so this change does not add further testing.
2025-01-10 18:36:24 +00:00

89 lines
3.2 KiB
C++

// 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/source/source_buffer.h"
#include <limits>
#include "llvm/Support/ErrorOr.h"
namespace Carbon {
namespace {
struct FilenameConverter : DiagnosticConverter<llvm::StringRef> {
auto ConvertLoc(llvm::StringRef filename, ContextFnT /*context_fn*/) const
-> ConvertedDiagnosticLoc override {
return {.loc = {.filename = filename}, .last_byte_offset = -1};
}
};
} // namespace
auto SourceBuffer::MakeFromStdin(DiagnosticConsumer& consumer)
-> std::optional<SourceBuffer> {
return MakeFromMemoryBuffer(llvm::MemoryBuffer::getSTDIN(), "<stdin>",
/*is_regular_file=*/false, consumer);
}
auto SourceBuffer::MakeFromFile(llvm::vfs::FileSystem& fs,
llvm::StringRef filename,
DiagnosticConsumer& consumer)
-> std::optional<SourceBuffer> {
FilenameConverter converter;
DiagnosticEmitter<llvm::StringRef> emitter(converter, consumer);
llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> file =
fs.openFileForRead(filename);
if (file.getError()) {
CARBON_DIAGNOSTIC(ErrorOpeningFile, Error,
"error opening file for read: {0}", std::string);
emitter.Emit(filename, ErrorOpeningFile, file.getError().message());
return std::nullopt;
}
llvm::ErrorOr<llvm::vfs::Status> status = (*file)->status();
if (status.getError()) {
CARBON_DIAGNOSTIC(ErrorStattingFile, Error, "error statting file: {0}",
std::string);
emitter.Emit(filename, ErrorStattingFile, file.getError().message());
return std::nullopt;
}
// `stat` on a file without a known size gives a size of 0, which causes
// `llvm::vfs::File::getBuffer` to produce an empty buffer. Use a size of -1
// in this case so we get the complete file contents.
bool is_regular_file = status->isRegularFile();
int64_t size = is_regular_file ? status->getSize() : -1;
return MakeFromMemoryBuffer(
(*file)->getBuffer(filename, size, /*RequiresNullTerminator=*/false),
filename, is_regular_file, consumer);
}
auto SourceBuffer::MakeFromMemoryBuffer(
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer,
llvm::StringRef filename, bool is_regular_file,
DiagnosticConsumer& consumer) -> std::optional<SourceBuffer> {
FilenameConverter converter;
DiagnosticEmitter<llvm::StringRef> emitter(converter, consumer);
if (buffer.getError()) {
CARBON_DIAGNOSTIC(ErrorReadingFile, Error, "error reading file: {0}",
std::string);
emitter.Emit(filename, ErrorReadingFile, buffer.getError().message());
return std::nullopt;
}
if (buffer.get()->getBufferSize() >= std::numeric_limits<int32_t>::max()) {
CARBON_DIAGNOSTIC(FileTooLarge, Error,
"file is over the 2GiB input limit; size is {0} bytes",
int64_t);
emitter.Emit(filename, FileTooLarge, buffer.get()->getBufferSize());
return std::nullopt;
}
return SourceBuffer(filename.str(), std::move(buffer.get()), is_regular_file);
}
} // namespace Carbon