1 //! Contains utility functions to generate suggestions.
2 #![deny(missing_docs_in_private_items)]
3 // currently ignores lifetimes and generics
7 use rustc
::lint
::{EarlyContext, LateContext, LintContext}
;
10 use std
::fmt
::Display
;
12 use syntax
::codemap
::{CharPos, Span}
;
13 use syntax
::parse
::token
;
14 use syntax
::print
::pprust
::token_to_string
;
15 use syntax
::util
::parser
::AssocOp
;
17 use utils
::{higher, snippet, snippet_opt}
;
18 use syntax_pos
::{BytePos, Pos}
;
20 /// A helper type to build suggestion correctly handling parenthesis.
22 /// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
23 NonParen(Cow
<'a
, str>),
24 /// An expression that does not fit in other variants.
25 MaybeParen(Cow
<'a
, str>),
26 /// A binary operator expression, including `as`-casts and explicit type
28 BinOp(AssocOp
, Cow
<'a
, str>),
31 /// Literal constant `1`, for convenience.
32 pub const ONE
: Sugg
<'
static> = Sugg
::NonParen(Cow
::Borrowed("1"));
34 impl<'a
> Display
for Sugg
<'a
> {
35 fn fmt(&self, f
: &mut std
::fmt
::Formatter
) -> Result
<(), std
::fmt
::Error
> {
37 Sugg
::NonParen(ref s
) | Sugg
::MaybeParen(ref s
) | Sugg
::BinOp(_
, ref s
) => s
.fmt(f
),
42 #[allow(wrong_self_convention)] // ok, because of the function `as_ty` method
44 /// Prepare a suggestion from an expression.
45 pub fn hir_opt(cx
: &LateContext
, expr
: &hir
::Expr
) -> Option
<Self> {
46 snippet_opt(cx
, expr
.span
).map(|snippet
| {
47 let snippet
= Cow
::Owned(snippet
);
51 hir
::ExprClosure(.., _
) |
54 hir
::ExprMatch(..) => Sugg
::MaybeParen(snippet
),
63 hir
::ExprInlineAsm(..) |
66 hir
::ExprMethodCall(..) |
72 hir
::ExprTupField(..) |
73 hir
::ExprWhile(..) => Sugg
::NonParen(snippet
),
74 hir
::ExprAssign(..) => Sugg
::BinOp(AssocOp
::Assign
, snippet
),
75 hir
::ExprAssignOp(op
, ..) => Sugg
::BinOp(hirbinop2assignop(op
), snippet
),
76 hir
::ExprBinary(op
, ..) => Sugg
::BinOp(AssocOp
::from_ast_binop(higher
::binop(op
.node
)), snippet
),
77 hir
::ExprCast(..) => Sugg
::BinOp(AssocOp
::As
, snippet
),
78 hir
::ExprType(..) => Sugg
::BinOp(AssocOp
::Colon
, snippet
),
83 /// Convenience function around `hir_opt` for suggestions with a default
85 pub fn hir(cx
: &LateContext
, expr
: &hir
::Expr
, default: &'a
str) -> Self {
86 Self::hir_opt(cx
, expr
).unwrap_or_else(|| Sugg
::NonParen(Cow
::Borrowed(default)))
89 /// Prepare a suggestion from an expression.
90 pub fn ast(cx
: &EarlyContext
, expr
: &ast
::Expr
, default: &'a
str) -> Self {
91 use syntax
::ast
::RangeLimits
;
93 let snippet
= snippet(cx
, expr
.span
, default);
96 ast
::ExprKind
::AddrOf(..) |
97 ast
::ExprKind
::Box(..) |
98 ast
::ExprKind
::Closure(..) |
99 ast
::ExprKind
::If(..) |
100 ast
::ExprKind
::IfLet(..) |
101 ast
::ExprKind
::InPlace(..) |
102 ast
::ExprKind
::Unary(..) |
103 ast
::ExprKind
::Match(..) => Sugg
::MaybeParen(snippet
),
104 ast
::ExprKind
::Block(..) |
105 ast
::ExprKind
::Break(..) |
106 ast
::ExprKind
::Call(..) |
107 ast
::ExprKind
::Catch(..) |
108 ast
::ExprKind
::Continue(..) |
109 ast
::ExprKind
::Yield(..) |
110 ast
::ExprKind
::Field(..) |
111 ast
::ExprKind
::ForLoop(..) |
112 ast
::ExprKind
::Index(..) |
113 ast
::ExprKind
::InlineAsm(..) |
114 ast
::ExprKind
::Lit(..) |
115 ast
::ExprKind
::Loop(..) |
116 ast
::ExprKind
::Mac(..) |
117 ast
::ExprKind
::MethodCall(..) |
118 ast
::ExprKind
::Paren(..) |
119 ast
::ExprKind
::Path(..) |
120 ast
::ExprKind
::Repeat(..) |
121 ast
::ExprKind
::Ret(..) |
122 ast
::ExprKind
::Struct(..) |
123 ast
::ExprKind
::Try(..) |
124 ast
::ExprKind
::Tup(..) |
125 ast
::ExprKind
::TupField(..) |
126 ast
::ExprKind
::Array(..) |
127 ast
::ExprKind
::While(..) |
128 ast
::ExprKind
::WhileLet(..) => Sugg
::NonParen(snippet
),
129 ast
::ExprKind
::Range(.., RangeLimits
::HalfOpen
) => Sugg
::BinOp(AssocOp
::DotDot
, snippet
),
130 ast
::ExprKind
::Range(.., RangeLimits
::Closed
) => Sugg
::BinOp(AssocOp
::DotDotEq
, snippet
),
131 ast
::ExprKind
::Assign(..) => Sugg
::BinOp(AssocOp
::Assign
, snippet
),
132 ast
::ExprKind
::AssignOp(op
, ..) => Sugg
::BinOp(astbinop2assignop(op
), snippet
),
133 ast
::ExprKind
::Binary(op
, ..) => Sugg
::BinOp(AssocOp
::from_ast_binop(op
.node
), snippet
),
134 ast
::ExprKind
::Cast(..) => Sugg
::BinOp(AssocOp
::As
, snippet
),
135 ast
::ExprKind
::Type(..) => Sugg
::BinOp(AssocOp
::Colon
, snippet
),
139 /// Convenience method to create the `<lhs> && <rhs>` suggestion.
140 pub fn and(self, rhs
: &Self) -> Sugg
<'
static> {
141 make_binop(ast
::BinOpKind
::And
, &self, rhs
)
144 /// Convenience method to create the `<lhs> as <rhs>` suggestion.
145 pub fn as_ty
<R
: Display
>(self, rhs
: R
) -> Sugg
<'
static> {
146 make_assoc(AssocOp
::As
, &self, &Sugg
::NonParen(rhs
.to_string().into()))
149 /// Convenience method to create the `&<expr>` suggestion.
150 pub fn addr(self) -> Sugg
<'
static> {
154 /// Convenience method to create the `&mut <expr>` suggestion.
155 pub fn mut_addr(self) -> Sugg
<'
static> {
156 make_unop("&mut ", self)
159 /// Convenience method to create the `*<expr>` suggestion.
160 pub fn deref(self) -> Sugg
<'
static> {
164 /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
166 pub fn range(self, end
: &Self, limit
: ast
::RangeLimits
) -> Sugg
<'
static> {
168 ast
::RangeLimits
::HalfOpen
=> make_assoc(AssocOp
::DotDot
, &self, end
),
169 ast
::RangeLimits
::Closed
=> make_assoc(AssocOp
::DotDotEq
, &self, end
),
173 /// Add parenthesis to any expression that might need them. Suitable to the
174 /// `self` argument of
175 /// a method call (eg. to build `bar.foo()` or `(1 + 2).foo()`).
176 pub fn maybe_par(self) -> Self {
178 Sugg
::NonParen(..) => self,
179 // (x) and (x).y() both don't need additional parens
180 Sugg
::MaybeParen(sugg
) => if sugg
.starts_with('
('
) && sugg
.ends_with('
)'
) {
181 Sugg
::MaybeParen(sugg
)
183 Sugg
::NonParen(format
!("({})", sugg
).into())
185 Sugg
::BinOp(_
, sugg
) => Sugg
::NonParen(format
!("({})", sugg
).into()),
190 impl<'a
, 'b
> std
::ops
::Add
<Sugg
<'b
>> for Sugg
<'a
> {
191 type Output
= Sugg
<'
static>;
192 fn add(self, rhs
: Sugg
<'b
>) -> Sugg
<'
static> {
193 make_binop(ast
::BinOpKind
::Add
, &self, &rhs
)
197 impl<'a
, 'b
> std
::ops
::Sub
<Sugg
<'b
>> for Sugg
<'a
> {
198 type Output
= Sugg
<'
static>;
199 fn sub(self, rhs
: Sugg
<'b
>) -> Sugg
<'
static> {
200 make_binop(ast
::BinOpKind
::Sub
, &self, &rhs
)
204 impl<'a
> std
::ops
::Not
for Sugg
<'a
> {
205 type Output
= Sugg
<'
static>;
206 fn not(self) -> Sugg
<'
static> {
211 /// Helper type to display either `foo` or `(foo)`.
212 struct ParenHelper
<T
> {
213 /// Whether parenthesis are needed.
215 /// The main thing to display.
219 impl<T
> ParenHelper
<T
> {
220 /// Build a `ParenHelper`.
221 fn new(paren
: bool
, wrapped
: T
) -> Self {
229 impl<T
: Display
> Display
for ParenHelper
<T
> {
230 fn fmt(&self, f
: &mut std
::fmt
::Formatter
) -> Result
<(), std
::fmt
::Error
> {
232 write
!(f
, "({})", self.wrapped
)
239 /// Build the string for `<op><expr>` adding parenthesis when necessary.
241 /// For convenience, the operator is taken as a string because all unary
242 /// operators have the same
244 pub fn make_unop(op
: &str, expr
: Sugg
) -> Sugg
<'
static> {
245 Sugg
::MaybeParen(format
!("{}{}", op
, expr
.maybe_par()).into())
248 /// Build the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
250 /// Precedence of shift operator relative to other arithmetic operation is
251 /// often confusing so
252 /// parenthesis will always be added for a mix of these.
253 pub fn make_assoc(op
: AssocOp
, lhs
: &Sugg
, rhs
: &Sugg
) -> Sugg
<'
static> {
254 /// Whether the operator is a shift operator `<<` or `>>`.
255 fn is_shift(op
: &AssocOp
) -> bool
{
256 matches
!(*op
, AssocOp
::ShiftLeft
| AssocOp
::ShiftRight
)
259 /// Whether the operator is a arithmetic operator (`+`, `-`, `*`, `/`, `%`).
260 fn is_arith(op
: &AssocOp
) -> bool
{
263 AssocOp
::Add
| AssocOp
::Subtract
| AssocOp
::Multiply
| AssocOp
::Divide
| AssocOp
::Modulus
267 /// Whether the operator `op` needs parenthesis with the operator `other`
270 fn needs_paren(op
: &AssocOp
, other
: &AssocOp
, dir
: Associativity
) -> bool
{
271 other
.precedence() < op
.precedence()
272 || (other
.precedence() == op
.precedence()
273 && ((op
!= other
&& associativity(op
) != dir
)
274 || (op
== other
&& associativity(op
) != Associativity
::Both
)))
275 || is_shift(op
) && is_arith(other
) || is_shift(other
) && is_arith(op
)
278 let lhs_paren
= if let Sugg
::BinOp(ref lop
, _
) = *lhs
{
279 needs_paren(&op
, lop
, Associativity
::Left
)
284 let rhs_paren
= if let Sugg
::BinOp(ref rop
, _
) = *rhs
{
285 needs_paren(&op
, rop
, Associativity
::Right
)
290 let lhs
= ParenHelper
::new(lhs_paren
, lhs
);
291 let rhs
= ParenHelper
::new(rhs_paren
, rhs
);
292 let sugg
= match op
{
300 AssocOp
::GreaterEqual
|
309 AssocOp
::ShiftRight
|
310 AssocOp
::Subtract
=> format
!("{} {} {}", lhs
, op
.to_ast_binop().expect("Those are AST ops").to_string(), rhs
),
311 AssocOp
::Inplace
=> format
!("in ({}) {}", lhs
, rhs
),
312 AssocOp
::Assign
=> format
!("{} = {}", lhs
, rhs
),
313 AssocOp
::AssignOp(op
) => format
!("{} {}= {}", lhs
, token_to_string(&token
::BinOp(op
)), rhs
),
314 AssocOp
::As
=> format
!("{} as {}", lhs
, rhs
),
315 AssocOp
::DotDot
=> format
!("{}..{}", lhs
, rhs
),
316 AssocOp
::DotDotEq
=> format
!("{}..={}", lhs
, rhs
),
317 AssocOp
::Colon
=> format
!("{}: {}", lhs
, rhs
),
320 Sugg
::BinOp(op
, sugg
.into())
323 /// Convinience wrapper arround `make_assoc` and `AssocOp::from_ast_binop`.
324 pub fn make_binop(op
: ast
::BinOpKind
, lhs
: &Sugg
, rhs
: &Sugg
) -> Sugg
<'
static> {
325 make_assoc(AssocOp
::from_ast_binop(op
), lhs
, rhs
)
328 #[derive(PartialEq, Eq, Clone, Copy)]
329 /// Operator associativity.
331 /// The operator is both left-associative and right-associative.
333 /// The operator is left-associative.
335 /// The operator is not associative.
337 /// The operator is right-associative.
341 /// Return the associativity/fixity of an operator. The difference with
342 /// `AssocOp::fixity` is that
343 /// an operator can be both left and right associative (such as `+`:
344 /// `a + b + c == (a + b) + c == a + (b + c)`.
346 /// Chained `as` and explicit `:` type coercion never need inner parenthesis so
347 /// they are considered
349 fn associativity(op
: &AssocOp
) -> Associativity
{
350 use syntax
::util
::parser
::AssocOp
::*;
353 Inplace
| Assign
| AssignOp(_
) => Associativity
::Right
,
354 Add
| BitAnd
| BitOr
| BitXor
| LAnd
| LOr
| Multiply
| As
| Colon
=> Associativity
::Both
,
365 Subtract
=> Associativity
::Left
,
366 DotDot
| DotDotEq
=> Associativity
::None
,
370 /// Convert a `hir::BinOp` to the corresponding assigning binary operator.
371 fn hirbinop2assignop(op
: hir
::BinOp
) -> AssocOp
{
372 use rustc
::hir
::BinOp_
::*;
373 use syntax
::parse
::token
::BinOpToken
::*;
375 AssocOp
::AssignOp(match op
.node
{
386 BiAnd
| BiEq
| BiGe
| BiGt
| BiLe
| BiLt
| BiNe
| BiOr
=> panic
!("This operator does not exist"),
390 /// Convert an `ast::BinOp` to the corresponding assigning binary operator.
391 fn astbinop2assignop(op
: ast
::BinOp
) -> AssocOp
{
392 use syntax
::ast
::BinOpKind
::*;
393 use syntax
::parse
::token
::BinOpToken
;
395 AssocOp
::AssignOp(match op
.node
{
396 Add
=> BinOpToken
::Plus
,
397 BitAnd
=> BinOpToken
::And
,
398 BitOr
=> BinOpToken
::Or
,
399 BitXor
=> BinOpToken
::Caret
,
400 Div
=> BinOpToken
::Slash
,
401 Mul
=> BinOpToken
::Star
,
402 Rem
=> BinOpToken
::Percent
,
403 Shl
=> BinOpToken
::Shl
,
404 Shr
=> BinOpToken
::Shr
,
405 Sub
=> BinOpToken
::Minus
,
406 And
| Eq
| Ge
| Gt
| Le
| Lt
| Ne
| Or
=> panic
!("This operator does not exist"),
410 /// Return the indentation before `span` if there are nothing but `[ \t]`
411 /// before it on its line.
412 fn indentation
<'a
, T
: LintContext
<'a
>>(cx
: &T
, span
: Span
) -> Option
<String
> {
413 let lo
= cx
.sess().codemap().lookup_char_pos(span
.lo());
414 if let Some(line
) = lo
.file
415 .get_line(lo
.line
- 1 /* line numbers in `Loc` are 1-based */)
417 if let Some((pos
, _
)) = line
.char_indices().find(|&(_
, c
)| c
!= ' '
&& c
!= '
\t'
) {
418 // we can mix char and byte positions here because we only consider `[ \t]`
419 if lo
.col
== CharPos(pos
) {
420 Some(line
[..pos
].into())
432 /// Convenience extension trait for `DiagnosticBuilder`.
433 pub trait DiagnosticBuilderExt
<'a
, T
: LintContext
<'a
>> {
434 /// Suggests to add an attribute to an item.
436 /// Correctly handles indentation of the attribute and item.
441 /// db.suggest_item_with_attr(cx, item, "#[derive(Default)]");
443 fn suggest_item_with_attr
<D
: Display
+ ?Sized
>(&mut self, cx
: &T
, item
: Span
, msg
: &str, attr
: &D
);
445 /// Suggest to add an item before another.
447 /// The item should not be indented (expect for inner indentation).
452 /// db.suggest_prepend_item(cx, item,
457 fn suggest_prepend_item(&mut self, cx
: &T
, item
: Span
, msg
: &str, new_item
: &str);
459 /// Suggest to completely remove an item.
461 /// This will remove an item and all following whitespace until the next non-whitespace
462 /// character. This should work correctly if item is on the same indentation level as the
468 /// db.suggest_remove_item(cx, item, "remove this")
470 fn suggest_remove_item(&mut self, cx
: &T
, item
: Span
, msg
: &str);
473 impl<'a
, 'b
, 'c
, T
: LintContext
<'c
>> DiagnosticBuilderExt
<'c
, T
> for rustc_errors
::DiagnosticBuilder
<'b
> {
474 fn suggest_item_with_attr
<D
: Display
+ ?Sized
>(&mut self, cx
: &T
, item
: Span
, msg
: &str, attr
: &D
) {
475 if let Some(indent
) = indentation(cx
, item
) {
476 let span
= item
.with_hi(item
.lo());
478 self.span_suggestion(span
, msg
, format
!("{}\n{}", attr
, indent
));
482 fn suggest_prepend_item(&mut self, cx
: &T
, item
: Span
, msg
: &str, new_item
: &str) {
483 if let Some(indent
) = indentation(cx
, item
) {
484 let span
= item
.with_hi(item
.lo());
486 let mut first
= true;
487 let new_item
= new_item
494 format
!("{}{}\n", indent
, l
)
497 .collect
::<String
>();
499 self.span_suggestion(span
, msg
, format
!("{}\n{}", new_item
, indent
));
503 fn suggest_remove_item(&mut self, cx
: &T
, item
: Span
, msg
: &str) {
504 let mut remove_span
= item
;
505 let hi
= cx
.sess().codemap().next_point(remove_span
).hi();
506 let fmpos
= cx
.sess().codemap().lookup_byte_offset(hi
);
508 if let Some(ref src
) = fmpos
.fm
.src
{
509 let non_whitespace_offset
= src
[fmpos
.pos
.to_usize()..].find(|c
| c
!= ' '
&& c
!= '
\t'
&& c
!= '
\n'
);
511 if let Some(non_whitespace_offset
) = non_whitespace_offset
{
512 remove_span
= remove_span
.with_hi(remove_span
.hi() + BytePos(non_whitespace_offset
as u32))
516 self.span_suggestion(remove_span
, msg
, String
::new());