-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #13336 - nyurik:ref-option-sig, r=llogiq
Suggest `Option<&T>` instead of `&Option<T>` closes #13054 ```rust // bad code fn foo(a: &Option<T>) {} fn bar(&self) -> &Option<T> {} // Use instead fn foo(a: Option<&T>) {} fn bar(&self) -> Option<&T> {} ``` Handles argument types and return types in functions, methods, and closures with explicit types. Honors `avoid_breaking_exported_api` parameter. See this great [YouTube video](https://www.youtube.com/watch?v=6c7pZYP_iIE) with the in-depth explanation. ### Open Questions These are not blocking, and could be done in separate PRs if needed. * [ ] Should `&Option<Box<T>>` be suggested as `Option<&T>` -- without the box? Handled by [clippy::borrowed_box](https://rust-lang.github.io/rust-clippy/master/index.html#/borrowed_box) * [ ] Should `&Option<String>` be suggested as `Option<&str>` -- using de-refed type? ### Possible Future Improvements These cases might also be good to handle, probably in a separate PR. ```rust fn lambdas() { let x = |a: &Option<String>| {}; let x = |a: &Option<String>| -> &Option<String> { todo!() }; } fn mut_ref_to_ref(a: &mut &Option<u8>) {} ``` changelog: [`ref_option`]: Suggest `Option<&T>` instead of `&Option<T>`
- Loading branch information
Showing
16 changed files
with
739 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use crate::functions::REF_OPTION; | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::is_trait_impl_item; | ||
use clippy_utils::source::snippet; | ||
use clippy_utils::ty::is_type_diagnostic_item; | ||
use rustc_errors::Applicability; | ||
use rustc_hir as hir; | ||
use rustc_hir::intravisit::FnKind; | ||
use rustc_hir::{FnDecl, HirId}; | ||
use rustc_lint::LateContext; | ||
use rustc_middle::ty::{self, GenericArgKind, Mutability, Ty}; | ||
use rustc_span::def_id::LocalDefId; | ||
use rustc_span::{Span, sym}; | ||
|
||
fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { | ||
if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() | ||
&& is_type_diagnostic_item(cx, *opt_ty, sym::Option) | ||
&& let ty::Adt(_, opt_gen) = opt_ty.kind() | ||
&& let [gen] = opt_gen.as_slice() | ||
&& let GenericArgKind::Type(gen_ty) = gen.unpack() | ||
&& !gen_ty.is_ref() | ||
// Need to gen the original spans, so first parsing mid, and hir parsing afterward | ||
&& let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind | ||
&& let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind | ||
&& let (Some(first), Some(last)) = (path.segments.first(), path.segments.last()) | ||
&& let Some(hir::GenericArgs { | ||
args: [hir::GenericArg::Type(opt_ty)], | ||
.. | ||
}) = last.args | ||
{ | ||
let lifetime = snippet(cx, lifetime.ident.span, ".."); | ||
fixes.push(( | ||
param.span, | ||
format!( | ||
"{}<&{lifetime}{}{}>", | ||
snippet(cx, first.ident.span.to(last.ident.span), ".."), | ||
if lifetime.is_empty() { "" } else { " " }, | ||
snippet(cx, opt_ty.span, "..") | ||
), | ||
)); | ||
} | ||
} | ||
|
||
fn check_fn_sig<'a>(cx: &LateContext<'a>, decl: &FnDecl<'a>, span: Span, sig: ty::FnSig<'a>) { | ||
let mut fixes = Vec::new(); | ||
// Check function arguments' types | ||
for (param, param_ty) in decl.inputs.iter().zip(sig.inputs()) { | ||
check_ty(cx, param, *param_ty, &mut fixes); | ||
} | ||
// Check return type | ||
if let hir::FnRetTy::Return(ty) = &decl.output { | ||
check_ty(cx, ty, sig.output(), &mut fixes); | ||
} | ||
if !fixes.is_empty() { | ||
span_lint_and_then( | ||
cx, | ||
REF_OPTION, | ||
span, | ||
"it is more idiomatic to use `Option<&T>` instead of `&Option<T>`", | ||
|diag| { | ||
diag.multipart_suggestion("change this to", fixes, Applicability::Unspecified); | ||
}, | ||
); | ||
} | ||
} | ||
|
||
#[allow(clippy::too_many_arguments)] | ||
pub(crate) fn check_fn<'a>( | ||
cx: &LateContext<'a>, | ||
kind: FnKind<'_>, | ||
decl: &FnDecl<'a>, | ||
span: Span, | ||
hir_id: HirId, | ||
def_id: LocalDefId, | ||
body: &hir::Body<'_>, | ||
avoid_breaking_exported_api: bool, | ||
) { | ||
if avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { | ||
return; | ||
} | ||
|
||
if let FnKind::Closure = kind { | ||
// Compute the span of the closure parameters + return type if set | ||
let span = if let hir::FnRetTy::Return(out_ty) = &decl.output { | ||
if decl.inputs.is_empty() { | ||
out_ty.span | ||
} else { | ||
span.with_hi(out_ty.span.hi()) | ||
} | ||
} else if let (Some(first), Some(last)) = (decl.inputs.first(), decl.inputs.last()) { | ||
first.span.to(last.span) | ||
} else { | ||
// No parameters - no point in checking | ||
return; | ||
}; | ||
|
||
// Figure out the signature of the closure | ||
let ty::Closure(_, args) = cx.typeck_results().expr_ty(body.value).kind() else { | ||
return; | ||
}; | ||
let sig = args.as_closure().sig().skip_binder(); | ||
|
||
check_fn_sig(cx, decl, span, sig); | ||
} else if !is_trait_impl_item(cx, hir_id) { | ||
let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); | ||
check_fn_sig(cx, decl, span, sig); | ||
} | ||
} | ||
|
||
pub(super) fn check_trait_item<'a>( | ||
cx: &LateContext<'a>, | ||
trait_item: &hir::TraitItem<'a>, | ||
avoid_breaking_exported_api: bool, | ||
) { | ||
if let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind | ||
&& !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(trait_item.owner_id.def_id)) | ||
{ | ||
let def_id = trait_item.owner_id.def_id; | ||
let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); | ||
check_fn_sig(cx, sig.decl, sig.span, ty_sig); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
avoid-breaking-exported-api = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
avoid-breaking-exported-api = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
//@revisions: private all | ||
//@[private] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/private | ||
//@[all] rustc-env:CLIPPY_CONF_DIR=tests/ui/ref_option/all | ||
|
||
#![allow(unused, clippy::needless_lifetimes, clippy::borrowed_box)] | ||
#![warn(clippy::ref_option)] | ||
|
||
fn opt_u8(a: Option<&u8>) {} | ||
fn opt_gen<T>(a: Option<&T>) {} | ||
fn opt_string(a: std::option::Option<&String>) {} | ||
fn ret_string<'a>(p: &'a str) -> Option<&'a u8> { | ||
panic!() | ||
} | ||
fn ret_string_static() -> Option<&'static u8> { | ||
panic!() | ||
} | ||
fn mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {} | ||
fn ret_box<'a>() -> Option<&'a Box<u8>> { | ||
panic!() | ||
} | ||
|
||
pub fn pub_opt_string(a: Option<&String>) {} | ||
pub fn pub_mult_string(a: Option<&String>, b: Option<&Vec<u8>>) {} | ||
|
||
pub trait PubTrait { | ||
fn pub_trait_opt(&self, a: Option<&Vec<u8>>); | ||
fn pub_trait_ret(&self) -> Option<&Vec<u8>>; | ||
} | ||
|
||
trait PrivateTrait { | ||
fn trait_opt(&self, a: Option<&String>); | ||
fn trait_ret(&self) -> Option<&String>; | ||
} | ||
|
||
pub struct PubStruct; | ||
|
||
impl PubStruct { | ||
pub fn pub_opt_params(&self, a: Option<&()>) {} | ||
pub fn pub_opt_ret(&self) -> Option<&String> { | ||
panic!() | ||
} | ||
|
||
fn private_opt_params(&self, a: Option<&()>) {} | ||
fn private_opt_ret(&self) -> Option<&String> { | ||
panic!() | ||
} | ||
} | ||
|
||
// valid, don't change | ||
fn mut_u8(a: &mut Option<u8>) {} | ||
pub fn pub_mut_u8(a: &mut Option<String>) {} | ||
|
||
// might be good to catch in the future | ||
fn mut_u8_ref(a: &mut &Option<u8>) {} | ||
pub fn pub_mut_u8_ref(a: &mut &Option<String>) {} | ||
fn lambdas() { | ||
// Not handled for now, not sure if we should | ||
let x = |a: &Option<String>| {}; | ||
let x = |a: &Option<String>| -> &Option<String> { panic!() }; | ||
} | ||
|
||
fn main() {} |
Oops, something went wrong.