Add a lint to warn about call to .*or(foo(..))

This commit is contained in:
mcarton 2016-01-16 18:47:45 +01:00
parent 604be945d2
commit c6604bb281
5 changed files with 90 additions and 4 deletions

View file

@ -6,7 +6,7 @@ A collection of lints to catch common mistakes and improve your Rust code.
[Jump to usage instructions](#usage) [Jump to usage instructions](#usage)
##Lints ##Lints
There are 92 lints included in this crate: There are 93 lints included in this crate:
name | default | meaning name | default | meaning
---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ---------------------------------------------------------------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -63,6 +63,7 @@ name
[option_map_unwrap_or](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or) | warn | using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)` [option_map_unwrap_or](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or) | warn | using `Option.map(f).unwrap_or(a)`, which is more succinctly expressed as `map_or(a, f)`
[option_map_unwrap_or_else](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or_else) | warn | using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)` [option_map_unwrap_or_else](https://github.com/Manishearth/rust-clippy/wiki#option_map_unwrap_or_else) | warn | using `Option.map(f).unwrap_or_else(g)`, which is more succinctly expressed as `map_or_else(g, f)`
[option_unwrap_used](https://github.com/Manishearth/rust-clippy/wiki#option_unwrap_used) | allow | using `Option.unwrap()`, which should at least get a better message using `expect()` [option_unwrap_used](https://github.com/Manishearth/rust-clippy/wiki#option_unwrap_used) | allow | using `Option.unwrap()`, which should at least get a better message using `expect()`
[or_fun_call](https://github.com/Manishearth/rust-clippy/wiki#or_fun_call) | warn | using any `*or` method when the `*or_else` would do
[out_of_bounds_indexing](https://github.com/Manishearth/rust-clippy/wiki#out_of_bounds_indexing) | deny | out of bound constant indexing [out_of_bounds_indexing](https://github.com/Manishearth/rust-clippy/wiki#out_of_bounds_indexing) | deny | out of bound constant indexing
[panic_params](https://github.com/Manishearth/rust-clippy/wiki#panic_params) | warn | missing parameters in `panic!` [panic_params](https://github.com/Manishearth/rust-clippy/wiki#panic_params) | warn | missing parameters in `panic!`
[precedence](https://github.com/Manishearth/rust-clippy/wiki#precedence) | warn | catches operations where precedence may be unclear. See the wiki for a list of cases caught [precedence](https://github.com/Manishearth/rust-clippy/wiki#precedence) | warn | catches operations where precedence may be unclear. See the wiki for a list of cases caught

View file

@ -194,6 +194,7 @@ pub fn plugin_registrar(reg: &mut Registry) {
methods::OK_EXPECT, methods::OK_EXPECT,
methods::OPTION_MAP_UNWRAP_OR, methods::OPTION_MAP_UNWRAP_OR,
methods::OPTION_MAP_UNWRAP_OR_ELSE, methods::OPTION_MAP_UNWRAP_OR_ELSE,
methods::OR_FUN_CALL,
methods::SEARCH_IS_SOME, methods::SEARCH_IS_SOME,
methods::SHOULD_IMPLEMENT_TRAIT, methods::SHOULD_IMPLEMENT_TRAIT,
methods::STR_TO_STRING, methods::STR_TO_STRING,

View file

@ -4,6 +4,7 @@ use rustc::middle::ty;
use rustc::middle::subst::{Subst, TypeSpace}; use rustc::middle::subst::{Subst, TypeSpace};
use std::iter; use std::iter;
use std::borrow::Cow; use std::borrow::Cow;
use syntax::ptr::P;
use utils::{snippet, span_lint, span_note_and_lint, match_path, match_type, method_chain_args, match_trait_method, use utils::{snippet, span_lint, span_note_and_lint, match_path, match_type, method_chain_args, match_trait_method,
walk_ptrs_ty_depth, walk_ptrs_ty}; walk_ptrs_ty_depth, walk_ptrs_ty};
@ -170,6 +171,25 @@ declare_lint!(pub SEARCH_IS_SOME, Warn,
"using an iterator search followed by `is_some()`, which is more succinctly \ "using an iterator search followed by `is_some()`, which is more succinctly \
expressed as a call to `any()`"); expressed as a call to `any()`");
/// **What it does:** This lint checks for calls to `.or(foo(..))`, `.unwrap_or(foo(..))`, etc., and
/// suggests to use `or_else`, `unwrap_or_else`, etc., instead.
///
/// **Why is this bad?** The function will always be called and potentially allocate an object
/// in expressions such as:
/// ```rust
/// foo.unwrap_or(String::new())
/// ```
/// this can instead be written:
/// ```rust
/// foo.unwrap_or_else(String::new)
/// ```
///
/// **Known problems:** If the function as side-effects, not calling it will change the semantic of
/// the program, but you shouldn't rely on that anyway. The will won't catch
/// `foo.unwrap_or(vec![])`.
declare_lint!(pub OR_FUN_CALL, Warn,
"using any `*or` method when the `*or_else` would do");
impl LintPass for MethodsPass { impl LintPass for MethodsPass {
fn get_lints(&self) -> LintArray { fn get_lints(&self) -> LintArray {
lint_array!(OPTION_UNWRAP_USED, lint_array!(OPTION_UNWRAP_USED,
@ -181,13 +201,15 @@ impl LintPass for MethodsPass {
WRONG_PUB_SELF_CONVENTION, WRONG_PUB_SELF_CONVENTION,
OK_EXPECT, OK_EXPECT,
OPTION_MAP_UNWRAP_OR, OPTION_MAP_UNWRAP_OR,
OPTION_MAP_UNWRAP_OR_ELSE) OPTION_MAP_UNWRAP_OR_ELSE,
OR_FUN_CALL)
} }
} }
impl LateLintPass for MethodsPass { impl LateLintPass for MethodsPass {
fn check_expr(&mut self, cx: &LateContext, expr: &Expr) { fn check_expr(&mut self, cx: &LateContext, expr: &Expr) {
if let ExprMethodCall(_, _, _) = expr.node { if let ExprMethodCall(name, _, ref args) = expr.node {
// Chain calls
if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
lint_unwrap(cx, expr, arglists[0]); lint_unwrap(cx, expr, arglists[0]);
} else if let Some(arglists) = method_chain_args(expr, &["to_string"]) { } else if let Some(arglists) = method_chain_args(expr, &["to_string"]) {
@ -207,6 +229,8 @@ impl LateLintPass for MethodsPass {
} else if let Some(arglists) = method_chain_args(expr, &["rposition", "is_some"]) { } else if let Some(arglists) = method_chain_args(expr, &["rposition", "is_some"]) {
lint_search_is_some(cx, expr, "rposition", arglists[0], arglists[1]); lint_search_is_some(cx, expr, "rposition", arglists[0], arglists[1]);
} }
lint_or_fun_call(cx, expr, &name.node.as_str(), &args);
} }
} }
@ -258,6 +282,39 @@ impl LateLintPass for MethodsPass {
} }
} }
/// Checks for the `OR_FUN_CALL` lint.
fn lint_or_fun_call(cx: &LateContext, expr: &Expr, name: &str, args: &[P<Expr>]) {
if args.len() == 2 && ["map_or", "ok_or", "or", "unwrap_or"].contains(&name) {
let self_ty = cx.tcx.expr_ty(&args[0]);
let is_result = if match_type(cx, self_ty, &RESULT_PATH) {
true
}
else if match_type(cx, self_ty, &OPTION_PATH) {
false
}
else {
return;
};
if let ExprCall(ref fun, ref or_args) = args[1].node {
let sugg = match (is_result, or_args.is_empty()) {
(true, _) => format!("|_| {}", snippet(cx, args[1].span, "..")),
(false, false) => format!("|| {}", snippet(cx, args[1].span, "..")),
(false, true) => format!("{}", snippet(cx, fun.span, "..")),
};
span_lint(cx, OR_FUN_CALL, expr.span,
&format!("use of `{}` followed by a function call", name))
.span_suggestion(expr.span, "try this",
format!("{}.{}_else({})",
snippet(cx, args[0].span, "_"),
name,
sugg));
}
}
}
#[allow(ptr_arg)] #[allow(ptr_arg)]
// Type of MethodArgs is potentially a Vec // Type of MethodArgs is potentially a Vec
/// lint use of `unwrap()` for `Option`s and `Result`s /// lint use of `unwrap()` for `Option`s and `Result`s

View file

@ -244,7 +244,7 @@ pub fn is_from_for_desugar(decl: &Decl) -> bool {
/// snippet(cx, expr.span, "..") /// snippet(cx, expr.span, "..")
/// ``` /// ```
pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> { pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
cx.sess().codemap().span_to_snippet(span).map(From::from).unwrap_or(Cow::Borrowed(default)) cx.sess().codemap().span_to_snippet(span).map(From::from).unwrap_or_else(|_| Cow::Borrowed(default))
} }
/// Convert a span to a code snippet. Returns `None` if not available. /// Convert a span to a code snippet. Returns `None` if not available.

View file

@ -175,6 +175,33 @@ fn search_is_some() {
let _ = foo.rposition().is_some(); let _ = foo.rposition().is_some();
} }
/// Checks implementation of the OR_FUN_CALL lint
fn or_fun_call() {
let foo = Some(vec![1]);
foo.unwrap_or(Vec::new());
//~^ERROR use of `unwrap_or`
//~|HELP try this
//~|SUGGESTION foo.unwrap_or_else(Vec::new);
let bar = Some(vec![1]);
bar.unwrap_or(Vec::with_capacity(12));
//~^ERROR use of `unwrap_or`
//~|HELP try this
//~|SUGGESTION bar.unwrap_or_else(|| Vec::with_capacity(12));
let baz : Result<_, ()> = Ok(vec![1]);
baz.unwrap_or(Vec::new());
//~^ERROR use of `unwrap_or`
//~|HELP try this
//~|SUGGESTION baz.unwrap_or_else(|_| Vec::new());
let qux : Result<_, ()> = Ok(vec![1]);
qux.unwrap_or(Vec::with_capacity(12));
//~^ERROR use of `unwrap_or`
//~|HELP try this
//~|SUGGESTION qux.unwrap_or_else(|_| Vec::with_capacity(12));
}
fn main() { fn main() {
use std::io; use std::io;