Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lint: from_iter_instead_of_collect #6101

Merged
merged 13 commits into from
Nov 3, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,7 @@ Released 2018-09-13
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
Expand Down
3 changes: 3 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&methods::FILTER_NEXT,
&methods::FIND_MAP,
&methods::FLAT_MAP_IDENTITY,
&methods::FROM_ITER_INSTEAD_OF_COLLECT,
&methods::GET_UNWRAP,
&methods::INEFFICIENT_TO_STRING,
&methods::INTO_ITER_ON_REF,
Expand Down Expand Up @@ -1422,6 +1423,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&methods::EXPECT_FUN_CALL),
LintId::of(&methods::FILTER_NEXT),
LintId::of(&methods::FLAT_MAP_IDENTITY),
LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT),
LintId::of(&methods::INTO_ITER_ON_REF),
LintId::of(&methods::ITERATOR_STEP_BY_ZERO),
LintId::of(&methods::ITER_CLONED_COLLECT),
Expand Down Expand Up @@ -1620,6 +1622,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&mem_replace::MEM_REPLACE_WITH_DEFAULT),
LintId::of(&methods::CHARS_LAST_CMP),
LintId::of(&methods::CHARS_NEXT_CMP),
LintId::of(&methods::FROM_ITER_INSTEAD_OF_COLLECT),
LintId::of(&methods::INTO_ITER_ON_REF),
LintId::of(&methods::ITER_CLONED_COLLECT),
LintId::of(&methods::ITER_NEXT_SLICE),
Expand Down
10 changes: 5 additions & 5 deletions clippy_lints/src/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, Symbol};
use std::iter::FromIterator;

declare_clippy_lint! {
/// **What it does:** Checks for lifetime annotations which can be removed by
Expand Down Expand Up @@ -214,14 +213,15 @@ fn could_use_elision<'tcx>(
}

if allowed_lts
.intersection(&FxHashSet::from_iter(
input_visitor
.intersection(
&input_visitor
.nested_elision_site_lts
.iter()
.chain(output_visitor.nested_elision_site_lts.iter())
.cloned()
.filter(|v| matches!(v, RefLt::Named(_))),
))
.filter(|v| matches!(v, RefLt::Named(_)))
.collect(),
)
.next()
.is_some()
{
Expand Down
63 changes: 63 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1369,6 +1369,39 @@ declare_clippy_lint! {
"using `.map(_).collect::<Result<(),_>()`, which can be replaced with `try_for_each`"
}

declare_clippy_lint! {
/// **What it does:** Checks for `from_iter()` function calls on types that implement the `FromIterator`
/// trait.
///
/// **Why is this bad?** It is recommended style to use collect. See
/// [FromIterator documentation](https://doc.rust-lang.org/std/iter/trait.FromIterator.html)
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// use std::iter::FromIterator;
///
/// let five_fives = std::iter::repeat(5).take(5);
///
/// let v = Vec::from_iter(five_fives);
///
/// assert_eq!(v, vec![5, 5, 5, 5, 5]);
/// ```
/// Use instead:
/// ```rust
/// let five_fives = std::iter::repeat(5).take(5);
///
/// let v: Vec<i32> = five_fives.collect();
///
/// assert_eq!(v, vec![5, 5, 5, 5, 5]);
/// ```
pub FROM_ITER_INSTEAD_OF_COLLECT,
style,
"use `.collect()` instead of `::from_iter()`"
}

declare_lint_pass!(Methods => [
UNWRAP_USED,
EXPECT_USED,
Expand Down Expand Up @@ -1419,6 +1452,7 @@ declare_lint_pass!(Methods => [
OPTION_AS_REF_DEREF,
UNNECESSARY_LAZY_EVALUATIONS,
MAP_COLLECT_RESULT_UNIT,
FROM_ITER_INSTEAD_OF_COLLECT,
]);

impl<'tcx> LateLintPass<'tcx> for Methods {
Expand Down Expand Up @@ -1505,6 +1539,13 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
}

match expr.kind {
hir::ExprKind::Call(ref func, ref args) => {
if let hir::ExprKind::Path(path) = &func.kind {
if match_qpath(path, &["from_iter"]) {
lint_from_iter(cx, expr, args);
}
}
},
hir::ExprKind::MethodCall(ref method_call, ref method_span, ref args, _) => {
lint_or_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args);
lint_expect_fun_call(cx, expr, *method_span, &method_call.ident.as_str(), args);
Expand Down Expand Up @@ -3831,6 +3872,28 @@ fn lint_filetype_is_file(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir
span_lint_and_help(cx, FILETYPE_IS_FILE, span, &lint_msg, None, &help_msg);
}

fn lint_from_iter(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
let ty = cx.typeck_results().expr_ty(expr);
let arg_ty = cx.typeck_results().expr_ty(&args[0]);

let from_iter_id = get_trait_def_id(cx, &paths::FROM_ITERATOR).unwrap();
let iter_id = get_trait_def_id(cx, &paths::ITERATOR).unwrap();

if implements_trait(cx, ty, from_iter_id, &[]) && implements_trait(cx, arg_ty, iter_id, &[]) {
// `expr` implements `FromIterator` trait
let iter_expr = snippet(cx, args[0].span, "..");
span_lint_and_sugg(
cx,
FROM_ITER_INSTEAD_OF_COLLECT,
expr.span,
"usage of `FromIterator::from_iter`",
"use `.collect()` instead of `::from_iter()`",
flip1995 marked this conversation as resolved.
Show resolved Hide resolved
format!("{}.collect()", iter_expr),
Applicability::MaybeIncorrect,
);
}
}

fn fn_header_equals(expected: hir::FnHeader, actual: hir::FnHeader) -> bool {
expected.constness == actual.constness
&& expected.unsafety == actual.unsafety
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub const FN: [&str; 3] = ["core", "ops", "Fn"];
pub const FN_MUT: [&str; 3] = ["core", "ops", "FnMut"];
pub const FN_ONCE: [&str; 3] = ["core", "ops", "FnOnce"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "FromIterator"];
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
pub const HASH: [&str; 3] = ["core", "hash", "Hash"];
Expand Down
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,13 @@ vec![
deprecation: None,
module: "drop_forget_ref",
},
Lint {
name: "from_iter_instead_of_collect",
group: "style",
desc: "use `.collect()` instead of `::from_iter()`",
deprecation: None,
module: "methods",
},
Lint {
name: "future_not_send",
group: "nursery",
Expand Down
13 changes: 13 additions & 0 deletions tests/ui/from_iter_instead_of_collect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#![warn(clippy::from_iter_instead_of_collect)]

use std::collections::HashMap;
use std::iter::FromIterator;

fn main() {
let iter_expr = std::iter::repeat(5).take(5);
Vec::from_iter(iter_expr);

HashMap::<usize, &i8>::from_iter(vec![5, 5, 5, 5].iter().enumerate());

Vec::from_iter(vec![42u32]);
}
16 changes: 16 additions & 0 deletions tests/ui/from_iter_instead_of_collect.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
error: usage of `FromIterator::from_iter`
--> $DIR/from_iter_instead_of_collect.rs:8:5
|
LL | Vec::from_iter(iter_expr);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter_expr.collect()`
|
= note: `-D clippy::from-iter-instead-of-collect` implied by `-D warnings`

error: usage of `FromIterator::from_iter`
--> $DIR/from_iter_instead_of_collect.rs:10:5
|
LL | HashMap::<usize, &i8>::from_iter(vec![5, 5, 5, 5].iter().enumerate());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `vec![5, 5, 5, 5].iter().enumerate().collect()`

error: aborting due to 2 previous errors

2 changes: 1 addition & 1 deletion tests/ui/get_unwrap.fixed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// run-rustfix
#![allow(unused_mut)]
#![allow(unused_mut, clippy::from_iter_instead_of_collect)]
#![deny(clippy::get_unwrap)]

use std::collections::BTreeMap;
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/get_unwrap.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// run-rustfix
#![allow(unused_mut)]
#![allow(unused_mut, clippy::from_iter_instead_of_collect)]
#![deny(clippy::get_unwrap)]

use std::collections::BTreeMap;
Expand Down