33//! This lint is **warn** by default
44
55use clippy_utils:: diagnostics:: span_lint_and_then;
6- 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} ;
78use if_chain:: if_chain;
9+ use rustc_ast:: util:: parser:: PREC_POSTFIX ;
10+ use rustc_data_structures:: fx:: FxIndexMap ;
811use rustc_errors:: Applicability ;
9- use rustc_hir:: { BindingAnnotation , BorrowKind , Expr , ExprKind , Mutability , Pat , PatKind } ;
12+ use rustc_hir:: { BindingAnnotation , Body , BodyId , BorrowKind , Expr , ExprKind , HirId , Mutability , Pat , PatKind , UnOp } ;
1013use rustc_lint:: { LateContext , LateLintPass } ;
1114use rustc_middle:: ty;
1215use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
1316use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
17+ use rustc_span:: Span ;
1418
1519declare_clippy_lint ! {
1620 /// **What it does:** Checks for address of operations (`&`) that are going to
@@ -34,13 +38,65 @@ declare_clippy_lint! {
3438 "taking a reference that is going to be automatically dereferenced"
3539}
3640
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 ] ) ;
3768#[ derive( Default ) ]
38- pub struct NeedlessBorrow ;
69+ pub struct NeedlessBorrow {
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 > > ,
81+ }
3982
40- 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+ }
4193
4294impl < ' tcx > LateLintPass < ' tcx > for NeedlessBorrow {
4395 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
96+ if let Some ( local) = path_to_local ( e) {
97+ self . check_local_usage ( cx, e, local) ;
98+ }
99+
44100 if e. span . from_expansion ( ) {
45101 return ;
46102 }
@@ -81,35 +137,132 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrow {
81137 }
82138 }
83139 }
140+
84141 fn check_pat ( & mut self , cx : & LateContext < ' tcx > , pat : & ' tcx Pat < ' _ > ) {
85- if pat. span . from_expansion ( ) {
86- 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+ }
87182 }
88- if_chain ! {
89- if let PatKind :: Binding ( BindingAnnotation :: Ref , .., name, _) = pat. kind;
90- if let ty:: Ref ( _, tam, mutbl) = * cx. typeck_results( ) . pat_ty( pat) . kind( ) ;
91- if mutbl == Mutability :: Not ;
92- if let ty:: Ref ( _, _, mutbl) = * tam. kind( ) ;
93- // only lint immutable refs, because borrowed `&mut T` cannot be moved out
94- if mutbl == Mutability :: Not ;
95- 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 ;
96190 span_lint_and_then (
97191 cx,
98- NEEDLESS_BORROW ,
99- pat. span,
192+ if pat. always_deref {
193+ NEEDLESS_BORROW
194+ } else {
195+ REF_BINDING_TO_REFERENCE
196+ } ,
197+ pat. spans ,
100198 "this pattern creates a reference to a reference" ,
101199 |diag| {
102- if let Some ( snippet) = snippet_opt( cx, name. span) {
103- diag. span_suggestion(
104- pat. span,
105- "change this to" ,
106- snippet,
107- Applicability :: MachineApplicable ,
108- ) ;
109- }
110- }
200+ diag. multipart_suggestion ( "try this" , replacements, app) ;
201+ } ,
111202 )
112203 }
204+ self . current_body = None ;
205+ }
206+ }
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+ }
265+ }
113266 }
114267 }
115268}
0 commit comments