Skip to content

Commit 2a042d6

Browse files
committed
Filter unstable and doc hidden variants in usefulness checking
Add test cases for unstable variants Add test cases for doc hidden variants Move is_doc_hidden to method on TyCtxt Add unstable variants test to reachable-patterns ui test Rename reachable-patterns -> omitted-patterns
1 parent 02f2b31 commit 2a042d6

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)