1#![deny(clippy::missing_docs_in_private_items)]
4
5use crate::consts::{ConstEvalCtxt, Constant};
6use crate::res::MaybeDef;
7use crate::{is_expn_of, sym};
8
9use rustc_ast::ast;
10use rustc_hir as hir;
11use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, LoopSource, MatchSource, Node, Pat, QPath, StructTailExpr};
12use rustc_lint::LateContext;
13use rustc_span::{Span, symbol};
14
15#[derive(Debug)]
18pub struct ForLoop<'tcx> {
19 pub pat: &'tcx Pat<'tcx>,
21 pub arg: &'tcx Expr<'tcx>,
23 pub body: &'tcx Expr<'tcx>,
25 pub loop_id: HirId,
27 pub span: Span,
29 pub label: Option<ast::Label>,
31}
32
33impl<'tcx> ForLoop<'tcx> {
34 pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
36 if let ExprKind::DropTemps(e) = expr.kind
37 && let ExprKind::Match(iterexpr, [arm], MatchSource::ForLoopDesugar) = e.kind
38 && let ExprKind::Call(_, [arg]) = iterexpr.kind
39 && let ExprKind::Loop(block, label, ..) = arm.body.kind
40 && let [stmt] = block.stmts
41 && let hir::StmtKind::Expr(e) = stmt.kind
42 && let ExprKind::Match(_, [_, some_arm], _) = e.kind
43 && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
44 {
45 return Some(Self {
46 pat: field.pat,
47 arg,
48 body: some_arm.body,
49 loop_id: arm.body.hir_id,
50 span: expr.span.ctxt().outer_expn_data().call_site,
51 label,
52 });
53 }
54 None
55 }
56}
57
58pub struct If<'hir> {
60 pub cond: &'hir Expr<'hir>,
62 pub then: &'hir Expr<'hir>,
64 pub r#else: Option<&'hir Expr<'hir>>,
66}
67
68impl<'hir> If<'hir> {
69 #[inline]
70 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
72 if let ExprKind::If(cond, then, r#else) = expr.kind
73 && !has_let_expr(cond)
74 {
75 Some(Self { cond, then, r#else })
76 } else {
77 None
78 }
79 }
80}
81
82pub struct IfLet<'hir> {
84 pub let_pat: &'hir Pat<'hir>,
86 pub let_expr: &'hir Expr<'hir>,
88 pub if_then: &'hir Expr<'hir>,
90 pub if_else: Option<&'hir Expr<'hir>>,
92 pub let_span: Span,
95}
96
97impl<'hir> IfLet<'hir> {
98 pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
100 if let ExprKind::If(
101 &Expr {
102 kind:
103 ExprKind::Let(&hir::LetExpr {
104 pat: let_pat,
105 init: let_expr,
106 span: let_span,
107 ..
108 }),
109 ..
110 },
111 if_then,
112 if_else,
113 ) = expr.kind
114 {
115 let mut iter = cx.tcx.hir_parent_iter(expr.hir_id);
116 if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next()
117 && let Some((
118 _,
119 Node::Expr(Expr {
120 kind: ExprKind::Loop(_, _, LoopSource::While, _),
121 ..
122 }),
123 )) = iter.next()
124 {
125 return None;
127 }
128 return Some(Self {
129 let_pat,
130 let_expr,
131 if_then,
132 if_else,
133 let_span,
134 });
135 }
136 None
137 }
138}
139
140#[derive(Debug)]
142pub enum IfLetOrMatch<'hir> {
143 Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
145 IfLet(
147 &'hir Expr<'hir>,
148 &'hir Pat<'hir>,
149 &'hir Expr<'hir>,
150 Option<&'hir Expr<'hir>>,
151 Span,
154 ),
155}
156
157impl<'hir> IfLetOrMatch<'hir> {
158 pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
160 match expr.kind {
161 ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
162 _ => IfLet::hir(cx, expr).map(
163 |IfLet {
164 let_expr,
165 let_pat,
166 if_then,
167 if_else,
168 let_span,
169 }| { Self::IfLet(let_expr, let_pat, if_then, if_else, let_span) },
170 ),
171 }
172 }
173
174 pub fn scrutinee(&self) -> &'hir Expr<'hir> {
175 match self {
176 Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
177 }
178 }
179}
180
181pub struct IfOrIfLet<'hir> {
183 pub cond: &'hir Expr<'hir>,
185 pub then: &'hir Expr<'hir>,
187 pub r#else: Option<&'hir Expr<'hir>>,
189}
190
191impl<'hir> IfOrIfLet<'hir> {
192 #[inline]
193 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
195 if let ExprKind::If(cond, then, r#else) = expr.kind {
196 Some(Self { cond, then, r#else })
197 } else {
198 None
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone)]
205pub struct Range<'a> {
206 pub start: Option<&'a Expr<'a>>,
208 pub end: Option<&'a Expr<'a>>,
210 pub limits: ast::RangeLimits,
212}
213
214impl<'a> Range<'a> {
215 #[expect(clippy::similar_names)]
217 pub fn hir(expr: &'a Expr<'_>) -> Option<Range<'a>> {
218 match expr.kind {
219 ExprKind::Call(path, [arg1, arg2])
220 if matches!(
221 path.kind,
222 ExprKind::Path(QPath::LangItem(hir::LangItem::RangeInclusiveNew, ..))
223 ) =>
224 {
225 Some(Range {
226 start: Some(arg1),
227 end: Some(arg2),
228 limits: ast::RangeLimits::Closed,
229 })
230 },
231 ExprKind::Struct(path, fields, StructTailExpr::None) => match (path, fields) {
232 (QPath::LangItem(hir::LangItem::RangeFull, ..), []) => Some(Range {
233 start: None,
234 end: None,
235 limits: ast::RangeLimits::HalfOpen,
236 }),
237 (QPath::LangItem(hir::LangItem::RangeFrom, ..), [field]) if field.ident.name == sym::start => {
238 Some(Range {
239 start: Some(field.expr),
240 end: None,
241 limits: ast::RangeLimits::HalfOpen,
242 })
243 },
244 (QPath::LangItem(hir::LangItem::Range, ..), [field1, field2]) => {
245 let (start, end) = match (field1.ident.name, field2.ident.name) {
246 (sym::start, sym::end) => (field1.expr, field2.expr),
247 (sym::end, sym::start) => (field2.expr, field1.expr),
248 _ => return None,
249 };
250 Some(Range {
251 start: Some(start),
252 end: Some(end),
253 limits: ast::RangeLimits::HalfOpen,
254 })
255 },
256 (QPath::LangItem(hir::LangItem::RangeToInclusive, ..), [field]) if field.ident.name == sym::end => {
257 Some(Range {
258 start: None,
259 end: Some(field.expr),
260 limits: ast::RangeLimits::Closed,
261 })
262 },
263 (QPath::LangItem(hir::LangItem::RangeTo, ..), [field]) if field.ident.name == sym::end => Some(Range {
264 start: None,
265 end: Some(field.expr),
266 limits: ast::RangeLimits::HalfOpen,
267 }),
268 _ => None,
269 },
270 _ => None,
271 }
272 }
273}
274
275pub enum VecArgs<'a> {
277 Repeat(&'a Expr<'a>, &'a Expr<'a>),
279 Vec(&'a [Expr<'a>]),
281}
282
283impl<'a> VecArgs<'a> {
284 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<VecArgs<'a>> {
287 if let ExprKind::Call(fun, args) = expr.kind
288 && let ExprKind::Path(ref qpath) = fun.kind
289 && is_expn_of(fun.span, sym::vec).is_some()
290 && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
291 && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
292 {
293 return match (name, args) {
294 (sym::vec_from_elem, [elem, size]) => {
295 Some(VecArgs::Repeat(elem, size))
297 },
298 (sym::slice_into_vec, [slice])
299 if let ExprKind::Call(_, [arg]) = slice.kind
300 && let ExprKind::Array(args) = arg.kind =>
301 {
302 Some(VecArgs::Vec(args))
304 },
305 (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
306 _ => None,
307 };
308 }
309
310 None
311 }
312}
313
314pub struct While<'hir> {
316 pub condition: &'hir Expr<'hir>,
318 pub body: &'hir Expr<'hir>,
320 pub span: Span,
322 pub label: Option<ast::Label>,
323}
324
325impl<'hir> While<'hir> {
326 #[inline]
327 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
329 if let ExprKind::Loop(
330 Block {
331 expr:
332 Some(Expr {
333 kind: ExprKind::If(condition, body, _),
334 ..
335 }),
336 ..
337 },
338 label,
339 LoopSource::While,
340 span,
341 ) = expr.kind
342 && !has_let_expr(condition)
343 {
344 return Some(Self {
345 condition,
346 body,
347 span,
348 label,
349 });
350 }
351 None
352 }
353}
354
355pub struct WhileLet<'hir> {
357 pub let_pat: &'hir Pat<'hir>,
359 pub let_expr: &'hir Expr<'hir>,
361 pub if_then: &'hir Expr<'hir>,
363 pub label: Option<ast::Label>,
364 pub let_span: Span,
367}
368
369impl<'hir> WhileLet<'hir> {
370 #[inline]
371 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
373 if let ExprKind::Loop(
374 &Block {
375 expr:
376 Some(&Expr {
377 kind:
378 ExprKind::If(
379 &Expr {
380 kind:
381 ExprKind::Let(&hir::LetExpr {
382 pat: let_pat,
383 init: let_expr,
384 span: let_span,
385 ..
386 }),
387 ..
388 },
389 if_then,
390 _,
391 ),
392 ..
393 }),
394 ..
395 },
396 label,
397 LoopSource::While,
398 _,
399 ) = expr.kind
400 {
401 return Some(Self {
402 let_pat,
403 let_expr,
404 if_then,
405 label,
406 let_span,
407 });
408 }
409 None
410 }
411}
412
413#[must_use]
415pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
416 match op {
417 hir::BinOpKind::Eq => ast::BinOpKind::Eq,
418 hir::BinOpKind::Ge => ast::BinOpKind::Ge,
419 hir::BinOpKind::Gt => ast::BinOpKind::Gt,
420 hir::BinOpKind::Le => ast::BinOpKind::Le,
421 hir::BinOpKind::Lt => ast::BinOpKind::Lt,
422 hir::BinOpKind::Ne => ast::BinOpKind::Ne,
423 hir::BinOpKind::Or => ast::BinOpKind::Or,
424 hir::BinOpKind::Add => ast::BinOpKind::Add,
425 hir::BinOpKind::And => ast::BinOpKind::And,
426 hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
427 hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
428 hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
429 hir::BinOpKind::Div => ast::BinOpKind::Div,
430 hir::BinOpKind::Mul => ast::BinOpKind::Mul,
431 hir::BinOpKind::Rem => ast::BinOpKind::Rem,
432 hir::BinOpKind::Shl => ast::BinOpKind::Shl,
433 hir::BinOpKind::Shr => ast::BinOpKind::Shr,
434 hir::BinOpKind::Sub => ast::BinOpKind::Sub,
435 }
436}
437
438#[derive(Clone, Copy)]
440pub enum VecInitKind {
441 New,
443 Default,
445 WithConstCapacity(u128),
447 WithExprCapacity(HirId),
449}
450
451pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
453 if let ExprKind::Call(func, args) = expr.kind {
454 match func.kind {
455 ExprKind::Path(QPath::TypeRelative(ty, name))
456 if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) =>
457 {
458 if name.ident.name == sym::new {
459 return Some(VecInitKind::New);
460 } else if name.ident.name == symbol::kw::Default {
461 return Some(VecInitKind::Default);
462 } else if name.ident.name == sym::with_capacity {
463 let arg = args.first()?;
464 return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
465 Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
466 _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
467 };
468 }
469 },
470 ExprKind::Path(QPath::Resolved(_, path))
471 if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
472 && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) =>
473 {
474 return Some(VecInitKind::Default);
475 },
476 _ => (),
477 }
478 }
479 None
480}
481
482pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
485 match &cond.kind {
486 ExprKind::Let(_) => true,
487 ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
488 _ => false,
489 }
490}