Skip to content

Commit

Permalink
new lint: Unintentional return of unit from closures expecting Ord
Browse files Browse the repository at this point in the history
This lint catches cases where the last statement of a closure expecting
an instance of Ord has a trailing semi-colon. It compiles since the
closure ends up return () which also implements Ord but causes
unexpected results in cases such as sort_by_key.

Fixes #5080
  • Loading branch information
rabisg0 committed Mar 30, 2020
1 parent 0e5e2c4 commit e7d54ce
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,7 @@ Released 2018-09-13
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented
[`uninit_assumed_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#uninit_assumed_init
[`unintentional_unit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#unintentional_unit_return
[`unit_arg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_arg
[`unit_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#unit_cmp
[`unknown_clippy_lints`]: https://rust-lang.github.io/rust-clippy/master/index.html#unknown_clippy_lints
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.

[There are 361 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are 362 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:

Expand Down
4 changes: 4 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ pub mod trivially_copy_pass_by_ref;
pub mod try_err;
pub mod types;
pub mod unicode;
pub mod unintentional_unit_return;
pub mod unsafe_removed_from_name;
pub mod unused_io_amount;
pub mod unused_self;
Expand Down Expand Up @@ -812,6 +813,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&unicode::NON_ASCII_LITERAL,
&unicode::UNICODE_NOT_NFC,
&unicode::ZERO_WIDTH_SPACE,
&unintentional_unit_return::UNINTENTIONAL_UNIT_RETURN,
&unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
&unused_io_amount::UNUSED_IO_AMOUNT,
&unused_self::UNUSED_SELF,
Expand Down Expand Up @@ -1021,6 +1023,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box wildcard_imports::WildcardImports);
store.register_early_pass(|| box macro_use::MacroUseImports);
store.register_late_pass(|| box verbose_file_reads::VerboseFileReads);
store.register_late_pass(|| box unintentional_unit_return::UnintentionalUnitReturn);

store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
Expand Down Expand Up @@ -1670,6 +1673,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&mutex_atomic::MUTEX_INTEGER),
LintId::of(&needless_borrow::NEEDLESS_BORROW),
LintId::of(&path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
LintId::of(&unintentional_unit_return::UNINTENTIONAL_UNIT_RETURN),
LintId::of(&use_self::USE_SELF),
]);
}
Expand Down
136 changes: 136 additions & 0 deletions clippy_lints/src/unintentional_unit_return.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use crate::utils::{get_trait_def_id, paths, span_lint};
use if_chain::if_chain;
use rustc::ty::{GenericPredicates, Predicate, ProjectionPredicate, TraitPredicate};
use rustc_hir::{Expr, ExprKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};

declare_clippy_lint! {
/// **What it does:** Checks for functions that expect closures of type
/// Fn(...) -> Ord where the implemented closure has a semi-colon
/// at the end of the last statement.
///
/// **Why is this bad?** Likely the semi-colon is unintentional which
/// returns () instead of the result of last statement. Since () implements Ord
/// it doesn't cause a compilation error
///
/// **Known problems:** If returning unit is intentional, then there is no
/// way of specifying this without triggering needless_return lint
///
/// **Example:**
///
/// ```rust
/// let mut twins = vec!((1,1), (2,2));
/// twins.sort_by_key(|x| { x.1; });
/// ```
pub UNINTENTIONAL_UNIT_RETURN,
nursery,
"fn arguments of type Fn(...) -> Once having last statements with a semi-colon, suggesting to remove the semi-colon if it is unintentional."
}

declare_lint_pass!(UnintentionalUnitReturn => [UNINTENTIONAL_UNIT_RETURN]);

fn unwrap_trait_pred<'tcx>(cx: &LateContext<'_, 'tcx>, pred: &Predicate<'tcx>) -> Option<TraitPredicate<'tcx>> {
if let Predicate::Trait(poly_trait_pred, _) = pred {
let trait_pred = cx.tcx.erase_late_bound_regions(&poly_trait_pred);
Some(trait_pred)
} else {
None
}
}

fn get_predicates_for_trait_path<'tcx>(
cx: &LateContext<'_, 'tcx>,
generics: GenericPredicates<'tcx>,
trait_path: &[&str],
) -> Vec<&'tcx Predicate<'tcx>> {
let mut preds = Vec::new();
generics.predicates.iter().for_each(|(pred, _)| {
if let Some(trait_pred) = unwrap_trait_pred(cx, pred) {
if let Some(trait_def_id) = get_trait_def_id(cx, trait_path) {
if trait_def_id == trait_pred.trait_ref.def_id {
preds.push(pred);
}
}
}
});
preds
}

fn get_projection_pred<'tcx>(
cx: &LateContext<'_, 'tcx>,
generics: GenericPredicates<'tcx>,
pred: TraitPredicate<'tcx>,
) -> Option<ProjectionPredicate<'tcx>> {
generics.predicates.iter().find_map(|(proj_pred, _)| {
if let Predicate::Projection(proj_pred) = proj_pred {
let projection_pred = cx.tcx.erase_late_bound_regions(proj_pred);
if projection_pred.projection_ty.substs == pred.trait_ref.substs {
return Some(projection_pred);
}
}
None
})
}

fn get_args_to_check<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &'tcx Expr<'tcx>) -> Vec<usize> {
let mut args_to_check = Vec::new();
if let Some(def_id) = cx.tables.type_dependent_def_id(expr.hir_id) {
let fn_sig = cx.tcx.fn_sig(def_id);
let generics = cx.tcx.predicates_of(def_id);
let fn_mut_preds = get_predicates_for_trait_path(cx, generics, &paths::FN_MUT);
let ord_preds = get_predicates_for_trait_path(cx, generics, &paths::ORD);
// Trying to call erase_late_bound_regions on fn_sig.inputs() gives the following error
// The trait `rustc::ty::TypeFoldable<'_>` is not implemented for `&[&rustc::ty::TyS<'_>]`
let inputs_output = cx.tcx.erase_late_bound_regions(&fn_sig.inputs_and_output());
inputs_output.iter().enumerate().for_each(|(i, inp)| {
// Ignore output param
if i == inputs_output.len() - 1 {
return;
}
fn_mut_preds.iter().for_each(|pred| {
let trait_pred = unwrap_trait_pred(cx, pred).unwrap();
if trait_pred.self_ty() == *inp {
if let Some(projection_pred) = get_projection_pred(cx, generics, trait_pred) {
let ret_is_ord = ord_preds
.iter()
.any(|ord_pred| unwrap_trait_pred(cx, ord_pred).unwrap().self_ty() == projection_pred.ty);
if ret_is_ord {
args_to_check.push(i);
}
}
}
});
});
}
args_to_check
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnintentionalUnitReturn {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) {
let arg_indices = get_args_to_check(cx, expr);
if_chain! {
if let ExprKind::MethodCall(_, _, ref args) = expr.kind;
then {
for i in arg_indices {
if_chain! {
if i < args.len();
if let ExprKind::Closure(_, _fn_decl, body_id, _span, _) = args[i].kind;
let body = cx.tcx.hir().body(body_id);
if let ExprKind::Block(block, _) = body.value.kind;
if let Some(stmt) = block.stmts.last();
if let StmtKind::Semi(_) = stmt.kind;
then {
//TODO : Maybe only filter the closures where the last statement return type also is an unit
span_lint(cx,
UNINTENTIONAL_UNIT_RETURN,
stmt.span,
"Semi-colon on the last line of this closure returns \
the unit type which also implements Ord.");
}
}
}
}
}
}
}
1 change: 1 addition & 0 deletions clippy_lints/src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub const FILE_TYPE: [&str; 3] = ["std", "fs", "FileType"];
pub const FMT_ARGUMENTS_NEW_V1: [&str; 4] = ["core", "fmt", "Arguments", "new_v1"];
pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments", "new_v1_formatted"];
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
pub const FN_MUT: [&str; 3] = ["std", "ops", "FnMut"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
pub const HASH: [&str; 2] = ["hash", "Hash"];
Expand Down
9 changes: 8 additions & 1 deletion src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use lint::Lint;
pub use lint::LINT_LEVELS;

// begin lint list, do not remove this comment, it’s used in `update_lints`
pub const ALL_LINTS: [Lint; 361] = [
pub const ALL_LINTS: [Lint; 362] = [
Lint {
name: "absurd_extreme_comparisons",
group: "correctness",
Expand Down Expand Up @@ -2191,6 +2191,13 @@ pub const ALL_LINTS: [Lint; 361] = [
deprecation: None,
module: "methods",
},
Lint {
name: "unintentional_unit_return",
group: "nursery",
desc: "fn arguments of type Fn(...) -> Once having last statements with a semi-colon, suggesting to remove the semi-colon if it is unintentional.",
deprecation: None,
module: "unintentional_unit_return",
},
Lint {
name: "unit_arg",
group: "complexity",
Expand Down
17 changes: 17 additions & 0 deletions tests/ui/unintentional_unit_return.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![warn(clippy::unintentional_unit_return)]

struct Struct {
field: isize,
}

fn double(i: isize) -> isize {
i * 2
}

fn main() {
let mut structs = vec![Struct { field: 2 }, Struct { field: 1 }];
structs.sort_by_key(|s| {
double(s.field);
});
structs.sort_by_key(|s| double(s.field));
}
10 changes: 10 additions & 0 deletions tests/ui/unintentional_unit_return.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
error: Semi-colon on the last line of this closure returns the unit type which also implements Ord.
--> $DIR/unintentional_unit_return.rs:14:9
|
LL | double(s.field);
| ^^^^^^^^^^^^^^^^
|
= note: `-D clippy::unintentional-unit-return` implied by `-D warnings`

error: aborting due to previous error

0 comments on commit e7d54ce

Please sign in to comment.