1use std::iter;
2
3use rustc_ast as ast;
4use rustc_ast::util::{classify, parser};
5use rustc_ast::{ExprKind, StmtKind};
6use rustc_errors::{MultiSpan, pluralize};
7use rustc_hir::def::{DefKind, Res};
8use rustc_hir::def_id::DefId;
9use rustc_hir::{self as hir, LangItem};
10use rustc_infer::traits::util::elaborate;
11use rustc_middle::ty::{self, Ty, adjustment};
12use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass};
13use rustc_span::{BytePos, Span, Symbol, kw, sym};
14use tracing::instrument;
15
16use crate::lints::{
17 PathStatementDrop, PathStatementDropSub, PathStatementNoEffect, UnusedAllocationDiag,
18 UnusedAllocationMutDiag, UnusedClosure, UnusedCoroutine, UnusedDef, UnusedDefSuggestion,
19 UnusedDelim, UnusedDelimSuggestion, UnusedImportBracesDiag, UnusedOp, UnusedOpSuggestion,
20 UnusedResult,
21};
22use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, Lint, LintContext};
23
24declare_lint! {
25 pub UNUSED_MUST_USE,
49 Warn,
50 "unused result of a type flagged as `#[must_use]`",
51 report_in_external_macro
52}
53
54declare_lint! {
55 pub UNUSED_RESULTS,
89 Allow,
90 "unused result of an expression in a statement"
91}
92
93declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]);
94
95impl<'tcx> LateLintPass<'tcx> for UnusedResults {
96 fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
97 let hir::StmtKind::Semi(mut expr) = s.kind else {
98 return;
99 };
100
101 let mut expr_is_from_block = false;
102 while let hir::ExprKind::Block(blk, ..) = expr.kind
103 && let hir::Block { expr: Some(e), .. } = blk
104 {
105 expr = e;
106 expr_is_from_block = true;
107 }
108
109 if let hir::ExprKind::Ret(..) = expr.kind {
110 return;
111 }
112
113 if let hir::ExprKind::Match(await_expr, _arms, hir::MatchSource::AwaitDesugar) = expr.kind
114 && let ty = cx.typeck_results().expr_ty(await_expr)
115 && let ty::Alias(ty::Opaque, ty::AliasTy { def_id: future_def_id, .. }) = ty.kind()
116 && cx.tcx.ty_is_opaque_future(ty)
117 && let async_fn_def_id = cx.tcx.parent(*future_def_id)
118 && matches!(cx.tcx.def_kind(async_fn_def_id), DefKind::Fn | DefKind::AssocFn)
119 && cx.tcx.asyncness(async_fn_def_id).is_async()
121 && check_must_use_def(
122 cx,
123 async_fn_def_id,
124 expr.span,
125 "output of future returned by ",
126 "",
127 expr_is_from_block,
128 )
129 {
130 return;
133 }
134
135 let ty = cx.typeck_results().expr_ty(expr);
136
137 let must_use_result = is_ty_must_use(cx, ty, expr, expr.span);
138 let type_lint_emitted_or_suppressed = match must_use_result {
139 Some(path) => {
140 emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block);
141 true
142 }
143 None => false,
144 };
145
146 let fn_warned = check_fn_must_use(cx, expr, expr_is_from_block);
147
148 if !fn_warned && type_lint_emitted_or_suppressed {
149 return;
152 }
153
154 let must_use_op = match expr.kind {
155 hir::ExprKind::Binary(bin_op, ..) => match bin_op.node {
159 hir::BinOpKind::Eq
160 | hir::BinOpKind::Lt
161 | hir::BinOpKind::Le
162 | hir::BinOpKind::Ne
163 | hir::BinOpKind::Ge
164 | hir::BinOpKind::Gt => Some("comparison"),
165 hir::BinOpKind::Add
166 | hir::BinOpKind::Sub
167 | hir::BinOpKind::Div
168 | hir::BinOpKind::Mul
169 | hir::BinOpKind::Rem => Some("arithmetic operation"),
170 hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"),
171 hir::BinOpKind::BitXor
172 | hir::BinOpKind::BitAnd
173 | hir::BinOpKind::BitOr
174 | hir::BinOpKind::Shl
175 | hir::BinOpKind::Shr => Some("bitwise operation"),
176 },
177 hir::ExprKind::AddrOf(..) => Some("borrow"),
178 hir::ExprKind::OffsetOf(..) => Some("`offset_of` call"),
179 hir::ExprKind::Unary(..) => Some("unary operation"),
180 _ => None,
181 };
182
183 let mut op_warned = false;
184
185 if let Some(must_use_op) = must_use_op {
186 cx.emit_span_lint(
187 UNUSED_MUST_USE,
188 expr.span,
189 UnusedOp {
190 op: must_use_op,
191 label: expr.span,
192 suggestion: if expr_is_from_block {
193 UnusedOpSuggestion::BlockTailExpr {
194 before_span: expr.span.shrink_to_lo(),
195 after_span: expr.span.shrink_to_hi(),
196 }
197 } else {
198 UnusedOpSuggestion::NormalExpr { span: expr.span.shrink_to_lo() }
199 },
200 },
201 );
202 op_warned = true;
203 }
204
205 if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) {
206 cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty });
207 }
208
209 fn check_fn_must_use(
210 cx: &LateContext<'_>,
211 expr: &hir::Expr<'_>,
212 expr_is_from_block: bool,
213 ) -> bool {
214 let maybe_def_id = match expr.kind {
215 hir::ExprKind::Call(callee, _) => {
216 match callee.kind {
217 hir::ExprKind::Path(ref qpath) => {
218 match cx.qpath_res(qpath, callee.hir_id) {
219 Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id),
220 _ => None,
223 }
224 }
225 _ => None,
226 }
227 }
228 hir::ExprKind::MethodCall(..) => {
229 cx.typeck_results().type_dependent_def_id(expr.hir_id)
230 }
231 _ => None,
232 };
233 if let Some(def_id) = maybe_def_id {
234 check_must_use_def(
235 cx,
236 def_id,
237 expr.span,
238 "return value of ",
239 "",
240 expr_is_from_block,
241 )
242 } else {
243 false
244 }
245 }
246
247 #[derive(Debug)]
249 enum MustUsePath {
250 Suppressed,
252 Def(Span, DefId, Option<Symbol>),
254 Boxed(Box<Self>),
255 Pinned(Box<Self>),
256 Opaque(Box<Self>),
257 TraitObject(Box<Self>),
258 TupleElement(Vec<(usize, Self)>),
259 Array(Box<Self>, u64),
260 Closure(Span),
262 Coroutine(Span),
264 }
265
266 #[instrument(skip(cx, expr), level = "debug", ret)]
267 fn is_ty_must_use<'tcx>(
268 cx: &LateContext<'tcx>,
269 ty: Ty<'tcx>,
270 expr: &hir::Expr<'_>,
271 span: Span,
272 ) -> Option<MustUsePath> {
273 if ty.is_unit()
274 || !ty.is_inhabited_from(
275 cx.tcx,
276 cx.tcx.parent_module(expr.hir_id).to_def_id(),
277 cx.typing_env(),
278 )
279 {
280 return Some(MustUsePath::Suppressed);
281 }
282
283 match *ty.kind() {
284 ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => {
285 is_ty_must_use(cx, boxed, expr, span)
286 .map(|inner| MustUsePath::Boxed(Box::new(inner)))
287 }
288 ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => {
289 let pinned_ty = args.type_at(0);
290 is_ty_must_use(cx, pinned_ty, expr, span)
291 .map(|inner| MustUsePath::Pinned(Box::new(inner)))
292 }
293 ty::Adt(def, _) => is_def_must_use(cx, def.did(), span),
294 ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => {
295 elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied())
296 .filter_only_self()
298 .find_map(|(pred, _span)| {
299 if let ty::ClauseKind::Trait(ref poly_trait_predicate) =
301 pred.kind().skip_binder()
302 {
303 let def_id = poly_trait_predicate.trait_ref.def_id;
304
305 is_def_must_use(cx, def_id, span)
306 } else {
307 None
308 }
309 })
310 .map(|inner| MustUsePath::Opaque(Box::new(inner)))
311 }
312 ty::Dynamic(binders, _, _) => binders.iter().find_map(|predicate| {
313 if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder()
314 {
315 let def_id = trait_ref.def_id;
316 is_def_must_use(cx, def_id, span)
317 .map(|inner| MustUsePath::TraitObject(Box::new(inner)))
318 } else {
319 None
320 }
321 }),
322 ty::Tuple(tys) => {
323 let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind {
324 debug_assert_eq!(elem_exprs.len(), tys.len());
325 elem_exprs
326 } else {
327 &[]
328 };
329
330 let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr));
332
333 let nested_must_use = tys
334 .iter()
335 .zip(elem_exprs)
336 .enumerate()
337 .filter_map(|(i, (ty, expr))| {
338 is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path))
339 })
340 .collect::<Vec<_>>();
341
342 if !nested_must_use.is_empty() {
343 Some(MustUsePath::TupleElement(nested_must_use))
344 } else {
345 None
346 }
347 }
348 ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) {
349 Some(0) | None => None,
351 Some(len) => is_ty_must_use(cx, ty, expr, span)
353 .map(|inner| MustUsePath::Array(Box::new(inner), len)),
354 },
355 ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)),
356 ty::Coroutine(def_id, ..) => {
357 let must_use = if cx.tcx.coroutine_is_async(def_id) {
359 let def_id = cx.tcx.lang_items().future_trait()?;
360 is_def_must_use(cx, def_id, span)
361 .map(|inner| MustUsePath::Opaque(Box::new(inner)))
362 } else {
363 None
364 };
365 must_use.or(Some(MustUsePath::Coroutine(span)))
366 }
367 _ => None,
368 }
369 }
370
371 fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option<MustUsePath> {
372 if let Some(attr) = cx.tcx.get_attr(def_id, sym::must_use) {
373 let reason = attr.value_str();
375 Some(MustUsePath::Def(span, def_id, reason))
376 } else {
377 None
378 }
379 }
380
381 fn check_must_use_def(
384 cx: &LateContext<'_>,
385 def_id: DefId,
386 span: Span,
387 descr_pre_path: &str,
388 descr_post_path: &str,
389 expr_is_from_block: bool,
390 ) -> bool {
391 is_def_must_use(cx, def_id, span)
392 .map(|must_use_path| {
393 emit_must_use_untranslated(
394 cx,
395 &must_use_path,
396 descr_pre_path,
397 descr_post_path,
398 1,
399 false,
400 expr_is_from_block,
401 )
402 })
403 .is_some()
404 }
405
406 #[instrument(skip(cx), level = "debug")]
407 fn emit_must_use_untranslated(
408 cx: &LateContext<'_>,
409 path: &MustUsePath,
410 descr_pre: &str,
411 descr_post: &str,
412 plural_len: usize,
413 is_inner: bool,
414 expr_is_from_block: bool,
415 ) {
416 let plural_suffix = pluralize!(plural_len);
417
418 match path {
419 MustUsePath::Suppressed => {}
420 MustUsePath::Boxed(path) => {
421 let descr_pre = &format!("{descr_pre}boxed ");
422 emit_must_use_untranslated(
423 cx,
424 path,
425 descr_pre,
426 descr_post,
427 plural_len,
428 true,
429 expr_is_from_block,
430 );
431 }
432 MustUsePath::Pinned(path) => {
433 let descr_pre = &format!("{descr_pre}pinned ");
434 emit_must_use_untranslated(
435 cx,
436 path,
437 descr_pre,
438 descr_post,
439 plural_len,
440 true,
441 expr_is_from_block,
442 );
443 }
444 MustUsePath::Opaque(path) => {
445 let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of ");
446 emit_must_use_untranslated(
447 cx,
448 path,
449 descr_pre,
450 descr_post,
451 plural_len,
452 true,
453 expr_is_from_block,
454 );
455 }
456 MustUsePath::TraitObject(path) => {
457 let descr_post = &format!(" trait object{plural_suffix}{descr_post}");
458 emit_must_use_untranslated(
459 cx,
460 path,
461 descr_pre,
462 descr_post,
463 plural_len,
464 true,
465 expr_is_from_block,
466 );
467 }
468 MustUsePath::TupleElement(elems) => {
469 for (index, path) in elems {
470 let descr_post = &format!(" in tuple element {index}");
471 emit_must_use_untranslated(
472 cx,
473 path,
474 descr_pre,
475 descr_post,
476 plural_len,
477 true,
478 expr_is_from_block,
479 );
480 }
481 }
482 MustUsePath::Array(path, len) => {
483 let descr_pre = &format!("{descr_pre}array{plural_suffix} of ");
484 emit_must_use_untranslated(
485 cx,
486 path,
487 descr_pre,
488 descr_post,
489 plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)),
490 true,
491 expr_is_from_block,
492 );
493 }
494 MustUsePath::Closure(span) => {
495 cx.emit_span_lint(
496 UNUSED_MUST_USE,
497 *span,
498 UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post },
499 );
500 }
501 MustUsePath::Coroutine(span) => {
502 cx.emit_span_lint(
503 UNUSED_MUST_USE,
504 *span,
505 UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post },
506 );
507 }
508 MustUsePath::Def(span, def_id, reason) => {
509 cx.emit_span_lint(
510 UNUSED_MUST_USE,
511 *span,
512 UnusedDef {
513 pre: descr_pre,
514 post: descr_post,
515 cx,
516 def_id: *def_id,
517 note: *reason,
518 suggestion: (!is_inner).then_some(if expr_is_from_block {
519 UnusedDefSuggestion::BlockTailExpr {
520 before_span: span.shrink_to_lo(),
521 after_span: span.shrink_to_hi(),
522 }
523 } else {
524 UnusedDefSuggestion::NormalExpr { span: span.shrink_to_lo() }
525 }),
526 },
527 );
528 }
529 }
530 }
531 }
532}
533
534declare_lint! {
535 pub PATH_STATEMENTS,
551 Warn,
552 "path statements with no effect"
553}
554
555declare_lint_pass!(PathStatements => [PATH_STATEMENTS]);
556
557impl<'tcx> LateLintPass<'tcx> for PathStatements {
558 fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
559 if let hir::StmtKind::Semi(expr) = s.kind {
560 if let hir::ExprKind::Path(_) = expr.kind {
561 let ty = cx.typeck_results().expr_ty(expr);
562 if ty.needs_drop(cx.tcx, cx.typing_env()) {
563 let sub = if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span)
564 {
565 PathStatementDropSub::Suggestion { span: s.span, snippet }
566 } else {
567 PathStatementDropSub::Help { span: s.span }
568 };
569 cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub })
570 } else {
571 cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect);
572 }
573 }
574 }
575 }
576}
577
578#[derive(Copy, Clone, Debug, PartialEq, Eq)]
579enum UnusedDelimsCtx {
580 FunctionArg,
581 MethodArg,
582 AssignedValue,
583 AssignedValueLetElse,
584 IfCond,
585 WhileCond,
586 ForIterExpr,
587 MatchScrutineeExpr,
588 ReturnValue,
589 BlockRetValue,
590 BreakValue,
591 LetScrutineeExpr,
592 ArrayLenExpr,
593 AnonConst,
594 MatchArmExpr,
595 IndexExpr,
596}
597
598impl From<UnusedDelimsCtx> for &'static str {
599 fn from(ctx: UnusedDelimsCtx) -> &'static str {
600 match ctx {
601 UnusedDelimsCtx::FunctionArg => "function argument",
602 UnusedDelimsCtx::MethodArg => "method argument",
603 UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => {
604 "assigned value"
605 }
606 UnusedDelimsCtx::IfCond => "`if` condition",
607 UnusedDelimsCtx::WhileCond => "`while` condition",
608 UnusedDelimsCtx::ForIterExpr => "`for` iterator expression",
609 UnusedDelimsCtx::MatchScrutineeExpr => "`match` scrutinee expression",
610 UnusedDelimsCtx::ReturnValue => "`return` value",
611 UnusedDelimsCtx::BlockRetValue => "block return value",
612 UnusedDelimsCtx::BreakValue => "`break` value",
613 UnusedDelimsCtx::LetScrutineeExpr => "`let` scrutinee expression",
614 UnusedDelimsCtx::ArrayLenExpr | UnusedDelimsCtx::AnonConst => "const expression",
615 UnusedDelimsCtx::MatchArmExpr => "match arm expression",
616 UnusedDelimsCtx::IndexExpr => "index expression",
617 }
618 }
619}
620
621trait UnusedDelimLint {
623 const DELIM_STR: &'static str;
624
625 const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool;
637
638 fn lint(&self) -> &'static Lint;
640
641 fn check_unused_delims_expr(
642 &self,
643 cx: &EarlyContext<'_>,
644 value: &ast::Expr,
645 ctx: UnusedDelimsCtx,
646 followed_by_block: bool,
647 left_pos: Option<BytePos>,
648 right_pos: Option<BytePos>,
649 is_kw: bool,
650 );
651
652 fn is_expr_delims_necessary(
653 inner: &ast::Expr,
654 ctx: UnusedDelimsCtx,
655 followed_by_block: bool,
656 ) -> bool {
657 let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse;
658
659 if followed_by_else {
660 match inner.kind {
661 ast::ExprKind::Binary(op, ..) if op.node.is_lazy() => return true,
662 _ if classify::expr_trailing_brace(inner).is_some() => return true,
663 _ => {}
664 }
665 }
666
667 if let ast::ExprKind::Range(..) = inner.kind
669 && matches!(ctx, UnusedDelimsCtx::LetScrutineeExpr)
670 {
671 return true;
672 }
673
674 if matches!(inner.kind, ast::ExprKind::AddrOf(ast::BorrowKind::Raw, ..)) {
678 return true;
679 }
680
681 {
711 let mut innermost = inner;
712 loop {
713 innermost = match &innermost.kind {
714 ExprKind::Binary(_op, lhs, _rhs) => lhs,
715 ExprKind::Call(fn_, _params) => fn_,
716 ExprKind::Cast(expr, _ty) => expr,
717 ExprKind::Type(expr, _ty) => expr,
718 ExprKind::Index(base, _subscript, _) => base,
719 _ => break,
720 };
721 if !classify::expr_requires_semi_to_be_stmt(innermost) {
722 return true;
723 }
724 }
725 }
726
727 if !followed_by_block {
730 return false;
731 }
732
733 {
735 let mut innermost = inner;
736 loop {
737 innermost = match &innermost.kind {
738 ExprKind::AddrOf(_, _, expr) => expr,
739 _ => {
740 if parser::contains_exterior_struct_lit(innermost) {
741 return true;
742 } else {
743 break;
744 }
745 }
746 }
747 }
748 }
749
750 let mut innermost = inner;
751 loop {
752 innermost = match &innermost.kind {
753 ExprKind::Unary(_op, expr) => expr,
754 ExprKind::Binary(_op, _lhs, rhs) => rhs,
755 ExprKind::AssignOp(_op, _lhs, rhs) => rhs,
756 ExprKind::Assign(_lhs, rhs, _span) => rhs,
757
758 ExprKind::Ret(_) | ExprKind::Yield(..) | ExprKind::Yeet(..) => return true,
759
760 ExprKind::Break(_label, None) => return false,
761 ExprKind::Break(_label, Some(break_expr)) => {
762 return matches!(break_expr.kind, ExprKind::Block(..));
763 }
764
765 ExprKind::Range(_lhs, Some(rhs), _limits) => {
766 return matches!(rhs.kind, ExprKind::Block(..));
767 }
768
769 _ => return parser::contains_exterior_struct_lit(inner),
770 }
771 }
772 }
773
774 fn emit_unused_delims_expr(
775 &self,
776 cx: &EarlyContext<'_>,
777 value: &ast::Expr,
778 ctx: UnusedDelimsCtx,
779 left_pos: Option<BytePos>,
780 right_pos: Option<BytePos>,
781 is_kw: bool,
782 ) {
783 let spans = match value.kind {
784 ast::ExprKind::Block(ref block, None) if let [stmt] = block.stmts.as_slice() => stmt
785 .span
786 .find_ancestor_inside(value.span)
787 .map(|span| (value.span.with_hi(span.lo()), value.span.with_lo(span.hi()))),
788 ast::ExprKind::Paren(ref expr) => {
789 let expr_span_with_attrs =
792 if let Some(attr_lo) = expr.attrs.iter().map(|attr| attr.span.lo()).min() {
793 expr.span.with_lo(attr_lo)
794 } else {
795 expr.span
796 };
797 expr_span_with_attrs.find_ancestor_inside(value.span).map(|expr_span| {
798 (value.span.with_hi(expr_span.lo()), value.span.with_lo(expr_span.hi()))
799 })
800 }
801 _ => return,
802 };
803 let keep_space = (
804 left_pos.is_some_and(|s| s >= value.span.lo()),
805 right_pos.is_some_and(|s| s <= value.span.hi()),
806 );
807 self.emit_unused_delims(cx, value.span, spans, ctx.into(), keep_space, is_kw);
808 }
809
810 fn emit_unused_delims(
811 &self,
812 cx: &EarlyContext<'_>,
813 value_span: Span,
814 spans: Option<(Span, Span)>,
815 msg: &str,
816 keep_space: (bool, bool),
817 is_kw: bool,
818 ) {
819 let primary_span = if let Some((lo, hi)) = spans {
820 if hi.is_empty() {
821 return;
823 }
824 MultiSpan::from(vec![lo, hi])
825 } else {
826 MultiSpan::from(value_span)
827 };
828 let suggestion = spans.map(|(lo, hi)| {
829 let sm = cx.sess().source_map();
830 let lo_replace = if (keep_space.0 || is_kw)
831 && let Ok(snip) = sm.span_to_prev_source(lo)
832 && !snip.ends_with(' ')
833 {
834 " "
835 } else {
836 ""
837 };
838
839 let hi_replace = if keep_space.1
840 && let Ok(snip) = sm.span_to_next_source(hi)
841 && !snip.starts_with(' ')
842 {
843 " "
844 } else {
845 ""
846 };
847 UnusedDelimSuggestion {
848 start_span: lo,
849 start_replace: lo_replace,
850 end_span: hi,
851 end_replace: hi_replace,
852 }
853 });
854 cx.emit_span_lint(
855 self.lint(),
856 primary_span,
857 UnusedDelim { delim: Self::DELIM_STR, item: msg, suggestion },
858 );
859 }
860
861 fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
862 use rustc_ast::ExprKind::*;
863 let (value, ctx, followed_by_block, left_pos, right_pos, is_kw) = match e.kind {
864 If(ref cond, ref block, _)
866 if !matches!(cond.kind, Let(..)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
867 {
868 let left = e.span.lo() + rustc_span::BytePos(2);
869 let right = block.span.lo();
870 (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right), true)
871 }
872
873 While(ref cond, ref block, ..)
875 if !matches!(cond.kind, Let(..)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
876 {
877 let left = e.span.lo() + rustc_span::BytePos(5);
878 let right = block.span.lo();
879 (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right), true)
880 }
881
882 ForLoop { ref iter, ref body, .. } => {
883 (iter, UnusedDelimsCtx::ForIterExpr, true, None, Some(body.span.lo()), true)
884 }
885
886 Match(ref head, _, ast::MatchKind::Prefix)
887 if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
888 {
889 let left = e.span.lo() + rustc_span::BytePos(5);
890 (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None, true)
891 }
892
893 Ret(Some(ref value)) => {
894 let left = e.span.lo() + rustc_span::BytePos(3);
895 (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None, true)
896 }
897
898 Break(_, Some(ref value)) => {
899 (value, UnusedDelimsCtx::BreakValue, false, None, None, true)
900 }
901
902 Index(_, ref value, _) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false),
903
904 Assign(_, ref value, _) | AssignOp(.., ref value) => {
905 (value, UnusedDelimsCtx::AssignedValue, false, None, None, false)
906 }
907 ref call_or_other => {
909 let (args_to_check, ctx) = match *call_or_other {
910 Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg),
911 MethodCall(ref call) => (&call.args[..], UnusedDelimsCtx::MethodArg),
912 _ => {
914 return;
915 }
916 };
917 if e.span.ctxt().outer_expn_data().call_site.from_expansion() {
922 return;
923 }
924 for arg in args_to_check {
925 self.check_unused_delims_expr(cx, arg, ctx, false, None, None, false);
926 }
927 return;
928 }
929 };
930 self.check_unused_delims_expr(
931 cx,
932 value,
933 ctx,
934 followed_by_block,
935 left_pos,
936 right_pos,
937 is_kw,
938 );
939 }
940
941 fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
942 match s.kind {
943 StmtKind::Let(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
944 if let Some((init, els)) = local.kind.init_else_opt() {
945 let ctx = match els {
946 None => UnusedDelimsCtx::AssignedValue,
947 Some(_) => UnusedDelimsCtx::AssignedValueLetElse,
948 };
949 self.check_unused_delims_expr(cx, init, ctx, false, None, None, false);
950 }
951 }
952 StmtKind::Expr(ref expr) => {
953 self.check_unused_delims_expr(
954 cx,
955 expr,
956 UnusedDelimsCtx::BlockRetValue,
957 false,
958 None,
959 None,
960 false,
961 );
962 }
963 _ => {}
964 }
965 }
966
967 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
968 use ast::ItemKind::*;
969
970 if let Const(box ast::ConstItem { expr: Some(expr), .. })
971 | Static(box ast::StaticItem { expr: Some(expr), .. }) = &item.kind
972 {
973 self.check_unused_delims_expr(
974 cx,
975 expr,
976 UnusedDelimsCtx::AssignedValue,
977 false,
978 None,
979 None,
980 false,
981 );
982 }
983 }
984}
985
986declare_lint! {
987 pub(super) UNUSED_PARENS,
1003 Warn,
1004 "`if`, `match`, `while` and `return` do not need parentheses"
1005}
1006
1007#[derive(Default)]
1008pub(crate) struct UnusedParens {
1009 with_self_ty_parens: bool,
1010 parens_in_cast_in_lt: Vec<ast::NodeId>,
1013}
1014
1015impl_lint_pass!(UnusedParens => [UNUSED_PARENS]);
1016
1017impl UnusedDelimLint for UnusedParens {
1018 const DELIM_STR: &'static str = "parentheses";
1019
1020 const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = true;
1021
1022 fn lint(&self) -> &'static Lint {
1023 UNUSED_PARENS
1024 }
1025
1026 fn check_unused_delims_expr(
1027 &self,
1028 cx: &EarlyContext<'_>,
1029 value: &ast::Expr,
1030 ctx: UnusedDelimsCtx,
1031 followed_by_block: bool,
1032 left_pos: Option<BytePos>,
1033 right_pos: Option<BytePos>,
1034 is_kw: bool,
1035 ) {
1036 match value.kind {
1037 ast::ExprKind::Paren(ref inner) => {
1038 if !Self::is_expr_delims_necessary(inner, ctx, followed_by_block)
1039 && value.attrs.is_empty()
1040 && !value.span.from_expansion()
1041 && (ctx != UnusedDelimsCtx::LetScrutineeExpr
1042 || !matches!(inner.kind, ast::ExprKind::Binary(
1043 rustc_span::source_map::Spanned { node, .. },
1044 _,
1045 _,
1046 ) if node.is_lazy()))
1047 && !((ctx == UnusedDelimsCtx::ReturnValue
1048 || ctx == UnusedDelimsCtx::BreakValue)
1049 && matches!(inner.kind, ast::ExprKind::Assign(_, _, _)))
1050 {
1051 self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw)
1052 }
1053 }
1054 ast::ExprKind::Let(_, ref expr, _, _) => {
1055 self.check_unused_delims_expr(
1056 cx,
1057 expr,
1058 UnusedDelimsCtx::LetScrutineeExpr,
1059 followed_by_block,
1060 None,
1061 None,
1062 false,
1063 );
1064 }
1065 _ => {}
1066 }
1067 }
1068}
1069
1070impl UnusedParens {
1071 fn check_unused_parens_pat(
1072 &self,
1073 cx: &EarlyContext<'_>,
1074 value: &ast::Pat,
1075 avoid_or: bool,
1076 avoid_mut: bool,
1077 keep_space: (bool, bool),
1078 ) {
1079 use ast::{BindingMode, PatKind};
1080
1081 if let PatKind::Paren(inner) = &value.kind {
1082 match inner.kind {
1083 PatKind::Range(..) => return,
1088 PatKind::Or(..) if avoid_or => return,
1090 PatKind::Ident(BindingMode::MUT, ..) if avoid_mut => {
1092 return;
1093 }
1094 _ => {}
1096 }
1097 let spans = if !value.span.from_expansion() {
1098 inner
1099 .span
1100 .find_ancestor_inside(value.span)
1101 .map(|inner| (value.span.with_hi(inner.lo()), value.span.with_lo(inner.hi())))
1102 } else {
1103 None
1104 };
1105 self.emit_unused_delims(cx, value.span, spans, "pattern", keep_space, false);
1106 }
1107 }
1108
1109 fn cast_followed_by_lt(&self, expr: &ast::Expr) -> Option<ast::NodeId> {
1110 if let ExprKind::Binary(op, lhs, _rhs) = &expr.kind
1111 && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl)
1112 {
1113 let mut cur = lhs;
1114 while let ExprKind::Binary(_, _, rhs) = &cur.kind {
1115 cur = rhs;
1116 }
1117
1118 if let ExprKind::Cast(_, ty) = &cur.kind
1119 && let ast::TyKind::Paren(_) = &ty.kind
1120 {
1121 return Some(ty.id);
1122 }
1123 }
1124 None
1125 }
1126}
1127
1128impl EarlyLintPass for UnusedParens {
1129 #[inline]
1130 fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
1131 if let Some(ty_id) = self.cast_followed_by_lt(e) {
1132 self.parens_in_cast_in_lt.push(ty_id);
1133 }
1134
1135 match e.kind {
1136 ExprKind::Let(ref pat, _, _, _) | ExprKind::ForLoop { ref pat, .. } => {
1137 self.check_unused_parens_pat(cx, pat, false, false, (true, true));
1138 }
1139 ExprKind::If(ref cond, ref block, ref else_)
1143 if matches!(cond.peel_parens().kind, ExprKind::Let(..)) =>
1144 {
1145 self.check_unused_delims_expr(
1146 cx,
1147 cond.peel_parens(),
1148 UnusedDelimsCtx::LetScrutineeExpr,
1149 true,
1150 None,
1151 None,
1152 true,
1153 );
1154 for stmt in &block.stmts {
1155 <Self as UnusedDelimLint>::check_stmt(self, cx, stmt);
1156 }
1157 if let Some(e) = else_ {
1158 <Self as UnusedDelimLint>::check_expr(self, cx, e);
1159 }
1160 return;
1161 }
1162 ExprKind::Match(ref _expr, ref arm, _) => {
1163 for a in arm {
1164 if let Some(body) = &a.body {
1165 self.check_unused_delims_expr(
1166 cx,
1167 body,
1168 UnusedDelimsCtx::MatchArmExpr,
1169 false,
1170 None,
1171 None,
1172 true,
1173 );
1174 }
1175 }
1176 }
1177 _ => {}
1178 }
1179
1180 <Self as UnusedDelimLint>::check_expr(self, cx, e)
1181 }
1182
1183 fn check_expr_post(&mut self, _cx: &EarlyContext<'_>, e: &ast::Expr) {
1184 if let Some(ty_id) = self.cast_followed_by_lt(e) {
1185 let id = self
1186 .parens_in_cast_in_lt
1187 .pop()
1188 .expect("check_expr and check_expr_post must balance");
1189 assert_eq!(
1190 id, ty_id,
1191 "check_expr, check_ty, and check_expr_post are called, in that order, by the visitor"
1192 );
1193 }
1194 }
1195
1196 fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) {
1197 use ast::Mutability;
1198 use ast::PatKind::*;
1199 let keep_space = (false, false);
1200 match &p.kind {
1201 Paren(_)
1203 | Wild | Never | Rest | Expr(..) | MacCall(..) | Range(..) | Ident(.., None) | Path(..) | Err(_) => {},
1205 TupleStruct(_, _, ps) | Tuple(ps) | Slice(ps) | Or(ps) => for p in ps {
1207 self.check_unused_parens_pat(cx, p, false, false, keep_space);
1208 },
1209 Struct(_, _, fps, _) => for f in fps {
1210 self.check_unused_parens_pat(cx, &f.pat, false, false, keep_space);
1211 },
1212 Ident(.., Some(p)) | Box(p) | Deref(p) | Guard(p, _) => self.check_unused_parens_pat(cx, p, true, false, keep_space),
1214 Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not, keep_space),
1217 }
1218 }
1219
1220 fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
1221 if let StmtKind::Let(ref local) = s.kind {
1222 self.check_unused_parens_pat(cx, &local.pat, true, false, (true, false));
1223 }
1224
1225 <Self as UnusedDelimLint>::check_stmt(self, cx, s)
1226 }
1227
1228 fn check_param(&mut self, cx: &EarlyContext<'_>, param: &ast::Param) {
1229 self.check_unused_parens_pat(cx, ¶m.pat, true, false, (false, false));
1230 }
1231
1232 fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
1233 self.check_unused_parens_pat(cx, &arm.pat, false, false, (false, false));
1234 }
1235
1236 fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
1237 if let ast::TyKind::Paren(_) = ty.kind
1238 && Some(&ty.id) == self.parens_in_cast_in_lt.last()
1239 {
1240 return;
1241 }
1242 match &ty.kind {
1243 ast::TyKind::Array(_, len) => {
1244 self.check_unused_delims_expr(
1245 cx,
1246 &len.value,
1247 UnusedDelimsCtx::ArrayLenExpr,
1248 false,
1249 None,
1250 None,
1251 false,
1252 );
1253 }
1254 ast::TyKind::Paren(r) => {
1255 match &r.kind {
1256 ast::TyKind::TraitObject(..) => {}
1257 ast::TyKind::BareFn(b)
1258 if self.with_self_ty_parens && b.generic_params.len() > 0 => {}
1259 ast::TyKind::ImplTrait(_, bounds) if bounds.len() > 1 => {}
1260 _ => {
1261 let spans = if !ty.span.from_expansion() {
1262 r.span
1263 .find_ancestor_inside(ty.span)
1264 .map(|r| (ty.span.with_hi(r.lo()), ty.span.with_lo(r.hi())))
1265 } else {
1266 None
1267 };
1268 self.emit_unused_delims(cx, ty.span, spans, "type", (false, false), false);
1269 }
1270 }
1271 self.with_self_ty_parens = false;
1272 }
1273 _ => {}
1274 }
1275 }
1276
1277 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
1278 <Self as UnusedDelimLint>::check_item(self, cx, item)
1279 }
1280
1281 fn enter_where_predicate(&mut self, _: &EarlyContext<'_>, pred: &ast::WherePredicate) {
1282 use rustc_ast::{WhereBoundPredicate, WherePredicateKind};
1283 if let WherePredicateKind::BoundPredicate(WhereBoundPredicate {
1284 bounded_ty,
1285 bound_generic_params,
1286 ..
1287 }) = &pred.kind
1288 && let ast::TyKind::Paren(_) = &bounded_ty.kind
1289 && bound_generic_params.is_empty()
1290 {
1291 self.with_self_ty_parens = true;
1292 }
1293 }
1294
1295 fn exit_where_predicate(&mut self, _: &EarlyContext<'_>, _: &ast::WherePredicate) {
1296 assert!(!self.with_self_ty_parens);
1297 }
1298}
1299
1300declare_lint! {
1301 pub(super) UNUSED_BRACES,
1319 Warn,
1320 "unnecessary braces around an expression"
1321}
1322
1323declare_lint_pass!(UnusedBraces => [UNUSED_BRACES]);
1324
1325impl UnusedDelimLint for UnusedBraces {
1326 const DELIM_STR: &'static str = "braces";
1327
1328 const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = false;
1329
1330 fn lint(&self) -> &'static Lint {
1331 UNUSED_BRACES
1332 }
1333
1334 fn check_unused_delims_expr(
1335 &self,
1336 cx: &EarlyContext<'_>,
1337 value: &ast::Expr,
1338 ctx: UnusedDelimsCtx,
1339 followed_by_block: bool,
1340 left_pos: Option<BytePos>,
1341 right_pos: Option<BytePos>,
1342 is_kw: bool,
1343 ) {
1344 match value.kind {
1345 ast::ExprKind::Block(ref inner, None)
1346 if inner.rules == ast::BlockCheckMode::Default =>
1347 {
1348 if let [stmt] = inner.stmts.as_slice() {
1373 if let ast::StmtKind::Expr(ref expr) = stmt.kind {
1374 if !Self::is_expr_delims_necessary(expr, ctx, followed_by_block)
1375 && (ctx != UnusedDelimsCtx::AnonConst
1376 || (matches!(expr.kind, ast::ExprKind::Lit(_))
1377 && !expr.span.from_expansion()))
1378 && !cx.sess().source_map().is_multiline(value.span)
1379 && value.attrs.is_empty()
1380 && !value.span.from_expansion()
1381 && !inner.span.from_expansion()
1382 {
1383 self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw)
1384 }
1385 }
1386 }
1387 }
1388 ast::ExprKind::Let(_, ref expr, _, _) => {
1389 self.check_unused_delims_expr(
1390 cx,
1391 expr,
1392 UnusedDelimsCtx::LetScrutineeExpr,
1393 followed_by_block,
1394 None,
1395 None,
1396 false,
1397 );
1398 }
1399 _ => {}
1400 }
1401 }
1402}
1403
1404impl EarlyLintPass for UnusedBraces {
1405 fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
1406 <Self as UnusedDelimLint>::check_stmt(self, cx, s)
1407 }
1408
1409 #[inline]
1410 fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
1411 <Self as UnusedDelimLint>::check_expr(self, cx, e);
1412
1413 if let ExprKind::Repeat(_, ref anon_const) = e.kind {
1414 self.check_unused_delims_expr(
1415 cx,
1416 &anon_const.value,
1417 UnusedDelimsCtx::AnonConst,
1418 false,
1419 None,
1420 None,
1421 false,
1422 );
1423 }
1424 }
1425
1426 fn check_generic_arg(&mut self, cx: &EarlyContext<'_>, arg: &ast::GenericArg) {
1427 if let ast::GenericArg::Const(ct) = arg {
1428 self.check_unused_delims_expr(
1429 cx,
1430 &ct.value,
1431 UnusedDelimsCtx::AnonConst,
1432 false,
1433 None,
1434 None,
1435 false,
1436 );
1437 }
1438 }
1439
1440 fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
1441 if let Some(anon_const) = &v.disr_expr {
1442 self.check_unused_delims_expr(
1443 cx,
1444 &anon_const.value,
1445 UnusedDelimsCtx::AnonConst,
1446 false,
1447 None,
1448 None,
1449 false,
1450 );
1451 }
1452 }
1453
1454 fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
1455 match ty.kind {
1456 ast::TyKind::Array(_, ref len) => {
1457 self.check_unused_delims_expr(
1458 cx,
1459 &len.value,
1460 UnusedDelimsCtx::ArrayLenExpr,
1461 false,
1462 None,
1463 None,
1464 false,
1465 );
1466 }
1467
1468 ast::TyKind::Typeof(ref anon_const) => {
1469 self.check_unused_delims_expr(
1470 cx,
1471 &anon_const.value,
1472 UnusedDelimsCtx::AnonConst,
1473 false,
1474 None,
1475 None,
1476 false,
1477 );
1478 }
1479
1480 _ => {}
1481 }
1482 }
1483
1484 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
1485 <Self as UnusedDelimLint>::check_item(self, cx, item)
1486 }
1487}
1488
1489declare_lint! {
1490 UNUSED_IMPORT_BRACES,
1515 Allow,
1516 "unnecessary braces around an imported item"
1517}
1518
1519declare_lint_pass!(UnusedImportBraces => [UNUSED_IMPORT_BRACES]);
1520
1521impl UnusedImportBraces {
1522 fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) {
1523 if let ast::UseTreeKind::Nested { ref items, .. } = use_tree.kind {
1524 for (tree, _) in items {
1526 self.check_use_tree(cx, tree, item);
1527 }
1528
1529 let [(tree, _)] = items.as_slice() else { return };
1531
1532 let node_name = match tree.kind {
1534 ast::UseTreeKind::Simple(rename) => {
1535 let orig_ident = tree.prefix.segments.last().unwrap().ident;
1536 if orig_ident.name == kw::SelfLower {
1537 return;
1538 }
1539 rename.unwrap_or(orig_ident).name
1540 }
1541 ast::UseTreeKind::Glob => sym::asterisk,
1542 ast::UseTreeKind::Nested { .. } => return,
1543 };
1544
1545 cx.emit_span_lint(
1546 UNUSED_IMPORT_BRACES,
1547 item.span,
1548 UnusedImportBracesDiag { node: node_name },
1549 );
1550 }
1551 }
1552}
1553
1554impl EarlyLintPass for UnusedImportBraces {
1555 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
1556 if let ast::ItemKind::Use(ref use_tree) = item.kind {
1557 self.check_use_tree(cx, use_tree, item);
1558 }
1559 }
1560}
1561
1562declare_lint! {
1563 pub(super) UNUSED_ALLOCATION,
1582 Warn,
1583 "detects unnecessary allocations that can be eliminated"
1584}
1585
1586declare_lint_pass!(UnusedAllocation => [UNUSED_ALLOCATION]);
1587
1588impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
1589 fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
1590 match e.kind {
1591 hir::ExprKind::Call(path_expr, [_])
1592 if let hir::ExprKind::Path(qpath) = &path_expr.kind
1593 && let Some(did) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()
1594 && cx.tcx.is_diagnostic_item(sym::box_new, did) => {}
1595 _ => return,
1596 }
1597
1598 for adj in cx.typeck_results().expr_adjustments(e) {
1599 if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(m)) = adj.kind {
1600 match m {
1601 adjustment::AutoBorrowMutability::Not => {
1602 cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag);
1603 }
1604 adjustment::AutoBorrowMutability::Mut { .. } => {
1605 cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag);
1606 }
1607 };
1608 }
1609 }
1610 }
1611}