1use std::iter;
2
3use rustc_ast::util::{classify, parser};
4use rustc_ast::{self as ast, ExprKind, HasAttrs as _, StmtKind};
5use rustc_attr_data_structures::{AttributeKind, find_attr};
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(reason) = find_attr!(
373 cx.tcx.get_all_attrs(def_id),
374 AttributeKind::MustUse { reason, .. } => reason
375 ) {
376 Some(MustUsePath::Def(span, def_id, *reason))
378 } else {
379 None
380 }
381 }
382
383 fn check_must_use_def(
386 cx: &LateContext<'_>,
387 def_id: DefId,
388 span: Span,
389 descr_pre_path: &str,
390 descr_post_path: &str,
391 expr_is_from_block: bool,
392 ) -> bool {
393 is_def_must_use(cx, def_id, span)
394 .map(|must_use_path| {
395 emit_must_use_untranslated(
396 cx,
397 &must_use_path,
398 descr_pre_path,
399 descr_post_path,
400 1,
401 false,
402 expr_is_from_block,
403 )
404 })
405 .is_some()
406 }
407
408 #[instrument(skip(cx), level = "debug")]
409 fn emit_must_use_untranslated(
410 cx: &LateContext<'_>,
411 path: &MustUsePath,
412 descr_pre: &str,
413 descr_post: &str,
414 plural_len: usize,
415 is_inner: bool,
416 expr_is_from_block: bool,
417 ) {
418 let plural_suffix = pluralize!(plural_len);
419
420 match path {
421 MustUsePath::Suppressed => {}
422 MustUsePath::Boxed(path) => {
423 let descr_pre = &format!("{descr_pre}boxed ");
424 emit_must_use_untranslated(
425 cx,
426 path,
427 descr_pre,
428 descr_post,
429 plural_len,
430 true,
431 expr_is_from_block,
432 );
433 }
434 MustUsePath::Pinned(path) => {
435 let descr_pre = &format!("{descr_pre}pinned ");
436 emit_must_use_untranslated(
437 cx,
438 path,
439 descr_pre,
440 descr_post,
441 plural_len,
442 true,
443 expr_is_from_block,
444 );
445 }
446 MustUsePath::Opaque(path) => {
447 let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of ");
448 emit_must_use_untranslated(
449 cx,
450 path,
451 descr_pre,
452 descr_post,
453 plural_len,
454 true,
455 expr_is_from_block,
456 );
457 }
458 MustUsePath::TraitObject(path) => {
459 let descr_post = &format!(" trait object{plural_suffix}{descr_post}");
460 emit_must_use_untranslated(
461 cx,
462 path,
463 descr_pre,
464 descr_post,
465 plural_len,
466 true,
467 expr_is_from_block,
468 );
469 }
470 MustUsePath::TupleElement(elems) => {
471 for (index, path) in elems {
472 let descr_post = &format!(" in tuple element {index}");
473 emit_must_use_untranslated(
474 cx,
475 path,
476 descr_pre,
477 descr_post,
478 plural_len,
479 true,
480 expr_is_from_block,
481 );
482 }
483 }
484 MustUsePath::Array(path, len) => {
485 let descr_pre = &format!("{descr_pre}array{plural_suffix} of ");
486 emit_must_use_untranslated(
487 cx,
488 path,
489 descr_pre,
490 descr_post,
491 plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)),
492 true,
493 expr_is_from_block,
494 );
495 }
496 MustUsePath::Closure(span) => {
497 cx.emit_span_lint(
498 UNUSED_MUST_USE,
499 *span,
500 UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post },
501 );
502 }
503 MustUsePath::Coroutine(span) => {
504 cx.emit_span_lint(
505 UNUSED_MUST_USE,
506 *span,
507 UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post },
508 );
509 }
510 MustUsePath::Def(span, def_id, reason) => {
511 cx.emit_span_lint(
512 UNUSED_MUST_USE,
513 *span,
514 UnusedDef {
515 pre: descr_pre,
516 post: descr_post,
517 cx,
518 def_id: *def_id,
519 note: *reason,
520 suggestion: (!is_inner).then_some(if expr_is_from_block {
521 UnusedDefSuggestion::BlockTailExpr {
522 before_span: span.shrink_to_lo(),
523 after_span: span.shrink_to_hi(),
524 }
525 } else {
526 UnusedDefSuggestion::NormalExpr { span: span.shrink_to_lo() }
527 }),
528 },
529 );
530 }
531 }
532 }
533 }
534}
535
536declare_lint! {
537 pub PATH_STATEMENTS,
553 Warn,
554 "path statements with no effect"
555}
556
557declare_lint_pass!(PathStatements => [PATH_STATEMENTS]);
558
559impl<'tcx> LateLintPass<'tcx> for PathStatements {
560 fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
561 if let hir::StmtKind::Semi(expr) = s.kind {
562 if let hir::ExprKind::Path(_) = expr.kind {
563 let ty = cx.typeck_results().expr_ty(expr);
564 if ty.needs_drop(cx.tcx, cx.typing_env()) {
565 let sub = if let Ok(snippet) = cx.sess().source_map().span_to_snippet(expr.span)
566 {
567 PathStatementDropSub::Suggestion { span: s.span, snippet }
568 } else {
569 PathStatementDropSub::Help { span: s.span }
570 };
571 cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementDrop { sub })
572 } else {
573 cx.emit_span_lint(PATH_STATEMENTS, s.span, PathStatementNoEffect);
574 }
575 }
576 }
577 }
578}
579
580#[derive(Copy, Clone, Debug, PartialEq, Eq)]
581enum UnusedDelimsCtx {
582 FunctionArg,
583 MethodArg,
584 AssignedValue,
585 AssignedValueLetElse,
586 IfCond,
587 WhileCond,
588 ForIterExpr,
589 MatchScrutineeExpr,
590 ReturnValue,
591 BlockRetValue,
592 BreakValue,
593 LetScrutineeExpr,
594 ArrayLenExpr,
595 AnonConst,
596 MatchArmExpr,
597 IndexExpr,
598}
599
600impl From<UnusedDelimsCtx> for &'static str {
601 fn from(ctx: UnusedDelimsCtx) -> &'static str {
602 match ctx {
603 UnusedDelimsCtx::FunctionArg => "function argument",
604 UnusedDelimsCtx::MethodArg => "method argument",
605 UnusedDelimsCtx::AssignedValue | UnusedDelimsCtx::AssignedValueLetElse => {
606 "assigned value"
607 }
608 UnusedDelimsCtx::IfCond => "`if` condition",
609 UnusedDelimsCtx::WhileCond => "`while` condition",
610 UnusedDelimsCtx::ForIterExpr => "`for` iterator expression",
611 UnusedDelimsCtx::MatchScrutineeExpr => "`match` scrutinee expression",
612 UnusedDelimsCtx::ReturnValue => "`return` value",
613 UnusedDelimsCtx::BlockRetValue => "block return value",
614 UnusedDelimsCtx::BreakValue => "`break` value",
615 UnusedDelimsCtx::LetScrutineeExpr => "`let` scrutinee expression",
616 UnusedDelimsCtx::ArrayLenExpr | UnusedDelimsCtx::AnonConst => "const expression",
617 UnusedDelimsCtx::MatchArmExpr => "match arm expression",
618 UnusedDelimsCtx::IndexExpr => "index expression",
619 }
620 }
621}
622
623trait UnusedDelimLint {
625 const DELIM_STR: &'static str;
626
627 const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool;
639
640 fn lint(&self) -> &'static Lint;
642
643 fn check_unused_delims_expr(
644 &self,
645 cx: &EarlyContext<'_>,
646 value: &ast::Expr,
647 ctx: UnusedDelimsCtx,
648 followed_by_block: bool,
649 left_pos: Option<BytePos>,
650 right_pos: Option<BytePos>,
651 is_kw: bool,
652 );
653
654 fn is_expr_delims_necessary(
655 inner: &ast::Expr,
656 ctx: UnusedDelimsCtx,
657 followed_by_block: bool,
658 ) -> bool {
659 let followed_by_else = ctx == UnusedDelimsCtx::AssignedValueLetElse;
660
661 if followed_by_else {
662 match inner.kind {
663 ast::ExprKind::Binary(op, ..) if op.node.is_lazy() => return true,
664 _ if classify::expr_trailing_brace(inner).is_some() => return true,
665 _ => {}
666 }
667 }
668
669 if let ast::ExprKind::Range(..) = inner.kind
671 && matches!(ctx, UnusedDelimsCtx::LetScrutineeExpr)
672 {
673 return true;
674 }
675
676 if matches!(inner.kind, ast::ExprKind::AddrOf(ast::BorrowKind::Raw, ..)) {
680 return true;
681 }
682
683 {
713 let mut innermost = inner;
714 loop {
715 innermost = match &innermost.kind {
716 ExprKind::Binary(_op, lhs, _rhs) => lhs,
717 ExprKind::Call(fn_, _params) => fn_,
718 ExprKind::Cast(expr, _ty) => expr,
719 ExprKind::Type(expr, _ty) => expr,
720 ExprKind::Index(base, _subscript, _) => base,
721 _ => break,
722 };
723 if !classify::expr_requires_semi_to_be_stmt(innermost) {
724 return true;
725 }
726 }
727 }
728
729 if !followed_by_block {
732 return false;
733 }
734
735 {
737 let mut innermost = inner;
738 loop {
739 innermost = match &innermost.kind {
740 ExprKind::AddrOf(_, _, expr) => expr,
741 _ => {
742 if parser::contains_exterior_struct_lit(innermost) {
743 return true;
744 } else {
745 break;
746 }
747 }
748 }
749 }
750 }
751
752 let mut innermost = inner;
753 loop {
754 innermost = match &innermost.kind {
755 ExprKind::Unary(_op, expr) => expr,
756 ExprKind::Binary(_op, _lhs, rhs) => rhs,
757 ExprKind::AssignOp(_op, _lhs, rhs) => rhs,
758 ExprKind::Assign(_lhs, rhs, _span) => rhs,
759
760 ExprKind::Ret(_) | ExprKind::Yield(..) | ExprKind::Yeet(..) => return true,
761
762 ExprKind::Break(_label, None) => return false,
763 ExprKind::Break(_label, Some(break_expr)) => {
764 return matches!(break_expr.kind, ExprKind::Block(..));
765 }
766
767 ExprKind::Range(_lhs, Some(rhs), _limits) => {
768 return matches!(rhs.kind, ExprKind::Block(..));
769 }
770
771 _ => return parser::contains_exterior_struct_lit(inner),
772 }
773 }
774 }
775
776 fn emit_unused_delims_expr(
777 &self,
778 cx: &EarlyContext<'_>,
779 value: &ast::Expr,
780 ctx: UnusedDelimsCtx,
781 left_pos: Option<BytePos>,
782 right_pos: Option<BytePos>,
783 is_kw: bool,
784 ) {
785 let span_with_attrs = match value.kind {
786 ast::ExprKind::Block(ref block, None) if let [stmt] = block.stmts.as_slice() => {
787 if let Some(attr_lo) = stmt.attrs().iter().map(|attr| attr.span.lo()).min() {
790 stmt.span.with_lo(attr_lo)
791 } else {
792 stmt.span
793 }
794 }
795 ast::ExprKind::Paren(ref expr) => {
796 if let Some(attr_lo) = expr.attrs.iter().map(|attr| attr.span.lo()).min() {
799 expr.span.with_lo(attr_lo)
800 } else {
801 expr.span
802 }
803 }
804 _ => return,
805 };
806 let spans = span_with_attrs
807 .find_ancestor_inside(value.span)
808 .map(|span| (value.span.with_hi(span.lo()), value.span.with_lo(span.hi())));
809 let keep_space = (
810 left_pos.is_some_and(|s| s >= value.span.lo()),
811 right_pos.is_some_and(|s| s <= value.span.hi()),
812 );
813 self.emit_unused_delims(cx, value.span, spans, ctx.into(), keep_space, is_kw);
814 }
815
816 fn emit_unused_delims(
817 &self,
818 cx: &EarlyContext<'_>,
819 value_span: Span,
820 spans: Option<(Span, Span)>,
821 msg: &str,
822 keep_space: (bool, bool),
823 is_kw: bool,
824 ) {
825 let primary_span = if let Some((lo, hi)) = spans {
826 if hi.is_empty() {
827 return;
829 }
830 MultiSpan::from(vec![lo, hi])
831 } else {
832 MultiSpan::from(value_span)
833 };
834 let suggestion = spans.map(|(lo, hi)| {
835 let sm = cx.sess().source_map();
836 let lo_replace = if (keep_space.0 || is_kw)
837 && let Ok(snip) = sm.span_to_prev_source(lo)
838 && !snip.ends_with(' ')
839 {
840 " "
841 } else {
842 ""
843 };
844
845 let hi_replace = if keep_space.1
846 && let Ok(snip) = sm.span_to_next_source(hi)
847 && !snip.starts_with(' ')
848 {
849 " "
850 } else {
851 ""
852 };
853 UnusedDelimSuggestion {
854 start_span: lo,
855 start_replace: lo_replace,
856 end_span: hi,
857 end_replace: hi_replace,
858 }
859 });
860 cx.emit_span_lint(
861 self.lint(),
862 primary_span,
863 UnusedDelim { delim: Self::DELIM_STR, item: msg, suggestion },
864 );
865 }
866
867 fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
868 use rustc_ast::ExprKind::*;
869 let (value, ctx, followed_by_block, left_pos, right_pos, is_kw) = match e.kind {
870 If(ref cond, ref block, _)
872 if !matches!(cond.kind, Let(..)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
873 {
874 let left = e.span.lo() + rustc_span::BytePos(2);
875 let right = block.span.lo();
876 (cond, UnusedDelimsCtx::IfCond, true, Some(left), Some(right), true)
877 }
878
879 While(ref cond, ref block, ..)
881 if !matches!(cond.kind, Let(..)) || Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
882 {
883 let left = e.span.lo() + rustc_span::BytePos(5);
884 let right = block.span.lo();
885 (cond, UnusedDelimsCtx::WhileCond, true, Some(left), Some(right), true)
886 }
887
888 ForLoop { ref iter, ref body, .. } => {
889 (iter, UnusedDelimsCtx::ForIterExpr, true, None, Some(body.span.lo()), true)
890 }
891
892 Match(ref head, _, ast::MatchKind::Prefix)
893 if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX =>
894 {
895 let left = e.span.lo() + rustc_span::BytePos(5);
896 (head, UnusedDelimsCtx::MatchScrutineeExpr, true, Some(left), None, true)
897 }
898
899 Ret(Some(ref value)) => {
900 let left = e.span.lo() + rustc_span::BytePos(3);
901 (value, UnusedDelimsCtx::ReturnValue, false, Some(left), None, true)
902 }
903
904 Break(_, Some(ref value)) => {
905 (value, UnusedDelimsCtx::BreakValue, false, None, None, true)
906 }
907
908 Index(_, ref value, _) => (value, UnusedDelimsCtx::IndexExpr, false, None, None, false),
909
910 Assign(_, ref value, _) | AssignOp(.., ref value) => {
911 (value, UnusedDelimsCtx::AssignedValue, false, None, None, false)
912 }
913 ref call_or_other => {
915 let (args_to_check, ctx) = match *call_or_other {
916 Call(_, ref args) => (&args[..], UnusedDelimsCtx::FunctionArg),
917 MethodCall(ref call) => (&call.args[..], UnusedDelimsCtx::MethodArg),
918 _ => {
920 return;
921 }
922 };
923 if e.span.ctxt().outer_expn_data().call_site.from_expansion() {
928 return;
929 }
930 for arg in args_to_check {
931 self.check_unused_delims_expr(cx, arg, ctx, false, None, None, false);
932 }
933 return;
934 }
935 };
936 self.check_unused_delims_expr(
937 cx,
938 value,
939 ctx,
940 followed_by_block,
941 left_pos,
942 right_pos,
943 is_kw,
944 );
945 }
946
947 fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
948 match s.kind {
949 StmtKind::Let(ref local) if Self::LINT_EXPR_IN_PATTERN_MATCHING_CTX => {
950 if let Some((init, els)) = local.kind.init_else_opt() {
951 if els.is_some()
952 && let ExprKind::Paren(paren) = &init.kind
953 && !init.span.eq_ctxt(paren.span)
954 {
955 return;
966 }
967 let ctx = match els {
968 None => UnusedDelimsCtx::AssignedValue,
969 Some(_) => UnusedDelimsCtx::AssignedValueLetElse,
970 };
971 self.check_unused_delims_expr(cx, init, ctx, false, None, None, false);
972 }
973 }
974 StmtKind::Expr(ref expr) => {
975 self.check_unused_delims_expr(
976 cx,
977 expr,
978 UnusedDelimsCtx::BlockRetValue,
979 false,
980 None,
981 None,
982 false,
983 );
984 }
985 _ => {}
986 }
987 }
988
989 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
990 use ast::ItemKind::*;
991
992 if let Const(box ast::ConstItem { expr: Some(expr), .. })
993 | Static(box ast::StaticItem { expr: Some(expr), .. }) = &item.kind
994 {
995 self.check_unused_delims_expr(
996 cx,
997 expr,
998 UnusedDelimsCtx::AssignedValue,
999 false,
1000 None,
1001 None,
1002 false,
1003 );
1004 }
1005 }
1006}
1007
1008declare_lint! {
1009 pub(super) UNUSED_PARENS,
1025 Warn,
1026 "`if`, `match`, `while` and `return` do not need parentheses"
1027}
1028
1029#[derive(Default)]
1030pub(crate) struct UnusedParens {
1031 with_self_ty_parens: bool,
1032 parens_in_cast_in_lt: Vec<ast::NodeId>,
1035}
1036
1037impl_lint_pass!(UnusedParens => [UNUSED_PARENS]);
1038
1039impl UnusedDelimLint for UnusedParens {
1040 const DELIM_STR: &'static str = "parentheses";
1041
1042 const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = true;
1043
1044 fn lint(&self) -> &'static Lint {
1045 UNUSED_PARENS
1046 }
1047
1048 fn check_unused_delims_expr(
1049 &self,
1050 cx: &EarlyContext<'_>,
1051 value: &ast::Expr,
1052 ctx: UnusedDelimsCtx,
1053 followed_by_block: bool,
1054 left_pos: Option<BytePos>,
1055 right_pos: Option<BytePos>,
1056 is_kw: bool,
1057 ) {
1058 match value.kind {
1059 ast::ExprKind::Paren(ref inner) => {
1060 if !Self::is_expr_delims_necessary(inner, ctx, followed_by_block)
1061 && value.attrs.is_empty()
1062 && !value.span.from_expansion()
1063 && (ctx != UnusedDelimsCtx::LetScrutineeExpr
1064 || !matches!(inner.kind, ast::ExprKind::Binary(
1065 rustc_span::source_map::Spanned { node, .. },
1066 _,
1067 _,
1068 ) if node.is_lazy()))
1069 && !((ctx == UnusedDelimsCtx::ReturnValue
1070 || ctx == UnusedDelimsCtx::BreakValue)
1071 && matches!(inner.kind, ast::ExprKind::Assign(_, _, _)))
1072 {
1073 self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw)
1074 }
1075 }
1076 ast::ExprKind::Let(_, ref expr, _, _) => {
1077 self.check_unused_delims_expr(
1078 cx,
1079 expr,
1080 UnusedDelimsCtx::LetScrutineeExpr,
1081 followed_by_block,
1082 None,
1083 None,
1084 false,
1085 );
1086 }
1087 _ => {}
1088 }
1089 }
1090}
1091
1092impl UnusedParens {
1093 fn check_unused_parens_pat(
1094 &self,
1095 cx: &EarlyContext<'_>,
1096 value: &ast::Pat,
1097 avoid_or: bool,
1098 avoid_mut: bool,
1099 keep_space: (bool, bool),
1100 ) {
1101 use ast::{BindingMode, PatKind};
1102
1103 if let PatKind::Paren(inner) = &value.kind {
1104 match inner.kind {
1105 PatKind::Range(..) => return,
1110 PatKind::Or(..) if avoid_or => return,
1112 PatKind::Ident(BindingMode::MUT, ..) if avoid_mut => {
1114 return;
1115 }
1116 _ => {}
1118 }
1119 let spans = if !value.span.from_expansion() {
1120 inner
1121 .span
1122 .find_ancestor_inside(value.span)
1123 .map(|inner| (value.span.with_hi(inner.lo()), value.span.with_lo(inner.hi())))
1124 } else {
1125 None
1126 };
1127 self.emit_unused_delims(cx, value.span, spans, "pattern", keep_space, false);
1128 }
1129 }
1130
1131 fn cast_followed_by_lt(&self, expr: &ast::Expr) -> Option<ast::NodeId> {
1132 if let ExprKind::Binary(op, lhs, _rhs) = &expr.kind
1133 && (op.node == ast::BinOpKind::Lt || op.node == ast::BinOpKind::Shl)
1134 {
1135 let mut cur = lhs;
1136 while let ExprKind::Binary(_, _, rhs) = &cur.kind {
1137 cur = rhs;
1138 }
1139
1140 if let ExprKind::Cast(_, ty) = &cur.kind
1141 && let ast::TyKind::Paren(_) = &ty.kind
1142 {
1143 return Some(ty.id);
1144 }
1145 }
1146 None
1147 }
1148}
1149
1150impl EarlyLintPass for UnusedParens {
1151 #[inline]
1152 fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
1153 if let Some(ty_id) = self.cast_followed_by_lt(e) {
1154 self.parens_in_cast_in_lt.push(ty_id);
1155 }
1156
1157 match e.kind {
1158 ExprKind::Let(ref pat, _, _, _) | ExprKind::ForLoop { ref pat, .. } => {
1159 self.check_unused_parens_pat(cx, pat, false, false, (true, true));
1160 }
1161 ExprKind::If(ref cond, ref block, ref else_)
1165 if matches!(cond.peel_parens().kind, ExprKind::Let(..)) =>
1166 {
1167 self.check_unused_delims_expr(
1168 cx,
1169 cond.peel_parens(),
1170 UnusedDelimsCtx::LetScrutineeExpr,
1171 true,
1172 None,
1173 None,
1174 true,
1175 );
1176 for stmt in &block.stmts {
1177 <Self as UnusedDelimLint>::check_stmt(self, cx, stmt);
1178 }
1179 if let Some(e) = else_ {
1180 <Self as UnusedDelimLint>::check_expr(self, cx, e);
1181 }
1182 return;
1183 }
1184 ExprKind::Match(ref _expr, ref arm, _) => {
1185 for a in arm {
1186 if let Some(body) = &a.body {
1187 self.check_unused_delims_expr(
1188 cx,
1189 body,
1190 UnusedDelimsCtx::MatchArmExpr,
1191 false,
1192 None,
1193 None,
1194 true,
1195 );
1196 }
1197 }
1198 }
1199 _ => {}
1200 }
1201
1202 <Self as UnusedDelimLint>::check_expr(self, cx, e)
1203 }
1204
1205 fn check_expr_post(&mut self, _cx: &EarlyContext<'_>, e: &ast::Expr) {
1206 if let Some(ty_id) = self.cast_followed_by_lt(e) {
1207 let id = self
1208 .parens_in_cast_in_lt
1209 .pop()
1210 .expect("check_expr and check_expr_post must balance");
1211 assert_eq!(
1212 id, ty_id,
1213 "check_expr, check_ty, and check_expr_post are called, in that order, by the visitor"
1214 );
1215 }
1216 }
1217
1218 fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &ast::Pat) {
1219 use ast::Mutability;
1220 use ast::PatKind::*;
1221 let keep_space = (false, false);
1222 match &p.kind {
1223 Paren(_)
1225 | Missing | Wild | Never | Rest | Expr(..) | MacCall(..) | Range(..) | Ident(.., None)
1227 | Path(..) | Err(_) => {},
1228 TupleStruct(_, _, ps) | Tuple(ps) | Slice(ps) | Or(ps) => for p in ps {
1230 self.check_unused_parens_pat(cx, p, false, false, keep_space);
1231 },
1232 Struct(_, _, fps, _) => for f in fps {
1233 self.check_unused_parens_pat(cx, &f.pat, false, false, keep_space);
1234 },
1235 Ident(.., Some(p)) | Box(p) | Deref(p) | Guard(p, _) => self.check_unused_parens_pat(cx, p, true, false, keep_space),
1237 Ref(p, m) => self.check_unused_parens_pat(cx, p, true, *m == Mutability::Not, keep_space),
1240 }
1241 }
1242
1243 fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
1244 if let StmtKind::Let(ref local) = s.kind {
1245 self.check_unused_parens_pat(cx, &local.pat, true, false, (true, false));
1246 }
1247
1248 <Self as UnusedDelimLint>::check_stmt(self, cx, s)
1249 }
1250
1251 fn check_param(&mut self, cx: &EarlyContext<'_>, param: &ast::Param) {
1252 self.check_unused_parens_pat(cx, ¶m.pat, true, false, (false, false));
1253 }
1254
1255 fn check_arm(&mut self, cx: &EarlyContext<'_>, arm: &ast::Arm) {
1256 self.check_unused_parens_pat(cx, &arm.pat, false, false, (false, false));
1257 }
1258
1259 fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
1260 if let ast::TyKind::Paren(_) = ty.kind
1261 && Some(&ty.id) == self.parens_in_cast_in_lt.last()
1262 {
1263 return;
1264 }
1265 match &ty.kind {
1266 ast::TyKind::Array(_, len) => {
1267 self.check_unused_delims_expr(
1268 cx,
1269 &len.value,
1270 UnusedDelimsCtx::ArrayLenExpr,
1271 false,
1272 None,
1273 None,
1274 false,
1275 );
1276 }
1277 ast::TyKind::Paren(r) => {
1278 match &r.kind {
1279 ast::TyKind::TraitObject(..) => {}
1280 ast::TyKind::BareFn(b)
1281 if self.with_self_ty_parens && b.generic_params.len() > 0 => {}
1282 ast::TyKind::ImplTrait(_, bounds) if bounds.len() > 1 => {}
1283 _ => {
1284 let spans = if !ty.span.from_expansion() {
1285 r.span
1286 .find_ancestor_inside(ty.span)
1287 .map(|r| (ty.span.with_hi(r.lo()), ty.span.with_lo(r.hi())))
1288 } else {
1289 None
1290 };
1291 self.emit_unused_delims(cx, ty.span, spans, "type", (false, false), false);
1292 }
1293 }
1294 self.with_self_ty_parens = false;
1295 }
1296 _ => {}
1297 }
1298 }
1299
1300 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
1301 <Self as UnusedDelimLint>::check_item(self, cx, item)
1302 }
1303
1304 fn enter_where_predicate(&mut self, _: &EarlyContext<'_>, pred: &ast::WherePredicate) {
1305 use rustc_ast::{WhereBoundPredicate, WherePredicateKind};
1306 if let WherePredicateKind::BoundPredicate(WhereBoundPredicate {
1307 bounded_ty,
1308 bound_generic_params,
1309 ..
1310 }) = &pred.kind
1311 && let ast::TyKind::Paren(_) = &bounded_ty.kind
1312 && bound_generic_params.is_empty()
1313 {
1314 self.with_self_ty_parens = true;
1315 }
1316 }
1317
1318 fn exit_where_predicate(&mut self, _: &EarlyContext<'_>, _: &ast::WherePredicate) {
1319 assert!(!self.with_self_ty_parens);
1320 }
1321}
1322
1323declare_lint! {
1324 pub(super) UNUSED_BRACES,
1342 Warn,
1343 "unnecessary braces around an expression"
1344}
1345
1346declare_lint_pass!(UnusedBraces => [UNUSED_BRACES]);
1347
1348impl UnusedDelimLint for UnusedBraces {
1349 const DELIM_STR: &'static str = "braces";
1350
1351 const LINT_EXPR_IN_PATTERN_MATCHING_CTX: bool = false;
1352
1353 fn lint(&self) -> &'static Lint {
1354 UNUSED_BRACES
1355 }
1356
1357 fn check_unused_delims_expr(
1358 &self,
1359 cx: &EarlyContext<'_>,
1360 value: &ast::Expr,
1361 ctx: UnusedDelimsCtx,
1362 followed_by_block: bool,
1363 left_pos: Option<BytePos>,
1364 right_pos: Option<BytePos>,
1365 is_kw: bool,
1366 ) {
1367 match value.kind {
1368 ast::ExprKind::Block(ref inner, None)
1369 if inner.rules == ast::BlockCheckMode::Default =>
1370 {
1371 if let [stmt] = inner.stmts.as_slice() {
1396 if let ast::StmtKind::Expr(ref expr) = stmt.kind {
1397 if !Self::is_expr_delims_necessary(expr, ctx, followed_by_block)
1398 && (ctx != UnusedDelimsCtx::AnonConst
1399 || (matches!(expr.kind, ast::ExprKind::Lit(_))
1400 && !expr.span.from_expansion()))
1401 && !cx.sess().source_map().is_multiline(value.span)
1402 && value.attrs.is_empty()
1403 && !value.span.from_expansion()
1404 && !inner.span.from_expansion()
1405 {
1406 self.emit_unused_delims_expr(cx, value, ctx, left_pos, right_pos, is_kw)
1407 }
1408 }
1409 }
1410 }
1411 ast::ExprKind::Let(_, ref expr, _, _) => {
1412 self.check_unused_delims_expr(
1413 cx,
1414 expr,
1415 UnusedDelimsCtx::LetScrutineeExpr,
1416 followed_by_block,
1417 None,
1418 None,
1419 false,
1420 );
1421 }
1422 _ => {}
1423 }
1424 }
1425}
1426
1427impl EarlyLintPass for UnusedBraces {
1428 fn check_stmt(&mut self, cx: &EarlyContext<'_>, s: &ast::Stmt) {
1429 <Self as UnusedDelimLint>::check_stmt(self, cx, s)
1430 }
1431
1432 #[inline]
1433 fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &ast::Expr) {
1434 <Self as UnusedDelimLint>::check_expr(self, cx, e);
1435
1436 if let ExprKind::Repeat(_, ref anon_const) = e.kind {
1437 self.check_unused_delims_expr(
1438 cx,
1439 &anon_const.value,
1440 UnusedDelimsCtx::AnonConst,
1441 false,
1442 None,
1443 None,
1444 false,
1445 );
1446 }
1447 }
1448
1449 fn check_generic_arg(&mut self, cx: &EarlyContext<'_>, arg: &ast::GenericArg) {
1450 if let ast::GenericArg::Const(ct) = arg {
1451 self.check_unused_delims_expr(
1452 cx,
1453 &ct.value,
1454 UnusedDelimsCtx::AnonConst,
1455 false,
1456 None,
1457 None,
1458 false,
1459 );
1460 }
1461 }
1462
1463 fn check_variant(&mut self, cx: &EarlyContext<'_>, v: &ast::Variant) {
1464 if let Some(anon_const) = &v.disr_expr {
1465 self.check_unused_delims_expr(
1466 cx,
1467 &anon_const.value,
1468 UnusedDelimsCtx::AnonConst,
1469 false,
1470 None,
1471 None,
1472 false,
1473 );
1474 }
1475 }
1476
1477 fn check_ty(&mut self, cx: &EarlyContext<'_>, ty: &ast::Ty) {
1478 match ty.kind {
1479 ast::TyKind::Array(_, ref len) => {
1480 self.check_unused_delims_expr(
1481 cx,
1482 &len.value,
1483 UnusedDelimsCtx::ArrayLenExpr,
1484 false,
1485 None,
1486 None,
1487 false,
1488 );
1489 }
1490
1491 ast::TyKind::Typeof(ref anon_const) => {
1492 self.check_unused_delims_expr(
1493 cx,
1494 &anon_const.value,
1495 UnusedDelimsCtx::AnonConst,
1496 false,
1497 None,
1498 None,
1499 false,
1500 );
1501 }
1502
1503 _ => {}
1504 }
1505 }
1506
1507 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
1508 <Self as UnusedDelimLint>::check_item(self, cx, item)
1509 }
1510}
1511
1512declare_lint! {
1513 UNUSED_IMPORT_BRACES,
1538 Allow,
1539 "unnecessary braces around an imported item"
1540}
1541
1542declare_lint_pass!(UnusedImportBraces => [UNUSED_IMPORT_BRACES]);
1543
1544impl UnusedImportBraces {
1545 fn check_use_tree(&self, cx: &EarlyContext<'_>, use_tree: &ast::UseTree, item: &ast::Item) {
1546 if let ast::UseTreeKind::Nested { ref items, .. } = use_tree.kind {
1547 for (tree, _) in items {
1549 self.check_use_tree(cx, tree, item);
1550 }
1551
1552 let [(tree, _)] = items.as_slice() else { return };
1554
1555 let node_name = match tree.kind {
1557 ast::UseTreeKind::Simple(rename) => {
1558 let orig_ident = tree.prefix.segments.last().unwrap().ident;
1559 if orig_ident.name == kw::SelfLower {
1560 return;
1561 }
1562 rename.unwrap_or(orig_ident).name
1563 }
1564 ast::UseTreeKind::Glob => sym::asterisk,
1565 ast::UseTreeKind::Nested { .. } => return,
1566 };
1567
1568 cx.emit_span_lint(
1569 UNUSED_IMPORT_BRACES,
1570 item.span,
1571 UnusedImportBracesDiag { node: node_name },
1572 );
1573 }
1574 }
1575}
1576
1577impl EarlyLintPass for UnusedImportBraces {
1578 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
1579 if let ast::ItemKind::Use(ref use_tree) = item.kind {
1580 self.check_use_tree(cx, use_tree, item);
1581 }
1582 }
1583}
1584
1585declare_lint! {
1586 pub(super) UNUSED_ALLOCATION,
1605 Warn,
1606 "detects unnecessary allocations that can be eliminated"
1607}
1608
1609declare_lint_pass!(UnusedAllocation => [UNUSED_ALLOCATION]);
1610
1611impl<'tcx> LateLintPass<'tcx> for UnusedAllocation {
1612 fn check_expr(&mut self, cx: &LateContext<'_>, e: &hir::Expr<'_>) {
1613 match e.kind {
1614 hir::ExprKind::Call(path_expr, [_])
1615 if let hir::ExprKind::Path(qpath) = &path_expr.kind
1616 && let Some(did) = cx.qpath_res(qpath, path_expr.hir_id).opt_def_id()
1617 && cx.tcx.is_diagnostic_item(sym::box_new, did) => {}
1618 _ => return,
1619 }
1620
1621 for adj in cx.typeck_results().expr_adjustments(e) {
1622 if let adjustment::Adjust::Borrow(adjustment::AutoBorrow::Ref(m)) = adj.kind {
1623 match m {
1624 adjustment::AutoBorrowMutability::Not => {
1625 cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationDiag);
1626 }
1627 adjustment::AutoBorrowMutability::Mut { .. } => {
1628 cx.emit_span_lint(UNUSED_ALLOCATION, e.span, UnusedAllocationMutDiag);
1629 }
1630 };
1631 }
1632 }
1633 }
1634}