Merge commit 'b71f3405606d49b9735606b479c3415a0ca9810f' into clippyup

This commit is contained in:
flip1995 2021-05-06 11:51:22 +02:00
commit 8e6ff8ffbd
No known key found for this signature in database
GPG key ID: 1CA0DF2AF59D68A5
65 changed files with 2327 additions and 1061 deletions

View file

@ -29,6 +29,7 @@ out
# gh pages docs # gh pages docs
util/gh-pages/lints.json util/gh-pages/lints.json
**/metadata_collection.json
# rustfmt backups # rustfmt backups
*.rs.bk *.rs.bk

View file

@ -118,7 +118,7 @@ which `IntelliJ Rust` will be able to understand.
Run `cargo dev ide_setup --repo-path <repo-path>` where `<repo-path>` is a path to the rustc repo Run `cargo dev ide_setup --repo-path <repo-path>` where `<repo-path>` is a path to the rustc repo
you just cloned. you just cloned.
The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to
Clippys `Cargo.toml`s and should allow rust-analyzer to understand most of the types that Clippy uses. Clippys `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses.
Just make sure to remove the dependencies again before finally making a pull request! Just make sure to remove the dependencies again before finally making a pull request!
[rustc_repo]: https://github.com/rust-lang/rust/ [rustc_repo]: https://github.com/rust-lang/rust/

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy" name = "clippy"
version = "0.1.53" version = "0.1.54"
authors = ["The Rust Clippy Developers"] authors = ["The Rust Clippy Developers"]
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy" repository = "https://github.com/rust-lang/rust-clippy"
@ -52,6 +52,7 @@ rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" }
deny-warnings = [] deny-warnings = []
integration = ["tempfile"] integration = ["tempfile"]
internal-lints = ["clippy_lints/internal-lints"] internal-lints = ["clippy_lints/internal-lints"]
metadata-collector-lint = ["internal-lints", "clippy_lints/metadata-collector-lint"]
[package.metadata.rust-analyzer] [package.metadata.rust-analyzer]
# This package uses #[feature(rustc_private)] # This package uses #[feature(rustc_private)]

View file

@ -1,7 +1,7 @@
[package] [package]
name = "clippy_lints" name = "clippy_lints"
# begin automatic update # begin automatic update
version = "0.1.53" version = "0.1.54"
# end automatic update # end automatic update
authors = ["The Rust Clippy Developers"] authors = ["The Rust Clippy Developers"]
description = "A bunch of helpful lints to avoid common pitfalls in Rust" description = "A bunch of helpful lints to avoid common pitfalls in Rust"
@ -20,6 +20,7 @@ pulldown-cmark = { version = "0.8", default-features = false }
quine-mc_cluskey = "0.2.2" quine-mc_cluskey = "0.2.2"
regex-syntax = "0.6" regex-syntax = "0.6"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true }
toml = "0.5.3" toml = "0.5.3"
unicode-normalization = "0.1" unicode-normalization = "0.1"
semver = "0.11" semver = "0.11"
@ -32,6 +33,7 @@ url = { version = "2.1.0", features = ["serde"] }
deny-warnings = [] deny-warnings = []
# build clippy with internal lints enabled, off by default # build clippy with internal lints enabled, off by default
internal-lints = ["clippy_utils/internal-lints"] internal-lints = ["clippy_utils/internal-lints"]
metadata-collector-lint = ["serde_json", "clippy_utils/metadata-collector-lint"]
[package.metadata.rust-analyzer] [package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)] # This crate uses #[feature(rustc_private)]

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::implements_trait; use clippy_utils::ty::implements_trait;
use clippy_utils::{get_trait_def_id, if_sequence, is_else_clause, paths, SpanlessEq}; use clippy_utils::{get_trait_def_id, if_sequence, in_constant, is_else_clause, paths, SpanlessEq};
use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -64,6 +64,10 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain {
return; return;
} }
if in_constant(cx, expr.hir_id) {
return;
}
// Check that there exists at least one explicit else condition // Check that there exists at least one explicit else condition
let (conds, _) = if_sequence(expr); let (conds, _) = if_sequence(expr);
if conds.len() < 2 { if conds.len() < 2 {

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
use clippy_utils::source::snippet_with_macro_callsite; use clippy_utils::source::snippet_with_macro_callsite;
use clippy_utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths}; use clippy_utils::{any_parent_is_automatically_derived, contains_name, in_macro, match_def_path, paths};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -75,6 +75,7 @@ impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]);
impl LateLintPass<'_> for Default { impl LateLintPass<'_> for Default {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! { if_chain! {
if !in_macro(expr.span);
// Avoid cases already linted by `field_reassign_with_default` // Avoid cases already linted by `field_reassign_with_default`
if !self.reassigned_linted.contains(&expr.span); if !self.reassigned_linted.contains(&expr.span);
if let ExprKind::Call(path, ..) = expr.kind; if let ExprKind::Call(path, ..) = expr.kind;

View file

@ -1,5 +1,6 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id}; use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id};
use if_chain::if_chain;
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind}; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
@ -70,20 +71,19 @@ declare_lint_pass!(EvalOrderDependence => [EVAL_ORDER_DEPENDENCE, DIVERGING_SUB_
impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence { impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// Find a write to a local variable. // Find a write to a local variable.
match expr.kind { let var = if_chain! {
ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) => { if let ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) = expr.kind;
if let Some(var) = path_to_local(lhs) { if let Some(var) = path_to_local(lhs);
let mut visitor = ReadVisitor { if expr.span.desugaring_kind().is_none();
cx, then { var } else { return; }
var, };
write_expr: expr, let mut visitor = ReadVisitor {
last_expr: expr, cx,
}; var,
check_for_unsequenced_reads(&mut visitor); write_expr: expr,
} last_expr: expr,
}, };
_ => {}, check_for_unsequenced_reads(&mut visitor);
}
} }
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
match stmt.kind { match stmt.kind {
@ -305,7 +305,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> {
self.cx, self.cx,
EVAL_ORDER_DEPENDENCE, EVAL_ORDER_DEPENDENCE,
expr.span, expr.span,
"unsequenced read of a variable", &format!("unsequenced read of `{}`", self.cx.tcx.hir().name(self.var)),
Some(self.write_expr.span), Some(self.write_expr.span),
"whether read occurs before this write depends on evaluation order", "whether read occurs before this write depends on evaluation order",
); );

View file

@ -1,13 +1,16 @@
use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{
use clippy_utils::match_panic_def_id; diagnostics::span_lint_and_sugg,
use clippy_utils::source::snippet_opt; get_async_fn_body, is_async_fn,
use if_chain::if_chain; source::{snippet_with_applicability, snippet_with_context, walk_span_to_context},
visitors::visit_break_exprs,
};
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind; use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind}; use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span; use rustc_span::{Span, SyntaxContext};
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for missing return statements at the end of a block. /// **What it does:** Checks for missing return statements at the end of a block.
@ -39,89 +42,160 @@ declare_clippy_lint! {
declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
static LINT_BREAK: &str = "change `break` to `return` as shown"; fn lint_return(cx: &LateContext<'_>, span: Span) {
static LINT_RETURN: &str = "add `return` as shown"; let mut app = Applicability::MachineApplicable;
let snip = snippet_with_applicability(cx, span, "..", &mut app);
fn lint(cx: &LateContext<'_>, outer_span: Span, inner_span: Span, msg: &str) { span_lint_and_sugg(
let outer_span = outer_span.source_callsite(); cx,
let inner_span = inner_span.source_callsite(); IMPLICIT_RETURN,
span,
span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing `return` statement", |diag| { "missing `return` statement",
if let Some(snippet) = snippet_opt(cx, inner_span) { "add `return` as shown",
diag.span_suggestion( format!("return {}", snip),
outer_span, app,
msg, );
format!("return {}", snippet),
Applicability::MachineApplicable,
);
}
});
} }
fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) { fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
span_lint_and_sugg(
cx,
IMPLICIT_RETURN,
break_span,
"missing `return` statement",
"change `break` to `return` as shown",
format!("return {}", snip),
app,
)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum LintLocation {
/// The lint was applied to a parent expression.
Parent,
/// The lint was applied to this expression, a child, or not applied.
Inner,
}
impl LintLocation {
fn still_parent(self, b: bool) -> Self {
if b { self } else { Self::Inner }
}
fn is_parent(self) -> bool {
self == Self::Parent
}
}
// Gets the call site if the span is in a child context. Otherwise returns `None`.
fn get_call_site(span: Span, ctxt: SyntaxContext) -> Option<Span> {
(span.ctxt() != ctxt).then(|| walk_span_to_context(span, ctxt).unwrap_or(span))
}
fn lint_implicit_returns(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
// The context of the function body.
ctxt: SyntaxContext,
// Whether the expression is from a macro expansion.
call_site_span: Option<Span>,
) -> LintLocation {
match expr.kind { match expr.kind {
// loops could be using `break` instead of `return` ExprKind::Block(
ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => { Block {
if let Some(expr) = &block.expr { expr: Some(block_expr), ..
expr_match(cx, expr); },
_,
) => lint_implicit_returns(
cx,
block_expr,
ctxt,
call_site_span.or_else(|| get_call_site(block_expr.span, ctxt)),
)
.still_parent(call_site_span.is_some()),
ExprKind::If(_, then_expr, Some(else_expr)) => {
// Both `then_expr` or `else_expr` are required to be blocks in the same context as the `if`. Don't
// bother checking.
let res = lint_implicit_returns(cx, then_expr, ctxt, call_site_span).still_parent(call_site_span.is_some());
if res.is_parent() {
// The return was added as a parent of this if expression.
return res;
} }
// only needed in the case of `break` with `;` at the end lint_implicit_returns(cx, else_expr, ctxt, call_site_span).still_parent(call_site_span.is_some())
else if let Some(stmt) = block.stmts.last() { },
if_chain! {
if let StmtKind::Semi(expr, ..) = &stmt.kind; ExprKind::Match(_, arms, _) => {
// make sure it's a break, otherwise we want to skip for arm in arms {
if let ExprKind::Break(.., Some(break_expr)) = &expr.kind; let res = lint_implicit_returns(
then { cx,
lint(cx, expr.span, break_expr.span, LINT_BREAK); arm.body,
ctxt,
call_site_span.or_else(|| get_call_site(arm.body.span, ctxt)),
)
.still_parent(call_site_span.is_some());
if res.is_parent() {
// The return was added as a parent of this match expression.
return res;
}
}
LintLocation::Inner
},
ExprKind::Loop(block, ..) => {
let mut add_return = false;
visit_break_exprs(block, |break_expr, dest, sub_expr| {
if dest.target_id.ok() == Some(expr.hir_id) {
if call_site_span.is_none() && break_expr.span.ctxt() == ctxt {
lint_break(cx, break_expr.span, sub_expr.unwrap().span);
} else {
// the break expression is from a macro call, add a return to the loop
add_return = true;
} }
} }
} });
}, if add_return {
// use `return` instead of `break` #[allow(clippy::option_if_let_else)]
ExprKind::Break(.., break_expr) => { if let Some(span) = call_site_span {
if let Some(break_expr) = break_expr { lint_return(cx, span);
lint(cx, expr.span, break_expr.span, LINT_BREAK); LintLocation::Parent
} } else {
}, lint_return(cx, expr.span);
ExprKind::If(.., if_expr, else_expr) => { LintLocation::Inner
expr_match(cx, if_expr);
if let Some(else_expr) = else_expr {
expr_match(cx, else_expr);
}
},
ExprKind::Match(.., arms, source) => {
let check_all_arms = match source {
MatchSource::IfLetDesugar {
contains_else_clause: has_else,
} => has_else,
_ => true,
};
if check_all_arms {
for arm in arms {
expr_match(cx, arm.body);
} }
} else { } else {
expr_match(cx, arms.first().expect("`if let` doesn't have a single arm").body); LintLocation::Inner
} }
}, },
// skip if it already has a return statement
ExprKind::Ret(..) => (), // If expressions without an else clause, and blocks without a final expression can only be the final expression
// make sure it's not a call that panics // if they are divergent, or return the unit type.
ExprKind::Call(expr, ..) => { ExprKind::If(_, _, None) | ExprKind::Block(Block { expr: None, .. }, _) | ExprKind::Ret(_) => {
if_chain! { LintLocation::Inner
if let ExprKind::Path(qpath) = &expr.kind; },
if let Some(path_def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id();
if match_panic_def_id(cx, path_def_id); // Any divergent expression doesn't need a return statement.
then { } ExprKind::MethodCall(..)
else { | ExprKind::Call(..)
lint(cx, expr.span, expr.span, LINT_RETURN) | ExprKind::Binary(..)
} | ExprKind::Unary(..)
| ExprKind::Index(..)
if cx.typeck_results().expr_ty(expr).is_never() =>
{
LintLocation::Inner
},
_ =>
{
#[allow(clippy::option_if_let_else)]
if let Some(span) = call_site_span {
lint_return(cx, span);
LintLocation::Parent
} else {
lint_return(cx, expr.span);
LintLocation::Inner
} }
}, },
// everything else is missing `return`
_ => lint(cx, expr.span, expr.span, LINT_RETURN),
} }
} }
@ -129,19 +203,32 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn {
fn check_fn( fn check_fn(
&mut self, &mut self,
cx: &LateContext<'tcx>, cx: &LateContext<'tcx>,
_: FnKind<'tcx>, kind: FnKind<'tcx>,
_: &'tcx FnDecl<'_>, decl: &'tcx FnDecl<'_>,
body: &'tcx Body<'_>, body: &'tcx Body<'_>,
span: Span, span: Span,
_: HirId, _: HirId,
) { ) {
if span.from_expansion() { if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_)))
|| span.ctxt() != body.value.span.ctxt()
|| in_external_macro(cx.sess(), span)
{
return; return;
} }
let body = cx.tcx.hir().body(body.id());
if cx.typeck_results().expr_ty(&body.value).is_unit() { let res_ty = cx.typeck_results().expr_ty(&body.value);
if res_ty.is_unit() || res_ty.is_never() {
return; return;
} }
expr_match(cx, &body.value);
let expr = if is_async_fn(kind) {
match get_async_fn_body(cx.tcx, body) {
Some(e) => e,
None => return,
}
} else {
&body.value
};
lint_implicit_returns(cx, expr, expr.span.ctxt(), None);
} }
} }

View file

@ -383,6 +383,7 @@ mod zero_sized_map_values;
// end lints modules, do not remove this comment, its used in `update_lints` // end lints modules, do not remove this comment, its used in `update_lints`
pub use crate::utils::conf::Conf; pub use crate::utils::conf::Conf;
use crate::utils::conf::TryConf;
/// Register all pre expansion lints /// Register all pre expansion lints
/// ///
@ -400,56 +401,40 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn read_conf(args: &[rustc_ast::NestedMetaItem], sess: &Session) -> Conf { pub fn read_conf(sess: &Session) -> Conf {
use std::path::Path; use std::path::Path;
match utils::conf::file_from_args(args) { let file_name = match utils::conf::lookup_conf_file() {
Ok(file_name) => { Ok(Some(path)) => path,
// if the user specified a file, it must exist, otherwise default to `clippy.toml` but Ok(None) => return Conf::default(),
// do not require the file to exist Err(error) => {
let file_name = match file_name { sess.struct_err(&format!("error finding Clippy's configuration file: {}", error))
Some(file_name) => file_name,
None => match utils::conf::lookup_conf_file() {
Ok(Some(path)) => path,
Ok(None) => return Conf::default(),
Err(error) => {
sess.struct_err(&format!("error finding Clippy's configuration file: {}", error))
.emit();
return Conf::default();
},
},
};
let file_name = if file_name.is_relative() {
sess.local_crate_source_file
.as_deref()
.and_then(Path::parent)
.unwrap_or_else(|| Path::new(""))
.join(file_name)
} else {
file_name
};
let (conf, errors) = utils::conf::read(&file_name);
// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
sess.struct_err(&format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
error
))
.emit(); .emit();
} return Conf::default();
},
};
conf let file_name = if file_name.is_relative() {
}, sess.local_crate_source_file
Err((err, span)) => { .as_deref()
sess.struct_span_err(span, err) .and_then(Path::parent)
.span_note(span, "Clippy will use default configuration") .unwrap_or_else(|| Path::new(""))
.emit(); .join(file_name)
Conf::default() } else {
}, file_name
};
let TryConf { conf, errors } = utils::conf::read(&file_name);
// all conf errors are non-fatal, we just use the default conf in case of error
for error in errors {
sess.struct_err(&format!(
"error reading Clippy's configuration file `{}`: {}",
file_name.display(),
error
))
.emit();
} }
conf
} }
/// Register all lints and lint groups with the rustc plugin registry /// Register all lints and lint groups with the rustc plugin registry
@ -1020,6 +1005,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem); store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem);
store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass); store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass);
} }
#[cfg(feature = "metadata-collector-lint")]
{
if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) {
store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::default());
}
}
store.register_late_pass(|| box utils::author::Author); store.register_late_pass(|| box utils::author::Author);
store.register_late_pass(|| box await_holding_invalid::AwaitHolding); store.register_late_pass(|| box await_holding_invalid::AwaitHolding);
store.register_late_pass(|| box serde_api::SerdeApi); store.register_late_pass(|| box serde_api::SerdeApi);

View file

@ -7,9 +7,10 @@ use clippy_utils::{is_trait_method, path_to_local_id, paths};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind}; use rustc_hir::{Block, Expr, ExprKind, GenericArg, GenericArgs, HirId, Local, Pat, PatKind, QPath, StmtKind, Ty};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_span::symbol::{sym, Ident}; use rustc_span::symbol::{sym, Ident};
use rustc_span::{MultiSpan, Span}; use rustc_span::{MultiSpan, Span};
@ -26,7 +27,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont
if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator); if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator);
if let Some(generic_args) = chain_method.args; if let Some(generic_args) = chain_method.args;
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0);
let ty = cx.typeck_results().node_type(ty.hir_id); if let Some(ty) = cx.typeck_results().node_type_opt(ty.hir_id);
if is_type_diagnostic_item(cx, ty, sym::vec_type) if is_type_diagnostic_item(cx, ty, sym::vec_type)
|| is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type)
|| match_type(cx, ty, &paths::BTREEMAP) || match_type(cx, ty, &paths::BTREEMAP)
@ -58,20 +59,33 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont
} }
fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) {
fn get_hir_id<'tcx>(ty: Option<&Ty<'tcx>>, method_args: Option<&GenericArgs<'tcx>>) -> Option<HirId> {
if let Some(ty) = ty {
return Some(ty.hir_id);
}
if let Some(generic_args) = method_args {
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0) {
return Some(ty.hir_id);
}
}
None
}
if let ExprKind::Block(block, _) = expr.kind { if let ExprKind::Block(block, _) = expr.kind {
for stmt in block.stmts { for stmt in block.stmts {
if_chain! { if_chain! {
if let StmtKind::Local( if let StmtKind::Local(
Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. },
init: Some(init_expr), .. } init: Some(init_expr), ty, .. }
) = stmt.kind; ) = stmt.kind;
if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind; if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind;
if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator); if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator);
if let Some(generic_args) = method_name.args; if let Some(hir_id) = get_hir_id(*ty, method_name.args);
if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); if let Some(ty) = cx.typeck_results().node_type_opt(hir_id);
if let ty = cx.typeck_results().node_type(ty.hir_id);
if is_type_diagnostic_item(cx, ty, sym::vec_type) || if is_type_diagnostic_item(cx, ty, sym::vec_type) ||
is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) ||
is_type_diagnostic_item(cx, ty, sym::BinaryHeap) ||
match_type(cx, ty, &paths::LINKED_LIST); match_type(cx, ty, &paths::LINKED_LIST);
if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident);
if let [iter_call] = &*iter_calls; if let [iter_call] = &*iter_calls;

View file

@ -28,11 +28,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &'
return; return;
} }
let used_in_condition = &var_visitor.ids; let used_in_condition = &var_visitor.ids;
let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { let mutated_in_body = mutated_variables(expr, cx);
used_in_condition.is_disjoint(&used_mutably) let mutated_in_condition = mutated_variables(cond, cx);
} else { let no_cond_variable_mutated =
return; if let (Some(used_mutably_body), Some(used_mutably_cond)) = (mutated_in_body, mutated_in_condition) {
}; used_in_condition.is_disjoint(&used_mutably_body) && used_in_condition.is_disjoint(&used_mutably_cond)
} else {
return;
};
let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v);
let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { let mut has_break_or_return_visitor = HasBreakOrReturnVisitor {

View file

@ -1590,9 +1590,9 @@ fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool {
// Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`)
fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> { fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option<BindingAnnotation> {
if_chain! { if_chain! {
if let PatKind::TupleStruct(ref qpath, pats, _) = arm.pat.kind; if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind;
if is_lang_ctor(cx, qpath, OptionSome); if is_lang_ctor(cx, qpath, OptionSome);
if let PatKind::Binding(rb, .., ident, _) = pats[0].kind; if let PatKind::Binding(rb, .., ident, _) = first_pat.kind;
if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut;
if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind; if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind;
if let ExprKind::Path(ref some_path) = e.kind; if let ExprKind::Path(ref some_path) = e.kind;
@ -1712,6 +1712,7 @@ mod redundant_pattern_match {
use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths}; use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths};
use if_chain::if_chain; use if_chain::if_chain;
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
use rustc_hir::{ use rustc_hir::{
@ -1739,6 +1740,13 @@ mod redundant_pattern_match {
/// deallocate memory. For these types, and composites containing them, changing the drop order /// deallocate memory. For these types, and composites containing them, changing the drop order
/// won't result in any observable side effects. /// won't result in any observable side effects.
fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default())
}
fn type_needs_ordered_drop_inner(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet<Ty<'tcx>>) -> bool {
if !seen.insert(ty) {
return false;
}
if !ty.needs_drop(cx.tcx, cx.param_env) { if !ty.needs_drop(cx.tcx, cx.param_env) {
false false
} else if !cx } else if !cx
@ -1750,12 +1758,12 @@ mod redundant_pattern_match {
// This type doesn't implement drop, so no side effects here. // This type doesn't implement drop, so no side effects here.
// Check if any component type has any. // Check if any component type has any.
match ty.kind() { match ty.kind() {
ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)), ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
ty::Array(ty, _) => type_needs_ordered_drop(cx, ty), ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen),
ty::Adt(adt, subs) => adt ty::Adt(adt, subs) => adt
.all_fields() .all_fields()
.map(|f| f.ty(cx.tcx, subs)) .map(|f| f.ty(cx.tcx, subs))
.any(|ty| type_needs_ordered_drop(cx, ty)), .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)),
_ => true, _ => true,
} }
} }
@ -1772,7 +1780,7 @@ mod redundant_pattern_match {
{ {
// Check all of the generic arguments. // Check all of the generic arguments.
if let ty::Adt(_, subs) = ty.kind() { if let ty::Adt(_, subs) = ty.kind() {
subs.types().any(|ty| type_needs_ordered_drop(cx, ty)) subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen))
} else { } else {
true true
} }

View file

@ -2189,27 +2189,6 @@ const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [
ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true),
]; ];
#[rustfmt::skip]
const PATTERN_METHODS: [(&str, usize); 17] = [
("contains", 1),
("starts_with", 1),
("ends_with", 1),
("find", 1),
("rfind", 1),
("split", 1),
("rsplit", 1),
("split_terminator", 1),
("rsplit_terminator", 1),
("splitn", 2),
("rsplitn", 2),
("matches", 1),
("rmatches", 1),
("match_indices", 1),
("rmatch_indices", 1),
("trim_start_matches", 1),
("trim_end_matches", 1),
];
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
enum SelfKind { enum SelfKind {
Value, Value,

View file

@ -9,9 +9,31 @@ use rustc_span::symbol::Symbol;
use super::SINGLE_CHAR_PATTERN; use super::SINGLE_CHAR_PATTERN;
const PATTERN_METHODS: [(&str, usize); 19] = [
("contains", 1),
("starts_with", 1),
("ends_with", 1),
("find", 1),
("rfind", 1),
("split", 1),
("rsplit", 1),
("split_terminator", 1),
("rsplit_terminator", 1),
("splitn", 2),
("rsplitn", 2),
("matches", 1),
("rmatches", 1),
("match_indices", 1),
("rmatch_indices", 1),
("strip_prefix", 1),
("strip_suffix", 1),
("trim_start_matches", 1),
("trim_end_matches", 1),
];
/// lint for length-1 `str`s for methods in `PATTERN_METHODS` /// lint for length-1 `str`s for methods in `PATTERN_METHODS`
pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) { pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) {
for &(method, pos) in &crate::methods::PATTERN_METHODS { for &(method, pos) in &PATTERN_METHODS {
if_chain! { if_chain! {
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(&args[0]).kind(); if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(&args[0]).kind();
if *ty.kind() == ty::Str; if *ty.kind() == ty::Str;

View file

@ -6,6 +6,7 @@ use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_middle::ty::{self, TyS};
use rustc_span::sym; use rustc_span::sym;
use super::UNNECESSARY_FILTER_MAP; use super::UNNECESSARY_FILTER_MAP;
@ -28,25 +29,28 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr<
found_mapping |= return_visitor.found_mapping; found_mapping |= return_visitor.found_mapping;
found_filtering |= return_visitor.found_filtering; found_filtering |= return_visitor.found_filtering;
if !found_filtering { let sugg = if !found_filtering {
span_lint( "map"
cx, } else if !found_mapping && !mutates_arg {
UNNECESSARY_FILTER_MAP, let in_ty = cx.typeck_results().node_type(body.params[0].hir_id);
expr.span, match cx.typeck_results().expr_ty(&body.value).kind() {
"this `.filter_map` can be written more simply using `.map`", ty::Adt(adt, subst)
); if cx.tcx.is_diagnostic_item(sym::option_type, adt.did)
&& TyS::same_type(in_ty, subst.type_at(0)) =>
{
"filter"
},
_ => return,
}
} else {
return; return;
} };
span_lint(
if !found_mapping && !mutates_arg { cx,
span_lint( UNNECESSARY_FILTER_MAP,
cx, expr.span,
UNNECESSARY_FILTER_MAP, &format!("this `.filter_map` can be written more simply using `.{}`", sugg),
expr.span, );
"this `.filter_map` can be written more simply using `.filter`",
);
return;
}
} }
} }

View file

@ -1,569 +0,0 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{
BindingMode, Expr, ExprKind, GenericParamKind, Generics, Lit, LitFloatType, LitIntType, LitKind, Mutability,
NodeId, Pat, PatKind, UnOp,
};
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::Applicability;
use rustc_hir::PrimTy;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
declare_clippy_lint! {
/// **What it does:** Checks for structure field patterns bound to wildcards.
///
/// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
/// the fields that are actually bound.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct Foo {
/// # a: i32,
/// # b: i32,
/// # c: i32,
/// # }
/// let f = Foo { a: 0, b: 0, c: 0 };
///
/// // Bad
/// match f {
/// Foo { a: _, b: 0, .. } => {},
/// Foo { a: _, b: _, c: _ } => {},
/// }
///
/// // Good
/// match f {
/// Foo { b: 0, .. } => {},
/// Foo { .. } => {},
/// }
/// ```
pub UNNEEDED_FIELD_PATTERN,
restriction,
"struct fields bound to a wildcard instead of using `..`"
}
declare_clippy_lint! {
/// **What it does:** Checks for function arguments having the similar names
/// differing by an underscore.
///
/// **Why is this bad?** It affects code readability.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// fn foo(a: i32, _a: i32) {}
///
/// // Good
/// fn bar(a: i32, _b: i32) {}
/// ```
pub DUPLICATE_UNDERSCORE_ARGUMENT,
style,
"function arguments having names which only differ by an underscore"
}
declare_clippy_lint! {
/// **What it does:** Detects expressions of the form `--x`.
///
/// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
/// decremented.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let mut x = 3;
/// --x;
/// ```
pub DOUBLE_NEG,
style,
"`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
}
declare_clippy_lint! {
/// **What it does:** Warns on hexadecimal literals with mixed-case letter
/// digits.
///
/// **Why is this bad?** It looks confusing.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// let y = 0x1a9BAcD;
///
/// // Good
/// let y = 0x1A9BACD;
/// ```
pub MIXED_CASE_HEX_LITERALS,
style,
"hex literals whose letter digits are not consistently upper- or lowercased"
}
declare_clippy_lint! {
/// **What it does:** Warns if literal suffixes are not separated by an
/// underscore.
///
/// **Why is this bad?** It is much less readable.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// let y = 123832i32;
///
/// // Good
/// let y = 123832_i32;
/// ```
pub UNSEPARATED_LITERAL_SUFFIX,
pedantic,
"literals whose suffix is not separated by an underscore"
}
declare_clippy_lint! {
/// **What it does:** Warns if an integral constant literal starts with `0`.
///
/// **Why is this bad?** In some languages (including the infamous C language
/// and most of its
/// family), this marks an octal constant. In Rust however, this is a decimal
/// constant. This could
/// be confusing for both the writer and a reader of the constant.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// In Rust:
/// ```rust
/// fn main() {
/// let a = 0123;
/// println!("{}", a);
/// }
/// ```
///
/// prints `123`, while in C:
///
/// ```c
/// #include <stdio.h>
///
/// int main() {
/// int a = 0123;
/// printf("%d\n", a);
/// }
/// ```
///
/// prints `83` (as `83 == 0o123` while `123 == 0o173`).
pub ZERO_PREFIXED_LITERAL,
complexity,
"integer literals starting with `0`"
}
declare_clippy_lint! {
/// **What it does:** Warns if a generic shadows a built-in type.
///
/// **Why is this bad?** This gives surprising type errors.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```ignore
/// impl<u32> Foo<u32> {
/// fn impl_func(&self) -> u32 {
/// 42
/// }
/// }
/// ```
pub BUILTIN_TYPE_SHADOW,
style,
"shadowing a builtin type"
}
declare_clippy_lint! {
/// **What it does:** Checks for patterns in the form `name @ _`.
///
/// **Why is this bad?** It's almost always more readable to just use direct
/// bindings.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # let v = Some("abc");
///
/// // Bad
/// match v {
/// Some(x) => (),
/// y @ _ => (),
/// }
///
/// // Good
/// match v {
/// Some(x) => (),
/// y => (),
/// }
/// ```
pub REDUNDANT_PATTERN,
style,
"using `name @ _` in a pattern"
}
declare_clippy_lint! {
/// **What it does:** Checks for tuple patterns with a wildcard
/// pattern (`_`) is next to a rest pattern (`..`).
///
/// _NOTE_: While `_, ..` means there is at least one element left, `..`
/// means there are 0 or more elements left. This can make a difference
/// when refactoring, but shouldn't result in errors in the refactored code,
/// since the wildcard pattern isn't used anyway.
/// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern
/// can match that element as well.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct TupleStruct(u32, u32, u32);
/// # let t = TupleStruct(1, 2, 3);
/// // Bad
/// match t {
/// TupleStruct(0, .., _) => (),
/// _ => (),
/// }
///
/// // Good
/// match t {
/// TupleStruct(0, ..) => (),
/// _ => (),
/// }
/// ```
pub UNNEEDED_WILDCARD_PATTERN,
complexity,
"tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
}
declare_lint_pass!(MiscEarlyLints => [
UNNEEDED_FIELD_PATTERN,
DUPLICATE_UNDERSCORE_ARGUMENT,
DOUBLE_NEG,
MIXED_CASE_HEX_LITERALS,
UNSEPARATED_LITERAL_SUFFIX,
ZERO_PREFIXED_LITERAL,
BUILTIN_TYPE_SHADOW,
REDUNDANT_PATTERN,
UNNEEDED_WILDCARD_PATTERN,
]);
impl EarlyLintPass for MiscEarlyLints {
fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
for param in &gen.params {
if let GenericParamKind::Type { .. } = param.kind {
if let Some(prim_ty) = PrimTy::from_name(param.ident.name) {
span_lint(
cx,
BUILTIN_TYPE_SHADOW,
param.ident.span,
&format!("this generic shadows the built-in type `{}`", prim_ty.name()),
);
}
}
}
}
fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind {
let mut wilds = 0;
let type_name = npat
.segments
.last()
.expect("A path must have at least one segment")
.ident
.name;
for field in pfields {
if let PatKind::Wild = field.pat.kind {
wilds += 1;
}
}
if !pfields.is_empty() && wilds == pfields.len() {
span_lint_and_help(
cx,
UNNEEDED_FIELD_PATTERN,
pat.span,
"all the struct fields are matched to a wildcard pattern, consider using `..`",
None,
&format!("try with `{} {{ .. }}` instead", type_name),
);
return;
}
if wilds > 0 {
for field in pfields {
if let PatKind::Wild = field.pat.kind {
wilds -= 1;
if wilds > 0 {
span_lint(
cx,
UNNEEDED_FIELD_PATTERN,
field.span,
"you matched a field with a wildcard pattern, consider using `..` instead",
);
} else {
let mut normal = vec![];
for field in pfields {
match field.pat.kind {
PatKind::Wild => {},
_ => {
if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
normal.push(n);
}
},
}
}
span_lint_and_help(
cx,
UNNEEDED_FIELD_PATTERN,
field.span,
"you matched a field with a wildcard pattern, consider using `..` \
instead",
None,
&format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
);
}
}
}
}
}
if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind {
let left_binding = match left {
BindingMode::ByRef(Mutability::Mut) => "ref mut ",
BindingMode::ByRef(Mutability::Not) => "ref ",
BindingMode::ByValue(..) => "",
};
if let PatKind::Wild = right.kind {
span_lint_and_sugg(
cx,
REDUNDANT_PATTERN,
pat.span,
&format!(
"the `{} @ _` pattern can be written as just `{}`",
ident.name, ident.name,
),
"try",
format!("{}{}", left_binding, ident.name),
Applicability::MachineApplicable,
);
}
}
check_unneeded_wildcard_pattern(cx, pat);
}
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
for arg in &fn_kind.decl().inputs {
if let PatKind::Ident(_, ident, None) = arg.pat.kind {
let arg_name = ident.to_string();
if let Some(arg_name) = arg_name.strip_prefix('_') {
if let Some(correspondence) = registered_names.get(arg_name) {
span_lint(
cx,
DUPLICATE_UNDERSCORE_ARGUMENT,
*correspondence,
&format!(
"`{}` already exists, having another argument having almost the same \
name makes code comprehension and documentation more difficult",
arg_name
),
);
}
} else {
registered_names.insert(arg_name, arg.pat.span);
}
}
}
}
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
match expr.kind {
ExprKind::Unary(UnOp::Neg, ref inner) => {
if let ExprKind::Unary(UnOp::Neg, _) = inner.kind {
span_lint(
cx,
DOUBLE_NEG,
expr.span,
"`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
);
}
},
ExprKind::Lit(ref lit) => Self::check_lit(cx, lit),
_ => (),
}
}
}
impl MiscEarlyLints {
fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
// We test if first character in snippet is a number, because the snippet could be an expansion
// from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
// Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
// See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
// FIXME: Find a better way to detect those cases.
let lit_snip = match snippet_opt(cx, lit.span) {
Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip,
_ => return,
};
if let LitKind::Int(value, lit_int_type) = lit.kind {
let suffix = match lit_int_type {
LitIntType::Signed(ty) => ty.name_str(),
LitIntType::Unsigned(ty) => ty.name_str(),
LitIntType::Unsuffixed => "",
};
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
return; // It's useless so shouldn't lint.
};
// Do not lint when literal is unsuffixed.
if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
span_lint_and_sugg(
cx,
UNSEPARATED_LITERAL_SUFFIX,
lit.span,
"integer type suffix should be separated by an underscore",
"add an underscore",
format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
Applicability::MachineApplicable,
);
}
if lit_snip.starts_with("0x") {
if maybe_last_sep_idx <= 2 {
// It's meaningless or causes range error.
return;
}
let mut seen = (false, false);
for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() {
match ch {
b'a'..=b'f' => seen.0 = true,
b'A'..=b'F' => seen.1 = true,
_ => {},
}
if seen.0 && seen.1 {
span_lint(
cx,
MIXED_CASE_HEX_LITERALS,
lit.span,
"inconsistent casing in hexadecimal literal",
);
break;
}
}
} else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
/* nothing to do */
} else if value != 0 && lit_snip.starts_with('0') {
span_lint_and_then(
cx,
ZERO_PREFIXED_LITERAL,
lit.span,
"this is a decimal constant",
|diag| {
diag.span_suggestion(
lit.span,
"if you mean to use a decimal constant, remove the `0` to avoid confusion",
lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(),
Applicability::MaybeIncorrect,
);
diag.span_suggestion(
lit.span,
"if you mean to use an octal constant, use `0o`",
format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')),
Applicability::MaybeIncorrect,
);
},
);
}
} else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
let suffix = float_ty.name_str();
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
return; // It's useless so shouldn't lint.
};
if lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
span_lint_and_sugg(
cx,
UNSEPARATED_LITERAL_SUFFIX,
lit.span,
"float type suffix should be separated by an underscore",
"add an underscore",
format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
Applicability::MachineApplicable,
);
}
}
}
}
fn check_unneeded_wildcard_pattern(cx: &EarlyContext<'_>, pat: &Pat) {
if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind {
fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) {
span_lint_and_sugg(
cx,
UNNEEDED_WILDCARD_PATTERN,
span,
if only_one {
"this pattern is unneeded as the `..` pattern can match that element"
} else {
"these patterns are unneeded as the `..` pattern can match those elements"
},
if only_one { "remove it" } else { "remove them" },
"".to_string(),
Applicability::MachineApplicable,
);
}
if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) {
if let Some((left_index, left_pat)) = patterns[..rest_index]
.iter()
.rev()
.take_while(|pat| matches!(pat.kind, PatKind::Wild))
.enumerate()
.last()
{
span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0);
}
if let Some((right_index, right_pat)) = patterns[rest_index + 1..]
.iter()
.take_while(|pat| matches!(pat.kind, PatKind::Wild))
.enumerate()
.last()
{
span_lint(
cx,
patterns[rest_index].span.shrink_to_hi().to(right_pat.span),
right_index == 0,
);
}
}
}
}

View file

@ -0,0 +1,19 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast::{GenericParam, GenericParamKind};
use rustc_hir::PrimTy;
use rustc_lint::EarlyContext;
use super::BUILTIN_TYPE_SHADOW;
pub(super) fn check(cx: &EarlyContext<'_>, param: &GenericParam) {
if let GenericParamKind::Type { .. } = param.kind {
if let Some(prim_ty) = PrimTy::from_name(param.ident.name) {
span_lint(
cx,
BUILTIN_TYPE_SHADOW,
param.ident.span,
&format!("this generic shadows the built-in type `{}`", prim_ty.name()),
);
}
}
}

View file

@ -0,0 +1,23 @@
use super::MiscEarlyLints;
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast::{Expr, ExprKind, UnOp};
use rustc_lint::EarlyContext;
use super::DOUBLE_NEG;
pub(super) fn check(cx: &EarlyContext<'_>, expr: &Expr) {
match expr.kind {
ExprKind::Unary(UnOp::Neg, ref inner) => {
if let ExprKind::Unary(UnOp::Neg, _) = inner.kind {
span_lint(
cx,
DOUBLE_NEG,
expr.span,
"`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op",
);
}
},
ExprKind::Lit(ref lit) => MiscEarlyLints::check_lit(cx, lit),
_ => (),
}
}

View file

@ -0,0 +1,34 @@
use clippy_utils::diagnostics::span_lint;
use rustc_ast::ast::Lit;
use rustc_lint::EarlyContext;
use super::MIXED_CASE_HEX_LITERALS;
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, suffix: &str, lit_snip: &str) {
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
return; // It's useless so shouldn't lint.
};
if maybe_last_sep_idx <= 2 {
// It's meaningless or causes range error.
return;
}
let mut seen = (false, false);
for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() {
match ch {
b'a'..=b'f' => seen.0 = true,
b'A'..=b'F' => seen.1 = true,
_ => {},
}
if seen.0 && seen.1 {
span_lint(
cx,
MIXED_CASE_HEX_LITERALS,
lit.span,
"inconsistent casing in hexadecimal literal",
);
break;
}
}
}

View file

@ -0,0 +1,348 @@
mod builtin_type_shadow;
mod double_neg;
mod mixed_case_hex_literals;
mod redundant_pattern;
mod unneeded_field_pattern;
mod unneeded_wildcard_pattern;
mod unseparated_literal_suffix;
mod zero_prefixed_literal;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Expr, Generics, Lit, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
declare_clippy_lint! {
/// **What it does:** Checks for structure field patterns bound to wildcards.
///
/// **Why is this bad?** Using `..` instead is shorter and leaves the focus on
/// the fields that are actually bound.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct Foo {
/// # a: i32,
/// # b: i32,
/// # c: i32,
/// # }
/// let f = Foo { a: 0, b: 0, c: 0 };
///
/// // Bad
/// match f {
/// Foo { a: _, b: 0, .. } => {},
/// Foo { a: _, b: _, c: _ } => {},
/// }
///
/// // Good
/// match f {
/// Foo { b: 0, .. } => {},
/// Foo { .. } => {},
/// }
/// ```
pub UNNEEDED_FIELD_PATTERN,
restriction,
"struct fields bound to a wildcard instead of using `..`"
}
declare_clippy_lint! {
/// **What it does:** Checks for function arguments having the similar names
/// differing by an underscore.
///
/// **Why is this bad?** It affects code readability.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// fn foo(a: i32, _a: i32) {}
///
/// // Good
/// fn bar(a: i32, _b: i32) {}
/// ```
pub DUPLICATE_UNDERSCORE_ARGUMENT,
style,
"function arguments having names which only differ by an underscore"
}
declare_clippy_lint! {
/// **What it does:** Detects expressions of the form `--x`.
///
/// **Why is this bad?** It can mislead C/C++ programmers to think `x` was
/// decremented.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// let mut x = 3;
/// --x;
/// ```
pub DOUBLE_NEG,
style,
"`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++"
}
declare_clippy_lint! {
/// **What it does:** Warns on hexadecimal literals with mixed-case letter
/// digits.
///
/// **Why is this bad?** It looks confusing.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// let y = 0x1a9BAcD;
///
/// // Good
/// let y = 0x1A9BACD;
/// ```
pub MIXED_CASE_HEX_LITERALS,
style,
"hex literals whose letter digits are not consistently upper- or lowercased"
}
declare_clippy_lint! {
/// **What it does:** Warns if literal suffixes are not separated by an
/// underscore.
///
/// **Why is this bad?** It is much less readable.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// // Bad
/// let y = 123832i32;
///
/// // Good
/// let y = 123832_i32;
/// ```
pub UNSEPARATED_LITERAL_SUFFIX,
pedantic,
"literals whose suffix is not separated by an underscore"
}
declare_clippy_lint! {
/// **What it does:** Warns if an integral constant literal starts with `0`.
///
/// **Why is this bad?** In some languages (including the infamous C language
/// and most of its
/// family), this marks an octal constant. In Rust however, this is a decimal
/// constant. This could
/// be confusing for both the writer and a reader of the constant.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// In Rust:
/// ```rust
/// fn main() {
/// let a = 0123;
/// println!("{}", a);
/// }
/// ```
///
/// prints `123`, while in C:
///
/// ```c
/// #include <stdio.h>
///
/// int main() {
/// int a = 0123;
/// printf("%d\n", a);
/// }
/// ```
///
/// prints `83` (as `83 == 0o123` while `123 == 0o173`).
pub ZERO_PREFIXED_LITERAL,
complexity,
"integer literals starting with `0`"
}
declare_clippy_lint! {
/// **What it does:** Warns if a generic shadows a built-in type.
///
/// **Why is this bad?** This gives surprising type errors.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```ignore
/// impl<u32> Foo<u32> {
/// fn impl_func(&self) -> u32 {
/// 42
/// }
/// }
/// ```
pub BUILTIN_TYPE_SHADOW,
style,
"shadowing a builtin type"
}
declare_clippy_lint! {
/// **What it does:** Checks for patterns in the form `name @ _`.
///
/// **Why is this bad?** It's almost always more readable to just use direct
/// bindings.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # let v = Some("abc");
///
/// // Bad
/// match v {
/// Some(x) => (),
/// y @ _ => (),
/// }
///
/// // Good
/// match v {
/// Some(x) => (),
/// y => (),
/// }
/// ```
pub REDUNDANT_PATTERN,
style,
"using `name @ _` in a pattern"
}
declare_clippy_lint! {
/// **What it does:** Checks for tuple patterns with a wildcard
/// pattern (`_`) is next to a rest pattern (`..`).
///
/// _NOTE_: While `_, ..` means there is at least one element left, `..`
/// means there are 0 or more elements left. This can make a difference
/// when refactoring, but shouldn't result in errors in the refactored code,
/// since the wildcard pattern isn't used anyway.
/// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern
/// can match that element as well.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// # struct TupleStruct(u32, u32, u32);
/// # let t = TupleStruct(1, 2, 3);
/// // Bad
/// match t {
/// TupleStruct(0, .., _) => (),
/// _ => (),
/// }
///
/// // Good
/// match t {
/// TupleStruct(0, ..) => (),
/// _ => (),
/// }
/// ```
pub UNNEEDED_WILDCARD_PATTERN,
complexity,
"tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
}
declare_lint_pass!(MiscEarlyLints => [
UNNEEDED_FIELD_PATTERN,
DUPLICATE_UNDERSCORE_ARGUMENT,
DOUBLE_NEG,
MIXED_CASE_HEX_LITERALS,
UNSEPARATED_LITERAL_SUFFIX,
ZERO_PREFIXED_LITERAL,
BUILTIN_TYPE_SHADOW,
REDUNDANT_PATTERN,
UNNEEDED_WILDCARD_PATTERN,
]);
impl EarlyLintPass for MiscEarlyLints {
fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) {
for param in &gen.params {
builtin_type_shadow::check(cx, param);
}
}
fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
unneeded_field_pattern::check(cx, pat);
redundant_pattern::check(cx, pat);
unneeded_wildcard_pattern::check(cx, pat);
}
fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();
for arg in &fn_kind.decl().inputs {
if let PatKind::Ident(_, ident, None) = arg.pat.kind {
let arg_name = ident.to_string();
if let Some(arg_name) = arg_name.strip_prefix('_') {
if let Some(correspondence) = registered_names.get(arg_name) {
span_lint(
cx,
DUPLICATE_UNDERSCORE_ARGUMENT,
*correspondence,
&format!(
"`{}` already exists, having another argument having almost the same \
name makes code comprehension and documentation more difficult",
arg_name
),
);
}
} else {
registered_names.insert(arg_name, arg.pat.span);
}
}
}
}
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
double_neg::check(cx, expr)
}
}
impl MiscEarlyLints {
fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) {
// We test if first character in snippet is a number, because the snippet could be an expansion
// from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
// Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
// See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
// FIXME: Find a better way to detect those cases.
let lit_snip = match snippet_opt(cx, lit.span) {
Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip,
_ => return,
};
if let LitKind::Int(value, lit_int_type) = lit.kind {
let suffix = match lit_int_type {
LitIntType::Signed(ty) => ty.name_str(),
LitIntType::Unsigned(ty) => ty.name_str(),
LitIntType::Unsuffixed => "",
};
unseparated_literal_suffix::check(cx, lit, &lit_snip, suffix, "integer");
if lit_snip.starts_with("0x") {
mixed_case_hex_literals::check(cx, lit, suffix, &lit_snip)
} else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
/* nothing to do */
} else if value != 0 && lit_snip.starts_with('0') {
zero_prefixed_literal::check(cx, lit, &lit_snip)
}
} else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind {
let suffix = float_ty.name_str();
unseparated_literal_suffix::check(cx, lit, &lit_snip, suffix, "float")
}
}
}

View file

@ -0,0 +1,31 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{BindingMode, Mutability, Pat, PatKind};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use super::REDUNDANT_PATTERN;
pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind {
let left_binding = match left {
BindingMode::ByRef(Mutability::Mut) => "ref mut ",
BindingMode::ByRef(Mutability::Not) => "ref ",
BindingMode::ByValue(..) => "",
};
if let PatKind::Wild = right.kind {
span_lint_and_sugg(
cx,
REDUNDANT_PATTERN,
pat.span,
&format!(
"the `{} @ _` pattern can be written as just `{}`",
ident.name, ident.name,
),
"try",
format!("{}{}", left_binding, ident.name),
Applicability::MachineApplicable,
);
}
}
}

View file

@ -0,0 +1,72 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use rustc_ast::ast::{Pat, PatKind};
use rustc_lint::{EarlyContext, LintContext};
use super::UNNEEDED_FIELD_PATTERN;
pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind {
let mut wilds = 0;
let type_name = npat
.segments
.last()
.expect("A path must have at least one segment")
.ident
.name;
for field in pfields {
if let PatKind::Wild = field.pat.kind {
wilds += 1;
}
}
if !pfields.is_empty() && wilds == pfields.len() {
span_lint_and_help(
cx,
UNNEEDED_FIELD_PATTERN,
pat.span,
"all the struct fields are matched to a wildcard pattern, consider using `..`",
None,
&format!("try with `{} {{ .. }}` instead", type_name),
);
return;
}
if wilds > 0 {
for field in pfields {
if let PatKind::Wild = field.pat.kind {
wilds -= 1;
if wilds > 0 {
span_lint(
cx,
UNNEEDED_FIELD_PATTERN,
field.span,
"you matched a field with a wildcard pattern, consider using `..` instead",
);
} else {
let mut normal = vec![];
for field in pfields {
match field.pat.kind {
PatKind::Wild => {},
_ => {
if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) {
normal.push(n);
}
},
}
}
span_lint_and_help(
cx,
UNNEEDED_FIELD_PATTERN,
field.span,
"you matched a field with a wildcard pattern, consider using `..` \
instead",
None,
&format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")),
);
}
}
}
}
}
}

View file

@ -0,0 +1,52 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::{Pat, PatKind};
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use rustc_span::source_map::Span;
use super::UNNEEDED_WILDCARD_PATTERN;
pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) {
if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind {
if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) {
if let Some((left_index, left_pat)) = patterns[..rest_index]
.iter()
.rev()
.take_while(|pat| matches!(pat.kind, PatKind::Wild))
.enumerate()
.last()
{
span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0);
}
if let Some((right_index, right_pat)) = patterns[rest_index + 1..]
.iter()
.take_while(|pat| matches!(pat.kind, PatKind::Wild))
.enumerate()
.last()
{
span_lint(
cx,
patterns[rest_index].span.shrink_to_hi().to(right_pat.span),
right_index == 0,
);
}
}
}
}
fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) {
span_lint_and_sugg(
cx,
UNNEEDED_WILDCARD_PATTERN,
span,
if only_one {
"this pattern is unneeded as the `..` pattern can match that element"
} else {
"these patterns are unneeded as the `..` pattern can match those elements"
},
if only_one { "remove it" } else { "remove them" },
"".to_string(),
Applicability::MachineApplicable,
);
}

View file

@ -0,0 +1,26 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use rustc_ast::ast::Lit;
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use super::UNSEPARATED_LITERAL_SUFFIX;
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &str, sugg_type: &str) {
let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) {
val
} else {
return; // It's useless so shouldn't lint.
};
// Do not lint when literal is unsuffixed.
if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' {
span_lint_and_sugg(
cx,
UNSEPARATED_LITERAL_SUFFIX,
lit.span,
&format!("{} type suffix should be separated by an underscore", sugg_type),
"add an underscore",
format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix),
Applicability::MachineApplicable,
);
}
}

View file

@ -0,0 +1,29 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast::Lit;
use rustc_errors::Applicability;
use rustc_lint::EarlyContext;
use super::ZERO_PREFIXED_LITERAL;
pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str) {
span_lint_and_then(
cx,
ZERO_PREFIXED_LITERAL,
lit.span,
"this is a decimal constant",
|diag| {
diag.span_suggestion(
lit.span,
"if you mean to use a decimal constant, remove the `0` to avoid confusion",
lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(),
Applicability::MaybeIncorrect,
);
diag.span_suggestion(
lit.span,
"if you mean to use an octal constant, use `0o`",
format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')),
Applicability::MaybeIncorrect,
);
},
);
}

View file

@ -7,7 +7,7 @@ use rustc_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, NestedVisitorMap, Visitor};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind}; use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, Lint}; use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym; use rustc_span::symbol::sym;
@ -157,26 +157,16 @@ impl SlowVectorInit {
vec_alloc: &VecAllocation<'_>, vec_alloc: &VecAllocation<'_>,
) { ) {
match initialization { match initialization {
InitializationType::Extend(e) | InitializationType::Resize(e) => Self::emit_lint( InitializationType::Extend(e) | InitializationType::Resize(e) => {
cx, Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization")
e, },
vec_alloc,
"slow zero-filling initialization",
SLOW_VECTOR_INITIALIZATION,
),
}; };
} }
fn emit_lint<'tcx>( fn emit_lint<'tcx>(cx: &LateContext<'tcx>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
cx: &LateContext<'tcx>,
slow_fill: &Expr<'_>,
vec_alloc: &VecAllocation<'_>,
msg: &str,
lint: &'static Lint,
) {
let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len"); let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len");
span_lint_and_then(cx, lint, slow_fill.span, msg, |diag| { span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
diag.span_suggestion( diag.span_suggestion(
vec_alloc.allocation_expr.span, vec_alloc.allocation_expr.span,
"consider replace allocation with", "consider replace allocation with",

View file

@ -47,7 +47,9 @@ impl EarlyLintPass for UnusedUnit {
if_chain! { if_chain! {
if let Some(stmt) = block.stmts.last(); if let Some(stmt) = block.stmts.last();
if let ast::StmtKind::Expr(ref expr) = stmt.kind; if let ast::StmtKind::Expr(ref expr) = stmt.kind;
if is_unit_expr(expr) && !stmt.span.from_expansion(); if is_unit_expr(expr);
let ctxt = block.span.ctxt();
if stmt.span.ctxt() == ctxt && expr.span.ctxt() == ctxt;
then { then {
let sp = expr.span; let sp = expr.span;
span_lint_and_sugg( span_lint_and_sugg(

View file

@ -1,121 +1,110 @@
//! Read configurations files. //! Read configurations files.
#![deny(clippy::missing_docs_in_private_items)] #![allow(clippy::module_name_repetitions)]
use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem}; use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor};
use rustc_span::source_map; use serde::Deserialize;
use source_map::Span; use std::error::Error;
use std::lazy::SyncLazy;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::{env, fmt, fs, io}; use std::{env, fmt, fs, io};
/// Gets the configuration file from arguments. /// Conf with parse errors
pub fn file_from_args(args: &[NestedMetaItem]) -> Result<Option<PathBuf>, (&'static str, Span)> { #[derive(Default)]
for arg in args.iter().filter_map(NestedMetaItem::meta_item) { pub struct TryConf {
if arg.has_name(sym!(conf_file)) { pub conf: Conf,
return match arg.kind { pub errors: Vec<String>,
MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)),
MetaItemKind::NameValue(ref value) => {
if let LitKind::Str(ref file, _) = value.kind {
Ok(Some(file.to_string().into()))
} else {
Err(("`conf_file` value must be a string", value.span))
}
},
};
}
}
Ok(None)
} }
/// Error from reading a configuration file. impl TryConf {
#[derive(Debug)] fn from_error(error: impl Error) -> Self {
pub enum Error { Self {
/// An I/O error. conf: Conf::default(),
Io(io::Error), errors: vec![error.to_string()],
/// Not valid toml or doesn't fit the expected config format
Toml(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(err) => err.fmt(f),
Self::Toml(err) => err.fmt(f),
} }
} }
} }
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
/// Vec of errors that might be collected during config toml parsing
static ERRORS: SyncLazy<Mutex<Vec<Error>>> = SyncLazy::new(|| Mutex::new(Vec::new()));
macro_rules! define_Conf { macro_rules! define_Conf {
($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => { ($(
mod helpers { #[$doc:meta]
use serde::Deserialize; $(#[conf_deprecated($dep:literal)])?
/// Type used to store lint configuration. ($name:ident: $ty:ty = $default:expr),
#[derive(Deserialize)] )*) => {
#[serde(rename_all = "kebab-case", deny_unknown_fields)] /// Clippy lint configuration
pub struct Conf { pub struct Conf {
$( $(#[$doc] pub $name: $ty,)*
#[$doc] }
#[serde(default = $config_str)]
#[serde(with = $config_str)] mod defaults {
pub $config: $Ty, $(pub fn $name() -> $ty { $default })*
)+ }
#[allow(dead_code)]
#[serde(default)] impl Default for Conf {
third_party: Option<::toml::Value>, fn default() -> Self {
Self { $($name: defaults::$name(),)* }
}
}
impl<'de> Deserialize<'de> for TryConf {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
deserializer.deserialize_map(ConfVisitor)
}
}
#[derive(Deserialize)]
#[serde(field_identifier, rename_all = "kebab-case")]
#[allow(non_camel_case_types)]
enum Field { $($name,)* third_party, }
struct ConfVisitor;
impl<'de> Visitor<'de> for ConfVisitor {
type Value = TryConf;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("Conf")
} }
$( fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error> where V: MapAccess<'de> {
mod $config { let mut errors = Vec::new();
use serde::Deserialize; $(let mut $name = None;)*
pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> { // could get `Field` here directly, but get `str` first for diagnostics
use super::super::{ERRORS, Error}; while let Some(name) = map.next_key::<&str>()? {
match Field::deserialize(name.into_deserializer())? {
Ok( $(Field::$name => {
<$Ty>::deserialize(deserializer).unwrap_or_else(|e| { $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)?
ERRORS match map.next_value() {
.lock() Err(e) => errors.push(e.to_string()),
.expect("no threading here") Ok(value) => match $name {
.push(Error::Toml(e.to_string())); Some(_) => errors.push(format!("duplicate field `{}`", name)),
super::$config() None => $name = Some(value),
}) }
) }
})*
// white-listed; ignore
Field::third_party => drop(map.next_value::<IgnoredAny>())
} }
} }
let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* };
#[must_use] Ok(TryConf { conf, errors })
fn $config() -> $Ty { }
let x = $default;
x
}
)+
} }
}; };
} }
pub use self::helpers::Conf; // N.B., this macro is parsed by util/lintlib.py
define_Conf! { define_Conf! {
/// Lint: CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR. The minimum rust version that the project supports /// Lint: CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE. The minimum rust version that the project supports
(msrv, "msrv": Option<String>, None), (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 /// 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()), (blacklisted_names: Vec<String> = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()),
/// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have
(cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25), (cognitive_complexity_threshold: u64 = 25),
/// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead.
(cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option<u64>, None), #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")]
(cyclomatic_complexity_threshold: Option<u64> = None),
/// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks
(doc_valid_idents, "doc_valid_idents": Vec<String>, [ (doc_valid_idents: Vec<String> = [
"KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB",
"DirectX", "DirectX",
"ECMAScript", "ECMAScript",
@ -136,54 +125,47 @@ define_Conf! {
"CamelCase", "CamelCase",
].iter().map(ToString::to_string).collect()), ].iter().map(ToString::to_string).collect()),
/// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have
(too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7), (too_many_arguments_threshold: u64 = 7),
/// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have
(type_complexity_threshold, "type_complexity_threshold": u64, 250), (type_complexity_threshold: u64 = 250),
/// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have
(single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4), (single_char_binding_names_threshold: u64 = 4),
/// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap
(too_large_for_stack, "too_large_for_stack": u64, 200), (too_large_for_stack: u64 = 200),
/// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger
(enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3), (enum_variant_name_threshold: u64 = 3),
/// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion
(enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200), (enum_variant_size_threshold: u64 = 200),
/// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros'
(verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1), (verbose_bit_mask_threshold: u64 = 1),
/// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals
(literal_representation_threshold, "literal_representation_threshold": u64, 16384), (literal_representation_threshold: u64 = 16384),
/// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference.
(trivial_copy_size_limit, "trivial_copy_size_limit": Option<u64>, None), (trivial_copy_size_limit: Option<u64> = None),
/// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value. /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value.
(pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256), (pass_by_value_size_limit: u64 = 256),
/// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have
(too_many_lines_threshold, "too_many_lines_threshold": u64, 100), (too_many_lines_threshold: u64 = 100),
/// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack
(array_size_threshold, "array_size_threshold": u64, 512_000), (array_size_threshold: u64 = 512_000),
/// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed
(vec_box_size_threshold, "vec_box_size_threshold": u64, 4096), (vec_box_size_threshold: u64 = 4096),
/// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted
(max_trait_bounds, "max_trait_bounds": u64, 3), (max_trait_bounds: u64 = 3),
/// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have
(max_struct_bools, "max_struct_bools": u64, 3), (max_struct_bools: u64 = 3),
/// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have
(max_fn_params_bools, "max_fn_params_bools": u64, 3), (max_fn_params_bools: u64 = 3),
/// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests). /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests).
(warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false), (warn_on_all_wildcard_imports: bool = false),
/// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths. /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths.
(disallowed_methods, "disallowed_methods": Vec<String>, Vec::<String>::new()), (disallowed_methods: Vec<String> = Vec::new()),
/// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators. /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators.
(unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true), (unreadable_literal_lint_fractions: bool = true),
/// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other
(upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive": bool, false), (upper_case_acronyms_aggressive: bool = false),
/// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest. /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest.
(cargo_ignore_publish, "cargo_ignore_publish": bool, false), (cargo_ignore_publish: bool = false),
}
impl Default for Conf {
#[must_use]
fn default() -> Self {
toml::from_str("").expect("we never error on empty config files")
}
} }
/// Search for the configuration file. /// Search for the configuration file.
@ -217,43 +199,13 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
} }
} }
/// Produces a `Conf` filled with the default values and forwards the errors
///
/// Used internally for convenience
fn default(errors: Vec<Error>) -> (Conf, Vec<Error>) {
(Conf::default(), errors)
}
/// Read the `toml` configuration file. /// Read the `toml` configuration file.
/// ///
/// In case of error, the function tries to continue as much as possible. /// In case of error, the function tries to continue as much as possible.
pub fn read(path: &Path) -> (Conf, Vec<Error>) { pub fn read(path: &Path) -> TryConf {
let content = match fs::read_to_string(path) { let content = match fs::read_to_string(path) {
Err(e) => return TryConf::from_error(e),
Ok(content) => content, Ok(content) => content,
Err(err) => return default(vec![err.into()]),
}; };
toml::from_str(&content).unwrap_or_else(TryConf::from_error)
assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty());
match toml::from_str(&content) {
Ok(toml) => {
let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
let toml_ref: &Conf = &toml;
let cyc_field: Option<u64> = toml_ref.cyclomatic_complexity_threshold;
if cyc_field.is_some() {
let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string();
errors.push(Error::Toml(cyc_err));
}
(toml, errors)
},
Err(e) => {
let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0);
errors.push(Error::Toml(e.to_string()));
default(errors)
},
}
} }

View file

@ -32,6 +32,9 @@ use rustc_typeck::hir_ty_to_ty;
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
#[cfg(feature = "metadata-collector-lint")]
pub mod metadata_collector;
declare_clippy_lint! { declare_clippy_lint! {
/// **What it does:** Checks for various things we like to keep tidy in clippy. /// **What it does:** Checks for various things we like to keep tidy in clippy.
/// ///

View file

@ -0,0 +1,632 @@
//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json
//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html)
//!
//! This module and therefor the entire lint is guarded by a feature flag called
//! `metadata-collector-lint`
//!
//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches
//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such
//! a simple mistake)
// # NITs
// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{
self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath,
};
use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId};
use rustc_middle::hir::map::Map;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, Loc, Span, Symbol};
use serde::{ser::SerializeStruct, Serialize, Serializer};
use std::collections::BinaryHeap;
use std::fs::{self, OpenOptions};
use std::io::prelude::*;
use std::path::Path;
use crate::utils::internal_lints::is_lint_ref_type;
use clippy_utils::{
diagnostics::span_lint, last_path_segment, match_function_call, match_path, paths, ty::match_type,
ty::walk_ptrs_ty_depth,
};
/// This is the output file of the lint collector.
const OUTPUT_FILE: &str = "../util/gh-pages/metadata_collection.json";
/// These lints are excluded from the export.
const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "internal_metadata_collector"];
/// These groups will be ignored by the lint group matcher. This is useful for collections like
/// `clippy::all`
const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"];
/// Lints within this group will be excluded from the collection
const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"];
const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [
&["clippy_utils", "diagnostics", "span_lint"],
&["clippy_utils", "diagnostics", "span_lint_and_help"],
&["clippy_utils", "diagnostics", "span_lint_and_note"],
&["clippy_utils", "diagnostics", "span_lint_hir"],
&["clippy_utils", "diagnostics", "span_lint_and_sugg"],
&["clippy_utils", "diagnostics", "span_lint_and_then"],
&["clippy_utils", "diagnostics", "span_lint_hir_and_then"],
];
const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [
("span_suggestion", false),
("span_suggestion_short", false),
("span_suggestion_verbose", false),
("span_suggestion_hidden", false),
("tool_only_span_suggestion", false),
("multipart_suggestion", true),
("multipart_suggestions", true),
("tool_only_multipart_suggestion", true),
("span_suggestions", true),
];
const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [
&["clippy_utils", "diagnostics", "multispan_sugg"],
&["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"],
];
/// The index of the applicability name of `paths::APPLICABILITY_VALUES`
const APPLICABILITY_NAME_INDEX: usize = 2;
declare_clippy_lint! {
/// **What it does:** Collects metadata about clippy lints for the website.
///
/// This lint will be used to report problems of syntax parsing. You should hopefully never
/// see this but never say never I guess ^^
///
/// **Why is this bad?** This is not a bad thing but definitely a hacky way to do it. See
/// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion
/// about the implementation.
///
/// **Known problems:** Hopefully none. It would be pretty uncool to have a problem here :)
///
/// **Example output:**
/// ```json,ignore
/// {
/// "id": "internal_metadata_collector",
/// "id_span": {
/// "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs",
/// "line": 1
/// },
/// "group": "clippy::internal",
/// "docs": " **What it does:** Collects metadata about clippy lints for the website. [...] "
/// }
/// ```
pub INTERNAL_METADATA_COLLECTOR,
internal_warn,
"A busy bee collection metadata about lints"
}
impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]);
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, Default)]
pub struct MetadataCollector {
/// All collected lints
///
/// We use a Heap here to have the lints added in alphabetic order in the export
lints: BinaryHeap<LintMetadata>,
applicability_info: FxHashMap<String, ApplicabilityInfo>,
}
impl Drop for MetadataCollector {
/// You might ask: How hacky is this?
/// My answer: YES
fn drop(&mut self) {
// The metadata collector gets dropped twice, this makes sure that we only write
// when the list is full
if self.lints.is_empty() {
return;
}
let mut applicability_info = std::mem::take(&mut self.applicability_info);
// Mapping the final data
let mut lints = std::mem::take(&mut self.lints).into_sorted_vec();
lints
.iter_mut()
.for_each(|x| x.applicability = applicability_info.remove(&x.id));
// Outputting
if Path::new(OUTPUT_FILE).exists() {
fs::remove_file(OUTPUT_FILE).unwrap();
}
let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap();
writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap();
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
struct LintMetadata {
id: String,
id_span: SerializableSpan,
group: String,
docs: String,
/// This field is only used in the output and will only be
/// mapped shortly before the actual output.
applicability: Option<ApplicabilityInfo>,
}
impl LintMetadata {
fn new(id: String, id_span: SerializableSpan, group: String, docs: String) -> Self {
Self {
id,
id_span,
group,
docs,
applicability: None,
}
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)]
struct SerializableSpan {
path: String,
line: usize,
}
impl std::fmt::Display for SerializableSpan {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line)
}
}
impl SerializableSpan {
fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self {
Self::from_span(cx, item.ident.span)
}
fn from_span(cx: &LateContext<'_>, span: Span) -> Self {
let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo());
Self {
path: format!("{}", loc.file.name),
line: loc.line,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
struct ApplicabilityInfo {
/// Indicates if any of the lint emissions uses multiple spans. This is related to
/// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can
/// currently not be applied automatically.
is_multi_part_suggestion: bool,
applicability: Option<usize>,
}
impl Serialize for ApplicabilityInfo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let index = self.applicability.unwrap_or_default();
let mut s = serializer.serialize_struct("ApplicabilityInfo", 2)?;
s.serialize_field("is_multi_part_suggestion", &self.is_multi_part_suggestion)?;
s.serialize_field(
"applicability",
&paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX],
)?;
s.end()
}
}
impl<'hir> LateLintPass<'hir> for MetadataCollector {
/// Collecting lint declarations like:
/// ```rust, ignore
/// declare_clippy_lint! {
/// /// **What it does:** Something IDK.
/// pub SOME_LINT,
/// internal,
/// "Who am I?"
/// }
/// ```
fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) {
if_chain! {
// item validation
if let ItemKind::Static(ref ty, Mutability::Not, _) = item.kind;
if is_lint_ref_type(cx, ty);
// blacklist check
let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase();
if !BLACK_LISTED_LINTS.contains(&lint_name.as_str());
// metadata extraction
if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item);
if let Some(docs) = extract_attr_docs_or_lint(cx, item);
then {
self.lints.push(LintMetadata::new(
lint_name,
SerializableSpan::from_item(cx, item),
group,
docs,
));
}
}
}
/// Collecting constant applicability from the actual lint emissions
///
/// Example:
/// ```rust, ignore
/// span_lint_and_sugg(
/// cx,
/// SOME_LINT,
/// item.span,
/// "Le lint message",
/// "Here comes help:",
/// "#![allow(clippy::all)]",
/// Applicability::MachineApplicable, // <-- Extracts this constant value
/// );
/// ```
fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) {
if let Some(args) = match_lint_emission(cx, expr) {
let mut emission_info = extract_emission_info(cx, args);
if emission_info.is_empty() {
// See:
// - src/misc.rs:734:9
// - src/methods/mod.rs:3545:13
// - src/methods/mod.rs:3496:13
// We are basically unable to resolve the lint name it self.
return;
}
for (lint_name, applicability, is_multi_part) in emission_info.drain(..) {
let app_info = self.applicability_info.entry(lint_name).or_default();
app_info.applicability = applicability;
app_info.is_multi_part_suggestion = is_multi_part;
}
}
}
}
// ==================================================================
// Lint definition extraction
// ==================================================================
fn sym_to_string(sym: Symbol) -> String {
sym.as_str().to_string()
}
fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
extract_attr_docs(cx, item).or_else(|| {
lint_collection_error_item(cx, item, "could not collect the lint documentation");
None
})
}
/// This function collects all documentation that has been added to an item using
/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks
///
/// ```ignore
/// #[doc = r"Hello world!"]
/// #[doc = r"=^.^="]
/// struct SomeItem {}
/// ```
///
/// Would result in `Hello world!\n=^.^=\n`
fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option<String> {
cx.tcx
.hir()
.attrs(item.hir_id())
.iter()
.filter_map(|ref x| x.doc_str().map(|sym| sym.as_str().to_string()))
.reduce(|mut acc, sym| {
acc.push_str(&sym);
acc.push('\n');
acc
})
}
fn get_lint_group_or_lint(cx: &LateContext<'_>, lint_name: &str, item: &'hir Item<'_>) -> Option<String> {
let result = cx.lint_store.check_lint_name(lint_name, Some(sym::clippy));
if let CheckLintNameResult::Tool(Ok(lint_lst)) = result {
get_lint_group(cx, lint_lst[0])
.or_else(|| {
lint_collection_error_item(cx, item, "Unable to determine lint group");
None
})
.filter(|group| !EXCLUDED_LINT_GROUPS.contains(&group.as_str()))
} else {
lint_collection_error_item(cx, item, "Unable to find lint in lint_store");
None
}
}
fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option<String> {
for (group_name, lints, _) in &cx.lint_store.get_lint_groups() {
if IGNORED_LINT_GROUPS.contains(group_name) {
continue;
}
if lints.iter().any(|x| *x == lint_id) {
return Some((*group_name).to_string());
}
}
None
}
// ==================================================================
// Lint emission
// ==================================================================
fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) {
span_lint(
cx,
INTERNAL_METADATA_COLLECTOR,
item.ident.span,
&format!("metadata collection error for `{}`: {}", item.ident.name, message),
);
}
// ==================================================================
// Applicability
// ==================================================================
/// This function checks if a given expression is equal to a simple lint emission function call.
/// It will return the function arguments if the emission matched any function.
fn match_lint_emission<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) -> Option<&'hir [hir::Expr<'hir>]> {
LINT_EMISSION_FUNCTIONS
.iter()
.find_map(|emission_fn| match_function_call(cx, expr, emission_fn))
}
fn take_higher_applicability(a: Option<usize>, b: Option<usize>) -> Option<usize> {
a.map_or(b, |a| a.max(b.unwrap_or_default()).into())
}
fn extract_emission_info<'hir>(
cx: &LateContext<'hir>,
args: &'hir [hir::Expr<'hir>],
) -> Vec<(String, Option<usize>, bool)> {
let mut lints = Vec::new();
let mut applicability = None;
let mut multi_part = false;
for arg in args {
let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(&arg));
if match_type(cx, arg_ty, &paths::LINT) {
// If we found the lint arg, extract the lint name
let mut resolved_lints = resolve_lints(cx, arg);
lints.append(&mut resolved_lints);
} else if match_type(cx, arg_ty, &paths::APPLICABILITY) {
applicability = resolve_applicability(cx, arg);
} else if arg_ty.is_closure() {
multi_part |= check_is_multi_part(cx, arg);
// TODO xFrednet 2021-03-01: don't use or_else but rather a comparison
applicability = applicability.or_else(|| resolve_applicability(cx, arg));
}
}
lints
.drain(..)
.map(|lint_name| (lint_name, applicability, multi_part))
.collect()
}
/// Resolves the possible lints that this expression could reference
fn resolve_lints(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Vec<String> {
let mut resolver = LintResolver::new(cx);
resolver.visit_expr(expr);
resolver.lints
}
/// This function tries to resolve the linked applicability to the given expression.
fn resolve_applicability(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<usize> {
let mut resolver = ApplicabilityResolver::new(cx);
resolver.visit_expr(expr);
resolver.complete()
}
fn check_is_multi_part(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool {
if let ExprKind::Closure(_, _, body_id, _, _) = closure_expr.kind {
let mut scanner = IsMultiSpanScanner::new(cx);
intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body_id));
return scanner.is_multi_part();
} else if let Some(local) = get_parent_local(cx, closure_expr) {
if let Some(local_init) = local.init {
return check_is_multi_part(cx, local_init);
}
}
false
}
struct LintResolver<'a, 'hir> {
cx: &'a LateContext<'hir>,
lints: Vec<String>,
}
impl<'a, 'hir> LintResolver<'a, 'hir> {
fn new(cx: &'a LateContext<'hir>) -> Self {
Self {
cx,
lints: Vec::<String>::default(),
}
}
}
impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
}
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
if_chain! {
if let ExprKind::Path(qpath) = &expr.kind;
if let QPath::Resolved(_, path) = qpath;
let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&expr));
if match_type(self.cx, expr_ty, &paths::LINT);
then {
if let hir::def::Res::Def(DefKind::Static, _) = path.res {
let lint_name = last_path_segment(qpath).ident.name;
self.lints.push(sym_to_string(lint_name).to_ascii_lowercase());
} else if let Some(local) = get_parent_local(self.cx, expr) {
if let Some(local_init) = local.init {
intravisit::walk_expr(self, local_init);
}
}
}
}
intravisit::walk_expr(self, expr);
}
}
/// This visitor finds the highest applicability value in the visited expressions
struct ApplicabilityResolver<'a, 'hir> {
cx: &'a LateContext<'hir>,
/// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES`
applicability_index: Option<usize>,
}
impl<'a, 'hir> ApplicabilityResolver<'a, 'hir> {
fn new(cx: &'a LateContext<'hir>) -> Self {
Self {
cx,
applicability_index: None,
}
}
fn add_new_index(&mut self, new_index: usize) {
self.applicability_index = take_higher_applicability(self.applicability_index, Some(new_index));
}
fn complete(self) -> Option<usize> {
self.applicability_index
}
}
impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
}
fn visit_path(&mut self, path: &'hir hir::Path<'hir>, _id: hir::HirId) {
for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() {
if match_path(path, enum_value) {
self.add_new_index(index);
return;
}
}
}
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&expr));
if_chain! {
if match_type(self.cx, expr_ty, &paths::APPLICABILITY);
if let Some(local) = get_parent_local(self.cx, expr);
if let Some(local_init) = local.init;
then {
intravisit::walk_expr(self, local_init);
}
};
// TODO xFrednet 2021-03-01: support function arguments?
intravisit::walk_expr(self, expr);
}
}
/// This returns the parent local node if the expression is a reference one
fn get_parent_local(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Local<'hir>> {
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind {
if let hir::def::Res::Local(local_hir) = path.res {
return get_parent_local_hir_id(cx, local_hir);
}
}
None
}
fn get_parent_local_hir_id(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> {
let map = cx.tcx.hir();
match map.find(map.get_parent_node(hir_id)) {
Some(hir::Node::Local(local)) => Some(local),
Some(hir::Node::Pat(pattern)) => get_parent_local_hir_id(cx, pattern.hir_id),
_ => None,
}
}
/// This visitor finds the highest applicability value in the visited expressions
struct IsMultiSpanScanner<'a, 'hir> {
cx: &'a LateContext<'hir>,
suggestion_count: usize,
}
impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> {
fn new(cx: &'a LateContext<'hir>) -> Self {
Self {
cx,
suggestion_count: 0,
}
}
/// Add a new single expression suggestion to the counter
fn add_single_span_suggestion(&mut self) {
self.suggestion_count += 1;
}
/// Signals that a suggestion with possible multiple spans was found
fn add_multi_part_suggestion(&mut self) {
self.suggestion_count += 2;
}
/// Checks if the suggestions include multiple spanns
fn is_multi_part(&self) -> bool {
self.suggestion_count > 1
}
}
impl<'a, 'hir> intravisit::Visitor<'hir> for IsMultiSpanScanner<'a, 'hir> {
type Map = Map<'hir>;
fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
intravisit::NestedVisitorMap::All(self.cx.tcx.hir())
}
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
// Early return if the lint is already multi span
if self.is_multi_part() {
return;
}
match &expr.kind {
ExprKind::Call(fn_expr, _args) => {
let found_function = SUGGESTION_FUNCTIONS
.iter()
.any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some());
if found_function {
// These functions are all multi part suggestions
self.add_single_span_suggestion()
}
},
ExprKind::MethodCall(path, _path_span, arg, _arg_span) => {
let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&arg[0]));
if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) {
let called_method = path.ident.name.as_str().to_string();
for (method_name, is_multi_part) in &SUGGESTION_DIAGNOSTIC_BUILDER_METHODS {
if *method_name == called_method {
if *is_multi_part {
self.add_multi_part_suggestion();
} else {
self.add_single_span_suggestion();
}
break;
}
}
}
},
_ => {},
}
intravisit::walk_expr(self, expr);
}
}

View file

@ -1,5 +1,5 @@
pub mod author; pub mod author;
pub mod conf; pub mod conf;
pub mod inspector; pub mod inspector;
#[cfg(feature = "internal-lints")] #[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))]
pub mod internal_lints; pub mod internal_lints;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "clippy_utils" name = "clippy_utils"
version = "0.1.53" version = "0.1.54"
authors = ["The Rust Clippy Developers"] authors = ["The Rust Clippy Developers"]
edition = "2018" edition = "2018"
publish = false publish = false
@ -15,6 +15,7 @@ rustc-semver="1.1.0"
[features] [features]
internal-lints = [] internal-lints = []
metadata-collector-lint = []
[package.metadata.rust-analyzer] [package.metadata.rust-analyzer]
# This crate uses #[feature(rustc_private)] # This crate uses #[feature(rustc_private)]

View file

@ -1,4 +1,12 @@
//! Clippy wrappers around rustc's diagnostic functions. //! Clippy wrappers around rustc's diagnostic functions.
//!
//! These functions are used by the `INTERNAL_METADATA_COLLECTOR` lint to collect the corresponding
//! lint applicability. Please make sure that you update the `LINT_EMISSION_FUNCTIONS` variable in
//! `clippy_lints::utils::internal_lints::metadata_collector` when a new function is added
//! or renamed.
//!
//! Thank you!
//! ~The `INTERNAL_METADATA_COLLECTOR` lint
use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir::HirId; use rustc_hir::HirId;

View file

@ -713,7 +713,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_expr(e); self.hash_expr(e);
for arm in arms { for arm in arms {
// TODO: arm.pat? self.hash_pat(arm.pat);
if let Some(ref e) = arm.guard { if let Some(ref e) = arm.guard {
self.hash_guard(e); self.hash_guard(e);
} }
@ -791,6 +791,72 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
// self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s); // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s);
} }
pub fn hash_pat(&mut self, pat: &Pat<'_>) {
std::mem::discriminant(&pat.kind).hash(&mut self.s);
match pat.kind {
PatKind::Binding(ann, _, _, pat) => {
ann.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
if let Some(pat) = pat {
self.hash_pat(pat);
}
},
PatKind::Box(pat) => self.hash_pat(pat),
PatKind::Lit(expr) => self.hash_expr(expr),
PatKind::Or(pats) => {
for pat in pats {
self.hash_pat(pat);
}
},
PatKind::Path(ref qpath) => self.hash_qpath(qpath),
PatKind::Range(s, e, i) => {
if let Some(s) = s {
self.hash_expr(s);
}
if let Some(e) = e {
self.hash_expr(e);
}
i.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s);
},
PatKind::Ref(pat, m) => {
self.hash_pat(pat);
m.hash(&mut self.s);
},
PatKind::Slice(l, m, r) => {
for pat in l {
self.hash_pat(pat);
}
if let Some(pat) = m {
self.hash_pat(pat);
}
for pat in r {
self.hash_pat(pat);
}
},
PatKind::Struct(ref qpath, fields, e) => {
self.hash_qpath(qpath);
for f in fields {
self.hash_name(f.ident.name);
self.hash_pat(f.pat);
}
e.hash(&mut self.s)
},
PatKind::Tuple(pats, e) => {
for pat in pats {
self.hash_pat(pat);
}
e.hash(&mut self.s);
},
PatKind::TupleStruct(ref qpath, pats, e) => {
self.hash_qpath(qpath);
for pat in pats {
self.hash_pat(pat);
}
e.hash(&mut self.s);
},
PatKind::Wild => {},
}
}
pub fn hash_path(&mut self, path: &Path<'_>) { pub fn hash_path(&mut self, path: &Path<'_>) {
match path.res { match path.res {
// constant hash since equality is dependant on inter-expression context // constant hash since equality is dependant on inter-expression context
@ -808,6 +874,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
match &b.kind { match &b.kind {
StmtKind::Local(local) => { StmtKind::Local(local) => {
self.hash_pat(local.pat);
if let Some(ref init) = local.init { if let Some(ref init) = local.init {
self.hash_expr(init); self.hash_expr(init);
} }
@ -827,7 +894,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
} }
} }
pub fn hash_lifetime(&mut self, lifetime: &Lifetime) { pub fn hash_lifetime(&mut self, lifetime: Lifetime) {
std::mem::discriminant(&lifetime.name).hash(&mut self.s); std::mem::discriminant(&lifetime.name).hash(&mut self.s);
if let LifetimeName::Param(ref name) = lifetime.name { if let LifetimeName::Param(ref name) = lifetime.name {
std::mem::discriminant(name).hash(&mut self.s); std::mem::discriminant(name).hash(&mut self.s);
@ -844,12 +911,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
} }
pub fn hash_ty(&mut self, ty: &Ty<'_>) { pub fn hash_ty(&mut self, ty: &Ty<'_>) {
self.hash_tykind(&ty.kind); std::mem::discriminant(&ty.kind).hash(&mut self.s);
} match ty.kind {
pub fn hash_tykind(&mut self, ty: &TyKind<'_>) {
std::mem::discriminant(ty).hash(&mut self.s);
match ty {
TyKind::Slice(ty) => { TyKind::Slice(ty) => {
self.hash_ty(ty); self.hash_ty(ty);
}, },
@ -857,11 +920,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
self.hash_ty(ty); self.hash_ty(ty);
self.hash_body(anon_const.body); self.hash_body(anon_const.body);
}, },
TyKind::Ptr(mut_ty) => { TyKind::Ptr(ref mut_ty) => {
self.hash_ty(&mut_ty.ty); self.hash_ty(&mut_ty.ty);
mut_ty.mutbl.hash(&mut self.s); mut_ty.mutbl.hash(&mut self.s);
}, },
TyKind::Rptr(lifetime, mut_ty) => { TyKind::Rptr(lifetime, ref mut_ty) => {
self.hash_lifetime(lifetime); self.hash_lifetime(lifetime);
self.hash_ty(&mut_ty.ty); self.hash_ty(&mut_ty.ty);
mut_ty.mutbl.hash(&mut self.s); mut_ty.mutbl.hash(&mut self.s);
@ -883,11 +946,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
bfn.decl.c_variadic.hash(&mut self.s); bfn.decl.c_variadic.hash(&mut self.s);
}, },
TyKind::Tup(ty_list) => { TyKind::Tup(ty_list) => {
for ty in *ty_list { for ty in ty_list {
self.hash_ty(ty); self.hash_ty(ty);
} }
}, },
TyKind::Path(qpath) => match qpath { TyKind::Path(ref qpath) => match qpath {
QPath::Resolved(ref maybe_ty, ref path) => { QPath::Resolved(ref maybe_ty, ref path) => {
if let Some(ref ty) = maybe_ty { if let Some(ref ty) = maybe_ty {
self.hash_ty(ty); self.hash_ty(ty);
@ -927,9 +990,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) { fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) {
for arg in arg_list { for arg in arg_list {
match arg { match *arg {
GenericArg::Lifetime(ref l) => self.hash_lifetime(l), GenericArg::Lifetime(l) => self.hash_lifetime(l),
GenericArg::Type(ref ty) => self.hash_ty(&ty), GenericArg::Type(ref ty) => self.hash_ty(ty),
GenericArg::Const(ref ca) => self.hash_body(ca.value.body), GenericArg::Const(ref ca) => self.hash_body(ca.value.body),
} }
} }

View file

@ -61,12 +61,12 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LOCAL_CRATE}; use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::LangItem::{ResultErr, ResultOk}; use rustc_hir::LangItem::{ResultErr, ResultOk};
use rustc_hir::{ use rustc_hir::{
def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl,
ImplItem, ImplItemKind, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path,
QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, PathSegment, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind,
}; };
use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_lint::{LateContext, Level, Lint, LintContext};
use rustc_middle::hir::exports::Export; use rustc_middle::hir::exports::Export;
@ -821,7 +821,13 @@ pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option<Node<'_>> {
/// Gets the parent expression, if any - this is useful to constrain a lint. /// Gets the parent expression, if any - this is useful to constrain a lint.
pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match get_parent_node(cx.tcx, e.hir_id) { get_parent_expr_for_hir(cx, e.hir_id)
}
/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for
/// constraint lints
pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> {
match get_parent_node(cx.tcx, hir_id) {
Some(Node::Expr(parent)) => Some(parent), Some(Node::Expr(parent)) => Some(parent),
_ => None, _ => None,
} }
@ -1301,6 +1307,40 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>,
(conds, blocks) (conds, blocks)
} }
/// Checks if the given function kind is an async function.
pub fn is_async_fn(kind: FnKind<'_>) -> bool {
matches!(kind, FnKind::ItemFn(_, _, header, _) if header.asyncness == IsAsync::Async)
}
/// Peels away all the compiler generated code surrounding the body of an async function,
pub fn get_async_fn_body(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> {
if let ExprKind::Call(
_,
&[Expr {
kind: ExprKind::Closure(_, _, body, _, _),
..
}],
) = body.value.kind
{
if let ExprKind::Block(
Block {
stmts: [],
expr:
Some(Expr {
kind: ExprKind::DropTemps(expr),
..
}),
..
},
_,
) = tcx.hir().body(body).value.kind
{
return Some(expr);
}
};
None
}
// Finds the `#[must_use]` attribute, if any // Finds the `#[must_use]` attribute, if any
pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> { pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> {
attrs.iter().find(|a| a.has_name(sym::must_use)) attrs.iter().find(|a| a.has_name(sym::must_use))

View file

@ -5,6 +5,17 @@
//! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information. //! See <https://github.com/rust-lang/rust-clippy/issues/5393> for more information.
pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"]; pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"];
#[cfg(feature = "metadata-collector-lint")]
pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"];
#[cfg(feature = "metadata-collector-lint")]
pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [
["rustc_lint_defs", "Applicability", "Unspecified"],
["rustc_lint_defs", "Applicability", "HasPlaceholders"],
["rustc_lint_defs", "Applicability", "MaybeIncorrect"],
["rustc_lint_defs", "Applicability", "MachineApplicable"],
];
#[cfg(feature = "metadata-collector-lint")]
pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"];
pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"]; pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"];
pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"]; pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"];
pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"]; pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"];
@ -72,7 +83,7 @@ pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"];
#[cfg(feature = "internal-lints")] #[cfg(feature = "internal-lints")]
pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"]; pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"];
pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"]; pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"];
#[cfg(feature = "internal-lints")] #[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))]
pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"]; pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"];
pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"]; pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"];
pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"]; pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"];

View file

@ -280,17 +280,17 @@ pub fn snippet_with_context(
default: &'a str, default: &'a str,
applicability: &mut Applicability, applicability: &mut Applicability,
) -> (Cow<'a, str>, bool) { ) -> (Cow<'a, str>, bool) {
let outer_span = hygiene::walk_chain(span, outer); let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else(
let (span, is_macro_call) = if outer_span.ctxt() == outer { || {
(outer_span, span.ctxt() != outer) // The span is from a macro argument, and the outer context is the macro using the argument
} else { if *applicability != Applicability::Unspecified {
// The span is from a macro argument, and the outer context is the macro using the argument *applicability = Applicability::MaybeIncorrect;
if *applicability != Applicability::Unspecified { }
*applicability = Applicability::MaybeIncorrect; // TODO: get the argument span.
} (span, false)
// TODO: get the argument span. },
(span, false) |outer_span| (outer_span, span.ctxt() != outer),
}; );
( (
snippet_with_applicability(cx, span, default, applicability), snippet_with_applicability(cx, span, default, applicability),
@ -298,6 +298,37 @@ pub fn snippet_with_context(
) )
} }
/// Walks the span up to the target context, thereby returning the macro call site if the span is
/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the
/// case of the span being in a macro expansion, but the target context is from expanding a macro
/// argument.
///
/// Given the following
///
/// ```rust,ignore
/// macro_rules! m { ($e:expr) => { f($e) }; }
/// g(m!(0))
/// ```
///
/// If called with a span of the call to `f` and a context of the call to `g` this will return a
/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span
/// containing `0` as the context is the same as the outer context.
///
/// This will traverse through multiple macro calls. Given the following:
///
/// ```rust,ignore
/// macro_rules! m { ($e:expr) => { n!($e, 0) }; }
/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; }
/// g(m!(0))
/// ```
///
/// If called with a span of the call to `f` and a context of the call to `g` this will return a
/// span containing `m!(0)`.
pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option<Span> {
let outer_span = hygiene::walk_chain(span, outer);
(outer_span.ctxt() == outer).then(|| outer_span)
}
/// Removes block comments from the given `Vec` of lines. /// Removes block comments from the given `Vec` of lines.
/// ///
/// # Examples /// # Examples

View file

@ -1,7 +1,7 @@
use crate::path_to_local_id; use crate::path_to_local_id;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor};
use rustc_hir::{Arm, Body, Expr, HirId, Stmt}; use rustc_hir::{Arm, Block, Body, Destination, Expr, ExprKind, HirId, Stmt};
use rustc_lint::LateContext; use rustc_lint::LateContext;
use rustc_middle::hir::map::Map; use rustc_middle::hir::map::Map;
@ -188,3 +188,54 @@ impl<'v> Visitor<'v> for LocalUsedVisitor<'v> {
NestedVisitorMap::OnlyBodies(self.hir) NestedVisitorMap::OnlyBodies(self.hir)
} }
} }
pub trait Visitable<'tcx> {
fn visit<V: Visitor<'tcx>>(self, v: &mut V);
}
impl Visitable<'tcx> for &'tcx Expr<'tcx> {
fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
v.visit_expr(self)
}
}
impl Visitable<'tcx> for &'tcx Block<'tcx> {
fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
v.visit_block(self)
}
}
impl<'tcx> Visitable<'tcx> for &'tcx Stmt<'tcx> {
fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
v.visit_stmt(self)
}
}
impl<'tcx> Visitable<'tcx> for &'tcx Body<'tcx> {
fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
v.visit_body(self)
}
}
impl<'tcx> Visitable<'tcx> for &'tcx Arm<'tcx> {
fn visit<V: Visitor<'tcx>>(self, v: &mut V) {
v.visit_arm(self)
}
}
pub fn visit_break_exprs<'tcx>(
node: impl Visitable<'tcx>,
f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>),
) {
struct V<F>(F);
impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V<F> {
type Map = ErasedMap<'tcx>;
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
NestedVisitorMap::None
}
fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
if let ExprKind::Break(dest, sub_expr) = e.kind {
self.0(e, dest, sub_expr)
}
walk_expr(self, e);
}
}
node.visit(&mut V(f));
}

View file

@ -454,7 +454,7 @@ in `clippy_lints/src/utils/conf.rs`:
```rust ```rust
define_Conf! { define_Conf! {
/// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports /// Lint: LIST, OF, LINTS, <THE_NEWLY_ADDED_LINT>. The minimum rust version that the project supports
(msrv, "msrv": Option<String>, None), (msrv: Option<String> = None),
... ...
} }
``` ```
@ -562,7 +562,7 @@ in the following steps:
like this: like this:
```rust ```rust
/// Lint: LINT_NAME. <The configuration field doc comment> /// Lint: LINT_NAME. <The configuration field doc comment>
(configuration_ident, "configuration_value": Type, DefaultValue), (configuration_ident: Type = DefaultValue),
``` ```
The configuration value and identifier should usually be the same. The doc comment will be The configuration value and identifier should usually be the same. The doc comment will be
automatically added to the lint documentation. automatically added to the lint documentation.

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "nightly-2021-04-22" channel = "nightly-2021-05-06"
components = ["llvm-tools-preview", "rustc-dev", "rust-src"] components = ["llvm-tools-preview", "rustc-dev", "rust-src"]

View file

@ -106,7 +106,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks {
(previous)(sess, lint_store); (previous)(sess, lint_store);
} }
let conf = clippy_lints::read_conf(&[], sess); let conf = clippy_lints::read_conf(sess);
clippy_lints::register_plugins(lint_store, sess, &conf); clippy_lints::register_plugins(lint_store, sess, &conf);
clippy_lints::register_pre_expansion_lints(lint_store); clippy_lints::register_pre_expansion_lints(lint_store);
clippy_lints::register_renamed(lint_store); clippy_lints::register_renamed(lint_store);

View file

@ -1,3 +1,8 @@
//! This test is a part of quality control and makes clippy eat what it produces. Awesome lints and
//! long error messages
//!
//! See [Eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for context
// Dogfood cannot run on Windows // Dogfood cannot run on Windows
#![cfg(not(windows))] #![cfg(not(windows))]
#![feature(once_cell)] #![feature(once_cell)]
@ -17,12 +22,14 @@ fn dogfood_clippy() {
return; return;
} }
let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let enable_metadata_collection = std::env::var("ENABLE_METADATA_COLLECTION").unwrap_or_else(|_| "0".to_string());
let mut command = Command::new(&*CLIPPY_PATH); let mut command = Command::new(&*CLIPPY_PATH);
command command
.current_dir(root_dir) .current_dir(root_dir)
.env("CLIPPY_DOGFOOD", "1") .env("CLIPPY_DOGFOOD", "1")
.env("CARGO_INCREMENTAL", "0") .env("CARGO_INCREMENTAL", "0")
.env("ENABLE_METADATA_COLLECTION", &enable_metadata_collection)
.arg("clippy") .arg("clippy")
.arg("--all-targets") .arg("--all-targets")
.arg("--all-features") .arg("--all-features")

View file

@ -1,4 +1,4 @@
error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence for key `blacklisted-names`
error: aborting due to previous error error: aborting due to previous error

View file

@ -1,4 +1,4 @@
error: error reading Clippy's configuration file `$DIR/clippy.toml`: found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. error: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead
error: aborting due to previous error error: aborting due to previous error

View file

@ -1,5 +1,5 @@
error: this generic shadows the built-in type `u32` error: this generic shadows the built-in type `u32`
--> $DIR/builtin-type-shadow.rs:4:8 --> $DIR/builtin_type_shadow.rs:4:8
| |
LL | fn foo<u32>(a: u32) -> u32 { LL | fn foo<u32>(a: u32) -> u32 {
| ^^^ | ^^^
@ -7,7 +7,7 @@ LL | fn foo<u32>(a: u32) -> u32 {
= note: `-D clippy::builtin-type-shadow` implied by `-D warnings` = note: `-D clippy::builtin-type-shadow` implied by `-D warnings`
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/builtin-type-shadow.rs:5:5 --> $DIR/builtin_type_shadow.rs:5:5
| |
LL | fn foo<u32>(a: u32) -> u32 { LL | fn foo<u32>(a: u32) -> u32 {
| --- --- expected `u32` because of return type | --- --- expected `u32` because of return type

View file

@ -203,4 +203,32 @@ mod issue_5212 {
} }
} }
enum Sign {
Negative,
Positive,
Zero,
}
impl Sign {
const fn sign_i8(n: i8) -> Self {
if n == 0 {
Sign::Zero
} else if n > 0 {
Sign::Positive
} else {
Sign::Negative
}
}
}
const fn sign_i8(n: i8) -> Sign {
if n == 0 {
Sign::Zero
} else if n > 0 {
Sign::Positive
} else {
Sign::Negative
}
}
fn main() {} fn main() {}

View file

@ -16,8 +16,6 @@ impl Iterator for Countdown {
fn main() { fn main() {
let my_iterator = Countdown(5); let my_iterator = Countdown(5);
let a: Vec<_> = my_iterator.take(1).collect(); assert_eq!(my_iterator.take(1).count(), 1);
assert_eq!(a.len(), 1); assert_eq!(my_iterator.count(), 5);
let b: Vec<_> = my_iterator.collect();
assert_eq!(b.len(), 5);
} }

View file

@ -0,0 +1,9 @@
#[derive(Default)]
struct A<T> {
a: Vec<A<T>>,
b: T,
}
fn main() {
if let Ok(_) = Ok::<_, ()>(A::<String>::default()) {}
}

View file

@ -0,0 +1,10 @@
error: redundant pattern matching, consider using `is_ok()`
--> $DIR/ice-7169.rs:8:12
|
LL | if let Ok(_) = Ok::<_, ()>(A::<String>::default()) {}
| -------^^^^^-------------------------------------- help: try this: `if Ok::<_, ()>(A::<String>::default()).is_ok()`
|
= note: `-D clippy::redundant-pattern-matching` implied by `-D warnings`
error: aborting due to previous error

View file

@ -1,3 +1,5 @@
// edition:2018
#[warn(clippy::eval_order_dependence)] #[warn(clippy::eval_order_dependence)]
#[allow( #[allow(
unused_assignments, unused_assignments,
@ -107,3 +109,7 @@ fn main() {
}, },
); );
} }
async fn issue_6925() {
let _ = vec![async { true }.await, async { false }.await];
}

View file

@ -1,48 +1,48 @@
error: unsequenced read of a variable error: unsequenced read of `x`
--> $DIR/eval_order_dependence.rs:15:9 --> $DIR/eval_order_dependence.rs:17:9
| |
LL | } + x; LL | } + x;
| ^ | ^
| |
= note: `-D clippy::eval-order-dependence` implied by `-D warnings` = note: `-D clippy::eval-order-dependence` implied by `-D warnings`
note: whether read occurs before this write depends on evaluation order note: whether read occurs before this write depends on evaluation order
--> $DIR/eval_order_dependence.rs:13:9 --> $DIR/eval_order_dependence.rs:15:9
| |
LL | x = 1; LL | x = 1;
| ^^^^^ | ^^^^^
error: unsequenced read of a variable error: unsequenced read of `x`
--> $DIR/eval_order_dependence.rs:18:5 --> $DIR/eval_order_dependence.rs:20:5
| |
LL | x += { LL | x += {
| ^ | ^
| |
note: whether read occurs before this write depends on evaluation order note: whether read occurs before this write depends on evaluation order
--> $DIR/eval_order_dependence.rs:19:9 --> $DIR/eval_order_dependence.rs:21:9
| |
LL | x = 20; LL | x = 20;
| ^^^^^^ | ^^^^^^
error: unsequenced read of a variable error: unsequenced read of `x`
--> $DIR/eval_order_dependence.rs:31:12 --> $DIR/eval_order_dependence.rs:33:12
| |
LL | a: x, LL | a: x,
| ^ | ^
| |
note: whether read occurs before this write depends on evaluation order note: whether read occurs before this write depends on evaluation order
--> $DIR/eval_order_dependence.rs:33:13 --> $DIR/eval_order_dependence.rs:35:13
| |
LL | x = 6; LL | x = 6;
| ^^^^^ | ^^^^^
error: unsequenced read of a variable error: unsequenced read of `x`
--> $DIR/eval_order_dependence.rs:40:9 --> $DIR/eval_order_dependence.rs:42:9
| |
LL | x += { LL | x += {
| ^ | ^
| |
note: whether read occurs before this write depends on evaluation order note: whether read occurs before this write depends on evaluation order
--> $DIR/eval_order_dependence.rs:41:13 --> $DIR/eval_order_dependence.rs:43:13
| |
LL | x = 20; LL | x = 20;
| ^^^^^^ | ^^^^^^

View file

@ -1,7 +1,8 @@
// edition:2018
// run-rustfix // run-rustfix
#![warn(clippy::implicit_return)] #![warn(clippy::implicit_return)]
#![allow(clippy::needless_return, unused)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)]
fn test_end_of_fn() -> bool { fn test_end_of_fn() -> bool {
if true { if true {
@ -12,7 +13,6 @@ fn test_end_of_fn() -> bool {
return true return true
} }
#[allow(clippy::needless_bool)]
fn test_if_block() -> bool { fn test_if_block() -> bool {
if true { return true } else { return false } if true { return true } else { return false }
} }
@ -25,7 +25,6 @@ fn test_match(x: bool) -> bool {
} }
} }
#[allow(clippy::needless_return)]
fn test_match_with_unreachable(x: bool) -> bool { fn test_match_with_unreachable(x: bool) -> bool {
match x { match x {
true => return false, true => return false,
@ -33,14 +32,12 @@ fn test_match_with_unreachable(x: bool) -> bool {
} }
} }
#[allow(clippy::never_loop)]
fn test_loop() -> bool { fn test_loop() -> bool {
loop { loop {
return true; return true;
} }
} }
#[allow(clippy::never_loop)]
fn test_loop_with_block() -> bool { fn test_loop_with_block() -> bool {
loop { loop {
{ {
@ -49,7 +46,6 @@ fn test_loop_with_block() -> bool {
} }
} }
#[allow(clippy::never_loop)]
fn test_loop_with_nests() -> bool { fn test_loop_with_nests() -> bool {
loop { loop {
if true { if true {
@ -83,15 +79,53 @@ fn test_return_macro() -> String {
return format!("test {}", "test") return format!("test {}", "test")
} }
fn main() { fn macro_branch_test() -> bool {
let _ = test_end_of_fn(); macro_rules! m {
let _ = test_if_block(); ($t:expr, $f:expr) => {
let _ = test_match(true); if true { $t } else { $f }
let _ = test_match_with_unreachable(true); };
let _ = test_loop(); }
let _ = test_loop_with_block(); return m!(true, false)
let _ = test_loop_with_nests();
let _ = test_loop_with_if_let();
test_closure();
let _ = test_return_macro();
} }
fn loop_test() -> bool {
'outer: loop {
if true {
return true;
}
let _ = loop {
if false {
return false;
}
if true {
break true;
}
};
}
}
fn loop_macro_test() -> bool {
macro_rules! m {
($e:expr) => {
break $e
};
}
return loop {
m!(true);
}
}
fn divergent_test() -> bool {
fn diverge() -> ! {
panic!()
}
diverge()
}
// issue #6940
async fn foo() -> bool {
return true
}
fn main() {}

View file

@ -1,7 +1,8 @@
// edition:2018
// run-rustfix // run-rustfix
#![warn(clippy::implicit_return)] #![warn(clippy::implicit_return)]
#![allow(clippy::needless_return, unused)] #![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)]
fn test_end_of_fn() -> bool { fn test_end_of_fn() -> bool {
if true { if true {
@ -12,7 +13,6 @@ fn test_end_of_fn() -> bool {
true true
} }
#[allow(clippy::needless_bool)]
fn test_if_block() -> bool { fn test_if_block() -> bool {
if true { true } else { false } if true { true } else { false }
} }
@ -25,7 +25,6 @@ fn test_match(x: bool) -> bool {
} }
} }
#[allow(clippy::needless_return)]
fn test_match_with_unreachable(x: bool) -> bool { fn test_match_with_unreachable(x: bool) -> bool {
match x { match x {
true => return false, true => return false,
@ -33,14 +32,12 @@ fn test_match_with_unreachable(x: bool) -> bool {
} }
} }
#[allow(clippy::never_loop)]
fn test_loop() -> bool { fn test_loop() -> bool {
loop { loop {
break true; break true;
} }
} }
#[allow(clippy::never_loop)]
fn test_loop_with_block() -> bool { fn test_loop_with_block() -> bool {
loop { loop {
{ {
@ -49,7 +46,6 @@ fn test_loop_with_block() -> bool {
} }
} }
#[allow(clippy::never_loop)]
fn test_loop_with_nests() -> bool { fn test_loop_with_nests() -> bool {
loop { loop {
if true { if true {
@ -83,15 +79,53 @@ fn test_return_macro() -> String {
format!("test {}", "test") format!("test {}", "test")
} }
fn main() { fn macro_branch_test() -> bool {
let _ = test_end_of_fn(); macro_rules! m {
let _ = test_if_block(); ($t:expr, $f:expr) => {
let _ = test_match(true); if true { $t } else { $f }
let _ = test_match_with_unreachable(true); };
let _ = test_loop(); }
let _ = test_loop_with_block(); m!(true, false)
let _ = test_loop_with_nests();
let _ = test_loop_with_if_let();
test_closure();
let _ = test_return_macro();
} }
fn loop_test() -> bool {
'outer: loop {
if true {
break true;
}
let _ = loop {
if false {
break 'outer false;
}
if true {
break true;
}
};
}
}
fn loop_macro_test() -> bool {
macro_rules! m {
($e:expr) => {
break $e
};
}
loop {
m!(true);
}
}
fn divergent_test() -> bool {
fn diverge() -> ! {
panic!()
}
diverge()
}
// issue #6940
async fn foo() -> bool {
true
}
fn main() {}

View file

@ -1,5 +1,5 @@
error: missing `return` statement error: missing `return` statement
--> $DIR/implicit_return.rs:12:5 --> $DIR/implicit_return.rs:13:5
| |
LL | true LL | true
| ^^^^ help: add `return` as shown: `return true` | ^^^^ help: add `return` as shown: `return true`
@ -31,40 +31,79 @@ LL | false => { true },
| ^^^^ help: add `return` as shown: `return true` | ^^^^ help: add `return` as shown: `return true`
error: missing `return` statement error: missing `return` statement
--> $DIR/implicit_return.rs:39:9 --> $DIR/implicit_return.rs:37:9
| |
LL | break true; LL | break true;
| ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
error: missing `return` statement error: missing `return` statement
--> $DIR/implicit_return.rs:47:13 --> $DIR/implicit_return.rs:44:13
| |
LL | break true; LL | break true;
| ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
error: missing `return` statement error: missing `return` statement
--> $DIR/implicit_return.rs:56:13 --> $DIR/implicit_return.rs:52:13
| |
LL | break true; LL | break true;
| ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
error: missing `return` statement error: missing `return` statement
--> $DIR/implicit_return.rs:74:18 --> $DIR/implicit_return.rs:70:18
| |
LL | let _ = || { true }; LL | let _ = || { true };
| ^^^^ help: add `return` as shown: `return true` | ^^^^ help: add `return` as shown: `return true`
error: missing `return` statement error: missing `return` statement
--> $DIR/implicit_return.rs:75:16 --> $DIR/implicit_return.rs:71:16
| |
LL | let _ = || true; LL | let _ = || true;
| ^^^^ help: add `return` as shown: `return true` | ^^^^ help: add `return` as shown: `return true`
error: missing `return` statement error: missing `return` statement
--> $DIR/implicit_return.rs:83:5 --> $DIR/implicit_return.rs:79:5
| |
LL | format!("test {}", "test") LL | format!("test {}", "test")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")` | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")`
error: aborting due to 11 previous errors error: missing `return` statement
--> $DIR/implicit_return.rs:88:5
|
LL | m!(true, false)
| ^^^^^^^^^^^^^^^ help: add `return` as shown: `return m!(true, false)`
error: missing `return` statement
--> $DIR/implicit_return.rs:94:13
|
LL | break true;
| ^^^^^^^^^^ help: change `break` to `return` as shown: `return true`
error: missing `return` statement
--> $DIR/implicit_return.rs:99:17
|
LL | break 'outer false;
| ^^^^^^^^^^^^^^^^^^ help: change `break` to `return` as shown: `return false`
error: missing `return` statement
--> $DIR/implicit_return.rs:114:5
|
LL | / loop {
LL | | m!(true);
LL | | }
| |_____^
|
help: add `return` as shown
|
LL | return loop {
LL | m!(true);
LL | }
|
error: missing `return` statement
--> $DIR/implicit_return.rs:128:5
|
LL | true
| ^^^^ help: add `return` as shown: `return true`
error: aborting due to 16 previous errors

View file

@ -192,11 +192,23 @@ fn while_loop_with_break_and_return() {
} }
} }
fn immutable_condition_false_positive(mut n: u64) -> u32 {
let mut count = 0;
while {
n >>= 1;
n != 0
} {
count += 1;
}
count
}
fn main() { fn main() {
immutable_condition(); immutable_condition();
unused_var(); unused_var();
used_immutable(); used_immutable();
internally_mutable(); internally_mutable();
immutable_condition_false_positive(5);
let mut c = Counter { count: 0 }; let mut c = Counter { count: 0 };
c.inc_n(5); c.inc_n(5);

View file

@ -1,4 +1,4 @@
use std::collections::{HashMap, VecDeque}; use std::collections::{BinaryHeap, HashMap, LinkedList, VecDeque};
fn main() { fn main() {
let sample = [1; 5]; let sample = [1; 5];
@ -43,3 +43,35 @@ fn main() {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
} }
} }
mod issue7110 {
// #7110 - lint for type annotation cases
use super::*;
fn lint_vec(string: &str) -> usize {
let buffer: Vec<&str> = string.split('/').collect();
buffer.len()
}
fn lint_vec_deque() -> usize {
let sample = [1; 5];
let indirect_len: VecDeque<_> = sample.iter().collect();
indirect_len.len()
}
fn lint_linked_list() -> usize {
let sample = [1; 5];
let indirect_len: LinkedList<_> = sample.iter().collect();
indirect_len.len()
}
fn lint_binary_heap() -> usize {
let sample = [1; 5];
let indirect_len: BinaryHeap<_> = sample.iter().collect();
indirect_len.len()
}
fn dont_lint(string: &str) -> usize {
let buffer: Vec<&str> = string.split('/').collect();
for buff in &buffer {
println!("{}", buff);
}
buffer.len()
}
}

View file

@ -69,5 +69,61 @@ LL |
LL | sample.into_iter().any(|x| x == a); LL | sample.into_iter().any(|x| x == a);
| |
error: aborting due to 5 previous errors error: avoid using `collect()` when not needed
--> $DIR/needless_collect_indirect.rs:52:51
|
LL | let buffer: Vec<&str> = string.split('/').collect();
| ^^^^^^^
LL | buffer.len()
| ------------ the iterator could be used here instead
|
help: take the original Iterator's count instead of collecting it and finding the length
|
LL |
LL | string.split('/').count()
|
error: avoid using `collect()` when not needed
--> $DIR/needless_collect_indirect.rs:57:55
|
LL | let indirect_len: VecDeque<_> = sample.iter().collect();
| ^^^^^^^
LL | indirect_len.len()
| ------------------ the iterator could be used here instead
|
help: take the original Iterator's count instead of collecting it and finding the length
|
LL |
LL | sample.iter().count()
|
error: avoid using `collect()` when not needed
--> $DIR/needless_collect_indirect.rs:62:57
|
LL | let indirect_len: LinkedList<_> = sample.iter().collect();
| ^^^^^^^
LL | indirect_len.len()
| ------------------ the iterator could be used here instead
|
help: take the original Iterator's count instead of collecting it and finding the length
|
LL |
LL | sample.iter().count()
|
error: avoid using `collect()` when not needed
--> $DIR/needless_collect_indirect.rs:67:57
|
LL | let indirect_len: BinaryHeap<_> = sample.iter().collect();
| ^^^^^^^
LL | indirect_len.len()
| ------------------ the iterator could be used here instead
|
help: take the original Iterator's count instead of collecting it and finding the length
|
LL |
LL | sample.iter().count()
|
error: aborting due to 9 previous errors

View file

@ -33,6 +33,8 @@ fn main() {
x.rmatch_indices('x'); x.rmatch_indices('x');
x.trim_start_matches('x'); x.trim_start_matches('x');
x.trim_end_matches('x'); x.trim_end_matches('x');
x.strip_prefix('x');
x.strip_suffix('x');
// Make sure we escape characters correctly. // Make sure we escape characters correctly.
x.split('\n'); x.split('\n');
x.split('\''); x.split('\'');

View file

@ -33,6 +33,8 @@ fn main() {
x.rmatch_indices("x"); x.rmatch_indices("x");
x.trim_start_matches("x"); x.trim_start_matches("x");
x.trim_end_matches("x"); x.trim_end_matches("x");
x.strip_prefix("x");
x.strip_suffix("x");
// Make sure we escape characters correctly. // Make sure we escape characters correctly.
x.split("\n"); x.split("\n");
x.split("'"); x.split("'");

View file

@ -121,64 +121,76 @@ LL | x.trim_end_matches("x");
| ^^^ help: try using a `char` instead: `'x'` | ^^^ help: try using a `char` instead: `'x'`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:37:13 --> $DIR/single_char_pattern.rs:36:20
|
LL | x.strip_prefix("x");
| ^^^ help: try using a `char` instead: `'x'`
error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:37:20
|
LL | x.strip_suffix("x");
| ^^^ help: try using a `char` instead: `'x'`
error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:39:13
| |
LL | x.split("/n"); LL | x.split("/n");
| ^^^^ help: try using a `char` instead: `'/n'` | ^^^^ help: try using a `char` instead: `'/n'`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:38:13 --> $DIR/single_char_pattern.rs:40:13
| |
LL | x.split("'"); LL | x.split("'");
| ^^^ help: try using a `char` instead: `'/''` | ^^^ help: try using a `char` instead: `'/''`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:39:13 --> $DIR/single_char_pattern.rs:41:13
| |
LL | x.split("/'"); LL | x.split("/'");
| ^^^^ help: try using a `char` instead: `'/''` | ^^^^ help: try using a `char` instead: `'/''`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:44:31 --> $DIR/single_char_pattern.rs:46:31
| |
LL | x.replace(";", ",").split(","); // issue #2978 LL | x.replace(";", ",").split(","); // issue #2978
| ^^^ help: try using a `char` instead: `','` | ^^^ help: try using a `char` instead: `','`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:45:19 --> $DIR/single_char_pattern.rs:47:19
| |
LL | x.starts_with("/x03"); // issue #2996 LL | x.starts_with("/x03"); // issue #2996
| ^^^^^^ help: try using a `char` instead: `'/x03'` | ^^^^^^ help: try using a `char` instead: `'/x03'`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:52:13 --> $DIR/single_char_pattern.rs:54:13
| |
LL | x.split(r"a"); LL | x.split(r"a");
| ^^^^ help: try using a `char` instead: `'a'` | ^^^^ help: try using a `char` instead: `'a'`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:53:13 --> $DIR/single_char_pattern.rs:55:13
| |
LL | x.split(r#"a"#); LL | x.split(r#"a"#);
| ^^^^^^ help: try using a `char` instead: `'a'` | ^^^^^^ help: try using a `char` instead: `'a'`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:54:13 --> $DIR/single_char_pattern.rs:56:13
| |
LL | x.split(r###"a"###); LL | x.split(r###"a"###);
| ^^^^^^^^^^ help: try using a `char` instead: `'a'` | ^^^^^^^^^^ help: try using a `char` instead: `'a'`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:55:13 --> $DIR/single_char_pattern.rs:57:13
| |
LL | x.split(r###"'"###); LL | x.split(r###"'"###);
| ^^^^^^^^^^ help: try using a `char` instead: `'/''` | ^^^^^^^^^^ help: try using a `char` instead: `'/''`
error: single-character string constant used as pattern error: single-character string constant used as pattern
--> $DIR/single_char_pattern.rs:56:13 --> $DIR/single_char_pattern.rs:58:13
| |
LL | x.split(r###"#"###); LL | x.split(r###"#"###);
| ^^^^^^^^^^ help: try using a `char` instead: `'#'` | ^^^^^^^^^^ help: try using a `char` instead: `'#'`
error: aborting due to 30 previous errors error: aborting due to 32 previous errors

View file

@ -15,3 +15,7 @@ fn main() {
let _ = (0..4).filter_map(i32::checked_abs); let _ = (0..4).filter_map(i32::checked_abs);
} }
fn filter_map_none_changes_item_type() -> impl Iterator<Item = bool> {
"".chars().filter_map(|_| None)
}

View file

@ -80,3 +80,10 @@ fn test2(){}
#[rustfmt::skip] #[rustfmt::skip]
fn test3(){} fn test3(){}
fn macro_expr() {
macro_rules! e {
() => (());
}
e!()
}

View file

@ -80,3 +80,10 @@ fn test2() ->(){}
#[rustfmt::skip] #[rustfmt::skip]
fn test3()-> (){} fn test3()-> (){}
fn macro_expr() {
macro_rules! e {
() => (());
}
e!()
}

View file

@ -14,7 +14,7 @@ lintname_re = re.compile(r'''pub\s+([A-Z_][A-Z_0-9]*)''')
group_re = re.compile(r'''\s*([a-z_][a-z_0-9]+)''') group_re = re.compile(r'''\s*([a-z_][a-z_0-9]+)''')
conf_re = re.compile(r'''define_Conf! {\n([^}]*)\n}''', re.MULTILINE) conf_re = re.compile(r'''define_Conf! {\n([^}]*)\n}''', re.MULTILINE)
confvar_re = re.compile( confvar_re = re.compile(
r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\([^,]+,\s+"([^"]+)":\s+([^,]+),\s+([^\.\)]+).*\),''', re.MULTILINE) r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\(([^:]+):\s*([^\s=]+)\s*=\s*([^\.\)]+).*\),''', re.MULTILINE)
comment_re = re.compile(r'''\s*/// ?(.*)''') comment_re = re.compile(r'''\s*/// ?(.*)''')
lint_levels = { lint_levels = {