]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/sugg.rs
Add underscore expressions for destructuring assignments
[rust.git] / src / tools / clippy / clippy_lints / src / utils / sugg.rs
1 //! Contains utility functions to generate suggestions.
2 #![deny(clippy::missing_docs_in_private_items)]
3
4 use crate::utils::{higher, snippet, snippet_opt, snippet_with_macro_callsite};
5 use rustc_ast::util::parser::AssocOp;
6 use rustc_ast::{ast, token};
7 use rustc_ast_pretty::pprust::token_kind_to_string;
8 use rustc_errors::Applicability;
9 use rustc_hir as hir;
10 use rustc_lint::{EarlyContext, LateContext, LintContext};
11 use rustc_span::source_map::{CharPos, Span};
12 use rustc_span::{BytePos, Pos};
13 use std::borrow::Cow;
14 use std::convert::TryInto;
15 use std::fmt::Display;
16 use std::ops::{Add, Neg, Not, Sub};
17
18 /// A helper type to build suggestion correctly handling parenthesis.
19 #[derive(Clone, PartialEq)]
20 pub enum Sugg<'a> {
21     /// An expression that never needs parenthesis such as `1337` or `[0; 42]`.
22     NonParen(Cow<'a, str>),
23     /// An expression that does not fit in other variants.
24     MaybeParen(Cow<'a, str>),
25     /// A binary operator expression, including `as`-casts and explicit type
26     /// coercion.
27     BinOp(AssocOp, Cow<'a, str>),
28 }
29
30 /// Literal constant `0`, for convenience.
31 pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
32 /// Literal constant `1`, for convenience.
33 pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
34 /// a constant represents an empty string, for convenience.
35 pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
36
37 impl Display for Sugg<'_> {
38     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
39         match *self {
40             Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) | Sugg::BinOp(_, ref s) => s.fmt(f),
41         }
42     }
43 }
44
45 #[allow(clippy::wrong_self_convention)] // ok, because of the function `as_ty` method
46 impl<'a> Sugg<'a> {
47     /// Prepare a suggestion from an expression.
48     pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
49         snippet_opt(cx, expr.span).map(|snippet| {
50             let snippet = Cow::Owned(snippet);
51             Self::hir_from_snippet(expr, snippet)
52         })
53     }
54
55     /// Convenience function around `hir_opt` for suggestions with a default
56     /// text.
57     pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
58         Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
59     }
60
61     /// Same as `hir`, but it adapts the applicability level by following rules:
62     ///
63     /// - Applicability level `Unspecified` will never be changed.
64     /// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
65     /// - If the default value is used and the applicability level is `MachineApplicable`, change it
66     ///   to
67     /// `HasPlaceholders`
68     pub fn hir_with_applicability(
69         cx: &LateContext<'_>,
70         expr: &hir::Expr<'_>,
71         default: &'a str,
72         applicability: &mut Applicability,
73     ) -> Self {
74         if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
75             *applicability = Applicability::MaybeIncorrect;
76         }
77         Self::hir_opt(cx, expr).unwrap_or_else(|| {
78             if *applicability == Applicability::MachineApplicable {
79                 *applicability = Applicability::HasPlaceholders;
80             }
81             Sugg::NonParen(Cow::Borrowed(default))
82         })
83     }
84
85     /// Same as `hir`, but will use the pre expansion span if the `expr` was in a macro.
86     pub fn hir_with_macro_callsite(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
87         let snippet = snippet_with_macro_callsite(cx, expr.span, default);
88
89         Self::hir_from_snippet(expr, snippet)
90     }
91
92     /// Generate a suggestion for an expression with the given snippet. This is used by the `hir_*`
93     /// function variants of `Sugg`, since these use different snippet functions.
94     fn hir_from_snippet(expr: &hir::Expr<'_>, snippet: Cow<'a, str>) -> Self {
95         if let Some(range) = higher::range(expr) {
96             let op = match range.limits {
97                 ast::RangeLimits::HalfOpen => AssocOp::DotDot,
98                 ast::RangeLimits::Closed => AssocOp::DotDotEq,
99             };
100             return Sugg::BinOp(op, snippet);
101         }
102
103         match expr.kind {
104             hir::ExprKind::AddrOf(..)
105             | hir::ExprKind::Box(..)
106             | hir::ExprKind::Closure(..)
107             | hir::ExprKind::Unary(..)
108             | hir::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
109             hir::ExprKind::Continue(..)
110             | hir::ExprKind::Yield(..)
111             | hir::ExprKind::Array(..)
112             | hir::ExprKind::Block(..)
113             | hir::ExprKind::Break(..)
114             | hir::ExprKind::Call(..)
115             | hir::ExprKind::Field(..)
116             | hir::ExprKind::Index(..)
117             | hir::ExprKind::InlineAsm(..)
118             | hir::ExprKind::LlvmInlineAsm(..)
119             | hir::ExprKind::ConstBlock(..)
120             | hir::ExprKind::Lit(..)
121             | hir::ExprKind::Loop(..)
122             | hir::ExprKind::MethodCall(..)
123             | hir::ExprKind::Path(..)
124             | hir::ExprKind::Repeat(..)
125             | hir::ExprKind::Ret(..)
126             | hir::ExprKind::Struct(..)
127             | hir::ExprKind::Tup(..)
128             | hir::ExprKind::DropTemps(_)
129             | hir::ExprKind::Err => Sugg::NonParen(snippet),
130             hir::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
131             hir::ExprKind::AssignOp(op, ..) => Sugg::BinOp(hirbinop2assignop(op), snippet),
132             hir::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(higher::binop(op.node)), snippet),
133             hir::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
134             hir::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
135         }
136     }
137
138     /// Prepare a suggestion from an expression.
139     pub fn ast(cx: &EarlyContext<'_>, expr: &ast::Expr, default: &'a str) -> Self {
140         use rustc_ast::ast::RangeLimits;
141
142         let snippet = if expr.span.from_expansion() {
143             snippet_with_macro_callsite(cx, expr.span, default)
144         } else {
145             snippet(cx, expr.span, default)
146         };
147
148         match expr.kind {
149             ast::ExprKind::AddrOf(..)
150             | ast::ExprKind::Box(..)
151             | ast::ExprKind::Closure(..)
152             | ast::ExprKind::If(..)
153             | ast::ExprKind::Let(..)
154             | ast::ExprKind::Unary(..)
155             | ast::ExprKind::Match(..) => Sugg::MaybeParen(snippet),
156             ast::ExprKind::Async(..)
157             | ast::ExprKind::Block(..)
158             | ast::ExprKind::Break(..)
159             | ast::ExprKind::Call(..)
160             | ast::ExprKind::Continue(..)
161             | ast::ExprKind::Yield(..)
162             | ast::ExprKind::Field(..)
163             | ast::ExprKind::ForLoop(..)
164             | ast::ExprKind::Index(..)
165             | ast::ExprKind::InlineAsm(..)
166             | ast::ExprKind::LlvmInlineAsm(..)
167             | ast::ExprKind::ConstBlock(..)
168             | ast::ExprKind::Lit(..)
169             | ast::ExprKind::Loop(..)
170             | ast::ExprKind::MacCall(..)
171             | ast::ExprKind::MethodCall(..)
172             | ast::ExprKind::Paren(..)
173             | ast::ExprKind::Underscore
174             | ast::ExprKind::Path(..)
175             | ast::ExprKind::Repeat(..)
176             | ast::ExprKind::Ret(..)
177             | ast::ExprKind::Struct(..)
178             | ast::ExprKind::Try(..)
179             | ast::ExprKind::TryBlock(..)
180             | ast::ExprKind::Tup(..)
181             | ast::ExprKind::Array(..)
182             | ast::ExprKind::While(..)
183             | ast::ExprKind::Await(..)
184             | ast::ExprKind::Err => Sugg::NonParen(snippet),
185             ast::ExprKind::Range(.., RangeLimits::HalfOpen) => Sugg::BinOp(AssocOp::DotDot, snippet),
186             ast::ExprKind::Range(.., RangeLimits::Closed) => Sugg::BinOp(AssocOp::DotDotEq, snippet),
187             ast::ExprKind::Assign(..) => Sugg::BinOp(AssocOp::Assign, snippet),
188             ast::ExprKind::AssignOp(op, ..) => Sugg::BinOp(astbinop2assignop(op), snippet),
189             ast::ExprKind::Binary(op, ..) => Sugg::BinOp(AssocOp::from_ast_binop(op.node), snippet),
190             ast::ExprKind::Cast(..) => Sugg::BinOp(AssocOp::As, snippet),
191             ast::ExprKind::Type(..) => Sugg::BinOp(AssocOp::Colon, snippet),
192         }
193     }
194
195     /// Convenience method to create the `<lhs> && <rhs>` suggestion.
196     pub fn and(self, rhs: &Self) -> Sugg<'static> {
197         make_binop(ast::BinOpKind::And, &self, rhs)
198     }
199
200     /// Convenience method to create the `<lhs> & <rhs>` suggestion.
201     pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
202         make_binop(ast::BinOpKind::BitAnd, &self, rhs)
203     }
204
205     /// Convenience method to create the `<lhs> as <rhs>` suggestion.
206     pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
207         make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
208     }
209
210     /// Convenience method to create the `&<expr>` suggestion.
211     pub fn addr(self) -> Sugg<'static> {
212         make_unop("&", self)
213     }
214
215     /// Convenience method to create the `&mut <expr>` suggestion.
216     pub fn mut_addr(self) -> Sugg<'static> {
217         make_unop("&mut ", self)
218     }
219
220     /// Convenience method to create the `*<expr>` suggestion.
221     pub fn deref(self) -> Sugg<'static> {
222         make_unop("*", self)
223     }
224
225     /// Convenience method to create the `&*<expr>` suggestion. Currently this
226     /// is needed because `sugg.deref().addr()` produces an unnecessary set of
227     /// parentheses around the deref.
228     pub fn addr_deref(self) -> Sugg<'static> {
229         make_unop("&*", self)
230     }
231
232     /// Convenience method to create the `&mut *<expr>` suggestion. Currently
233     /// this is needed because `sugg.deref().mut_addr()` produces an unnecessary
234     /// set of parentheses around the deref.
235     pub fn mut_addr_deref(self) -> Sugg<'static> {
236         make_unop("&mut *", self)
237     }
238
239     /// Convenience method to transform suggestion into a return call
240     pub fn make_return(self) -> Sugg<'static> {
241         Sugg::NonParen(Cow::Owned(format!("return {}", self)))
242     }
243
244     /// Convenience method to transform suggestion into a block
245     /// where the suggestion is a trailing expression
246     pub fn blockify(self) -> Sugg<'static> {
247         Sugg::NonParen(Cow::Owned(format!("{{ {} }}", self)))
248     }
249
250     /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
251     /// suggestion.
252     #[allow(dead_code)]
253     pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
254         match limit {
255             ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
256             ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
257         }
258     }
259
260     /// Adds parenthesis to any expression that might need them. Suitable to the
261     /// `self` argument of a method call
262     /// (e.g., to build `bar.foo()` or `(1 + 2).foo()`).
263     pub fn maybe_par(self) -> Self {
264         match self {
265             Sugg::NonParen(..) => self,
266             // `(x)` and `(x).y()` both don't need additional parens.
267             Sugg::MaybeParen(sugg) => {
268                 if sugg.starts_with('(') && sugg.ends_with(')') {
269                     Sugg::MaybeParen(sugg)
270                 } else {
271                     Sugg::NonParen(format!("({})", sugg).into())
272                 }
273             },
274             Sugg::BinOp(_, sugg) => Sugg::NonParen(format!("({})", sugg).into()),
275         }
276     }
277 }
278
279 // Copied from the rust standart library, and then edited
280 macro_rules! forward_binop_impls_to_ref {
281     (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
282         impl $imp<$t> for &$t {
283             type Output = $o;
284
285             fn $method(self, other: $t) -> $o {
286                 $imp::$method(self, &other)
287             }
288         }
289
290         impl $imp<&$t> for $t {
291             type Output = $o;
292
293             fn $method(self, other: &$t) -> $o {
294                 $imp::$method(&self, other)
295             }
296         }
297
298         impl $imp for $t {
299             type Output = $o;
300
301             fn $method(self, other: $t) -> $o {
302                 $imp::$method(&self, &other)
303             }
304         }
305     };
306 }
307
308 impl Add for &Sugg<'_> {
309     type Output = Sugg<'static>;
310     fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
311         make_binop(ast::BinOpKind::Add, self, rhs)
312     }
313 }
314
315 impl Sub for &Sugg<'_> {
316     type Output = Sugg<'static>;
317     fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
318         make_binop(ast::BinOpKind::Sub, self, rhs)
319     }
320 }
321
322 forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
323 forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
324
325 impl Neg for Sugg<'_> {
326     type Output = Sugg<'static>;
327     fn neg(self) -> Sugg<'static> {
328         make_unop("-", self)
329     }
330 }
331
332 impl Not for Sugg<'_> {
333     type Output = Sugg<'static>;
334     fn not(self) -> Sugg<'static> {
335         make_unop("!", self)
336     }
337 }
338
339 /// Helper type to display either `foo` or `(foo)`.
340 struct ParenHelper<T> {
341     /// `true` if parentheses are needed.
342     paren: bool,
343     /// The main thing to display.
344     wrapped: T,
345 }
346
347 impl<T> ParenHelper<T> {
348     /// Builds a `ParenHelper`.
349     fn new(paren: bool, wrapped: T) -> Self {
350         Self { paren, wrapped }
351     }
352 }
353
354 impl<T: Display> Display for ParenHelper<T> {
355     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
356         if self.paren {
357             write!(f, "({})", self.wrapped)
358         } else {
359             self.wrapped.fmt(f)
360         }
361     }
362 }
363
364 /// Builds the string for `<op><expr>` adding parenthesis when necessary.
365 ///
366 /// For convenience, the operator is taken as a string because all unary
367 /// operators have the same
368 /// precedence.
369 pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
370     Sugg::MaybeParen(format!("{}{}", op, expr.maybe_par()).into())
371 }
372
373 /// Builds the string for `<lhs> <op> <rhs>` adding parenthesis when necessary.
374 ///
375 /// Precedence of shift operator relative to other arithmetic operation is
376 /// often confusing so
377 /// parenthesis will always be added for a mix of these.
378 pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
379     /// Returns `true` if the operator is a shift operator `<<` or `>>`.
380     fn is_shift(op: AssocOp) -> bool {
381         matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
382     }
383
384     /// Returns `true` if the operator is a arithmetic operator
385     /// (i.e., `+`, `-`, `*`, `/`, `%`).
386     fn is_arith(op: AssocOp) -> bool {
387         matches!(
388             op,
389             AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
390         )
391     }
392
393     /// Returns `true` if the operator `op` needs parenthesis with the operator
394     /// `other` in the direction `dir`.
395     fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
396         other.precedence() < op.precedence()
397             || (other.precedence() == op.precedence()
398                 && ((op != other && associativity(op) != dir)
399                     || (op == other && associativity(op) != Associativity::Both)))
400             || is_shift(op) && is_arith(other)
401             || is_shift(other) && is_arith(op)
402     }
403
404     let lhs_paren = if let Sugg::BinOp(lop, _) = *lhs {
405         needs_paren(op, lop, Associativity::Left)
406     } else {
407         false
408     };
409
410     let rhs_paren = if let Sugg::BinOp(rop, _) = *rhs {
411         needs_paren(op, rop, Associativity::Right)
412     } else {
413         false
414     };
415
416     let lhs = ParenHelper::new(lhs_paren, lhs);
417     let rhs = ParenHelper::new(rhs_paren, rhs);
418     let sugg = match op {
419         AssocOp::Add
420         | AssocOp::BitAnd
421         | AssocOp::BitOr
422         | AssocOp::BitXor
423         | AssocOp::Divide
424         | AssocOp::Equal
425         | AssocOp::Greater
426         | AssocOp::GreaterEqual
427         | AssocOp::LAnd
428         | AssocOp::LOr
429         | AssocOp::Less
430         | AssocOp::LessEqual
431         | AssocOp::Modulus
432         | AssocOp::Multiply
433         | AssocOp::NotEqual
434         | AssocOp::ShiftLeft
435         | AssocOp::ShiftRight
436         | AssocOp::Subtract => format!(
437             "{} {} {}",
438             lhs,
439             op.to_ast_binop().expect("Those are AST ops").to_string(),
440             rhs
441         ),
442         AssocOp::Assign => format!("{} = {}", lhs, rhs),
443         AssocOp::AssignOp(op) => format!("{} {}= {}", lhs, token_kind_to_string(&token::BinOp(op)), rhs),
444         AssocOp::As => format!("{} as {}", lhs, rhs),
445         AssocOp::DotDot => format!("{}..{}", lhs, rhs),
446         AssocOp::DotDotEq => format!("{}..={}", lhs, rhs),
447         AssocOp::Colon => format!("{}: {}", lhs, rhs),
448     };
449
450     Sugg::BinOp(op, sugg.into())
451 }
452
453 /// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
454 pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
455     make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
456 }
457
458 #[derive(PartialEq, Eq, Clone, Copy)]
459 /// Operator associativity.
460 enum Associativity {
461     /// The operator is both left-associative and right-associative.
462     Both,
463     /// The operator is left-associative.
464     Left,
465     /// The operator is not associative.
466     None,
467     /// The operator is right-associative.
468     Right,
469 }
470
471 /// Returns the associativity/fixity of an operator. The difference with
472 /// `AssocOp::fixity` is that an operator can be both left and right associative
473 /// (such as `+`: `a + b + c == (a + b) + c == a + (b + c)`.
474 ///
475 /// Chained `as` and explicit `:` type coercion never need inner parenthesis so
476 /// they are considered
477 /// associative.
478 #[must_use]
479 fn associativity(op: AssocOp) -> Associativity {
480     use rustc_ast::util::parser::AssocOp::{
481         Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Colon, Divide, DotDot, DotDotEq, Equal, Greater,
482         GreaterEqual, LAnd, LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
483     };
484
485     match op {
486         Assign | AssignOp(_) => Associativity::Right,
487         Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As | Colon => Associativity::Both,
488         Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
489         | Subtract => Associativity::Left,
490         DotDot | DotDotEq => Associativity::None,
491     }
492 }
493
494 /// Converts a `hir::BinOp` to the corresponding assigning binary operator.
495 fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
496     use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
497
498     AssocOp::AssignOp(match op.node {
499         hir::BinOpKind::Add => Plus,
500         hir::BinOpKind::BitAnd => And,
501         hir::BinOpKind::BitOr => Or,
502         hir::BinOpKind::BitXor => Caret,
503         hir::BinOpKind::Div => Slash,
504         hir::BinOpKind::Mul => Star,
505         hir::BinOpKind::Rem => Percent,
506         hir::BinOpKind::Shl => Shl,
507         hir::BinOpKind::Shr => Shr,
508         hir::BinOpKind::Sub => Minus,
509
510         hir::BinOpKind::And
511         | hir::BinOpKind::Eq
512         | hir::BinOpKind::Ge
513         | hir::BinOpKind::Gt
514         | hir::BinOpKind::Le
515         | hir::BinOpKind::Lt
516         | hir::BinOpKind::Ne
517         | hir::BinOpKind::Or => panic!("This operator does not exist"),
518     })
519 }
520
521 /// Converts an `ast::BinOp` to the corresponding assigning binary operator.
522 fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
523     use rustc_ast::ast::BinOpKind::{
524         Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
525     };
526     use rustc_ast::token::BinOpToken;
527
528     AssocOp::AssignOp(match op.node {
529         Add => BinOpToken::Plus,
530         BitAnd => BinOpToken::And,
531         BitOr => BinOpToken::Or,
532         BitXor => BinOpToken::Caret,
533         Div => BinOpToken::Slash,
534         Mul => BinOpToken::Star,
535         Rem => BinOpToken::Percent,
536         Shl => BinOpToken::Shl,
537         Shr => BinOpToken::Shr,
538         Sub => BinOpToken::Minus,
539         And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
540     })
541 }
542
543 /// Returns the indentation before `span` if there are nothing but `[ \t]`
544 /// before it on its line.
545 fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
546     let lo = cx.sess().source_map().lookup_char_pos(span.lo());
547     lo.file
548         .get_line(lo.line - 1 /* line numbers in `Loc` are 1-based */)
549         .and_then(|line| {
550             if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
551                 // We can mix char and byte positions here because we only consider `[ \t]`.
552                 if lo.col == CharPos(pos) {
553                     Some(line[..pos].into())
554                 } else {
555                     None
556                 }
557             } else {
558                 None
559             }
560         })
561 }
562
563 /// Convenience extension trait for `DiagnosticBuilder`.
564 pub trait DiagnosticBuilderExt<T: LintContext> {
565     /// Suggests to add an attribute to an item.
566     ///
567     /// Correctly handles indentation of the attribute and item.
568     ///
569     /// # Example
570     ///
571     /// ```rust,ignore
572     /// diag.suggest_item_with_attr(cx, item, "#[derive(Default)]");
573     /// ```
574     fn suggest_item_with_attr<D: Display + ?Sized>(
575         &mut self,
576         cx: &T,
577         item: Span,
578         msg: &str,
579         attr: &D,
580         applicability: Applicability,
581     );
582
583     /// Suggest to add an item before another.
584     ///
585     /// The item should not be indented (except for inner indentation).
586     ///
587     /// # Example
588     ///
589     /// ```rust,ignore
590     /// diag.suggest_prepend_item(cx, item,
591     /// "fn foo() {
592     ///     bar();
593     /// }");
594     /// ```
595     fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
596
597     /// Suggest to completely remove an item.
598     ///
599     /// This will remove an item and all following whitespace until the next non-whitespace
600     /// character. This should work correctly if item is on the same indentation level as the
601     /// following item.
602     ///
603     /// # Example
604     ///
605     /// ```rust,ignore
606     /// diag.suggest_remove_item(cx, item, "remove this")
607     /// ```
608     fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
609 }
610
611 impl<T: LintContext> DiagnosticBuilderExt<T> for rustc_errors::DiagnosticBuilder<'_> {
612     fn suggest_item_with_attr<D: Display + ?Sized>(
613         &mut self,
614         cx: &T,
615         item: Span,
616         msg: &str,
617         attr: &D,
618         applicability: Applicability,
619     ) {
620         if let Some(indent) = indentation(cx, item) {
621             let span = item.with_hi(item.lo());
622
623             self.span_suggestion(span, msg, format!("{}\n{}", attr, indent), applicability);
624         }
625     }
626
627     fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
628         if let Some(indent) = indentation(cx, item) {
629             let span = item.with_hi(item.lo());
630
631             let mut first = true;
632             let new_item = new_item
633                 .lines()
634                 .map(|l| {
635                     if first {
636                         first = false;
637                         format!("{}\n", l)
638                     } else {
639                         format!("{}{}\n", indent, l)
640                     }
641                 })
642                 .collect::<String>();
643
644             self.span_suggestion(span, msg, format!("{}\n{}", new_item, indent), applicability);
645         }
646     }
647
648     fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
649         let mut remove_span = item;
650         let hi = cx.sess().source_map().next_point(remove_span).hi();
651         let fmpos = cx.sess().source_map().lookup_byte_offset(hi);
652
653         if let Some(ref src) = fmpos.sf.src {
654             let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
655
656             if let Some(non_whitespace_offset) = non_whitespace_offset {
657                 remove_span = remove_span
658                     .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")))
659             }
660         }
661
662         self.span_suggestion(remove_span, msg, String::new(), applicability);
663     }
664 }
665
666 #[cfg(test)]
667 mod test {
668     use super::Sugg;
669     use std::borrow::Cow;
670
671     const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
672
673     #[test]
674     fn make_return_transform_sugg_into_a_return_call() {
675         assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
676     }
677
678     #[test]
679     fn blockify_transforms_sugg_into_a_block() {
680         assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
681     }
682 }