]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/lifetimes.rs
New upstream version 1.23.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / lifetimes.rs
CommitLineData
ea8adc8c
XL
1use reexport::*;
2use rustc::lint::*;
3use rustc::hir::def::Def;
4use rustc::hir::*;
abe05a73
XL
5use rustc::hir::intravisit::{walk_fn_decl, walk_generics, walk_ty, walk_ty_param_bound, NestedVisitorMap, Visitor};
6use std::collections::{HashMap, HashSet};
ea8adc8c 7use syntax::codemap::Span;
abe05a73 8use utils::{in_external_macro, last_path_segment, span_lint};
ea8adc8c
XL
9use syntax::symbol::keywords;
10
11/// **What it does:** Checks for lifetime annotations which can be removed by
12/// relying on lifetime elision.
13///
14/// **Why is this bad?** The additional lifetimes make the code look more
15/// complicated, while there is nothing out of the ordinary going on. Removing
16/// them leads to more readable code.
17///
18/// **Known problems:** Potential false negatives: we bail out if the function
19/// has a `where` clause where lifetimes are mentioned.
20///
21/// **Example:**
22/// ```rust
23/// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { x }
24/// ```
25declare_lint! {
26 pub NEEDLESS_LIFETIMES,
27 Warn,
28 "using explicit lifetimes for references in function arguments when elision rules \
29 would allow omitting them"
30}
31
32/// **What it does:** Checks for lifetimes in generics that are never used
33/// anywhere else.
34///
35/// **Why is this bad?** The additional lifetimes make the code look more
36/// complicated, while there is nothing out of the ordinary going on. Removing
37/// them leads to more readable code.
38///
39/// **Known problems:** None.
40///
41/// **Example:**
42/// ```rust
43/// fn unused_lifetime<'a>(x: u8) { .. }
44/// ```
45declare_lint! {
46 pub UNUSED_LIFETIMES,
47 Warn,
48 "unused lifetimes in function definitions"
49}
50
51#[derive(Copy, Clone)]
52pub struct LifetimePass;
53
54impl LintPass for LifetimePass {
55 fn get_lints(&self) -> LintArray {
56 lint_array!(NEEDLESS_LIFETIMES, UNUSED_LIFETIMES)
57 }
58}
59
60impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LifetimePass {
61 fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
62 if let ItemFn(ref decl, _, _, _, ref generics, id) = item.node {
63 check_fn_inner(cx, decl, Some(id), generics, item.span);
64 }
65 }
66
67 fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
68 if let ImplItemKind::Method(ref sig, id) = item.node {
abe05a73 69 check_fn_inner(cx, &sig.decl, Some(id), &item.generics, item.span);
ea8adc8c
XL
70 }
71 }
72
73 fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
74 if let TraitItemKind::Method(ref sig, ref body) = item.node {
75 let body = match *body {
76 TraitMethod::Required(_) => None,
77 TraitMethod::Provided(id) => Some(id),
78 };
abe05a73 79 check_fn_inner(cx, &sig.decl, body, &item.generics, item.span);
ea8adc8c
XL
80 }
81 }
82}
83
84/// The lifetime of a &-reference.
85#[derive(PartialEq, Eq, Hash, Debug)]
86enum RefLt {
87 Unnamed,
88 Static,
89 Named(Name),
90}
91
92fn check_fn_inner<'a, 'tcx>(
93 cx: &LateContext<'a, 'tcx>,
94 decl: &'tcx FnDecl,
95 body: Option<BodyId>,
96 generics: &'tcx Generics,
97 span: Span,
98) {
99 if in_external_macro(cx, span) || has_where_lifetimes(cx, &generics.where_clause) {
100 return;
101 }
102
103 let mut bounds_lts = Vec::new();
104 for typ in &generics.ty_params {
105 for bound in &typ.bounds {
106 if let TraitTyParamBound(ref trait_ref, _) = *bound {
abe05a73 107 let params = &trait_ref
ea8adc8c
XL
108 .trait_ref
109 .path
110 .segments
111 .last()
112 .expect("a path must have at least one segment")
abe05a73
XL
113 .parameters;
114 if let Some(ref params) = *params {
115 for bound in &params.lifetimes {
116 if bound.name.name() != "'static" && !bound.is_elided() {
117 return;
118 }
119 bounds_lts.push(bound);
ea8adc8c 120 }
ea8adc8c
XL
121 }
122 }
123 }
124 }
125 if could_use_elision(cx, decl, body, &generics.lifetimes, bounds_lts) {
126 span_lint(
127 cx,
128 NEEDLESS_LIFETIMES,
129 span,
130 "explicit lifetimes given in parameter types where they could be elided",
131 );
132 }
133 report_extra_lifetimes(cx, decl, generics);
134}
135
136fn could_use_elision<'a, 'tcx: 'a>(
137 cx: &LateContext<'a, 'tcx>,
138 func: &'tcx FnDecl,
139 body: Option<BodyId>,
140 named_lts: &'tcx [LifetimeDef],
141 bounds_lts: Vec<&'tcx Lifetime>,
142) -> bool {
143 // There are two scenarios where elision works:
144 // * no output references, all input references have different LT
145 // * output references, exactly one input reference with same LT
146 // All lifetimes must be unnamed, 'static or defined without bounds on the
147 // level of the current item.
148
149 // check named LTs
150 let allowed_lts = allowed_lts_from(named_lts);
151
152 // these will collect all the lifetimes for references in arg/return types
153 let mut input_visitor = RefVisitor::new(cx);
154 let mut output_visitor = RefVisitor::new(cx);
155
156 // extract lifetimes in input argument types
157 for arg in &func.inputs {
158 input_visitor.visit_ty(arg);
159 }
160 // extract lifetimes in output type
161 if let Return(ref ty) = func.output {
162 output_visitor.visit_ty(ty);
163 }
164
165 let input_lts = match input_visitor.into_vec() {
166 Some(lts) => lts_from_bounds(lts, bounds_lts.into_iter()),
167 None => return false,
168 };
169 let output_lts = match output_visitor.into_vec() {
170 Some(val) => val,
171 None => return false,
172 };
173
174 if let Some(body_id) = body {
abe05a73
XL
175 let mut checker = BodyLifetimeChecker {
176 lifetimes_used_in_body: false,
177 };
ea8adc8c
XL
178 checker.visit_expr(&cx.tcx.hir.body(body_id).value);
179 if checker.lifetimes_used_in_body {
180 return false;
181 }
182 }
183
184 // check for lifetimes from higher scopes
185 for lt in input_lts.iter().chain(output_lts.iter()) {
186 if !allowed_lts.contains(lt) {
187 return false;
188 }
189 }
190
191 // no input lifetimes? easy case!
192 if input_lts.is_empty() {
193 false
194 } else if output_lts.is_empty() {
195 // no output lifetimes, check distinctness of input lifetimes
196
197 // only unnamed and static, ok
abe05a73
XL
198 let unnamed_and_static = input_lts
199 .iter()
200 .all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static);
ea8adc8c
XL
201 if unnamed_and_static {
202 return false;
203 }
204 // we have no output reference, so we only need all distinct lifetimes
205 input_lts.len() == unique_lifetimes(&input_lts)
206 } else {
207 // we have output references, so we need one input reference,
208 // and all output lifetimes must be the same
209 if unique_lifetimes(&output_lts) > 1 {
210 return false;
211 }
212 if input_lts.len() == 1 {
213 match (&input_lts[0], &output_lts[0]) {
214 (&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
215 (&RefLt::Named(_), &RefLt::Unnamed) => true,
abe05a73
XL
216 _ => false, /* already elided, different named lifetimes
217 * or something static going on */
ea8adc8c
XL
218 }
219 } else {
220 false
221 }
222 }
223}
224
225fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
226 let mut allowed_lts = HashSet::new();
227 for lt in named_lts {
228 if lt.bounds.is_empty() {
abe05a73 229 allowed_lts.insert(RefLt::Named(lt.lifetime.name.name()));
ea8adc8c
XL
230 }
231 }
232 allowed_lts.insert(RefLt::Unnamed);
233 allowed_lts.insert(RefLt::Static);
234 allowed_lts
235}
236
237fn lts_from_bounds<'a, T: Iterator<Item = &'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
238 for lt in bounds_lts {
abe05a73
XL
239 if lt.name.name() != "'static" {
240 vec.push(RefLt::Named(lt.name.name()));
ea8adc8c
XL
241 }
242 }
243
244 vec
245}
246
247/// Number of unique lifetimes in the given vector.
248fn unique_lifetimes(lts: &[RefLt]) -> usize {
249 lts.iter().collect::<HashSet<_>>().len()
250}
251
252/// A visitor usable for `rustc_front::visit::walk_ty()`.
253struct RefVisitor<'a, 'tcx: 'a> {
254 cx: &'a LateContext<'a, 'tcx>,
255 lts: Vec<RefLt>,
256 abort: bool,
257}
258
259impl<'v, 't> RefVisitor<'v, 't> {
260 fn new(cx: &'v LateContext<'v, 't>) -> Self {
261 Self {
262 cx: cx,
263 lts: Vec::new(),
264 abort: false,
265 }
266 }
267
268 fn record(&mut self, lifetime: &Option<Lifetime>) {
269 if let Some(ref lt) = *lifetime {
abe05a73 270 if lt.name.name() == "'static" {
ea8adc8c
XL
271 self.lts.push(RefLt::Static);
272 } else if lt.is_elided() {
273 self.lts.push(RefLt::Unnamed);
274 } else {
abe05a73 275 self.lts.push(RefLt::Named(lt.name.name()));
ea8adc8c
XL
276 }
277 } else {
278 self.lts.push(RefLt::Unnamed);
279 }
280 }
281
282 fn into_vec(self) -> Option<Vec<RefLt>> {
abe05a73
XL
283 if self.abort {
284 None
285 } else {
286 Some(self.lts)
287 }
ea8adc8c
XL
288 }
289
290 fn collect_anonymous_lifetimes(&mut self, qpath: &QPath, ty: &Ty) {
abe05a73
XL
291 if let Some(ref last_path_segment) = last_path_segment(qpath).parameters {
292 if !last_path_segment.parenthesized && last_path_segment.lifetimes.is_empty() {
293 let hir_id = self.cx.tcx.hir.node_to_hir_id(ty.id);
294 match self.cx.tables.qpath_def(qpath, hir_id) {
295 Def::TyAlias(def_id) | Def::Struct(def_id) => {
296 let generics = self.cx.tcx.generics_of(def_id);
297 for _ in generics.regions.as_slice() {
298 self.record(&None);
299 }
300 },
301 Def::Trait(def_id) => {
302 let trait_def = self.cx.tcx.trait_def(def_id);
303 for _ in &self.cx.tcx.generics_of(trait_def.def_id).regions {
304 self.record(&None);
305 }
306 },
307 _ => (),
308 }
ea8adc8c
XL
309 }
310 }
311 }
312}
313
314impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> {
315 // for lifetimes as parameters of generics
316 fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
317 self.record(&Some(*lifetime));
318 }
319
320 fn visit_ty(&mut self, ty: &'tcx Ty) {
321 match ty.node {
322 TyRptr(ref lt, _) if lt.is_elided() => {
323 self.record(&None);
324 },
325 TyPath(ref path) => {
326 self.collect_anonymous_lifetimes(path, ty);
327 },
abe05a73
XL
328 TyImplTraitExistential(ref param_bounds) |
329 TyImplTraitUniversal(_, ref param_bounds) => for bound in param_bounds {
330 if let RegionTyParamBound(_) = *bound {
331 self.record(&None);
ea8adc8c
XL
332 }
333 },
334 TyTraitObject(ref bounds, ref lt) => {
335 if !lt.is_elided() {
336 self.abort = true;
337 }
338 for bound in bounds {
339 self.visit_poly_trait_ref(bound, TraitBoundModifier::None);
340 }
341 return;
342 },
343 _ => (),
344 }
345 walk_ty(self, ty);
346 }
347 fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
348 NestedVisitorMap::None
349 }
350}
351
352/// Are any lifetimes mentioned in the `where` clause? If yes, we don't try to
353/// reason about elision.
354fn has_where_lifetimes<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, where_clause: &'tcx WhereClause) -> bool {
355 for predicate in &where_clause.predicates {
356 match *predicate {
357 WherePredicate::RegionPredicate(..) => return true,
358 WherePredicate::BoundPredicate(ref pred) => {
359 // a predicate like F: Trait or F: for<'a> Trait<'a>
360 let mut visitor = RefVisitor::new(cx);
361 // walk the type F, it may not contain LT refs
362 walk_ty(&mut visitor, &pred.bounded_ty);
363 if !visitor.lts.is_empty() {
364 return true;
365 }
366 // if the bounds define new lifetimes, they are fine to occur
367 let allowed_lts = allowed_lts_from(&pred.bound_lifetimes);
368 // now walk the bounds
369 for bound in pred.bounds.iter() {
370 walk_ty_param_bound(&mut visitor, bound);
371 }
372 // and check that all lifetimes are allowed
373 match visitor.into_vec() {
374 None => return false,
abe05a73
XL
375 Some(lts) => for lt in lts {
376 if !allowed_lts.contains(&lt) {
377 return true;
ea8adc8c
XL
378 }
379 },
380 }
381 },
382 WherePredicate::EqPredicate(ref pred) => {
383 let mut visitor = RefVisitor::new(cx);
384 walk_ty(&mut visitor, &pred.lhs_ty);
385 walk_ty(&mut visitor, &pred.rhs_ty);
386 if !visitor.lts.is_empty() {
387 return true;
388 }
389 },
390 }
391 }
392 false
393}
394
395struct LifetimeChecker {
396 map: HashMap<Name, Span>,
397}
398
399impl<'tcx> Visitor<'tcx> for LifetimeChecker {
400 // for lifetimes as parameters of generics
401 fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
abe05a73 402 self.map.remove(&lifetime.name.name());
ea8adc8c
XL
403 }
404
405 fn visit_lifetime_def(&mut self, _: &'tcx LifetimeDef) {
406 // don't actually visit `<'a>` or `<'a: 'b>`
407 // we've already visited the `'a` declarations and
408 // don't want to spuriously remove them
409 // `'b` in `'a: 'b` is useless unless used elsewhere in
410 // a non-lifetime bound
411 }
412 fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
413 NestedVisitorMap::None
414 }
415}
416
417fn report_extra_lifetimes<'a, 'tcx: 'a>(cx: &LateContext<'a, 'tcx>, func: &'tcx FnDecl, generics: &'tcx Generics) {
418 let hs = generics
419 .lifetimes
420 .iter()
abe05a73 421 .map(|lt| (lt.lifetime.name.name(), lt.lifetime.span))
ea8adc8c
XL
422 .collect();
423 let mut checker = LifetimeChecker { map: hs };
424
425 walk_generics(&mut checker, generics);
426 walk_fn_decl(&mut checker, func);
427
428 for &v in checker.map.values() {
429 span_lint(cx, UNUSED_LIFETIMES, v, "this lifetime isn't used in the function definition");
430 }
431}
432
433struct BodyLifetimeChecker {
434 lifetimes_used_in_body: bool,
435}
436
437impl<'tcx> Visitor<'tcx> for BodyLifetimeChecker {
438 // for lifetimes as parameters of generics
439 fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
abe05a73 440 if lifetime.name.name() != keywords::Invalid.name() && lifetime.name.name() != "'static" {
ea8adc8c
XL
441 self.lifetimes_used_in_body = true;
442 }
443 }
444
445 fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
446 NestedVisitorMap::None
447 }
448}