]>
Commit | Line | Data |
---|---|---|
c295e0f8 | 1 | use super::deconstruct_pat::{Constructor, DeconstructedPat}; |
fc512014 | 2 | use super::usefulness::{ |
c295e0f8 | 3 | compute_match_usefulness, MatchArm, MatchCheckCtxt, Reachability, UsefulnessReport, |
fc512014 | 4 | }; |
17df50a5 | 5 | use super::{PatCtxt, PatternError}; |
223e47cc | 6 | |
f035d41b | 7 | use rustc_arena::TypedArena; |
3dfed10e | 8 | use rustc_ast::Mutability; |
5e7ed085 | 9 | use rustc_errors::{ |
2b03887a | 10 | error_code, pluralize, struct_span_err, Applicability, DelayDm, Diagnostic, DiagnosticBuilder, |
04454e1e | 11 | ErrorGuaranteed, MultiSpan, |
5e7ed085 | 12 | }; |
dfeec247 XL |
13 | use rustc_hir as hir; |
14 | use rustc_hir::def::*; | |
15 | use rustc_hir::def_id::DefId; | |
5099ac24 | 16 | use rustc_hir::intravisit::{self, Visitor}; |
dfeec247 | 17 | use rustc_hir::{HirId, Pat}; |
c295e0f8 XL |
18 | use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; |
19 | use rustc_session::lint::builtin::{ | |
20 | BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS, | |
21 | }; | |
dfeec247 | 22 | use rustc_session::Session; |
5099ac24 | 23 | use rustc_span::source_map::Spanned; |
064997fb | 24 | use rustc_span::{BytePos, Span}; |
1a4d82fc | 25 | |
923072b8 | 26 | pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: DefId) { |
f9f354fc | 27 | let body_id = match def_id.as_local() { |
e74abb32 | 28 | None => return, |
064997fb | 29 | Some(def_id) => tcx.hir().body_owned_by(def_id), |
ff7c6d11 XL |
30 | }; |
31 | ||
c295e0f8 | 32 | let pattern_arena = TypedArena::default(); |
f9f354fc XL |
33 | let mut visitor = MatchVisitor { |
34 | tcx, | |
3dfed10e | 35 | typeck_results: tcx.typeck_body(body_id), |
f9f354fc | 36 | param_env: tcx.param_env(def_id), |
c295e0f8 | 37 | pattern_arena: &pattern_arena, |
f9f354fc | 38 | }; |
416331ca | 39 | visitor.visit_body(tcx.hir().body(body_id)); |
ff7c6d11 XL |
40 | } |
41 | ||
5e7ed085 FG |
42 | fn create_e0004( |
43 | sess: &Session, | |
44 | sp: Span, | |
45 | error_message: String, | |
46 | ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { | |
c30ab7b3 | 47 | struct_span_err!(sess, sp, E0004, "{}", &error_message) |
223e47cc LB |
48 | } |
49 | ||
c295e0f8 XL |
50 | #[derive(PartialEq)] |
51 | enum RefutableFlag { | |
52 | Irrefutable, | |
53 | Refutable, | |
54 | } | |
55 | use RefutableFlag::*; | |
56 | ||
57 | struct MatchVisitor<'a, 'p, 'tcx> { | |
dc9dc135 | 58 | tcx: TyCtxt<'tcx>, |
3dfed10e | 59 | typeck_results: &'a ty::TypeckResults<'tcx>, |
7cac9316 | 60 | param_env: ty::ParamEnv<'tcx>, |
c295e0f8 | 61 | pattern_arena: &'p TypedArena<DeconstructedPat<'p, 'tcx>>, |
1a4d82fc JJ |
62 | } |
63 | ||
c295e0f8 | 64 | impl<'tcx> Visitor<'tcx> for MatchVisitor<'_, '_, 'tcx> { |
dfeec247 | 65 | fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { |
c30ab7b3 | 66 | intravisit::walk_expr(self, ex); |
94222f64 | 67 | match &ex.kind { |
5e7ed085 FG |
68 | hir::ExprKind::Match(scrut, arms, source) => { |
69 | self.check_match(scrut, arms, *source, ex.span) | |
70 | } | |
a2a8927a XL |
71 | hir::ExprKind::Let(hir::Let { pat, init, span, .. }) => { |
72 | self.check_let(pat, init, *span) | |
73 | } | |
94222f64 | 74 | _ => {} |
c30ab7b3 | 75 | } |
1a4d82fc | 76 | } |
c30ab7b3 | 77 | |
dfeec247 | 78 | fn visit_local(&mut self, loc: &'tcx hir::Local<'tcx>) { |
c30ab7b3 | 79 | intravisit::walk_local(self, loc); |
064997fb FG |
80 | let els = loc.els; |
81 | if let Some(init) = loc.init && els.is_some() { | |
487cf647 FG |
82 | // Build a span without the else { ... } as we don't want to underline |
83 | // the entire else block in the IDE setting. | |
84 | let span = loc.span.with_hi(init.span.hi()); | |
85 | self.check_let(&loc.pat, init, span); | |
064997fb | 86 | } |
c30ab7b3 | 87 | |
e74abb32 XL |
88 | let (msg, sp) = match loc.source { |
89 | hir::LocalSource::Normal => ("local binding", Some(loc.span)), | |
e74abb32 XL |
90 | hir::LocalSource::AsyncFn => ("async fn binding", None), |
91 | hir::LocalSource::AwaitDesugar => ("`await` future binding", None), | |
29967ef6 | 92 | hir::LocalSource::AssignDesugar(_) => ("destructuring assignment binding", None), |
e74abb32 | 93 | }; |
064997fb FG |
94 | if els.is_none() { |
95 | self.check_irrefutable(&loc.pat, msg, sp); | |
96 | } | |
1a4d82fc | 97 | } |
c30ab7b3 | 98 | |
dfeec247 XL |
99 | fn visit_param(&mut self, param: &'tcx hir::Param<'tcx>) { |
100 | intravisit::walk_param(self, param); | |
101 | self.check_irrefutable(¶m.pat, "function argument", None); | |
c30ab7b3 | 102 | } |
1a4d82fc JJ |
103 | } |
104 | ||
e74abb32 | 105 | impl PatCtxt<'_, '_> { |
136023e0 | 106 | fn report_inlining_errors(&self) { |
cc61c64b XL |
107 | for error in &self.errors { |
108 | match *error { | |
c30ab7b3 | 109 | PatternError::StaticInPattern(span) => { |
2c00a5a8 XL |
110 | self.span_e0158(span, "statics cannot be referenced in patterns") |
111 | } | |
dc9dc135 | 112 | PatternError::AssocConstInPattern(span) => { |
2c00a5a8 | 113 | self.span_e0158(span, "associated consts cannot be referenced in patterns") |
c30ab7b3 | 114 | } |
ba9703b0 XL |
115 | PatternError::ConstParamInPattern(span) => { |
116 | self.span_e0158(span, "const parameters cannot be referenced in patterns") | |
117 | } | |
0531ce1d | 118 | PatternError::NonConstPath(span) => { |
29967ef6 | 119 | rustc_middle::mir::interpret::struct_error( |
94b46f34 | 120 | self.tcx.at(span), |
0531ce1d | 121 | "runtime values cannot be referenced in patterns", |
e74abb32 XL |
122 | ) |
123 | .emit(); | |
1a4d82fc JJ |
124 | } |
125 | } | |
c30ab7b3 SL |
126 | } |
127 | } | |
2c00a5a8 XL |
128 | |
129 | fn span_e0158(&self, span: Span, text: &str) { | |
dfeec247 | 130 | struct_span_err!(self.tcx.sess, span, E0158, "{}", text).emit(); |
2c00a5a8 | 131 | } |
cc61c64b XL |
132 | } |
133 | ||
c295e0f8 XL |
134 | impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { |
135 | fn check_patterns(&self, pat: &Pat<'_>, rf: RefutableFlag) { | |
74b04a01 | 136 | pat.walk_always(|pat| check_borrow_conflicts_in_at_patterns(self, pat)); |
c295e0f8 | 137 | check_for_bindings_named_same_as_variants(self, pat, rf); |
cc61c64b | 138 | } |
1a4d82fc | 139 | |
c295e0f8 | 140 | fn lower_pattern( |
dfeec247 XL |
141 | &self, |
142 | cx: &mut MatchCheckCtxt<'p, 'tcx>, | |
143 | pat: &'tcx hir::Pat<'tcx>, | |
144 | have_errors: &mut bool, | |
c295e0f8 | 145 | ) -> &'p DeconstructedPat<'p, 'tcx> { |
3dfed10e | 146 | let mut patcx = PatCtxt::new(self.tcx, self.param_env, self.typeck_results); |
dfeec247 XL |
147 | patcx.include_lint_checks(); |
148 | let pattern = patcx.lower_pattern(pat); | |
c295e0f8 | 149 | let pattern: &_ = cx.pattern_arena.alloc(DeconstructedPat::from_pat(cx, &pattern)); |
dfeec247 XL |
150 | if !patcx.errors.is_empty() { |
151 | *have_errors = true; | |
136023e0 | 152 | patcx.report_inlining_errors(); |
dfeec247 | 153 | } |
c295e0f8 | 154 | pattern |
dfeec247 XL |
155 | } |
156 | ||
c295e0f8 | 157 | fn new_cx(&self, hir_id: HirId) -> MatchCheckCtxt<'p, 'tcx> { |
f9f354fc XL |
158 | MatchCheckCtxt { |
159 | tcx: self.tcx, | |
160 | param_env: self.param_env, | |
161 | module: self.tcx.parent_module(hir_id).to_def_id(), | |
162 | pattern_arena: &self.pattern_arena, | |
163 | } | |
dfeec247 XL |
164 | } |
165 | ||
a2a8927a | 166 | fn check_let(&mut self, pat: &'tcx hir::Pat<'tcx>, scrutinee: &hir::Expr<'_>, span: Span) { |
c295e0f8 | 167 | self.check_patterns(pat, Refutable); |
a2a8927a | 168 | let mut cx = self.new_cx(scrutinee.hir_id); |
c295e0f8 | 169 | let tpat = self.lower_pattern(&mut cx, pat, &mut false); |
5e7ed085 | 170 | self.check_let_reachability(&mut cx, pat.hir_id, tpat, span); |
94222f64 XL |
171 | } |
172 | ||
dfeec247 XL |
173 | fn check_match( |
174 | &mut self, | |
175 | scrut: &hir::Expr<'_>, | |
3c0e092e | 176 | hir_arms: &'tcx [hir::Arm<'tcx>], |
dfeec247 | 177 | source: hir::MatchSource, |
5e7ed085 | 178 | expr_span: Span, |
dfeec247 | 179 | ) { |
94222f64 XL |
180 | let mut cx = self.new_cx(scrut.hir_id); |
181 | ||
3c0e092e | 182 | for arm in hir_arms { |
dfeec247 | 183 | // Check the arm for some things unrelated to exhaustiveness. |
c295e0f8 | 184 | self.check_patterns(&arm.pat, Refutable); |
923072b8 FG |
185 | if let Some(hir::Guard::IfLet(ref let_expr)) = arm.guard { |
186 | self.check_patterns(let_expr.pat, Refutable); | |
187 | let tpat = self.lower_pattern(&mut cx, let_expr.pat, &mut false); | |
188 | self.check_let_reachability(&mut cx, let_expr.pat.hir_id, tpat, tpat.span()); | |
fc512014 XL |
189 | } |
190 | } | |
191 | ||
f9f354fc | 192 | let mut have_errors = false; |
1a4d82fc | 193 | |
3c0e092e | 194 | let arms: Vec<_> = hir_arms |
f9f354fc | 195 | .iter() |
fc512014 | 196 | .map(|hir::Arm { pat, guard, .. }| MatchArm { |
c295e0f8 | 197 | pat: self.lower_pattern(&mut cx, pat, &mut have_errors), |
fc512014 XL |
198 | hir_id: pat.hir_id, |
199 | has_guard: guard.is_some(), | |
f9f354fc XL |
200 | }) |
201 | .collect(); | |
1a4d82fc | 202 | |
fc512014 | 203 | // Bail out early if lowering failed. |
f9f354fc XL |
204 | if have_errors { |
205 | return; | |
206 | } | |
1a4d82fc | 207 | |
fc512014 XL |
208 | let scrut_ty = self.typeck_results.expr_ty_adjusted(scrut); |
209 | let report = compute_match_usefulness(&cx, &arms, scrut.hir_id, scrut_ty); | |
210 | ||
c295e0f8 | 211 | match source { |
3c0e092e XL |
212 | // Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }` |
213 | // when the iterator is an uninhabited type. unreachable_code will trigger instead. | |
214 | hir::MatchSource::ForLoopDesugar if arms.len() == 1 => {} | |
c295e0f8 XL |
215 | hir::MatchSource::ForLoopDesugar | hir::MatchSource::Normal => { |
216 | report_arm_reachability(&cx, &report) | |
94222f64 | 217 | } |
c295e0f8 XL |
218 | // Unreachable patterns in try and await expressions occur when one of |
219 | // the arms are an uninhabited type. Which is OK. | |
220 | hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar => {} | |
221 | } | |
f9f354fc | 222 | |
fc512014 | 223 | // Check if the match is exhaustive. |
fc512014 XL |
224 | let witnesses = report.non_exhaustiveness_witnesses; |
225 | if !witnesses.is_empty() { | |
3c0e092e XL |
226 | if source == hir::MatchSource::ForLoopDesugar && hir_arms.len() == 2 { |
227 | // the for loop pattern is not irrefutable | |
228 | let pat = hir_arms[1].pat.for_loop_some().unwrap(); | |
229 | self.check_irrefutable(pat, "`for` loop binding", None); | |
230 | } else { | |
5e7ed085 | 231 | non_exhaustive_match(&cx, scrut_ty, scrut.span, witnesses, hir_arms, expr_span); |
3c0e092e | 232 | } |
fc512014 | 233 | } |
c30ab7b3 SL |
234 | } |
235 | ||
5e7ed085 FG |
236 | fn check_let_reachability( |
237 | &mut self, | |
238 | cx: &mut MatchCheckCtxt<'p, 'tcx>, | |
239 | pat_id: HirId, | |
240 | pat: &'p DeconstructedPat<'p, 'tcx>, | |
241 | span: Span, | |
242 | ) { | |
243 | if self.check_let_chain(cx, pat_id) { | |
244 | return; | |
245 | } | |
246 | ||
247 | if is_let_irrefutable(cx, pat_id, pat) { | |
248 | irrefutable_let_pattern(cx.tcx, pat_id, span); | |
249 | } | |
250 | } | |
251 | ||
252 | fn check_let_chain(&mut self, cx: &mut MatchCheckCtxt<'p, 'tcx>, pat_id: HirId) -> bool { | |
253 | let hir = self.tcx.hir(); | |
254 | let parent = hir.get_parent_node(pat_id); | |
255 | ||
256 | // First, figure out if the given pattern is part of a let chain, | |
257 | // and if so, obtain the top node of the chain. | |
258 | let mut top = parent; | |
259 | let mut part_of_chain = false; | |
260 | loop { | |
261 | let new_top = hir.get_parent_node(top); | |
262 | if let hir::Node::Expr( | |
263 | hir::Expr { | |
264 | kind: hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, lhs, rhs), | |
265 | .. | |
266 | }, | |
267 | .., | |
268 | ) = hir.get(new_top) | |
269 | { | |
270 | // If this isn't the first iteration, we need to check | |
271 | // if there is a let expr before us in the chain, so | |
272 | // that we avoid doubly checking the let chain. | |
273 | ||
274 | // The way a chain of &&s is encoded is ((let ... && let ...) && let ...) && let ... | |
275 | // as && is left-to-right associative. Thus, we need to check rhs. | |
276 | if part_of_chain && matches!(rhs.kind, hir::ExprKind::Let(..)) { | |
277 | return true; | |
278 | } | |
279 | // If there is a let at the lhs, and we provide the rhs, we don't do any checking either. | |
280 | if !part_of_chain && matches!(lhs.kind, hir::ExprKind::Let(..)) && rhs.hir_id == top | |
281 | { | |
282 | return true; | |
283 | } | |
284 | } else { | |
285 | // We've reached the top. | |
286 | break; | |
287 | } | |
288 | ||
289 | // Since this function is called within a let context, it is reasonable to assume that any parent | |
290 | // `&&` infers a let chain | |
291 | part_of_chain = true; | |
292 | top = new_top; | |
293 | } | |
294 | if !part_of_chain { | |
295 | return false; | |
296 | } | |
297 | ||
298 | // Second, obtain the refutabilities of all exprs in the chain, | |
299 | // and record chain members that aren't let exprs. | |
300 | let mut chain_refutabilities = Vec::new(); | |
301 | let hir::Node::Expr(top_expr) = hir.get(top) else { | |
302 | // We ensure right above that it's an Expr | |
303 | unreachable!() | |
304 | }; | |
305 | let mut cur_expr = top_expr; | |
306 | loop { | |
307 | let mut add = |expr: &hir::Expr<'tcx>| { | |
308 | let refutability = match expr.kind { | |
309 | hir::ExprKind::Let(hir::Let { pat, init, span, .. }) => { | |
310 | let mut ncx = self.new_cx(init.hir_id); | |
311 | let tpat = self.lower_pattern(&mut ncx, pat, &mut false); | |
312 | ||
313 | let refutable = !is_let_irrefutable(&mut ncx, pat.hir_id, tpat); | |
314 | Some((*span, refutable)) | |
315 | } | |
316 | _ => None, | |
317 | }; | |
318 | chain_refutabilities.push(refutability); | |
319 | }; | |
320 | if let hir::Expr { | |
321 | kind: hir::ExprKind::Binary(Spanned { node: hir::BinOpKind::And, .. }, lhs, rhs), | |
322 | .. | |
323 | } = cur_expr | |
324 | { | |
325 | add(rhs); | |
326 | cur_expr = lhs; | |
327 | } else { | |
328 | add(cur_expr); | |
329 | break; | |
330 | } | |
331 | } | |
332 | chain_refutabilities.reverse(); | |
333 | ||
334 | // Third, emit the actual warnings. | |
335 | ||
336 | if chain_refutabilities.iter().all(|r| matches!(*r, Some((_, false)))) { | |
337 | // The entire chain is made up of irrefutable `let` statements | |
338 | let let_source = let_source_parent(self.tcx, top, None); | |
339 | irrefutable_let_patterns( | |
340 | cx.tcx, | |
341 | top, | |
342 | let_source, | |
343 | chain_refutabilities.len(), | |
344 | top_expr.span, | |
345 | ); | |
346 | return true; | |
347 | } | |
348 | let lint_affix = |affix: &[Option<(Span, bool)>], kind, suggestion| { | |
349 | let span_start = affix[0].unwrap().0; | |
350 | let span_end = affix.last().unwrap().unwrap().0; | |
351 | let span = span_start.to(span_end); | |
352 | let cnt = affix.len(); | |
2b03887a FG |
353 | let s = pluralize!(cnt); |
354 | cx.tcx.struct_span_lint_hir( | |
355 | IRREFUTABLE_LET_PATTERNS, | |
356 | top, | |
357 | span, | |
358 | format!("{kind} irrefutable pattern{s} in let chain"), | |
359 | |lint| { | |
360 | lint.note(format!( | |
361 | "{these} pattern{s} will always match", | |
362 | these = pluralize!("this", cnt), | |
363 | )) | |
364 | .help(format!( | |
365 | "consider moving {} {suggestion}", | |
366 | if cnt > 1 { "them" } else { "it" } | |
367 | )) | |
368 | }, | |
369 | ); | |
5e7ed085 FG |
370 | }; |
371 | if let Some(until) = chain_refutabilities.iter().position(|r| !matches!(*r, Some((_, false)))) && until > 0 { | |
372 | // The chain has a non-zero prefix of irrefutable `let` statements. | |
373 | ||
374 | // Check if the let source is while, for there is no alternative place to put a prefix, | |
375 | // and we shouldn't lint. | |
2b03887a FG |
376 | // For let guards inside a match, prefixes might use bindings of the match pattern, |
377 | // so can't always be moved out. | |
378 | // FIXME: Add checking whether the bindings are actually used in the prefix, | |
379 | // and lint if they are not. | |
5e7ed085 | 380 | let let_source = let_source_parent(self.tcx, top, None); |
2b03887a | 381 | if !matches!(let_source, LetSource::WhileLet | LetSource::IfLetGuard) { |
5e7ed085 FG |
382 | // Emit the lint |
383 | let prefix = &chain_refutabilities[..until]; | |
384 | lint_affix(prefix, "leading", "outside of the construct"); | |
385 | } | |
386 | } | |
387 | if let Some(from) = chain_refutabilities.iter().rposition(|r| !matches!(*r, Some((_, false)))) && from != (chain_refutabilities.len() - 1) { | |
388 | // The chain has a non-empty suffix of irrefutable `let` statements | |
389 | let suffix = &chain_refutabilities[from + 1..]; | |
390 | lint_affix(suffix, "trailing", "into the body"); | |
391 | } | |
392 | true | |
393 | } | |
394 | ||
dfeec247 | 395 | fn check_irrefutable(&self, pat: &'tcx Pat<'tcx>, origin: &str, sp: Option<Span>) { |
f9f354fc | 396 | let mut cx = self.new_cx(pat.hir_id); |
e74abb32 | 397 | |
c295e0f8 XL |
398 | let pattern = self.lower_pattern(&mut cx, pat, &mut false); |
399 | let pattern_ty = pattern.ty(); | |
fc512014 XL |
400 | let arms = vec![MatchArm { pat: pattern, hir_id: pat.hir_id, has_guard: false }]; |
401 | let report = compute_match_usefulness(&cx, &arms, pat.hir_id, pattern_ty); | |
402 | ||
403 | // Note: we ignore whether the pattern is unreachable (i.e. whether the type is empty). We | |
404 | // only care about exhaustiveness here. | |
405 | let witnesses = report.non_exhaustiveness_witnesses; | |
406 | if witnesses.is_empty() { | |
407 | // The pattern is irrefutable. | |
3c0e092e | 408 | self.check_patterns(pat, Irrefutable); |
fc512014 XL |
409 | return; |
410 | } | |
f9f354fc | 411 | |
c295e0f8 | 412 | let joined_patterns = joined_uncovered_patterns(&cx, &witnesses); |
5e7ed085 FG |
413 | |
414 | let mut bindings = vec![]; | |
415 | ||
f9f354fc XL |
416 | let mut err = struct_span_err!( |
417 | self.tcx.sess, | |
418 | pat.span, | |
419 | E0005, | |
420 | "refutable pattern in {}: {} not covered", | |
421 | origin, | |
422 | joined_patterns | |
423 | ); | |
424 | let suggest_if_let = match &pat.kind { | |
425 | hir::PatKind::Path(hir::QPath::Resolved(None, path)) | |
426 | if path.segments.len() == 1 && path.segments[0].args.is_none() => | |
427 | { | |
428 | const_not_var(&mut err, cx.tcx, pat, path); | |
429 | false | |
430 | } | |
431 | _ => { | |
5e7ed085 FG |
432 | pat.walk(&mut |pat: &hir::Pat<'_>| { |
433 | match pat.kind { | |
434 | hir::PatKind::Binding(_, _, ident, _) => { | |
435 | bindings.push(ident); | |
436 | } | |
437 | _ => {} | |
438 | } | |
439 | true | |
440 | }); | |
441 | ||
f9f354fc XL |
442 | err.span_label(pat.span, pattern_not_covered_label(&witnesses, &joined_patterns)); |
443 | true | |
444 | } | |
445 | }; | |
446 | ||
447 | if let (Some(span), true) = (sp, suggest_if_let) { | |
448 | err.note( | |
449 | "`let` bindings require an \"irrefutable pattern\", like a `struct` or \ | |
450 | an `enum` with only one variant", | |
451 | ); | |
064997fb | 452 | if self.tcx.sess.source_map().is_span_accessible(span) { |
5e7ed085 FG |
453 | let semi_span = span.shrink_to_hi().with_lo(span.hi() - BytePos(1)); |
454 | let start_span = span.shrink_to_lo(); | |
455 | let end_span = semi_span.shrink_to_lo(); | |
456 | err.multipart_suggestion( | |
457 | &format!( | |
458 | "you might want to use `if let` to ignore the variant{} that {} matched", | |
459 | pluralize!(witnesses.len()), | |
460 | match witnesses.len() { | |
461 | 1 => "isn't", | |
462 | _ => "aren't", | |
463 | }, | |
464 | ), | |
465 | vec![ | |
466 | match &bindings[..] { | |
467 | [] => (start_span, "if ".to_string()), | |
468 | [binding] => (start_span, format!("let {} = if ", binding)), | |
469 | bindings => ( | |
470 | start_span, | |
471 | format!( | |
472 | "let ({}) = if ", | |
473 | bindings | |
474 | .iter() | |
475 | .map(|ident| ident.to_string()) | |
476 | .collect::<Vec<_>>() | |
477 | .join(", ") | |
478 | ), | |
479 | ), | |
480 | }, | |
481 | match &bindings[..] { | |
482 | [] => (semi_span, " { todo!() }".to_string()), | |
483 | [binding] => { | |
484 | (end_span, format!(" {{ {} }} else {{ todo!() }}", binding)) | |
485 | } | |
486 | bindings => ( | |
487 | end_span, | |
488 | format!( | |
489 | " {{ ({}) }} else {{ todo!() }}", | |
490 | bindings | |
491 | .iter() | |
492 | .map(|ident| ident.to_string()) | |
493 | .collect::<Vec<_>>() | |
494 | .join(", ") | |
495 | ), | |
496 | ), | |
497 | }, | |
498 | ], | |
f9f354fc | 499 | Applicability::HasPlaceholders, |
e74abb32 | 500 | ); |
2b03887a | 501 | if !bindings.is_empty() { |
5e7ed085 FG |
502 | err.span_suggestion_verbose( |
503 | semi_span.shrink_to_lo(), | |
504 | &format!( | |
f2b60f7d FG |
505 | "alternatively, you might want to use \ |
506 | let else to handle the variant{} that {} matched", | |
5e7ed085 FG |
507 | pluralize!(witnesses.len()), |
508 | match witnesses.len() { | |
509 | 1 => "isn't", | |
510 | _ => "aren't", | |
511 | }, | |
512 | ), | |
487cf647 | 513 | " else { todo!() }", |
5e7ed085 FG |
514 | Applicability::HasPlaceholders, |
515 | ); | |
516 | } | |
e74abb32 | 517 | } |
f9f354fc XL |
518 | err.note( |
519 | "for more information, visit \ | |
520 | https://doc.rust-lang.org/book/ch18-02-refutability.html", | |
521 | ); | |
522 | } | |
e74abb32 | 523 | |
f9f354fc XL |
524 | adt_defined_here(&cx, &mut err, pattern_ty, &witnesses); |
525 | err.note(&format!("the matched value is of type `{}`", pattern_ty)); | |
526 | err.emit(); | |
1a4d82fc JJ |
527 | } |
528 | } | |
529 | ||
e74abb32 XL |
530 | /// A path pattern was interpreted as a constant, not a new variable. |
531 | /// This caused an irrefutable match failure in e.g. `let`. | |
5e7ed085 | 532 | fn const_not_var(err: &mut Diagnostic, tcx: TyCtxt<'_>, pat: &Pat<'_>, path: &hir::Path<'_>) { |
e74abb32 XL |
533 | let descr = path.res.descr(); |
534 | err.span_label( | |
535 | pat.span, | |
536 | format!("interpreted as {} {} pattern, not a new variable", path.res.article(), descr,), | |
537 | ); | |
538 | ||
539 | err.span_suggestion( | |
540 | pat.span, | |
541 | "introduce a variable instead", | |
542 | format!("{}_var", path.segments[0].ident).to_lowercase(), | |
543 | // Cannot use `MachineApplicable` as it's not really *always* correct | |
544 | // because there may be such an identifier in scope or the user maybe | |
545 | // really wanted to match against the constant. This is quite unlikely however. | |
546 | Applicability::MaybeIncorrect, | |
547 | ); | |
548 | ||
549 | if let Some(span) = tcx.hir().res_span(path.res) { | |
550 | err.span_label(span, format!("{} defined here", descr)); | |
551 | } | |
552 | } | |
553 | ||
c295e0f8 XL |
554 | fn check_for_bindings_named_same_as_variants( |
555 | cx: &MatchVisitor<'_, '_, '_>, | |
556 | pat: &Pat<'_>, | |
557 | rf: RefutableFlag, | |
558 | ) { | |
dfeec247 | 559 | pat.walk_always(|p| { |
5e7ed085 FG |
560 | if let hir::PatKind::Binding(_, _, ident, None) = p.kind |
561 | && let Some(ty::BindByValue(hir::Mutability::Not)) = | |
3dfed10e | 562 | cx.typeck_results.extract_binding_mode(cx.tcx.sess, p.hir_id, p.span) |
5e7ed085 FG |
563 | && let pat_ty = cx.typeck_results.pat_ty(p).peel_refs() |
564 | && let ty::Adt(edef, _) = pat_ty.kind() | |
565 | && edef.is_enum() | |
566 | && edef.variants().iter().any(|variant| { | |
487cf647 | 567 | variant.ident(cx.tcx) == ident && variant.ctor_kind() == Some(CtorKind::Const) |
5e7ed085 FG |
568 | }) |
569 | { | |
570 | let variant_count = edef.variants().len(); | |
571 | cx.tcx.struct_span_lint_hir( | |
572 | BINDINGS_WITH_VARIANT_NAME, | |
573 | p.hir_id, | |
574 | p.span, | |
2b03887a FG |
575 | DelayDm(|| format!( |
576 | "pattern binding `{}` is named the same as one \ | |
577 | of the variants of the type `{}`", | |
578 | ident, cx.tcx.def_path_str(edef.did()) | |
579 | )), | |
5e7ed085 FG |
580 | |lint| { |
581 | let ty_path = cx.tcx.def_path_str(edef.did()); | |
2b03887a FG |
582 | lint.code(error_code!(E0170)); |
583 | ||
5e7ed085 FG |
584 | // If this is an irrefutable pattern, and there's > 1 variant, |
585 | // then we can't actually match on this. Applying the below | |
586 | // suggestion would produce code that breaks on `check_irrefutable`. | |
587 | if rf == Refutable || variant_count == 1 { | |
2b03887a | 588 | lint.span_suggestion( |
74b04a01 | 589 | p.span, |
5e7ed085 FG |
590 | "to match on the variant, qualify the path", |
591 | format!("{}::{}", ty_path, ident), | |
592 | Applicability::MachineApplicable, | |
593 | ); | |
8faf50e0 | 594 | } |
2b03887a FG |
595 | |
596 | lint | |
5e7ed085 FG |
597 | }, |
598 | ) | |
1a4d82fc | 599 | } |
1a4d82fc JJ |
600 | }); |
601 | } | |
602 | ||
c30ab7b3 | 603 | /// Checks for common cases of "catchall" patterns that may not be intended as such. |
c295e0f8 XL |
604 | fn pat_is_catchall(pat: &DeconstructedPat<'_, '_>) -> bool { |
605 | use Constructor::*; | |
606 | match pat.ctor() { | |
607 | Wildcard => true, | |
608 | Single => pat.iter_fields().all(|pat| pat_is_catchall(pat)), | |
e74abb32 | 609 | _ => false, |
c30ab7b3 | 610 | } |
1a4d82fc JJ |
611 | } |
612 | ||
dfeec247 | 613 | fn unreachable_pattern(tcx: TyCtxt<'_>, span: Span, id: HirId, catchall: Option<Span>) { |
2b03887a | 614 | tcx.struct_span_lint_hir(UNREACHABLE_PATTERNS, id, span, "unreachable pattern", |lint| { |
74b04a01 XL |
615 | if let Some(catchall) = catchall { |
616 | // We had a catchall pattern, hint at that. | |
2b03887a FG |
617 | lint.span_label(span, "unreachable pattern"); |
618 | lint.span_label(catchall, "matches any value"); | |
74b04a01 | 619 | } |
2b03887a | 620 | lint |
74b04a01 | 621 | }); |
dfeec247 XL |
622 | } |
623 | ||
94222f64 | 624 | fn irrefutable_let_pattern(tcx: TyCtxt<'_>, id: HirId, span: Span) { |
5e7ed085 FG |
625 | let source = let_source(tcx, id); |
626 | irrefutable_let_patterns(tcx, id, source, 1, span); | |
627 | } | |
628 | ||
629 | fn irrefutable_let_patterns( | |
630 | tcx: TyCtxt<'_>, | |
631 | id: HirId, | |
632 | source: LetSource, | |
633 | count: usize, | |
634 | span: Span, | |
635 | ) { | |
94222f64 XL |
636 | macro_rules! emit_diag { |
637 | ( | |
638 | $lint:expr, | |
639 | $source_name:expr, | |
640 | $note_sufix:expr, | |
641 | $help_sufix:expr | |
642 | ) => {{ | |
5e7ed085 FG |
643 | let s = pluralize!(count); |
644 | let these = pluralize!("this", count); | |
2b03887a FG |
645 | tcx.struct_span_lint_hir( |
646 | IRREFUTABLE_LET_PATTERNS, | |
647 | id, | |
648 | span, | |
649 | format!("irrefutable {} pattern{s}", $source_name), | |
650 | |lint| { | |
651 | lint.note(&format!( | |
652 | "{these} pattern{s} will always match, so the {}", | |
653 | $note_sufix | |
654 | )) | |
655 | .help(concat!("consider ", $help_sufix)) | |
656 | }, | |
657 | ) | |
94222f64 XL |
658 | }}; |
659 | } | |
660 | ||
2b03887a | 661 | match source { |
94222f64 XL |
662 | LetSource::GenericLet => { |
663 | emit_diag!(lint, "`let`", "`let` is useless", "removing `let`"); | |
664 | } | |
665 | LetSource::IfLet => { | |
666 | emit_diag!( | |
667 | lint, | |
668 | "`if let`", | |
669 | "`if let` is useless", | |
670 | "replacing the `if let` with a `let`" | |
671 | ); | |
6a06907d | 672 | } |
94222f64 XL |
673 | LetSource::IfLetGuard => { |
674 | emit_diag!( | |
675 | lint, | |
676 | "`if let` guard", | |
677 | "guard is useless", | |
678 | "removing the guard and adding a `let` inside the match arm" | |
679 | ); | |
6a06907d | 680 | } |
487cf647 | 681 | LetSource::LetElse => { |
94222f64 XL |
682 | emit_diag!( |
683 | lint, | |
684 | "`let...else`", | |
685 | "`else` clause is useless", | |
686 | "removing the `else` clause" | |
687 | ); | |
6a06907d | 688 | } |
94222f64 XL |
689 | LetSource::WhileLet => { |
690 | emit_diag!( | |
691 | lint, | |
692 | "`while let`", | |
693 | "loop will never exit", | |
694 | "instead using a `loop { ... }` with a `let` inside it" | |
695 | ); | |
6a06907d | 696 | } |
2b03887a | 697 | }; |
dfeec247 XL |
698 | } |
699 | ||
5e7ed085 | 700 | fn is_let_irrefutable<'p, 'tcx>( |
60c5eb7d | 701 | cx: &mut MatchCheckCtxt<'p, 'tcx>, |
fc512014 | 702 | pat_id: HirId, |
c295e0f8 | 703 | pat: &'p DeconstructedPat<'p, 'tcx>, |
5e7ed085 | 704 | ) -> bool { |
fc512014 | 705 | let arms = [MatchArm { pat, hir_id: pat_id, has_guard: false }]; |
c295e0f8 XL |
706 | let report = compute_match_usefulness(&cx, &arms, pat_id, pat.ty()); |
707 | ||
708 | // Report if the pattern is unreachable, which can only occur when the type is uninhabited. | |
709 | // This also reports unreachable sub-patterns though, so we can't just replace it with an | |
710 | // `is_uninhabited` check. | |
711 | report_arm_reachability(&cx, &report); | |
fc512014 | 712 | |
5e7ed085 FG |
713 | // If the list of witnesses is empty, the match is exhaustive, |
714 | // i.e. the `if let` pattern is irrefutable. | |
715 | report.non_exhaustiveness_witnesses.is_empty() | |
fc512014 XL |
716 | } |
717 | ||
718 | /// Report unreachable arms, if any. | |
c295e0f8 | 719 | fn report_arm_reachability<'p, 'tcx>( |
fc512014 XL |
720 | cx: &MatchCheckCtxt<'p, 'tcx>, |
721 | report: &UsefulnessReport<'p, 'tcx>, | |
c295e0f8 | 722 | ) { |
6a06907d | 723 | use Reachability::*; |
c30ab7b3 | 724 | let mut catchall = None; |
c295e0f8 | 725 | for (arm, is_useful) in report.arm_usefulness.iter() { |
fc512014 | 726 | match is_useful { |
c295e0f8 | 727 | Unreachable => unreachable_pattern(cx.tcx, arm.pat.span(), arm.hir_id, catchall), |
6a06907d | 728 | Reachable(unreachables) if unreachables.is_empty() => {} |
fc512014 | 729 | // The arm is reachable, but contains unreachable subpatterns (from or-patterns). |
6a06907d XL |
730 | Reachable(unreachables) => { |
731 | let mut unreachables = unreachables.clone(); | |
29967ef6 XL |
732 | // Emit lints in the order in which they occur in the file. |
733 | unreachables.sort_unstable(); | |
734 | for span in unreachables { | |
fc512014 | 735 | unreachable_pattern(cx.tcx, span, arm.hir_id, None); |
c30ab7b3 | 736 | } |
223e47cc | 737 | } |
60c5eb7d | 738 | } |
fc512014 | 739 | if !arm.has_guard && catchall.is_none() && pat_is_catchall(arm.pat) { |
c295e0f8 | 740 | catchall = Some(arm.pat.span()); |
223e47cc LB |
741 | } |
742 | } | |
743 | } | |
744 | ||
fc512014 XL |
745 | /// Report that a match is not exhaustive. |
746 | fn non_exhaustive_match<'p, 'tcx>( | |
747 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
532ac7d7 XL |
748 | scrut_ty: Ty<'tcx>, |
749 | sp: Span, | |
c295e0f8 | 750 | witnesses: Vec<DeconstructedPat<'p, 'tcx>>, |
5e7ed085 FG |
751 | arms: &[hir::Arm<'tcx>], |
752 | expr_span: Span, | |
532ac7d7 | 753 | ) { |
5e7ed085 | 754 | let is_empty_match = arms.is_empty(); |
1b1a35ee | 755 | let non_empty_enum = match scrut_ty.kind() { |
5e7ed085 | 756 | ty::Adt(def, _) => def.is_enum() && !def.variants().is_empty(), |
60c5eb7d XL |
757 | _ => false, |
758 | }; | |
759 | // In the case of an empty match, replace the '`_` not covered' diagnostic with something more | |
760 | // informative. | |
761 | let mut err; | |
5e7ed085 FG |
762 | let pattern; |
763 | let mut patterns_len = 0; | |
60c5eb7d XL |
764 | if is_empty_match && !non_empty_enum { |
765 | err = create_e0004( | |
766 | cx.tcx.sess, | |
767 | sp, | |
768 | format!("non-exhaustive patterns: type `{}` is non-empty", scrut_ty), | |
769 | ); | |
5e7ed085 | 770 | pattern = "_".to_string(); |
60c5eb7d | 771 | } else { |
c295e0f8 | 772 | let joined_patterns = joined_uncovered_patterns(cx, &witnesses); |
60c5eb7d XL |
773 | err = create_e0004( |
774 | cx.tcx.sess, | |
775 | sp, | |
776 | format!("non-exhaustive patterns: {} not covered", joined_patterns), | |
777 | ); | |
778 | err.span_label(sp, pattern_not_covered_label(&witnesses, &joined_patterns)); | |
5e7ed085 FG |
779 | patterns_len = witnesses.len(); |
780 | pattern = if witnesses.len() < 4 { | |
781 | witnesses | |
782 | .iter() | |
783 | .map(|witness| witness.to_pat(cx).to_string()) | |
784 | .collect::<Vec<String>>() | |
785 | .join(" | ") | |
786 | } else { | |
787 | "_".to_string() | |
788 | }; | |
60c5eb7d XL |
789 | }; |
790 | ||
17df50a5 | 791 | let is_variant_list_non_exhaustive = match scrut_ty.kind() { |
5e7ed085 | 792 | ty::Adt(def, _) if def.is_variant_list_non_exhaustive() && !def.did().is_local() => true, |
17df50a5 XL |
793 | _ => false, |
794 | }; | |
795 | ||
e1599b0c | 796 | adt_defined_here(cx, &mut err, scrut_ty, &witnesses); |
17df50a5 XL |
797 | err.note(&format!( |
798 | "the matched value is of type `{}`{}", | |
799 | scrut_ty, | |
800 | if is_variant_list_non_exhaustive { ", which is marked as non-exhaustive" } else { "" } | |
801 | )); | |
f035d41b XL |
802 | if (scrut_ty == cx.tcx.types.usize || scrut_ty == cx.tcx.types.isize) |
803 | && !is_empty_match | |
804 | && witnesses.len() == 1 | |
c295e0f8 | 805 | && matches!(witnesses[0].ctor(), Constructor::NonExhaustive) |
f035d41b XL |
806 | { |
807 | err.note(&format!( | |
5e7ed085 FG |
808 | "`{}` does not have a fixed maximum value, so a wildcard `_` is necessary to match \ |
809 | exhaustively", | |
f035d41b XL |
810 | scrut_ty, |
811 | )); | |
fc512014 | 812 | if cx.tcx.sess.is_nightly_build() { |
f035d41b | 813 | err.help(&format!( |
5e7ed085 FG |
814 | "add `#![feature(precise_pointer_size_matching)]` to the crate attributes to \ |
815 | enable precise `{}` matching", | |
f035d41b XL |
816 | scrut_ty, |
817 | )); | |
818 | } | |
819 | } | |
5869c6ff | 820 | if let ty::Ref(_, sub_ty, _) = scrut_ty.kind() { |
487cf647 | 821 | if !sub_ty.is_inhabited_from(cx.tcx, cx.module, cx.param_env) { |
5869c6ff XL |
822 | err.note("references are always considered inhabited"); |
823 | } | |
824 | } | |
5e7ed085 FG |
825 | |
826 | let mut suggestion = None; | |
827 | let sm = cx.tcx.sess.source_map(); | |
828 | match arms { | |
923072b8 | 829 | [] if sp.eq_ctxt(expr_span) => { |
5e7ed085 FG |
830 | // Get the span for the empty match body `{}`. |
831 | let (indentation, more) = if let Some(snippet) = sm.indentation_before(sp) { | |
832 | (format!("\n{}", snippet), " ") | |
833 | } else { | |
834 | (" ".to_string(), "") | |
835 | }; | |
836 | suggestion = Some(( | |
837 | sp.shrink_to_hi().with_hi(expr_span.hi()), | |
838 | format!( | |
839 | " {{{indentation}{more}{pattern} => todo!(),{indentation}}}", | |
840 | indentation = indentation, | |
841 | more = more, | |
842 | pattern = pattern, | |
843 | ), | |
844 | )); | |
845 | } | |
846 | [only] => { | |
923072b8 FG |
847 | let (pre_indentation, is_multiline) = if let Some(snippet) = sm.indentation_before(only.span) |
848 | && let Ok(with_trailing) = sm.span_extend_while(only.span, |c| c.is_whitespace() || c == ',') | |
849 | && sm.is_multiline(with_trailing) | |
850 | { | |
851 | (format!("\n{}", snippet), true) | |
852 | } else { | |
853 | (" ".to_string(), false) | |
854 | }; | |
855 | let comma = if matches!(only.body.kind, hir::ExprKind::Block(..)) | |
856 | && only.span.eq_ctxt(only.body.span) | |
857 | && is_multiline | |
858 | { | |
859 | "" | |
5e7ed085 | 860 | } else { |
923072b8 | 861 | "," |
5e7ed085 | 862 | }; |
5e7ed085 FG |
863 | suggestion = Some(( |
864 | only.span.shrink_to_hi(), | |
865 | format!("{}{}{} => todo!()", comma, pre_indentation, pattern), | |
866 | )); | |
867 | } | |
923072b8 | 868 | [.., prev, last] if prev.span.eq_ctxt(last.span) => { |
f2b60f7d FG |
869 | let comma = if matches!(last.body.kind, hir::ExprKind::Block(..)) |
870 | && last.span.eq_ctxt(last.body.span) | |
871 | { | |
872 | "" | |
873 | } else { | |
874 | "," | |
875 | }; | |
876 | let spacing = if sm.is_multiline(prev.span.between(last.span)) { | |
877 | sm.indentation_before(last.span).map(|indent| format!("\n{indent}")) | |
878 | } else { | |
879 | Some(" ".to_string()) | |
880 | }; | |
881 | if let Some(spacing) = spacing { | |
5e7ed085 FG |
882 | suggestion = Some(( |
883 | last.span.shrink_to_hi(), | |
f2b60f7d | 884 | format!("{}{}{} => todo!()", comma, spacing, pattern), |
5e7ed085 FG |
885 | )); |
886 | } | |
887 | } | |
888 | _ => {} | |
889 | } | |
890 | ||
891 | let msg = format!( | |
892 | "ensure that all possible cases are being handled by adding a match arm with a wildcard \ | |
893 | pattern{}{}", | |
894 | if patterns_len > 1 && patterns_len < 4 && suggestion.is_some() { | |
895 | ", a match arm with multiple or-patterns" | |
896 | } else { | |
897 | // we are either not suggesting anything, or suggesting `_` | |
898 | "" | |
899 | }, | |
900 | match patterns_len { | |
901 | // non-exhaustive enum case | |
902 | 0 if suggestion.is_some() => " as shown", | |
903 | 0 => "", | |
904 | 1 if suggestion.is_some() => " or an explicit pattern as shown", | |
905 | 1 => " or an explicit pattern", | |
906 | _ if suggestion.is_some() => " as shown, or multiple match arms", | |
907 | _ => " or multiple match arms", | |
908 | }, | |
909 | ); | |
910 | if let Some((span, sugg)) = suggestion { | |
911 | err.span_suggestion_verbose(span, &msg, sugg, Applicability::HasPlaceholders); | |
912 | } else { | |
913 | err.help(&msg); | |
914 | } | |
60c5eb7d | 915 | err.emit(); |
e1599b0c | 916 | } |
7cac9316 | 917 | |
923072b8 | 918 | pub(crate) fn joined_uncovered_patterns<'p, 'tcx>( |
c295e0f8 XL |
919 | cx: &MatchCheckCtxt<'p, 'tcx>, |
920 | witnesses: &[DeconstructedPat<'p, 'tcx>], | |
921 | ) -> String { | |
e1599b0c | 922 | const LIMIT: usize = 3; |
c295e0f8 | 923 | let pat_to_str = |pat: &DeconstructedPat<'p, 'tcx>| pat.to_pat(cx).to_string(); |
e1599b0c XL |
924 | match witnesses { |
925 | [] => bug!(), | |
c295e0f8 | 926 | [witness] => format!("`{}`", witness.to_pat(cx)), |
e1599b0c | 927 | [head @ .., tail] if head.len() < LIMIT => { |
c295e0f8 XL |
928 | let head: Vec<_> = head.iter().map(pat_to_str).collect(); |
929 | format!("`{}` and `{}`", head.join("`, `"), tail.to_pat(cx)) | |
1a4d82fc | 930 | } |
e1599b0c XL |
931 | _ => { |
932 | let (head, tail) = witnesses.split_at(LIMIT); | |
c295e0f8 | 933 | let head: Vec<_> = head.iter().map(pat_to_str).collect(); |
e1599b0c | 934 | format!("`{}` and {} more", head.join("`, `"), tail.len()) |
9fa01778 | 935 | } |
1a4d82fc JJ |
936 | } |
937 | } | |
938 | ||
923072b8 | 939 | pub(crate) fn pattern_not_covered_label( |
c295e0f8 XL |
940 | witnesses: &[DeconstructedPat<'_, '_>], |
941 | joined_patterns: &str, | |
942 | ) -> String { | |
60c5eb7d | 943 | format!("pattern{} {} not covered", rustc_errors::pluralize!(witnesses.len()), joined_patterns) |
e1599b0c XL |
944 | } |
945 | ||
946 | /// Point at the definition of non-covered `enum` variants. | |
c295e0f8 XL |
947 | fn adt_defined_here<'p, 'tcx>( |
948 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
5e7ed085 | 949 | err: &mut Diagnostic, |
c295e0f8 XL |
950 | ty: Ty<'tcx>, |
951 | witnesses: &[DeconstructedPat<'p, 'tcx>], | |
e1599b0c XL |
952 | ) { |
953 | let ty = ty.peel_refs(); | |
1b1a35ee | 954 | if let ty::Adt(def, _) = ty.kind() { |
5e7ed085 FG |
955 | let mut spans = vec![]; |
956 | if witnesses.len() < 5 { | |
957 | for sp in maybe_point_at_variant(cx, *def, witnesses.iter()) { | |
958 | spans.push(sp); | |
e1599b0c XL |
959 | } |
960 | } | |
5e7ed085 FG |
961 | let def_span = cx |
962 | .tcx | |
963 | .hir() | |
964 | .get_if_local(def.did()) | |
965 | .and_then(|node| node.ident()) | |
966 | .map(|ident| ident.span) | |
967 | .unwrap_or_else(|| cx.tcx.def_span(def.did())); | |
968 | let mut span: MultiSpan = | |
969 | if spans.is_empty() { def_span.into() } else { spans.clone().into() }; | |
970 | ||
064997fb | 971 | span.push_span_label(def_span, ""); |
5e7ed085 | 972 | for pat in spans { |
064997fb | 973 | span.push_span_label(pat, "not covered"); |
5e7ed085 FG |
974 | } |
975 | err.span_note(span, &format!("`{}` defined here", ty)); | |
e1599b0c XL |
976 | } |
977 | } | |
978 | ||
c295e0f8 XL |
979 | fn maybe_point_at_variant<'a, 'p: 'a, 'tcx: 'a>( |
980 | cx: &MatchCheckCtxt<'p, 'tcx>, | |
5e7ed085 | 981 | def: AdtDef<'tcx>, |
c295e0f8 XL |
982 | patterns: impl Iterator<Item = &'a DeconstructedPat<'p, 'tcx>>, |
983 | ) -> Vec<Span> { | |
984 | use Constructor::*; | |
532ac7d7 | 985 | let mut covered = vec![]; |
c295e0f8 XL |
986 | for pattern in patterns { |
987 | if let Variant(variant_index) = pattern.ctor() { | |
5e7ed085 FG |
988 | if let ty::Adt(this_def, _) = pattern.ty().kind() && this_def.did() != def.did() { |
989 | continue; | |
532ac7d7 | 990 | } |
5e7ed085 | 991 | let sp = def.variant(*variant_index).ident(cx.tcx).span; |
c295e0f8 XL |
992 | if covered.contains(&sp) { |
993 | // Don't point at variants that have already been covered due to other patterns to avoid | |
994 | // visual clutter. | |
995 | continue; | |
996 | } | |
997 | covered.push(sp); | |
532ac7d7 | 998 | } |
c295e0f8 | 999 | covered.extend(maybe_point_at_variant(cx, def, pattern.iter_fields())); |
532ac7d7 XL |
1000 | } |
1001 | covered | |
1002 | } | |
1003 | ||
74b04a01 | 1004 | /// Check if a by-value binding is by-value. That is, check if the binding's type is not `Copy`. |
2b03887a FG |
1005 | fn is_binding_by_move(cx: &MatchVisitor<'_, '_, '_>, hir_id: HirId) -> bool { |
1006 | !cx.typeck_results.node_type(hir_id).is_copy_modulo_regions(cx.tcx, cx.param_env) | |
74b04a01 XL |
1007 | } |
1008 | ||
74b04a01 | 1009 | /// Check that there are no borrow or move conflicts in `binding @ subpat` patterns. |
dfeec247 XL |
1010 | /// |
1011 | /// For example, this would reject: | |
1012 | /// - `ref x @ Some(ref mut y)`, | |
74b04a01 XL |
1013 | /// - `ref mut x @ Some(ref y)`, |
1014 | /// - `ref mut x @ Some(ref mut y)`, | |
1015 | /// - `ref mut? x @ Some(y)`, and | |
1016 | /// - `x @ Some(ref mut? y)`. | |
dfeec247 XL |
1017 | /// |
1018 | /// This analysis is *not* subsumed by NLL. | |
c295e0f8 | 1019 | fn check_borrow_conflicts_in_at_patterns(cx: &MatchVisitor<'_, '_, '_>, pat: &Pat<'_>) { |
74b04a01 XL |
1020 | // Extract `sub` in `binding @ sub`. |
1021 | let (name, sub) = match &pat.kind { | |
1022 | hir::PatKind::Binding(.., name, Some(sub)) => (*name, sub), | |
1023 | _ => return, | |
dfeec247 | 1024 | }; |
74b04a01 | 1025 | let binding_span = pat.span.with_hi(name.span.hi()); |
dfeec247 | 1026 | |
3dfed10e | 1027 | let typeck_results = cx.typeck_results; |
74b04a01 | 1028 | let sess = cx.tcx.sess; |
1a4d82fc | 1029 | |
74b04a01 | 1030 | // Get the binding move, extract the mutability if by-ref. |
3dfed10e | 1031 | let mut_outer = match typeck_results.extract_binding_mode(sess, pat.hir_id, pat.span) { |
2b03887a | 1032 | Some(ty::BindByValue(_)) if is_binding_by_move(cx, pat.hir_id) => { |
74b04a01 XL |
1033 | // We have `x @ pat` where `x` is by-move. Reject all borrows in `pat`. |
1034 | let mut conflicts_ref = Vec::new(); | |
1035 | sub.each_binding(|_, hir_id, span, _| { | |
3dfed10e | 1036 | match typeck_results.extract_binding_mode(sess, hir_id, span) { |
74b04a01 XL |
1037 | Some(ty::BindByValue(_)) | None => {} |
1038 | Some(ty::BindByReference(_)) => conflicts_ref.push(span), | |
dfeec247 | 1039 | } |
74b04a01 XL |
1040 | }); |
1041 | if !conflicts_ref.is_empty() { | |
1042 | let occurs_because = format!( | |
1043 | "move occurs because `{}` has type `{}` which does not implement the `Copy` trait", | |
1044 | name, | |
3dfed10e | 1045 | typeck_results.node_type(pat.hir_id), |
74b04a01 | 1046 | ); |
487cf647 FG |
1047 | let mut err = sess.struct_span_err(pat.span, "borrow of moved value"); |
1048 | err.span_label(binding_span, format!("value moved into `{}` here", name)) | |
74b04a01 | 1049 | .span_label(binding_span, occurs_because) |
487cf647 FG |
1050 | .span_labels(conflicts_ref, "value borrowed here after move"); |
1051 | if pat.span.contains(binding_span) { | |
1052 | err.span_suggestion_verbose( | |
1053 | binding_span.shrink_to_lo(), | |
1054 | "borrow this binding in the pattern to avoid moving the value", | |
1055 | "ref ".to_string(), | |
1056 | Applicability::MachineApplicable, | |
1057 | ); | |
1058 | } | |
1059 | err.emit(); | |
dfeec247 | 1060 | } |
74b04a01 XL |
1061 | return; |
1062 | } | |
1063 | Some(ty::BindByValue(_)) | None => return, | |
1064 | Some(ty::BindByReference(m)) => m, | |
1065 | }; | |
dfeec247 | 1066 | |
74b04a01 XL |
1067 | // We now have `ref $mut_outer binding @ sub` (semantically). |
1068 | // Recurse into each binding in `sub` and find mutability or move conflicts. | |
1069 | let mut conflicts_move = Vec::new(); | |
1070 | let mut conflicts_mut_mut = Vec::new(); | |
1071 | let mut conflicts_mut_ref = Vec::new(); | |
1072 | sub.each_binding(|_, hir_id, span, name| { | |
3dfed10e | 1073 | match typeck_results.extract_binding_mode(sess, hir_id, span) { |
74b04a01 XL |
1074 | Some(ty::BindByReference(mut_inner)) => match (mut_outer, mut_inner) { |
1075 | (Mutability::Not, Mutability::Not) => {} // Both sides are `ref`. | |
1076 | (Mutability::Mut, Mutability::Mut) => conflicts_mut_mut.push((span, name)), // 2x `ref mut`. | |
1077 | _ => conflicts_mut_ref.push((span, name)), // `ref` + `ref mut` in either direction. | |
1078 | }, | |
2b03887a | 1079 | Some(ty::BindByValue(_)) if is_binding_by_move(cx, hir_id) => { |
74b04a01 | 1080 | conflicts_move.push((span, name)) // `ref mut?` + by-move conflict. |
dfeec247 | 1081 | } |
74b04a01 | 1082 | Some(ty::BindByValue(_)) | None => {} // `ref mut?` + by-copy is fine. |
dfeec247 XL |
1083 | } |
1084 | }); | |
74b04a01 XL |
1085 | |
1086 | // Report errors if any. | |
1087 | if !conflicts_mut_mut.is_empty() { | |
1088 | // Report mutability conflicts for e.g. `ref mut x @ Some(ref mut y)`. | |
1089 | let mut err = sess | |
1090 | .struct_span_err(pat.span, "cannot borrow value as mutable more than once at a time"); | |
1091 | err.span_label(binding_span, format!("first mutable borrow, by `{}`, occurs here", name)); | |
1092 | for (span, name) in conflicts_mut_mut { | |
1093 | err.span_label(span, format!("another mutable borrow, by `{}`, occurs here", name)); | |
1094 | } | |
1095 | for (span, name) in conflicts_mut_ref { | |
1096 | err.span_label(span, format!("also borrowed as immutable, by `{}`, here", name)); | |
1097 | } | |
1098 | for (span, name) in conflicts_move { | |
1099 | err.span_label(span, format!("also moved into `{}` here", name)); | |
1100 | } | |
1101 | err.emit(); | |
1102 | } else if !conflicts_mut_ref.is_empty() { | |
1103 | // Report mutability conflicts for e.g. `ref x @ Some(ref mut y)` or the converse. | |
1104 | let (primary, also) = match mut_outer { | |
1105 | Mutability::Mut => ("mutable", "immutable"), | |
1106 | Mutability::Not => ("immutable", "mutable"), | |
1107 | }; | |
1108 | let msg = | |
1109 | format!("cannot borrow value as {} because it is also borrowed as {}", also, primary); | |
1110 | let mut err = sess.struct_span_err(pat.span, &msg); | |
1111 | err.span_label(binding_span, format!("{} borrow, by `{}`, occurs here", primary, name)); | |
1112 | for (span, name) in conflicts_mut_ref { | |
1113 | err.span_label(span, format!("{} borrow, by `{}`, occurs here", also, name)); | |
1114 | } | |
1115 | for (span, name) in conflicts_move { | |
1116 | err.span_label(span, format!("also moved into `{}` here", name)); | |
1117 | } | |
1118 | err.emit(); | |
1119 | } else if !conflicts_move.is_empty() { | |
1120 | // Report by-ref and by-move conflicts, e.g. `ref x @ y`. | |
1121 | let mut err = | |
1122 | sess.struct_span_err(pat.span, "cannot move out of value because it is borrowed"); | |
1123 | err.span_label(binding_span, format!("value borrowed, by `{}`, here", name)); | |
1124 | for (span, name) in conflicts_move { | |
1125 | err.span_label(span, format!("value moved into `{}` here", name)); | |
1126 | } | |
1127 | err.emit(); | |
1128 | } | |
1a4d82fc JJ |
1129 | } |
1130 | ||
94222f64 XL |
1131 | #[derive(Clone, Copy, Debug)] |
1132 | pub enum LetSource { | |
1133 | GenericLet, | |
1134 | IfLet, | |
1135 | IfLetGuard, | |
487cf647 | 1136 | LetElse, |
94222f64 XL |
1137 | WhileLet, |
1138 | } | |
dfeec247 | 1139 | |
94222f64 XL |
1140 | fn let_source(tcx: TyCtxt<'_>, pat_id: HirId) -> LetSource { |
1141 | let hir = tcx.hir(); | |
5099ac24 | 1142 | |
94222f64 | 1143 | let parent = hir.get_parent_node(pat_id); |
5e7ed085 FG |
1144 | let_source_parent(tcx, parent, Some(pat_id)) |
1145 | } | |
1146 | ||
1147 | fn let_source_parent(tcx: TyCtxt<'_>, parent: HirId, pat_id: Option<HirId>) -> LetSource { | |
1148 | let hir = tcx.hir(); | |
1149 | ||
5099ac24 FG |
1150 | let parent_node = hir.get(parent); |
1151 | ||
1152 | match parent_node { | |
94222f64 | 1153 | hir::Node::Arm(hir::Arm { |
923072b8 | 1154 | guard: Some(hir::Guard::IfLet(&hir::Let { pat: hir::Pat { hir_id, .. }, .. })), |
94222f64 | 1155 | .. |
923072b8 | 1156 | }) if Some(*hir_id) == pat_id => { |
94222f64 XL |
1157 | return LetSource::IfLetGuard; |
1158 | } | |
94222f64 XL |
1159 | _ => {} |
1160 | } | |
5099ac24 | 1161 | |
94222f64 XL |
1162 | let parent_parent = hir.get_parent_node(parent); |
1163 | let parent_parent_node = hir.get(parent_parent); | |
2b03887a | 1164 | match parent_parent_node { |
487cf647 FG |
1165 | hir::Node::Stmt(hir::Stmt { kind: hir::StmtKind::Local(_), .. }) => { |
1166 | return LetSource::LetElse; | |
2b03887a FG |
1167 | } |
1168 | hir::Node::Arm(hir::Arm { guard: Some(hir::Guard::If(_)), .. }) => { | |
1169 | return LetSource::IfLetGuard; | |
1170 | } | |
1171 | _ => {} | |
064997fb | 1172 | } |
3157f602 | 1173 | |
94222f64 XL |
1174 | let parent_parent_parent = hir.get_parent_node(parent_parent); |
1175 | let parent_parent_parent_parent = hir.get_parent_node(parent_parent_parent); | |
1176 | let parent_parent_parent_parent_node = hir.get(parent_parent_parent_parent); | |
dfeec247 | 1177 | |
94222f64 XL |
1178 | if let hir::Node::Expr(hir::Expr { |
1179 | kind: hir::ExprKind::Loop(_, _, hir::LoopSource::While, _), | |
1180 | .. | |
1181 | }) = parent_parent_parent_parent_node | |
1182 | { | |
5099ac24 | 1183 | return LetSource::WhileLet; |
223e47cc | 1184 | } |
5099ac24 FG |
1185 | |
1186 | if let hir::Node::Expr(hir::Expr { kind: hir::ExprKind::If(..), .. }) = parent_parent_node { | |
1187 | return LetSource::IfLet; | |
1188 | } | |
1189 | ||
1190 | LetSource::GenericLet | |
1191 | } |