Merge commit 'd9ddce8a223cb9916389c039777b6966ea448dc8' into clippyup

This commit is contained in:
Philipp Krones 2022-06-04 13:34:07 +02:00
parent 57304823db
commit f067783461
269 changed files with 7488 additions and 2651 deletions

View File

@ -3276,9 +3276,11 @@ Released 2018-09-13
<!-- begin autogenerated links to lint list -->
[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
[`allow_attributes_without_reason`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes_without_reason
[`almost_complete_letter_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_letter_range
[`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped
[`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant
[`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions
[`as_underscore`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_underscore
[`assertions_on_constants`]: https://rust-lang.github.io/rust-clippy/master/index.html#assertions_on_constants
[`assign_op_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_op_pattern
[`assign_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#assign_ops
@ -3296,6 +3298,7 @@ Released 2018-09-13
[`bool_assert_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
[`bool_comparison`]: https://rust-lang.github.io/rust-clippy/master/index.html#bool_comparison
[`borrow_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_as_ptr
[`borrow_deref_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_deref_ref
[`borrow_interior_mutable_const`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrow_interior_mutable_const
[`borrowed_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#borrowed_box
[`box_collection`]: https://rust-lang.github.io/rust-clippy/master/index.html#box_collection
@ -3362,6 +3365,7 @@ Released 2018-09-13
[`disallowed_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_type
[`disallowed_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types
[`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression
[`doc_link_with_quotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_with_quotes
[`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
[`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons
[`double_must_use`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_must_use
@ -3435,6 +3439,7 @@ Released 2018-09-13
[`from_over_into`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_over_into
[`from_str_radix_10`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_str_radix_10
[`future_not_send`]: https://rust-lang.github.io/rust-clippy/master/index.html#future_not_send
[`get_first`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_first
[`get_last_with_len`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_last_with_len
[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion
@ -3556,6 +3561,7 @@ Released 2018-09-13
[`min_max`]: https://rust-lang.github.io/rust-clippy/master/index.html#min_max
[`misaligned_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#misaligned_transmute
[`mismatched_target_os`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatched_target_os
[`mismatching_type_param_order`]: https://rust-lang.github.io/rust-clippy/master/index.html#mismatching_type_param_order
[`misrefactored_assign_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#misrefactored_assign_op
[`missing_const_for_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn
[`missing_docs_in_private_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items
@ -3613,6 +3619,7 @@ Released 2018-09-13
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
[`new_without_default_derive`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default_derive
[`no_effect`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect
[`no_effect_replace`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_replace
[`no_effect_underscore_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#no_effect_underscore_binding
[`non_ascii_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_ascii_literal
[`non_octal_unix_permissions`]: https://rust-lang.github.io/rust-clippy/master/index.html#non_octal_unix_permissions
@ -3729,7 +3736,6 @@ Released 2018-09-13
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
[`significant_drop_in_scrutinee`]: https://rust-lang.github.io/rust-clippy/master/index.html#significant_drop_in_scrutinee
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
@ -3750,6 +3756,7 @@ Released 2018-09-13
[`suspicious_operation_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_operation_groupings
[`suspicious_splitn`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_splitn
[`suspicious_unary_op_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_unary_op_formatting
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
@ -3820,6 +3827,7 @@ Released 2018-09-13
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
[`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount
[`unused_label`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_label
[`unused_rounding`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_rounding
[`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self
[`unused_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_unit
[`unusual_byte_groupings`]: https://rust-lang.github.io/rust-clippy/master/index.html#unusual_byte_groupings

View File

@ -28,7 +28,7 @@ tempfile = { version = "3.2", optional = true }
termize = "0.1"
[dev-dependencies]
compiletest_rs = { version = "0.7.1", features = ["tmp"] }
compiletest_rs = { version = "0.8", features = ["tmp"] }
tester = "0.9"
regex = "1.5"
# This is used by the `collect-metadata` alias.
@ -40,6 +40,7 @@ filetime = "0.2"
rustc-workspace-hack = "1.0"
# UI test dependencies
clap = { version = "3.1", features = ["derive"] }
clippy_utils = { path = "clippy_utils" }
derive-new = "0.5"
if_chain = "1.0"
@ -48,7 +49,7 @@ quote = "1.0"
serde = { version = "1.0.125", features = ["derive"] }
syn = { version = "1.0", features = ["full"] }
futures = "0.3"
parking_lot = "0.11.2"
parking_lot = "0.12"
tokio = { version = "1", features = ["io-util"] }
rustc-semver = "1.1"

View File

@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
aho-corasick = "0.7"
clap = "2.33"
clap = "3.1"
indoc = "1.0"
itertools = "0.10.1"
opener = "0.5"

View File

@ -2,20 +2,20 @@
// warn on lints, that are included in `rust-lang/rust`s bootstrap
#![warn(rust_2018_idioms, unused_lifetimes)]
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use clap::{Arg, ArgMatches, Command};
use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints};
use indoc::indoc;
fn main() {
let matches = get_clap_config();
match matches.subcommand() {
("bless", Some(matches)) => {
Some(("bless", matches)) => {
bless::bless(matches.is_present("ignore-timestamp"));
},
("fmt", Some(matches)) => {
Some(("fmt", matches)) => {
fmt::run(matches.is_present("check"), matches.is_present("verbose"));
},
("update_lints", Some(matches)) => {
Some(("update_lints", matches)) => {
if matches.is_present("print-only") {
update_lints::print_lints();
} else if matches.is_present("check") {
@ -24,7 +24,7 @@ fn main() {
update_lints::update(update_lints::UpdateMode::Change);
}
},
("new_lint", Some(matches)) => {
Some(("new_lint", matches)) => {
match new_lint::create(
matches.value_of("pass"),
matches.value_of("name"),
@ -35,8 +35,8 @@ fn main() {
Err(e) => eprintln!("Unable to create lint: {}", e),
}
},
("setup", Some(sub_command)) => match sub_command.subcommand() {
("intellij", Some(matches)) => {
Some(("setup", sub_command)) => match sub_command.subcommand() {
Some(("intellij", matches)) => {
if matches.is_present("remove") {
setup::intellij::remove_rustc_src();
} else {
@ -47,14 +47,14 @@ fn main() {
);
}
},
("git-hook", Some(matches)) => {
Some(("git-hook", matches)) => {
if matches.is_present("remove") {
setup::git_hook::remove_hook();
} else {
setup::git_hook::install_hook(matches.is_present("force-override"));
}
},
("vscode-tasks", Some(matches)) => {
Some(("vscode-tasks", matches)) => {
if matches.is_present("remove") {
setup::vscode::remove_tasks();
} else {
@ -63,23 +63,23 @@ fn main() {
},
_ => {},
},
("remove", Some(sub_command)) => match sub_command.subcommand() {
("git-hook", Some(_)) => setup::git_hook::remove_hook(),
("intellij", Some(_)) => setup::intellij::remove_rustc_src(),
("vscode-tasks", Some(_)) => setup::vscode::remove_tasks(),
Some(("remove", sub_command)) => match sub_command.subcommand() {
Some(("git-hook", _)) => setup::git_hook::remove_hook(),
Some(("intellij", _)) => setup::intellij::remove_rustc_src(),
Some(("vscode-tasks", _)) => setup::vscode::remove_tasks(),
_ => {},
},
("serve", Some(matches)) => {
Some(("serve", matches)) => {
let port = matches.value_of("port").unwrap().parse().unwrap();
let lint = matches.value_of("lint");
serve::run(port, lint);
},
("lint", Some(matches)) => {
Some(("lint", matches)) => {
let path = matches.value_of("path").unwrap();
let args = matches.values_of("args").into_iter().flatten();
lint::run(path, args);
},
("rename_lint", Some(matches)) => {
Some(("rename_lint", matches)) => {
let old_name = matches.value_of("old_name").unwrap();
let new_name = matches.value_of("new_name").unwrap_or(old_name);
let uplift = matches.is_present("uplift");
@ -89,35 +89,24 @@ fn main() {
}
}
fn get_clap_config<'a>() -> ArgMatches<'a> {
App::new("Clippy developer tooling")
.setting(AppSettings::ArgRequiredElseHelp)
fn get_clap_config() -> ArgMatches {
Command::new("Clippy developer tooling")
.arg_required_else_help(true)
.subcommand(
SubCommand::with_name("bless")
.about("bless the test output changes")
.arg(
Arg::with_name("ignore-timestamp")
.long("ignore-timestamp")
.help("Include files updated before clippy was built"),
),
Command::new("bless").about("bless the test output changes").arg(
Arg::new("ignore-timestamp")
.long("ignore-timestamp")
.help("Include files updated before clippy was built"),
),
)
.subcommand(
SubCommand::with_name("fmt")
Command::new("fmt")
.about("Run rustfmt on all projects and tests")
.arg(
Arg::with_name("check")
.long("check")
.help("Use the rustfmt --check option"),
)
.arg(
Arg::with_name("verbose")
.short("v")
.long("verbose")
.help("Echo commands run"),
),
.arg(Arg::new("check").long("check").help("Use the rustfmt --check option"))
.arg(Arg::new("verbose").short('v').long("verbose").help("Echo commands run")),
)
.subcommand(
SubCommand::with_name("update_lints")
Command::new("update_lints")
.about("Updates lint registration and information from the source code")
.long_about(
"Makes sure that:\n \
@ -127,23 +116,23 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
* lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \
* all lints are registered in the lint store",
)
.arg(Arg::with_name("print-only").long("print-only").help(
.arg(Arg::new("print-only").long("print-only").help(
"Print a table of lints to STDOUT. \
This does not include deprecated and internal lints. \
(Does not modify any files)",
))
.arg(
Arg::with_name("check")
Arg::new("check")
.long("check")
.help("Checks that `cargo dev update_lints` has been run. Used on CI."),
),
)
.subcommand(
SubCommand::with_name("new_lint")
Command::new("new_lint")
.about("Create new lint and run `cargo dev update_lints`")
.arg(
Arg::with_name("pass")
.short("p")
Arg::new("pass")
.short('p')
.long("pass")
.help("Specify whether the lint runs during the early or late pass")
.takes_value(true)
@ -151,16 +140,16 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
.required(true),
)
.arg(
Arg::with_name("name")
.short("n")
Arg::new("name")
.short('n')
.long("name")
.help("Name of the new lint in snake case, ex: fn_too_long")
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name("category")
.short("c")
Arg::new("category")
.short('c')
.long("category")
.help("What category the lint belongs to")
.default_value("nursery")
@ -179,29 +168,25 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
])
.takes_value(true),
)
.arg(
Arg::with_name("msrv")
.long("msrv")
.help("Add MSRV config code to the lint"),
),
.arg(Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint")),
)
.subcommand(
SubCommand::with_name("setup")
Command::new("setup")
.about("Support for setting up your personal development environment")
.setting(AppSettings::ArgRequiredElseHelp)
.arg_required_else_help(true)
.subcommand(
SubCommand::with_name("intellij")
Command::new("intellij")
.about("Alter dependencies so Intellij Rust can find rustc internals")
.arg(
Arg::with_name("remove")
Arg::new("remove")
.long("remove")
.help("Remove the dependencies added with 'cargo dev setup intellij'")
.required(false),
)
.arg(
Arg::with_name("rustc-repo-path")
Arg::new("rustc-repo-path")
.long("repo-path")
.short("r")
.short('r')
.help("The path to a rustc repo that will be used for setting the dependencies")
.takes_value(true)
.value_name("path")
@ -210,66 +195,65 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
),
)
.subcommand(
SubCommand::with_name("git-hook")
Command::new("git-hook")
.about("Add a pre-commit git hook that formats your code to make it look pretty")
.arg(
Arg::with_name("remove")
Arg::new("remove")
.long("remove")
.help("Remove the pre-commit hook added with 'cargo dev setup git-hook'")
.required(false),
)
.arg(
Arg::with_name("force-override")
Arg::new("force-override")
.long("force-override")
.short("f")
.short('f')
.help("Forces the override of an existing git pre-commit hook")
.required(false),
),
)
.subcommand(
SubCommand::with_name("vscode-tasks")
Command::new("vscode-tasks")
.about("Add several tasks to vscode for formatting, validation and testing")
.arg(
Arg::with_name("remove")
Arg::new("remove")
.long("remove")
.help("Remove the tasks added with 'cargo dev setup vscode-tasks'")
.required(false),
)
.arg(
Arg::with_name("force-override")
Arg::new("force-override")
.long("force-override")
.short("f")
.short('f')
.help("Forces the override of existing vscode tasks")
.required(false),
),
),
)
.subcommand(
SubCommand::with_name("remove")
Command::new("remove")
.about("Support for undoing changes done by the setup command")
.setting(AppSettings::ArgRequiredElseHelp)
.subcommand(SubCommand::with_name("git-hook").about("Remove any existing pre-commit git hook"))
.subcommand(SubCommand::with_name("vscode-tasks").about("Remove any existing vscode tasks"))
.arg_required_else_help(true)
.subcommand(Command::new("git-hook").about("Remove any existing pre-commit git hook"))
.subcommand(Command::new("vscode-tasks").about("Remove any existing vscode tasks"))
.subcommand(
SubCommand::with_name("intellij")
.about("Removes rustc source paths added via `cargo dev setup intellij`"),
Command::new("intellij").about("Removes rustc source paths added via `cargo dev setup intellij`"),
),
)
.subcommand(
SubCommand::with_name("serve")
Command::new("serve")
.about("Launch a local 'ALL the Clippy Lints' website in a browser")
.arg(
Arg::with_name("port")
Arg::new("port")
.long("port")
.short("p")
.short('p')
.help("Local port for the http server")
.default_value("8000")
.validator_os(serve::validate_port),
)
.arg(Arg::with_name("lint").help("Which lint's page to load initially (optional)")),
.arg(Arg::new("lint").help("Which lint's page to load initially (optional)")),
)
.subcommand(
SubCommand::with_name("lint")
Command::new("lint")
.about("Manually run clippy on a file or package")
.after_help(indoc! {"
EXAMPLES
@ -288,33 +272,33 @@ fn get_clap_config<'a>() -> ArgMatches<'a> {
cargo dev lint ~/my-project -- -- -W clippy::pedantic
"})
.arg(
Arg::with_name("path")
Arg::new("path")
.required(true)
.help("The path to a file or package directory to lint"),
)
.arg(
Arg::with_name("args")
.multiple(true)
Arg::new("args")
.multiple_occurrences(true)
.help("Pass extra arguments to cargo/clippy-driver"),
),
)
.subcommand(
SubCommand::with_name("rename_lint")
Command::new("rename_lint")
.about("Renames the given lint")
.arg(
Arg::with_name("old_name")
Arg::new("old_name")
.index(1)
.required(true)
.help("The name of the lint to rename"),
)
.arg(
Arg::with_name("new_name")
Arg::new("new_name")
.index(2)
.required_unless("uplift")
.required_unless_present("uplift")
.help("The new name of the lint"),
)
.arg(
Arg::with_name("uplift")
Arg::new("uplift")
.long("uplift")
.help("This lint will be uplifted into rustc"),
),

View File

@ -1,4 +1,5 @@
use std::ffi::{OsStr, OsString};
use std::ffi::OsStr;
use std::num::ParseIntError;
use std::path::Path;
use std::process::Command;
use std::thread;
@ -59,9 +60,6 @@ fn mtime(path: impl AsRef<Path>) -> SystemTime {
}
#[allow(clippy::missing_errors_doc)]
pub fn validate_port(arg: &OsStr) -> Result<(), OsString> {
match arg.to_string_lossy().parse::<u16>() {
Ok(_port) => Ok(()),
Err(err) => Err(OsString::from(err.to_string())),
}
pub fn validate_port(arg: &OsStr) -> Result<(), ParseIntError> {
arg.to_string_lossy().parse::<u16>().map(|_| ())
}

View File

@ -0,0 +1,100 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{trim_span, walk_span_to_context};
use clippy_utils::{meets_msrv, msrvs};
use rustc_ast::ast::{Expr, ExprKind, LitKind, Pat, PatKind, RangeEnd, RangeLimits};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Checks for ranges which almost include the entire range of letters from 'a' to 'z', but
/// don't because they're a half open range.
///
/// ### Why is this bad?
/// This (`'a'..'z'`) is almost certainly a typo meant to include all letters.
///
/// ### Example
/// ```rust
/// let _ = 'a'..'z';
/// ```
/// Use instead:
/// ```rust
/// let _ = 'a'..='z';
/// ```
#[clippy::version = "1.63.0"]
pub ALMOST_COMPLETE_LETTER_RANGE,
suspicious,
"almost complete letter range"
}
impl_lint_pass!(AlmostCompleteLetterRange => [ALMOST_COMPLETE_LETTER_RANGE]);
pub struct AlmostCompleteLetterRange {
msrv: Option<RustcVersion>,
}
impl AlmostCompleteLetterRange {
pub fn new(msrv: Option<RustcVersion>) -> Self {
Self { msrv }
}
}
impl EarlyLintPass for AlmostCompleteLetterRange {
fn check_expr(&mut self, cx: &EarlyContext<'_>, e: &Expr) {
if let ExprKind::Range(Some(start), Some(end), RangeLimits::HalfOpen) = &e.kind {
let ctxt = e.span.ctxt();
let sugg = if let Some(start) = walk_span_to_context(start.span, ctxt)
&& let Some(end) = walk_span_to_context(end.span, ctxt)
&& meets_msrv(self.msrv, msrvs::RANGE_INCLUSIVE)
{
Some((trim_span(cx.sess().source_map(), start.between(end)), "..="))
} else {
None
};
check_range(cx, e.span, start, end, sugg);
}
}
fn check_pat(&mut self, cx: &EarlyContext<'_>, p: &Pat) {
if let PatKind::Range(Some(start), Some(end), kind) = &p.kind
&& matches!(kind.node, RangeEnd::Excluded)
{
let sugg = if meets_msrv(self.msrv, msrvs::RANGE_INCLUSIVE) {
"..="
} else {
"..."
};
check_range(cx, p.span, start, end, Some((kind.span, sugg)));
}
}
extract_msrv_attr!(EarlyContext);
}
fn check_range(cx: &EarlyContext<'_>, span: Span, start: &Expr, end: &Expr, sugg: Option<(Span, &str)>) {
if let ExprKind::Lit(start_lit) = &start.peel_parens().kind
&& let ExprKind::Lit(end_lit) = &end.peel_parens().kind
&& matches!(
(&start_lit.kind, &end_lit.kind),
(LitKind::Byte(b'a') | LitKind::Char('a'), LitKind::Byte(b'z') | LitKind::Char('z'))
| (LitKind::Byte(b'A') | LitKind::Char('A'), LitKind::Byte(b'Z') | LitKind::Char('Z'))
)
{
span_lint_and_then(
cx,
ALMOST_COMPLETE_LETTER_RANGE,
span,
"almost complete ascii letter range",
|diag| {
if let Some((span, sugg)) = sugg {
diag.span_suggestion(
span,
"use an inclusive range",
sugg.to_owned(),
Applicability::MaybeIncorrect,
);
}
}
);
}
}

View File

@ -28,7 +28,7 @@ declare_clippy_lint! {
/// let x = 3.14;
/// let y = 1_f64 / x;
/// ```
/// Use predefined constants instead:
/// Use instead:
/// ```rust
/// let x = std::f32::consts::PI;
/// let y = std::f64::consts::FRAC_1_PI;

View File

@ -29,15 +29,14 @@ declare_clippy_lint! {
/// f(a as u16);
/// ```
///
/// Usually better represents the semantics you expect:
/// Use instead:
/// ```rust,ignore
/// f(a.try_into()?);
/// ```
/// or
/// ```rust,ignore
///
/// // or
///
/// f(a.try_into().expect("Unexpected u16 overflow in f"));
/// ```
///
#[clippy::version = "1.41.0"]
pub AS_CONVERSIONS,
restriction,

View File

@ -0,0 +1,74 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, TyKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Check for the usage of `as _` conversion using inferred type.
///
/// ### Why is this bad?
/// The conversion might include lossy conversion and dangerous cast that might go
/// undetected du to the type being inferred.
///
/// The lint is allowed by default as using `_` is less wordy than always specifying the type.
///
/// ### Example
/// ```rust
/// fn foo(n: usize) {}
/// let n: u16 = 256;
/// foo(n as _);
/// ```
/// Use instead:
/// ```rust
/// fn foo(n: usize) {}
/// let n: u16 = 256;
/// foo(n as usize);
/// ```
#[clippy::version = "1.63.0"]
pub AS_UNDERSCORE,
restriction,
"detects `as _` conversion"
}
declare_lint_pass!(AsUnderscore => [AS_UNDERSCORE]);
impl<'tcx> LateLintPass<'tcx> for AsUnderscore {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
if in_external_macro(cx.sess(), expr.span) {
return;
}
if let ExprKind::Cast(_, ty) = expr.kind && let TyKind::Infer = ty.kind {
let ty_resolved = cx.typeck_results().expr_ty(expr);
if let ty::Error(_) = ty_resolved.kind() {
span_lint_and_help(
cx,
AS_UNDERSCORE,
expr.span,
"using `as _` conversion",
None,
"consider giving the type explicitly",
);
} else {
span_lint_and_then(
cx,
AS_UNDERSCORE,
expr.span,
"using `as _` conversion",
|diag| {
diag.span_suggestion(
ty.span,
"consider giving the type explicitly",
format!("{}", ty_resolved),
Applicability::MachineApplicable,
);
}
);
}
}
}
}

View File

@ -27,10 +27,16 @@ declare_clippy_lint! {
/// let mut a = 5;
/// let b = 0;
/// // ...
/// // Bad
/// a = a + b;
///
/// // Good
/// a = a + b;
/// ```
///
/// Use instead:
/// ```rust
/// let mut a = 5;
/// let b = 0;
/// // ...
///
/// a += b;
/// ```
#[clippy::version = "pre 1.29.0"]

View File

@ -89,13 +89,14 @@ declare_clippy_lint! {
///
/// ### Example
/// ```ignore
/// // Bad
/// #[deny(dead_code)]
/// extern crate foo;
/// #[forbid(dead_code)]
/// use foo::bar;
/// ```
///
/// // Ok
/// Use instead:
/// ```rust,ignore
/// #[allow(unused_imports)]
/// use foo::baz;
/// #[allow(unused_imports)]
@ -146,15 +147,19 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
/// #[allow(dead_code)]
///
/// fn not_quite_good_code() { }
/// ```
///
/// Use instead:
/// ```rust
/// // Good (as inner attribute)
/// #![allow(dead_code)]
///
/// fn this_is_fine() { }
///
/// // Bad
/// #[allow(dead_code)]
///
/// fn not_quite_good_code() { }
/// // or
///
/// // Good (as outer attribute)
/// #[allow(dead_code)]
@ -175,12 +180,11 @@ declare_clippy_lint! {
/// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
///
/// ### Example
/// Bad:
/// ```rust
/// #![deny(clippy::restriction)]
/// ```
///
/// Good:
/// Use instead:
/// ```rust
/// #![deny(clippy::as_conversions)]
/// ```
@ -205,13 +209,12 @@ declare_clippy_lint! {
/// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
///
/// ### Example
/// Bad:
/// ```rust
/// #[cfg_attr(rustfmt, rustfmt_skip)]
/// fn main() { }
/// ```
///
/// Good:
/// Use instead:
/// ```rust
/// #[rustfmt::skip]
/// fn main() { }
@ -231,20 +234,20 @@ declare_clippy_lint! {
/// by the conditional compilation engine.
///
/// ### Example
/// Bad:
/// ```rust
/// #[cfg(linux)]
/// fn conditional() { }
/// ```
///
/// Good:
/// Use instead:
/// ```rust
/// # mod hidden {
/// #[cfg(target_os = "linux")]
/// fn conditional() { }
/// ```
/// # }
///
/// // or
///
/// Or:
/// ```rust
/// #[cfg(unix)]
/// fn conditional() { }
/// ```
@ -266,14 +269,13 @@ declare_clippy_lint! {
/// ensure that others understand the reasoning
///
/// ### Example
/// Bad:
/// ```rust
/// #![feature(lint_reasons)]
///
/// #![allow(clippy::some_lint)]
/// ```
///
/// Good:
/// Use instead:
/// ```rust
/// #![feature(lint_reasons)]
///
@ -585,15 +587,21 @@ impl EarlyLintPass for EarlyAttributes {
}
fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
for attr in &item.attrs {
let mut iter = item.attrs.iter().peekable();
while let Some(attr) = iter.next() {
if matches!(attr.kind, AttrKind::Normal(..))
&& attr.style == AttrStyle::Outer
&& is_present_in_source(cx, attr.span)
{
let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt(), item.span.parent());
let end_of_attr_to_next_attr_or_item = Span::new(
attr.span.hi(),
iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()),
item.span.ctxt(),
item.span.parent(),
);
if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) {
if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
let lines = snippet.split('\n').collect::<Vec<_>>();
let lines = without_block_comments(lines);
@ -623,8 +631,15 @@ fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: Opti
if feature_item.has_name(sym::rustfmt);
// check for `rustfmt_skip` and `rustfmt::skip`
if let Some(skip_item) = &items[1].meta_item();
if skip_item.has_name(sym!(rustfmt_skip)) ||
skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym::skip;
if skip_item.has_name(sym!(rustfmt_skip))
|| skip_item
.path
.segments
.last()
.expect("empty path in attribute")
.ident
.name
== sym::skip;
// Only lint outer attributes, because custom inner attributes are unstable
// Tracking issue: https://github.com/rust-lang/rust/issues/54726
if attr.style == AttrStyle::Outer;

View File

@ -22,21 +22,17 @@ declare_clippy_lint! {
///
/// ### Examples
/// ```rust
/// // Bad
/// # fn somefunc() -> bool { true };
/// if { true } { /* ... */ }
///
/// // Good
/// if true { /* ... */ }
/// if { let x = somefunc(); x } { /* ... */ }
/// ```
///
/// // or
///
/// Use instead:
/// ```rust
/// # fn somefunc() -> bool { true };
/// // Bad
/// if { let x = somefunc(); x } { /* ... */ }
/// if true { /* ... */ }
///
/// // Good
/// let res = { let x = somefunc(); x };
/// if res { /* ... */ }
/// ```

View File

@ -27,8 +27,14 @@ declare_clippy_lint! {
///
/// ### Example
/// ```ignore
/// if a && true // should be: if a
/// if !(a == b) // should be: if a != b
/// if a && true {}
/// if !(a == b) {}
/// ```
///
/// Use instead:
/// ```rust,ignore
/// if a {}
/// if a != b {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub NONMINIMAL_BOOL,
@ -48,10 +54,15 @@ declare_clippy_lint! {
/// Ignores short circuiting behavior.
///
/// ### Example
/// ```ignore
/// ```rust,ignore
/// // The `b` is unnecessary, the expression is equivalent to `if a`.
/// if a && b || a { ... }
/// ```
/// The `b` is unnecessary, the expression is equivalent to `if a`.
///
/// Use instead:
/// ```rust,ignore
/// if a {}
/// ```
#[clippy::version = "pre 1.29.0"]
pub LOGIC_BUG,
correctness,

View File

@ -0,0 +1,118 @@
use crate::reference::DEREF_ADDROF;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_parent_expr, is_lint_allowed};
use rustc_errors::Applicability;
use rustc_hir::{ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::Mutability;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for `&*(&T)`.
///
/// ### Why is this bad?
/// Dereferencing and then borrowing a reference value has no effect in most cases.
///
/// ### Known problems
/// false negative on such code:
/// ```
/// let x = &12;
/// let addr_x = &x as *const _ as usize;
/// let addr_y = &&*x as *const _ as usize; // assert ok now, and lint triggerd.
/// // But if we fix it, assert will fail.
/// assert_ne!(addr_x, addr_y);
/// ```
///
/// ### Example
/// ```rust
/// let s = &String::new();
///
/// // Bad
/// let a: &String = &* s;
/// foo(&*s);
///
/// // Good
/// let a: &String = s;
/// foo(&**s);
///
/// fn foo(_: &str){ }
/// ```
#[clippy::version = "1.59.0"]
pub BORROW_DEREF_REF,
complexity,
"deref on an immutable reference returns the same type as itself"
}
declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]);
impl LateLintPass<'_> for BorrowDerefRef {
fn check_expr(&mut self, cx: &LateContext<'_>, e: &rustc_hir::Expr<'_>) {
if_chain! {
if !e.span.from_expansion();
if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind;
if !addrof_target.span.from_expansion();
if let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind;
if !deref_target.span.from_expansion();
if !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..) );
let ref_ty = cx.typeck_results().expr_ty(deref_target);
if let ty::Ref(_, inner_ty, Mutability::Not) = ref_ty.kind();
then{
if let Some(parent_expr) = get_parent_expr(cx, e){
if matches!(parent_expr.kind, ExprKind::Unary(UnOp::Deref, ..)) &&
!is_lint_allowed(cx, DEREF_ADDROF, parent_expr.hir_id) {
return;
}
// modification to `&mut &*x` is different from `&mut x`
if matches!(deref_target.kind, ExprKind::Path(..)
| ExprKind::Field(..)
| ExprKind::Index(..)
| ExprKind::Unary(UnOp::Deref, ..))
&& matches!(parent_expr.kind, ExprKind::AddrOf(_, Mutability::Mut, _)) {
return;
}
}
span_lint_and_then(
cx,
BORROW_DEREF_REF,
e.span,
"deref on an immutable reference",
|diag| {
diag.span_suggestion(
e.span,
"if you would like to reborrow, try removing `&*`",
snippet_opt(cx, deref_target.span).unwrap(),
Applicability::MachineApplicable
);
// has deref trait -> give 2 help
// doesn't have deref trait -> give 1 help
if let Some(deref_trait_id) = cx.tcx.lang_items().deref_trait(){
if !implements_trait(cx, *inner_ty, deref_trait_id, &[]) {
return;
}
}
diag.span_suggestion(
e.span,
"if you would like to deref, try using `&**`",
format!(
"&**{}",
&snippet_opt(cx, deref_target.span).unwrap(),
),
Applicability::MaybeIncorrect
);
}
);
}
}
}
}

View File

@ -28,7 +28,13 @@ declare_clippy_lint! {
/// ### Example
/// ```rust
/// # let vec = vec![1_u8];
/// &vec.iter().filter(|x| **x == 0u8).count(); // use bytecount::count instead
/// let count = vec.iter().filter(|x| **x == 0u8).count();
/// ```
///
/// Use instead:
/// ```rust,ignore
/// # let vec = vec![1_u8];
/// let count = bytecount::count(&vec, 0u8);
/// ```
#[clippy::version = "pre 1.29.0"]
pub NAIVE_BYTECOUNT,

View File

@ -1,11 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{meets_msrv, msrvs};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Ty;
use rustc_middle::ty::{self, Ty};
use rustc_semver::RustcVersion;
use super::CAST_ABS_TO_UNSIGNED;
@ -18,25 +17,28 @@ pub(super) fn check(
cast_to: Ty<'_>,
msrv: Option<RustcVersion>,
) {
if_chain! {
if meets_msrv(msrv, msrvs::UNSIGNED_ABS);
if cast_from.is_integral();
if cast_to.is_integral();
if cast_from.is_signed();
if !cast_to.is_signed();
if let ExprKind::MethodCall(method_path, args, _) = cast_expr.kind;
if let method_name = method_path.ident.name.as_str();
if method_name == "abs";
then {
span_lint_and_sugg(
cx,
CAST_ABS_TO_UNSIGNED,
expr.span,
&format!("casting the result of `{}::{}()` to {}", cast_from, method_name, cast_to),
"replace with",
format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..")),
Applicability::MachineApplicable,
);
}
if meets_msrv(msrv, msrvs::UNSIGNED_ABS)
&& let ty::Int(from) = cast_from.kind()
&& let ty::Uint(to) = cast_to.kind()
&& let ExprKind::MethodCall(method_path, args, _) = cast_expr.kind
&& method_path.ident.name.as_str() == "abs"
{
let span = if from.bit_width() == to.bit_width() {
expr.span
} else {
// if the result of `.unsigned_abs` would be a different type, keep the cast
// e.g. `i64 -> usize`, `i16 -> u8`
cast_expr.span
};
span_lint_and_sugg(
cx,
CAST_ABS_TO_UNSIGNED,
span,
&format!("casting the result of `{cast_from}::abs()` to {cast_to}"),
"replace with",
format!("{}.unsigned_abs()", Sugg::hir(cx, &args[0], "..")),
Applicability::MachineApplicable,
);
}
}

View File

@ -318,7 +318,7 @@ fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function:
if let QPath::TypeRelative(ty, path) = &path;
if path.ident.name.as_str() == function;
if let TyKind::Path(QPath::Resolved(None, tp)) = &ty.kind;
if let [int] = &*tp.segments;
if let [int] = tp.segments;
then {
let name = int.ident.name.as_str();
candidates.iter().find(|c| &name == *c).copied()
@ -332,7 +332,7 @@ fn get_implementing_type<'a>(path: &QPath<'_>, candidates: &'a [&str], function:
fn int_ty_to_sym<'tcx>(path: &QPath<'_>) -> Option<&'tcx str> {
if_chain! {
if let QPath::Resolved(_, path) = *path;
if let [ty] = &*path.segments;
if let [ty] = path.segments;
then {
let name = ty.ident.name.as_str();
INTS.iter().find(|c| &name == *c).copied()

View File

@ -25,7 +25,7 @@ declare_clippy_lint! {
/// complexity.
///
/// ### Example
/// No. You'll see it when you get the warning.
/// You'll see it when you get the warning.
#[clippy::version = "1.35.0"]
pub COGNITIVE_COMPLEXITY,
nursery,

View File

@ -41,7 +41,7 @@ declare_clippy_lint! {
///
/// ```
///
/// Should be written:
/// Use instead:
///
/// ```rust,ignore
/// if x && y {

View File

@ -34,7 +34,7 @@ declare_clippy_lint! {
/// }
/// ```
///
/// Could be written:
/// Use instead:
///
/// ```rust,ignore
/// use std::cmp::Ordering;

View File

@ -141,7 +141,7 @@ declare_clippy_lint! {
/// };
/// ```
///
/// Could be written as:
/// Use instead:
/// ```ignore
/// println!("Hello World");
/// let foo = if … {

View File

@ -90,7 +90,7 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
while let Some(curr) = cursor.next() {
if_chain! {
if !prev_is_dollar;
if let Some(span) = is_crate_keyword(&curr);
if let Some(span) = is_crate_keyword(curr);
if let Some(next) = cursor.look_ahead(0);
if is_token(next, &TokenKind::ModSep);
then {
@ -103,7 +103,7 @@ fn contains_unhygienic_crate_reference(tts: &TokenStream) -> Option<Span> {
return span;
}
}
prev_is_dollar = is_token(&curr, &TokenKind::Dollar);
prev_is_dollar = is_token(curr, &TokenKind::Dollar);
}
None
}

View File

@ -5,7 +5,7 @@ use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;
declare_clippy_lint! {
@ -30,14 +30,27 @@ declare_clippy_lint! {
"`dbg!` macro is intended as a debugging tool"
}
declare_lint_pass!(DbgMacro => [DBG_MACRO]);
#[derive(Copy, Clone)]
pub struct DbgMacro {
allow_dbg_in_tests: bool,
}
impl_lint_pass!(DbgMacro => [DBG_MACRO]);
impl DbgMacro {
pub fn new(allow_dbg_in_tests: bool) -> Self {
DbgMacro { allow_dbg_in_tests }
}
}
impl LateLintPass<'_> for DbgMacro {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
if cx.tcx.is_diagnostic_item(sym::dbg_macro, macro_call.def_id) {
// we make an exception for test code
if is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id) {
// allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
if self.allow_dbg_in_tests
&& (is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id))
{
return;
}
let mut applicability = Applicability::MachineApplicable;

View File

@ -21,7 +21,7 @@ declare_clippy_lint! {
/// bar: bool
/// }
///
/// impl std::default::Default for Foo {
/// impl Default for Foo {
/// fn default() -> Self {
/// Self {
/// bar: false

View File

@ -1,16 +1,17 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::paths;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy};
use clippy_utils::{is_lint_allowed, match_def_path};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor};
use rustc_hir::{
BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, TraitRef, UnsafeSource, Unsafety,
self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource, Unsafety,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, Ty};
use rustc_middle::ty::subst::GenericArg;
use rustc_middle::ty::{self, BoundConstness, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate, TraitRef, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use rustc_span::sym;
@ -224,7 +225,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
fn check_hash_peq<'tcx>(
cx: &LateContext<'tcx>,
span: Span,
trait_ref: &TraitRef<'_>,
trait_ref: &hir::TraitRef<'_>,
ty: Ty<'tcx>,
hash_is_automatically_derived: bool,
) {
@ -277,7 +278,7 @@ fn check_hash_peq<'tcx>(
fn check_ord_partial_ord<'tcx>(
cx: &LateContext<'tcx>,
span: Span,
trait_ref: &TraitRef<'_>,
trait_ref: &hir::TraitRef<'_>,
ty: Ty<'tcx>,
ord_is_automatically_derived: bool,
) {
@ -328,7 +329,7 @@ fn check_ord_partial_ord<'tcx>(
}
/// Implementation of the `EXPL_IMPL_CLONE_ON_COPY` lint.
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
let clone_id = match cx.tcx.lang_items().clone_trait() {
Some(id) if trait_ref.trait_def_id() == Some(id) => id,
_ => return,
@ -378,7 +379,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &T
fn check_unsafe_derive_deserialize<'tcx>(
cx: &LateContext<'tcx>,
item: &Item<'_>,
trait_ref: &TraitRef<'_>,
trait_ref: &hir::TraitRef<'_>,
ty: Ty<'tcx>,
) {
fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool {
@ -455,13 +456,41 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> {
}
/// Implementation of the `DERIVE_PARTIAL_EQ_WITHOUT_EQ` lint.
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &TraitRef<'_>, ty: Ty<'tcx>) {
fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) {
if_chain! {
if let ty::Adt(adt, substs) = ty.kind();
if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq);
if let Some(peq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::PartialEq);
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id);
if !implements_trait(cx, ty, eq_trait_def_id, substs);
// New `ParamEnv` replacing `T: PartialEq` with `T: Eq`
let param_env = ParamEnv::new(
cx.tcx.mk_predicates(cx.param_env.caller_bounds().iter().map(|p| {
let kind = p.kind();
match kind.skip_binder() {
PredicateKind::Trait(p)
if p.trait_ref.def_id == peq_trait_def_id
&& p.trait_ref.substs.get(0) == p.trait_ref.substs.get(1)
&& matches!(p.trait_ref.self_ty().kind(), ty::Param(_))
&& p.constness == BoundConstness::NotConst
&& p.polarity == ImplPolarity::Positive =>
{
cx.tcx.mk_predicate(kind.rebind(PredicateKind::Trait(TraitPredicate {
trait_ref: TraitRef::new(
eq_trait_def_id,
cx.tcx.mk_substs([GenericArg::from(p.trait_ref.self_ty())].into_iter()),
),
constness: BoundConstness::NotConst,
polarity: ImplPolarity::Positive,
})))
},
_ => p,
}
})),
cx.param_env.reveal(),
cx.param_env.constness(),
);
if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, substs);
then {
// If all of our fields implement `Eq`, we can implement `Eq` too
for variant in adt.variants() {

View File

@ -0,0 +1,60 @@
use clippy_utils::diagnostics::span_lint;
use itertools::Itertools;
use rustc_ast::{AttrKind, Attribute};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Detects the syntax `['foo']` in documentation comments (notice quotes instead of backticks)
/// outside of code blocks
/// ### Why is this bad?
/// It is likely a typo when defining an intra-doc link
///
/// ### Example
/// ```rust
/// /// See also: ['foo']
/// fn bar() {}
/// ```
/// Use instead:
/// ```rust
/// /// See also: [`foo`]
/// fn bar() {}
/// ```
#[clippy::version = "1.60.0"]
pub DOC_LINK_WITH_QUOTES,
pedantic,
"possible typo for an intra-doc link"
}
declare_lint_pass!(DocLinkWithQuotes => [DOC_LINK_WITH_QUOTES]);
impl EarlyLintPass for DocLinkWithQuotes {
fn check_attribute(&mut self, ctx: &EarlyContext<'_>, attr: &Attribute) {
if let AttrKind::DocComment(_, symbol) = attr.kind {
if contains_quote_link(symbol.as_str()) {
span_lint(
ctx,
DOC_LINK_WITH_QUOTES,
attr.span,
"possible intra-doc link using quotes instead of backticks",
);
}
}
}
}
fn contains_quote_link(s: &str) -> bool {
let mut in_backticks = false;
let mut found_opening = false;
for c in s.chars().tuple_windows::<(char, char)>() {
match c {
('`', _) => in_backticks = !in_backticks,
('[', '\'') if !in_backticks => found_opening = true,
('\'', ']') if !in_backticks && found_opening => return true,
_ => {},
}
}
false
}

View File

@ -24,7 +24,7 @@ declare_clippy_lint! {
/// if x == y || x < y {}
/// ```
///
/// Could be written as:
/// Use instead:
///
/// ```rust
/// # let x = 1;

View File

@ -13,23 +13,21 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
/// // Bad
/// fn simple_double_parens() -> i32 {
/// ((0))
/// }
///
/// // Good
/// # fn foo(bar: usize) {}
/// foo((0));
/// ```
///
/// Use instead:
/// ```rust
/// fn simple_no_parens() -> i32 {
/// 0
/// }
///
/// // or
///
/// # fn foo(bar: usize) {}
/// // Bad
/// foo((0));
///
/// // Good
/// foo(0);
/// ```
#[clippy::version = "pre 1.29.0"]

View File

@ -22,15 +22,17 @@ declare_clippy_lint! {
/// ### Example
/// ```rust
/// # use std::time::Duration;
/// let dur = Duration::new(5, 0);
/// # let duration = Duration::new(5, 0);
/// let micros = duration.subsec_nanos() / 1_000;
/// let millis = duration.subsec_nanos() / 1_000_000;
/// ```
///
/// // Bad
/// let _micros = dur.subsec_nanos() / 1_000;
/// let _millis = dur.subsec_nanos() / 1_000_000;
///
/// // Good
/// let _micros = dur.subsec_micros();
/// let _millis = dur.subsec_millis();
/// Use instead:
/// ```rust
/// # use std::time::Duration;
/// # let duration = Duration::new(5, 0);
/// let micros = duration.subsec_micros();
/// let millis = duration.subsec_millis();
/// ```
#[clippy::version = "pre 1.29.0"]
pub DURATION_SUBSEC,

View File

@ -26,7 +26,7 @@ declare_clippy_lint! {
/// }
/// ```
///
/// Could be written:
/// Use instead:
///
/// ```rust
/// # fn a() {}

View File

@ -23,12 +23,11 @@ declare_clippy_lint! {
///
///
/// ### Example
/// Bad:
/// ```rust
/// enum Test {}
/// ```
///
/// Good:
/// Use instead:
/// ```rust
/// #![feature(never_type)]
///

View File

@ -46,7 +46,7 @@ declare_clippy_lint! {
/// map.insert(k, v);
/// }
/// ```
/// can both be rewritten as:
/// Use instead:
/// ```rust
/// # use std::collections::HashMap;
/// # let mut map = HashMap::new();

View File

@ -32,7 +32,7 @@ declare_clippy_lint! {
/// BattenbergCake,
/// }
/// ```
/// Could be written as:
/// Use instead:
/// ```rust
/// enum Cake {
/// BlackForest,

View File

@ -30,9 +30,9 @@ declare_clippy_lint! {
/// ```rust
/// # let x = 1;
/// if x + 1 == x + 1 {}
/// ```
/// or
/// ```rust
///
/// // or
///
/// # let a = 3;
/// # let b = 4;
/// assert_eq!(a, a);

View File

@ -26,7 +26,7 @@ declare_clippy_lint! {
/// do_thing();
/// }
/// ```
/// Should be written
/// Use instead:
/// ```rust,ignore
/// if x == Some(2) {
/// do_thing();

View File

@ -31,12 +31,14 @@ declare_clippy_lint! {
/// ### Example
/// ```rust
/// # fn foo(bar: usize) {}
/// // Bad
/// let x = Box::new(1);
/// foo(*x);
/// println!("{}", *x);
/// ```
///
/// // Good
/// Use instead:
/// ```rust
/// # fn foo(bar: usize) {}
/// let x = 1;
/// foo(x);
/// println!("{}", x);

View File

@ -18,7 +18,6 @@ declare_clippy_lint! {
/// readability and API.
///
/// ### Example
/// Bad:
/// ```rust
/// struct S {
/// is_pending: bool,
@ -27,7 +26,7 @@ declare_clippy_lint! {
/// }
/// ```
///
/// Good:
/// Use instead:
/// ```rust
/// enum S {
/// Pending,

View File

@ -22,8 +22,16 @@ declare_clippy_lint! {
/// ```rust
/// # use std::io::Write;
/// # let bar = "furchtbar";
/// // this would be clearer as `eprintln!("foo: {:?}", bar);`
/// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap();
/// writeln!(&mut std::io::stdout(), "foo: {:?}", bar).unwrap();
/// ```
///
/// Use instead:
/// ```rust
/// # use std::io::Write;
/// # let bar = "furchtbar";
/// eprintln!("foo: {:?}", bar);
/// println!("foo: {:?}", bar);
/// ```
#[clippy::version = "pre 1.29.0"]
pub EXPLICIT_WRITE,

View File

@ -20,7 +20,6 @@ declare_clippy_lint! {
/// ```rust
/// struct Foo(i32);
///
/// // Bad
/// impl From<String> for Foo {
/// fn from(s: String) -> Self {
/// Foo(s.parse().unwrap())
@ -28,8 +27,8 @@ declare_clippy_lint! {
/// }
/// ```
///
/// Use instead:
/// ```rust
/// // Good
/// struct Foo(i32);
///
/// impl TryFrom<String> for Foo {

View File

@ -19,11 +19,12 @@ declare_clippy_lint! {
///
/// ### Example
/// ```rust
/// // Bad
/// let v: f32 = 0.123_456_789_9;
/// println!("{}", v); // 0.123_456_789
/// ```
///
/// // Good
/// Use instead:
/// ```rust
/// let v: f64 = 0.123_456_789_9;
/// println!("{}", v); // 0.123_456_789_9
/// ```

View File

@ -35,8 +35,7 @@ declare_clippy_lint! {
/// let _ = a.exp() - 1.0;
/// ```
///
/// is better expressed as
///
/// Use instead:
/// ```rust
/// let a = 3f32;
/// let _ = a.cbrt();

View File

@ -25,12 +25,13 @@ declare_clippy_lint! {
///
/// ### Examples
/// ```rust
///
/// // Bad
/// let foo = "foo";
/// format!("{}", foo);
/// ```
///
/// // Good
/// Use instead:
/// ```rust
/// let foo = "foo";
/// foo.to_owned();
/// ```
#[clippy::version = "pre 1.29.0"]

View File

@ -36,12 +36,18 @@ declare_clippy_lint! {
/// This is either a typo in the binary operator or confusing.
///
/// ### Example
/// ```rust,ignore
/// if foo <- 30 { // this should be `foo < -30` but looks like a different operator
/// }
/// ```rust
/// # let foo = true;
/// # let bar = false;
/// // &&! looks like a different operator
/// if foo &&! bar {}
/// ```
///
/// if foo &&! bar { // this should be `foo && !bar` but looks like a different operator
/// }
/// Use instead:
/// ```rust
/// # let foo = true;
/// # let bar = false;
/// if foo && !bar {}
/// ```
#[clippy::version = "1.40.0"]
pub SUSPICIOUS_UNARY_OP_FORMATTING,

View File

@ -0,0 +1,69 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_slice_of_primitives, match_def_path, paths};
use if_chain::if_chain;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
declare_clippy_lint! {
/// ### What it does
/// Checks for using `x.get(0)` instead of
/// `x.first()`.
///
/// ### Why is this bad?
/// Using `x.first()` is easier to read and has the same
/// result.
///
/// ### Example
/// ```rust
/// // Bad
/// let x = vec![2, 3, 5];
/// let first_element = x.get(0);
/// ```
/// Use instead:
/// ```rust
/// // Good
/// let x = vec![2, 3, 5];
/// let first_element = x.first();
/// ```
#[clippy::version = "1.63.0"]
pub GET_FIRST,
style,
"Using `x.get(0)` when `x.first()` is simpler"
}
declare_lint_pass!(GetFirst => [GET_FIRST]);
impl<'tcx> LateLintPass<'tcx> for GetFirst {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if_chain! {
if let hir::ExprKind::MethodCall(_, [struct_calling_on, method_arg], _) = &expr.kind;
if let Some(expr_def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id);
if match_def_path(cx, expr_def_id, &paths::SLICE_GET);
if let Some(_) = is_slice_of_primitives(cx, struct_calling_on);
if let hir::ExprKind::Lit(Spanned { node: LitKind::Int(0, _), .. }) = method_arg.kind;
then {
let mut applicability = Applicability::MachineApplicable;
let slice_name = snippet_with_applicability(
cx,
struct_calling_on.span, "..",
&mut applicability,
);
span_lint_and_sugg(
cx,
GET_FIRST,
expr.span,
&format!("accessing first element with `{0}.get(0)`", slice_name),
"try",
format!("{}.first()", slice_name),
applicability,
);
}
}
}
}

View File

@ -1,107 +0,0 @@
//! lint on using `x.get(x.len() - 1)` instead of `x.last()`
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::SpanlessEq;
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for using `x.get(x.len() - 1)` instead of
/// `x.last()`.
///
/// ### Why is this bad?
/// Using `x.last()` is easier to read and has the same
/// result.
///
/// Note that using `x[x.len() - 1]` is semantically different from
/// `x.last()`. Indexing into the array will panic on out-of-bounds
/// accesses, while `x.get()` and `x.last()` will return `None`.
///
/// There is another lint (get_unwrap) that covers the case of using
/// `x.get(index).unwrap()` instead of `x[index]`.
///
/// ### Example
/// ```rust
/// // Bad
/// let x = vec![2, 3, 5];
/// let last_element = x.get(x.len() - 1);
///
/// // Good
/// let x = vec![2, 3, 5];
/// let last_element = x.last();
/// ```
#[clippy::version = "1.37.0"]
pub GET_LAST_WITH_LEN,
complexity,
"Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler"
}
declare_lint_pass!(GetLastWithLen => [GET_LAST_WITH_LEN]);
impl<'tcx> LateLintPass<'tcx> for GetLastWithLen {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
// Is a method call
if let ExprKind::MethodCall(path, args, _) = expr.kind;
// Method name is "get"
if path.ident.name == sym!(get);
// Argument 0 (the struct we're calling the method on) is a vector
if let Some(struct_calling_on) = args.get(0);
let struct_ty = cx.typeck_results().expr_ty(struct_calling_on);
if is_type_diagnostic_item(cx, struct_ty, sym::Vec);
// Argument to "get" is a subtraction
if let Some(get_index_arg) = args.get(1);
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub,
..
},
lhs,
rhs,
) = &get_index_arg.kind;
// LHS of subtraction is "x.len()"
if let ExprKind::MethodCall(arg_lhs_path, lhs_args, _) = &lhs.kind;
if arg_lhs_path.ident.name == sym::len;
if let Some(arg_lhs_struct) = lhs_args.get(0);
// The two vectors referenced (x in x.get(...) and in x.len())
if SpanlessEq::new(cx).eq_expr(struct_calling_on, arg_lhs_struct);
// RHS of subtraction is 1
if let ExprKind::Lit(rhs_lit) = &rhs.kind;
if let LitKind::Int(1, ..) = rhs_lit.node;
then {
let mut applicability = Applicability::MachineApplicable;
let vec_name = snippet_with_applicability(
cx,
struct_calling_on.span, "vec",
&mut applicability,
);
span_lint_and_sugg(
cx,
GET_LAST_WITH_LEN,
expr.span,
&format!("accessing last element with `{0}.get({0}.len() - 1)`", vec_name),
"try",
format!("{}.last()", vec_name),
applicability,
);
}
}
}
}

View File

@ -1,15 +1,14 @@
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind};
use clippy_utils::consts::{constant_full_int, constant_simple, Constant, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{clip, unsext};
use rustc_errors::Applicability;
use rustc_hir::{BinOp, BinOpKind, Expr, ExprKind, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Span;
use clippy_utils::consts::{constant_full_int, constant_simple, Constant, FullInt};
use clippy_utils::diagnostics::span_lint;
use clippy_utils::{clip, unsext};
declare_clippy_lint! {
/// ### What it does
/// Checks for identity operations, e.g., `x + 0`.
@ -23,11 +22,6 @@ declare_clippy_lint! {
/// # let x = 1;
/// x / 1 + 0 * 1 - 0 | 0;
/// ```
///
/// ### Known problems
/// False negatives: `f(0 + if b { 1 } else { 2 } + 3);` is reducible to
/// `f(if b { 1 } else { 2 } + 3);`. But the lint doesn't trigger for the code.
/// See [#8724](https://github.com/rust-lang/rust-clippy/issues/8724)
#[clippy::version = "pre 1.29.0"]
pub IDENTITY_OP,
complexity,
@ -45,31 +39,22 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
if !is_allowed(cx, *cmp, left, right) {
match cmp.node {
BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => {
if reducible_to_right(cx, expr, right) {
check(cx, left, 0, expr.span, right.span);
}
check(cx, right, 0, expr.span, left.span);
check(cx, left, 0, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, 0, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => {
check(cx, right, 0, expr.span, left.span);
check(cx, right, 0, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Mul => {
if reducible_to_right(cx, expr, right) {
check(cx, left, 1, expr.span, right.span);
}
check(cx, right, 1, expr.span, left.span);
check(cx, left, 1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, 1, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Div => check(cx, right, 1, expr.span, left.span),
BinOpKind::Div => check(cx, right, 1, expr.span, left.span, Parens::Unneeded),
BinOpKind::BitAnd => {
if reducible_to_right(cx, expr, right) {
check(cx, left, -1, expr.span, right.span);
}
check(cx, right, -1, expr.span, left.span);
},
BinOpKind::Rem => {
// Don't call reducible_to_right because N % N is always reducible to 1
check_remainder(cx, left, right, expr.span, left.span);
check(cx, left, -1, expr.span, right.span, needs_parenthesis(cx, expr, right));
check(cx, right, -1, expr.span, left.span, Parens::Unneeded);
},
BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span),
_ => (),
}
}
@ -77,24 +62,50 @@ impl<'tcx> LateLintPass<'tcx> for IdentityOp {
}
}
/// Checks if `left op ..right` can be actually reduced to `right`
/// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }`
/// cannot be reduced to `if b { 1 } else { 2 } + if b { 3 } else { 4 }`
/// See #8724
fn reducible_to_right(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> bool {
if let ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) = right.kind {
is_toplevel_binary(cx, binary)
} else {
true
}
#[derive(Copy, Clone)]
enum Parens {
Needed,
Unneeded,
}
fn is_toplevel_binary(cx: &LateContext<'_>, must_be_binary: &Expr<'_>) -> bool {
if let Some(parent) = get_parent_expr(cx, must_be_binary) && let ExprKind::Binary(..) = &parent.kind {
false
} else {
true
/// Checks if `left op right` needs parenthesis when reduced to `right`
/// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced
/// to `if b { 1 } else { 2 } + if b { 3 } else { 4 }` where the `if` could be
/// interpreted as a statement
///
/// See #8724
fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> Parens {
match right.kind {
ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => {
// ensure we're checking against the leftmost expression of `right`
//
// ~~~ `lhs`
// 0 + {4} * 2
// ~~~~~~~ `right`
return needs_parenthesis(cx, binary, lhs);
},
ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => {},
_ => return Parens::Unneeded,
}
let mut prev_id = binary.hir_id;
for (_, node) in cx.tcx.hir().parent_iter(binary.hir_id) {
if let Node::Expr(expr) = node
&& let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) = expr.kind
&& lhs.hir_id == prev_id
{
// keep going until we find a node that encompasses left of `binary`
prev_id = expr.hir_id;
continue;
}
match node {
Node::Block(_) | Node::Stmt(_) => break,
_ => return Parens::Unneeded,
};
}
Parens::Needed
}
fn is_allowed(cx: &LateContext<'_>, cmp: BinOp, left: &Expr<'_>, right: &Expr<'_>) -> bool {
@ -115,11 +126,11 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span
(Some(FullInt::U(lv)), Some(FullInt::U(rv))) => lv < rv,
_ => return,
} {
span_ineffective_operation(cx, span, arg);
span_ineffective_operation(cx, span, arg, Parens::Unneeded);
}
}
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens) {
if let Some(Constant::Int(v)) = constant_simple(cx, cx.typeck_results(), e).map(Constant::peel_refs) {
let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() {
ty::Int(ity) => unsext(cx.tcx, -1_i128, ity),
@ -132,19 +143,27 @@ fn check(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span) {
1 => v == 1,
_ => unreachable!(),
} {
span_ineffective_operation(cx, span, arg);
span_ineffective_operation(cx, span, arg, parens);
}
}
}
fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span) {
span_lint(
fn span_ineffective_operation(cx: &LateContext<'_>, span: Span, arg: Span, parens: Parens) {
let mut applicability = Applicability::MachineApplicable;
let expr_snippet = snippet_with_applicability(cx, arg, "..", &mut applicability);
let suggestion = match parens {
Parens::Needed => format!("({expr_snippet})"),
Parens::Unneeded => expr_snippet.into_owned(),
};
span_lint_and_sugg(
cx,
IDENTITY_OP,
span,
&format!(
"the operation is ineffective. Consider reducing it to `{}`",
snippet(cx, arg, "..")
),
"this operation has no effect",
"consider reducing it to",
suggestion,
applicability,
);
}

View File

@ -1,12 +1,13 @@
//! lint when there is a large size difference between variants on an enum
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{diagnostics::span_lint_and_then, ty::is_copy};
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::layout::LayoutOf;
use rustc_middle::ty::{Adt, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::source_map::Span;
@ -26,6 +27,15 @@ declare_clippy_lint! {
/// the overhead is negligible and the boxing is counter-productive. Always
/// measure the change this lint suggests.
///
/// For types that implement `Copy`, the suggestion to `Box` a variant's
/// data would require removing the trait impl. The types can of course
/// still be `Clone`, but that is worse ergonomically. Depending on the
/// use case it may be possible to store the large data in an auxillary
/// structure (e.g. Arena or ECS).
///
/// The lint will ignore generic types if the layout depends on the
/// generics, even if the size difference will be large anyway.
///
/// ### Example
/// ```rust
/// // Bad
@ -74,7 +84,7 @@ struct VariantInfo {
impl_lint_pass!(LargeEnumVariant => [LARGE_ENUM_VARIANT]);
impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item<'tcx>) {
if in_external_macro(cx.tcx.sess, item.span) {
return;
}
@ -132,37 +142,43 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
let fields = def.variants[variants_size[0].ind].data.fields();
variants_size[0].fields_size.sort_by(|a, b| (a.size.cmp(&b.size)));
let mut applicability = Applicability::MaybeIncorrect;
let sugg: Vec<(Span, String)> = variants_size[0]
.fields_size
.iter()
.rev()
.map_while(|val| {
if difference > self.maximum_size_difference_allowed {
difference = difference.saturating_sub(val.size);
Some((
fields[val.ind].ty.span,
format!(
"Box<{}>",
snippet_with_applicability(
cx,
fields[val.ind].ty.span,
"..",
&mut applicability
)
.into_owned()
),
))
} else {
None
}
})
.collect();
if is_copy(cx, ty) || maybe_copy(cx, ty) {
diag.span_note(
item.ident.span,
"boxing a variant would require the type no longer be `Copy`",
);
} else {
let sugg: Vec<(Span, String)> = variants_size[0]
.fields_size
.iter()
.rev()
.map_while(|val| {
if difference > self.maximum_size_difference_allowed {
difference = difference.saturating_sub(val.size);
Some((
fields[val.ind].ty.span,
format!(
"Box<{}>",
snippet_with_applicability(
cx,
fields[val.ind].ty.span,
"..",
&mut applicability
)
.into_owned()
),
))
} else {
None
}
})
.collect();
if !sugg.is_empty() {
diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect);
return;
if !sugg.is_empty() {
diag.multipart_suggestion(help_text, sugg, Applicability::MaybeIncorrect);
return;
}
}
diag.span_help(def.variants[variants_size[0].ind].span, help_text);
},
);
@ -170,3 +186,13 @@ impl<'tcx> LateLintPass<'tcx> for LargeEnumVariant {
}
}
}
fn maybe_copy<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
if let Adt(_def, substs) = ty.kind()
&& substs.types().next().is_some()
&& let Some(copy_trait) = cx.tcx.lang_items().copy_trait()
{
return cx.tcx.non_blanket_impls_for_ty(copy_trait, ty).next().is_some();
}
false
}

View File

@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for LetIfSeq {
if let hir::ExprKind::If(hir::Expr { kind: hir::ExprKind::DropTemps(cond), ..}, then, else_) = if_.kind;
if !is_local_used(cx, *cond, canonical_id);
if let hir::ExprKind::Block(then, _) = then.kind;
if let Some(value) = check_assign(cx, canonical_id, &*then);
if let Some(value) = check_assign(cx, canonical_id, then);
if !is_local_used(cx, value, canonical_id);
then {
let span = stmt.span.to(if_.span);

View File

@ -4,6 +4,7 @@
store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS),
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
LintId::of(approx_const::APPROX_CONSTANT),
LintId::of(assertions_on_constants::ASSERTIONS_ON_CONSTANTS),
LintId::of(assign_ops::ASSIGN_OP_PATTERN),
@ -24,6 +25,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(bool_assert_comparison::BOOL_ASSERT_COMPARISON),
LintId::of(booleans::LOGIC_BUG),
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(borrow_deref_ref::BORROW_DEREF_REF),
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
LintId::of(casts::CAST_ABS_TO_UNSIGNED),
LintId::of(casts::CAST_ENUM_CONSTRUCTOR),
@ -36,7 +38,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(casts::UNNECESSARY_CAST),
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
LintId::of(collapsible_if::COLLAPSIBLE_IF),
LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
LintId::of(comparison_chain::COMPARISON_CHAIN),
LintId::of(copies::IFS_SAME_COND),
LintId::of(copies::IF_SAME_THEN_ELSE),
@ -91,7 +92,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
LintId::of(functions::RESULT_UNIT_ERR),
LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
LintId::of(get_first::GET_FIRST),
LintId::of(identity_op::IDENTITY_OP),
LintId::of(if_let_mutex::IF_LET_MUTEX),
LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING),
@ -132,23 +133,25 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(main_recursion::MAIN_RECURSION),
LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(manual_bits::MANUAL_BITS),
LintId::of(manual_map::MANUAL_MAP),
LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(manual_strip::MANUAL_STRIP),
LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
LintId::of(map_clone::MAP_CLONE),
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
LintId::of(match_result_ok::MATCH_RESULT_OK),
LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
LintId::of(matches::COLLAPSIBLE_MATCH),
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
LintId::of(matches::MANUAL_MAP),
LintId::of(matches::MANUAL_UNWRAP_OR),
LintId::of(matches::MATCH_AS_REF),
LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
LintId::of(matches::MATCH_OVERLAPPING_ARM),
LintId::of(matches::MATCH_REF_PATS),
LintId::of(matches::MATCH_SINGLE_BINDING),
LintId::of(matches::MATCH_STR_CASE_MISMATCH),
LintId::of(matches::NEEDLESS_MATCH),
LintId::of(matches::REDUNDANT_PATTERN_MATCHING),
LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(matches::SINGLE_MATCH),
LintId::of(matches::WILDCARD_IN_OR_PATTERNS),
LintId::of(mem_replace::MEM_REPLACE_OPTION_WITH_NONE),
@ -166,6 +169,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::FILTER_MAP_IDENTITY),
LintId::of(methods::FILTER_NEXT),
LintId::of(methods::FLAT_MAP_IDENTITY),
LintId::of(methods::GET_LAST_WITH_LEN),
LintId::of(methods::INSPECT_FOR_EACH),
LintId::of(methods::INTO_ITER_ON_REF),
LintId::of(methods::IS_DIGIT_ASCII_RADIX),
@ -189,6 +193,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(methods::NEEDLESS_OPTION_TAKE),
LintId::of(methods::NEEDLESS_SPLITN),
LintId::of(methods::NEW_RET_NO_SELF),
LintId::of(methods::NO_EFFECT_REPLACE),
LintId::of(methods::OK_EXPECT),
LintId::of(methods::OPTION_AS_REF_DEREF),
LintId::of(methods::OPTION_FILTER_MAP),
@ -278,7 +283,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(self_assignment::SELF_ASSIGNMENT),
LintId::of(self_named_constructors::SELF_NAMED_CONSTRUCTORS),
LintId::of(serde_api::SERDE_API_MISUSE),
LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
LintId::of(size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT),
LintId::of(slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
@ -289,6 +293,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
LintId::of(swap::ALMOST_SWAPPED),
LintId::of(swap::MANUAL_SWAP),
LintId::of(swap_ptr_to_ref::SWAP_PTR_TO_REF),
LintId::of(tabs_in_doc_comments::TABS_IN_DOC_COMMENTS),
LintId::of(temporary_assignment::TEMPORARY_ASSIGNMENT),
LintId::of(to_digit_is_some::TO_DIGIT_IS_SOME),
@ -302,6 +307,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
LintId::of(transmute::UNSOUND_COLLECTION_TRANSMUTE),
LintId::of(transmute::USELESS_TRANSMUTE),
LintId::of(transmute::WRONG_TRANSMUTE),
LintId::of(transmuting_null::TRANSMUTING_NULL),
LintId::of(types::BORROWED_BOX),

View File

@ -5,6 +5,7 @@
store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec![
LintId::of(attrs::DEPRECATED_CFG_ATTR),
LintId::of(booleans::NONMINIMAL_BOOL),
LintId::of(borrow_deref_ref::BORROW_DEREF_REF),
LintId::of(bytes_count_to_len::BYTES_COUNT_TO_LEN),
LintId::of(casts::CHAR_LIT_AS_U8),
LintId::of(casts::UNNECESSARY_CAST),
@ -15,7 +16,6 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(explicit_write::EXPLICIT_WRITE),
LintId::of(format::USELESS_FORMAT),
LintId::of(functions::TOO_MANY_ARGUMENTS),
LintId::of(get_last_with_len::GET_LAST_WITH_LEN),
LintId::of(identity_op::IDENTITY_OP),
LintId::of(int_plus_one::INT_PLUS_ONE),
LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES),
@ -25,9 +25,9 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(loops::SINGLE_ELEMENT_LOOP),
LintId::of(loops::WHILE_LET_LOOP),
LintId::of(manual_strip::MANUAL_STRIP),
LintId::of(manual_unwrap_or::MANUAL_UNWRAP_OR),
LintId::of(map_unit_fn::OPTION_MAP_UNIT_FN),
LintId::of(map_unit_fn::RESULT_MAP_UNIT_FN),
LintId::of(matches::MANUAL_UNWRAP_OR),
LintId::of(matches::MATCH_AS_REF),
LintId::of(matches::MATCH_SINGLE_BINDING),
LintId::of(matches::NEEDLESS_MATCH),
@ -37,6 +37,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(methods::FILTER_MAP_IDENTITY),
LintId::of(methods::FILTER_NEXT),
LintId::of(methods::FLAT_MAP_IDENTITY),
LintId::of(methods::GET_LAST_WITH_LEN),
LintId::of(methods::INSPECT_FOR_EACH),
LintId::of(methods::ITER_COUNT),
LintId::of(methods::MANUAL_FILTER_MAP),
@ -90,6 +91,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec!
LintId::of(transmute::TRANSMUTE_INT_TO_FLOAT),
LintId::of(transmute::TRANSMUTE_NUM_TO_BYTES),
LintId::of(transmute::TRANSMUTE_PTR_TO_REF),
LintId::of(transmute::USELESS_TRANSMUTE),
LintId::of(types::BORROWED_BOX),
LintId::of(types::TYPE_COMPLEXITY),
LintId::of(types::VEC_BOX),

View File

@ -39,7 +39,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(loops::ITER_NEXT_LOOP),
LintId::of(loops::NEVER_LOOP),
LintId::of(loops::WHILE_IMMUTABLE_CONDITION),
LintId::of(match_str_case_mismatch::MATCH_STR_CASE_MISMATCH),
LintId::of(matches::MATCH_STR_CASE_MISMATCH),
LintId::of(mem_replace::MEM_REPLACE_WITH_UNINIT),
LintId::of(methods::CLONE_DOUBLE_REF),
LintId::of(methods::ITERATOR_STEP_BY_ZERO),

View File

@ -34,10 +34,10 @@ store.register_lints(&[
#[cfg(feature = "internal")]
utils::internal_lints::UNNECESSARY_SYMBOL_STR,
absurd_extreme_comparisons::ABSURD_EXTREME_COMPARISONS,
almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE,
approx_const::APPROX_CONSTANT,
arithmetic::FLOAT_ARITHMETIC,
arithmetic::INTEGER_ARITHMETIC,
as_conversions::AS_CONVERSIONS,
as_underscore::AS_UNDERSCORE,
asm_syntax::INLINE_ASM_X86_ATT_SYNTAX,
asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX,
assertions_on_constants::ASSERTIONS_ON_CONSTANTS,
@ -64,6 +64,7 @@ store.register_lints(&[
booleans::LOGIC_BUG,
booleans::NONMINIMAL_BOOL,
borrow_as_ptr::BORROW_AS_PTR,
borrow_deref_ref::BORROW_DEREF_REF,
bytecount::NAIVE_BYTECOUNT,
bytes_count_to_len::BYTES_COUNT_TO_LEN,
cargo::CARGO_COMMON_METADATA,
@ -93,7 +94,6 @@ store.register_lints(&[
cognitive_complexity::COGNITIVE_COMPLEXITY,
collapsible_if::COLLAPSIBLE_ELSE_IF,
collapsible_if::COLLAPSIBLE_IF,
collapsible_match::COLLAPSIBLE_MATCH,
comparison_chain::COMPARISON_CHAIN,
copies::BRANCHES_SHARING_CODE,
copies::IFS_SAME_COND,
@ -124,6 +124,7 @@ store.register_lints(&[
doc::MISSING_PANICS_DOC,
doc::MISSING_SAFETY_DOC,
doc::NEEDLESS_DOCTEST_MAIN,
doc_link_with_quotes::DOC_LINK_WITH_QUOTES,
double_comparison::DOUBLE_COMPARISONS,
double_parens::DOUBLE_PARENS,
drop_forget_ref::DROP_COPY,
@ -183,7 +184,7 @@ store.register_lints(&[
functions::TOO_MANY_ARGUMENTS,
functions::TOO_MANY_LINES,
future_not_send::FUTURE_NOT_SEND,
get_last_with_len::GET_LAST_WITH_LEN,
get_first::GET_FIRST,
identity_op::IDENTITY_OP,
if_let_mutex::IF_LET_MUTEX,
if_not_else::IF_NOT_ELSE,
@ -250,33 +251,36 @@ store.register_lints(&[
manual_assert::MANUAL_ASSERT,
manual_async_fn::MANUAL_ASYNC_FN,
manual_bits::MANUAL_BITS,
manual_map::MANUAL_MAP,
manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
manual_ok_or::MANUAL_OK_OR,
manual_strip::MANUAL_STRIP,
manual_unwrap_or::MANUAL_UNWRAP_OR,
map_clone::MAP_CLONE,
map_err_ignore::MAP_ERR_IGNORE,
map_unit_fn::OPTION_MAP_UNIT_FN,
map_unit_fn::RESULT_MAP_UNIT_FN,
match_on_vec_items::MATCH_ON_VEC_ITEMS,
match_result_ok::MATCH_RESULT_OK,
match_str_case_mismatch::MATCH_STR_CASE_MISMATCH,
matches::COLLAPSIBLE_MATCH,
matches::INFALLIBLE_DESTRUCTURING_MATCH,
matches::MANUAL_MAP,
matches::MANUAL_UNWRAP_OR,
matches::MATCH_AS_REF,
matches::MATCH_BOOL,
matches::MATCH_LIKE_MATCHES_MACRO,
matches::MATCH_ON_VEC_ITEMS,
matches::MATCH_OVERLAPPING_ARM,
matches::MATCH_REF_PATS,
matches::MATCH_SAME_ARMS,
matches::MATCH_SINGLE_BINDING,
matches::MATCH_STR_CASE_MISMATCH,
matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
matches::MATCH_WILD_ERR_ARM,
matches::NEEDLESS_MATCH,
matches::REDUNDANT_PATTERN_MATCHING,
matches::REST_PAT_IN_FULLY_BOUND_STRUCTS,
matches::SIGNIFICANT_DROP_IN_SCRUTINEE,
matches::SINGLE_MATCH,
matches::SINGLE_MATCH_ELSE,
matches::TRY_ERR,
matches::WILDCARD_ENUM_MATCH_ARM,
matches::WILDCARD_IN_OR_PATTERNS,
mem_forget::MEM_FORGET,
@ -302,6 +306,7 @@ store.register_lints(&[
methods::FLAT_MAP_IDENTITY,
methods::FLAT_MAP_OPTION,
methods::FROM_ITER_INSTEAD_OF_COLLECT,
methods::GET_LAST_WITH_LEN,
methods::GET_UNWRAP,
methods::IMPLICIT_CLONE,
methods::INEFFICIENT_TO_STRING,
@ -330,6 +335,7 @@ store.register_lints(&[
methods::NEEDLESS_OPTION_TAKE,
methods::NEEDLESS_SPLITN,
methods::NEW_RET_NO_SELF,
methods::NO_EFFECT_REPLACE,
methods::OK_EXPECT,
methods::OPTION_AS_REF_DEREF,
methods::OPTION_FILTER_MAP,
@ -377,6 +383,7 @@ store.register_lints(&[
misc_early::UNNEEDED_WILDCARD_PATTERN,
misc_early::UNSEPARATED_LITERAL_SUFFIX,
misc_early::ZERO_PREFIXED_LITERAL,
mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER,
missing_const_for_fn::MISSING_CONST_FOR_FN,
missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS,
missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES,
@ -418,6 +425,8 @@ store.register_lints(&[
non_octal_unix_permissions::NON_OCTAL_UNIX_PERMISSIONS,
non_send_fields_in_send_ty::NON_SEND_FIELDS_IN_SEND_TY,
nonstandard_macro_braces::NONSTANDARD_MACRO_BRACES,
numeric_arithmetic::FLOAT_ARITHMETIC,
numeric_arithmetic::INTEGER_ARITHMETIC,
octal_escapes::OCTAL_ESCAPES,
only_used_in_recursion::ONLY_USED_IN_RECURSION,
open_options::NONSENSICAL_OPEN_OPTIONS,
@ -473,7 +482,6 @@ store.register_lints(&[
shadow::SHADOW_REUSE,
shadow::SHADOW_SAME,
shadow::SHADOW_UNRELATED,
significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE,
single_char_lifetime_names::SINGLE_CHAR_LIFETIME_NAMES,
single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS,
size_of_in_element_count::SIZE_OF_IN_ELEMENT_COUNT,
@ -493,6 +501,7 @@ store.register_lints(&[
suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
swap::ALMOST_SWAPPED,
swap::MANUAL_SWAP,
swap_ptr_to_ref::SWAP_PTR_TO_REF,
tabs_in_doc_comments::TABS_IN_DOC_COMMENTS,
temporary_assignment::TEMPORARY_ASSIGNMENT,
to_digit_is_some::TO_DIGIT_IS_SOME,
@ -514,7 +523,6 @@ store.register_lints(&[
transmute::USELESS_TRANSMUTE,
transmute::WRONG_TRANSMUTE,
transmuting_null::TRANSMUTING_NULL,
try_err::TRY_ERR,
types::BORROWED_BOX,
types::BOX_COLLECTION,
types::LINKEDLIST,
@ -544,6 +552,7 @@ store.register_lints(&[
unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME,
unused_async::UNUSED_ASYNC,
unused_io_amount::UNUSED_IO_AMOUNT,
unused_rounding::UNUSED_ROUNDING,
unused_self::UNUSED_SELF,
unused_unit::UNUSED_UNIT,
unwrap::PANICKING_UNWRAP,

View File

@ -25,11 +25,10 @@ store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE),
LintId::of(redundant_pub_crate::REDUNDANT_PUB_CRATE),
LintId::of(regex::TRIVIAL_REGEX),
LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(strings::STRING_LIT_AS_BYTES),
LintId::of(suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS),
LintId::of(trailing_empty_array::TRAILING_EMPTY_ARRAY),
LintId::of(transmute::TRANSMUTE_UNDEFINED_REPR),
LintId::of(transmute::USELESS_TRANSMUTE),
LintId::of(unused_rounding::UNUSED_ROUNDING),
LintId::of(use_self::USE_SELF),
])

View File

@ -26,6 +26,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(doc::DOC_MARKDOWN),
LintId::of(doc::MISSING_ERRORS_DOC),
LintId::of(doc::MISSING_PANICS_DOC),
LintId::of(doc_link_with_quotes::DOC_LINK_WITH_QUOTES),
LintId::of(empty_enum::EMPTY_ENUM),
LintId::of(enum_variants::MODULE_NAME_REPETITIONS),
LintId::of(eta_reduction::REDUNDANT_CLOSURE_FOR_METHOD_CALLS),
@ -50,8 +51,8 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(macro_use::MACRO_USE_IMPORTS),
LintId::of(manual_assert::MANUAL_ASSERT),
LintId::of(manual_ok_or::MANUAL_OK_OR),
LintId::of(match_on_vec_items::MATCH_ON_VEC_ITEMS),
LintId::of(matches::MATCH_BOOL),
LintId::of(matches::MATCH_ON_VEC_ITEMS),
LintId::of(matches::MATCH_SAME_ARMS),
LintId::of(matches::MATCH_WILDCARD_FOR_SINGLE_VARIANTS),
LintId::of(matches::MATCH_WILD_ERR_ARM),
@ -66,6 +67,7 @@ store.register_group(true, "clippy::pedantic", Some("clippy_pedantic"), vec![
LintId::of(methods::UNNECESSARY_JOIN),
LintId::of(misc::FLOAT_CMP),
LintId::of(misc::USED_UNDERSCORE_BINDING),
LintId::of(mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER),
LintId::of(mut_mut::MUT_MUT),
LintId::of(needless_bitwise_bool::NEEDLESS_BITWISE_BOOL),
LintId::of(needless_continue::NEEDLESS_CONTINUE),

View File

@ -3,9 +3,8 @@
// Manual edits will be overwritten.
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(arithmetic::FLOAT_ARITHMETIC),
LintId::of(arithmetic::INTEGER_ARITHMETIC),
LintId::of(as_conversions::AS_CONVERSIONS),
LintId::of(as_underscore::AS_UNDERSCORE),
LintId::of(asm_syntax::INLINE_ASM_X86_ATT_SYNTAX),
LintId::of(asm_syntax::INLINE_ASM_X86_INTEL_SYNTAX),
LintId::of(attrs::ALLOW_ATTRIBUTES_WITHOUT_REASON),
@ -32,6 +31,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(literal_representation::DECIMAL_LITERAL_REPRESENTATION),
LintId::of(map_err_ignore::MAP_ERR_IGNORE),
LintId::of(matches::REST_PAT_IN_FULLY_BOUND_STRUCTS),
LintId::of(matches::TRY_ERR),
LintId::of(matches::WILDCARD_ENUM_MATCH_ARM),
LintId::of(mem_forget::MEM_FORGET),
LintId::of(methods::CLONE_ON_REF_PTR),
@ -50,6 +50,8 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(module_style::MOD_MODULE_FILES),
LintId::of(module_style::SELF_NAMED_MODULE_FILES),
LintId::of(modulo_arithmetic::MODULO_ARITHMETIC),
LintId::of(numeric_arithmetic::FLOAT_ARITHMETIC),
LintId::of(numeric_arithmetic::INTEGER_ARITHMETIC),
LintId::of(panic_in_result_fn::PANIC_IN_RESULT_FN),
LintId::of(panic_unimplemented::PANIC),
LintId::of(panic_unimplemented::TODO),
@ -67,7 +69,6 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
LintId::of(strings::STRING_SLICE),
LintId::of(strings::STRING_TO_STRING),
LintId::of(strings::STR_TO_STRING),
LintId::of(try_err::TRY_ERR),
LintId::of(types::RC_BUFFER),
LintId::of(types::RC_MUTEX),
LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS),

View File

@ -12,7 +12,6 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(casts::FN_TO_NUMERIC_CAST_WITH_TRUNCATION),
LintId::of(collapsible_if::COLLAPSIBLE_ELSE_IF),
LintId::of(collapsible_if::COLLAPSIBLE_IF),
LintId::of(collapsible_match::COLLAPSIBLE_MATCH),
LintId::of(comparison_chain::COMPARISON_CHAIN),
LintId::of(default::FIELD_REASSIGN_WITH_DEFAULT),
LintId::of(dereference::NEEDLESS_BORROW),
@ -31,6 +30,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(functions::DOUBLE_MUST_USE),
LintId::of(functions::MUST_USE_UNIT),
LintId::of(functions::RESULT_UNIT_ERR),
LintId::of(get_first::GET_FIRST),
LintId::of(inherent_to_string::INHERENT_TO_STRING),
LintId::of(init_numbered_fields::INIT_NUMBERED_FIELDS),
LintId::of(len_zero::COMPARISON_TO_EMPTY),
@ -45,11 +45,12 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![
LintId::of(main_recursion::MAIN_RECURSION),
LintId::of(manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(manual_bits::MANUAL_BITS),
LintId::of(manual_map::MANUAL_MAP),
LintId::of(manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(map_clone::MAP_CLONE),
LintId::of(match_result_ok::MATCH_RESULT_OK),
LintId::of(matches::COLLAPSIBLE_MATCH),
LintId::of(matches::INFALLIBLE_DESTRUCTURING_MATCH),
LintId::of(matches::MANUAL_MAP),
LintId::of(matches::MATCH_LIKE_MATCHES_MACRO),
LintId::of(matches::MATCH_OVERLAPPING_ARM),
LintId::of(matches::MATCH_REF_PATS),

View File

@ -3,6 +3,7 @@
// Manual edits will be overwritten.
store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec![
LintId::of(almost_complete_letter_range::ALMOST_COMPLETE_LETTER_RANGE),
LintId::of(assign_ops::MISREFACTORED_ASSIGN_OP),
LintId::of(attrs::BLANKET_CLIPPY_RESTRICTION_LINTS),
LintId::of(await_holding_invalid::AWAIT_HOLDING_INVALID_TYPE),
@ -23,11 +24,13 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
LintId::of(loops::EMPTY_LOOP),
LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES),
LintId::of(loops::MUT_RANGE_BOUND),
LintId::of(matches::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(methods::NO_EFFECT_REPLACE),
LintId::of(methods::SUSPICIOUS_MAP),
LintId::of(mut_key::MUTABLE_KEY_TYPE),
LintId::of(octal_escapes::OCTAL_ESCAPES),
LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
LintId::of(significant_drop_in_scrutinee::SIGNIFICANT_DROP_IN_SCRUTINEE),
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
LintId::of(swap_ptr_to_ref::SWAP_PTR_TO_REF),
])

View File

@ -168,9 +168,10 @@ mod renamed_lints;
// begin lints modules, do not remove this comment, its used in `update_lints`
mod absurd_extreme_comparisons;
mod almost_complete_letter_range;
mod approx_const;
mod arithmetic;
mod as_conversions;
mod as_underscore;
mod asm_syntax;
mod assertions_on_constants;
mod assign_ops;
@ -183,6 +184,7 @@ mod blocks_in_if_conditions;
mod bool_assert_comparison;
mod booleans;
mod borrow_as_ptr;
mod borrow_deref_ref;
mod bytecount;
mod bytes_count_to_len;
mod cargo;
@ -191,7 +193,6 @@ mod casts;
mod checked_conversions;
mod cognitive_complexity;
mod collapsible_if;
mod collapsible_match;
mod comparison_chain;
mod copies;
mod copy_iterator;
@ -208,6 +209,7 @@ mod disallowed_methods;
mod disallowed_script_idents;
mod disallowed_types;
mod doc;
mod doc_link_with_quotes;
mod double_comparison;
mod double_parens;
mod drop_forget_ref;
@ -242,7 +244,7 @@ mod from_over_into;
mod from_str_radix_10;
mod functions;
mod future_not_send;
mod get_last_with_len;
mod get_first;
mod identity_op;
mod if_let_mutex;
mod if_not_else;
@ -278,17 +280,13 @@ mod main_recursion;
mod manual_assert;
mod manual_async_fn;
mod manual_bits;
mod manual_map;
mod manual_non_exhaustive;
mod manual_ok_or;
mod manual_strip;
mod manual_unwrap_or;
mod map_clone;
mod map_err_ignore;
mod map_unit_fn;
mod match_on_vec_items;
mod match_result_ok;
mod match_str_case_mismatch;
mod matches;
mod mem_forget;
mod mem_replace;
@ -296,6 +294,7 @@ mod methods;
mod minmax;
mod misc;
mod misc_early;
mod mismatching_type_param_order;
mod missing_const_for_fn;
mod missing_doc;
mod missing_enforced_import_rename;
@ -328,6 +327,7 @@ mod non_expressive_names;
mod non_octal_unix_permissions;
mod non_send_fields_in_send_ty;
mod nonstandard_macro_braces;
mod numeric_arithmetic;
mod octal_escapes;
mod only_used_in_recursion;
mod open_options;
@ -367,7 +367,6 @@ mod self_named_constructors;
mod semicolon_if_nothing_returned;
mod serde_api;
mod shadow;
mod significant_drop_in_scrutinee;
mod single_char_lifetime_names;
mod single_component_path_imports;
mod size_of_in_element_count;
@ -378,6 +377,7 @@ mod strlen_on_c_strings;
mod suspicious_operation_groupings;
mod suspicious_trait_impl;
mod swap;
mod swap_ptr_to_ref;
mod tabs_in_doc_comments;
mod temporary_assignment;
mod to_digit_is_some;
@ -385,7 +385,6 @@ mod trailing_empty_array;
mod trait_bounds;
mod transmute;
mod transmuting_null;
mod try_err;
mod types;
mod undocumented_unsafe_blocks;
mod unicode;
@ -402,6 +401,7 @@ mod unnested_or_patterns;
mod unsafe_removed_from_name;
mod unused_async;
mod unused_io_amount;
mod unused_rounding;
mod unused_self;
mod unused_unit;
mod unwrap;
@ -562,7 +562,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(len_zero::LenZero));
store.register_late_pass(|| Box::new(attrs::Attributes));
store.register_late_pass(|| Box::new(blocks_in_if_conditions::BlocksInIfConditions));
store.register_late_pass(|| Box::new(collapsible_match::CollapsibleMatch));
store.register_late_pass(|| Box::new(unicode::Unicode));
store.register_late_pass(|| Box::new(uninit_vec::UninitVec));
store.register_late_pass(|| Box::new(unit_hash::UnitHash));
@ -636,6 +635,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(mutex_atomic::Mutex));
store.register_late_pass(|| Box::new(needless_update::NeedlessUpdate));
store.register_late_pass(|| Box::new(needless_borrowed_ref::NeedlessBorrowedRef));
store.register_late_pass(|| Box::new(borrow_deref_ref::BorrowDerefRef));
store.register_late_pass(|| Box::new(no_effect::NoEffect));
store.register_late_pass(|| Box::new(temporary_assignment::TemporaryAssignment));
store.register_late_pass(|| Box::new(transmute::Transmute));
@ -652,7 +652,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(strings::StringLitAsBytes));
store.register_late_pass(|| Box::new(derive::Derive));
store.register_late_pass(|| Box::new(derivable_impls::DerivableImpls));
store.register_late_pass(|| Box::new(get_last_with_len::GetLastWithLen));
store.register_late_pass(|| Box::new(drop_forget_ref::DropForgetRef));
store.register_late_pass(|| Box::new(empty_enum::EmptyEnum));
store.register_late_pass(|| Box::new(absurd_extreme_comparisons::AbsurdExtremeComparisons));
@ -678,7 +677,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(doc::DocMarkdown::new(doc_valid_idents.clone())));
store.register_late_pass(|| Box::new(neg_multiply::NegMultiply));
store.register_late_pass(|| Box::new(mem_forget::MemForget));
store.register_late_pass(|| Box::new(arithmetic::Arithmetic::default()));
store.register_late_pass(|| Box::new(numeric_arithmetic::NumericArithmetic::default()));
store.register_late_pass(|| Box::new(assign_ops::AssignOps));
store.register_late_pass(|| Box::new(let_if_seq::LetIfSeq));
store.register_late_pass(|| Box::new(mixed_read_write_in_expression::EvalOrderDependence));
@ -700,7 +699,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
);
store.register_late_pass(move || Box::new(pass_by_ref_or_value));
store.register_late_pass(|| Box::new(ref_option_ref::RefOptionRef));
store.register_late_pass(|| Box::new(try_err::TryErr));
store.register_late_pass(|| Box::new(bytecount::ByteCount));
store.register_late_pass(|| Box::new(infinite_iter::InfiniteIter));
store.register_late_pass(|| Box::new(inline_fn_without_body::InlineFnWithoutBody));
@ -812,7 +810,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(if_not_else::IfNotElse));
store.register_late_pass(|| Box::new(equatable_if_let::PatternEquality));
store.register_late_pass(|| Box::new(mut_mutex_lock::MutMutexLock));
store.register_late_pass(|| Box::new(match_on_vec_items::MatchOnVecItems));
store.register_late_pass(|| Box::new(manual_async_fn::ManualAsyncFn));
store.register_late_pass(|| Box::new(vec_resize_to_zero::VecResizeToZero));
store.register_late_pass(|| Box::new(panic_in_result_fn::PanicInResultFn));
@ -830,7 +827,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(repeat_once::RepeatOnce));
store.register_late_pass(|| Box::new(unwrap_in_result::UnwrapInResult));
store.register_late_pass(|| Box::new(self_assignment::SelfAssignment));
store.register_late_pass(|| Box::new(manual_unwrap_or::ManualUnwrapOr));
store.register_late_pass(|| Box::new(manual_ok_or::ManualOkOr));
store.register_late_pass(|| Box::new(float_equality_without_abs::FloatEqualityWithoutAbs));
store.register_late_pass(|| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned));
@ -849,7 +845,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
});
store.register_late_pass(|| Box::new(redundant_slicing::RedundantSlicing));
store.register_late_pass(|| Box::new(from_str_radix_10::FromStrRadix10));
store.register_late_pass(|| Box::new(manual_map::ManualMap));
store.register_late_pass(move || Box::new(if_then_some_else_none::IfThenSomeElseNone::new(msrv)));
store.register_late_pass(|| Box::new(bool_assert_comparison::BoolAssertComparison));
store.register_early_pass(move || Box::new(module_style::ModStyle));
@ -875,7 +870,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks));
store.register_late_pass(|| Box::new(match_str_case_mismatch::MatchStrCaseMismatch));
store.register_late_pass(move || Box::new(format_args::FormatArgs));
store.register_late_pass(|| Box::new(trailing_empty_array::TrailingEmptyArray));
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
@ -886,9 +880,10 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move || Box::new(borrow_as_ptr::BorrowAsPtr::new(msrv)));
store.register_late_pass(move || Box::new(manual_bits::ManualBits::new(msrv)));
store.register_late_pass(|| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_early_pass(|| Box::new(doc_link_with_quotes::DocLinkWithQuotes));
store.register_late_pass(|| Box::new(only_used_in_recursion::OnlyUsedInRecursion));
store.register_late_pass(|| Box::new(significant_drop_in_scrutinee::SignificantDropInScrutinee));
store.register_late_pass(|| Box::new(dbg_macro::DbgMacro));
let allow_dbg_in_tests = conf.allow_dbg_in_tests;
store.register_late_pass(move || Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests)));
let cargo_ignore_publish = conf.cargo_ignore_publish;
store.register_late_pass(move || {
Box::new(cargo::Cargo {
@ -906,6 +901,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(strings::TrimSplitWhitespace));
store.register_late_pass(|| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default()));
store.register_late_pass(|| Box::new(get_first::GetFirst));
store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding));
store.register_early_pass(move || Box::new(almost_complete_letter_range::AlmostCompleteLetterRange::new(msrv)));
store.register_late_pass(|| Box::new(swap_ptr_to_ref::SwapPtrToRef));
store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch));
store.register_late_pass(|| Box::new(as_underscore::AsUnderscore));
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -180,29 +180,24 @@ declare_clippy_lint! {
/// ### Example
/// ```rust
/// # let opt = Some(1);
///
/// // Bad
/// # let res: Result<i32, std::io::Error> = Ok(1);
/// for x in opt {
/// // ..
/// }
///
/// // Good
/// if let Some(x) = opt {
/// for x in &res {
/// // ..
/// }
/// ```
///
/// // or
///
/// Use instead:
/// ```rust
/// # let opt = Some(1);
/// # let res: Result<i32, std::io::Error> = Ok(1);
///
/// // Bad
/// for x in &res {
/// if let Some(x) = opt {
/// // ..
/// }
///
/// // Good
/// if let Ok(x) = res {
/// // ..
/// }

View File

@ -3,9 +3,7 @@ use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
use clippy_utils::source::snippet;
use clippy_utils::ty::has_iter_method;
use clippy_utils::visitors::is_local_used;
use clippy_utils::{
contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq,
};
use clippy_utils::{contains_name, higher, is_integer_const, match_trait_method, paths, sugg, SpanlessEq};
use if_chain::if_chain;
use rustc_ast::ast;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
@ -29,7 +27,12 @@ pub(super) fn check<'tcx>(
body: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>,
) {
if let Some(higher::Range { start: Some(start), ref end, limits }) = higher::Range::hir(arg) {
if let Some(higher::Range {
start: Some(start),
ref end,
limits,
}) = higher::Range::hir(arg)
{
// the var must be a single name
if let PatKind::Binding(_, canonical_id, ident, _) = pat.kind {
let mut visitor = VarVisitor {
@ -104,22 +107,19 @@ pub(super) fn check<'tcx>(
}
}
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty)
{
if is_len_call(end, indexed) || is_end_eq_array_len(cx, end, limits, indexed_ty) {
String::new()
} else if visitor.indexed_mut.contains(&indexed)
&& contains_name(indexed, take_expr)
{
} else if visitor.indexed_mut.contains(&indexed) && contains_name(indexed, take_expr) {
return;
} else {
match limits {
ast::RangeLimits::Closed => {
let take_expr = sugg::Sugg::hir(cx, take_expr, "<count>");
format!(".take({})", take_expr + sugg::ONE)
}
},
ast::RangeLimits::HalfOpen => {
format!(".take({})", snippet(cx, take_expr.span, ".."))
}
},
}
}
} else {
@ -145,10 +145,7 @@ pub(super) fn check<'tcx>(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
&format!(
"the loop variable `{}` is used to index `{}`",
ident.name, indexed
),
&format!("the loop variable `{}` is used to index `{}`", ident.name, indexed),
|diag| {
multispan_sugg(
diag,
@ -157,10 +154,7 @@ pub(super) fn check<'tcx>(
(pat.span, format!("({}, <item>)", ident.name)),
(
arg.span,
format!(
"{}.{}().enumerate(){}{}",
indexed, method, method_1, method_2
),
format!("{}.{}().enumerate(){}{}", indexed, method, method_1, method_2),
),
],
);
@ -177,10 +171,7 @@ pub(super) fn check<'tcx>(
cx,
NEEDLESS_RANGE_LOOP,
arg.span,
&format!(
"the loop variable `{}` is only used to index `{}`",
ident.name, indexed
),
&format!("the loop variable `{}` is only used to index `{}`", ident.name, indexed),
|diag| {
multispan_sugg(
diag,
@ -257,12 +248,7 @@ struct VarVisitor<'a, 'tcx> {
}
impl<'a, 'tcx> VarVisitor<'a, 'tcx> {
fn check(
&mut self,
idx: &'tcx Expr<'_>,
seqexpr: &'tcx Expr<'_>,
expr: &'tcx Expr<'_>,
) -> bool {
fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool {
if_chain! {
// the indexed container is referenced by a name
if let ExprKind::Path(ref seqpath) = seqexpr.kind;
@ -351,13 +337,13 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
self.visit_expr(lhs);
self.prefer_mutable = false;
self.visit_expr(rhs);
}
},
ExprKind::AddrOf(BorrowKind::Ref, mutbl, expr) => {
if mutbl == Mutability::Mut {
self.prefer_mutable = true;
}
self.visit_expr(expr);
}
},
ExprKind::Call(f, args) => {
self.visit_expr(f);
for expr in args {
@ -370,11 +356,10 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
}
self.visit_expr(expr);
}
}
},
ExprKind::MethodCall(_, args, _) => {
let def_id = self.cx.typeck_results().type_dependent_def_id(expr.hir_id).unwrap();
for (ty, expr) in iter::zip(self.cx.tcx.fn_sig(def_id).inputs().skip_binder(), args)
{
for (ty, expr) in iter::zip(self.cx.tcx.fn_sig(def_id).inputs().skip_binder(), args) {
self.prefer_mutable = false;
if let ty::Ref(_, _, mutbl) = *ty.kind() {
if mutbl == Mutability::Mut {
@ -383,11 +368,11 @@ impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> {
}
self.visit_expr(expr);
}
}
},
ExprKind::Closure(_, _, body_id, ..) => {
let body = self.cx.tcx.hir().body(body_id);
self.visit_expr(&body.value);
}
},
_ => walk_expr(self, expr),
}
self.prefer_mutable = old;

View File

@ -146,7 +146,7 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult {
if arms.is_empty() {
e
} else {
let arms = never_loop_expr_branch(&mut arms.iter().map(|a| &*a.body), main_loop_id);
let arms = never_loop_expr_branch(&mut arms.iter().map(|a| a.body), main_loop_id);
combine_seq(e, arms)
}
},

View File

@ -1,316 +0,0 @@
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher::IfLetOrMatch;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
use clippy_utils::{
can_move_expr_to_closure, in_constant, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id,
peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{
def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path,
QPath, UnsafeSource,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::{sym, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
/// Checks for usages of `match` which could be implemented using `map`
///
/// ### Why is this bad?
/// Using the `map` method is clearer and more concise.
///
/// ### Example
/// ```rust
/// match Some(0) {
/// Some(x) => Some(x + 1),
/// None => None,
/// };
/// ```
/// Use instead:
/// ```rust
/// Some(0).map(|x| x + 1);
/// ```
#[clippy::version = "1.52.0"]
pub MANUAL_MAP,
style,
"reimplementation of `map`"
}
declare_lint_pass!(ManualMap => [MANUAL_MAP]);
impl<'tcx> LateLintPass<'tcx> for ManualMap {
#[expect(clippy::too_many_lines)]
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (scrutinee, then_pat, then_body, else_pat, else_body) = match IfLetOrMatch::parse(cx, expr) {
Some(IfLetOrMatch::IfLet(scrutinee, pat, body, Some(r#else))) => (scrutinee, pat, body, None, r#else),
Some(IfLetOrMatch::Match(
scrutinee,
[arm1 @ Arm { guard: None, .. }, arm2 @ Arm { guard: None, .. }],
_,
)) => (scrutinee, arm1.pat, arm1.body, Some(arm2.pat), arm2.body),
_ => return,
};
if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
return;
}
let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
{
return;
}
let expr_ctxt = expr.span.ctxt();
let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
try_parse_pattern(cx, then_pat, expr_ctxt),
else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr, false, expr_ctxt) {
Some(expr) => expr,
None => return,
};
// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
&& !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
match can_move_expr_to_closure(cx, some_expr.expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
}
},
None => return,
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
// it's being passed by value.
let scrutinee = peel_hir_expr_refs(scrutinee).0;
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
let scrutinee_str =
if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
if_chain! {
if !some_expr.needs_unsafe_block;
if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
if func.span.ctxt() == some_expr.expr.span.ctxt();
then {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
} else {
if path_to_local_id(some_expr.expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
if some_expr.needs_unsafe_block {
format!("|{}{}| unsafe {{ {} }}", annotation, some_binding, expr_snip)
} else {
format!("|{}{}| {}", annotation, some_binding, expr_snip)
}
}
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
if some_expr.needs_unsafe_block {
format!("|{}| unsafe {{ {} }}", pat_snip, expr_snip)
} else {
format!("|{}| {}", pat_snip, expr_snip)
}
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
} else {
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)
},
app,
);
}
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if path_to_local_id(arg, binding)
&& cx.typeck_results().expr_adjustments(arg).is_empty()
&& !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
{
Some(func)
},
_ => None,
}
}
enum OptionPat<'a> {
Wild,
None,
Some {
// The pattern contained in the `Some` tuple.
pattern: &'a Pat<'a>,
// The number of references before the `Some` tuple.
// e.g. `&&Some(_)` has a ref count of 2.
ref_count: usize,
},
}
struct SomeExpr<'tcx> {
expr: &'tcx Expr<'tcx>,
needs_unsafe_block: bool,
}
// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
fn f<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
ref_count: usize,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
PatKind::TupleStruct(ref qpath, [pattern], _)
if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
{
Some(OptionPat::Some { pattern, ref_count })
},
_ => None,
}
}
f(cx, pat, 0, ctxt)
}
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
fn get_some_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
needs_unsafe_block: bool,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
// TODO: Allow more complex expressions.
match expr.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(ref qpath),
..
},
[arg],
) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(SomeExpr {
expr: arg,
needs_unsafe_block,
}),
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
rules,
..
},
_,
) => get_some_expr(
cx,
expr,
needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
ctxt,
),
_ => None,
}
}
// Checks for the `None` value.
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
matches!(peel_blocks(expr).kind, ExprKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
}

View File

@ -113,7 +113,7 @@ impl EarlyLintPass for ManualNonExhaustiveStruct {
let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
VisibilityKind::Public => None,
VisibilityKind::Inherited => Some(Ok(f)),
_ => Some(Err(())),
VisibilityKind::Restricted { .. } => Some(Err(())),
});
if let Some(Ok(field)) = iter.next()
&& iter.next().is_none()

View File

@ -1,123 +0,0 @@
use clippy_utils::consts::constant_simple;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{in_constant, is_lang_ctor, path_to_local_id, sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{Arm, Expr, ExprKind, PatKind};
use rustc_lint::LintContext;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
///
/// ### Why is this bad?
/// Concise code helps focusing on behavior instead of boilerplate.
///
/// ### Example
/// ```rust
/// let foo: Option<i32> = None;
/// match foo {
/// Some(v) => v,
/// None => 1,
/// };
/// ```
///
/// Use instead:
/// ```rust
/// let foo: Option<i32> = None;
/// foo.unwrap_or(1);
/// ```
#[clippy::version = "1.49.0"]
pub MANUAL_UNWRAP_OR,
complexity,
"finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
}
declare_lint_pass!(ManualUnwrapOr => [MANUAL_UNWRAP_OR]);
impl<'tcx> LateLintPass<'tcx> for ManualUnwrapOr {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if in_external_macro(cx.sess(), expr.span) || in_constant(cx, expr.hir_id) {
return;
}
lint_manual_unwrap_or(cx, expr);
}
}
fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
if_chain! {
if arms.len() == 2;
if arms.iter().all(|arm| arm.guard.is_none());
if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| {
match arm.pat.kind {
PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
PatKind::TupleStruct(ref qpath, [pat], _) =>
matches!(pat.kind, PatKind::Wild) && is_lang_ctor(cx, qpath, ResultErr),
_ => false,
}
});
let unwrap_arm = &arms[1 - idx];
if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind;
if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
if path_to_local_id(unwrap_arm.body, binding_hir_id);
if cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty();
if !contains_return_break_continue_macro(or_arm.body);
then {
Some(or_arm)
} else {
None
}
}
}
if_chain! {
if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind;
let ty = cx.typeck_results().expr_ty(scrutinee);
if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) {
Some("Option")
} else if is_type_diagnostic_item(cx, ty, sym::Result) {
Some("Result")
} else {
None
};
if let Some(or_arm) = applicable_or_arm(cx, match_arms);
if let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span);
if let Some(indent) = indent_of(cx, expr.span);
if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();
then {
let reindented_or_body =
reindent_multiline(or_body_snippet.into(), true, Some(indent));
let suggestion = if scrutinee.span.from_expansion() {
// we don't want parentheses around macro, e.g. `(some_macro!()).unwrap_or(0)`
sugg::Sugg::hir_with_macro_callsite(cx, scrutinee, "..")
}
else {
sugg::Sugg::hir(cx, scrutinee, "..").maybe_par()
};
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR, expr.span,
&format!("this pattern reimplements `{}::unwrap_or`", ty_name),
"replace with",
format!(
"{}.unwrap_or({})",
suggestion,
reindented_or_body,
),
Applicability::MachineApplicable,
);
}
}
}

View File

@ -1,104 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
/// Checks for `match vec[idx]` or `match vec[n..m]`.
///
/// ### Why is this bad?
/// This can panic at runtime.
///
/// ### Example
/// ```rust, no_run
/// let arr = vec![0, 1, 2, 3];
/// let idx = 1;
///
/// // Bad
/// match arr[idx] {
/// 0 => println!("{}", 0),
/// 1 => println!("{}", 3),
/// _ => {},
/// }
/// ```
/// Use instead:
/// ```rust, no_run
/// let arr = vec![0, 1, 2, 3];
/// let idx = 1;
///
/// // Good
/// match arr.get(idx) {
/// Some(0) => println!("{}", 0),
/// Some(1) => println!("{}", 3),
/// _ => {},
/// }
/// ```
#[clippy::version = "1.45.0"]
pub MATCH_ON_VEC_ITEMS,
pedantic,
"matching on vector elements can panic"
}
declare_lint_pass!(MatchOnVecItems => [MATCH_ON_VEC_ITEMS]);
impl<'tcx> LateLintPass<'tcx> for MatchOnVecItems {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if_chain! {
if !in_external_macro(cx.sess(), expr.span);
if let ExprKind::Match(match_expr, _, MatchSource::Normal) = expr.kind;
if let Some(idx_expr) = is_vec_indexing(cx, match_expr);
if let ExprKind::Index(vec, idx) = idx_expr.kind;
then {
// FIXME: could be improved to suggest surrounding every pattern with Some(_),
// but only when `or_patterns` are stabilized.
span_lint_and_sugg(
cx,
MATCH_ON_VEC_ITEMS,
match_expr.span,
"indexing into a vector may panic",
"try this",
format!(
"{}.get({})",
snippet(cx, vec.span, ".."),
snippet(cx, idx.span, "..")
),
Applicability::MaybeIncorrect
);
}
}
}
}
fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::Index(array, index) = expr.kind;
if is_vector(cx, array);
if !is_full_range(cx, index);
then {
return Some(expr);
}
}
None
}
fn is_vector(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
let ty = ty.peel_refs();
is_type_diagnostic_item(cx, ty, sym::Vec)
}
fn is_full_range(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
let ty = ty.peel_refs();
is_type_lang_item(cx, ty, LangItem::RangeFull)
}

View File

@ -6,68 +6,28 @@ use if_chain::if_chain;
use rustc_errors::MultiSpan;
use rustc_hir::LangItem::OptionNone;
use rustc_hir::{Arm, Expr, Guard, HirId, Let, Pat, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_lint::LateContext;
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
/// without adding any branches.
///
/// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
/// cases where merging would most likely make the code more readable.
///
/// ### Why is this bad?
/// It is unnecessarily verbose and complex.
///
/// ### Example
/// ```rust
/// fn func(opt: Option<Result<u64, String>>) {
/// let n = match opt {
/// Some(n) => match n {
/// Ok(n) => n,
/// _ => return,
/// }
/// None => return,
/// };
/// }
/// ```
/// Use instead:
/// ```rust
/// fn func(opt: Option<Result<u64, String>>) {
/// let n = match opt {
/// Some(Ok(n)) => n,
/// _ => return,
/// };
/// }
/// ```
#[clippy::version = "1.50.0"]
pub COLLAPSIBLE_MATCH,
style,
"Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
}
use super::COLLAPSIBLE_MATCH;
declare_lint_pass!(CollapsibleMatch => [COLLAPSIBLE_MATCH]);
impl<'tcx> LateLintPass<'tcx> for CollapsibleMatch {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
match IfLetOrMatch::parse(cx, expr) {
Some(IfLetOrMatch::Match(_, arms, _)) => {
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
for arm in arms {
check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
}
}
},
Some(IfLetOrMatch::IfLet(_, pat, body, els)) => {
check_arm(cx, false, pat, body, None, els);
},
None => {},
pub(super) fn check_match<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
if let Some(els_arm) = arms.iter().rfind(|arm| arm_is_wild_like(cx, arm)) {
for arm in arms {
check_arm(cx, true, arm.pat, arm.body, arm.guard.as_ref(), Some(els_arm.body));
}
}
}
pub(super) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
body: &'tcx Expr<'_>,
else_expr: Option<&'tcx Expr<'_>>,
) {
check_arm(cx, false, pat, body, None, else_expr);
}
fn check_arm<'tcx>(
cx: &LateContext<'tcx>,
outer_is_match: bool,

View File

@ -0,0 +1,306 @@
use crate::{map_unit_fn::OPTION_MAP_UNIT_FN, matches::MATCH_AS_REF};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
use clippy_utils::{
can_move_expr_to_closure, is_else_clause, is_lang_ctor, is_lint_allowed, path_to_local_id, peel_blocks,
peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
};
use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{
def::Res, Arm, BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path,
QPath, UnsafeSource,
};
use rustc_lint::LateContext;
use rustc_span::{sym, SyntaxContext};
use super::MANUAL_MAP;
pub(super) fn check_match<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
scrutinee: &'tcx Expr<'_>,
arms: &'tcx [Arm<'_>],
) {
if let [arm1, arm2] = arms
&& arm1.guard.is_none()
&& arm2.guard.is_none()
{
check(cx, expr, scrutinee, arm1.pat, arm1.body, Some(arm2.pat), arm2.body);
}
}
pub(super) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
let_pat: &'tcx Pat<'_>,
let_expr: &'tcx Expr<'_>,
then_expr: &'tcx Expr<'_>,
else_expr: &'tcx Expr<'_>,
) {
check(cx, expr, let_expr, let_pat, then_expr, None, else_expr);
}
#[expect(clippy::too_many_lines)]
fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
scrutinee: &'tcx Expr<'_>,
then_pat: &'tcx Pat<'_>,
then_body: &'tcx Expr<'_>,
else_pat: Option<&'tcx Pat<'_>>,
else_body: &'tcx Expr<'_>,
) {
let (scrutinee_ty, ty_ref_count, ty_mutability) =
peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(scrutinee));
if !(is_type_diagnostic_item(cx, scrutinee_ty, sym::Option)
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Option))
{
return;
}
let expr_ctxt = expr.span.ctxt();
let (some_expr, some_pat, pat_ref_count, is_wild_none) = match (
try_parse_pattern(cx, then_pat, expr_ctxt),
else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)),
) {
(Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, true)
},
(Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => {
(else_body, pattern, ref_count, false)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, true)
},
(Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => {
(then_body, pattern, ref_count, false)
},
_ => return,
};
// Top level or patterns aren't allowed in closures.
if matches!(some_pat.kind, PatKind::Or(_)) {
return;
}
let some_expr = match get_some_expr(cx, some_expr, false, expr_ctxt) {
Some(expr) => expr,
None => return,
};
// These two lints will go back and forth with each other.
if cx.typeck_results().expr_ty(some_expr.expr) == cx.tcx.types.unit
&& !is_lint_allowed(cx, OPTION_MAP_UNIT_FN, expr.hir_id)
{
return;
}
// `map` won't perform any adjustments.
if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
return;
}
// Determine which binding mode to use.
let explicit_ref = some_pat.contains_explicit_ref_binding();
let binding_ref = explicit_ref.or_else(|| (ty_ref_count != pat_ref_count).then(|| ty_mutability));
let as_ref_str = match binding_ref {
Some(Mutability::Mut) => ".as_mut()",
Some(Mutability::Not) => ".as_ref()",
None => "",
};
match can_move_expr_to_closure(cx, some_expr.expr) {
Some(captures) => {
// Check if captures the closure will need conflict with borrows made in the scrutinee.
// TODO: check all the references made in the scrutinee expression. This will require interacting
// with the borrow checker. Currently only `<local>[.<field>]*` is checked for.
if let Some(binding_ref_mutability) = binding_ref {
let e = peel_hir_expr_while(scrutinee, |e| match e.kind {
ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) => Some(e),
_ => None,
});
if let ExprKind::Path(QPath::Resolved(None, Path { res: Res::Local(l), .. })) = e.kind {
match captures.get(l) {
Some(CaptureKind::Value | CaptureKind::Ref(Mutability::Mut)) => return,
Some(CaptureKind::Ref(Mutability::Not)) if binding_ref_mutability == Mutability::Mut => {
return;
},
Some(CaptureKind::Ref(Mutability::Not)) | None => (),
}
}
}
},
None => return,
};
let mut app = Applicability::MachineApplicable;
// Remove address-of expressions from the scrutinee. Either `as_ref` will be called, or
// it's being passed by value.
let scrutinee = peel_hir_expr_refs(scrutinee).0;
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
let scrutinee_str = if scrutinee.span.ctxt() == expr.span.ctxt() && scrutinee.precedence().order() < PREC_POSTFIX {
format!("({})", scrutinee_str)
} else {
scrutinee_str.into()
};
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
if_chain! {
if !some_expr.needs_unsafe_block;
if let Some(func) = can_pass_as_func(cx, id, some_expr.expr);
if func.span.ctxt() == some_expr.expr.span.ctxt();
then {
snippet_with_applicability(cx, func.span, "..", &mut app).into_owned()
} else {
if path_to_local_id(some_expr.expr, id)
&& !is_lint_allowed(cx, MATCH_AS_REF, expr.hir_id)
&& binding_ref.is_some()
{
return;
}
// `ref` and `ref mut` annotations were handled earlier.
let annotation = if matches!(annotation, BindingAnnotation::Mutable) {
"mut "
} else {
""
};
let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
if some_expr.needs_unsafe_block {
format!("|{}{}| unsafe {{ {} }}", annotation, some_binding, expr_snip)
} else {
format!("|{}{}| {}", annotation, some_binding, expr_snip)
}
}
}
} else if !is_wild_none && explicit_ref.is_none() {
// TODO: handle explicit reference annotations.
let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
let expr_snip = snippet_with_context(cx, some_expr.expr.span, expr_ctxt, "..", &mut app).0;
if some_expr.needs_unsafe_block {
format!("|{}| unsafe {{ {} }}", pat_snip, expr_snip)
} else {
format!("|{}| {}", pat_snip, expr_snip)
}
} else {
// Refutable bindings and mixed reference annotations can't be handled by `map`.
return;
};
span_lint_and_sugg(
cx,
MANUAL_MAP,
expr.span,
"manual implementation of `Option::map`",
"try this",
if else_pat.is_none() && is_else_clause(cx.tcx, expr) {
format!("{{ {}{}.map({}) }}", scrutinee_str, as_ref_str, body_str)
} else {
format!("{}{}.map({})", scrutinee_str, as_ref_str, body_str)
},
app,
);
}
// Checks whether the expression could be passed as a function, or whether a closure is needed.
// Returns the function to be passed to `map` if it exists.
fn can_pass_as_func<'tcx>(cx: &LateContext<'tcx>, binding: HirId, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
match expr.kind {
ExprKind::Call(func, [arg])
if path_to_local_id(arg, binding)
&& cx.typeck_results().expr_adjustments(arg).is_empty()
&& !type_is_unsafe_function(cx, cx.typeck_results().expr_ty(func).peel_refs()) =>
{
Some(func)
},
_ => None,
}
}
enum OptionPat<'a> {
Wild,
None,
Some {
// The pattern contained in the `Some` tuple.
pattern: &'a Pat<'a>,
// The number of references before the `Some` tuple.
// e.g. `&&Some(_)` has a ref count of 2.
ref_count: usize,
},
}
struct SomeExpr<'tcx> {
expr: &'tcx Expr<'tcx>,
needs_unsafe_block: bool,
}
// Try to parse into a recognized `Option` pattern.
// i.e. `_`, `None`, `Some(..)`, or a reference to any of those.
fn try_parse_pattern<'tcx>(cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>, ctxt: SyntaxContext) -> Option<OptionPat<'tcx>> {
fn f<'tcx>(
cx: &LateContext<'tcx>,
pat: &'tcx Pat<'_>,
ref_count: usize,
ctxt: SyntaxContext,
) -> Option<OptionPat<'tcx>> {
match pat.kind {
PatKind::Wild => Some(OptionPat::Wild),
PatKind::Ref(pat, _) => f(cx, pat, ref_count + 1, ctxt),
PatKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone) => Some(OptionPat::None),
PatKind::TupleStruct(ref qpath, [pattern], _)
if is_lang_ctor(cx, qpath, OptionSome) && pat.span.ctxt() == ctxt =>
{
Some(OptionPat::Some { pattern, ref_count })
},
_ => None,
}
}
f(cx, pat, 0, ctxt)
}
// Checks for an expression wrapped by the `Some` constructor. Returns the contained expression.
fn get_some_expr<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
needs_unsafe_block: bool,
ctxt: SyntaxContext,
) -> Option<SomeExpr<'tcx>> {
// TODO: Allow more complex expressions.
match expr.kind {
ExprKind::Call(
Expr {
kind: ExprKind::Path(ref qpath),
..
},
[arg],
) if ctxt == expr.span.ctxt() && is_lang_ctor(cx, qpath, OptionSome) => Some(SomeExpr {
expr: arg,
needs_unsafe_block,
}),
ExprKind::Block(
Block {
stmts: [],
expr: Some(expr),
rules,
..
},
_,
) => get_some_expr(
cx,
expr,
needs_unsafe_block || *rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
ctxt,
),
_ => None,
}
}
// Checks for the `None` value.
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
matches!(peel_blocks(expr).kind, ExprKind::Path(ref qpath) if is_lang_ctor(cx, qpath, OptionNone))
}

View File

@ -0,0 +1,83 @@
use clippy_utils::consts::constant_simple;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::usage::contains_return_break_continue_macro;
use clippy_utils::{is_lang_ctor, path_to_local_id, sugg};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk};
use rustc_hir::{Arm, Expr, PatKind};
use rustc_lint::LateContext;
use rustc_span::sym;
use super::MANUAL_UNWRAP_OR;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
let ty = cx.typeck_results().expr_ty(scrutinee);
if_chain! {
if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::Option) {
Some("Option")
} else if is_type_diagnostic_item(cx, ty, sym::Result) {
Some("Result")
} else {
None
};
if let Some(or_arm) = applicable_or_arm(cx, arms);
if let Some(or_body_snippet) = snippet_opt(cx, or_arm.body.span);
if let Some(indent) = indent_of(cx, expr.span);
if constant_simple(cx, cx.typeck_results(), or_arm.body).is_some();
then {
let reindented_or_body =
reindent_multiline(or_body_snippet.into(), true, Some(indent));
let suggestion = if scrutinee.span.from_expansion() {
// we don't want parentheses around macro, e.g. `(some_macro!()).unwrap_or(0)`
sugg::Sugg::hir_with_macro_callsite(cx, scrutinee, "..")
}
else {
sugg::Sugg::hir(cx, scrutinee, "..").maybe_par()
};
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR, expr.span,
&format!("this pattern reimplements `{}::unwrap_or`", ty_name),
"replace with",
format!(
"{}.unwrap_or({})",
suggestion,
reindented_or_body,
),
Applicability::MachineApplicable,
);
}
}
}
fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> {
if_chain! {
if arms.len() == 2;
if arms.iter().all(|arm| arm.guard.is_none());
if let Some((idx, or_arm)) = arms.iter().enumerate().find(|(_, arm)| {
match arm.pat.kind {
PatKind::Path(ref qpath) => is_lang_ctor(cx, qpath, OptionNone),
PatKind::TupleStruct(ref qpath, [pat], _) =>
matches!(pat.kind, PatKind::Wild) && is_lang_ctor(cx, qpath, ResultErr),
_ => false,
}
});
let unwrap_arm = &arms[1 - idx];
if let PatKind::TupleStruct(ref qpath, [unwrap_pat], _) = unwrap_arm.pat.kind;
if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk);
if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind;
if path_to_local_id(unwrap_arm.body, binding_hir_id);
if cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty();
if !contains_return_break_continue_macro(or_arm.body);
then {
Some(or_arm)
} else {
None
}
}
}

View File

@ -24,8 +24,8 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
let exprs = if let PatKind::Lit(arm_bool) = arms[0].pat.kind {
if let ExprKind::Lit(ref lit) = arm_bool.kind {
match lit.node {
LitKind::Bool(true) => Some((&*arms[0].body, &*arms[1].body)),
LitKind::Bool(false) => Some((&*arms[1].body, &*arms[0].body)),
LitKind::Bool(true) => Some((arms[0].body, arms[1].body)),
LitKind::Bool(false) => Some((arms[1].body, arms[0].body)),
_ => None,
}
} else {

View File

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_wild;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{higher, is_wild};
use rustc_ast::{Attribute, LitKind};
use rustc_errors::Applicability;
use rustc_hir::{Arm, BorrowKind, Expr, ExprKind, Guard, Pat};
@ -11,22 +11,24 @@ use rustc_span::source_map::Spanned;
use super::MATCH_LIKE_MATCHES_MACRO;
/// Lint a `match` or `if let .. { .. } else { .. }` expr that could be replaced by `matches!`
pub(crate) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(higher::IfLet {
let_pat,
pub(crate) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
let_pat: &'tcx Pat<'_>,
let_expr: &'tcx Expr<'_>,
then_expr: &'tcx Expr<'_>,
else_expr: &'tcx Expr<'_>,
) {
find_matches_sugg(
cx,
let_expr,
if_then,
if_else: Some(if_else),
}) = higher::IfLet::hir(cx, expr)
{
find_matches_sugg(
cx,
let_expr,
IntoIterator::into_iter([(&[][..], Some(let_pat), if_then, None), (&[][..], None, if_else, None)]),
expr,
true,
);
}
IntoIterator::into_iter([
(&[][..], Some(let_pat), then_expr, None),
(&[][..], None, else_expr, None),
]),
expr,
true,
);
}
pub(super) fn check_match<'tcx>(

View File

@ -0,0 +1,61 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::LateContext;
use rustc_span::sym;
use super::MATCH_ON_VEC_ITEMS;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>) {
if_chain! {
if let Some(idx_expr) = is_vec_indexing(cx, scrutinee);
if let ExprKind::Index(vec, idx) = idx_expr.kind;
then {
// FIXME: could be improved to suggest surrounding every pattern with Some(_),
// but only when `or_patterns` are stabilized.
span_lint_and_sugg(
cx,
MATCH_ON_VEC_ITEMS,
scrutinee.span,
"indexing into a vector may panic",
"try this",
format!(
"{}.get({})",
snippet(cx, vec.span, ".."),
snippet(cx, idx.span, "..")
),
Applicability::MaybeIncorrect
);
}
}
}
fn is_vec_indexing<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
if_chain! {
if let ExprKind::Index(array, index) = expr.kind;
if is_vector(cx, array);
if !is_full_range(cx, index);
then {
return Some(expr);
}
}
None
}
fn is_vector(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
let ty = ty.peel_refs();
is_type_diagnostic_item(cx, ty, sym::Vec)
}
fn is_full_range(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
let ty = ty.peel_refs();
is_type_lang_item(cx, ty, LangItem::RangeFull)
}

View File

@ -3,48 +3,13 @@ use clippy_utils::ty::is_type_diagnostic_item;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_hir::{Arm, Expr, ExprKind, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::Symbol;
use rustc_span::{sym, Span};
declare_clippy_lint! {
/// ### What it does
/// Checks for `match` expressions modifying the case of a string with non-compliant arms
///
/// ### Why is this bad?
/// The arm is unreachable, which is likely a mistake
///
/// ### Example
/// ```rust
/// # let text = "Foo";
///
/// match &*text.to_ascii_lowercase() {
/// "foo" => {},
/// "Bar" => {},
/// _ => {},
/// }
/// ```
/// Use instead:
/// ```rust
/// # let text = "Foo";
///
/// match &*text.to_ascii_lowercase() {
/// "foo" => {},
/// "bar" => {},
/// _ => {},
/// }
/// ```
#[clippy::version = "1.58.0"]
pub MATCH_STR_CASE_MISMATCH,
correctness,
"creation of a case altering match expression with non-compliant arms"
}
declare_lint_pass!(MatchStrCaseMismatch => [MATCH_STR_CASE_MISMATCH]);
use super::MATCH_STR_CASE_MISMATCH;
#[derive(Debug)]
enum CaseMethod {
@ -54,25 +19,21 @@ enum CaseMethod {
AsciiUppercase,
}
impl<'tcx> LateLintPass<'tcx> for MatchStrCaseMismatch {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if_chain! {
if !in_external_macro(cx.tcx.sess, expr.span);
if let ExprKind::Match(match_expr, arms, MatchSource::Normal) = expr.kind;
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(match_expr).kind();
if let ty::Str = ty.kind();
then {
let mut visitor = MatchExprVisitor {
cx,
case_method: None,
};
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arms: &'tcx [Arm<'_>]) {
if_chain! {
if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind();
if let ty::Str = ty.kind();
then {
let mut visitor = MatchExprVisitor {
cx,
case_method: None,
};
visitor.visit_expr(match_expr);
visitor.visit_expr(scrutinee);
if let Some(case_method) = visitor.case_method {
if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) {
lint(cx, &case_method, bad_case_span, bad_case_sym.as_str());
}
if let Some(case_method) = visitor.case_method {
if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) {
lint(cx, &case_method, bad_case_span, bad_case_sym.as_str());
}
}
}

View File

@ -1,26 +1,34 @@
use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context};
use clippy_utils::{meets_msrv, msrvs};
use clippy_utils::{higher, in_constant, meets_msrv, msrvs};
use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
use rustc_lexer::{tokenize, TokenKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_semver::RustcVersion;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{Span, SpanData, SyntaxContext};
mod collapsible_match;
mod infallible_destructuring_match;
mod manual_map;
mod manual_unwrap_or;
mod match_as_ref;
mod match_bool;
mod match_like_matches;
mod match_on_vec_items;
mod match_ref_pats;
mod match_same_arms;
mod match_single_binding;
mod match_str_case_mismatch;
mod match_wild_enum;
mod match_wild_err_arm;
mod needless_match;
mod overlapping_arms;
mod redundant_pattern_match;
mod rest_pat_in_fully_bound_struct;
mod significant_drop_in_scrutinee;
mod single_match;
mod try_err;
mod wild_in_or_pats;
declare_clippy_lint! {
@ -610,6 +618,274 @@ declare_clippy_lint! {
"`match` or match-like `if let` that are unnecessary"
}
declare_clippy_lint! {
/// ### What it does
/// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
/// without adding any branches.
///
/// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
/// cases where merging would most likely make the code more readable.
///
/// ### Why is this bad?
/// It is unnecessarily verbose and complex.
///
/// ### Example
/// ```rust
/// fn func(opt: Option<Result<u64, String>>) {
/// let n = match opt {
/// Some(n) => match n {
/// Ok(n) => n,
/// _ => return,
/// }
/// None => return,
/// };
/// }
/// ```
/// Use instead:
/// ```rust
/// fn func(opt: Option<Result<u64, String>>) {
/// let n = match opt {
/// Some(Ok(n)) => n,
/// _ => return,
/// };
/// }
/// ```
#[clippy::version = "1.50.0"]
pub COLLAPSIBLE_MATCH,
style,
"Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
}
declare_clippy_lint! {
/// ### What it does
/// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
///
/// ### Why is this bad?
/// Concise code helps focusing on behavior instead of boilerplate.
///
/// ### Example
/// ```rust
/// let foo: Option<i32> = None;
/// match foo {
/// Some(v) => v,
/// None => 1,
/// };
/// ```
///
/// Use instead:
/// ```rust
/// let foo: Option<i32> = None;
/// foo.unwrap_or(1);
/// ```
#[clippy::version = "1.49.0"]
pub MANUAL_UNWRAP_OR,
complexity,
"finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `match vec[idx]` or `match vec[n..m]`.
///
/// ### Why is this bad?
/// This can panic at runtime.
///
/// ### Example
/// ```rust, no_run
/// let arr = vec![0, 1, 2, 3];
/// let idx = 1;
///
/// // Bad
/// match arr[idx] {
/// 0 => println!("{}", 0),
/// 1 => println!("{}", 3),
/// _ => {},
/// }
/// ```
/// Use instead:
/// ```rust, no_run
/// let arr = vec![0, 1, 2, 3];
/// let idx = 1;
///
/// // Good
/// match arr.get(idx) {
/// Some(0) => println!("{}", 0),
/// Some(1) => println!("{}", 3),
/// _ => {},
/// }
/// ```
#[clippy::version = "1.45.0"]
pub MATCH_ON_VEC_ITEMS,
pedantic,
"matching on vector elements can panic"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `match` expressions modifying the case of a string with non-compliant arms
///
/// ### Why is this bad?
/// The arm is unreachable, which is likely a mistake
///
/// ### Example
/// ```rust
/// # let text = "Foo";
/// match &*text.to_ascii_lowercase() {
/// "foo" => {},
/// "Bar" => {},
/// _ => {},
/// }
/// ```
/// Use instead:
/// ```rust
/// # let text = "Foo";
/// match &*text.to_ascii_lowercase() {
/// "foo" => {},
/// "bar" => {},
/// _ => {},
/// }
/// ```
#[clippy::version = "1.58.0"]
pub MATCH_STR_CASE_MISMATCH,
correctness,
"creation of a case altering match expression with non-compliant arms"
}
declare_clippy_lint! {
/// ### What it does
/// Check for temporaries returned from function calls in a match scrutinee that have the
/// `clippy::has_significant_drop` attribute.
///
/// ### Why is this bad?
/// The `clippy::has_significant_drop` attribute can be added to types whose Drop impls have
/// an important side-effect, such as unlocking a mutex, making it important for users to be
/// able to accurately understand their lifetimes. When a temporary is returned in a function
/// call in a match scrutinee, its lifetime lasts until the end of the match block, which may
/// be surprising.
///
/// For `Mutex`es this can lead to a deadlock. This happens when the match scrutinee uses a
/// function call that returns a `MutexGuard` and then tries to lock again in one of the match
/// arms. In that case the `MutexGuard` in the scrutinee will not be dropped until the end of
/// the match block and thus will not unlock.
///
/// ### Example
/// ```rust.ignore
/// # use std::sync::Mutex;
///
/// # struct State {}
///
/// # impl State {
/// # fn foo(&self) -> bool {
/// # true
/// # }
///
/// # fn bar(&self) {}
/// # }
///
///
/// let mutex = Mutex::new(State {});
///
/// match mutex.lock().unwrap().foo() {
/// true => {
/// mutex.lock().unwrap().bar(); // Deadlock!
/// }
/// false => {}
/// };
///
/// println!("All done!");
///
/// ```
/// Use instead:
/// ```rust
/// # use std::sync::Mutex;
///
/// # struct State {}
///
/// # impl State {
/// # fn foo(&self) -> bool {
/// # true
/// # }
///
/// # fn bar(&self) {}
/// # }
///
/// let mutex = Mutex::new(State {});
///
/// let is_foo = mutex.lock().unwrap().foo();
/// match is_foo {
/// true => {
/// mutex.lock().unwrap().bar();
/// }
/// false => {}
/// };
///
/// println!("All done!");
/// ```
#[clippy::version = "1.60.0"]
pub SIGNIFICANT_DROP_IN_SCRUTINEE,
suspicious,
"warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usages of `Err(x)?`.
///
/// ### Why is this bad?
/// The `?` operator is designed to allow calls that
/// can fail to be easily chained. For example, `foo()?.bar()` or
/// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will
/// always return), it is more clear to write `return Err(x)`.
///
/// ### Example
/// ```rust
/// fn foo(fail: bool) -> Result<i32, String> {
/// if fail {
/// Err("failed")?;
/// }
/// Ok(0)
/// }
/// ```
/// Could be written:
///
/// ```rust
/// fn foo(fail: bool) -> Result<i32, String> {
/// if fail {
/// return Err("failed".into());
/// }
/// Ok(0)
/// }
/// ```
#[clippy::version = "1.38.0"]
pub TRY_ERR,
restriction,
"return errors explicitly rather than hiding them behind a `?`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for usages of `match` which could be implemented using `map`
///
/// ### Why is this bad?
/// Using the `map` method is clearer and more concise.
///
/// ### Example
/// ```rust
/// match Some(0) {
/// Some(x) => Some(x + 1),
/// None => None,
/// };
/// ```
/// Use instead:
/// ```rust
/// Some(0).map(|x| x + 1);
/// ```
#[clippy::version = "1.52.0"]
pub MANUAL_MAP,
style,
"reimplementation of `map`"
}
#[derive(Default)]
pub struct Matches {
msrv: Option<RustcVersion>,
@ -644,19 +920,42 @@ impl_lint_pass!(Matches => [
MATCH_LIKE_MATCHES_MACRO,
MATCH_SAME_ARMS,
NEEDLESS_MATCH,
COLLAPSIBLE_MATCH,
MANUAL_UNWRAP_OR,
MATCH_ON_VEC_ITEMS,
MATCH_STR_CASE_MISMATCH,
SIGNIFICANT_DROP_IN_SCRUTINEE,
TRY_ERR,
MANUAL_MAP,
]);
impl<'tcx> LateLintPass<'tcx> for Matches {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if expr.span.from_expansion() {
if in_external_macro(cx.sess(), expr.span) {
return;
}
let from_expansion = expr.span.from_expansion();
if let ExprKind::Match(ex, arms, source) = expr.kind {
if !span_starts_with(cx, expr.span, "match") {
if source == MatchSource::Normal && !span_starts_with(cx, expr.span, "match") {
return;
}
if !contains_cfg_arm(cx, expr, ex, arms) {
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) {
significant_drop_in_scrutinee::check(cx, expr, ex, source);
}
collapsible_match::check_match(cx, arms);
if !from_expansion {
// These don't depend on a relationship between multiple arms
match_wild_err_arm::check(cx, ex, arms);
wild_in_or_pats::check(cx, arms);
}
if source == MatchSource::TryDesugar {
try_err::check(cx, expr, ex);
}
if !from_expansion && !contains_cfg_arm(cx, expr, ex, arms) {
if source == MatchSource::Normal {
if !(meets_msrv(self.msrv, msrvs::MATCHES_MACRO)
&& match_like_matches::check_match(cx, expr, ex, arms))
@ -671,6 +970,13 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
match_wild_enum::check(cx, ex, arms);
match_as_ref::check(cx, ex, arms, expr);
needless_match::check_match(cx, ex, arms, expr);
match_on_vec_items::check(cx, ex);
match_str_case_mismatch::check(cx, ex, arms);
if !in_constant(cx, expr.hir_id) {
manual_unwrap_or::check(cx, expr, ex, arms);
manual_map::check_match(cx, expr, ex, arms);
}
if self.infallible_destructuring_match_linted {
self.infallible_destructuring_match_linted = false;
@ -680,16 +986,35 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
}
match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
}
// These don't depend on a relationship between multiple arms
match_wild_err_arm::check(cx, ex, arms);
wild_in_or_pats::check(cx, arms);
} else {
if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
match_like_matches::check(cx, expr);
} else if let Some(if_let) = higher::IfLet::hir(cx, expr) {
collapsible_match::check_if_let(cx, if_let.let_pat, if_let.if_then, if_let.if_else);
if !from_expansion {
if let Some(else_expr) = if_let.if_else {
if meets_msrv(self.msrv, msrvs::MATCHES_MACRO) {
match_like_matches::check_if_let(
cx,
expr,
if_let.let_pat,
if_let.let_expr,
if_let.if_then,
else_expr,
);
}
if !in_constant(cx, expr.hir_id) {
manual_map::check_if_let(cx, expr, if_let.let_pat, if_let.let_expr, if_let.if_then, else_expr);
}
}
redundant_pattern_match::check_if_let(
cx,
expr,
if_let.let_pat,
if_let.let_expr,
if_let.if_else.is_some(),
);
needless_match::check_if_let(cx, expr, &if_let);
}
} else if !from_expansion {
redundant_pattern_match::check(cx, expr);
needless_match::check(cx, expr);
}
}

View File

@ -47,20 +47,18 @@ pub(crate) fn check_match(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>],
/// some_enum
/// }
/// ```
pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>) {
if let Some(ref if_let) = higher::IfLet::hir(cx, ex) {
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let(cx, if_let) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
ex.span,
"this if-let expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
applicability,
);
}
pub(crate) fn check_if_let<'tcx>(cx: &LateContext<'tcx>, ex: &Expr<'_>, if_let: &higher::IfLet<'tcx>) {
if !is_else_clause(cx.tcx, ex) && expr_ty_matches_p_ty(cx, if_let.let_expr, ex) && check_if_let_inner(cx, if_let) {
let mut applicability = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
NEEDLESS_MATCH,
ex.span,
"this if-let expression is unnecessary",
"replace it with",
snippet_with_applicability(cx, if_let.let_expr.span, "..", &mut applicability).to_string(),
applicability,
);
}
}
@ -77,7 +75,7 @@ fn check_all_arms(cx: &LateContext<'_>, match_expr: &Expr<'_>, arms: &[Arm<'_>])
true
}
fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
fn check_if_let_inner(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
if let Some(if_else) = if_let.if_else {
if !pat_same_as_expr(if_let.let_pat, peel_blocks_with_stmt(if_let.if_then)) {
return false;
@ -85,7 +83,7 @@ fn check_if_let(cx: &LateContext<'_>, if_let: &higher::IfLet<'_>) -> bool {
// Recursively check for each `else if let` phrase,
if let Some(ref nested_if_let) = higher::IfLet::hir(cx, if_else) {
return check_if_let(cx, nested_if_let);
return check_if_let_inner(cx, nested_if_let);
}
if matches!(if_else.kind, ExprKind::Block(..)) {

View File

@ -18,19 +18,21 @@ use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
use rustc_span::sym;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(higher::IfLet {
if_else,
let_pat,
let_expr,
..
}) = higher::IfLet::hir(cx, expr)
{
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "if", if_else.is_some());
} else if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
if let Some(higher::WhileLet { let_pat, let_expr, .. }) = higher::WhileLet::hir(expr) {
find_sugg_for_if_let(cx, expr, let_pat, let_expr, "while", false);
}
}
pub(super) fn check_if_let<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
pat: &'tcx Pat<'_>,
scrutinee: &'tcx Expr<'_>,
has_else: bool,
) {
find_sugg_for_if_let(cx, expr, pat, scrutinee, "if", has_else);
}
// Extract the generic arguments out of a type
fn try_get_generic_ty(ty: Ty<'_>, index: usize) -> Option<Ty<'_>> {
if_chain! {

View File

@ -4,103 +4,25 @@ use clippy_utils::get_attr;
use clippy_utils::source::{indent_of, snippet};
use rustc_errors::{Applicability, Diagnostic};
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_hir::{Expr, ExprKind, MatchSource};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty::subst::GenericArgKind;
use rustc_middle::ty::{Ty, TypeAndMut};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
declare_clippy_lint! {
/// ### What it does
/// Check for temporaries returned from function calls in a match scrutinee that have the
/// `clippy::has_significant_drop` attribute.
///
/// ### Why is this bad?
/// The `clippy::has_significant_drop` attribute can be added to types whose Drop impls have
/// an important side-effect, such as unlocking a mutex, making it important for users to be
/// able to accurately understand their lifetimes. When a temporary is returned in a function
/// call in a match scrutinee, its lifetime lasts until the end of the match block, which may
/// be surprising.
///
/// For `Mutex`es this can lead to a deadlock. This happens when the match scrutinee uses a
/// function call that returns a `MutexGuard` and then tries to lock again in one of the match
/// arms. In that case the `MutexGuard` in the scrutinee will not be dropped until the end of
/// the match block and thus will not unlock.
///
/// ### Example
/// ```rust.ignore
/// # use std::sync::Mutex;
///
/// # struct State {}
///
/// # impl State {
/// # fn foo(&self) -> bool {
/// # true
/// # }
///
/// # fn bar(&self) {}
/// # }
///
///
/// let mutex = Mutex::new(State {});
///
/// match mutex.lock().unwrap().foo() {
/// true => {
/// mutex.lock().unwrap().bar(); // Deadlock!
/// }
/// false => {}
/// };
///
/// println!("All done!");
///
/// ```
/// Use instead:
/// ```rust
/// # use std::sync::Mutex;
///
/// # struct State {}
///
/// # impl State {
/// # fn foo(&self) -> bool {
/// # true
/// # }
///
/// # fn bar(&self) {}
/// # }
///
/// let mutex = Mutex::new(State {});
///
/// let is_foo = mutex.lock().unwrap().foo();
/// match is_foo {
/// true => {
/// mutex.lock().unwrap().bar();
/// }
/// false => {}
/// };
///
/// println!("All done!");
/// ```
#[clippy::version = "1.60.0"]
pub SIGNIFICANT_DROP_IN_SCRUTINEE,
suspicious,
"warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime"
}
use super::SIGNIFICANT_DROP_IN_SCRUTINEE;
declare_lint_pass!(SignificantDropInScrutinee => [SIGNIFICANT_DROP_IN_SCRUTINEE]);
impl<'tcx> LateLintPass<'tcx> for SignificantDropInScrutinee {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(suggestions) = has_significant_drop_in_scrutinee(cx, expr) {
for found in suggestions {
span_lint_and_then(
cx,
SIGNIFICANT_DROP_IN_SCRUTINEE,
found.found_span,
"temporary with significant drop in match scrutinee",
|diag| set_diagnostic(diag, cx, expr, found),
);
}
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
scrutinee: &'tcx Expr<'_>,
source: MatchSource,
) {
if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
for found in suggestions {
span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
set_diagnostic(diag, cx, expr, found);
});
}
}
}
@ -152,13 +74,18 @@ fn set_diagnostic<'tcx>(diag: &mut Diagnostic, cx: &LateContext<'tcx>, expr: &'t
/// may have a surprising lifetime.
fn has_significant_drop_in_scrutinee<'tcx, 'a>(
cx: &'a LateContext<'tcx>,
expr: &'tcx Expr<'tcx>,
) -> Option<Vec<FoundSigDrop>> {
scrutinee: &'tcx Expr<'tcx>,
source: MatchSource,
) -> Option<(Vec<FoundSigDrop>, &'static str)> {
let mut helper = SigDropHelper::new(cx);
match expr.kind {
ExprKind::Match(match_expr, _, _) => helper.find_sig_drop(match_expr),
_ => None,
}
helper.find_sig_drop(scrutinee).map(|drops| {
let message = if source == MatchSource::Normal {
"temporary with significant drop in match scrutinee"
} else {
"temporary with significant drop in for loop"
};
(drops, message)
})
}
struct SigDropHelper<'a, 'tcx> {
@ -213,6 +140,19 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
self.sig_drop_spans.take()
}
fn replace_current_sig_drop(
&mut self,
found_span: Span,
is_unit_return_val: bool,
lint_suggestion: LintSuggestion,
) {
self.current_sig_drop.replace(FoundSigDrop {
found_span,
is_unit_return_val,
lint_suggestion,
});
}
/// This will try to set the current suggestion (so it can be moved into the suggestions vec
/// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us
/// an opportunity to look for another type in the chain that will be trivially copyable.
@ -229,25 +169,15 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
// but let's avoid any chance of an ICE
if let Some(TypeAndMut { ty, .. }) = ty.builtin_deref(true) {
if ty.is_trivially_pure_clone_copy() {
self.current_sig_drop.replace(FoundSigDrop {
found_span: expr.span,
is_unit_return_val: false,
lint_suggestion: LintSuggestion::MoveAndDerefToCopy,
});
self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy);
} else if allow_move_and_clone {
self.current_sig_drop.replace(FoundSigDrop {
found_span: expr.span,
is_unit_return_val: false,
lint_suggestion: LintSuggestion::MoveAndClone,
});
self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone);
}
}
} else if ty.is_trivially_pure_clone_copy() {
self.current_sig_drop.replace(FoundSigDrop {
found_span: expr.span,
is_unit_return_val: false,
lint_suggestion: LintSuggestion::MoveOnly,
});
self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveOnly);
} else if allow_move_and_clone {
self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone);
}
}
@ -279,11 +209,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
// If either side had a significant drop, suggest moving the entire scrutinee to avoid
// unnecessary copies and to simplify cases where both sides have significant drops.
if self.has_significant_drop {
self.current_sig_drop.replace(FoundSigDrop {
found_span: span,
is_unit_return_val,
lint_suggestion: LintSuggestion::MoveOnly,
});
self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly);
}
self.special_handling_for_binary_op = false;
@ -363,34 +289,34 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
}
}
ExprKind::Box(..) |
ExprKind::Array(..) |
ExprKind::Call(..) |
ExprKind::Unary(..) |
ExprKind::If(..) |
ExprKind::Match(..) |
ExprKind::Field(..) |
ExprKind::Index(..) |
ExprKind::Ret(..) |
ExprKind::Repeat(..) |
ExprKind::Yield(..) |
ExprKind::MethodCall(..) => walk_expr(self, ex),
ExprKind::Array(..) |
ExprKind::Call(..) |
ExprKind::Unary(..) |
ExprKind::If(..) |
ExprKind::Match(..) |
ExprKind::Field(..) |
ExprKind::Index(..) |
ExprKind::Ret(..) |
ExprKind::Repeat(..) |
ExprKind::Yield(..) |
ExprKind::MethodCall(..) => walk_expr(self, ex),
ExprKind::AddrOf(_, _, _) |
ExprKind::Block(_, _) |
ExprKind::Break(_, _) |
ExprKind::Cast(_, _) |
// Don't want to check the closure itself, only invocation, which is covered by MethodCall
ExprKind::Closure(_, _, _, _, _) |
ExprKind::ConstBlock(_) |
ExprKind::Continue(_) |
ExprKind::DropTemps(_) |
ExprKind::Err |
ExprKind::InlineAsm(_) |
ExprKind::Let(_) |
ExprKind::Lit(_) |
ExprKind::Loop(_, _, _, _) |
ExprKind::Path(_) |
ExprKind::Struct(_, _, _) |
ExprKind::Type(_, _) => {
ExprKind::Block(_, _) |
ExprKind::Break(_, _) |
ExprKind::Cast(_, _) |
// Don't want to check the closure itself, only invocation, which is covered by MethodCall
ExprKind::Closure(_, _, _, _, _) |
ExprKind::ConstBlock(_) |
ExprKind::Continue(_) |
ExprKind::DropTemps(_) |
ExprKind::Err |
ExprKind::InlineAsm(_) |
ExprKind::Let(_) |
ExprKind::Lit(_) |
ExprKind::Loop(_, _, _, _) |
ExprKind::Path(_) |
ExprKind::Struct(_, _, _) |
ExprKind::Type(_, _) => {
return;
}
}

View File

@ -0,0 +1,145 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{get_parent_expr, is_lang_ctor, match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::ResultErr;
use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::{hygiene, sym};
use super::TRY_ERR;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, scrutinee: &'tcx Expr<'_>) {
// Looks for a structure like this:
// match ::std::ops::Try::into_result(Err(5)) {
// ::std::result::Result::Err(err) =>
// #[allow(unreachable_code)]
// return ::std::ops::Try::from_error(::std::convert::From::from(err)),
// ::std::result::Result::Ok(val) =>
// #[allow(unreachable_code)]
// val,
// };
if_chain! {
if let ExprKind::Call(match_fun, try_args) = scrutinee.kind;
if let ExprKind::Path(ref match_fun_path) = match_fun.kind;
if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, ..));
if let Some(try_arg) = try_args.get(0);
if let ExprKind::Call(err_fun, err_args) = try_arg.kind;
if let Some(err_arg) = err_args.get(0);
if let ExprKind::Path(ref err_fun_path) = err_fun.kind;
if is_lang_ctor(cx, err_fun_path, ResultErr);
if let Some(return_ty) = find_return_type(cx, &expr.kind);
then {
let prefix;
let suffix;
let err_ty;
if let Some(ty) = result_error_type(cx, return_ty) {
prefix = "Err(";
suffix = ")";
err_ty = ty;
} else if let Some(ty) = poll_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Err(";
suffix = "))";
err_ty = ty;
} else if let Some(ty) = poll_option_result_error_type(cx, return_ty) {
prefix = "Poll::Ready(Some(Err(";
suffix = ")))";
err_ty = ty;
} else {
return;
};
let expr_err_ty = cx.typeck_results().expr_ty(err_arg);
let span = hygiene::walk_chain(err_arg.span, try_arg.span.ctxt());
let mut applicability = Applicability::MachineApplicable;
let origin_snippet = snippet_with_applicability(cx, span, "_", &mut applicability);
let ret_prefix = if get_parent_expr(cx, expr).map_or(false, |e| matches!(e.kind, ExprKind::Ret(_))) {
"" // already returns
} else {
"return "
};
let suggestion = if err_ty == expr_err_ty {
format!("{}{}{}{}", ret_prefix, prefix, origin_snippet, suffix)
} else {
format!("{}{}{}.into(){}", ret_prefix, prefix, origin_snippet, suffix)
};
span_lint_and_sugg(
cx,
TRY_ERR,
expr.span,
"returning an `Err(_)` with the `?` operator",
"try this",
suggestion,
applicability,
);
}
}
}
/// Finds function return type by examining return expressions in match arms.
fn find_return_type<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx ExprKind<'_>) -> Option<Ty<'tcx>> {
if let ExprKind::Match(_, arms, MatchSource::TryDesugar) = expr {
for arm in arms.iter() {
if let ExprKind::Ret(Some(ret)) = arm.body.kind {
return Some(cx.typeck_results().expr_ty(ret));
}
}
}
None
}
/// Extracts the error type from Result<T, E>.
fn result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(_, subst) = ty.kind();
if is_type_diagnostic_item(cx, ty, sym::Result);
then {
Some(subst.type_at(1))
} else {
None
}
}
}
/// Extracts the error type from Poll<Result<T, E>>.
fn poll_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
if match_def_path(cx, def.did(), &paths::POLL);
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
if cx.tcx.is_diagnostic_item(sym::Result, ready_def.did());
then {
Some(ready_subst.type_at(1))
} else {
None
}
}
}
/// Extracts the error type from Poll<Option<Result<T, E>>>.
fn poll_option_result_error_type<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
if_chain! {
if let ty::Adt(def, subst) = ty.kind();
if match_def_path(cx, def.did(), &paths::POLL);
let ready_ty = subst.type_at(0);
if let ty::Adt(ready_def, ready_subst) = ready_ty.kind();
if cx.tcx.is_diagnostic_item(sym::Option, ready_def.did());
let some_ty = ready_subst.type_at(0);
if let ty::Adt(some_def, some_subst) = some_ty.kind();
if cx.tcx.is_diagnostic_item(sym::Result, some_def.did());
then {
Some(some_subst.type_at(1))
} else {
None
}
}
}

View File

@ -0,0 +1,55 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::SpanlessEq;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::source_map::Spanned;
use rustc_span::sym;
use super::GET_LAST_WITH_LEN;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) {
// Argument to "get" is a subtraction
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
},
lhs,
rhs,
) = arg.kind
// LHS of subtraction is "x.len()"
&& let ExprKind::MethodCall(lhs_path, [lhs_recv], _) = &lhs.kind
&& lhs_path.ident.name == sym::len
// RHS of subtraction is 1
&& let ExprKind::Lit(rhs_lit) = &rhs.kind
&& let LitKind::Int(1, ..) = rhs_lit.node
// check that recv == lhs_recv `recv.get(lhs_recv.len() - 1)`
&& SpanlessEq::new(cx).eq_expr(recv, lhs_recv)
&& !recv.can_have_side_effects()
{
let method = match cx.typeck_results().expr_ty_adjusted(recv).peel_refs().kind() {
ty::Adt(def, _) if cx.tcx.is_diagnostic_item(sym::VecDeque, def.did()) => "back",
ty::Slice(_) => "last",
_ => return,
};
let mut applicability = Applicability::MachineApplicable;
let recv_snippet = snippet_with_applicability(cx, recv.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
GET_LAST_WITH_LEN,
expr.span,
&format!("accessing last element with `{recv_snippet}.get({recv_snippet}.len() - 1)`"),
"try",
format!("{recv_snippet}.{method}()"),
applicability,
);
}
}

View File

@ -34,13 +34,18 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, cal
if let ast::LitKind::Int(start_idx, _) = start_lit.node;
then {
let mut applicability = Applicability::MachineApplicable;
let suggest = if start_idx == 0 {
format!("{}.first()", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability))
} else {
format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx)
};
span_lint_and_sugg(
cx,
ITER_NEXT_SLICE,
expr.span,
"using `.iter().next()` on a Slice without end index",
"try calling",
format!("{}.get({})", snippet_with_applicability(cx, caller_var.span, "..", &mut applicability), start_idx),
suggest,
applicability,
);
}
@ -55,7 +60,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>, cal
"using `.iter().next()` on an array",
"try calling",
format!(
"{}.get(0)",
"{}.first()",
snippet_with_applicability(cx, caller_expr.span, "..", &mut applicability)
),
applicability,

View File

@ -21,6 +21,7 @@ mod filter_next;
mod flat_map_identity;
mod flat_map_option;
mod from_iter_instead_of_collect;
mod get_last_with_len;
mod get_unwrap;
mod implicit_clone;
mod inefficient_to_string;
@ -44,6 +45,7 @@ mod map_identity;
mod map_unwrap_or;
mod needless_option_as_deref;
mod needless_option_take;
mod no_effect_replace;
mod ok_expect;
mod option_as_ref_deref;
mod option_map_or_none;
@ -192,25 +194,18 @@ declare_clippy_lint! {
///
/// ### Examples
/// ```rust
/// # let opt = Some(1);
///
/// // Bad
/// opt.unwrap();
///
/// // Good
/// opt.expect("more helpful message");
/// # let option = Some(1);
/// # let result: Result<usize, ()> = Ok(1);
/// option.unwrap();
/// result.unwrap();
/// ```
///
/// // or
///
/// Use instead:
/// ```rust
/// # let res: Result<usize, ()> = Ok(1);
///
/// // Bad
/// res.unwrap();
///
/// // Good
/// res.expect("more helpful message");
/// # let option = Some(1);
/// # let result: Result<usize, ()> = Ok(1);
/// option.expect("more helpful message");
/// result.expect("more helpful message");
/// ```
#[clippy::version = "1.45.0"]
pub UNWRAP_USED,
@ -233,27 +228,21 @@ declare_clippy_lint! {
///
/// ### Examples
/// ```rust,ignore
/// # let opt = Some(1);
///
/// // Bad
/// opt.expect("one");
///
/// // Good
/// let opt = Some(1);
/// opt?;
/// # let option = Some(1);
/// # let result: Result<usize, ()> = Ok(1);
/// option.expect("one");
/// result.expect("one");
/// ```
///
/// Use instead:
/// ```rust,ignore
/// # let option = Some(1);
/// # let result: Result<usize, ()> = Ok(1);
/// option?;
///
/// // or
///
/// ```rust
/// # let res: Result<usize, ()> = Ok(1);
///
/// // Bad
/// res.expect("one");
///
/// // Good
/// res?;
/// # Ok::<(), ()>(())
/// result?;
/// ```
#[clippy::version = "1.45.0"]
pub EXPECT_USED,
@ -429,26 +418,20 @@ declare_clippy_lint! {
///
/// ### Examples
/// ```rust
/// # let x = Some(1);
///
/// // Bad
/// x.map(|a| a + 1).unwrap_or(0);
///
/// // Good
/// x.map_or(0, |a| a + 1);
/// # let option = Some(1);
/// # let result: Result<usize, ()> = Ok(1);
/// # fn some_function(foo: ()) -> usize { 1 }
/// option.map(|a| a + 1).unwrap_or(0);
/// result.map(|a| a + 1).unwrap_or_else(some_function);
/// ```
///
/// // or
///
/// Use instead:
/// ```rust
/// # let x: Result<usize, ()> = Ok(1);
/// # let option = Some(1);
/// # let result: Result<usize, ()> = Ok(1);
/// # fn some_function(foo: ()) -> usize { 1 }
///
/// // Bad
/// x.map(|a| a + 1).unwrap_or_else(some_function);
///
/// // Good
/// x.map_or_else(some_function, |a| a + 1);
/// option.map_or(0, |a| a + 1);
/// result.map_or_else(some_function, |a| a + 1);
/// ```
#[clippy::version = "1.45.0"]
pub MAP_UNWRAP_OR,
@ -791,13 +774,14 @@ declare_clippy_lint! {
/// # let foo = Some(String::new());
/// foo.unwrap_or(String::new());
/// ```
/// this can instead be written:
///
/// Use instead:
/// ```rust
/// # let foo = Some(String::new());
/// foo.unwrap_or_else(String::new);
/// ```
/// or
/// ```rust
///
/// // or
///
/// # let foo = Some(String::new());
/// foo.unwrap_or_default();
/// ```
@ -861,15 +845,14 @@ declare_clippy_lint! {
/// # let err_code = "418";
/// # let err_msg = "I'm a teapot";
/// foo.expect(&format!("Err {}: {}", err_code, err_msg));
/// ```
/// or
/// ```rust
///
/// // or
///
/// # let foo = Some(String::new());
/// # let err_code = "418";
/// # let err_msg = "I'm a teapot";
/// foo.expect(format!("Err {}: {}", err_code, err_msg).as_str());
/// ```
/// this can instead be written:
///
/// Use instead:
/// ```rust
/// # let foo = Some(String::new());
/// # let err_code = "418";
@ -1209,6 +1192,38 @@ declare_clippy_lint! {
"replace `.drain(..)` with `.into_iter()`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for using `x.get(x.len() - 1)` instead of
/// `x.last()`.
///
/// ### Why is this bad?
/// Using `x.last()` is easier to read and has the same
/// result.
///
/// Note that using `x[x.len() - 1]` is semantically different from
/// `x.last()`. Indexing into the array will panic on out-of-bounds
/// accesses, while `x.get()` and `x.last()` will return `None`.
///
/// There is another lint (get_unwrap) that covers the case of using
/// `x.get(index).unwrap()` instead of `x[index]`.
///
/// ### Example
/// ```rust
/// // Bad
/// let x = vec![2, 3, 5];
/// let last_element = x.get(x.len() - 1);
///
/// // Good
/// let x = vec![2, 3, 5];
/// let last_element = x.last();
/// ```
#[clippy::version = "1.37.0"]
pub GET_LAST_WITH_LEN,
complexity,
"Using `x.get(x.len() - 1)` when `x.last()` is correct and simpler"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for use of `.get().unwrap()` (or
@ -2195,6 +2210,24 @@ declare_clippy_lint! {
"using `.as_ref().take()` on a temporary value"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for `replace` statements which have no effect.
///
/// ### Why is this bad?
/// It's either a mistake or confusing.
///
/// ### Example
/// ```rust
/// "1234".replace("12", "12");
/// "1234".replacen("12", "12", 1);
/// ```
#[clippy::version = "1.62.0"]
pub NO_EFFECT_REPLACE,
suspicious,
"replace with no effect"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Option<RustcVersion>,
@ -2264,6 +2297,7 @@ impl_lint_pass!(Methods => [
BYTES_NTH,
ITER_SKIP_NEXT,
GET_UNWRAP,
GET_LAST_WITH_LEN,
STRING_EXTEND_CHARS,
ITER_CLONED_COLLECT,
ITER_WITH_DRAIN,
@ -2294,6 +2328,7 @@ impl_lint_pass!(Methods => [
NEEDLESS_OPTION_AS_DEREF,
IS_DIGIT_ASCII_RADIX,
NEEDLESS_OPTION_TAKE,
NO_EFFECT_REPLACE,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -2590,6 +2625,7 @@ impl Methods {
inspect_for_each::check(cx, expr, span2);
}
},
("get", [arg]) => get_last_with_len::check(cx, expr, recv, arg),
("get_or_insert_with", [arg]) => unnecessary_lazy_eval::check(cx, expr, recv, arg, "get_or_insert"),
("is_file", []) => filetype_is_file::check(cx, expr, recv),
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, self.msrv),
@ -2705,6 +2741,9 @@ impl Methods {
unnecessary_lazy_eval::check(cx, expr, recv, u_arg, "unwrap_or");
},
},
("replace" | "replacen", [arg1, arg2] | [arg1, arg2, _]) => {
no_effect_replace::check(cx, expr, arg1, arg2);
},
_ => {},
}
}

View File

@ -0,0 +1,47 @@
use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::SpanlessEq;
use if_chain::if_chain;
use rustc_ast::LitKind;
use rustc_hir::ExprKind;
use rustc_lint::LateContext;
use rustc_span::sym;
use super::NO_EFFECT_REPLACE;
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx rustc_hir::Expr<'_>,
arg1: &'tcx rustc_hir::Expr<'_>,
arg2: &'tcx rustc_hir::Expr<'_>,
) {
let ty = cx.typeck_results().expr_ty(expr).peel_refs();
if !(ty.is_str() || is_type_diagnostic_item(cx, ty, sym::String)) {
return;
}
if_chain! {
if let ExprKind::Lit(spanned) = &arg1.kind;
if let Some(param1) = lit_string_value(&spanned.node);
if let ExprKind::Lit(spanned) = &arg2.kind;
if let LitKind::Str(param2, _) = &spanned.node;
if param1 == param2.as_str();
then {
span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
}
}
if SpanlessEq::new(cx).eq_expr(arg1, arg2) {
span_lint(cx, NO_EFFECT_REPLACE, expr.span, "replacing text with itself");
}
}
fn lit_string_value(node: &LitKind) -> Option<String> {
match node {
LitKind::Char(value) => Some(value.to_string()),
LitKind::Str(value, _) => Some(value.as_str().to_owned()),
_ => None,
}
}

View File

@ -97,7 +97,7 @@ pub(super) fn check<'tcx>(
let func_snippet = snippet(cx, map_arg.span, "..");
let msg = "called `map_or(None, ..)` on an `Option` value. This can be done more directly by calling \
`and_then(..)` instead";
return span_lint_and_sugg(
span_lint_and_sugg(
cx,
OPTION_MAP_OR_NONE,
expr.span,
@ -110,7 +110,7 @@ pub(super) fn check<'tcx>(
let msg = "called `map_or(None, Some)` on a `Result` value. This can be done more directly by calling \
`ok()` instead";
let self_snippet = snippet(cx, recv.span, "..");
return span_lint_and_sugg(
span_lint_and_sugg(
cx,
RESULT_MAP_OR_INTO_OPTION,
expr.span,

View File

@ -415,7 +415,7 @@ fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id:
/// Returns true if the named method can be used to convert the receiver to its "owned"
/// representation.
fn is_to_owned_like(cx: &LateContext<'_>, method_name: Symbol, method_def_id: DefId) -> bool {
is_clone_like(cx, &*method_name.as_str(), method_def_id)
is_clone_like(cx, method_name.as_str(), method_def_id)
|| is_cow_into_owned(cx, method_name, method_def_id)
|| is_to_string(cx, method_name, method_def_id)
}

View File

@ -18,11 +18,11 @@ declare_clippy_lint! {
/// the least it hurts readability of the code.
///
/// ### Example
/// ```ignore
/// ```rust,ignore
/// min(0, max(100, x))
/// ```
/// or
/// ```ignore
///
/// // or
///
/// x.max(100).min(0)
/// ```
/// It will always be equal to `0`. Probably the author meant to clamp the value

View File

@ -103,11 +103,14 @@ declare_clippy_lint! {
/// let x = 1.2331f64;
/// let y = 1.2332f64;
///
/// // Bad
/// if y == 1.23f64 { }
/// if y != x {} // where both are floats
/// ```
///
/// // Good
/// Use instead:
/// ```rust
/// # let x = 1.2331f64;
/// # let y = 1.2332f64;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;
@ -258,10 +261,13 @@ declare_clippy_lint! {
/// let x: f64 = 1.0;
/// const ONE: f64 = 1.00;
///
/// // Bad
/// if x == ONE { } // where both are floats
/// ```
///
/// // Good
/// Use instead:
/// ```rust
/// # let x: f64 = 1.0;
/// # const ONE: f64 = 1.00;
/// let error_margin = f64::EPSILON; // Use an epsilon for comparison
/// // Or, if Rust <= 1.42, use `std::f64::EPSILON` constant instead.
/// // let error_margin = std::f64::EPSILON;

View File

@ -0,0 +1,116 @@
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{GenericArg, Item, ItemKind, QPath, Ty, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GenericParamDefKind;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
/// ### What it does
/// Checks for type parameters which are positioned inconsistently between
/// a type definition and impl block. Specifically, a paramater in an impl
/// block which has the same name as a parameter in the type def, but is in
/// a different place.
///
/// ### Why is this bad?
/// Type parameters are determined by their position rather than name.
/// Naming type parameters inconsistently may cause you to refer to the
/// wrong type parameter.
///
/// ### Example
/// ```rust
/// struct Foo<A, B> {
/// x: A,
/// y: B,
/// }
/// // inside the impl, B refers to Foo::A
/// impl<B, A> Foo<B, A> {}
/// ```
/// Use instead:
/// ```rust
/// struct Foo<A, B> {
/// x: A,
/// y: B,
/// }
/// impl<A, B> Foo<A, B> {}
/// ```
#[clippy::version = "1.62.0"]
pub MISMATCHING_TYPE_PARAM_ORDER,
pedantic,
"type parameter positioned inconsistently between type def and impl block"
}
declare_lint_pass!(TypeParamMismatch => [MISMATCHING_TYPE_PARAM_ORDER]);
impl<'tcx> LateLintPass<'tcx> for TypeParamMismatch {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if_chain! {
if !item.span.from_expansion();
if let ItemKind::Impl(imp) = &item.kind;
if let TyKind::Path(QPath::Resolved(_, path)) = &imp.self_ty.kind;
if let Some(segment) = path.segments.iter().next();
if let Some(generic_args) = segment.args;
if !generic_args.args.is_empty();
then {
// get the name and span of the generic parameters in the Impl
let impl_params = generic_args.args.iter()
.filter_map(|p|
match p {
GenericArg::Type(Ty {kind: TyKind::Path(QPath::Resolved(_, path)), ..}) =>
Some((path.segments[0].ident.to_string(), path.span)),
_ => None,
}
);
// find the type that the Impl is for
// only lint on struct/enum/union for now
let defid = match path.res {
Res::Def(DefKind::Struct | DefKind::Enum | DefKind::Union, defid) => defid,
_ => return,
};
// get the names of the generic parameters in the type
let type_params = &cx.tcx.generics_of(defid).params;
let type_param_names: Vec<_> = type_params.iter()
.filter_map(|p|
match p.kind {
GenericParamDefKind::Type {..} => Some(p.name.to_string()),
_ => None,
}
).collect();
// hashmap of name -> index for mismatch_param_name
let type_param_names_hashmap: FxHashMap<&String, usize> =
type_param_names.iter().enumerate().map(|(i, param)| (param, i)).collect();
let type_name = segment.ident;
for (i, (impl_param_name, impl_param_span)) in impl_params.enumerate() {
if mismatch_param_name(i, &impl_param_name, &type_param_names_hashmap) {
let msg = format!("`{}` has a similarly named generic type parameter `{}` in its declaration, but in a different order",
type_name, impl_param_name);
let help = format!("try `{}`, or a name that does not conflict with `{}`'s generic params",
type_param_names[i], type_name);
span_lint_and_help(
cx,
MISMATCHING_TYPE_PARAM_ORDER,
impl_param_span,
&msg,
None,
&help
);
}
}
}
}
}
}
// Checks if impl_param_name is the same as one of type_param_names,
// and is in a different position
fn mismatch_param_name(i: usize, impl_param_name: &String, type_param_names: &FxHashMap<&String, usize>) -> bool {
if let Some(j) = type_param_names.get(impl_param_name) {
if i != *j {
return true;
}
}
false
}

View File

@ -120,7 +120,7 @@ impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> {
self.visit_expr(if_expr);
}
// make sure top level arm expressions aren't linted
self.maybe_walk_expr(&*arm.body);
self.maybe_walk_expr(arm.body);
}
},
_ => walk_expr(self, e),

View File

@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for MutableKeyType {
if let hir::PatKind::Wild = local.pat.kind {
return;
}
check_ty(cx, local.span, cx.typeck_results().pat_ty(&*local.pat));
check_ty(cx, local.span, cx.typeck_results().pat_ty(local.pat));
}
}

View File

@ -22,9 +22,12 @@ declare_clippy_lint! {
/// ### Example
/// ```rust,ignore
/// debug_assert_eq!(vec![3].pop(), Some(3));
///
/// // or
/// fn take_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
/// debug_assert!(take_a_mut_parameter(&mut 5));
///
/// # let mut x = 5;
/// # fn takes_a_mut_parameter(_: &mut u32) -> bool { unimplemented!() }
/// debug_assert!(takes_a_mut_parameter(&mut x));
/// ```
#[clippy::version = "1.40.0"]
pub DEBUG_ASSERT_WITH_MUT_CALL,

View File

@ -194,14 +194,15 @@ fn assignment_suggestions<'tcx>(
}))
.collect::<Option<Vec<(Span, String)>>>()?;
let applicability = if suggestions.len() > 1 {
match suggestions.len() {
// All of `exprs` are never types
// https://github.com/rust-lang/rust-clippy/issues/8911
0 => None,
1 => Some((Applicability::MachineApplicable, suggestions)),
// multiple suggestions don't work with rustfix in multipart_suggest
// https://github.com/rust-lang/rustfix/issues/141
Applicability::Unspecified
} else {
Applicability::MachineApplicable
};
Some((applicability, suggestions))
_ => Some((Applicability::Unspecified, suggestions)),
}
}
struct Usage<'tcx> {

View File

@ -51,16 +51,16 @@ declare_clippy_lint! {
}
#[derive(Copy, Clone, Default)]
pub struct Arithmetic {
pub struct NumericArithmetic {
expr_span: Option<Span>,
/// This field is used to check whether expressions are constants, such as in enum discriminants
/// and consts
const_span: Option<Span>,
}
impl_lint_pass!(Arithmetic => [INTEGER_ARITHMETIC, FLOAT_ARITHMETIC]);
impl_lint_pass!(NumericArithmetic => [INTEGER_ARITHMETIC, FLOAT_ARITHMETIC]);
impl<'tcx> LateLintPass<'tcx> for Arithmetic {
impl<'tcx> LateLintPass<'tcx> for NumericArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
if self.expr_span.is_some() {
return;

View File

@ -61,7 +61,7 @@ fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, body: &'tcx hir
expr_visitor_no_bodies(|expr| {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return true };
if matches!(
&*cx.tcx.item_name(macro_call.def_id).as_str(),
cx.tcx.item_name(macro_call.def_id).as_str(),
"unimplemented" | "unreachable" | "panic" | "todo" | "assert" | "assert_eq" | "assert_ne"
) {
panics.push(macro_call.span);

View File

@ -233,7 +233,7 @@ impl<'tcx> LateLintPass<'tcx> for PassByRefOrValue {
}
if let hir::TraitItemKind::Fn(method_sig, _) = &item.kind {
self.check_poly_fn(cx, item.def_id, &*method_sig.decl, None);
self.check_poly_fn(cx, item.def_id, method_sig.decl, None);
}
}

View File

@ -194,7 +194,7 @@ impl<'tcx> LateLintPass<'tcx> for Ranges {
},
ExprKind::Binary(ref op, l, r) => {
if meets_msrv(self.msrv, msrvs::RANGE_CONTAINS) {
check_possible_range_contains(cx, op.node, l, r, expr);
check_possible_range_contains(cx, op.node, l, r, expr, expr.span);
}
},
_ => {},
@ -213,12 +213,12 @@ fn check_possible_range_contains(
left: &Expr<'_>,
right: &Expr<'_>,
expr: &Expr<'_>,
span: Span,
) {
if in_constant(cx, expr.hir_id) {
return;
}
let span = expr.span;
let combine_and = match op {
BinOpKind::And | BinOpKind::BitAnd => true,
BinOpKind::Or | BinOpKind::BitOr => false,
@ -294,6 +294,20 @@ fn check_possible_range_contains(
);
}
}
// If the LHS is the same operator, we have to recurse to get the "real" RHS, since they have
// the same operator precedence
if_chain! {
if let ExprKind::Binary(ref lhs_op, _left, new_lhs) = left.kind;
if op == lhs_op.node;
let new_span = Span::new(new_lhs.span.lo(), right.span.hi(), expr.span.ctxt(), expr.span.parent());
if let Some(snip) = &snippet_opt(cx, new_span);
// Do not continue if we have mismatched number of parens, otherwise the suggestion is wrong
if snip.matches('(').count() == snip.matches(')').count();
then {
check_possible_range_contains(cx, op, new_lhs, right, expr, new_span);
}
}
}
struct RangeBounds<'a> {

View File

@ -2,7 +2,9 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::VecArgs;
use clippy_utils::last_path_segment;
use clippy_utils::macros::root_macro_call_first_node;
use clippy_utils::paths;
use clippy_utils::source::{indent_of, snippet};
use clippy_utils::ty::match_type;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
@ -11,10 +13,11 @@ use rustc_span::{sym, Span, Symbol};
declare_clippy_lint! {
/// ### What it does
/// Checks for `Arc::new` or `Rc::new` in `vec![elem; len]`
/// Checks for reference-counted pointers (`Arc`, `Rc`, `rc::Weak`, and `sync::Weak`)
/// in `vec![elem; len]`
///
/// ### Why is this bad?
/// This will create `elem` once and clone it `len` times - doing so with `Arc` or `Rc`
/// This will create `elem` once and clone it `len` times - doing so with `Arc`/`Rc`/`Weak`
/// is a bit misleading, as it will create references to the same pointer, rather
/// than different instances.
///
@ -26,7 +29,6 @@ declare_clippy_lint! {
/// ```
/// Use instead:
/// ```rust
///
/// // Initialize each value separately:
/// let mut data = Vec::with_capacity(100);
/// for _ in 0..100 {
@ -42,7 +44,7 @@ declare_clippy_lint! {
#[clippy::version = "1.62.0"]
pub RC_CLONE_IN_VEC_INIT,
suspicious,
"initializing `Arc` or `Rc` in `vec![elem; len]`"
"initializing reference-counted pointer in `vec![elem; len]`"
}
declare_lint_pass!(RcCloneInVecInit => [RC_CLONE_IN_VEC_INIT]);
@ -50,26 +52,12 @@ impl LateLintPass<'_> for RcCloneInVecInit {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return; };
let Some(VecArgs::Repeat(elem, len)) = VecArgs::hir(cx, expr) else { return; };
let Some(symbol) = new_reference_call(cx, elem) else { return; };
let Some((symbol, func_span)) = ref_init(cx, elem) else { return; };
emit_lint(cx, symbol, macro_call.span, elem, len);
emit_lint(cx, symbol, macro_call.span, elem, len, func_span);
}
}
fn elem_snippet(cx: &LateContext<'_>, elem: &Expr<'_>, symbol_name: &str) -> String {
let elem_snippet = snippet(cx, elem.span, "..").to_string();
if elem_snippet.contains('\n') {
// This string must be found in `elem_snippet`, otherwise we won't be constructing
// the snippet in the first place.
let reference_creation = format!("{symbol_name}::new");
let (code_until_reference_creation, _right) = elem_snippet.split_once(&reference_creation).unwrap();
return format!("{code_until_reference_creation}{reference_creation}(..)");
}
elem_snippet
}
fn loop_init_suggestion(elem: &str, len: &str, indent: &str) -> String {
format!(
r#"{{
@ -89,17 +77,17 @@ fn extract_suggestion(elem: &str, len: &str, indent: &str) -> String {
)
}
fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, lint_span: Span, elem: &Expr<'_>, len: &Expr<'_>) {
fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, lint_span: Span, elem: &Expr<'_>, len: &Expr<'_>, func_span: Span) {
let symbol_name = symbol.as_str();
span_lint_and_then(
cx,
RC_CLONE_IN_VEC_INIT,
lint_span,
&format!("calling `{symbol_name}::new` in `vec![elem; len]`"),
"initializing a reference-counted pointer in `vec![elem; len]`",
|diag| {
let len_snippet = snippet(cx, len.span, "..");
let elem_snippet = elem_snippet(cx, elem, symbol_name);
let elem_snippet = format!("{}(..)", snippet(cx, elem.span.with_hi(func_span.hi()), ".."));
let indentation = " ".repeat(indent_of(cx, lint_span).unwrap_or(0));
let loop_init_suggestion = loop_init_suggestion(&elem_snippet, len_snippet.as_ref(), &indentation);
let extract_suggestion = extract_suggestion(&elem_snippet, len_snippet.as_ref(), &indentation);
@ -109,7 +97,7 @@ fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, lint_span: Span, elem: &Expr<
lint_span,
format!("consider initializing each `{symbol_name}` element individually"),
loop_init_suggestion,
Applicability::Unspecified,
Applicability::HasPlaceholders,
);
diag.span_suggestion(
lint_span,
@ -117,23 +105,33 @@ fn emit_lint(cx: &LateContext<'_>, symbol: Symbol, lint_span: Span, elem: &Expr<
"or if this is intentional, consider extracting the `{symbol_name}` initialization to a variable"
),
extract_suggestion,
Applicability::Unspecified,
Applicability::HasPlaceholders,
);
},
);
}
/// Checks whether the given `expr` is a call to `Arc::new` or `Rc::new`
fn new_reference_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<Symbol> {
/// Checks whether the given `expr` is a call to `Arc::new`, `Rc::new`, or evaluates to a `Weak`
fn ref_init(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(Symbol, Span)> {
if_chain! {
if let ExprKind::Call(func, _args) = expr.kind;
if let ExprKind::Path(ref func_path @ QPath::TypeRelative(ty, _)) = func.kind;
if let TyKind::Path(ref ty_path) = ty.kind;
if let Some(def_id) = cx.qpath_res(ty_path, ty.hir_id).opt_def_id();
if last_path_segment(func_path).ident.name == sym::new;
then {
return cx.tcx.get_diagnostic_name(def_id).filter(|symbol| symbol == &sym::Arc || symbol == &sym::Rc);
if last_path_segment(func_path).ident.name == sym::new
&& let Some(symbol) = cx
.tcx
.get_diagnostic_name(def_id)
.filter(|symbol| symbol == &sym::Arc || symbol == &sym::Rc) {
return Some((symbol, func.span));
}
let ty_path = cx.typeck_results().expr_ty(expr);
if match_type(cx, ty_path, &paths::WEAK_RC) || match_type(cx, ty_path, &paths::WEAK_ARC) {
return Some((Symbol::intern("Weak"), func.span));
}
}
}

View File

@ -288,8 +288,8 @@ fn is_call_with_ref_arg<'tcx>(
if let mir::TerminatorKind::Call { func, args, destination, .. } = kind;
if args.len() == 1;
if let mir::Operand::Move(mir::Place { local, .. }) = &args[0];
if let ty::FnDef(def_id, _) = *func.ty(&*mir, cx.tcx).kind();
if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx));
if let ty::FnDef(def_id, _) = *func.ty(mir, cx.tcx).kind();
if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(mir, cx.tcx));
if !is_copy(cx, inner_ty);
then {
Some((def_id, *local, inner_ty, destination.as_local()?))
@ -318,7 +318,7 @@ fn find_stmt_assigns_to<'tcx>(
None
})?;
match (by_ref, &*rvalue) {
match (by_ref, rvalue) {
(true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place))) => {
Some(base_local_and_movability(cx, mir, *place))
},

View File

@ -51,12 +51,12 @@ impl RedundantStaticLifetimes {
fn visit_type(&mut self, ty: &Ty, cx: &EarlyContext<'_>, reason: &str) {
match ty.kind {
// Be careful of nested structures (arrays and tuples)
TyKind::Array(ref ty, _) => {
self.visit_type(&*ty, cx, reason);
TyKind::Array(ref ty, _) | TyKind::Slice(ref ty) => {
self.visit_type(ty, cx, reason);
},
TyKind::Tup(ref tup) => {
for tup_ty in tup {
self.visit_type(&*tup_ty, cx, reason);
self.visit_type(tup_ty, cx, reason);
}
},
// This is what we are looking for !
@ -89,9 +89,6 @@ impl RedundantStaticLifetimes {
}
self.visit_type(&*borrow_type.ty, cx, reason);
},
TyKind::Slice(ref ty) => {
self.visit_type(ty, cx, reason);
},
_ => {},
}
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::source::snippet_opt;
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::{fn_def_id, path_to_local_id};
use if_chain::if_chain;
use rustc_ast::ast::Attribute;
@ -226,14 +226,10 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, inner_span: Option<Spa
}
match inner_span {
Some(inner_span) => {
if in_external_macro(cx.tcx.sess, inner_span) || inner_span.from_expansion() {
return;
}
let mut applicability = Applicability::MachineApplicable;
span_lint_and_then(cx, NEEDLESS_RETURN, ret_span, "unneeded `return` statement", |diag| {
if let Some(snippet) = snippet_opt(cx, inner_span) {
diag.span_suggestion(ret_span, "remove `return`", snippet, Applicability::MachineApplicable);
}
let (snippet, _) = snippet_with_context(cx, inner_span, ret_span.ctxt(), "..", &mut applicability);
diag.span_suggestion(ret_span, "remove `return`", snippet, applicability);
});
},
None => match replacement {
@ -287,7 +283,7 @@ struct BorrowVisitor<'a, 'tcx> {
impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if self.borrows {
if self.borrows || expr.span.from_expansion() {
return;
}

View File

@ -5,9 +5,7 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::Res;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::hir_id::ItemLocalId;
use rustc_hir::{
Block, Body, BodyOwnerKind, Expr, ExprKind, HirId, Let, Node, Pat, PatKind, QPath, UnOp,
};
use rustc_hir::{Block, Body, BodyOwnerKind, Expr, ExprKind, HirId, Let, Node, Pat, PatKind, QPath, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{Span, Symbol};
@ -141,31 +139,34 @@ impl<'tcx> LateLintPass<'tcx> for Shadow {
fn check_body(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
let hir = cx.tcx.hir();
if !matches!(hir.body_owner_kind(hir.body_owner_def_id(body.id())), BodyOwnerKind::Closure)
{
if !matches!(
hir.body_owner_kind(hir.body_owner_def_id(body.id())),
BodyOwnerKind::Closure
) {
self.bindings.push(FxHashMap::default());
}
}
fn check_body_post(&mut self, cx: &LateContext<'_>, body: &Body<'_>) {
let hir = cx.tcx.hir();
if !matches!(hir.body_owner_kind(hir.body_owner_def_id(body.id())), BodyOwnerKind::Closure)
{
if !matches!(
hir.body_owner_kind(hir.body_owner_def_id(body.id())),
BodyOwnerKind::Closure
) {
self.bindings.pop();
}
}
}
fn is_shadow(
cx: &LateContext<'_>,
owner: LocalDefId,
first: ItemLocalId,
second: ItemLocalId,
) -> bool {
let scope_tree = cx.tcx.region_scope_tree(owner);
let first_scope = scope_tree.var_scope(first).unwrap();
let second_scope = scope_tree.var_scope(second).unwrap();
scope_tree.is_subscope_of(second_scope, first_scope)
fn is_shadow(cx: &LateContext<'_>, owner: LocalDefId, first: ItemLocalId, second: ItemLocalId) -> bool {
let scope_tree = cx.tcx.region_scope_tree(owner.to_def_id());
if let Some(first_scope) = scope_tree.var_scope(first) {
if let Some(second_scope) = scope_tree.var_scope(second) {
return scope_tree.is_subscope_of(second_scope, first_scope);
}
}
false
}
fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span) {
@ -177,16 +178,15 @@ fn lint_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, shadowed: HirId, span: Span)
snippet(cx, expr.span, "..")
);
(SHADOW_SAME, msg)
}
},
Some(expr) if is_local_used(cx, expr, shadowed) => {
let msg = format!("`{}` is shadowed", snippet(cx, pat.span, "_"));
(SHADOW_REUSE, msg)
}
},
_ => {
let msg =
format!("`{}` shadows a previous, unrelated binding", snippet(cx, pat.span, "_"));
let msg = format!("`{}` shadows a previous, unrelated binding", snippet(cx, pat.span, "_"));
(SHADOW_UNRELATED, msg)
}
},
};
span_lint_and_note(
cx,
@ -215,7 +215,14 @@ fn is_self_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, mut expr: &Expr<'_>, hir_
expr = match expr.kind {
ExprKind::Box(e)
| ExprKind::AddrOf(_, _, e)
| ExprKind::Block(&Block { stmts: [], expr: Some(e), .. }, _)
| ExprKind::Block(
&Block {
stmts: [],
expr: Some(e),
..
},
_,
)
| ExprKind::Unary(UnOp::Deref, e) => e,
ExprKind::Path(QPath::Resolved(None, path)) => break path.res == Res::Local(hir_id),
_ => break false,

View File

@ -110,7 +110,7 @@ fn get_pointee_ty_and_count_expr<'tcx>(
// Find calls to copy_{from,to}{,_nonoverlapping} and write_bytes methods
if let ExprKind::MethodCall(method_path, [ptr_self, .., count], _) = expr.kind;
let method_ident = method_path.ident.as_str();
if METHODS.iter().any(|m| *m == &*method_ident);
if METHODS.iter().any(|m| *m == method_ident);
// Get the pointee type
if let ty::RawPtr(TypeAndMut { ty: pointee_ty, .. }) =

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