33//! This lint is **warn** by default
44
55use clippy_utils:: diagnostics:: span_lint_and_then;
6- use clippy_utils:: is_automatically_derived ;
7- use clippy_utils:: source :: snippet_opt ;
6+ use clippy_utils:: source :: { snippet_opt , snippet_with_applicability , snippet_with_context } ;
7+ use clippy_utils:: { get_parent_expr , in_macro , path_to_local } ;
88use if_chain:: if_chain;
9+ use rustc_ast:: util:: parser:: PREC_POSTFIX ;
10+ use rustc_data_structures:: fx:: FxIndexMap ;
911use rustc_errors:: Applicability ;
10- use rustc_hir:: { BindingAnnotation , BorrowKind , Expr , ExprKind , Item , Mutability , Pat , PatKind } ;
12+ use rustc_hir:: { BindingAnnotation , Body , BodyId , BorrowKind , Expr , ExprKind , HirId , Mutability , Pat , PatKind , UnOp } ;
1113use rustc_lint:: { LateContext , LateLintPass } ;
1214use rustc_middle:: ty;
1315use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
1416use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
15- use rustc_span:: def_id :: LocalDefId ;
17+ use rustc_span:: Span ;
1618
1719declare_clippy_lint ! {
1820 /// **What it does:** Checks for address of operations (`&`) that are going to
@@ -36,16 +38,66 @@ declare_clippy_lint! {
3638 "taking a reference that is going to be automatically dereferenced"
3739}
3840
41+ declare_clippy_lint ! {
42+ /// **What it does:** Checks for `ref` bindings which create a reference to a reference.
43+ ///
44+ /// **Why is this bad?** The address-of operator at the use site is clearer about the need for a reference.
45+ ///
46+ /// **Known problems:** None.
47+ ///
48+ /// **Example:**
49+ /// ```rust
50+ /// // Bad
51+ /// let x = Some("");
52+ /// if let Some(ref x) = x {
53+ /// // use `x` here
54+ /// }
55+ ///
56+ /// // Good
57+ /// let x = Some("");
58+ /// if let Some(x) = x {
59+ /// // use `&x` here
60+ /// }
61+ /// ```
62+ pub REF_BINDING_TO_REFERENCE ,
63+ pedantic,
64+ "`ref` binding to a reference"
65+ }
66+
67+ impl_lint_pass ! ( NeedlessBorrow => [ NEEDLESS_BORROW , REF_BINDING_TO_REFERENCE ] ) ;
3968#[ derive( Default ) ]
4069pub struct NeedlessBorrow {
41- derived_item : Option < LocalDefId > ,
70+ /// The body the first local was found in. Used to emit lints when the traversal of the body has
71+ /// been finished. Note we can't lint at the end of every body as they can be nested within each
72+ /// other.
73+ current_body : Option < BodyId > ,
74+ /// The list of locals currently being checked by the lint.
75+ /// If the value is `None`, then the binding has been seen as a ref pattern, but is not linted.
76+ /// This is needed for or patterns where one of the branches can be linted, but another can not
77+ /// be.
78+ ///
79+ /// e.g. `m!(x) | Foo::Bar(ref x)`
80+ ref_locals : FxIndexMap < HirId , Option < RefPat > > ,
4281}
4382
44- impl_lint_pass ! ( NeedlessBorrow => [ NEEDLESS_BORROW ] ) ;
83+ struct RefPat {
84+ /// Whether every usage of the binding is dereferenced.
85+ always_deref : bool ,
86+ /// The spans of all the ref bindings for this local.
87+ spans : Vec < Span > ,
88+ /// The applicability of this suggestion.
89+ app : Applicability ,
90+ /// All the replacements which need to be made.
91+ replacements : Vec < ( Span , String ) > ,
92+ }
4593
4694impl < ' tcx > LateLintPass < ' tcx > for NeedlessBorrow {
4795 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
48- if e. span . from_expansion ( ) || self . derived_item . is_some ( ) {
96+ if let Some ( local) = path_to_local ( e) {
97+ self . check_local_usage ( cx, e, local) ;
98+ }
99+
100+ if e. span . from_expansion ( ) {
49101 return ;
50102 }
51103 if let ExprKind :: AddrOf ( BorrowKind :: Ref , Mutability :: Not , inner) = e. kind {
@@ -85,50 +137,131 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
85137 }
86138 }
87139 }
140+
88141 fn check_pat ( & mut self , cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > ) {
89- if pat. span . from_expansion ( ) || self . derived_item . is_some ( ) {
90- return ;
142+ if let PatKind :: Binding ( BindingAnnotation :: Ref , id, name, _) = pat. kind {
143+ if let Some ( opt_prev_pat) = self . ref_locals . get_mut ( & id) {
144+ // This binding id has been seen before. Add this pattern to the list of changes.
145+ if let Some ( prev_pat) = opt_prev_pat {
146+ if in_macro ( pat. span ) {
147+ // Doesn't match the context of the previous pattern. Can't lint here.
148+ * opt_prev_pat = None ;
149+ } else {
150+ prev_pat. spans . push ( pat. span ) ;
151+ prev_pat. replacements . push ( (
152+ pat. span ,
153+ snippet_with_context ( cx, name. span , pat. span . ctxt ( ) , ".." , & mut prev_pat. app )
154+ . 0
155+ . into ( ) ,
156+ ) ) ;
157+ }
158+ }
159+ return ;
160+ }
161+
162+ if_chain ! {
163+ if !in_macro( pat. span) ;
164+ if let ty:: Ref ( _, tam, _) = * cx. typeck_results( ) . pat_ty( pat) . kind( ) ;
165+ // only lint immutable refs, because borrowed `&mut T` cannot be moved out
166+ if let ty:: Ref ( _, _, Mutability :: Not ) = * tam. kind( ) ;
167+ then {
168+ let mut app = Applicability :: MachineApplicable ;
169+ let snip = snippet_with_context( cx, name. span, pat. span. ctxt( ) , ".." , & mut app) . 0 ;
170+ self . current_body = self . current_body. or( cx. enclosing_body) ;
171+ self . ref_locals. insert(
172+ id,
173+ Some ( RefPat {
174+ always_deref: true ,
175+ spans: vec![ pat. span] ,
176+ app,
177+ replacements: vec![ ( pat. span, snip. into( ) ) ] ,
178+ } ) ,
179+ ) ;
180+ }
181+ }
91182 }
92- if_chain ! {
93- if let PatKind :: Binding ( BindingAnnotation :: Ref , .., name, _) = pat. kind;
94- if let ty:: Ref ( _, tam, mutbl) = * cx. typeck_results( ) . pat_ty( pat) . kind( ) ;
95- if mutbl == Mutability :: Not ;
96- if let ty:: Ref ( _, _, mutbl) = * tam. kind( ) ;
97- // only lint immutable refs, because borrowed `&mut T` cannot be moved out
98- if mutbl == Mutability :: Not ;
99- then {
183+ }
184+
185+ fn check_body_post ( & mut self , cx : & LateContext < ' tcx > , body : & ' tcx Body < ' _ > ) {
186+ if Some ( body. id ( ) ) == self . current_body {
187+ for pat in self . ref_locals . drain ( ..) . filter_map ( |( _, x) | x) {
188+ let replacements = pat. replacements ;
189+ let app = pat. app ;
100190 span_lint_and_then (
101191 cx,
102- NEEDLESS_BORROW ,
103- pat. span,
192+ if pat. always_deref {
193+ NEEDLESS_BORROW
194+ } else {
195+ REF_BINDING_TO_REFERENCE
196+ } ,
197+ pat. spans ,
104198 "this pattern creates a reference to a reference" ,
105199 |diag| {
106- if let Some ( snippet) = snippet_opt( cx, name. span) {
107- diag. span_suggestion(
108- pat. span,
109- "change this to" ,
110- snippet,
111- Applicability :: MachineApplicable ,
112- ) ;
113- }
114- }
200+ diag. multipart_suggestion ( "try this" , replacements, app) ;
201+ } ,
115202 )
116203 }
204+ self . current_body = None ;
117205 }
118206 }
119-
120- fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
121- let attrs = cx. tcx . hir ( ) . attrs ( item. hir_id ( ) ) ;
122- if is_automatically_derived ( attrs) {
123- debug_assert ! ( self . derived_item. is_none( ) ) ;
124- self . derived_item = Some ( item. def_id ) ;
125- }
126- }
127-
128- fn check_item_post ( & mut self , _: & LateContext < ' tcx > , item : & ' tcx Item < ' _ > ) {
129- if let Some ( id) = self . derived_item {
130- if item. def_id == id {
131- self . derived_item = None ;
207+ }
208+ impl NeedlessBorrow {
209+ fn check_local_usage ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > , local : HirId ) {
210+ if let Some ( outer_pat) = self . ref_locals . get_mut ( & local) {
211+ if let Some ( pat) = outer_pat {
212+ // Check for auto-deref
213+ if !matches ! (
214+ cx. typeck_results( ) . expr_adjustments( e) ,
215+ [
216+ Adjustment {
217+ kind: Adjust :: Deref ( _) ,
218+ ..
219+ } ,
220+ Adjustment {
221+ kind: Adjust :: Deref ( _) ,
222+ ..
223+ } ,
224+ ..
225+ ]
226+ ) {
227+ match get_parent_expr ( cx, e) {
228+ // Field accesses are the same no matter the number of references.
229+ Some ( Expr {
230+ kind : ExprKind :: Field ( ..) ,
231+ ..
232+ } ) => ( ) ,
233+ Some ( & Expr {
234+ span,
235+ kind : ExprKind :: Unary ( UnOp :: Deref , _) ,
236+ ..
237+ } ) if !in_macro ( span) => {
238+ // Remove explicit deref.
239+ let snip = snippet_with_context ( cx, e. span , span. ctxt ( ) , ".." , & mut pat. app ) . 0 ;
240+ pat. replacements . push ( ( span, snip. into ( ) ) ) ;
241+ } ,
242+ Some ( parent) if !in_macro ( parent. span ) => {
243+ // Double reference might be needed at this point.
244+ if parent. precedence ( ) . order ( ) == PREC_POSTFIX {
245+ // Parentheses would be needed here, don't lint.
246+ * outer_pat = None ;
247+ } else {
248+ pat. always_deref = false ;
249+ let snip = snippet_with_context ( cx, e. span , parent. span . ctxt ( ) , ".." , & mut pat. app ) . 0 ;
250+ pat. replacements . push ( ( e. span , format ! ( "&{}" , snip) ) ) ;
251+ }
252+ } ,
253+ _ if !in_macro ( e. span ) => {
254+ // Double reference might be needed at this point.
255+ pat. always_deref = false ;
256+ let snip = snippet_with_applicability ( cx, e. span , ".." , & mut pat. app ) ;
257+ pat. replacements . push ( ( e. span , format ! ( "&{}" , snip) ) ) ;
258+ } ,
259+ // Edge case for macros. The span of the identifier will usually match the context of the
260+ // binding, but not if the identifier was created in a macro. e.g. `concat_idents` and proc
261+ // macros
262+ _ => * outer_pat = None ,
263+ }
264+ }
132265 }
133266 }
134267 }
0 commit comments