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 {
291 return if cx.tcx.is_diagnostic_item(sym::vec_from_elem, fun_def_id) && args.len() == 2 {
292 Some(VecArgs::Repeat(&args[0], &args[1]))
294 } else if cx.tcx.is_diagnostic_item(sym::slice_into_vec, fun_def_id) && args.len() == 1 {
295 if let ExprKind::Call(_, [arg]) = &args[0].kind
297 && let ExprKind::Array(args) = arg.kind
298 {
299 Some(VecArgs::Vec(args))
300 } else {
301 None
302 }
303 } else if cx.tcx.is_diagnostic_item(sym::vec_new, fun_def_id) && args.is_empty() {
304 Some(VecArgs::Vec(&[]))
305 } else {
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}
323
324impl<'hir> While<'hir> {
325 #[inline]
326 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
328 if let ExprKind::Loop(
329 Block {
330 expr:
331 Some(Expr {
332 kind: ExprKind::If(condition, body, _),
333 ..
334 }),
335 ..
336 },
337 _,
338 LoopSource::While,
339 span,
340 ) = expr.kind
341 && !has_let_expr(condition)
342 {
343 return Some(Self { condition, body, span });
344 }
345 None
346 }
347}
348
349pub struct WhileLet<'hir> {
351 pub let_pat: &'hir Pat<'hir>,
353 pub let_expr: &'hir Expr<'hir>,
355 pub if_then: &'hir Expr<'hir>,
357 pub label: Option<ast::Label>,
358 pub let_span: Span,
361}
362
363impl<'hir> WhileLet<'hir> {
364 #[inline]
365 pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
367 if let ExprKind::Loop(
368 &Block {
369 expr:
370 Some(&Expr {
371 kind:
372 ExprKind::If(
373 &Expr {
374 kind:
375 ExprKind::Let(&hir::LetExpr {
376 pat: let_pat,
377 init: let_expr,
378 span: let_span,
379 ..
380 }),
381 ..
382 },
383 if_then,
384 _,
385 ),
386 ..
387 }),
388 ..
389 },
390 label,
391 LoopSource::While,
392 _,
393 ) = expr.kind
394 {
395 return Some(Self {
396 let_pat,
397 let_expr,
398 if_then,
399 label,
400 let_span,
401 });
402 }
403 None
404 }
405}
406
407#[must_use]
409pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
410 match op {
411 hir::BinOpKind::Eq => ast::BinOpKind::Eq,
412 hir::BinOpKind::Ge => ast::BinOpKind::Ge,
413 hir::BinOpKind::Gt => ast::BinOpKind::Gt,
414 hir::BinOpKind::Le => ast::BinOpKind::Le,
415 hir::BinOpKind::Lt => ast::BinOpKind::Lt,
416 hir::BinOpKind::Ne => ast::BinOpKind::Ne,
417 hir::BinOpKind::Or => ast::BinOpKind::Or,
418 hir::BinOpKind::Add => ast::BinOpKind::Add,
419 hir::BinOpKind::And => ast::BinOpKind::And,
420 hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
421 hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
422 hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
423 hir::BinOpKind::Div => ast::BinOpKind::Div,
424 hir::BinOpKind::Mul => ast::BinOpKind::Mul,
425 hir::BinOpKind::Rem => ast::BinOpKind::Rem,
426 hir::BinOpKind::Shl => ast::BinOpKind::Shl,
427 hir::BinOpKind::Shr => ast::BinOpKind::Shr,
428 hir::BinOpKind::Sub => ast::BinOpKind::Sub,
429 }
430}
431
432#[derive(Clone, Copy)]
434pub enum VecInitKind {
435 New,
437 Default,
439 WithConstCapacity(u128),
441 WithExprCapacity(HirId),
443}
444
445pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
447 if let ExprKind::Call(func, args) = expr.kind {
448 match func.kind {
449 ExprKind::Path(QPath::TypeRelative(ty, name))
450 if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
451 {
452 if name.ident.name == sym::new {
453 return Some(VecInitKind::New);
454 } else if name.ident.name == symbol::kw::Default {
455 return Some(VecInitKind::Default);
456 } else if name.ident.name == sym::with_capacity {
457 let arg = args.first()?;
458 return match ConstEvalCtxt::new(cx).eval_simple(arg) {
459 Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
460 _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
461 };
462 }
463 },
464 ExprKind::Path(QPath::Resolved(_, path))
465 if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
466 && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
467 {
468 return Some(VecInitKind::Default);
469 },
470 _ => (),
471 }
472 }
473 None
474}
475
476pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
479 match &cond.kind {
480 ExprKind::Let(_) => true,
481 ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
482 _ => false,
483 }
484}