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