]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/utils/higher.rs
Rollup merge of #73870 - sexxi-goose:projection-ty, r=nikomatsakis
[rust.git] / src / tools / clippy / clippy_lints / src / utils / higher.rs
1 //! This module contains functions for retrieve the original AST from lowered
2 //! `hir`.
3
4 #![deny(clippy::missing_docs_in_private_items)]
5
6 use crate::utils::{is_expn_of, match_def_path, match_qpath, paths};
7 use if_chain::if_chain;
8 use rustc_ast::ast;
9 use rustc_hir as hir;
10 use rustc_lint::LateContext;
11 use rustc_middle::ty;
12
13 /// Converts a hir binary operator to the corresponding `ast` type.
14 #[must_use]
15 pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
16     match op {
17         hir::BinOpKind::Eq => ast::BinOpKind::Eq,
18         hir::BinOpKind::Ge => ast::BinOpKind::Ge,
19         hir::BinOpKind::Gt => ast::BinOpKind::Gt,
20         hir::BinOpKind::Le => ast::BinOpKind::Le,
21         hir::BinOpKind::Lt => ast::BinOpKind::Lt,
22         hir::BinOpKind::Ne => ast::BinOpKind::Ne,
23         hir::BinOpKind::Or => ast::BinOpKind::Or,
24         hir::BinOpKind::Add => ast::BinOpKind::Add,
25         hir::BinOpKind::And => ast::BinOpKind::And,
26         hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
27         hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
28         hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
29         hir::BinOpKind::Div => ast::BinOpKind::Div,
30         hir::BinOpKind::Mul => ast::BinOpKind::Mul,
31         hir::BinOpKind::Rem => ast::BinOpKind::Rem,
32         hir::BinOpKind::Shl => ast::BinOpKind::Shl,
33         hir::BinOpKind::Shr => ast::BinOpKind::Shr,
34         hir::BinOpKind::Sub => ast::BinOpKind::Sub,
35     }
36 }
37
38 /// Represent a range akin to `ast::ExprKind::Range`.
39 #[derive(Debug, Copy, Clone)]
40 pub struct Range<'a> {
41     /// The lower bound of the range, or `None` for ranges such as `..X`.
42     pub start: Option<&'a hir::Expr<'a>>,
43     /// The upper bound of the range, or `None` for ranges such as `X..`.
44     pub end: Option<&'a hir::Expr<'a>>,
45     /// Whether the interval is open or closed.
46     pub limits: ast::RangeLimits,
47 }
48
49 /// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
50 pub fn range<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
51     /// Finds the field named `name` in the field. Always return `Some` for
52     /// convenience.
53     fn get_field<'c>(name: &str, fields: &'c [hir::Field<'_>]) -> Option<&'c hir::Expr<'c>> {
54         let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
55
56         Some(expr)
57     }
58
59     let def_path = match cx.tables().expr_ty(expr).kind {
60         ty::Adt(def, _) => cx.tcx.def_path(def.did),
61         _ => return None,
62     };
63
64     // sanity checks for std::ops::RangeXXXX
65     if def_path.data.len() != 3 {
66         return None;
67     }
68     if def_path.data.get(0)?.data.as_symbol() != sym!(ops) {
69         return None;
70     }
71     if def_path.data.get(1)?.data.as_symbol() != sym!(range) {
72         return None;
73     }
74     let type_name = def_path.data.get(2)?.data.as_symbol();
75     let range_types = [
76         "RangeFrom",
77         "RangeFull",
78         "RangeInclusive",
79         "Range",
80         "RangeTo",
81         "RangeToInclusive",
82     ];
83     if !range_types.contains(&&*type_name.as_str()) {
84         return None;
85     }
86
87     // The range syntax is expanded to literal paths starting with `core` or `std`
88     // depending on
89     // `#[no_std]`. Testing both instead of resolving the paths.
90
91     match expr.kind {
92         hir::ExprKind::Path(ref path) => {
93             if match_qpath(path, &paths::RANGE_FULL_STD) || match_qpath(path, &paths::RANGE_FULL) {
94                 Some(Range {
95                     start: None,
96                     end: None,
97                     limits: ast::RangeLimits::HalfOpen,
98                 })
99             } else {
100                 None
101             }
102         },
103         hir::ExprKind::Call(ref path, ref args) => {
104             if let hir::ExprKind::Path(ref path) = path.kind {
105                 if match_qpath(path, &paths::RANGE_INCLUSIVE_STD_NEW) || match_qpath(path, &paths::RANGE_INCLUSIVE_NEW)
106                 {
107                     Some(Range {
108                         start: Some(&args[0]),
109                         end: Some(&args[1]),
110                         limits: ast::RangeLimits::Closed,
111                     })
112                 } else {
113                     None
114                 }
115             } else {
116                 None
117             }
118         },
119         hir::ExprKind::Struct(ref path, ref fields, None) => {
120             if match_qpath(path, &paths::RANGE_FROM_STD) || match_qpath(path, &paths::RANGE_FROM) {
121                 Some(Range {
122                     start: Some(get_field("start", fields)?),
123                     end: None,
124                     limits: ast::RangeLimits::HalfOpen,
125                 })
126             } else if match_qpath(path, &paths::RANGE_STD) || match_qpath(path, &paths::RANGE) {
127                 Some(Range {
128                     start: Some(get_field("start", fields)?),
129                     end: Some(get_field("end", fields)?),
130                     limits: ast::RangeLimits::HalfOpen,
131                 })
132             } else if match_qpath(path, &paths::RANGE_TO_INCLUSIVE_STD) || match_qpath(path, &paths::RANGE_TO_INCLUSIVE)
133             {
134                 Some(Range {
135                     start: None,
136                     end: Some(get_field("end", fields)?),
137                     limits: ast::RangeLimits::Closed,
138                 })
139             } else if match_qpath(path, &paths::RANGE_TO_STD) || match_qpath(path, &paths::RANGE_TO) {
140                 Some(Range {
141                     start: None,
142                     end: Some(get_field("end", fields)?),
143                     limits: ast::RangeLimits::HalfOpen,
144                 })
145             } else {
146                 None
147             }
148         },
149         _ => None,
150     }
151 }
152
153 /// Checks if a `let` statement is from a `for` loop desugaring.
154 pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
155     // This will detect plain for-loops without an actual variable binding:
156     //
157     // ```
158     // for x in some_vec {
159     //     // do stuff
160     // }
161     // ```
162     if_chain! {
163         if let Some(ref expr) = local.init;
164         if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
165         then {
166             return true;
167         }
168     }
169
170     // This detects a variable binding in for loop to avoid `let_unit_value`
171     // lint (see issue #1964).
172     //
173     // ```
174     // for _ in vec![()] {
175     //     // anything
176     // }
177     // ```
178     if let hir::LocalSource::ForLoopDesugar = local.source {
179         return true;
180     }
181
182     false
183 }
184
185 /// Recover the essential nodes of a desugared for loop:
186 /// `for pat in arg { body }` becomes `(pat, arg, body)`.
187 pub fn for_loop<'tcx>(
188     expr: &'tcx hir::Expr<'tcx>,
189 ) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
190     if_chain! {
191         if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
192         if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
193         if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none();
194         if let hir::ExprKind::Loop(ref block, _, _) = arms[0].body.kind;
195         if block.expr.is_none();
196         if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
197         if let hir::StmtKind::Local(ref local) = let_stmt.kind;
198         if let hir::StmtKind::Expr(ref expr) = body.kind;
199         then {
200             return Some((&*local.pat, &iterargs[0], expr));
201         }
202     }
203     None
204 }
205
206 /// Recover the essential nodes of a desugared while loop:
207 /// `while cond { body }` becomes `(cond, body)`.
208 pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
209     if_chain! {
210         if let hir::ExprKind::Loop(block, _, hir::LoopSource::While) = &expr.kind;
211         if let hir::Block { expr: Some(expr), .. } = &**block;
212         if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
213         if let hir::ExprKind::DropTemps(cond) = &cond.kind;
214         if let [arm, ..] = &arms[..];
215         if let hir::Arm { body, .. } = arm;
216         then {
217             return Some((cond, body));
218         }
219     }
220     None
221 }
222
223 /// Recover the essential nodes of a desugared if block
224 /// `if cond { then } else { els }` becomes `(cond, then, Some(els))`
225 pub fn if_block<'tcx>(
226     expr: &'tcx hir::Expr<'tcx>,
227 ) -> Option<(
228     &'tcx hir::Expr<'tcx>,
229     &'tcx hir::Expr<'tcx>,
230     Option<&'tcx hir::Expr<'tcx>>,
231 )> {
232     if let hir::ExprKind::Match(ref cond, ref arms, hir::MatchSource::IfDesugar { contains_else_clause }) = expr.kind {
233         let cond = if let hir::ExprKind::DropTemps(ref cond) = cond.kind {
234             cond
235         } else {
236             panic!("If block desugar must contain DropTemps");
237         };
238         let then = &arms[0].body;
239         let els = if contains_else_clause {
240             Some(&*arms[1].body)
241         } else {
242             None
243         };
244         Some((cond, then, els))
245     } else {
246         None
247     }
248 }
249
250 /// Represent the pre-expansion arguments of a `vec!` invocation.
251 pub enum VecArgs<'a> {
252     /// `vec![elem; len]`
253     Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
254     /// `vec![a, b, c]`
255     Vec(&'a [hir::Expr<'a>]),
256 }
257
258 /// Returns the arguments of the `vec!` macro if this expression was expanded
259 /// from `vec!`.
260 pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<VecArgs<'e>> {
261     if_chain! {
262         if let hir::ExprKind::Call(ref fun, ref args) = expr.kind;
263         if let hir::ExprKind::Path(ref qpath) = fun.kind;
264         if is_expn_of(fun.span, "vec").is_some();
265         if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
266         then {
267             return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
268                 // `vec![elem; size]` case
269                 Some(VecArgs::Repeat(&args[0], &args[1]))
270             }
271             else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
272                 // `vec![a, b, c]` case
273                 if_chain! {
274                     if let hir::ExprKind::Box(ref boxed) = args[0].kind;
275                     if let hir::ExprKind::Array(ref args) = boxed.kind;
276                     then {
277                         return Some(VecArgs::Vec(&*args));
278                     }
279                 }
280
281                 None
282             }
283             else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
284                 Some(VecArgs::Vec(&[]))
285             }
286             else {
287                 None
288             };
289         }
290     }
291
292     None
293 }