Skip to content

Commit 28443e6

Browse files
committed
Auto merge of rust-lang#12070 - roife:fix/issue-12034, r=Centri3
Fix issue rust-lang#12034: add autofixes for unnecessary_fallible_conversions fixes rust-lang#12034 Currently, the `unnecessary_fallible_conversions` lint was capable of autofixing expressions like `0i32.try_into().unwrap()`. However, it couldn't autofix expressions in the form of `i64::try_from(0i32).unwrap()` or `<i64 as TryFrom<i32>>::try_from(0).unwrap()`. This pull request extends the functionality to correctly autofix these latter forms as well. changelog: [`unnecessary_fallible_conversions`]: Add autofixes for more forms
2 parents fb398a5 + 3c76b2c commit 28443e6

4 files changed

+293
-46
lines changed

clippy_lints/src/methods/unnecessary_fallible_conversions.rs

+100-41
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,79 @@ use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::get_parent_expr;
33
use clippy_utils::ty::implements_trait;
44
use rustc_errors::Applicability;
5-
use rustc_hir::{Expr, ExprKind};
5+
use rustc_hir::{Expr, ExprKind, QPath};
66
use rustc_lint::LateContext;
77
use rustc_middle::ty;
88
use rustc_middle::ty::print::with_forced_trimmed_paths;
99
use rustc_span::{sym, Span};
1010

1111
use super::UNNECESSARY_FALLIBLE_CONVERSIONS;
1212

13+
#[derive(Copy, Clone)]
14+
enum SpansKind {
15+
TraitFn { trait_span: Span, fn_span: Span },
16+
Fn { fn_span: Span },
17+
}
18+
1319
/// What function is being called and whether that call is written as a method call or a function
1420
/// call
1521
#[derive(Copy, Clone)]
1622
#[expect(clippy::enum_variant_names)]
1723
enum FunctionKind {
1824
/// `T::try_from(U)`
19-
TryFromFunction,
25+
TryFromFunction(Option<SpansKind>),
2026
/// `t.try_into()`
2127
TryIntoMethod,
2228
/// `U::try_into(t)`
23-
TryIntoFunction,
29+
TryIntoFunction(Option<SpansKind>),
30+
}
31+
32+
impl FunctionKind {
33+
fn appl_sugg(&self, parent_unwrap_call: Option<Span>, primary_span: Span) -> (Applicability, Vec<(Span, String)>) {
34+
let Some(unwrap_span) = parent_unwrap_call else {
35+
return (Applicability::Unspecified, self.default_sugg(primary_span));
36+
};
37+
38+
match &self {
39+
FunctionKind::TryFromFunction(None) | FunctionKind::TryIntoFunction(None) => {
40+
(Applicability::Unspecified, self.default_sugg(primary_span))
41+
},
42+
_ => (
43+
Applicability::MachineApplicable,
44+
self.machine_applicable_sugg(primary_span, unwrap_span),
45+
),
46+
}
47+
}
48+
49+
fn default_sugg(&self, primary_span: Span) -> Vec<(Span, String)> {
50+
let replacement = match *self {
51+
FunctionKind::TryFromFunction(_) => "From::from",
52+
FunctionKind::TryIntoFunction(_) => "Into::into",
53+
FunctionKind::TryIntoMethod => "into",
54+
};
55+
56+
vec![(primary_span, String::from(replacement))]
57+
}
58+
59+
fn machine_applicable_sugg(&self, primary_span: Span, unwrap_span: Span) -> Vec<(Span, String)> {
60+
let (trait_name, fn_name) = match self {
61+
FunctionKind::TryFromFunction(_) => ("From".to_owned(), "from".to_owned()),
62+
FunctionKind::TryIntoFunction(_) | FunctionKind::TryIntoMethod => ("Into".to_owned(), "into".to_owned()),
63+
};
64+
65+
let mut sugg = match *self {
66+
FunctionKind::TryFromFunction(Some(spans)) | FunctionKind::TryIntoFunction(Some(spans)) => match spans {
67+
SpansKind::TraitFn { trait_span, fn_span } => vec![(trait_span, trait_name), (fn_span, fn_name)],
68+
SpansKind::Fn { fn_span } => vec![(fn_span, fn_name)],
69+
},
70+
FunctionKind::TryIntoMethod => vec![(primary_span, fn_name)],
71+
// Or the suggestion is not machine-applicable
72+
_ => unreachable!(),
73+
};
74+
75+
sugg.push((unwrap_span, String::new()));
76+
sugg
77+
}
2478
}
2579

2680
fn check<'tcx>(
@@ -35,8 +89,8 @@ fn check<'tcx>(
3589
&& self_ty != other_ty
3690
&& let Some(self_ty) = self_ty.as_type()
3791
&& let Some(from_into_trait) = cx.tcx.get_diagnostic_item(match kind {
38-
FunctionKind::TryFromFunction => sym::From,
39-
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction => sym::Into,
92+
FunctionKind::TryFromFunction(_) => sym::From,
93+
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction(_) => sym::Into,
4094
})
4195
// If `T: TryFrom<U>` and `T: From<U>` both exist, then that means that the `TryFrom`
4296
// _must_ be from the blanket impl and cannot have been manually implemented
@@ -45,49 +99,37 @@ fn check<'tcx>(
4599
&& implements_trait(cx, self_ty, from_into_trait, &[other_ty])
46100
&& let Some(other_ty) = other_ty.as_type()
47101
{
102+
// Extend the span to include the unwrap/expect call:
103+
// `foo.try_into().expect("..")`
104+
// ^^^^^^^^^^^^^^^^^^^^^^^
105+
//
106+
// `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
107+
// so that can be machine-applicable
48108
let parent_unwrap_call = get_parent_expr(cx, expr).and_then(|parent| {
49109
if let ExprKind::MethodCall(path, .., span) = parent.kind
50110
&& let sym::unwrap | sym::expect = path.ident.name
51111
{
52-
Some(span)
112+
// include `.` before `unwrap`/`expect`
113+
Some(span.with_lo(expr.span.hi()))
53114
} else {
54115
None
55116
}
56117
});
57-
let (source_ty, target_ty, sugg, span, applicability) = match kind {
58-
FunctionKind::TryIntoMethod if let Some(unwrap_span) = parent_unwrap_call => {
59-
// Extend the span to include the unwrap/expect call:
60-
// `foo.try_into().expect("..")`
61-
// ^^^^^^^^^^^^^^^^^^^^^^^
62-
//
63-
// `try_into().unwrap()` specifically can be trivially replaced with just `into()`,
64-
// so that can be machine-applicable
65-
66-
(
67-
self_ty,
68-
other_ty,
69-
"into()",
70-
primary_span.with_hi(unwrap_span.hi()),
71-
Applicability::MachineApplicable,
72-
)
73-
},
74-
FunctionKind::TryFromFunction => (
75-
other_ty,
76-
self_ty,
77-
"From::from",
78-
primary_span,
79-
Applicability::Unspecified,
80-
),
81-
FunctionKind::TryIntoFunction => (
82-
self_ty,
83-
other_ty,
84-
"Into::into",
85-
primary_span,
86-
Applicability::Unspecified,
87-
),
88-
FunctionKind::TryIntoMethod => (self_ty, other_ty, "into", primary_span, Applicability::Unspecified),
118+
119+
// If there is an unwrap/expect call, extend the span to include the call
120+
let span = if let Some(unwrap_call) = parent_unwrap_call {
121+
primary_span.with_hi(unwrap_call.hi())
122+
} else {
123+
primary_span
124+
};
125+
126+
let (source_ty, target_ty) = match kind {
127+
FunctionKind::TryIntoMethod | FunctionKind::TryIntoFunction(_) => (self_ty, other_ty),
128+
FunctionKind::TryFromFunction(_) => (other_ty, self_ty),
89129
};
90130

131+
let (applicability, sugg) = kind.appl_sugg(parent_unwrap_call, primary_span);
132+
91133
span_lint_and_then(
92134
cx,
93135
UNNECESSARY_FALLIBLE_CONVERSIONS,
@@ -97,7 +139,7 @@ fn check<'tcx>(
97139
with_forced_trimmed_paths!({
98140
diag.note(format!("converting `{source_ty}` to `{target_ty}` cannot fail"));
99141
});
100-
diag.span_suggestion(span, "use", sugg, applicability);
142+
diag.multipart_suggestion("use", sugg, applicability);
101143
},
102144
);
103145
}
@@ -125,13 +167,30 @@ pub(super) fn check_function(cx: &LateContext<'_>, expr: &Expr<'_>, callee: &Exp
125167
&& let Some(item_def_id) = cx.qpath_res(qpath, callee.hir_id).opt_def_id()
126168
&& let Some(trait_def_id) = cx.tcx.trait_of_item(item_def_id)
127169
{
170+
let qpath_spans = match qpath {
171+
QPath::Resolved(_, path) => {
172+
if let [trait_seg, fn_seg] = path.segments {
173+
Some(SpansKind::TraitFn {
174+
trait_span: trait_seg.ident.span,
175+
fn_span: fn_seg.ident.span,
176+
})
177+
} else {
178+
None
179+
}
180+
},
181+
QPath::TypeRelative(_, seg) => Some(SpansKind::Fn {
182+
fn_span: seg.ident.span,
183+
}),
184+
QPath::LangItem(_, _) => unreachable!("`TryFrom` and `TryInto` are not lang items"),
185+
};
186+
128187
check(
129188
cx,
130189
expr,
131190
cx.typeck_results().node_args(callee.hir_id),
132191
match cx.tcx.get_diagnostic_name(trait_def_id) {
133-
Some(sym::TryFrom) => FunctionKind::TryFromFunction,
134-
Some(sym::TryInto) => FunctionKind::TryIntoFunction,
192+
Some(sym::TryFrom) => FunctionKind::TryFromFunction(qpath_spans),
193+
Some(sym::TryInto) => FunctionKind::TryIntoFunction(qpath_spans),
135194
_ => return,
136195
},
137196
callee.span,
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
#![warn(clippy::unnecessary_fallible_conversions)]
22

33
fn main() {
4+
// --- TryFromMethod `T::try_from(u)` ---
5+
46
let _: i64 = 0i32.into();
7+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
8+
59
let _: i64 = 0i32.into();
10+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
11+
12+
// --- TryFromFunction `T::try_from(U)` ---
13+
14+
let _ = i64::from(0i32);
15+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
16+
17+
let _ = i64::from(0i32);
18+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
19+
20+
// --- TryIntoFunction `U::try_into(t)` ---
21+
22+
let _: i64 = i32::into(0);
23+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
24+
25+
let _: i64 = i32::into(0i32);
26+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
27+
28+
// --- TryFromFunction `<T as TryFrom<U>>::try_from(U)` ---
29+
30+
let _ = <i64 as From<i32>>::from(0);
31+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
32+
33+
let _ = <i64 as From<i32>>::from(0);
34+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
35+
36+
// --- TryIntoFunction `<U as TryInto<_>>::try_into(U)` ---
37+
38+
let _: i64 = <i32 as Into<_>>::into(0);
39+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
40+
41+
let _: i64 = <i32 as Into<_>>::into(0);
42+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
643
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,43 @@
11
#![warn(clippy::unnecessary_fallible_conversions)]
22

33
fn main() {
4+
// --- TryFromMethod `T::try_from(u)` ---
5+
46
let _: i64 = 0i32.try_into().unwrap();
7+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
8+
59
let _: i64 = 0i32.try_into().expect("can't happen");
10+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
11+
12+
// --- TryFromFunction `T::try_from(U)` ---
13+
14+
let _ = i64::try_from(0i32).unwrap();
15+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
16+
17+
let _ = i64::try_from(0i32).expect("can't happen");
18+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
19+
20+
// --- TryIntoFunction `U::try_into(t)` ---
21+
22+
let _: i64 = i32::try_into(0).unwrap();
23+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
24+
25+
let _: i64 = i32::try_into(0i32).expect("can't happen");
26+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
27+
28+
// --- TryFromFunction `<T as TryFrom<U>>::try_from(U)` ---
29+
30+
let _ = <i64 as TryFrom<i32>>::try_from(0).unwrap();
31+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
32+
33+
let _ = <i64 as TryFrom<i32>>::try_from(0).expect("can't happen");
34+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
35+
36+
// --- TryIntoFunction `<U as TryInto<_>>::try_into(U)` ---
37+
38+
let _: i64 = <i32 as TryInto<_>>::try_into(0).unwrap();
39+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
40+
41+
let _: i64 = <i32 as TryInto<_>>::try_into(0).expect("can't happen");
42+
//~^ ERROR: use of a fallible conversion when an infallible one could be used
643
}

0 commit comments

Comments
 (0)