]> git.lizzy.rs Git - rust.git/blob - src/utils.rs
Merge branch 'pr-482'
[rust.git] / src / utils.rs
1 use rustc::lint::*;
2 use rustc_front::hir::*;
3 use reexport::*;
4 use syntax::codemap::{ExpnInfo, Span, ExpnFormat};
5 use rustc::front::map::Node::*;
6 use rustc::middle::def_id::DefId;
7 use rustc::middle::ty;
8 use std::borrow::Cow;
9 use syntax::ast::Lit_::*;
10 use syntax::ast;
11
12 use rustc::session::Session;
13 use std::str::FromStr;
14
15 // module DefPaths for certain structs/enums we check for
16 pub const OPTION_PATH: [&'static str; 3] = ["core", "option", "Option"];
17 pub const RESULT_PATH: [&'static str; 3] = ["core", "result", "Result"];
18 pub const STRING_PATH: [&'static str; 3] = ["collections", "string", "String"];
19 pub const VEC_PATH:    [&'static str; 3] = ["collections", "vec", "Vec"];
20 pub const LL_PATH:     [&'static str; 3] = ["collections", "linked_list", "LinkedList"];
21 pub const OPEN_OPTIONS_PATH: [&'static str; 3] = ["std", "fs", "OpenOptions"];
22 pub const MUTEX_PATH:  [&'static str; 4] = ["std", "sync", "mutex", "Mutex"];
23 pub const CLONE_PATH:  [&'static str; 2] = ["Clone", "clone"];
24
25 /// Produce a nested chain of if-lets and ifs from the patterns:
26 ///
27 ///     if_let_chain! {
28 ///         [
29 ///             let Some(y) = x,
30 ///             y.len() == 2,
31 ///             let Some(z) = y,
32 ///         ],
33 ///         {
34 ///             block
35 ///         }
36 ///     }
37 ///
38 /// becomes
39 ///
40 ///     if let Some(y) = x {
41 ///         if y.len() == 2 {
42 ///             if let Some(z) = y {
43 ///                 block
44 ///             }
45 ///         }
46 ///     }
47 #[macro_export]
48 macro_rules! if_let_chain {
49     ([let $pat:pat = $expr:expr, $($tt:tt)+], $block:block) => {
50         if let $pat = $expr {
51            if_let_chain!{ [$($tt)+], $block }
52         }
53     };
54     ([let $pat:pat = $expr:expr], $block:block) => {
55         if let $pat = $expr {
56            $block
57         }
58     };
59     ([$expr:expr, $($tt:tt)+], $block:block) => {
60         if $expr {
61            if_let_chain!{ [$($tt)+], $block }
62         }
63     };
64     ([$expr:expr], $block:block) => {
65         if $expr {
66            $block
67         }
68     };
69 }
70
71 /// returns true if this expn_info was expanded by any macro
72 pub fn in_macro<T: LintContext>(cx: &T, span: Span) -> bool {
73     cx.sess().codemap().with_expn_info(span.expn_id,
74             |info| info.is_some())
75 }
76
77 /// returns true if the macro that expanded the crate was outside of
78 /// the current crate or was a compiler plugin
79 pub fn in_external_macro<T: LintContext>(cx: &T, span: Span) -> bool {
80     /// invokes in_macro with the expansion info of the given span
81     /// slightly heavy, try to use this after other checks have already happened
82     fn in_macro_ext<T: LintContext>(cx: &T, opt_info: Option<&ExpnInfo>) -> bool {
83         // no ExpnInfo = no macro
84         opt_info.map_or(false, |info| {
85             if let ExpnFormat::MacroAttribute(..) = info.callee.format {
86                     // these are all plugins
87                     return true;
88             }
89             // no span for the callee = external macro
90             info.callee.span.map_or(true, |span| {
91                 // no snippet = external macro or compiler-builtin expansion
92                 cx.sess().codemap().span_to_snippet(span).ok().map_or(true, |code|
93                     // macro doesn't start with "macro_rules"
94                     // = compiler plugin
95                     !code.starts_with("macro_rules")
96                 )
97             })
98         })
99     }
100
101     cx.sess().codemap().with_expn_info(span.expn_id,
102             |info| in_macro_ext(cx, info))
103 }
104
105 /// check if a DefId's path matches the given absolute type path
106 /// usage e.g. with
107 /// `match_def_path(cx, id, &["core", "option", "Option"])`
108 pub fn match_def_path(cx: &LateContext, def_id: DefId, path: &[&str]) -> bool {
109     cx.tcx.with_path(def_id, |iter| iter.zip(path)
110                                         .all(|(nm, p)| nm.name().as_str() == *p))
111 }
112
113 /// check if type is struct or enum type with given def path
114 pub fn match_type(cx: &LateContext, ty: ty::Ty, path: &[&str]) -> bool {
115     match ty.sty {
116         ty::TyEnum(ref adt, _) | ty::TyStruct(ref adt, _) => {
117             match_def_path(cx, adt.did, path)
118         }
119         _ => {
120             false
121         }
122     }
123 }
124
125 /// check if method call given in "expr" belongs to given trait
126 pub fn match_impl_method(cx: &LateContext, expr: &Expr, path: &[&str]) -> bool {
127     let method_call = ty::MethodCall::expr(expr.id);
128
129     let trt_id = cx.tcx.tables
130                        .borrow().method_map.get(&method_call)
131                        .and_then(|callee| cx.tcx.impl_of_method(callee.def_id));
132     if let Some(trt_id) = trt_id {
133         match_def_path(cx, trt_id, path)
134     } else {
135         false
136     }
137 }
138 /// check if method call given in "expr" belongs to given trait
139 pub fn match_trait_method(cx: &LateContext, expr: &Expr, path: &[&str]) -> bool {
140     let method_call = ty::MethodCall::expr(expr.id);
141     let trt_id = cx.tcx.tables
142                        .borrow().method_map.get(&method_call)
143                        .and_then(|callee| cx.tcx.trait_of_item(callee.def_id));
144     if let Some(trt_id) = trt_id {
145         match_def_path(cx, trt_id, path)
146     } else {
147         false
148     }
149 }
150
151 /// match a Path against a slice of segment string literals, e.g.
152 /// `match_path(path, &["std", "rt", "begin_unwind"])`
153 pub fn match_path(path: &Path, segments: &[&str]) -> bool {
154     path.segments.iter().rev().zip(segments.iter().rev()).all(
155         |(a, b)| a.identifier.name.as_str() == *b)
156 }
157
158 /// match a Path against a slice of segment string literals, e.g.
159 /// `match_path(path, &["std", "rt", "begin_unwind"])`
160 pub fn match_path_ast(path: &ast::Path, segments: &[&str]) -> bool {
161     path.segments.iter().rev().zip(segments.iter().rev()).all(
162         |(a, b)| a.identifier.name.as_str() == *b)
163 }
164
165 /// get the name of the item the expression is in, if available
166 pub fn get_item_name(cx: &LateContext, expr: &Expr) -> Option<Name> {
167     let parent_id = cx.tcx.map.get_parent(expr.id);
168     match cx.tcx.map.find(parent_id) {
169         Some(NodeItem(&Item{ ref name, .. })) |
170         Some(NodeTraitItem(&TraitItem{ ref name, .. })) |
171         Some(NodeImplItem(&ImplItem{ ref name, .. })) => {
172             Some(*name)
173         }
174         _ => None,
175     }
176 }
177
178 /// checks if a `let` decl is from a for loop desugaring
179 pub fn is_from_for_desugar(decl: &Decl) -> bool {
180     if_let_chain! {
181         [
182             let DeclLocal(ref loc) = decl.node,
183             let Some(ref expr) = loc.init,
184             let ExprMatch(_, _, MatchSource::ForLoopDesugar) = expr.node
185         ],
186         { return true; }
187     };
188     false
189 }
190
191
192 /// convert a span to a code snippet if available, otherwise use default, e.g.
193 /// `snippet(cx, expr.span, "..")`
194 pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
195     cx.sess().codemap().span_to_snippet(span).map(From::from).unwrap_or(Cow::Borrowed(default))
196 }
197
198 /// convert a span (from a block) to a code snippet if available, otherwise use default, e.g.
199 /// `snippet(cx, expr.span, "..")`
200 /// This trims the code of indentation, except for the first line
201 /// Use it for blocks or block-like things which need to be printed as such
202 pub fn snippet_block<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<'a, str> {
203     let snip = snippet(cx, span, default);
204     trim_multiline(snip, true)
205 }
206
207 /// Like snippet_block, but add braces if the expr is not an ExprBlock
208 /// Also takes an Option<String> which can be put inside the braces
209 pub fn expr_block<'a, T: LintContext>(cx: &T, expr: &Expr,
210                                       option: Option<String>,
211                                       default: &'a str) -> Cow<'a, str> {
212     let code = snippet_block(cx, expr.span, default);
213     let string = option.map_or("".to_owned(), |s| s);
214     if let ExprBlock(_) = expr.node {
215         Cow::Owned(format!("{}{}", code, string))
216     } else if string.is_empty() {
217         Cow::Owned(format!("{{ {} }}", code))
218     } else {
219         Cow::Owned(format!("{{\n{};\n{}\n}}", code, string))
220     }
221 }
222
223 /// Trim indentation from a multiline string
224 /// with possibility of ignoring the first line
225 pub fn trim_multiline(s: Cow<str>, ignore_first: bool) -> Cow<str> {
226     let s_space = trim_multiline_inner(s, ignore_first, ' ');
227     let s_tab = trim_multiline_inner(s_space, ignore_first, '\t');
228     trim_multiline_inner(s_tab, ignore_first, ' ')
229 }
230
231 fn trim_multiline_inner(s: Cow<str>, ignore_first: bool, ch: char) -> Cow<str> {
232     let x = s.lines().skip(ignore_first as usize)
233              .filter_map(|l| { if l.len() > 0 { // ignore empty lines
234                                 Some(l.char_indices()
235                                       .find(|&(_,x)| x != ch)
236                                       .unwrap_or((l.len(), ch)).0)
237                                } else {None}})
238              .min().unwrap_or(0);
239     if x > 0 {
240         Cow::Owned(s.lines().enumerate().map(|(i,l)| if (ignore_first && i == 0) ||
241                                                          l.len() == 0 {
242                                                         l
243                                                      } else {
244                                                         l.split_at(x).1
245                                                      }).collect::<Vec<_>>()
246                                        .join("\n"))
247     } else {
248         s
249     }
250 }
251
252 /// get a parent expr if any – this is useful to constrain a lint
253 pub fn get_parent_expr<'c>(cx: &'c LateContext, e: &Expr) -> Option<&'c Expr> {
254     let map = &cx.tcx.map;
255     let node_id : NodeId = e.id;
256     let parent_id : NodeId = map.get_parent_node(node_id);
257     if node_id == parent_id { return None; }
258     map.find(parent_id).and_then(|node|
259         if let NodeExpr(parent) = node { Some(parent) } else { None } )
260 }
261
262 pub fn get_enclosing_block<'c>(cx: &'c LateContext, node: NodeId) -> Option<&'c Block> {
263     let map = &cx.tcx.map;
264     let enclosing_node = map.get_enclosing_scope(node)
265                             .and_then(|enclosing_id| map.find(enclosing_id));
266     if let Some(node) = enclosing_node {
267         match node {
268             NodeBlock(ref block) => Some(block),
269             NodeItem(&Item{ node: ItemFn(_, _, _, _, _, ref block), .. }) => Some(block),
270             _ => None
271         }
272     } else { None }
273 }
274
275 #[cfg(not(feature="structured_logging"))]
276 pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: Span, msg: &str) {
277     cx.span_lint(lint, sp, msg);
278     if cx.current_level(lint) != Level::Allow {
279         cx.sess().fileline_help(sp, &format!("for further information visit \
280             https://github.com/Manishearth/rust-clippy/wiki#{}",
281             lint.name_lower()))
282     }
283 }
284
285 #[cfg(feature="structured_logging")]
286 pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: Span, msg: &str) {
287     // lint.name / lint.desc is can give details of the lint
288     // cx.sess().codemap() has all these nice functions for line/column/snippet details
289     // http://doc.rust-lang.org/syntax/codemap/struct.CodeMap.html#method.span_to_string
290     cx.span_lint(lint, sp, msg);
291     if cx.current_level(lint) != Level::Allow {
292         cx.sess().fileline_help(sp, &format!("for further information visit \
293             https://github.com/Manishearth/rust-clippy/wiki#{}",
294             lint.name_lower()))
295     }
296 }
297
298 pub fn span_help_and_lint<T: LintContext>(cx: &T, lint: &'static Lint, span: Span,
299         msg: &str, help: &str) {
300     cx.span_lint(lint, span, msg);
301     if cx.current_level(lint) != Level::Allow {
302         cx.sess().fileline_help(span, &format!("{}\nfor further information \
303             visit https://github.com/Manishearth/rust-clippy/wiki#{}",
304             help, lint.name_lower()))
305     }
306 }
307
308 pub fn span_note_and_lint<T: LintContext>(cx: &T, lint: &'static Lint, span: Span,
309         msg: &str, note_span: Span, note: &str) {
310     cx.span_lint(lint, span, msg);
311     if cx.current_level(lint) != Level::Allow {
312         if note_span == span {
313             cx.sess().fileline_note(note_span, note)
314         } else {
315             cx.sess().span_note(note_span, note)
316         }
317         cx.sess().fileline_help(span, &format!("for further information visit \
318             https://github.com/Manishearth/rust-clippy/wiki#{}",
319             lint.name_lower()))
320     }
321 }
322
323 /// return the base type for references and raw pointers
324 pub fn walk_ptrs_ty(ty: ty::Ty) -> ty::Ty {
325     match ty.sty {
326         ty::TyRef(_, ref tm) | ty::TyRawPtr(ref tm) => walk_ptrs_ty(tm.ty),
327         _ => ty
328     }
329 }
330
331 /// return the base type for references and raw pointers, and count reference depth
332 pub fn walk_ptrs_ty_depth(ty: ty::Ty) -> (ty::Ty, usize) {
333     fn inner(ty: ty::Ty, depth: usize) -> (ty::Ty, usize) {
334         match ty.sty {
335             ty::TyRef(_, ref tm) | ty::TyRawPtr(ref tm) => inner(tm.ty, depth + 1),
336             _ => (ty, depth)
337         }
338     }
339     inner(ty, 0)
340 }
341
342 pub fn is_integer_literal(expr: &Expr, value: u64) -> bool
343 {
344     // FIXME: use constant folding
345     if let ExprLit(ref spanned) = expr.node {
346         if let LitInt(v, _) = spanned.node {
347             return v == value;
348         }
349     }
350     false
351 }
352
353 pub fn is_adjusted(cx: &LateContext, e: &Expr) -> bool {
354     cx.tcx.tables.borrow().adjustments.get(&e.id).is_some()
355 }
356
357 /// Produce a nested chain of if-lets and ifs from the patterns:
358 ///
359 ///     if_let_chain! {
360 ///         [
361 ///             Some(y) = x,
362 ///             y.len() == 2,
363 ///             Some(z) = y,
364 ///         ],
365 ///         {
366 ///             block
367 ///         }
368 ///     }
369 ///
370 /// becomes
371 ///
372 ///     if let Some(y) = x {
373 ///         if y.len() == 2 {
374 ///             if let Some(z) = y {
375 ///                 block
376 ///             }
377 ///         }
378 ///     }
379 #[macro_export]
380 macro_rules! if_let_chain {
381     ([let $pat:pat = $expr:expr, $($tt:tt)+], $block:block) => {
382         if let $pat = $expr {
383            if_let_chain!{ [$($tt)+], $block }
384         }
385     };
386     ([let $pat:pat = $expr:expr], $block:block) => {
387         if let $pat = $expr {
388            $block
389         }
390     };
391     ([$expr:expr, $($tt:tt)+], $block:block) => {
392         if $expr {
393            if_let_chain!{ [$($tt)+], $block }
394         }
395     };
396     ([$expr:expr], $block:block) => {
397         if $expr {
398            $block
399         }
400     };
401 }
402
403 pub struct LimitStack {
404     stack: Vec<u64>,
405 }
406
407 impl Drop for LimitStack {
408     fn drop(&mut self) {
409         assert_eq!(self.stack.len(), 1);
410     }
411 }
412
413 impl LimitStack {
414     pub fn new(limit: u64) -> LimitStack {
415         LimitStack {
416             stack: vec![limit],
417         }
418     }
419     pub fn limit(&self) -> u64 {
420         *self.stack.last().expect("there should always be a value in the stack")
421     }
422     pub fn push_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
423         let stack = &mut self.stack;
424         parse_attrs(
425             sess,
426             attrs,
427             name,
428             |val| stack.push(val),
429         );
430     }
431     pub fn pop_attrs(&mut self, sess: &Session, attrs: &[ast::Attribute], name: &'static str) {
432         let stack = &mut self.stack;
433         parse_attrs(
434             sess,
435             attrs,
436             name,
437             |val| assert_eq!(stack.pop(), Some(val)),
438         );
439     }
440 }
441
442 fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'static str, mut f: F) {
443     for attr in attrs {
444         let attr = &attr.node;
445         if attr.is_sugared_doc { continue; }
446         if let ast::MetaNameValue(ref key, ref value) = attr.value.node {
447             if *key == name {
448                 if let LitStr(ref s, _) = value.node {
449                     if let Ok(value) = FromStr::from_str(s) {
450                         f(value)
451                     } else {
452                         sess.span_err(value.span, "not a number");
453                     }
454                 } else {
455                     unreachable!()
456                 }
457             }
458         }
459     }
460 }