1#![deny(clippy::missing_docs_in_private_items)]
3
4use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_with_context};
5use crate::ty::expr_sig;
6use crate::{get_parent_expr_for_hir, higher};
7use rustc_ast::util::parser::AssocOp;
8use rustc_ast::{ast, token};
9use rustc_ast_pretty::pprust::token_kind_to_string;
10use rustc_errors::Applicability;
11use rustc_hir as hir;
12use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind};
13use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
14use rustc_lint::{EarlyContext, LateContext, LintContext};
15use rustc_middle::hir::place::ProjectionKind;
16use rustc_middle::mir::{FakeReadCause, Mutability};
17use rustc_middle::ty;
18use rustc_span::{BytePos, CharPos, Pos, Span, SyntaxContext};
19use std::borrow::Cow;
20use std::fmt::{self, Display, Write as _};
21use std::ops::{Add, Neg, Not, Sub};
22
23#[derive(Clone, Debug, PartialEq)]
25pub enum Sugg<'a> {
26 NonParen(Cow<'a, str>),
28 MaybeParen(Cow<'a, str>),
30 BinOp(AssocOp, Cow<'a, str>, Cow<'a, str>),
33}
34
35pub const ZERO: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("0"));
37pub const ONE: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("1"));
39pub const EMPTY: Sugg<'static> = Sugg::NonParen(Cow::Borrowed(""));
41
42impl Display for Sugg<'_> {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
44 match *self {
45 Sugg::NonParen(ref s) | Sugg::MaybeParen(ref s) => s.fmt(f),
46 Sugg::BinOp(op, ref lhs, ref rhs) => binop_to_string(op, lhs, rhs).fmt(f),
47 }
48 }
49}
50
51#[expect(clippy::wrong_self_convention)] impl<'a> Sugg<'a> {
53 pub fn hir_opt(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option<Self> {
55 let ctxt = expr.span.ctxt();
56 let get_snippet = |span| snippet_with_context(cx, span, ctxt, "", &mut Applicability::Unspecified).0;
57 snippet_opt(cx, expr.span).map(|_| Self::hir_from_snippet(expr, get_snippet))
58 }
59
60 pub fn hir(cx: &LateContext<'_>, expr: &hir::Expr<'_>, default: &'a str) -> Self {
63 Self::hir_opt(cx, expr).unwrap_or(Sugg::NonParen(Cow::Borrowed(default)))
64 }
65
66 pub fn hir_with_applicability(
73 cx: &LateContext<'_>,
74 expr: &hir::Expr<'_>,
75 default: &'a str,
76 applicability: &mut Applicability,
77 ) -> Self {
78 if *applicability != Applicability::Unspecified && expr.span.from_expansion() {
79 *applicability = Applicability::MaybeIncorrect;
80 }
81 Self::hir_opt(cx, expr).unwrap_or_else(|| {
82 if *applicability == Applicability::MachineApplicable {
83 *applicability = Applicability::HasPlaceholders;
84 }
85 Sugg::NonParen(Cow::Borrowed(default))
86 })
87 }
88
89 pub fn hir_with_context(
97 cx: &LateContext<'_>,
98 expr: &hir::Expr<'_>,
99 ctxt: SyntaxContext,
100 default: &'a str,
101 applicability: &mut Applicability,
102 ) -> Self {
103 if expr.span.ctxt() == ctxt {
104 Self::hir_from_snippet(expr, |span| {
105 snippet_with_context(cx, span, ctxt, default, applicability).0
106 })
107 } else {
108 let (snip, _) = snippet_with_context(cx, expr.span, ctxt, default, applicability);
109 Sugg::NonParen(snip)
110 }
111 }
112
113 fn hir_from_snippet(expr: &hir::Expr<'_>, mut get_snippet: impl FnMut(Span) -> Cow<'a, str>) -> Self {
116 if let Some(range) = higher::Range::hir(expr) {
117 let op = match range.limits {
118 ast::RangeLimits::HalfOpen => AssocOp::DotDot,
119 ast::RangeLimits::Closed => AssocOp::DotDotEq,
120 };
121 let start = range.start.map_or("".into(), |expr| get_snippet(expr.span));
122 let end = range.end.map_or("".into(), |expr| get_snippet(expr.span));
123
124 return Sugg::BinOp(op, start, end);
125 }
126
127 match expr.kind {
128 ExprKind::AddrOf(..)
129 | ExprKind::If(..)
130 | ExprKind::Let(..)
131 | ExprKind::Closure { .. }
132 | ExprKind::Unary(..)
133 | ExprKind::Match(..) => Sugg::MaybeParen(get_snippet(expr.span)),
134 ExprKind::Continue(..)
135 | ExprKind::Yield(..)
136 | ExprKind::Array(..)
137 | ExprKind::Block(..)
138 | ExprKind::Break(..)
139 | ExprKind::Call(..)
140 | ExprKind::Field(..)
141 | ExprKind::Index(..)
142 | ExprKind::InlineAsm(..)
143 | ExprKind::OffsetOf(..)
144 | ExprKind::ConstBlock(..)
145 | ExprKind::Lit(..)
146 | ExprKind::Loop(..)
147 | ExprKind::MethodCall(..)
148 | ExprKind::Path(..)
149 | ExprKind::Repeat(..)
150 | ExprKind::Ret(..)
151 | ExprKind::Become(..)
152 | ExprKind::Struct(..)
153 | ExprKind::Tup(..)
154 | ExprKind::Err(_)
155 | ExprKind::UnsafeBinderCast(..) => Sugg::NonParen(get_snippet(expr.span)),
156 ExprKind::DropTemps(inner) => Self::hir_from_snippet(inner, get_snippet),
157 ExprKind::Assign(lhs, rhs, _) => {
158 Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span))
159 },
160 ExprKind::AssignOp(op, lhs, rhs) => {
161 Sugg::BinOp(hirbinop2assignop(op), get_snippet(lhs.span), get_snippet(rhs.span))
162 },
163 ExprKind::Binary(op, lhs, rhs) => Sugg::BinOp(
164 AssocOp::from_ast_binop(op.node),
165 get_snippet(lhs.span),
166 get_snippet(rhs.span),
167 ),
168 ExprKind::Cast(lhs, ty) |
169 ExprKind::Type(lhs, ty) => Sugg::BinOp(AssocOp::As, get_snippet(lhs.span), get_snippet(ty.span)),
171 }
172 }
173
174 pub fn ast(
176 cx: &EarlyContext<'_>,
177 expr: &ast::Expr,
178 default: &'a str,
179 ctxt: SyntaxContext,
180 app: &mut Applicability,
181 ) -> Self {
182 use rustc_ast::ast::RangeLimits;
183
184 let mut snippet = |span: Span| snippet_with_context(cx, span, ctxt, default, app).0;
185
186 match expr.kind {
187 _ if expr.span.ctxt() != ctxt => Sugg::NonParen(snippet(expr.span)),
188 ast::ExprKind::AddrOf(..)
189 | ast::ExprKind::Closure { .. }
190 | ast::ExprKind::If(..)
191 | ast::ExprKind::Let(..)
192 | ast::ExprKind::Unary(..)
193 | ast::ExprKind::Match(..) => match snippet_with_context(cx, expr.span, ctxt, default, app) {
194 (snip, false) => Sugg::MaybeParen(snip),
195 (snip, true) => Sugg::NonParen(snip),
196 },
197 ast::ExprKind::Gen(..)
198 | ast::ExprKind::Block(..)
199 | ast::ExprKind::Break(..)
200 | ast::ExprKind::Call(..)
201 | ast::ExprKind::Continue(..)
202 | ast::ExprKind::Yield(..)
203 | ast::ExprKind::Field(..)
204 | ast::ExprKind::ForLoop { .. }
205 | ast::ExprKind::Index(..)
206 | ast::ExprKind::InlineAsm(..)
207 | ast::ExprKind::OffsetOf(..)
208 | ast::ExprKind::ConstBlock(..)
209 | ast::ExprKind::Lit(..)
210 | ast::ExprKind::IncludedBytes(..)
211 | ast::ExprKind::Loop(..)
212 | ast::ExprKind::MacCall(..)
213 | ast::ExprKind::MethodCall(..)
214 | ast::ExprKind::Paren(..)
215 | ast::ExprKind::Underscore
216 | ast::ExprKind::Path(..)
217 | ast::ExprKind::Repeat(..)
218 | ast::ExprKind::Ret(..)
219 | ast::ExprKind::Become(..)
220 | ast::ExprKind::Yeet(..)
221 | ast::ExprKind::FormatArgs(..)
222 | ast::ExprKind::Struct(..)
223 | ast::ExprKind::Try(..)
224 | ast::ExprKind::TryBlock(..)
225 | ast::ExprKind::Tup(..)
226 | ast::ExprKind::Array(..)
227 | ast::ExprKind::While(..)
228 | ast::ExprKind::Await(..)
229 | ast::ExprKind::Err(_)
230 | ast::ExprKind::Dummy
231 | ast::ExprKind::UnsafeBinderCast(..) => Sugg::NonParen(snippet(expr.span)),
232 ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
233 AssocOp::DotDot,
234 lhs.as_ref().map_or("".into(), |lhs| snippet(lhs.span)),
235 rhs.as_ref().map_or("".into(), |rhs| snippet(rhs.span)),
236 ),
237 ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp(
238 AssocOp::DotDotEq,
239 lhs.as_ref().map_or("".into(), |lhs| snippet(lhs.span)),
240 rhs.as_ref().map_or("".into(), |rhs| snippet(rhs.span)),
241 ),
242 ast::ExprKind::Assign(ref lhs, ref rhs, _) => Sugg::BinOp(
243 AssocOp::Assign,
244 snippet(lhs.span),
245 snippet(rhs.span),
246 ),
247 ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp(
248 astbinop2assignop(op),
249 snippet(lhs.span),
250 snippet(rhs.span),
251 ),
252 ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp(
253 AssocOp::from_ast_binop(op.node),
254 snippet(lhs.span),
255 snippet(rhs.span),
256 ),
257 ast::ExprKind::Cast(ref lhs, ref ty) |
258 ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp(
260 AssocOp::As,
261 snippet(lhs.span),
262 snippet(ty.span),
263 ),
264 }
265 }
266
267 pub fn and(self, rhs: &Self) -> Sugg<'static> {
269 make_binop(ast::BinOpKind::And, &self, rhs)
270 }
271
272 pub fn bit_and(self, rhs: &Self) -> Sugg<'static> {
274 make_binop(ast::BinOpKind::BitAnd, &self, rhs)
275 }
276
277 pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
279 make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
280 }
281
282 pub fn addr(self) -> Sugg<'static> {
284 make_unop("&", self)
285 }
286
287 pub fn mut_addr(self) -> Sugg<'static> {
289 make_unop("&mut ", self)
290 }
291
292 pub fn deref(self) -> Sugg<'static> {
294 make_unop("*", self)
295 }
296
297 pub fn addr_deref(self) -> Sugg<'static> {
301 make_unop("&*", self)
302 }
303
304 pub fn mut_addr_deref(self) -> Sugg<'static> {
308 make_unop("&mut *", self)
309 }
310
311 pub fn make_return(self) -> Sugg<'static> {
313 Sugg::NonParen(Cow::Owned(format!("return {self}")))
314 }
315
316 pub fn blockify(self) -> Sugg<'static> {
319 Sugg::NonParen(Cow::Owned(format!("{{ {self} }}")))
320 }
321
322 pub fn asyncify(self) -> Sugg<'static> {
325 Sugg::NonParen(Cow::Owned(format!("async {self}")))
326 }
327
328 pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
331 match limit {
332 ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
333 ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
334 }
335 }
336
337 #[must_use]
341 pub fn maybe_par(self) -> Self {
342 match self {
343 Sugg::NonParen(..) => self,
344 Sugg::MaybeParen(sugg) => {
346 if has_enclosing_paren(&sugg) {
347 Sugg::MaybeParen(sugg)
348 } else {
349 Sugg::NonParen(format!("({sugg})").into())
350 }
351 },
352 Sugg::BinOp(op, lhs, rhs) => {
353 let sugg = binop_to_string(op, &lhs, &rhs);
354 Sugg::NonParen(format!("({sugg})").into())
355 },
356 }
357 }
358
359 pub fn into_string(self) -> String {
360 match self {
361 Sugg::NonParen(p) | Sugg::MaybeParen(p) => p.into_owned(),
362 Sugg::BinOp(b, l, r) => binop_to_string(b, &l, &r),
363 }
364 }
365}
366
367fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
369 match op {
370 AssocOp::Add
371 | AssocOp::Subtract
372 | AssocOp::Multiply
373 | AssocOp::Divide
374 | AssocOp::Modulus
375 | AssocOp::LAnd
376 | AssocOp::LOr
377 | AssocOp::BitXor
378 | AssocOp::BitAnd
379 | AssocOp::BitOr
380 | AssocOp::ShiftLeft
381 | AssocOp::ShiftRight
382 | AssocOp::Equal
383 | AssocOp::Less
384 | AssocOp::LessEqual
385 | AssocOp::NotEqual
386 | AssocOp::Greater
387 | AssocOp::GreaterEqual => {
388 format!("{lhs} {} {rhs}", op.to_ast_binop().expect("Those are AST ops").as_str())
389 },
390 AssocOp::Assign => format!("{lhs} = {rhs}"),
391 AssocOp::AssignOp(op) => {
392 format!("{lhs} {}= {rhs}", token_kind_to_string(&token::BinOp(op)))
393 },
394 AssocOp::As => format!("{lhs} as {rhs}"),
395 AssocOp::DotDot => format!("{lhs}..{rhs}"),
396 AssocOp::DotDotEq => format!("{lhs}..={rhs}"),
397 }
398}
399
400pub fn has_enclosing_paren(sugg: impl AsRef<str>) -> bool {
402 let mut chars = sugg.as_ref().chars();
403 if chars.next() == Some('(') {
404 let mut depth = 1;
405 for c in &mut chars {
406 if c == '(' {
407 depth += 1;
408 } else if c == ')' {
409 depth -= 1;
410 }
411 if depth == 0 {
412 break;
413 }
414 }
415 chars.next().is_none()
416 } else {
417 false
418 }
419}
420
421macro_rules! forward_binop_impls_to_ref {
423 (impl $imp:ident, $method:ident for $t:ty, type Output = $o:ty) => {
424 impl $imp<$t> for &$t {
425 type Output = $o;
426
427 fn $method(self, other: $t) -> $o {
428 $imp::$method(self, &other)
429 }
430 }
431
432 impl $imp<&$t> for $t {
433 type Output = $o;
434
435 fn $method(self, other: &$t) -> $o {
436 $imp::$method(&self, other)
437 }
438 }
439
440 impl $imp for $t {
441 type Output = $o;
442
443 fn $method(self, other: $t) -> $o {
444 $imp::$method(&self, &other)
445 }
446 }
447 };
448}
449
450impl Add for &Sugg<'_> {
451 type Output = Sugg<'static>;
452 fn add(self, rhs: &Sugg<'_>) -> Sugg<'static> {
453 make_binop(ast::BinOpKind::Add, self, rhs)
454 }
455}
456
457impl Sub for &Sugg<'_> {
458 type Output = Sugg<'static>;
459 fn sub(self, rhs: &Sugg<'_>) -> Sugg<'static> {
460 make_binop(ast::BinOpKind::Sub, self, rhs)
461 }
462}
463
464forward_binop_impls_to_ref!(impl Add, add for Sugg<'_>, type Output = Sugg<'static>);
465forward_binop_impls_to_ref!(impl Sub, sub for Sugg<'_>, type Output = Sugg<'static>);
466
467impl Neg for Sugg<'_> {
468 type Output = Sugg<'static>;
469 fn neg(self) -> Sugg<'static> {
470 match &self {
471 Self::BinOp(AssocOp::As, ..) => Sugg::MaybeParen(format!("-({self})").into()),
472 _ => make_unop("-", self),
473 }
474 }
475}
476
477impl<'a> Not for Sugg<'a> {
478 type Output = Sugg<'a>;
479 fn not(self) -> Sugg<'a> {
480 use AssocOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
481
482 if let Sugg::BinOp(op, lhs, rhs) = self {
483 let to_op = match op {
484 Equal => NotEqual,
485 NotEqual => Equal,
486 Less => GreaterEqual,
487 GreaterEqual => Less,
488 Greater => LessEqual,
489 LessEqual => Greater,
490 _ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
491 };
492 Sugg::BinOp(to_op, lhs, rhs)
493 } else {
494 make_unop("!", self)
495 }
496 }
497}
498
499struct ParenHelper<T> {
501 paren: bool,
503 wrapped: T,
505}
506
507impl<T> ParenHelper<T> {
508 fn new(paren: bool, wrapped: T) -> Self {
510 Self { paren, wrapped }
511 }
512}
513
514impl<T: Display> Display for ParenHelper<T> {
515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
516 if self.paren {
517 write!(f, "({})", self.wrapped)
518 } else {
519 self.wrapped.fmt(f)
520 }
521 }
522}
523
524pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
530 Sugg::MaybeParen(format!("{op}{}", expr.maybe_par()).into())
531}
532
533pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
539 fn is_shift(op: AssocOp) -> bool {
541 matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
542 }
543
544 fn is_arith(op: AssocOp) -> bool {
547 matches!(
548 op,
549 AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
550 )
551 }
552
553 fn needs_paren(op: AssocOp, other: AssocOp, dir: Associativity) -> bool {
556 other.precedence() < op.precedence()
557 || (other.precedence() == op.precedence()
558 && ((op != other && associativity(op) != dir)
559 || (op == other && associativity(op) != Associativity::Both)))
560 || is_shift(op) && is_arith(other)
561 || is_shift(other) && is_arith(op)
562 }
563
564 let lhs_paren = if let Sugg::BinOp(lop, _, _) = *lhs {
565 needs_paren(op, lop, Associativity::Left)
566 } else {
567 false
568 };
569
570 let rhs_paren = if let Sugg::BinOp(rop, _, _) = *rhs {
571 needs_paren(op, rop, Associativity::Right)
572 } else {
573 false
574 };
575
576 let lhs = ParenHelper::new(lhs_paren, lhs).to_string();
577 let rhs = ParenHelper::new(rhs_paren, rhs).to_string();
578 Sugg::BinOp(op, lhs.into(), rhs.into())
579}
580
581pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
583 make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
584}
585
586#[derive(PartialEq, Eq, Clone, Copy)]
587enum Associativity {
589 Both,
591 Left,
593 None,
595 Right,
597}
598
599#[must_use]
607fn associativity(op: AssocOp) -> Associativity {
608 use rustc_ast::util::parser::AssocOp::{
609 Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Divide, DotDot, DotDotEq, Equal, Greater, GreaterEqual, LAnd,
610 LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
611 };
612
613 match op {
614 Assign | AssignOp(_) => Associativity::Right,
615 Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As => Associativity::Both,
616 Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
617 | Subtract => Associativity::Left,
618 DotDot | DotDotEq => Associativity::None,
619 }
620}
621
622fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
624 use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
625
626 AssocOp::AssignOp(match op.node {
627 hir::BinOpKind::Add => Plus,
628 hir::BinOpKind::BitAnd => And,
629 hir::BinOpKind::BitOr => Or,
630 hir::BinOpKind::BitXor => Caret,
631 hir::BinOpKind::Div => Slash,
632 hir::BinOpKind::Mul => Star,
633 hir::BinOpKind::Rem => Percent,
634 hir::BinOpKind::Shl => Shl,
635 hir::BinOpKind::Shr => Shr,
636 hir::BinOpKind::Sub => Minus,
637
638 hir::BinOpKind::And
639 | hir::BinOpKind::Eq
640 | hir::BinOpKind::Ge
641 | hir::BinOpKind::Gt
642 | hir::BinOpKind::Le
643 | hir::BinOpKind::Lt
644 | hir::BinOpKind::Ne
645 | hir::BinOpKind::Or => panic!("This operator does not exist"),
646 })
647}
648
649fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
651 use rustc_ast::ast::BinOpKind::{
652 Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
653 };
654 use rustc_ast::token::BinOpToken;
655
656 AssocOp::AssignOp(match op.node {
657 Add => BinOpToken::Plus,
658 BitAnd => BinOpToken::And,
659 BitOr => BinOpToken::Or,
660 BitXor => BinOpToken::Caret,
661 Div => BinOpToken::Slash,
662 Mul => BinOpToken::Star,
663 Rem => BinOpToken::Percent,
664 Shl => BinOpToken::Shl,
665 Shr => BinOpToken::Shr,
666 Sub => BinOpToken::Minus,
667 And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
668 })
669}
670
671fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
674 let lo = cx.sess().source_map().lookup_char_pos(span.lo());
675 lo.file
676 .get_line(lo.line - 1 )
677 .and_then(|line| {
678 if let Some((pos, _)) = line.char_indices().find(|&(_, c)| c != ' ' && c != '\t') {
679 if lo.col == CharPos(pos) {
681 Some(line[..pos].into())
682 } else {
683 None
684 }
685 } else {
686 None
687 }
688 })
689}
690
691pub trait DiagExt<T: LintContext> {
693 fn suggest_item_with_attr<D: Display + ?Sized>(
703 &mut self,
704 cx: &T,
705 item: Span,
706 msg: &str,
707 attr: &D,
708 applicability: Applicability,
709 );
710
711 fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability);
724
725 fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability);
737}
738
739impl<T: LintContext> DiagExt<T> for rustc_errors::Diag<'_, ()> {
740 fn suggest_item_with_attr<D: Display + ?Sized>(
741 &mut self,
742 cx: &T,
743 item: Span,
744 msg: &str,
745 attr: &D,
746 applicability: Applicability,
747 ) {
748 if let Some(indent) = indentation(cx, item) {
749 let span = item.with_hi(item.lo());
750
751 self.span_suggestion(span, msg.to_string(), format!("{attr}\n{indent}"), applicability);
752 }
753 }
754
755 fn suggest_prepend_item(&mut self, cx: &T, item: Span, msg: &str, new_item: &str, applicability: Applicability) {
756 if let Some(indent) = indentation(cx, item) {
757 let span = item.with_hi(item.lo());
758
759 let mut first = true;
760 let new_item = new_item
761 .lines()
762 .map(|l| {
763 if first {
764 first = false;
765 format!("{l}\n")
766 } else {
767 format!("{indent}{l}\n")
768 }
769 })
770 .collect::<String>();
771
772 self.span_suggestion(span, msg.to_string(), format!("{new_item}\n{indent}"), applicability);
773 }
774 }
775
776 fn suggest_remove_item(&mut self, cx: &T, item: Span, msg: &str, applicability: Applicability) {
777 let mut remove_span = item;
778 let fmpos = cx.sess().source_map().lookup_byte_offset(remove_span.hi());
779
780 if let Some(ref src) = fmpos.sf.src {
781 let non_whitespace_offset = src[fmpos.pos.to_usize()..].find(|c| c != ' ' && c != '\t' && c != '\n');
782
783 if let Some(non_whitespace_offset) = non_whitespace_offset {
784 remove_span = remove_span
785 .with_hi(remove_span.hi() + BytePos(non_whitespace_offset.try_into().expect("offset too large")));
786 }
787 }
788
789 self.span_suggestion(remove_span, msg.to_string(), "", applicability);
790 }
791}
792
793pub struct DerefClosure {
796 pub applicability: Applicability,
798 pub suggestion: String,
800}
801
802pub fn deref_closure_args(cx: &LateContext<'_>, closure: &hir::Expr<'_>) -> Option<DerefClosure> {
808 if let ExprKind::Closure(&Closure {
809 fn_decl, def_id, body, ..
810 }) = closure.kind
811 {
812 let closure_body = cx.tcx.hir().body(body);
813 let closure_arg_is_type_annotated_double_ref = if let TyKind::Ref(_, MutTy { ty, .. }) = fn_decl.inputs[0].kind
816 {
817 matches!(ty.kind, TyKind::Ref(_, MutTy { .. }))
818 } else {
819 false
820 };
821
822 let mut visitor = DerefDelegate {
823 cx,
824 closure_span: closure.span,
825 closure_arg_is_type_annotated_double_ref,
826 next_pos: closure.span.lo(),
827 suggestion_start: String::new(),
828 applicability: Applicability::MachineApplicable,
829 };
830
831 ExprUseVisitor::for_clippy(cx, def_id, &mut visitor)
832 .consume_body(closure_body)
833 .into_ok();
834
835 if !visitor.suggestion_start.is_empty() {
836 return Some(DerefClosure {
837 applicability: visitor.applicability,
838 suggestion: visitor.finish(),
839 });
840 }
841 }
842 None
843}
844
845struct DerefDelegate<'a, 'tcx> {
848 cx: &'a LateContext<'tcx>,
850 closure_span: Span,
852 closure_arg_is_type_annotated_double_ref: bool,
854 next_pos: BytePos,
856 suggestion_start: String,
858 applicability: Applicability,
860}
861
862impl<'tcx> DerefDelegate<'_, 'tcx> {
863 pub fn finish(&mut self) -> String {
868 let end_span = Span::new(self.next_pos, self.closure_span.hi(), self.closure_span.ctxt(), None);
869 let end_snip = snippet_with_applicability(self.cx, end_span, "..", &mut self.applicability);
870 let sugg = format!("{}{end_snip}", self.suggestion_start);
871 if self.closure_arg_is_type_annotated_double_ref {
872 sugg.replacen('&', "", 1)
873 } else {
874 sugg
875 }
876 }
877
878 fn func_takes_arg_by_double_ref(&self, parent_expr: &'tcx hir::Expr<'_>, cmt_hir_id: HirId) -> bool {
880 let ty = match parent_expr.kind {
881 ExprKind::MethodCall(_, receiver, call_args, _) => {
882 if let Some(sig) = self
883 .cx
884 .typeck_results()
885 .type_dependent_def_id(parent_expr.hir_id)
886 .map(|did| self.cx.tcx.fn_sig(did).instantiate_identity().skip_binder())
887 {
888 std::iter::once(receiver)
889 .chain(call_args.iter())
890 .position(|arg| arg.hir_id == cmt_hir_id)
891 .map(|i| sig.inputs()[i])
892 } else {
893 return false;
894 }
895 },
896 ExprKind::Call(func, call_args) => {
897 if let Some(sig) = expr_sig(self.cx, func) {
898 call_args
899 .iter()
900 .position(|arg| arg.hir_id == cmt_hir_id)
901 .and_then(|i| sig.input(i))
902 .map(ty::Binder::skip_binder)
903 } else {
904 return false;
905 }
906 },
907 _ => return false,
908 };
909
910 ty.is_some_and(|ty| matches!(ty.kind(), ty::Ref(_, inner, _) if inner.is_ref()))
911 }
912}
913
914impl<'tcx> Delegate<'tcx> for DerefDelegate<'_, 'tcx> {
915 fn consume(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
916
917 fn borrow(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {
918 if let PlaceBase::Local(id) = cmt.place.base {
919 let map = self.cx.tcx.hir();
920 let span = map.span(cmt.hir_id);
921 let start_span = Span::new(self.next_pos, span.lo(), span.ctxt(), None);
922 let mut start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
923
924 let ident_str = map.name(id).to_string();
926 let ident_str_with_proj = snippet(self.cx, span, "..").to_string();
928
929 if cmt.place.projections.is_empty() {
930 let _: fmt::Result = write!(self.suggestion_start, "{start_snip}&{ident_str}");
933 } else {
934 if let Some(parent_expr) = get_parent_expr_for_hir(self.cx, cmt.hir_id) {
943 match &parent_expr.kind {
944 ExprKind::MethodCall(_, self_expr, ..) if self_expr.hir_id == cmt.hir_id => {
947 let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{ident_str_with_proj}");
948 self.next_pos = span.hi();
949 return;
950 },
951 ExprKind::Call(_, call_args) | ExprKind::MethodCall(_, _, call_args, _) => {
954 let expr = self.cx.tcx.hir().expect_expr(cmt.hir_id);
955 let arg_ty_kind = self.cx.typeck_results().expr_ty(expr).kind();
956
957 if matches!(arg_ty_kind, ty::Ref(_, _, Mutability::Not)) {
958 let takes_arg_by_double_ref =
960 self.func_takes_arg_by_double_ref(parent_expr, cmt.hir_id);
961
962 let has_field_or_index_projection =
965 cmt.place.projections.iter().any(|proj| {
966 matches!(proj.kind, ProjectionKind::Field(..) | ProjectionKind::Index)
967 });
968
969 let ident_sugg = if !call_args.is_empty()
972 && !takes_arg_by_double_ref
973 && (self.closure_arg_is_type_annotated_double_ref || has_field_or_index_projection)
974 {
975 let ident = if has_field_or_index_projection {
976 ident_str_with_proj
977 } else {
978 ident_str
979 };
980 format!("{start_snip}{ident}")
981 } else {
982 format!("{start_snip}&{ident_str}")
983 };
984 self.suggestion_start.push_str(&ident_sugg);
985 self.next_pos = span.hi();
986 return;
987 }
988
989 self.applicability = Applicability::Unspecified;
990 },
991 _ => (),
992 }
993 }
994
995 let mut replacement_str = ident_str;
996 let mut projections_handled = false;
997 cmt.place.projections.iter().enumerate().for_each(|(i, proj)| {
998 match proj.kind {
999 ProjectionKind::Field(..) => match cmt.place.ty_before_projection(i).kind() {
1002 ty::Adt(..) | ty::Tuple(_) => {
1003 replacement_str.clone_from(&ident_str_with_proj);
1004 projections_handled = true;
1005 },
1006 _ => (),
1007 },
1008 ProjectionKind::Index => {
1013 let start_span = Span::new(self.next_pos, span.hi(), span.ctxt(), None);
1014 start_snip = snippet_with_applicability(self.cx, start_span, "..", &mut self.applicability);
1015 replacement_str.clear();
1016 projections_handled = true;
1017 },
1018 ProjectionKind::Subslice |
1020 ProjectionKind::OpaqueCast => (),
1022 ProjectionKind::Deref => {
1023 if let ty::Ref(_, inner, _) = cmt.place.ty_before_projection(i).kind() {
1028 if matches!(inner.kind(), ty::Ref(_, innermost, _) if innermost.is_array()) {
1029 projections_handled = true;
1030 }
1031 }
1032 },
1033 }
1034 });
1035
1036 if !projections_handled {
1039 let last_deref = cmt
1040 .place
1041 .projections
1042 .iter()
1043 .rposition(|proj| proj.kind == ProjectionKind::Deref);
1044
1045 if let Some(pos) = last_deref {
1046 let mut projections = cmt.place.projections.clone();
1047 projections.truncate(pos);
1048
1049 for item in projections {
1050 if item.kind == ProjectionKind::Deref {
1051 replacement_str = format!("*{replacement_str}");
1052 }
1053 }
1054 }
1055 }
1056
1057 let _: fmt::Result = write!(self.suggestion_start, "{start_snip}{replacement_str}");
1058 }
1059 self.next_pos = span.hi();
1060 }
1061 }
1062
1063 fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {}
1064
1065 fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {}
1066}
1067
1068#[cfg(test)]
1069mod test {
1070 use super::Sugg;
1071
1072 use rustc_ast::util::parser::AssocOp;
1073 use std::borrow::Cow;
1074
1075 const SUGGESTION: Sugg<'static> = Sugg::NonParen(Cow::Borrowed("function_call()"));
1076
1077 #[test]
1078 fn make_return_transform_sugg_into_a_return_call() {
1079 assert_eq!("return function_call()", SUGGESTION.make_return().to_string());
1080 }
1081
1082 #[test]
1083 fn blockify_transforms_sugg_into_a_block() {
1084 assert_eq!("{ function_call() }", SUGGESTION.blockify().to_string());
1085 }
1086
1087 #[test]
1088 fn binop_maybe_par() {
1089 let sugg = Sugg::BinOp(AssocOp::Add, "1".into(), "1".into());
1090 assert_eq!("(1 + 1)", sugg.maybe_par().to_string());
1091
1092 let sugg = Sugg::BinOp(AssocOp::Add, "(1 + 1)".into(), "(1 + 1)".into());
1093 assert_eq!("((1 + 1) + (1 + 1))", sugg.maybe_par().to_string());
1094 }
1095 #[test]
1096 fn not_op() {
1097 use AssocOp::{Add, Equal, Greater, GreaterEqual, LAnd, LOr, Less, LessEqual, NotEqual};
1098
1099 fn test_not(op: AssocOp, correct: &str) {
1100 let sugg = Sugg::BinOp(op, "x".into(), "y".into());
1101 assert_eq!((!sugg).to_string(), correct);
1102 }
1103
1104 test_not(Equal, "x != y");
1106 test_not(NotEqual, "x == y");
1107 test_not(Less, "x >= y");
1108 test_not(LessEqual, "x > y");
1109 test_not(Greater, "x <= y");
1110 test_not(GreaterEqual, "x < y");
1111
1112 test_not(Add, "!(x + y)");
1114 test_not(LAnd, "!(x && y)");
1115 test_not(LOr, "!(x || y)");
1116 }
1117}