Skip to content

Commit 4e9caa3

Browse files
committed
question_mark: suggest as_ref/as_mut if ref binding is used
1 parent a25cbd5 commit 4e9caa3

File tree

4 files changed

+160
-3
lines changed

4 files changed

+160
-3
lines changed

Diff for: clippy_lints/src/question_mark.rs

+41-2
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,47 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
145145
{
146146
let mut applicability = Applicability::MaybeIncorrect;
147147
let init_expr_str = snippet_with_applicability(cx, init_expr.span, "..", &mut applicability);
148-
let receiver_str = snippet_with_applicability(cx, inner_pat.span, "..", &mut applicability);
149-
let sugg = format!("let {receiver_str} = {init_expr_str}?;",);
148+
// Take care when binding is `ref`
149+
let sugg = if let PatKind::Binding(
150+
BindingMode(ByRef::Yes(ref_mutability), binding_mutability),
151+
_hir_id,
152+
ident,
153+
subpattern,
154+
) = inner_pat.kind
155+
{
156+
let (from_method, replace_to) = match ref_mutability {
157+
Mutability::Mut => (".as_mut()", "&mut "),
158+
Mutability::Not => (".as_ref()", "&"),
159+
};
160+
161+
let mutability_str = match binding_mutability {
162+
Mutability::Mut => "mut ",
163+
Mutability::Not => "",
164+
};
165+
166+
// Handle subpattern (@ subpattern)
167+
let maybe_subpattern = match subpattern {
168+
Some(Pat {
169+
kind: PatKind::Binding(BindingMode(ByRef::Yes(_), _), _, subident, None),
170+
..
171+
}) => {
172+
// avoid `&ref`
173+
// note that, because you can't have aliased, mutable references, we don't have to worry about
174+
// the outer and inner mutability being different
175+
format!(" @ {subident}")
176+
},
177+
Some(subpattern) => {
178+
let substr = snippet_with_applicability(cx, subpattern.span, "..", &mut applicability);
179+
format!(" @ {replace_to}{substr}")
180+
},
181+
None => String::new(),
182+
};
183+
184+
format!("let {mutability_str}{ident}{maybe_subpattern} = {init_expr_str}{from_method}?;")
185+
} else {
186+
let receiver_str = snippet_with_applicability(cx, inner_pat.span, "..", &mut applicability);
187+
format!("let {receiver_str} = {init_expr_str}?;")
188+
};
150189
span_lint_and_sugg(
151190
cx,
152191
QUESTION_MARK,

Diff for: tests/ui/question_mark.fixed

+34
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,37 @@ fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
375375
//~^^^ question_mark
376376
Some(())
377377
}
378+
379+
struct StructWithOptionString {
380+
opt_x: Option<String>,
381+
}
382+
383+
struct WrapperStructWithString(String);
384+
385+
#[allow(clippy::disallowed_names)]
386+
fn issue_13417(foo: &mut StructWithOptionString) -> Option<String> {
387+
let x = foo.opt_x.as_ref()?;
388+
//~^^^ question_mark
389+
let opt_y = Some(x.clone());
390+
std::mem::replace(&mut foo.opt_x, opt_y)
391+
}
392+
393+
#[allow(clippy::disallowed_names)]
394+
fn issue_13417_mut(foo: &mut StructWithOptionString) -> Option<String> {
395+
let x = foo.opt_x.as_mut()?;
396+
//~^^^ question_mark
397+
let opt_y = Some(x.clone());
398+
std::mem::replace(&mut foo.opt_x, opt_y)
399+
}
400+
401+
#[allow(clippy::disallowed_names)]
402+
#[allow(unused)]
403+
fn issue_13417_weirder(foo: &mut StructWithOptionString, mut bar: Option<WrapperStructWithString>) -> Option<()> {
404+
let x @ y = foo.opt_x.as_ref()?;
405+
//~^^^ question_mark
406+
let x @ &WrapperStructWithString(_) = bar.as_ref()?;
407+
//~^^^ question_mark
408+
let x @ &mut WrapperStructWithString(_) = bar.as_mut()?;
409+
//~^^^ question_mark
410+
Some(())
411+
}

Diff for: tests/ui/question_mark.rs

+44
Original file line numberDiff line numberDiff line change
@@ -452,3 +452,47 @@ fn issue12412(foo: &Foo, bar: &Bar) -> Option<()> {
452452
//~^^^ question_mark
453453
Some(())
454454
}
455+
456+
struct StructWithOptionString {
457+
opt_x: Option<String>,
458+
}
459+
460+
struct WrapperStructWithString(String);
461+
462+
#[allow(clippy::disallowed_names)]
463+
fn issue_13417(foo: &mut StructWithOptionString) -> Option<String> {
464+
let Some(ref x) = foo.opt_x else {
465+
return None;
466+
};
467+
//~^^^ question_mark
468+
let opt_y = Some(x.clone());
469+
std::mem::replace(&mut foo.opt_x, opt_y)
470+
}
471+
472+
#[allow(clippy::disallowed_names)]
473+
fn issue_13417_mut(foo: &mut StructWithOptionString) -> Option<String> {
474+
let Some(ref mut x) = foo.opt_x else {
475+
return None;
476+
};
477+
//~^^^ question_mark
478+
let opt_y = Some(x.clone());
479+
std::mem::replace(&mut foo.opt_x, opt_y)
480+
}
481+
482+
#[allow(clippy::disallowed_names)]
483+
#[allow(unused)]
484+
fn issue_13417_weirder(foo: &mut StructWithOptionString, mut bar: Option<WrapperStructWithString>) -> Option<()> {
485+
let Some(ref x @ ref y) = foo.opt_x else {
486+
return None;
487+
};
488+
//~^^^ question_mark
489+
let Some(ref x @ WrapperStructWithString(_)) = bar else {
490+
return None;
491+
};
492+
//~^^^ question_mark
493+
let Some(ref mut x @ WrapperStructWithString(_)) = bar else {
494+
return None;
495+
};
496+
//~^^^ question_mark
497+
Some(())
498+
}

Diff for: tests/ui/question_mark.stderr

+41-1
Original file line numberDiff line numberDiff line change
@@ -215,5 +215,45 @@ LL | | return None;
215215
LL | | };
216216
| |______^ help: replace it with: `let v = bar.foo.owned.clone()?;`
217217

218-
error: aborting due to 22 previous errors
218+
error: this `let...else` may be rewritten with the `?` operator
219+
--> tests/ui/question_mark.rs:464:5
220+
|
221+
LL | / let Some(ref x) = foo.opt_x else {
222+
LL | | return None;
223+
LL | | };
224+
| |______^ help: replace it with: `let x = foo.opt_x.as_ref()?;`
225+
226+
error: this `let...else` may be rewritten with the `?` operator
227+
--> tests/ui/question_mark.rs:474:5
228+
|
229+
LL | / let Some(ref mut x) = foo.opt_x else {
230+
LL | | return None;
231+
LL | | };
232+
| |______^ help: replace it with: `let x = foo.opt_x.as_mut()?;`
233+
234+
error: this `let...else` may be rewritten with the `?` operator
235+
--> tests/ui/question_mark.rs:485:5
236+
|
237+
LL | / let Some(ref x @ ref y) = foo.opt_x else {
238+
LL | | return None;
239+
LL | | };
240+
| |______^ help: replace it with: `let x @ y = foo.opt_x.as_ref()?;`
241+
242+
error: this `let...else` may be rewritten with the `?` operator
243+
--> tests/ui/question_mark.rs:489:5
244+
|
245+
LL | / let Some(ref x @ WrapperStructWithString(_)) = bar else {
246+
LL | | return None;
247+
LL | | };
248+
| |______^ help: replace it with: `let x @ &WrapperStructWithString(_) = bar.as_ref()?;`
249+
250+
error: this `let...else` may be rewritten with the `?` operator
251+
--> tests/ui/question_mark.rs:493:5
252+
|
253+
LL | / let Some(ref mut x @ WrapperStructWithString(_)) = bar else {
254+
LL | | return None;
255+
LL | | };
256+
| |______^ help: replace it with: `let x @ &mut WrapperStructWithString(_) = bar.as_mut()?;`
257+
258+
error: aborting due to 27 previous errors
219259

0 commit comments

Comments
 (0)