Skip to content

Commit 35e6057

Browse files
committed
needless_pass_by_value: reference the innermost Option content
If types such as `Option<Option<String>>` are not used by value, then `Option<Option<&String>>` will be suggested, instead of `Option<&Option<String>>`.
1 parent 8f280ff commit 35e6057

File tree

4 files changed

+83
-17
lines changed

4 files changed

+83
-17
lines changed

clippy_lints/src/needless_pass_by_value.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::is_self;
32
use clippy_utils::ptr::get_spans;
43
use clippy_utils::source::{SpanRangeExt, snippet};
54
use clippy_utils::ty::{
65
implements_trait, implements_trait_with_env_from_iter, is_copy, is_type_diagnostic_item, is_type_lang_item,
76
};
7+
use clippy_utils::{is_self, peel_hir_ty_options};
88
use rustc_abi::ExternAbi;
99
use rustc_errors::{Applicability, Diag};
1010
use rustc_hir::intravisit::FnKind;
@@ -279,18 +279,13 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
279279
}
280280
}
281281

282-
let suggestion = if is_type_diagnostic_item(cx, ty, sym::Option)
283-
&& let snip = snippet(cx, input.span, "_")
284-
&& let Some((first, rest)) = snip.split_once('<')
285-
{
286-
format!("{first}<&{rest}")
287-
} else {
288-
format!("&{}", snippet(cx, input.span, "_"))
289-
};
282+
let inner_input = peel_hir_ty_options(cx, input);
283+
let before_span = input.span.until(inner_input.span);
284+
let after_span = before_span.shrink_to_hi().to(input.span.shrink_to_hi());
290285
diag.span_suggestion(
291286
input.span,
292287
"consider taking a reference instead",
293-
suggestion,
288+
format!("{}&{}", snippet(cx, before_span, "_"), snippet(cx, after_span, "_")),
294289
Applicability::MaybeIncorrect,
295290
);
296291
};

clippy_utils/src/lib.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ use rustc_hir::hir_id::{HirIdMap, HirIdSet};
107107
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr};
108108
use rustc_hir::{
109109
self as hir, Arm, BindingMode, Block, BlockCheckMode, Body, ByRef, Closure, ConstArgKind, ConstContext,
110-
Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArgs, HirId, Impl, ImplItem, ImplItemKind,
111-
ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode, Param, Pat,
112-
PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitFn, TraitItem, TraitItemKind,
113-
TraitItemRef, TraitRef, TyKind, UnOp, def,
110+
Destination, Expr, ExprField, ExprKind, FnDecl, FnRetTy, GenericArg, GenericArgs, HirId, Impl, ImplItem,
111+
ImplItemKind, ImplItemRef, Item, ItemKind, LangItem, LetStmt, MatchSource, Mutability, Node, OwnerId, OwnerNode,
112+
Param, Pat, PatExpr, PatExprKind, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, TraitFn, TraitItem,
113+
TraitItemKind, TraitItemRef, TraitRef, TyKind, UnOp, def,
114114
};
115115
use rustc_lexer::{TokenKind, tokenize};
116116
use rustc_lint::{LateContext, Level, Lint, LintContext};
@@ -435,7 +435,7 @@ pub fn qpath_generic_tys<'tcx>(qpath: &QPath<'tcx>) -> impl Iterator<Item = &'tc
435435
.map_or(&[][..], |a| a.args)
436436
.iter()
437437
.filter_map(|a| match a {
438-
hir::GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
438+
GenericArg::Type(ty) => Some(ty.as_unambig_ty()),
439439
_ => None,
440440
})
441441
}
@@ -3709,3 +3709,21 @@ pub fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
37093709
true
37103710
}
37113711
}
3712+
3713+
/// Peel `Option<…>` from `hir_ty` as long as the HIR name is `Option` and it corresponds to the
3714+
/// `core::Option<_>` type.
3715+
pub fn peel_hir_ty_options<'tcx>(cx: &LateContext<'tcx>, mut hir_ty: &'tcx hir::Ty<'tcx>) -> &'tcx hir::Ty<'tcx> {
3716+
let Some(option_def_id) = cx.tcx.get_diagnostic_item(sym::Option) else {
3717+
return hir_ty;
3718+
};
3719+
while let TyKind::Path(QPath::Resolved(None, path)) = hir_ty.kind
3720+
&& let Some(segment) = path.segments.last()
3721+
&& segment.ident.name == sym::Option
3722+
&& let Res::Def(DefKind::Enum, def_id) = segment.res
3723+
&& def_id == option_def_id
3724+
&& let [GenericArg::Type(arg_ty)] = segment.args().args
3725+
{
3726+
hir_ty = arg_ty.as_unambig_ty();
3727+
}
3728+
hir_ty
3729+
}

tests/ui/needless_pass_by_value.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,35 @@ fn option_inner_ref(x: Option<String>) {
196196
assert!(x.is_some());
197197
}
198198

199+
mod non_standard {
200+
#[derive(Debug)]
201+
pub struct Option<T>(T);
202+
}
203+
204+
fn non_standard_option(x: non_standard::Option<String>) {
205+
//~^ needless_pass_by_value
206+
dbg!(&x);
207+
}
208+
209+
fn option_by_name(x: Option<std::option::Option<core::option::Option<non_standard::Option<String>>>>) {
210+
//~^ needless_pass_by_value
211+
dbg!(&x);
212+
}
213+
214+
type OptStr = Option<String>;
215+
216+
fn non_option(x: OptStr) {
217+
//~^ needless_pass_by_value
218+
dbg!(&x);
219+
}
220+
221+
type Opt<T> = Option<T>;
222+
223+
fn non_option_either(x: Opt<String>) {
224+
//~^ needless_pass_by_value
225+
dbg!(&x);
226+
}
227+
199228
fn main() {
200229
// This should not cause an ICE either
201230
// https://github.com/rust-lang/rust-clippy/issues/3144

tests/ui/needless_pass_by_value.stderr

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ error: this argument is passed by value, but not consumed in the function body
2929
--> tests/ui/needless_pass_by_value.rs:58:18
3030
|
3131
LL | fn test_match(x: Option<Option<String>>, y: Option<Option<String>>) {
32-
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `Option<&Option<String>>`
32+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `Option<Option<&String>>`
3333

3434
error: this argument is passed by value, but not consumed in the function body
3535
--> tests/ui/needless_pass_by_value.rs:73:24
@@ -185,5 +185,29 @@ error: this argument is passed by value, but not consumed in the function body
185185
LL | fn option_inner_ref(x: Option<String>) {
186186
| ^^^^^^^^^^^^^^ help: consider taking a reference instead: `Option<&String>`
187187

188-
error: aborting due to 23 previous errors
188+
error: this argument is passed by value, but not consumed in the function body
189+
--> tests/ui/needless_pass_by_value.rs:204:27
190+
|
191+
LL | fn non_standard_option(x: non_standard::Option<String>) {
192+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `&non_standard::Option<String>`
193+
194+
error: this argument is passed by value, but not consumed in the function body
195+
--> tests/ui/needless_pass_by_value.rs:209:22
196+
|
197+
LL | fn option_by_name(x: Option<std::option::Option<core::option::Option<non_standard::Option<String>>>>) {
198+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider taking a reference instead: `Option<std::option::Option<core::option::Option<&non_standard::Option<String>>>>`
199+
200+
error: this argument is passed by value, but not consumed in the function body
201+
--> tests/ui/needless_pass_by_value.rs:216:18
202+
|
203+
LL | fn non_option(x: OptStr) {
204+
| ^^^^^^ help: consider taking a reference instead: `&OptStr`
205+
206+
error: this argument is passed by value, but not consumed in the function body
207+
--> tests/ui/needless_pass_by_value.rs:223:25
208+
|
209+
LL | fn non_option_either(x: Opt<String>) {
210+
| ^^^^^^^^^^^ help: consider taking a reference instead: `&Opt<String>`
211+
212+
error: aborting due to 27 previous errors
189213

0 commit comments

Comments
 (0)