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::{
11 self as hir, Arm, Block, Expr, ExprKind, HirId, LetStmt, LocalSource, LoopSource, MatchSource, Node, Pat, QPath,
12 StructTailExpr,
13};
14use rustc_lint::LateContext;
15use rustc_span::{Span, symbol};
16
17#[derive(Debug)]
20pub struct ForLoop<'tcx> {
21 pub pat: &'tcx Pat<'tcx>,
23 pub arg: &'tcx Expr<'tcx>,
25 pub body: &'tcx Expr<'tcx>,
27 pub loop_id: HirId,
29 pub span: Span,
31 pub label: Option<ast::Label>,
33}
34
35impl<'tcx> ForLoop<'tcx> {
36 pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
38 if let ExprKind::DropTemps(e) = expr.kind
39 && let ExprKind::Match(iterexpr, [arm], MatchSource::ForLoopDesugar) = e.kind
40 && let ExprKind::Call(_, [arg]) = iterexpr.kind
41 && let ExprKind::Loop(block, label, ..) = arm.body.kind
42 && let [stmt] = block.stmts
43 && let hir::StmtKind::Expr(e) = stmt.kind
44 && let ExprKind::Match(_, [_, some_arm], _) = e.kind
45 && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
46 {
47 return Some(Self {
48 pat: field.pat,
49 arg,
50 body: some_arm.body,
51 loop_id: arm.body.hir_id,
52 span: expr.span.ctxt().outer_expn_data().call_site,
53 label,
54 });
55 }
56 None
57 }
58}
59
60pub struct If<'hir> {
62 pub cond: &'hir Expr<'hir>,
64 pub then: &'hir Expr<'hir>,
66 pub r#else: Option<&'hir Expr<'hir>>,
68}
69
70impl<'hir> If<'hir> {
71 #[inline]
72 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
74 if let ExprKind::If(cond, then, r#else) = expr.kind
75 && !has_let_expr(cond)
76 {
77 Some(Self { cond, then, r#else })
78 } else {
79 None
80 }
81 }
82}
83
84pub struct IfLet<'hir> {
86 pub let_pat: &'hir Pat<'hir>,
88 pub let_expr: &'hir Expr<'hir>,
90 pub if_then: &'hir Expr<'hir>,
92 pub if_else: Option<&'hir Expr<'hir>>,
94 pub let_span: Span,
97}
98
99impl<'hir> IfLet<'hir> {
100 pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
102 if let ExprKind::If(
103 &Expr {
104 kind:
105 ExprKind::Let(&hir::LetExpr {
106 pat: let_pat,
107 init: let_expr,
108 span: let_span,
109 ..
110 }),
111 ..
112 },
113 if_then,
114 if_else,
115 ) = expr.kind
116 {
117 let mut iter = cx.tcx.hir_parent_iter(expr.hir_id);
118 if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next()
119 && let Some((
120 _,
121 Node::Expr(Expr {
122 kind: ExprKind::Loop(_, _, LoopSource::While, _),
123 ..
124 }),
125 )) = iter.next()
126 {
127 return None;
129 }
130 return Some(Self {
131 let_pat,
132 let_expr,
133 if_then,
134 if_else,
135 let_span,
136 });
137 }
138 None
139 }
140}
141
142#[derive(Debug)]
144pub enum IfLetOrMatch<'hir> {
145 Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
147 IfLet(
149 &'hir Expr<'hir>,
150 &'hir Pat<'hir>,
151 &'hir Expr<'hir>,
152 Option<&'hir Expr<'hir>>,
153 Span,
156 ),
157}
158
159impl<'hir> IfLetOrMatch<'hir> {
160 pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
162 match expr.kind {
163 ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
164 _ => IfLet::hir(cx, expr).map(
165 |IfLet {
166 let_expr,
167 let_pat,
168 if_then,
169 if_else,
170 let_span,
171 }| { Self::IfLet(let_expr, let_pat, if_then, if_else, let_span) },
172 ),
173 }
174 }
175
176 pub fn scrutinee(&self) -> &'hir Expr<'hir> {
177 match self {
178 Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
179 }
180 }
181}
182
183pub struct IfOrIfLet<'hir> {
185 pub cond: &'hir Expr<'hir>,
187 pub then: &'hir Expr<'hir>,
189 pub r#else: Option<&'hir Expr<'hir>>,
191}
192
193impl<'hir> IfOrIfLet<'hir> {
194 #[inline]
195 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
197 if let ExprKind::If(cond, then, r#else) = expr.kind {
198 Some(Self { cond, then, r#else })
199 } else {
200 None
201 }
202 }
203}
204
205#[derive(Debug, Copy, Clone)]
207pub struct Range<'a> {
208 pub ty: RangeTy,
210 pub start: Option<&'a Expr<'a>>,
212 pub end: Option<&'a Expr<'a>>,
214 pub span: Span,
215}
216
217impl<'a> Range<'a> {
218 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<Range<'a>> {
220 let span = expr.range_span()?;
221 let (ty, start, end) = match expr.kind {
222 ExprKind::Call(path, [arg1, arg2])
223 if let ExprKind::Path(qpath) = path.kind
224 && cx.tcx.qpath_is_lang_item(qpath, hir::LangItem::RangeInclusiveNew) =>
225 {
226 (RangeTy::OpsInclusive, Some(arg1), Some(arg2))
227 },
228 ExprKind::Struct(&qpath, fields, StructTailExpr::None) => match (cx.tcx.qpath_lang_item(qpath)?, fields) {
229 (hir::LangItem::RangeFull, []) => (RangeTy::OpsFull, None, None),
230 (hir::LangItem::RangeFrom, [start]) if start.ident.name == sym::start => {
231 (RangeTy::OpsFrom, Some(start.expr), None)
232 },
233 (hir::LangItem::RangeFromCopy, [start]) if start.ident.name == sym::start => {
234 (RangeTy::RangeFrom, Some(start.expr), None)
235 },
236 (hir::LangItem::Range, [start, end] | [end, start])
237 if start.ident.name == sym::start && end.ident.name == sym::end =>
238 {
239 (RangeTy::OpsRange, Some(start.expr), Some(end.expr))
240 },
241 (hir::LangItem::RangeCopy, [start, end] | [end, start])
242 if start.ident.name == sym::start && end.ident.name == sym::end =>
243 {
244 (RangeTy::RangeRange, Some(start.expr), Some(end.expr))
245 },
246 (hir::LangItem::RangeInclusiveCopy, [start, last] | [last, start])
247 if start.ident.name == sym::start && last.ident.name == sym::last =>
248 {
249 (RangeTy::RangeInclusive, Some(start.expr), Some(last.expr))
250 },
251 (hir::LangItem::RangeToInclusive, [end]) if end.ident.name == sym::end => {
252 (RangeTy::OpsToInclusive, None, Some(end.expr))
253 },
254 (hir::LangItem::RangeToInclusiveCopy, [last]) if last.ident.name == sym::last => {
255 (RangeTy::RangeToInclusive, None, Some(last.expr))
256 },
257 (hir::LangItem::RangeTo, [end]) if end.ident.name == sym::end => (RangeTy::OpsTo, None, Some(end.expr)),
258 _ => return None,
259 },
260 _ => return None,
261 };
262
263 Some(Range { ty, start, end, span })
264 }
265}
266
267#[derive(Debug, Copy, Clone, Eq, PartialEq)]
271pub enum RangeTy {
272 OpsFrom,
274 RangeFrom,
276
277 OpsFull,
279
280 OpsRange,
282 RangeRange,
284
285 OpsInclusive,
287 RangeInclusive,
289
290 OpsTo,
292
293 OpsToInclusive,
295 RangeToInclusive,
297}
298
299#[expect(clippy::match_same_arms, reason = "regularity over density")]
300impl RangeTy {
301 pub fn implements_into_iterator(self) -> bool {
304 match self {
305 RangeTy::OpsFrom => true,
306 RangeTy::RangeFrom => true,
307 RangeTy::OpsRange => true,
308 RangeTy::RangeRange => true,
309 RangeTy::OpsInclusive => true,
310 RangeTy::RangeInclusive => true,
311
312 RangeTy::OpsFull => false,
313 RangeTy::OpsTo => false,
314 RangeTy::OpsToInclusive => false,
315 RangeTy::RangeToInclusive => false,
316 }
317 }
318
319 pub fn implements_iterator(self) -> bool {
322 match self {
323 RangeTy::OpsFrom => true,
324 RangeTy::OpsRange => true,
325 RangeTy::OpsInclusive => true,
326
327 RangeTy::RangeFrom => false,
329 RangeTy::RangeRange => false,
330 RangeTy::RangeInclusive => false,
331
332 RangeTy::OpsFull => false,
334 RangeTy::OpsTo => false,
335 RangeTy::OpsToInclusive => false,
336 RangeTy::RangeToInclusive => false,
337 }
338 }
339
340 pub fn limits(self) -> ast::RangeLimits {
341 match self {
342 RangeTy::RangeFrom => ast::RangeLimits::HalfOpen,
343 RangeTy::OpsRange => ast::RangeLimits::HalfOpen,
344 RangeTy::RangeRange => ast::RangeLimits::HalfOpen,
345
346 RangeTy::OpsFrom => ast::RangeLimits::HalfOpen,
347 RangeTy::OpsTo => ast::RangeLimits::HalfOpen,
348 RangeTy::OpsFull => ast::RangeLimits::HalfOpen,
349
350 RangeTy::OpsInclusive => ast::RangeLimits::Closed,
351 RangeTy::RangeInclusive => ast::RangeLimits::Closed,
352 RangeTy::OpsToInclusive => ast::RangeLimits::Closed,
353 RangeTy::RangeToInclusive => ast::RangeLimits::Closed,
354 }
355 }
356}
357
358pub enum VecArgs<'a> {
360 Repeat(&'a Expr<'a>, &'a Expr<'a>),
362 Vec(&'a [Expr<'a>]),
364}
365
366impl<'a> VecArgs<'a> {
367 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<VecArgs<'a>> {
370 if let ExprKind::Call(fun, args) = expr.kind
371 && let ExprKind::Path(ref qpath) = fun.kind
372 && is_expn_of(fun.span, sym::vec).is_some()
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 {
376 return match (name, args) {
377 (sym::vec_from_elem, [elem, size]) => {
378 Some(VecArgs::Repeat(elem, size))
380 },
381 (sym::box_assume_init_into_vec_unsafe, [write_box_via_move])
382 if let ExprKind::Call(_, [_box, elems]) = write_box_via_move.kind
383 && let ExprKind::Array(elems) = elems.kind =>
384 {
385 Some(VecArgs::Vec(elems))
387 },
388 (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
389 _ => None,
390 };
391 }
392
393 None
394 }
395}
396
397pub struct While<'hir> {
399 pub condition: &'hir Expr<'hir>,
401 pub body: &'hir Expr<'hir>,
403 pub span: Span,
405 pub label: Option<ast::Label>,
406}
407
408impl<'hir> While<'hir> {
409 #[inline]
410 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
412 if let ExprKind::Loop(
413 Block {
414 expr:
415 Some(Expr {
416 kind: ExprKind::If(condition, body, _),
417 ..
418 }),
419 ..
420 },
421 label,
422 LoopSource::While,
423 span,
424 ) = expr.kind
425 && !has_let_expr(condition)
426 {
427 return Some(Self {
428 condition,
429 body,
430 span,
431 label,
432 });
433 }
434 None
435 }
436}
437
438pub struct WhileLet<'hir> {
440 pub let_pat: &'hir Pat<'hir>,
442 pub let_expr: &'hir Expr<'hir>,
444 pub if_then: &'hir Expr<'hir>,
446 pub label: Option<ast::Label>,
447 pub let_span: Span,
450}
451
452impl<'hir> WhileLet<'hir> {
453 #[inline]
454 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
456 if let ExprKind::Loop(
457 &Block {
458 expr:
459 Some(&Expr {
460 kind:
461 ExprKind::If(
462 &Expr {
463 kind:
464 ExprKind::Let(&hir::LetExpr {
465 pat: let_pat,
466 init: let_expr,
467 span: let_span,
468 ..
469 }),
470 ..
471 },
472 if_then,
473 _,
474 ),
475 ..
476 }),
477 ..
478 },
479 label,
480 LoopSource::While,
481 _,
482 ) = expr.kind
483 {
484 return Some(Self {
485 let_pat,
486 let_expr,
487 if_then,
488 label,
489 let_span,
490 });
491 }
492 None
493 }
494}
495
496pub struct CompoundAssignment<'hir> {
499 pub assignees: Vec<&'hir Expr<'hir>>,
501 pub init: &'hir Expr<'hir>,
503}
504
505impl<'hir> CompoundAssignment<'hir> {
506 #[inline]
508 pub fn hir(expr: &'hir Expr<'_>) -> Option<Self> {
509 if let ExprKind::Block(
514 Block {
515 stmts: [assign, rest @ ..],
516 expr: None,
517 ..
518 },
519 None,
520 ) = expr.kind
521 && let hir::StmtKind::Let(LetStmt {
522 init: Some(init),
523 source: LocalSource::AssignDesugar,
524 ..
525 }) = assign.kind
526 {
527 let mut assignees = Vec::with_capacity(rest.len());
528 for stmt in rest {
529 if let hir::StmtKind::Expr(expr) = stmt.kind
530 && let ExprKind::Assign(target, _, _) = expr.kind
531 {
532 assignees.push(target);
533 } else {
534 return None;
535 }
536 }
537 Some(CompoundAssignment { assignees, init })
538 } else {
539 None
540 }
541 }
542}
543
544#[must_use]
546pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
547 match op {
548 hir::BinOpKind::Eq => ast::BinOpKind::Eq,
549 hir::BinOpKind::Ge => ast::BinOpKind::Ge,
550 hir::BinOpKind::Gt => ast::BinOpKind::Gt,
551 hir::BinOpKind::Le => ast::BinOpKind::Le,
552 hir::BinOpKind::Lt => ast::BinOpKind::Lt,
553 hir::BinOpKind::Ne => ast::BinOpKind::Ne,
554 hir::BinOpKind::Or => ast::BinOpKind::Or,
555 hir::BinOpKind::Add => ast::BinOpKind::Add,
556 hir::BinOpKind::And => ast::BinOpKind::And,
557 hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
558 hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
559 hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
560 hir::BinOpKind::Div => ast::BinOpKind::Div,
561 hir::BinOpKind::Mul => ast::BinOpKind::Mul,
562 hir::BinOpKind::Rem => ast::BinOpKind::Rem,
563 hir::BinOpKind::Shl => ast::BinOpKind::Shl,
564 hir::BinOpKind::Shr => ast::BinOpKind::Shr,
565 hir::BinOpKind::Sub => ast::BinOpKind::Sub,
566 }
567}
568
569#[derive(Clone, Copy)]
571pub enum VecInitKind {
572 New,
574 Default,
576 WithConstCapacity(u128),
578 WithExprCapacity(HirId),
580}
581
582pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
584 if let ExprKind::Call(func, args) = expr.kind {
585 match func.kind {
586 ExprKind::Path(QPath::TypeRelative(ty, name))
587 if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) =>
588 {
589 if name.ident.name == sym::new {
590 return Some(VecInitKind::New);
591 } else if name.ident.name == symbol::kw::Default {
592 return Some(VecInitKind::Default);
593 } else if name.ident.name == sym::with_capacity {
594 let arg = args.first()?;
595 return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
596 Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
597 _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
598 };
599 }
600 },
601 ExprKind::Path(QPath::Resolved(_, path))
602 if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
603 && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) =>
604 {
605 return Some(VecInitKind::Default);
606 },
607 _ => (),
608 }
609 }
610 None
611}
612
613pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
616 match &cond.kind {
617 ExprKind::Let(_) => true,
618 ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
619 _ => false,
620 }
621}