Auto merge of #93368 - eddyb:diagbld-guarantee, r=estebank
rustc_errors: let `DiagnosticBuilder::emit` return a "guarantee of emission".
That is, `DiagnosticBuilder` is now generic over the return type of `.emit()`, so we'll now have:
* `DiagnosticBuilder<ErrorReported>` for error (incl. fatal/bug) diagnostics
* can only be created via a `const L: Level`-generic constructor, that limits allowed variants via a `where` clause, so not even `rustc_errors` can accidentally bypass this limitation
* asserts `diagnostic.is_error()` on emission, just in case the construction restriction was bypassed (e.g. by replacing the whole `Diagnostic` inside `DiagnosticBuilder`)
* `.emit()` returns `ErrorReported`, as a "proof" token that `.emit()` was called
(though note that this isn't a real guarantee until after completing the work on
#69426)
* `DiagnosticBuilder<()>` for everything else (warnings, notes, etc.)
* can also be obtained from other `DiagnosticBuilder`s by calling `.forget_guarantee()`
This PR is a companion to other ongoing work, namely:
* #69426
and it's ongoing implementation:
#93222
the API changes in this PR are needed to get statically-checked "only errors produce `ErrorReported` from `.emit()`", but doesn't itself provide any really strong guarantees without those other `ErrorReported` changes
* #93244
would make the choices of API changes (esp. naming) in this PR fit better overall
In order to be able to let `.emit()` return anything trustable, several changes had to be made:
* `Diagnostic`'s `level` field is now private to `rustc_errors`, to disallow arbitrary "downgrade"s from "some kind of error" to "warning" (or anything else that doesn't cause compilation to fail)
* it's still possible to replace the whole `Diagnostic` inside the `DiagnosticBuilder`, sadly, that's harder to fix, but it's unlikely enough that we can paper over it with asserts on `.emit()`
* `.cancel()` now consumes `DiagnosticBuilder`, preventing `.emit()` calls on a cancelled diagnostic
* it's also now done internally, through `DiagnosticBuilder`-private state, instead of having a `Level::Cancelled` variant that can be read (or worse, written) by the user
* this removes a hazard of calling `.cancel()` on an error then continuing to attach details to it, and even expect to be able to `.emit()` it
* warnings were switched to *only* `can_emit_warnings` on emission (instead of pre-cancelling early)
* `struct_dummy` was removed (as it relied on a pre-`Cancelled` `Diagnostic`)
* since `.emit()` doesn't consume the `DiagnosticBuilder` <sub>(I tried and gave up, it's much more work than this PR)</sub>,
we have to make `.emit()` idempotent wrt the guarantees it returns
* thankfully, `err.emit(); err.emit();` can return `ErrorReported` both times, as the second `.emit()` call has no side-effects *only* because the first one did do the appropriate emission
* `&mut Diagnostic` is now used in a lot of function signatures, which used to take `&mut DiagnosticBuilder` (in the interest of not having to make those functions generic)
* the APIs were already mostly identical, allowing for low-effort porting to this new setup
* only some of the suggestion methods needed some rework, to have the extra `DiagnosticBuilder` functionality on the `Diagnostic` methods themselves (that change is also present in #93259)
* `.emit()`/`.cancel()` aren't available, but IMO calling them from an "error decorator/annotator" function isn't a good practice, and can lead to strange behavior (from the caller's perspective)
* `.downgrade_to_delayed_bug()` was added, letting you convert any `.is_error()` diagnostic into a `delay_span_bug` one (which works because in both cases the guarantees available are the same)
This PR should ideally be reviewed commit-by-commit, since there is a lot of fallout in each.
r? `@estebank` cc `@Manishearth` `@nikomatsakis` `@mark-i-m`
This commit is contained in:
@@ -379,7 +379,7 @@ fn do_mir_borrowck<'a, 'tcx>(
|
||||
// Convert any reservation warnings into lints.
|
||||
let reservation_warnings = mem::take(&mut mbcx.reservation_warnings);
|
||||
for (_, (place, span, location, bk, borrow)) in reservation_warnings {
|
||||
let mut initial_diag = mbcx.report_conflicting_borrow(location, (place, span), bk, &borrow);
|
||||
let initial_diag = mbcx.report_conflicting_borrow(location, (place, span), bk, &borrow);
|
||||
|
||||
let scope = mbcx.body.source_info(location).scope;
|
||||
let lint_root = match &mbcx.body.source_scopes[scope].local_data {
|
||||
@@ -398,7 +398,7 @@ fn do_mir_borrowck<'a, 'tcx>(
|
||||
diag.message = initial_diag.styled_message().clone();
|
||||
diag.span = initial_diag.span.clone();
|
||||
|
||||
mbcx.buffer_error(diag);
|
||||
mbcx.buffer_non_error_diag(diag);
|
||||
},
|
||||
);
|
||||
initial_diag.cancel();
|
||||
@@ -2293,8 +2293,8 @@ mod error {
|
||||
/// when errors in the map are being re-added to the error buffer so that errors with the
|
||||
/// same primary span come out in a consistent order.
|
||||
buffered_move_errors:
|
||||
BTreeMap<Vec<MoveOutIndex>, (PlaceRef<'tcx>, DiagnosticBuilder<'tcx>)>,
|
||||
/// Errors to be reported buffer
|
||||
BTreeMap<Vec<MoveOutIndex>, (PlaceRef<'tcx>, DiagnosticBuilder<'tcx, ErrorReported>)>,
|
||||
/// Diagnostics to be reported buffer.
|
||||
buffered: Vec<Diagnostic>,
|
||||
/// Set to Some if we emit an error during borrowck
|
||||
tainted_by_errors: Option<ErrorReported>,
|
||||
@@ -2309,27 +2309,37 @@ mod error {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_>) {
|
||||
// FIXME(eddyb) this is a suboptimal API because `tainted_by_errors` is
|
||||
// set before any emission actually happens (weakening the guarantee).
|
||||
pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_, ErrorReported>) {
|
||||
self.tainted_by_errors = Some(ErrorReported {});
|
||||
t.buffer(&mut self.buffered);
|
||||
}
|
||||
|
||||
pub fn buffer_non_error_diag(&mut self, t: DiagnosticBuilder<'_, ()>) {
|
||||
t.buffer(&mut self.buffered);
|
||||
}
|
||||
|
||||
pub fn set_tainted_by_errors(&mut self) {
|
||||
self.tainted_by_errors = Some(ErrorReported {});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
|
||||
pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_>) {
|
||||
pub fn buffer_error(&mut self, t: DiagnosticBuilder<'_, ErrorReported>) {
|
||||
self.errors.buffer_error(t);
|
||||
}
|
||||
|
||||
pub fn buffer_non_error_diag(&mut self, t: DiagnosticBuilder<'_, ()>) {
|
||||
self.errors.buffer_non_error_diag(t);
|
||||
}
|
||||
|
||||
pub fn buffer_move_error(
|
||||
&mut self,
|
||||
move_out_indices: Vec<MoveOutIndex>,
|
||||
place_and_err: (PlaceRef<'tcx>, DiagnosticBuilder<'tcx>),
|
||||
place_and_err: (PlaceRef<'tcx>, DiagnosticBuilder<'tcx, ErrorReported>),
|
||||
) -> bool {
|
||||
if let Some((_, mut diag)) =
|
||||
if let Some((_, diag)) =
|
||||
self.errors.buffered_move_errors.insert(move_out_indices, place_and_err)
|
||||
{
|
||||
// Cancel the old diagnostic so we don't ICE
|
||||
@@ -2365,7 +2375,7 @@ mod error {
|
||||
pub fn has_move_error(
|
||||
&self,
|
||||
move_out_indices: &[MoveOutIndex],
|
||||
) -> Option<&(PlaceRef<'tcx>, DiagnosticBuilder<'cx>)> {
|
||||
) -> Option<&(PlaceRef<'tcx>, DiagnosticBuilder<'cx, ErrorReported>)> {
|
||||
self.errors.buffered_move_errors.get(move_out_indices)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user