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_hir::attrs::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> {
178 match self {
179 Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
180 }
181 }
182}
183
184pub struct IfOrIfLet<'hir> {
186 pub cond: &'hir Expr<'hir>,
188 pub then: &'hir Expr<'hir>,
190 pub r#else: Option<&'hir Expr<'hir>>,
192}
193
194impl<'hir> IfOrIfLet<'hir> {
195 #[inline]
196 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
198 if let ExprKind::If(cond, then, r#else) = expr.kind {
199 Some(Self { cond, then, r#else })
200 } else {
201 None
202 }
203 }
204}
205
206#[derive(Debug, Copy, Clone)]
208pub struct Range<'a> {
209 pub ty: RangeTy,
211 pub start: Option<&'a Expr<'a>>,
213 pub end: Option<&'a Expr<'a>>,
215 pub span: Span,
216}
217
218impl<'a> Range<'a> {
219 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<Range<'a>> {
221 let span = expr.range_span()?;
222 let (ty, start, end) = match expr.kind {
223 ExprKind::Call(path, [arg1, arg2])
224 if let ExprKind::Path(qpath) = path.kind
225 && cx.tcx.qpath_is_lang_item(qpath, LangItem::RangeInclusiveNew) =>
226 {
227 (RangeTy::OpsInclusive, Some(arg1), Some(arg2))
228 },
229 ExprKind::Struct(&qpath, fields, StructTailExpr::None) => match (cx.tcx.qpath_lang_item(qpath)?, fields) {
230 (LangItem::RangeFull, []) => (RangeTy::OpsFull, None, None),
231 (LangItem::RangeFrom, [start]) if start.ident.name == sym::start => {
232 (RangeTy::OpsFrom, Some(start.expr), None)
233 },
234 (LangItem::RangeFromCopy, [start]) if start.ident.name == sym::start => {
235 (RangeTy::RangeFrom, Some(start.expr), None)
236 },
237 (LangItem::Range, [start, end] | [end, start])
238 if start.ident.name == sym::start && end.ident.name == sym::end =>
239 {
240 (RangeTy::OpsRange, Some(start.expr), Some(end.expr))
241 },
242 (LangItem::RangeCopy, [start, end] | [end, start])
243 if start.ident.name == sym::start && end.ident.name == sym::end =>
244 {
245 (RangeTy::RangeRange, Some(start.expr), Some(end.expr))
246 },
247 (LangItem::RangeInclusiveCopy, [start, last] | [last, start])
248 if start.ident.name == sym::start && last.ident.name == sym::last =>
249 {
250 (RangeTy::RangeInclusive, Some(start.expr), Some(last.expr))
251 },
252 (LangItem::RangeToInclusive, [end]) if end.ident.name == sym::end => {
253 (RangeTy::OpsToInclusive, None, Some(end.expr))
254 },
255 (LangItem::RangeToInclusiveCopy, [last]) if last.ident.name == sym::last => {
256 (RangeTy::RangeToInclusive, None, Some(last.expr))
257 },
258 (LangItem::RangeTo, [end]) if end.ident.name == sym::end => (RangeTy::OpsTo, None, Some(end.expr)),
259 _ => return None,
260 },
261 _ => return None,
262 };
263
264 Some(Range { ty, start, end, span })
265 }
266}
267
268#[derive(Debug, Copy, Clone, Eq, PartialEq)]
272pub enum RangeTy {
273 OpsFrom,
275 RangeFrom,
277
278 OpsFull,
280
281 OpsRange,
283 RangeRange,
285
286 OpsInclusive,
288 RangeInclusive,
290
291 OpsTo,
293
294 OpsToInclusive,
296 RangeToInclusive,
298}
299
300#[expect(clippy::match_same_arms, reason = "regularity over density")]
301impl RangeTy {
302 pub fn implements_into_iterator(self) -> bool {
305 match self {
306 RangeTy::OpsFrom => true,
307 RangeTy::RangeFrom => true,
308 RangeTy::OpsRange => true,
309 RangeTy::RangeRange => true,
310 RangeTy::OpsInclusive => true,
311 RangeTy::RangeInclusive => true,
312
313 RangeTy::OpsFull => false,
314 RangeTy::OpsTo => false,
315 RangeTy::OpsToInclusive => false,
316 RangeTy::RangeToInclusive => false,
317 }
318 }
319
320 pub fn implements_iterator(self) -> bool {
323 match self {
324 RangeTy::OpsFrom => true,
325 RangeTy::OpsRange => true,
326 RangeTy::OpsInclusive => true,
327
328 RangeTy::RangeFrom => false,
330 RangeTy::RangeRange => false,
331 RangeTy::RangeInclusive => false,
332
333 RangeTy::OpsFull => false,
335 RangeTy::OpsTo => false,
336 RangeTy::OpsToInclusive => false,
337 RangeTy::RangeToInclusive => false,
338 }
339 }
340
341 pub fn limits(self) -> ast::RangeLimits {
342 match self {
343 RangeTy::RangeFrom => ast::RangeLimits::HalfOpen,
344 RangeTy::OpsRange => ast::RangeLimits::HalfOpen,
345 RangeTy::RangeRange => ast::RangeLimits::HalfOpen,
346
347 RangeTy::OpsFrom => ast::RangeLimits::HalfOpen,
348 RangeTy::OpsTo => ast::RangeLimits::HalfOpen,
349 RangeTy::OpsFull => ast::RangeLimits::HalfOpen,
350
351 RangeTy::OpsInclusive => ast::RangeLimits::Closed,
352 RangeTy::RangeInclusive => ast::RangeLimits::Closed,
353 RangeTy::OpsToInclusive => ast::RangeLimits::Closed,
354 RangeTy::RangeToInclusive => ast::RangeLimits::Closed,
355 }
356 }
357}
358
359pub enum VecArgs<'a> {
361 Repeat(&'a Expr<'a>, &'a Expr<'a>),
363 Vec(&'a [Expr<'a>]),
365}
366
367impl<'a> VecArgs<'a> {
368 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<VecArgs<'a>> {
371 if let ExprKind::Call(fun, args) = expr.kind
372 && let ExprKind::Path(ref qpath) = fun.kind
373 && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
374 && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
375 && matches!(
376 name,
377 sym::vec_from_elem | sym::box_assume_init_into_vec_unsafe | sym::vec_new
378 )
379 && is_expn_of(fun.span, sym::vec).is_some()
381 {
382 return match (name, args) {
383 (sym::vec_from_elem, [elem, size]) => {
384 Some(VecArgs::Repeat(elem, size))
386 },
387 (sym::box_assume_init_into_vec_unsafe, [write_box_via_move])
388 if let ExprKind::Call(_, [_box, elems]) = write_box_via_move.kind
389 && let ExprKind::Array(elems) = elems.kind =>
390 {
391 Some(VecArgs::Vec(elems))
393 },
394 (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
395 _ => None,
396 };
397 }
398
399 None
400 }
401}
402
403pub struct While<'hir> {
405 pub condition: &'hir Expr<'hir>,
407 pub body: &'hir Expr<'hir>,
409 pub span: Span,
411 pub label: Option<ast::Label>,
412}
413
414impl<'hir> While<'hir> {
415 #[inline]
416 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
418 if let ExprKind::Loop(
419 Block {
420 expr:
421 Some(Expr {
422 kind: ExprKind::If(condition, body, _),
423 ..
424 }),
425 ..
426 },
427 label,
428 LoopSource::While,
429 span,
430 ) = expr.kind
431 && !has_let_expr(condition)
432 {
433 return Some(Self {
434 condition,
435 body,
436 span,
437 label,
438 });
439 }
440 None
441 }
442}
443
444pub struct WhileLet<'hir> {
446 pub let_pat: &'hir Pat<'hir>,
448 pub let_expr: &'hir Expr<'hir>,
450 pub if_then: &'hir Expr<'hir>,
452 pub label: Option<ast::Label>,
453 pub let_span: Span,
456}
457
458impl<'hir> WhileLet<'hir> {
459 #[inline]
460 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
462 if let ExprKind::Loop(
463 &Block {
464 expr:
465 Some(&Expr {
466 kind:
467 ExprKind::If(
468 &Expr {
469 kind:
470 ExprKind::Let(&hir::LetExpr {
471 pat: let_pat,
472 init: let_expr,
473 span: let_span,
474 ..
475 }),
476 ..
477 },
478 if_then,
479 _,
480 ),
481 ..
482 }),
483 ..
484 },
485 label,
486 LoopSource::While,
487 _,
488 ) = expr.kind
489 {
490 return Some(Self {
491 let_pat,
492 let_expr,
493 if_then,
494 label,
495 let_span,
496 });
497 }
498 None
499 }
500}
501
502pub struct CompoundAssignment<'hir> {
505 pub assignees: Vec<&'hir Expr<'hir>>,
507 pub init: &'hir Expr<'hir>,
509}
510
511impl<'hir> CompoundAssignment<'hir> {
512 #[inline]
514 pub fn hir(expr: &'hir Expr<'_>) -> Option<Self> {
515 if let ExprKind::Block(
520 Block {
521 stmts: [assign, rest @ ..],
522 expr: None,
523 ..
524 },
525 None,
526 ) = expr.kind
527 && let hir::StmtKind::Let(LetStmt {
528 init: Some(init),
529 source: LocalSource::AssignDesugar,
530 ..
531 }) = assign.kind
532 {
533 let mut assignees = Vec::with_capacity(rest.len());
534 for stmt in rest {
535 if let hir::StmtKind::Expr(expr) = stmt.kind
536 && let ExprKind::Assign(target, _, _) = expr.kind
537 {
538 assignees.push(target);
539 } else {
540 return None;
541 }
542 }
543 Some(CompoundAssignment { assignees, init })
544 } else {
545 None
546 }
547 }
548}
549
550#[must_use]
552pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
553 match op {
554 hir::BinOpKind::Eq => ast::BinOpKind::Eq,
555 hir::BinOpKind::Ge => ast::BinOpKind::Ge,
556 hir::BinOpKind::Gt => ast::BinOpKind::Gt,
557 hir::BinOpKind::Le => ast::BinOpKind::Le,
558 hir::BinOpKind::Lt => ast::BinOpKind::Lt,
559 hir::BinOpKind::Ne => ast::BinOpKind::Ne,
560 hir::BinOpKind::Or => ast::BinOpKind::Or,
561 hir::BinOpKind::Add => ast::BinOpKind::Add,
562 hir::BinOpKind::And => ast::BinOpKind::And,
563 hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
564 hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
565 hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
566 hir::BinOpKind::Div => ast::BinOpKind::Div,
567 hir::BinOpKind::Mul => ast::BinOpKind::Mul,
568 hir::BinOpKind::Rem => ast::BinOpKind::Rem,
569 hir::BinOpKind::Shl => ast::BinOpKind::Shl,
570 hir::BinOpKind::Shr => ast::BinOpKind::Shr,
571 hir::BinOpKind::Sub => ast::BinOpKind::Sub,
572 }
573}
574
575#[derive(Clone, Copy)]
577pub enum VecInitKind {
578 New,
580 Default,
582 WithConstCapacity(u128),
584 WithExprCapacity(HirId),
586}
587
588pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
590 if let ExprKind::Call(func, args) = expr.kind {
591 match func.kind {
592 ExprKind::Path(QPath::TypeRelative(ty, name))
593 if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) =>
594 {
595 if name.ident.name == sym::new {
596 return Some(VecInitKind::New);
597 } else if name.ident.name == symbol::kw::Default {
598 return Some(VecInitKind::Default);
599 } else if name.ident.name == sym::with_capacity {
600 let arg = args.first()?;
601 return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
602 Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
603 _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
604 };
605 }
606 },
607 ExprKind::Path(QPath::Resolved(_, path))
608 if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
609 && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) =>
610 {
611 return Some(VecInitKind::Default);
612 },
613 _ => (),
614 }
615 }
616 None
617}
618
619pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
622 match &cond.kind {
623 ExprKind::Let(_) => true,
624 ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
625 _ => false,
626 }
627}