Skip to content

Commit 0349ca7

Browse files
Add clippy::result_as_ref_deref lint
1 parent 7b566c2 commit 0349ca7

File tree

8 files changed

+304
-16
lines changed

8 files changed

+304
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5881,6 +5881,7 @@ Released 2018-09-13
58815881
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
58825882
[`reserve_after_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#reserve_after_initialization
58835883
[`rest_pat_in_fully_bound_structs`]: https://rust-lang.github.io/rust-clippy/master/index.html#rest_pat_in_fully_bound_structs
5884+
[`result_as_ref_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_as_ref_deref
58845885
[`result_expect_used`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_expect_used
58855886
[`result_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_filter_map
58865887
[`result_large_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err

clippy_config/src/msrvs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ msrv_aliases! {
2020
1,83,0 { CONST_EXTERN_FN }
2121
1,83,0 { CONST_FLOAT_BITS_CONV }
2222
1,81,0 { LINT_REASONS_STABILIZATION }
23-
1,80,0 { BOX_INTO_ITER}
23+
1,80,0 { BOX_INTO_ITER }
2424
1,77,0 { C_STR_LITERALS }
2525
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
2626
1,73,0 { MANUAL_DIV_CEIL }
@@ -39,7 +39,7 @@ msrv_aliases! {
3939
1,52,0 { STR_SPLIT_ONCE, REM_EUCLID_CONST }
4040
1,51,0 { BORROW_AS_PTR, SEEK_FROM_CURRENT, UNSIGNED_ABS }
4141
1,50,0 { BOOL_THEN, CLAMP }
42-
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST }
42+
1,47,0 { TAU, IS_ASCII_DIGIT_CONST, ARRAY_IMPL_ANY_LEN, SATURATING_SUB_CONST, RESULT_AS_DEREF }
4343
1,46,0 { CONST_IF_MATCH }
4444
1,45,0 { STR_STRIP_PREFIX }
4545
1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
445445
crate::methods::READ_LINE_WITHOUT_TRIM_INFO,
446446
crate::methods::REDUNDANT_AS_STR_INFO,
447447
crate::methods::REPEAT_ONCE_INFO,
448+
crate::methods::RESULT_AS_REF_DEREF_INFO,
448449
crate::methods::RESULT_FILTER_MAP_INFO,
449450
crate::methods::RESULT_MAP_OR_INTO_OPTION_INFO,
450451
crate::methods::SEARCH_IS_SOME_INFO,

clippy_lints/src/methods/option_as_ref_deref.rs renamed to clippy_lints/src/methods/manual_as_ref_deref.rs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@ use rustc_lint::LateContext;
99
use rustc_middle::ty;
1010
use rustc_span::{Symbol, sym};
1111

12-
use super::OPTION_AS_REF_DEREF;
12+
use super::{OPTION_AS_REF_DEREF, RESULT_AS_REF_DEREF};
1313

14-
/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
14+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15+
enum Target {
16+
Option,
17+
Result,
18+
}
19+
20+
/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s and `Result`s
21+
#[allow(clippy::too_many_lines)]
1522
pub(super) fn check(
1623
cx: &LateContext<'_>,
1724
expr: &hir::Expr<'_>,
@@ -20,14 +27,23 @@ pub(super) fn check(
2027
is_mut: bool,
2128
msrv: &Msrv,
2229
) {
23-
if !msrv.meets(msrvs::OPTION_AS_DEREF) {
24-
return;
25-
}
26-
2730
let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
2831

29-
let option_ty = cx.typeck_results().expr_ty(as_ref_recv);
30-
if !is_type_diagnostic_item(cx, option_ty, sym::Option) {
32+
let target = 'target: {
33+
let target_ty = cx.typeck_results().expr_ty(as_ref_recv);
34+
if is_type_diagnostic_item(cx, target_ty, sym::Option) {
35+
break 'target Target::Option;
36+
}
37+
if is_type_diagnostic_item(cx, target_ty, sym::Result) {
38+
break 'target Target::Result;
39+
}
40+
return;
41+
};
42+
43+
if target == Target::Option && !msrv.meets(msrvs::OPTION_AS_DEREF) {
44+
return;
45+
}
46+
if target == Target::Result && !msrv.meets(msrvs::RESULT_AS_DEREF) {
3147
return;
3248
}
3349

@@ -103,10 +119,20 @@ pub(super) fn check(
103119
let hint = format!("{}.{method_hint}()", snippet(cx, as_ref_recv.span, ".."));
104120
let suggestion = format!("consider using {method_hint}");
105121

106-
let msg = format!("called `{current_method}` on an `Option` value");
122+
let target_name_with_article = match target {
123+
Target::Option => "an `Option`",
124+
Target::Result => "a `Result`",
125+
};
126+
let msg = format!("called `{current_method}` on {target_name_with_article} value");
127+
128+
let lint = match target {
129+
Target::Option => OPTION_AS_REF_DEREF,
130+
Target::Result => RESULT_AS_REF_DEREF,
131+
};
132+
107133
span_lint_and_sugg(
108134
cx,
109-
OPTION_AS_REF_DEREF,
135+
lint,
110136
expr.span,
111137
msg,
112138
suggestion,

clippy_lints/src/methods/mod.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ mod iter_skip_zero;
5252
mod iter_with_drain;
5353
mod iterator_step_by_zero;
5454
mod join_absolute_paths;
55+
mod manual_as_ref_deref;
5556
mod manual_c_str_literals;
5657
mod manual_inspect;
5758
mod manual_is_variant_and;
@@ -76,7 +77,6 @@ mod obfuscated_if_else;
7677
mod ok_expect;
7778
mod open_options;
7879
mod option_as_ref_cloned;
79-
mod option_as_ref_deref;
8080
mod option_map_or_err_ok;
8181
mod option_map_or_none;
8282
mod option_map_unwrap_or;
@@ -1759,7 +1759,8 @@ declare_clippy_lint! {
17591759

17601760
declare_clippy_lint! {
17611761
/// ### What it does
1762-
/// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases (such as String::as_str).
1762+
/// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases
1763+
/// (such as `String::as_str`) on `Option`.
17631764
///
17641765
/// ### Why is this bad?
17651766
/// Readability, this can be written more concisely as
@@ -1783,6 +1784,33 @@ declare_clippy_lint! {
17831784
"using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
17841785
}
17851786

1787+
declare_clippy_lint! {
1788+
/// ### What it does
1789+
/// Checks for usage of `_.as_ref().map(Deref::deref)` or its aliases
1790+
/// (such as `String::as_str`) on `Result`.
1791+
///
1792+
/// ### Why is this bad?
1793+
/// Readability, this can be written more concisely as
1794+
/// `_.as_deref()`.
1795+
///
1796+
/// ### Example
1797+
/// ```no_run
1798+
/// # let res = Ok::<_, ()>("".to_string());
1799+
/// res.as_ref().map(String::as_str)
1800+
/// # ;
1801+
/// ```
1802+
/// Use instead:
1803+
/// ```no_run
1804+
/// # let res = Ok::<_, ()>("".to_string());
1805+
/// res.as_deref()
1806+
/// # ;
1807+
/// ```
1808+
#[clippy::version = "1.83.0"]
1809+
pub RESULT_AS_REF_DEREF,
1810+
complexity,
1811+
"using `as_ref().map(Deref::deref)`, which is more succinctly expressed as `as_deref()`"
1812+
}
1813+
17861814
declare_clippy_lint! {
17871815
/// ### What it does
17881816
/// Checks for usage of `iter().next()` on a Slice or an Array
@@ -4252,6 +4280,7 @@ impl_lint_pass!(Methods => [
42524280
ZST_OFFSET,
42534281
FILETYPE_IS_FILE,
42544282
OPTION_AS_REF_DEREF,
4283+
RESULT_AS_REF_DEREF,
42554284
UNNECESSARY_LAZY_EVALUATIONS,
42564285
MAP_COLLECT_RESULT_UNIT,
42574286
FROM_ITER_INSTEAD_OF_COLLECT,
@@ -4821,8 +4850,8 @@ impl Methods {
48214850
}
48224851
if let Some((name, recv2, args, span2, _)) = method_call(recv) {
48234852
match (name, args) {
4824-
("as_mut", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, true, &self.msrv),
4825-
("as_ref", []) => option_as_ref_deref::check(cx, expr, recv2, m_arg, false, &self.msrv),
4853+
("as_mut", []) => manual_as_ref_deref::check(cx, expr, recv2, m_arg, true, &self.msrv),
4854+
("as_ref", []) => manual_as_ref_deref::check(cx, expr, recv2, m_arg, false, &self.msrv),
48264855
("filter", [f_arg]) => {
48274856
filter_map::check(cx, expr, recv2, f_arg, span2, recv, m_arg, span, false);
48284857
},

tests/ui/result_as_ref_deref.fixed

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#![allow(unused, clippy::redundant_clone, clippy::useless_vec)]
2+
#![warn(clippy::result_as_ref_deref)]
3+
4+
use std::ffi::{CString, OsString};
5+
use std::ops::{Deref, DerefMut};
6+
use std::path::PathBuf;
7+
8+
fn main() {
9+
let mut res = Ok::<_, ()>(String::from("123"));
10+
11+
let _ = res.clone().as_deref().map(str::len);
12+
13+
#[rustfmt::skip]
14+
let _ = res.clone().as_deref()
15+
.map(str::len);
16+
17+
let _ = res.as_deref_mut();
18+
19+
let _ = res.as_deref();
20+
let _ = res.as_deref();
21+
let _ = res.as_deref_mut();
22+
let _ = res.as_deref_mut();
23+
let _ = Ok::<_, ()>(CString::new(vec![]).unwrap()).as_deref();
24+
let _ = Ok::<_, ()>(OsString::new()).as_deref();
25+
let _ = Ok::<_, ()>(PathBuf::new()).as_deref();
26+
let _ = Ok::<_, ()>(Vec::<()>::new()).as_deref();
27+
let _ = Ok::<_, ()>(Vec::<()>::new()).as_deref_mut();
28+
29+
let _ = res.as_deref();
30+
let _ = res.clone().as_deref_mut().map(|x| x.len());
31+
32+
let vc = vec![String::new()];
33+
let _ = Ok::<_, ()>(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted
34+
35+
let _: Result<&str, &()> = Ok(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted
36+
37+
let _ = res.as_deref();
38+
let _ = res.as_deref_mut();
39+
40+
let _ = res.as_deref();
41+
}
42+
43+
#[clippy::msrv = "1.46"]
44+
fn msrv_1_46() {
45+
let res = Ok::<_, ()>(String::from("123"));
46+
let _ = res.as_ref().map(String::as_str);
47+
}
48+
49+
#[clippy::msrv = "1.47"]
50+
fn msrv_1_47() {
51+
let res = Ok::<_, ()>(String::from("123"));
52+
let _ = res.as_deref();
53+
}

tests/ui/result_as_ref_deref.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#![allow(unused, clippy::redundant_clone, clippy::useless_vec)]
2+
#![warn(clippy::result_as_ref_deref)]
3+
4+
use std::ffi::{CString, OsString};
5+
use std::ops::{Deref, DerefMut};
6+
use std::path::PathBuf;
7+
8+
fn main() {
9+
let mut res = Ok::<_, ()>(String::from("123"));
10+
11+
let _ = res.clone().as_ref().map(Deref::deref).map(str::len);
12+
13+
#[rustfmt::skip]
14+
let _ = res.clone()
15+
.as_ref().map(
16+
Deref::deref
17+
)
18+
.map(str::len);
19+
20+
let _ = res.as_mut().map(DerefMut::deref_mut);
21+
22+
let _ = res.as_ref().map(String::as_str);
23+
let _ = res.as_ref().map(|x| x.as_str());
24+
let _ = res.as_mut().map(String::as_mut_str);
25+
let _ = res.as_mut().map(|x| x.as_mut_str());
26+
let _ = Ok::<_, ()>(CString::new(vec![]).unwrap())
27+
.as_ref()
28+
.map(CString::as_c_str);
29+
let _ = Ok::<_, ()>(OsString::new()).as_ref().map(OsString::as_os_str);
30+
let _ = Ok::<_, ()>(PathBuf::new()).as_ref().map(PathBuf::as_path);
31+
let _ = Ok::<_, ()>(Vec::<()>::new()).as_ref().map(Vec::as_slice);
32+
let _ = Ok::<_, ()>(Vec::<()>::new()).as_mut().map(Vec::as_mut_slice);
33+
34+
let _ = res.as_ref().map(|x| x.deref());
35+
let _ = res.clone().as_mut().map(|x| x.deref_mut()).map(|x| x.len());
36+
37+
let vc = vec![String::new()];
38+
let _ = Ok::<_, ()>(1_usize).as_ref().map(|x| vc[*x].as_str()); // should not be linted
39+
40+
let _: Result<&str, &()> = Ok(&String::new()).as_ref().map(|x| x.as_str()); // should not be linted
41+
42+
let _ = res.as_ref().map(|x| &**x);
43+
let _ = res.as_mut().map(|x| &mut **x);
44+
45+
let _ = res.as_ref().map(std::ops::Deref::deref);
46+
}
47+
48+
#[clippy::msrv = "1.46"]
49+
fn msrv_1_46() {
50+
let res = Ok::<_, ()>(String::from("123"));
51+
let _ = res.as_ref().map(String::as_str);
52+
}
53+
54+
#[clippy::msrv = "1.47"]
55+
fn msrv_1_47() {
56+
let res = Ok::<_, ()>(String::from("123"));
57+
let _ = res.as_ref().map(String::as_str);
58+
}

0 commit comments

Comments
 (0)