Skip to content

Commit db0a3a6

Browse files
committed
add option_as_ref_deref lint
1 parent a68ef55 commit db0a3a6

File tree

9 files changed

+289
-2
lines changed

9 files changed

+289
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,7 @@ Released 2018-09-13
12221222
[`ok_expect`]: https://rust-lang.github.io/rust-clippy/master/index.html#ok_expect
12231223
[`op_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
12241224
[`option_and_then_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_and_then_some
1225+
[`option_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_as_ref_deref
12251226
[`option_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_expect_used
12261227
[`option_map_or_none`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_or_none
12271228
[`option_map_unit_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#option_map_unit_fn

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

9-
[There are 341 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 342 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

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

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
622622
&methods::NEW_RET_NO_SELF,
623623
&methods::OK_EXPECT,
624624
&methods::OPTION_AND_THEN_SOME,
625+
&methods::OPTION_AS_REF_DEREF,
625626
&methods::OPTION_EXPECT_USED,
626627
&methods::OPTION_MAP_OR_NONE,
627628
&methods::OPTION_MAP_UNWRAP_OR,
@@ -1196,6 +1197,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
11961197
LintId::of(&methods::NEW_RET_NO_SELF),
11971198
LintId::of(&methods::OK_EXPECT),
11981199
LintId::of(&methods::OPTION_AND_THEN_SOME),
1200+
LintId::of(&methods::OPTION_AS_REF_DEREF),
11991201
LintId::of(&methods::OPTION_MAP_OR_NONE),
12001202
LintId::of(&methods::OR_FUN_CALL),
12011203
LintId::of(&methods::SEARCH_IS_SOME),
@@ -1450,6 +1452,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
14501452
LintId::of(&methods::FILTER_NEXT),
14511453
LintId::of(&methods::FLAT_MAP_IDENTITY),
14521454
LintId::of(&methods::OPTION_AND_THEN_SOME),
1455+
LintId::of(&methods::OPTION_AS_REF_DEREF),
14531456
LintId::of(&methods::SEARCH_IS_SOME),
14541457
LintId::of(&methods::SUSPICIOUS_MAP),
14551458
LintId::of(&methods::UNNECESSARY_FILTER_MAP),

clippy_lints/src/methods/mod.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,27 @@ declare_clippy_lint! {
11021102
"Check for offset calculations on raw pointers to zero-sized types"
11031103
}
11041104

1105+
declare_clippy_lint! {
1106+
/// **What it does:** Checks for usage of `_.as_ref().map(Deref::deref)` or it's aliases (such as String::as_str).
1107+
///
1108+
/// **Why is this bad?** Readability, this can be written more concisely as a
1109+
/// single method call.
1110+
///
1111+
/// **Known problems:** None.
1112+
///
1113+
/// **Example:**
1114+
/// ```rust,ignore
1115+
/// opt.as_ref().map(String::as_str)
1116+
/// ```
1117+
/// Can be written as
1118+
/// ```rust,ignore
1119+
/// opt.as_deref()
1120+
/// ```
1121+
pub OPTION_AS_REF_DEREF,
1122+
complexity,
1123+
"using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
1124+
}
1125+
11051126
declare_lint_pass!(Methods => [
11061127
OPTION_UNWRAP_USED,
11071128
RESULT_UNWRAP_USED,
@@ -1148,6 +1169,7 @@ declare_lint_pass!(Methods => [
11481169
UNINIT_ASSUMED_INIT,
11491170
MANUAL_SATURATING_ARITHMETIC,
11501171
ZST_OFFSET,
1172+
OPTION_AS_REF_DEREF,
11511173
]);
11521174

11531175
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
@@ -1210,6 +1232,8 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Methods {
12101232
["add"] | ["offset"] | ["sub"] | ["wrapping_offset"] | ["wrapping_add"] | ["wrapping_sub"] => {
12111233
check_pointer_offset(cx, expr, arg_lists[0])
12121234
},
1235+
["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false),
1236+
["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true),
12131237
_ => {},
12141238
}
12151239

@@ -2892,6 +2916,79 @@ fn lint_suspicious_map(cx: &LateContext<'_, '_>, expr: &hir::Expr) {
28922916
);
28932917
}
28942918

2919+
/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
2920+
fn lint_option_as_ref_deref<'a, 'tcx>(
2921+
cx: &LateContext<'a, 'tcx>,
2922+
expr: &hir::Expr,
2923+
as_ref_args: &'tcx [hir::Expr],
2924+
map_args: &'tcx [hir::Expr],
2925+
is_mut: bool,
2926+
) {
2927+
let option_ty = cx.tables.expr_ty(&as_ref_args[0]);
2928+
if !match_type(cx, option_ty, &paths::OPTION) {
2929+
return;
2930+
}
2931+
2932+
let deref_aliases: [&[&str]; 9] = [
2933+
&paths::DEREF_TRAIT_METHOD,
2934+
&paths::DEREF_MUT_TRAIT_METHOD,
2935+
&paths::CSTRING_AS_C_STR,
2936+
&paths::OS_STRING_AS_OS_STR,
2937+
&paths::PATH_BUF_AS_PATH,
2938+
&paths::STRING_AS_STR,
2939+
&paths::STRING_AS_MUT_STR,
2940+
&paths::VEC_AS_SLICE,
2941+
&paths::VEC_AS_MUT_SLICE,
2942+
];
2943+
2944+
let is_deref = match map_args[1].kind {
2945+
hir::ExprKind::Path(ref expr_qpath) => deref_aliases.iter().any(|path| match_qpath(expr_qpath, path)),
2946+
hir::ExprKind::Closure(_, _, body_id, _, _) => {
2947+
let closure_body = cx.tcx.hir().body(body_id);
2948+
let closure_expr = remove_blocks(&closure_body.value);
2949+
if_chain! {
2950+
if let hir::ExprKind::MethodCall(_, _, args) = &closure_expr.kind;
2951+
if args.len() == 1;
2952+
if let hir::ExprKind::Path(qpath) = &args[0].kind;
2953+
if let hir::def::Res::Local(local_id) = cx.tables.qpath_res(qpath, args[0].hir_id);
2954+
if closure_body.params[0].pat.hir_id == local_id;
2955+
if let Some(adj) = cx.tables.adjustments().get(args[0].hir_id).map(|v| {
2956+
v.iter().map(|x| &x.kind).collect::<Box<[_]>>()
2957+
});
2958+
if let [ty::adjustment::Adjust::Deref(None), ty::adjustment::Adjust::Borrow(_)] = *adj;
2959+
then {
2960+
let method_did = cx.tables.type_dependent_def_id(closure_expr.hir_id).unwrap();
2961+
deref_aliases.iter().any(|path| match_def_path(cx, method_did, path))
2962+
} else {
2963+
false
2964+
}
2965+
}
2966+
},
2967+
2968+
_ => false,
2969+
};
2970+
2971+
if is_deref {
2972+
let current_method = if is_mut {
2973+
".as_mut().map(DerefMut::deref_mut)"
2974+
} else {
2975+
".as_ref().map(Deref::deref)"
2976+
};
2977+
let method_hint = if is_mut { "as_deref_mut" } else { "as_deref" };
2978+
let hint = format!("{}.{}()", snippet(cx, as_ref_args[0].span, ".."), method_hint);
2979+
let suggestion = format!("try using {} instead", method_hint);
2980+
2981+
let msg = format!(
2982+
"called `{0}` (or with one of deref aliases) on an Option value. \
2983+
This can be done more directly by calling `{1}` instead",
2984+
current_method, hint
2985+
);
2986+
span_lint_and_then(cx, OPTION_AS_REF_DEREF, expr.span, &msg, |db| {
2987+
db.span_suggestion(expr.span, &suggestion, hint, Applicability::MachineApplicable);
2988+
});
2989+
}
2990+
}
2991+
28952992
/// Given a `Result<T, E>` type, return its error type (`E`).
28962993
fn get_error_type<'a>(cx: &LateContext<'_, '_>, ty: Ty<'a>) -> Option<Ty<'a>> {
28972994
match ty.kind {

clippy_lints/src/utils/paths.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
1818
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
1919
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
2020
pub const CSTRING: [&str; 4] = ["std", "ffi", "c_str", "CString"];
21+
pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
2122
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
2223
pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
24+
pub const DEREF_MUT_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "DerefMut", "deref_mut"];
2325
pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
2426
pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
2527
pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
@@ -62,10 +64,12 @@ pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
6264
pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
6365
pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
6466
pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"];
67+
pub const OS_STRING_AS_OS_STR: [&str; 5] = ["std", "ffi", "os_str", "OsString", "as_os_str"];
6568
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
6669
pub const PARTIAL_ORD: [&str; 3] = ["core", "cmp", "PartialOrd"];
6770
pub const PATH: [&str; 3] = ["std", "path", "Path"];
6871
pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
72+
pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
6973
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
7074
pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
7175
pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
@@ -104,6 +108,8 @@ pub const STD_CONVERT_IDENTITY: [&str; 3] = ["std", "convert", "identity"];
104108
pub const STD_MEM_TRANSMUTE: [&str; 3] = ["std", "mem", "transmute"];
105109
pub const STD_PTR_NULL: [&str; 3] = ["std", "ptr", "null"];
106110
pub const STRING: [&str; 3] = ["alloc", "string", "String"];
111+
pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
112+
pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
107113
pub const SYNTAX_CONTEXT: [&str; 3] = ["syntax_pos", "hygiene", "SyntaxContext"];
108114
pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
109115
pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
@@ -113,6 +119,8 @@ pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
113119
pub const TRY_FROM_ERROR: [&str; 4] = ["std", "ops", "Try", "from_error"];
114120
pub const TRY_INTO_RESULT: [&str; 4] = ["std", "ops", "Try", "into_result"];
115121
pub const VEC: [&str; 3] = ["alloc", "vec", "Vec"];
122+
pub const VEC_AS_MUT_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_mut_slice"];
123+
pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
116124
pub const VEC_DEQUE: [&str; 4] = ["alloc", "collections", "vec_deque", "VecDeque"];
117125
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
118126
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];

src/lintlist/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 341] = [
9+
pub const ALL_LINTS: [Lint; 342] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1435,6 +1435,13 @@ pub const ALL_LINTS: [Lint; 341] = [
14351435
deprecation: None,
14361436
module: "methods",
14371437
},
1438+
Lint {
1439+
name: "option_as_ref_deref",
1440+
group: "complexity",
1441+
desc: "using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`",
1442+
deprecation: None,
1443+
module: "methods",
1444+
},
14381445
Lint {
14391446
name: "option_expect_used",
14401447
group: "restriction",

tests/ui/option_as_ref_deref.fixed

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// run-rustfix
2+
3+
#![allow(unused_imports, clippy::redundant_clone)]
4+
#![warn(clippy::option_as_ref_deref)]
5+
6+
use std::ffi::{CString, OsString};
7+
use std::ops::{Deref, DerefMut};
8+
use std::path::PathBuf;
9+
10+
fn main() {
11+
let mut opt = Some(String::from("123"));
12+
13+
let _ = opt.clone().as_deref().map(str::len);
14+
15+
#[rustfmt::skip]
16+
let _ = opt.clone().as_deref()
17+
.map(str::len);
18+
19+
let _ = opt.as_deref_mut();
20+
21+
let _ = opt.as_deref();
22+
let _ = opt.as_deref();
23+
let _ = opt.as_deref_mut();
24+
let _ = opt.as_deref_mut();
25+
let _ = Some(CString::new(vec![]).unwrap()).as_deref();
26+
let _ = Some(OsString::new()).as_deref();
27+
let _ = Some(PathBuf::new()).as_deref();
28+
let _ = Some(Vec::<()>::new()).as_deref();
29+
let _ = Some(Vec::<()>::new()).as_deref_mut();
30+
31+
let _ = opt.as_deref();
32+
let _ = opt.clone().as_deref_mut().map(|x| x.len());
33+
34+
let vc = vec![String::new()];
35+
let _ = Some(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted
36+
37+
let _: Option<&str> = Some(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted
38+
}

tests/ui/option_as_ref_deref.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// run-rustfix
2+
3+
#![allow(unused_imports, clippy::redundant_clone)]
4+
#![warn(clippy::option_as_ref_deref)]
5+
6+
use std::ffi::{CString, OsString};
7+
use std::ops::{Deref, DerefMut};
8+
use std::path::PathBuf;
9+
10+
fn main() {
11+
let mut opt = Some(String::from("123"));
12+
13+
let _ = opt.clone().as_ref().map(Deref::deref).map(str::len);
14+
15+
#[rustfmt::skip]
16+
let _ = opt.clone()
17+
.as_ref().map(
18+
Deref::deref
19+
)
20+
.map(str::len);
21+
22+
let _ = opt.as_mut().map(DerefMut::deref_mut);
23+
24+
let _ = opt.as_ref().map(String::as_str);
25+
let _ = opt.as_ref().map(|x| x.as_str());
26+
let _ = opt.as_mut().map(String::as_mut_str);
27+
let _ = opt.as_mut().map(|x| x.as_mut_str());
28+
let _ = Some(CString::new(vec![]).unwrap()).as_ref().map(CString::as_c_str);
29+
let _ = Some(OsString::new()).as_ref().map(OsString::as_os_str);
30+
let _ = Some(PathBuf::new()).as_ref().map(PathBuf::as_path);
31+
let _ = Some(Vec::<()>::new()).as_ref().map(Vec::as_slice);
32+
let _ = Some(Vec::<()>::new()).as_mut().map(Vec::as_mut_slice);
33+
34+
let _ = opt.as_ref().map(|x| x.deref());
35+
let _ = opt.clone().as_mut().map(|x| x.deref_mut()).map(|x| x.len());
36+
37+
let vc = vec![String::new()];
38+
let _ = Some(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted
39+
40+
let _: Option<&str> = Some(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted
41+
}

0 commit comments

Comments
 (0)