diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index f9b66239bf9a4..80527966e1966 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -87,6 +87,7 @@ declare_lint_pass! { RUST_2021_PRELUDE_COLLISIONS, SEMICOLON_IN_EXPRESSIONS_FROM_MACROS, SINGLE_USE_LIFETIMES, + SMALL_GAPS_BETWEEN_RANGES, SOFT_UNSTABLE, STABLE_FEATURES, SUSPICIOUS_AUTO_TRAIT_IMPLS, @@ -835,6 +836,36 @@ declare_lint! { "detects range patterns with overlapping endpoints" } +declare_lint! { + /// The `small_gaps_between_ranges` lint detects `match` expressions that use [range patterns] + /// that skip over a single number. + /// + /// [range patterns]: https://doc.rust-lang.org/nightly/reference/patterns.html#range-patterns + /// + /// ### Example + /// + /// ```rust + /// # #![feature(exclusive_range_pattern)] + /// let x = 123u32; + /// match x { + /// 0..100 => { println!("small"); } + /// 101..1000 => { println!("large"); } + /// _ => { println!("larger"); } + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// It is likely a mistake to have range patterns in a match expression that miss out a single + /// number. Check that the beginning and end values are what you expect, and keep in mind that + /// with `..=` the right bound is inclusive, and with `..` it is exclusive. + pub SMALL_GAPS_BETWEEN_RANGES, + Warn, + "detects range patterns separated by a single number" +} + declare_lint! { /// The `bindings_with_variant_name` lint detects pattern bindings with /// the same name as one of the matched variants. diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs index 4943c11848b8a..ade3ea289cc59 100644 --- a/compiler/rustc_middle/src/thir/visit.rs +++ b/compiler/rustc_middle/src/thir/visit.rs @@ -3,26 +3,26 @@ use super::{ PatKind, Stmt, StmtKind, Thir, }; -pub trait Visitor<'a, 'tcx: 'a>: Sized { - fn thir(&self) -> &'a Thir<'tcx>; +pub trait Visitor<'thir, 'tcx: 'thir>: Sized { + fn thir(&self) -> &'thir Thir<'tcx>; - fn visit_expr(&mut self, expr: &Expr<'tcx>) { + fn visit_expr(&mut self, expr: &'thir Expr<'tcx>) { walk_expr(self, expr); } - fn visit_stmt(&mut self, stmt: &Stmt<'tcx>) { + fn visit_stmt(&mut self, stmt: &'thir Stmt<'tcx>) { walk_stmt(self, stmt); } - fn visit_block(&mut self, block: &Block) { + fn visit_block(&mut self, block: &'thir Block) { walk_block(self, block); } - fn visit_arm(&mut self, arm: &Arm<'tcx>) { + fn visit_arm(&mut self, arm: &'thir Arm<'tcx>) { walk_arm(self, arm); } - fn visit_pat(&mut self, pat: &Pat<'tcx>) { + fn visit_pat(&mut self, pat: &'thir Pat<'tcx>) { walk_pat(self, pat); } @@ -36,7 +36,10 @@ pub trait Visitor<'a, 'tcx: 'a>: Sized { // other `visit*` functions. } -pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Expr<'tcx>) { +pub fn walk_expr<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( + visitor: &mut V, + expr: &'thir Expr<'tcx>, +) { use ExprKind::*; match expr.kind { Scope { value, region_scope: _, lint_level: _ } => { @@ -168,7 +171,10 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp } } -pub fn walk_stmt<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, stmt: &Stmt<'tcx>) { +pub fn walk_stmt<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( + visitor: &mut V, + stmt: &'thir Stmt<'tcx>, +) { match &stmt.kind { StmtKind::Expr { expr, scope: _ } => visitor.visit_expr(&visitor.thir()[*expr]), StmtKind::Let { @@ -191,7 +197,10 @@ pub fn walk_stmt<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, stmt: &Stm } } -pub fn walk_block<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, block: &Block) { +pub fn walk_block<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( + visitor: &mut V, + block: &'thir Block, +) { for &stmt in &*block.stmts { visitor.visit_stmt(&visitor.thir()[stmt]); } @@ -200,7 +209,10 @@ pub fn walk_block<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, block: &B } } -pub fn walk_arm<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, arm: &Arm<'tcx>) { +pub fn walk_arm<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( + visitor: &mut V, + arm: &'thir Arm<'tcx>, +) { match arm.guard { Some(Guard::If(expr)) => visitor.visit_expr(&visitor.thir()[expr]), Some(Guard::IfLet(ref pat, expr)) => { @@ -213,7 +225,10 @@ pub fn walk_arm<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, arm: &Arm<' visitor.visit_expr(&visitor.thir()[arm.body]); } -pub fn walk_pat<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, pat: &Pat<'tcx>) { +pub fn walk_pat<'thir, 'tcx: 'thir, V: Visitor<'thir, 'tcx>>( + visitor: &mut V, + pat: &'thir Pat<'tcx>, +) { use PatKind::*; match &pat.kind { AscribeUserType { subpattern, ascription: _ } diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 62190848dd55e..d529c5ea15983 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -175,7 +175,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for LayoutConstrainedPlaceVisitor<'a, 'tcx> { self.thir } - fn visit_expr(&mut self, expr: &Expr<'tcx>) { + fn visit_expr(&mut self, expr: &'a Expr<'tcx>) { match expr.kind { ExprKind::Field { lhs, .. } => { if let ty::Adt(adt_def, _) = self.thir[lhs].ty.kind() { @@ -206,7 +206,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { self.thir } - fn visit_block(&mut self, block: &Block) { + fn visit_block(&mut self, block: &'a Block) { match block.safety_mode { // compiler-generated unsafe code should not count towards the usefulness of // an outer unsafe block @@ -234,7 +234,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { } } - fn visit_pat(&mut self, pat: &Pat<'tcx>) { + fn visit_pat(&mut self, pat: &'a Pat<'tcx>) { if self.in_union_destructure { match pat.kind { // binding to a variable allows getting stuff out of variable @@ -319,7 +319,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { } } - fn visit_expr(&mut self, expr: &Expr<'tcx>) { + fn visit_expr(&mut self, expr: &'a Expr<'tcx>) { // could we be in the LHS of an assignment to a field? match expr.kind { ExprKind::Field { .. } diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 8677cba6a7c3d..f8a2a648a7872 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -453,14 +453,14 @@ pub enum UnusedUnsafeEnclosing { }, } -pub(crate) struct NonExhaustivePatternsTypeNotEmpty<'p, 'tcx, 'm> { - pub cx: &'m RustcMatchCheckCtxt<'p, 'tcx>, +pub(crate) struct NonExhaustivePatternsTypeNotEmpty<'p, 'thir, 'tcx, 'm> { + pub cx: &'m RustcMatchCheckCtxt<'p, 'thir, 'tcx>, pub expr_span: Span, pub span: Span, pub ty: Ty<'tcx>, } -impl<'a> IntoDiagnostic<'a> for NonExhaustivePatternsTypeNotEmpty<'_, '_, '_> { +impl<'a> IntoDiagnostic<'a> for NonExhaustivePatternsTypeNotEmpty<'_, '_, '_, '_> { fn into_diagnostic(self, dcx: &'a DiagCtxt, level: Level) -> DiagnosticBuilder<'_> { let mut diag = DiagnosticBuilder::new( dcx, 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 fcccdf105f649..a6d058731454e 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -82,7 +82,7 @@ struct MatchVisitor<'thir, 'p, 'tcx> { thir: &'thir Thir<'tcx>, lint_level: HirId, let_source: LetSource, - pattern_arena: &'p TypedArena>, + pattern_arena: &'p TypedArena>, dropless_arena: &'p DroplessArena, /// Tracks if we encountered an error while checking this body. That the first function to /// report it stores it here. Some functions return `Result` to allow callers to short-circuit @@ -98,7 +98,7 @@ impl<'thir, 'tcx> Visitor<'thir, 'tcx> for MatchVisitor<'thir, '_, 'tcx> { } #[instrument(level = "trace", skip(self))] - fn visit_arm(&mut self, arm: &Arm<'tcx>) { + fn visit_arm(&mut self, arm: &'thir Arm<'tcx>) { self.with_lint_level(arm.lint_level, |this| { match arm.guard { Some(Guard::If(expr)) => { @@ -121,7 +121,7 @@ impl<'thir, 'tcx> Visitor<'thir, 'tcx> for MatchVisitor<'thir, '_, 'tcx> { } #[instrument(level = "trace", skip(self))] - fn visit_expr(&mut self, ex: &Expr<'tcx>) { + fn visit_expr(&mut self, ex: &'thir Expr<'tcx>) { match ex.kind { ExprKind::Scope { value, lint_level, .. } => { self.with_lint_level(lint_level, |this| { @@ -174,7 +174,7 @@ impl<'thir, 'tcx> Visitor<'thir, 'tcx> for MatchVisitor<'thir, '_, 'tcx> { self.with_let_source(LetSource::None, |this| visit::walk_expr(this, ex)); } - fn visit_stmt(&mut self, stmt: &Stmt<'tcx>) { + fn visit_stmt(&mut self, stmt: &'thir Stmt<'tcx>) { match stmt.kind { StmtKind::Let { box ref pattern, initializer, else_block, lint_level, span, .. @@ -224,7 +224,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { /// subexpressions we are not handling ourselves. fn visit_land( &mut self, - ex: &Expr<'tcx>, + ex: &'thir Expr<'tcx>, accumulator: &mut Vec>, ) -> Result<(), ErrorGuaranteed> { match ex.kind { @@ -251,7 +251,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { /// expression. This must call `visit_expr` on the subexpressions we are not handling ourselves. fn visit_land_rhs( &mut self, - ex: &Expr<'tcx>, + ex: &'thir Expr<'tcx>, ) -> Result, ErrorGuaranteed> { match ex.kind { ExprKind::Scope { value, lint_level, .. } => { @@ -275,9 +275,9 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { fn lower_pattern( &mut self, - cx: &MatchCheckCtxt<'p, 'tcx>, - pat: &Pat<'tcx>, - ) -> Result<&'p DeconstructedPat<'p, 'tcx>, ErrorGuaranteed> { + cx: &MatchCheckCtxt<'p, 'thir, 'tcx>, + pat: &'thir Pat<'tcx>, + ) -> Result<&'p DeconstructedPat<'p, 'thir, 'tcx>, ErrorGuaranteed> { if let Err(err) = pat.pat_error_reported() { self.error = Err(err); Err(err) @@ -370,7 +370,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { whole_match_span: Option, scrutinee: Option<&Expr<'tcx>>, scrut_span: Span, - ) -> MatchCheckCtxt<'p, 'tcx> { + ) -> MatchCheckCtxt<'p, 'thir, 'tcx> { let refutable = match refutability { Irrefutable => false, Refutable => true, @@ -395,7 +395,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { } #[instrument(level = "trace", skip(self))] - fn check_let(&mut self, pat: &Pat<'tcx>, scrutinee: Option, span: Span) { + fn check_let(&mut self, pat: &'thir Pat<'tcx>, scrutinee: Option, span: Span) { assert!(self.let_source != LetSource::None); let scrut = scrutinee.map(|id| &self.thir[id]); if let LetSource::PlainLet = self.let_source { @@ -547,10 +547,11 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { fn analyze_binding( &mut self, - pat: &Pat<'tcx>, + pat: &'thir Pat<'tcx>, refutability: RefutableFlag, scrut: Option<&Expr<'tcx>>, - ) -> Result<(MatchCheckCtxt<'p, 'tcx>, UsefulnessReport<'p, 'tcx>), ErrorGuaranteed> { + ) -> Result<(MatchCheckCtxt<'p, 'thir, 'tcx>, UsefulnessReport<'p, 'thir, 'tcx>), ErrorGuaranteed> + { let cx = self.new_cx(refutability, None, scrut, pat.span); let pat = self.lower_pattern(&cx, pat)?; let arms = [MatchArm { pat, arm_data: self.lint_level, has_guard: false }]; @@ -560,7 +561,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { fn is_let_irrefutable( &mut self, - pat: &Pat<'tcx>, + pat: &'thir Pat<'tcx>, scrut: Option<&Expr<'tcx>>, ) -> Result { let (cx, report) = self.analyze_binding(pat, Refutable, scrut)?; @@ -575,7 +576,7 @@ impl<'thir, 'p, 'tcx> MatchVisitor<'thir, 'p, 'tcx> { #[instrument(level = "trace", skip(self))] fn check_binding_is_irrefutable( &mut self, - pat: &Pat<'tcx>, + pat: &'thir Pat<'tcx>, origin: &str, scrut: Option<&Expr<'tcx>>, sp: Option, @@ -836,9 +837,9 @@ fn report_irrefutable_let_patterns( } /// Report unreachable arms, if any. -fn report_arm_reachability<'p, 'tcx>( - cx: &MatchCheckCtxt<'p, 'tcx>, - report: &UsefulnessReport<'p, 'tcx>, +fn report_arm_reachability<'p, 'thir, 'tcx>( + cx: &MatchCheckCtxt<'p, 'thir, 'tcx>, + report: &UsefulnessReport<'p, 'thir, 'tcx>, ) { let report_unreachable_pattern = |span, hir_id, catchall: Option| { cx.tcx.emit_spanned_lint( @@ -856,27 +857,27 @@ fn report_arm_reachability<'p, 'tcx>( for (arm, is_useful) in report.arm_usefulness.iter() { match is_useful { Usefulness::Redundant => { - report_unreachable_pattern(*arm.pat.data().unwrap(), arm.arm_data, catchall) + report_unreachable_pattern(arm.pat.data().unwrap().span, arm.arm_data, catchall) } Usefulness::Useful(redundant_subpats) if redundant_subpats.is_empty() => {} // The arm is reachable, but contains redundant subpatterns (from or-patterns). Usefulness::Useful(redundant_subpats) => { let mut redundant_subpats = redundant_subpats.clone(); // Emit lints in the order in which they occur in the file. - redundant_subpats.sort_unstable_by_key(|pat| pat.data()); + redundant_subpats.sort_unstable_by_key(|pat| pat.data().unwrap().span); for pat in redundant_subpats { - report_unreachable_pattern(*pat.data().unwrap(), arm.arm_data, None); + report_unreachable_pattern(pat.data().unwrap().span, arm.arm_data, None); } } } if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) { - catchall = Some(*arm.pat.data().unwrap()); + catchall = Some(arm.pat.data().unwrap().span); } } } /// Checks for common cases of "catchall" patterns that may not be intended as such. -fn pat_is_catchall(pat: &DeconstructedPat<'_, '_>) -> bool { +fn pat_is_catchall(pat: &DeconstructedPat<'_, '_, '_>) -> bool { match pat.ctor() { Constructor::Wildcard => true, Constructor::Struct | Constructor::Ref => pat.iter_fields().all(|pat| pat_is_catchall(pat)), @@ -885,12 +886,12 @@ fn pat_is_catchall(pat: &DeconstructedPat<'_, '_>) -> bool { } /// Report that a match is not exhaustive. -fn report_non_exhaustive_match<'p, 'tcx>( - cx: &MatchCheckCtxt<'p, 'tcx>, +fn report_non_exhaustive_match<'p, 'thir, 'tcx>( + cx: &MatchCheckCtxt<'p, 'thir, 'tcx>, thir: &Thir<'tcx>, scrut_ty: Ty<'tcx>, sp: Span, - witnesses: Vec>, + witnesses: Vec>, arms: &[ArmId], expr_span: Span, ) -> ErrorGuaranteed { @@ -1085,12 +1086,12 @@ fn report_non_exhaustive_match<'p, 'tcx>( err.emit() } -fn joined_uncovered_patterns<'p, 'tcx>( - cx: &MatchCheckCtxt<'p, 'tcx>, - witnesses: &[WitnessPat<'p, 'tcx>], +fn joined_uncovered_patterns<'p, 'thir, 'tcx>( + cx: &MatchCheckCtxt<'p, 'thir, 'tcx>, + witnesses: &[WitnessPat<'p, 'thir, 'tcx>], ) -> String { const LIMIT: usize = 3; - let pat_to_str = |pat: &WitnessPat<'p, 'tcx>| cx.hoist_witness_pat(pat).to_string(); + let pat_to_str = |pat: &WitnessPat<'p, 'thir, 'tcx>| cx.hoist_witness_pat(pat).to_string(); match witnesses { [] => bug!(), [witness] => format!("`{}`", cx.hoist_witness_pat(witness)), @@ -1107,8 +1108,8 @@ fn joined_uncovered_patterns<'p, 'tcx>( } fn collect_non_exhaustive_tys<'tcx>( - cx: &MatchCheckCtxt<'_, 'tcx>, - pat: &WitnessPat<'_, 'tcx>, + cx: &MatchCheckCtxt<'_, '_, 'tcx>, + pat: &WitnessPat<'_, '_, 'tcx>, non_exhaustive_tys: &mut FxIndexSet>, ) { if matches!(pat.ctor(), Constructor::NonExhaustive) { @@ -1127,7 +1128,7 @@ fn collect_non_exhaustive_tys<'tcx>( fn report_adt_defined_here<'tcx>( tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, - witnesses: &[WitnessPat<'_, 'tcx>], + witnesses: &[WitnessPat<'_, '_, 'tcx>], point_at_non_local_ty: bool, ) -> Option> { let ty = ty.peel_refs(); @@ -1149,10 +1150,10 @@ fn report_adt_defined_here<'tcx>( Some(AdtDefinedHere { adt_def_span, ty, variants }) } -fn maybe_point_at_variant<'a, 'p: 'a, 'tcx: 'p>( +fn maybe_point_at_variant<'a, 'p: 'a, 'thir: 'p, 'tcx: 'thir>( tcx: TyCtxt<'tcx>, def: AdtDef<'tcx>, - patterns: impl Iterator>, + patterns: impl Iterator>, ) -> Vec { let mut covered = vec![]; for pattern in patterns { diff --git a/compiler/rustc_pattern_analysis/messages.ftl b/compiler/rustc_pattern_analysis/messages.ftl index 827928f97d7cb..a3d8d94a4d64c 100644 --- a/compiler/rustc_pattern_analysis/messages.ftl +++ b/compiler/rustc_pattern_analysis/messages.ftl @@ -11,6 +11,10 @@ pattern_analysis_overlapping_range_endpoints = multiple patterns overlap on thei .label = ... with this range .note = you likely meant to write mutually exclusive ranges +pattern_analysis_small_gap_between_ranges = multiple ranges are one apart + .label = this range doesn't match `{$gap}` because `..` is a non-inclusive range + .suggestion = use an inclusive range instead + pattern_analysis_uncovered = {$count -> [1] pattern `{$witness_1}` [2] patterns `{$witness_1}` and `{$witness_2}` diff --git a/compiler/rustc_pattern_analysis/src/constructor.rs b/compiler/rustc_pattern_analysis/src/constructor.rs index b688051ca9ccb..00f2fbf5d51e2 100644 --- a/compiler/rustc_pattern_analysis/src/constructor.rs +++ b/compiler/rustc_pattern_analysis/src/constructor.rs @@ -193,7 +193,7 @@ impl fmt::Display for RangeEnd { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum MaybeInfiniteInt { NegInfinity, - /// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`. + /// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite_{int,uint}`. #[non_exhaustive] Finite(u128), /// The integer after `u128::MAX`. We need it to represent `x..=u128::MAX` as an exclusive range. @@ -230,25 +230,22 @@ impl MaybeInfiniteInt { } /// Note: this will not turn a finite value into an infinite one or vice-versa. - pub fn minus_one(self) -> Self { + pub fn minus_one(self) -> Option { match self { - Finite(n) => match n.checked_sub(1) { - Some(m) => Finite(m), - None => panic!("Called `MaybeInfiniteInt::minus_one` on 0"), - }, - JustAfterMax => Finite(u128::MAX), - x => x, + Finite(n) => n.checked_sub(1).map(Finite), + JustAfterMax => Some(Finite(u128::MAX)), + x => Some(x), } } /// Note: this will not turn a finite value into an infinite one or vice-versa. - pub fn plus_one(self) -> Self { + pub fn plus_one(self) -> Option { match self { Finite(n) => match n.checked_add(1) { - Some(m) => Finite(m), - None => JustAfterMax, + Some(m) => Some(Finite(m)), + None => Some(JustAfterMax), }, - JustAfterMax => panic!("Called `MaybeInfiniteInt::plus_one` on u128::MAX+1"), - x => x, + JustAfterMax => None, + x => Some(x), } } } @@ -269,18 +266,25 @@ impl IntRange { pub fn is_singleton(&self) -> bool { // Since `lo` and `hi` can't be the same `Infinity` and `plus_one` never changes from finite // to infinite, this correctly only detects ranges that contain exacly one `Finite(x)`. - self.lo.plus_one() == self.hi + // `unwrap()` is ok since `self.lo` will never be `JustAfterMax`. + self.lo.plus_one().unwrap() == self.hi } + /// Construct a singleton range. + /// `x` be a `Finite(_)` value. #[inline] pub fn from_singleton(x: MaybeInfiniteInt) -> IntRange { - IntRange { lo: x, hi: x.plus_one() } + // `unwrap()` is ok on a finite value + IntRange { lo: x, hi: x.plus_one().unwrap() } } + /// Construct a range with these boundaries. + /// `lo` must not be `PosInfinity` or `JustAfterMax`. `hi` must not be `NegInfinity`. + /// If `end` is `Included`, `hi` must also not be `JustAfterMax`. #[inline] pub fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange { if end == RangeEnd::Included { - hi = hi.plus_one(); + hi = hi.plus_one().unwrap(); } if lo >= hi { // This should have been caught earlier by E0030. diff --git a/compiler/rustc_pattern_analysis/src/errors.rs b/compiler/rustc_pattern_analysis/src/errors.rs index 88770b0c43b37..d8737e63add45 100644 --- a/compiler/rustc_pattern_analysis/src/errors.rs +++ b/compiler/rustc_pattern_analysis/src/errors.rs @@ -19,10 +19,10 @@ pub struct Uncovered<'tcx> { } impl<'tcx> Uncovered<'tcx> { - pub fn new<'p>( + pub fn new<'p, 'thir>( span: Span, - cx: &RustcMatchCheckCtxt<'p, 'tcx>, - witnesses: Vec>, + cx: &RustcMatchCheckCtxt<'p, 'thir, 'tcx>, + witnesses: Vec>, ) -> Self { let witness_1 = cx.hoist_witness_pat(witnesses.get(0).unwrap()); Self { @@ -72,6 +72,43 @@ impl<'tcx> AddToDiagnostic for Overlap<'tcx> { } } +#[derive(LintDiagnostic)] +#[diag(pattern_analysis_small_gap_between_ranges)] +pub struct SmallGapBetweenRanges<'tcx> { + #[label] + #[suggestion(code = "{suggestion}", applicability = "maybe-incorrect")] + /// This is an exclusive range that looks like `lo..gap` (i.e. doesn't match `gap`). + pub first_range: Span, + /// Suggest `lo..=gap` instead. + pub suggestion: String, + pub gap: Pat<'tcx>, + #[subdiagnostic] + /// All these ranges skipped over `gap` which we think is probably a mistake. + pub gap_with: Vec>, +} + +pub struct GappedRange<'tcx> { + pub span: Span, + pub gap: Pat<'tcx>, + pub first_range: Pat<'tcx>, +} + +impl<'tcx> AddToDiagnostic for GappedRange<'tcx> { + fn add_to_diagnostic_with(self, diag: &mut Diagnostic, _: F) + where + F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage, + { + let GappedRange { span, gap, first_range } = self; + + // FIXME(mejrs) unfortunately `#[derive(LintDiagnostic)]` + // does not support `#[subdiagnostic(eager)]`... + let message = format!( + "this seems to continue range `{first_range}`, but `{gap}` isn't matched by either of them" + ); + diag.span_label(span, message); + } +} + #[derive(LintDiagnostic)] #[diag(pattern_analysis_non_exhaustive_omitted_pattern)] #[help] diff --git a/compiler/rustc_pattern_analysis/src/lib.rs b/compiler/rustc_pattern_analysis/src/lib.rs index a1c9b15766676..a45d66ad7d77b 100644 --- a/compiler/rustc_pattern_analysis/src/lib.rs +++ b/compiler/rustc_pattern_analysis/src/lib.rs @@ -26,15 +26,7 @@ use rustc_index::Idx; use rustc_middle::ty::Ty; use crate::constructor::{Constructor, ConstructorSet}; -#[cfg(feature = "rustc")] -use crate::lints::{ - lint_nonexhaustive_missing_variants, lint_overlapping_range_endpoints, PatternColumn, -}; use crate::pat::DeconstructedPat; -#[cfg(feature = "rustc")] -use crate::rustc::RustcMatchCheckCtxt; -#[cfg(feature = "rustc")] -use crate::usefulness::{compute_match_usefulness, ValidityConstraint}; // It's not possible to only enable the `typed_arena` dependency when the `rustc` feature is off, so // we use another feature instead. The crate won't compile if one of these isn't enabled. @@ -107,27 +99,33 @@ pub struct MatchArm<'p, Cx: TypeCx> { /// The entrypoint for this crate. Computes whether a match is exhaustive and which of its arms are /// useful, and runs some lints. #[cfg(feature = "rustc")] -pub fn analyze_match<'p, 'tcx>( - tycx: &RustcMatchCheckCtxt<'p, 'tcx>, - arms: &[rustc::MatchArm<'p, 'tcx>], +pub fn analyze_match<'p, 'thir, 'tcx: 'thir>( + tycx: &rustc::RustcMatchCheckCtxt<'p, 'thir, 'tcx>, + arms: &[rustc::MatchArm<'p, 'thir, 'tcx>], scrut_ty: Ty<'tcx>, -) -> rustc::UsefulnessReport<'p, 'tcx> { +) -> rustc::UsefulnessReport<'p, 'thir, 'tcx> { + use crate::lints::PatternColumn; + use crate::usefulness::ValidityConstraint; + // Arena to store the extra wildcards we construct during analysis. let wildcard_arena = tycx.pattern_arena; let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee); let cx = MatchCtxt { tycx, wildcard_arena }; - let report = compute_match_usefulness(cx, arms, scrut_ty, scrut_validity); + let report = usefulness::compute_match_usefulness(cx, arms, scrut_ty, scrut_validity); - let pat_column = PatternColumn::new(arms); + // Only run the lints if the match is exhaustive. + if report.non_exhaustiveness_witnesses.is_empty() { + let pat_column = PatternColumn::new(arms); - // Lint on ranges that overlap on their endpoints, which is likely a mistake. - lint_overlapping_range_endpoints(cx, &pat_column); + // Detect possible range-related mistakes. + lints::lint_likely_range_mistakes(cx, &pat_column); - // Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting - // `if let`s. Only run if the match is exhaustive otherwise the error is redundant. - if tycx.refutable && report.non_exhaustiveness_witnesses.is_empty() { - lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty) + // Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid + // hitting `if let`s. + if tycx.refutable { + lints::lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty) + } } report diff --git a/compiler/rustc_pattern_analysis/src/lints.rs b/compiler/rustc_pattern_analysis/src/lints.rs index 2be6e8e3db346..065a7f7577e10 100644 --- a/compiler/rustc_pattern_analysis/src/lints.rs +++ b/compiler/rustc_pattern_analysis/src/lints.rs @@ -1,16 +1,15 @@ +use rustc_hir::RangeEnd; use smallvec::SmallVec; use rustc_data_structures::captures::Captures; +use rustc_middle::thir; use rustc_middle::ty::{self, Ty}; use rustc_session::lint; use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS; use rustc_span::Span; use crate::constructor::{IntRange, MaybeInfiniteInt}; -use crate::errors::{ - NonExhaustiveOmittedPattern, NonExhaustiveOmittedPatternLintOnArm, Overlap, - OverlappingRangeEndpoints, Uncovered, -}; +use crate::errors; use crate::rustc::{ Constructor, DeconstructedPat, MatchArm, MatchCtxt, PlaceCtxt, RustcMatchCheckCtxt, SplitConstructorSet, WitnessPat, @@ -28,12 +27,12 @@ use crate::TypeCx; /// /// This is not used in the main algorithm; only in lints. #[derive(Debug)] -pub(crate) struct PatternColumn<'a, 'p, 'tcx> { - patterns: Vec<&'a DeconstructedPat<'p, 'tcx>>, +pub(crate) struct PatternColumn<'a, 'p, 'thir, 'tcx> { + patterns: Vec<&'a DeconstructedPat<'p, 'thir, 'tcx>>, } -impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> { - pub(crate) fn new(arms: &[MatchArm<'p, 'tcx>]) -> Self { +impl<'a, 'p, 'thir, 'tcx> PatternColumn<'a, 'p, 'thir, 'tcx> { + pub(crate) fn new(arms: &[MatchArm<'p, 'thir, 'tcx>]) -> Self { let mut patterns = Vec::with_capacity(arms.len()); for arm in arms { if arm.pat.is_or_pat() { @@ -48,7 +47,7 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> { fn is_empty(&self) -> bool { self.patterns.is_empty() } - fn head_ty(&self, cx: MatchCtxt<'a, 'p, 'tcx>) -> Option> { + fn head_ty(&self, cx: MatchCtxt<'a, 'p, 'thir, 'tcx>) -> Option> { if self.patterns.len() == 0 { return None; } @@ -59,12 +58,17 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> { } /// Do constructor splitting on the constructors of the column. - fn analyze_ctors(&self, pcx: &PlaceCtxt<'_, 'p, 'tcx>) -> SplitConstructorSet<'p, 'tcx> { + fn analyze_ctors( + &self, + pcx: &PlaceCtxt<'_, 'p, 'thir, 'tcx>, + ) -> SplitConstructorSet<'p, 'thir, 'tcx> { let column_ctors = self.patterns.iter().map(|p| p.ctor()); pcx.ctors_for_ty().split(pcx, column_ctors) } - fn iter<'b>(&'b self) -> impl Iterator> + Captures<'b> { + fn iter<'b>( + &'b self, + ) -> impl Iterator> + Captures<'b> { self.patterns.iter().copied() } @@ -75,9 +79,9 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> { /// which may change the lengths. fn specialize( &self, - pcx: &PlaceCtxt<'a, 'p, 'tcx>, - ctor: &Constructor<'p, 'tcx>, - ) -> Vec> { + pcx: &PlaceCtxt<'a, 'p, 'thir, 'tcx>, + ctor: &Constructor<'p, 'thir, 'tcx>, + ) -> Vec> { let arity = ctor.arity(pcx); if arity == 0 { return Vec::new(); @@ -113,10 +117,10 @@ impl<'a, 'p, 'tcx> PatternColumn<'a, 'p, 'tcx> { /// Traverse the patterns to collect any variants of a non_exhaustive enum that fail to be mentioned /// in a given column. #[instrument(level = "debug", skip(cx), ret)] -fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>( - cx: MatchCtxt<'a, 'p, 'tcx>, - column: &PatternColumn<'a, 'p, 'tcx>, -) -> Vec> { +fn collect_nonexhaustive_missing_variants<'a, 'p, 'thir, 'tcx>( + cx: MatchCtxt<'a, 'p, 'thir, 'tcx>, + column: &PatternColumn<'a, 'p, 'thir, 'tcx>, +) -> Vec> { let Some(ty) = column.head_ty(cx) else { return Vec::new(); }; @@ -160,13 +164,13 @@ fn collect_nonexhaustive_missing_variants<'a, 'p, 'tcx>( witnesses } -pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>( - cx: MatchCtxt<'a, 'p, 'tcx>, - arms: &[MatchArm<'p, 'tcx>], - pat_column: &PatternColumn<'a, 'p, 'tcx>, +pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'thir, 'tcx>( + cx: MatchCtxt<'a, 'p, 'thir, 'tcx>, + arms: &[MatchArm<'p, 'thir, 'tcx>], + pat_column: &PatternColumn<'a, 'p, 'thir, 'tcx>, scrut_ty: Ty<'tcx>, ) { - let rcx: &RustcMatchCheckCtxt<'_, '_> = cx.tycx; + let rcx: &RustcMatchCheckCtxt<'_, '_, '_> = cx.tycx; if !matches!( rcx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, rcx.match_lint_level).0, rustc_session::lint::Level::Allow @@ -181,9 +185,9 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>( NON_EXHAUSTIVE_OMITTED_PATTERNS, rcx.match_lint_level, rcx.scrut_span, - NonExhaustiveOmittedPattern { + errors::NonExhaustiveOmittedPattern { scrut_ty, - uncovered: Uncovered::new(rcx.scrut_span, rcx, witnesses), + uncovered: errors::Uncovered::new(rcx.scrut_span, rcx, witnesses), }, ); } @@ -195,7 +199,7 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>( let (lint_level, lint_level_source) = rcx.tcx.lint_level_at_node(NON_EXHAUSTIVE_OMITTED_PATTERNS, arm.arm_data); if !matches!(lint_level, rustc_session::lint::Level::Allow) { - let decorator = NonExhaustiveOmittedPatternLintOnArm { + let decorator = errors::NonExhaustiveOmittedPatternLintOnArm { lint_span: lint_level_source.span(), suggest_lint_on_match: rcx.whole_match_span.map(|span| span.shrink_to_lo()), lint_level: lint_level.as_str(), @@ -203,7 +207,7 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>( }; use rustc_errors::DecorateLint; - let mut err = rcx.tcx.sess.struct_span_warn(*arm.pat.data().unwrap(), ""); + let mut err = rcx.tcx.sess.struct_span_warn(arm.pat.data().unwrap().span, ""); err.set_primary_message(decorator.msg()); decorator.decorate_lint(&mut err); err.emit(); @@ -212,39 +216,40 @@ pub(crate) fn lint_nonexhaustive_missing_variants<'a, 'p, 'tcx>( } } -/// Traverse the patterns to warn the user about ranges that overlap on their endpoints. +/// Traverse the patterns to warn the user about ranges that overlap on their endpoints or are +/// distant by one. #[instrument(level = "debug", skip(cx))] -pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>( - cx: MatchCtxt<'a, 'p, 'tcx>, - column: &PatternColumn<'a, 'p, 'tcx>, +pub(crate) fn lint_likely_range_mistakes<'a, 'p, 'thir, 'tcx>( + cx: MatchCtxt<'a, 'p, 'thir, 'tcx>, + column: &PatternColumn<'a, 'p, 'thir, 'tcx>, ) { let Some(ty) = column.head_ty(cx) else { return; }; let pcx = &PlaceCtxt::new_dummy(cx, ty); - let rcx: &RustcMatchCheckCtxt<'_, '_> = cx.tycx; + let rcx: &RustcMatchCheckCtxt<'_, '_, '_> = cx.tycx; let set = column.analyze_ctors(pcx); if matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_)) { - let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| { + let emit_overlap_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| { let overlap_as_pat = rcx.hoist_pat_range(overlap, ty); let overlaps: Vec<_> = overlapped_spans .iter() .copied() - .map(|span| Overlap { range: overlap_as_pat.clone(), span }) + .map(|span| errors::Overlap { range: overlap_as_pat.clone(), span }) .collect(); rcx.tcx.emit_spanned_lint( lint::builtin::OVERLAPPING_RANGE_ENDPOINTS, rcx.match_lint_level, this_span, - OverlappingRangeEndpoints { overlap: overlaps, range: this_span }, + errors::OverlappingRangeEndpoints { overlap: overlaps, range: this_span }, ); }; - // If two ranges overlapped, the split set will contain their intersection as a singleton. - let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range()); - for overlap_range in split_int_ranges.clone() { + let present_int_ranges = set.present.iter().filter_map(|c| c.as_int_range()); + for overlap_range in present_int_ranges { + // A single point overlap will show up as a singleton after range splitting. if overlap_range.is_singleton() { let overlap: MaybeInfiniteInt = overlap_range.lo; // Ranges that look like `lo..=overlap`. @@ -253,35 +258,103 @@ pub(crate) fn lint_overlapping_range_endpoints<'a, 'p, 'tcx>( let mut suffixes: SmallVec<[_; 1]> = Default::default(); // Iterate on patterns that contained `overlap`. for pat in column.iter() { + // Look through the column for ranges that start/end in the overlap. let Constructor::IntRange(this_range) = pat.ctor() else { continue }; - let this_span = *pat.data().unwrap(); + let this_span = pat.data().unwrap().span; if this_range.is_singleton() { // Don't lint when one of the ranges is a singleton. continue; } - if this_range.lo == overlap { + if overlap == this_range.lo { // `this_range` looks like `overlap..=this_range.hi`; it overlaps with any // ranges that look like `lo..=overlap`. if !prefixes.is_empty() { - emit_lint(overlap_range, this_span, &prefixes); + emit_overlap_lint(overlap_range, this_span, &prefixes); } suffixes.push(this_span) - } else if this_range.hi == overlap.plus_one() { + } else if overlap.plus_one() == Some(this_range.hi) { // `this_range` looks like `this_range.lo..=overlap`; it overlaps with any // ranges that look like `overlap..=hi`. if !suffixes.is_empty() { - emit_lint(overlap_range, this_span, &suffixes); + emit_overlap_lint(overlap_range, this_span, &suffixes); } prefixes.push(this_span) } } } } + + let missing_int_ranges = set.missing.iter().filter_map(|c| c.as_int_range()); + for gap_range in missing_int_ranges { + // A single missing point will show up as a singleton after range splitting. + if gap_range.is_singleton() { + let gap: MaybeInfiniteInt = gap_range.lo; + // Ranges that look like `lo..gap` (exclusive ones only). + let mut onebefore: SmallVec<[_; 1]> = Default::default(); + // Ranges that start on `gap+1` or singletons `gap+1`. + let mut oneafter: SmallVec<[_; 1]> = Default::default(); + for pat in column.iter() { + // Look through the column for ranges near the gap. + let Constructor::IntRange(this_range) = pat.ctor() else { continue }; + let this_pat: &thir::Pat<'_> = pat.data().unwrap(); + + if gap == this_range.hi && !this_range.is_singleton() { + if let thir::PatKind::Range(range) = &this_pat.kind { + if range.end == RangeEnd::Excluded { + onebefore.push(this_pat) + } + } + } else if gap.plus_one() == Some(this_range.lo) { + oneafter.push(this_pat) + } + } + + if !onebefore.is_empty() && !oneafter.is_empty() { + let gap_as_pat = rcx.hoist_pat_range(gap_range, ty); + for pat_before in onebefore { + // `pat_before` is an exclusive range like `lo..gap`. `oneafter` contains + // ranges that start with `gap+1`. + let suggested_range: thir::Pat<'_> = { + // Suggest `lo..=gap` instead. + let mut suggested_range = pat_before.clone(); + let thir::PatKind::Range(range) = &mut suggested_range.kind else { + unreachable!() + }; + range.end = RangeEnd::Included; + suggested_range + }; + rcx.tcx.emit_spanned_lint( + lint::builtin::SMALL_GAPS_BETWEEN_RANGES, + rcx.match_lint_level, + pat_before.span, + errors::SmallGapBetweenRanges { + // Point at this range. + first_range: pat_before.span, + // That's the gap that isn't covered. + gap: gap_as_pat.clone(), + // Suggest `lo..=gap` instead. + suggestion: suggested_range.to_string(), + // All these ranges skipped over `gap` which we think is probably a + // mistake. + gap_with: oneafter + .iter() + .map(|pat| errors::GappedRange { + span: pat.span, + gap: gap_as_pat.clone(), + first_range: pat_before.clone(), + }) + .collect(), + }, + ); + } + } + } + } } else { // Recurse into the fields. for ctor in set.present { for col in column.specialize(pcx, &ctor) { - lint_overlapping_range_endpoints(cx, &col); + lint_likely_range_mistakes(cx, &col); } } } diff --git a/compiler/rustc_pattern_analysis/src/rustc.rs b/compiler/rustc_pattern_analysis/src/rustc.rs index a5a47724f3f02..7d64e9836875d 100644 --- a/compiler/rustc_pattern_analysis/src/rustc.rs +++ b/compiler/rustc_pattern_analysis/src/rustc.rs @@ -25,24 +25,28 @@ use crate::TypeCx; use crate::constructor::Constructor::*; // Re-export rustc-specific versions of all these types. -pub type Constructor<'p, 'tcx> = crate::constructor::Constructor>; -pub type ConstructorSet<'p, 'tcx> = - crate::constructor::ConstructorSet>; -pub type DeconstructedPat<'p, 'tcx> = - crate::pat::DeconstructedPat<'p, RustcMatchCheckCtxt<'p, 'tcx>>; -pub type MatchArm<'p, 'tcx> = crate::MatchArm<'p, RustcMatchCheckCtxt<'p, 'tcx>>; -pub type MatchCtxt<'a, 'p, 'tcx> = crate::MatchCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'tcx>>; -pub(crate) type PlaceCtxt<'a, 'p, 'tcx> = - crate::usefulness::PlaceCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'tcx>>; -pub(crate) type SplitConstructorSet<'p, 'tcx> = - crate::constructor::SplitConstructorSet>; -pub type Usefulness<'p, 'tcx> = crate::usefulness::Usefulness<'p, RustcMatchCheckCtxt<'p, 'tcx>>; -pub type UsefulnessReport<'p, 'tcx> = - crate::usefulness::UsefulnessReport<'p, RustcMatchCheckCtxt<'p, 'tcx>>; -pub type WitnessPat<'p, 'tcx> = crate::pat::WitnessPat>; +pub type Constructor<'p, 'thir, 'tcx> = + crate::constructor::Constructor>; +pub type ConstructorSet<'p, 'thir, 'tcx> = + crate::constructor::ConstructorSet>; +pub type DeconstructedPat<'p, 'thir, 'tcx> = + crate::pat::DeconstructedPat<'p, RustcMatchCheckCtxt<'p, 'thir, 'tcx>>; +pub type MatchArm<'p, 'thir, 'tcx> = crate::MatchArm<'p, RustcMatchCheckCtxt<'p, 'thir, 'tcx>>; +pub type MatchCtxt<'a, 'p, 'thir, 'tcx> = + crate::MatchCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'thir, 'tcx>>; +pub(crate) type PlaceCtxt<'a, 'p, 'thir, 'tcx> = + crate::usefulness::PlaceCtxt<'a, 'p, RustcMatchCheckCtxt<'p, 'thir, 'tcx>>; +pub(crate) type SplitConstructorSet<'p, 'thir, 'tcx> = + crate::constructor::SplitConstructorSet>; +pub type Usefulness<'p, 'thir, 'tcx> = + crate::usefulness::Usefulness<'p, RustcMatchCheckCtxt<'p, 'thir, 'tcx>>; +pub type UsefulnessReport<'p, 'thir, 'tcx> = + crate::usefulness::UsefulnessReport<'p, RustcMatchCheckCtxt<'p, 'thir, 'tcx>>; +pub type WitnessPat<'p, 'thir, 'tcx> = crate::pat::WitnessPat>; #[derive(Clone)] -pub struct RustcMatchCheckCtxt<'p, 'tcx> { +#[allow(explicit_outlives_requirements)] // FIXME #119228 +pub struct RustcMatchCheckCtxt<'p, 'thir, 'tcx: 'thir> { pub tcx: TyCtxt<'tcx>, pub typeck_results: &'tcx ty::TypeckResults<'tcx>, /// The module in which the match occurs. This is necessary for @@ -52,7 +56,7 @@ pub struct RustcMatchCheckCtxt<'p, 'tcx> { /// outside its module and should not be matchable with an empty match statement. pub module: DefId, pub param_env: ty::ParamEnv<'tcx>, - pub pattern_arena: &'p TypedArena>, + pub pattern_arena: &'p TypedArena>, pub dropless_arena: &'p DroplessArena, /// Lint level at the match. pub match_lint_level: HirId, @@ -67,13 +71,13 @@ pub struct RustcMatchCheckCtxt<'p, 'tcx> { pub known_valid_scrutinee: bool, } -impl<'p, 'tcx> fmt::Debug for RustcMatchCheckCtxt<'p, 'tcx> { +impl<'p, 'thir, 'tcx> fmt::Debug for RustcMatchCheckCtxt<'p, 'thir, 'tcx> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RustcMatchCheckCtxt").finish() } } -impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { +impl<'p, 'thir, 'tcx> RustcMatchCheckCtxt<'p, 'thir, 'tcx> { fn reveal_opaque(&self, key: OpaqueTypeKey<'tcx>) -> Option> { self.typeck_results.concrete_opaque_types.get(&key).map(|x| x.ty) } @@ -132,7 +136,8 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { &'a self, ty: Ty<'tcx>, variant: &'a VariantDef, - ) -> impl Iterator)> + Captures<'p> + Captures<'a> { + ) -> impl Iterator)> + Captures<'thir> + Captures<'p> + Captures<'a> + { let cx = self; let ty::Adt(adt, args) = ty.kind() else { bug!() }; // Whether we must not match the fields of this variant exhaustively. @@ -154,7 +159,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { } pub(crate) fn variant_index_for_adt( - ctor: &Constructor<'p, 'tcx>, + ctor: &Constructor<'p, 'thir, 'tcx>, adt: ty::AdtDef<'tcx>, ) -> VariantIdx { match *ctor { @@ -170,7 +175,11 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { /// Returns the types of the fields for a given constructor. The result must have a length of /// `ctor.arity()`. #[instrument(level = "trace", skip(self))] - pub(crate) fn ctor_sub_tys(&self, ctor: &Constructor<'p, 'tcx>, ty: Ty<'tcx>) -> &[Ty<'tcx>] { + pub(crate) fn ctor_sub_tys( + &self, + ctor: &Constructor<'p, 'thir, 'tcx>, + ty: Ty<'tcx>, + ) -> &[Ty<'tcx>] { let cx = self; match ctor { Struct | Variant(_) | UnionField => match ty.kind() { @@ -217,7 +226,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { } /// The number of fields for this constructor. - pub(crate) fn ctor_arity(&self, ctor: &Constructor<'p, 'tcx>, ty: Ty<'tcx>) -> usize { + pub(crate) fn ctor_arity(&self, ctor: &Constructor<'p, 'thir, 'tcx>, ty: Ty<'tcx>) -> usize { match ctor { Struct | Variant(_) | UnionField => match ty.kind() { ty::Tuple(fs) => fs.len(), @@ -254,7 +263,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { /// /// See [`crate::constructor`] for considerations of emptiness. #[instrument(level = "debug", skip(self), ret)] - pub fn ctors_for_ty(&self, ty: Ty<'tcx>) -> ConstructorSet<'p, 'tcx> { + pub fn ctors_for_ty(&self, ty: Ty<'tcx>) -> ConstructorSet<'p, 'thir, 'tcx> { let cx = self; let make_uint_range = |start, end| { IntRange::from_range( @@ -399,7 +408,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { /// Note: the input patterns must have been lowered through /// `rustc_mir_build::thir::pattern::check_match::MatchVisitor::lower_pattern`. - pub fn lower_pat(&self, pat: &Pat<'tcx>) -> DeconstructedPat<'p, 'tcx> { + pub fn lower_pat(&self, pat: &'thir Pat<'tcx>) -> DeconstructedPat<'p, 'thir, 'tcx> { let singleton = |pat| std::slice::from_ref(self.pattern_arena.alloc(pat)); let cx = self; let ctor; @@ -540,7 +549,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { // `Ref`), and has one field. That field has constructor `Str(value)` and no // subfields. // Note: `t` is `str`, not `&str`. - let subpattern = DeconstructedPat::new(Str(*value), &[], *t, pat.span); + let subpattern = DeconstructedPat::new(Str(*value), &[], *t, pat); ctor = Ref; fields = singleton(subpattern) } @@ -624,7 +633,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { fields = &[]; } } - DeconstructedPat::new(ctor, fields, pat.ty, pat.span) + DeconstructedPat::new(ctor, fields, pat.ty, pat) } /// Convert back to a `thir::PatRangeBoundary` for diagnostic purposes. @@ -685,12 +694,12 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { let value = mir::Const::from_ty_const(c, cx.tcx); lo = PatRangeBoundary::Finite(value); } - let hi = if matches!(range.hi, Finite(0)) { + let hi = if let Some(hi) = range.hi.minus_one() { + hi + } else { // The range encodes `..ty::MIN`, so we can't convert it to an inclusive range. end = rustc_hir::RangeEnd::Excluded; range.hi - } else { - range.hi.minus_one() }; let hi = cx.hoist_pat_range_bdy(hi, ty); PatKind::Range(Box::new(PatRange { lo, hi, end, ty })) @@ -700,7 +709,7 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { } /// Convert back to a `thir::Pat` for diagnostic purposes. This panics for patterns that don't /// appear in diagnostics, like float ranges. - pub fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'tcx>) -> Pat<'tcx> { + pub fn hoist_witness_pat(&self, pat: &WitnessPat<'p, 'thir, 'tcx>) -> Pat<'tcx> { let cx = self; let is_wildcard = |pat: &Pat<'_>| matches!(pat.kind, PatKind::Wild); let mut subpatterns = pat.iter_fields().map(|p| Box::new(cx.hoist_witness_pat(p))); @@ -889,12 +898,12 @@ impl<'p, 'tcx> RustcMatchCheckCtxt<'p, 'tcx> { } } -impl<'p, 'tcx> TypeCx for RustcMatchCheckCtxt<'p, 'tcx> { +impl<'p, 'thir, 'tcx> TypeCx for RustcMatchCheckCtxt<'p, 'thir, 'tcx> { type Ty = Ty<'tcx>; type VariantIdx = VariantIdx; type StrLit = Const<'tcx>; type ArmData = HirId; - type PatData = Span; + type PatData = &'thir Pat<'tcx>; fn is_exhaustive_patterns_feature_on(&self) -> bool { self.tcx.features().exhaustive_patterns diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index b521a5c1145f6..0805891869ef3 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -379,7 +379,7 @@ impl<'a, 'tcx> visit::Visitor<'a, 'tcx> for IsThirPolymorphic<'a, 'tcx> { } #[instrument(skip(self), level = "debug")] - fn visit_expr(&mut self, expr: &thir::Expr<'tcx>) { + fn visit_expr(&mut self, expr: &'a thir::Expr<'tcx>) { self.is_poly |= self.expr_is_poly(expr); if !self.is_poly { visit::walk_expr(self, expr) @@ -387,7 +387,7 @@ impl<'a, 'tcx> visit::Visitor<'a, 'tcx> for IsThirPolymorphic<'a, 'tcx> { } #[instrument(skip(self), level = "debug")] - fn visit_pat(&mut self, pat: &thir::Pat<'tcx>) { + fn visit_pat(&mut self, pat: &'a thir::Pat<'tcx>) { self.is_poly |= self.pat_is_poly(pat); if !self.is_poly { visit::walk_pat(self, pat); diff --git a/src/tools/clippy/tests/ui/manual_range_patterns.fixed b/src/tools/clippy/tests/ui/manual_range_patterns.fixed index b348d7071f6e9..3edfc649a208b 100644 --- a/src/tools/clippy/tests/ui/manual_range_patterns.fixed +++ b/src/tools/clippy/tests/ui/manual_range_patterns.fixed @@ -1,4 +1,5 @@ #![allow(unused)] +#![allow(small_gaps_between_ranges)] #![warn(clippy::manual_range_patterns)] #![feature(exclusive_range_pattern)] diff --git a/src/tools/clippy/tests/ui/manual_range_patterns.rs b/src/tools/clippy/tests/ui/manual_range_patterns.rs index a0750f54b73f1..555a2f5c0ebf7 100644 --- a/src/tools/clippy/tests/ui/manual_range_patterns.rs +++ b/src/tools/clippy/tests/ui/manual_range_patterns.rs @@ -1,4 +1,5 @@ #![allow(unused)] +#![allow(small_gaps_between_ranges)] #![warn(clippy::manual_range_patterns)] #![feature(exclusive_range_pattern)] diff --git a/src/tools/clippy/tests/ui/manual_range_patterns.stderr b/src/tools/clippy/tests/ui/manual_range_patterns.stderr index fbeb9455769df..37db60e113dc1 100644 --- a/src/tools/clippy/tests/ui/manual_range_patterns.stderr +++ b/src/tools/clippy/tests/ui/manual_range_patterns.stderr @@ -1,5 +1,5 @@ error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:8:25 + --> $DIR/manual_range_patterns.rs:9:25 | LL | let _ = matches!(f, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10` @@ -8,109 +8,109 @@ LL | let _ = matches!(f, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10); = help: to override `-D warnings` add `#[allow(clippy::manual_range_patterns)]` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:9:25 + --> $DIR/manual_range_patterns.rs:10:25 | LL | let _ = matches!(f, 4 | 2 | 3 | 1 | 5 | 6 | 9 | 7 | 8 | 10); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:16:25 + --> $DIR/manual_range_patterns.rs:17:25 | LL | let _ = matches!(f, 1 | (2..=4)); | ^^^^^^^^^^^ help: try: `1..=4` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:17:25 + --> $DIR/manual_range_patterns.rs:18:25 | LL | let _ = matches!(f, 1 | (2..4)); | ^^^^^^^^^^ help: try: `1..4` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:18:25 + --> $DIR/manual_range_patterns.rs:19:25 | LL | let _ = matches!(f, (1..=10) | (2..=13) | (14..=48324728) | 48324729); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=48324729` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:19:25 + --> $DIR/manual_range_patterns.rs:20:25 | LL | let _ = matches!(f, 0 | (1..=10) | 48324730 | (2..=13) | (14..=48324728) | 48324729); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0..=48324730` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:20:25 + --> $DIR/manual_range_patterns.rs:21:25 | LL | let _ = matches!(f, 0..=1 | 0..=2 | 0..=3); | ^^^^^^^^^^^^^^^^^^^^^ help: try: `0..=3` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:23:9 + --> $DIR/manual_range_patterns.rs:24:9 | LL | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 => true, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:26:25 + --> $DIR/manual_range_patterns.rs:27:25 | LL | let _ = matches!(f, -1 | -5 | 3 | -2 | -4 | -3 | 0 | 1 | 2); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-5..=3` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:28:25 + --> $DIR/manual_range_patterns.rs:29:25 | LL | let _ = matches!(f, -1_000_000..=1_000_000 | -1_000_001 | 1_000_001); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-1_000_001..=1_000_001` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:31:17 + --> $DIR/manual_range_patterns.rs:32:17 | LL | matches!(f, 0x00 | 0x01 | 0x02 | 0x03); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0x00..=0x03` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:32:17 + --> $DIR/manual_range_patterns.rs:33:17 | LL | matches!(f, 0x00..=0x05 | 0x06 | 0x07); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `0x00..=0x07` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:33:17 + --> $DIR/manual_range_patterns.rs:34:17 | LL | matches!(f, -0x09 | -0x08 | -0x07..=0x00); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `-0x09..=0x00` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:35:17 + --> $DIR/manual_range_patterns.rs:36:17 | LL | matches!(f, 0..5 | 5); | ^^^^^^^^ help: try: `0..=5` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:36:17 + --> $DIR/manual_range_patterns.rs:37:17 | LL | matches!(f, 0 | 1..5); | ^^^^^^^^ help: try: `0..5` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:38:17 + --> $DIR/manual_range_patterns.rs:39:17 | LL | matches!(f, 0..=5 | 6..10); | ^^^^^^^^^^^^^ help: try: `0..10` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:39:17 + --> $DIR/manual_range_patterns.rs:40:17 | LL | matches!(f, 0..5 | 5..=10); | ^^^^^^^^^^^^^ help: try: `0..=10` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:40:17 + --> $DIR/manual_range_patterns.rs:41:17 | LL | matches!(f, 5..=10 | 0..5); | ^^^^^^^^^^^^^ help: try: `0..=10` error: this OR pattern can be rewritten using a range - --> $DIR/manual_range_patterns.rs:44:26 + --> $DIR/manual_range_patterns.rs:45:26 | LL | matches!($e, 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `1..=10` diff --git a/tests/ui/match/issue-18060.rs b/tests/ui/match/issue-18060.rs index b5f3d0f74bc9a..d8d6531016ecf 100644 --- a/tests/ui/match/issue-18060.rs +++ b/tests/ui/match/issue-18060.rs @@ -1,6 +1,8 @@ // run-pass // Regression test for #18060: match arms were matching in the wrong order. +#[allow(overlapping_range_endpoints)] +#[allow(small_gaps_between_ranges)] fn main() { assert_eq!(2, match (1, 3) { (0, 2..=5) => 1, (1, 3) => 2, (_, 2..=5) => 3, (_, _) => 4 }); assert_eq!(2, match (1, 3) { (1, 3) => 2, (_, 2..=5) => 3, (_, _) => 4 }); diff --git a/tests/ui/mir/mir_match_test.rs b/tests/ui/mir/mir_match_test.rs index d41a7f4a1db4a..c29012af9533e 100644 --- a/tests/ui/mir/mir_match_test.rs +++ b/tests/ui/mir/mir_match_test.rs @@ -1,5 +1,6 @@ #![feature(exclusive_range_pattern)] #![allow(overlapping_range_endpoints)] +#![allow(small_gaps_between_ranges)] // run-pass diff --git a/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.rs b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.rs new file mode 100644 index 0000000000000..ba356abe73344 --- /dev/null +++ b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.rs @@ -0,0 +1,79 @@ +#![feature(exclusive_range_pattern)] +#![deny(small_gaps_between_ranges)] + +macro_rules! m { + ($s:expr, $t1:pat, $t2:pat) => { + match $s { + $t1 => {} + $t2 => {} + _ => {} + } + }; +} + +fn main() { + match 0u8 { + 20..30 => {} //~ ERROR multiple ranges are one apart + 31..=40 => {} + _ => {} + } + match 0u8 { + 20..30 => {} //~ ERROR multiple ranges are one apart + 31 => {} + _ => {} + } + + m!(0u8, 20..30, 31..=40); //~ ERROR multiple ranges are one apart + m!(0u8, 31..=40, 20..30); //~ ERROR multiple ranges are one apart + m!(0u8, 20..30, 29..=40); //~ WARN multiple patterns overlap on their endpoints + m!(0u8, 20..30, 30..=40); + m!(0u8, 20..30, 31..=40); //~ ERROR multiple ranges are one apart + m!(0u8, 20..30, 32..=40); + m!(0u8, 20..30, 31..=32); //~ ERROR multiple ranges are one apart + // Don't lint two singletons. + m!(0u8, 30, 32); + // Don't lint on the equivalent inclusive range + m!(0u8, 20..=29, 31..=40); + // Don't lint if the lower one is a singleton. + m!(0u8, 30, 32..=40); + + // Don't lint if the gap is caught by another range. + match 0u8 { + 0..10 => {} + 11..20 => {} + 10 => {} + _ => {} + } + match 0u8 { + 0..10 => {} + 11..20 => {} + 5..15 => {} + _ => {} + } + + match 0u8 { + 0..10 => {} //~ ERROR multiple ranges are one apart + 21..30 => {} + 11..20 => {} //~ ERROR multiple ranges are one apart + _ => {} + } + // The lint is naive: it doesn't look at neighboring patterns. Therefore the suggestion can be + // incorrect. + match (0u8, true) { + (0..10, true) => {} //~ ERROR multiple ranges are one apart + (11..20, true) => {} + (11..20, false) => {} + _ => {} + } + match (true, 0u8) { + (true, 0..10) => {} //~ ERROR multiple ranges are one apart + (true, 11..20) => {} + (false, 11..20) => {} + _ => {} + } + match Some(0u8) { + Some(0..10) => {} //~ ERROR multiple ranges are one apart + Some(11..20) => {} + _ => {} + } +} diff --git a/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.stderr b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.stderr new file mode 100644 index 0000000000000..64b84f1c7d4aa --- /dev/null +++ b/tests/ui/pattern/usefulness/integer-ranges/gap_between_ranges.stderr @@ -0,0 +1,138 @@ +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:16:9 + | +LL | 20..30 => {} + | ^^^^^^ + | | + | this range doesn't match `30_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `20_u8..=30_u8` +LL | 31..=40 => {} + | ------- this seems to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them + | +note: the lint level is defined here + --> $DIR/gap_between_ranges.rs:2:9 + | +LL | #![deny(small_gaps_between_ranges)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:21:9 + | +LL | 20..30 => {} + | ^^^^^^ + | | + | this range doesn't match `30_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `20_u8..=30_u8` +LL | 31 => {} + | -- this seems to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:26:13 + | +LL | m!(0u8, 20..30, 31..=40); + | ^^^^^^ ------- this seems to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them + | | + | this range doesn't match `30_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `20_u8..=30_u8` + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:27:22 + | +LL | m!(0u8, 31..=40, 20..30); + | ------- ^^^^^^ + | | | + | | this range doesn't match `30_u8` because `..` is a non-inclusive range + | | help: use an inclusive range instead: `20_u8..=30_u8` + | this seems to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them + +warning: multiple patterns overlap on their endpoints + --> $DIR/gap_between_ranges.rs:28:21 + | +LL | m!(0u8, 20..30, 29..=40); + | ------ ^^^^^^^ ... with this range + | | + | this range overlaps on `29_u8`... + | + = note: you likely meant to write mutually exclusive ranges + = note: `#[warn(overlapping_range_endpoints)]` on by default + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:30:13 + | +LL | m!(0u8, 20..30, 31..=40); + | ^^^^^^ ------- this seems to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them + | | + | this range doesn't match `30_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `20_u8..=30_u8` + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:32:13 + | +LL | m!(0u8, 20..30, 31..=32); + | ^^^^^^ ------- this seems to continue range `20_u8..30_u8`, but `30_u8` isn't matched by either of them + | | + | this range doesn't match `30_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `20_u8..=30_u8` + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:55:9 + | +LL | 0..10 => {} + | ^^^^^ + | | + | this range doesn't match `10_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `0_u8..=10_u8` +LL | 21..30 => {} +LL | 11..20 => {} + | ------ this seems to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:57:9 + | +LL | 21..30 => {} + | ------ this seems to continue range `11_u8..20_u8`, but `20_u8` isn't matched by either of them +LL | 11..20 => {} + | ^^^^^^ + | | + | this range doesn't match `20_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `11_u8..=20_u8` + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:63:10 + | +LL | (0..10, true) => {} + | ^^^^^ + | | + | this range doesn't match `10_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `0_u8..=10_u8` +LL | (11..20, true) => {} + | ------ this seems to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them +LL | (11..20, false) => {} + | ------ this seems to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:69:16 + | +LL | (true, 0..10) => {} + | ^^^^^ + | | + | this range doesn't match `10_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `0_u8..=10_u8` +LL | (true, 11..20) => {} + | ------ this seems to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them +LL | (false, 11..20) => {} + | ------ this seems to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them + +error: multiple ranges are one apart + --> $DIR/gap_between_ranges.rs:75:14 + | +LL | Some(0..10) => {} + | ^^^^^ + | | + | this range doesn't match `10_u8` because `..` is a non-inclusive range + | help: use an inclusive range instead: `0_u8..=10_u8` +LL | Some(11..20) => {} + | ------ this seems to continue range `0_u8..10_u8`, but `10_u8` isn't matched by either of them + +error: aborting due to 11 previous errors; 1 warning emitted + diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs index 247fdd91572cd..2d27575e26374 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/reachability.rs +++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.rs @@ -1,5 +1,6 @@ #![feature(exclusive_range_pattern)] #![allow(overlapping_range_endpoints)] +#![allow(small_gaps_between_ranges)] #![deny(unreachable_patterns)] macro_rules! m { diff --git a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr index c5b028d2038c3..0f52dfd83b9cb 100644 --- a/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr +++ b/tests/ui/pattern/usefulness/integer-ranges/reachability.stderr @@ -1,137 +1,137 @@ error: unreachable pattern - --> $DIR/reachability.rs:18:17 + --> $DIR/reachability.rs:19:17 | LL | m!(0u8, 42, 42); | ^^ | note: the lint level is defined here - --> $DIR/reachability.rs:3:9 + --> $DIR/reachability.rs:4:9 | LL | #![deny(unreachable_patterns)] | ^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:22:22 + --> $DIR/reachability.rs:23:22 | LL | m!(0u8, 20..=30, 20); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:23:22 + --> $DIR/reachability.rs:24:22 | LL | m!(0u8, 20..=30, 21); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:24:22 + --> $DIR/reachability.rs:25:22 | LL | m!(0u8, 20..=30, 25); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:25:22 + --> $DIR/reachability.rs:26:22 | LL | m!(0u8, 20..=30, 29); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:26:22 + --> $DIR/reachability.rs:27:22 | LL | m!(0u8, 20..=30, 30); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:29:21 + --> $DIR/reachability.rs:30:21 | LL | m!(0u8, 20..30, 20); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:30:21 + --> $DIR/reachability.rs:31:21 | LL | m!(0u8, 20..30, 21); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:31:21 + --> $DIR/reachability.rs:32:21 | LL | m!(0u8, 20..30, 25); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:32:21 + --> $DIR/reachability.rs:33:21 | LL | m!(0u8, 20..30, 29); | ^^ error: unreachable pattern - --> $DIR/reachability.rs:36:22 + --> $DIR/reachability.rs:37:22 | LL | m!(0u8, 20..=30, 20..=30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:37:22 + --> $DIR/reachability.rs:38:22 | LL | m!(0u8, 20.. 30, 20.. 30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:38:22 + --> $DIR/reachability.rs:39:22 | LL | m!(0u8, 20..=30, 20.. 30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:40:22 + --> $DIR/reachability.rs:41:22 | LL | m!(0u8, 20..=30, 21..=30); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:41:22 + --> $DIR/reachability.rs:42:22 | LL | m!(0u8, 20..=30, 20..=29); | ^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:43:24 + --> $DIR/reachability.rs:44:24 | LL | m!('a', 'A'..='z', 'a'..='z'); | ^^^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:50:9 + --> $DIR/reachability.rs:51:9 | LL | 5..=8 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:56:9 + --> $DIR/reachability.rs:57:9 | LL | 5..15 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:63:9 + --> $DIR/reachability.rs:64:9 | LL | 5..25 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:71:9 + --> $DIR/reachability.rs:72:9 | LL | 5..25 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:77:9 + --> $DIR/reachability.rs:78:9 | LL | 5..15 => {}, | ^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:84:9 + --> $DIR/reachability.rs:85:9 | LL | _ => {}, | - matches any value @@ -139,19 +139,19 @@ LL | '\u{D7FF}'..='\u{E000}' => {}, | ^^^^^^^^^^^^^^^^^^^^^^^ unreachable pattern error: unreachable pattern - --> $DIR/reachability.rs:89:9 + --> $DIR/reachability.rs:90:9 | LL | '\u{D7FF}'..='\u{E000}' => {}, | ^^^^^^^^^^^^^^^^^^^^^^^ error: unreachable pattern - --> $DIR/reachability.rs:105:9 + --> $DIR/reachability.rs:106:9 | LL | &FOO => {} | ^^^^ error: unreachable pattern - --> $DIR/reachability.rs:106:9 + --> $DIR/reachability.rs:107:9 | LL | BAR => {} | ^^^