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