Merge commit 'c1664c50b27a51f7a78c93ba65558e7c33eabee6' into clippyup

This commit is contained in:
flip1995 2020-12-06 15:01:03 +01:00
commit d3eb49baa9
No known key found for this signature in database
GPG Key ID: 2CEFCDB27ED0BE79
139 changed files with 4334 additions and 576 deletions

View File

@ -128,14 +128,14 @@ jobs:
SYSROOT=$(rustc --print sysroot)
echo "$SYSROOT/bin" >> $GITHUB_PATH
- name: Build
run: cargo build --features deny-warnings
- name: Build with internal lints
run: cargo build --features deny-warnings,internal-lints
- name: Test
run: cargo test --features deny-warnings
- name: Test with internal lints
run: cargo test --features deny-warnings,internal-lints
- name: Test clippy_lints
run: cargo test --features deny-warnings
- name: Test clippy_lints with internal lints
run: cargo test --features deny-warnings,internal-lints
working-directory: clippy_lints
- name: Test rustc_tools_util

View File

@ -1770,6 +1770,7 @@ Released 2018-09-13
[`cmp_owned`]: https://rust-lang.github.io/rust-clippy/master/index.html#cmp_owned
[`cognitive_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#cognitive_complexity
[`collapsible_if`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if
[`collapsible_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_match
[`comparison_chain`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_chain
[`comparison_to_empty`]: https://rust-lang.github.io/rust-clippy/master/index.html#comparison_to_empty
[`copy_iterator`]: https://rust-lang.github.io/rust-clippy/master/index.html#copy_iterator
@ -2056,6 +2057,7 @@ Released 2018-09-13
[`single_element_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_element_loop
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
@ -2073,6 +2075,7 @@ Released 2018-09-13
[`suspicious_else_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_else_formatting
[`suspicious_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_map
[`suspicious_op_assign_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_op_assign_impl
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment

View File

@ -14,11 +14,16 @@ All contributors are expected to follow the [Rust Code of Conduct].
- [Contributing to Clippy](#contributing-to-clippy)
- [Getting started](#getting-started)
- [High level approach](#high-level-approach)
- [Finding something to fix/improve](#finding-something-to-fiximprove)
- [Writing code](#writing-code)
- [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work)
- [How Clippy works](#how-clippy-works)
- [Fixing build failures caused by Rust](#fixing-build-failures-caused-by-rust)
- [Patching git-subtree to work with big repos](#patching-git-subtree-to-work-with-big-repos)
- [Performing the sync](#performing-the-sync)
- [Syncing back changes in Clippy to [`rust-lang/rust`]](#syncing-back-changes-in-clippy-to-rust-langrust)
- [Defining remotes](#defining-remotes)
- [Issue and PR triage](#issue-and-pr-triage)
- [Bors and Homu](#bors-and-homu)
- [Contributions](#contributions)
@ -320,8 +325,8 @@ commands [here][homu_instructions].
[l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash
[l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug
[homu]: https://github.com/rust-lang/homu
[homu_instructions]: https://buildbot2.rust-lang.org/homu/
[homu_queue]: https://buildbot2.rust-lang.org/homu/queue/clippy
[homu_instructions]: https://bors.rust-lang.org/
[homu_queue]: https://bors.rust-lang.org/queue/clippy
## Contributions

View File

@ -32,7 +32,7 @@ path = "src/driver.rs"
clippy_lints = { version = "0.0.212", path = "clippy_lints" }
# end automatic update
semver = "0.11"
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util"}
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
tempfile = { version = "3.1.0", optional = true }
[dev-dependencies]
@ -49,8 +49,9 @@ derive-new = "0.5"
rustc-workspace-hack = "1.0.0"
[build-dependencies]
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util"}
rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
[features]
deny-warnings = []
integration = ["tempfile"]
internal-lints = ["clippy_lints/internal-lints"]

View File

@ -182,7 +182,7 @@ cargo clippy -- -W clippy::lint_name
```
This also works with lint groups. For example you
can run Clippy with warnings for all lints enabled:
can run Clippy with warnings for all lints enabled:
```terminal
cargo clippy -- -W clippy::pedantic
```
@ -194,6 +194,33 @@ cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::...
```
Note that if you've run clippy before, this may only take effect after you've modified a file or ran `cargo clean`.
### Specifying the minimum supported Rust version
Projects that intend to support old versions of Rust can disable lints pertaining to newer features by
specifying the minimum supported Rust version (MSRV) in the clippy configuration file.
```toml
msrv = "1.30.0"
```
The MSRV can also be specified as an inner attribute, like below.
```rust
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.30.0"]
fn main() {
...
}
```
You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
is equivalent to `msrv = 1.30.0`.
Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly.
Lints that recognize this configuration option can be found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
## Contributing
If you want to contribute to Clippy, you can find more information in [CONTRIBUTING.md](https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md).

View File

@ -146,16 +146,30 @@ pub fn gen_deprecated<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String>
}
#[must_use]
pub fn gen_register_lint_list<'a>(lints: impl Iterator<Item = &'a Lint>) -> Vec<String> {
let pre = " store.register_lints(&[".to_string();
let post = " ]);".to_string();
let mut inner = lints
pub fn gen_register_lint_list<'a>(
internal_lints: impl Iterator<Item = &'a Lint>,
usable_lints: impl Iterator<Item = &'a Lint>,
) -> Vec<String> {
let header = " store.register_lints(&[".to_string();
let footer = " ]);".to_string();
let internal_lints = internal_lints
.sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase()))
.map(|l| {
format!(
" #[cfg(feature = \"internal-lints\")]\n &{}::{},",
l.module,
l.name.to_uppercase()
)
});
let other_lints = usable_lints
.sorted_by_key(|l| format!(" &{}::{},", l.module, l.name.to_uppercase()))
.map(|l| format!(" &{}::{},", l.module, l.name.to_uppercase()))
.sorted()
.collect::<Vec<String>>();
inner.insert(0, pre);
inner.push(post);
inner
.sorted();
let mut lint_list = vec![header];
lint_list.extend(internal_lints);
lint_list.extend(other_lints);
lint_list.push(footer);
lint_list
}
/// Gathers all files in `src/clippy_lints` and gathers all lints inside

View File

@ -68,7 +68,7 @@ pub fn run(update_mode: UpdateMode) {
"end register lints",
false,
update_mode == UpdateMode::Change,
|| gen_register_lint_list(usable_lints.iter().chain(internal_lints.iter())),
|| gen_register_lint_list(internal_lints.iter(), usable_lints.iter()),
)
.changed;

View File

@ -28,6 +28,7 @@ smallvec = { version = "1", features = ["union"] }
toml = "0.5.3"
unicode-normalization = "0.1"
semver = "0.11"
rustc-semver="1.1.0"
# NOTE: cargo requires serde feat in its url dep
# see <https://github.com/rust-lang/rust/pull/63587#issuecomment-522343864>
url = { version = "2.1.0", features = ["serde"] }
@ -36,3 +37,5 @@ syn = { version = "1", features = ["full"] }
[features]
deny-warnings = []
# build clippy with internal lints enabled, off by default
internal-lints = []

View File

@ -5,7 +5,6 @@ use crate::utils::{
span_lint_and_sugg, span_lint_and_then, without_block_comments,
};
use if_chain::if_chain;
use rustc_span::lev_distance::find_best_match_for_name;
use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
use rustc_errors::Applicability;
use rustc_hir::{
@ -15,6 +14,7 @@ use rustc_lint::{CheckLintNameResult, EarlyContext, EarlyLintPass, LateContext,
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::lev_distance::find_best_match_for_name;
use rustc_span::source_map::Span;
use rustc_span::sym;
use rustc_span::symbol::{Symbol, SymbolStr};

View File

@ -0,0 +1,172 @@
use crate::utils::visitors::LocalUsedVisitor;
use crate::utils::{span_lint_and_then, SpanlessEq};
use if_chain::if_chain;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind, QPath, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{DefIdTree, TyCtxt};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{MultiSpan, Span};
declare_clippy_lint! {
/// **What it does:** Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
/// without adding any branches.
///
/// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
/// cases where merging would most likely make the code more readable.
///
/// **Why is this bad?** It is unnecessarily verbose and complex.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// fn func(opt: Option<Result<u64, String>>) {
/// let n = match opt {
/// Some(n) => match n {
/// Ok(n) => n,
/// _ => return,
/// }
/// None => return,
/// };
/// }
/// ```
/// Use instead:
/// ```rust
/// fn func(opt: Option<Result<u64, String>>) {
/// let n = match opt {
/// Some(Ok(n)) => n,
/// _ => return,
/// };
/// }
/// ```
pub COLLAPSIBLE_MATCH,
style,
"Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
}
declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if let ExprKind::Match(_expr, arms, _source) = expr.kind {
if let Some(wild_arm) = arms.iter().rfind(|arm| arm_is_wild_like(arm, cx.tcx)) {
for arm in arms {
check_arm(arm, wild_arm, cx);
}
}
}
}
}
fn check_arm(arm: &Arm<'_>, wild_outer_arm: &Arm<'_>, cx: &LateContext<'_>) {
if_chain! {
let expr = strip_singleton_blocks(arm.body);
if let ExprKind::Match(expr_in, arms_inner, _) = expr.kind;
// the outer arm pattern and the inner match
if expr_in.span.ctxt() == arm.pat.span.ctxt();
// there must be no more than two arms in the inner match for this lint
if arms_inner.len() == 2;
// no if guards on the inner match
if arms_inner.iter().all(|arm| arm.guard.is_none());
// match expression must be a local binding
// match <local> { .. }
if let ExprKind::Path(QPath::Resolved(None, path)) = expr_in.kind;
if let Res::Local(binding_id) = path.res;
// one of the branches must be "wild-like"
if let Some(wild_inner_arm_idx) = arms_inner.iter().rposition(|arm_inner| arm_is_wild_like(arm_inner, cx.tcx));
let (wild_inner_arm, non_wild_inner_arm) =
(&arms_inner[wild_inner_arm_idx], &arms_inner[1 - wild_inner_arm_idx]);
if !pat_contains_or(non_wild_inner_arm.pat);
// the binding must come from the pattern of the containing match arm
// ..<local>.. => match <local> { .. }
if let Some(binding_span) = find_pat_binding(arm.pat, binding_id);
// the "wild-like" branches must be equal
if SpanlessEq::new(cx).eq_expr(wild_inner_arm.body, wild_outer_arm.body);
// the binding must not be used in the if guard
if !matches!(arm.guard, Some(Guard::If(guard)) if LocalUsedVisitor::new(binding_id).check_expr(guard));
// ...or anywhere in the inner match
if !arms_inner.iter().any(|arm| LocalUsedVisitor::new(binding_id).check_arm(arm));
then {
span_lint_and_then(
cx,
COLLAPSIBLE_MATCH,
expr.span,
"Unnecessary nested match",
|diag| {
let mut help_span = MultiSpan::from_spans(vec![binding_span, non_wild_inner_arm.pat.span]);
help_span.push_span_label(binding_span, "Replace this binding".into());
help_span.push_span_label(non_wild_inner_arm.pat.span, "with this pattern".into());
diag.span_help(help_span, "The outer pattern can be modified to include the inner pattern.");
},
);
}
}
}
fn strip_singleton_blocks<'hir>(mut expr: &'hir Expr<'hir>) -> &'hir Expr<'hir> {
while let ExprKind::Block(block, _) = expr.kind {
match (block.stmts, block.expr) {
([stmt], None) => match stmt.kind {
StmtKind::Expr(e) | StmtKind::Semi(e) => expr = e,
_ => break,
},
([], Some(e)) => expr = e,
_ => break,
}
}
expr
}
/// A "wild-like" pattern is wild ("_") or `None`.
/// For this lint to apply, both the outer and inner match expressions
/// must have "wild-like" branches that can be combined.
fn arm_is_wild_like(arm: &Arm<'_>, tcx: TyCtxt<'_>) -> bool {
if arm.guard.is_some() {
return false;
}
match arm.pat.kind {
PatKind::Binding(..) | PatKind::Wild => true,
PatKind::Path(QPath::Resolved(None, path)) if is_none_ctor(path.res, tcx) => true,
_ => false,
}
}
fn find_pat_binding(pat: &Pat<'_>, hir_id: HirId) -> Option<Span> {
let mut span = None;
pat.walk_short(|p| match &p.kind {
// ignore OR patterns
PatKind::Or(_) => false,
PatKind::Binding(_bm, _, _ident, _) => {
let found = p.hir_id == hir_id;
if found {
span = Some(p.span);
}
!found
},
_ => true,
});
span
}
fn pat_contains_or(pat: &Pat<'_>) -> bool {
let mut result = false;
pat.walk(|p| {
let is_or = matches!(p.kind, PatKind::Or(_));
result |= is_or;
!is_or
});
result
}
fn is_none_ctor(res: Res, tcx: TyCtxt<'_>) -> bool {
if let Some(none_id) = tcx.lang_items().option_none_variant() {
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = res {
if let Some(variant_id) = tcx.parent(id) {
return variant_id == none_id;
}
}
}
false
}

View File

@ -12,7 +12,8 @@ declare_clippy_lint! {
/// **Why is this bad?** `if` is not guaranteed to be exhaustive and conditionals can get
/// repetitive
///
/// **Known problems:** None.
/// **Known problems:** The match statement may be slower due to the compiler
/// not inlining the call to cmp. See issue #5354
///
/// **Example:**
/// ```rust,ignore

View File

@ -280,8 +280,7 @@ fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Op
// only take assignments to fields where the left-hand side field is a field of
// the same binding as the previous statement
if let ExprKind::Field(ref binding, field_ident) = assign_lhs.kind;
if let ExprKind::Path(ref qpath) = binding.kind;
if let QPath::Resolved(_, path) = qpath;
if let ExprKind::Path(QPath::Resolved(_, path)) = binding.kind;
if let Some(second_binding_name) = path.segments.last();
if second_binding_name.ident.name == binding_name;
then {

View File

@ -51,26 +51,6 @@ declare_deprecated_lint! {
"`Vec::as_mut_slice` has been stabilized in 1.7"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This used to check for `.to_string()` method calls on values
/// of type `&str`. This is not unidiomatic and with specialization coming, `to_string` could be
/// specialized to be as efficient as `to_owned`.
pub STR_TO_STRING,
"using `str::to_string` is common even today and specialization will likely happen soon"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///
/// **Deprecation reason:** This used to check for `.to_string()` method calls on values
/// of type `String`. This is not unidiomatic and with specialization coming, `to_string` could be
/// specialized to be as efficient as `clone`.
pub STRING_TO_STRING,
"using `string::to_string` is common even today and specialization will likely happen soon"
}
declare_deprecated_lint! {
/// **What it does:** Nothing. This lint has been deprecated.
///

View File

@ -1,10 +1,10 @@
use crate::utils::{
eq_expr_value, higher, implements_trait, in_macro, is_copy, is_expn_of, multispan_sugg, snippet, span_lint,
span_lint_and_then,
ast_utils::is_useless_with_eq_exprs, eq_expr_value, higher, implements_trait, in_macro, is_copy, is_expn_of,
multispan_sugg, snippet, span_lint, span_lint_and_then,
};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -102,7 +102,7 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
if macro_with_not_op(&left.kind) || macro_with_not_op(&right.kind) {
return;
}
if is_valid_operator(op) && eq_expr_value(cx, left, right) {
if is_useless_with_eq_exprs(higher::binop(op.node)) && eq_expr_value(cx, left, right) {
span_lint(
cx,
EQ_OP,
@ -245,22 +245,3 @@ impl<'tcx> LateLintPass<'tcx> for EqOp {
}
}
}
fn is_valid_operator(op: BinOp) -> bool {
matches!(
op.node,
BinOpKind::Sub
| BinOpKind::Div
| BinOpKind::Eq
| BinOpKind::Lt
| BinOpKind::Le
| BinOpKind::Gt
| BinOpKind::Ge
| BinOpKind::Ne
| BinOpKind::And
| BinOpKind::Or
| BinOpKind::BitXor
| BinOpKind::BitAnd
| BinOpKind::BitOr
)
}

View File

@ -41,8 +41,7 @@ declare_lint_pass!(OkIfLet => [IF_LET_SOME_RESULT]);
impl<'tcx> LateLintPass<'tcx> for OkIfLet {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { //begin checking variables
if let ExprKind::Match(ref op, ref body, source) = expr.kind; //test if expr is a match
if let MatchSource::IfLetDesugar { .. } = source; //test if it is an If Let
if let ExprKind::Match(ref op, ref body, MatchSource::IfLetDesugar { .. }) = expr.kind; //test if expr is if let
if let ExprKind::MethodCall(_, ok_span, ref result_types, _) = op.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
if let PatKind::TupleStruct(QPath::Resolved(_, ref x), ref y, _) = body[0].pat.kind; //get operation
if method_chain_args(op, &["ok"]).is_some(); //test to see if using ok() methoduse std::marker::Sized;

View File

@ -68,8 +68,7 @@ fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) {
if_chain! {
if let StmtKind::Semi(expr, ..) = &stmt.kind;
// make sure it's a break, otherwise we want to skip
if let ExprKind::Break(.., break_expr) = &expr.kind;
if let Some(break_expr) = break_expr;
if let ExprKind::Break(.., Some(break_expr)) = &expr.kind;
then {
lint(cx, expr.span, break_expr.span, LINT_BREAK);
}

View File

@ -59,8 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub {
if let Some(target) = subtracts_one(cx, e);
// Extracting out the variable name
if let ExprKind::Path(ref assign_path) = target.kind;
if let QPath::Resolved(_, ref ares_path) = assign_path;
if let ExprKind::Path(QPath::Resolved(_, ref ares_path)) = target.kind;
then {
// Handle symmetric conditions in the if statement

View File

@ -58,12 +58,12 @@ impl EarlyLintPass for ItemsAfterStatements {
return;
}
// skip initial items
// skip initial items and trailing semicolons
let stmts = item
.stmts
.iter()
.map(|stmt| &stmt.kind)
.skip_while(|s| matches!(**s, StmtKind::Item(..)));
.skip_while(|s| matches!(**s, StmtKind::Item(..) | StmtKind::Empty));
// lint on all further items
for stmt in stmts {

View File

@ -52,8 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays {
if let ItemKind::Const(hir_ty, _) = &item.kind;
let ty = hir_ty_to_ty(cx.tcx, hir_ty);
if let ty::Array(element_type, cst) = ty.kind();
if let ConstKind::Value(val) = cst.val;
if let ConstValue::Scalar(element_count) = val;
if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val;
if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx);
if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes());
if self.maximum_allowed_size < element_count * element_size;

View File

@ -43,8 +43,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays {
if_chain! {
if let ExprKind::Repeat(_, _) = expr.kind;
if let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind();
if let ConstKind::Value(val) = cst.val;
if let ConstValue::Scalar(element_count) = val;
if let ConstKind::Value(ConstValue::Scalar(element_count)) = cst.val;
if let Ok(element_count) = element_count.to_machine_usize(&cx.tcx);
if let Ok(element_size) = cx.layout_of(element_type).map(|l| l.size.bytes());
if self.maximum_allowed_size < element_count * element_size;

View File

@ -1,12 +1,11 @@
use crate::utils::visitors::LocalUsedVisitor;
use crate::utils::{higher, qpath_res, snippet, span_lint_and_then};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit;
use rustc_hir::BindingAnnotation;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
@ -66,10 +65,10 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
if let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind;
if let hir::StmtKind::Expr(ref if_) = expr.kind;
if let Some((ref cond, ref then, ref else_)) = higher::if_block(&if_);
if !used_in_expr(cx, canonical_id, cond);
if !LocalUsedVisitor::new(canonical_id).check_expr(cond);
if let hir::ExprKind::Block(ref then, _) = then.kind;
if let Some(value) = check_assign(cx, canonical_id, &*then);
if !used_in_expr(cx, canonical_id, value);
if !LocalUsedVisitor::new(canonical_id).check_expr(value);
then {
let span = stmt.span.to(if_.span);
@ -136,32 +135,6 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
}
}
struct UsedVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
id: hir::HirId,
used: bool,
}
impl<'a, 'tcx> intravisit::Visitor<'tcx> for UsedVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if let hir::ExprKind::Path(ref qpath) = expr.kind;
if let Res::Local(local_id) = qpath_res(self.cx, qpath, expr.hir_id);
if self.id == local_id;
then {
self.used = true;
return;
}
}
intravisit::walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
}
fn check_assign<'tcx>(
cx: &LateContext<'tcx>,
decl: hir::HirId,
@ -176,18 +149,10 @@ fn check_assign<'tcx>(
if let Res::Local(local_id) = qpath_res(cx, qpath, var.hir_id);
if decl == local_id;
then {
let mut v = UsedVisitor {
cx,
id: decl,
used: false,
};
let mut v = LocalUsedVisitor::new(decl);
for s in block.stmts.iter().take(block.stmts.len()-1) {
intravisit::walk_stmt(&mut v, s);
if v.used {
return None;
}
if block.stmts.iter().take(block.stmts.len()-1).any(|stmt| v.check_stmt(stmt)) {
return None;
}
return Some(value);
@ -196,9 +161,3 @@ fn check_assign<'tcx>(
None
}
fn used_in_expr<'tcx>(cx: &LateContext<'tcx>, id: hir::HirId, expr: &'tcx hir::Expr<'_>) -> bool {
let mut v = UsedVisitor { cx, id, used: false };
intravisit::walk_expr(&mut v, expr);
v.used
}

View File

@ -44,6 +44,7 @@ extern crate rustc_target;
extern crate rustc_trait_selection;
extern crate rustc_typeck;
use crate::utils::parse_msrv;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::LintId;
use rustc_session::Session;
@ -171,6 +172,7 @@ mod cargo_common_metadata;
mod checked_conversions;
mod cognitive_complexity;
mod collapsible_if;
mod collapsible_match;
mod comparison_chain;
mod copies;
mod copy_iterator;
@ -304,9 +306,11 @@ mod self_assignment;
mod serde_api;
mod shadow;
mod single_component_path_imports;
mod size_of_in_element_count;
mod slow_vector_initialization;
mod stable_sort_primitive;
mod strings;
mod suspicious_operation_groupings;
mod suspicious_trait_impl;
mod swap;
mod tabs_in_doc_comments;
@ -440,14 +444,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
"clippy::unstable_as_mut_slice",
"`Vec::as_mut_slice` has been stabilized in 1.7",
);
store.register_removed(
"clippy::str_to_string",
"using `str::to_string` is common even today and specialization will likely happen soon",
);
store.register_removed(
"clippy::string_to_string",
"using `string::to_string` is common even today and specialization will likely happen soon",
);
store.register_removed(
"clippy::misaligned_transmute",
"this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",
@ -504,6 +500,24 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
// begin register lints, do not remove this comment, its used in `update_lints`
store.register_lints(&[
#[cfg(feature = "internal-lints")]
&utils::internal_lints::CLIPPY_LINTS_INTERNAL,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::COMPILER_LINT_FUNCTIONS,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::DEFAULT_LINT,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::INVALID_PATHS,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::LINT_WITHOUT_LINT_PASS,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::OUTER_EXPN_EXPN_DATA,
#[cfg(feature = "internal-lints")]
&utils::internal_lints::PRODUCE_ICE,
&approx_const::APPROX_CONSTANT,
&arithmetic::FLOAT_ARITHMETIC,
&arithmetic::INTEGER_ARITHMETIC,
@ -537,6 +551,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&checked_conversions::CHECKED_CONVERSIONS,
&cognitive_complexity::COGNITIVE_COMPLEXITY,
&collapsible_if::COLLAPSIBLE_IF,
&collapsible_match::COLLAPSIBLE_MATCH,
&comparison_chain::COMPARISON_CHAIN,
&copies::IFS_SAME_COND,
&copies::IF_SAME_THEN_ELSE,
@ -833,12 +848,16 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&shadow::SHADOW_SAME,
&shadow::SHADOW_UNRELATED,
&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
&stable_sort_primitive::STABLE_SORT_PRIMITIVE,
&strings::STRING_ADD,
&strings::STRING_ADD_ASSIGN,
&strings::STRING_FROM_UTF8_AS_BYTES,
&strings::STRING_LIT_AS_BYTES,
&strings::STRING_TO_STRING,
&strings::STR_TO_STRING,
&suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS,
&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
&swap::ALMOST_SWAPPED,
@ -907,15 +926,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&unwrap_in_result::UNWRAP_IN_RESULT,
&use_self::USE_SELF,
&useless_conversion::USELESS_CONVERSION,
&utils::internal_lints::CLIPPY_LINTS_INTERNAL,
&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS,
&utils::internal_lints::COMPILER_LINT_FUNCTIONS,
&utils::internal_lints::DEFAULT_LINT,
&utils::internal_lints::INVALID_PATHS,
&utils::internal_lints::LINT_WITHOUT_LINT_PASS,
&utils::internal_lints::MATCH_TYPE_ON_DIAGNOSTIC_ITEM,
&utils::internal_lints::OUTER_EXPN_EXPN_DATA,
&utils::internal_lints::PRODUCE_ICE,
&vec::USELESS_VEC,
&vec_resize_to_zero::VEC_RESIZE_TO_ZERO,
&verbose_file_reads::VERBOSE_FILE_READS,
@ -934,14 +944,22 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
]);
// end register lints, do not remove this comment, its used in `update_lints`
// all the internal lints
#[cfg(feature = "internal-lints")]
{
store.register_early_pass(|| box utils::internal_lints::ClippyLintsInternal);
store.register_early_pass(|| box utils::internal_lints::ProduceIce);
store.register_late_pass(|| box utils::inspector::DeepCodeInspector);
store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
store.register_late_pass(|| box utils::internal_lints::InvalidPaths);
store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default());
store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem);
store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
}
store.register_late_pass(|| box utils::author::Author);
store.register_late_pass(|| box await_holding_invalid::AwaitHolding);
store.register_late_pass(|| box serde_api::SerdeAPI);
store.register_late_pass(|| box utils::internal_lints::CompilerLintFunctions::new());
store.register_late_pass(|| box utils::internal_lints::LintWithoutLintPass::default());
store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
store.register_late_pass(|| box utils::internal_lints::InvalidPaths);
store.register_late_pass(|| box utils::inspector::DeepCodeInspector);
store.register_late_pass(|| box utils::author::Author);
let vec_box_size_threshold = conf.vec_box_size_threshold;
store.register_late_pass(move || box types::Types::new(vec_box_size_threshold));
store.register_late_pass(|| box booleans::NonminimalBool);
@ -964,12 +982,25 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box len_zero::LenZero);
store.register_late_pass(|| box attrs::Attributes);
store.register_late_pass(|| box blocks_in_if_conditions::BlocksInIfConditions);
store.register_late_pass(|| box collapsible_match::CollapsibleMatch);
store.register_late_pass(|| box unicode::Unicode);
store.register_late_pass(|| box unit_return_expecting_ord::UnitReturnExpectingOrd);
store.register_late_pass(|| box strings::StringAdd);
store.register_late_pass(|| box implicit_return::ImplicitReturn);
store.register_late_pass(|| box implicit_saturating_sub::ImplicitSaturatingSub);
store.register_late_pass(|| box methods::Methods);
let msrv = conf.msrv.as_ref().and_then(|s| {
parse_msrv(s, None, None).or_else(|| {
sess.err(&format!("error reading Clippy's configuration file. `{}` is not a valid Rust version", s));
None
})
});
store.register_late_pass(move || box methods::Methods::new(msrv));
store.register_late_pass(move || box matches::Matches::new(msrv));
store.register_early_pass(move || box manual_non_exhaustive::ManualNonExhaustive::new(msrv));
store.register_late_pass(move || box manual_strip::ManualStrip::new(msrv));
store.register_late_pass(|| box size_of_in_element_count::SizeOfInElementCount);
store.register_late_pass(|| box map_clone::MapClone);
store.register_late_pass(|| box map_err_ignore::MapErrIgnore);
store.register_late_pass(|| box shadow::Shadow);
@ -983,7 +1014,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box types::Casts);
let type_complexity_threshold = conf.type_complexity_threshold;
store.register_late_pass(move || box types::TypeComplexity::new(type_complexity_threshold));
store.register_late_pass(|| box matches::Matches::default());
store.register_late_pass(|| box minmax::MinMaxPass);
store.register_late_pass(|| box open_options::OpenOptions);
store.register_late_pass(|| box zero_div_zero::ZeroDiv);
@ -1057,6 +1087,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box types::UnitArg);
store.register_late_pass(|| box double_comparison::DoubleComparisons);
store.register_late_pass(|| box question_mark::QuestionMark);
store.register_early_pass(|| box suspicious_operation_groupings::SuspiciousOperationGroupings);
store.register_late_pass(|| box suspicious_trait_impl::SuspiciousImpl);
store.register_late_pass(|| box map_unit_fn::MapUnit);
store.register_late_pass(|| box inherent_impl::MultipleInherentImpl::default());
@ -1107,10 +1138,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box cargo_common_metadata::CargoCommonMetadata);
store.register_late_pass(|| box multiple_crate_versions::MultipleCrateVersions);
store.register_late_pass(|| box wildcard_dependencies::WildcardDependencies);
store.register_early_pass(|| box literal_representation::LiteralDigitGrouping);
let literal_representation_lint_fraction_readability = conf.unreadable_literal_lint_fractions;
store.register_early_pass(move || box literal_representation::LiteralDigitGrouping::new(literal_representation_lint_fraction_readability));
let literal_representation_threshold = conf.literal_representation_threshold;
store.register_early_pass(move || box literal_representation::DecimalLiteralRepresentation::new(literal_representation_threshold));
store.register_early_pass(|| box utils::internal_lints::ClippyLintsInternal);
let enum_variant_name_threshold = conf.enum_variant_name_threshold;
store.register_early_pass(move || box enum_variants::EnumVariantNames::new(enum_variant_name_threshold));
store.register_early_pass(|| box tabs_in_doc_comments::TabsInDocComments);
@ -1124,7 +1155,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || box large_const_arrays::LargeConstArrays::new(array_size_threshold));
store.register_late_pass(|| box floating_point_arithmetic::FloatingPointArithmetic);
store.register_early_pass(|| box as_conversions::AsConversions);
store.register_early_pass(|| box utils::internal_lints::ProduceIce);
store.register_late_pass(|| box let_underscore::LetUnderscore);
store.register_late_pass(|| box atomic_ordering::AtomicOrdering);
store.register_early_pass(|| box single_component_path_imports::SingleComponentPathImports);
@ -1140,16 +1170,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box dereference::Dereferencing);
store.register_late_pass(|| box option_if_let_else::OptionIfLetElse);
store.register_late_pass(|| box future_not_send::FutureNotSend);
store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls);
store.register_late_pass(|| box if_let_mutex::IfLetMutex);
store.register_late_pass(|| box mut_mutex_lock::MutMutexLock);
store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems);
store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive);
store.register_late_pass(|| box manual_async_fn::ManualAsyncFn);
store.register_early_pass(|| box redundant_field_names::RedundantFieldNames);
store.register_late_pass(|| box vec_resize_to_zero::VecResizeToZero);
store.register_late_pass(|| box panic_in_result_fn::PanicInResultFn);
let single_char_binding_names_threshold = conf.single_char_binding_names_threshold;
store.register_early_pass(move || box non_expressive_names::NonExpressiveNames {
single_char_binding_names_threshold,
@ -1166,14 +1193,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box manual_ok_or::ManualOkOr);
store.register_late_pass(|| box float_equality_without_abs::FloatEqualityWithoutAbs);
store.register_late_pass(|| box async_yields_async::AsyncYieldsAsync);
store.register_late_pass(|| box manual_strip::ManualStrip);
store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem);
let disallowed_methods = conf.disallowed_methods.iter().cloned().collect::<FxHashSet<_>>();
store.register_late_pass(move || box disallowed_method::DisallowedMethod::new(&disallowed_methods));
store.register_early_pass(|| box asm_syntax::InlineAsmX86AttSyntax);
store.register_early_pass(|| box asm_syntax::InlineAsmX86IntelSyntax);
store.register_late_pass(|| box undropped_manually_drops::UndroppedManuallyDrops);
store.register_late_pass(|| box strings::StrToString);
store.register_late_pass(|| box strings::StringToString);
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@ -1192,6 +1218,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&integer_division::INTEGER_DIVISION),
LintId::of(&let_underscore::LET_UNDERSCORE_MUST_USE),
LintId::of(&literal_representation::DECIMAL_LITERAL_REPRESENTATION),
LintId::of(&map_err_ignore::MAP_ERR_IGNORE),
LintId::of(&matches::REST_PAT_IN_FULLY_BOUND_STRUCTS),
LintId::of(&matches::WILDCARD_ENUM_MATCH_ARM),
LintId::of(&mem_forget::MEM_FORGET),
@ -1215,6 +1242,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&shadow::SHADOW_REUSE),
LintId::of(&shadow::SHADOW_SAME),
LintId::of(&strings::STRING_ADD),
LintId::of(&strings::STRING_TO_STRING),
LintId::of(&strings::STR_TO_STRING),
LintId::of(&types::RC_BUFFER),
LintId::of(&unwrap_in_result::UNWRAP_IN_RESULT),
LintId::of(&verbose_file_reads::VERBOSE_FILE_READS),
@ -1256,7 +1285,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&loops::EXPLICIT_ITER_LOOP),
LintId::of(&macro_use::MACRO_USE_IMPORTS),
LintId::of(&manual_ok_or::MANUAL_OK_OR),
LintId::of(&map_err_ignore::MAP_ERR_IGNORE),
LintId::of(&match_on_vec_items::MATCH_ON_VEC_ITEMS),
LintId::of(&matches::MATCH_BOOL),
LintId::of(&matches::MATCH_SAME_ARMS),
@ -1304,6 +1332,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&wildcard_imports::WILDCARD_IMPORTS),
]);
#[cfg(feature = "internal-lints")]
store.register_group(true, "clippy::internal", Some("clippy_internal"), vec![
LintId::of(&utils::internal_lints::CLIPPY_LINTS_INTERNAL),
LintId::of(&utils::internal_lints::COLLAPSIBLE_SPAN_LINT_CALLS),
@ -1337,6 +1366,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&booleans::NONMINIMAL_BOOL),
LintId::of(&bytecount::NAIVE_BYTECOUNT),
LintId::of(&collapsible_if::COLLAPSIBLE_IF),
LintId::of(&collapsible_match::COLLAPSIBLE_MATCH),
LintId::of(&comparison_chain::COMPARISON_CHAIN),
LintId::of(&copies::IFS_SAME_COND),
LintId::of(&copies::IF_SAME_THEN_ELSE),
@ -1533,9 +1563,11 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&self_assignment::SELF_ASSIGNMENT),
LintId::of(&serde_api::SERDE_API_MISUSE),
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE),
LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES),
LintId::of(&suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
LintId::of(&swap::ALMOST_SWAPPED),
@ -1602,6 +1634,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&blacklisted_name::BLACKLISTED_NAME),
LintId::of(&blocks_in_if_conditions::BLOCKS_IN_IF_CONDITIONS),
LintId::of(&collapsible_if::COLLAPSIBLE_IF),
LintId::of(&collapsible_match::COLLAPSIBLE_MATCH),
LintId::of(&comparison_chain::COMPARISON_CHAIN),
LintId::of(&default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(&doc::MISSING_SAFETY_DOC),
@ -1687,6 +1720,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&returns::LET_AND_RETURN),
LintId::of(&returns::NEEDLESS_RETURN),
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(&suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
LintId::of(&tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
LintId::of(&to_digit_is_some::TO_DIGIT_IS_SOME),
LintId::of(&try_err::TRY_ERR),
@ -1839,6 +1873,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&regex::INVALID_REGEX),
LintId::of(&self_assignment::SELF_ASSIGNMENT),
LintId::of(&serde_api::SERDE_API_MISUSE),
LintId::of(&size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
LintId::of(&swap::ALMOST_SWAPPED),
@ -1930,14 +1965,6 @@ fn register_removed_non_tool_lints(store: &mut rustc_lint::LintStore) {
"unstable_as_mut_slice",
"`Vec::as_mut_slice` has been stabilized in 1.7",
);
store.register_removed(
"str_to_string",
"using `str::to_string` is common even today and specialization will likely happen soon",
);
store.register_removed(
"string_to_string",
"using `string::to_string` is common even today and specialization will likely happen soon",
);
store.register_removed(
"misaligned_transmute",
"this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr",

View File

@ -11,7 +11,7 @@ use rustc_ast::ast::{Expr, ExprKind, Lit, LitKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
declare_clippy_lint! {
/// **What it does:** Warns if a long integral or floating-point constant does
@ -32,7 +32,7 @@ declare_clippy_lint! {
/// ```
pub UNREADABLE_LITERAL,
pedantic,
"long integer literal without underscores"
"long literal without underscores"
}
declare_clippy_lint! {
@ -208,7 +208,13 @@ impl WarningType {
}
}
declare_lint_pass!(LiteralDigitGrouping => [
#[allow(clippy::module_name_repetitions)]
#[derive(Copy, Clone)]
pub struct LiteralDigitGrouping {
lint_fraction_readability: bool,
}
impl_lint_pass!(LiteralDigitGrouping => [
UNREADABLE_LITERAL,
INCONSISTENT_DIGIT_GROUPING,
LARGE_DIGIT_GROUPS,
@ -223,7 +229,7 @@ impl EarlyLintPass for LiteralDigitGrouping {
}
if let ExprKind::Lit(ref lit) = expr.kind {
Self::check_lit(cx, lit)
self.check_lit(cx, lit)
}
}
}
@ -232,7 +238,13 @@ impl EarlyLintPass for LiteralDigitGrouping {
const UUID_GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12];
impl LiteralDigitGrouping {
fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
pub fn new(lint_fraction_readability: bool) -> Self {
Self {
lint_fraction_readability,
}
}
fn check_lit(self, cx: &EarlyContext<'_>, lit: &Lit) {
if_chain! {
if let Some(src) = snippet_opt(cx, lit.span);
if let Some(mut num_lit) = NumericLiteral::from_lit(&src, &lit);
@ -247,9 +259,12 @@ impl LiteralDigitGrouping {
let result = (|| {
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix)?;
let integral_group_size = Self::get_group_size(num_lit.integer.split('_'), num_lit.radix, true)?;
if let Some(fraction) = num_lit.fraction {
let fractional_group_size = Self::get_group_size(fraction.rsplit('_'), num_lit.radix)?;
let fractional_group_size = Self::get_group_size(
fraction.rsplit('_'),
num_lit.radix,
self.lint_fraction_readability)?;
let consistent = Self::parts_consistent(integral_group_size,
fractional_group_size,
@ -363,7 +378,11 @@ impl LiteralDigitGrouping {
/// Returns the size of the digit groups (or None if ungrouped) if successful,
/// otherwise returns a `WarningType` for linting.
fn get_group_size<'a>(groups: impl Iterator<Item = &'a str>, radix: Radix) -> Result<Option<usize>, WarningType> {
fn get_group_size<'a>(
groups: impl Iterator<Item = &'a str>,
radix: Radix,
lint_unreadable: bool,
) -> Result<Option<usize>, WarningType> {
let mut groups = groups.map(str::len);
let first = groups.next().expect("At least one group");
@ -380,7 +399,7 @@ impl LiteralDigitGrouping {
} else {
Ok(Some(second))
}
} else if first > 5 {
} else if first > 5 && lint_unreadable {
Err(WarningType::UnreadableLiteral)
} else {
Ok(None)

View File

@ -2,6 +2,7 @@ use crate::consts::constant;
use crate::utils::paths;
use crate::utils::sugg::Sugg;
use crate::utils::usage::{is_unused, mutated_variables};
use crate::utils::visitors::LocalUsedVisitor;
use crate::utils::{
contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item,
@ -1919,8 +1920,7 @@ fn check_for_single_element_loop<'tcx>(
if_chain! {
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref arg_expr) = arg.kind;
if let PatKind::Binding(.., target, _) = pat.kind;
if let ExprKind::Array(ref arg_expr_list) = arg_expr.kind;
if let [arg_expression] = arg_expr_list;
if let ExprKind::Array([arg_expression]) = arg_expr.kind;
if let ExprKind::Path(ref list_item) = arg_expression.kind;
if let Some(list_item_name) = single_segment_path(list_item).map(|ps| ps.ident.name);
if let ExprKind::Block(ref block, _) = body.kind;
@ -2025,8 +2025,7 @@ fn check_for_mutability(cx: &LateContext<'_>, bound: &Expr<'_>) -> Option<HirId>
let node_str = cx.tcx.hir().get(hir_id);
if_chain! {
if let Node::Binding(pat) = node_str;
if let PatKind::Binding(bind_ann, ..) = pat.kind;
if let BindingAnnotation::Mutable = bind_ann;
if let PatKind::Binding(BindingAnnotation::Mutable, ..) = pat.kind;
then {
return Some(hir_id);
}
@ -2071,28 +2070,6 @@ fn pat_is_wild<'tcx>(pat: &'tcx PatKind<'_>, body: &'tcx Expr<'_>) -> bool {
}
}
struct LocalUsedVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
local: HirId,
used: bool,
}
impl<'a, 'tcx> Visitor<'tcx> for LocalUsedVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if same_var(self.cx, expr, self.local) {
self.used = true;
} else {
walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}
struct VarVisitor<'a, 'tcx> {
/// context reference
cx: &'a LateContext<'tcx>,
@ -2126,11 +2103,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
then {
let index_used_directly = same_var(self.cx, idx, self.var);
let indexed_indirectly = {
let mut used_visitor = LocalUsedVisitor {
cx: self.cx,
local: self.var,
used: false,
};
let mut used_visitor = LocalUsedVisitor::new(self.var);
walk_expr(&mut used_visitor, idx);
used_visitor.used
};
@ -2950,7 +2923,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo
for ref stmt in block.stmts {
if_chain! {
if let StmtKind::Local(
Local { pat: Pat { kind: PatKind::Binding(_, _, ident, .. ), .. },
Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
init: Some(ref init_expr), .. }
) = stmt.kind;
if let ExprKind::MethodCall(ref method_name, _, &[ref iter_source], ..) = init_expr.kind;
@ -2964,6 +2937,16 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo
if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
if iter_calls.len() == 1;
then {
let mut used_count_visitor = UsedCountVisitor {
cx,
id: *pat_id,
count: 0,
};
walk_block(&mut used_count_visitor, block);
if used_count_visitor.count > 1 {
return;
}
// Suggest replacing iter_call with iter_replacement, and removing stmt
let iter_call = &iter_calls[0];
span_lint_and_then(
@ -3087,6 +3070,28 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor {
}
}
struct UsedCountVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
id: HirId,
count: usize,
}
impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if same_var(self.cx, expr, self.id) {
self.count += 1;
} else {
walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
}
/// Detect the occurrences of calls to `iter` or `into_iter` for the
/// given identifier
fn detect_iter_and_into_iters<'tcx>(block: &'tcx Block<'tcx>, identifier: Ident) -> Option<Vec<IterFunction>> {

View File

@ -1,12 +1,15 @@
use crate::utils::{snippet_opt, span_lint_and_then};
use crate::utils::{meets_msrv, snippet_opt, span_lint_and_then};
use if_chain::if_chain;
use rustc_ast::ast::{Attribute, Item, ItemKind, StructField, Variant, VariantData, VisibilityKind};
use rustc_attr as attr;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Span};
const MANUAL_NON_EXHAUSTIVE_MSRV: RustcVersion = RustcVersion::new(1, 40, 0);
declare_clippy_lint! {
/// **What it does:** Checks for manual implementations of the non-exhaustive pattern.
///
@ -55,10 +58,26 @@ declare_clippy_lint! {
"manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
}
declare_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
#[derive(Clone)]
pub struct ManualNonExhaustive {
msrv: Option<RustcVersion>,
}
impl ManualNonExhaustive {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
impl EarlyLintPass for ManualNonExhaustive {
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
if !meets_msrv(self.msrv.as_ref(), &MANUAL_NON_EXHAUSTIVE_MSRV) {
return;
}
match &item.kind {
ItemKind::Enum(def, _) => {
check_manual_non_exhaustive_enum(cx, item, &def.variants);
@ -73,6 +92,8 @@ impl EarlyLintPass for ManualNonExhaustive {
_ => {},
}
}
extract_msrv_attr!(EarlyContext);
}
fn check_manual_non_exhaustive_enum(cx: &EarlyContext<'_>, item: &Item, variants: &[Variant]) {

View File

@ -1,7 +1,7 @@
use crate::consts::{constant, Constant};
use crate::utils::usage::mutated_variables;
use crate::utils::{
eq_expr_value, higher, match_def_path, multispan_sugg, paths, qpath_res, snippet, span_lint_and_then,
eq_expr_value, higher, match_def_path, meets_msrv, multispan_sugg, paths, qpath_res, snippet, span_lint_and_then,
};
use if_chain::if_chain;
@ -10,13 +10,16 @@ use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::BinOpKind;
use rustc_hir::{BorrowKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Spanned;
use rustc_span::Span;
const MANUAL_STRIP_MSRV: RustcVersion = RustcVersion::new(1, 45, 0);
declare_clippy_lint! {
/// **What it does:**
/// Suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing using
@ -51,7 +54,18 @@ declare_clippy_lint! {
"suggests using `strip_{prefix,suffix}` over `str::{starts,ends}_with` and slicing"
}
declare_lint_pass!(ManualStrip => [MANUAL_STRIP]);
pub struct ManualStrip {
msrv: Option<RustcVersion>,
}
impl ManualStrip {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl_lint_pass!(ManualStrip => [MANUAL_STRIP]);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum StripKind {
@ -61,6 +75,10 @@ enum StripKind {
impl<'tcx> LateLintPass<'tcx> for ManualStrip {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !meets_msrv(self.msrv.as_ref(), &MANUAL_STRIP_MSRV) {
return;
}
if_chain! {
if let Some((cond, then, _)) = higher::if_block(&expr);
if let ExprKind::MethodCall(_, _, [target_arg, pattern], _) = cond.kind;
@ -114,6 +132,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
}
}
}
extract_msrv_attr!(LateContext);
}
// Returns `Some(arg)` if `expr` matches `arg.len()` and `None` otherwise.
@ -199,8 +219,7 @@ fn find_stripping<'tcx>(
if is_ref_str(self.cx, ex);
let unref = peel_ref(ex);
if let ExprKind::Index(indexed, index) = &unref.kind;
if let Some(range) = higher::range(index);
if let higher::Range { start, end, .. } = range;
if let Some(higher::Range { start, end, .. }) = higher::range(index);
if let ExprKind::Path(path) = &indexed.kind;
if qpath_res(self.cx, path, ex.hir_id) == self.target;
then {

View File

@ -99,7 +99,7 @@ declare_clippy_lint! {
/// }
/// ```
pub MAP_ERR_IGNORE,
pedantic,
restriction,
"`map_err` should not ignore the original error"
}
@ -133,9 +133,9 @@ impl<'tcx> LateLintPass<'tcx> for MapErrIgnore {
cx,
MAP_ERR_IGNORE,
body_span,
"`map_err(|_|...` ignores the original error",
"`map_err(|_|...` wildcard pattern discards the original error",
None,
"Consider wrapping the error in an enum variant",
"Consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)",
);
}
}

View File

@ -3,8 +3,8 @@ use crate::utils::sugg::Sugg;
use crate::utils::usage::is_unused;
use crate::utils::{
expr_block, get_arg_name, get_parent_expr, in_macro, indent_of, is_allowed, is_expn_of, is_refutable,
is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, multispan_sugg, remove_blocks, snippet,
snippet_block, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
is_type_diagnostic_item, is_wild, match_qpath, match_type, match_var, meets_msrv, multispan_sugg, remove_blocks,
snippet, snippet_block, snippet_with_applicability, span_lint_and_help, span_lint_and_note, span_lint_and_sugg,
span_lint_and_then,
};
use crate::utils::{paths, search_same, SpanlessEq, SpanlessHash};
@ -20,6 +20,7 @@ use rustc_hir::{
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty, TyS};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::{Span, Spanned};
use rustc_span::{sym, Symbol};
@ -411,8 +412,8 @@ declare_clippy_lint! {
}
declare_clippy_lint! {
/// **What it does:** Lint for redundant pattern matching over `Result` or
/// `Option`
/// **What it does:** Lint for redundant pattern matching over `Result`, `Option`,
/// `std::task::Poll` or `std::net::IpAddr`
///
/// **Why is this bad?** It's more concise and clear to just use the proper
/// utility function
@ -422,10 +423,16 @@ declare_clippy_lint! {
/// **Example:**
///
/// ```rust
/// # use std::task::Poll;
/// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
/// if let Ok(_) = Ok::<i32, i32>(42) {}
/// if let Err(_) = Err::<i32, i32>(42) {}
/// if let None = None::<()> {}
/// if let Some(_) = Some(42) {}
/// if let Poll::Pending = Poll::Pending::<()> {}
/// if let Poll::Ready(_) = Poll::Ready(42) {}
/// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
/// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
/// match Ok::<i32, i32>(42) {
/// Ok(_) => true,
/// Err(_) => false,
@ -435,10 +442,16 @@ declare_clippy_lint! {
/// The more idiomatic use would be:
///
/// ```rust
/// # use std::task::Poll;
/// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
/// if Ok::<i32, i32>(42).is_ok() {}
/// if Err::<i32, i32>(42).is_err() {}
/// if None::<()>.is_none() {}
/// if Some(42).is_some() {}
/// if Poll::Pending::<()>.is_pending() {}
/// if Poll::Ready(42).is_ready() {}
/// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
/// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
/// Ok::<i32, i32>(42).is_ok();
/// ```
pub REDUNDANT_PATTERN_MATCHING,
@ -452,7 +465,8 @@ declare_clippy_lint! {
///
/// **Why is this bad?** Readability and needless complexity.
///
/// **Known problems:** None
/// **Known problems:** This lint falsely triggers, if there are arms with
/// `cfg` attributes that remove an arm evaluating to `false`.
///
/// **Example:**
/// ```rust
@ -521,9 +535,20 @@ declare_clippy_lint! {
#[derive(Default)]
pub struct Matches {
msrv: Option<RustcVersion>,
infallible_destructuring_match_linted: bool,
}
impl Matches {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self {
msrv,
..Matches::default()
}
}
}
impl_lint_pass!(Matches => [
SINGLE_MATCH,
MATCH_REF_PATS,
@ -543,6 +568,8 @@ impl_lint_pass!(Matches => [
MATCH_SAME_ARMS,
]);
const MATCH_LIKE_MATCHES_MACRO_MSRV: RustcVersion = RustcVersion::new(1, 42, 0);
impl<'tcx> LateLintPass<'tcx> for Matches {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if in_external_macro(cx.sess(), expr.span) || in_macro(expr.span) {
@ -550,7 +577,12 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
redundant_pattern_match::check(cx, expr);
if !check_match_like_matches(cx, expr) {
if meets_msrv(self.msrv.as_ref(), &MATCH_LIKE_MATCHES_MACRO_MSRV) {
if !check_match_like_matches(cx, expr) {
lint_match_arms(cx, expr);
}
} else {
lint_match_arms(cx, expr);
}
@ -614,8 +646,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
if_chain! {
if !in_external_macro(cx.sess(), pat.span);
if !in_macro(pat.span);
if let PatKind::Struct(ref qpath, fields, true) = pat.kind;
if let QPath::Resolved(_, ref path) = qpath;
if let PatKind::Struct(QPath::Resolved(_, ref path), fields, true) = pat.kind;
if let Some(def_id) = path.res.opt_def_id();
let ty = cx.tcx.type_of(def_id);
if let ty::Adt(def, _) = ty.kind();
@ -634,6 +665,8 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
}
}
extract_msrv_attr!(LateContext);
}
#[rustfmt::skip]
@ -922,16 +955,14 @@ fn check_wild_enum_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>])
if let QPath::Resolved(_, p) = path {
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
}
} else if let PatKind::TupleStruct(ref path, ref patterns, ..) = arm.pat.kind {
if let QPath::Resolved(_, p) = path {
// Some simple checks for exhaustive patterns.
// There is a room for improvements to detect more cases,
// but it can be more expensive to do so.
let is_pattern_exhaustive =
|pat: &&Pat<'_>| matches!(pat.kind, PatKind::Wild | PatKind::Binding(.., None));
if patterns.iter().all(is_pattern_exhaustive) {
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
}
} else if let PatKind::TupleStruct(QPath::Resolved(_, p), ref patterns, ..) = arm.pat.kind {
// Some simple checks for exhaustive patterns.
// There is a room for improvements to detect more cases,
// but it can be more expensive to do so.
let is_pattern_exhaustive =
|pat: &&Pat<'_>| matches!(pat.kind, PatKind::Wild | PatKind::Binding(.., None));
if patterns.iter().all(is_pattern_exhaustive) {
missing_variants.retain(|e| e.ctor_def_id != Some(p.res.def_id()));
}
}
}
@ -1134,13 +1165,16 @@ fn find_matches_sugg(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr
if b0 != b1;
let if_guard = &b0_arms[0].guard;
if if_guard.is_none() || b0_arms.len() == 1;
if b0_arms[0].attrs.is_empty();
if b0_arms[1..].iter()
.all(|arm| {
find_bool_lit(&arm.body.kind, desugared).map_or(false, |b| b == b0) &&
arm.guard.is_none()
arm.guard.is_none() && arm.attrs.is_empty()
});
then {
let mut applicability = Applicability::MachineApplicable;
// The suggestion may be incorrect, because some arms can have `cfg` attributes
// evaluated into `false` and so such arms will be stripped before.
let mut applicability = Applicability::MaybeIncorrect;
let pat = {
use itertools::Itertools as _;
b0_arms.iter()
@ -1403,8 +1437,7 @@ fn is_ref_some_arm(arm: &Arm<'_>) -> Option<BindingAnnotation> {
if let ExprKind::Call(ref e, ref args) = remove_blocks(&arm.body).kind;
if let ExprKind::Path(ref some_path) = e.kind;
if match_qpath(some_path, &paths::OPTION_SOME) && args.len() == 1;
if let ExprKind::Path(ref qpath) = args[0].kind;
if let &QPath::Resolved(_, ref path2) = qpath;
if let ExprKind::Path(QPath::Resolved(_, ref path2)) = args[0].kind;
if path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name;
then {
return Some(rb)
@ -1538,6 +1571,12 @@ mod redundant_pattern_match {
"is_err()"
} else if match_qpath(path, &paths::OPTION_SOME) {
"is_some()"
} else if match_qpath(path, &paths::POLL_READY) {
"is_ready()"
} else if match_qpath(path, &paths::IPADDR_V4) {
"is_ipv4()"
} else if match_qpath(path, &paths::IPADDR_V6) {
"is_ipv6()"
} else {
return;
}
@ -1545,7 +1584,15 @@ mod redundant_pattern_match {
return;
}
},
PatKind::Path(ref path) if match_qpath(path, &paths::OPTION_NONE) => "is_none()",
PatKind::Path(ref path) => {
if match_qpath(path, &paths::OPTION_NONE) {
"is_none()"
} else if match_qpath(path, &paths::POLL_PENDING) {
"is_pending()"
} else {
return;
}
},
_ => return,
};
@ -1610,6 +1657,17 @@ mod redundant_pattern_match {
"is_ok()",
"is_err()",
)
.or_else(|| {
find_good_method_for_match(
arms,
path_left,
path_right,
&paths::IPADDR_V4,
&paths::IPADDR_V6,
"is_ipv4()",
"is_ipv6()",
)
})
} else {
None
}
@ -1628,6 +1686,17 @@ mod redundant_pattern_match {
"is_some()",
"is_none()",
)
.or_else(|| {
find_good_method_for_match(
arms,
path_left,
path_right,
&paths::POLL_READY,
&paths::POLL_PENDING,
"is_ready()",
"is_pending()",
)
})
} else {
None
}

View File

@ -90,8 +90,7 @@ fn is_min_or_max<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option<M
if_chain! {
if let hir::ExprKind::Call(func, args) = &expr.kind;
if args.is_empty();
if let hir::ExprKind::Path(path) = &func.kind;
if let hir::QPath::TypeRelative(_, segment) = path;
if let hir::ExprKind::Path(hir::QPath::TypeRelative(_, segment)) = &func.kind;
then {
match &*segment.ident.as_str() {
"max_value" => return Some(MinMax::Max),

View File

@ -14,13 +14,12 @@ use if_chain::if_chain;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{TraitItem, TraitItemKind};
use rustc_lint::{LateContext, LateLintPass, Lint, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, TraitRef, Ty, TyS};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
use rustc_span::symbol::{sym, SymbolStr};
@ -28,11 +27,12 @@ use crate::consts::{constant, Constant};
use crate::utils::eager_or_lazy::is_lazyness_candidate;
use crate::utils::usage::mutated_variables;
use crate::utils::{
contains_ty, get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait, in_macro,
is_copy, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment, match_def_path, match_qpath,
match_trait_method, match_type, match_var, method_calls, method_chain_args, paths, remove_blocks, return_ty,
single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, walk_ptrs_ty_depth, SpanlessEq,
contains_return, contains_ty, get_arg_name, get_parent_expr, get_trait_def_id, has_iter_method, higher,
implements_trait, in_macro, is_copy, is_expn_of, is_type_diagnostic_item, iter_input_pats, last_path_segment,
match_def_path, match_qpath, match_trait_method, match_type, match_var, meets_msrv, method_calls,
method_chain_args, paths, remove_blocks, return_ty, single_segment_path, snippet, snippet_with_applicability,
snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg,
walk_ptrs_ty_depth, SpanlessEq,
};
declare_clippy_lint! {
@ -1404,7 +1404,18 @@ declare_clippy_lint! {
"use `.collect()` instead of `::from_iter()`"
}
declare_lint_pass!(Methods => [
pub struct Methods {
msrv: Option<RustcVersion>,
}
impl Methods {
#[must_use]
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl_lint_pass!(Methods => [
UNWRAP_USED,
EXPECT_USED,
SHOULD_IMPLEMENT_TRAIT,
@ -1531,8 +1542,12 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
check_pointer_offset(cx, expr, arg_lists[0])
},
["is_file", ..] => lint_filetype_is_file(cx, expr, arg_lists[0]),
["map", "as_ref"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false),
["map", "as_mut"] => lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true),
["map", "as_ref"] => {
lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], false, self.msrv.as_ref())
},
["map", "as_mut"] => {
lint_option_as_ref_deref(cx, expr, arg_lists[1], arg_lists[0], true, self.msrv.as_ref())
},
["unwrap_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "unwrap_or"),
["get_or_insert_with", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "get_or_insert"),
["ok_or_else", ..] => unnecessary_lazy_eval::lint(cx, expr, arg_lists[0], "ok_or"),
@ -1738,6 +1753,8 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
}
}
}
extract_msrv_attr!(LateContext);
}
/// Checks for the `OR_FUN_CALL` lint.
@ -3453,6 +3470,8 @@ fn lint_suspicious_map(cx: &LateContext<'_>, expr: &hir::Expr<'_>) {
);
}
const OPTION_AS_REF_DEREF_MSRV: RustcVersion = RustcVersion::new(1, 40, 0);
/// lint use of `_.as_ref().map(Deref::deref)` for `Option`s
fn lint_option_as_ref_deref<'tcx>(
cx: &LateContext<'tcx>,
@ -3460,7 +3479,12 @@ fn lint_option_as_ref_deref<'tcx>(
as_ref_args: &[hir::Expr<'_>],
map_args: &[hir::Expr<'_>],
is_mut: bool,
msrv: Option<&RustcVersion>,
) {
if !meets_msrv(msrv, &OPTION_AS_REF_DEREF_MSRV) {
return;
}
let same_mutability = |m| (is_mut && m == &hir::Mutability::Mut) || (!is_mut && m == &hir::Mutability::Not);
let option_ty = cx.typeck_results().expr_ty(&as_ref_args[0]);
@ -3846,36 +3870,6 @@ fn is_bool(ty: &hir::Ty<'_>) -> bool {
}
}
// Returns `true` if `expr` contains a return expression
fn contains_return(expr: &hir::Expr<'_>) -> bool {
struct RetCallFinder {
found: bool,
}
impl<'tcx> intravisit::Visitor<'tcx> for RetCallFinder {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if self.found {
return;
}
if let hir::ExprKind::Ret(..) = &expr.kind {
self.found = true;
} else {
intravisit::walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::None
}
}
let mut visitor = RetCallFinder { found: false };
visitor.visit_expr(expr);
visitor.found
}
fn check_pointer_offset(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>]) {
if_chain! {
if args.len() == 2;

View File

@ -18,7 +18,7 @@ use crate::utils::sugg::Sugg;
use crate::utils::{
get_item_name, get_parent_expr, higher, implements_trait, in_constant, is_integer_const, iter_input_pats,
last_path_segment, match_qpath, match_trait_method, paths, snippet, snippet_opt, span_lint, span_lint_and_sugg,
span_lint_and_then, span_lint_hir_and_then, SpanlessEq,
span_lint_and_then, span_lint_hir_and_then, unsext, SpanlessEq,
};
declare_clippy_lint! {
@ -139,12 +139,14 @@ declare_clippy_lint! {
}
declare_clippy_lint! {
/// **What it does:** Checks for getting the remainder of a division by one.
/// **What it does:** Checks for getting the remainder of a division by one or minus
/// one.
///
/// **Why is this bad?** The result can only ever be zero. No one will write
/// such code deliberately, unless trying to win an Underhanded Rust
/// Contest. Even for that contest, it's probably a bad idea. Use something more
/// underhanded.
/// **Why is this bad?** The result for a divisor of one can only ever be zero; for
/// minus one it can cause panic/overflow (if the left operand is the minimal value of
/// the respective integer type) or results in zero. No one will write such code
/// deliberately, unless trying to win an Underhanded Rust Contest. Even for that
/// contest, it's probably a bad idea. Use something more underhanded.
///
/// **Known problems:** None.
///
@ -152,10 +154,11 @@ declare_clippy_lint! {
/// ```rust
/// # let x = 1;
/// let a = x % 1;
/// let a = x % -1;
/// ```
pub MODULO_ONE,
correctness,
"taking a number modulo 1, which always returns 0"
"taking a number modulo +/-1, which can either panic/overflow or always returns 0"
}
declare_clippy_lint! {
@ -378,60 +381,8 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
return;
},
ExprKind::Binary(ref cmp, ref left, ref right) => {
let op = cmp.node;
if op.is_comparison() {
check_nan(cx, left, expr);
check_nan(cx, right, expr);
check_to_owned(cx, left, right, true);
check_to_owned(cx, right, left, false);
}
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
if is_allowed(cx, left) || is_allowed(cx, right) {
return;
}
// Allow comparing the results of signum()
if is_signum(cx, left) && is_signum(cx, right) {
return;
}
if let Some(name) = get_item_name(cx, expr) {
let name = name.as_str();
if name == "eq"
|| name == "ne"
|| name == "is_nan"
|| name.starts_with("eq_")
|| name.ends_with("_eq")
{
return;
}
}
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
let (lint, msg) = get_lint_and_message(
is_named_constant(cx, left) || is_named_constant(cx, right),
is_comparing_arrays,
);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let lhs = Sugg::hir(cx, left, "..");
let rhs = Sugg::hir(cx, right, "..");
if !is_comparing_arrays {
diag.span_suggestion(
expr.span,
"consider comparing them within some margin of error",
format!(
"({}).abs() {} error_margin",
lhs - rhs,
if op == BinOpKind::Eq { '<' } else { '>' }
),
Applicability::HasPlaceholders, // snippet
);
}
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
});
} else if op == BinOpKind::Rem && is_integer_const(cx, right, 1) {
span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
}
check_binary(cx, expr, cmp, left, right);
return;
},
_ => {},
}
@ -744,3 +695,74 @@ fn check_cast(cx: &LateContext<'_>, span: Span, e: &Expr<'_>, ty: &hir::Ty<'_>)
}
}
}
fn check_binary(
cx: &LateContext<'a>,
expr: &Expr<'_>,
cmp: &rustc_span::source_map::Spanned<rustc_hir::BinOpKind>,
left: &'a Expr<'_>,
right: &'a Expr<'_>,
) {
let op = cmp.node;
if op.is_comparison() {
check_nan(cx, left, expr);
check_nan(cx, right, expr);
check_to_owned(cx, left, right, true);
check_to_owned(cx, right, left, false);
}
if (op == BinOpKind::Eq || op == BinOpKind::Ne) && (is_float(cx, left) || is_float(cx, right)) {
if is_allowed(cx, left) || is_allowed(cx, right) {
return;
}
// Allow comparing the results of signum()
if is_signum(cx, left) && is_signum(cx, right) {
return;
}
if let Some(name) = get_item_name(cx, expr) {
let name = name.as_str();
if name == "eq" || name == "ne" || name == "is_nan" || name.starts_with("eq_") || name.ends_with("_eq") {
return;
}
}
let is_comparing_arrays = is_array(cx, left) || is_array(cx, right);
let (lint, msg) = get_lint_and_message(
is_named_constant(cx, left) || is_named_constant(cx, right),
is_comparing_arrays,
);
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let lhs = Sugg::hir(cx, left, "..");
let rhs = Sugg::hir(cx, right, "..");
if !is_comparing_arrays {
diag.span_suggestion(
expr.span,
"consider comparing them within some margin of error",
format!(
"({}).abs() {} error_margin",
lhs - rhs,
if op == BinOpKind::Eq { '<' } else { '>' }
),
Applicability::HasPlaceholders, // snippet
);
}
diag.note("`f32::EPSILON` and `f64::EPSILON` are available for the `error_margin`");
});
} else if op == BinOpKind::Rem {
if is_integer_const(cx, right, 1) {
span_lint(cx, MODULO_ONE, expr.span, "any number modulo 1 will be 0");
}
if let ty::Int(ity) = cx.typeck_results().expr_ty(right).kind() {
if is_integer_const(cx, right, unsext(cx.tcx, -1, *ity)) {
span_lint(
cx,
MODULO_ONE,
expr.span,
"any number modulo -1 will panic/overflow or result in 0",
);
}
};
}
}

View File

@ -6,7 +6,6 @@ use crate::utils::sugg::Sugg;
use crate::utils::{
higher, is_expn_of, parent_node_is_if_expr, snippet_with_applicability, span_lint, span_lint_and_sugg,
};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, StmtKind, UnOp};
@ -198,13 +197,9 @@ struct ExpressionInfoWithSpan {
}
fn is_unary_not(e: &Expr<'_>) -> (bool, Span) {
if_chain! {
if let ExprKind::Unary(unop, operand) = e.kind;
if let UnOp::UnNot = unop;
then {
return (true, operand.span);
}
};
if let ExprKind::Unary(UnOp::UnNot, operand) = e.kind {
return (true, operand.span);
}
(false, e.span)
}

View File

@ -66,7 +66,7 @@ declare_clippy_lint! {
/// ```
pub UNREACHABLE,
restriction,
"`unreachable!` should not be present in production code"
"usage of the `unreachable!` macro"
}
declare_lint_pass!(PanicUnimplemented => [UNIMPLEMENTED, UNREACHABLE, TODO, PANIC]);
@ -85,12 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for PanicUnimplemented {
} else if is_expn_of(expr.span, "todo").is_some() {
span_lint(cx, TODO, span, "`todo` should not be present in production code");
} else if is_expn_of(expr.span, "unreachable").is_some() {
span_lint(
cx,
UNREACHABLE,
span,
"`unreachable` should not be present in production code",
);
span_lint(cx, UNREACHABLE, span, "usage of the `unreachable!` macro");
} else if is_expn_of(expr.span, "panic").is_some() {
span_lint(cx, PANIC, span, "`panic` should not be present in production code");
}

View File

@ -176,8 +176,7 @@ impl QuestionMark {
if block.stmts.len() == 1;
if let Some(expr) = block.stmts.iter().last();
if let StmtKind::Semi(ref expr) = expr.kind;
if let ExprKind::Ret(ret_expr) = expr.kind;
if let Some(ret_expr) = ret_expr;
if let ExprKind::Ret(Some(ret_expr)) = expr.kind;
then {
return Some(ret_expr);

View File

@ -104,7 +104,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
cx: &'a LateContext<'tcx>,
path: &'tcx hir::Path<'tcx>,
count: usize,
};
}
impl<'a, 'tcx> hir_visit::Visitor<'tcx> for ClosureUsageCount<'a, 'tcx> {
type Map = Map<'tcx>;
@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall {
fn nested_visit_map(&mut self) -> hir_visit::NestedVisitorMap<Self::Map> {
hir_visit::NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
}
};
}
let mut closure_usage_count = ClosureUsageCount { cx, path, count: 0 };
closure_usage_count.visit_block(block);
closure_usage_count.count

View File

@ -0,0 +1,145 @@
//! Lint on use of `size_of` or `size_of_val` of T in an expression
//! expecting a count of T
use crate::utils::{match_def_path, paths, span_lint_and_help};
use if_chain::if_chain;
use rustc_hir::BinOpKind;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Ty, TyS, TypeAndMut};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// **What it does:** Detects expressions where
/// `size_of::<T>` or `size_of_val::<T>` is used as a
/// count of elements of type `T`
///
/// **Why is this bad?** These functions expect a count
/// of `T` and not a number of bytes
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust,no_run
/// # use std::ptr::copy_nonoverlapping;
/// # use std::mem::size_of;
/// const SIZE: usize = 128;
/// let x = [2u8; SIZE];
/// let mut y = [2u8; SIZE];
/// unsafe { copy_nonoverlapping(x.as_ptr(), y.as_mut_ptr(), size_of::<u8>() * SIZE) };
/// ```
pub SIZE_OF_IN_ELEMENT_COUNT,
correctness,
"using `size_of::<T>` or `size_of_val::<T>` where a count of elements of `T` is expected"
}
declare_lint_pass!(SizeOfInElementCount => [SIZE_OF_IN_ELEMENT_COUNT]);
fn get_size_of_ty(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<Ty<'tcx>> {
match expr.kind {
ExprKind::Call(count_func, _func_args) => {
if_chain! {
if let ExprKind::Path(ref count_func_qpath) = count_func.kind;
if let Some(def_id) = cx.qpath_res(count_func_qpath, count_func.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::MEM_SIZE_OF)
|| match_def_path(cx, def_id, &paths::MEM_SIZE_OF_VAL);
then {
cx.typeck_results().node_substs(count_func.hir_id).types().next()
} else {
None
}
}
},
ExprKind::Binary(op, left, right) if BinOpKind::Mul == op.node || BinOpKind::Div == op.node => {
get_size_of_ty(cx, left).or_else(|| get_size_of_ty(cx, right))
},
ExprKind::Cast(expr, _) => get_size_of_ty(cx, expr),
_ => None,
}
}
fn get_pointee_ty_and_count_expr(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<(Ty<'tcx>, &'tcx Expr<'tcx>)> {
const FUNCTIONS: [&[&str]; 8] = [
&paths::COPY_NONOVERLAPPING,
&paths::COPY,
&paths::WRITE_BYTES,
&paths::PTR_SWAP_NONOVERLAPPING,
&paths::PTR_SLICE_FROM_RAW_PARTS,
&paths::PTR_SLICE_FROM_RAW_PARTS_MUT,
&paths::SLICE_FROM_RAW_PARTS,
&paths::SLICE_FROM_RAW_PARTS_MUT,
];
const METHODS: [&str; 11] = [
"write_bytes",
"copy_to",
"copy_from",
"copy_to_nonoverlapping",
"copy_from_nonoverlapping",
"add",
"wrapping_add",
"sub",
"wrapping_sub",
"offset",
"wrapping_offset",
];
if_chain! {
// Find calls to ptr::{copy, copy_nonoverlapping}
// and ptr::{swap_nonoverlapping, write_bytes},
if let ExprKind::Call(func, [.., count]) = expr.kind;
if let ExprKind::Path(ref func_qpath) = func.kind;
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if FUNCTIONS.iter().any(|func_path| match_def_path(cx, def_id, func_path));
// Get the pointee type
if let Some(pointee_ty) = cx.typeck_results().node_substs(func.hir_id).types().next();
then {
return Some((pointee_ty, count));
}
};
if_chain! {
// Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods
if let ExprKind::MethodCall(method_path, _, [ptr_self, .., count], _) = expr.kind;
let method_ident = method_path.ident.as_str();
if METHODS.iter().any(|m| *m == &*method_ident);
// Get the pointee type
if let ty::RawPtr(TypeAndMut { ty: pointee_ty, .. }) =
cx.typeck_results().expr_ty(ptr_self).kind();
then {
return Some((pointee_ty, count));
}
};
None
}
impl<'tcx> LateLintPass<'tcx> for SizeOfInElementCount {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
const HELP_MSG: &str = "use a count of elements instead of a count of bytes\
, it already gets multiplied by the size of the type";
const LINT_MSG: &str = "found a count of bytes \
instead of a count of elements of `T`";
if_chain! {
// Find calls to functions with an element count parameter and get
// the pointee type and count parameter expression
if let Some((pointee_ty, count_expr)) = get_pointee_ty_and_count_expr(cx, expr);
// Find a size_of call in the count parameter expression and
// check that it's the same type
if let Some(ty_used_for_size_of) = get_size_of_ty(cx, count_expr);
if TyS::same_type(pointee_ty, ty_used_for_size_of);
then {
span_lint_and_help(
cx,
SIZE_OF_IN_ELEMENT_COUNT,
count_expr.span,
LINT_MSG,
None,
HELP_MSG
);
}
};
}
}

View File

@ -2,6 +2,7 @@ use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
use rustc_span::sym;
@ -11,7 +12,7 @@ use if_chain::if_chain;
use crate::utils::SpanlessEq;
use crate::utils::{
get_parent_expr, is_allowed, is_type_diagnostic_item, match_function_call, method_calls, paths, span_lint,
span_lint_and_sugg,
span_lint_and_help, span_lint_and_sugg,
};
declare_clippy_lint! {
@ -221,8 +222,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
if method_names[0] == sym!(as_bytes);
// Check for slicer
if let ExprKind::Struct(ref path, _, _) = right.kind;
if let QPath::LangItem(LangItem::Range, _) = path;
if let ExprKind::Struct(QPath::LangItem(LangItem::Range, _), _, _) = right.kind;
then {
let mut applicability = Applicability::MachineApplicable;
@ -289,3 +289,100 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
}
}
}
declare_clippy_lint! {
/// **What it does:** This lint checks for `.to_string()` method calls on values of type `&str`.
///
/// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string.
/// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better
/// expressed with `.to_owned()`.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// // example code where clippy issues a warning
/// let _ = "str".to_string();
/// ```
/// Use instead:
/// ```rust
/// // example code which does not raise clippy warning
/// let _ = "str".to_owned();
/// ```
pub STR_TO_STRING,
restriction,
"using `to_string()` on a `&str`, which should be `to_owned()`"
}
declare_lint_pass!(StrToString => [STR_TO_STRING]);
impl LateLintPass<'_> for StrToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = &expr.kind;
if path.ident.name == sym!(to_string);
let ty = cx.typeck_results().expr_ty(&args[0]);
if let ty::Ref(_, ty, ..) = ty.kind();
if *ty.kind() == ty::Str;
then {
span_lint_and_help(
cx,
STR_TO_STRING,
expr.span,
"`to_string()` called on a `&str`",
None,
"consider using `.to_owned()`",
);
}
}
}
}
declare_clippy_lint! {
/// **What it does:** This lint checks for `.to_string()` method calls on values of type `String`.
///
/// **Why is this bad?** The `to_string` method is also used on other types to convert them to a string.
/// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`.
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// // example code where clippy issues a warning
/// let msg = String::from("Hello World");
/// let _ = msg.to_string();
/// ```
/// Use instead:
/// ```rust
/// // example code which does not raise clippy warning
/// let msg = String::from("Hello World");
/// let _ = msg.clone();
/// ```
pub STRING_TO_STRING,
restriction,
"using `to_string()` on a `String`, which should be `clone()`"
}
declare_lint_pass!(StringToString => [STRING_TO_STRING]);
impl LateLintPass<'_> for StringToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::MethodCall(path, _, args, _) = &expr.kind;
if path.ident.name == sym!(to_string);
let ty = cx.typeck_results().expr_ty(&args[0]);
if is_type_diagnostic_item(cx, ty, sym!(string_type));
then {
span_lint_and_help(
cx,
STRING_TO_STRING,
expr.span,
"`to_string()` called on a `String`",
None,
"consider using `.clone()`",
);
}
}
}
}

View File

@ -0,0 +1,693 @@
use crate::utils::ast_utils::{eq_id, is_useless_with_eq_exprs, IdentIter};
use crate::utils::{snippet_with_applicability, span_lint_and_sugg};
use core::ops::{Add, AddAssign};
use if_chain::if_chain;
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, StmtKind};
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
use rustc_span::symbol::Ident;
use rustc_span::Span;
declare_clippy_lint! {
/// **What it does:**
/// Checks for unlikely usages of binary operators that are almost
/// certainly typos and/or copy/paste errors, given the other usages
/// of binary operators nearby.
/// **Why is this bad?**
/// They are probably bugs and if they aren't then they look like bugs
/// and you should add a comment explaining why you are doing such an
/// odd set of operations.
/// **Known problems:**
/// There may be some false positives if you are trying to do something
/// unusual that happens to look like a typo.
///
/// **Example:**
///
/// ```rust
/// struct Vec3 {
/// x: f64,
/// y: f64,
/// z: f64,
/// }
///
/// impl Eq for Vec3 {}
///
/// impl PartialEq for Vec3 {
/// fn eq(&self, other: &Self) -> bool {
/// // This should trigger the lint because `self.x` is compared to `other.y`
/// self.x == other.y && self.y == other.y && self.z == other.z
/// }
/// }
/// ```
/// Use instead:
/// ```rust
/// # struct Vec3 {
/// # x: f64,
/// # y: f64,
/// # z: f64,
/// # }
/// // same as above except:
/// impl PartialEq for Vec3 {
/// fn eq(&self, other: &Self) -> bool {
/// // Note we now compare other.x to self.x
/// self.x == other.x && self.y == other.y && self.z == other.z
/// }
/// }
/// ```
pub SUSPICIOUS_OPERATION_GROUPINGS,
style,
"groupings of binary operations that look suspiciously like typos"
}
declare_lint_pass!(SuspiciousOperationGroupings => [SUSPICIOUS_OPERATION_GROUPINGS]);
impl EarlyLintPass for SuspiciousOperationGroupings {
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if expr.span.from_expansion() {
return;
}
if let Some(binops) = extract_related_binops(&expr.kind) {
check_binops(cx, &binops.iter().collect::<Vec<_>>());
let mut op_types = Vec::with_capacity(binops.len());
// We could use a hashmap, etc. to avoid being O(n*m) here, but
// we want the lints to be emitted in a consistent order. Besides,
// m, (the number of distinct `BinOpKind`s in `binops`)
// will often be small, and does have an upper limit.
binops.iter().map(|b| b.op).for_each(|op| {
if !op_types.contains(&op) {
op_types.push(op);
}
});
for op_type in op_types {
let ops: Vec<_> = binops.iter().filter(|b| b.op == op_type).collect();
check_binops(cx, &ops);
}
}
}
}
fn check_binops(cx: &EarlyContext<'_>, binops: &[&BinaryOp<'_>]) {
let binop_count = binops.len();
if binop_count < 2 {
// Single binary operation expressions would likely be false
// positives.
return;
}
let mut one_ident_difference_count = 0;
let mut no_difference_info = None;
let mut double_difference_info = None;
let mut expected_ident_loc = None;
let mut paired_identifiers = FxHashSet::default();
for (i, BinaryOp { left, right, op, .. }) in binops.iter().enumerate() {
match ident_difference_expr(left, right) {
IdentDifference::NoDifference => {
if is_useless_with_eq_exprs(*op) {
// The `eq_op` lint should catch this in this case.
return;
}
no_difference_info = Some(i);
},
IdentDifference::Single(ident_loc) => {
one_ident_difference_count += 1;
if let Some(previous_expected) = expected_ident_loc {
if previous_expected != ident_loc {
// This expression doesn't match the form we're
// looking for.
return;
}
} else {
expected_ident_loc = Some(ident_loc);
}
// If there was only a single difference, all other idents
// must have been the same, and thus were paired.
for id in skip_index(IdentIter::from(*left), ident_loc.index) {
paired_identifiers.insert(id);
}
},
IdentDifference::Double(ident_loc1, ident_loc2) => {
double_difference_info = Some((i, ident_loc1, ident_loc2));
},
IdentDifference::Multiple | IdentDifference::NonIdent => {
// It's too hard to know whether this is a bug or not.
return;
},
}
}
let mut applicability = Applicability::MachineApplicable;
if let Some(expected_loc) = expected_ident_loc {
match (no_difference_info, double_difference_info) {
(Some(i), None) => attempt_to_emit_no_difference_lint(cx, binops, i, expected_loc),
(None, Some((double_difference_index, ident_loc1, ident_loc2))) => {
if_chain! {
if one_ident_difference_count == binop_count - 1;
if let Some(binop) = binops.get(double_difference_index);
then {
let changed_loc = if ident_loc1 == expected_loc {
ident_loc2
} else if ident_loc2 == expected_loc {
ident_loc1
} else {
// This expression doesn't match the form we're
// looking for.
return;
};
if let Some(sugg) = ident_swap_sugg(
cx,
&paired_identifiers,
binop,
changed_loc,
&mut applicability,
) {
emit_suggestion(
cx,
binop.span,
sugg,
applicability,
);
}
}
}
},
_ => {},
}
}
}
fn attempt_to_emit_no_difference_lint(
cx: &EarlyContext<'_>,
binops: &[&BinaryOp<'_>],
i: usize,
expected_loc: IdentLocation,
) {
if let Some(binop) = binops.get(i).cloned() {
// We need to try and figure out which identifier we should
// suggest using instead. Since there could be multiple
// replacement candidates in a given expression, and we're
// just taking the first one, we may get some bad lint
// messages.
let mut applicability = Applicability::MaybeIncorrect;
// We assume that the correct ident is one used elsewhere in
// the other binops, in a place that there was a single
// difference between idents before.
let old_left_ident = get_ident(binop.left, expected_loc);
let old_right_ident = get_ident(binop.right, expected_loc);
for b in skip_index(binops.iter(), i) {
if_chain! {
if let (Some(old_ident), Some(new_ident)) =
(old_left_ident, get_ident(b.left, expected_loc));
if old_ident != new_ident;
if let Some(sugg) = suggestion_with_swapped_ident(
cx,
binop.left,
expected_loc,
new_ident,
&mut applicability,
);
then {
emit_suggestion(
cx,
binop.span,
replace_left_sugg(cx, &binop, &sugg, &mut applicability),
applicability,
);
return;
}
}
if_chain! {
if let (Some(old_ident), Some(new_ident)) =
(old_right_ident, get_ident(b.right, expected_loc));
if old_ident != new_ident;
if let Some(sugg) = suggestion_with_swapped_ident(
cx,
binop.right,
expected_loc,
new_ident,
&mut applicability,
);
then {
emit_suggestion(
cx,
binop.span,
replace_right_sugg(cx, &binop, &sugg, &mut applicability),
applicability,
);
return;
}
}
}
}
}
fn emit_suggestion(cx: &EarlyContext<'_>, span: Span, sugg: String, applicability: Applicability) {
span_lint_and_sugg(
cx,
SUSPICIOUS_OPERATION_GROUPINGS,
span,
"This sequence of operators looks suspiciously like a bug.",
"I think you meant",
sugg,
applicability,
)
}
fn ident_swap_sugg(
cx: &EarlyContext<'_>,
paired_identifiers: &FxHashSet<Ident>,
binop: &BinaryOp<'_>,
location: IdentLocation,
applicability: &mut Applicability,
) -> Option<String> {
let left_ident = get_ident(&binop.left, location)?;
let right_ident = get_ident(&binop.right, location)?;
let sugg = match (
paired_identifiers.contains(&left_ident),
paired_identifiers.contains(&right_ident),
) {
(true, true) | (false, false) => {
// We don't have a good guess of what ident should be
// used instead, in these cases.
*applicability = Applicability::MaybeIncorrect;
// We arbitraily choose one side to suggest changing,
// since we don't have a better guess. If the user
// ends up duplicating a clause, the `logic_bug` lint
// should catch it.
let right_suggestion =
suggestion_with_swapped_ident(cx, &binop.right, location, left_ident, applicability)?;
replace_right_sugg(cx, binop, &right_suggestion, applicability)
},
(false, true) => {
// We haven't seen a pair involving the left one, so
// it's probably what is wanted.
let right_suggestion =
suggestion_with_swapped_ident(cx, &binop.right, location, left_ident, applicability)?;
replace_right_sugg(cx, binop, &right_suggestion, applicability)
},
(true, false) => {
// We haven't seen a pair involving the right one, so
// it's probably what is wanted.
let left_suggestion = suggestion_with_swapped_ident(cx, &binop.left, location, right_ident, applicability)?;
replace_left_sugg(cx, binop, &left_suggestion, applicability)
},
};
Some(sugg)
}
fn replace_left_sugg(
cx: &EarlyContext<'_>,
binop: &BinaryOp<'_>,
left_suggestion: &str,
applicability: &mut Applicability,
) -> String {
format!(
"{} {} {}",
left_suggestion,
binop.op.to_string(),
snippet_with_applicability(cx, binop.right.span, "..", applicability),
)
}
fn replace_right_sugg(
cx: &EarlyContext<'_>,
binop: &BinaryOp<'_>,
right_suggestion: &str,
applicability: &mut Applicability,
) -> String {
format!(
"{} {} {}",
snippet_with_applicability(cx, binop.left.span, "..", applicability),
binop.op.to_string(),
right_suggestion,
)
}
#[derive(Clone, Debug)]
struct BinaryOp<'exprs> {
op: BinOpKind,
span: Span,
left: &'exprs Expr,
right: &'exprs Expr,
}
impl BinaryOp<'exprs> {
fn new(op: BinOpKind, span: Span, (left, right): (&'exprs Expr, &'exprs Expr)) -> Self {
Self { op, span, left, right }
}
}
fn strip_non_ident_wrappers(expr: &Expr) -> &Expr {
let mut output = expr;
loop {
output = match &output.kind {
ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner,
_ => {
return output;
},
};
}
}
fn extract_related_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
append_opt_vecs(chained_binops(kind), if_statment_binops(kind))
}
fn if_statment_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
match kind {
ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind),
ExprKind::Paren(ref e) => if_statment_binops(&e.kind),
ExprKind::Block(ref block, _) => {
let mut output = None;
for stmt in &block.stmts {
match stmt.kind {
StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => {
output = append_opt_vecs(output, if_statment_binops(&e.kind));
},
_ => {},
}
}
output
},
_ => None,
}
}
fn append_opt_vecs<A>(target_opt: Option<Vec<A>>, source_opt: Option<Vec<A>>) -> Option<Vec<A>> {
match (target_opt, source_opt) {
(Some(mut target), Some(mut source)) => {
target.reserve(source.len());
for op in source.drain(..) {
target.push(op);
}
Some(target)
},
(Some(v), None) | (None, Some(v)) => Some(v),
(None, None) => None,
}
}
fn chained_binops(kind: &ExprKind) -> Option<Vec<BinaryOp<'_>>> {
match kind {
ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer),
ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind),
_ => None,
}
}
fn chained_binops_helper(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option<Vec<BinaryOp<'expr>>> {
match (&left_outer.kind, &right_outer.kind) {
(
ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e),
ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e),
) => chained_binops_helper(left_e, right_e),
(ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer),
(_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => {
chained_binops_helper(left_outer, right_e)
},
(
ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right),
ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right),
) => match (
chained_binops_helper(left_left, left_right),
chained_binops_helper(right_left, right_right),
) {
(Some(mut left_ops), Some(mut right_ops)) => {
left_ops.reserve(right_ops.len());
for op in right_ops.drain(..) {
left_ops.push(op);
}
Some(left_ops)
},
(Some(mut left_ops), _) => {
left_ops.push(BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)));
Some(left_ops)
},
(_, Some(mut right_ops)) => {
right_ops.insert(0, BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)));
Some(right_ops)
},
(None, None) => Some(vec![
BinaryOp::new(*left_op, left_outer.span, (left_left, left_right)),
BinaryOp::new(*right_op, right_outer.span, (right_left, right_right)),
]),
},
_ => None,
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
struct IdentLocation {
index: usize,
}
impl Add for IdentLocation {
type Output = IdentLocation;
fn add(self, other: Self) -> Self::Output {
Self {
index: self.index + other.index,
}
}
}
impl AddAssign for IdentLocation {
fn add_assign(&mut self, other: Self) {
*self = *self + other
}
}
#[derive(Clone, Copy, Debug)]
enum IdentDifference {
NoDifference,
Single(IdentLocation),
Double(IdentLocation, IdentLocation),
Multiple,
NonIdent,
}
impl Add for IdentDifference {
type Output = IdentDifference;
fn add(self, other: Self) -> Self::Output {
match (self, other) {
(Self::NoDifference, output) | (output, Self::NoDifference) => output,
(Self::Multiple, _)
| (_, Self::Multiple)
| (Self::Double(_, _), Self::Single(_))
| (Self::Single(_) | Self::Double(_, _), Self::Double(_, _)) => Self::Multiple,
(Self::NonIdent, _) | (_, Self::NonIdent) => Self::NonIdent,
(Self::Single(il1), Self::Single(il2)) => Self::Double(il1, il2),
}
}
}
impl AddAssign for IdentDifference {
fn add_assign(&mut self, other: Self) {
*self = *self + other
}
}
impl IdentDifference {
/// Returns true if learning about more differences will not change the value
/// of this `IdentDifference`, and false otherwise.
fn is_complete(&self) -> bool {
match self {
Self::NoDifference | Self::Single(_) | Self::Double(_, _) => false,
Self::Multiple | Self::NonIdent => true,
}
}
}
fn ident_difference_expr(left: &Expr, right: &Expr) -> IdentDifference {
ident_difference_expr_with_base_location(left, right, IdentLocation::default()).0
}
fn ident_difference_expr_with_base_location(
left: &Expr,
right: &Expr,
mut base: IdentLocation,
) -> (IdentDifference, IdentLocation) {
// Ideally, this function should not use IdentIter because it should return
// early if the expressions have any non-ident differences. We want that early
// return because if without that restriction the lint would lead to false
// positives.
//
// But, we cannot (easily?) use a `rustc_ast::visit::Visitor`, since we need
// the two expressions to be walked in lockstep. And without a `Visitor`, we'd
// have to do all the AST traversal ourselves, which is a lot of work, since to
// do it properly we'd need to be able to handle more or less every possible
// AST node since `Item`s can be written inside `Expr`s.
//
// In practice, it seems likely that expressions, above a certain size, that
// happen to use the exact same idents in the exact same order, and which are
// not structured the same, would be rare. Therefore it seems likely that if
// we do only the first layer of matching ourselves and eventually fallback on
// IdentIter, then the output of this function will be almost always be correct
// in practice.
//
// If it turns out that problematic cases are more prelavent than we assume,
// then we should be able to change this function to do the correct traversal,
// without needing to change the rest of the code.
#![allow(clippy::enum_glob_use)]
use ExprKind::*;
match (
&strip_non_ident_wrappers(left).kind,
&strip_non_ident_wrappers(right).kind,
) {
(Yield(_), Yield(_))
| (Try(_), Try(_))
| (Paren(_), Paren(_))
| (Repeat(_, _), Repeat(_, _))
| (Struct(_, _, _), Struct(_, _, _))
| (MacCall(_), MacCall(_))
| (LlvmInlineAsm(_), LlvmInlineAsm(_))
| (InlineAsm(_), InlineAsm(_))
| (Ret(_), Ret(_))
| (Continue(_), Continue(_))
| (Break(_, _), Break(_, _))
| (AddrOf(_, _, _), AddrOf(_, _, _))
| (Path(_, _), Path(_, _))
| (Range(_, _, _), Range(_, _, _))
| (Index(_, _), Index(_, _))
| (Field(_, _), Field(_, _))
| (AssignOp(_, _, _), AssignOp(_, _, _))
| (Assign(_, _, _), Assign(_, _, _))
| (TryBlock(_), TryBlock(_))
| (Await(_), Await(_))
| (Async(_, _, _), Async(_, _, _))
| (Block(_, _), Block(_, _))
| (Closure(_, _, _, _, _, _), Closure(_, _, _, _, _, _))
| (Match(_, _), Match(_, _))
| (Loop(_, _), Loop(_, _))
| (ForLoop(_, _, _, _), ForLoop(_, _, _, _))
| (While(_, _, _), While(_, _, _))
| (If(_, _, _), If(_, _, _))
| (Let(_, _), Let(_, _))
| (Type(_, _), Type(_, _))
| (Cast(_, _), Cast(_, _))
| (Lit(_), Lit(_))
| (Unary(_, _), Unary(_, _))
| (Binary(_, _, _), Binary(_, _, _))
| (Tup(_), Tup(_))
| (MethodCall(_, _, _), MethodCall(_, _, _))
| (Call(_, _), Call(_, _))
| (ConstBlock(_), ConstBlock(_))
| (Array(_), Array(_))
| (Box(_), Box(_)) => {
// keep going
},
_ => {
return (IdentDifference::NonIdent, base);
},
}
let mut difference = IdentDifference::NoDifference;
for (left_attr, right_attr) in left.attrs.iter().zip(right.attrs.iter()) {
let (new_difference, new_base) =
ident_difference_via_ident_iter_with_base_location(left_attr, right_attr, base);
base = new_base;
difference += new_difference;
if difference.is_complete() {
return (difference, base);
}
}
let (new_difference, new_base) = ident_difference_via_ident_iter_with_base_location(left, right, base);
base = new_base;
difference += new_difference;
(difference, base)
}
fn ident_difference_via_ident_iter_with_base_location<Iterable: Into<IdentIter>>(
left: Iterable,
right: Iterable,
mut base: IdentLocation,
) -> (IdentDifference, IdentLocation) {
// See the note in `ident_difference_expr_with_base_location` about `IdentIter`
let mut difference = IdentDifference::NoDifference;
let mut left_iterator = left.into();
let mut right_iterator = right.into();
loop {
match (left_iterator.next(), right_iterator.next()) {
(Some(left_ident), Some(right_ident)) => {
if !eq_id(left_ident, right_ident) {
difference += IdentDifference::Single(base);
if difference.is_complete() {
return (difference, base);
}
}
},
(Some(_), None) | (None, Some(_)) => {
return (IdentDifference::NonIdent, base);
},
(None, None) => {
return (difference, base);
},
}
base += IdentLocation { index: 1 };
}
}
fn get_ident(expr: &Expr, location: IdentLocation) -> Option<Ident> {
IdentIter::from(expr).nth(location.index)
}
fn suggestion_with_swapped_ident(
cx: &EarlyContext<'_>,
expr: &Expr,
location: IdentLocation,
new_ident: Ident,
applicability: &mut Applicability,
) -> Option<String> {
get_ident(expr, location).and_then(|current_ident| {
if eq_id(current_ident, new_ident) {
// We never want to suggest a non-change
return None;
}
Some(format!(
"{}{}{}",
snippet_with_applicability(cx, expr.span.with_hi(current_ident.span.lo()), "..", applicability),
new_ident.to_string(),
snippet_with_applicability(cx, expr.span.with_lo(current_ident.span.hi()), "..", applicability),
))
})
}
fn skip_index<A, Iter>(iter: Iter, index: usize) -> impl Iterator<Item = A>
where
Iter: Iterator<Item = A>,
{
iter.enumerate()
.filter_map(move |(i, a)| if i == index { None } else { Some(a) })
}

View File

@ -168,8 +168,7 @@ fn check_trait_bound_duplication(cx: &LateContext<'_>, gen: &'_ Generics<'_>) {
if_chain! {
if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate;
if !in_macro(bound_predicate.span);
if let TyKind::Path(ref path) = bound_predicate.bounded_ty.kind;
if let QPath::Resolved(_, Path { ref segments, .. }) = path;
if let TyKind::Path(QPath::Resolved(_, Path { ref segments, .. })) = bound_predicate.bounded_ty.kind;
if let Some(segment) = segments.first();
if let Some(trait_resolutions_direct) = map.get(&segment.ident);
then {

View File

@ -48,8 +48,7 @@ impl<'tcx> LateLintPass<'tcx> for TransmutingNull {
if_chain! {
if let ExprKind::Path(ref _qpath) = args[0].kind;
let x = const_eval_context.expr(&args[0]);
if let Some(constant) = x;
if let Constant::RawPtr(0) = constant;
if let Some(Constant::RawPtr(0)) = x;
then {
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG)
}

View File

@ -8,6 +8,7 @@ use if_chain::if_chain;
use rustc_ast::{FloatTy, IntTy, LitFloatType, LitIntType, LitKind, UintTy};
use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{
BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericBounds, GenericParamKind, HirId,
@ -737,8 +738,7 @@ fn is_any_trait(t: &hir::Ty<'_>) -> bool {
fn get_bounds_if_impl_trait<'tcx>(cx: &LateContext<'tcx>, qpath: &QPath<'_>, id: HirId) -> Option<GenericBounds<'tcx>> {
if_chain! {
if let Some(did) = qpath_res(cx, qpath, id).opt_def_id();
if let Some(node) = cx.tcx.hir().get_if_local(did);
if let Node::GenericParam(generic_param) = node;
if let Some(Node::GenericParam(generic_param)) = cx.tcx.hir().get_if_local(did);
if let GenericParamKind::Type { synthetic, .. } = generic_param.kind;
if synthetic == Some(SyntheticTyParamKind::ImplTrait);
then {
@ -1469,8 +1469,7 @@ fn check_loss_of_sign(cx: &LateContext<'_>, expr: &Expr<'_>, op: &Expr<'_>, cast
// don't lint for positive constants
let const_val = constant(cx, &cx.typeck_results(), op);
if_chain! {
if let Some((const_val, _)) = const_val;
if let Constant::Int(n) = const_val;
if let Some((Constant::Int(n), _)) = const_val;
if let ty::Int(ity) = *cast_from.kind();
if sext(cx.tcx, n, ity) >= 0;
then {
@ -1632,7 +1631,14 @@ impl<'tcx> LateLintPass<'tcx> for Casts {
if expr.span.from_expansion() {
return;
}
if let ExprKind::Cast(ref ex, _) = expr.kind {
if let ExprKind::Cast(ref ex, cast_to) = expr.kind {
if let TyKind::Path(QPath::Resolved(_, path)) = cast_to.kind {
if let Res::Def(_, def_id) = path.res {
if cx.tcx.has_attr(def_id, sym::cfg) || cx.tcx.has_attr(def_id, sym::cfg_attr) {
return;
}
}
}
let (cast_from, cast_to) = (cx.typeck_results().expr_ty(ex), cx.typeck_results().expr_ty(expr));
lint_fn_to_numeric_cast(cx, expr, ex, cast_from, cast_to);
if let Some(lit) = get_numeric_literal(ex) {
@ -1711,7 +1717,7 @@ fn show_unnecessary_cast(cx: &LateContext<'_>, expr: &Expr<'_>, literal_str: &st
expr.span,
&format!("casting {} literal to `{}` is unnecessary", literal_kind_name, cast_to),
"try",
format!("{}_{}", literal_str, cast_to),
format!("{}_{}", literal_str.trim_end_matches('.'), cast_to),
Applicability::MachineApplicable,
);
}

View File

@ -1,5 +1,5 @@
use crate::utils::{
in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then,
contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then,
visitors::find_all_ret_expressions,
};
use if_chain::if_chain;
@ -95,6 +95,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
if let ExprKind::Path(ref qpath) = func.kind;
if match_qpath(qpath, path);
if args.len() == 1;
if !contains_return(&args[0]);
then {
suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string()));
true
@ -134,7 +135,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
diag.multipart_suggestion(
"...and change the returning expressions",
suggs,
Applicability::MachineApplicable,
Applicability::MaybeIncorrect,
);
},
);

View File

@ -10,6 +10,17 @@ use rustc_ast::{self as ast, *};
use rustc_span::symbol::Ident;
use std::mem;
pub mod ident_iter;
pub use ident_iter::IdentIter;
pub fn is_useless_with_eq_exprs(kind: BinOpKind) -> bool {
use BinOpKind::*;
matches!(
kind,
Sub | Div | Eq | Lt | Le | Gt | Ge | Ne | And | Or | BitXor | BitAnd | BitOr
)
}
/// Checks if each element in the first slice is contained within the latter as per `eq_fn`.
pub fn unordered_over<X>(left: &[X], right: &[X], mut eq_fn: impl FnMut(&X, &X) -> bool) -> bool {
left.len() == right.len() && left.iter().all(|l| right.iter().any(|r| eq_fn(l, r)))

View File

@ -0,0 +1,45 @@
use core::iter::FusedIterator;
use rustc_ast::visit::{walk_attribute, walk_expr, Visitor};
use rustc_ast::{Attribute, Expr};
use rustc_span::symbol::Ident;
pub struct IdentIter(std::vec::IntoIter<Ident>);
impl Iterator for IdentIter {
type Item = Ident;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl FusedIterator for IdentIter {}
impl From<&Expr> for IdentIter {
fn from(expr: &Expr) -> Self {
let mut visitor = IdentCollector::default();
walk_expr(&mut visitor, expr);
IdentIter(visitor.0.into_iter())
}
}
impl From<&Attribute> for IdentIter {
fn from(attr: &Attribute) -> Self {
let mut visitor = IdentCollector::default();
walk_attribute(&mut visitor, attr);
IdentIter(visitor.0.into_iter())
}
}
#[derive(Default)]
struct IdentCollector(Vec<Ident>);
impl Visitor<'_> for IdentCollector {
fn visit_ident(&mut self, ident: Ident) {
self.0.push(ident);
}
}

View File

@ -21,6 +21,7 @@ pub const BUILTIN_ATTRIBUTES: &[(&str, DeprecationStatus)] = &[
DeprecationStatus::Replaced("cognitive_complexity"),
),
("dump", DeprecationStatus::None),
("msrv", DeprecationStatus::None),
];
pub struct LimitStack {
@ -123,6 +124,24 @@ fn parse_attrs<F: FnMut(u64)>(sess: &Session, attrs: &[ast::Attribute], name: &'
}
}
pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'static str) -> Option<ast::Attribute> {
let mut unique_attr = None;
for attr in get_attr(sess, attrs, name) {
match attr.style {
ast::AttrStyle::Inner if unique_attr.is_none() => unique_attr = Some(attr.clone()),
ast::AttrStyle::Inner => {
sess.struct_span_err(attr.span, &format!("`{}` is defined multiple times", name))
.span_note(unique_attr.as_ref().unwrap().span, "first definition found here")
.emit();
},
ast::AttrStyle::Outer => {
sess.span_err(attr.span, &format!("`{}` cannot be an outer attribute", name));
},
}
}
unique_attr
}
/// Return true if the attributes contain any of `proc_macro`,
/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise
pub fn is_proc_macro(sess: &Session, attrs: &[ast::Attribute]) -> bool {

View File

@ -106,6 +106,8 @@ macro_rules! define_Conf {
pub use self::helpers::Conf;
define_Conf! {
/// Lint: MANUAL_NON_EXHAUSTIVE, MANUAL_STRIP, OPTION_AS_REF_DEREF, MATCH_LIKE_MATCHES_MACRO. The minimum rust version that the project supports
(msrv, "msrv": Option<String>, None),
/// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses
(blacklisted_names, "blacklisted_names": Vec<String>, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
/// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
@ -168,6 +170,8 @@ define_Conf! {
(warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false),
/// Lint: DISALLOWED_METHOD. The list of blacklisted methods to lint about. NB: `bar` is not here since it has legitimate uses
(disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()),
/// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
(unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true),
}
impl Default for Conf {

View File

@ -186,7 +186,9 @@ pub fn span_lint_hir_and_then(
/// |
/// = note: `-D fold-any` implied by `-D warnings`
/// ```
#[allow(clippy::collapsible_span_lint_calls)]
#[allow(clippy::unknown_clippy_lints)]
#[cfg_attr(feature = "internal-lints", allow(clippy::collapsible_span_lint_calls))]
pub fn span_lint_and_sugg<'a, T: LintContext>(
cx: &'a T,
lint: &'static Lint,

View File

@ -162,8 +162,7 @@ pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<
if let hir::Block { expr: Some(expr), .. } = &**block;
if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
if let hir::ExprKind::DropTemps(cond) = &cond.kind;
if let [arm, ..] = &arms[..];
if let hir::Arm { body, .. } = arm;
if let [hir::Arm { body, .. }, ..] = &arms[..];
then {
return Some((cond, body));
}

View File

@ -81,7 +81,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
}
}
match (&left.kind, &right.kind) {
match (&reduce_exprkind(&left.kind), &reduce_exprkind(&right.kind)) {
(&ExprKind::AddrOf(lb, l_mut, ref le), &ExprKind::AddrOf(rb, r_mut, ref re)) => {
lb == rb && l_mut == r_mut && self.eq_expr(le, re)
},
@ -306,6 +306,32 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> {
}
}
/// Some simple reductions like `{ return }` => `return`
fn reduce_exprkind<'hir>(kind: &'hir ExprKind<'hir>) -> &ExprKind<'hir> {
if let ExprKind::Block(block, _) = kind {
match (block.stmts, block.expr) {
// `{}` => `()`
([], None) => &ExprKind::Tup(&[]),
([], Some(expr)) => match expr.kind {
// `{ return .. }` => `return ..`
ExprKind::Ret(..) => &expr.kind,
_ => kind,
},
([stmt], None) => match stmt.kind {
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
// `{ return ..; }` => `return ..`
ExprKind::Ret(..) => &expr.kind,
_ => kind,
},
_ => kind,
},
_ => kind,
}
} else {
kind
}
}
fn swap_binop<'a>(
binop: BinOpKind,
lhs: &'a Expr<'a>,

View File

@ -14,6 +14,7 @@ pub mod eager_or_lazy;
pub mod higher;
mod hir_utils;
pub mod inspector;
#[cfg(feature = "internal-lints")]
pub mod internal_lints;
pub mod numeric_literal;
pub mod paths;
@ -51,6 +52,8 @@ use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::map::Map;
use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
use rustc_middle::ty::{self, layout::IntegerExt, Ty, TyCtxt, TypeFoldable};
use rustc_semver::RustcVersion;
use rustc_session::Session;
use rustc_span::hygiene::{ExpnKind, MacroKind};
use rustc_span::source_map::original_sp;
use rustc_span::sym as rustc_sym;
@ -62,6 +65,49 @@ use smallvec::SmallVec;
use crate::consts::{constant, Constant};
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
if let Ok(version) = RustcVersion::parse(msrv) {
return Some(version);
} else if let Some(sess) = sess {
if let Some(span) = span {
sess.span_err(span, &format!("`{}` is not a valid Rust version", msrv));
}
}
None
}
pub fn meets_msrv(msrv: Option<&RustcVersion>, lint_msrv: &RustcVersion) -> bool {
msrv.map_or(true, |msrv| msrv.meets(*lint_msrv))
}
macro_rules! extract_msrv_attr {
(LateContext) => {
extract_msrv_attr!(@LateContext, ());
};
(EarlyContext) => {
extract_msrv_attr!(@EarlyContext);
};
(@$context:ident$(, $call:tt)?) => {
fn enter_lint_attrs(&mut self, cx: &rustc_lint::$context<'tcx>, attrs: &'tcx [rustc_ast::ast::Attribute]) {
use $crate::utils::get_unique_inner_attr;
match get_unique_inner_attr(cx.sess$($call)?, attrs, "msrv") {
Some(msrv_attr) => {
if let Some(msrv) = msrv_attr.value_str() {
self.msrv = $crate::utils::parse_msrv(
&msrv.to_string(),
Some(cx.sess$($call)?),
Some(msrv_attr.span),
);
} else {
cx.sess$($call)?.span_err(msrv_attr.span, "bad clippy attribute");
}
},
_ => (),
}
}
};
}
/// Returns `true` if the two spans come from differing expansions (i.e., one is
/// from a macro and one isn't).
#[must_use]
@ -527,6 +573,36 @@ pub fn contains_name(name: Symbol, expr: &Expr<'_>) -> bool {
cn.result
}
/// Returns `true` if `expr` contains a return expression
pub fn contains_return(expr: &hir::Expr<'_>) -> bool {
struct RetCallFinder {
found: bool,
}
impl<'tcx> hir::intravisit::Visitor<'tcx> for RetCallFinder {
type Map = Map<'tcx>;
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) {
if self.found {
return;
}
if let hir::ExprKind::Ret(..) = &expr.kind {
self.found = true;
} else {
hir::intravisit::walk_expr(self, expr);
}
}
fn nested_visit_map(&mut self) -> hir::intravisit::NestedVisitorMap<Self::Map> {
hir::intravisit::NestedVisitorMap::None
}
}
let mut visitor = RetCallFinder { found: false };
visitor.visit_expr(expr);
visitor.found
}
/// Converts a span to a code snippet if available, otherwise use default.
///
/// This is useful if you want to provide suggestions for your lint or more generally, if you want

View File

@ -20,6 +20,8 @@ pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
pub const COPY: [&str; 3] = ["core", "intrinsics", "copy_nonoverlapping"];
pub const COPY_NONOVERLAPPING: [&str; 3] = ["core", "intrinsics", "copy"];
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
pub const CSTRING_AS_C_STR: [&str; 5] = ["std", "ffi", "c_str", "CString", "as_c_str"];
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
@ -31,6 +33,7 @@ pub const DISPLAY_TRAIT: [&str; 3] = ["core", "fmt", "Display"];
pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
pub const DROP: [&str; 3] = ["core", "mem", "drop"];
pub const DURATION: [&str; 3] = ["core", "time", "Duration"];
#[cfg(feature = "internal-lints")]
pub const EARLY_CONTEXT: [&str; 2] = ["rustc_lint", "EarlyContext"];
pub const EXIT: [&str; 3] = ["std", "process", "exit"];
pub const F32_EPSILON: [&str; 4] = ["core", "f32", "<impl f32>", "EPSILON"];
@ -58,9 +61,13 @@ pub const INTO: [&str; 3] = ["core", "convert", "Into"];
pub const INTO_ITERATOR: [&str; 5] = ["core", "iter", "traits", "collect", "IntoIterator"];
pub const IO_READ: [&str; 3] = ["std", "io", "Read"];
pub const IO_WRITE: [&str; 3] = ["std", "io", "Write"];
pub const IPADDR_V4: [&str; 4] = ["std", "net", "IpAddr", "V4"];
pub const IPADDR_V6: [&str; 4] = ["std", "net", "IpAddr", "V6"];
pub const ITERATOR: [&str; 5] = ["core", "iter", "traits", "iterator", "Iterator"];
#[cfg(feature = "internal-lints")]
pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
#[cfg(feature = "internal-lints")]
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];
@ -68,6 +75,8 @@ pub const MEM_MANUALLY_DROP: [&str; 4] = ["core", "mem", "manually_drop", "Manua
pub const MEM_MAYBEUNINIT: [&str; 4] = ["core", "mem", "maybe_uninit", "MaybeUninit"];
pub const MEM_MAYBEUNINIT_UNINIT: [&str; 5] = ["core", "mem", "maybe_uninit", "MaybeUninit", "uninit"];
pub const MEM_REPLACE: [&str; 3] = ["core", "mem", "replace"];
pub const MEM_SIZE_OF: [&str; 3] = ["core", "mem", "size_of"];
pub const MEM_SIZE_OF_VAL: [&str; 3] = ["core", "mem", "size_of_val"];
pub const MUTEX_GUARD: [&str; 4] = ["std", "sync", "mutex", "MutexGuard"];
pub const OPEN_OPTIONS: [&str; 3] = ["std", "fs", "OpenOptions"];
pub const OPS_MODULE: [&str; 2] = ["core", "ops"];
@ -90,9 +99,14 @@ pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
pub const POLL: [&str; 4] = ["core", "task", "poll", "Poll"];
pub const POLL_PENDING: [&str; 5] = ["core", "task", "poll", "Poll", "Pending"];
pub const POLL_READY: [&str; 5] = ["core", "task", "poll", "Poll", "Ready"];
pub const PTR_EQ: [&str; 3] = ["core", "ptr", "eq"];
pub const PTR_NULL: [&str; 3] = ["core", "ptr", "null"];
pub const PTR_NULL_MUT: [&str; 3] = ["core", "ptr", "null_mut"];
pub const PTR_SLICE_FROM_RAW_PARTS: [&str; 3] = ["core", "ptr", "slice_from_raw_parts"];
pub const PTR_SLICE_FROM_RAW_PARTS_MUT: [&str; 3] = ["core", "ptr", "slice_from_raw_parts_mut"];
pub const PTR_SWAP_NONOVERLAPPING: [&str; 3] = ["core", "ptr", "swap_nonoverlapping"];
pub const PUSH_STR: [&str; 4] = ["alloc", "string", "String", "push_str"];
pub const RANGE_ARGUMENT_TRAIT: [&str; 3] = ["core", "ops", "RangeBounds"];
pub const RC: [&str; 3] = ["alloc", "rc", "Rc"];
@ -114,6 +128,8 @@ pub const RWLOCK_READ_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockReadGu
pub const RWLOCK_WRITE_GUARD: [&str; 4] = ["std", "sync", "rwlock", "RwLockWriteGuard"];
pub const SERDE_DESERIALIZE: [&str; 3] = ["serde", "de", "Deserialize"];
pub const SERDE_DE_VISITOR: [&str; 3] = ["serde", "de", "Visitor"];
pub const SLICE_FROM_RAW_PARTS: [&str; 4] = ["core", "slice", "raw", "from_raw_parts"];
pub const SLICE_FROM_RAW_PARTS_MUT: [&str; 4] = ["core", "slice", "raw", "from_raw_parts_mut"];
pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec"];
pub const SLICE_ITER: [&str; 4] = ["core", "slice", "iter", "Iter"];
pub const STDERR: [&str; 4] = ["std", "io", "stdio", "stderr"];
@ -129,6 +145,7 @@ pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
#[cfg(feature = "internal-lints")]
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];
pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
@ -146,3 +163,4 @@ pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];
pub const WRITE_BYTES: [&str; 3] = ["core", "intrinsics", "write_bytes"];

View File

@ -1,5 +1,7 @@
use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::def::Res;
use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Arm, Expr, ExprKind, HirId, QPath, Stmt};
use rustc_lint::LateContext;
use rustc_middle::hir::map::Map;
@ -123,3 +125,54 @@ where
!ret_finder.failed
}
}
pub struct LocalUsedVisitor {
pub local_hir_id: HirId,
pub used: bool,
}
impl LocalUsedVisitor {
pub fn new(local_hir_id: HirId) -> Self {
Self {
local_hir_id,
used: false,
}
}
fn check<T>(&mut self, t: T, visit: fn(&mut Self, T)) -> bool {
visit(self, t);
std::mem::replace(&mut self.used, false)
}
pub fn check_arm(&mut self, arm: &Arm<'_>) -> bool {
self.check(arm, Self::visit_arm)
}
pub fn check_expr(&mut self, expr: &Expr<'_>) -> bool {
self.check(expr, Self::visit_expr)
}
pub fn check_stmt(&mut self, stmt: &Stmt<'_>) -> bool {
self.check(stmt, Self::visit_stmt)
}
}
impl<'v> Visitor<'v> for LocalUsedVisitor {
type Map = Map<'v>;
fn visit_expr(&mut self, expr: &'v Expr<'v>) {
if let ExprKind::Path(QPath::Resolved(None, path)) = expr.kind {
if let Res::Local(id) = path.res {
if id == self.local_hir_id {
self.used = true;
return;
}
}
}
walk_expr(self, expr);
}
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
}

View File

@ -12,6 +12,9 @@ use std::path::{Path, PathBuf};
mod cargo;
// whether to run internal tests or not
const RUN_INTERNAL_TESTS: bool = cfg!(feature = "internal-lints");
fn host_lib() -> PathBuf {
option_env!("HOST_LIBS").map_or(cargo::CARGO_TARGET_DIR.join(env!("PROFILE")), PathBuf::from)
}
@ -96,6 +99,16 @@ fn run_mode(cfg: &mut compiletest::Config) {
compiletest::run_tests(&cfg);
}
fn run_internal_tests(cfg: &mut compiletest::Config) {
// only run internal tests with the internal-tests feature
if !RUN_INTERNAL_TESTS {
return;
}
cfg.mode = TestMode::Ui;
cfg.src_base = Path::new("tests").join("ui-internal");
compiletest::run_tests(&cfg);
}
fn run_ui_toml(config: &mut compiletest::Config) {
fn run_tests(config: &compiletest::Config, mut tests: Vec<tester::TestDescAndFn>) -> Result<bool, io::Error> {
let mut result = true;
@ -199,7 +212,6 @@ fn run_ui_cargo(config: &mut compiletest::Config) {
Some("main.rs") => {},
_ => continue,
}
let paths = compiletest::common::TestPaths {
file: file_path,
base: config.src_base.clone(),
@ -253,4 +265,5 @@ fn compile_test() {
run_mode(&mut config);
run_ui_toml(&mut config);
run_ui_cargo(&mut config);
run_internal_tests(&mut config);
}

View File

@ -18,7 +18,8 @@ fn dogfood_clippy() {
}
let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let output = Command::new(&*CLIPPY_PATH)
let mut command = Command::new(&*CLIPPY_PATH);
command
.current_dir(root_dir)
.env("CLIPPY_DOGFOOD", "1")
.env("CARGO_INCREMENTAL", "0")
@ -27,11 +28,16 @@ fn dogfood_clippy() {
.arg("--all-features")
.arg("--")
.args(&["-D", "clippy::all"])
.args(&["-D", "clippy::internal"])
.args(&["-D", "clippy::pedantic"])
.arg("-Cdebuginfo=0") // disable debuginfo to generate less data in the target dir
.output()
.unwrap();
.arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir
// internal lints only exist if we build with the internal-lints feature
if cfg!(feature = "internal-lints") {
command.args(&["-D", "clippy::internal"]);
}
let output = command.output().unwrap();
println!("status: {}", output.status);
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));

View File

@ -0,0 +1 @@
msrv = "invalid.version"

View File

@ -0,0 +1,3 @@
#![allow(clippy::redundant_clone)]
fn main() {}

View File

@ -0,0 +1,4 @@
error: error reading Clippy's configuration file. `invalid.version` is not a valid Rust version
error: aborting due to previous error

View File

@ -0,0 +1 @@
unreadable-literal-lint-fractions = false

View File

@ -0,0 +1,22 @@
#[deny(clippy::unreadable_literal)]
fn allow_inconsistent_digit_grouping() {
#![allow(clippy::inconsistent_digit_grouping)]
let _pass1 = 100_200_300.123456789;
}
fn main() {
allow_inconsistent_digit_grouping();
let _pass1 = 100_200_300.100_200_300;
let _pass2 = 1.123456789;
let _pass3 = 1.0;
let _pass4 = 10000.00001;
let _pass5 = 1.123456789e1;
// due to clippy::inconsistent-digit-grouping
let _fail1 = 100_200_300.123456789;
// fail due to the integer part
let _fail2 = 100200300.300200100;
}

View File

@ -0,0 +1,10 @@
error: digits grouped inconsistently by underscores
--> $DIR/test.rs:18:18
|
LL | let _fail1 = 100_200_300.123456789;
| ^^^^^^^^^^^^^^^^^^^^^ help: consider: `100_200_300.123_456_789`
|
= note: `-D clippy::inconsistent-digit-grouping` implied by `-D warnings`
error: aborting due to previous error

View File

@ -0,0 +1 @@
msrv = "1.0.0"

View File

@ -0,0 +1,68 @@
#![allow(clippy::redundant_clone)]
#![warn(clippy::manual_non_exhaustive)]
use std::ops::Deref;
mod enums {
enum E {
A,
B,
#[doc(hidden)]
_C,
}
// user forgot to remove the marker
#[non_exhaustive]
enum Ep {
A,
B,
#[doc(hidden)]
_C,
}
}
fn option_as_ref_deref() {
let mut opt = Some(String::from("123"));
let _ = opt.as_ref().map(String::as_str);
let _ = opt.as_ref().map(|x| x.as_str());
let _ = opt.as_mut().map(String::as_mut_str);
let _ = opt.as_mut().map(|x| x.as_mut_str());
}
fn match_like_matches() {
let _y = match Some(5) {
Some(0) => true,
_ => false,
};
}
fn match_same_arms() {
match (1, 2, 3) {
(1, .., 3) => 42,
(.., 3) => 42, //~ ERROR match arms have same body
_ => 0,
};
}
fn match_same_arms2() {
let _ = match Some(42) {
Some(_) => 24,
None => 24, //~ ERROR match arms have same body
};
}
fn manual_strip_msrv() {
let s = "hello, world!";
if s.starts_with("hello, ") {
assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
}
}
fn main() {
option_as_ref_deref();
match_like_matches();
match_same_arms();
match_same_arms2();
manual_strip_msrv();
}

View File

@ -1,4 +1,4 @@
error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `third-party` at line 5 column 1
error: error reading Clippy's configuration file `$DIR/clippy.toml`: unknown field `foobar`, expected one of `msrv`, `blacklisted-names`, `cognitive-complexity-threshold`, `cyclomatic-complexity-threshold`, `doc-valid-idents`, `too-many-arguments-threshold`, `type-complexity-threshold`, `single-char-binding-names-threshold`, `too-large-for-stack`, `enum-variant-name-threshold`, `enum-variant-size-threshold`, `verbose-bit-mask-threshold`, `literal-representation-threshold`, `trivial-copy-size-limit`, `pass-by-value-size-limit`, `too-many-lines-threshold`, `array-size-threshold`, `vec-box-size-threshold`, `max-trait-bounds`, `max-struct-bools`, `max-fn-params-bools`, `warn-on-all-wildcard-imports`, `disallowed-methods`, `unreadable-literal-lint-fractions`, `third-party` at line 5 column 1
error: aborting due to previous error

View File

@ -1,7 +1,19 @@
#[warn(clippy::as_conversions)]
// aux-build:macro_rules.rs
#![warn(clippy::as_conversions)]
#[macro_use]
extern crate macro_rules;
fn with_external_macro() {
as_conv_with_arg!(0u32 as u64);
as_conv!();
}
fn main() {
let i = 0u32 as u64;
let j = &i as *const u64 as *mut u64;
with_external_macro();
}

View File

@ -1,5 +1,5 @@
error: using a potentially dangerous silent `as` conversion
--> $DIR/as_conversions.rs:4:13
--> $DIR/as_conversions.rs:14:13
|
LL | let i = 0u32 as u64;
| ^^^^^^^^^^^
@ -8,7 +8,7 @@ LL | let i = 0u32 as u64;
= help: consider using a safe wrapper for this conversion
error: using a potentially dangerous silent `as` conversion
--> $DIR/as_conversions.rs:6:13
--> $DIR/as_conversions.rs:16:13
|
LL | let j = &i as *const u64 as *mut u64;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -16,7 +16,7 @@ LL | let j = &i as *const u64 as *mut u64;
= help: consider using a safe wrapper for this conversion
error: using a potentially dangerous silent `as` conversion
--> $DIR/as_conversions.rs:6:13
--> $DIR/as_conversions.rs:16:13
|
LL | let j = &i as *const u64 as *mut u64;
| ^^^^^^^^^^^^^^^^

View File

@ -70,3 +70,17 @@ macro_rules! ref_arg_function {
fn fun_example(ref _x: usize) {}
};
}
#[macro_export]
macro_rules! as_conv_with_arg {
(0u32 as u64) => {
()
};
}
#[macro_export]
macro_rules! as_conv {
() => {
0u32 as u64
};
}

View File

@ -0,0 +1,239 @@
#![warn(clippy::collapsible_match)]
#![allow(clippy::needless_return, clippy::no_effect, clippy::single_match)]
fn lint_cases(opt_opt: Option<Option<u32>>, res_opt: Result<Option<u32>, String>) {
// match without block
match res_opt {
Ok(val) => match val {
Some(n) => foo(n),
_ => return,
},
_ => return,
}
// match with block
match res_opt {
Ok(val) => match val {
Some(n) => foo(n),
_ => return,
},
_ => return,
}
// if let, if let
if let Ok(val) = res_opt {
if let Some(n) = val {
take(n);
}
}
// if let else, if let else
if let Ok(val) = res_opt {
if let Some(n) = val {
take(n);
} else {
return;
}
} else {
return;
}
// if let, match
if let Ok(val) = res_opt {
match val {
Some(n) => foo(n),
_ => (),
}
}
// match, if let
match res_opt {
Ok(val) => {
if let Some(n) = val {
take(n);
}
},
_ => {},
}
// if let else, match
if let Ok(val) = res_opt {
match val {
Some(n) => foo(n),
_ => return,
}
} else {
return;
}
// match, if let else
match res_opt {
Ok(val) => {
if let Some(n) = val {
take(n);
} else {
return;
}
},
_ => return,
}
// None in inner match same as outer wild branch
match res_opt {
Ok(val) => match val {
Some(n) => foo(n),
None => return,
},
_ => return,
}
// None in outer match same as inner wild branch
match opt_opt {
Some(val) => match val {
Some(n) => foo(n),
_ => return,
},
None => return,
}
}
fn negative_cases(res_opt: Result<Option<u32>, String>, res_res: Result<Result<u32, String>, String>) {
// no wild pattern in outer match
match res_opt {
Ok(val) => match val {
Some(n) => foo(n),
_ => return,
},
Err(_) => return,
}
// inner branch is not wild or None
match res_res {
Ok(val) => match val {
Ok(n) => foo(n),
Err(_) => return,
},
_ => return,
}
// statement before inner match
match res_opt {
Ok(val) => {
"hi buddy";
match val {
Some(n) => foo(n),
_ => return,
}
},
_ => return,
}
// statement after inner match
match res_opt {
Ok(val) => {
match val {
Some(n) => foo(n),
_ => return,
}
"hi buddy";
},
_ => return,
}
// wild branches do not match
match res_opt {
Ok(val) => match val {
Some(n) => foo(n),
_ => {
"sup";
return;
},
},
_ => return,
}
// binding used in if guard
match res_opt {
Ok(val) if val.is_some() => match val {
Some(n) => foo(n),
_ => return,
},
_ => return,
}
// binding used in inner match body
match res_opt {
Ok(val) => match val {
Some(_) => take(val),
_ => return,
},
_ => return,
}
// if guard on inner match
{
match res_opt {
Ok(val) => match val {
Some(n) if make() => foo(n),
_ => return,
},
_ => return,
}
match res_opt {
Ok(val) => match val {
_ => make(),
_ if make() => return,
},
_ => return,
}
}
// differing macro contexts
{
macro_rules! mac {
($val:ident) => {
match $val {
Some(n) => foo(n),
_ => return,
}
};
}
match res_opt {
Ok(val) => mac!(val),
_ => return,
}
}
// OR pattern
enum E<T> {
A(T),
B(T),
C(T),
};
match make::<E<Option<u32>>>() {
E::A(val) | E::B(val) => match val {
Some(n) => foo(n),
_ => return,
},
_ => return,
}
match make::<Option<E<u32>>>() {
Some(val) => match val {
E::A(val) | E::B(val) => foo(val),
_ => return,
},
_ => return,
}
}
fn make<T>() -> T {
unimplemented!()
}
fn foo<T, U>(t: T) -> U {
unimplemented!()
}
fn take<T>(t: T) {}
fn main() {}

View File

@ -0,0 +1,179 @@
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:7:20
|
LL | Ok(val) => match val {
| ____________________^
LL | | Some(n) => foo(n),
LL | | _ => return,
LL | | },
| |_________^
|
= note: `-D clippy::collapsible-match` implied by `-D warnings`
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:7:12
|
LL | Ok(val) => match val {
| ^^^ Replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:16:20
|
LL | Ok(val) => match val {
| ____________________^
LL | | Some(n) => foo(n),
LL | | _ => return,
LL | | },
| |_________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:16:12
|
LL | Ok(val) => match val {
| ^^^ Replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:25:9
|
LL | / if let Some(n) = val {
LL | | take(n);
LL | | }
| |_________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:24:15
|
LL | if let Ok(val) = res_opt {
| ^^^ Replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:32:9
|
LL | / if let Some(n) = val {
LL | | take(n);
LL | | } else {
LL | | return;
LL | | }
| |_________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:31:15
|
LL | if let Ok(val) = res_opt {
| ^^^ Replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:43:9
|
LL | / match val {
LL | | Some(n) => foo(n),
LL | | _ => (),
LL | | }
| |_________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:42:15
|
LL | if let Ok(val) = res_opt {
| ^^^ Replace this binding
LL | match val {
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:52:13
|
LL | / if let Some(n) = val {
LL | | take(n);
LL | | }
| |_____________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:51:12
|
LL | Ok(val) => {
| ^^^ Replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:61:9
|
LL | / match val {
LL | | Some(n) => foo(n),
LL | | _ => return,
LL | | }
| |_________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:60:15
|
LL | if let Ok(val) = res_opt {
| ^^^ Replace this binding
LL | match val {
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:72:13
|
LL | / if let Some(n) = val {
LL | | take(n);
LL | | } else {
LL | | return;
LL | | }
| |_____________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:71:12
|
LL | Ok(val) => {
| ^^^ Replace this binding
LL | if let Some(n) = val {
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:83:20
|
LL | Ok(val) => match val {
| ____________________^
LL | | Some(n) => foo(n),
LL | | None => return,
LL | | },
| |_________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:83:12
|
LL | Ok(val) => match val {
| ^^^ Replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match.rs:92:22
|
LL | Some(val) => match val {
| ______________________^
LL | | Some(n) => foo(n),
LL | | _ => return,
LL | | },
| |_________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match.rs:92:14
|
LL | Some(val) => match val {
| ^^^ Replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: aborting due to 10 previous errors

View File

@ -0,0 +1,53 @@
#![warn(clippy::collapsible_match)]
#![allow(clippy::needless_return, clippy::no_effect, clippy::single_match)]
fn lint_cases(opt_opt: Option<Option<u32>>, res_opt: Result<Option<u32>, String>) {
// if guards on outer match
{
match res_opt {
Ok(val) if make() => match val {
Some(n) => foo(n),
_ => return,
},
_ => return,
}
match res_opt {
Ok(val) => match val {
Some(n) => foo(n),
_ => return,
},
_ if make() => return,
_ => return,
}
}
// macro
{
macro_rules! mac {
($outer:expr => $pat:pat, $e:expr => $inner_pat:pat, $then:expr) => {
match $outer {
$pat => match $e {
$inner_pat => $then,
_ => return,
},
_ => return,
}
};
}
// Lint this since the patterns are not defined by the macro.
// Allows the lint to work on if_chain! for example.
// Fixing the lint requires knowledge of the specific macro, but we optimistically assume that
// there is still a better way to write this.
mac!(res_opt => Ok(val), val => Some(n), foo(n));
}
}
fn make<T>() -> T {
unimplemented!()
}
fn foo<T, U>(t: T) -> U {
unimplemented!()
}
fn main() {}

View File

@ -0,0 +1,61 @@
error: Unnecessary nested match
--> $DIR/collapsible_match2.rs:8:34
|
LL | Ok(val) if make() => match val {
| __________________________________^
LL | | Some(n) => foo(n),
LL | | _ => return,
LL | | },
| |_____________^
|
= note: `-D clippy::collapsible-match` implied by `-D warnings`
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match2.rs:8:16
|
LL | Ok(val) if make() => match val {
| ^^^ Replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match2.rs:15:24
|
LL | Ok(val) => match val {
| ________________________^
LL | | Some(n) => foo(n),
LL | | _ => return,
LL | | },
| |_____________^
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match2.rs:15:16
|
LL | Ok(val) => match val {
| ^^^ Replace this binding
LL | Some(n) => foo(n),
| ^^^^^^^ with this pattern
error: Unnecessary nested match
--> $DIR/collapsible_match2.rs:29:29
|
LL | $pat => match $e {
| _____________________________^
LL | | $inner_pat => $then,
LL | | _ => return,
LL | | },
| |_____________________^
...
LL | mac!(res_opt => Ok(val), val => Some(n), foo(n));
| ------------------------------------------------- in this macro invocation
|
help: The outer pattern can be modified to include the inner pattern.
--> $DIR/collapsible_match2.rs:41:28
|
LL | mac!(res_opt => Ok(val), val => Some(n), foo(n));
| ^^^ ^^^^^^^ with this pattern
| |
| Replace this binding
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 3 previous errors

View File

@ -1,5 +1,3 @@
#[warn(clippy::str_to_string)]
#[warn(clippy::string_to_string)]
#[warn(clippy::unstable_as_slice)]
#[warn(clippy::unstable_as_mut_slice)]
#[warn(clippy::misaligned_transmute)]

View File

@ -1,88 +1,76 @@
error: lint `clippy::str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon`
--> $DIR/deprecated.rs:1:8
|
LL | #[warn(clippy::str_to_string)]
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D renamed-and-removed-lints` implied by `-D warnings`
error: lint `clippy::string_to_string` has been removed: `using `string::to_string` is common even today and specialization will likely happen soon`
--> $DIR/deprecated.rs:2:8
|
LL | #[warn(clippy::string_to_string)]
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::unstable_as_slice` has been removed: ``Vec::as_slice` has been stabilized in 1.7`
--> $DIR/deprecated.rs:3:8
--> $DIR/deprecated.rs:1:8
|
LL | #[warn(clippy::unstable_as_slice)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D renamed-and-removed-lints` implied by `-D warnings`
error: lint `clippy::unstable_as_mut_slice` has been removed: ``Vec::as_mut_slice` has been stabilized in 1.7`
--> $DIR/deprecated.rs:4:8
--> $DIR/deprecated.rs:2:8
|
LL | #[warn(clippy::unstable_as_mut_slice)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::misaligned_transmute` has been removed: `this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr`
--> $DIR/deprecated.rs:5:8
--> $DIR/deprecated.rs:3:8
|
LL | #[warn(clippy::misaligned_transmute)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::unused_collect` has been removed: ``collect` has been marked as #[must_use] in rustc and that covers all cases of this lint`
--> $DIR/deprecated.rs:6:8
--> $DIR/deprecated.rs:4:8
|
LL | #[warn(clippy::unused_collect)]
| ^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::invalid_ref` has been removed: `superseded by rustc lint `invalid_value``
--> $DIR/deprecated.rs:7:8
--> $DIR/deprecated.rs:5:8
|
LL | #[warn(clippy::invalid_ref)]
| ^^^^^^^^^^^^^^^^^^^
error: lint `clippy::into_iter_on_array` has been removed: `this lint has been uplifted to rustc and is now called `array_into_iter``
--> $DIR/deprecated.rs:8:8
--> $DIR/deprecated.rs:6:8
|
LL | #[warn(clippy::into_iter_on_array)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::unused_label` has been removed: `this lint has been uplifted to rustc and is now called `unused_labels``
--> $DIR/deprecated.rs:9:8
--> $DIR/deprecated.rs:7:8
|
LL | #[warn(clippy::unused_label)]
| ^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::regex_macro` has been removed: `the regex! macro has been removed from the regex crate in 2018`
--> $DIR/deprecated.rs:10:8
--> $DIR/deprecated.rs:8:8
|
LL | #[warn(clippy::regex_macro)]
| ^^^^^^^^^^^^^^^^^^^
error: lint `clippy::drop_bounds` has been removed: `this lint has been uplifted to rustc and is now called `drop_bounds``
--> $DIR/deprecated.rs:11:8
--> $DIR/deprecated.rs:9:8
|
LL | #[warn(clippy::drop_bounds)]
| ^^^^^^^^^^^^^^^^^^^
error: lint `clippy::temporary_cstring_as_ptr` has been removed: `this lint has been uplifted to rustc and is now called `temporary_cstring_as_ptr``
--> $DIR/deprecated.rs:12:8
--> $DIR/deprecated.rs:10:8
|
LL | #[warn(clippy::temporary_cstring_as_ptr)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::panic_params` has been removed: `this lint has been uplifted to rustc and is now called `panic_fmt``
--> $DIR/deprecated.rs:13:8
--> $DIR/deprecated.rs:11:8
|
LL | #[warn(clippy::panic_params)]
| ^^^^^^^^^^^^^^^^^^^^
error: lint `clippy::str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon`
error: lint `clippy::unstable_as_slice` has been removed: ``Vec::as_slice` has been stabilized in 1.7`
--> $DIR/deprecated.rs:1:8
|
LL | #[warn(clippy::str_to_string)]
| ^^^^^^^^^^^^^^^^^^^^^
LL | #[warn(clippy::unstable_as_slice)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 14 previous errors
error: aborting due to 12 previous errors

View File

@ -1,5 +1,3 @@
#[warn(str_to_string)]
#[warn(string_to_string)]
#[warn(unstable_as_slice)]
#[warn(unstable_as_mut_slice)]
#[warn(misaligned_transmute)]

View File

@ -1,40 +1,28 @@
error: lint `str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon`
--> $DIR/deprecated_old.rs:1:8
|
LL | #[warn(str_to_string)]
| ^^^^^^^^^^^^^
|
= note: `-D renamed-and-removed-lints` implied by `-D warnings`
error: lint `string_to_string` has been removed: `using `string::to_string` is common even today and specialization will likely happen soon`
--> $DIR/deprecated_old.rs:2:8
|
LL | #[warn(string_to_string)]
| ^^^^^^^^^^^^^^^^
error: lint `unstable_as_slice` has been removed: ``Vec::as_slice` has been stabilized in 1.7`
--> $DIR/deprecated_old.rs:3:8
--> $DIR/deprecated_old.rs:1:8
|
LL | #[warn(unstable_as_slice)]
| ^^^^^^^^^^^^^^^^^
|
= note: `-D renamed-and-removed-lints` implied by `-D warnings`
error: lint `unstable_as_mut_slice` has been removed: ``Vec::as_mut_slice` has been stabilized in 1.7`
--> $DIR/deprecated_old.rs:4:8
--> $DIR/deprecated_old.rs:2:8
|
LL | #[warn(unstable_as_mut_slice)]
| ^^^^^^^^^^^^^^^^^^^^^
error: lint `misaligned_transmute` has been removed: `this lint has been split into cast_ptr_alignment and transmute_ptr_to_ptr`
--> $DIR/deprecated_old.rs:5:8
--> $DIR/deprecated_old.rs:3:8
|
LL | #[warn(misaligned_transmute)]
| ^^^^^^^^^^^^^^^^^^^^
error: lint `str_to_string` has been removed: `using `str::to_string` is common even today and specialization will likely happen soon`
error: lint `unstable_as_slice` has been removed: ``Vec::as_slice` has been stabilized in 1.7`
--> $DIR/deprecated_old.rs:1:8
|
LL | #[warn(str_to_string)]
| ^^^^^^^^^^^^^
LL | #[warn(unstable_as_slice)]
| ^^^^^^^^^^^^^^^^^
error: aborting due to 6 previous errors
error: aborting due to 4 previous errors

View File

@ -86,3 +86,12 @@ fn check_ignore_macro() {
// checks if the lint ignores macros with `!` operator
!bool_macro!(1) && !bool_macro!("");
}
struct Nested {
inner: ((i32,), (i32,), (i32,)),
}
fn check_nested(n1: &Nested, n2: &Nested) -> bool {
// `n2.inner.0.0` mistyped as `n1.inner.0.0`
(n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0
}

View File

@ -162,5 +162,13 @@ error: equal expressions as operands to `/`
LL | const D: u32 = A / A;
| ^^^^^
error: aborting due to 27 previous errors
error: equal expressions as operands to `==`
--> $DIR/eq_op.rs:96:5
|
LL | (n1.inner.0).0 == (n1.inner.0).0 && (n1.inner.1).0 == (n2.inner.1).0 && (n1.inner.2).0 == (n2.inner.2).0
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[deny(clippy::eq_op)]` on by default
error: aborting due to 28 previous errors

View File

@ -37,3 +37,16 @@ fn mac() {
b!();
println!("{}", a);
}
fn semicolon() {
struct S {
a: u32,
};
impl S {
fn new(a: u32) -> Self {
Self { a }
}
}
let _ = S::new(3);
}

View File

@ -22,5 +22,9 @@ fn main() -> Result<(), Errors> {
println!("{:?}", x.map_err(|_| Errors::Ignored));
// Should not warn you because you explicitly ignore the parameter
// using a named wildcard value
println!("{:?}", x.map_err(|_foo| Errors::Ignored));
Ok(())
}

View File

@ -1,11 +1,11 @@
error: `map_err(|_|...` ignores the original error
error: `map_err(|_|...` wildcard pattern discards the original error
--> $DIR/map_err.rs:23:32
|
LL | println!("{:?}", x.map_err(|_| Errors::Ignored));
| ^^^
|
= note: `-D clippy::map-err-ignore` implied by `-D warnings`
= help: Consider wrapping the error in an enum variant
= help: Consider storing the original error as a source in the new error, or silence this warning using an ignored identifier (`.map_err(|_foo| ...`)
error: aborting due to previous error

View File

@ -0,0 +1,87 @@
#![allow(clippy::redundant_clone)]
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.0.0"]
use std::ops::Deref;
fn option_as_ref_deref() {
let mut opt = Some(String::from("123"));
let _ = opt.as_ref().map(String::as_str);
let _ = opt.as_ref().map(|x| x.as_str());
let _ = opt.as_mut().map(String::as_mut_str);
let _ = opt.as_mut().map(|x| x.as_mut_str());
}
fn match_like_matches() {
let _y = match Some(5) {
Some(0) => true,
_ => false,
};
}
fn match_same_arms() {
match (1, 2, 3) {
(1, .., 3) => 42,
(.., 3) => 42, //~ ERROR match arms have same body
_ => 0,
};
}
fn match_same_arms2() {
let _ = match Some(42) {
Some(_) => 24,
None => 24, //~ ERROR match arms have same body
};
}
pub fn manual_strip_msrv() {
let s = "hello, world!";
if s.starts_with("hello, ") {
assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
}
}
fn main() {
option_as_ref_deref();
match_like_matches();
match_same_arms();
match_same_arms2();
manual_strip_msrv();
}
mod meets_msrv {
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.45.0"]
fn main() {
let s = "hello, world!";
if s.starts_with("hello, ") {
assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
}
}
}
mod just_under_msrv {
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.46.0"]
fn main() {
let s = "hello, world!";
if s.starts_with("hello, ") {
assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
}
}
}
mod just_above_msrv {
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.44.0"]
fn main() {
let s = "hello, world!";
if s.starts_with("hello, ") {
assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
}
}
}

View File

@ -0,0 +1,37 @@
error: stripping a prefix manually
--> $DIR/min_rust_version_attr.rs:60:24
|
LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
| ^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::manual-strip` implied by `-D warnings`
note: the prefix was tested here
--> $DIR/min_rust_version_attr.rs:59:9
|
LL | if s.starts_with("hello, ") {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: try using the `strip_prefix` method
|
LL | if let Some(<stripped>) = s.strip_prefix("hello, ") {
LL | assert_eq!(<stripped>.to_uppercase(), "WORLD!");
|
error: stripping a prefix manually
--> $DIR/min_rust_version_attr.rs:72:24
|
LL | assert_eq!(s["hello, ".len()..].to_uppercase(), "WORLD!");
| ^^^^^^^^^^^^^^^^^^^^
|
note: the prefix was tested here
--> $DIR/min_rust_version_attr.rs:71:9
|
LL | if s.starts_with("hello, ") {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: try using the `strip_prefix` method
|
LL | if let Some(<stripped>) = s.strip_prefix("hello, ") {
LL | assert_eq!(<stripped>.to_uppercase(), "WORLD!");
|
error: aborting due to 2 previous errors

View File

@ -0,0 +1,4 @@
#![feature(custom_inner_attributes)]
#![clippy::msrv = "invalid.version"]
fn main() {}

View File

@ -0,0 +1,8 @@
error: `invalid.version` is not a valid Rust version
--> $DIR/min_rust_version_invalid_attr.rs:2:1
|
LL | #![clippy::msrv = "invalid.version"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error

View File

@ -0,0 +1,11 @@
#![feature(custom_inner_attributes)]
#![clippy::msrv = "1.40"]
#![clippy::msrv = "=1.35.0"]
#![clippy::msrv = "1.10.1"]
mod foo {
#![clippy::msrv = "1"]
#![clippy::msrv = "1.0.0"]
}
fn main() {}

Some files were not shown because too many files have changed in this diff Show More