]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/sugg.rs
Merge pull request #3269 from rust-lang-nursery/relicense
[rust.git] / clippy_lints / src / utils / sugg.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10
11 //! Contains utility functions to generate suggestions.
12 #![deny(clippy::missing_docs_in_private_items)]
13
14 use matches::matches;
15 use crate::rustc::hir;
16 use crate::rustc::lint::{EarlyContext, LateContext, LintContext};
17 use crate::rustc_errors;
18 use std::borrow::Cow;
19 use std::fmt::Display;
20 use std;
21 use crate::syntax::source_map::{CharPos, Span};
22 use crate::syntax::parse::token;
23 use crate::syntax::print::pprust::token_to_string;
24 use crate::syntax::util::parser::AssocOp;
25 use crate::syntax::ast;
26 use crate::utils::{higher, snippet, snippet_opt};
27 use crate::syntax_pos::{BytePos, Pos};
28 use crate::rustc_errors::Applicability;
29
30 /// A helper type to build suggestion correctly handling parenthesis.
31 pub enum Sugg<'a> {
32     /// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
33     NonParen(Cow<'a, str>),
34     /// An expression that does not fit in other variants.
35     MaybeParen(Cow<'a, str>),
36     /// A binary operator expression, including `as`-casts and explicit type
37     /// coercion.
38     BinOp(AssocOp, Cow<'a, str>),
39 }
40
41 /// Literal constant `1`, for convenience.
42 pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
43
44 impl Display for Sugg<'_> {
45     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
46         match *self {
47             Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
48         }
49     }
50 }
51
52 #[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
53 impl<'a> Sugg<'a> {
54     /// Prepare a suggestion from an expression.
55     pub fn hir_opt(cx: &LateContext<'_, '_>, expr: &hir::Expr) -> Option<Self> {
56         snippet_opt(cx, expr.span).map(|snippet| {
57             let snippet = Cow::Owned(snippet);
58             match expr.node {
59                 hir::ExprKind::AddrOf(..) |
60                 hir::ExprKind::Box(..) |
61                 hir::ExprKind::Closure(.., _) |
62                 hir::ExprKind::If(..) |
63                 hir::ExprKind::Unary(..) |
64                 hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
65                 hir::ExprKind::Continue(..) |
66                 hir::ExprKind::Yield(..) |
67                 hir::ExprKind::Array(..) |
68                 hir::ExprKind::Block(..) |
69                 hir::ExprKind::Break(..) |
70                 hir::ExprKind::Call(..) |
71                 hir::ExprKind::Field(..) |
72                 hir::ExprKind::Index(..) |
73                 hir::ExprKind::InlineAsm(..) |
74                 hir::ExprKind::Lit(..) |
75                 hir::ExprKind::Loop(..) |
76                 hir::ExprKind::MethodCall(..) |
77                 hir::ExprKind::Path(..) |
78                 hir::ExprKind::Repeat(..) |
79                 hir::ExprKind::Ret(..) |
80                 hir::ExprKind::Struct(..) |
81                 hir::ExprKind::Tup(..) |
82                 hir::ExprKind::While(..) => Sugg::NonParen(snippet),
83                 hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
84                 hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
85                 hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
86                 hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
87                 hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
88             }
89         })
90     }
91
92     /// Convenience function around `hir_opt` for suggestions with a default
93     /// text.
94     pub fn hir(cx: &LateContext<'_, '_>, expr: &hir::Expr, default: &'a str) -> Self {
95         Self::hir_opt(cx, expr).unwrap_or_else(|| Sugg::NonParen(Cow::Borrowed(default)))
96     }
97
98     /// Prepare a suggestion from an expression.
99     pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
100         use crate::syntax::ast::RangeLimits;
101
102         let snippet = snippet(cx, expr.span, default);
103
104         match expr.node {
105             ast::ExprKind::AddrOf(..) |
106             ast::ExprKind::Box(..) |
107             ast::ExprKind::Closure(..) |
108             ast::ExprKind::If(..) |
109             ast::ExprKind::IfLet(..) |
110             ast::ExprKind::ObsoleteInPlace(..) |
111             ast::ExprKind::Unary(..) |
112             ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
113             ast::ExprKind::Async(..) |
114             ast::ExprKind::Block(..) |
115             ast::ExprKind::Break(..) |
116             ast::ExprKind::Call(..) |
117             ast::ExprKind::Continue(..) |
118             ast::ExprKind::Yield(..) |
119             ast::ExprKind::Field(..) |
120             ast::ExprKind::ForLoop(..) |
121             ast::ExprKind::Index(..) |
122             ast::ExprKind::InlineAsm(..) |
123             ast::ExprKind::Lit(..) |
124             ast::ExprKind::Loop(..) |
125             ast::ExprKind::Mac(..) |
126             ast::ExprKind::MethodCall(..) |
127             ast::ExprKind::Paren(..) |
128             ast::ExprKind::Path(..) |
129             ast::ExprKind::Repeat(..) |
130             ast::ExprKind::Ret(..) |
131             ast::ExprKind::Struct(..) |
132             ast::ExprKind::Try(..) |
133             ast::ExprKind::TryBlock(..) |
134             ast::ExprKind::Tup(..) |
135             ast::ExprKind::Array(..) |
136             ast::ExprKind::While(..) |
137             ast::ExprKind::WhileLet(..) => Sugg::NonParen(snippet),
138             ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
139             ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
140             ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
141             ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
142             ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
143             ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
144             ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
145         }
146     }
147
148     /// Convenience method to create the `<lhs> && <rhs>` suggestion.
149     pub fn and(self, rhs: &Self) -> Sugg<'static> {
150         make_binop(ast::BinOpKind::And, &self, rhs)
151     }
152
153     /// Convenience method to create the `<lhs> as <rhs>` suggestion.
154     pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
155         make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
156     }
157
158     /// Convenience method to create the `&<expr>` suggestion.
159     pub fn addr(self) -> Sugg<'static> {
160         make_unop("&", self)
161     }
162
163     /// Convenience method to create the `&mut <expr>` suggestion.
164     pub fn mut_addr(self) -> Sugg<'static> {
165         make_unop("&mut ", self)
166     }
167
168     /// Convenience method to create the `*<expr>` suggestion.
169     pub fn deref(self) -> Sugg<'static> {
170         make_unop("*", self)
171     }
172
173     /// Convenience method to create the `&*<expr>` suggestion. Currently this
174     /// is needed because `sugg.deref().addr()` produces an unnecessary set of
175     /// parentheses around the deref.
176     pub fn addr_deref(self) -> Sugg<'static> {
177         make_unop("&*", self)
178     }
179
180     /// Convenience method to create the `&mut *<expr>` suggestion. Currently
181     /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
182     /// set of parentheses around the deref.
183     pub fn mut_addr_deref(self) -> Sugg<'static> {
184         make_unop("&mut *", self)
185     }
186
187     /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
188     /// suggestion.
189     #[allow(dead_code)]
190     pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
191         match limit {
192             ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
193             ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
194         }
195     }
196
197     /// Add parenthesis to any expression that might need them. Suitable to the
198     /// `self` argument of
199     /// a method call (eg. to build `bar.foo()` or `(1 + 2).foo()`).
200     pub fn maybe_par(self) -> Self {
201         match self {
202             Sugg::NonParen(..) => self,
203             // (x) and (x).y() both don't need additional parens
204             Sugg::MaybeParen(sugg) => if sugg.starts_with('(') && sugg.ends_with(')') {
205                 Sugg::MaybeParen(sugg)
206             } else {
207                 Sugg::NonParen(format!("({})", sugg).into())
208             },
209             Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()),
210         }
211     }
212 }
213
214 impl<'a, 'b> std::ops::Add<Sugg<'b>> for Sugg<'a> {
215     type Output = Sugg<'static>;
216     fn add(self, rhs: Sugg<'b>) -> Sugg<'static> {
217         make_binop(ast::BinOpKind::Add, &self, &rhs)
218     }
219 }
220
221 impl<'a, 'b> std::ops::Sub<Sugg<'b>> for Sugg<'a> {
222     type Output = Sugg<'static>;
223     fn sub(self, rhs: Sugg<'b>) -> Sugg<'static> {
224         make_binop(ast::BinOpKind::Sub, &self, &rhs)
225     }
226 }
227
228 impl<'a> std::ops::Not for Sugg<'a> {
229     type Output = Sugg<'static>;
230     fn not(self) -> Sugg<'static> {
231         make_unop("!", self)
232     }
233 }
234
235 /// Helper type to display either `foo` or `(foo)`.
236 struct ParenHelper<T> {
237     /// Whether parenthesis are needed.
238     paren: bool,
239     /// The main thing to display.
240     wrapped: T,
241 }
242
243 impl<T> ParenHelper<T> {
244     /// Build a `ParenHelper`.
245     fn new(paren: bool, wrapped: T) -> Self {
246         Self {
247             paren,
248             wrapped,
249         }
250     }
251 }
252
253 impl<T: Display> Display for ParenHelper<T> {
254     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
255         if self.paren {
256             write!(f, "({})", self.wrapped)
257         } else {
258             self.wrapped.fmt(f)
259         }
260     }
261 }
262
263 /// Build the string for `<op><expr>` adding parenthesis when necessary.
264 ///
265 /// For convenience, the operator is taken as a string because all unary
266 /// operators have the same
267 /// precedence.
268 pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
269     Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
270 }
271
272 /// Build the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
273 ///
274 /// Precedence of shift operator relative to other arithmetic operation is
275 /// often confusing so
276 /// parenthesis will always be added for a mix of these.
277 pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
278     /// Whether the operator is a shift operator `<<` or `>>`.
279     fn is_shift(op: &AssocOp) -> bool {
280         matches!(*op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
281     }
282
283     /// Whether the operator is a arithmetic operator (`+`, `-`, `*`, `/`, `%`).
284     fn is_arith(op: &AssocOp) -> bool {
285         matches!(
286             *op,
287             AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
288         )
289     }
290
291     /// Whether the operator `op` needs parenthesis with the operator `other`
292     /// in the direction
293     /// `dir`.
294     fn needs_paren(op: &AssocOp, other: &AssocOp, dir: Associativity) -> bool {
295         other.precedence() < op.precedence()
296             || (other.precedence() == op.precedence()
297                 && ((op != other && associativity(op) != dir)
298                     || (op == other && associativity(op) != Associativity::Both)))
299             || is_shift(op) && is_arith(other) || is_shift(other) && is_arith(op)
300     }
301
302     let lhs_paren = if let Sugg::BinOp(ref lop, _) = *lhs {
303         needs_paren(&op, lop, Associativity::Left)
304     } else {
305         false
306     };
307
308     let rhs_paren = if let Sugg::BinOp(ref rop, _) = *rhs {
309         needs_paren(&op, rop, Associativity::Right)
310     } else {
311         false
312     };
313
314     let lhs = ParenHelper::new(lhs_paren, lhs);
315     let rhs = ParenHelper::new(rhs_paren, rhs);
316     let sugg = match op {
317         AssocOp::Add |
318         AssocOp::BitAnd |
319         AssocOp::BitOr |
320         AssocOp::BitXor |
321         AssocOp::Divide |
322         AssocOp::Equal |
323         AssocOp::Greater |
324         AssocOp::GreaterEqual |
325         AssocOp::LAnd |
326         AssocOp::LOr |
327         AssocOp::Less |
328         AssocOp::LessEqual |
329         AssocOp::Modulus |
330         AssocOp::Multiply |
331         AssocOp::NotEqual |
332         AssocOp::ShiftLeft |
333         AssocOp::ShiftRight |
334         AssocOp::Subtract => format!("{} {} {}", lhs, op.to_ast_binop().expect("Those are AST ops").to_string(), rhs),
335         AssocOp::Assign => format!("{} = {}", lhs, rhs),
336         AssocOp::ObsoleteInPlace => format!("in ({}) {}", lhs, rhs),
337         AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_to_string(&token::BinOp(op)), rhs),
338         AssocOp::As => format!("{} as {}", lhs, rhs),
339         AssocOp::DotDot => format!("{}..{}", lhs, rhs),
340         AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
341         AssocOp::Colon => format!("{}: {}", lhs, rhs),
342     };
343
344     Sugg::BinOp(op, sugg.into())
345 }
346
347 /// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
348 pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
349     make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
350 }
351
352 #[derive(PartialEq, Eq, Clone, Copy)]
353 /// Operator associativity.
354 enum Associativity {
355     /// The operator is both left-associative and right-associative.
356     Both,
357     /// The operator is left-associative.
358     Left,
359     /// The operator is not associative.
360     None,
361     /// The operator is right-associative.
362     Right,
363 }
364
365 /// Return the associativity/fixity of an operator. The difference with
366 /// `AssocOp::fixity` is that
367 /// an operator can be both left and right associative (such as `+`:
368 /// `a + b + c == (a + b) + c == a + (b + c)`.
369 ///
370 /// Chained `as` and explicit `:` type coercion never need inner parenthesis so
371 /// they are considered
372 /// associative.
373 fn associativity(op: &AssocOp) -> Associativity {
374     use crate::syntax::util::parser::AssocOp::*;
375
376     match *op {
377         ObsoleteInPlace | Assign | AssignOp(_) => Associativity::Right,
378         Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
379         Divide |
380         Equal |
381         Greater |
382         GreaterEqual |
383         Less |
384         LessEqual |
385         Modulus |
386         NotEqual |
387         ShiftLeft |
388         ShiftRight |
389         Subtract => Associativity::Left,
390         DotDot | DotDotEq => Associativity::None,
391     }
392 }
393
394 /// Convert a `hir::BinOp` to the corresponding assigning binary operator.
395 fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
396     use crate::syntax::parse::token::BinOpToken::*;
397
398     AssocOp::AssignOp(match op.node {
399         hir::BinOpKind::Add => Plus,
400         hir::BinOpKind::BitAnd => And,
401         hir::BinOpKind::BitOr => Or,
402         hir::BinOpKind::BitXor => Caret,
403         hir::BinOpKind::Div => Slash,
404         hir::BinOpKind::Mul => Star,
405         hir::BinOpKind::Rem => Percent,
406         hir::BinOpKind::Shl => Shl,
407         hir::BinOpKind::Shr => Shr,
408         hir::BinOpKind::Sub => Minus,
409
410         | hir::BinOpKind::And
411         | hir::BinOpKind::Eq
412         | hir::BinOpKind::Ge
413         | hir::BinOpKind::Gt
414         | hir::BinOpKind::Le
415         | hir::BinOpKind::Lt
416         | hir::BinOpKind::Ne
417         | hir::BinOpKind::Or
418         => panic!("This operator does not exist"),
419     })
420 }
421
422 /// Convert an `ast::BinOp` to the corresponding assigning binary operator.
423 fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
424     use crate::syntax::ast::BinOpKind::*;
425     use crate::syntax::parse::token::BinOpToken;
426
427     AssocOp::AssignOp(match op.node {
428         Add => BinOpToken::Plus,
429         BitAnd => BinOpToken::And,
430         BitOr => BinOpToken::Or,
431         BitXor => BinOpToken::Caret,
432         Div => BinOpToken::Slash,
433         Mul => BinOpToken::Star,
434         Rem => BinOpToken::Percent,
435         Shl => BinOpToken::Shl,
436         Shr => BinOpToken::Shr,
437         Sub => BinOpToken::Minus,
438         And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
439     })
440 }
441
442 /// Return the indentation before `span` if there are nothing but `[ \t]`
443 /// before it on its line.
444 fn indentation<'a, T: LintContext<'a>>(cx: &T, span: Span) -> Option<String> {
445     let lo = cx.sess().source_map().lookup_char_pos(span.lo());
446     if let Some(line) = lo.file
447         .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
448     {
449         if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
450             // we can mix char and byte positions here because we only consider `[ \t]`
451             if lo.col == CharPos(pos) {
452                 Some(line[..pos].into())
453             } else {
454                 None
455             }
456         } else {
457             None
458         }
459     } else {
460         None
461     }
462 }
463
464 /// Convenience extension trait for `DiagnosticBuilder`.
465 pub trait DiagnosticBuilderExt<'a, T: LintContext<'a>> {
466     /// Suggests to add an attribute to an item.
467     ///
468     /// Correctly handles indentation of the attribute and item.
469     ///
470     /// # Example
471     ///
472     /// ```rust,ignore
473     /// db.suggest_item_with_attr(cx, item, "#[derive(Default)]");
474     /// ```
475     fn suggest_item_with_attr<D: Display + ?Sized>(&mut self, cx: &T, item: Span, msg: &str, attr: &D, applicability: Applicability);
476
477     /// Suggest to add an item before another.
478     ///
479     /// The item should not be indented (expect for inner indentation).
480     ///
481     /// # Example
482     ///
483     /// ```rust,ignore
484     /// db.suggest_prepend_item(cx, item,
485     /// "fn foo() {
486     ///     bar();
487     /// }");
488     /// ```
489     fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
490
491     /// Suggest to completely remove an item.
492     ///
493     /// This will remove an item and all following whitespace until the next non-whitespace
494     /// character. This should work correctly if item is on the same indentation level as the
495     /// following item.
496     ///
497     /// # Example
498     ///
499     /// ```rust,ignore
500     /// db.suggest_remove_item(cx, item, "remove this")
501     /// ```
502     fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
503 }
504
505 impl<'a, 'b, 'c, T: LintContext<'c>> DiagnosticBuilderExt<'c, T> for rustc_errors::DiagnosticBuilder<'b> {
506     fn suggest_item_with_attr<D: Display + ?Sized>(&mut self, cx: &T, item: Span, msg: &str, attr: &D, applicability: Applicability) {
507         if let Some(indent) = indentation(cx, item) {
508             let span = item.with_hi(item.lo());
509
510             self.span_suggestion_with_applicability(
511                         span,
512                         msg,
513                         format!("{}\n{}", attr, indent),
514                         applicability,
515                         );
516         }
517     }
518
519     fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
520         if let Some(indent) = indentation(cx, item) {
521             let span = item.with_hi(item.lo());
522
523             let mut first = true;
524             let new_item = new_item
525                 .lines()
526                 .map(|l| {
527                     if first {
528                         first = false;
529                         format!("{}\n", l)
530                     } else {
531                         format!("{}{}\n", indent, l)
532                     }
533                 })
534                 .collect::<String>();
535
536             self.span_suggestion_with_applicability(
537                         span,
538                         msg,
539                         format!("{}\n{}", new_item, indent),
540                         applicability,
541                         );
542         }
543     }
544
545     fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
546         let mut remove_span = item;
547         let hi = cx.sess().source_map().next_point(remove_span).hi();
548         let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
549
550         if let Some(ref src) = fmpos.fm.src {
551             let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
552
553             if let Some(non_whitespace_offset) = non_whitespace_offset {
554                 remove_span = remove_span.with_hi(remove_span.hi() + BytePos(non_whitespace_offset as u32))
555             }
556         }
557
558         self.span_suggestion_with_applicability(
559             remove_span,
560             msg,
561             String::new(),
562             applicability,
563         );
564     }
565 }