]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/utils/sugg.rs
Rustup to ea0dc9297283daff6486807f43e190b4eb561412 III
[rust.git] / clippy_lints / src / utils / sugg.rs
1 use rustc::hir;
2 use rustc::lint::{EarlyContext, LateContext};
3 use std::borrow::Cow;
4 use std;
5 use syntax::ast;
6 use syntax::util::parser::AssocOp;
7 use utils::{higher, snippet};
8
9 /// A helper type to build suggestion correctly handling parenthesis.
10 pub enum Sugg<'a> {
11     /// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
12     NonParen(Cow<'a, str>),
13     /// An expression that does not fit in other variants.
14     MaybeParen(Cow<'a, str>),
15     /// A binary operator expression, including `as`-casts and explicit type coercion.
16     BinOp(AssocOp, Cow<'a, str>),
17 }
18
19 impl<'a> std::fmt::Display for Sugg<'a> {
20     fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
21         match *self {
22             Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => {
23                 s.fmt(f)
24             }
25         }
26     }
27 }
28
29 impl<'a> Sugg<'a> {
30     pub fn hir(cx: &LateContext, expr: &'a hir::Expr, default: &'a str) -> Sugg<'a> {
31         let snippet = snippet(cx, expr.span, default);
32
33         match expr.node {
34             hir::ExprAddrOf(..) |
35             hir::ExprBox(..) |
36             hir::ExprClosure(..) |
37             hir::ExprIf(..) |
38             hir::ExprUnary(..) |
39             hir::ExprMatch(..) => Sugg::MaybeParen(snippet),
40             hir::ExprAgain(..) |
41             hir::ExprBlock(..) |
42             hir::ExprBreak(..) |
43             hir::ExprCall(..) |
44             hir::ExprField(..) |
45             hir::ExprIndex(..) |
46             hir::ExprInlineAsm(..) |
47             hir::ExprLit(..) |
48             hir::ExprLoop(..) |
49             hir::ExprMethodCall(..) |
50             hir::ExprPath(..) |
51             hir::ExprRepeat(..) |
52             hir::ExprRet(..) |
53             hir::ExprStruct(..) |
54             hir::ExprTup(..) |
55             hir::ExprTupField(..) |
56             hir::ExprVec(..) |
57             hir::ExprWhile(..) => Sugg::NonParen(snippet),
58             hir::ExprAssign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
59             hir::ExprAssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
60             hir::ExprBinary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
61             hir::ExprCast(..) => Sugg::BinOp(AssocOp::As, snippet),
62             hir::ExprType(..) => Sugg::BinOp(AssocOp::Colon, snippet),
63         }
64     }
65
66     pub fn ast(cx: &EarlyContext, expr: &'a ast::Expr, default: &'a str) -> Sugg<'a> {
67         use syntax::ast::RangeLimits;
68
69         let snippet = snippet(cx, expr.span, default);
70
71         match expr.node {
72             ast::ExprKind::AddrOf(..) |
73             ast::ExprKind::Box(..) |
74             ast::ExprKind::Closure(..) |
75             ast::ExprKind::If(..) |
76             ast::ExprKind::IfLet(..) |
77             ast::ExprKind::InPlace(..) |
78             ast::ExprKind::Unary(..) |
79             ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
80             ast::ExprKind::Block(..) |
81             ast::ExprKind::Break(..) |
82             ast::ExprKind::Call(..) |
83             ast::ExprKind::Continue(..) |
84             ast::ExprKind::Field(..) |
85             ast::ExprKind::ForLoop(..) |
86             ast::ExprKind::Index(..) |
87             ast::ExprKind::InlineAsm(..) |
88             ast::ExprKind::Lit(..) |
89             ast::ExprKind::Loop(..) |
90             ast::ExprKind::Mac(..) |
91             ast::ExprKind::MethodCall(..) |
92             ast::ExprKind::Paren(..) |
93             ast::ExprKind::Path(..) |
94             ast::ExprKind::Repeat(..) |
95             ast::ExprKind::Ret(..) |
96             ast::ExprKind::Struct(..) |
97             ast::ExprKind::Try(..) |
98             ast::ExprKind::Tup(..) |
99             ast::ExprKind::TupField(..) |
100             ast::ExprKind::Vec(..) |
101             ast::ExprKind::While(..) |
102             ast::ExprKind::WhileLet(..) => Sugg::NonParen(snippet),
103             ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
104             ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotDot, snippet),
105             ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
106             ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
107             ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
108             ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
109             ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
110         }
111     }
112
113     /// Convenience method to create the `lhs && rhs` suggestion.
114     pub fn and(&self, rhs: &Self) -> Sugg<'static> {
115         make_binop(ast::BinOpKind::And, self, rhs)
116     }
117
118     /// Convenience method to create the `&<expr>` suggestion.
119     pub fn addr(&self) -> Sugg<'static> {
120         make_unop("&", self)
121     }
122 }
123
124 impl<'a, 'b> std::ops::Sub<&'b Sugg<'b>> for &'a Sugg<'a> {
125     type Output = Sugg<'static>;
126     fn sub(self, rhs: &'b Sugg<'b>) -> Sugg<'static> {
127         make_binop(ast::BinOpKind::Sub, self, rhs)
128     }
129 }
130
131 impl<'a> std::ops::Not for &'a Sugg<'a> {
132     type Output = Sugg<'static>;
133     fn not(self) -> Sugg<'static> {
134         make_unop("!", self)
135     }
136 }
137
138 struct ParenHelper<T> {
139     paren: bool,
140     wrapped: T,
141 }
142
143 impl<T> ParenHelper<T> {
144     fn new(paren: bool, wrapped: T) -> Self {
145         ParenHelper {
146             paren: paren,
147             wrapped: wrapped,
148         }
149     }
150 }
151
152 impl<T: std::fmt::Display> std::fmt::Display for ParenHelper<T> {
153     fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
154         if self.paren {
155             write!(f, "({})", self.wrapped)
156         } else {
157             self.wrapped.fmt(f)
158         }
159     }
160 }
161
162 /// Build the string for `<op> <expr>` adding parenthesis when necessary.
163 ///
164 /// For convenience, the operator is taken as a string because all unary operators have the same
165 /// precedence.
166 pub fn make_unop(op: &str, expr: &Sugg) -> Sugg<'static> {
167     let needs_paren = !matches!(*expr, Sugg::NonParen(..));
168     Sugg::MaybeParen(format!("{}{}", op, ParenHelper::new(needs_paren, expr)).into())
169 }
170
171 /// Build the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
172 ///
173 /// Precedence of shift operator relative to other arithmetic operation is often confusing so
174 /// parenthesis will always be added for a mix of these.
175 pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg, rhs: &Sugg) -> Sugg<'static> {
176     fn is_shift(op: &AssocOp) -> bool {
177         matches!(*op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
178     }
179
180     fn is_arith(op: &AssocOp) -> bool {
181         matches!(*op, AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus)
182     }
183
184     fn needs_paren(op: &AssocOp, other: &AssocOp, dir: Associativity) -> bool {
185         other.precedence() < op.precedence() ||
186             (other.precedence() == op.precedence() &&
187                 ((op != other && associativity(op) != dir) ||
188                  (op == other && associativity(op) != Associativity::Both))) ||
189              is_shift(op) && is_arith(other) ||
190              is_shift(other) && is_arith(op)
191     }
192
193     let aop = AssocOp::from_ast_binop(op);
194
195     let lhs_paren = if let Sugg::BinOp(ref lop, _) = *lhs {
196         needs_paren(&aop, lop, Associativity::Left)
197     } else {
198         false
199     };
200
201     let rhs_paren = if let Sugg::BinOp(ref rop, _) = *rhs {
202         needs_paren(&aop, rop, Associativity::Right)
203     } else {
204         false
205     };
206
207     Sugg::BinOp(aop,
208                 format!("{} {} {}",
209                         ParenHelper::new(lhs_paren, lhs),
210                         op.to_string(),
211                         ParenHelper::new(rhs_paren, rhs)).into())
212 }
213
214 #[derive(PartialEq, Eq)]
215 enum Associativity {
216     Both,
217     Left,
218     None,
219     Right,
220 }
221
222 /// Return the associativity/fixity of an operator. The difference with `AssocOp::fixity` is that
223 /// an operator can be both left and right associative (such as `+`:
224 /// `a + b + c == (a + b) + c == a + (b + c)`.
225 ///
226 /// Chained `as` and explicit `:` type coercion never need inner parenthesis so they are considered
227 /// associative.
228 fn associativity(op: &AssocOp) -> Associativity {
229     use syntax::util::parser::AssocOp::*;
230
231     match *op {
232         Inplace | Assign | AssignOp(_) => Associativity::Right,
233         Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply |
234         As | Colon => Associativity::Both,
235     Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft |
236         ShiftRight | Subtract => Associativity::Left,
237         DotDot | DotDotDot => Associativity::None
238     }
239 }
240
241 /// Convert a `hir::BinOp` to the corresponding assigning binary operator.
242 fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
243     use rustc::hir::BinOp_::*;
244     use syntax::parse::token::BinOpToken::*;
245
246     AssocOp::AssignOp(match op.node {
247         BiAdd => Plus,
248         BiBitAnd => And,
249         BiBitOr => Or,
250         BiBitXor => Caret,
251         BiDiv => Slash,
252         BiMul => Star,
253         BiRem => Percent,
254         BiShl => Shl,
255         BiShr => Shr,
256         BiSub => Minus,
257         BiAnd | BiEq | BiGe | BiGt | BiLe | BiLt | BiNe | BiOr => panic!("This operator does not exist"),
258     })
259 }
260
261 /// Convert an `ast::BinOp` to the corresponding assigning binary operator.
262 fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
263     use syntax::ast::BinOpKind::*;
264     use syntax::parse::token::BinOpToken;
265
266     AssocOp::AssignOp(match op.node {
267         Add => BinOpToken::Plus,
268         BitAnd => BinOpToken::And,
269         BitOr => BinOpToken::Or,
270         BitXor => BinOpToken::Caret,
271         Div => BinOpToken::Slash,
272         Mul => BinOpToken::Star,
273         Rem => BinOpToken::Percent,
274         Shl => BinOpToken::Shl,
275         Shr => BinOpToken::Shr,
276         Sub => BinOpToken::Minus,
277         And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
278     })
279 }