pattern_analysis: rework how we hide empty private fields
Consider this:
```rust
mod foo {
pub struct Bar {
pub a: bool,
b: !,
}
}
fn match_a_bar(bar: foo::Bar) -> bool {
match bar {
Bar { a, .. } => a,
}
}
```
Because the field `b` is private, matches outside the module are not allowed to observe the fact that `Bar` is empty. In particular `match bar {}` is valid within the module `foo` but an error outside (assuming `exhaustive_patterns`).
We currently handle this by hiding the field `b` when it's both private and empty. This means that the pattern `Bar { a, .. }` is lowered to `Bar(a, _)` if we're inside of `foo` and to `Bar(a)` outside. This involves a bit of a dance to keep field indices straight. But most importantly this makes pattern lowering depend on the module.
In this PR, I instead do nothing special when lowering. Only during analysis do we track whether a place must be skipped.
r? `@compiler-errors`
Overhaul `Diagnostic` and `DiagnosticBuilder`
Implements the first part of https://github.com/rust-lang/compiler-team/issues/722, which moves functionality and use away from `Diagnostic`, onto `DiagnosticBuilder`.
Likely follow-ups:
- Move things around, because this PR was written to minimize diff size, so some things end up in sub-optimal places. E.g. `DiagnosticBuilder` has impls in both `diagnostic.rs` and `diagnostic_builder.rs`.
- Rename `Diagnostic` as `DiagInner` and `DiagnosticBuilder` as `Diag`.
r? `@davidtwco`
pattern_analysis: Move constructor selection logic to `PlaceInfo`
This is a small refactor PR. There was a dense bit of constructor-related logic in `compute_exhaustiveness_and_usefulness`. I'm moving it out into a `PlaceInfo` method to make it easier to follow both separately. I also have plans that will complicate it further so it's good that it's somewhat encapsulated.
r? `@compiler-errors`
Currently many diagnostic modifier methods are available on both
`Diagnostic` and `DiagnosticBuilder`. This commit removes most of them
from `Diagnostic`. To minimize the diff size, it keeps them within
`diagnostic.rs` but changes the surrounding `impl Diagnostic` block to
`impl DiagnosticBuilder`. (I intend to move things around later, to give
a more sensible code layout.)
`Diagnostic` keeps a few methods that it still needs, like `sub`,
`arg`, and `replace_args`.
The `forward!` macro, which defined two additional methods per call
(e.g. `note` and `with_note`), is replaced by the `with_fn!` macro,
which defines one additional method per call (e.g. `with_note`). It's
now also only used when necessary -- not all modifier methods currently
need a `with_*` form. (New ones can be easily added as necessary.)
All this also requires changing `trait AddToDiagnostic` so its methods
take `DiagnosticBuilder` instead of `Diagnostic`, which leads to many
mechanical changes. `SubdiagnosticMessageOp` gains a type parameter `G`.
There are three subdiagnostics -- `DelayedAtWithoutNewline`,
`DelayedAtWithNewline`, and `InvalidFlushedDelayedDiagnosticLevel` --
that are created within the diagnostics machinery and appended to
external diagnostics. These are handled at the `Diagnostic` level, which
means it's now hard to construct them via `derive(Diagnostic)`, so
instead we construct them by hand. This has no effect on what they look
like when printed.
There are lots of new `allow` markers for `untranslatable_diagnostics`
and `diagnostics_outside_of_impl`. This is because
`#[rustc_lint_diagnostics]` annotations were present on the `Diagnostic`
modifier methods, but missing from the `DiagnosticBuilder` modifier
methods. They're now present.
pattern_analysis: track usefulness without interior mutability
Because of or-patterns, exhaustiveness needs to be able to lint if a sub-pattern is redundant, e.g. in `Some(_) | Some(true)`. So far the only sane solution I had found was interior mutability. This is a bit of an abstraction leak, and would become a footgun if we ever reused the same `DeconstructedPat`. This PR replaces interior mutability with an address-indexed hashmap, which is logically equivalent.
Invert diagnostic lints.
That is, change `diagnostic_outside_of_impl` and `untranslatable_diagnostic` from `allow` to `deny`, because more than half of the compiler has been converted to use translated diagnostics.
This commit removes more `deny` attributes than it adds `allow` attributes, which proves that this change is warranted.
r? ````@davidtwco````
Remove unused args from functions
`#[instrument]` suppresses the unused arguments from a function, *and* suppresses unused methods too! This PR removes things which are only used via `#[instrument]` calls, and fixes some other errors (privacy?) that I will comment inline.
It's possible that some of these arguments were being passed in for the purposes of being instrumented, but I am unconvinced by most of them.
pattern_analysis: gather up place-relevant info
We track 3 things about each place during exhaustiveness: its type, its (data) validity, and whether it's the scrutinee place. This PR gathers all three into a single struct.
r? `````@compiler-errors`````
pattern_analysis: use a plain `Vec` in `DeconstructedPat`
The use of an arena-allocated slice in `DeconstructedPat` dates to when we needed the arena anyway for lifetime reasons. Now that we don't, I'm thinking that if `thir::Pat` can use plain old `Vec`s, maybe so can I.
r? ```@ghost```
That is, change `diagnostic_outside_of_impl` and
`untranslatable_diagnostic` from `allow` to `deny`, because more than
half of the compiler has be converted to use translated diagnostics.
This commit removes more `deny` attributes than it adds `allow`
attributes, which proves that this change is warranted.
pattern_analysis: Gracefully abort on type incompatibility
This leaves the option for a consumer of the crate to return `Err` instead of panicking on type error. rust-analyzer could use that (e.g. https://github.com/rust-lang/rust-analyzer/issues/15808).
Since the only use of `TypeCx::bug` is in `Constructor::is_covered_by`, it is tempting to return `false` instead of `Err()`, but that would cause "non-exhaustive match" false positives.
r? `@compiler-errors`
never patterns: It is correct to lower `!` to `_`.
This is just a comment update but a non-trivial one: it is correct to lower `!` patterns as `_`. The reasoning is that `!` matches all the possible values of the type, since the type is empty. Moreover, we do want to warn that the `Err` is redundant in:
```rust
match x {
!,
Err(!),
}
```
which is consistent with `!` behaving like a wildcard.
I did try to introduce `Constructor::Never` and it ended up needing to behave exactly like `Constructor::Wildcard`.
r? ```@compiler-errors```
Since the only use of `TypeCx::bug` is in `Constructor::is_covered_by`,
it is tempting to return `false` instead of `Err()`, but that would
cause "non-exhaustive match" false positives.
Add the `min_exhaustive_patterns` feature gate
## Motivation
Pattern-matching on empty types is tricky around unsafe code. For that reason, current stable rust conservatively requires arms for empty types in all but the simplest case. It has long been the intention to allow omitting empty arms when it's safe to do so. The [`exhaustive_patterns`](https://github.com/rust-lang/rust/issues/51085) feature allows the omission of all empty arms, but hasn't been stabilized because that was deemed dangerous around unsafe code.
## Proposal
This feature aims to stabilize an uncontroversial subset of exhaustive_patterns. Namely: when `min_exhaustive_patterns` is enabled and the data we're matching on is guaranteed to be valid by rust's operational semantics, then we allow empty arms to be omitted. E.g.:
```rust
let x: Result<T, !> = foo();
match x { // ok
Ok(y) => ...,
}
let Ok(y) = x; // ok
```
If the place is not guaranteed to hold valid data (namely ptr dereferences, ref dereferences (conservatively) and union field accesses), then we keep stable behavior i.e. we (usually) require arms for the empty cases.
```rust
unsafe {
let ptr: *const Result<u32, !> = ...;
match *ptr {
Ok(x) => { ... }
Err(_) => { ... } // still required
}
}
let foo: Result<u32, &!> = ...;
match foo {
Ok(x) => { ... }
Err(&_) => { ... } // still required because of the dereference
}
unsafe {
let ptr: *const ! = ...;
match *ptr {} // already allowed on stable
}
```
Note that we conservatively consider that a valid reference can point to invalid data, hence we don't allow arms of type `&!` and similar cases to be omitted. This could eventually change depending on [opsem decisions](https://github.com/rust-lang/unsafe-code-guidelines/issues/413). Whenever opsem is undecided on a case, we conservatively keep today's stable behavior.
I proposed this behavior in the [`never_patterns`](https://github.com/rust-lang/rust/issues/118155) feature gate but it makes sense on its own and could be stabilized more quickly. The two proposals nicely complement each other.
## Unresolved Questions
Part of the question is whether this requires an RFC. I'd argue this doesn't need one since there is no design question beyond the intent to omit unreachable patterns, but I'm aware the problem can be framed in ways that require design (I'm thinking of the [original never patterns proposal](https://smallcultfollowing.com/babysteps/blog/2018/08/13/never-patterns-exhaustive-matching-and-uninhabited-types-oh-my/), which would frame this behavior as "auto-nevering" happening).
EDIT: I initially proposed a future-compatibility lint as part of this feature, I don't anymore.
pattern_analysis: Reuse most of the `DeconstructedPat` `Debug` impl
The `DeconstructedPat: Debug` is best-effort because we'd need `tcx` to get things like field names etc. Since rust-analyzer has a similar constraint, this PR moves most the impl to be shared between the two. While I was at it I also fixed a nit in the `IntRange: Debug` impl.
r? `@compiler-errors`
Exhaustiveness: simplify empty pattern logic
The logic that handles empty patterns had gotten quite convoluted. This PR simplifies it a lot. I tried to make the logic as easy as possible to follow; this only does logically equivalent changes.
The first commit is a drive-by comment clarification that was requested after another PR a while back.
r? `@compiler-errors`
pat_analysis: Don't rely on contiguous `VariantId`s outside of rustc
Today's pattern_analysis uses `BitSet` and `IndexVec` on the provided enum variant ids, which only makes sense if these ids count the variants from 0. In rust-analyzer, the variant ids are global interning ids, which would make `BitSet` and `IndexVec` ridiculously wasteful. In this PR I add some shims to use `FxHashSet`/`FxHashMap` instead outside of rustc.
r? ```@compiler-errors```
Rollup of 11 pull requests
Successful merges:
- #115046 (Use version-sorting for all sorting)
- #118915 (Add some comments, add `can_define_opaque_ty` check to `try_normalize_ty_recur`)
- #119006 (Fix is_global special address handling)
- #119637 (Pass LLVM error message back to pass wrapper.)
- #119715 (Exhaustiveness: abort on type error)
- #119763 (Cleanup things in and around `Diagnostic`)
- #119788 (change function name in comments)
- #119790 (Fix all_trait* methods to return all traits available in StableMIR)
- #119803 (Silence some follow-up errors [1/x])
- #119804 (Stabilize mutex_unpoison feature)
- #119832 (Meta: Add project const traits to triagebot config)
r? `@ghost`
`@rustbot` modify labels: rollup
It's incorrect because `CtorSet::split` returns a non-present
constructor into `present` in one specific case: variable-length slices
of an empty type. That's because empty constructors of arity 0 break the
algorithm. This is a tricky corner case that's hard to do cleanly. The
assert wasn't adding much anyway.
Exhaustiveness: Statically enforce revealing of opaques
In https://github.com/rust-lang/rust/pull/116821 it was decided that exhaustiveness should operate on the hidden type of an opaque type when relevant. This PR makes sure we consistently reveal opaques within exhaustiveness. This makes it possible to remove `reveal_opaque_ty` from the `TypeCx` trait which was an unfortunate implementation detail.
r? `@compiler-errors`
`Diagnostic` has 40 methods that return `&mut Self` and could be
considered setters. Four of them have a `set_` prefix. This doesn't seem
necessary for a type that implements the builder pattern. This commit
removes the `set_` prefixes on those four methods.
Clean up some lifetimes in `rustc_pattern_analysis`
This PR removes some redundant lifetimes. I figured out that we were shortening the lifetime of an arena-allocated `&'p DeconstructedPat<'p>` to `'a DeconstructedPat<'p>`, which forced us to carry both lifetimes when we could otherwise carry just one.
This PR also removes and elides some unnecessary lifetimes.
I also cherry-picked 0292eb9bb9b897f5c0926c6a8530877f67e7cc9b, and then simplified more lifetimes in `MatchVisitor`, which should make #119233 a very simple PR!
r? Nadrieril
Remove `DiagCtxt` API duplication
`DiagCtxt` defines the internal API for creating and emitting diagnostics: methods like `struct_err`, `struct_span_warn`, `note`, `create_fatal`, `emit_bug`. There are over 50 methods.
Some of these methods are then duplicated across several other types: `Session`, `ParseSess`, `Parser`, `ExtCtxt`, and `MirBorrowckCtxt`. `Session` duplicates the most, though half the ones it does are unused. Each duplicated method just calls forward to the corresponding method in `DiagCtxt`. So this duplication exists to (in the best case) shorten chains like `ecx.tcx.sess.parse_sess.dcx.emit_err()` to `ecx.emit_err()`.
This API duplication is ugly and has been bugging me for a while. And it's inconsistent: there's no real logic about which methods are duplicated, and the use of `#[rustc_lint_diagnostic]` and `#[track_caller]` attributes vary across the duplicates.
This PR removes the duplicated API methods and makes all diagnostic creation and emission go through `DiagCtxt`. It also adds `dcx` getter methods to several types to shorten chains. This approach scales *much* better than API duplication; indeed, the PR adds `dcx()` to numerous types that didn't have API duplication: `TyCtxt`, `LoweringCtxt`, `ConstCx`, `FnCtxt`, `TypeErrCtxt`, `InferCtxt`, `CrateLoader`, `CheckAttrVisitor`, and `Resolver`. These result in a lot of changes from `foo.tcx.sess.emit_err()` to `foo.dcx().emit_err()`. (You could do this with more types, but it gets into diminishing returns territory for types that don't emit many diagnostics.)
After all these changes, some call sites are more verbose, some are less verbose, and many are the same. The total number of lines is reduced, mostly because of the removed API duplication. And consistency is increased, because calls to `emit_err` and friends are always preceded with `.dcx()` or `.dcx`.
r? `@compiler-errors`
Exhaustiveness: Improve complexity on some wide matches
https://github.com/rust-lang/rust/issues/118437 revealed an exponential case in exhaustiveness checking. While [exponential cases are unavoidable](https://compilercrim.es/rust-np/), this one only showed up after my https://github.com/rust-lang/rust/pull/117611 rewrite of the algorithm. I remember anticipating a case like this and dismissing it as unrealistic, but here we are :').
The tricky match is as follows:
```rust
match command {
BaseCommand { field01: true, .. } => {}
BaseCommand { field02: true, .. } => {}
BaseCommand { field03: true, .. } => {}
BaseCommand { field04: true, .. } => {}
BaseCommand { field05: true, .. } => {}
BaseCommand { field06: true, .. } => {}
BaseCommand { field07: true, .. } => {}
BaseCommand { field08: true, .. } => {}
BaseCommand { field09: true, .. } => {}
BaseCommand { field10: true, .. } => {}
// ...20 more of the same
_ => {}
}
```
To fix this, this PR formalizes a concept of "relevancy" (naming is hard) that was already used to decide what patterns to report. Now we track it for every row, which in wide matches like the above can drastically cut on the number of cases we explore. After this fix, the above match is checked with linear-many cases instead of exponentially-many.
Fixes https://github.com/rust-lang/rust/issues/118437
r? `@cjgillot`
Make exhaustiveness usable outside of rustc
With this PR, `rustc_pattern_analysis` compiles on stable (with the `stable` feature)! `rust-analyzer` will be able to use it to provide match-related diagnostics and refactors.
Two questions:
- Should I name the feature `nightly` instead of `rustc` for consistency with other crates? `rustc` makes more sense imo.
- `typed-arena` is an optional dependency but tidy made me add it to the allow-list anyway. Can I avoid that somehow?
r? `@compiler-errors`