]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/methods/from_iter_instead_of_collect.rs
Auto merge of #6957 - camsteffen:eq-ty-kind, r=flip1995
[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::ty::implements_trait;
3 use clippy_utils::{get_trait_def_id, match_qpath, paths, sugg};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir as hir;
7 use rustc_hir::ExprKind;
8 use rustc_lint::{LateContext, LintContext};
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_kind: &ExprKind<'_>) {
15     if_chain! {
16         if let hir::ExprKind::Path(path) = func_kind;
17         if match_qpath(path, &["from_iter"]);
18         let ty = cx.typeck_results().expr_ty(expr);
19         let arg_ty = cx.typeck_results().expr_ty(&args[0]);
20         if let Some(from_iter_id) = get_trait_def_id(cx, &paths::FROM_ITERATOR);
21         if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
22
23         if implements_trait(cx, ty, from_iter_id, &[]) && implements_trait(cx, arg_ty, iter_id, &[]);
24         then {
25             // `expr` implements `FromIterator` trait
26             let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_par();
27             let turbofish = extract_turbofish(cx, expr, ty);
28             let sugg = format!("{}.collect::<{}>()", iter_expr, turbofish);
29             span_lint_and_sugg(
30                 cx,
31                 FROM_ITER_INSTEAD_OF_COLLECT,
32                 expr.span,
33                 "usage of `FromIterator::from_iter`",
34                 "use `.collect()` instead of `::from_iter()`",
35                 sugg,
36                 Applicability::MaybeIncorrect,
37             );
38         }
39     }
40 }
41
42 fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'tcx>) -> String {
43     if_chain! {
44         let call_site = expr.span.source_callsite();
45         if let Ok(snippet) = cx.sess().source_map().span_to_snippet(call_site);
46         let snippet_split = snippet.split("::").collect::<Vec<_>>();
47         if let Some((_, elements)) = snippet_split.split_last();
48
49         then {
50             // is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`)
51             if let Some(type_specifier) = snippet_split.iter().find(|e| e.starts_with('<') && e.ends_with('>')) {
52                 // remove the type specifier from the path elements
53                 let without_ts = elements.iter().filter_map(|e| {
54                     if e == type_specifier { None } else { Some((*e).to_string()) }
55                 }).collect::<Vec<_>>();
56                 // join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`)
57                 format!("{}{}", without_ts.join("::"), type_specifier)
58             } else {
59                 // type is not explicitly specified so wildcards are needed
60                 // i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>`
61                 let ty_str = ty.to_string();
62                 let start = ty_str.find('<').unwrap_or(0);
63                 let end = ty_str.find('>').unwrap_or_else(|| ty_str.len());
64                 let nb_wildcard = ty_str[start..end].split(',').count();
65                 let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1));
66                 format!("{}<{}>", elements.join("::"), wildcards)
67             }
68         } else {
69             ty.to_string()
70         }
71     }
72 }