Skip to content

Commit 16a588a

Browse files
committed
Suggest Option<&T> instead of &Option<T>
1 parent e8ba5d1 commit 16a588a

File tree

13 files changed

+566
-0
lines changed

13 files changed

+566
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5814,6 +5814,7 @@ Released 2018-09-13
58145814
[`ref_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr
58155815
[`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference
58165816
[`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref
5817+
[`ref_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option
58175818
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
58185819
[`ref_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_patterns
58195820
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro

book/src/lint_configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
353353
* [`rc_buffer`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer)
354354
* [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)
355355
* [`redundant_allocation`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation)
356+
* [`ref_option`](https://rust-lang.github.io/rust-clippy/master/index.html#ref_option)
356357
* [`single_call_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#single_call_fn)
357358
* [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref)
358359
* [`unnecessary_box_returns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns)

clippy_config/src/conf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ define_Conf! {
378378
rc_buffer,
379379
rc_mutex,
380380
redundant_allocation,
381+
ref_option,
381382
single_call_fn,
382383
trivially_copy_pass_by_ref,
383384
unnecessary_box_returns,

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
202202
crate::functions::MUST_USE_CANDIDATE_INFO,
203203
crate::functions::MUST_USE_UNIT_INFO,
204204
crate::functions::NOT_UNSAFE_PTR_ARG_DEREF_INFO,
205+
crate::functions::REF_OPTION_INFO,
205206
crate::functions::RENAMED_FUNCTION_PARAMS_INFO,
206207
crate::functions::RESULT_LARGE_ERR_INFO,
207208
crate::functions::RESULT_UNIT_ERR_INFO,

clippy_lints/src/functions/mod.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod impl_trait_in_params;
22
mod misnamed_getters;
33
mod must_use;
44
mod not_unsafe_ptr_arg_deref;
5+
mod ref_option;
56
mod renamed_function_params;
67
mod result;
78
mod too_many_arguments;
@@ -399,6 +400,26 @@ declare_clippy_lint! {
399400
"renamed function parameters in trait implementation"
400401
}
401402

403+
declare_clippy_lint! {
404+
/// ### What it does
405+
/// Warns when a function signature uses `&Option<T>` instead of `Option<&T>`.
406+
/// ### Why is this bad?
407+
/// More flexibility, better memory optimization, and more idiomatic Rust code.
408+
/// See [YouTube video](https://www.youtube.com/watch?v=6c7pZYP_iIE)
409+
/// ### Example
410+
/// ```no_run
411+
/// fn foo(a: &Option<String>) {}
412+
/// ```
413+
/// Use instead:
414+
/// ```no_run
415+
/// fn foo(a: Option<&String>) {}
416+
/// ```
417+
#[clippy::version = "1.82.0"]
418+
pub REF_OPTION,
419+
nursery,
420+
"function signature uses `&Option<T>` instead of `Option<&T>`"
421+
}
422+
402423
pub struct Functions {
403424
too_many_arguments_threshold: u64,
404425
too_many_lines_threshold: u64,
@@ -437,6 +458,7 @@ impl_lint_pass!(Functions => [
437458
MISNAMED_GETTERS,
438459
IMPL_TRAIT_IN_PARAMS,
439460
RENAMED_FUNCTION_PARAMS,
461+
REF_OPTION,
440462
]);
441463

442464
impl<'tcx> LateLintPass<'tcx> for Functions {
@@ -455,18 +477,21 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
455477
not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id);
456478
misnamed_getters::check_fn(cx, kind, decl, body, span);
457479
impl_trait_in_params::check_fn(cx, &kind, body, hir_id);
480+
ref_option::check_fn(cx, kind, decl, span);
458481
}
459482

460483
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
461484
must_use::check_item(cx, item);
462485
result::check_item(cx, item, self.large_error_threshold);
486+
ref_option::check_item(cx, item, self.avoid_breaking_exported_api);
463487
}
464488

465489
fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) {
466490
must_use::check_impl_item(cx, item);
467491
result::check_impl_item(cx, item, self.large_error_threshold);
468492
impl_trait_in_params::check_impl_item(cx, item);
469493
renamed_function_params::check_impl_item(cx, item, &self.trait_ids);
494+
ref_option::check_impl_item(cx, item, self.avoid_breaking_exported_api);
470495
}
471496

472497
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
@@ -475,5 +500,6 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
475500
must_use::check_trait_item(cx, item);
476501
result::check_trait_item(cx, item, self.large_error_threshold);
477502
impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api);
503+
ref_option::check_trait_item(cx, item, self.avoid_breaking_exported_api);
478504
}
479505
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use crate::functions::hir::GenericArg;
2+
use crate::functions::REF_OPTION;
3+
use clippy_utils::diagnostics::span_lint_and_then;
4+
use clippy_utils::source::snippet;
5+
use rustc_errors::Applicability;
6+
use rustc_hir as hir;
7+
use rustc_hir::intravisit::FnKind;
8+
use rustc_hir::{FnDecl, GenericArgs, ImplItem, MutTy, QPath, TraitItem, Ty, TyKind};
9+
use rustc_lint::LateContext;
10+
use rustc_span::{sym, Span};
11+
12+
fn check_ty(cx: &LateContext<'_>, param: &Ty<'_>, fixes: &mut Vec<(Span, String)>) {
13+
// TODO: is this the right way to check if ty is an Option?
14+
if let TyKind::Ref(lifetime, MutTy { ty, .. }) = param.kind
15+
&& let TyKind::Path(QPath::Resolved(_, path)) = ty.kind
16+
&& path.segments.len() == 1
17+
&& let seg = &path.segments[0]
18+
&& seg.ident.name == sym::Option
19+
// check if option contains a regular type, not a reference
20+
// TODO: Should this instead just check that opt_ty is a TyKind::Path?
21+
&& let Some(GenericArgs { args: [GenericArg::Type(opt_ty)], .. }) = seg.args
22+
&& !matches!(opt_ty.kind, TyKind::Ref(..))
23+
{
24+
// FIXME: Should this use the Option path from the original type?
25+
// FIXME: Should reference be added in some other way to the snippet?
26+
// FIXME: What should the lifetime be of the reference in Option<&T>?
27+
let lifetime = snippet(cx, lifetime.ident.span, "..");
28+
fixes.push((
29+
param.span,
30+
format!(
31+
"Option<&{}{}{}>",
32+
lifetime,
33+
if lifetime.is_empty() { "" } else { " " },
34+
snippet(cx, opt_ty.span, "..")
35+
),
36+
));
37+
}
38+
}
39+
40+
fn check_fn_sig(cx: &LateContext<'_>, decl: &FnDecl<'_>, span: Span) {
41+
// dbg!(snippet(cx, span, ".."));
42+
let mut fixes = Vec::new();
43+
// Check function arguments' types
44+
for param in decl.inputs {
45+
check_ty(cx, param, &mut fixes);
46+
}
47+
// Check return type
48+
if let hir::FnRetTy::Return(ty) = &decl.output {
49+
check_ty(cx, ty, &mut fixes);
50+
}
51+
if !fixes.is_empty() {
52+
// FIXME: These changes will often result in broken code that will need to be fixed manually
53+
// What should the applicability be?
54+
span_lint_and_then(
55+
cx,
56+
REF_OPTION,
57+
span,
58+
"it is more idiomatic to use `Option<&T>` instead of `&Option<T>`",
59+
|diag| {
60+
diag.multipart_suggestion("change this to", fixes, Applicability::HasPlaceholders);
61+
},
62+
);
63+
}
64+
}
65+
66+
pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>, avoid_breaking_exported_api: bool) {
67+
if let hir::ItemKind::Fn(ref sig, _, _) = item.kind
68+
&& !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(item.owner_id.def_id))
69+
{
70+
check_fn_sig(cx, sig.decl, sig.span);
71+
}
72+
}
73+
74+
pub(super) fn check_impl_item(cx: &LateContext<'_>, impl_item: &ImplItem<'_>, avoid_breaking_exported_api: bool) {
75+
if let hir::ImplItemKind::Fn(ref sig, _) = impl_item.kind
76+
&& !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(impl_item.owner_id.def_id))
77+
{
78+
check_fn_sig(cx, sig.decl, sig.span);
79+
}
80+
}
81+
82+
pub(super) fn check_trait_item(cx: &LateContext<'_>, trait_item: &TraitItem<'_>, avoid_breaking_exported_api: bool) {
83+
if let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind
84+
&& !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(trait_item.owner_id.def_id))
85+
{
86+
check_fn_sig(cx, sig.decl, sig.span);
87+
}
88+
}
89+
90+
pub(crate) fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, span: Span) {
91+
if let FnKind::Closure = kind {
92+
// Compute the span of the closure parameters + return type if set
93+
let span = if let hir::FnRetTy::Return(out_ty) = &decl.output {
94+
if decl.inputs.is_empty() {
95+
out_ty.span
96+
} else {
97+
span.with_hi(out_ty.span.hi())
98+
}
99+
} else if let (Some(first), Some(last)) = (decl.inputs.first(), decl.inputs.last()) {
100+
first.span.to(last.span)
101+
} else {
102+
// No parameters - no point in checking
103+
return;
104+
};
105+
106+
check_fn_sig(cx, decl, span);
107+
}
108+
}

tests/ui/ref_option/all/clippy.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
avoid-breaking-exported-api = false
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
avoid-breaking-exported-api = true
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//@revisions: private all
2+
//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private
3+
//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all
4+
5+
#![allow(unused, clippy::all)]
6+
#![warn(clippy::ref_option)]
7+
8+
fn main() {}
9+
10+
// FIXME: Using `&None` instead of `unimplemented!()` is a better test, but it results in an error:
11+
// `after rustfix is applied, all errors should be gone, but weren't`
12+
13+
fn opt_u8(a: Option<&u8>) {}
14+
fn opt_gen<T>(a: Option<&T>) {}
15+
fn opt_string(a: Option<&String>) {}
16+
fn ret_string<'a>(p: &'a String) -> Option<&'a u8> {
17+
panic!()
18+
}
19+
fn ret_string_static() -> Option<&'static u8> {
20+
panic!()
21+
}
22+
fn mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
23+
pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {}
24+
25+
pub fn pub_opt_string(a: Option<&String>) {}
26+
27+
pub trait PubTrait {
28+
fn trait_opt(&self, a: Option<&Vec<u8>>);
29+
fn trait_ret(&self) -> Option<&Vec<u8>>;
30+
}
31+
32+
trait PrivateTrait {
33+
fn private_trait_opt(&self, a: Option<&String>);
34+
fn private_trait_ret(&self) -> Option<&String>;
35+
}
36+
37+
pub struct PubStruct;
38+
39+
impl PubStruct {
40+
pub fn pub_opt_params(&self, a: Option<&()>) {}
41+
pub fn pub_opt_ret(&self) -> Option<&String> {
42+
panic!()
43+
}
44+
45+
fn private_opt_params(&self, a: Option<&()>) {}
46+
fn private_opt_ret(&self) -> Option<&String> {
47+
panic!()
48+
}
49+
}
50+
51+
fn lambdas() {
52+
let s = Some("hello".to_string());
53+
let x = |a: Option<&String>| {};
54+
let x = |a: Option<&String>| -> Option<&String> { panic!() };
55+
}

0 commit comments

Comments
 (0)