-//! This module contains functions for retrieve the original AST from lowered
-//! `hir`.
+//! This module contains functions that retrieves specifiec elements.
#![deny(clippy::missing_docs_in_private_items)]
use if_chain::if_chain;
use rustc_ast::ast::{self, LitKind};
use rustc_hir as hir;
-use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
+use rustc_hir::{Arm, Block, BorrowKind, Expr, ExprKind, LoopSource, MatchSource, Node, Pat, StmtKind, UnOp};
use rustc_lint::LateContext;
use rustc_span::{sym, ExpnKind, Span, Symbol};
-/// Represent a range akin to `ast::ExprKind::Range`.
-#[derive(Debug, Copy, Clone)]
-pub struct Range<'a> {
- /// The lower bound of the range, or `None` for ranges such as `..X`.
- pub start: Option<&'a hir::Expr<'a>>,
- /// The upper bound of the range, or `None` for ranges such as `X..`.
- pub end: Option<&'a hir::Expr<'a>>,
- /// Whether the interval is open or closed.
- pub limits: ast::RangeLimits,
+/// The essential nodes of a desugared for loop as well as the entire span:
+/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
+pub struct ForLoop<'tcx> {
+ /// `for` loop item
+ pub pat: &'tcx hir::Pat<'tcx>,
+ /// `IntoIterator` argument
+ pub arg: &'tcx hir::Expr<'tcx>,
+ /// `for` loop body
+ pub body: &'tcx hir::Expr<'tcx>,
+ /// entire `for` loop span
+ pub span: Span,
}
-/// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
-pub fn range<'a>(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
- /// Finds the field named `name` in the field. Always return `Some` for
- /// convenience.
- fn get_field<'c>(name: &str, fields: &'c [hir::ExprField<'_>]) -> Option<&'c hir::Expr<'c>> {
- let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
-
- Some(expr)
+impl<'tcx> ForLoop<'tcx> {
+ #[inline]
+ /// Parses a desugared `for` loop
+ pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
+ if_chain! {
+ if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
+ if let Some(first_arm) = arms.get(0);
+ if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind;
+ if let Some(first_arg) = iterargs.get(0);
+ if iterargs.len() == 1 && arms.len() == 1 && first_arm.guard.is_none();
+ if let hir::ExprKind::Loop(block, ..) = first_arm.body.kind;
+ if block.expr.is_none();
+ if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
+ if let hir::StmtKind::Local(local) = let_stmt.kind;
+ if let hir::StmtKind::Expr(body_expr) = body.kind;
+ then {
+ return Some(Self {
+ pat: &*local.pat,
+ arg: first_arg,
+ body: body_expr,
+ span: first_arm.span
+ });
+ }
+ }
+ None
}
+}
- match expr.kind {
- hir::ExprKind::Call(path, args)
- if matches!(
- path.kind,
- hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
- ) =>
+/// An `if` expression without `DropTemps`
+pub struct If<'hir> {
+ /// `if` condition
+ pub cond: &'hir Expr<'hir>,
+ /// `if` then expression
+ pub then: &'hir Expr<'hir>,
+ /// `else` expression
+ pub r#else: Option<&'hir Expr<'hir>>,
+}
+
+impl<'hir> If<'hir> {
+ #[inline]
+ /// Parses an `if` expression
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::If(
+ Expr {
+ kind: ExprKind::DropTemps(cond),
+ ..
+ },
+ then,
+ r#else,
+ ) = expr.kind
{
- Some(Range {
- start: Some(&args[0]),
- end: Some(&args[1]),
- limits: ast::RangeLimits::Closed,
- })
- },
- hir::ExprKind::Struct(path, fields, None) => match path {
- hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
- start: None,
- end: None,
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
- start: Some(get_field("start", fields)?),
- end: None,
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
- start: Some(get_field("start", fields)?),
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::HalfOpen,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
- start: None,
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::Closed,
- }),
- hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
- start: None,
- end: Some(get_field("end", fields)?),
- limits: ast::RangeLimits::HalfOpen,
- }),
- _ => None,
- },
- _ => None,
+ Some(Self { cond, then, r#else })
+ } else {
+ None
+ }
}
}
-/// Checks if a `let` statement is from a `for` loop desugaring.
-pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
- // This will detect plain for-loops without an actual variable binding:
- //
- // ```
- // for x in some_vec {
- // // do stuff
- // }
- // ```
- if_chain! {
- if let Some(expr) = local.init;
- if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
- then {
- return true;
+/// An `if let` expression
+pub struct IfLet<'hir> {
+ /// `if let` pattern
+ pub let_pat: &'hir Pat<'hir>,
+ /// `if let` scrutinee
+ pub let_expr: &'hir Expr<'hir>,
+ /// `if let` then expression
+ pub if_then: &'hir Expr<'hir>,
+ /// `if let` else expression
+ pub if_else: Option<&'hir Expr<'hir>>,
+}
+
+impl<'hir> IfLet<'hir> {
+ /// Parses an `if let` expression
+ pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::If(
+ Expr {
+ kind: ExprKind::Let(let_pat, let_expr, _),
+ ..
+ },
+ if_then,
+ if_else,
+ ) = expr.kind
+ {
+ let mut iter = cx.tcx.hir().parent_iter(expr.hir_id);
+ if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() {
+ if let Some((
+ _,
+ Node::Expr(Expr {
+ kind: ExprKind::Loop(_, _, LoopSource::While, _),
+ ..
+ }),
+ )) = iter.next()
+ {
+ // while loop desugar
+ return None;
+ }
+ }
+ return Some(Self {
+ let_pat,
+ let_expr,
+ if_then,
+ if_else,
+ });
}
+ None
}
+}
- // This detects a variable binding in for loop to avoid `let_unit_value`
- // lint (see issue #1964).
- //
- // ```
- // for _ in vec![()] {
- // // anything
- // }
- // ```
- if let hir::LocalSource::ForLoopDesugar = local.source {
- return true;
+/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
+pub enum IfLetOrMatch<'hir> {
+ /// Any `match` expression
+ Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
+ /// scrutinee, pattern, then block, else block
+ IfLet(
+ &'hir Expr<'hir>,
+ &'hir Pat<'hir>,
+ &'hir Expr<'hir>,
+ Option<&'hir Expr<'hir>>,
+ ),
+}
+
+impl<'hir> IfLetOrMatch<'hir> {
+ /// Parses an `if let` or `match` expression
+ pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
+ match expr.kind {
+ ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
+ _ => IfLet::hir(cx, expr).map(
+ |IfLet {
+ let_expr,
+ let_pat,
+ if_then,
+ if_else,
+ }| { Self::IfLet(let_expr, let_pat, if_then, if_else) },
+ ),
+ }
}
+}
- false
+/// An `if` or `if let` expression
+pub struct IfOrIfLet<'hir> {
+ /// `if` condition that is maybe a `let` expression
+ pub cond: &'hir Expr<'hir>,
+ /// `if` then expression
+ pub then: &'hir Expr<'hir>,
+ /// `else` expression
+ pub r#else: Option<&'hir Expr<'hir>>,
}
-/// Recover the essential nodes of a desugared for loop as well as the entire span:
-/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
-pub fn for_loop<'tcx>(
- expr: &'tcx hir::Expr<'tcx>,
-) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>, Span)> {
- if_chain! {
- if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
- if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind;
- if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none();
- if let hir::ExprKind::Loop(block, ..) = arms[0].body.kind;
- if block.expr.is_none();
- if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
- if let hir::StmtKind::Local(local) = let_stmt.kind;
- if let hir::StmtKind::Expr(expr) = body.kind;
- then {
- return Some((&*local.pat, &iterargs[0], expr, arms[0].span));
+impl<'hir> IfOrIfLet<'hir> {
+ #[inline]
+ /// Parses an `if` or `if let` expression
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::If(cond, then, r#else) = expr.kind {
+ if let ExprKind::DropTemps(new_cond) = cond.kind {
+ return Some(Self {
+ cond: new_cond,
+ r#else,
+ then,
+ });
+ }
+ if let ExprKind::Let(..) = cond.kind {
+ return Some(Self { cond, then, r#else });
+ }
}
+ None
}
- None
}
-/// Recover the essential nodes of a desugared while loop:
-/// `while cond { body }` becomes `(cond, body)`.
-pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
- if_chain! {
- if let hir::ExprKind::Loop(hir::Block { expr: Some(expr), .. }, _, hir::LoopSource::While, _) = &expr.kind;
- if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
- if let hir::ExprKind::DropTemps(cond) = &cond.kind;
- if let [hir::Arm { body, .. }, ..] = &arms[..];
- then {
- return Some((cond, body));
+/// Represent a range akin to `ast::ExprKind::Range`.
+#[derive(Debug, Copy, Clone)]
+pub struct Range<'a> {
+ /// The lower bound of the range, or `None` for ranges such as `..X`.
+ pub start: Option<&'a hir::Expr<'a>>,
+ /// The upper bound of the range, or `None` for ranges such as `X..`.
+ pub end: Option<&'a hir::Expr<'a>>,
+ /// Whether the interval is open or closed.
+ pub limits: ast::RangeLimits,
+}
+
+impl<'a> Range<'a> {
+ /// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
+ pub fn hir(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
+ /// Finds the field named `name` in the field. Always return `Some` for
+ /// convenience.
+ fn get_field<'c>(name: &str, fields: &'c [hir::ExprField<'_>]) -> Option<&'c hir::Expr<'c>> {
+ let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
+ Some(expr)
+ }
+
+ match expr.kind {
+ hir::ExprKind::Call(path, args)
+ if matches!(
+ path.kind,
+ hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
+ ) =>
+ {
+ Some(Range {
+ start: Some(&args[0]),
+ end: Some(&args[1]),
+ limits: ast::RangeLimits::Closed,
+ })
+ },
+ hir::ExprKind::Struct(path, fields, None) => match &path {
+ hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
+ start: None,
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: None,
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
+ start: Some(get_field("start", fields)?),
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::Closed,
+ }),
+ hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
+ start: None,
+ end: Some(get_field("end", fields)?),
+ limits: ast::RangeLimits::HalfOpen,
+ }),
+ _ => None,
+ },
+ _ => None,
}
}
- None
}
/// Represent the pre-expansion arguments of a `vec!` invocation.
Vec(&'a [hir::Expr<'a>]),
}
-/// Returns the arguments of the `vec!` macro if this expression was expanded
-/// from `vec!`.
-pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<VecArgs<'e>> {
- if_chain! {
- if let hir::ExprKind::Call(fun, args) = expr.kind;
- if let hir::ExprKind::Path(ref qpath) = fun.kind;
- if is_expn_of(fun.span, "vec").is_some();
- if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
- then {
- return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
- // `vec![elem; size]` case
- Some(VecArgs::Repeat(&args[0], &args[1]))
- }
- else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
- // `vec![a, b, c]` case
- if_chain! {
- if let hir::ExprKind::Box(boxed) = args[0].kind;
- if let hir::ExprKind::Array(args) = boxed.kind;
- then {
- return Some(VecArgs::Vec(&*args));
- }
+impl<'a> VecArgs<'a> {
+ /// Returns the arguments of the `vec!` macro if this expression was expanded
+ /// from `vec!`.
+ pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> {
+ if_chain! {
+ if let hir::ExprKind::Call(fun, args) = expr.kind;
+ if let hir::ExprKind::Path(ref qpath) = fun.kind;
+ if is_expn_of(fun.span, "vec").is_some();
+ if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
+ then {
+ return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
+ // `vec![elem; size]` case
+ Some(VecArgs::Repeat(&args[0], &args[1]))
}
+ else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
+ // `vec![a, b, c]` case
+ if_chain! {
+ if let hir::ExprKind::Box(boxed) = args[0].kind;
+ if let hir::ExprKind::Array(args) = boxed.kind;
+ then {
+ return Some(VecArgs::Vec(args));
+ }
+ }
- None
- }
- else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
- Some(VecArgs::Vec(&[]))
+ None
+ }
+ else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
+ Some(VecArgs::Vec(&[]))
+ }
+ else {
+ None
+ };
}
- else {
- None
- };
}
+
+ None
}
+}
- None
+/// A desugared `while` loop
+pub struct While<'hir> {
+ /// `while` loop condition
+ pub condition: &'hir Expr<'hir>,
+ /// `while` loop body
+ pub body: &'hir Expr<'hir>,
+}
+
+impl<'hir> While<'hir> {
+ #[inline]
+ /// Parses a desugared `while` loop
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::Loop(
+ Block {
+ expr:
+ Some(Expr {
+ kind:
+ ExprKind::If(
+ Expr {
+ kind: ExprKind::DropTemps(condition),
+ ..
+ },
+ body,
+ _,
+ ),
+ ..
+ }),
+ ..
+ },
+ _,
+ LoopSource::While,
+ _,
+ ) = expr.kind
+ {
+ return Some(Self { condition, body });
+ }
+ None
+ }
+}
+
+/// A desugared `while let` loop
+pub struct WhileLet<'hir> {
+ /// `while let` loop item pattern
+ pub let_pat: &'hir Pat<'hir>,
+ /// `while let` loop scrutinee
+ pub let_expr: &'hir Expr<'hir>,
+ /// `while let` loop body
+ pub if_then: &'hir Expr<'hir>,
+}
+
+impl<'hir> WhileLet<'hir> {
+ #[inline]
+ /// Parses a desugared `while let` loop
+ pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
+ if let ExprKind::Loop(
+ Block {
+ expr:
+ Some(Expr {
+ kind:
+ ExprKind::If(
+ Expr {
+ kind: ExprKind::Let(let_pat, let_expr, _),
+ ..
+ },
+ if_then,
+ _,
+ ),
+ ..
+ }),
+ ..
+ },
+ _,
+ LoopSource::While,
+ _,
+ ) = expr.kind
+ {
+ return Some(Self {
+ let_pat,
+ let_expr,
+ if_then,
+ });
+ }
+ None
+ }
+}
+
+/// Converts a hir binary operator to the corresponding `ast` type.
+#[must_use]
+pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
+ match op {
+ hir::BinOpKind::Eq => ast::BinOpKind::Eq,
+ hir::BinOpKind::Ge => ast::BinOpKind::Ge,
+ hir::BinOpKind::Gt => ast::BinOpKind::Gt,
+ hir::BinOpKind::Le => ast::BinOpKind::Le,
+ hir::BinOpKind::Lt => ast::BinOpKind::Lt,
+ hir::BinOpKind::Ne => ast::BinOpKind::Ne,
+ hir::BinOpKind::Or => ast::BinOpKind::Or,
+ hir::BinOpKind::Add => ast::BinOpKind::Add,
+ hir::BinOpKind::And => ast::BinOpKind::And,
+ hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
+ hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
+ hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
+ hir::BinOpKind::Div => ast::BinOpKind::Div,
+ hir::BinOpKind::Mul => ast::BinOpKind::Mul,
+ hir::BinOpKind::Rem => ast::BinOpKind::Rem,
+ hir::BinOpKind::Shl => ast::BinOpKind::Shl,
+ hir::BinOpKind::Shr => ast::BinOpKind::Shr,
+ hir::BinOpKind::Sub => ast::BinOpKind::Sub,
+ }
}
/// Extract args from an assert-like macro.
if let StmtKind::Semi(matchexpr) = block.stmts.get(0)?.kind {
// macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
if_chain! {
- if let ExprKind::If(clause, _, _) = matchexpr.kind;
- if let ExprKind::Unary(UnOp::Not, condition) = clause.kind;
+ if let Some(If { cond, .. }) = If::hir(matchexpr);
+ if let ExprKind::Unary(UnOp::Not, condition) = cond.kind;
then {
return Some(vec![condition]);
}
// Arguments::new_v1
[strs_ref, args] => Some((strs_ref, args, None)),
// Arguments::new_v1_formatted
- [strs_ref, args, fmt_expr] => Some((strs_ref, args, Some(fmt_expr))),
+ [strs_ref, args, fmt_expr, _unsafe_arg] => Some((strs_ref, args, Some(fmt_expr))),
_ => None,
};
if let ExprKind::AddrOf(BorrowKind::Ref, _, strs_arr) = strs_ref.kind;
}
}
}
+
+/// Checks if a `let` statement is from a `for` loop desugaring.
+pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
+ // This will detect plain for-loops without an actual variable binding:
+ //
+ // ```
+ // for x in some_vec {
+ // // do stuff
+ // }
+ // ```
+ if_chain! {
+ if let Some(expr) = local.init;
+ if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
+ then {
+ return true;
+ }
+ }
+
+ // This detects a variable binding in for loop to avoid `let_unit_value`
+ // lint (see issue #1964).
+ //
+ // ```
+ // for _ in vec![()] {
+ // // anything
+ // }
+ // ```
+ if let hir::LocalSource::ForLoopDesugar = local.source {
+ return true;
+ }
+
+ false
+}
+
+/// A parsed `panic!` expansion
+pub struct PanicExpn<'tcx> {
+ /// Span of `panic!(..)`
+ pub call_site: Span,
+ /// Inner `format_args!` expansion
+ pub format_args: FormatArgsExpn<'tcx>,
+}
+
+impl PanicExpn<'tcx> {
+ /// Parses an expanded `panic!` invocation
+ pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
+ if_chain! {
+ if let ExprKind::Block(block, _) = expr.kind;
+ if let Some(init) = block.expr;
+ if let ExprKind::Call(_, [format_args]) = init.kind;
+ let expn_data = expr.span.ctxt().outer_expn_data();
+ if let ExprKind::AddrOf(_, _, format_args) = format_args.kind;
+ if let Some(format_args) = FormatArgsExpn::parse(format_args);
+ then {
+ Some(PanicExpn {
+ call_site: expn_data.call_site,
+ format_args,
+ })
+ } else {
+ None
+ }
+ }
+ }
+}