Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustc_errors: let DiagnosticBuilder::emit return a "guarantee of emission". #93368

Merged
merged 6 commits into from Feb 25, 2022

Conversation

eddyb
Copy link
Member

@eddyb eddyb commented Jan 27, 2022

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
      Make emitting an error necessary to acquire a ErrorReported token #69426)
  • DiagnosticBuilder<()> for everything else (warnings, notes, etc.)
    • can also be obtained from other DiagnosticBuilders by calling .forget_guarantee()

This PR is a companion to other ongoing work, namely:

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 (I tried and gave up, it's much more work than this PR),
    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 rustc_errors: only box the diagnostic field in DiagnosticBuilder. #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

@rustbot rustbot added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels Jan 27, 2022
@rust-highfive
Copy link
Collaborator

Some changes occurred in src/tools/rustfmt.

cc @calebcartwright

Some changes occured to the CTFE / Miri engine

cc @rust-lang/miri

Some changes occurred in src/tools/clippy.

cc @rust-lang/clippy

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 27, 2022
@eddyb
Copy link
Member Author

eddyb commented Jan 27, 2022

@bors try @rust-timer queue

@rust-timer
Copy link
Collaborator

Awaiting bors try build completion.

@rustbot label: +S-waiting-on-perf

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jan 27, 2022
@bors
Copy link
Contributor

bors commented Jan 27, 2022

⌛ Trying commit 0b5259b113998b904e2d715960ccad75cddf7db5 with merge d71c4f293fafbe0f5a5edbb5894e9f489269697b...

@rust-log-analyzer

This comment has been minimized.

@bors
Copy link
Contributor

bors commented Jan 27, 2022

☀️ Try build successful - checks-actions
Build commit: d71c4f293fafbe0f5a5edbb5894e9f489269697b (d71c4f293fafbe0f5a5edbb5894e9f489269697b)

@rust-timer
Copy link
Collaborator

Queued d71c4f293fafbe0f5a5edbb5894e9f489269697b with parent 21b4a9c, future comparison URL.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (d71c4f293fafbe0f5a5edbb5894e9f489269697b): comparison url.

Summary: This benchmark run did not return any relevant results.

If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf.

Benchmarking this pull request likely means that it is perf-sensitive, so we're automatically marking it as not fit for rolling up. While you can manually mark this PR as fit for rollup, we strongly recommend not doing so since this PR led to changes in compiler perf.

@bors rollup=never
@rustbot label: +S-waiting-on-review -S-waiting-on-perf -perf-regression

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jan 27, 2022
@eddyb
Copy link
Member Author

eddyb commented Jan 27, 2022

That "incremental" test failure (not an issue with incremental, to be clear, just output formatting) was interesting. I just fixed it and had to change a few details, which results in this change in how delay_span_bug is reported:

(click to expand before/after)
  • before:
$ rustc src/test/incremental/delayed_span_bug.rs 
error: internal compiler error: delayed span bug triggered by #[rustc_error(delay_span_bug_from_inside_query)]
 --> src/test/incremental/delayed_span_bug.rs:8:1
  |
8 | fn main() {}
  | ^^^^^^^^^
  |
  = note: delayed at compiler/rustc_middle/src/util/bug.rs:45:14

thread 'rustc' panicked at 'no errors encountered even though `delay_span_bug` issued', compiler/rustc_errors/src/lib.rs:1188:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

error: internal compiler error: unexpected panic

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md

note: rustc 1.60.0-nightly (bfe156467 2022-01-22) running on x86_64-unknown-linux-gnu

query stack during panic:
end of query stack
  • after:
$ rustc +A src/test/incremental/delayed_span_bug.rs
error: internal compiler error: no errors encountered even though `delay_span_bug` issued

error: internal compiler error: delayed span bug triggered by #[rustc_error(delay_span_bug_from_inside_query)]
 --> src/test/incremental/delayed_span_bug.rs:8:1
  |
8 | fn main() {}
  | ^^^^^^^^^
  |
  = note: delayed at compiler/rustc_middle/src/util/bug.rs:45:14

thread 'rustc' panicked at 'Box<dyn Any>', compiler/rustc_errors/src/lib.rs:1205:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md

note: rustc 1.60.0-dev running on x86_64-unknown-linux-gnu

query stack during panic:
end of query stack

The difference is even more visible with colors:
image

In particular, the indication that delay_span_bug was tripped, is before the delayed ICEs:

error: internal compiler error: no errors encountered even though `delay_span_bug` issued

(Also we could probably replace error: internal compiler error: with compiler bug: to be more compact but that kind of change doesn't really belong in this PR, I got carried away as it is...)

@rust-log-analyzer

This comment has been minimized.

@bors

This comment has been minimized.

@bors
Copy link
Contributor

bors commented Feb 8, 2022

☔ The latest upstream changes (presumably #93762) made this pull request unmergeable. Please resolve the merge conflicts.

@nikomatsakis
Copy link
Contributor

This is nice =)

Comment on lines +949 to +932
// FIXME(eddyb) this should check for `has_errors` and stop pushing
// once *any* errors were emitted (and truncate `delayed_span_bugs`
// when an error is first emitted, also), but maybe there's a case
// in which that's not sound? otherwise this is really inefficient.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think doing that change would cause -Ztreat-err-as-bug=N stop working correctly to stop for a specific N.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think -Ztreat-err-as-bug cares about delay_span_bug at all? Like, the latter pushes to a separate buffer, my comment is about the inefficiency of keeping those around once they've become irrelevant. You can't actually get any behavior from delay_span_bug unless no errors were emitted at all, and -Ztreat-err-as-bug only affects real errors.

@@ -121,7 +121,7 @@ pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String
Ok(..) => {}
Err(err) => err.cancel(),
},
Err(errs) => errs.into_iter().for_each(|mut err| err.cancel()),
Err(errs) => drop(errs),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gives me a second of pause: does it mean that drop(err) is a valid cancelation methodology? Doesn't that go against what you're trying to accomplish?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.cancel() was being called on a Diagnostic, not a DiagnosticBuilder. It basically was doing absolutely nothing before (other than writing to plain memory nothing will look at afterwards), I could've just as well replaced this with Err(_) => {}.

Perhaps some interface is confused about what types it should've used?

@estebank
Copy link
Contributor

That was quite a review ^_^'

r=me after rebase

@estebank estebank added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 17, 2022
@bors
Copy link
Contributor

bors commented Feb 25, 2022

⌛ Testing commit b7e95de with merge d4de1f2...

@bors
Copy link
Contributor

bors commented Feb 25, 2022

☀️ Test successful - checks-actions
Approved by: estebank
Pushing d4de1f2 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Feb 25, 2022
@bors bors merged commit d4de1f2 into rust-lang:master Feb 25, 2022
@rustbot rustbot added this to the 1.61.0 milestone Feb 25, 2022
@rust-highfive
Copy link
Collaborator

📣 Toolstate changed by #93368!

Tested on commit d4de1f2.
Direct link to PR: #93368

💔 miri on windows: test-fail → build-fail (cc @eddyb @oli-obk @RalfJung).
💔 miri on linux: test-fail → build-fail (cc @eddyb @oli-obk @RalfJung).

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (d4de1f2): comparison url.

Summary: This benchmark run shows 1 relevant improvement 🎉 but 4 relevant regressions 😿 to instruction counts.

  • Arithmetic mean of relevant regressions: 0.8%
  • Arithmetic mean of all relevant changes: 0.5%
  • Largest improvement in instruction counts: -0.5% on full builds of deeply-nested-async debug
  • Largest regression in instruction counts: 0.9% on incr-unchanged builds of externs debug

If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf.

Next Steps: If you can justify the regressions found in this perf run, please indicate this with @rustbot label: +perf-regression-triaged along with sufficient written justification. If you cannot justify the regressions please open an issue or create a new PR that fixes the regressions, add a comment linking to the newly created issue or PR, and then add the perf-regression-triaged label to this PR.

@rustbot label: +perf-regression

@rustbot rustbot added the perf-regression Performance regression. label Feb 25, 2022
@eddyb eddyb deleted the diagbld-guarantee branch February 25, 2022 04:51
@eddyb
Copy link
Member Author

eddyb commented Feb 25, 2022

Is the externs benchmark unreliable? The previous run (#93368 (comment)) came out clean.

@estebank
Copy link
Contributor

@pnkfelix do you know who might have context to answer @eddyb's question above?

@erikdesjardins
Copy link
Contributor

erikdesjardins commented Feb 26, 2022

I saw the exact same externs regression in #94130. Cachegrind diffs of this PR and that PR's perf runs look very similar--the regression is caused by the TLS __getit function not being inlined at a callsite that it was before. (See the bottom of #94130 (comment) -- that PR had other legitimate perf changes, but the externs one is the same.)

So I opened #94373, which makes __getit #[inline(always)], and it...didn't affect externs. Probably because I got unlucky, and it started getting inlined again on its own, for whatever reason.

So I would say it looks like externs is unreliable right now.

#94373 has minimal perf impact (no regressions), and may eliminate this flakiness, so it might be worth merging.

@Mark-Simulacrum Mark-Simulacrum removed the perf-regression Performance regression. label Mar 1, 2022
bors added a commit to rust-lang-ci/rust that referenced this pull request Mar 7, 2022
[beta] backport fix for rust-lang#94502

this issue was fixed as part of rust-lang#93368, so i extracted the change from there

closes rust-lang#94502
flip1995 pushed a commit to flip1995/rust that referenced this pull request Mar 14, 2022
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
     rust-lang#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:
* rust-lang#69426
  and it's ongoing implementation:
  rust-lang#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
* rust-lang#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 rust-lang#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`
dario23 added a commit to dario23/rust-semverver that referenced this pull request May 4, 2022
dario23 added a commit to dario23/rust-semverver that referenced this pull request May 6, 2022
Change the trait_impls stdout file back, the `Display` impls have gotten
changed back in the meantime it seems.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet