]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use clippy_utils::ast_utils::{eq_id, is_useless_with_eq_exprs, IdentIter}; |
2 | use clippy_utils::diagnostics::span_lint_and_sugg; | |
3 | use clippy_utils::source::snippet_with_applicability; | |
f20569fa XL |
4 | use core::ops::{Add, AddAssign}; |
5 | use if_chain::if_chain; | |
6 | use rustc_ast::ast::{BinOpKind, Expr, ExprKind, StmtKind}; | |
7 | use rustc_data_structures::fx::FxHashSet; | |
8 | use rustc_errors::Applicability; | |
9 | use rustc_lint::{EarlyContext, EarlyLintPass}; | |
10 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
11 | use rustc_span::source_map::Spanned; | |
12 | use rustc_span::symbol::Ident; | |
13 | use rustc_span::Span; | |
14 | ||
15 | declare_clippy_lint! { | |
16 | /// **What it does:** | |
17 | /// Checks for unlikely usages of binary operators that are almost | |
18 | /// certainly typos and/or copy/paste errors, given the other usages | |
19 | /// of binary operators nearby. | |
20 | /// **Why is this bad?** | |
21 | /// They are probably bugs and if they aren't then they look like bugs | |
22 | /// and you should add a comment explaining why you are doing such an | |
23 | /// odd set of operations. | |
24 | /// **Known problems:** | |
25 | /// There may be some false positives if you are trying to do something | |
26 | /// unusual that happens to look like a typo. | |
27 | /// | |
28 | /// **Example:** | |
29 | /// | |
30 | /// ```rust | |
31 | /// struct Vec3 { | |
32 | /// x: f64, | |
33 | /// y: f64, | |
34 | /// z: f64, | |
35 | /// } | |
36 | /// | |
37 | /// impl Eq for Vec3 {} | |
38 | /// | |
39 | /// impl PartialEq for Vec3 { | |
40 | /// fn eq(&self, other: &Self) -> bool { | |
41 | /// // This should trigger the lint because `self.x` is compared to `other.y` | |
42 | /// self.x == other.y && self.y == other.y && self.z == other.z | |
43 | /// } | |
44 | /// } | |
45 | /// ``` | |
46 | /// Use instead: | |
47 | /// ```rust | |
48 | /// # struct Vec3 { | |
49 | /// # x: f64, | |
50 | /// # y: f64, | |
51 | /// # z: f64, | |
52 | /// # } | |
53 | /// // same as above except: | |
54 | /// impl PartialEq for Vec3 { | |
55 | /// fn eq(&self, other: &Self) -> bool { | |
56 | /// // Note we now compare other.x to self.x | |
57 | /// self.x == other.x && self.y == other.y && self.z == other.z | |
58 | /// } | |
59 | /// } | |
60 | /// ``` | |
61 | pub SUSPICIOUS_OPERATION_GROUPINGS, | |
62 | style, | |
63 | "groupings of binary operations that look suspiciously like typos" | |
64 | } | |
65 | ||
66 | declare_lint_pass!(SuspiciousOperationGroupings => [SUSPICIOUS_OPERATION_GROUPINGS]); | |
67 | ||
68 | impl EarlyLintPass for SuspiciousOperationGroupings { | |
69 | fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { | |
70 | if expr.span.from_expansion() { | |
71 | return; | |
72 | } | |
73 | ||
74 | if let Some(binops) = extract_related_binops(&expr.kind) { | |
75 | check_binops(cx, &binops.iter().collect::<Vec<_>>()); | |
76 | ||
77 | let mut op_types = Vec::with_capacity(binops.len()); | |
78 | // We could use a hashmap, etc. to avoid being O(n*m) here, but | |
79 | // we want the lints to be emitted in a consistent order. Besides, | |
80 | // m, (the number of distinct `BinOpKind`s in `binops`) | |
81 | // will often be small, and does have an upper limit. | |
82 | binops.iter().map(|b| b.op).for_each(|op| { | |
83 | if !op_types.contains(&op) { | |
84 | op_types.push(op); | |
85 | } | |
86 | }); | |
87 | ||
88 | for op_type in op_types { | |
89 | let ops: Vec<_> = binops.iter().filter(|b| b.op == op_type).collect(); | |
90 | ||
91 | check_binops(cx, &ops); | |
92 | } | |
93 | } | |
94 | } | |
95 | } | |
96 | ||
97 | fn check_binops(cx: &EarlyContext<'_>, binops: &[&BinaryOp<'_>]) { | |
98 | let binop_count = binops.len(); | |
99 | if binop_count < 2 { | |
100 | // Single binary operation expressions would likely be false | |
101 | // positives. | |
102 | return; | |
103 | } | |
104 | ||
105 | let mut one_ident_difference_count = 0; | |
106 | let mut no_difference_info = None; | |
107 | let mut double_difference_info = None; | |
108 | let mut expected_ident_loc = None; | |
109 | ||
110 | let mut paired_identifiers = FxHashSet::default(); | |
111 | ||
112 | for (i, BinaryOp { left, right, op, .. }) in binops.iter().enumerate() { | |
113 | match ident_difference_expr(left, right) { | |
114 | IdentDifference::NoDifference => { | |
115 | if is_useless_with_eq_exprs(*op) { | |
116 | // The `eq_op` lint should catch this in this case. | |
117 | return; | |
118 | } | |
119 | ||
120 | no_difference_info = Some(i); | |
121 | }, | |
122 | IdentDifference::Single(ident_loc) => { | |
123 | one_ident_difference_count += 1; | |
124 | if let Some(previous_expected) = expected_ident_loc { | |
125 | if previous_expected != ident_loc { | |
126 | // This expression doesn't match the form we're | |
127 | // looking for. | |
128 | return; | |
129 | } | |
130 | } else { | |
131 | expected_ident_loc = Some(ident_loc); | |
132 | } | |
133 | ||
134 | // If there was only a single difference, all other idents | |
135 | // must have been the same, and thus were paired. | |
136 | for id in skip_index(IdentIter::from(*left), ident_loc.index) { | |
137 | paired_identifiers.insert(id); | |
138 | } | |
139 | }, | |
140 | IdentDifference::Double(ident_loc1, ident_loc2) => { | |
141 | double_difference_info = Some((i, ident_loc1, ident_loc2)); | |
142 | }, | |
143 | IdentDifference::Multiple | IdentDifference::NonIdent => { | |
144 | // It's too hard to know whether this is a bug or not. | |
145 | return; | |
146 | }, | |
147 | } | |
148 | } | |
149 | ||
150 | let mut applicability = Applicability::MachineApplicable; | |
151 | ||
152 | if let Some(expected_loc) = expected_ident_loc { | |
153 | match (no_difference_info, double_difference_info) { | |
154 | (Some(i), None) => attempt_to_emit_no_difference_lint(cx, binops, i, expected_loc), | |
155 | (None, Some((double_difference_index, ident_loc1, ident_loc2))) => { | |
156 | if_chain! { | |
157 | if one_ident_difference_count == binop_count - 1; | |
158 | if let Some(binop) = binops.get(double_difference_index); | |
159 | then { | |
160 | let changed_loc = if ident_loc1 == expected_loc { | |
161 | ident_loc2 | |
162 | } else if ident_loc2 == expected_loc { | |
163 | ident_loc1 | |
164 | } else { | |
165 | // This expression doesn't match the form we're | |
166 | // looking for. | |
167 | return; | |
168 | }; | |
169 | ||
170 | if let Some(sugg) = ident_swap_sugg( | |
171 | cx, | |
172 | &paired_identifiers, | |
173 | binop, | |
174 | changed_loc, | |
175 | &mut applicability, | |
176 | ) { | |
177 | emit_suggestion( | |
178 | cx, | |
179 | binop.span, | |
180 | sugg, | |
181 | applicability, | |
182 | ); | |
183 | } | |
184 | } | |
185 | } | |
186 | }, | |
187 | _ => {}, | |
188 | } | |
189 | } | |
190 | } | |
191 | ||
192 | fn attempt_to_emit_no_difference_lint( | |
193 | cx: &EarlyContext<'_>, | |
194 | binops: &[&BinaryOp<'_>], | |
195 | i: usize, | |
196 | expected_loc: IdentLocation, | |
197 | ) { | |
cdc7bbd5 | 198 | if let Some(binop) = binops.get(i).copied() { |
f20569fa XL |
199 | // We need to try and figure out which identifier we should |
200 | // suggest using instead. Since there could be multiple | |
201 | // replacement candidates in a given expression, and we're | |
202 | // just taking the first one, we may get some bad lint | |
203 | // messages. | |
204 | let mut applicability = Applicability::MaybeIncorrect; | |
205 | ||
206 | // We assume that the correct ident is one used elsewhere in | |
207 | // the other binops, in a place that there was a single | |
208 | // difference between idents before. | |
209 | let old_left_ident = get_ident(binop.left, expected_loc); | |
210 | let old_right_ident = get_ident(binop.right, expected_loc); | |
211 | ||
212 | for b in skip_index(binops.iter(), i) { | |
213 | if_chain! { | |
214 | if let (Some(old_ident), Some(new_ident)) = | |
215 | (old_left_ident, get_ident(b.left, expected_loc)); | |
216 | if old_ident != new_ident; | |
217 | if let Some(sugg) = suggestion_with_swapped_ident( | |
218 | cx, | |
219 | binop.left, | |
220 | expected_loc, | |
221 | new_ident, | |
222 | &mut applicability, | |
223 | ); | |
224 | then { | |
225 | emit_suggestion( | |
226 | cx, | |
227 | binop.span, | |
cdc7bbd5 | 228 | replace_left_sugg(cx, binop, &sugg, &mut applicability), |
f20569fa XL |
229 | applicability, |
230 | ); | |
231 | return; | |
232 | } | |
233 | } | |
234 | ||
235 | if_chain! { | |
236 | if let (Some(old_ident), Some(new_ident)) = | |
237 | (old_right_ident, get_ident(b.right, expected_loc)); | |
238 | if old_ident != new_ident; | |
239 | if let Some(sugg) = suggestion_with_swapped_ident( | |
240 | cx, | |
241 | binop.right, | |
242 | expected_loc, | |
243 | new_ident, | |
244 | &mut applicability, | |
245 | ); | |
246 | then { | |
247 | emit_suggestion( | |
248 | cx, | |
249 | binop.span, | |
cdc7bbd5 | 250 | replace_right_sugg(cx, binop, &sugg, &mut applicability), |
f20569fa XL |
251 | applicability, |
252 | ); | |
253 | return; | |
254 | } | |
255 | } | |
256 | } | |
257 | } | |
258 | } | |
259 | ||
260 | fn emit_suggestion(cx: &EarlyContext<'_>, span: Span, sugg: String, applicability: Applicability) { | |
261 | span_lint_and_sugg( | |
262 | cx, | |
263 | SUSPICIOUS_OPERATION_GROUPINGS, | |
264 | span, | |
265 | "this sequence of operators looks suspiciously like a bug", | |
266 | "did you mean", | |
267 | sugg, | |
268 | applicability, | |
269 | ) | |
270 | } | |
271 | ||
272 | fn ident_swap_sugg( | |
273 | cx: &EarlyContext<'_>, | |
274 | paired_identifiers: &FxHashSet<Ident>, | |
275 | binop: &BinaryOp<'_>, | |
276 | location: IdentLocation, | |
277 | applicability: &mut Applicability, | |
278 | ) -> Option<String> { | |
cdc7bbd5 XL |
279 | let left_ident = get_ident(binop.left, location)?; |
280 | let right_ident = get_ident(binop.right, location)?; | |
f20569fa XL |
281 | |
282 | let sugg = match ( | |
283 | paired_identifiers.contains(&left_ident), | |
284 | paired_identifiers.contains(&right_ident), | |
285 | ) { | |
286 | (true, true) | (false, false) => { | |
287 | // We don't have a good guess of what ident should be | |
288 | // used instead, in these cases. | |
289 | *applicability = Applicability::MaybeIncorrect; | |
290 | ||
291 | // We arbitraily choose one side to suggest changing, | |
292 | // since we don't have a better guess. If the user | |
293 | // ends up duplicating a clause, the `logic_bug` lint | |
294 | // should catch it. | |
295 | ||
cdc7bbd5 | 296 | let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?; |
f20569fa XL |
297 | |
298 | replace_right_sugg(cx, binop, &right_suggestion, applicability) | |
299 | }, | |
300 | (false, true) => { | |
301 | // We haven't seen a pair involving the left one, so | |
302 | // it's probably what is wanted. | |
303 | ||
cdc7bbd5 | 304 | let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?; |
f20569fa XL |
305 | |
306 | replace_right_sugg(cx, binop, &right_suggestion, applicability) | |
307 | }, | |
308 | (true, false) => { | |
309 | // We haven't seen a pair involving the right one, so | |
310 | // it's probably what is wanted. | |
cdc7bbd5 | 311 | let left_suggestion = suggestion_with_swapped_ident(cx, binop.left, location, right_ident, applicability)?; |
f20569fa XL |
312 | |
313 | replace_left_sugg(cx, binop, &left_suggestion, applicability) | |
314 | }, | |
315 | }; | |
316 | ||
317 | Some(sugg) | |
318 | } | |
319 | ||
320 | fn replace_left_sugg( | |
321 | cx: &EarlyContext<'_>, | |
322 | binop: &BinaryOp<'_>, | |
323 | left_suggestion: &str, | |
324 | applicability: &mut Applicability, | |
325 | ) -> String { | |
326 | format!( | |
327 | "{} {} {}", | |
328 | left_suggestion, | |
329 | binop.op.to_string(), | |
330 | snippet_with_applicability(cx, binop.right.span, "..", applicability), | |
331 | ) | |
332 | } | |
333 | ||
334 | fn replace_right_sugg( | |
335 | cx: &EarlyContext<'_>, | |
336 | binop: &BinaryOp<'_>, | |
337 | right_suggestion: &str, | |
338 | applicability: &mut Applicability, | |
339 | ) -> String { | |
340 | format!( | |
341 | "{} {} {}", | |
342 | snippet_with_applicability(cx, binop.left.span, "..", applicability), | |
343 | binop.op.to_string(), | |
344 | right_suggestion, | |
345 | ) | |
346 | } | |
347 | ||
348 | #[derive(Clone, Debug)] | |
349 | struct BinaryOp<'exprs> { | |
350 | op: BinOpKind, | |
351 | span: Span, | |
352 | left: &'exprs Expr, | |
353 | right: &'exprs Expr, | |
354 | } | |
355 | ||
356 | impl BinaryOp<'exprs> { | |
357 | fn new(op: BinOpKind, span: Span, (left, right): (&'exprs Expr, &'exprs Expr)) -> Self { | |
358 | Self { op, span, left, right } | |
359 | } | |
360 | } | |
361 | ||
362 | fn strip_non_ident_wrappers(expr: &Expr) -> &Expr { | |
363 | let mut output = expr; | |
364 | loop { | |
365 | output = match &output.kind { | |
366 | ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner, | |
367 | _ => { | |
368 | return output; | |
369 | }, | |
370 | }; | |
371 | } | |
372 | } | |
373 | ||
374 | fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { | |
375 | append_opt_vecs(chained_binops(kind), if_statment_binops(kind)) | |
376 | } | |
377 | ||
378 | fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { | |
379 | match kind { | |
380 | ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind), | |
381 | ExprKind::Paren(ref e) => if_statment_binops(&e.kind), | |
382 | ExprKind::Block(ref block, _) => { | |
383 | let mut output = None; | |
384 | for stmt in &block.stmts { | |
385 | match stmt.kind { | |
386 | StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => { | |
387 | output = append_opt_vecs(output, if_statment_binops(&e.kind)); | |
388 | }, | |
389 | _ => {}, | |
390 | } | |
391 | } | |
392 | output | |
393 | }, | |
394 | _ => None, | |
395 | } | |
396 | } | |
397 | ||
398 | fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> { | |
399 | match (target_opt, source_opt) { | |
400 | (Some(mut target), Some(mut source)) => { | |
401 | target.reserve(source.len()); | |
402 | for op in source.drain(..) { | |
403 | target.push(op); | |
404 | } | |
405 | Some(target) | |
406 | }, | |
407 | (Some(v), None) | (None, Some(v)) => Some(v), | |
408 | (None, None) => None, | |
409 | } | |
410 | } | |
411 | ||
412 | fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> { | |
413 | match kind { | |
414 | ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer), | |
415 | ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind), | |
416 | _ => None, | |
417 | } | |
418 | } | |
419 | ||
420 | fn chained_binops_helper(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option<Vec<BinaryOp<'expr>>> { | |
421 | match (&left_outer.kind, &right_outer.kind) { | |
422 | ( | |
423 | ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), | |
424 | ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e), | |
425 | ) => chained_binops_helper(left_e, right_e), | |
426 | (ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer), | |
427 | (_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => { | |
428 | chained_binops_helper(left_outer, right_e) | |
429 | }, | |
430 | ( | |
431 | ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right), | |
432 | ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right), | |
433 | ) => match ( | |
434 | chained_binops_helper(left_left, left_right), | |
435 | chained_binops_helper(right_left, right_right), | |
436 | ) { | |
437 | (Some(mut left_ops), Some(mut right_ops)) => { | |
438 | left_ops.reserve(right_ops.len()); | |
439 | for op in right_ops.drain(..) { | |
440 | left_ops.push(op); | |
441 | } | |
442 | Some(left_ops) | |
443 | }, | |
444 | (Some(mut left_ops), _) => { | |
445 | left_ops.push(BinaryOp::new(*right_op, right_outer.span, (right_left, right_right))); | |
446 | Some(left_ops) | |
447 | }, | |
448 | (_, Some(mut right_ops)) => { | |
449 | right_ops.insert(0, BinaryOp::new(*left_op, left_outer.span, (left_left, left_right))); | |
450 | Some(right_ops) | |
451 | }, | |
452 | (None, None) => Some(vec![ | |
453 | BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)), | |
454 | BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)), | |
455 | ]), | |
456 | }, | |
457 | _ => None, | |
458 | } | |
459 | } | |
460 | ||
461 | #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] | |
462 | struct IdentLocation { | |
463 | index: usize, | |
464 | } | |
465 | ||
466 | impl Add for IdentLocation { | |
467 | type Output = IdentLocation; | |
468 | ||
469 | fn add(self, other: Self) -> Self::Output { | |
470 | Self { | |
471 | index: self.index + other.index, | |
472 | } | |
473 | } | |
474 | } | |
475 | ||
476 | impl AddAssign for IdentLocation { | |
477 | fn add_assign(&mut self, other: Self) { | |
478 | *self = *self + other | |
479 | } | |
480 | } | |
481 | ||
482 | #[derive(Clone, Copy, Debug)] | |
483 | enum IdentDifference { | |
484 | NoDifference, | |
485 | Single(IdentLocation), | |
486 | Double(IdentLocation, IdentLocation), | |
487 | Multiple, | |
488 | NonIdent, | |
489 | } | |
490 | ||
491 | impl Add for IdentDifference { | |
492 | type Output = IdentDifference; | |
493 | ||
494 | fn add(self, other: Self) -> Self::Output { | |
495 | match (self, other) { | |
496 | (Self::NoDifference, output) | (output, Self::NoDifference) => output, | |
497 | (Self::Multiple, _) | |
498 | | (_, Self::Multiple) | |
499 | | (Self::Double(_, _), Self::Single(_)) | |
500 | | (Self::Single(_) | Self::Double(_, _), Self::Double(_, _)) => Self::Multiple, | |
501 | (Self::NonIdent, _) | (_, Self::NonIdent) => Self::NonIdent, | |
502 | (Self::Single(il1), Self::Single(il2)) => Self::Double(il1, il2), | |
503 | } | |
504 | } | |
505 | } | |
506 | ||
507 | impl AddAssign for IdentDifference { | |
508 | fn add_assign(&mut self, other: Self) { | |
509 | *self = *self + other | |
510 | } | |
511 | } | |
512 | ||
513 | impl IdentDifference { | |
514 | /// Returns true if learning about more differences will not change the value | |
515 | /// of this `IdentDifference`, and false otherwise. | |
516 | fn is_complete(&self) -> bool { | |
517 | match self { | |
518 | Self::NoDifference | Self::Single(_) | Self::Double(_, _) => false, | |
519 | Self::Multiple | Self::NonIdent => true, | |
520 | } | |
521 | } | |
522 | } | |
523 | ||
524 | fn ident_difference_expr(left: &Expr, right: &Expr) -> IdentDifference { | |
525 | ident_difference_expr_with_base_location(left, right, IdentLocation::default()).0 | |
526 | } | |
527 | ||
528 | fn ident_difference_expr_with_base_location( | |
529 | left: &Expr, | |
530 | right: &Expr, | |
531 | mut base: IdentLocation, | |
532 | ) -> (IdentDifference, IdentLocation) { | |
533 | // Ideally, this function should not use IdentIter because it should return | |
534 | // early if the expressions have any non-ident differences. We want that early | |
535 | // return because if without that restriction the lint would lead to false | |
536 | // positives. | |
537 | // | |
538 | // But, we cannot (easily?) use a `rustc_ast::visit::Visitor`, since we need | |
539 | // the two expressions to be walked in lockstep. And without a `Visitor`, we'd | |
540 | // have to do all the AST traversal ourselves, which is a lot of work, since to | |
541 | // do it properly we'd need to be able to handle more or less every possible | |
542 | // AST node since `Item`s can be written inside `Expr`s. | |
543 | // | |
544 | // In practice, it seems likely that expressions, above a certain size, that | |
545 | // happen to use the exact same idents in the exact same order, and which are | |
546 | // not structured the same, would be rare. Therefore it seems likely that if | |
547 | // we do only the first layer of matching ourselves and eventually fallback on | |
548 | // IdentIter, then the output of this function will be almost always be correct | |
549 | // in practice. | |
550 | // | |
551 | // If it turns out that problematic cases are more prelavent than we assume, | |
552 | // then we should be able to change this function to do the correct traversal, | |
553 | // without needing to change the rest of the code. | |
554 | ||
555 | #![allow(clippy::enum_glob_use)] | |
556 | use ExprKind::*; | |
557 | ||
558 | match ( | |
559 | &strip_non_ident_wrappers(left).kind, | |
560 | &strip_non_ident_wrappers(right).kind, | |
561 | ) { | |
562 | (Yield(_), Yield(_)) | |
563 | | (Try(_), Try(_)) | |
564 | | (Paren(_), Paren(_)) | |
565 | | (Repeat(_, _), Repeat(_, _)) | |
566 | | (Struct(_), Struct(_)) | |
567 | | (MacCall(_), MacCall(_)) | |
568 | | (LlvmInlineAsm(_), LlvmInlineAsm(_)) | |
569 | | (InlineAsm(_), InlineAsm(_)) | |
570 | | (Ret(_), Ret(_)) | |
571 | | (Continue(_), Continue(_)) | |
572 | | (Break(_, _), Break(_, _)) | |
573 | | (AddrOf(_, _, _), AddrOf(_, _, _)) | |
574 | | (Path(_, _), Path(_, _)) | |
575 | | (Range(_, _, _), Range(_, _, _)) | |
576 | | (Index(_, _), Index(_, _)) | |
577 | | (Field(_, _), Field(_, _)) | |
578 | | (AssignOp(_, _, _), AssignOp(_, _, _)) | |
579 | | (Assign(_, _, _), Assign(_, _, _)) | |
580 | | (TryBlock(_), TryBlock(_)) | |
581 | | (Await(_), Await(_)) | |
582 | | (Async(_, _, _), Async(_, _, _)) | |
583 | | (Block(_, _), Block(_, _)) | |
584 | | (Closure(_, _, _, _, _, _), Closure(_, _, _, _, _, _)) | |
585 | | (Match(_, _), Match(_, _)) | |
586 | | (Loop(_, _), Loop(_, _)) | |
587 | | (ForLoop(_, _, _, _), ForLoop(_, _, _, _)) | |
588 | | (While(_, _, _), While(_, _, _)) | |
589 | | (If(_, _, _), If(_, _, _)) | |
590 | | (Let(_, _), Let(_, _)) | |
591 | | (Type(_, _), Type(_, _)) | |
592 | | (Cast(_, _), Cast(_, _)) | |
593 | | (Lit(_), Lit(_)) | |
594 | | (Unary(_, _), Unary(_, _)) | |
595 | | (Binary(_, _, _), Binary(_, _, _)) | |
596 | | (Tup(_), Tup(_)) | |
597 | | (MethodCall(_, _, _), MethodCall(_, _, _)) | |
598 | | (Call(_, _), Call(_, _)) | |
599 | | (ConstBlock(_), ConstBlock(_)) | |
600 | | (Array(_), Array(_)) | |
601 | | (Box(_), Box(_)) => { | |
602 | // keep going | |
603 | }, | |
604 | _ => { | |
605 | return (IdentDifference::NonIdent, base); | |
606 | }, | |
607 | } | |
608 | ||
609 | let mut difference = IdentDifference::NoDifference; | |
610 | ||
611 | for (left_attr, right_attr) in left.attrs.iter().zip(right.attrs.iter()) { | |
612 | let (new_difference, new_base) = | |
613 | ident_difference_via_ident_iter_with_base_location(left_attr, right_attr, base); | |
614 | base = new_base; | |
615 | difference += new_difference; | |
616 | if difference.is_complete() { | |
617 | return (difference, base); | |
618 | } | |
619 | } | |
620 | ||
621 | let (new_difference, new_base) = ident_difference_via_ident_iter_with_base_location(left, right, base); | |
622 | base = new_base; | |
623 | difference += new_difference; | |
624 | ||
625 | (difference, base) | |
626 | } | |
627 | ||
628 | fn ident_difference_via_ident_iter_with_base_location<Iterable: Into<IdentIter>>( | |
629 | left: Iterable, | |
630 | right: Iterable, | |
631 | mut base: IdentLocation, | |
632 | ) -> (IdentDifference, IdentLocation) { | |
633 | // See the note in `ident_difference_expr_with_base_location` about `IdentIter` | |
634 | let mut difference = IdentDifference::NoDifference; | |
635 | ||
636 | let mut left_iterator = left.into(); | |
637 | let mut right_iterator = right.into(); | |
638 | ||
639 | loop { | |
640 | match (left_iterator.next(), right_iterator.next()) { | |
641 | (Some(left_ident), Some(right_ident)) => { | |
642 | if !eq_id(left_ident, right_ident) { | |
643 | difference += IdentDifference::Single(base); | |
644 | if difference.is_complete() { | |
645 | return (difference, base); | |
646 | } | |
647 | } | |
648 | }, | |
649 | (Some(_), None) | (None, Some(_)) => { | |
650 | return (IdentDifference::NonIdent, base); | |
651 | }, | |
652 | (None, None) => { | |
653 | return (difference, base); | |
654 | }, | |
655 | } | |
656 | base += IdentLocation { index: 1 }; | |
657 | } | |
658 | } | |
659 | ||
660 | fn get_ident(expr: &Expr, location: IdentLocation) -> Option<Ident> { | |
661 | IdentIter::from(expr).nth(location.index) | |
662 | } | |
663 | ||
664 | fn suggestion_with_swapped_ident( | |
665 | cx: &EarlyContext<'_>, | |
666 | expr: &Expr, | |
667 | location: IdentLocation, | |
668 | new_ident: Ident, | |
669 | applicability: &mut Applicability, | |
670 | ) -> Option<String> { | |
671 | get_ident(expr, location).and_then(|current_ident| { | |
672 | if eq_id(current_ident, new_ident) { | |
673 | // We never want to suggest a non-change | |
674 | return None; | |
675 | } | |
676 | ||
677 | Some(format!( | |
678 | "{}{}{}", | |
679 | snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability), | |
680 | new_ident.to_string(), | |
681 | snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability), | |
682 | )) | |
683 | }) | |
684 | } | |
685 | ||
686 | fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A> | |
687 | where | |
688 | Iter: Iterator<Item = A>, | |
689 | { | |
690 | iter.enumerate() | |
691 | .filter_map(move |(i, a)| if i == index { None } else { Some(a) }) | |
692 | } |