| use rustc_arena::{DroplessArena, TypedArena}; | |
| use rustc_ast::Mutability; | |
| use rustc_data_structures::fx::FxIndexSet; | |
| use rustc_data_structures::stack::ensure_sufficient_stack; | |
| use rustc_errors::codes::*; | |
| use rustc_errors::{Applicability, ErrorGuaranteed, MultiSpan, msg, struct_span_code_err}; | |
| use rustc_hir::def::*; | |
| use rustc_hir::def_id::{DefId, LocalDefId}; | |
| use rustc_hir::{self as hir, BindingMode, ByRef, HirId, MatchSource}; | |
| use rustc_infer::infer::TyCtxtInferExt; | |
| use rustc_middle::bug; | |
| use rustc_middle::thir::visit::Visitor; | |
| use rustc_middle::thir::*; | |
| use rustc_middle::ty::print::with_no_trimmed_paths; | |
| use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; | |
| use rustc_pattern_analysis::diagnostics::Uncovered; | |
| use rustc_pattern_analysis::rustc::{ | |
| Constructor, DeconstructedPat, MatchArm, RedundancyExplanation, RevealedTy, | |
| RustcPatCtxt as PatCtxt, Usefulness, UsefulnessReport, WitnessPat, | |
| }; | |
| use rustc_session::lint::builtin::{ | |
| BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS, | |
| }; | |
| use rustc_span::edit_distance::find_best_match_for_name; | |
| use rustc_span::hygiene::DesugaringKind; | |
| use rustc_span::{Ident, Span}; | |
| use rustc_trait_selection::infer::InferCtxtExt; | |
| use tracing::instrument; | |
| use crate::diagnostics::*; | |
| pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> { | |
| let typeck_results = tcx.typeck(def_id); | |
| let (thir, expr) = tcx.thir_body(def_id)?; | |
| let thir = thir.borrow(); | |
| let pattern_arena = TypedArena::default(); | |
| let dropless_arena = DroplessArena::default(); | |
| let mut visitor = MatchVisitor { | |
| tcx, | |
| thir: &*thir, | |
| typeck_results, | |
| // FIXME(#132279): We're in a body, should handle opaques. | |
| typing_env: ty::TypingEnv::non_body_analysis(tcx, def_id), | |
| hir_source: tcx.local_def_id_to_hir_id(def_id), | |
| let_source: LetSource::None, | |
| pattern_arena: &pattern_arena, | |
| dropless_arena: &dropless_arena, | |
| error: Ok(()), | |
| }; | |
| visitor.visit_expr(&thir[expr]); | |
| let origin = match tcx.def_kind(def_id) { | |
| DefKind::AssocFn | DefKind::Fn => "function argument", | |
| DefKind::Closure => "closure argument", | |
| // other types of MIR don't have function parameters, and we don't need to | |
| // categorize those for the irrefutable check. | |
| _ if thir.params.is_empty() => "", | |
| kind => bug!("unexpected function parameters in THIR: {kind:?} {def_id:?}"), | |
| }; | |
| for param in thir.params.iter() { | |
| if let Some(ref pattern) = param.pat { | |
| visitor.check_binding_is_irrefutable(pattern, origin, None, None, None); | |
| } | |
| } | |
| visitor.error | |
| } | |
| #[derive(Debug, Copy, Clone, PartialEq)] | |
| enum RefutableFlag { | |
| Irrefutable, | |
| Refutable, | |
| } | |
| use RefutableFlag::*; | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| enum LetSource { | |
| None, | |
| PlainLet, | |
| IfLet, | |
| IfLetGuard, | |
| LetElse, | |
| WhileLet, | |
| Else, | |
| ElseIfLet, | |
| } | |
| struct MatchVisitor<'p, 'tcx> { | |
| tcx: TyCtxt<'tcx>, | |
| typing_env: ty::TypingEnv<'tcx>, | |
| typeck_results: &'tcx ty::TypeckResults<'tcx>, | |
| thir: &'p Thir<'tcx>, | |
| hir_source: HirId, | |
| let_source: LetSource, | |
| pattern_arena: &'p TypedArena<DeconstructedPat<'p, 'tcx>>, | |
| 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 | |
| /// on error, but callers don't need to store it here again. | |
| error: Result<(), ErrorGuaranteed>, | |
| } | |
| // Visitor for a thir body. This calls `check_match` and `check_let` as appropriate. | |
| impl<'p, 'tcx> Visitor<'p, 'tcx> for MatchVisitor<'p, 'tcx> { | |
| fn thir(&self) -> &'p Thir<'tcx> { | |
| self.thir | |
| } | |
| #[instrument(level = "trace", skip(self))] | |
| fn visit_arm(&mut self, arm: &'p Arm<'tcx>) { | |
| self.with_hir_source(arm.hir_id, |this| { | |
| if let Some(expr) = arm.guard { | |
| this.with_let_source(LetSource::IfLetGuard, |this| { | |
| this.visit_expr(&this.thir[expr]) | |
| }); | |
| } | |
| this.visit_pat(&arm.pattern); | |
| this.visit_expr(&self.thir[arm.body]); | |
| }); | |
| } | |
| #[instrument(level = "trace", skip(self))] | |
| fn visit_expr(&mut self, ex: &'p Expr<'tcx>) { | |
| match ex.kind { | |
| ExprKind::Scope { value, hir_id, .. } => { | |
| self.with_hir_source(hir_id, |this| { | |
| this.visit_expr(&this.thir[value]); | |
| }); | |
| return; | |
| } | |
| ExprKind::If { cond, then, else_opt, if_then_scope: _ } => { | |
| // Give a specific `let_source` for the condition. | |
| let let_source = match ex.span.desugaring_kind() { | |
| Some(DesugaringKind::WhileLoop) => LetSource::WhileLet, | |
| _ => match self.let_source { | |
| LetSource::Else => LetSource::ElseIfLet, | |
| _ => LetSource::IfLet, | |
| }, | |
| }; | |
| self.with_let_source(let_source, |this| this.visit_expr(&self.thir[cond])); | |
| self.with_let_source(LetSource::None, |this| { | |
| this.visit_expr(&this.thir[then]); | |
| }); | |
| if let Some(else_) = else_opt { | |
| self.with_let_source(LetSource::Else, |this| { | |
| this.visit_expr(&this.thir[else_]) | |
| }); | |
| } | |
| return; | |
| } | |
| ExprKind::Match { scrutinee, ref arms, match_source } => { | |
| self.check_match(scrutinee, arms, match_source, ex.span); | |
| } | |
| ExprKind::LoopMatch { | |
| match_data: LoopMatchMatchData { scrutinee, ref arms, span }, | |
| .. | |
| } => { | |
| self.check_match(scrutinee, arms, MatchSource::Normal, span); | |
| } | |
| ExprKind::Let { ref pat, expr } => { | |
| self.check_let(pat, Some(expr), ex.span, None); | |
| } | |
| ExprKind::LogicalOp { op: LogicalOp::And, .. } | |
| if !matches!(self.let_source, LetSource::None) => | |
| { | |
| let mut chain_refutabilities = Vec::new(); | |
| let Ok(()) = self.visit_land(ex, &mut chain_refutabilities) else { return }; | |
| // Lint only single irrefutable let binding. | |
| if let [Some((_, Irrefutable))] = chain_refutabilities[..] { | |
| self.lint_single_let(ex.span, None, None); | |
| } | |
| return; | |
| } | |
| _ => {} | |
| }; | |
| self.with_let_source(LetSource::None, |this| visit::walk_expr(this, ex)); | |
| } | |
| fn visit_stmt(&mut self, stmt: &'p Stmt<'tcx>) { | |
| match stmt.kind { | |
| StmtKind::Let { ref pattern, initializer, else_block, hir_id, span, .. } => { | |
| self.with_hir_source(hir_id, |this| { | |
| let let_source = | |
| if else_block.is_some() { LetSource::LetElse } else { LetSource::PlainLet }; | |
| let else_span = else_block.map(|bid| this.thir.blocks[bid].span); | |
| this.with_let_source(let_source, |this| { | |
| this.check_let(pattern, initializer, span, else_span) | |
| }); | |
| visit::walk_stmt(this, stmt); | |
| }); | |
| } | |
| StmtKind::Expr { .. } => { | |
| visit::walk_stmt(self, stmt); | |
| } | |
| } | |
| } | |
| } | |
| impl<'p, 'tcx> MatchVisitor<'p, 'tcx> { | |
| #[instrument(level = "trace", skip(self, f))] | |
| fn with_let_source(&mut self, let_source: LetSource, f: impl FnOnce(&mut Self)) { | |
| let old_let_source = self.let_source; | |
| self.let_source = let_source; | |
| ensure_sufficient_stack(|| f(self)); | |
| self.let_source = old_let_source; | |
| } | |
| fn with_hir_source<T>(&mut self, new_hir_source: HirId, f: impl FnOnce(&mut Self) -> T) -> T { | |
| let old_hir_source = self.hir_source; | |
| self.hir_source = new_hir_source; | |
| let ret = f(self); | |
| self.hir_source = old_hir_source; | |
| ret | |
| } | |
| /// Visit a nested chain of `&&`. Used for if-let chains. This must call `visit_expr` on the | |
| /// subexpressions we are not handling ourselves. | |
| fn visit_land( | |
| &mut self, | |
| ex: &'p Expr<'tcx>, | |
| accumulator: &mut Vec<Option<(Span, RefutableFlag)>>, | |
| ) -> Result<(), ErrorGuaranteed> { | |
| match ex.kind { | |
| ExprKind::Scope { value, hir_id, .. } => { | |
| self.with_hir_source(hir_id, |this| this.visit_land(&this.thir[value], accumulator)) | |
| } | |
| ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } => { | |
| // We recurse into the lhs only, because `&&` chains associate to the left. | |
| let res_lhs = self.visit_land(&self.thir[lhs], accumulator); | |
| let res_rhs = self.visit_land_rhs(&self.thir[rhs])?; | |
| accumulator.push(res_rhs); | |
| res_lhs | |
| } | |
| _ => { | |
| let res = self.visit_land_rhs(ex)?; | |
| accumulator.push(res); | |
| Ok(()) | |
| } | |
| } | |
| } | |
| /// Visit the right-hand-side of a `&&`. Used for if-let chains. Returns `Some` if the | |
| /// expression was ultimately a `let ... = ...`, and `None` if it was a normal boolean | |
| /// expression. This must call `visit_expr` on the subexpressions we are not handling ourselves. | |
| fn visit_land_rhs( | |
| &mut self, | |
| ex: &'p Expr<'tcx>, | |
| ) -> Result<Option<(Span, RefutableFlag)>, ErrorGuaranteed> { | |
| match ex.kind { | |
| ExprKind::Scope { value, hir_id, .. } => { | |
| self.with_hir_source(hir_id, |this| this.visit_land_rhs(&this.thir[value])) | |
| } | |
| ExprKind::Let { ref pat, expr } => { | |
| let expr = &self.thir()[expr]; | |
| self.with_let_source(LetSource::None, |this| { | |
| this.visit_expr(expr); | |
| }); | |
| Ok(Some((ex.span, self.is_let_irrefutable(pat, Some(expr))?))) | |
| } | |
| _ => { | |
| self.with_let_source(LetSource::None, |this| { | |
| this.visit_expr(ex); | |
| }); | |
| Ok(None) | |
| } | |
| } | |
| } | |
| fn lower_pattern( | |
| &mut self, | |
| cx: &PatCtxt<'p, 'tcx>, | |
| pat: &'p Pat<'tcx>, | |
| ) -> Result<&'p DeconstructedPat<'p, 'tcx>, ErrorGuaranteed> { | |
| if let Err(err) = pat.pat_error_reported() { | |
| self.error = Err(err); | |
| Err(err) | |
| } else { | |
| // Check the pattern for some things unrelated to exhaustiveness. | |
| let refutable = if cx.refutable { Refutable } else { Irrefutable }; | |
| let mut err = Ok(()); | |
| pat.walk_always(|pat| { | |
| check_borrow_conflicts_in_at_patterns(self, pat); | |
| check_for_bindings_named_same_as_variants(self, pat, refutable); | |
| err = err.and(check_never_pattern(cx, pat)); | |
| }); | |
| err?; | |
| Ok(self.pattern_arena.alloc(cx.lower_pat(pat))) | |
| } | |
| } | |
| /// Inspects the match scrutinee expression to determine whether the place it evaluates to may | |
| /// hold invalid data. | |
| fn is_known_valid_scrutinee(&self, scrutinee: &Expr<'tcx>) -> bool { | |
| use ExprKind::*; | |
| match &scrutinee.kind { | |
| // Pointers can validly point to a place with invalid data. It is undecided whether | |
| // references can too, so we conservatively assume they can. | |
| Deref { .. } => false, | |
| // Inherit validity of the parent place, unless the parent is an union. | |
| Field { lhs, .. } => { | |
| let lhs = &self.thir()[*lhs]; | |
| match lhs.ty.kind() { | |
| ty::Adt(def, _) if def.is_union() => false, | |
| _ => self.is_known_valid_scrutinee(lhs), | |
| } | |
| } | |
| // Essentially a field access. | |
| Index { lhs, .. } => { | |
| let lhs = &self.thir()[*lhs]; | |
| self.is_known_valid_scrutinee(lhs) | |
| } | |
| // No-op. | |
| Scope { value, .. } => self.is_known_valid_scrutinee(&self.thir()[*value]), | |
| // Casts don't cause a load. | |
| NeverToAny { source } | |
| | Cast { source } | |
| | Use { source } | |
| | PointerCoercion { source, .. } | |
| | PlaceTypeAscription { source, .. } | |
| | ValueTypeAscription { source, .. } | |
| | PlaceUnwrapUnsafeBinder { source } | |
| | ValueUnwrapUnsafeBinder { source } | |
| | WrapUnsafeBinder { source } => self.is_known_valid_scrutinee(&self.thir()[*source]), | |
| // These diverge. | |
| Become { .. } | |
| | Break { .. } | |
| | Continue { .. } | |
| | ConstContinue { .. } | |
| | Return { .. } => true, | |
| // These are statements that evaluate to `()`. | |
| Assign { .. } | AssignOp { .. } | InlineAsm { .. } | Let { .. } => true, | |
| // These evaluate to a value. | |
| RawBorrow { .. } | |
| | Adt { .. } | |
| | Array { .. } | |
| | Binary { .. } | |
| | Block { .. } | |
| | Borrow { .. } | |
| | Call { .. } | |
| | ByUse { .. } | |
| | Closure { .. } | |
| | ConstBlock { .. } | |
| | ConstParam { .. } | |
| | If { .. } | |
| | Literal { .. } | |
| | LogicalOp { .. } | |
| | Loop { .. } | |
| | LoopMatch { .. } | |
| | Match { .. } | |
| | NamedConst { .. } | |
| | NonHirLiteral { .. } | |
| | Repeat { .. } | |
| | StaticRef { .. } | |
| | ThreadLocalRef { .. } | |
| | Tuple { .. } | |
| | Unary { .. } | |
| | UpvarRef { .. } | |
| | VarRef { .. } | |
| | ZstLiteral { .. } | |
| | Yield { .. } | |
| | Reborrow { .. } => true, | |
| } | |
| } | |
| fn new_cx( | |
| &self, | |
| refutability: RefutableFlag, | |
| whole_match_span: Option<Span>, | |
| scrutinee: Option<&Expr<'tcx>>, | |
| scrut_span: Span, | |
| ) -> PatCtxt<'p, 'tcx> { | |
| let refutable = match refutability { | |
| Irrefutable => false, | |
| Refutable => true, | |
| }; | |
| // If we don't have a scrutinee we're either a function parameter or a `let x;`. Both cases | |
| // require validity. | |
| let known_valid_scrutinee = | |
| scrutinee.map(|scrut| self.is_known_valid_scrutinee(scrut)).unwrap_or(true); | |
| PatCtxt { | |
| tcx: self.tcx, | |
| typeck_results: self.typeck_results, | |
| typing_env: self.typing_env, | |
| module: self.tcx.parent_module(self.hir_source).to_def_id(), | |
| dropless_arena: self.dropless_arena, | |
| match_lint_level: self.hir_source, | |
| whole_match_span, | |
| scrut_span, | |
| refutable, | |
| known_valid_scrutinee, | |
| internal_state: Default::default(), | |
| } | |
| } | |
| fn analyze_patterns( | |
| &mut self, | |
| cx: &PatCtxt<'p, 'tcx>, | |
| arms: &[MatchArm<'p, 'tcx>], | |
| scrut_ty: Ty<'tcx>, | |
| ) -> Result<UsefulnessReport<'p, 'tcx>, ErrorGuaranteed> { | |
| let report = | |
| rustc_pattern_analysis::rustc::analyze_match(&cx, &arms, scrut_ty).map_err(|err| { | |
| self.error = Err(err); | |
| err | |
| })?; | |
| // Warn unreachable subpatterns. | |
| for (arm, is_useful) in report.arm_usefulness.iter() { | |
| if let Usefulness::Useful(redundant_subpats) = is_useful | |
| && !redundant_subpats.is_empty() | |
| { | |
| 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().span); | |
| for (pat, explanation) in redundant_subpats { | |
| report_unreachable_pattern(cx, arm.arm_data, pat, &explanation, None) | |
| } | |
| } | |
| } | |
| Ok(report) | |
| } | |
| #[instrument(level = "trace", skip(self))] | |
| fn check_let( | |
| &mut self, | |
| pat: &'p Pat<'tcx>, | |
| scrutinee: Option<ExprId>, | |
| span: Span, | |
| else_span: Option<Span>, | |
| ) { | |
| assert!(self.let_source != LetSource::None); | |
| let scrut = scrutinee.map(|id| &self.thir[id]); | |
| if let LetSource::PlainLet = self.let_source { | |
| // `lhs = rhs` destructuring assignments are lowered to a `let` tagged | |
| // `AssignDesugar`; report them as assignments, not `let` bindings (#157553). | |
| if let hir::Node::LetStmt(&hir::LetStmt { | |
| source: hir::LocalSource::AssignDesugar, | |
| .. | |
| }) = self.tcx.hir_node(self.hir_source) | |
| { | |
| self.check_binding_is_irrefutable( | |
| pat, | |
| "assignment", | |
| Some(Inform { descr: "destructuring assignments" }), | |
| scrut, | |
| None, | |
| ); | |
| } else { | |
| self.check_binding_is_irrefutable( | |
| pat, | |
| "local binding", | |
| Some(Inform { descr: "`let` bindings" }), | |
| scrut, | |
| Some(span), | |
| ); | |
| } | |
| } else if let Ok(Irrefutable) = self.is_let_irrefutable(pat, scrut) { | |
| if span.from_expansion() { | |
| self.lint_single_let(span, None, None); | |
| return; | |
| } | |
| let let_else_span = self.check_irrefutable_option_some(pat, scrut, span); | |
| let sm = self.tcx.sess.source_map(); | |
| let next_token_start = sm.span_extend_while_whitespace(span.clone()).hi(); | |
| let line_span = sm.span_extend_to_line(span.clone()).with_lo(next_token_start); | |
| let else_keyword_span = sm.span_until_whitespace(line_span); | |
| self.lint_single_let(span, Some(else_keyword_span), let_else_span); | |
| } | |
| } | |
| /// Check case `let x = Some(y);`, user likely intended to destructure `Option` | |
| fn check_irrefutable_option_some( | |
| &self, | |
| pat: &'p Pat<'tcx>, | |
| initializer: Option<&Expr<'tcx>>, | |
| span: Span, | |
| ) -> Option<LetElseReplacementSuggestion> { | |
| if let sm = self.tcx.sess.source_map() | |
| && let Some(initializer) = initializer | |
| && let Some(s_ty) = initializer.ty.ty_adt_def() | |
| && self.tcx.is_diagnostic_item(rustc_span::sym::Option, s_ty.did()) | |
| && let ExprKind::Scope { value, .. } = initializer.kind | |
| && let initializer_expr = &self.thir[value] | |
| && let ExprKind::Adt(AdtExpr { fields, .. }) = &initializer_expr.kind | |
| && let Some(field) = fields.first() | |
| && let inner = &self.thir[field.expr] | |
| && let Some(inner_ty) = inner.ty.ty_adt_def() | |
| && self.tcx.is_diagnostic_item(rustc_span::sym::Option, inner_ty.did()) | |
| && let Ok(rhs) = sm.span_to_snippet(inner.span) | |
| && let Ok(lhs) = sm.span_to_snippet(pat.span) | |
| { | |
| let lhs = format!("Some({})", lhs); | |
| Some(LetElseReplacementSuggestion { span, lhs, rhs }) | |
| } else { | |
| None | |
| } | |
| } | |
| fn check_match( | |
| &mut self, | |
| scrut: ExprId, | |
| arms: &[ArmId], | |
| source: hir::MatchSource, | |
| expr_span: Span, | |
| ) { | |
| let scrut = &self.thir[scrut]; | |
| let cx = self.new_cx(Refutable, Some(expr_span), Some(scrut), scrut.span); | |
| let mut tarms = Vec::with_capacity(arms.len()); | |
| for &arm in arms { | |
| let arm = &self.thir.arms[arm]; | |
| let got_error = self.with_hir_source(arm.hir_id, |this| { | |
| let Ok(pat) = this.lower_pattern(&cx, &arm.pattern) else { return true }; | |
| let arm = | |
| MatchArm { pat, arm_data: this.hir_source, has_guard: arm.guard.is_some() }; | |
| tarms.push(arm); | |
| false | |
| }); | |
| if got_error { | |
| return; | |
| } | |
| } | |
| let Ok(report) = self.analyze_patterns(&cx, &tarms, scrut.ty) else { return }; | |
| match source { | |
| // Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }` | |
| // when the iterator is an uninhabited type. unreachable_code will trigger instead. | |
| hir::MatchSource::ForLoopDesugar if arms.len() == 1 => {} | |
| hir::MatchSource::ForLoopDesugar | |
| | hir::MatchSource::Postfix | |
| | hir::MatchSource::Normal | |
| | hir::MatchSource::FormatArgs => { | |
| let is_match_arm = | |
| matches!(source, hir::MatchSource::Postfix | hir::MatchSource::Normal); | |
| report_arm_reachability(&cx, &report, is_match_arm); | |
| } | |
| // Unreachable patterns in try and await expressions occur when one of | |
| // the arms are an uninhabited type. Which is OK. | |
| hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar(_) => {} | |
| } | |
| // Check if the match is exhaustive. | |
| let witnesses = report.non_exhaustiveness_witnesses; | |
| if !witnesses.is_empty() { | |
| if source == hir::MatchSource::ForLoopDesugar | |
| && let [_, snd_arm] = *arms | |
| { | |
| // the for loop pattern is not irrefutable | |
| let pat = &self.thir[snd_arm].pattern; | |
| // `pat` should be `Some(<pat_field>)` from a desugared for loop. | |
| debug_assert_eq!(pat.span.desugaring_kind(), Some(DesugaringKind::ForLoop)); | |
| let PatKind::Variant { ref subpatterns, .. } = pat.kind else { bug!() }; | |
| let [pat_field] = &subpatterns[..] else { bug!() }; | |
| self.check_binding_is_irrefutable( | |
| &pat_field.pattern, | |
| "`for` loop binding", | |
| None, | |
| None, | |
| None, | |
| ); | |
| } else { | |
| // span after scrutinee, or after `.match`. That is, the braces, arms, | |
| // and any whitespace preceding the braces. | |
| let braces_span = match source { | |
| hir::MatchSource::Normal => scrut | |
| .span | |
| .find_ancestor_in_same_ctxt(expr_span) | |
| .map(|scrut_span| scrut_span.shrink_to_hi().with_hi(expr_span.hi())), | |
| hir::MatchSource::Postfix => { | |
| // This is horrendous, and we should deal with it by just | |
| // stashing the span of the braces somewhere (like in the match source). | |
| scrut.span.find_ancestor_in_same_ctxt(expr_span).and_then(|scrut_span| { | |
| let sm = self.tcx.sess.source_map(); | |
| let brace_span = sm.span_extend_to_next_char(scrut_span, '{', true); | |
| if sm.span_to_snippet(sm.next_point(brace_span)).as_deref() == Ok("{") { | |
| let sp = brace_span.shrink_to_hi().with_hi(expr_span.hi()); | |
| // We also need to extend backwards for whitespace | |
| sm.span_extend_prev_while(sp, |c| c.is_whitespace()).ok() | |
| } else { | |
| None | |
| } | |
| }) | |
| } | |
| hir::MatchSource::ForLoopDesugar | |
| | hir::MatchSource::TryDesugar(_) | |
| | hir::MatchSource::AwaitDesugar | |
| | hir::MatchSource::FormatArgs => None, | |
| }; | |
| // Check if the match would be exhaustive if all guards were removed. | |
| // If so, we leave a note that guards don't count towards exhaustivity. | |
| let would_be_exhaustive_without_guards = { | |
| let any_arm_has_guard = tarms.iter().any(|arm| arm.has_guard); | |
| any_arm_has_guard && { | |
| let guardless_arms: Vec<_> = | |
| tarms.iter().map(|arm| MatchArm { has_guard: false, ..*arm }).collect(); | |
| rustc_pattern_analysis::rustc::analyze_match(&cx, &guardless_arms, scrut.ty) | |
| .is_ok_and(|report| report.non_exhaustiveness_witnesses.is_empty()) | |
| } | |
| }; | |
| self.error = Err(report_non_exhaustive_match( | |
| &cx, | |
| self.thir, | |
| scrut.ty, | |
| scrut.span, | |
| witnesses, | |
| arms, | |
| braces_span, | |
| would_be_exhaustive_without_guards, | |
| )); | |
| } | |
| } | |
| } | |
| #[instrument(level = "trace", skip(self))] | |
| fn lint_single_let( | |
| &mut self, | |
| let_span: Span, | |
| else_keyword_span: Option<Span>, | |
| let_else_span: Option<LetElseReplacementSuggestion>, | |
| ) { | |
| report_irrefutable_let_patterns( | |
| self.tcx, | |
| self.hir_source, | |
| self.let_source, | |
| 1, | |
| let_span, | |
| else_keyword_span, | |
| let_else_span, | |
| ); | |
| } | |
| fn analyze_binding( | |
| &mut self, | |
| pat: &'p Pat<'tcx>, | |
| refutability: RefutableFlag, | |
| scrut: Option<&Expr<'tcx>>, | |
| ) -> Result<(PatCtxt<'p, 'tcx>, UsefulnessReport<'p, '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.hir_source, has_guard: false }]; | |
| let report = self.analyze_patterns(&cx, &arms, pat.ty().inner())?; | |
| Ok((cx, report)) | |
| } | |
| fn is_let_irrefutable( | |
| &mut self, | |
| pat: &'p Pat<'tcx>, | |
| scrut: Option<&Expr<'tcx>>, | |
| ) -> Result<RefutableFlag, ErrorGuaranteed> { | |
| let (cx, report) = self.analyze_binding(pat, Refutable, scrut)?; | |
| // Report if the pattern is unreachable, which can only occur when the type is uninhabited. | |
| report_arm_reachability(&cx, &report, false); | |
| // If the list of witnesses is empty, the match is exhaustive, i.e. the `if let` pattern is | |
| // irrefutable. | |
| Ok(if report.non_exhaustiveness_witnesses.is_empty() { Irrefutable } else { Refutable }) | |
| } | |
| #[instrument(level = "trace", skip(self))] | |
| fn check_binding_is_irrefutable( | |
| &mut self, | |
| pat: &'p Pat<'tcx>, | |
| origin: &str, | |
| inform: Option<Inform>, | |
| scrut: Option<&Expr<'tcx>>, | |
| sp: Option<Span>, | |
| ) { | |
| let pattern_ty = pat.ty; | |
| let Ok((cx, report)) = self.analyze_binding(pat, Irrefutable, scrut) else { return }; | |
| let witnesses = report.non_exhaustiveness_witnesses; | |
| if witnesses.is_empty() { | |
| // The pattern is irrefutable. | |
| return; | |
| } | |
| let mut let_suggestion = None; | |
| let mut misc_suggestion = None; | |
| let mut interpreted_as_const = None; | |
| let mut interpreted_as_const_sugg = None; | |
| if let Some(def_id) = is_const_pat_that_looks_like_binding(self.tcx, pat) { | |
| let span = self.tcx.def_span(def_id); | |
| let variable = self.tcx.item_name(def_id).to_string(); | |
| // When we encounter a constant as the binding name, point at the `const` definition. | |
| interpreted_as_const = Some(InterpretedAsConst { span, variable: variable.clone() }); | |
| interpreted_as_const_sugg = Some(InterpretedAsConstSugg { span: pat.span, variable }); | |
| } else if let PatKind::Constant { .. } = pat.kind | |
| && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(pat.span) | |
| { | |
| // If the pattern to match is an integer literal: | |
| if snippet.chars().all(|c| c.is_digit(10)) { | |
| // Then give a suggestion, the user might've meant to create a binding instead. | |
| misc_suggestion = Some(MiscPatternSuggestion::AttemptedIntegerLiteral { | |
| start_span: pat.span.shrink_to_lo(), | |
| }); | |
| } | |
| } | |
| if let Some(span) = sp | |
| && self.tcx.sess.source_map().is_span_accessible(span) | |
| && interpreted_as_const.is_none() | |
| && scrut.is_some() | |
| { | |
| let mut bindings = vec![]; | |
| pat.each_binding(|name, _, _, _| bindings.push(name)); | |
| let semi_span = span.shrink_to_hi(); | |
| let start_span = span.shrink_to_lo(); | |
| let end_span = semi_span.shrink_to_lo(); | |
| let count = witnesses.len(); | |
| let_suggestion = Some(if bindings.is_empty() { | |
| SuggestLet::If { start_span, semi_span, count } | |
| } else { | |
| SuggestLet::Else { end_span, count } | |
| }); | |
| }; | |
| let adt_defined_here = report_adt_defined_here(self.tcx, pattern_ty, &witnesses, false); | |
| // Emit an extra note if the first uncovered witness would be uninhabited | |
| // if we disregard visibility. | |
| let witness_1_is_privately_uninhabited = if let Some(witness_1) = witnesses.get(0) | |
| && let ty::Adt(adt, args) = witness_1.ty().kind() | |
| && adt.is_enum() | |
| && let Constructor::Variant(variant_index) = witness_1.ctor() | |
| { | |
| let variant_inhabited = adt | |
| .variant(*variant_index) | |
| .inhabited_predicate(self.tcx, *adt) | |
| .instantiate(self.tcx, args); | |
| variant_inhabited.apply(self.tcx, cx.typing_env, cx.module) | |
| && !variant_inhabited.apply_ignore_module(self.tcx, cx.typing_env) | |
| } else { | |
| false | |
| }; | |
| let witness_1 = cx.print_witness_pat(witnesses.get(0).unwrap()); | |
| self.error = Err(self.tcx.dcx().emit_err(PatternNotCovered { | |
| span: pat.span, | |
| origin, | |
| uncovered: Uncovered::new(pat.span, &cx, witnesses), | |
| inform, | |
| interpreted_as_const, | |
| interpreted_as_const_sugg, | |
| witness_1_is_privately_uninhabited, | |
| witness_1, | |
| _p: (), | |
| pattern_ty, | |
| let_suggestion, | |
| misc_suggestion, | |
| adt_defined_here, | |
| })); | |
| } | |
| } | |
| /// Check if a by-value binding is by-value. That is, check if the binding's type is not `Copy`. | |
| /// Check that there are no borrow or move conflicts in `binding @ subpat` patterns. | |
| /// | |
| /// For example, this would reject: | |
| /// - `ref x @ Some(ref mut y)`, | |
| /// - `ref mut x @ Some(ref y)`, | |
| /// - `ref mut x @ Some(ref mut y)`, | |
| /// - `ref mut? x @ Some(y)`, and | |
| /// - `x @ Some(ref mut? y)`. | |
| /// | |
| /// This analysis is *not* subsumed by NLL. | |
| fn check_borrow_conflicts_in_at_patterns<'tcx>(cx: &MatchVisitor<'_, 'tcx>, pat: &Pat<'tcx>) { | |
| // Extract `sub` in `binding @ sub`. | |
| let PatKind::Binding { name, mode, ty, subpattern: Some(ref sub), .. } = pat.kind else { | |
| return; | |
| }; | |
| let is_binding_by_move = |ty: Ty<'tcx>| !cx.tcx.type_is_copy_modulo_regions(cx.typing_env, ty); | |
| let sess = cx.tcx.sess; | |
| // Get the binding move, extract the mutability if by-ref. | |
| let mut_outer = match mode.0 { | |
| ByRef::No if is_binding_by_move(ty) => { | |
| // We have `x @ pat` where `x` is by-move. Reject all borrows in `pat`. | |
| let mut conflicts_ref = Vec::new(); | |
| sub.each_binding(|_, mode, _, span| { | |
| if matches!(mode, ByRef::Yes(..)) { | |
| conflicts_ref.push(span) | |
| } | |
| }); | |
| if !conflicts_ref.is_empty() { | |
| sess.dcx().emit_err(BorrowOfMovedValue { | |
| binding_span: pat.span, | |
| conflicts_ref, | |
| name: Ident::new(name, pat.span), | |
| ty, | |
| suggest_borrowing: Some(pat.span.shrink_to_lo()), | |
| }); | |
| } | |
| return; | |
| } | |
| ByRef::No => return, | |
| ByRef::Yes(_, m) => m, | |
| }; | |
| // We now have `ref $mut_outer binding @ sub` (semantically). | |
| // Recurse into each binding in `sub` and find mutability or move conflicts. | |
| let mut conflicts_move = Vec::new(); | |
| let mut conflicts_mut_mut = Vec::new(); | |
| let mut conflicts_mut_ref = Vec::new(); | |
| sub.each_binding(|name, mode, ty, span| { | |
| match mode { | |
| ByRef::Yes(_, mut_inner) => match (mut_outer, mut_inner) { | |
| // Both sides are `ref`. | |
| (Mutability::Not, Mutability::Not) => {} | |
| // 2x `ref mut`. | |
| (Mutability::Mut, Mutability::Mut) => { | |
| conflicts_mut_mut.push(Conflict::Mut { span, name }) | |
| } | |
| (Mutability::Not, Mutability::Mut) => { | |
| conflicts_mut_ref.push(Conflict::Mut { span, name }) | |
| } | |
| (Mutability::Mut, Mutability::Not) => { | |
| conflicts_mut_ref.push(Conflict::Ref { span, name }) | |
| } | |
| }, | |
| ByRef::No if is_binding_by_move(ty) => { | |
| conflicts_move.push(Conflict::Moved { span, name }) // `ref mut?` + by-move conflict. | |
| } | |
| ByRef::No => {} // `ref mut?` + by-copy is fine. | |
| } | |
| }); | |
| let report_mut_mut = !conflicts_mut_mut.is_empty(); | |
| let report_mut_ref = !conflicts_mut_ref.is_empty(); | |
| let report_move_conflict = !conflicts_move.is_empty(); | |
| let mut occurrences = match mut_outer { | |
| Mutability::Mut => vec![Conflict::Mut { span: pat.span, name }], | |
| Mutability::Not => vec![Conflict::Ref { span: pat.span, name }], | |
| }; | |
| occurrences.extend(conflicts_mut_mut); | |
| occurrences.extend(conflicts_mut_ref); | |
| occurrences.extend(conflicts_move); | |
| // Report errors if any. | |
| if report_mut_mut { | |
| // Report mutability conflicts for e.g. `ref mut x @ Some(ref mut y)`. | |
| sess.dcx().emit_err(MultipleMutBorrows { span: pat.span, occurrences }); | |
| } else if report_mut_ref { | |
| // Report mutability conflicts for e.g. `ref x @ Some(ref mut y)` or the converse. | |
| match mut_outer { | |
| Mutability::Mut => { | |
| sess.dcx().emit_err(AlreadyMutBorrowed { span: pat.span, occurrences }); | |
| } | |
| Mutability::Not => { | |
| sess.dcx().emit_err(AlreadyBorrowed { span: pat.span, occurrences }); | |
| } | |
| }; | |
| } else if report_move_conflict { | |
| // Report by-ref and by-move conflicts, e.g. `ref x @ y`. | |
| sess.dcx().emit_err(MovedWhileBorrowed { span: pat.span, occurrences }); | |
| } | |
| } | |
| fn check_for_bindings_named_same_as_variants( | |
| cx: &MatchVisitor<'_, '_>, | |
| pat: &Pat<'_>, | |
| rf: RefutableFlag, | |
| ) { | |
| if let PatKind::Binding { | |
| name, | |
| mode: BindingMode(ByRef::No, Mutability::Not), | |
| subpattern: None, | |
| ty, | |
| .. | |
| } = pat.kind | |
| && let ty::Adt(edef, _) = ty.peel_refs().kind() | |
| && edef.is_enum() | |
| && edef | |
| .variants() | |
| .iter() | |
| .any(|variant| variant.name == name && variant.ctor_kind() == Some(CtorKind::Const)) | |
| { | |
| let variant_count = edef.variants().len(); | |
| let ty_path = with_no_trimmed_paths!(cx.tcx.def_path_str(edef.did())); | |
| cx.tcx.emit_node_span_lint( | |
| BINDINGS_WITH_VARIANT_NAME, | |
| cx.hir_source, | |
| pat.span, | |
| BindingsWithVariantName { | |
| // If this is an irrefutable pattern, and there's > 1 variant, | |
| // then we can't actually match on this. Applying the below | |
| // suggestion would produce code that breaks on `check_binding_is_irrefutable`. | |
| suggestion: if rf == Refutable || variant_count == 1 { | |
| Some(pat.span) | |
| } else { | |
| None | |
| }, | |
| ty_path, | |
| name: Ident::new(name, pat.span), | |
| }, | |
| ) | |
| } | |
| } | |
| /// Check that never patterns are only used on inhabited types. | |
| fn check_never_pattern<'tcx>( | |
| cx: &PatCtxt<'_, 'tcx>, | |
| pat: &Pat<'tcx>, | |
| ) -> Result<(), ErrorGuaranteed> { | |
| if let PatKind::Never = pat.kind { | |
| if !cx.is_uninhabited(pat.ty) { | |
| return Err(cx.tcx.dcx().emit_err(NonEmptyNeverPattern { span: pat.span, ty: pat.ty })); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| fn report_irrefutable_let_patterns( | |
| tcx: TyCtxt<'_>, | |
| id: HirId, | |
| source: LetSource, | |
| count: usize, | |
| span: Span, | |
| else_keyword_span: Option<Span>, | |
| let_else_span: Option<LetElseReplacementSuggestion>, | |
| ) { | |
| macro_rules! emit_diag { | |
| ($lint:tt) => {{ | |
| tcx.emit_node_span_lint(IRREFUTABLE_LET_PATTERNS, id, span, $lint { count }); | |
| }}; | |
| } | |
| match source { | |
| LetSource::None | LetSource::PlainLet | LetSource::Else => bug!(), | |
| LetSource::IfLet | LetSource::ElseIfLet => emit_diag!(IrrefutableLetPatternsIfLet), | |
| LetSource::IfLetGuard => emit_diag!(IrrefutableLetPatternsIfLetGuard), | |
| LetSource::LetElse => { | |
| let spans = match else_keyword_span { | |
| Some(else_keyword_span) => { | |
| let mut spans = MultiSpan::from_span(else_keyword_span); | |
| spans.push_span_label( | |
| span, | |
| msg!("assigning to binding pattern will always succeed"), | |
| ); | |
| spans | |
| } | |
| None => span.into(), | |
| }; | |
| tcx.emit_node_span_lint( | |
| IRREFUTABLE_LET_PATTERNS, | |
| id, | |
| spans, | |
| IrrefutableLetPatternsLetElse { be_replaced: let_else_span }, | |
| ); | |
| } | |
| LetSource::WhileLet => emit_diag!(IrrefutableLetPatternsWhileLet), | |
| } | |
| } | |
| /// Report unreachable arms, if any. | |
| fn report_unreachable_pattern<'p, 'tcx>( | |
| cx: &PatCtxt<'p, 'tcx>, | |
| hir_id: HirId, | |
| pat: &DeconstructedPat<'p, 'tcx>, | |
| explanation: &RedundancyExplanation<'p, 'tcx>, | |
| whole_arm_span: Option<Span>, | |
| ) { | |
| static CAP_COVERED_BY_MANY: usize = 4; | |
| let pat_span = pat.data().span; | |
| let mut lint = UnreachablePatternInner { | |
| span: Some(pat_span), | |
| matches_no_values: None, | |
| matches_no_values_ty: **pat.ty(), | |
| uninhabited_note: None, | |
| covered_by_catchall: None, | |
| covered_by_one: None, | |
| covered_by_many: None, | |
| wanted_constant: None, | |
| accessible_constant: None, | |
| inaccessible_constant: None, | |
| pattern_let_binding: None, | |
| suggest_remove: None, | |
| }; | |
| let mut covered_by_many_n_more_count = None; | |
| match explanation.covered_by.as_slice() { | |
| [] => { | |
| // Empty pattern; we report the uninhabited type that caused the emptiness. | |
| lint.span = None; // Don't label the pattern itself | |
| lint.uninhabited_note = Some(()); // Give a link about empty types | |
| lint.matches_no_values = Some(pat_span); | |
| lint.suggest_remove = whole_arm_span; // Suggest to remove the match arm | |
| pat.walk(&mut |subpat| { | |
| let ty = **subpat.ty(); | |
| if cx.is_uninhabited(ty) { | |
| lint.matches_no_values_ty = ty; | |
| false // No need to dig further. | |
| } else if matches!(subpat.ctor(), Constructor::Ref | Constructor::UnionField) { | |
| false // Don't explore further since they are not by-value. | |
| } else { | |
| true | |
| } | |
| }); | |
| } | |
| [covering_pat] if pat_is_catchall(covering_pat) => { | |
| // A binding pattern that matches all, a single binding name. | |
| let pat = covering_pat.data(); | |
| lint.covered_by_catchall = Some(pat.span); | |
| find_fallback_pattern_typo(cx, hir_id, pat, &mut lint); | |
| } | |
| [covering_pat] => { | |
| lint.covered_by_one = Some(covering_pat.data().span); | |
| } | |
| covering_pats => { | |
| let mut iter = covering_pats.iter(); | |
| let mut multispan = MultiSpan::from_span(pat_span); | |
| for p in iter.by_ref().take(CAP_COVERED_BY_MANY) { | |
| multispan.push_span_label(p.data().span, msg!("matches some of the same values")); | |
| } | |
| let remain = iter.count(); | |
| if remain == 0 { | |
| multispan.push_span_label(pat_span, msg!("collectively making this unreachable")); | |
| } else { | |
| covered_by_many_n_more_count = Some(remain); | |
| multispan.push_span_label( | |
| pat_span, | |
| msg!("...and {$covered_by_many_n_more_count} other patterns collectively make this unreachable"), | |
| ); | |
| } | |
| lint.covered_by_many = Some(multispan); | |
| } | |
| } | |
| cx.tcx.emit_node_span_lint( | |
| UNREACHABLE_PATTERNS, | |
| hir_id, | |
| pat_span, | |
| UnreachablePattern { inner: lint, covered_by_many_n_more_count }, | |
| ); | |
| } | |
| /// Detect typos that were meant to be a `const` but were interpreted as a new pattern binding. | |
| fn find_fallback_pattern_typo<'tcx>( | |
| cx: &PatCtxt<'_, 'tcx>, | |
| hir_id: HirId, | |
| pat: &Pat<'tcx>, | |
| lint: &mut UnreachablePatternInner<'_>, | |
| ) { | |
| if cx.tcx.lint_level_spec_at_node(UNREACHABLE_PATTERNS, hir_id).is_allow() { | |
| // This is because we use `with_no_trimmed_paths` later, so if we never emit the lint we'd | |
| // ICE. At the same time, we don't really need to do all of this if we won't emit anything. | |
| return; | |
| } | |
| if let PatKind::Binding { name, subpattern: None, ty, .. } = pat.kind { | |
| // See if the binding might have been a `const` that was mistyped or out of scope. | |
| let mut accessible = vec![]; | |
| let mut accessible_path = vec![]; | |
| let mut inaccessible = vec![]; | |
| let mut imported = vec![]; | |
| let mut imported_spans = vec![]; | |
| let (infcx, param_env) = cx.tcx.infer_ctxt().build_with_typing_env(cx.typing_env); | |
| let parent = cx.tcx.hir_get_parent_item(hir_id); | |
| for item in cx.tcx.hir_crate_items(()).free_items() { | |
| if let DefKind::Use = cx.tcx.def_kind(item.owner_id) { | |
| // Look for consts being re-exported. | |
| let item = cx.tcx.hir_expect_item(item.owner_id.def_id); | |
| let hir::ItemKind::Use(path, _) = item.kind else { | |
| continue; | |
| }; | |
| if let Some(value_ns) = path.res.value_ns | |
| && let Res::Def(DefKind::Const { .. }, id) = value_ns | |
| && infcx.can_eq( | |
| param_env, | |
| ty, | |
| cx.tcx.type_of(id).instantiate_identity().skip_norm_wip(), | |
| ) | |
| { | |
| if cx.tcx.visibility(id).is_accessible_from(parent, cx.tcx) { | |
| // The original const is accessible, suggest using it directly. | |
| let item_name = cx.tcx.item_name(id); | |
| accessible.push(item_name); | |
| accessible_path.push(with_no_trimmed_paths!(cx.tcx.def_path_str(id))); | |
| } else if cx.tcx.visibility(item.owner_id).is_accessible_from(parent, cx.tcx) { | |
| // The const is accessible only through the re-export, point at | |
| // the `use`. | |
| let ident = item.kind.ident().unwrap(); | |
| imported.push(ident.name); | |
| imported_spans.push(ident.span); | |
| } | |
| } | |
| } | |
| if let DefKind::Const { .. } = cx.tcx.def_kind(item.owner_id) | |
| && infcx.can_eq( | |
| param_env, | |
| ty, | |
| cx.tcx.type_of(item.owner_id).instantiate_identity().skip_norm_wip(), | |
| ) | |
| { | |
| // Look for local consts. | |
| let item_name = cx.tcx.item_name(item.owner_id); | |
| let vis = cx.tcx.visibility(item.owner_id); | |
| if vis.is_accessible_from(parent, cx.tcx) { | |
| accessible.push(item_name); | |
| // FIXME: the line below from PR #135310 is a workaround for the ICE in issue | |
| // #135289, where a macro in a dependency can create unreachable patterns in the | |
| // current crate. Path trimming expects diagnostics for a typoed const, but no | |
| // diagnostics are emitted and we ICE. See | |
| // `tests/ui/resolve/const-with-typo-in-pattern-binding-ice-135289.rs` for a | |
| // test that reproduces the ICE if we don't use `with_no_trimmed_paths!`. | |
| let path = with_no_trimmed_paths!(cx.tcx.def_path_str(item.owner_id)); | |
| accessible_path.push(path); | |
| } else if name == item_name { | |
| // The const exists somewhere in this crate, but it can't be imported | |
| // from this pattern's scope. We'll just point at its definition. | |
| inaccessible.push(cx.tcx.def_span(item.owner_id)); | |
| } | |
| } | |
| } | |
| if let Some((i, &const_name)) = | |
| accessible.iter().enumerate().find(|&(_, &const_name)| const_name == name) | |
| { | |
| // The pattern name is an exact match, so the pattern needed to be imported. | |
| lint.wanted_constant = Some(WantedConstant { | |
| span: pat.span, | |
| is_typo: false, | |
| const_name: const_name.to_string(), | |
| const_path: accessible_path[i].clone(), | |
| }); | |
| } else if let Some(name) = find_best_match_for_name(&accessible, name, None) { | |
| // The pattern name is likely a typo. | |
| lint.wanted_constant = Some(WantedConstant { | |
| span: pat.span, | |
| is_typo: true, | |
| const_name: name.to_string(), | |
| const_path: name.to_string(), | |
| }); | |
| } else if let Some(i) = | |
| imported.iter().enumerate().find(|&(_, &const_name)| const_name == name).map(|(i, _)| i) | |
| { | |
| // The const with the exact name wasn't re-exported from an import in this | |
| // crate, we point at the import. | |
| lint.accessible_constant = Some(imported_spans[i]); | |
| } else if let Some(name) = find_best_match_for_name(&imported, name, None) { | |
| // The typoed const wasn't re-exported by an import in this crate, we suggest | |
| // the right name (which will likely require another follow up suggestion). | |
| lint.wanted_constant = Some(WantedConstant { | |
| span: pat.span, | |
| is_typo: true, | |
| const_path: name.to_string(), | |
| const_name: name.to_string(), | |
| }); | |
| } else if !inaccessible.is_empty() { | |
| for span in inaccessible { | |
| // The const with the exact name match isn't accessible, we just point at it. | |
| lint.inaccessible_constant = Some(span); | |
| } | |
| } else { | |
| // Look for local bindings for people that might have gotten confused with how | |
| // `let` and `const` works. | |
| for (_, node) in cx.tcx.hir_parent_iter(hir_id) { | |
| match node { | |
| hir::Node::Stmt(hir::Stmt { kind: hir::StmtKind::Let(let_stmt), .. }) => { | |
| if let hir::PatKind::Binding(_, _, binding_name, _) = let_stmt.pat.kind { | |
| if name == binding_name.name { | |
| lint.pattern_let_binding = Some(binding_name.span); | |
| } | |
| } | |
| } | |
| hir::Node::Block(hir::Block { stmts, .. }) => { | |
| for stmt in *stmts { | |
| if let hir::StmtKind::Let(let_stmt) = stmt.kind | |
| && let hir::PatKind::Binding(_, _, binding_name, _) = | |
| let_stmt.pat.kind | |
| && name == binding_name.name | |
| { | |
| lint.pattern_let_binding = Some(binding_name.span); | |
| } | |
| } | |
| } | |
| hir::Node::Item(_) => break, | |
| _ => {} | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /// Report unreachable arms, if any. | |
| fn report_arm_reachability<'p, 'tcx>( | |
| cx: &PatCtxt<'p, 'tcx>, | |
| report: &UsefulnessReport<'p, 'tcx>, | |
| is_match_arm: bool, | |
| ) { | |
| let sm = cx.tcx.sess.source_map(); | |
| for (arm, is_useful) in report.arm_usefulness.iter() { | |
| if let Usefulness::Redundant(explanation) = is_useful { | |
| let hir_id = arm.arm_data; | |
| let arm_span = cx.tcx.hir_span(hir_id); | |
| let whole_arm_span = if is_match_arm { | |
| // If the arm is followed by a comma, extend the span to include it. | |
| if let Some(comma) = sm.span_followed_by(arm_span, ",") { | |
| Some(arm_span.to(comma)) | |
| } else { | |
| Some(arm_span) | |
| } | |
| } else { | |
| None | |
| }; | |
| report_unreachable_pattern(cx, hir_id, arm.pat, explanation, whole_arm_span) | |
| } | |
| } | |
| } | |
| /// Checks for common cases of "catchall" patterns that may not be intended as such. | |
| fn pat_is_catchall(pat: &DeconstructedPat<'_, '_>) -> bool { | |
| match pat.ctor() { | |
| Constructor::Wildcard => true, | |
| Constructor::Struct | Constructor::Ref => { | |
| pat.iter_fields().all(|ipat| pat_is_catchall(&ipat.pat)) | |
| } | |
| _ => false, | |
| } | |
| } | |
| /// If the given pattern is a named constant that looks like it could have been | |
| /// intended to be a binding, returns the `DefId` of the named constant. | |
| /// | |
| /// Diagnostics use this to give more detailed suggestions for non-exhaustive | |
| /// matches. | |
| fn is_const_pat_that_looks_like_binding<'tcx>(tcx: TyCtxt<'tcx>, pat: &Pat<'tcx>) -> Option<DefId> { | |
| // The pattern must be a named constant, and the name that appears in | |
| // the pattern's source text must resemble a plain identifier without any | |
| // `::` namespace separators or other non-identifier characters. | |
| if let Some(def_id) = try { pat.extra.as_deref()?.expanded_const? } | |
| && matches!(tcx.def_kind(def_id), DefKind::Const { .. }) | |
| && let Ok(snippet) = tcx.sess.source_map().span_to_snippet(pat.span) | |
| && snippet.chars().all(|c| c.is_alphanumeric() || c == '_') | |
| { | |
| Some(def_id) | |
| } else { | |
| None | |
| } | |
| } | |
| /// Report that a match is not exhaustive. | |
| fn report_non_exhaustive_match<'p, 'tcx>( | |
| cx: &PatCtxt<'p, 'tcx>, | |
| thir: &Thir<'tcx>, | |
| scrut_ty: Ty<'tcx>, | |
| sp: Span, | |
| witnesses: Vec<WitnessPat<'p, 'tcx>>, | |
| arms: &[ArmId], | |
| braces_span: Option<Span>, | |
| would_be_exhaustive_without_guards: bool, | |
| ) -> ErrorGuaranteed { | |
| let is_empty_match = arms.is_empty(); | |
| let non_empty_enum = match scrut_ty.kind() { | |
| ty::Adt(def, _) => def.is_enum() && !def.variants().is_empty(), | |
| _ => false, | |
| }; | |
| // In the case of an empty match, replace the '`_` not covered' diagnostic with something more | |
| // informative. | |
| if is_empty_match && !non_empty_enum { | |
| return cx.tcx.dcx().emit_err(NonExhaustivePatternsTypeNotEmpty { | |
| cx, | |
| scrut_span: sp, | |
| braces_span, | |
| ty: scrut_ty, | |
| }); | |
| } | |
| // FIXME: migration of this diagnostic will require list support | |
| let joined_patterns = joined_uncovered_patterns(cx, &witnesses); | |
| let mut err = struct_span_code_err!( | |
| cx.tcx.dcx(), | |
| sp, | |
| E0004, | |
| "non-exhaustive patterns: {joined_patterns} not covered" | |
| ); | |
| err.span_label( | |
| sp, | |
| format!( | |
| "pattern{} {} not covered", | |
| rustc_errors::pluralize!(witnesses.len()), | |
| joined_patterns | |
| ), | |
| ); | |
| // Point at the definition of non-covered `enum` variants. | |
| if let Some(AdtDefinedHere { adt_def_span, ty, variants }) = | |
| report_adt_defined_here(cx.tcx, scrut_ty, &witnesses, true) | |
| { | |
| let mut multi_span = MultiSpan::from_span(adt_def_span); | |
| multi_span.push_span_label(adt_def_span, ""); | |
| for Variant { span } in variants { | |
| multi_span.push_span_label(span, "not covered"); | |
| } | |
| err.span_note(multi_span, format!("`{ty}` defined here")); | |
| } | |
| err.note(format!("the matched value is of type `{}`", scrut_ty)); | |
| if !is_empty_match { | |
| let mut special_tys = FxIndexSet::default(); | |
| // Look at the first witness. | |
| collect_special_tys(cx, &witnesses[0], &mut special_tys); | |
| for ty in special_tys { | |
| if ty.is_ptr_sized_integral() { | |
| if ty.inner() == cx.tcx.types.usize { | |
| err.note(format!( | |
| "`{ty}::MAX` is not treated as exhaustive, \ | |
| so half-open ranges are necessary to match exhaustively", | |
| )); | |
| } else if ty.inner() == cx.tcx.types.isize { | |
| err.note(format!( | |
| "`{ty}::MIN` and `{ty}::MAX` are not treated as exhaustive, \ | |
| so half-open ranges are necessary to match exhaustively", | |
| )); | |
| } | |
| } else if ty.inner() == cx.tcx.types.str_ { | |
| err.note("`&str` cannot be matched exhaustively, so a wildcard `_` is necessary"); | |
| } else if cx.is_foreign_non_exhaustive_enum(ty) { | |
| err.note(format!("`{ty}` is marked as non-exhaustive, so a wildcard `_` is necessary to match exhaustively")); | |
| } else if cx.is_uninhabited(ty.inner()) { | |
| // The type is uninhabited yet there is a witness: we must be in the `MaybeInvalid` | |
| // case. | |
| err.note(format!("`{ty}` is uninhabited but is not being matched by value, so a wildcard `_` is required")); | |
| } | |
| } | |
| } | |
| if let ty::Ref(_, sub_ty, _) = scrut_ty.kind() { | |
| if !sub_ty.is_inhabited_from(cx.tcx, cx.module, cx.typing_env) { | |
| err.note("references are always considered inhabited"); | |
| } | |
| } | |
| for &arm in arms { | |
| let arm = &thir.arms[arm]; | |
| if let Some(def_id) = is_const_pat_that_looks_like_binding(cx.tcx, &arm.pattern) { | |
| let const_name = cx.tcx.item_name(def_id); | |
| err.span_label( | |
| arm.pattern.span, | |
| format!( | |
| "this pattern doesn't introduce a new catch-all binding, but rather pattern \ | |
| matches against the value of constant `{const_name}`", | |
| ), | |
| ); | |
| err.span_note(cx.tcx.def_span(def_id), format!("constant `{const_name}` defined here")); | |
| err.span_suggestion_verbose( | |
| arm.pattern.span.shrink_to_hi(), | |
| "if you meant to introduce a binding, use a different name", | |
| "_var".to_string(), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| } | |
| } | |
| // Whether we suggest the actual missing patterns or `_`. | |
| let suggest_the_witnesses = witnesses.len() < 4; | |
| let suggested_arm = if suggest_the_witnesses { | |
| let pattern = witnesses | |
| .iter() | |
| .map(|witness| cx.print_witness_pat(witness)) | |
| .collect::<Vec<String>>() | |
| .join(" | "); | |
| if witnesses.iter().all(|p| p.is_never_pattern()) && cx.tcx.features().never_patterns() { | |
| // Arms with a never pattern don't take a body. | |
| pattern | |
| } else { | |
| format!("{pattern} => todo!()") | |
| } | |
| } else { | |
| format!("_ => todo!()") | |
| }; | |
| let mut suggestion = None; | |
| let sm = cx.tcx.sess.source_map(); | |
| match arms { | |
| [] if let Some(braces_span) = braces_span => { | |
| // Get the span for the empty match body `{}`. | |
| let (indentation, more) = if let Some(snippet) = sm.indentation_before(sp) { | |
| (format!("\n{snippet}"), " ") | |
| } else { | |
| (" ".to_string(), "") | |
| }; | |
| suggestion = Some(( | |
| braces_span, | |
| format!(" {{{indentation}{more}{suggested_arm},{indentation}}}",), | |
| )); | |
| } | |
| [only] => { | |
| let only = &thir[*only]; | |
| let (pre_indentation, is_multiline) = if let Some(snippet) = | |
| sm.indentation_before(only.span) | |
| && let Ok(with_trailing) = | |
| sm.span_extend_while(only.span, |c| c.is_whitespace() || c == ',') | |
| && sm.is_multiline(with_trailing) | |
| { | |
| (format!("\n{snippet}"), true) | |
| } else { | |
| (" ".to_string(), false) | |
| }; | |
| let only_body = &thir[only.body]; | |
| let comma = if matches!(only_body.kind, ExprKind::Block { .. }) | |
| && only.span.eq_ctxt(only_body.span) | |
| && is_multiline | |
| { | |
| "" | |
| } else { | |
| "," | |
| }; | |
| suggestion = Some(( | |
| only.span.shrink_to_hi(), | |
| format!("{comma}{pre_indentation}{suggested_arm}"), | |
| )); | |
| } | |
| [.., prev, last] => { | |
| let prev = &thir[*prev]; | |
| let last = &thir[*last]; | |
| if prev.span.eq_ctxt(last.span) { | |
| let last_body = &thir[last.body]; | |
| let comma = if matches!(last_body.kind, ExprKind::Block { .. }) | |
| && last.span.eq_ctxt(last_body.span) | |
| { | |
| "" | |
| } else { | |
| "," | |
| }; | |
| let spacing = if sm.is_multiline(prev.span.between(last.span)) { | |
| sm.indentation_before(last.span).map(|indent| format!("\n{indent}")) | |
| } else { | |
| Some(" ".to_string()) | |
| }; | |
| if let Some(spacing) = spacing { | |
| suggestion = Some(( | |
| last.span.shrink_to_hi(), | |
| format!("{comma}{spacing}{suggested_arm}"), | |
| )); | |
| } | |
| } | |
| } | |
| _ => {} | |
| } | |
| let msg = format!( | |
| "ensure that all possible cases are being handled by adding a match arm with a wildcard \ | |
| pattern{}{}", | |
| if witnesses.len() > 1 && suggest_the_witnesses && suggestion.is_some() { | |
| ", a match arm with multiple or-patterns" | |
| } else { | |
| // we are either not suggesting anything, or suggesting `_` | |
| "" | |
| }, | |
| match witnesses.len() { | |
| // non-exhaustive enum case | |
| 0 if suggestion.is_some() => " as shown", | |
| 0 => "", | |
| 1 if suggestion.is_some() => " or an explicit pattern as shown", | |
| 1 => " or an explicit pattern", | |
| _ if suggestion.is_some() => " as shown, or multiple match arms", | |
| _ => " or multiple match arms", | |
| }, | |
| ); | |
| if would_be_exhaustive_without_guards { | |
| err.subdiagnostic(NonExhaustiveMatchAllArmsGuarded); | |
| } | |
| if let Some((span, sugg)) = suggestion { | |
| err.span_suggestion_verbose(span, msg, sugg, Applicability::HasPlaceholders); | |
| } else { | |
| err.help(msg); | |
| } | |
| err.emit() | |
| } | |
| fn joined_uncovered_patterns<'p, 'tcx>( | |
| cx: &PatCtxt<'p, 'tcx>, | |
| witnesses: &[WitnessPat<'p, 'tcx>], | |
| ) -> String { | |
| const LIMIT: usize = 3; | |
| let pat_to_str = |pat: &WitnessPat<'p, 'tcx>| cx.print_witness_pat(pat); | |
| match witnesses { | |
| [] => bug!(), | |
| [witness] => format!("`{}`", cx.print_witness_pat(witness)), | |
| [head @ .., tail] if head.len() < LIMIT => { | |
| let head: Vec<_> = head.iter().map(pat_to_str).collect(); | |
| format!("`{}` and `{}`", head.join("`, `"), cx.print_witness_pat(tail)) | |
| } | |
| _ => { | |
| let (head, tail) = witnesses.split_at(LIMIT); | |
| let head: Vec<_> = head.iter().map(pat_to_str).collect(); | |
| format!("`{}` and {} more", head.join("`, `"), tail.len()) | |
| } | |
| } | |
| } | |
| /// Collect types that require specific explanations when they show up in witnesses. | |
| fn collect_special_tys<'tcx>( | |
| cx: &PatCtxt<'_, 'tcx>, | |
| pat: &WitnessPat<'_, 'tcx>, | |
| special_tys: &mut FxIndexSet<RevealedTy<'tcx>>, | |
| ) { | |
| if matches!(pat.ctor(), Constructor::NonExhaustive | Constructor::Never) { | |
| special_tys.insert(*pat.ty()); | |
| } | |
| if let Constructor::IntRange(range) = pat.ctor() { | |
| if cx.is_range_beyond_boundaries(range, *pat.ty()) { | |
| // The range denotes the values before `isize::MIN` or the values after `usize::MAX`/`isize::MAX`. | |
| special_tys.insert(*pat.ty()); | |
| } | |
| } | |
| pat.iter_fields().for_each(|field_pat| collect_special_tys(cx, field_pat, special_tys)) | |
| } | |
| fn report_adt_defined_here<'tcx>( | |
| tcx: TyCtxt<'tcx>, | |
| ty: Ty<'tcx>, | |
| witnesses: &[WitnessPat<'_, 'tcx>], | |
| point_at_non_local_ty: bool, | |
| ) -> Option<AdtDefinedHere<'tcx>> { | |
| let ty = ty.peel_refs(); | |
| let ty::Adt(def, _) = ty.kind() else { | |
| return None; | |
| }; | |
| let adt_def_span = | |
| tcx.hir_get_if_local(def.did()).and_then(|node| node.ident()).map(|ident| ident.span); | |
| let adt_def_span = if point_at_non_local_ty { | |
| adt_def_span.unwrap_or_else(|| tcx.def_span(def.did())) | |
| } else { | |
| adt_def_span? | |
| }; | |
| let mut variants = vec![]; | |
| for span in maybe_point_at_variant(tcx, *def, witnesses.iter().take(5)) { | |
| variants.push(Variant { span }); | |
| } | |
| Some(AdtDefinedHere { adt_def_span, ty, variants }) | |
| } | |
| fn maybe_point_at_variant<'a, 'p: 'a, 'tcx: 'p>( | |
| tcx: TyCtxt<'tcx>, | |
| def: AdtDef<'tcx>, | |
| patterns: impl Iterator<Item = &'a WitnessPat<'p, 'tcx>>, | |
| ) -> Vec<Span> { | |
| let mut covered = vec![]; | |
| for pattern in patterns { | |
| if let Constructor::Variant(variant_index) = pattern.ctor() { | |
| if let ty::Adt(this_def, _) = pattern.ty().kind() | |
| && this_def.did() != def.did() | |
| { | |
| continue; | |
| } | |
| let sp = def.variant(*variant_index).ident(tcx).span; | |
| if covered.contains(&sp) { | |
| // Don't point at variants that have already been covered due to other patterns to avoid | |
| // visual clutter. | |
| continue; | |
| } | |
| covered.push(sp); | |
| } | |
| covered.extend(maybe_point_at_variant(tcx, def, pattern.iter_fields())); | |
| } | |
| covered | |
| } |