Merge pull request #215 from birkenfeld/type_improvements

Type improvements
This commit is contained in:
Manish Goregaokar 2015-08-22 12:13:56 +05:30
commit e1a4248e68
8 changed files with 88 additions and 121 deletions

View file

@ -2,7 +2,8 @@ use syntax::ast::*;
use rustc::lint::*; use rustc::lint::*;
use rustc::middle::ty; use rustc::middle::ty;
use utils::{span_lint, match_def_path, walk_ptrs_ty}; use utils::{span_lint, match_type, walk_ptrs_ty};
use utils::{OPTION_PATH, RESULT_PATH, STRING_PATH};
#[derive(Copy,Clone)] #[derive(Copy,Clone)]
pub struct MethodsPass; pub struct MethodsPass;
@ -16,45 +17,31 @@ declare_lint!(pub STR_TO_STRING, Warn,
declare_lint!(pub STRING_TO_STRING, Warn, declare_lint!(pub STRING_TO_STRING, Warn,
"calling `String.to_string()` which is a no-op"); "calling `String.to_string()` which is a no-op");
#[allow(unused_imports)]
impl LintPass for MethodsPass { impl LintPass for MethodsPass {
fn get_lints(&self) -> LintArray { fn get_lints(&self) -> LintArray {
lint_array!(OPTION_UNWRAP_USED, RESULT_UNWRAP_USED, STR_TO_STRING, STRING_TO_STRING) lint_array!(OPTION_UNWRAP_USED, RESULT_UNWRAP_USED, STR_TO_STRING, STRING_TO_STRING)
} }
fn check_expr(&mut self, cx: &Context, expr: &Expr) { fn check_expr(&mut self, cx: &Context, expr: &Expr) {
{
// In case stuff gets moved around
use core::option::Option;
use core::result::Result;
use collections::string::String;
}
if let ExprMethodCall(ref ident, _, ref args) = expr.node { if let ExprMethodCall(ref ident, _, ref args) = expr.node {
let ref obj_ty = walk_ptrs_ty(cx.tcx.expr_ty(&*args[0])).sty; let obj_ty = walk_ptrs_ty(cx.tcx.expr_ty(&*args[0]));
if ident.node.name == "unwrap" { if ident.node.name == "unwrap" {
if let ty::TyEnum(did, _) = *obj_ty { if match_type(cx, obj_ty, &OPTION_PATH) {
if match_def_path(cx, did.did, &["core", "option", "Option"]) { span_lint(cx, OPTION_UNWRAP_USED, expr.span,
span_lint(cx, OPTION_UNWRAP_USED, expr.span, "used unwrap() on an Option value. If you don't want \
"used unwrap() on an Option value. If you don't want \ to handle the None case gracefully, consider using \
to handle the None case gracefully, consider using expect() to provide a better panic message");
expect() to provide a better panic message"); } else if match_type(cx, obj_ty, &RESULT_PATH) {
} span_lint(cx, RESULT_UNWRAP_USED, expr.span,
else if match_def_path(cx, did.did, &["core", "result", "Result"]) { "used unwrap() on a Result value. Graceful handling \
span_lint(cx, RESULT_UNWRAP_USED, expr.span, of Err values is preferred");
"used unwrap() on a Result value. Graceful handling \
of Err values is preferred");
}
} }
} }
else if ident.node.name == "to_string" { else if ident.node.name == "to_string" {
if let ty::TyStr = *obj_ty { if obj_ty.sty == ty::TyStr {
span_lint(cx, STR_TO_STRING, expr.span, "`str.to_owned()` is faster"); span_lint(cx, STR_TO_STRING, expr.span, "`str.to_owned()` is faster");
} } else if match_type(cx, obj_ty, &STRING_PATH) {
else if let ty::TyStruct(did, _) = *obj_ty { span_lint(cx, STRING_TO_STRING, expr.span, "`String.to_string()` is a no-op");
if match_def_path(cx, did.did, &["collections", "string", "String"]) {
span_lint(cx, STRING_TO_STRING, expr.span,
"`String.to_string()` is a no-op")
}
} }
} }
} }

View file

@ -4,10 +4,10 @@
use rustc::lint::*; use rustc::lint::*;
use syntax::ast::*; use syntax::ast::*;
use syntax::codemap::Span; use rustc::middle::ty;
use types::match_ty_unwrap; use utils::{span_lint, match_type};
use utils::span_lint; use utils::{STRING_PATH, VEC_PATH};
declare_lint! { declare_lint! {
pub PTR_ARG, pub PTR_ARG,
@ -45,22 +45,21 @@ impl LintPass for PtrArg {
fn check_fn(cx: &Context, decl: &FnDecl) { fn check_fn(cx: &Context, decl: &FnDecl) {
for arg in &decl.inputs { for arg in &decl.inputs {
match &arg.ty.node { if arg.ty.node == TyInfer { // "self" arguments
&TyPtr(ref p) | &TyRptr(_, ref p) => continue;
check_ptr_subtype(cx, arg.ty.span, &p.ty), }
_ => () let ref sty = cx.tcx.pat_ty(&*arg.pat).sty;
if let &ty::TyRef(_, ty::TypeAndMut { ty, mutbl: MutImmutable }) = sty {
if match_type(cx, ty, &VEC_PATH) {
span_lint(cx, PTR_ARG, arg.ty.span,
"writing `&Vec<_>` instead of `&[_]` involves one more reference \
and cannot be used with non-Vec-based slices. Consider changing \
the type to `&[...]`");
} else if match_type(cx, ty, &STRING_PATH) {
span_lint(cx, PTR_ARG, arg.ty.span,
"writing `&String` instead of `&str` involves a new object \
where a slice will do. Consider changing the type to `&str`");
}
} }
} }
} }
fn check_ptr_subtype(cx: &Context, span: Span, ty: &Ty) {
match_ty_unwrap(ty, &["Vec"]).map_or_else(|| match_ty_unwrap(ty,
&["String"]).map_or((), |_| {
span_lint(cx, PTR_ARG, span,
"writing `&String` instead of `&str` involves a new object \
where a slice will do. Consider changing the type to `&str`")
}), |_| span_lint(cx, PTR_ARG, span,
"writing `&Vec<_>` instead of \
`&[_]` involves one more reference and cannot be used with \
non-Vec-based slices. Consider changing the type to `&[...]`"))
}

View file

@ -1,8 +1,7 @@
use rustc::lint::{Context, LintArray, LintPass}; use rustc::lint::{Context, LintArray, LintPass};
use rustc::middle::ty::TypeVariants::TyStruct;
use syntax::ast::*; use syntax::ast::*;
use syntax::codemap::Spanned; use syntax::codemap::Spanned;
use utils::{match_def_path}; use utils::match_type;
declare_lint! { declare_lint! {
pub RANGE_STEP_BY_ZERO, Warn, pub RANGE_STEP_BY_ZERO, Warn,
@ -34,11 +33,9 @@ impl LintPass for StepByZero {
fn is_range(cx: &Context, expr: &Expr) -> bool { fn is_range(cx: &Context, expr: &Expr) -> bool {
// No need for walk_ptrs_ty here because step_by moves self, so it // No need for walk_ptrs_ty here because step_by moves self, so it
// can't be called on a borrowed range. // can't be called on a borrowed range.
if let TyStruct(did, _) = cx.tcx.expr_ty(expr).sty { let ty = cx.tcx.expr_ty(expr);
// Note: RangeTo and RangeFull don't have step_by // Note: RangeTo and RangeFull don't have step_by
match_def_path(cx, did.did, &["core", "ops", "Range"]) || match_type(cx, ty, &["core", "ops", "Range"]) || match_type(cx, ty, &["core", "ops", "RangeFrom"])
match_def_path(cx, did.did, &["core", "ops", "RangeFrom"])
} else { false }
} }
fn is_lit_zero(expr: &Expr) -> bool { fn is_lit_zero(expr: &Expr) -> bool {

View file

@ -4,12 +4,12 @@
//! disable the subsumed lint unless it has a higher level //! disable the subsumed lint unless it has a higher level
use rustc::lint::*; use rustc::lint::*;
use rustc::middle::ty::TypeVariants::TyStruct;
use syntax::ast::*; use syntax::ast::*;
use syntax::codemap::Spanned; use syntax::codemap::Spanned;
use eq_op::is_exp_equal; use eq_op::is_exp_equal;
use utils::{match_def_path, span_lint, walk_ptrs_ty, get_parent_expr}; use utils::{match_type, span_lint, walk_ptrs_ty, get_parent_expr};
use utils::STRING_PATH;
declare_lint! { declare_lint! {
pub STRING_ADD_ASSIGN, pub STRING_ADD_ASSIGN,
@ -61,10 +61,7 @@ impl LintPass for StringAdd {
} }
fn is_string(cx: &Context, e: &Expr) -> bool { fn is_string(cx: &Context, e: &Expr) -> bool {
let ty = walk_ptrs_ty(cx.tcx.expr_ty(e)); match_type(cx, walk_ptrs_ty(cx.tcx.expr_ty(e)), &STRING_PATH)
if let TyStruct(did, _) = ty.sty {
match_def_path(cx, did.did, &["collections", "string", "String"])
} else { false }
} }
fn is_add(cx: &Context, src: &Expr, target: &Expr) -> bool { fn is_add(cx: &Context, src: &Expr, target: &Expr) -> bool {

View file

@ -2,11 +2,11 @@ use rustc::lint::*;
use syntax::ast; use syntax::ast;
use syntax::ast::*; use syntax::ast::*;
use syntax::ast_util::{is_comparison_binop, binop_to_string}; use syntax::ast_util::{is_comparison_binop, binop_to_string};
use syntax::ptr::P;
use rustc::middle::ty; use rustc::middle::ty;
use syntax::codemap::ExpnInfo; use syntax::codemap::ExpnInfo;
use utils::{in_macro, snippet, span_lint, span_help_and_lint, in_external_macro}; use utils::{in_macro, match_type, snippet, span_lint, span_help_and_lint, in_external_macro};
use utils::{LL_PATH, VEC_PATH};
/// Handles all the linting of funky types /// Handles all the linting of funky types
#[allow(missing_copy_implementations)] #[allow(missing_copy_implementations)]
@ -18,61 +18,26 @@ declare_lint!(pub LINKEDLIST, Warn,
"usage of LinkedList, usually a vector is faster, or a more specialized data \ "usage of LinkedList, usually a vector is faster, or a more specialized data \
structure like a RingBuf"); structure like a RingBuf");
/// Matches a type with a provided string, and returns its type parameters if successful
pub fn match_ty_unwrap<'a>(ty: &'a Ty, segments: &[&str]) -> Option<&'a [P<Ty>]> {
match ty.node {
TyPath(_, Path {segments: ref seg, ..}) => {
// So ast::Path isn't the full path, just the tokens that were provided.
// I could muck around with the maps and find the full path
// however the more efficient way is to simply reverse the iterators and zip them
// which will compare them in reverse until one of them runs out of segments
if seg.iter().rev().zip(segments.iter().rev()).all(|(a,b)| a.identifier.name == b) {
match seg[..].last() {
Some(&PathSegment {parameters: AngleBracketedParameters(ref a), ..}) => {
Some(&a.types[..])
}
_ => None
}
} else {
None
}
},
_ => None
}
}
#[allow(unused_imports)]
impl LintPass for TypePass { impl LintPass for TypePass {
fn get_lints(&self) -> LintArray { fn get_lints(&self) -> LintArray {
lint_array!(BOX_VEC, LINKEDLIST) lint_array!(BOX_VEC, LINKEDLIST)
} }
fn check_ty(&mut self, cx: &Context, ty: &ast::Ty) { fn check_ty(&mut self, cx: &Context, ast_ty: &ast::Ty) {
{ if let Some(ty) = cx.tcx.ast_ty_to_ty_cache.borrow().get(&ast_ty.id) {
// In case stuff gets moved around if let ty::TyBox(ref inner) = ty.sty {
use std::boxed::Box; if match_type(cx, inner, &VEC_PATH) {
use std::vec::Vec; span_help_and_lint(
} cx, BOX_VEC, ast_ty.span,
match_ty_unwrap(ty, &["std", "boxed", "Box"]).and_then(|t| t.first()) "you seem to be trying to use `Box<Vec<T>>`. Did you mean to use `Vec<T>`?",
.and_then(|t| match_ty_unwrap(&**t, &["std", "vec", "Vec"])) "`Vec<T>` is already on the heap, `Box<Vec<T>>` makes an extra allocation");
.map(|_| { }
span_help_and_lint(cx, BOX_VEC, ty.span, }
"you seem to be trying to use `Box<Vec<T>>`. Did you mean to use `Vec<T>`?", else if match_type(cx, ty, &LL_PATH) {
"`Vec<T>` is already on the heap, `Box<Vec<T>>` makes an extra allocation"); span_help_and_lint(
}); cx, LINKEDLIST, ast_ty.span,
{ "I see you're using a LinkedList! Perhaps you meant some other data structure?",
// In case stuff gets moved around "a RingBuf might work");
use collections::linked_list::LinkedList as DL1;
use std::collections::linked_list::LinkedList as DL2;
}
let dlists = [vec!["std","collections","linked_list","LinkedList"],
vec!["collections","linked_list","LinkedList"]];
for path in &dlists {
if match_ty_unwrap(ty, &path[..]).is_some() {
span_help_and_lint(cx, LINKEDLIST, ty.span,
"I see you're using a LinkedList! Perhaps you meant some other data structure?",
"a RingBuf might work");
return;
} }
} }
} }

View file

@ -6,6 +6,13 @@ use rustc::ast_map::Node::NodeExpr;
use rustc::middle::ty; use rustc::middle::ty;
use std::borrow::Cow; use std::borrow::Cow;
// module DefPaths for certain structs/enums we check for
pub const OPTION_PATH: [&'static str; 3] = ["core", "option", "Option"];
pub const RESULT_PATH: [&'static str; 3] = ["core", "result", "Result"];
pub const STRING_PATH: [&'static str; 3] = ["collections", "string", "String"];
pub const VEC_PATH: [&'static str; 3] = ["collections", "vec", "Vec"];
pub const LL_PATH: [&'static str; 3] = ["collections", "linked_list", "LinkedList"];
/// returns true if the macro that expanded the crate was outside of /// returns true if the macro that expanded the crate was outside of
/// the current crate or was a compiler plugin /// the current crate or was a compiler plugin
pub fn in_macro(cx: &Context, opt_info: Option<&ExpnInfo>) -> bool { pub fn in_macro(cx: &Context, opt_info: Option<&ExpnInfo>) -> bool {
@ -37,6 +44,18 @@ pub fn match_def_path(cx: &Context, def_id: DefId, path: &[&str]) -> bool {
.zip(path.iter()).all(|(nm, p)| nm == p)) .zip(path.iter()).all(|(nm, p)| nm == p))
} }
/// check if type is struct or enum type with given def path
pub fn match_type(cx: &Context, ty: ty::Ty, path: &[&str]) -> bool {
match ty.sty {
ty::TyEnum(ref adt, _) | ty::TyStruct(ref adt, _) => {
match_def_path(cx, adt.did, path)
}
_ => {
false
}
}
}
/// match a Path against a slice of segment string literals, e.g. /// match a Path against a slice of segment string literals, e.g.
/// `match_path(path, &["std", "rt", "begin_unwind"])` /// `match_path(path, &["std", "rt", "begin_unwind"])`
pub fn match_path(path: &Path, segments: &[&str]) -> bool { pub fn match_path(path: &Path, segments: &[&str]) -> bool {

2
tests/compile-fail/dlist.rs Normal file → Executable file
View file

@ -12,4 +12,4 @@ pub fn test(foo: LinkedList<u8>) { //~ ERROR I see you're using a LinkedList!
fn main(){ fn main(){
test(LinkedList::new()); test(LinkedList::new());
} }

View file

@ -1,20 +1,23 @@
#![feature(plugin)] #![feature(plugin)]
#![plugin(clippy)] #![plugin(clippy)]
#![allow(unused)]
#![deny(ptr_arg)]
#[deny(ptr_arg)]
#[allow(unused)]
fn do_vec(x: &Vec<i64>) { //~ERROR writing `&Vec<_>` instead of `&[_]` fn do_vec(x: &Vec<i64>) { //~ERROR writing `&Vec<_>` instead of `&[_]`
//Nothing here //Nothing here
} }
#[deny(ptr_arg)] fn do_vec_mut(x: &mut Vec<i64>) { // no error here
#[allow(unused)] //Nothing here
}
fn do_str(x: &String) { //~ERROR writing `&String` instead of `&str` fn do_str(x: &String) { //~ERROR writing `&String` instead of `&str`
//Nothing here either //Nothing here either
} }
fn main() { fn do_str_mut(x: &mut String) { // no error here
let x = vec![1i64, 2, 3]; //Nothing here either
do_vec(&x); }
do_str(&"hello".to_owned());
fn main() {
} }