Skip to content

Commit d7c97a0

Browse files
committed
Auto merge of rust-lang#89105 - DevinR528:reachable-fix, r=Nadrieril
Fix: non_exhaustive_omitted_patterns by filtering unstable and doc hidden variants Fixes: rust-lang#89042 Now that rust-lang#86809 has been merged there are cases (std::io::ErrorKind) where unstable feature gated variants were included in warning/error messages when the feature was not turned on. This filters those variants out of the return of `SplitWildcard::new`. Variants marked `doc(hidden)` are filtered out of the witnesses list in `Usefulness::apply_constructor`. Probably worth a perf run :shrug: since this area can be sensitive.
2 parents 0446743 + 2a042d6 commit d7c97a0

16 files changed

+428
-92
lines changed

compiler/rustc_middle/src/ty/mod.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use rustc_macros::HashStable;
3838
use rustc_query_system::ich::StableHashingContext;
3939
use rustc_session::cstore::CrateStoreDyn;
4040
use rustc_span::symbol::{kw, Ident, Symbol};
41-
use rustc_span::Span;
41+
use rustc_span::{sym, Span};
4242
use rustc_target::abi::Align;
4343

4444
use std::cmp::Ordering;
@@ -1900,6 +1900,14 @@ impl<'tcx> TyCtxt<'tcx> {
19001900
self.sess.contains_name(&self.get_attrs(did), attr)
19011901
}
19021902

1903+
/// Determines whether an item is annotated with `doc(hidden)`.
1904+
pub fn is_doc_hidden(self, did: DefId) -> bool {
1905+
self.get_attrs(did)
1906+
.iter()
1907+
.filter_map(|attr| if attr.has_name(sym::doc) { attr.meta_item_list() } else { None })
1908+
.any(|items| items.iter().any(|item| item.has_name(sym::hidden)))
1909+
}
1910+
19031911
/// Returns `true` if this is an `auto trait`.
19041912
pub fn trait_is_auto(self, trait_def_id: DefId) -> bool {
19051913
self.trait_def(trait_def_id).has_auto_impl

compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs

+56-29
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ use rustc_data_structures::captures::Captures;
5252
use rustc_index::vec::Idx;
5353

5454
use rustc_hir::{HirId, RangeEnd};
55-
use rustc_middle::mir::interpret::ConstValue;
5655
use rustc_middle::mir::Field;
5756
use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange};
5857
use rustc_middle::ty::layout::IntegerExt;
5958
use rustc_middle::ty::{self, Const, Ty, TyCtxt, VariantDef};
59+
use rustc_middle::{middle::stability::EvalResult, mir::interpret::ConstValue};
6060
use rustc_session::lint;
6161
use rustc_span::{Span, DUMMY_SP};
6262
use rustc_target::abi::{Integer, Size, VariantIdx};
@@ -675,6 +675,36 @@ impl<'tcx> Constructor<'tcx> {
675675
}
676676
}
677677

678+
/// Checks if the `Constructor` is a variant and `TyCtxt::eval_stability` returns
679+
/// `EvalResult::Deny { .. }`.
680+
///
681+
/// This means that the variant has a stdlib unstable feature marking it.
682+
pub(super) fn is_unstable_variant(&self, pcx: PatCtxt<'_, '_, 'tcx>) -> bool {
683+
if let Constructor::Variant(idx) = self {
684+
if let ty::Adt(adt, _) = pcx.ty.kind() {
685+
let variant_def_id = adt.variants[*idx].def_id;
686+
// Filter variants that depend on a disabled unstable feature.
687+
return matches!(
688+
pcx.cx.tcx.eval_stability(variant_def_id, None, DUMMY_SP, None),
689+
EvalResult::Deny { .. }
690+
);
691+
}
692+
}
693+
false
694+
}
695+
696+
/// Checks if the `Constructor` is a `Constructor::Variant` with a `#[doc(hidden)]`
697+
/// attribute.
698+
pub(super) fn is_doc_hidden_variant(&self, pcx: PatCtxt<'_, '_, 'tcx>) -> bool {
699+
if let Constructor::Variant(idx) = self {
700+
if let ty::Adt(adt, _) = pcx.ty.kind() {
701+
let variant_def_id = adt.variants[*idx].def_id;
702+
return pcx.cx.tcx.is_doc_hidden(variant_def_id);
703+
}
704+
}
705+
false
706+
}
707+
678708
fn variant_index_for_adt(&self, adt: &'tcx ty::AdtDef) -> VariantIdx {
679709
match *self {
680710
Variant(idx) => idx,
@@ -929,36 +959,33 @@ impl<'tcx> SplitWildcard<'tcx> {
929959
// witness.
930960
let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive_enum(pcx.ty);
931961

962+
let is_exhaustive_pat_feature = cx.tcx.features().exhaustive_patterns;
963+
932964
// If `exhaustive_patterns` is disabled and our scrutinee is an empty enum, we treat it
933965
// as though it had an "unknown" constructor to avoid exposing its emptiness. The
934966
// exception is if the pattern is at the top level, because we want empty matches to be
935967
// considered exhaustive.
936-
let is_secretly_empty = def.variants.is_empty()
937-
&& !cx.tcx.features().exhaustive_patterns
938-
&& !pcx.is_top_level;
939-
940-
if is_secretly_empty {
941-
smallvec![NonExhaustive]
942-
} else if is_declared_nonexhaustive {
943-
def.variants
944-
.indices()
945-
.map(|idx| Variant(idx))
946-
.chain(Some(NonExhaustive))
947-
.collect()
948-
} else if cx.tcx.features().exhaustive_patterns {
949-
// If `exhaustive_patterns` is enabled, we exclude variants known to be
950-
// uninhabited.
951-
def.variants
952-
.iter_enumerated()
953-
.filter(|(_, v)| {
954-
!v.uninhabited_from(cx.tcx, substs, def.adt_kind(), cx.param_env)
955-
.contains(cx.tcx, cx.module)
956-
})
957-
.map(|(idx, _)| Variant(idx))
958-
.collect()
959-
} else {
960-
def.variants.indices().map(|idx| Variant(idx)).collect()
968+
let is_secretly_empty =
969+
def.variants.is_empty() && !is_exhaustive_pat_feature && !pcx.is_top_level;
970+
971+
let mut ctors: SmallVec<[_; 1]> = def
972+
.variants
973+
.iter_enumerated()
974+
.filter(|(_, v)| {
975+
// If `exhaustive_patterns` is enabled, we exclude variants known to be
976+
// uninhabited.
977+
let is_uninhabited = is_exhaustive_pat_feature
978+
&& v.uninhabited_from(cx.tcx, substs, def.adt_kind(), cx.param_env)
979+
.contains(cx.tcx, cx.module);
980+
!is_uninhabited
981+
})
982+
.map(|(idx, _)| Variant(idx))
983+
.collect();
984+
985+
if is_secretly_empty || is_declared_nonexhaustive {
986+
ctors.push(NonExhaustive);
961987
}
988+
ctors
962989
}
963990
ty::Char => {
964991
smallvec![
@@ -1068,7 +1095,7 @@ impl<'tcx> SplitWildcard<'tcx> {
10681095
Missing {
10691096
nonexhaustive_enum_missing_real_variants: self
10701097
.iter_missing(pcx)
1071-
.any(|c| !c.is_non_exhaustive()),
1098+
.any(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx))),
10721099
}
10731100
} else {
10741101
Missing { nonexhaustive_enum_missing_real_variants: false }
@@ -1222,9 +1249,9 @@ impl<'p, 'tcx> Fields<'p, 'tcx> {
12221249

12231250
/// Values and patterns can be represented as a constructor applied to some fields. This represents
12241251
/// a pattern in this form.
1225-
/// This also keeps track of whether the pattern has been foundreachable during analysis. For this
1252+
/// This also keeps track of whether the pattern has been found reachable during analysis. For this
12261253
/// reason we should be careful not to clone patterns for which we care about that. Use
1227-
/// `clone_and_forget_reachability` is you're sure.
1254+
/// `clone_and_forget_reachability` if you're sure.
12281255
pub(crate) struct DeconstructedPat<'p, 'tcx> {
12291256
ctor: Constructor<'tcx>,
12301257
fields: Fields<'p, 'tcx>,

compiler/rustc_mir_build/src/thir/pattern/usefulness.rs

+26-6
Original file line numberDiff line numberDiff line change
@@ -585,15 +585,33 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> {
585585
} else {
586586
let mut split_wildcard = SplitWildcard::new(pcx);
587587
split_wildcard.split(pcx, matrix.heads().map(DeconstructedPat::ctor));
588+
589+
// This lets us know if we skipped any variants because they are marked
590+
// `doc(hidden)` or they are unstable feature gate (only stdlib types).
591+
let mut hide_variant_show_wild = false;
588592
// Construct for each missing constructor a "wild" version of this
589593
// constructor, that matches everything that can be built with
590594
// it. For example, if `ctor` is a `Constructor::Variant` for
591595
// `Option::Some`, we get the pattern `Some(_)`.
592-
split_wildcard
596+
let mut new: Vec<DeconstructedPat<'_, '_>> = split_wildcard
593597
.iter_missing(pcx)
594-
.cloned()
595-
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
596-
.collect()
598+
.filter_map(|missing_ctor| {
599+
// Check if this variant is marked `doc(hidden)`
600+
if missing_ctor.is_doc_hidden_variant(pcx)
601+
|| missing_ctor.is_unstable_variant(pcx)
602+
{
603+
hide_variant_show_wild = true;
604+
return None;
605+
}
606+
Some(DeconstructedPat::wild_from_ctor(pcx, missing_ctor.clone()))
607+
})
608+
.collect();
609+
610+
if hide_variant_show_wild {
611+
new.push(DeconstructedPat::wildcard(pcx.ty));
612+
}
613+
614+
new
597615
};
598616

599617
witnesses
@@ -851,8 +869,10 @@ fn is_useful<'p, 'tcx>(
851869
split_wildcard
852870
.iter_missing(pcx)
853871
// Filter out the `NonExhaustive` because we want to list only real
854-
// variants.
855-
.filter(|c| !c.is_non_exhaustive())
872+
// variants. Also remove any unstable feature gated variants.
873+
// Because of how we computed `nonexhaustive_enum_missing_real_variants`,
874+
// this will not return an empty `Vec`.
875+
.filter(|c| !(c.is_non_exhaustive() || c.is_unstable_variant(pcx)))
856876
.cloned()
857877
.map(|missing_ctor| DeconstructedPat::wild_from_ctor(pcx, missing_ctor))
858878
.collect::<Vec<_>>()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub enum Foo {
2+
A,
3+
B,
4+
#[doc(hidden)]
5+
C,
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![feature(staged_api)]
2+
#![stable(feature = "stable_test_feature", since = "1.0.0")]
3+
4+
#[stable(feature = "stable_test_feature", since = "1.0.0")]
5+
pub enum Foo {
6+
#[stable(feature = "stable_test_feature", since = "1.0.0")]
7+
Stable,
8+
#[stable(feature = "stable_test_feature", since = "1.0.0")]
9+
Stable2,
10+
#[unstable(feature = "unstable_test_feature", issue = "none")]
11+
Unstable,
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// aux-build:hidden.rs
2+
3+
extern crate hidden;
4+
5+
use hidden::Foo;
6+
7+
fn main() {
8+
match Foo::A {
9+
Foo::A => {}
10+
Foo::B => {}
11+
}
12+
//~^^^^ non-exhaustive patterns: `_` not covered
13+
14+
match Foo::A {
15+
Foo::A => {}
16+
Foo::C => {}
17+
}
18+
//~^^^^ non-exhaustive patterns: `B` not covered
19+
20+
match Foo::A {
21+
Foo::A => {}
22+
}
23+
//~^^^ non-exhaustive patterns: `B` and `_` not covered
24+
25+
match None {
26+
None => {}
27+
Some(Foo::A) => {}
28+
}
29+
//~^^^^ non-exhaustive patterns: `Some(B)` and `Some(_)` not covered
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
error[E0004]: non-exhaustive patterns: `_` not covered
2+
--> $DIR/doc-hidden-non-exhaustive.rs:8:11
3+
|
4+
LL | match Foo::A {
5+
| ^^^^^^ pattern `_` not covered
6+
|
7+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
8+
= note: the matched value is of type `Foo`
9+
10+
error[E0004]: non-exhaustive patterns: `B` not covered
11+
--> $DIR/doc-hidden-non-exhaustive.rs:14:11
12+
|
13+
LL | match Foo::A {
14+
| ^^^^^^ pattern `B` not covered
15+
|
16+
::: $DIR/auxiliary/hidden.rs:3:5
17+
|
18+
LL | B,
19+
| - not covered
20+
|
21+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
22+
= note: the matched value is of type `Foo`
23+
24+
error[E0004]: non-exhaustive patterns: `B` and `_` not covered
25+
--> $DIR/doc-hidden-non-exhaustive.rs:20:11
26+
|
27+
LL | match Foo::A {
28+
| ^^^^^^ patterns `B` and `_` not covered
29+
|
30+
::: $DIR/auxiliary/hidden.rs:3:5
31+
|
32+
LL | B,
33+
| - not covered
34+
|
35+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
36+
= note: the matched value is of type `Foo`
37+
38+
error[E0004]: non-exhaustive patterns: `Some(B)` and `Some(_)` not covered
39+
--> $DIR/doc-hidden-non-exhaustive.rs:25:11
40+
|
41+
LL | match None {
42+
| ^^^^ patterns `Some(B)` and `Some(_)` not covered
43+
|
44+
::: $SRC_DIR/core/src/option.rs:LL:COL
45+
|
46+
LL | Some(#[stable(feature = "rust1", since = "1.0.0")] T),
47+
| ---- not covered
48+
|
49+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
50+
= note: the matched value is of type `Option<Foo>`
51+
52+
error: aborting due to 4 previous errors
53+
54+
For more information about this error, try `rustc --explain E0004`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// aux-build:unstable.rs
2+
3+
extern crate unstable;
4+
5+
use unstable::Foo;
6+
7+
fn main() {
8+
match Foo::Stable {
9+
Foo::Stable => {}
10+
}
11+
//~^^^ non-exhaustive patterns: `Stable2` and `_` not covered
12+
13+
match Foo::Stable {
14+
Foo::Stable => {}
15+
Foo::Stable2 => {}
16+
}
17+
//~^^^^ non-exhaustive patterns: `_` not covered
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error[E0004]: non-exhaustive patterns: `Stable2` and `_` not covered
2+
--> $DIR/stable-gated-patterns.rs:8:11
3+
|
4+
LL | match Foo::Stable {
5+
| ^^^^^^^^^^^ patterns `Stable2` and `_` not covered
6+
|
7+
::: $DIR/auxiliary/unstable.rs:9:5
8+
|
9+
LL | Stable2,
10+
| ------- not covered
11+
|
12+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
13+
= note: the matched value is of type `Foo`
14+
15+
error[E0004]: non-exhaustive patterns: `_` not covered
16+
--> $DIR/stable-gated-patterns.rs:13:11
17+
|
18+
LL | match Foo::Stable {
19+
| ^^^^^^^^^^^ pattern `_` not covered
20+
|
21+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
22+
= note: the matched value is of type `Foo`
23+
24+
error: aborting due to 2 previous errors
25+
26+
For more information about this error, try `rustc --explain E0004`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![feature(unstable_test_feature)]
2+
3+
// aux-build:unstable.rs
4+
5+
extern crate unstable;
6+
7+
use unstable::Foo;
8+
9+
fn main() {
10+
match Foo::Stable {
11+
Foo::Stable => {}
12+
Foo::Stable2 => {}
13+
}
14+
//~^^^^ non-exhaustive patterns: `Unstable` not covered
15+
16+
// Ok: all variants are explicitly matched
17+
match Foo::Stable {
18+
Foo::Stable => {}
19+
Foo::Stable2 => {}
20+
Foo::Unstable => {}
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0004]: non-exhaustive patterns: `Unstable` not covered
2+
--> $DIR/unstable-gated-patterns.rs:10:11
3+
|
4+
LL | match Foo::Stable {
5+
| ^^^^^^^^^^^ pattern `Unstable` not covered
6+
|
7+
::: $DIR/auxiliary/unstable.rs:11:5
8+
|
9+
LL | Unstable,
10+
| -------- not covered
11+
|
12+
= help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
13+
= note: the matched value is of type `Foo`
14+
15+
error: aborting due to previous error
16+
17+
For more information about this error, try `rustc --explain E0004`.

0 commit comments

Comments
 (0)