diff options
| author | Devin Ragotzy <devin.ragotzy@gmail.com> | 2021-09-10 16:45:04 -0400 |
|---|---|---|
| committer | Devin Ragotzy <devin.ragotzy@gmail.com> | 2021-09-14 15:45:13 -0400 |
| commit | 33a06b73d90bde1d2fc4902672f4cbeb233b83e2 (patch) | |
| tree | f47c17d3d3eaf2020f7b02b4b9aabac7ffb2f999 /compiler/rustc_mir_build/src | |
| parent | c3c0f80d6081092faff801542dd82f0e2420152b (diff) | |
| download | rust-33a06b73d90bde1d2fc4902672f4cbeb233b83e2.tar.gz rust-33a06b73d90bde1d2fc4902672f4cbeb233b83e2.zip | |
Add reachable_patterns lint to rfc-2008-non_exhaustive
Add linting on non_exhaustive structs and enum variants Add ui tests for non_exhaustive reachable lint Rename to non_exhaustive_omitted_patterns and avoid triggering on if let
Diffstat (limited to 'compiler/rustc_mir_build/src')
3 files changed, 170 insertions, 51 deletions
diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index b34c1e07be7..4c51b9207bb 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -14,8 +14,9 @@ use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; use rustc_hir::{HirId, Pat}; use rustc_middle::thir::PatKind; use rustc_middle::ty::{self, Ty, TyCtxt}; -use rustc_session::lint::builtin::BINDINGS_WITH_VARIANT_NAME; -use rustc_session::lint::builtin::{IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS}; +use rustc_session::lint::builtin::{ + BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS, +}; use rustc_session::Session; use rustc_span::{DesugaringKind, ExpnKind, Span}; use std::slice; @@ -559,7 +560,7 @@ fn non_exhaustive_match<'p, 'tcx>( err.emit(); } -fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String { +crate fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String { const LIMIT: usize = 3; match witnesses { [] => bug!(), @@ -576,7 +577,7 @@ fn joined_uncovered_patterns(witnesses: &[super::Pat<'_>]) -> String { } } -fn pattern_not_covered_label(witnesses: &[super::Pat<'_>], joined_patterns: &str) -> String { +crate fn pattern_not_covered_label(witnesses: &[super::Pat<'_>], joined_patterns: &str) -> String { format!("pattern{} {} not covered", rustc_errors::pluralize!(witnesses.len()), joined_patterns) } diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index ace13ea4462..cee2a4db0a8 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -606,8 +606,9 @@ pub(super) enum Constructor<'tcx> { /// for those types for which we cannot list constructors explicitly, like `f64` and `str`. NonExhaustive, /// Stands for constructors that are not seen in the matrix, as explained in the documentation - /// for [`SplitWildcard`]. - Missing, + /// for [`SplitWildcard`]. The carried `bool` is used for the `non_exhaustive_omitted_patterns` + /// lint. + Missing { nonexhaustive_enum_missing_real_variants: bool }, /// Wildcard pattern. Wildcard, } @@ -617,6 +618,10 @@ impl<'tcx> Constructor<'tcx> { matches!(self, Wildcard) } + pub(super) fn is_non_exhaustive(&self) -> bool { + matches!(self, NonExhaustive) + } + fn as_int_range(&self) -> Option<&IntRange> { match self { IntRange(range) => Some(range), @@ -756,7 +761,7 @@ impl<'tcx> Constructor<'tcx> { // Wildcards cover anything (_, Wildcard) => true, // The missing ctors are not covered by anything in the matrix except wildcards. - (Missing | Wildcard, _) => false, + (Missing { .. } | Wildcard, _) => false, (Single, Single) => true, (Variant(self_id), Variant(other_id)) => self_id == other_id, @@ -829,7 +834,7 @@ impl<'tcx> Constructor<'tcx> { .any(|other| slice.is_covered_by(other)), // This constructor is never covered by anything else NonExhaustive => false, - Str(..) | FloatRange(..) | Opaque | Missing | Wildcard => { + Str(..) | FloatRange(..) | Opaque | Missing { .. } | Wildcard => { span_bug!(pcx.span, "found unexpected ctor in all_ctors: {:?}", self) } } @@ -919,8 +924,14 @@ impl<'tcx> SplitWildcard<'tcx> { && !cx.tcx.features().exhaustive_patterns && !pcx.is_top_level; - if is_secretly_empty || is_declared_nonexhaustive { + if is_secretly_empty { smallvec![NonExhaustive] + } else if is_declared_nonexhaustive { + def.variants + .indices() + .map(|idx| Variant(idx)) + .chain(Some(NonExhaustive)) + .collect() } else if cx.tcx.features().exhaustive_patterns { // If `exhaustive_patterns` is enabled, we exclude variants known to be // uninhabited. @@ -975,6 +986,7 @@ impl<'tcx> SplitWildcard<'tcx> { // This type is one for which we cannot list constructors, like `str` or `f64`. _ => smallvec![NonExhaustive], }; + SplitWildcard { matrix_ctors: Vec::new(), all_ctors } } @@ -1039,7 +1051,17 @@ impl<'tcx> SplitWildcard<'tcx> { // sometimes prefer reporting the list of constructors instead of just `_`. let report_when_all_missing = pcx.is_top_level && !IntRange::is_integral(pcx.ty); let ctor = if !self.matrix_ctors.is_empty() || report_when_all_missing { - Missing + if pcx.is_non_exhaustive { + Missing { + nonexhaustive_enum_missing_real_variants: self + .iter_missing(pcx) + .filter(|c| !c.is_non_exhaustive()) + .next() + .is_some(), + } + } else { + Missing { nonexhaustive_enum_missing_real_variants: false } + } } else { Wildcard }; @@ -1176,7 +1198,12 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { } _ => bug!("bad slice pattern {:?} {:?}", constructor, ty), }, - Str(..) | FloatRange(..) | IntRange(..) | NonExhaustive | Opaque | Missing + Str(..) + | FloatRange(..) + | IntRange(..) + | NonExhaustive + | Opaque + | Missing { .. } | Wildcard => Fields::Slice(&[]), }; debug!("Fields::wildcards({:?}, {:?}) = {:#?}", constructor, ty, ret); @@ -1189,15 +1216,18 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { /// This is roughly the inverse of `specialize_constructor`. /// /// Examples: - /// `ctor`: `Constructor::Single` - /// `ty`: `Foo(u32, u32, u32)` - /// `self`: `[10, 20, _]` + /// + /// ```text + /// ctor: `Constructor::Single` + /// ty: `Foo(u32, u32, u32)` + /// self: `[10, 20, _]` /// returns `Foo(10, 20, _)` /// - /// `ctor`: `Constructor::Variant(Option::Some)` - /// `ty`: `Option<bool>` - /// `self`: `[false]` + /// ctor: `Constructor::Variant(Option::Some)` + /// ty: `Option<bool>` + /// self: `[false]` /// returns `Some(false)` + /// ``` pub(super) fn apply(self, pcx: PatCtxt<'_, 'p, 'tcx>, ctor: &Constructor<'tcx>) -> Pat<'tcx> { let subpatterns_and_indices = self.patterns_and_indices(); let mut subpatterns = subpatterns_and_indices.iter().map(|&(_, p)| p).cloned(); @@ -1265,7 +1295,7 @@ impl<'p, 'tcx> Fields<'p, 'tcx> { NonExhaustive => PatKind::Wild, Wildcard => return Pat::wildcard_from_ty(pcx.ty), Opaque => bug!("we should not try to apply an opaque constructor"), - Missing => bug!( + Missing { .. } => bug!( "trying to apply the `Missing` constructor; this should have been done in `apply_constructors`" ), }; diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs index 344006e9fb4..f4255713e2a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs @@ -280,20 +280,23 @@ //! The details are not necessary to understand this file, so we explain them in //! [`super::deconstruct_pat`]. Splitting is done by the [`Constructor::split`] function. +use self::ArmType::*; use self::Usefulness::*; -use self::WitnessPreference::*; +use super::check_match::{joined_uncovered_patterns, pattern_not_covered_label}; use super::deconstruct_pat::{Constructor, Fields, SplitWildcard}; use super::{PatternFoldable, PatternFolder}; use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::FxHashMap; +use hir::def_id::DefId; +use hir::HirId; use rustc_arena::TypedArena; -use rustc_hir::def_id::DefId; -use rustc_hir::HirId; +use rustc_hir as hir; use rustc_middle::thir::{Pat, PatKind}; use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS; use rustc_span::Span; use smallvec::{smallvec, SmallVec}; @@ -343,6 +346,8 @@ pub(super) struct PatCtxt<'a, 'p, 'tcx> { /// Whether the current pattern is the whole pattern as found in a match arm, or if it's a /// subpattern. pub(super) is_top_level: bool, + /// Wether the current pattern is from a `non_exhaustive` enum. + pub(super) is_non_exhaustive: bool, } impl<'a, 'p, 'tcx> fmt::Debug for PatCtxt<'a, 'p, 'tcx> { @@ -862,7 +867,7 @@ impl<'p, 'tcx> SubPatSet<'p, 'tcx> { /// of potential unreachable sub-patterns (in the presence of or-patterns). When checking /// exhaustiveness of a whole match, we use the `WithWitnesses` variant, which carries a list of /// witnesses of non-exhaustiveness when there are any. -/// Which variant to use is dictated by `WitnessPreference`. +/// Which variant to use is dictated by `ArmType`. #[derive(Clone, Debug)] enum Usefulness<'p, 'tcx> { /// Carries a set of subpatterns that have been found to be reachable. If empty, this indicates @@ -877,16 +882,24 @@ enum Usefulness<'p, 'tcx> { } impl<'p, 'tcx> Usefulness<'p, 'tcx> { - fn new_useful(preference: WitnessPreference) -> Self { + fn new_useful(preference: ArmType) -> Self { match preference { - ConstructWitness => WithWitnesses(vec![Witness(vec![])]), - LeaveOutWitness => NoWitnesses(SubPatSet::full()), + FakeExtraWildcard => WithWitnesses(vec![Witness(vec![])]), + RealArm => NoWitnesses(SubPatSet::full()), } } - fn new_not_useful(preference: WitnessPreference) -> Self { + + fn new_not_useful(preference: ArmType) -> Self { match preference { - ConstructWitness => WithWitnesses(vec![]), - LeaveOutWitness => NoWitnesses(SubPatSet::empty()), + FakeExtraWildcard => WithWitnesses(vec![]), + RealArm => NoWitnesses(SubPatSet::empty()), + } + } + + fn is_useful(&self) -> bool { + match self { + Usefulness::NoWitnesses(set) => !set.is_empty(), + Usefulness::WithWitnesses(witnesses) => !witnesses.is_empty(), } } @@ -903,7 +916,7 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> { /// When trying several branches and each returns a `Usefulness`, we need to combine the /// results together. - fn merge(pref: WitnessPreference, usefulnesses: impl Iterator<Item = Self>) -> Self { + fn merge(pref: ArmType, usefulnesses: impl Iterator<Item = Self>) -> Self { let mut ret = Self::new_not_useful(pref); for u in usefulnesses { ret.extend(u); @@ -926,7 +939,7 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> { } } - /// After calculating usefulness after a specialization, call this to recontruct a usefulness + /// After calculating usefulness after a specialization, call this to reconstruct a usefulness /// that makes sense for the matrix pre-specialization. This new usefulness can then be merged /// with the results of specializing with the other constructors. fn apply_constructor( @@ -939,19 +952,31 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> { match self { WithWitnesses(witnesses) if witnesses.is_empty() => WithWitnesses(witnesses), WithWitnesses(witnesses) => { - let new_witnesses = if matches!(ctor, Constructor::Missing) { - let mut split_wildcard = SplitWildcard::new(pcx); - split_wildcard.split(pcx, matrix.head_ctors(pcx.cx)); - // Construct for each missing constructor a "wild" version of this - // constructor, that matches everything that can be built with - // it. For example, if `ctor` is a `Constructor::Variant` for - // `Option::Some`, we get the pattern `Some(_)`. - let new_patterns: Vec<_> = split_wildcard - .iter_missing(pcx) - .map(|missing_ctor| { - Fields::wildcards(pcx, missing_ctor).apply(pcx, missing_ctor) - }) - .collect(); + let new_witnesses = if let Constructor::Missing { .. } = ctor { + // We got the special `Missing` constructor, so each of the missing constructors + // gives a new pattern that is not caught by the match. We list those patterns. + let new_patterns = if pcx.is_non_exhaustive { + // Here we don't want the user to try to list all variants, we want them to add + // a wildcard, so we only suggest that. + vec![ + Fields::wildcards(pcx, &Constructor::NonExhaustive) + .apply(pcx, &Constructor::NonExhaustive), + ] + } else { + let mut split_wildcard = SplitWildcard::new(pcx); + split_wildcard.split(pcx, matrix.head_ctors(pcx.cx)); + // Construct for each missing constructor a "wild" version of this + // constructor, that matches everything that can be built with + // it. For example, if `ctor` is a `Constructor::Variant` for + // `Option::Some`, we get the pattern `Some(_)`. + split_wildcard + .iter_missing(pcx) + .map(|missing_ctor| { + Fields::wildcards(pcx, missing_ctor).apply(pcx, missing_ctor) + }) + .collect() + }; + witnesses .into_iter() .flat_map(|witness| { @@ -976,9 +1001,9 @@ impl<'p, 'tcx> Usefulness<'p, 'tcx> { } #[derive(Copy, Clone, Debug)] -enum WitnessPreference { - ConstructWitness, - LeaveOutWitness, +enum ArmType { + FakeExtraWildcard, + RealArm, } /// A witness of non-exhaustiveness for error reporting, represented @@ -1056,6 +1081,32 @@ impl<'tcx> Witness<'tcx> { } } +/// Report that a match of a `non_exhaustive` enum marked with `non_exhaustive_omitted_patterns` +/// is not exhaustive enough. +/// +/// NB: The partner lint for structs lives in `compiler/rustc_typeck/src/check/pat.rs`. +fn lint_non_exhaustive_omitted_patterns<'p, 'tcx>( + cx: &MatchCheckCtxt<'p, 'tcx>, + scrut_ty: Ty<'tcx>, + sp: Span, + hir_id: HirId, + witnesses: Vec<Pat<'tcx>>, +) { + let joined_patterns = joined_uncovered_patterns(&witnesses); + cx.tcx.struct_span_lint_hir(NON_EXHAUSTIVE_OMITTED_PATTERNS, hir_id, sp, |build| { + let mut lint = build.build("some variants are not matched explicitly"); + lint.span_label(sp, pattern_not_covered_label(&witnesses, &joined_patterns)); + lint.help( + "ensure that all variants are matched explicitly by adding the suggested match arms", + ); + lint.note(&format!( + "the matched value is of type `{}` and the `non_exhaustive_omitted_patterns` attribute was found", + scrut_ty, + )); + lint.emit(); + }); +} + /// Algorithm from <http://moscova.inria.fr/~maranget/papers/warn/index.html>. /// The algorithm from the paper has been modified to correctly handle empty /// types. The changes are: @@ -1086,7 +1137,7 @@ fn is_useful<'p, 'tcx>( cx: &MatchCheckCtxt<'p, 'tcx>, matrix: &Matrix<'p, 'tcx>, v: &PatStack<'p, 'tcx>, - witness_preference: WitnessPreference, + witness_preference: ArmType, hir_id: HirId, is_under_guard: bool, is_top_level: bool, @@ -1113,7 +1164,8 @@ fn is_useful<'p, 'tcx>( // FIXME(Nadrieril): Hack to work around type normalization issues (see #72476). let ty = matrix.heads().next().map_or(v.head().ty, |r| r.ty); - let pcx = PatCtxt { cx, ty, span: v.head().span, is_top_level }; + let is_non_exhaustive = cx.is_foreign_non_exhaustive_enum(ty); + let pcx = PatCtxt { cx, ty, span: v.head().span, is_top_level, is_non_exhaustive }; // If the first pattern is an or-pattern, expand it. let ret = if is_or_pat(v.head()) { @@ -1148,6 +1200,7 @@ fn is_useful<'p, 'tcx>( } // We split the head constructor of `v`. let split_ctors = v_ctor.split(pcx, matrix.head_ctors(cx)); + let is_non_exhaustive_and_wild = is_non_exhaustive && v_ctor.is_wildcard(); // For each constructor, we compute whether there's a value that starts with it that would // witness the usefulness of `v`. let start_matrix = &matrix; @@ -1160,10 +1213,46 @@ fn is_useful<'p, 'tcx>( let v = v.pop_head_constructor(&ctor_wild_subpatterns); let usefulness = is_useful(cx, &spec_matrix, &v, witness_preference, hir_id, is_under_guard, false); + + // When all the conditions are met we have a match with a `non_exhaustive` enum + // that has the potential to trigger the `non_exhaustive_omitted_patterns` lint. + // To understand the workings checkout `Constructor::split` and `SplitWildcard::new/into_ctors` + if is_non_exhaustive_and_wild + // We check that the match has a wildcard pattern and that that wildcard is useful, + // meaning there are variants that are covered by the wildcard. Without the check + // for `witness_preference` the lint would trigger on `if let NonExhaustiveEnum::A = foo {}` + && usefulness.is_useful() && matches!(witness_preference, RealArm) + && matches!( + &ctor, + Constructor::Missing { nonexhaustive_enum_missing_real_variants: true } + ) + { + let patterns = { + let mut split_wildcard = SplitWildcard::new(pcx); + split_wildcard.split(pcx, matrix.head_ctors(pcx.cx)); + // Construct for each missing constructor a "wild" version of this + // constructor, that matches everything that can be built with + // it. For example, if `ctor` is a `Constructor::Variant` for + // `Option::Some`, we get the pattern `Some(_)`. + split_wildcard + .iter_missing(pcx) + // Filter out the `Constructor::NonExhaustive` variant it's meaningless + // to our lint + .filter(|c| !c.is_non_exhaustive()) + .map(|missing_ctor| { + Fields::wildcards(pcx, missing_ctor).apply(pcx, missing_ctor) + }) + .collect::<Vec<_>>() + }; + + lint_non_exhaustive_omitted_patterns(pcx.cx, pcx.ty, pcx.span, hir_id, patterns); + } + usefulness.apply_constructor(pcx, start_matrix, &ctor, &ctor_wild_subpatterns) }); Usefulness::merge(witness_preference, usefulnesses) }; + debug!(?ret); ret } @@ -1214,8 +1303,7 @@ crate fn compute_match_usefulness<'p, 'tcx>( .copied() .map(|arm| { let v = PatStack::from_pattern(arm.pat); - let usefulness = - is_useful(cx, &matrix, &v, LeaveOutWitness, arm.hir_id, arm.has_guard, true); + let usefulness = is_useful(cx, &matrix, &v, RealArm, arm.hir_id, arm.has_guard, true); if !arm.has_guard { matrix.push(v); } @@ -1232,7 +1320,7 @@ crate fn compute_match_usefulness<'p, 'tcx>( let wild_pattern = cx.pattern_arena.alloc(Pat::wildcard_from_ty(scrut_ty)); let v = PatStack::from_pattern(wild_pattern); - let usefulness = is_useful(cx, &matrix, &v, ConstructWitness, scrut_hir_id, false, true); + let usefulness = is_useful(cx, &matrix, &v, FakeExtraWildcard, scrut_hir_id, false, true); let non_exhaustiveness_witnesses = match usefulness { WithWitnesses(pats) => pats.into_iter().map(|w| w.single_pattern()).collect(), NoWitnesses(_) => bug!(), |
