Skip to content

Commit 0c592df

Browse files
fix(match_as_ref): suggest as_ref when the reference needs to be cast (#15934)
I first implemented the naive version, which just talks about, and suggests using, `Option::as_ref`. But the resulting error message sounded a bit misleading -- after all, the manual implementation was factually that of `Option::as_mut`, and we only suggest `as_ref` as a means of downcasting. So then I added another help message to mention the need for reference downcasting, which.. still looks awkward. Honestly it might be the easiest to hide the reference downcasting into the `.map` after all, so that it encapsulates _all_ the casting needed following the switch to `as_ref`/`as_mut`. changelog: [`match_as_ref`]: suggest `as_ref` when the reference needs to be cast r? @samueltardieu
2 parents 2bb3d0c + 22bdd9f commit 0c592df

File tree

4 files changed

+104
-21
lines changed

4 files changed

+104
-21
lines changed

clippy_lints/src/matches/match_as_ref.rs

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,30 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
2121
} else {
2222
None
2323
}
24+
&& let output_ty = cx.typeck_results().expr_ty(expr)
25+
&& let input_ty = cx.typeck_results().expr_ty(ex)
26+
&& let Some(input_ty) = option_arg_ty(cx, input_ty)
27+
&& let Some(output_ty) = option_arg_ty(cx, output_ty)
28+
&& let ty::Ref(_, output_ty, output_mutbl) = *output_ty.kind()
2429
{
2530
let method = match arm_ref_mutbl {
2631
Mutability::Not => "as_ref",
2732
Mutability::Mut => "as_mut",
2833
};
2934

30-
let output_ty = cx.typeck_results().expr_ty(expr);
31-
let input_ty = cx.typeck_results().expr_ty(ex);
35+
// ```
36+
// let _: Option<&T> = match opt {
37+
// Some(ref mut t) => Some(t),
38+
// None => None,
39+
// };
40+
// ```
41+
// We need to suggest `t.as_ref()` in order downcast the reference from `&mut` to `&`.
42+
// We may or may not need to cast the type as well, for which we'd need `.map()`, and that could
43+
// theoretically take care of the reference downcasting as well, but we chose to keep these two
44+
// operations separate
45+
let need_as_ref = arm_ref_mutbl == Mutability::Mut && output_mutbl == Mutability::Not;
3246

33-
let cast = if let Some(input_ty) = option_arg_ty(cx, input_ty)
34-
&& let Some(output_ty) = option_arg_ty(cx, output_ty)
35-
&& let ty::Ref(_, output_ty, _) = *output_ty.kind()
36-
&& input_ty != output_ty
37-
{
38-
".map(|x| x as _)"
39-
} else {
40-
""
41-
};
47+
let cast = if input_ty == output_ty { "" } else { ".map(|x| x as _)" };
4248

4349
let mut applicability = Applicability::MachineApplicable;
4450
span_lint_and_then(
@@ -47,15 +53,28 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
4753
expr.span,
4854
format!("manual implementation of `Option::{method}`"),
4955
|diag| {
50-
diag.span_suggestion_verbose(
51-
expr.span,
52-
format!("use `Option::{method}()` directly"),
53-
format!(
54-
"{}.{method}(){cast}",
55-
Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(),
56-
),
57-
applicability,
58-
);
56+
if need_as_ref {
57+
diag.note("but the type is coerced to a non-mutable reference, and so `as_ref` can used instead");
58+
diag.span_suggestion_verbose(
59+
expr.span,
60+
"use `Option::as_ref()`",
61+
format!(
62+
"{}.as_ref(){cast}",
63+
Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(),
64+
),
65+
applicability,
66+
);
67+
} else {
68+
diag.span_suggestion_verbose(
69+
expr.span,
70+
format!("use `Option::{method}()` directly"),
71+
format!(
72+
"{}.{method}(){cast}",
73+
Sugg::hir_with_applicability(cx, ex, "_", &mut applicability).maybe_paren(),
74+
),
75+
applicability,
76+
);
77+
}
5978
},
6079
);
6180
}

tests/ui/match_as_ref.fixed

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,9 @@ fn recv_requiring_parens() {
8484

8585
let _ = (!S).as_ref();
8686
}
87+
88+
fn issue15932() {
89+
let _: Option<&u32> = Some(0).as_ref();
90+
91+
let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _);
92+
}

tests/ui/match_as_ref.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,17 @@ fn recv_requiring_parens() {
100100
Some(ref v) => Some(v),
101101
};
102102
}
103+
104+
fn issue15932() {
105+
let _: Option<&u32> = match Some(0) {
106+
//~^ match_as_ref
107+
None => None,
108+
Some(ref mut v) => Some(v),
109+
};
110+
111+
let _: Option<&dyn std::fmt::Debug> = match Some(0) {
112+
//~^ match_as_ref
113+
None => None,
114+
Some(ref mut v) => Some(v),
115+
};
116+
}

tests/ui/match_as_ref.stderr

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,49 @@ LL - };
8383
LL + let _ = (!S).as_ref();
8484
|
8585

86-
error: aborting due to 4 previous errors
86+
error: manual implementation of `Option::as_mut`
87+
--> tests/ui/match_as_ref.rs:105:27
88+
|
89+
LL | let _: Option<&u32> = match Some(0) {
90+
| ___________________________^
91+
LL | |
92+
LL | | None => None,
93+
LL | | Some(ref mut v) => Some(v),
94+
LL | | };
95+
| |_____^
96+
|
97+
= note: but the type is coerced to a non-mutable reference, and so `as_ref` can used instead
98+
help: use `Option::as_ref()`
99+
|
100+
LL - let _: Option<&u32> = match Some(0) {
101+
LL -
102+
LL - None => None,
103+
LL - Some(ref mut v) => Some(v),
104+
LL - };
105+
LL + let _: Option<&u32> = Some(0).as_ref();
106+
|
107+
108+
error: manual implementation of `Option::as_mut`
109+
--> tests/ui/match_as_ref.rs:111:43
110+
|
111+
LL | let _: Option<&dyn std::fmt::Debug> = match Some(0) {
112+
| ___________________________________________^
113+
LL | |
114+
LL | | None => None,
115+
LL | | Some(ref mut v) => Some(v),
116+
LL | | };
117+
| |_____^
118+
|
119+
= note: but the type is coerced to a non-mutable reference, and so `as_ref` can used instead
120+
help: use `Option::as_ref()`
121+
|
122+
LL - let _: Option<&dyn std::fmt::Debug> = match Some(0) {
123+
LL -
124+
LL - None => None,
125+
LL - Some(ref mut v) => Some(v),
126+
LL - };
127+
LL + let _: Option<&dyn std::fmt::Debug> = Some(0).as_ref().map(|x| x as _);
128+
|
129+
130+
error: aborting due to 6 previous errors
87131

0 commit comments

Comments
 (0)