1#![deny(clippy::missing_docs_in_private_items)]
4
5use crate::consts::{ConstEvalCtxt, Constant};
6use crate::res::MaybeDef as _;
7use crate::{is_expn_of, sym};
8
9use rustc_ast::ast;
10use rustc_attr_ir::lang_items::LangItem;
11use rustc_hir::{
12 self as hir, Arm, Block, Expr, ExprKind, HirId, LetStmt, LocalSource, LoopSource, MatchSource, Node, Pat, QPath,
13 StructTailExpr,
14};
15use rustc_lint::LateContext;
16use rustc_span::{Span, symbol};
17
18#[derive(Debug)]
21pub struct ForLoop<'tcx> {
22 pub pat: &'tcx Pat<'tcx>,
24 pub arg: &'tcx Expr<'tcx>,
26 pub body: &'tcx Expr<'tcx>,
28 pub loop_id: HirId,
30 pub span: Span,
32 pub label: Option<ast::Label>,
34}
35
36impl<'tcx> ForLoop<'tcx> {
37 pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
39 if let ExprKind::DropTemps(e) = expr.kind
40 && let ExprKind::Match(iterexpr, [arm], MatchSource::ForLoopDesugar) = e.kind
41 && let ExprKind::Call(_, [arg]) = iterexpr.kind
42 && let ExprKind::Loop(block, label, ..) = arm.body.kind
43 && let [stmt] = block.stmts
44 && let hir::StmtKind::Expr(e) = stmt.kind
45 && let ExprKind::Match(_, [_, some_arm], _) = e.kind
46 && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
47 {
48 return Some(Self {
49 pat: field.pat,
50 arg,
51 body: some_arm.body,
52 loop_id: arm.body.hir_id,
53 span: expr.span.ctxt().outer_expn_data().call_site,
54 label,
55 });
56 }
57 None
58 }
59}
60
61pub struct If<'hir> {
63 pub cond: &'hir Expr<'hir>,
65 pub then: &'hir Expr<'hir>,
67 pub r#else: Option<&'hir Expr<'hir>>,
69}
70
71impl<'hir> If<'hir> {
72 #[inline]
73 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
75 if let ExprKind::If(cond, then, r#else) = expr.kind
76 && !has_let_expr(cond)
77 {
78 Some(Self { cond, then, r#else })
79 } else {
80 None
81 }
82 }
83}
84
85pub struct IfLet<'hir> {
87 pub let_pat: &'hir Pat<'hir>,
89 pub let_expr: &'hir Expr<'hir>,
91 pub if_then: &'hir Expr<'hir>,
93 pub if_else: Option<&'hir Expr<'hir>>,
95 pub let_span: Span,
98}
99
100impl<'hir> IfLet<'hir> {
101 pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
103 if let ExprKind::If(
104 &Expr {
105 kind:
106 ExprKind::Let(&hir::LetExpr {
107 pat: let_pat,
108 init: let_expr,
109 span: let_span,
110 ..
111 }),
112 ..
113 },
114 if_then,
115 if_else,
116 ) = expr.kind
117 {
118 let mut iter = cx.tcx.hir_parent_iter(expr.hir_id);
119 if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next()
120 && let Some((
121 _,
122 Node::Expr(Expr {
123 kind: ExprKind::Loop(_, _, LoopSource::While, _),
124 ..
125 }),
126 )) = iter.next()
127 {
128 return None;
130 }
131 return Some(Self {
132 let_pat,
133 let_expr,
134 if_then,
135 if_else,
136 let_span,
137 });
138 }
139 None
140 }
141}
142
143#[derive(Debug)]
145pub enum IfLetOrMatch<'hir> {
146 Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
148 IfLet(
150 &'hir Expr<'hir>,
151 &'hir Pat<'hir>,
152 &'hir Expr<'hir>,
153 Option<&'hir Expr<'hir>>,
154 Span,
157 ),
158}
159
160impl<'hir> IfLetOrMatch<'hir> {
161 pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
163 match expr.kind {
164 ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
165 _ => IfLet::hir(cx, expr).map(
166 |IfLet {
167 let_expr,
168 let_pat,
169 if_then,
170 if_else,
171 let_span,
172 }| { Self::IfLet(let_expr, let_pat, if_then, if_else, let_span) },
173 ),
174 }
175 }
176
177 pub fn scrutinee(&self) -> &'hir Expr<'hir> {
179 match self {
180 Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
181 }
182 }
183}
184
185pub struct IfOrIfLet<'hir> {
187 pub cond: &'hir Expr<'hir>,
189 pub then: &'hir Expr<'hir>,
191 pub r#else: Option<&'hir Expr<'hir>>,
193}
194
195impl<'hir> IfOrIfLet<'hir> {
196 #[inline]
197 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
199 if let ExprKind::If(cond, then, r#else) = expr.kind {
200 Some(Self { cond, then, r#else })
201 } else {
202 None
203 }
204 }
205}
206
207#[derive(Debug, Copy, Clone)]
209pub struct Range<'a> {
210 pub ty: RangeTy,
212 pub start: Option<&'a Expr<'a>>,
214 pub end: Option<&'a Expr<'a>>,
216 pub span: Span,
218}
219
220impl<'a> Range<'a> {
221 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<Range<'a>> {
223 let span = expr.range_span()?;
224 let (ty, start, end) = match expr.kind {
225 ExprKind::Call(path, [arg1, arg2])
226 if let ExprKind::Path(qpath) = path.kind
227 && cx.tcx.qpath_is_lang_item(qpath, LangItem::RangeInclusiveNew) =>
228 {
229 (RangeTy::OpsInclusive, Some(arg1), Some(arg2))
230 },
231 ExprKind::Struct(&qpath, fields, StructTailExpr::None) => match (cx.tcx.qpath_lang_item(qpath)?, fields) {
232 (LangItem::RangeFull, []) => (RangeTy::OpsFull, None, None),
233 (LangItem::RangeFrom, [start]) if start.ident.name == sym::start => {
234 (RangeTy::OpsFrom, Some(start.expr), None)
235 },
236 (LangItem::RangeFromCopy, [start]) if start.ident.name == sym::start => {
237 (RangeTy::RangeFrom, Some(start.expr), None)
238 },
239 (LangItem::Range, [start, end] | [end, start])
240 if start.ident.name == sym::start && end.ident.name == sym::end =>
241 {
242 (RangeTy::OpsRange, Some(start.expr), Some(end.expr))
243 },
244 (LangItem::RangeCopy, [start, end] | [end, start])
245 if start.ident.name == sym::start && end.ident.name == sym::end =>
246 {
247 (RangeTy::RangeRange, Some(start.expr), Some(end.expr))
248 },
249 (LangItem::RangeInclusiveCopy, [start, last] | [last, start])
250 if start.ident.name == sym::start && last.ident.name == sym::last =>
251 {
252 (RangeTy::RangeInclusive, Some(start.expr), Some(last.expr))
253 },
254 (LangItem::RangeToInclusive, [end]) if end.ident.name == sym::end => {
255 (RangeTy::OpsToInclusive, None, Some(end.expr))
256 },
257 (LangItem::RangeToInclusiveCopy, [last]) if last.ident.name == sym::last => {
258 (RangeTy::RangeToInclusive, None, Some(last.expr))
259 },
260 (LangItem::RangeTo, [end]) if end.ident.name == sym::end => (RangeTy::OpsTo, None, Some(end.expr)),
261 _ => return None,
262 },
263 _ => return None,
264 };
265
266 Some(Range { ty, start, end, span })
267 }
268}
269
270#[derive(Debug, Copy, Clone, Eq, PartialEq)]
274pub enum RangeTy {
275 OpsFrom,
277 RangeFrom,
279
280 OpsFull,
282
283 OpsRange,
285 RangeRange,
287
288 OpsInclusive,
290 RangeInclusive,
292
293 OpsTo,
295
296 OpsToInclusive,
298 RangeToInclusive,
300}
301
302#[expect(clippy::match_same_arms, reason = "regularity over density")]
303impl RangeTy {
304 pub fn implements_into_iterator(self) -> bool {
307 match self {
308 RangeTy::OpsFrom => true,
309 RangeTy::RangeFrom => true,
310 RangeTy::OpsRange => true,
311 RangeTy::RangeRange => true,
312 RangeTy::OpsInclusive => true,
313 RangeTy::RangeInclusive => true,
314
315 RangeTy::OpsFull => false,
316 RangeTy::OpsTo => false,
317 RangeTy::OpsToInclusive => false,
318 RangeTy::RangeToInclusive => false,
319 }
320 }
321
322 pub fn implements_iterator(self) -> bool {
325 match self {
326 RangeTy::OpsFrom => true,
327 RangeTy::OpsRange => true,
328 RangeTy::OpsInclusive => true,
329
330 RangeTy::RangeFrom => false,
332 RangeTy::RangeRange => false,
333 RangeTy::RangeInclusive => false,
334
335 RangeTy::OpsFull => false,
337 RangeTy::OpsTo => false,
338 RangeTy::OpsToInclusive => false,
339 RangeTy::RangeToInclusive => false,
340 }
341 }
342
343 pub fn limits(self) -> ast::RangeLimits {
345 match self {
346 RangeTy::RangeFrom => ast::RangeLimits::HalfOpen,
347 RangeTy::OpsRange => ast::RangeLimits::HalfOpen,
348 RangeTy::RangeRange => ast::RangeLimits::HalfOpen,
349
350 RangeTy::OpsFrom => ast::RangeLimits::HalfOpen,
351 RangeTy::OpsTo => ast::RangeLimits::HalfOpen,
352 RangeTy::OpsFull => ast::RangeLimits::HalfOpen,
353
354 RangeTy::OpsInclusive => ast::RangeLimits::Closed,
355 RangeTy::RangeInclusive => ast::RangeLimits::Closed,
356 RangeTy::OpsToInclusive => ast::RangeLimits::Closed,
357 RangeTy::RangeToInclusive => ast::RangeLimits::Closed,
358 }
359 }
360}
361
362pub enum VecArgs<'a> {
364 Repeat(&'a Expr<'a>, &'a Expr<'a>),
366 Vec(&'a [Expr<'a>]),
368}
369
370impl<'a> VecArgs<'a> {
371 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<VecArgs<'a>> {
374 if let ExprKind::Call(fun, args) = expr.kind
375 && let ExprKind::Path(ref qpath) = fun.kind
376 && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
377 && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
378 && matches!(
379 name,
380 sym::vec_from_elem | sym::box_assume_init_into_vec_unsafe | sym::vec_new
381 )
382 && is_expn_of(fun.span, sym::vec).is_some()
384 {
385 return match (name, args) {
386 (sym::vec_from_elem, [elem, size]) => {
387 Some(VecArgs::Repeat(elem, size))
389 },
390 (sym::box_assume_init_into_vec_unsafe, [write_box_via_move])
391 if let ExprKind::Call(_, [_box, elems]) = write_box_via_move.kind
392 && let ExprKind::Array(elems) = elems.kind =>
393 {
394 Some(VecArgs::Vec(elems))
396 },
397 (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
398 _ => None,
399 };
400 }
401
402 None
403 }
404}
405
406pub struct While<'hir> {
408 pub condition: &'hir Expr<'hir>,
410 pub body: &'hir Expr<'hir>,
412 pub span: Span,
414 pub label: Option<ast::Label>,
416}
417
418impl<'hir> While<'hir> {
419 #[inline]
420 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
422 if let ExprKind::Loop(
423 Block {
424 expr:
425 Some(Expr {
426 kind: ExprKind::If(condition, body, _),
427 ..
428 }),
429 ..
430 },
431 label,
432 LoopSource::While,
433 span,
434 ) = expr.kind
435 && !has_let_expr(condition)
436 {
437 return Some(Self {
438 condition,
439 body,
440 span,
441 label,
442 });
443 }
444 None
445 }
446}
447
448pub struct WhileLet<'hir> {
450 pub let_pat: &'hir Pat<'hir>,
452 pub let_expr: &'hir Expr<'hir>,
454 pub if_then: &'hir Expr<'hir>,
456 pub label: Option<ast::Label>,
458 pub let_span: Span,
461}
462
463impl<'hir> WhileLet<'hir> {
464 #[inline]
465 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
467 if let ExprKind::Loop(
468 &Block {
469 expr:
470 Some(&Expr {
471 kind:
472 ExprKind::If(
473 &Expr {
474 kind:
475 ExprKind::Let(&hir::LetExpr {
476 pat: let_pat,
477 init: let_expr,
478 span: let_span,
479 ..
480 }),
481 ..
482 },
483 if_then,
484 _,
485 ),
486 ..
487 }),
488 ..
489 },
490 label,
491 LoopSource::While,
492 _,
493 ) = expr.kind
494 {
495 return Some(Self {
496 let_pat,
497 let_expr,
498 if_then,
499 label,
500 let_span,
501 });
502 }
503 None
504 }
505}
506
507pub struct CompoundAssignment<'hir> {
510 pub assignees: Vec<&'hir Expr<'hir>>,
512 pub init: &'hir Expr<'hir>,
514}
515
516impl<'hir> CompoundAssignment<'hir> {
517 #[inline]
519 pub fn hir(expr: &'hir Expr<'_>) -> Option<Self> {
520 if let ExprKind::Block(
525 Block {
526 stmts: [assign, rest @ ..],
527 expr: None,
528 ..
529 },
530 None,
531 ) = expr.kind
532 && let hir::StmtKind::Let(LetStmt {
533 init: Some(init),
534 source: LocalSource::AssignDesugar,
535 ..
536 }) = assign.kind
537 {
538 let mut assignees = Vec::with_capacity(rest.len());
539 for stmt in rest {
540 if let hir::StmtKind::Expr(expr) = stmt.kind
541 && let ExprKind::Assign(target, _, _) = expr.kind
542 {
543 assignees.push(target);
544 } else {
545 return None;
546 }
547 }
548 Some(CompoundAssignment { assignees, init })
549 } else {
550 None
551 }
552 }
553}
554
555#[must_use]
557pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
558 match op {
559 hir::BinOpKind::Eq => ast::BinOpKind::Eq,
560 hir::BinOpKind::Ge => ast::BinOpKind::Ge,
561 hir::BinOpKind::Gt => ast::BinOpKind::Gt,
562 hir::BinOpKind::Le => ast::BinOpKind::Le,
563 hir::BinOpKind::Lt => ast::BinOpKind::Lt,
564 hir::BinOpKind::Ne => ast::BinOpKind::Ne,
565 hir::BinOpKind::Or => ast::BinOpKind::Or,
566 hir::BinOpKind::Add => ast::BinOpKind::Add,
567 hir::BinOpKind::And => ast::BinOpKind::And,
568 hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
569 hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
570 hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
571 hir::BinOpKind::Div => ast::BinOpKind::Div,
572 hir::BinOpKind::Mul => ast::BinOpKind::Mul,
573 hir::BinOpKind::Rem => ast::BinOpKind::Rem,
574 hir::BinOpKind::Shl => ast::BinOpKind::Shl,
575 hir::BinOpKind::Shr => ast::BinOpKind::Shr,
576 hir::BinOpKind::Sub => ast::BinOpKind::Sub,
577 }
578}
579
580#[derive(Clone, Copy)]
582pub enum VecInitKind {
583 New,
585 Default,
587 WithConstCapacity(u128),
589 WithExprCapacity(HirId),
591}
592
593pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
595 if let ExprKind::Call(func, args) = expr.kind {
596 match func.kind {
597 ExprKind::Path(QPath::TypeRelative(ty, name))
598 if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) =>
599 {
600 if name.ident.name == sym::new {
601 return Some(VecInitKind::New);
602 } else if name.ident.name == symbol::kw::Default {
603 return Some(VecInitKind::Default);
604 } else if name.ident.name == sym::with_capacity {
605 let arg = args.first()?;
606 return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
607 Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
608 _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
609 };
610 }
611 },
612 ExprKind::Path(QPath::Resolved(_, path))
613 if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
614 && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) =>
615 {
616 return Some(VecInitKind::Default);
617 },
618 _ => (),
619 }
620 }
621 None
622}
623
624pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
627 match &cond.kind {
628 ExprKind::Let(_) => true,
629 ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
630 _ => false,
631 }
632}