1#![deny(clippy::missing_docs_in_private_items)]
4
5use crate::consts::{ConstEvalCtxt, Constant};
6use crate::ty::is_type_diagnostic_item;
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
15pub struct ForLoop<'tcx> {
18 pub pat: &'tcx Pat<'tcx>,
20 pub arg: &'tcx Expr<'tcx>,
22 pub body: &'tcx Expr<'tcx>,
24 pub loop_id: HirId,
26 pub span: Span,
28 pub label: Option<ast::Label>,
30}
31
32impl<'tcx> ForLoop<'tcx> {
33 pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
35 if let ExprKind::DropTemps(e) = expr.kind
36 && let ExprKind::Match(iterexpr, [arm], MatchSource::ForLoopDesugar) = e.kind
37 && let ExprKind::Call(_, [arg]) = iterexpr.kind
38 && let ExprKind::Loop(block, label, ..) = arm.body.kind
39 && let [stmt] = block.stmts
40 && let hir::StmtKind::Expr(e) = stmt.kind
41 && let ExprKind::Match(_, [_, some_arm], _) = e.kind
42 && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
43 {
44 return Some(Self {
45 pat: field.pat,
46 arg,
47 body: some_arm.body,
48 loop_id: arm.body.hir_id,
49 span: expr.span.ctxt().outer_expn_data().call_site,
50 label,
51 });
52 }
53 None
54 }
55}
56
57pub struct If<'hir> {
59 pub cond: &'hir Expr<'hir>,
61 pub then: &'hir Expr<'hir>,
63 pub r#else: Option<&'hir Expr<'hir>>,
65}
66
67impl<'hir> If<'hir> {
68 #[inline]
69 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
71 if let ExprKind::If(cond, then, r#else) = expr.kind
72 && !has_let_expr(cond)
73 {
74 Some(Self { cond, then, r#else })
75 } else {
76 None
77 }
78 }
79}
80
81pub struct IfLet<'hir> {
83 pub let_pat: &'hir Pat<'hir>,
85 pub let_expr: &'hir Expr<'hir>,
87 pub if_then: &'hir Expr<'hir>,
89 pub if_else: Option<&'hir Expr<'hir>>,
91 pub let_span: Span,
94}
95
96impl<'hir> IfLet<'hir> {
97 pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
99 if let ExprKind::If(
100 &Expr {
101 kind:
102 ExprKind::Let(&hir::LetExpr {
103 pat: let_pat,
104 init: let_expr,
105 span: let_span,
106 ..
107 }),
108 ..
109 },
110 if_then,
111 if_else,
112 ) = expr.kind
113 {
114 let mut iter = cx.tcx.hir_parent_iter(expr.hir_id);
115 if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next()
116 && let Some((
117 _,
118 Node::Expr(Expr {
119 kind: ExprKind::Loop(_, _, LoopSource::While, _),
120 ..
121 }),
122 )) = iter.next()
123 {
124 return None;
126 }
127 return Some(Self {
128 let_pat,
129 let_expr,
130 if_then,
131 if_else,
132 let_span,
133 });
134 }
135 None
136 }
137}
138
139#[derive(Debug)]
141pub enum IfLetOrMatch<'hir> {
142 Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
144 IfLet(
146 &'hir Expr<'hir>,
147 &'hir Pat<'hir>,
148 &'hir Expr<'hir>,
149 Option<&'hir Expr<'hir>>,
150 Span,
153 ),
154}
155
156impl<'hir> IfLetOrMatch<'hir> {
157 pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
159 match expr.kind {
160 ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
161 _ => IfLet::hir(cx, expr).map(
162 |IfLet {
163 let_expr,
164 let_pat,
165 if_then,
166 if_else,
167 let_span,
168 }| { Self::IfLet(let_expr, let_pat, if_then, if_else, let_span) },
169 ),
170 }
171 }
172
173 pub fn scrutinee(&self) -> &'hir Expr<'hir> {
174 match self {
175 Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
176 }
177 }
178}
179
180pub struct IfOrIfLet<'hir> {
182 pub cond: &'hir Expr<'hir>,
184 pub then: &'hir Expr<'hir>,
186 pub r#else: Option<&'hir Expr<'hir>>,
188}
189
190impl<'hir> IfOrIfLet<'hir> {
191 #[inline]
192 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
194 if let ExprKind::If(cond, then, r#else) = expr.kind {
195 Some(Self { cond, then, r#else })
196 } else {
197 None
198 }
199 }
200}
201
202#[derive(Debug, Copy, Clone)]
204pub struct Range<'a> {
205 pub start: Option<&'a Expr<'a>>,
207 pub end: Option<&'a Expr<'a>>,
209 pub limits: ast::RangeLimits,
211}
212
213impl<'a> Range<'a> {
214 #[allow(clippy::similar_names)]
216 pub fn hir(expr: &'a Expr<'_>) -> Option<Range<'a>> {
217 match expr.kind {
218 ExprKind::Call(path, [arg1, arg2])
219 if matches!(
220 path.kind,
221 ExprKind::Path(QPath::LangItem(hir::LangItem::RangeInclusiveNew, ..))
222 ) =>
223 {
224 Some(Range {
225 start: Some(arg1),
226 end: Some(arg2),
227 limits: ast::RangeLimits::Closed,
228 })
229 },
230 ExprKind::Struct(path, fields, StructTailExpr::None) => match (path, fields) {
231 (QPath::LangItem(hir::LangItem::RangeFull, ..), []) => Some(Range {
232 start: None,
233 end: None,
234 limits: ast::RangeLimits::HalfOpen,
235 }),
236 (QPath::LangItem(hir::LangItem::RangeFrom, ..), [field]) if field.ident.name == sym::start => {
237 Some(Range {
238 start: Some(field.expr),
239 end: None,
240 limits: ast::RangeLimits::HalfOpen,
241 })
242 },
243 (QPath::LangItem(hir::LangItem::Range, ..), [field1, field2]) => {
244 let (start, end) = match (field1.ident.name, field2.ident.name) {
245 (sym::start, sym::end) => (field1.expr, field2.expr),
246 (sym::end, sym::start) => (field2.expr, field1.expr),
247 _ => return None,
248 };
249 Some(Range {
250 start: Some(start),
251 end: Some(end),
252 limits: ast::RangeLimits::HalfOpen,
253 })
254 },
255 (QPath::LangItem(hir::LangItem::RangeToInclusive, ..), [field]) if field.ident.name == sym::end => {
256 Some(Range {
257 start: None,
258 end: Some(field.expr),
259 limits: ast::RangeLimits::Closed,
260 })
261 },
262 (QPath::LangItem(hir::LangItem::RangeTo, ..), [field]) if field.ident.name == sym::end => Some(Range {
263 start: None,
264 end: Some(field.expr),
265 limits: ast::RangeLimits::HalfOpen,
266 }),
267 _ => None,
268 },
269 _ => None,
270 }
271 }
272}
273
274pub enum VecArgs<'a> {
276 Repeat(&'a Expr<'a>, &'a Expr<'a>),
278 Vec(&'a [Expr<'a>]),
280}
281
282impl<'a> VecArgs<'a> {
283 pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<VecArgs<'a>> {
286 if let ExprKind::Call(fun, args) = expr.kind
287 && let ExprKind::Path(ref qpath) = fun.kind
288 && is_expn_of(fun.span, sym::vec).is_some()
289 && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
290 && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
291 {
292 return match (name, args) {
293 (sym::vec_from_elem, [elem, size]) => {
294 Some(VecArgs::Repeat(elem, size))
296 },
297 (sym::slice_into_vec, [slice])
298 if let ExprKind::Call(_, [arg]) = slice.kind
299 && let ExprKind::Array(args) = arg.kind =>
300 {
301 Some(VecArgs::Vec(args))
303 },
304 (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
305 _ => None,
306 };
307 }
308
309 None
310 }
311}
312
313pub struct While<'hir> {
315 pub condition: &'hir Expr<'hir>,
317 pub body: &'hir Expr<'hir>,
319 pub span: Span,
321}
322
323impl<'hir> While<'hir> {
324 #[inline]
325 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
327 if let ExprKind::Loop(
328 Block {
329 expr:
330 Some(Expr {
331 kind: ExprKind::If(condition, body, _),
332 ..
333 }),
334 ..
335 },
336 _,
337 LoopSource::While,
338 span,
339 ) = expr.kind
340 && !has_let_expr(condition)
341 {
342 return Some(Self { condition, body, span });
343 }
344 None
345 }
346}
347
348pub struct WhileLet<'hir> {
350 pub let_pat: &'hir Pat<'hir>,
352 pub let_expr: &'hir Expr<'hir>,
354 pub if_then: &'hir Expr<'hir>,
356 pub label: Option<ast::Label>,
357 pub let_span: Span,
360}
361
362impl<'hir> WhileLet<'hir> {
363 #[inline]
364 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
366 if let ExprKind::Loop(
367 &Block {
368 expr:
369 Some(&Expr {
370 kind:
371 ExprKind::If(
372 &Expr {
373 kind:
374 ExprKind::Let(&hir::LetExpr {
375 pat: let_pat,
376 init: let_expr,
377 span: let_span,
378 ..
379 }),
380 ..
381 },
382 if_then,
383 _,
384 ),
385 ..
386 }),
387 ..
388 },
389 label,
390 LoopSource::While,
391 _,
392 ) = expr.kind
393 {
394 return Some(Self {
395 let_pat,
396 let_expr,
397 if_then,
398 label,
399 let_span,
400 });
401 }
402 None
403 }
404}
405
406#[must_use]
408pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
409 match op {
410 hir::BinOpKind::Eq => ast::BinOpKind::Eq,
411 hir::BinOpKind::Ge => ast::BinOpKind::Ge,
412 hir::BinOpKind::Gt => ast::BinOpKind::Gt,
413 hir::BinOpKind::Le => ast::BinOpKind::Le,
414 hir::BinOpKind::Lt => ast::BinOpKind::Lt,
415 hir::BinOpKind::Ne => ast::BinOpKind::Ne,
416 hir::BinOpKind::Or => ast::BinOpKind::Or,
417 hir::BinOpKind::Add => ast::BinOpKind::Add,
418 hir::BinOpKind::And => ast::BinOpKind::And,
419 hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
420 hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
421 hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
422 hir::BinOpKind::Div => ast::BinOpKind::Div,
423 hir::BinOpKind::Mul => ast::BinOpKind::Mul,
424 hir::BinOpKind::Rem => ast::BinOpKind::Rem,
425 hir::BinOpKind::Shl => ast::BinOpKind::Shl,
426 hir::BinOpKind::Shr => ast::BinOpKind::Shr,
427 hir::BinOpKind::Sub => ast::BinOpKind::Sub,
428 }
429}
430
431#[derive(Clone, Copy)]
433pub enum VecInitKind {
434 New,
436 Default,
438 WithConstCapacity(u128),
440 WithExprCapacity(HirId),
442}
443
444pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
446 if let ExprKind::Call(func, args) = expr.kind {
447 match func.kind {
448 ExprKind::Path(QPath::TypeRelative(ty, name))
449 if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
450 {
451 if name.ident.name == sym::new {
452 return Some(VecInitKind::New);
453 } else if name.ident.name == symbol::kw::Default {
454 return Some(VecInitKind::Default);
455 } else if name.ident.name == sym::with_capacity {
456 let arg = args.first()?;
457 return match ConstEvalCtxt::new(cx).eval_simple(arg) {
458 Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
459 _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
460 };
461 }
462 },
463 ExprKind::Path(QPath::Resolved(_, path))
464 if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
465 && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
466 {
467 return Some(VecInitKind::Default);
468 },
469 _ => (),
470 }
471 }
472 None
473}
474
475pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
478 match &cond.kind {
479 ExprKind::Let(_) => true,
480 ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
481 _ => false,
482 }
483}