]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/from_iter_instead_of_collect.rs
99c03844f49275e02461ca8a02c0195c81e96772
[rust.git] / clippy_lints / src / methods / from_iter_instead_of_collect.rs
1 use clippy_utils::diagnostics::span_lint_and_sugg;
2 use clippy_utils::source::snippet_opt;
3 use clippy_utils::ty::implements_trait;
4 use clippy_utils::{is_expr_path_def_path, paths, sugg};
5 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir as hir;
8 use rustc_lint::LateContext;
9 use rustc_middle::ty::Ty;
10 use rustc_span::sym;
11
12 use super::FROM_ITER_INSTEAD_OF_COLLECT;
13
14 pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func: &hir::Expr<'_>) {
15     if_chain! {
16         if is_expr_path_def_path(cx, func, &paths::FROM_ITERATOR_METHOD);
17         let ty = cx.typeck_results().expr_ty(expr);
18         let arg_ty = cx.typeck_results().expr_ty(&args[0]);
19         if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
20
21         if implements_trait(cx, arg_ty, iter_id, &[]);
22         then {
23             // `expr` implements `FromIterator` trait
24             let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par();
25             let turbofish = extract_turbofish(cx, expr, ty);
26             let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish);
27             span_lint_and_sugg(
28                 cx,
29                 FROM_ITER_INSTEAD_OF_COLLECT,
30                 expr.span,
31                 "usage of `FromIterator::from_iter`",
32                 "use `.collect()` instead of `::from_iter()`",
33                 sugg,
34                 Applicability::MaybeIncorrect,
35             );
36         }
37     }
38 }
39
40 fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String {
41     fn strip_angle_brackets(s: &str) -> Option<&str> {
42         s.strip_prefix('<')?.strip_suffix('>')
43     }
44
45     let call_site = expr.span.source_callsite();
46     if_chain! {
47         if let Some(snippet) = snippet_opt(cx, call_site);
48         let snippet_split = snippet.split("::").collect::<Vec<_>>();
49         if let Some((_, elements)) = snippet_split.split_last();
50
51         then {
52             if_chain! {
53                 if let [type_specifier, _] = snippet_split.as_slice();
54                 if let Some(type_specifier) = strip_angle_brackets(type_specifier);
55                 if let Some((type_specifier, ..)) = type_specifier.split_once(" as ");
56                 then {
57                     type_specifier.to_string()
58                 } else {
59                     // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`)
60                     if let Some(type_specifier) = snippet_split.iter().find(|e| strip_angle_brackets(e).is_some()) {
61                         // remove the type specifier from the path elements
62                         let without_ts = elements.iter().filter_map(|e| {
63                             if e == type_specifier { None } else { Some((*e).to_string()) }
64                         }).collect::<Vec<_>>();
65                         // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`)
66                         format!("{}{}", without_ts.join("::"), type_specifier)
67                     } else {
68                         // type is not explicitly specified so wildcards are needed
69                         // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>`
70                         let ty_str = ty.to_string();
71                         let start = ty_str.find('<').unwrap_or(0);
72                         let end = ty_str.find('>').unwrap_or_else(|| ty_str.len());
73                         let nb_wildcard = ty_str[start..end].split(',').count();
74                         let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1));
75                         format!("{}<{}>", elements.join("::"), wildcards)
76                     }
77                 }
78             }
79         } else {
80             ty.to_string()
81         }
82     }
83 }