Skip to content

Commit 7c2bf28

Browse files
committed
Auto merge of #10358 - pksunkara:unnecessary-unwrap, r=llogiq
Add `unnecessary_literal_unwrap` lint Add lint for more unnecessary unwraps and suggest fixes for them. Fixes #10352 - [x] Followed [lint naming conventions][lint_naming] - [x] Added passing UI tests (including committed `.stderr` file) - [x] `cargo test` passes locally - [x] Executed `cargo dev update_lints` - [x] Added lint documentation - [x] Run `cargo dev fmt` r? `@llogiq` --- changelog: New lint [`unnecessary_literal_unwrap`] [#10358](#10358) <!-- changelog_checked -->
2 parents da56c35 + bfd5aba commit 7c2bf28

File tree

72 files changed

+1883
-379
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1883
-379
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5248,6 +5248,7 @@ Released 2018-09-13
52485248
[`unnecessary_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fold
52495249
[`unnecessary_join`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_join
52505250
[`unnecessary_lazy_evaluations`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations
5251+
[`unnecessary_literal_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_literal_unwrap
52515252
[`unnecessary_mut_passed`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_mut_passed
52525253
[`unnecessary_operation`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_operation
52535254
[`unnecessary_owned_empty_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_owned_empty_strings

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
408408
crate::methods::UNNECESSARY_FOLD_INFO,
409409
crate::methods::UNNECESSARY_JOIN_INFO,
410410
crate::methods::UNNECESSARY_LAZY_EVALUATIONS_INFO,
411+
crate::methods::UNNECESSARY_LITERAL_UNWRAP_INFO,
411412
crate::methods::UNNECESSARY_SORT_BY_INFO,
412413
crate::methods::UNNECESSARY_TO_OWNED_INFO,
413414
crate::methods::UNWRAP_OR_ELSE_DEFAULT_INFO,

clippy_lints/src/methods/mod.rs

+71-24
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ mod unnecessary_fold;
9393
mod unnecessary_iter_cloned;
9494
mod unnecessary_join;
9595
mod unnecessary_lazy_eval;
96+
mod unnecessary_literal_unwrap;
9697
mod unnecessary_sort_by;
9798
mod unnecessary_to_owned;
9899
mod unwrap_or_else_default;
@@ -273,6 +274,32 @@ declare_clippy_lint! {
273274
"using `.unwrap()` on `Result` or `Option`, which should at least get a better message using `expect()`"
274275
}
275276

277+
declare_clippy_lint! {
278+
/// ### What it does
279+
/// Checks for `.unwrap()` related calls on `Result`s and `Option`s that are constructed.
280+
///
281+
/// ### Why is this bad?
282+
/// It is better to write the value directly without the indirection.
283+
///
284+
/// ### Examples
285+
/// ```rust
286+
/// let val1 = Some(1).unwrap();
287+
/// let val2 = Ok::<_, ()>(1).unwrap();
288+
/// let val3 = Err::<(), _>(1).unwrap_err();
289+
/// ```
290+
///
291+
/// Use instead:
292+
/// ```rust
293+
/// let val1 = 1;
294+
/// let val2 = 1;
295+
/// let val3 = 1;
296+
/// ```
297+
#[clippy::version = "1.69.0"]
298+
pub UNNECESSARY_LITERAL_UNWRAP,
299+
complexity,
300+
"using `unwrap()` related calls on `Result` and `Option` constructors"
301+
}
302+
276303
declare_clippy_lint! {
277304
/// ### What it does
278305
/// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s.
@@ -3349,6 +3376,7 @@ impl_lint_pass!(Methods => [
33493376
SUSPICIOUS_COMMAND_ARG_SPACE,
33503377
CLEAR_WITH_DRAIN,
33513378
MANUAL_NEXT_BACK,
3379+
UNNECESSARY_LITERAL_UNWRAP,
33523380
]);
33533381

33543382
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3606,12 +3634,18 @@ impl Methods {
36063634
case_sensitive_file_extension_comparisons::check(cx, expr, span, recv, arg);
36073635
}
36083636
},
3609-
("expect", [_]) => match method_call(recv) {
3610-
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
3611-
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, span, err_span, &self.msrv),
3612-
_ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
3637+
("expect", [_]) => {
3638+
match method_call(recv) {
3639+
Some(("ok", recv, [], _, _)) => ok_expect::check(cx, expr, recv),
3640+
Some(("err", recv, [], err_span, _)) => err_expect::check(cx, expr, recv, span, err_span, &self.msrv),
3641+
_ => expect_used::check(cx, expr, recv, false, self.allow_expect_in_tests),
3642+
}
3643+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
3644+
},
3645+
("expect_err", [_]) => {
3646+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
3647+
expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests);
36133648
},
3614-
("expect_err", [_]) => expect_used::check(cx, expr, recv, true, self.allow_expect_in_tests),
36153649
("extend", [arg]) => {
36163650
string_extend_chars::check(cx, expr, recv, arg);
36173651
extend_with_drain::check(cx, expr, recv, arg);
@@ -3816,28 +3850,41 @@ impl Methods {
38163850
},
38173851
_ => {},
38183852
}
3853+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
38193854
unwrap_used::check(cx, expr, recv, false, self.allow_unwrap_in_tests);
38203855
},
3821-
("unwrap_err", []) => unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests),
3822-
("unwrap_or", [u_arg]) => match method_call(recv) {
3823-
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _, _)) => {
3824-
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
3825-
},
3826-
Some(("map", m_recv, [m_arg], span, _)) => {
3827-
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
3828-
},
3829-
Some(("then_some", t_recv, [t_arg], _, _)) => {
3830-
obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
3831-
},
3832-
_ => {},
3856+
("unwrap_err", []) => {
3857+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
3858+
unwrap_used::check(cx, expr, recv, true, self.allow_unwrap_in_tests);
38333859
},
3834-
("unwrap_or_else", [u_arg]) => match method_call(recv) {
3835-
Some(("map", recv, [map_arg], _, _))
3836-
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
3837-
_ => {
3838-
unwrap_or_else_default::check(cx, expr, recv, u_arg);
3839-
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
3840-
},
3860+
("unwrap_or", [u_arg]) => {
3861+
match method_call(recv) {
3862+
Some((arith @ ("checked_add" | "checked_sub" | "checked_mul"), lhs, [rhs], _, _)) => {
3863+
manual_saturating_arithmetic::check(cx, expr, lhs, rhs, u_arg, &arith["checked_".len()..]);
3864+
},
3865+
Some(("map", m_recv, [m_arg], span, _)) => {
3866+
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span);
3867+
},
3868+
Some(("then_some", t_recv, [t_arg], _, _)) => {
3869+
obfuscated_if_else::check(cx, expr, t_recv, t_arg, u_arg);
3870+
},
3871+
_ => {},
3872+
}
3873+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
3874+
},
3875+
("unwrap_or_default", []) => {
3876+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
3877+
}
3878+
("unwrap_or_else", [u_arg]) => {
3879+
match method_call(recv) {
3880+
Some(("map", recv, [map_arg], _, _))
3881+
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, &self.msrv) => {},
3882+
_ => {
3883+
unwrap_or_else_default::check(cx, expr, recv, u_arg);
3884+
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
3885+
},
3886+
}
3887+
unnecessary_literal_unwrap::check(cx, expr, recv, name, args);
38413888
},
38423889
("zip", [arg]) => {
38433890
if let ExprKind::MethodCall(name, iter_recv, [], _) = recv.kind
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use clippy_utils::{diagnostics::span_lint_and_then, is_res_lang_ctor, last_path_segment, path_res, MaybePath};
2+
use rustc_errors::Applicability;
3+
use rustc_hir as hir;
4+
use rustc_lint::LateContext;
5+
6+
use super::UNNECESSARY_LITERAL_UNWRAP;
7+
8+
fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) -> Option<&'a hir::Ty<'a>> {
9+
let args = args?;
10+
11+
if args.len() <= index {
12+
return None;
13+
}
14+
15+
match args[index] {
16+
hir::GenericArg::Type(ty) => match ty.kind {
17+
hir::TyKind::Infer => None,
18+
_ => Some(ty),
19+
},
20+
_ => None,
21+
}
22+
}
23+
24+
pub(super) fn check(
25+
cx: &LateContext<'_>,
26+
expr: &hir::Expr<'_>,
27+
recv: &hir::Expr<'_>,
28+
method: &str,
29+
args: &[hir::Expr<'_>],
30+
) {
31+
let init = clippy_utils::expr_or_init(cx, recv);
32+
33+
let (constructor, call_args, ty) = if let hir::ExprKind::Call(call, call_args) = init.kind {
34+
let Some(qpath) = call.qpath_opt() else { return };
35+
36+
let args = last_path_segment(qpath).args.map(|args| args.args);
37+
let res = cx.qpath_res(qpath, call.hir_id());
38+
39+
if is_res_lang_ctor(cx, res, hir::LangItem::OptionSome) {
40+
("Some", call_args, get_ty_from_args(args, 0))
41+
} else if is_res_lang_ctor(cx, res, hir::LangItem::ResultOk) {
42+
("Ok", call_args, get_ty_from_args(args, 0))
43+
} else if is_res_lang_ctor(cx, res, hir::LangItem::ResultErr) {
44+
("Err", call_args, get_ty_from_args(args, 1))
45+
} else {
46+
return;
47+
}
48+
} else if is_res_lang_ctor(cx, path_res(cx, init), hir::LangItem::OptionNone) {
49+
let call_args: &[hir::Expr<'_>] = &[];
50+
("None", call_args, None)
51+
} else {
52+
return;
53+
};
54+
55+
let help_message = format!("used `{method}()` on `{constructor}` value");
56+
let suggestion_message = format!("remove the `{constructor}` and `{method}()`");
57+
58+
span_lint_and_then(cx, UNNECESSARY_LITERAL_UNWRAP, expr.span, &help_message, |diag| {
59+
let suggestions = match (constructor, method, ty) {
60+
("None", "unwrap", _) => Some(vec![(expr.span, "panic!()".to_string())]),
61+
("None", "expect", _) => Some(vec![
62+
(expr.span.with_hi(args[0].span.lo()), "panic!(".to_string()),
63+
(expr.span.with_lo(args[0].span.hi()), ")".to_string()),
64+
]),
65+
(_, _, Some(_)) => None,
66+
("Ok", "unwrap_err", None) | ("Err", "unwrap", None) => Some(vec![
67+
(
68+
recv.span.with_hi(call_args[0].span.lo()),
69+
"panic!(\"{:?}\", ".to_string(),
70+
),
71+
(expr.span.with_lo(call_args[0].span.hi()), ")".to_string()),
72+
]),
73+
("Ok", "expect_err", None) | ("Err", "expect", None) => Some(vec![
74+
(
75+
recv.span.with_hi(call_args[0].span.lo()),
76+
"panic!(\"{1}: {:?}\", ".to_string(),
77+
),
78+
(call_args[0].span.with_lo(args[0].span.lo()), ", ".to_string()),
79+
]),
80+
(_, _, None) => Some(vec![
81+
(recv.span.with_hi(call_args[0].span.lo()), String::new()),
82+
(expr.span.with_lo(call_args[0].span.hi()), String::new()),
83+
]),
84+
};
85+
86+
match (init.span == recv.span, suggestions) {
87+
(true, Some(suggestions)) => {
88+
diag.multipart_suggestion(suggestion_message, suggestions, Applicability::MachineApplicable);
89+
},
90+
_ => {
91+
diag.span_help(init.span, suggestion_message);
92+
},
93+
}
94+
});
95+
}

tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.fixed

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@run-rustfix
22
#![warn(clippy::uninlined_format_args)]
3+
#![allow(clippy::unnecessary_literal_unwrap)]
34

45
fn main() {
56
let local_i32 = 1;

tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@run-rustfix
22
#![warn(clippy::uninlined_format_args)]
3+
#![allow(clippy::unnecessary_literal_unwrap)]
34

45
fn main() {
56
let local_i32 = 1;

tests/ui-toml/allow_mixed_uninlined_format_args/uninlined_format_args.stderr

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: variables can be used directly in the `format!` string
2-
--> $DIR/uninlined_format_args.rs:9:5
2+
--> $DIR/uninlined_format_args.rs:10:5
33
|
44
LL | println!("val='{}'", local_i32);
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -12,7 +12,7 @@ LL + println!("val='{local_i32}'");
1212
|
1313

1414
error: variables can be used directly in the `format!` string
15-
--> $DIR/uninlined_format_args.rs:10:5
15+
--> $DIR/uninlined_format_args.rs:11:5
1616
|
1717
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
1818
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -24,7 +24,7 @@ LL + println!("Hello {} is {local_f64:.local_i32$}", "x");
2424
|
2525

2626
error: literal with an empty format string
27-
--> $DIR/uninlined_format_args.rs:10:35
27+
--> $DIR/uninlined_format_args.rs:11:35
2828
|
2929
LL | println!("Hello {} is {:.*}", "x", local_i32, local_f64);
3030
| ^^^
@@ -37,7 +37,7 @@ LL + println!("Hello x is {:.*}", local_i32, local_f64);
3737
|
3838

3939
error: variables can be used directly in the `format!` string
40-
--> $DIR/uninlined_format_args.rs:11:5
40+
--> $DIR/uninlined_format_args.rs:12:5
4141
|
4242
LL | println!("Hello {} is {:.*}", local_i32, 5, local_f64);
4343
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -49,7 +49,7 @@ LL + println!("Hello {local_i32} is {local_f64:.*}", 5);
4949
|
5050

5151
error: variables can be used directly in the `format!` string
52-
--> $DIR/uninlined_format_args.rs:12:5
52+
--> $DIR/uninlined_format_args.rs:13:5
5353
|
5454
LL | println!("Hello {} is {2:.*}", local_i32, 5, local_f64);
5555
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -61,7 +61,7 @@ LL + println!("Hello {local_i32} is {local_f64:.*}", 5);
6161
|
6262

6363
error: variables can be used directly in the `format!` string
64-
--> $DIR/uninlined_format_args.rs:13:5
64+
--> $DIR/uninlined_format_args.rs:14:5
6565
|
6666
LL | println!("{}, {}", local_i32, local_opt.unwrap());
6767
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui-toml/arithmetic_side_effects_allowed/arithmetic_side_effects_allowed.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::arithmetic_side_effects)]
2+
#![allow(clippy::unnecessary_literal_unwrap)]
23

34
use core::ops::{Add, Neg};
45

tests/ui-toml/arithmetic_side_effects_allowed/arithmetic_side_effects_allowed.stderr

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,55 @@
11
error: arithmetic operation that can potentially result in unexpected side-effects
2-
--> $DIR/arithmetic_side_effects_allowed.rs:68:13
2+
--> $DIR/arithmetic_side_effects_allowed.rs:69:13
33
|
44
LL | let _ = Baz + Baz;
55
| ^^^^^^^^^
66
|
77
= note: `-D clippy::arithmetic-side-effects` implied by `-D warnings`
88

99
error: arithmetic operation that can potentially result in unexpected side-effects
10-
--> $DIR/arithmetic_side_effects_allowed.rs:79:13
10+
--> $DIR/arithmetic_side_effects_allowed.rs:80:13
1111
|
1212
LL | let _ = 1i32 + Baz;
1313
| ^^^^^^^^^^
1414

1515
error: arithmetic operation that can potentially result in unexpected side-effects
16-
--> $DIR/arithmetic_side_effects_allowed.rs:82:13
16+
--> $DIR/arithmetic_side_effects_allowed.rs:83:13
1717
|
1818
LL | let _ = 1i64 + Foo;
1919
| ^^^^^^^^^^
2020

2121
error: arithmetic operation that can potentially result in unexpected side-effects
22-
--> $DIR/arithmetic_side_effects_allowed.rs:86:13
22+
--> $DIR/arithmetic_side_effects_allowed.rs:87:13
2323
|
2424
LL | let _ = 1i64 + Baz;
2525
| ^^^^^^^^^^
2626

2727
error: arithmetic operation that can potentially result in unexpected side-effects
28-
--> $DIR/arithmetic_side_effects_allowed.rs:97:13
28+
--> $DIR/arithmetic_side_effects_allowed.rs:98:13
2929
|
3030
LL | let _ = Baz + 1i32;
3131
| ^^^^^^^^^^
3232

3333
error: arithmetic operation that can potentially result in unexpected side-effects
34-
--> $DIR/arithmetic_side_effects_allowed.rs:100:13
34+
--> $DIR/arithmetic_side_effects_allowed.rs:101:13
3535
|
3636
LL | let _ = Foo + 1i64;
3737
| ^^^^^^^^^^
3838

3939
error: arithmetic operation that can potentially result in unexpected side-effects
40-
--> $DIR/arithmetic_side_effects_allowed.rs:104:13
40+
--> $DIR/arithmetic_side_effects_allowed.rs:105:13
4141
|
4242
LL | let _ = Baz + 1i64;
4343
| ^^^^^^^^^^
4444

4545
error: arithmetic operation that can potentially result in unexpected side-effects
46-
--> $DIR/arithmetic_side_effects_allowed.rs:113:13
46+
--> $DIR/arithmetic_side_effects_allowed.rs:114:13
4747
|
4848
LL | let _ = -Bar;
4949
| ^^^^
5050

5151
error: arithmetic operation that can potentially result in unexpected side-effects
52-
--> $DIR/arithmetic_side_effects_allowed.rs:115:13
52+
--> $DIR/arithmetic_side_effects_allowed.rs:116:13
5353
|
5454
LL | let _ = -Baz;
5555
| ^^^^

tests/ui-toml/expect_used/expect_used.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//@compile-flags: --test
22
#![warn(clippy::expect_used)]
3+
#![allow(clippy::unnecessary_literal_unwrap)]
34

45
fn expect_option() {
56
let opt = Some(0);

0 commit comments

Comments
 (0)