Skip to content

Fix false positive with NEEDLESS_LIFETIMES and some cleanup #599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/collapsible_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use syntax::codemap::Spanned;
use utils::{in_macro, snippet, snippet_block, span_lint_and_then};

/// **What it does:** This lint checks for nested `if`-statements which can be collapsed by
/// `&&`-combining their conditions and for `else { if .. } expressions that can be collapsed to
/// `&&`-combining their conditions and for `else { if .. }` expressions that can be collapsed to
/// `else if ..`. It is `Warn` by default.
///
/// **Why is this bad?** Each `if`-statement adds one level of nesting, which makes code look more complex than it really is.
Expand Down
2 changes: 2 additions & 0 deletions src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use rustc::middle::ty::TypeVariants;
/// impl PartialEq for Foo {
/// ..
/// }
/// ```
declare_lint! {
pub DERIVE_HASH_NOT_EQ,
Warn,
Expand All @@ -52,6 +53,7 @@ declare_lint! {
/// impl Clone for Foo {
/// ..
/// }
/// ```
declare_lint! {
pub EXPL_IMPL_CLONE_ON_COPY,
Warn,
Expand Down
6 changes: 3 additions & 3 deletions src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,19 @@ fn check_for_insert(cx: &LateContext, span: Span, map: &Expr, key: &Expr, expr:
], {
let help = if sole_expr {
format!("{}.entry({}).or_insert({})",
snippet(cx, map.span, ".."),
snippet(cx, map.span, "map"),
snippet(cx, params[1].span, ".."),
snippet(cx, params[2].span, ".."))
}
else {
format!("{}.entry({})",
snippet(cx, map.span, ".."),
snippet(cx, map.span, "map"),
snippet(cx, params[1].span, ".."))
};

span_lint_and_then(cx, MAP_ENTRY, span,
&format!("usage of `contains_key` followed by `insert` on `{}`", kind), |db| {
db.span_suggestion(span, "Consider using", help.clone());
db.span_suggestion(span, "Consider using", help);
});
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/items_after_statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ use syntax::attr::*;
use syntax::ast::*;
use utils::in_macro;

/// **What it does:** It `Warn`s on blocks where there are items that are declared in the middle of or after the statements
/// **What it does:** It `Warn`s on blocks where there are items that are declared in the middle of
/// or after the statements
///
/// **Why is this bad?** Items live for the entire scope they are declared in. But statements are processed in order. This might cause confusion as it's hard to figure out which item is meant in a statement.
/// **Why is this bad?** Items live for the entire scope they are declared in. But statements are
/// processed in order. This might cause confusion as it's hard to figure out which item is meant
/// in a statement.
///
/// **Known problems:** None
///
Expand All @@ -23,6 +26,7 @@ use utils::in_macro;
/// }
/// foo(); // prints "foo"
/// }
/// ```
declare_lint! { pub ITEMS_AFTER_STATEMENTS, Warn, "finds blocks where an item comes after a statement" }

pub struct ItemsAfterStatemets;
Expand Down
60 changes: 45 additions & 15 deletions src/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,30 @@ enum RefLt {
Static,
Named(Name),
}
use self::RefLt::*;

fn bound_lifetimes(bound: &TyParamBound) -> Option<HirVec<&Lifetime>> {
if let TraitTyParamBound(ref trait_ref, _) = *bound {
let lt = trait_ref.trait_ref.path.segments
.last().expect("a path must have at least one segment")
.parameters.lifetimes();

Some(lt)
} else {
None
}
}

fn check_fn_inner(cx: &LateContext, decl: &FnDecl, slf: Option<&ExplicitSelf>, generics: &Generics, span: Span) {
if in_external_macro(cx, span) || has_where_lifetimes(cx, &generics.where_clause) {
return;
}
if could_use_elision(cx, decl, slf, &generics.lifetimes) {

let bounds_lts =
generics.ty_params
.iter()
.flat_map(|ref typ| typ.bounds.iter().filter_map(bound_lifetimes).flat_map(|lts| lts));

if could_use_elision(cx, decl, slf, &generics.lifetimes, bounds_lts) {
span_lint(cx,
NEEDLESS_LIFETIMES,
span,
Expand All @@ -80,7 +97,10 @@ fn check_fn_inner(cx: &LateContext, decl: &FnDecl, slf: Option<&ExplicitSelf>, g
report_extra_lifetimes(cx, decl, &generics, slf);
}

fn could_use_elision(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>, named_lts: &[LifetimeDef]) -> bool {
fn could_use_elision<'a, T: Iterator<Item=&'a Lifetime>>(
cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>,
named_lts: &[LifetimeDef], bounds_lts: T
) -> bool {
// There are two scenarios where elision works:
// * no output references, all input references have different LT
// * output references, exactly one input reference with same LT
Expand Down Expand Up @@ -112,7 +132,7 @@ fn could_use_elision(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>
output_visitor.visit_ty(ty);
}

let input_lts = input_visitor.into_vec();
let input_lts = lts_from_bounds(input_visitor.into_vec(), bounds_lts);
let output_lts = output_visitor.into_vec();

// check for lifetimes from higher scopes
Expand All @@ -129,7 +149,7 @@ fn could_use_elision(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>
// no output lifetimes, check distinctness of input lifetimes

// only unnamed and static, ok
if input_lts.iter().all(|lt| *lt == Unnamed || *lt == Static) {
if input_lts.iter().all(|lt| *lt == RefLt::Unnamed || *lt == RefLt::Static) {
return false;
}
// we have no output reference, so we only need all distinct lifetimes
Expand All @@ -142,8 +162,8 @@ fn could_use_elision(cx: &LateContext, func: &FnDecl, slf: Option<&ExplicitSelf>
}
if input_lts.len() == 1 {
match (&input_lts[0], &output_lts[0]) {
(&Named(n1), &Named(n2)) if n1 == n2 => true,
(&Named(_), &Unnamed) => true,
(&RefLt::Named(n1), &RefLt::Named(n2)) if n1 == n2 => true,
(&RefLt::Named(_), &RefLt::Unnamed) => true,
_ => false, // already elided, different named lifetimes
// or something static going on
}
Expand All @@ -157,22 +177,32 @@ fn allowed_lts_from(named_lts: &[LifetimeDef]) -> HashSet<RefLt> {
let mut allowed_lts = HashSet::new();
for lt in named_lts {
if lt.bounds.is_empty() {
allowed_lts.insert(Named(lt.lifetime.name));
allowed_lts.insert(RefLt::Named(lt.lifetime.name));
}
}
allowed_lts.insert(Unnamed);
allowed_lts.insert(Static);
allowed_lts.insert(RefLt::Unnamed);
allowed_lts.insert(RefLt::Static);
allowed_lts
}

fn lts_from_bounds<'a, T: Iterator<Item=&'a Lifetime>>(mut vec: Vec<RefLt>, bounds_lts: T) -> Vec<RefLt> {
for lt in bounds_lts {
if lt.name.as_str() != "'static" {
vec.push(RefLt::Named(lt.name));
}
}

vec
}

/// Number of unique lifetimes in the given vector.
fn unique_lifetimes(lts: &[RefLt]) -> usize {
lts.iter().collect::<HashSet<_>>().len()
}

/// A visitor usable for rustc_front::visit::walk_ty().
/// A visitor usable for `rustc_front::visit::walk_ty()`.
struct RefVisitor<'v, 't: 'v> {
cx: &'v LateContext<'v, 't>, // context reference
cx: &'v LateContext<'v, 't>,
lts: Vec<RefLt>,
}

Expand All @@ -187,12 +217,12 @@ impl<'v, 't> RefVisitor<'v, 't> {
fn record(&mut self, lifetime: &Option<Lifetime>) {
if let Some(ref lt) = *lifetime {
if lt.name.as_str() == "'static" {
self.lts.push(Static);
self.lts.push(RefLt::Static);
} else {
self.lts.push(Named(lt.name));
self.lts.push(RefLt::Named(lt.name));
}
} else {
self.lts.push(Unnamed);
self.lts.push(RefLt::Unnamed);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/matches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ fn check_match_bool(cx: &LateContext, ex: &Expr, arms: &[Arm], expr: &Expr) {
expr.span,
"you seem to be trying to match on a boolean expression. Consider using \
an if..else block:", move |db| {
if let Some(ref sugg) = sugg {
db.span_suggestion(expr.span, "try this", sugg.clone());
if let Some(sugg) = sugg {
db.span_suggestion(expr.span, "try this", sugg);
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ fn is_cast_ty_equal(left: &Ty, right: &Ty) -> bool {
}
}

/// Return the pre-expansion span is this comes from a expansion of the macro `name`.
/// Return the pre-expansion span if is this comes from an expansion of the macro `name`.
pub fn is_expn_of(cx: &LateContext, mut span: Span, name: &str) -> Option<Span> {
loop {
let span_name_span = cx.tcx.sess.codemap().with_expn_info(span.expn_id, |expn| {
Expand Down
2 changes: 1 addition & 1 deletion src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use utils::{is_expn_of, match_path, snippet, span_lint_and_then};
/// **Known problems:** None.
///
/// **Example:**
/// ```rust, ignore
/// ```rust,ignore
/// foo(&vec![1, 2])
/// ```
declare_lint! {
Expand Down
5 changes: 5 additions & 0 deletions tests/compile-fail/lifetimes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#![deny(needless_lifetimes, unused_lifetimes)]
#![allow(dead_code)]

fn distinct_lifetimes<'a, 'b>(_x: &'a u8, _y: &'b u8, _z: u8) { }
//~^ERROR explicit lifetimes given

Expand Down Expand Up @@ -97,6 +98,7 @@ fn struct_with_lt3<'a>(_foo: &Foo<'a> ) -> &'a str { unimplemented!() }
fn struct_with_lt4<'a, 'b>(_foo: &'a Foo<'b> ) -> &'a str { unimplemented!() }

trait WithLifetime<'a> {}

type WithLifetimeAlias<'a> = WithLifetime<'a>;

// should not warn because it won't build without the lifetime
Expand All @@ -123,5 +125,8 @@ fn named_input_elided_output<'a>(_arg: &'a str) -> &str { unimplemented!() } //~

fn elided_input_named_output<'a>(_arg: &str) -> &'a str { unimplemented!() }

fn trait_bound_ok<'a, T: WithLifetime<'static>>(_: &'a u8, _: T) { unimplemented!() } //~ERROR explicit lifetimes given
fn trait_bound<'a, T: WithLifetime<'a>>(_: &'a u8, _: T) { unimplemented!() }

fn main() {
}
4 changes: 3 additions & 1 deletion util/update_lints.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python
# Generate a Markdown table of all lints, and put it in README.md.
# With -n option, only print the new table to stdout.
# With -c option, print a warning and set exit status to 1 if a file would be changed.
# With -c option, print a warning and set exit status to 1 if a file would be
# changed.

import os
import re
Expand All @@ -18,6 +19,7 @@

wiki_link = 'https://github.com/Manishearth/rust-clippy/wiki'


def collect(lints, fn):
"""Collect all lints from a file.

Expand Down
37 changes: 26 additions & 11 deletions util/update_wiki.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
#!/usr/bin/env python
# Generate the wiki Home.md page from the contained doc comments
# requires the checked out wiki in ../rust-clippy.wiki/
# with -c option, print a warning and set exit status 1 if the file would be changed.
import os, re, sys
# with -c option, print a warning and set exit status 1 if the file would be
# changed.
import os
import re
import sys


def parse_path(p="src"):
d = {}
Expand All @@ -14,10 +18,10 @@ def parse_path(p="src"):
START = 0
LINT = 1


def parse_file(d, f):
last_comment = []
comment = True
lint = None

with open(f) as rs:
for line in rs:
Expand All @@ -35,27 +39,33 @@ def parse_file(d, f):
l = line.strip()
m = re.search(r"pub\s+([A-Z_]+)", l)
if m:
print "found %s in %s" % (m.group(1).lower(), f)
print("found %s in %s" % (m.group(1).lower(), f))
d[m.group(1).lower()] = last_comment
last_comment = []
comment = True
if "}" in l:
print "Warning: Missing Lint-Name in", f
print("Warning: Missing Lint-Name in", f)
comment = True

PREFIX = """Welcome to the rust-clippy wiki!

Here we aim to collect further explanations on the lints clippy provides. So without further ado:
Here we aim to collect further explanations on the lints clippy provides. So \
without further ado:

"""

WARNING = """
# A word of warning

Clippy works as a *plugin* to the compiler, which means using an unstable internal API. We have gotten quite good at keeping pace with the API evolution, but the consequence is that clippy absolutely needs to be compiled with the version of `rustc` it will run on, otherwise you will get strange errors of missing symbols."""
Clippy works as a *plugin* to the compiler, which means using an unstable \
internal API. We have gotten quite good at keeping pace with the API \
evolution, but the consequence is that clippy absolutely needs to be compiled \
with the version of `rustc` it will run on, otherwise you will get strange \
errors of missing symbols."""


def write_wiki_page(d, f):
keys = d.keys()
keys = list(d.keys())
keys.sort()
with open(f, "w") as w:
w.write(PREFIX)
Expand All @@ -65,6 +75,7 @@ def write_wiki_page(d, f):
for k in keys:
w.write("\n# `%s`\n\n%s" % (k, "".join(d[k])))


def check_wiki_page(d, f):
errors = []
with open(f) as w:
Expand All @@ -74,17 +85,21 @@ def check_wiki_page(d, f):
v = d.pop(m.group(1), "()")
if v == "()":
errors.append("Missing wiki entry: " + m.group(1))
keys = d.keys()
keys = list(d.keys())
keys.sort()
for k in keys:
errors.append("Spurious wiki entry: " + k)
if errors:
print "\n".join(errors)
print("\n".join(errors))
sys.exit(1)

if __name__ == "__main__":

def main():
d = parse_path()
if "-c" in sys.argv:
check_wiki_page(d, "../rust-clippy.wiki/Home.md")
else:
write_wiki_page(d, "../rust-clippy.wiki/Home.md")

if __name__ == "__main__":
main()