]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/suspicious_operation_groupings.rs
Remove clippy_utils::consts re-export
[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     /// **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     nursery,
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 ) {
198     if let Some(binop) = binops.get(i).copied() {
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,
228                         replace_left_sugg(cx, binop, &sugg, &mut applicability),
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,
250                         replace_right_sugg(cx, binop, &sugg, &mut applicability),
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> {
279     let left_ident = get_ident(binop.left, location)?;
280     let right_ident = get_ident(binop.right, location)?;
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
296             let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?;
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
304             let right_suggestion = suggestion_with_swapped_ident(cx, binop.right, location, left_ident, applicability)?;
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.
311             let left_suggestion = suggestion_with_swapped_ident(cx, binop.left, location, right_ident, applicability)?;
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 }