Merge remote-tracking branch 'upstream/master' into rustup

This commit is contained in:
Philipp Krones 2024-05-16 14:20:56 -07:00
commit c200dad300
No known key found for this signature in database
GPG Key ID: 1CA0DF2AF59D68A5
124 changed files with 3808 additions and 1162 deletions

View File

@ -5249,6 +5249,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_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation
[`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
@ -5447,6 +5448,7 @@ Released 2018-09-13
[`little_endian_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#little_endian_bytes
[`logic_bug`]: https://rust-lang.github.io/rust-clippy/master/index.html#logic_bug
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
[`macro_metavars_in_unsafe`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
[`manual_assert`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_assert
@ -5702,6 +5704,7 @@ Released 2018-09-13
[`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref
[`ref_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_patterns
[`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro
[`renamed_function_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#renamed_function_params
[`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once
[`repeat_vec_with_capacity`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_vec_with_capacity
[`replace_consts`]: https://rust-lang.github.io/rust-clippy/master/index.html#replace_consts
@ -5941,6 +5944,7 @@ Released 2018-09-13
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
[`allow-print-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-print-in-tests
[`allow-private-module-inception`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-private-module-inception
[`allow-renamed-params-for`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-renamed-params-for
[`allow-unwrap-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-unwrap-in-tests
[`allow-useless-vec-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-useless-vec-in-tests
[`allowed-dotfiles`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allowed-dotfiles
@ -6002,4 +6006,5 @@ Released 2018-09-13
[`vec-box-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#vec-box-size-threshold
[`verbose-bit-mask-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#verbose-bit-mask-threshold
[`warn-on-all-wildcard-imports`]: https://doc.rust-lang.org/clippy/lint_configuration.html#warn-on-all-wildcard-imports
[`warn-unsafe-macro-metavars-in-private-macros`]: https://doc.rust-lang.org/clippy/lint_configuration.html#warn-unsafe-macro-metavars-in-private-macros
<!-- end autogenerated links to configuration documentation -->

View File

@ -122,6 +122,28 @@ Whether to allow module inception if it's not public.
* [`module_inception`](https://rust-lang.github.io/rust-clippy/master/index.html#module_inception)
## `allow-renamed-params-for`
List of trait paths to ignore when checking renamed function parameters.
#### Example
```toml
allow-renamed-params-for = [ "std::convert::From" ]
```
#### Noteworthy
- By default, the following traits are ignored: `From`, `TryFrom`, `FromStr`
- `".."` can be used as part of the list to indicate that the configured values should be appended to the
default configuration of Clippy. By default, any configuration will replace the default value.
**Default Value:** `["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"]`
---
**Affected lints:**
* [`renamed_function_params`](https://rust-lang.github.io/rust-clippy/master/index.html#renamed_function_params)
## `allow-unwrap-in-tests`
Whether `unwrap` should be allowed in test functions or `#[cfg(test)]`
@ -900,3 +922,13 @@ Whether to allow certain wildcard imports (prelude, super in tests).
* [`wildcard_imports`](https://rust-lang.github.io/rust-clippy/master/index.html#wildcard_imports)
## `warn-unsafe-macro-metavars-in-private-macros`
Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
**Default Value:** `false`
---
**Affected lints:**
* [`macro_metavars_in_unsafe`](https://rust-lang.github.io/rust-clippy/master/index.html#macro_metavars_in_unsafe)

View File

@ -40,6 +40,8 @@ const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[
const DEFAULT_DISALLOWED_NAMES: &[&str] = &["foo", "baz", "quux"];
const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z", "w", "n"];
const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"];
const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] =
&["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"];
/// Conf with parse errors
#[derive(Default)]
@ -613,6 +615,27 @@ define_Conf! {
/// - Use `".."` as part of the list to indicate that the configured values should be appended to the
/// default configuration of Clippy. By default, any configuration will replace the default value
(allowed_prefixes: Vec<String> = DEFAULT_ALLOWED_PREFIXES.iter().map(ToString::to_string).collect()),
/// Lint: RENAMED_FUNCTION_PARAMS.
///
/// List of trait paths to ignore when checking renamed function parameters.
///
/// #### Example
///
/// ```toml
/// allow-renamed-params-for = [ "std::convert::From" ]
/// ```
///
/// #### Noteworthy
///
/// - By default, the following traits are ignored: `From`, `TryFrom`, `FromStr`
/// - `".."` can be used as part of the list to indicate that the configured values should be appended to the
/// default configuration of Clippy. By default, any configuration will replace the default value.
(allow_renamed_params_for: Vec<String> =
DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS.iter().map(ToString::to_string).collect()),
/// Lint: MACRO_METAVARS_IN_UNSAFE.
///
/// Whether to also emit warnings for unsafe blocks with metavariable expansions in **private** macros.
(warn_unsafe_macro_metavars_in_private_macros: bool = false),
}
/// Search for the configuration file.
@ -674,6 +697,10 @@ fn deserialize(file: &SourceFile) -> TryConf {
extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS);
extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES);
extend_vec_if_indicator_present(&mut conf.conf.allowed_prefixes, DEFAULT_ALLOWED_PREFIXES);
extend_vec_if_indicator_present(
&mut conf.conf.allow_renamed_params_for,
DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS,
);
// TODO: THIS SHOULD BE TESTED, this comment will be gone soon
if conf.conf.allowed_idents_below_min_chars.contains("..") {
conf.conf

View File

@ -26,7 +26,8 @@ msrv_aliases! {
1,63,0 { CLONE_INTO }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE }
1,59,0 { THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY, CONST_RAW_PTR_DEREF }
1,56,0 { CONST_FN_UNION }
1,55,0 { SEEK_REWIND }
1,54,0 { INTO_KEYS }
1,53,0 { OR_PATTERNS, MANUAL_BITS, BTREE_MAP_RETAIN, BTREE_SET_RETAIN, ARRAY_INTO_ITERATOR }

View File

@ -1,11 +1,12 @@
[package]
name = "clippy_dev"
description = "Clippy developer tooling"
version = "0.0.1"
edition = "2021"
[dependencies]
aho-corasick = "1.0"
clap = "4.1.4"
clap = { version = "4.4", features = ["derive"] }
indoc = "1.0"
itertools = "0.12"
opener = "0.6"

View File

@ -2,350 +2,292 @@
// warn on lints, that are included in `rust-lang/rust`s bootstrap
#![warn(rust_2018_idioms, unused_lifetimes)]
use clap::{Arg, ArgAction, ArgMatches, Command};
use clap::{Args, Parser, Subcommand};
use clippy_dev::{dogfood, fmt, lint, new_lint, serve, setup, update_lints};
use indoc::indoc;
use std::convert::Infallible;
fn main() {
let matches = get_clap_config();
let dev = Dev::parse();
match matches.subcommand() {
Some(("bless", _)) => {
match dev.command {
DevCommand::Bless => {
eprintln!("use `cargo bless` to automatically replace `.stderr` and `.fixed` files as tests are being run");
},
Some(("dogfood", matches)) => {
dogfood::dogfood(
matches.get_flag("fix"),
matches.get_flag("allow-dirty"),
matches.get_flag("allow-staged"),
);
},
Some(("fmt", matches)) => {
fmt::run(matches.get_flag("check"), matches.get_flag("verbose"));
},
Some(("update_lints", matches)) => {
if matches.get_flag("print-only") {
DevCommand::Dogfood {
fix,
allow_dirty,
allow_staged,
} => dogfood::dogfood(fix, allow_dirty, allow_staged),
DevCommand::Fmt { check, verbose } => fmt::run(check, verbose),
DevCommand::UpdateLints { print_only, check } => {
if print_only {
update_lints::print_lints();
} else if matches.get_flag("check") {
} else if check {
update_lints::update(update_lints::UpdateMode::Check);
} else {
update_lints::update(update_lints::UpdateMode::Change);
}
},
Some(("new_lint", matches)) => {
match new_lint::create(
matches.get_one::<String>("pass").unwrap(),
matches.get_one::<String>("name"),
matches.get_one::<String>("category").map(String::as_str),
matches.get_one::<String>("type").map(String::as_str),
matches.get_flag("msrv"),
) {
Ok(()) => update_lints::update(update_lints::UpdateMode::Change),
Err(e) => eprintln!("Unable to create lint: {e}"),
}
DevCommand::NewLint {
pass,
name,
category,
r#type,
msrv,
} => match new_lint::create(&pass, &name, &category, r#type.as_deref(), msrv) {
Ok(()) => update_lints::update(update_lints::UpdateMode::Change),
Err(e) => eprintln!("Unable to create lint: {e}"),
},
Some(("setup", sub_command)) => match sub_command.subcommand() {
Some(("git-hook", matches)) => {
if matches.get_flag("remove") {
setup::git_hook::remove_hook();
} else {
setup::git_hook::install_hook(matches.get_flag("force-override"));
}
},
Some(("intellij", matches)) => {
if matches.get_flag("remove") {
DevCommand::Setup(SetupCommand { subcommand }) => match subcommand {
SetupSubcommand::Intellij { remove, repo_path } => {
if remove {
setup::intellij::remove_rustc_src();
} else {
setup::intellij::setup_rustc_src(
matches
.get_one::<String>("rustc-repo-path")
.expect("this field is mandatory and therefore always valid"),
);
setup::intellij::setup_rustc_src(&repo_path);
}
},
Some(("toolchain", matches)) => {
setup::toolchain::create(
matches.get_flag("force"),
matches.get_flag("release"),
matches.get_one::<String>("name").unwrap(),
);
SetupSubcommand::GitHook { remove, force_override } => {
if remove {
setup::git_hook::remove_hook();
} else {
setup::git_hook::install_hook(force_override);
}
},
Some(("vscode-tasks", matches)) => {
if matches.get_flag("remove") {
SetupSubcommand::Toolchain { force, release, name } => setup::toolchain::create(force, release, &name),
SetupSubcommand::VscodeTasks { remove, force_override } => {
if remove {
setup::vscode::remove_tasks();
} else {
setup::vscode::install_tasks(matches.get_flag("force-override"));
setup::vscode::install_tasks(force_override);
}
},
_ => {},
},
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(),
_ => {},
DevCommand::Remove(RemoveCommand { subcommand }) => match subcommand {
RemoveSubcommand::Intellij => setup::intellij::remove_rustc_src(),
RemoveSubcommand::GitHook => setup::git_hook::remove_hook(),
RemoveSubcommand::VscodeTasks => setup::vscode::remove_tasks(),
},
Some(("serve", matches)) => {
let port = *matches.get_one::<u16>("port").unwrap();
let lint = matches.get_one::<String>("lint");
serve::run(port, lint);
},
Some(("lint", matches)) => {
let path = matches.get_one::<String>("path").unwrap();
let args = matches.get_many::<String>("args").into_iter().flatten();
lint::run(path, args);
},
Some(("rename_lint", matches)) => {
let old_name = matches.get_one::<String>("old_name").unwrap();
let new_name = matches.get_one::<String>("new_name").unwrap_or(old_name);
let uplift = matches.get_flag("uplift");
update_lints::rename(old_name, new_name, uplift);
},
Some(("deprecate", matches)) => {
let name = matches.get_one::<String>("name").unwrap();
let reason = matches.get_one("reason");
update_lints::deprecate(name, reason);
},
_ => {},
DevCommand::Serve { port, lint } => serve::run(port, lint),
DevCommand::Lint { path, args } => lint::run(&path, args.iter()),
DevCommand::RenameLint {
old_name,
new_name,
uplift,
} => update_lints::rename(&old_name, new_name.as_ref().unwrap_or(&old_name), uplift),
DevCommand::Deprecate { name, reason } => update_lints::deprecate(&name, reason.as_deref()),
}
}
fn get_clap_config() -> ArgMatches {
Command::new("Clippy developer tooling")
.arg_required_else_help(true)
.subcommands([
Command::new("bless").about("bless the test output changes").arg(
Arg::new("ignore-timestamp")
.long("ignore-timestamp")
.action(ArgAction::SetTrue)
.help("Include files updated before clippy was built"),
),
Command::new("dogfood").about("Runs the dogfood test").args([
Arg::new("fix")
.long("fix")
.action(ArgAction::SetTrue)
.help("Apply the suggestions when possible"),
Arg::new("allow-dirty")
.long("allow-dirty")
.action(ArgAction::SetTrue)
.help("Fix code even if the working directory has changes")
.requires("fix"),
Arg::new("allow-staged")
.long("allow-staged")
.action(ArgAction::SetTrue)
.help("Fix code even if the working directory has staged changes")
.requires("fix"),
]),
Command::new("fmt")
.about("Run rustfmt on all projects and tests")
.args([
Arg::new("check")
.long("check")
.action(ArgAction::SetTrue)
.help("Use the rustfmt --check option"),
Arg::new("verbose")
.short('v')
.long("verbose")
.action(ArgAction::SetTrue)
.help("Echo commands run"),
]),
Command::new("update_lints")
.about("Updates lint registration and information from the source code")
.long_about(
"Makes sure that:\n \
* the lint count in README.md is correct\n \
* the changelog contains markdown link references at the bottom\n \
* all lint groups include the correct lints\n \
* lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \
* all lints are registered in the lint store",
)
.args([
Arg::new("print-only")
.long("print-only")
.action(ArgAction::SetTrue)
.help(
"Print a table of lints to STDOUT. \
This does not include deprecated and internal lints. \
(Does not modify any files)",
),
Arg::new("check")
.long("check")
.action(ArgAction::SetTrue)
.help("Checks that `cargo dev update_lints` has been run. Used on CI."),
]),
Command::new("new_lint")
.about("Create new lint and run `cargo dev update_lints`")
.args([
Arg::new("pass")
.short('p')
.long("pass")
.help("Specify whether the lint runs during the early or late pass")
.value_parser(["early", "late"])
.conflicts_with("type")
.default_value("late"),
Arg::new("name")
.short('n')
.long("name")
.help("Name of the new lint in snake case, ex: fn_too_long")
.required(true)
.value_parser(|name: &str| Ok::<_, Infallible>(name.replace('-', "_"))),
Arg::new("category")
.short('c')
.long("category")
.help("What category the lint belongs to")
.default_value("nursery")
.value_parser([
"style",
"correctness",
"suspicious",
"complexity",
"perf",
"pedantic",
"restriction",
"cargo",
"nursery",
"internal",
]),
Arg::new("type").long("type").help("What directory the lint belongs in"),
Arg::new("msrv")
.long("msrv")
.action(ArgAction::SetTrue)
.help("Add MSRV config code to the lint"),
]),
Command::new("setup")
.about("Support for setting up your personal development environment")
.arg_required_else_help(true)
.subcommands([
Command::new("git-hook")
.about("Add a pre-commit git hook that formats your code to make it look pretty")
.args([
Arg::new("remove")
.long("remove")
.action(ArgAction::SetTrue)
.help("Remove the pre-commit hook added with 'cargo dev setup git-hook'"),
Arg::new("force-override")
.long("force-override")
.short('f')
.action(ArgAction::SetTrue)
.help("Forces the override of an existing git pre-commit hook"),
]),
Command::new("intellij")
.about("Alter dependencies so Intellij Rust can find rustc internals")
.args([
Arg::new("remove")
.long("remove")
.action(ArgAction::SetTrue)
.help("Remove the dependencies added with 'cargo dev setup intellij'"),
Arg::new("rustc-repo-path")
.long("repo-path")
.short('r')
.help("The path to a rustc repo that will be used for setting the dependencies")
.value_name("path")
.conflicts_with("remove")
.required(true),
]),
Command::new("toolchain")
.about("Install a rustup toolchain pointing to the local clippy build")
.args([
Arg::new("force")
.long("force")
.short('f')
.action(ArgAction::SetTrue)
.help("Override an existing toolchain"),
Arg::new("release")
.long("release")
.short('r')
.action(ArgAction::SetTrue)
.help("Point to --release clippy binaries"),
Arg::new("name")
.long("name")
.default_value("clippy")
.help("The name of the created toolchain"),
]),
Command::new("vscode-tasks")
.about("Add several tasks to vscode for formatting, validation and testing")
.args([
Arg::new("remove")
.long("remove")
.action(ArgAction::SetTrue)
.help("Remove the tasks added with 'cargo dev setup vscode-tasks'"),
Arg::new("force-override")
.long("force-override")
.short('f')
.action(ArgAction::SetTrue)
.help("Forces the override of existing vscode tasks"),
]),
]),
Command::new("remove")
.about("Support for undoing changes done by the setup command")
.arg_required_else_help(true)
.subcommands([
Command::new("git-hook").about("Remove any existing pre-commit git hook"),
Command::new("vscode-tasks").about("Remove any existing vscode tasks"),
Command::new("intellij").about("Removes rustc source paths added via `cargo dev setup intellij`"),
]),
Command::new("serve")
.about("Launch a local 'ALL the Clippy Lints' website in a browser")
.args([
Arg::new("port")
.long("port")
.short('p')
.help("Local port for the http server")
.default_value("8000")
.value_parser(clap::value_parser!(u16)),
Arg::new("lint").help("Which lint's page to load initially (optional)"),
]),
Command::new("lint")
.about("Manually run clippy on a file or package")
.after_help(indoc! {"
EXAMPLES
Lint a single file:
cargo dev lint tests/ui/attrs.rs
Lint a package directory:
cargo dev lint tests/ui-cargo/wildcard_dependencies/fail
cargo dev lint ~/my-project
Run rustfix:
cargo dev lint ~/my-project -- --fix
Set lint levels:
cargo dev lint file.rs -- -W clippy::pedantic
cargo dev lint ~/my-project -- -- -W clippy::pedantic
"})
.args([
Arg::new("path")
.required(true)
.help("The path to a file or package directory to lint"),
Arg::new("args")
.action(ArgAction::Append)
.help("Pass extra arguments to cargo/clippy-driver"),
]),
Command::new("rename_lint").about("Renames the given lint").args([
Arg::new("old_name")
.index(1)
.required(true)
.help("The name of the lint to rename"),
Arg::new("new_name")
.index(2)
.required_unless_present("uplift")
.help("The new name of the lint"),
Arg::new("uplift")
.long("uplift")
.action(ArgAction::SetTrue)
.help("This lint will be uplifted into rustc"),
]),
Command::new("deprecate").about("Deprecates the given lint").args([
Arg::new("name")
.index(1)
.required(true)
.help("The name of the lint to deprecate"),
Arg::new("reason")
.long("reason")
.short('r')
.help("The reason for deprecation"),
]),
])
.get_matches()
#[derive(Parser)]
#[command(name = "dev", about)]
struct Dev {
#[command(subcommand)]
command: DevCommand,
}
#[derive(Subcommand)]
enum DevCommand {
/// Bless the test output changes
Bless,
/// Runs the dogfood test
Dogfood {
#[arg(long)]
/// Apply the suggestions when possible
fix: bool,
#[arg(long, requires = "fix")]
/// Fix code even if the working directory has changes
allow_dirty: bool,
#[arg(long, requires = "fix")]
/// Fix code even if the working directory has staged changes
allow_staged: bool,
},
/// Run rustfmt on all projects and tests
Fmt {
#[arg(long)]
/// Use the rustfmt --check option
check: bool,
#[arg(short, long)]
/// Echo commands run
verbose: bool,
},
#[command(name = "update_lints")]
/// Updates lint registration and information from the source code
///
/// Makes sure that: {n}
/// * the lint count in README.md is correct {n}
/// * the changelog contains markdown link references at the bottom {n}
/// * all lint groups include the correct lints {n}
/// * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod` {n}
/// * all lints are registered in the lint store
UpdateLints {
#[arg(long)]
/// Print a table of lints to STDOUT
///
/// This does not include deprecated and internal lints. (Does not modify any files)
print_only: bool,
#[arg(long)]
/// Checks that `cargo dev update_lints` has been run. Used on CI.
check: bool,
},
#[command(name = "new_lint")]
/// Create a new lint and run `cargo dev update_lints`
NewLint {
#[arg(short, long, value_parser = ["early", "late"], conflicts_with = "type", default_value = "late")]
/// Specify whether the lint runs during the early or late pass
pass: String,
#[arg(
short,
long,
value_parser = |name: &str| Ok::<_, Infallible>(name.replace('-', "_")),
)]
/// Name of the new lint in snake case, ex: `fn_too_long`
name: String,
#[arg(
short,
long,
value_parser = [
"style",
"correctness",
"suspicious",
"complexity",
"perf",
"pedantic",
"restriction",
"cargo",
"nursery",
"internal",
],
default_value = "nursery",
)]
/// What category the lint belongs to
category: String,
#[arg(long)]
/// What directory the lint belongs in
r#type: Option<String>,
#[arg(long)]
/// Add MSRV config code to the lint
msrv: bool,
},
/// Support for setting up your personal development environment
Setup(SetupCommand),
/// Support for removing changes done by the setup command
Remove(RemoveCommand),
/// Launch a local 'ALL the Clippy Lints' website in a browser
Serve {
#[arg(short, long, default_value = "8000")]
/// Local port for the http server
port: u16,
#[arg(long)]
/// Which lint's page to load initially (optional)
lint: Option<String>,
},
#[allow(clippy::doc_markdown)]
/// Manually run clippy on a file or package
///
/// ## Examples
///
/// Lint a single file: {n}
/// cargo dev lint tests/ui/attrs.rs
///
/// Lint a package directory: {n}
/// cargo dev lint tests/ui-cargo/wildcard_dependencies/fail {n}
/// cargo dev lint ~/my-project
///
/// Run rustfix: {n}
/// cargo dev lint ~/my-project -- --fix
///
/// Set lint levels: {n}
/// cargo dev lint file.rs -- -W clippy::pedantic {n}
/// cargo dev lint ~/my-project -- -- -W clippy::pedantic
Lint {
/// The path to a file or package directory to lint
path: String,
/// Pass extra arguments to cargo/clippy-driver
args: Vec<String>,
},
#[command(name = "rename_lint")]
/// Rename a lint
RenameLint {
/// The name of the lint to rename
old_name: String,
#[arg(required_unless_present = "uplift")]
/// The new name of the lint
new_name: Option<String>,
#[arg(long)]
/// This lint will be uplifted into rustc
uplift: bool,
},
/// Deprecate the given lint
Deprecate {
/// The name of the lint to deprecate
name: String,
#[arg(long, short)]
/// The reason for deprecation
reason: Option<String>,
},
}
#[derive(Args)]
struct SetupCommand {
#[command(subcommand)]
subcommand: SetupSubcommand,
}
#[derive(Subcommand)]
enum SetupSubcommand {
/// Alter dependencies so Intellij Rust can find rustc internals
Intellij {
#[arg(long)]
/// Remove the dependencies added with 'cargo dev setup intellij'
remove: bool,
#[arg(long, short, conflicts_with = "remove")]
/// The path to a rustc repo that will be used for setting the dependencies
repo_path: String,
},
/// Add a pre-commit git hook that formats your code to make it look pretty
GitHook {
#[arg(long)]
/// Remove the pre-commit hook added with 'cargo dev setup git-hook'
remove: bool,
#[arg(long, short)]
/// Forces the override of an existing git pre-commit hook
force_override: bool,
},
/// Install a rustup toolchain pointing to the local clippy build
Toolchain {
#[arg(long, short)]
/// Override an existing toolchain
force: bool,
#[arg(long, short)]
/// Point to --release clippy binary
release: bool,
#[arg(long, default_value = "clippy")]
/// Name of the toolchain
name: String,
},
/// Add several tasks to vscode for formatting, validation and testing
VscodeTasks {
#[arg(long)]
/// Remove the tasks added with 'cargo dev setup vscode-tasks'
remove: bool,
#[arg(long, short)]
/// Forces the override of existing vscode tasks
force_override: bool,
},
}
#[derive(Args)]
struct RemoveCommand {
#[command(subcommand)]
subcommand: RemoveSubcommand,
}
#[derive(Subcommand)]
enum RemoveSubcommand {
/// Remove the dependencies added with 'cargo dev setup intellij'
Intellij,
/// Remove the pre-commit git hook
GitHook,
/// Remove the tasks added with 'cargo dev setup vscode-tasks'
VscodeTasks,
}

View File

@ -36,22 +36,16 @@ impl<T> Context for io::Result<T> {
/// # Errors
///
/// This function errors out if the files couldn't be created or written to.
pub fn create(
pass: &String,
lint_name: Option<&String>,
category: Option<&str>,
mut ty: Option<&str>,
msrv: bool,
) -> io::Result<()> {
if category == Some("cargo") && ty.is_none() {
pub fn create(pass: &str, name: &str, category: &str, mut ty: Option<&str>, msrv: bool) -> io::Result<()> {
if category == "cargo" && ty.is_none() {
// `cargo` is a special category, these lints should always be in `clippy_lints/src/cargo`
ty = Some("cargo");
}
let lint = LintData {
pass,
name: lint_name.expect("`name` argument is validated by clap"),
category: category.expect("`category` argument is validated by clap"),
name,
category,
ty,
project_root: clippy_project_root(),
};

View File

@ -8,7 +8,7 @@ use std::{env, thread};
/// # Panics
///
/// Panics if the python commands could not be spawned
pub fn run(port: u16, lint: Option<&String>) -> ! {
pub fn run(port: u16, lint: Option<String>) -> ! {
let mut url = Some(match lint {
None => format!("http://localhost:{port}"),
Some(lint) => format!("http://localhost:{port}/#{lint}"),

View File

@ -314,7 +314,7 @@ const DEFAULT_DEPRECATION_REASON: &str = "default deprecation note";
/// # Panics
///
/// If a file path could not read from or written to
pub fn deprecate(name: &str, reason: Option<&String>) {
pub fn deprecate(name: &str, reason: Option<&str>) {
fn finish(
(lints, mut deprecated_lints, renamed_lints): (Vec<Lint>, Vec<DeprecatedLint>, Vec<RenamedLint>),
name: &str,
@ -335,7 +335,7 @@ pub fn deprecate(name: &str, reason: Option<&String>) {
println!("note: you must run `cargo uitest` to update the test results");
}
let reason = reason.map_or(DEFAULT_DEPRECATION_REASON, String::as_str);
let reason = reason.unwrap_or(DEFAULT_DEPRECATION_REASON);
let name_lower = name.to_lowercase();
let name_upper = name.to_uppercase();

View File

@ -2,15 +2,15 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::HirNode;
use clippy_utils::sugg::Sugg;
use clippy_utils::{is_trait_method, path_to_local};
use clippy_utils::{is_trait_method, local_is_initialized, path_to_local};
use rustc_errors::Applicability;
use rustc_hir::{self as hir, Expr, ExprKind, Node};
use rustc_hir::{self as hir, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, Instance, Mutability};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::DefId;
use rustc_span::symbol::sym;
use rustc_span::ExpnKind;
use rustc_span::{ExpnKind, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
@ -36,6 +36,7 @@ declare_clippy_lint! {
/// Use instead:
/// ```rust
/// struct Thing;
///
/// impl Clone for Thing {
/// fn clone(&self) -> Self { todo!() }
/// fn clone_from(&mut self, other: &Self) { todo!() }
@ -47,7 +48,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.78.0"]
pub ASSIGNING_CLONES,
perf,
pedantic,
"assigning the result of cloning may be inefficient"
}
@ -67,7 +68,8 @@ impl_lint_pass!(AssigningClones => [ASSIGNING_CLONES]);
impl<'tcx> LateLintPass<'tcx> for AssigningClones {
fn check_expr(&mut self, cx: &LateContext<'tcx>, assign_expr: &'tcx Expr<'_>) {
// Do not fire the lint in macros
let expn_data = assign_expr.span().ctxt().outer_expn_data();
let ctxt = assign_expr.span().ctxt();
let expn_data = ctxt.outer_expn_data();
match expn_data.kind {
ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) | ExpnKind::Macro(..) => return,
ExpnKind::Root => {},
@ -82,7 +84,7 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
};
if is_ok_to_suggest(cx, lhs, &call, &self.msrv) {
suggest(cx, assign_expr, lhs, &call);
suggest(cx, ctxt, assign_expr, lhs, &call);
}
}
@ -163,9 +165,7 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC
// TODO: This check currently bails if the local variable has no initializer.
// That is overly conservative - the lint should fire even if there was no initializer,
// but the variable has been initialized before `lhs` was evaluated.
if let Some(Node::LetStmt(local)) = cx.tcx.hir().parent_id_iter(local).next().map(|p| cx.tcx.hir_node(p))
&& local.init.is_none()
{
if !local_is_initialized(cx, local) {
return false;
}
}
@ -222,14 +222,20 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC
implemented_fns.contains_key(&provided_fn.def_id)
}
fn suggest<'tcx>(cx: &LateContext<'tcx>, assign_expr: &Expr<'tcx>, lhs: &Expr<'tcx>, call: &CallCandidate<'tcx>) {
fn suggest<'tcx>(
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
assign_expr: &Expr<'tcx>,
lhs: &Expr<'tcx>,
call: &CallCandidate<'tcx>,
) {
span_lint_and_then(cx, ASSIGNING_CLONES, assign_expr.span, call.message(), |diag| {
let mut applicability = Applicability::Unspecified;
diag.span_suggestion(
assign_expr.span,
call.suggestion_msg(),
call.suggested_replacement(cx, lhs, &mut applicability),
call.suggested_replacement(cx, ctxt, lhs, &mut applicability),
applicability,
);
});
@ -275,6 +281,7 @@ impl<'tcx> CallCandidate<'tcx> {
fn suggested_replacement(
&self,
cx: &LateContext<'tcx>,
ctxt: SyntaxContext,
lhs: &Expr<'tcx>,
applicability: &mut Applicability,
) -> String {
@ -294,7 +301,7 @@ impl<'tcx> CallCandidate<'tcx> {
// Determine whether we need to reference the argument to clone_from().
let clone_receiver_type = cx.typeck_results().expr_ty(receiver);
let clone_receiver_adj_type = cx.typeck_results().expr_ty_adjusted(receiver);
let mut arg_sugg = Sugg::hir_with_applicability(cx, receiver, "_", applicability);
let mut arg_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability);
if clone_receiver_type != clone_receiver_adj_type {
// The receiver may have been a value type, so we need to add an `&` to
// be sure the argument to clone_from will be a reference.
@ -312,7 +319,7 @@ impl<'tcx> CallCandidate<'tcx> {
Sugg::hir_with_applicability(cx, lhs, "_", applicability).mut_addr()
};
// The RHS had to be exactly correct before the call, there is no auto-deref for function calls.
let rhs_sugg = Sugg::hir_with_applicability(cx, self_arg, "_", applicability);
let rhs_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability);
format!("Clone::clone_from({self_sugg}, {rhs_sugg})")
},
@ -341,11 +348,11 @@ impl<'tcx> CallCandidate<'tcx> {
match self.kind {
CallKind::MethodCall { receiver } => {
let receiver_sugg = Sugg::hir_with_applicability(cx, receiver, "_", applicability);
let receiver_sugg = Sugg::hir_with_context(cx, receiver, ctxt, "_", applicability);
format!("{receiver_sugg}.clone_into({rhs_sugg})")
},
CallKind::FunctionCall { self_arg, .. } => {
let self_sugg = Sugg::hir_with_applicability(cx, self_arg, "_", applicability);
let self_sugg = Sugg::hir_with_context(cx, self_arg, ctxt, "_", applicability);
format!("ToOwned::clone_into({self_sugg}, {rhs_sugg})")
},
}

View File

@ -36,9 +36,10 @@ fn check_duplicated_attr(
}
let Some(ident) = attr.ident() else { return };
let name = ident.name;
if name == sym::doc || name == sym::cfg_attr {
if name == sym::doc || name == sym::cfg_attr || name == sym::rustc_on_unimplemented {
// FIXME: Would be nice to handle `cfg_attr` as well. Only problem is to check that cfg
// conditions are the same.
// `#[rustc_on_unimplemented]` contains duplicated subattributes, that's expected.
return;
}
if let Some(direct_parent) = parent.last()

View File

@ -61,11 +61,21 @@ declare_clippy_lint! {
///
/// This lint permits lint attributes for lints emitted on the items themself.
/// For `use` items these lints are:
/// * ambiguous_glob_reexports
/// * dead_code
/// * deprecated
/// * hidden_glob_reexports
/// * unreachable_pub
/// * unused_imports
/// * unused
/// * unused_braces
/// * unused_import_braces
/// * clippy::disallowed_types
/// * clippy::enum_glob_use
/// * clippy::macro_use_imports
/// * clippy::module_name_repetitions
/// * clippy::redundant_pub_crate
/// * clippy::single_component_path_imports
/// * clippy::unsafe_removed_from_name
/// * clippy::wildcard_imports
///
/// For `extern crate` items these lints are:

View File

@ -2,6 +2,7 @@ use super::utils::{extract_clippy_lint, is_lint_level, is_word};
use super::{Attribute, USELESS_ATTRIBUTE};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::{first_line_of_span, snippet_opt};
use rustc_ast::NestedMetaItem;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LintContext};
@ -20,26 +21,40 @@ pub(super) fn check(cx: &LateContext<'_>, item: &Item<'_>, attrs: &[Attribute])
for lint in lint_list {
match item.kind {
ItemKind::Use(..) => {
if is_word(lint, sym::unused_imports)
|| is_word(lint, sym::deprecated)
|| is_word(lint, sym!(unreachable_pub))
|| is_word(lint, sym!(unused))
|| is_word(lint, sym!(unused_import_braces))
|| extract_clippy_lint(lint).map_or(false, |s| {
matches!(
s.as_str(),
"wildcard_imports"
| "enum_glob_use"
| "redundant_pub_crate"
| "macro_use_imports"
| "unsafe_removed_from_name"
| "module_name_repetitions"
| "single_component_path_imports"
)
})
if let NestedMetaItem::MetaItem(meta_item) = lint
&& meta_item.is_word()
&& let Some(ident) = meta_item.ident()
&& matches!(
ident.name.as_str(),
"ambiguous_glob_reexports"
| "dead_code"
| "deprecated"
| "hidden_glob_reexports"
| "unreachable_pub"
| "unused"
| "unused_braces"
| "unused_import_braces"
| "unused_imports"
)
{
return;
}
if extract_clippy_lint(lint).is_some_and(|symbol| {
matches!(
symbol.as_str(),
"wildcard_imports"
| "enum_glob_use"
| "redundant_pub_crate"
| "macro_use_imports"
| "unsafe_removed_from_name"
| "module_name_repetitions"
| "single_component_path_imports"
| "disallowed_types"
)
}) {
return;
}
},
ItemKind::ExternCrate(..) => {
if is_word(lint, sym::unused_imports) && skip_unused_imports {

View File

@ -49,7 +49,7 @@ impl LintConfig {
type LintTable = BTreeMap<Spanned<String>, Spanned<LintConfig>>;
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Default)]
struct Lints {
#[serde(default)]
rust: LintTable,
@ -57,9 +57,18 @@ struct Lints {
clippy: LintTable,
}
#[derive(Deserialize, Debug, Default)]
struct Workspace {
#[serde(default)]
lints: Lints,
}
#[derive(Deserialize, Debug)]
struct CargoToml {
#[serde(default)]
lints: Lints,
#[serde(default)]
workspace: Workspace,
}
#[derive(Default, Debug)]
@ -164,5 +173,7 @@ pub fn check(cx: &LateContext<'_>) {
check_table(cx, cargo_toml.lints.rust, &rustc_groups, &file);
check_table(cx, cargo_toml.lints.clippy, &clippy_groups, &file);
check_table(cx, cargo_toml.workspace.lints.rust, &rustc_groups, &file);
check_table(cx, cargo_toml.workspace.lints.clippy, &clippy_groups, &file);
}
}

View File

@ -255,8 +255,10 @@ fn expr_add_sign(cx: &LateContext<'_>, expr: &Expr<'_>) -> Sign {
/// Peels binary operators such as [`BinOpKind::Mul`], [`BinOpKind::Div`] or [`BinOpKind::Rem`],
/// where the result depends on:
///
/// - the number of negative values in the entire expression, or
/// - the number of negative values on the left hand side of the expression.
///
/// Ignores overflow.
///
///
@ -303,8 +305,10 @@ fn exprs_with_muldiv_binop_peeled<'e>(expr: &'e Expr<'_>) -> Vec<&'e Expr<'e>> {
}
/// Peels binary operators such as [`BinOpKind::Add`], where the result depends on:
///
/// - all the expressions being positive, or
/// - all the expressions being negative.
///
/// Ignores overflow.
///
/// Expressions using other operators are preserved, so we can try to evaluate them later.

View File

@ -140,6 +140,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::disallowed_names::DISALLOWED_NAMES_INFO,
crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO,
crate::disallowed_types::DISALLOWED_TYPES_INFO,
crate::doc::DOC_LAZY_CONTINUATION_INFO,
crate::doc::DOC_LINK_WITH_QUOTES_INFO,
crate::doc::DOC_MARKDOWN_INFO,
crate::doc::EMPTY_DOCS_INFO,
@ -205,6 +206,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::functions::MUST_USE_CANDIDATE_INFO,
crate::functions::MUST_USE_UNIT_INFO,
crate::functions::NOT_UNSAFE_PTR_ARG_DEREF_INFO,
crate::functions::RENAMED_FUNCTION_PARAMS_INFO,
crate::functions::RESULT_LARGE_ERR_INFO,
crate::functions::RESULT_UNIT_ERR_INFO,
crate::functions::TOO_MANY_ARGUMENTS_INFO,
@ -294,6 +296,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::loops::WHILE_IMMUTABLE_CONDITION_INFO,
crate::loops::WHILE_LET_LOOP_INFO,
crate::loops::WHILE_LET_ON_ITERATOR_INFO,
crate::macro_metavars_in_unsafe::MACRO_METAVARS_IN_UNSAFE_INFO,
crate::macro_use::MACRO_USE_IMPORTS_INFO,
crate::main_recursion::MAIN_RECURSION_INFO,
crate::manual_assert::MANUAL_ASSERT_INFO,

View File

@ -0,0 +1,95 @@
use clippy_utils::diagnostics::span_lint_and_then;
use itertools::Itertools;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_lint::LateContext;
use rustc_span::{BytePos, Span};
use std::ops::Range;
use super::DOC_LAZY_CONTINUATION;
fn map_container_to_text(c: &super::Container) -> &'static str {
match c {
super::Container::Blockquote => "> ",
// numbered list can have up to nine digits, plus the dot, plus four spaces on either side
super::Container::List(indent) => &" "[0..*indent],
}
}
// TODO: Adjust the parameters as necessary
pub(super) fn check(
cx: &LateContext<'_>,
doc: &str,
range: Range<usize>,
mut span: Span,
containers: &[super::Container],
) {
if doc[range.clone()].contains('\t') {
// We don't do tab stops correctly.
return;
}
let ccount = doc[range.clone()].chars().filter(|c| *c == '>').count();
let blockquote_level = containers
.iter()
.filter(|c| matches!(c, super::Container::Blockquote))
.count();
let lcount = doc[range.clone()].chars().filter(|c| *c == ' ').count();
let list_indentation = containers
.iter()
.map(|c| {
if let super::Container::List(indent) = c {
*indent
} else {
0
}
})
.sum();
if ccount < blockquote_level || lcount < list_indentation {
let msg = if ccount < blockquote_level {
"doc quote missing `>` marker"
} else {
"doc list item missing indentation"
};
span_lint_and_then(cx, DOC_LAZY_CONTINUATION, span, msg, |diag| {
if ccount == 0 && blockquote_level == 0 {
// simpler suggestion style for indentation
let indent = list_indentation - lcount;
diag.span_suggestion_with_style(
span.shrink_to_hi(),
"indent this line",
std::iter::repeat(" ").take(indent).join(""),
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);
diag.help("if this is supposed to be its own paragraph, add a blank line");
return;
}
let mut doc_start_range = &doc[range];
let mut suggested = String::new();
for c in containers {
let text = map_container_to_text(c);
if doc_start_range.starts_with(text) {
doc_start_range = &doc_start_range[text.len()..];
span = span
.with_lo(span.lo() + BytePos(u32::try_from(text.len()).expect("text is not 2**32 or bigger")));
} else if matches!(c, super::Container::Blockquote)
&& let Some(i) = doc_start_range.find('>')
{
doc_start_range = &doc_start_range[i + 1..];
span =
span.with_lo(span.lo() + BytePos(u32::try_from(i).expect("text is not 2**32 or bigger") + 1));
} else {
suggested.push_str(text);
}
}
diag.span_suggestion_with_style(
span,
"add markers to start of line",
suggested,
Applicability::MachineApplicable,
SuggestionStyle::ShowAlways,
);
diag.help("if this not intended to be a quote at all, escape it with `\\>`");
});
}
}

View File

@ -1,3 +1,4 @@
use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
use clippy_utils::diagnostics::{span_lint, span_lint_and_note};
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{is_doc_hidden, return_ty};
@ -6,15 +7,13 @@ use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::{sym, Span};
use super::{DocHeaders, MISSING_ERRORS_DOC, MISSING_PANICS_DOC, MISSING_SAFETY_DOC, UNNECESSARY_SAFETY_DOC};
pub fn check(
cx: &LateContext<'_>,
owner_id: OwnerId,
sig: FnSig<'_>,
headers: DocHeaders,
body_id: Option<BodyId>,
panic_span: Option<Span>,
panic_info: Option<(Span, bool)>,
check_private_items: bool,
) {
if !check_private_items && !cx.effective_visibilities.is_exported(owner_id.def_id) {
@ -48,13 +47,13 @@ pub fn check(
),
_ => (),
}
if !headers.panics && panic_span.is_some() {
if !headers.panics && panic_info.map_or(false, |el| !el.1) {
span_lint_and_note(
cx,
MISSING_PANICS_DOC,
span,
"docs for function which may panic missing `# Panics` section",
panic_span,
panic_info.map(|el| el.0),
"first possible panic found here",
);
}

View File

@ -1,13 +1,14 @@
mod lazy_continuation;
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::Visitable;
use clippy_utils::{is_entrypoint_fn, is_trait_impl_item, method_chain_args};
use clippy_utils::{in_constant, is_entrypoint_fn, is_trait_impl_item, method_chain_args};
use pulldown_cmark::Event::{
Code, End, FootnoteReference, HardBreak, Html, Rule, SoftBreak, Start, TaskListMarker, Text,
};
use pulldown_cmark::Tag::{BlockQuote, CodeBlock, Heading, Item, Link, Paragraph};
use pulldown_cmark::Tag::{BlockQuote, CodeBlock, FootnoteDefinition, Heading, Item, Link, Paragraph};
use pulldown_cmark::{BrokenLink, CodeBlockKind, CowStr, Options};
use rustc_ast::ast::Attribute;
use rustc_data_structures::fx::FxHashSet;
@ -362,6 +363,63 @@ declare_clippy_lint! {
"docstrings exist but documentation is empty"
}
declare_clippy_lint! {
/// ### What it does
///
/// In CommonMark Markdown, the language used to write doc comments, a
/// paragraph nested within a list or block quote does not need any line
/// after the first one to be indented or marked. The specification calls
/// this a "lazy paragraph continuation."
///
/// ### Why is this bad?
///
/// This is easy to write but hard to read. Lazy continuations makes
/// unintended markers hard to see, and make it harder to deduce the
/// document's intended structure.
///
/// ### Example
///
/// This table is probably intended to have two rows,
/// but it does not. It has zero rows, and is followed by
/// a block quote.
/// ```no_run
/// /// Range | Description
/// /// ----- | -----------
/// /// >= 1 | fully opaque
/// /// < 1 | partially see-through
/// fn set_opacity(opacity: f32) {}
/// ```
///
/// Fix it by escaping the marker:
/// ```no_run
/// /// Range | Description
/// /// ----- | -----------
/// /// \>= 1 | fully opaque
/// /// < 1 | partially see-through
/// fn set_opacity(opacity: f32) {}
/// ```
///
/// This example is actually intended to be a list:
/// ```no_run
/// /// * Do nothing.
/// /// * Then do something. Whatever it is needs done,
/// /// it should be done right now.
/// # fn do_stuff() {}
/// ```
///
/// Fix it by indenting the list contents:
/// ```no_run
/// /// * Do nothing.
/// /// * Then do something. Whatever it is needs done,
/// /// it should be done right now.
/// # fn do_stuff() {}
/// ```
#[clippy::version = "1.80.0"]
pub DOC_LAZY_CONTINUATION,
style,
"require every line of a paragraph to be indented and marked"
}
#[derive(Clone)]
pub struct Documentation {
valid_idents: FxHashSet<String>,
@ -388,6 +446,7 @@ impl_lint_pass!(Documentation => [
UNNECESSARY_SAFETY_DOC,
SUSPICIOUS_DOC_COMMENTS,
EMPTY_DOCS,
DOC_LAZY_CONTINUATION,
]);
impl<'tcx> LateLintPass<'tcx> for Documentation {
@ -402,14 +461,14 @@ impl<'tcx> LateLintPass<'tcx> for Documentation {
if !(is_entrypoint_fn(cx, item.owner_id.to_def_id()) || in_external_macro(cx.tcx.sess, item.span)) {
let body = cx.tcx.hir().body(body_id);
let panic_span = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value);
let panic_info = FindPanicUnwrap::find_span(cx, cx.tcx.typeck(item.owner_id), body.value);
missing_headers::check(
cx,
item.owner_id,
sig,
headers,
Some(body_id),
panic_span,
panic_info,
self.check_private_items,
);
}
@ -551,6 +610,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
cx,
valid_idents,
parser.into_offset_iter(),
&doc,
Fragments {
fragments: &fragments,
doc: &doc,
@ -560,6 +620,11 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
enum Container {
Blockquote,
List(usize),
}
/// Checks parsed documentation.
/// This walks the "events" (think sections of markdown) produced by `pulldown_cmark`,
/// so lints here will generally access that information.
@ -569,6 +634,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
cx: &LateContext<'_>,
valid_idents: &FxHashSet<String>,
events: Events,
doc: &str,
fragments: Fragments<'_>,
) -> DocHeaders {
// true if a safety header was found
@ -576,6 +642,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut in_code = false;
let mut in_link = None;
let mut in_heading = false;
let mut in_footnote_definition = false;
let mut is_rust = false;
let mut no_test = false;
let mut ignore = false;
@ -586,7 +653,11 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
let mut code_level = 0;
let mut blockquote_level = 0;
for (event, range) in events {
let mut containers = Vec::new();
let mut events = events.peekable();
while let Some((event, range)) = events.next() {
match event {
Html(tag) => {
if tag.starts_with("<code") {
@ -599,8 +670,14 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
blockquote_level -= 1;
}
},
Start(BlockQuote) => blockquote_level += 1,
End(BlockQuote) => blockquote_level -= 1,
Start(BlockQuote) => {
blockquote_level += 1;
containers.push(Container::Blockquote);
},
End(BlockQuote) => {
blockquote_level -= 1;
containers.pop();
},
Start(CodeBlock(ref kind)) => {
in_code = true;
if let CodeBlockKind::Fenced(lang) = kind {
@ -633,6 +710,13 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
if let Start(Heading(_, _, _)) = event {
in_heading = true;
}
if let Start(Item) = event {
if let Some((_next_event, next_range)) = events.peek() {
containers.push(Container::List(next_range.start - range.start));
} else {
containers.push(Container::List(0));
}
}
ticks_unbalanced = false;
paragraph_range = range;
},
@ -640,6 +724,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
if let End(Heading(_, _, _)) = event {
in_heading = false;
}
if let End(Item) = event {
containers.pop();
}
if ticks_unbalanced && let Some(span) = fragments.span(cx, paragraph_range.clone()) {
span_lint_and_help(
cx,
@ -658,8 +745,26 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
}
text_to_check = Vec::new();
},
Start(FootnoteDefinition(..)) => in_footnote_definition = true,
End(FootnoteDefinition(..)) => in_footnote_definition = false,
Start(_tag) | End(_tag) => (), // We don't care about other tags
SoftBreak | HardBreak | TaskListMarker(_) | Code(_) | Rule => (),
SoftBreak | HardBreak => {
if !containers.is_empty()
&& let Some((_next_event, next_range)) = events.peek()
&& let Some(next_span) = fragments.span(cx, next_range.clone())
&& let Some(span) = fragments.span(cx, range.clone())
&& !in_footnote_definition
{
lazy_continuation::check(
cx,
doc,
range.end..next_range.start,
Span::new(span.hi(), next_span.lo(), span.ctxt(), span.parent()),
&containers[..],
);
}
},
TaskListMarker(_) | Code(_) | Rule => (),
FootnoteReference(text) | Text(text) => {
paragraph_range.end = range.end;
ticks_unbalanced |= text.contains('`') && !in_code;
@ -701,6 +806,7 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
struct FindPanicUnwrap<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
is_const: bool,
panic_span: Option<Span>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
}
@ -710,14 +816,15 @@ impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
typeck_results: &'tcx ty::TypeckResults<'tcx>,
body: impl Visitable<'tcx>,
) -> Option<Span> {
) -> Option<(Span, bool)> {
let mut vis = Self {
cx,
is_const: false,
panic_span: None,
typeck_results,
};
body.visit(&mut vis);
vis.panic_span
vis.panic_span.map(|el| (el, vis.is_const))
}
}
@ -736,6 +843,7 @@ impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
"assert" | "assert_eq" | "assert_ne"
)
{
self.is_const = in_constant(self.cx, expr.hir_id);
self.panic_span = Some(macro_call.span);
}
}

View File

@ -104,7 +104,9 @@ impl<'tcx> LateLintPass<'tcx> for BoxedLocal {
too_large_for_stack: self.too_large_for_stack,
};
ExprUseVisitor::for_clippy(cx, fn_def_id, &mut v).consume_body(body).into_ok();
ExprUseVisitor::for_clippy(cx, fn_def_id, &mut v)
.consume_body(body)
.into_ok();
for node in v.set {
span_lint_hir(

View File

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
use clippy_utils::macros::{format_args_inputs_span, FormatArgsStorage};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, path_def_id};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{BindingMode, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use rustc_span::{sym, ExpnId};
declare_clippy_lint! {
@ -38,7 +38,17 @@ declare_clippy_lint! {
"using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work"
}
declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
pub struct ExplicitWrite {
format_args: FormatArgsStorage,
}
impl ExplicitWrite {
pub fn new(format_args: FormatArgsStorage) -> Self {
Self { format_args }
}
}
impl_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
@ -57,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
Some(sym::io_stderr) => ("stderr", "e"),
_ => return,
};
let Some(format_args) = find_format_args(cx, write_arg, ExpnId::root()) else {
let Some(format_args) = self.format_args.get(cx, write_arg, ExpnId::root()) else {
return;
};
@ -83,7 +93,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
};
let mut applicability = Applicability::MachineApplicable;
let inputs_snippet =
snippet_with_applicability(cx, format_args_inputs_span(&format_args), "..", &mut applicability);
snippet_with_applicability(cx, format_args_inputs_span(format_args), "..", &mut applicability);
span_lint_and_sugg(
cx,
EXPLICIT_WRITE,

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node};
use clippy_utils::macros::{find_format_arg_expr, root_macro_call_first_node, FormatArgsStorage};
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::Sugg;
use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait};
@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use rustc_span::{sym, Span};
declare_clippy_lint! {
@ -39,13 +39,24 @@ declare_clippy_lint! {
"useless use of `format!`"
}
declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
#[allow(clippy::module_name_repetitions)]
pub struct UselessFormat {
format_args: FormatArgsStorage,
}
impl UselessFormat {
pub fn new(format_args: FormatArgsStorage) -> Self {
Self { format_args }
}
}
impl_lint_pass!(UselessFormat => [USELESS_FORMAT]);
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
&& let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn)
{
let mut applicability = Applicability::MachineApplicable;
let call_site = macro_call.span;

View File

@ -3,8 +3,8 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diag_trait_item;
use clippy_utils::macros::{
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
is_format_macro, is_panic, matching_root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall,
find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro,
is_panic, matching_root_macro_call, root_macro_call_first_node, FormatArgsStorage, FormatParamUsage, MacroCall,
};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item};
@ -167,15 +167,18 @@ impl_lint_pass!(FormatArgs => [
UNUSED_FORMAT_SPECS,
]);
#[allow(clippy::struct_field_names)]
pub struct FormatArgs {
format_args: FormatArgsStorage,
msrv: Msrv,
ignore_mixed: bool,
}
impl FormatArgs {
#[must_use]
pub fn new(msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self {
pub fn new(format_args: FormatArgsStorage, msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self {
Self {
format_args,
msrv,
ignore_mixed: allow_mixed_uninlined_format_args,
}
@ -186,13 +189,13 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& is_format_macro(cx, macro_call.def_id)
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
&& let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn)
{
let linter = FormatArgsExpr {
cx,
expr,
macro_call: &macro_call,
format_args: &format_args,
format_args,
ignore_mixed: self.ignore_mixed,
};

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
use clippy_utils::macros::{find_format_arg_expr, is_format_macro, root_macro_call_first_node, FormatArgsStorage};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
@ -99,13 +99,15 @@ struct FormatTraitNames {
#[derive(Default)]
pub struct FormatImpl {
format_args: FormatArgsStorage,
// Whether we are inside Display or Debug trait impl - None for neither
format_trait_impl: Option<FormatTraitNames>,
}
impl FormatImpl {
pub fn new() -> Self {
pub fn new(format_args: FormatArgsStorage) -> Self {
Self {
format_args,
format_trait_impl: None,
}
}
@ -129,6 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
if let Some(format_trait_impl) = self.format_trait_impl {
let linter = FormatImplExpr {
cx,
format_args: &self.format_args,
expr,
format_trait_impl,
};
@ -141,6 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
struct FormatImplExpr<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
format_args: &'a FormatArgsStorage,
expr: &'tcx Expr<'tcx>,
format_trait_impl: FormatTraitNames,
}
@ -175,7 +179,7 @@ impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> {
if let Some(outer_macro) = root_macro_call_first_node(self.cx, self.expr)
&& let macro_def_id = outer_macro.def_id
&& is_format_macro(self.cx, macro_def_id)
&& let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn)
&& let Some(format_args) = self.format_args.get(self.cx, self.expr, outer_macro.expn)
{
for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece

View File

@ -181,6 +181,9 @@ fn convert_to_from(
let from = snippet_opt(cx, self_ty.span)?;
let into = snippet_opt(cx, target_ty.span)?;
let return_type = matches!(sig.decl.output, FnRetTy::Return(_))
.then_some(String::from("Self"))
.unwrap_or_default();
let mut suggestions = vec![
// impl Into<T> for U -> impl From<T> for U
// ~~~~ ~~~~
@ -197,13 +200,10 @@ fn convert_to_from(
// fn into([mut] self) -> T -> fn into([mut] v: T) -> T
// ~~~~ ~~~~
(self_ident.span, format!("val: {from}")),
];
if let FnRetTy::Return(_) = sig.decl.output {
// fn into(self) -> T -> fn into(self) -> Self
// ~ ~~~~
suggestions.push((sig.decl.output.span(), String::from("Self")));
}
(sig.decl.output.span(), return_type),
];
let mut finder = SelfFinder {
cx,

View File

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_integer_literal;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use clippy_utils::{in_constant, is_integer_literal};
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, LangItem, PrimTy, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
@ -47,6 +47,9 @@ impl<'tcx> LateLintPass<'tcx> for FromStrRadix10 {
fn check_expr(&mut self, cx: &LateContext<'tcx>, exp: &Expr<'tcx>) {
if let ExprKind::Call(maybe_path, [src, radix]) = &exp.kind
&& let ExprKind::Path(QPath::TypeRelative(ty, pathseg)) = &maybe_path.kind
// do not lint in constant context, because the suggestion won't work.
// NB: keep this check until a new `const_trait_impl` is available and stablized.
&& !in_constant(cx, exp.hir_id)
// check if the first part of the path is some integer primitive
&& let TyKind::Path(ty_qpath) = &ty.kind

View File

@ -2,15 +2,17 @@ mod impl_trait_in_params;
mod misnamed_getters;
mod must_use;
mod not_unsafe_ptr_arg_deref;
mod renamed_function_params;
mod result;
mod too_many_arguments;
mod too_many_lines;
use clippy_utils::def_path_def_ids;
use rustc_hir as hir;
use rustc_hir::intravisit;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::def_id::{DefIdSet, LocalDefId};
use rustc_span::Span;
declare_clippy_lint! {
@ -359,13 +361,51 @@ declare_clippy_lint! {
"`impl Trait` is used in the function's parameters"
}
#[derive(Copy, Clone)]
#[allow(clippy::struct_field_names)]
declare_clippy_lint! {
/// ### What it does
/// Lints when the name of function parameters from trait impl is
/// different than its default implementation.
///
/// ### Why is this bad?
/// Using the default name for parameters of a trait method is often
/// more desirable for consistency's sake.
///
/// ### Example
/// ```rust
/// struct A(u32);
///
/// impl PartialEq for A {
/// fn eq(&self, b: &Self) -> bool {
/// self.0 == b.0
/// }
/// }
/// ```
/// Use instead:
/// ```rust
/// struct A(u32);
///
/// impl PartialEq for A {
/// fn eq(&self, other: &Self) -> bool {
/// self.0 == other.0
/// }
/// }
/// ```
#[clippy::version = "1.74.0"]
pub RENAMED_FUNCTION_PARAMS,
restriction,
"renamed function parameters in trait implementation"
}
#[derive(Clone)]
pub struct Functions {
too_many_arguments_threshold: u64,
too_many_lines_threshold: u64,
large_error_threshold: u64,
avoid_breaking_exported_api: bool,
allow_renamed_params_for: Vec<String>,
/// A set of resolved `def_id` of traits that are configured to allow
/// function params renaming.
trait_ids: DefIdSet,
}
impl Functions {
@ -374,12 +414,15 @@ impl Functions {
too_many_lines_threshold: u64,
large_error_threshold: u64,
avoid_breaking_exported_api: bool,
allow_renamed_params_for: Vec<String>,
) -> Self {
Self {
too_many_arguments_threshold,
too_many_lines_threshold,
large_error_threshold,
avoid_breaking_exported_api,
allow_renamed_params_for,
trait_ids: DefIdSet::default(),
}
}
}
@ -395,6 +438,7 @@ impl_lint_pass!(Functions => [
RESULT_LARGE_ERR,
MISNAMED_GETTERS,
IMPL_TRAIT_IN_PARAMS,
RENAMED_FUNCTION_PARAMS,
]);
impl<'tcx> LateLintPass<'tcx> for Functions {
@ -424,6 +468,7 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
must_use::check_impl_item(cx, item);
result::check_impl_item(cx, item, self.large_error_threshold);
impl_trait_in_params::check_impl_item(cx, item);
renamed_function_params::check_impl_item(cx, item, &self.trait_ids);
}
fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::TraitItem<'_>) {
@ -433,4 +478,12 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
result::check_trait_item(cx, item, self.large_error_threshold);
impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api);
}
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
for path in &self.allow_renamed_params_for {
let path_segments: Vec<&str> = path.split("::").collect();
let ids = def_path_def_ids(cx, &path_segments);
self.trait_ids.extend(ids);
}
}
}

View File

@ -0,0 +1,110 @@
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::{Applicability, MultiSpan};
use rustc_hir::def_id::{DefId, DefIdSet};
use rustc_hir::hir_id::OwnerId;
use rustc_hir::{Impl, ImplItem, ImplItemKind, ImplItemRef, ItemKind, Node, TraitRef};
use rustc_lint::LateContext;
use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::Span;
use super::RENAMED_FUNCTION_PARAMS;
pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &ImplItem<'_>, ignored_traits: &DefIdSet) {
if !item.span.from_expansion()
&& let ImplItemKind::Fn(_, body_id) = item.kind
&& let parent_node = cx.tcx.parent_hir_node(item.hir_id())
&& let Node::Item(parent_item) = parent_node
&& let ItemKind::Impl(Impl {
items,
of_trait: Some(trait_ref),
..
}) = &parent_item.kind
&& let Some(did) = trait_item_def_id_of_impl(items, item.owner_id)
&& !is_from_ignored_trait(trait_ref, ignored_traits)
{
let mut param_idents_iter = cx.tcx.hir().body_param_names(body_id);
let mut default_param_idents_iter = cx.tcx.fn_arg_names(did).iter().copied();
let renames = RenamedFnArgs::new(&mut default_param_idents_iter, &mut param_idents_iter);
if !renames.0.is_empty() {
let multi_span = renames.multi_span();
let plural = if renames.0.len() == 1 { "" } else { "s" };
span_lint_and_then(
cx,
RENAMED_FUNCTION_PARAMS,
multi_span,
format!("renamed function parameter{plural} of trait impl"),
|diag| {
diag.multipart_suggestion(
format!("consider using the default name{plural}"),
renames.0,
Applicability::Unspecified,
);
},
);
}
}
}
struct RenamedFnArgs(Vec<(Span, String)>);
impl RenamedFnArgs {
/// Comparing between an iterator of default names and one with current names,
/// then collect the ones that got renamed.
fn new<I, T>(default_names: &mut I, current_names: &mut T) -> Self
where
I: Iterator<Item = Ident>,
T: Iterator<Item = Ident>,
{
let mut renamed: Vec<(Span, String)> = vec![];
debug_assert!(default_names.size_hint() == current_names.size_hint());
while let (Some(def_name), Some(cur_name)) = (default_names.next(), current_names.next()) {
let current_name = cur_name.name;
let default_name = def_name.name;
if is_unused_or_empty_symbol(current_name) || is_unused_or_empty_symbol(default_name) {
continue;
}
if current_name != default_name {
renamed.push((cur_name.span, default_name.to_string()));
}
}
Self(renamed)
}
fn multi_span(&self) -> MultiSpan {
self.0
.iter()
.map(|(span, _)| span)
.copied()
.collect::<Vec<Span>>()
.into()
}
}
fn is_unused_or_empty_symbol(symbol: Symbol) -> bool {
// FIXME: `body_param_names` currently returning empty symbols for `wild` as well,
// so we need to check if the symbol is empty first.
// Therefore the check of whether it's equal to [`kw::Underscore`] has no use for now,
// but it would be nice to keep it here just to be future-proof.
symbol.is_empty() || symbol == kw::Underscore || symbol.as_str().starts_with('_')
}
/// Get the [`trait_item_def_id`](ImplItemRef::trait_item_def_id) of a relevant impl item.
fn trait_item_def_id_of_impl(items: &[ImplItemRef], target: OwnerId) -> Option<DefId> {
items.iter().find_map(|item| {
if item.id.owner_id == target {
item.trait_item_def_id
} else {
None
}
})
}
fn is_from_ignored_trait(of_trait: &TraitRef<'_>, ignored_traits: &DefIdSet) -> bool {
let Some(trait_did) = of_trait.trait_def_id() else {
return false;
};
ignored_traits.contains(&trait_did)
}

View File

@ -4,8 +4,8 @@ use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, FnDecl};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, AliasTy, ClauseKind, PredicateKind};
use rustc_middle::ty::print::PrintTraitRefExt;
use rustc_middle::ty::{self, AliasTy, ClauseKind, PredicateKind};
use rustc_session::declare_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::{sym, Span};

View File

@ -61,11 +61,6 @@ extern crate clippy_utils;
#[macro_use]
extern crate declare_clippy_lint;
use std::collections::BTreeMap;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
#[cfg(feature = "internal")]
pub mod deprecated_lints;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
@ -199,6 +194,7 @@ mod lifetimes;
mod lines_filter_map_ok;
mod literal_representation;
mod loops;
mod macro_metavars_in_unsafe;
mod macro_use;
mod main_recursion;
mod manual_assert;
@ -385,6 +381,10 @@ mod zero_sized_map_values;
// end lints modules, do not remove this comment, its used in `update_lints`
use clippy_config::{get_configuration_metadata, Conf};
use clippy_utils::macros::FormatArgsStorage;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
use std::collections::BTreeMap;
/// Register all pre expansion lints
///
@ -597,9 +597,11 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
ref allowed_duplicate_crates,
allow_comparison_to_zero,
ref allowed_prefixes,
ref allow_renamed_params_for,
blacklisted_names: _,
cyclomatic_complexity_threshold: _,
warn_unsafe_macro_metavars_in_private_macros,
} = *conf;
let msrv = || msrv.clone();
@ -616,6 +618,14 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
}
}
let format_args_storage = FormatArgsStorage::default();
let format_args = format_args_storage.clone();
store.register_early_pass(move || {
Box::new(utils::format_args_collector::FormatArgsCollector::new(
format_args.clone(),
))
});
// all the internal lints
#[cfg(feature = "internal")]
{
@ -656,7 +666,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
.collect(),
))
});
store.register_early_pass(|| Box::<utils::format_args_collector::FormatArgsCollector>::default());
store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
store.register_late_pass(|_| Box::new(utils::author::Author));
store.register_late_pass(move |_| {
@ -698,6 +707,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| {
Box::new(methods::Methods::new(
avoid_breaking_exported_api,
@ -705,6 +715,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
allow_expect_in_tests,
allow_unwrap_in_tests,
allowed_dotfiles.clone(),
format_args.clone(),
))
});
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv())));
@ -769,7 +780,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::<regex::Regex>::default());
store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
store.register_late_pass(|_| Box::new(format::UselessFormat));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone())));
store.register_late_pass(|_| Box::new(swap::Swap));
store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional));
store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
@ -780,6 +792,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
too_many_lines_threshold,
large_error_threshold,
avoid_breaking_exported_api,
allow_renamed_params_for.clone(),
))
});
store.register_late_pass(move |_| Box::new(doc::Documentation::new(doc_valid_idents, check_private_items)));
@ -793,7 +806,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount));
store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)));
store.register_late_pass(|_| Box::new(explicit_write::ExplicitWrite));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone())));
store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue));
store.register_late_pass(move |tcx| {
Box::new(pass_by_ref_or_value::PassByRefOrValue::new(
@ -835,7 +849,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
store.register_late_pass(|_| Box::new(format_impl::FormatImpl::new()));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone())));
store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval));
store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));
@ -961,8 +976,14 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
accept_comment_above_attributes,
))
});
store
.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined_format_args)));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| {
Box::new(format_args::FormatArgs::new(
format_args.clone(),
msrv(),
allow_mixed_uninlined_format_args,
))
});
store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit));
@ -973,7 +994,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_late_pass(|_| Box::<only_used_in_recursion::OnlyUsedInRecursion>::default());
store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests)));
store.register_late_pass(move |_| Box::new(write::Write::new(allow_print_in_tests)));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(write::Write::new(format_args.clone(), allow_print_in_tests)));
store.register_late_pass(move |_| {
Box::new(cargo::Cargo {
ignore_publish: cargo_ignore_publish,
@ -1136,6 +1158,12 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects));
store.register_late_pass(|_| Box::new(manual_unwrap_or_default::ManualUnwrapOrDefault));
store.register_late_pass(|_| Box::new(integer_division_remainder_used::IntegerDivisionRemainderUsed));
store.register_late_pass(move |_| {
Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe {
warn_unsafe_macro_metavars_in_private_macros,
..Default::default()
})
});
// add lints here, do not remove this comment, it's used in `new_lint`
}

View File

@ -60,12 +60,9 @@ fn check_for_mutation(
span_low: None,
span_high: None,
};
ExprUseVisitor::for_clippy(
cx,
body.hir_id.owner.def_id,
&mut delegate,
)
.walk_expr(body).into_ok();
ExprUseVisitor::for_clippy(cx, body.hir_id.owner.def_id, &mut delegate)
.walk_expr(body)
.into_ok();
delegate.mutation_span()
}

View File

@ -0,0 +1,256 @@
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::is_lint_allowed;
use itertools::Itertools;
use rustc_hir::def_id::LocalDefId;
use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, Visitor};
use rustc_hir::{BlockCheckMode, Expr, ExprKind, HirId, Stmt, UnsafeSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::{sym, Span, SyntaxContext};
declare_clippy_lint! {
/// ### What it does
/// Looks for macros that expand metavariables in an unsafe block.
///
/// ### Why is this bad?
/// This hides an unsafe block and allows the user of the macro to write unsafe code without an explicit
/// unsafe block at callsite, making it possible to perform unsafe operations in seemingly safe code.
///
/// The macro should be restructured so that these metavariables are referenced outside of unsafe blocks
/// and that the usual unsafety checks apply to the macro argument.
///
/// This is usually done by binding it to a variable outside of the unsafe block
/// and then using that variable inside of the block as shown in the example, or by referencing it a second time
/// in a safe context, e.g. `if false { $expr }`.
///
/// ### Known limitations
/// Due to how macros are represented in the compiler at the time Clippy runs its lints,
/// it's not possible to look for metavariables in macro definitions directly.
///
/// Instead, this lint looks at expansions of macros.
/// This leads to false negatives for macros that are never actually invoked.
///
/// By default, this lint is rather conservative and will only emit warnings on publicly-exported
/// macros from the same crate, because oftentimes private internal macros are one-off macros where
/// this lint would just be noise (e.g. macros that generate `impl` blocks).
/// The default behavior should help with preventing a high number of such false positives,
/// however it can be configured to also emit warnings in private macros if desired.
///
/// ### Example
/// ```no_run
/// /// Gets the first element of a slice
/// macro_rules! first {
/// ($slice:expr) => {
/// unsafe {
/// let slice = $slice; // ⚠️ expansion inside of `unsafe {}`
///
/// assert!(!slice.is_empty());
/// // SAFETY: slice is checked to have at least one element
/// slice.first().unwrap_unchecked()
/// }
/// }
/// }
///
/// assert_eq!(*first!(&[1i32]), 1);
///
/// // This will compile as a consequence (note the lack of `unsafe {}`)
/// assert_eq!(*first!(std::hint::unreachable_unchecked() as &[i32]), 1);
/// ```
/// Use instead:
/// ```compile_fail
/// macro_rules! first {
/// ($slice:expr) => {{
/// let slice = $slice; // ✅ outside of `unsafe {}`
/// unsafe {
/// assert!(!slice.is_empty());
/// // SAFETY: slice is checked to have at least one element
/// slice.first().unwrap_unchecked()
/// }
/// }}
/// }
///
/// assert_eq!(*first!(&[1]), 1);
///
/// // This won't compile:
/// assert_eq!(*first!(std::hint::unreachable_unchecked() as &[i32]), 1);
/// ```
#[clippy::version = "1.80.0"]
pub MACRO_METAVARS_IN_UNSAFE,
suspicious,
"expanding macro metavariables in an unsafe block"
}
impl_lint_pass!(ExprMetavarsInUnsafe => [MACRO_METAVARS_IN_UNSAFE]);
#[derive(Clone, Debug)]
pub enum MetavarState {
ReferencedInUnsafe { unsafe_blocks: Vec<HirId> },
ReferencedInSafe,
}
#[derive(Default)]
pub struct ExprMetavarsInUnsafe {
pub warn_unsafe_macro_metavars_in_private_macros: bool,
/// A metavariable can be expanded more than once, potentially across multiple bodies, so it
/// requires some state kept across HIR nodes to make it possible to delay a warning
/// and later undo:
///
/// ```ignore
/// macro_rules! x {
/// ($v:expr) => {
/// unsafe { $v; } // unsafe context, it might be possible to emit a warning here, so add it to the map
///
/// $v; // `$v` expanded another time but in a safe context, set to ReferencedInSafe to suppress
/// }
/// }
/// ```
pub metavar_expns: BTreeMap<Span, MetavarState>,
}
struct BodyVisitor<'a, 'tcx> {
/// Stack of unsafe blocks -- the top item always represents the last seen unsafe block from
/// within a relevant macro.
macro_unsafe_blocks: Vec<HirId>,
/// When this is >0, it means that the node currently being visited is "within" a
/// macro definition. This is not necessary for correctness, it merely helps reduce the number
/// of spans we need to insert into the map, since only spans from macros are relevant.
expn_depth: u32,
cx: &'a LateContext<'tcx>,
lint: &'a mut ExprMetavarsInUnsafe,
}
fn is_public_macro(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
(cx.effective_visibilities.is_exported(def_id) || cx.tcx.has_attr(def_id, sym::macro_export))
&& !cx.tcx.is_doc_hidden(def_id)
}
impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> {
fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) {
let from_expn = s.span.from_expansion();
if from_expn {
self.expn_depth += 1;
}
walk_stmt(self, s);
if from_expn {
self.expn_depth -= 1;
}
}
fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) {
let ctxt = e.span.ctxt();
if let ExprKind::Block(block, _) = e.kind
&& let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules
&& !ctxt.is_root()
&& let Some(macro_def_id) = ctxt.outer_expn_data().macro_def_id
&& let Some(macro_def_id) = macro_def_id.as_local()
&& (self.lint.warn_unsafe_macro_metavars_in_private_macros || is_public_macro(self.cx, macro_def_id))
{
self.macro_unsafe_blocks.push(block.hir_id);
walk_block(self, block);
self.macro_unsafe_blocks.pop();
} else if ctxt.is_root() && self.expn_depth > 0 {
let unsafe_block = self.macro_unsafe_blocks.last().copied();
match (self.lint.metavar_expns.entry(e.span), unsafe_block) {
(Entry::Vacant(e), None) => {
e.insert(MetavarState::ReferencedInSafe);
},
(Entry::Vacant(e), Some(unsafe_block)) => {
e.insert(MetavarState::ReferencedInUnsafe {
unsafe_blocks: vec![unsafe_block],
});
},
(Entry::Occupied(mut e), None) => {
if let MetavarState::ReferencedInUnsafe { .. } = *e.get() {
e.insert(MetavarState::ReferencedInSafe);
}
},
(Entry::Occupied(mut e), Some(unsafe_block)) => {
if let MetavarState::ReferencedInUnsafe { unsafe_blocks } = e.get_mut()
&& !unsafe_blocks.contains(&unsafe_block)
{
unsafe_blocks.push(unsafe_block);
}
},
}
// NB: No need to visit descendant nodes. They're guaranteed to represent the same
// metavariable
} else {
walk_expr(self, e);
}
}
}
impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx rustc_hir::Body<'tcx>) {
if is_lint_allowed(cx, MACRO_METAVARS_IN_UNSAFE, body.value.hir_id) {
return;
}
// This BodyVisitor is separate and not part of the lint pass because there is no
// `check_stmt_post` on `(Late)LintPass`, which we'd need to detect when we're leaving a macro span
let mut vis = BodyVisitor {
#[expect(clippy::bool_to_int_with_if)] // obfuscates the meaning
expn_depth: if body.value.span.from_expansion() { 1 } else { 0 },
macro_unsafe_blocks: Vec::new(),
lint: self,
cx
};
vis.visit_body(body);
}
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
// Aggregate all unsafe blocks from all spans:
// ```
// macro_rules! x {
// ($w:expr, $x:expr, $y:expr) => { $w; unsafe { $w; $x; }; unsafe { $x; $y; }; }
// }
// $w: [] (unsafe#0 is never added because it was referenced in a safe context)
// $x: [unsafe#0, unsafe#1]
// $y: [unsafe#1]
// ```
// We want to lint unsafe blocks #0 and #1
let bad_unsafe_blocks = self
.metavar_expns
.iter()
.filter_map(|(_, state)| match state {
MetavarState::ReferencedInUnsafe { unsafe_blocks } => Some(unsafe_blocks.as_slice()),
MetavarState::ReferencedInSafe => None,
})
.flatten()
.copied()
.map(|id| {
// Remove the syntax context to hide "in this macro invocation" in the diagnostic.
// The invocation doesn't matter. Also we want to dedupe by the unsafe block and not by anything
// related to the callsite.
let span = cx.tcx.hir().span(id);
(id, Span::new(span.lo(), span.hi(), SyntaxContext::root(), None))
})
.dedup_by(|&(_, a), &(_, b)| a == b);
for (id, span) in bad_unsafe_blocks {
span_lint_hir_and_then(
cx,
MACRO_METAVARS_IN_UNSAFE,
id,
span,
"this macro expands metavariables in an unsafe block",
|diag| {
diag.note("this allows the user of the macro to write unsafe code outside of an unsafe block");
diag.help(
"consider expanding any metavariables outside of this block, e.g. by storing them in a variable",
);
diag.help(
"... or also expand referenced metavariables in a safe context to require an unsafe block at callsite",
);
},
);
}
}
}

View File

@ -611,15 +611,22 @@ impl<'tcx> BinaryOp<'tcx> {
/// The clamp meta pattern is a pattern shared between many (but not all) patterns.
/// In summary, this pattern consists of two if statements that meet many criteria,
///
/// - binary operators that are one of [`>`, `<`, `>=`, `<=`].
///
/// - Both binary statements must have a shared argument
///
/// - Which can appear on the left or right side of either statement
///
/// - The binary operators must define a finite range for the shared argument. To put this in
/// the terms of Rust `std` library, the following ranges are acceptable
///
/// - `Range`
/// - `RangeInclusive`
///
/// And all other range types are not accepted. For the purposes of `clamp` it's irrelevant
/// whether the range is inclusive or not, the output is the same.
///
/// - The result of each if statement must be equal to the argument unique to that if statement. The
/// result can not be the shared argument in either case.
fn is_clamp_meta_pattern<'tcx>(

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::source::snippet;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_lint_allowed, path_to_local, search_same, SpanlessEq, SpanlessHash};
use core::cmp::Ordering;
use core::{iter, slice};
@ -9,9 +9,9 @@ use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{Arm, Expr, ExprKind, HirId, HirIdMap, HirIdMapEntry, HirIdSet, Pat, PatKind, RangeEnd};
use rustc_lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
use rustc_lint::LateContext;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::ty;
use rustc_span::{ErrorGuaranteed, Symbol};
use rustc_span::{ErrorGuaranteed, Span, Symbol};
use super::MATCH_SAME_ARMS;
@ -110,20 +110,22 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
&& check_same_body()
};
let mut appl = Applicability::MaybeIncorrect;
let indexed_arms: Vec<(usize, &Arm<'_>)> = arms.iter().enumerate().collect();
for (&(i, arm1), &(j, arm2)) in search_same(&indexed_arms, hash, eq) {
if matches!(arm2.pat.kind, PatKind::Wild) {
if !cx.tcx.features().non_exhaustive_omitted_patterns_lint
|| is_lint_allowed(cx, NON_EXHAUSTIVE_OMITTED_PATTERNS, arm2.hir_id)
{
let arm_span = adjusted_arm_span(cx, arm1.span);
span_lint_hir_and_then(
cx,
MATCH_SAME_ARMS,
arm1.hir_id,
arm1.span,
arm_span,
"this match arm has an identical body to the `_` wildcard arm",
|diag| {
diag.span_suggestion(arm1.span, "try removing the arm", "", Applicability::MaybeIncorrect)
diag.span_suggestion(arm_span, "try removing the arm", "", appl)
.help("or try changing either arm body")
.span_note(arm2.span, "`_` wildcard arm here");
},
@ -144,23 +146,36 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) {
keep_arm.span,
"this match arm has an identical body to another arm",
|diag| {
let move_pat_snip = snippet(cx, move_arm.pat.span, "<pat2>");
let keep_pat_snip = snippet(cx, keep_arm.pat.span, "<pat1>");
let move_pat_snip = snippet_with_applicability(cx, move_arm.pat.span, "<pat2>", &mut appl);
let keep_pat_snip = snippet_with_applicability(cx, keep_arm.pat.span, "<pat1>", &mut appl);
diag.span_suggestion(
keep_arm.pat.span,
"try merging the arm patterns",
"or try merging the arm patterns",
format!("{keep_pat_snip} | {move_pat_snip}"),
Applicability::MaybeIncorrect,
appl,
)
.help("or try changing either arm body")
.span_note(move_arm.span, "other arm here");
.span_suggestion(
adjusted_arm_span(cx, move_arm.span),
"and remove this obsolete arm",
"",
appl,
)
.help("try changing either arm body");
},
);
}
}
}
/// Extend arm's span to include the comma and whitespaces after it.
fn adjusted_arm_span(cx: &LateContext<'_>, span: Span) -> Span {
let source_map = cx.sess().source_map();
source_map
.span_extend_while(span, |c| c == ',' || c.is_ascii_whitespace())
.unwrap_or(span)
}
#[derive(Clone, Copy)]
enum NormalizedPat<'a> {
Wild,

View File

@ -115,45 +115,60 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
}
}
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
self.cx.typeck_results().expr_ty(ex)
fn is_sig_drop_expr(&mut self, ex: &'tcx Expr<'_>) -> bool {
!ex.is_syntactic_place_expr() && self.has_sig_drop_attr(self.cx.typeck_results().expr_ty(ex))
}
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
!self.seen_types.insert(ty)
fn has_sig_drop_attr(&mut self, ty: Ty<'tcx>) -> bool {
self.seen_types.clear();
self.has_sig_drop_attr_impl(ty)
}
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool {
if let Some(adt) = ty.ty_adt_def() {
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
if get_attr(
self.cx.sess(),
self.cx.tcx.get_attrs_unchecked(adt.did()),
"has_significant_drop",
)
.count()
> 0
{
return true;
}
}
match ty.kind() {
rustc_middle::ty::Adt(a, b) => {
for f in a.all_fields() {
let ty = f.ty(cx.tcx, b);
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
return true;
}
}
for generic_arg in *b {
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
if self.has_sig_drop_attr(cx, ty) {
return true;
}
}
}
false
},
rustc_middle::ty::Array(ty, _)
| rustc_middle::ty::RawPtr(ty, _)
| rustc_middle::ty::Ref(_, ty, _)
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
_ => false,
if !self.seen_types.insert(ty) {
return false;
}
let result = match ty.kind() {
rustc_middle::ty::Adt(adt, args) => {
// if some field has significant drop,
adt.all_fields()
.map(|field| field.ty(self.cx.tcx, args))
.any(|ty| self.has_sig_drop_attr_impl(ty))
// or if there is no generic lifetime and..
// (to avoid false positive on `Ref<'a, MutexGuard<Foo>>`)
|| (args
.iter()
.all(|arg| !matches!(arg.unpack(), GenericArgKind::Lifetime(_)))
// some generic parameter has significant drop
// (to avoid false negative on `Box<MutexGuard<Foo>>`)
&& args
.iter()
.filter_map(|arg| match arg.unpack() {
GenericArgKind::Type(ty) => Some(ty),
_ => None,
})
.any(|ty| self.has_sig_drop_attr_impl(ty)))
},
rustc_middle::ty::Tuple(tys) => tys.iter().any(|ty| self.has_sig_drop_attr_impl(ty)),
rustc_middle::ty::Array(ty, _) | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr_impl(*ty),
_ => false,
};
result
}
}
@ -232,7 +247,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
if self.current_sig_drop.is_some() {
return;
}
let ty = self.sig_drop_checker.get_type(expr);
let ty = self.cx.typeck_results().expr_ty(expr);
if ty.is_ref() {
// We checked that the type was ref, so builtin_deref will return Some,
// but let's avoid any chance of an ICE.
@ -279,11 +294,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
if !self.is_chain_end
&& self
.sig_drop_checker
.has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
{
if !self.is_chain_end && self.sig_drop_checker.is_sig_drop_expr(ex) {
self.has_significant_drop = true;
return;
}
@ -387,10 +398,7 @@ fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'
impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if self
.sig_drop_checker
.has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
{
if self.sig_drop_checker.is_sig_drop_expr(ex) {
self.found_sig_drop_spans.insert(ex.span);
return;
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node};
use clippy_utils::macros::{format_args_inputs_span, root_macro_call_first_node, FormatArgsStorage};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use rustc_errors::Applicability;
@ -16,6 +16,7 @@ use super::EXPECT_FUN_CALL;
#[allow(clippy::too_many_lines)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
format_args_storage: &FormatArgsStorage,
expr: &hir::Expr<'_>,
method_span: Span,
name: &str,
@ -134,9 +135,9 @@ pub(super) fn check<'tcx>(
// Special handling for `format!` as arg_root
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
&& let Some(format_args) = find_format_args(cx, arg_root, macro_call.expn)
&& let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn)
{
let span = format_args_inputs_span(&format_args);
let span = format_args_inputs_span(format_args);
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_sugg(
cx,

View File

@ -126,15 +126,15 @@ enum FilterType {
///
/// How this is done:
/// 1. we know that this is invoked in a method call with `filter` as the method name via `mod.rs`
/// 2. we check that we are in a trait method. Therefore we are in an
/// `(x as Iterator).filter({filter_arg})` method call.
/// 2. we check that we are in a trait method. Therefore we are in an `(x as
/// Iterator).filter({filter_arg})` method call.
/// 3. we check that the parent expression is not a map. This is because we don't want to lint
/// twice, and we already have a specialized lint for that.
/// 4. we check that the span of the filter does not contain a comment.
/// 5. we get the type of the `Item` in the `Iterator`, and compare against the type of Option and
/// Result.
/// Result.
/// 6. we finally check the contents of the filter argument to see if it is a call to `is_some` or
/// `is_ok`.
/// `is_ok`.
/// 7. if all of the above are true, then we return the `FilterType`
fn expression_type(
cx: &LateContext<'_>,

View File

@ -12,8 +12,10 @@ use rustc_middle::ty;
use rustc_span::{sym, Span};
/// lint use of:
///
/// - `hashmap.iter().map(|(_, v)| v)`
/// - `hashmap.into_iter().map(|(_, v)| v)`
///
/// on `HashMaps` and `BTreeMaps` in std
pub(super) fn check<'tcx>(

View File

@ -69,12 +69,9 @@ pub(super) fn check<'tcx>(
used_move: HirIdSet::default(),
};
ExprUseVisitor::for_clippy(
cx,
closure.def_id,
&mut delegate,
)
.consume_body(body).into_ok();
ExprUseVisitor::for_clippy(cx, closure.def_id, &mut delegate)
.consume_body(body)
.into_ok();
let mut to_be_discarded = false;

View File

@ -133,6 +133,7 @@ use bind_instead_of_map::BindInsteadOfMap;
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::macros::FormatArgsStorage;
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty};
pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES;
@ -4087,12 +4088,14 @@ declare_clippy_lint! {
suspicious,
"is_empty() called on strings known at compile time"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
allowed_dotfiles: FxHashSet<String>,
format_args: FormatArgsStorage,
}
impl Methods {
@ -4103,6 +4106,7 @@ impl Methods {
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
mut allowed_dotfiles: FxHashSet<String>,
format_args: FormatArgsStorage,
) -> Self {
allowed_dotfiles.extend(DEFAULT_ALLOWED_DOTFILES.iter().map(ToString::to_string));
@ -4112,6 +4116,7 @@ impl Methods {
allow_expect_in_tests,
allow_unwrap_in_tests,
allowed_dotfiles,
format_args,
}
}
}
@ -4281,7 +4286,15 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
ExprKind::MethodCall(method_call, receiver, args, _) => {
let method_span = method_call.ident.span;
or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args);
expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args);
expect_fun_call::check(
cx,
&self.format_args,
expr,
method_span,
method_call.ident.as_str(),
receiver,
args,
);
clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args);
clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args);
inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args);

View File

@ -3,10 +3,12 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::higher::ForLoop;
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{get_iterator_item_ty, implements_trait};
use clippy_utils::{fn_def_id, get_parent_expr};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{can_mut_borrow_both, fn_def_id, get_parent_expr, path_to_local};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind};
use rustc_hir::{BindingMode, Expr, ExprKind, Node, PatKind};
use rustc_lint::LateContext;
use rustc_span::{sym, Symbol};
@ -40,6 +42,53 @@ pub fn check_for_loop_iter(
&& !clone_or_copy_needed
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
{
// Issue 12098
// https://github.com/rust-lang/rust-clippy/issues/12098
// if the assignee have `mut borrow` conflict with the iteratee
// the lint should not execute, former didn't consider the mut case
// check whether `expr` is mutable
fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
if let Some(hir_id) = path_to_local(expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
{
matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
} else {
true
}
}
fn is_caller_or_fields_change(cx: &LateContext<'_>, body: &Expr<'_>, caller: &Expr<'_>) -> bool {
let mut change = false;
if let ExprKind::Block(block, ..) = body.kind {
for_each_expr(block, |e| {
match e.kind {
ExprKind::Assign(assignee, _, _) | ExprKind::AssignOp(_, assignee, _) => {
change |= !can_mut_borrow_both(cx, caller, assignee);
},
_ => {},
}
// the return value has no effect but the function need one return value
ControlFlow::<()>::Continue(())
});
}
change
}
if let ExprKind::Call(_, [child, ..]) = expr.kind {
// filter first layer of iterator
let mut child = child;
// get inner real caller requests for clone
while let ExprKind::MethodCall(_, caller, _, _) = child.kind {
child = caller;
}
if is_mutable(cx, child) && is_caller_or_fields_change(cx, body, child) {
// skip lint
return true;
}
};
// the lint should not be executed if no violation happens
let snippet = if let ExprKind::MethodCall(maybe_iter_method_name, collection, [], _) = receiver.kind
&& maybe_iter_method_name.ident.name == sym::iter
&& let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator)

View File

@ -5,7 +5,8 @@ use clippy_utils::ty::implements_trait;
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind, Mutability, Param, Pat, PatKind, Path, PathSegment, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, GenericArgKind};
use rustc_middle::ty;
use rustc_middle::ty::GenericArgKind;
use rustc_span::sym;
use rustc_span::symbol::Ident;
use std::iter;

View File

@ -120,6 +120,7 @@ fn pat_bindings(pat: &Pat<'_>) -> Vec<HirId> {
/// operations performed on `binding_hir_ids` are:
/// * to take non-mutable references to them
/// * to use them as non-mutable `&self` in method calls
///
/// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true
/// when `CloneOrCopyVisitor` is done visiting.
struct CloneOrCopyVisitor<'cx, 'tcx> {

View File

@ -96,10 +96,6 @@ impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> {
self.found = true;
return;
},
ExprKind::If(..) => {
self.found = true;
return;
},
ExprKind::Path(_) => {
if let Some(adj) = self.cx.typeck_results().adjustments().get(expr.hir_id) {
if adj

View File

@ -1,6 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
use clippy_utils::mir::PossibleBorrowerMap;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::{expr_use_ctxt, peel_n_hir_expr_refs, DefinedTy, ExprUseNode};
@ -11,7 +11,6 @@ use rustc_hir::{Body, Expr, ExprKind, Mutability, Path, QPath};
use rustc_index::bit_set::BitSet;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::{Rvalue, StatementKind};
use rustc_middle::ty::{
self, ClauseKind, EarlyBinder, FnSig, GenericArg, GenericArgKind, ParamTy, ProjectionPredicate, Ty,
};
@ -106,7 +105,6 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessBorrowsForGenericArgs<'tcx> {
}
&& let count = needless_borrow_count(
cx,
&mut self.possible_borrowers,
fn_id,
cx.typeck_results().node_args(hir_id),
i,
@ -155,11 +153,9 @@ fn path_has_args(p: &QPath<'_>) -> bool {
/// The following constraints will be checked:
/// * The borrowed expression meets all the generic type's constraints.
/// * The generic type appears only once in the functions signature.
/// * The borrowed value will not be moved if it is used later in the function.
#[expect(clippy::too_many_arguments)]
/// * The borrowed value is Copy itself OR not a variable (created by a function call)
fn needless_borrow_count<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
fn_id: DefId,
callee_args: ty::GenericArgsRef<'tcx>,
arg_index: usize,
@ -234,9 +230,9 @@ fn needless_borrow_count<'tcx>(
let referent_ty = cx.typeck_results().expr_ty(referent);
if !is_copy(cx, referent_ty)
&& (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
|| !referent_used_exactly_once(cx, possible_borrowers, reference))
if (!is_copy(cx, referent_ty) && !referent_ty.is_ref())
&& let ExprKind::AddrOf(_, _, inner) = reference.kind
&& !matches!(inner.kind, ExprKind::Call(..) | ExprKind::MethodCall(..))
{
return false;
}
@ -339,37 +335,6 @@ fn is_mixed_projection_predicate<'tcx>(
}
}
fn referent_used_exactly_once<'tcx>(
cx: &LateContext<'tcx>,
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
reference: &Expr<'tcx>,
) -> bool {
if let Some(mir) = enclosing_mir(cx.tcx, reference.hir_id)
&& let Some(local) = expr_local(cx.tcx, reference)
&& let [location] = *local_assignments(mir, local).as_slice()
&& let block_data = &mir.basic_blocks[location.block]
&& let Some(statement) = block_data.statements.get(location.statement_index)
&& let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) = statement.kind
&& !place.is_indirect_first_projection()
{
let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
if possible_borrowers
.last()
.map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
{
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
}
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
// itself. See the comment in that method for an explanation as to why.
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
&& used_exactly_once(mir, place.local).unwrap_or(false)
} else {
false
}
}
// Iteratively replaces `param_ty` with `new_ty` in `args`, and similarly for each resulting
// projected type that is a type parameter. Returns `false` if replacing the types would have an
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
@ -408,7 +373,11 @@ fn replace_types<'tcx>(
&& let Some(term_ty) = projection_predicate.term.ty()
&& let ty::Param(term_param_ty) = term_ty.kind()
{
let projection = projection_predicate.projection_term.with_self_ty(cx.tcx, new_ty).expect_ty(cx.tcx).to_ty(cx.tcx);
let projection = projection_predicate
.projection_term
.with_self_ty(cx.tcx, new_ty)
.expect_ty(cx.tcx)
.to_ty(cx.tcx);
if let Ok(projected_ty) = cx.tcx.try_normalize_erasing_regions(cx.param_env, projection)
&& args[term_param_ty.index as usize] != GenericArg::from(projected_ty)

View File

@ -178,8 +178,7 @@ impl EarlyLintPass for NeedlessContinue {
/// Given an expression, returns true if either of the following is true
///
/// - The expression is a `continue` node.
/// - The expression node is a block with the first statement being a
/// `continue`.
/// - The expression node is a block with the first statement being a `continue`.
fn needless_continue_in_else(else_expr: &ast::Expr, label: Option<&ast::Label>) -> bool {
match else_expr.kind {
ast::ExprKind::Block(ref else_block, _) => is_first_block_stmt_continue(else_block, label),

View File

@ -273,24 +273,16 @@ fn check<'tcx>(
msg_span,
"unneeded late initialization",
|diag| {
diag.tool_only_span_suggestion(
local_stmt.span,
"remove the local",
"",
Applicability::MachineApplicable,
);
diag.span_suggestion(
assign.lhs_span,
format!("declare `{binding_name}` here"),
let_snippet,
diag.multipart_suggestion(
format!("move the declaration `{binding_name}` here"),
vec![(local_stmt.span, String::new()), (assign.lhs_span, let_snippet)],
Applicability::MachineApplicable,
);
},
);
},
ExprKind::If(cond, then_expr, Some(else_expr)) if !contains_let(cond) => {
let (applicability, suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
let (applicability, mut suggestions) = assignment_suggestions(cx, binding_id, [then_expr, else_expr])?;
span_lint_and_then(
cx,
@ -298,30 +290,26 @@ fn check<'tcx>(
local_stmt.span,
"unneeded late initialization",
|diag| {
diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
diag.span_suggestion_verbose(
usage.stmt.span.shrink_to_lo(),
format!("declare `{binding_name}` here"),
format!("{let_snippet} = "),
applicability,
);
diag.multipart_suggestion("remove the assignments from the branches", suggestions, applicability);
suggestions.push((local_stmt.span, String::new()));
suggestions.push((usage.stmt.span.shrink_to_lo(), format!("{let_snippet} = ")));
if usage.needs_semi {
diag.span_suggestion(
usage.stmt.span.shrink_to_hi(),
"add a semicolon after the `if` expression",
";",
applicability,
);
suggestions.push((usage.stmt.span.shrink_to_hi(), ";".to_owned()));
}
diag.multipart_suggestion(
format!(
"move the declaration `{binding_name}` here and remove the assignments from the branches"
),
suggestions,
applicability,
);
},
);
},
ExprKind::Match(_, arms, MatchSource::Normal) => {
let (applicability, suggestions) = assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
let (applicability, mut suggestions) =
assignment_suggestions(cx, binding_id, arms.iter().map(|arm| arm.body))?;
span_lint_and_then(
cx,
@ -329,29 +317,18 @@ fn check<'tcx>(
local_stmt.span,
"unneeded late initialization",
|diag| {
diag.tool_only_span_suggestion(local_stmt.span, "remove the local", String::new(), applicability);
suggestions.push((local_stmt.span, String::new()));
suggestions.push((usage.stmt.span.shrink_to_lo(), format!("{let_snippet} = ")));
diag.span_suggestion_verbose(
usage.stmt.span.shrink_to_lo(),
format!("declare `{binding_name}` here"),
format!("{let_snippet} = "),
applicability,
);
if usage.needs_semi {
suggestions.push((usage.stmt.span.shrink_to_hi(), ";".to_owned()));
}
diag.multipart_suggestion(
"remove the assignments from the `match` arms",
format!("move the declaration `{binding_name}` here and remove the assignments from the `match` arms"),
suggestions,
applicability,
);
if usage.needs_semi {
diag.span_suggestion(
usage.stmt.span.shrink_to_hi(),
"add a semicolon after the `match` expression",
";",
applicability,
);
}
},
);
},

View File

@ -117,7 +117,9 @@ fn check_closures<'tcx>(
.associated_body()
.map(|(_, body_id)| hir.body(body_id))
{
euv::ExprUseVisitor::for_clippy(cx, closure, &mut *ctx).consume_body(body).into_ok();
euv::ExprUseVisitor::for_clippy(cx, closure, &mut *ctx)
.consume_body(body)
.into_ok();
}
}
}
@ -194,7 +196,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> {
async_closures: FxHashSet::default(),
tcx: cx.tcx,
};
euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx).consume_body(body).into_ok();
euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx)
.consume_body(body)
.into_ok();
let mut checked_closures = FxHashSet::default();

View File

@ -133,7 +133,9 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
// function body.
let MovedVariablesCtxt { moved_vars } = {
let mut ctx = MovedVariablesCtxt::default();
euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx).consume_body(body).into_ok();
euv::ExprUseVisitor::for_clippy(cx, fn_def_id, &mut ctx)
.consume_body(body)
.into_ok();
ctx
};

View File

@ -94,7 +94,6 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect {
fn check_block_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx rustc_hir::Block<'tcx>) {
for hir_id in self.local_bindings.pop().unwrap() {
// FIXME(rust/#120456) - is `swap_remove` correct?
if let Some(span) = self.underscore_bindings.swap_remove(&hir_id) {
span_lint_hir(
cx,
@ -109,7 +108,6 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect {
fn check_expr(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(def_id) = path_to_local(expr) {
// FIXME(rust/#120456) - is `swap_remove` correct?
self.underscore_bindings.swap_remove(&def_id);
}
}
@ -118,7 +116,11 @@ impl<'tcx> LateLintPass<'tcx> for NoEffect {
impl NoEffect {
fn check_no_effect(&mut self, cx: &LateContext<'_>, stmt: &Stmt<'_>) -> bool {
if let StmtKind::Semi(expr) = stmt.kind {
// move `expr.span.from_expansion()` ahead
// Covered by rustc `path_statements` lint
if matches!(expr.kind, ExprKind::Path(_)) {
return true;
}
if expr.span.from_expansion() {
return false;
}

View File

@ -389,6 +389,10 @@ declare_lint_pass!(StrToString => [STR_TO_STRING]);
impl<'tcx> LateLintPass<'tcx> for StrToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
&& path.ident.name == sym::to_string
&& let ty = cx.typeck_results().expr_ty(self_arg)
@ -437,6 +441,10 @@ declare_lint_pass!(StringToString => [STRING_TO_STRING]);
impl<'tcx> LateLintPass<'tcx> for StringToString {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) {
if expr.span.from_expansion() {
return;
}
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
&& path.ident.name == sym::to_string
&& let ty = cx.typeck_results().expr_ty(self_arg)

View File

@ -28,7 +28,7 @@ pub(super) fn check<'tcx>(
let int_ty = substs.type_at(0);
if from_ty != int_ty {
return false;
return false;
}
span_lint_and_then(

View File

@ -251,11 +251,7 @@ impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> {
local_id: unwrap_info.local_id,
};
let vis = ExprUseVisitor::for_clippy(
self.cx,
cond.hir_id.owner.def_id,
&mut delegate,
);
let vis = ExprUseVisitor::for_clippy(self.cx, cond.hir_id.owner.def_id, &mut delegate);
vis.walk_expr(cond).into_ok();
vis.walk_expr(branch).into_ok();

View File

@ -1,4 +1,4 @@
use clippy_utils::macros::AST_FORMAT_ARGS;
use clippy_utils::macros::FormatArgsStorage;
use clippy_utils::source::snippet_opt;
use itertools::Itertools;
use rustc_ast::{Crate, Expr, ExprKind, FormatArgs};
@ -9,13 +9,20 @@ use rustc_session::impl_lint_pass;
use rustc_span::{hygiene, Span};
use std::iter::once;
use std::mem;
use std::rc::Rc;
/// Collects [`rustc_ast::FormatArgs`] so that future late passes can call
/// [`clippy_utils::macros::find_format_args`]
#[derive(Default)]
/// Populates [`FormatArgsStorage`] with AST [`FormatArgs`] nodes
pub struct FormatArgsCollector {
format_args: FxHashMap<Span, Rc<FormatArgs>>,
format_args: FxHashMap<Span, FormatArgs>,
storage: FormatArgsStorage,
}
impl FormatArgsCollector {
pub fn new(storage: FormatArgsStorage) -> Self {
Self {
format_args: FxHashMap::default(),
storage,
}
}
}
impl_lint_pass!(FormatArgsCollector => []);
@ -27,16 +34,12 @@ impl EarlyLintPass for FormatArgsCollector {
return;
}
self.format_args
.insert(expr.span.with_parent(None), Rc::new((**args).clone()));
self.format_args.insert(expr.span.with_parent(None), (**args).clone());
}
}
fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) {
AST_FORMAT_ARGS.with(|ast_format_args| {
let result = ast_format_args.set(mem::take(&mut self.format_args));
debug_assert!(result.is_ok());
});
self.storage.set(mem::take(&mut self.format_args));
}
}

View File

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::macros::{find_format_args, format_arg_removal_span, root_macro_call_first_node, MacroCall};
use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall};
use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_ast::token::LitKind;
@ -236,13 +236,15 @@ declare_clippy_lint! {
#[derive(Default)]
pub struct Write {
format_args: FormatArgsStorage,
in_debug_impl: bool,
allow_print_in_tests: bool,
}
impl Write {
pub fn new(allow_print_in_tests: bool) -> Self {
pub fn new(format_args: FormatArgsStorage, allow_print_in_tests: bool) -> Self {
Self {
format_args,
allow_print_in_tests,
..Default::default()
}
@ -307,7 +309,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
_ => return,
}
if let Some(format_args) = find_format_args(cx, expr, macro_call.expn) {
if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) {
// ignore `writeln!(w)` and `write!(v, some_macro!())`
if format_args.span.from_expansion() {
return;
@ -315,15 +317,15 @@ impl<'tcx> LateLintPass<'tcx> for Write {
match diag_name {
sym::print_macro | sym::eprint_macro | sym::write_macro => {
check_newline(cx, &format_args, &macro_call, name);
check_newline(cx, format_args, &macro_call, name);
},
sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
check_empty_string(cx, &format_args, &macro_call, name);
check_empty_string(cx, format_args, &macro_call, name);
},
_ => {},
}
check_literal(cx, &format_args, name);
check_literal(cx, format_args, name);
if !self.in_debug_impl {
for piece in &format_args.template {

View File

@ -193,6 +193,21 @@ pub fn find_binding_init<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Option<
None
}
/// Checks if the given local has an initializer or is from something other than a `let` statement
///
/// e.g. returns true for `x` in `fn f(x: usize) { .. }` and `let x = 1;` but false for `let x;`
pub fn local_is_initialized(cx: &LateContext<'_>, local: HirId) -> bool {
for (_, node) in cx.tcx.hir().parent_iter(local) {
match node {
Node::Pat(..) | Node::PatField(..) => {},
Node::LetStmt(let_stmt) => return let_stmt.init.is_some(),
_ => return true,
}
}
false
}
/// Returns `true` if the given `NodeId` is inside a constant context
///
/// # Example
@ -1499,15 +1514,18 @@ pub fn is_else_clause_in_let_else(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> bool {
}
/// Checks whether the given `Expr` is a range equivalent to a `RangeFull`.
///
/// For the lower bound, this means that:
/// - either there is none
/// - or it is the smallest value that can be represented by the range's integer type
///
/// For the upper bound, this means that:
/// - either there is none
/// - or it is the largest value that can be represented by the range's integer type and is
/// inclusive
/// - or it is a call to some container's `len` method and is exclusive, and the range is passed to
/// a method call on that same container (e.g. `v.drain(..v.len())`)
///
/// If the given `Expr` is not some kind of range, the function returns `false`.
pub fn is_range_full(cx: &LateContext<'_>, expr: &Expr<'_>, container_path: Option<&Path<'_>>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);

View File

@ -5,15 +5,13 @@ use crate::visitors::{for_each_expr, Descend};
use arrayvec::ArrayVec;
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sync::{Lrc, OnceLock};
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
use rustc_lint::LateContext;
use rustc_span::def_id::DefId;
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol};
use std::cell::OnceCell;
use std::ops::ControlFlow;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
sym::assert_eq_macro,
@ -388,50 +386,44 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) ->
}
}
thread_local! {
/// We preserve the [`FormatArgs`] structs from the early pass for use in the late pass to be
/// able to access the many features of a [`LateContext`].
/// Stores AST [`FormatArgs`] nodes for use in late lint passes, as they are in a desugared form in
/// the HIR
#[derive(Default, Clone)]
pub struct FormatArgsStorage(Lrc<OnceLock<FxHashMap<Span, FormatArgs>>>);
impl FormatArgsStorage {
/// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of
/// `expn_id`
///
/// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an
/// assumption that the early pass that populates the map and the later late passes will all be
/// running on the same thread.
#[doc(hidden)]
pub static AST_FORMAT_ARGS: OnceCell<FxHashMap<Span, Rc<FormatArgs>>> = {
static CALLED: AtomicBool = AtomicBool::new(false);
debug_assert!(
!CALLED.swap(true, Ordering::SeqCst),
"incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread",
);
OnceCell::new()
};
}
/// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of
/// `expn_id`
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<Rc<FormatArgs>> {
let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
{
ControlFlow::Break(expr)
/// See also [`find_format_arg_expr`]
pub fn get(&self, cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<&FormatArgs> {
let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
{
ControlFlow::Break(expr)
} else {
ControlFlow::Continue(Descend::Yes)
}
} else {
ControlFlow::Continue(Descend::Yes)
ControlFlow::Continue(Descend::No)
}
} else {
ControlFlow::Continue(Descend::No)
}
})?;
})?;
AST_FORMAT_ARGS.with(|ast_format_args| {
ast_format_args
.get()?
.get(&format_args_expr.span.with_parent(None))
.cloned()
})
debug_assert!(self.0.get().is_some(), "`FormatArgsStorage` not yet populated");
self.0.get()?.get(&format_args_expr.span.with_parent(None))
}
/// Should only be called by `FormatArgsCollector`
pub fn set(&self, format_args: FxHashMap<Span, FormatArgs>) {
self.0
.set(format_args)
.expect("`FormatArgsStorage::set` should only be called once");
}
}
/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if

View File

@ -3,7 +3,7 @@
// of terminologies might not be relevant in the context of Clippy. Note that its behavior might
// differ from the time of `rustc` even if the name stays the same.
use clippy_config::msrvs::Msrv;
use clippy_config::msrvs::{self, Msrv};
use hir::LangItem;
use rustc_attr::StableSince;
use rustc_const_eval::transform::check_consts::ConstCx;
@ -42,7 +42,7 @@ pub fn is_min_const_fn<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, msrv: &Msrv)
for bb in &*body.basic_blocks {
check_terminator(tcx, body, bb.terminator(), msrv)?;
for stmt in &bb.statements {
check_statement(tcx, body, def_id, stmt)?;
check_statement(tcx, body, def_id, stmt, msrv)?;
}
}
Ok(())
@ -102,13 +102,14 @@ fn check_rvalue<'tcx>(
def_id: DefId,
rvalue: &Rvalue<'tcx>,
span: Span,
msrv: &Msrv,
) -> McfResult {
match rvalue {
Rvalue::ThreadLocalRef(_) => Err((span, "cannot access thread local storage in const fn".into())),
Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
check_place(tcx, *place, span, body)
check_place(tcx, *place, span, body, msrv)
},
Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body),
Rvalue::CopyForDeref(place) => check_place(tcx, *place, span, body, msrv),
Rvalue::Repeat(operand, _)
| Rvalue::Use(operand)
| Rvalue::Cast(
@ -122,7 +123,7 @@ fn check_rvalue<'tcx>(
| CastKind::PointerCoercion(PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer),
operand,
_,
) => check_operand(tcx, operand, span, body),
) => check_operand(tcx, operand, span, body, msrv),
Rvalue::Cast(
CastKind::PointerCoercion(
PointerCoercion::UnsafeFnPointer
@ -133,15 +134,13 @@ fn check_rvalue<'tcx>(
_,
) => Err((span, "function pointer casts are not allowed in const fn".into())),
Rvalue::Cast(CastKind::PointerCoercion(PointerCoercion::Unsize), op, cast_ty) => {
let pointee_ty = if let Some(deref_ty) = cast_ty.builtin_deref(true) {
deref_ty
} else {
let Some(pointee_ty) = cast_ty.builtin_deref(true) else {
// We cannot allow this for now.
return Err((span, "unsizing casts are only allowed for references right now".into()));
};
let unsized_ty = tcx.struct_tail_erasing_lifetimes(pointee_ty, tcx.param_env(def_id));
if let ty::Slice(_) | ty::Str = unsized_ty.kind() {
check_operand(tcx, op, span, body)?;
check_operand(tcx, op, span, body, msrv)?;
// Casting/coercing things to slices is fine.
Ok(())
} else {
@ -162,8 +161,8 @@ fn check_rvalue<'tcx>(
)),
// binops are fine on integers
Rvalue::BinaryOp(_, box (lhs, rhs)) | Rvalue::CheckedBinaryOp(_, box (lhs, rhs)) => {
check_operand(tcx, lhs, span, body)?;
check_operand(tcx, rhs, span, body)?;
check_operand(tcx, lhs, span, body, msrv)?;
check_operand(tcx, rhs, span, body, msrv)?;
let ty = lhs.ty(body, tcx);
if ty.is_integral() || ty.is_bool() || ty.is_char() {
Ok(())
@ -179,14 +178,14 @@ fn check_rvalue<'tcx>(
Rvalue::UnaryOp(_, operand) => {
let ty = operand.ty(body, tcx);
if ty.is_integral() || ty.is_bool() {
check_operand(tcx, operand, span, body)
check_operand(tcx, operand, span, body, msrv)
} else {
Err((span, "only int and `bool` operations are stable in const fn".into()))
}
},
Rvalue::Aggregate(_, operands) => {
for operand in operands {
check_operand(tcx, operand, span, body)?;
check_operand(tcx, operand, span, body, msrv)?;
}
Ok(())
},
@ -198,28 +197,29 @@ fn check_statement<'tcx>(
body: &Body<'tcx>,
def_id: DefId,
statement: &Statement<'tcx>,
msrv: &Msrv,
) -> McfResult {
let span = statement.source_info.span;
match &statement.kind {
StatementKind::Assign(box (place, rval)) => {
check_place(tcx, *place, span, body)?;
check_rvalue(tcx, body, def_id, rval, span)
check_place(tcx, *place, span, body, msrv)?;
check_rvalue(tcx, body, def_id, rval, span, msrv)
},
StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body),
StatementKind::FakeRead(box (_, place)) => check_place(tcx, *place, span, body, msrv),
// just an assignment
StatementKind::SetDiscriminant { place, .. } | StatementKind::Deinit(place) => {
check_place(tcx, **place, span, body)
check_place(tcx, **place, span, body, msrv)
},
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body),
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => check_operand(tcx, op, span, body, msrv),
StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(
rustc_middle::mir::CopyNonOverlapping { dst, src, count },
)) => {
check_operand(tcx, dst, span, body)?;
check_operand(tcx, src, span, body)?;
check_operand(tcx, count, span, body)
check_operand(tcx, dst, span, body, msrv)?;
check_operand(tcx, src, span, body, msrv)?;
check_operand(tcx, count, span, body, msrv)
},
// These are all NOPs
StatementKind::StorageLive(_)
@ -233,7 +233,13 @@ fn check_statement<'tcx>(
}
}
fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
fn check_operand<'tcx>(
tcx: TyCtxt<'tcx>,
operand: &Operand<'tcx>,
span: Span,
body: &Body<'tcx>,
msrv: &Msrv,
) -> McfResult {
match operand {
Operand::Move(place) => {
if !place.projection.as_ref().is_empty()
@ -245,9 +251,9 @@ fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, b
));
}
check_place(tcx, *place, span, body)
check_place(tcx, *place, span, body, msrv)
},
Operand::Copy(place) => check_place(tcx, *place, span, body),
Operand::Copy(place) => check_place(tcx, *place, span, body, msrv),
Operand::Constant(c) => match c.check_static_ptr(tcx) {
Some(_) => Err((span, "cannot access `static` items in const fn".into())),
None => Ok(()),
@ -255,23 +261,27 @@ fn check_operand<'tcx>(tcx: TyCtxt<'tcx>, operand: &Operand<'tcx>, span: Span, b
}
}
fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>) -> McfResult {
fn check_place<'tcx>(tcx: TyCtxt<'tcx>, place: Place<'tcx>, span: Span, body: &Body<'tcx>, msrv: &Msrv) -> McfResult {
for (base, elem) in place.as_ref().iter_projections() {
match elem {
ProjectionElem::Field(..) => {
let base_ty = base.ty(body, tcx).ty;
if let Some(def) = base_ty.ty_adt_def() {
// No union field accesses in `const fn`
if def.is_union() {
return Err((span, "accessing union fields is unstable".into()));
}
if base.ty(body, tcx).ty.is_union() && !msrv.meets(msrvs::CONST_FN_UNION) {
return Err((span, "accessing union fields is unstable".into()));
}
},
ProjectionElem::Deref => match base.ty(body, tcx).ty.kind() {
ty::RawPtr(_, hir::Mutability::Mut) => {
return Err((span, "dereferencing raw mut pointer in const fn is unstable".into()));
},
ty::RawPtr(_, hir::Mutability::Not) if !msrv.meets(msrvs::CONST_RAW_PTR_DEREF) => {
return Err((span, "dereferencing raw const pointer in const fn is unstable".into()));
},
_ => (),
},
ProjectionElem::ConstantIndex { .. }
| ProjectionElem::OpaqueCast(..)
| ProjectionElem::Downcast(..)
| ProjectionElem::Subslice { .. }
| ProjectionElem::Deref
| ProjectionElem::Subtype(_)
| ProjectionElem::Index(_) => {},
}
@ -304,7 +314,7 @@ fn check_terminator<'tcx>(
}
Ok(())
},
TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body),
TerminatorKind::SwitchInt { discr, targets: _ } => check_operand(tcx, discr, span, body, msrv),
TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } => {
Err((span, "const fn coroutines are unstable".into()))
},
@ -341,10 +351,10 @@ fn check_terminator<'tcx>(
));
}
check_operand(tcx, func, span, body)?;
check_operand(tcx, func, span, body, msrv)?;
for arg in args {
check_operand(tcx, &arg.node, span, body)?;
check_operand(tcx, &arg.node, span, body, msrv)?;
}
Ok(())
} else {
@ -357,7 +367,7 @@ fn check_terminator<'tcx>(
msg: _,
target: _,
unwind: _,
} => check_operand(tcx, cond, span, body),
} => check_operand(tcx, cond, span, body, msrv),
TerminatorKind::InlineAsm { .. } => Err((span, "cannot use inline assembly in const fn".into())),
}
}

View File

@ -8,7 +8,7 @@ use rustc_hir::{BlockCheckMode, Expr, ExprKind, UnsafeSource};
use rustc_lint::{LateContext, LintContext};
use rustc_session::Session;
use rustc_span::source_map::{original_sp, SourceMap};
use rustc_span::{hygiene, BytePos, SourceFileAndLine, Pos, SourceFile, Span, SpanData, SyntaxContext, DUMMY_SP};
use rustc_span::{hygiene, BytePos, Pos, SourceFile, SourceFileAndLine, Span, SpanData, SyntaxContext, DUMMY_SP};
use std::borrow::Cow;
use std::ops::Range;
@ -250,7 +250,7 @@ pub fn snippet<'a, T: LintContext>(cx: &T, span: Span, default: &'a str) -> Cow<
/// - Applicability level `Unspecified` will never be changed.
/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
/// - If the default value is used and the applicability level is `MachineApplicable`, change it to
/// `HasPlaceholders`
/// `HasPlaceholders`
pub fn snippet_with_applicability<'a, T: LintContext>(
cx: &T,
span: Span,

View File

@ -67,8 +67,7 @@ impl<'a> Sugg<'a> {
/// - Applicability level `Unspecified` will never be changed.
/// - If the span is inside a macro, change the applicability level to `MaybeIncorrect`.
/// - If the default value is used and the applicability level is `MachineApplicable`, change it
/// to
/// `HasPlaceholders`
/// to `HasPlaceholders`
pub fn hir_with_applicability(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,

View File

@ -18,8 +18,8 @@ use rustc_middle::traits::EvaluationResult;
use rustc_middle::ty::layout::ValidityRequirement;
use rustc_middle::ty::{
self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef,
GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, ToPredicate, TraitRef, Ty, TyCtxt,
TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr,
GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, ToPredicate, TraitRef, Ty, TyCtxt, TypeSuperVisitable,
TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr,
};
use rustc_span::symbol::Ident;
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
@ -273,11 +273,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>(
let infcx = tcx.infer_ctxt().build();
let args = args
.into_iter()
.map(|arg| {
arg.into().unwrap_or_else(|| {
infcx.next_ty_var(DUMMY_SP).into()
})
})
.map(|arg| arg.into().unwrap_or_else(|| infcx.next_ty_var(DUMMY_SP).into()))
.collect::<Vec<_>>();
// If an effect arg was not specified, we need to specify it.
@ -795,7 +791,8 @@ fn sig_from_bounds<'tcx>(
inputs = Some(i);
},
ty::ClauseKind::Projection(p)
if Some(p.projection_term.def_id) == lang_items.fn_once_output() && p.projection_term.self_ty() == ty =>
if Some(p.projection_term.def_id) == lang_items.fn_once_output()
&& p.projection_term.self_ty() == ty =>
{
if output.is_some() {
// Multiple different fn trait impls. Is this even allowed?
@ -956,11 +953,7 @@ pub struct AdtVariantInfo {
impl AdtVariantInfo {
/// Returns ADT variants ordered by size
pub fn new<'tcx>(
cx: &LateContext<'tcx>,
adt: AdtDef<'tcx>,
subst: GenericArgsRef<'tcx>
) -> Vec<Self> {
pub fn new<'tcx>(cx: &LateContext<'tcx>, adt: AdtDef<'tcx>, subst: GenericArgsRef<'tcx>) -> Vec<Self> {
let mut variants_size = adt
.variants()
.iter()

View File

@ -16,13 +16,9 @@ pub fn mutated_variables<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) ->
used_mutably: HirIdSet::default(),
skip: false,
};
ExprUseVisitor::for_clippy(
cx,
expr.hir_id.owner.def_id,
&mut delegate,
)
.walk_expr(expr)
.into_ok();
ExprUseVisitor::for_clippy(cx, expr.hir_id.owner.def_id, &mut delegate)
.walk_expr(expr)
.into_ok();
if delegate.skip {
return None;

View File

@ -13,7 +13,7 @@ default-run = "lintcheck"
[dependencies]
anyhow = "1.0.69"
cargo_metadata = "0.15.3"
clap = { version = "4.1.8", features = ["derive", "env"] }
clap = { version = "4.4", features = ["derive", "env"] }
crates_io_api = "0.8.1"
crossbeam-channel = "0.5.6"
flate2 = "1.0"

View File

@ -42,4 +42,32 @@ help: to have lints override the group set `pedantic` to a lower priority
19 | pedantic = { level = "warn", priority = -2 }
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: could not compile `fail` (lib) due to 3 previous errors
error: lint group `rust_2018_idioms` has the same priority (0) as a lint
--> Cargo.toml:23:1
|
23 | rust_2018_idioms = "warn"
| ^^^^^^^^^^^^^^^^ ------ has an implicit priority of 0
24 | bare_trait_objects = "allow"
| ------------------ has the same priority as this lint
|
= note: the order of the lints in the table is ignored by Cargo
help: to have lints override the group set `rust_2018_idioms` to a lower priority
|
23 | rust_2018_idioms = { level = "warn", priority = -1 }
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: lint group `pedantic` has the same priority (0) as a lint
--> Cargo.toml:27:1
|
27 | pedantic = "warn"
| ^^^^^^^^ ------ has an implicit priority of 0
28 | similar_names = "allow"
| ------------- has the same priority as this lint
|
= note: the order of the lints in the table is ignored by Cargo
help: to have lints override the group set `pedantic` to a lower priority
|
27 | pedantic = { level = "warn", priority = -1 }
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: could not compile `fail` (lib) due to 5 previous errors

View File

@ -18,3 +18,11 @@ deprecated = "allow"
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
similar_names = { level = "allow", priority = -1 }
[workspace.lints.rust]
rust_2018_idioms = "warn"
bare_trait_objects = "allow"
[workspace.lints.clippy]
pedantic = "warn"
similar_names = "allow"

View File

@ -19,8 +19,8 @@ LL | const DEREF_MUT_TRAIT: [&str; 4] = ["core", "ops", "deref", "DerefMut"]
error: hardcoded path to a diagnostic item
--> tests/ui-internal/unnecessary_def_path_hardcoded_path.rs:12:43
|
LL | const OPS_MOD: [&str; 5] = ["core", "ops"];
| ^^^^^^^^^^^^^^^
LL | const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: convert all references to use `sym::deref_method`

View File

@ -0,0 +1,260 @@
//! Tests macro_metavars_in_unsafe with default configuration
#![feature(decl_macro, lint_reasons)]
#![warn(clippy::macro_metavars_in_unsafe)]
#![allow(clippy::no_effect)]
#[macro_export]
macro_rules! allow_works {
($v:expr) => {
#[expect(clippy::macro_metavars_in_unsafe)]
unsafe {
$v;
};
};
}
#[macro_export]
macro_rules! simple {
($v:expr) => {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
dbg!($v);
}
};
}
#[macro_export]
#[rustfmt::skip] // for some reason rustfmt rewrites $r#unsafe to r#u$nsafe, bug?
macro_rules! raw_symbol {
($r#mod:expr, $r#unsafe:expr) => {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$r#mod;
}
$r#unsafe;
};
}
#[macro_export]
macro_rules! multilevel_unsafe {
($v:expr) => {
unsafe {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$v;
}
}
};
}
#[macro_export]
macro_rules! in_function {
($v:expr) => {
unsafe {
fn f() {
// function introduces a new body, so don't lint.
$v;
}
}
};
}
#[macro_export]
macro_rules! in_function_with_unsafe {
($v:expr) => {
unsafe {
fn f() {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$v;
}
}
}
};
}
#[macro_export]
macro_rules! const_static {
($c:expr, $s:expr) => {
unsafe {
// const and static introduces new body, don't lint
const _X: i32 = $c;
static _Y: i32 = $s;
}
};
}
#[macro_export]
macro_rules! const_generic_in_struct {
($inside_unsafe:expr, $outside_unsafe:expr) => {
unsafe {
struct Ty<
const L: i32 = 1,
const M: i32 = {
1;
unsafe { $inside_unsafe }
//~^ ERROR: this macro expands metavariables in an unsafe block
},
const N: i32 = { $outside_unsafe },
>;
}
};
}
#[macro_export]
macro_rules! fn_with_const_generic {
($inside_unsafe:expr, $outside_unsafe:expr) => {
unsafe {
fn f<const N: usize>() {
$outside_unsafe;
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$inside_unsafe;
}
}
}
};
}
#[macro_export]
macro_rules! variables {
($inside_unsafe:expr, $outside_unsafe:expr) => {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$inside_unsafe;
let inside_unsafe = 1;
inside_unsafe;
}
$outside_unsafe;
let outside_unsafe = 1;
outside_unsafe;
};
}
#[macro_export]
macro_rules! multiple_matchers {
($inside_unsafe:expr, $outside_unsafe:expr) => {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$inside_unsafe;
}
$outside_unsafe;
};
($($v:expr, $x:expr),+) => {
$(
$v;
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$x;
}
);+
};
}
#[macro_export]
macro_rules! multiple_unsafe_blocks {
($w:expr, $x:expr, $y:expr) => {
$w;
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$x;
}
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$x;
$y;
}
};
}
pub macro macro2_0($v:expr) {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$v;
}
}
// don't lint private macros with the default configuration
macro_rules! private_mac {
($v:expr) => {
unsafe {
$v;
}
};
}
// don't lint exported macros that are doc(hidden) because they also aren't part of the public API
#[macro_export]
#[doc(hidden)]
macro_rules! exported_but_hidden {
($v:expr) => {
unsafe {
$v;
}
};
}
// don't lint if the same metavariable is expanded in an unsafe block and then outside of one:
// unsafe {} is still needed at callsite so not problematic
#[macro_export]
macro_rules! does_require_unsafe {
($v:expr) => {
unsafe {
$v;
}
$v;
};
}
#[macro_export]
macro_rules! unsafe_from_root_ctxt {
($v:expr) => {
// Expands to unsafe { 1 }, but the unsafe block is from the root ctxt and not this macro,
// so no warning.
$v;
};
}
// invoked from another macro, should still generate a warning
#[macro_export]
macro_rules! nested_macro_helper {
($v:expr) => {{
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
$v;
}
}};
}
#[macro_export]
macro_rules! nested_macros {
($v:expr, $v2:expr) => {{
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
nested_macro_helper!($v);
$v;
}
}};
}
fn main() {
allow_works!(1);
simple!(1);
raw_symbol!(1, 1);
multilevel_unsafe!(1);
in_function!(1);
in_function_with_unsafe!(1);
const_static!(1, 1);
const_generic_in_struct!(1, 1);
fn_with_const_generic!(1, 1);
variables!(1, 1);
multiple_matchers!(1, 1);
multiple_matchers!(1, 1, 1, 1);
macro2_0!(1);
private_mac!(1);
exported_but_hidden!(1);
does_require_unsafe!(1);
multiple_unsafe_blocks!(1, 1, 1);
unsafe_from_root_ctxt!(unsafe { 1 });
nested_macros!(1, 1);
}

View File

@ -0,0 +1,187 @@
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:19:9
|
LL | / unsafe {
LL | |
LL | | dbg!($v);
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
= note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]`
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:30:9
|
LL | / unsafe {
LL | |
LL | | $r#mod;
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:42:13
|
LL | / unsafe {
LL | |
LL | | $v;
LL | | }
| |_____________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:67:17
|
LL | / unsafe {
LL | |
LL | | $v;
LL | | }
| |_________________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:95:21
|
LL | unsafe { $inside_unsafe }
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:110:17
|
LL | / unsafe {
LL | |
LL | | $inside_unsafe;
LL | | }
| |_________________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:122:9
|
LL | / unsafe {
LL | |
LL | | $inside_unsafe;
LL | | let inside_unsafe = 1;
LL | | inside_unsafe;
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:137:9
|
LL | / unsafe {
LL | |
LL | | $inside_unsafe;
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:146:13
|
LL | / unsafe {
LL | |
LL | | $x;
LL | | }
| |_____________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:171:5
|
LL | / unsafe {
LL | |
LL | | $v;
LL | | }
| |_____^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:158:9
|
LL | / unsafe {
LL | |
LL | | $x;
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:162:9
|
LL | / unsafe {
LL | |
LL | | $x;
LL | | $y;
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:222:9
|
LL | / unsafe {
LL | |
LL | | $v;
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/default/test.rs:232:9
|
LL | / unsafe {
LL | |
LL | | nested_macro_helper!($v);
LL | | $v;
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
error: aborting due to 14 previous errors

View File

@ -0,0 +1 @@
warn-unsafe-macro-metavars-in-private-macros = true

View File

@ -0,0 +1,15 @@
//! Tests macro_metavars_in_unsafe with private (non-exported) macros
#![warn(clippy::macro_metavars_in_unsafe)]
macro_rules! mac {
($v:expr) => {
unsafe {
//~^ ERROR: this macro expands metavariables in an unsafe block
dbg!($v);
}
};
}
fn main() {
mac!(1);
}

View File

@ -0,0 +1,17 @@
error: this macro expands metavariables in an unsafe block
--> tests/ui-toml/macro_metavars_in_unsafe/private/test.rs:6:9
|
LL | / unsafe {
LL | |
LL | | dbg!($v);
LL | | }
| |_________^
|
= note: this allows the user of the macro to write unsafe code outside of an unsafe block
= help: consider expanding any metavariables outside of this block, e.g. by storing them in a variable
= help: ... or also expand referenced metavariables in a safe context to require an unsafe block at callsite
= note: `-D clippy::macro-metavars-in-unsafe` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::macro_metavars_in_unsafe)]`
error: aborting due to 1 previous error

View File

@ -0,0 +1,2 @@
# Ignore `From`, `TryFrom`, `FromStr` by default
# allow-renamed-params-for = []

View File

@ -0,0 +1,2 @@
# Ignore `From`, `TryFrom`, `FromStr` by default
allow-renamed-params-for = [ "..", "std::ops::Add", "renamed_function_params::MyTrait" ]

View File

@ -0,0 +1,46 @@
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:30:18
|
LL | fn eq(&self, rhs: &Self) -> bool {
| ^^^ help: consider using the default name: `other`
|
= note: `-D clippy::renamed-function-params` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::renamed_function_params)]`
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:34:18
|
LL | fn ne(&self, rhs: &Self) -> bool {
| ^^^ help: consider using the default name: `other`
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:48:19
|
LL | fn foo(&self, i_dont_wanna_use_your_name: u8) {} // only lint in `extend`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider using the default name: `val`
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:55:31
|
LL | fn hash<H: Hasher>(&self, states: &mut H) {
| ^^^^^^ help: consider using the default name: `state`
error: renamed function parameters of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:59:30
|
LL | fn hash_slice<H: Hasher>(date: &[Self], states: &mut H) {
| ^^^^ ^^^^^^
|
help: consider using the default names
|
LL | fn hash_slice<H: Hasher>(data: &[Self], state: &mut H) {
| ~~~~ ~~~~~
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:80:18
|
LL | fn add(self, b: B) -> C {
| ^ help: consider using the default name: `rhs`
error: aborting due to 6 previous errors

View File

@ -0,0 +1,34 @@
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:30:18
|
LL | fn eq(&self, rhs: &Self) -> bool {
| ^^^ help: consider using the default name: `other`
|
= note: `-D clippy::renamed-function-params` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::renamed_function_params)]`
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:34:18
|
LL | fn ne(&self, rhs: &Self) -> bool {
| ^^^ help: consider using the default name: `other`
error: renamed function parameter of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:55:31
|
LL | fn hash<H: Hasher>(&self, states: &mut H) {
| ^^^^^^ help: consider using the default name: `state`
error: renamed function parameters of trait impl
--> tests/ui-toml/renamed_function_params/renamed_function_params.rs:59:30
|
LL | fn hash_slice<H: Hasher>(date: &[Self], states: &mut H) {
| ^^^^ ^^^^^^
|
help: consider using the default names
|
LL | fn hash_slice<H: Hasher>(data: &[Self], state: &mut H) {
| ~~~~ ~~~~~
error: aborting due to 4 previous errors

View File

@ -0,0 +1,110 @@
//@no-rustfix
//@revisions: default extend
//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/renamed_function_params/default
//@[extend] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/renamed_function_params/extend
#![warn(clippy::renamed_function_params)]
#![allow(clippy::partialeq_ne_impl, clippy::to_string_trait_impl)]
#![allow(unused)]
use std::hash::{Hash, Hasher};
struct A;
impl From<A> for String {
fn from(_value: A) -> Self {
String::new()
}
}
impl ToString for A {
fn to_string(&self) -> String {
String::new()
}
}
struct B(u32);
impl std::convert::From<B> for String {
fn from(b: B) -> Self {
b.0.to_string()
}
}
impl PartialEq for B {
fn eq(&self, rhs: &Self) -> bool {
//~^ ERROR: renamed function parameter of trait impl
self.0 == rhs.0
}
fn ne(&self, rhs: &Self) -> bool {
//~^ ERROR: renamed function parameter of trait impl
self.0 != rhs.0
}
}
trait MyTrait {
fn foo(&self, val: u8);
fn bar(a: u8, b: u8);
fn baz(self, _val: u8);
fn quz(&self, _: u8);
}
impl MyTrait for B {
fn foo(&self, i_dont_wanna_use_your_name: u8) {} // only lint in `extend`
fn bar(_a: u8, _: u8) {}
fn baz(self, val: u8) {}
fn quz(&self, val: u8) {}
}
impl Hash for B {
fn hash<H: Hasher>(&self, states: &mut H) {
//~^ ERROR: renamed function parameter of trait impl
self.0.hash(states);
}
fn hash_slice<H: Hasher>(date: &[Self], states: &mut H) {
//~^ ERROR: renamed function parameters of trait impl
for d in date {
d.hash(states);
}
}
}
impl B {
fn totally_irrelevant(&self, right: bool) {}
fn some_fn(&self, other: impl MyTrait) {}
}
#[derive(Copy, Clone)]
enum C {
A,
B(u32),
}
impl std::ops::Add<B> for C {
type Output = C;
fn add(self, b: B) -> C {
// only lint in `extend`
C::B(b.0)
}
}
impl From<A> for C {
fn from(_: A) -> C {
C::A
}
}
trait CustomTraitA {
fn foo(&self, other: u32);
}
trait CustomTraitB {
fn bar(&self, value: u8);
}
macro_rules! impl_trait {
($impl_for:ident, $tr:ty, $fn_name:ident, $t:ty) => {
impl $tr for $impl_for {
fn $fn_name(&self, v: $t) {}
}
};
}
impl_trait!(C, CustomTraitA, foo, u32);
impl_trait!(C, CustomTraitB, bar, u8);
fn main() {}

View File

@ -40,3 +40,9 @@ fn main() {
let _ = HashMap;
let _: usize = 64_usize;
}
mod useless_attribute {
// Regression test for https://github.com/rust-lang/rust-clippy/issues/12753
#[allow(clippy::disallowed_types)]
use std::collections::HashMap;
}

View File

@ -10,6 +10,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
allow-one-hash-in-raw-strings
allow-print-in-tests
allow-private-module-inception
allow-renamed-params-for
allow-unwrap-in-tests
allow-useless-vec-in-tests
allowed-dotfiles
@ -74,6 +75,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect
vec-box-size-threshold
verbose-bit-mask-threshold
warn-on-all-wildcard-imports
warn-unsafe-macro-metavars-in-private-macros
--> $DIR/tests/ui-toml/toml_unknown_key/clippy.toml:2:1
|
LL | foobar = 42
@ -91,6 +93,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
allow-one-hash-in-raw-strings
allow-print-in-tests
allow-private-module-inception
allow-renamed-params-for
allow-unwrap-in-tests
allow-useless-vec-in-tests
allowed-dotfiles
@ -155,6 +158,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect
vec-box-size-threshold
verbose-bit-mask-threshold
warn-on-all-wildcard-imports
warn-unsafe-macro-metavars-in-private-macros
--> $DIR/tests/ui-toml/toml_unknown_key/clippy.toml:4:1
|
LL | barfoo = 53
@ -172,6 +176,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
allow-one-hash-in-raw-strings
allow-print-in-tests
allow-private-module-inception
allow-renamed-params-for
allow-unwrap-in-tests
allow-useless-vec-in-tests
allowed-dotfiles
@ -236,6 +241,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni
vec-box-size-threshold
verbose-bit-mask-threshold
warn-on-all-wildcard-imports
warn-unsafe-macro-metavars-in-private-macros
--> $DIR/tests/ui-toml/toml_unknown_key/clippy.toml:7:1
|
LL | allow_mixed_uninlined_format_args = true

View File

@ -62,6 +62,16 @@ fn clone_method_rhs_complex(mut_thing: &mut HasCloneFrom, ref_thing: &HasCloneFr
mut_thing.clone_from(ref_thing + ref_thing);
}
fn clone_method_macro() {
let mut s = String::from("");
s.clone_from(&format!("{} {}", "hello", "world"));
}
fn clone_function_macro() {
let mut s = String::from("");
Clone::clone_from(&mut s, &format!("{} {}", "hello", "world"));
}
fn assign_to_init_mut_var(b: HasCloneFrom) -> HasCloneFrom {
let mut a = HasCloneFrom;
for _ in 1..10 {
@ -86,6 +96,12 @@ fn assign_to_uninit_mut_var(b: HasCloneFrom) {
a = b.clone();
}
fn late_init_let_tuple() {
let (p, q): (String, String);
p = "ghi".to_string();
q = p.clone();
}
#[derive(Clone)]
pub struct HasDeriveClone;
@ -208,6 +224,16 @@ fn owned_function_val(mut mut_thing: String, ref_str: &str) {
ToOwned::clone_into(ref_str, &mut mut_thing);
}
fn owned_method_macro() {
let mut s = String::from("");
format!("{} {}", "hello", "world").clone_into(&mut s);
}
fn owned_function_macro() {
let mut s = String::from("");
ToOwned::clone_into(&format!("{} {}", "hello", "world"), &mut s);
}
struct FakeToOwned;
impl FakeToOwned {
/// This looks just like `ToOwned::to_owned`

View File

@ -62,6 +62,16 @@ fn clone_method_rhs_complex(mut_thing: &mut HasCloneFrom, ref_thing: &HasCloneFr
*mut_thing = (ref_thing + ref_thing).clone();
}
fn clone_method_macro() {
let mut s = String::from("");
s = format!("{} {}", "hello", "world").clone();
}
fn clone_function_macro() {
let mut s = String::from("");
s = Clone::clone(&format!("{} {}", "hello", "world"));
}
fn assign_to_init_mut_var(b: HasCloneFrom) -> HasCloneFrom {
let mut a = HasCloneFrom;
for _ in 1..10 {
@ -86,6 +96,12 @@ fn assign_to_uninit_mut_var(b: HasCloneFrom) {
a = b.clone();
}
fn late_init_let_tuple() {
let (p, q): (String, String);
p = "ghi".to_string();
q = p.clone();
}
#[derive(Clone)]
pub struct HasDeriveClone;
@ -208,6 +224,16 @@ fn owned_function_val(mut mut_thing: String, ref_str: &str) {
mut_thing = ToOwned::to_owned(ref_str);
}
fn owned_method_macro() {
let mut s = String::from("");
s = format!("{} {}", "hello", "world").to_owned();
}
fn owned_function_macro() {
let mut s = String::from("");
s = ToOwned::to_owned(&format!("{} {}", "hello", "world"));
}
struct FakeToOwned;
impl FakeToOwned {
/// This looks just like `ToOwned::to_owned`

View File

@ -62,64 +62,88 @@ LL | *mut_thing = (ref_thing + ref_thing).clone();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_from()`: `mut_thing.clone_from(ref_thing + ref_thing)`
error: assigning the result of `Clone::clone()` may be inefficient
--> tests/ui/assigning_clones.rs:68:9
--> tests/ui/assigning_clones.rs:67:5
|
LL | s = format!("{} {}", "hello", "world").clone();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_from()`: `s.clone_from(&format!("{} {}", "hello", "world"))`
error: assigning the result of `Clone::clone()` may be inefficient
--> tests/ui/assigning_clones.rs:72:5
|
LL | s = Clone::clone(&format!("{} {}", "hello", "world"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_from()`: `Clone::clone_from(&mut s, &format!("{} {}", "hello", "world"))`
error: assigning the result of `Clone::clone()` may be inefficient
--> tests/ui/assigning_clones.rs:78:9
|
LL | a = b.clone();
| ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)`
error: assigning the result of `Clone::clone()` may be inefficient
--> tests/ui/assigning_clones.rs:133:5
--> tests/ui/assigning_clones.rs:149:5
|
LL | a = b.clone();
| ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)`
error: assigning the result of `Clone::clone()` may be inefficient
--> tests/ui/assigning_clones.rs:140:5
--> tests/ui/assigning_clones.rs:156:5
|
LL | a = b.clone();
| ^^^^^^^^^^^^^ help: use `clone_from()`: `a.clone_from(&b)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:141:5
--> tests/ui/assigning_clones.rs:157:5
|
LL | a = c.to_owned();
| ^^^^^^^^^^^^^^^^ help: use `clone_into()`: `c.clone_into(&mut a)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:171:5
--> tests/ui/assigning_clones.rs:187:5
|
LL | *mut_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(mut_string)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:175:5
--> tests/ui/assigning_clones.rs:191:5
|
LL | mut_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut mut_string)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:196:5
--> tests/ui/assigning_clones.rs:212:5
|
LL | **mut_box_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:200:5
--> tests/ui/assigning_clones.rs:216:5
|
LL | **mut_box_string = ref_str.to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ref_str.clone_into(&mut (*mut_box_string))`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:204:5
--> tests/ui/assigning_clones.rs:220:5
|
LL | *mut_thing = ToOwned::to_owned(ref_str);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, mut_thing)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:208:5
--> tests/ui/assigning_clones.rs:224:5
|
LL | mut_thing = ToOwned::to_owned(ref_str);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(ref_str, &mut mut_thing)`
error: aborting due to 20 previous errors
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:229:5
|
LL | s = format!("{} {}", "hello", "world").to_owned();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `format!("{} {}", "hello", "world").clone_into(&mut s)`
error: assigning the result of `ToOwned::to_owned()` may be inefficient
--> tests/ui/assigning_clones.rs:234:5
|
LL | s = ToOwned::to_owned(&format!("{} {}", "hello", "world"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `clone_into()`: `ToOwned::clone_into(&format!("{} {}", "hello", "world"), &mut s)`
error: aborting due to 24 previous errors

View File

@ -1,7 +1,6 @@
#![allow(clippy::non_canonical_clone_impl, clippy::non_canonical_partial_ord_impl, dead_code)]
#![warn(clippy::expl_impl_clone_on_copy)]
#[derive(Copy)]
struct Qux;

View File

@ -1,5 +1,5 @@
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:8:1
--> tests/ui/derive.rs:7:1
|
LL | / impl Clone for Qux {
LL | |
@ -10,7 +10,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:8:1
--> tests/ui/derive.rs:7:1
|
LL | / impl Clone for Qux {
LL | |
@ -23,7 +23,7 @@ LL | | }
= help: to override `-D warnings` add `#[allow(clippy::expl_impl_clone_on_copy)]`
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:33:1
--> tests/ui/derive.rs:32:1
|
LL | / impl<'a> Clone for Lt<'a> {
LL | |
@ -34,7 +34,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:33:1
--> tests/ui/derive.rs:32:1
|
LL | / impl<'a> Clone for Lt<'a> {
LL | |
@ -45,7 +45,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:45:1
--> tests/ui/derive.rs:44:1
|
LL | / impl Clone for BigArray {
LL | |
@ -56,7 +56,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:45:1
--> tests/ui/derive.rs:44:1
|
LL | / impl Clone for BigArray {
LL | |
@ -67,7 +67,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:57:1
--> tests/ui/derive.rs:56:1
|
LL | / impl Clone for FnPtr {
LL | |
@ -78,7 +78,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:57:1
--> tests/ui/derive.rs:56:1
|
LL | / impl Clone for FnPtr {
LL | |
@ -89,7 +89,7 @@ LL | | }
| |_^
error: you are implementing `Clone` explicitly on a `Copy` type
--> tests/ui/derive.rs:78:1
--> tests/ui/derive.rs:77:1
|
LL | / impl<T: Clone> Clone for Generic2<T> {
LL | |
@ -100,7 +100,7 @@ LL | | }
| |_^
|
note: consider deriving `Clone` or removing `Copy`
--> tests/ui/derive.rs:78:1
--> tests/ui/derive.rs:77:1
|
LL | / impl<T: Clone> Clone for Generic2<T> {
LL | |

View File

@ -0,0 +1,47 @@
#![warn(clippy::doc_lazy_continuation)]
/// > blockquote with
/// > lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn first() {}
/// > blockquote with no
/// > lazy continuation
fn first_nowarn() {}
/// > blockquote with no
///
/// lazy continuation
fn two_nowarn() {}
/// > nest here
/// >
/// > > nest here
/// > > lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn two() {}
/// > nest here
/// >
/// > > nest here
/// > > lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn three() {}
/// > * > nest here
/// > > lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn four() {}
/// > * > nest here
/// > > lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn four_point_1() {}
/// * > nest here lazy continuation
fn five() {}
/// 1. > nest here
/// > lazy continuation (this results in strange indentation, but still works)
//~^ ERROR: doc quote missing `>` marker
fn six() {}

View File

@ -0,0 +1,47 @@
#![warn(clippy::doc_lazy_continuation)]
/// > blockquote with
/// lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn first() {}
/// > blockquote with no
/// > lazy continuation
fn first_nowarn() {}
/// > blockquote with no
///
/// lazy continuation
fn two_nowarn() {}
/// > nest here
/// >
/// > > nest here
/// > lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn two() {}
/// > nest here
/// >
/// > > nest here
/// lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn three() {}
/// > * > nest here
/// lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn four() {}
/// > * > nest here
/// lazy continuation
//~^ ERROR: doc quote missing `>` marker
fn four_point_1() {}
/// * > nest here lazy continuation
fn five() {}
/// 1. > nest here
/// lazy continuation (this results in strange indentation, but still works)
//~^ ERROR: doc quote missing `>` marker
fn six() {}

View File

@ -0,0 +1,76 @@
error: doc quote missing `>` marker
--> tests/ui/doc/doc_lazy_blockquote.rs:4:5
|
LL | /// lazy continuation
| ^
|
= help: if this not intended to be a quote at all, escape it with `\>`
= note: `-D clippy::doc-lazy-continuation` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::doc_lazy_continuation)]`
help: add markers to start of line
|
LL | /// > lazy continuation
| +
error: doc quote missing `>` marker
--> tests/ui/doc/doc_lazy_blockquote.rs:20:5
|
LL | /// > lazy continuation
| ^^
|
= help: if this not intended to be a quote at all, escape it with `\>`
help: add markers to start of line
|
LL | /// > > lazy continuation
| +
error: doc quote missing `>` marker
--> tests/ui/doc/doc_lazy_blockquote.rs:27:5
|
LL | /// lazy continuation
| ^
|
= help: if this not intended to be a quote at all, escape it with `\>`
help: add markers to start of line
|
LL | /// > > lazy continuation
| +++
error: doc quote missing `>` marker
--> tests/ui/doc/doc_lazy_blockquote.rs:32:5
|
LL | /// lazy continuation
| ^
|
= help: if this not intended to be a quote at all, escape it with `\>`
help: add markers to start of line
|
LL | /// > > lazy continuation
| +++++++
error: doc quote missing `>` marker
--> tests/ui/doc/doc_lazy_blockquote.rs:37:5
|
LL | /// lazy continuation
| ^
|
= help: if this not intended to be a quote at all, escape it with `\>`
help: add markers to start of line
|
LL | /// > > lazy continuation
| +++++
error: doc quote missing `>` marker
--> tests/ui/doc/doc_lazy_blockquote.rs:45:5
|
LL | /// lazy continuation (this results in strange indentation, but still works)
| ^
|
= help: if this not intended to be a quote at all, escape it with `\>`
help: add markers to start of line
|
LL | /// > lazy continuation (this results in strange indentation, but still works)
| +
error: aborting due to 6 previous errors

View File

@ -0,0 +1,42 @@
#![warn(clippy::doc_lazy_continuation)]
/// 1. nest here
/// lazy continuation
//~^ ERROR: doc list item missing indentation
fn one() {}
/// 1. first line
/// lazy list continuations don't make warnings with this lint
//~^ ERROR: doc list item missing indentation
/// because they don't have the
//~^ ERROR: doc list item missing indentation
fn two() {}
/// - nest here
/// lazy continuation
//~^ ERROR: doc list item missing indentation
fn three() {}
/// - first line
/// lazy list continuations don't make warnings with this lint
//~^ ERROR: doc list item missing indentation
/// because they don't have the
//~^ ERROR: doc list item missing indentation
fn four() {}
/// - nest here
/// lazy continuation
//~^ ERROR: doc list item missing indentation
fn five() {}
/// - - first line
/// this will warn on the lazy continuation
//~^ ERROR: doc list item missing indentation
/// and so should this
//~^ ERROR: doc list item missing indentation
fn six() {}
/// - - first line
///
/// this is not a lazy continuation
fn seven() {}

View File

@ -0,0 +1,42 @@
#![warn(clippy::doc_lazy_continuation)]
/// 1. nest here
/// lazy continuation
//~^ ERROR: doc list item missing indentation
fn one() {}
/// 1. first line
/// lazy list continuations don't make warnings with this lint
//~^ ERROR: doc list item missing indentation
/// because they don't have the
//~^ ERROR: doc list item missing indentation
fn two() {}
/// - nest here
/// lazy continuation
//~^ ERROR: doc list item missing indentation
fn three() {}
/// - first line
/// lazy list continuations don't make warnings with this lint
//~^ ERROR: doc list item missing indentation
/// because they don't have the
//~^ ERROR: doc list item missing indentation
fn four() {}
/// - nest here
/// lazy continuation
//~^ ERROR: doc list item missing indentation
fn five() {}
/// - - first line
/// this will warn on the lazy continuation
//~^ ERROR: doc list item missing indentation
/// and so should this
//~^ ERROR: doc list item missing indentation
fn six() {}
/// - - first line
///
/// this is not a lazy continuation
fn seven() {}

View File

@ -0,0 +1,112 @@
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:4:5
|
LL | /// lazy continuation
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
= note: `-D clippy::doc-lazy-continuation` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::doc_lazy_continuation)]`
help: indent this line
|
LL | /// lazy continuation
| +++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:9:5
|
LL | /// lazy list continuations don't make warnings with this lint
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// lazy list continuations don't make warnings with this lint
| +++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:11:5
|
LL | /// because they don't have the
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// because they don't have the
| +++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:16:5
|
LL | /// lazy continuation
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// lazy continuation
| ++++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:21:5
|
LL | /// lazy list continuations don't make warnings with this lint
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// lazy list continuations don't make warnings with this lint
| ++++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:23:5
|
LL | /// because they don't have the
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// because they don't have the
| ++++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:28:5
|
LL | /// lazy continuation
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// lazy continuation
| ++++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:33:5
|
LL | /// this will warn on the lazy continuation
| ^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// this will warn on the lazy continuation
| ++++++
error: doc list item missing indentation
--> tests/ui/doc/doc_lazy_list.rs:35:5
|
LL | /// and so should this
| ^^^^
|
= help: if this is supposed to be its own paragraph, add a blank line
help: indent this line
|
LL | /// and so should this
| ++
error: aborting due to 9 previous errors

View File

@ -1,5 +1,5 @@
//@aux-build:proc_macro_attr.rs
#![feature(rustc_attrs)]
#![warn(clippy::duplicated_attributes)]
#![cfg(any(unix, windows))]
#![allow(dead_code)]
@ -20,6 +20,10 @@ fn foo() {}
#[cfg(unix)] // cfgs are not handled
fn bar() {}
// No warning:
#[rustc_on_unimplemented(on(_Self = "&str", label = "`a"), on(_Self = "alloc::string::String", label = "a"))]
trait Abc {}
#[proc_macro_attr::duplicated_attr()] // Should not warn!
fn babar() {}

View File

@ -1,3 +1,4 @@
#![feature(const_int_from_str)]
#![warn(clippy::from_str_radix_10)]
mod some_mod {
@ -59,3 +60,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn issue_12732() {
const A: Result<u32, std::num::ParseIntError> = u32::from_str_radix("123", 10);
const B: () = {
let _ = u32::from_str_radix("123", 10);
};
const fn foo() {
let _ = u32::from_str_radix("123", 10);
}
}

View File

@ -1,3 +1,4 @@
#![feature(const_int_from_str)]
#![warn(clippy::from_str_radix_10)]
mod some_mod {
@ -59,3 +60,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn issue_12732() {
const A: Result<u32, std::num::ParseIntError> = u32::from_str_radix("123", 10);
const B: () = {
let _ = u32::from_str_radix("123", 10);
};
const fn foo() {
let _ = u32::from_str_radix("123", 10);
}
}

View File

@ -1,5 +1,5 @@
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:28:5
--> tests/ui/from_str_radix_10.rs:29:5
|
LL | u32::from_str_radix("30", 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"30".parse::<u32>()`
@ -8,43 +8,43 @@ LL | u32::from_str_radix("30", 10)?;
= help: to override `-D warnings` add `#[allow(clippy::from_str_radix_10)]`
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:31:5
--> tests/ui/from_str_radix_10.rs:32:5
|
LL | i64::from_str_radix("24", 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"24".parse::<i64>()`
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:33:5
--> tests/ui/from_str_radix_10.rs:34:5
|
LL | isize::from_str_radix("100", 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"100".parse::<isize>()`
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:35:5
--> tests/ui/from_str_radix_10.rs:36:5
|
LL | u8::from_str_radix("7", 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `"7".parse::<u8>()`
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:37:5
--> tests/ui/from_str_radix_10.rs:38:5
|
LL | u16::from_str_radix(&("10".to_owned() + "5"), 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `("10".to_owned() + "5").parse::<u16>()`
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:39:5
--> tests/ui/from_str_radix_10.rs:40:5
|
LL | i128::from_str_radix(Test + Test, 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(Test + Test).parse::<i128>()`
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:43:5
--> tests/ui/from_str_radix_10.rs:44:5
|
LL | i32::from_str_radix(string, 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `string.parse::<i32>()`
error: this call to `from_str_radix` can be replaced with a call to `str::parse`
--> tests/ui/from_str_radix_10.rs:47:5
--> tests/ui/from_str_radix_10.rs:48:5
|
LL | i32::from_str_radix(&stringier, 10)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `stringier.parse::<i32>()`

View File

@ -2,7 +2,7 @@ error: this match arm has an identical body to the `_` wildcard arm
--> tests/ui/match_same_arms.rs:12:9
|
LL | Abc::A => 0,
| ^^^^^^^^^^^ help: try removing the arm
| ^^^^^^^^^^^^^ help: try removing the arm
|
= help: or try changing either arm body
note: `_` wildcard arm here
@ -17,106 +17,114 @@ error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms.rs:18:9
|
LL | (1, .., 3) => 42,
| ----------^^^^^^
| |
| help: try merging the arm patterns: `(1, .., 3) | (.., 3)`
| ^^^^^^^^^^^^^^^^
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms.rs:19:9
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | (1, .., 3) | (.., 3) => 42,
| ~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - (.., 3) => 42,
|
LL | (.., 3) => 42,
| ^^^^^^^^^^^^^
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms.rs:25:9
|
LL | 51 => 1,
| --^^^^^
| |
| help: try merging the arm patterns: `51 | 42`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms.rs:24:9
|
LL | 42 => 1,
| ^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | 51 | 42 => 1,
| ~~~~~~~
help: and remove this obsolete arm
|
LL - 42 => 1,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms.rs:26:9
|
LL | 41 => 2,
| --^^^^^
| |
| help: try merging the arm patterns: `41 | 52`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms.rs:27:9
|
LL | 52 => 2,
| ^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | 41 | 52 => 2,
| ~~~~~~~
help: and remove this obsolete arm
|
LL - 52 => 2,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms.rs:33:9
|
LL | 2 => 2,
| -^^^^^
| |
| help: try merging the arm patterns: `2 | 1`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms.rs:32:9
|
LL | 1 => 2,
| ^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | 2 | 1 => 2,
| ~~~~~
help: and remove this obsolete arm
|
LL - 1 => 2,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms.rs:35:9
|
LL | 3 => 2,
| -^^^^^
| |
| help: try merging the arm patterns: `3 | 1`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms.rs:32:9
|
LL | 1 => 2,
| ^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | 3 | 1 => 2,
| ~~~~~
help: and remove this obsolete arm
|
LL - 1 => 2,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms.rs:33:9
|
LL | 2 => 2,
| -^^^^^
| |
| help: try merging the arm patterns: `2 | 3`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms.rs:35:9
|
LL | 3 => 2,
| ^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | 2 | 3 => 2,
| ~~~~~
help: and remove this obsolete arm
|
LL - 3 => 2,
LL +
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms.rs:52:17
|
LL | CommandInfo::External { name, .. } => name.to_string(),
| ----------------------------------^^^^^^^^^^^^^^^^^^^^
| |
| help: try merging the arm patterns: `CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. }`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms.rs:51:17
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | CommandInfo::External { name, .. } | CommandInfo::BuiltIn { name, .. } => name.to_string(),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - CommandInfo::BuiltIn { name, .. } => name.to_string(),
|
LL | CommandInfo::BuiltIn { name, .. } => name.to_string(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 8 previous errors

View File

@ -0,0 +1,241 @@
#![warn(clippy::match_same_arms)]
#![allow(
clippy::disallowed_names,
clippy::diverging_sub_expression,
clippy::uninlined_format_args,
clippy::match_single_binding,
clippy::match_like_matches_macro
)]
fn bar<T>(_: T) {}
fn foo() -> bool {
unimplemented!()
}
fn match_same_arms() {
let _ = match 42 {
_ => {
foo();
let mut a = 42 + [23].len() as i32;
if true {
a += 7;
}
a = -31 - a;
a
},
};
//~^^^^^^^^^^^^^^^^^^^ ERROR: this match arm has an identical body to the `_` wildcard arm
let _ = match 42 {
51 | 42 => foo(), //~ ERROR: this match arm has an identical body to another arm
_ => true,
};
let _ = match Some(42) {
None | Some(_) => 24, //~ ERROR: this match arm has an identical body to another arm
};
let _ = match Some(42) {
Some(foo) => 24,
None => 24,
};
let _ = match Some(42) {
Some(42) => 24,
Some(a) => 24, // bindings are different
None => 0,
};
let _ = match Some(42) {
Some(a) if a > 0 => 24,
Some(a) => 24, // one arm has a guard
None => 0,
};
match (Some(42), Some(42)) {
(None, Some(a)) | (Some(a), None) => bar(a), //~ ERROR: this match arm has an identical body to another arm
_ => (),
}
// No warning because guards are different
let _ = match Some(42) {
Some(a) if a == 42 => a,
Some(a) if a == 24 => a,
Some(_) => 24,
None => 0,
};
let _ = match (Some(42), Some(42)) {
(None, Some(a)) | (Some(a), None) if a == 42 => a, //~ ERROR: this match arm has an identical body to another arm
_ => 0,
};
match (Some(42), Some(42)) {
(Some(a), ..) | (.., Some(a)) => bar(a), //~ ERROR: this match arm has an identical body to another arm
_ => (),
}
let _ = match Some(()) {
Some(()) => 0.0,
None => -0.0,
};
match (Some(42), Some("")) {
(Some(a), None) => bar(a),
(None, Some(a)) => bar(a), // bindings have different types
_ => (),
}
let x: Result<i32, &str> = Ok(3);
// No warning because of the guard.
match x {
Ok(x) if x * x == 64 => println!("ok"),
Ok(_) => println!("ok"),
Err(_) => println!("err"),
}
// This used to be a false positive; see issue #1996.
match x {
Ok(3) => println!("ok"),
Ok(x) if x * x == 64 => println!("ok 64"),
Ok(_) => println!("ok"),
Err(_) => println!("err"),
}
match (x, Some(1i32)) {
(Ok(x), Some(_)) | (Ok(_), Some(x)) => println!("ok {}", x), //~ ERROR: this match arm has an identical body to another arm
_ => println!("err"),
}
// No warning; different types for `x`.
match (x, Some(1.0f64)) {
(Ok(x), Some(_)) => println!("ok {}", x),
(Ok(_), Some(x)) => println!("ok {}", x),
_ => println!("err"),
}
// False negative #2251.
match x {
Ok(_tmp) => println!("ok"),
Ok(_) | Ok(3) => println!("ok"), //~ ERROR: this match arm has an identical body to another arm
Err(_) => {
unreachable!();
},
}
// False positive #1390
macro_rules! empty {
($e:expr) => {};
}
match 0 {
0 => {
empty!(0);
},
1 => {
empty!(1);
},
x => {
empty!(x);
},
};
// still lint if the tokens are the same
match 0 {
1 | 0 => {
empty!(0);
},
x => {
empty!(x);
},
}
//~^^^^^^^ ERROR: this match arm has an identical body to another arm
match_expr_like_matches_macro_priority();
}
fn match_expr_like_matches_macro_priority() {
enum E {
A,
B,
C,
}
let x = E::A;
let _ans = match x {
E::A => false,
E::B => false,
_ => true,
};
}
fn main() {
let _ = match Some(0) {
Some(0) => 0,
Some(1) => 1,
#[cfg(feature = "foo")]
Some(2) => 2,
_ => 1,
};
enum Foo {
X(u32),
Y(u32),
Z(u32),
}
// Don't lint. `Foo::X(0)` and `Foo::Z(_)` overlap with the arm in between.
let _ = match Foo::X(0) {
Foo::X(0) => 1,
Foo::X(_) | Foo::Y(_) | Foo::Z(0) => 2,
Foo::Z(_) => 1,
_ => 0,
};
// Suggest moving `Foo::Z(_)` up.
let _ = match Foo::X(0) {
Foo::X(0) | Foo::Z(_) => 1, //~ ERROR: this match arm has an identical body to another arm
Foo::X(_) | Foo::Y(_) => 2,
_ => 0,
};
// Suggest moving `Foo::X(0)` down.
let _ = match Foo::X(0) {
Foo::Y(_) | Foo::Z(0) => 2,
Foo::Z(_) | Foo::X(0) => 1, //~ ERROR: this match arm has an identical body to another arm
_ => 0,
};
// Don't lint.
let _ = match 0 {
-2 => 1,
-5..=50 => 2,
-150..=88 => 1,
_ => 3,
};
struct Bar {
x: u32,
y: u32,
z: u32,
}
// Lint.
let _ = match None {
Some(Bar { y: 10, z: 0, .. }) => 2,
None => 50,
Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. }) => 1, //~ ERROR: this match arm has an identical body to another arm
_ => 200,
};
let _ = match 0 {
0 => todo!(),
1 => todo!(),
2 => core::convert::identity::<u32>(todo!()),
3 => core::convert::identity::<u32>(todo!()),
_ => 5,
};
let _ = match 0 {
1 | 0 => cfg!(not_enable),
_ => false,
};
}

View File

@ -2,9 +2,10 @@
#![allow(
clippy::disallowed_names,
clippy::diverging_sub_expression,
clippy::uninlined_format_args
clippy::uninlined_format_args,
clippy::match_single_binding,
clippy::match_like_matches_macro
)]
//@no-rustfix
fn bar<T>(_: T) {}
fn foo() -> bool {
unimplemented!()

View File

@ -1,18 +1,18 @@
error: this match arm has an identical body to the `_` wildcard arm
--> tests/ui/match_same_arms2.rs:15:9
--> tests/ui/match_same_arms2.rs:16:9
|
LL | / 42 => {
LL | | foo();
LL | | let mut a = 42 + [23].len() as i32;
LL | | if true {
... |
LL | | a
LL | | },
| |_________^ help: try removing the arm
LL | | _ => {
| |________^ help: try removing the arm
|
= help: or try changing either arm body
note: `_` wildcard arm here
--> tests/ui/match_same_arms2.rs:24:9
--> tests/ui/match_same_arms2.rs:25:9
|
LL | / _ => {
LL | | foo();
@ -26,203 +26,200 @@ LL | | },
= help: to override `-D warnings` add `#[allow(clippy::match_same_arms)]`
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:38:9
--> tests/ui/match_same_arms2.rs:39:9
|
LL | 51 => foo(),
| --^^^^^^^^^
| |
| help: try merging the arm patterns: `51 | 42`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:37:9
|
LL | 42 => foo(),
| ^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | 51 | 42 => foo(),
| ~~~~~~~
help: and remove this obsolete arm
|
LL - 42 => foo(),
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:44:9
--> tests/ui/match_same_arms2.rs:45:9
|
LL | None => 24,
| ----^^^^^^
| |
| help: try merging the arm patterns: `None | Some(_)`
| ^^^^^^^^^^
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:43:9
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | None | Some(_) => 24,
| ~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - Some(_) => 24,
|
LL | Some(_) => 24,
| ^^^^^^^^^^^^^
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:66:9
--> tests/ui/match_same_arms2.rs:67:9
|
LL | (None, Some(a)) => bar(a),
| ---------------^^^^^^^^^^
| |
| help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:65:9
|
LL | (Some(a), None) => bar(a),
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | (None, Some(a)) | (Some(a), None) => bar(a),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - (Some(a), None) => bar(a),
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:80:9
--> tests/ui/match_same_arms2.rs:81:9
|
LL | (None, Some(a)) if a == 42 => a,
| ---------------^^^^^^^^^^^^^^^^
| |
| help: try merging the arm patterns: `(None, Some(a)) | (Some(a), None)`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:79:9
|
LL | (Some(a), None) if a == 42 => a,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | (None, Some(a)) | (Some(a), None) if a == 42 => a,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - (Some(a), None) if a == 42 => a,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:85:9
|
LL | (Some(a), ..) => bar(a),
| -------------^^^^^^^^^^
| |
| help: try merging the arm patterns: `(Some(a), ..) | (.., Some(a))`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:86:9
|
LL | (.., Some(a)) => bar(a),
LL | (Some(a), ..) => bar(a),
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | (Some(a), ..) | (.., Some(a)) => bar(a),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - (.., Some(a)) => bar(a),
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:119:9
|
LL | (Ok(x), Some(_)) => println!("ok {}", x),
| ----------------^^^^^^^^^^^^^^^^^^^^^^^^
| |
| help: try merging the arm patterns: `(Ok(x), Some(_)) | (Ok(_), Some(x))`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:120:9
|
LL | (Ok(_), Some(x)) => println!("ok {}", x),
LL | (Ok(x), Some(_)) => println!("ok {}", x),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | (Ok(x), Some(_)) | (Ok(_), Some(x)) => println!("ok {}", x),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - (Ok(_), Some(x)) => println!("ok {}", x),
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:135:9
--> tests/ui/match_same_arms2.rs:136:9
|
LL | Ok(_) => println!("ok"),
| -----^^^^^^^^^^^^^^^^^^
| |
| help: try merging the arm patterns: `Ok(_) | Ok(3)`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:134:9
|
LL | Ok(3) => println!("ok"),
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | Ok(_) | Ok(3) => println!("ok"),
| ~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - Ok(3) => println!("ok"),
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:162:9
--> tests/ui/match_same_arms2.rs:163:9
|
LL | 1 => {
| ^ help: try merging the arm patterns: `1 | 0`
| _________|
| |
LL | / 1 => {
LL | | empty!(0);
LL | | },
| |_________^
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:159:9
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | / 0 => {
LL | | empty!(0);
LL | | },
| |_________^
error: match expression looks like `matches!` macro
--> tests/ui/match_same_arms2.rs:181:16
LL | 1 | 0 => {
| ~~~~~
help: and remove this obsolete arm
|
LL | let _ans = match x {
| ________________^
LL | | E::A => false,
LL | | E::B => false,
LL | | _ => true,
LL | | };
| |_____^ help: try: `!matches!(x, E::A | E::B)`
LL - 0 => {
LL - empty!(0);
LL - },
|
= note: `-D clippy::match-like-matches-macro` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::match_like_matches_macro)]`
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:213:9
|
LL | Foo::X(0) => 1,
| ---------^^^^^
| |
| help: try merging the arm patterns: `Foo::X(0) | Foo::Z(_)`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:215:9
|
LL | Foo::Z(_) => 1,
| ^^^^^^^^^^^^^^
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:223:9
|
LL | Foo::Z(_) => 1,
| ---------^^^^^
| |
| help: try merging the arm patterns: `Foo::Z(_) | Foo::X(0)`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:221:9
--> tests/ui/match_same_arms2.rs:214:9
|
LL | Foo::X(0) => 1,
| ^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | Foo::X(0) | Foo::Z(_) => 1,
| ~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - Foo::Z(_) => 1,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:246:9
--> tests/ui/match_same_arms2.rs:224:9
|
LL | Foo::Z(_) => 1,
| ^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | Foo::Z(_) | Foo::X(0) => 1,
| ~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - Foo::X(0) => 1,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:247:9
|
LL | Some(Bar { y: 0, x: 5, .. }) => 1,
| ----------------------------^^^^^
| |
| help: try merging the arm patterns: `Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. })`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:243:9
|
LL | Some(Bar { x: 0, y: 5, .. }) => 1,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | Some(Bar { y: 0, x: 5, .. }) | Some(Bar { x: 0, y: 5, .. }) => 1,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: and remove this obsolete arm
|
LL - Some(Bar { x: 0, y: 5, .. }) => 1,
|
error: this match arm has an identical body to another arm
--> tests/ui/match_same_arms2.rs:260:9
--> tests/ui/match_same_arms2.rs:261:9
|
LL | 1 => cfg!(not_enable),
| -^^^^^^^^^^^^^^^^^^^^
| |
| help: try merging the arm patterns: `1 | 0`
|
= help: or try changing either arm body
note: other arm here
--> tests/ui/match_same_arms2.rs:259:9
|
LL | 0 => cfg!(not_enable),
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: try changing either arm body
help: or try merging the arm patterns
|
LL | 1 | 0 => cfg!(not_enable),
| ~~~~~
help: and remove this obsolete arm
|
LL - 0 => cfg!(not_enable),
|
error: aborting due to 14 previous errors
error: aborting due to 13 previous errors

View File

@ -0,0 +1,61 @@
#![feature(non_exhaustive_omitted_patterns_lint)]
#![warn(clippy::match_same_arms)]
#![no_main]
use std::sync::atomic::Ordering; // #[non_exhaustive] enum
fn repeat() -> ! {
panic!()
}
pub fn f(x: Ordering) {
#[deny(non_exhaustive_omitted_patterns)]
match x {
Ordering::Relaxed => println!("relaxed"),
Ordering::Release => println!("release"),
Ordering::Acquire => println!("acquire"),
Ordering::AcqRel | Ordering::SeqCst => repeat(),
_ => repeat(),
}
}
mod f {
#![deny(non_exhaustive_omitted_patterns)]
use super::*;
pub fn f(x: Ordering) {
match x {
Ordering::Relaxed => println!("relaxed"),
Ordering::Release => println!("release"),
Ordering::Acquire => println!("acquire"),
Ordering::AcqRel | Ordering::SeqCst => repeat(),
_ => repeat(),
}
}
}
// Below should still lint
pub fn g(x: Ordering) {
match x {
Ordering::Relaxed => println!("relaxed"),
Ordering::Release => println!("release"),
Ordering::Acquire => println!("acquire"),
//~^ ERROR: this match arm has an identical body to the `_` wildcard arm
_ => repeat(),
}
}
mod g {
use super::*;
pub fn g(x: Ordering) {
match x {
Ordering::Relaxed => println!("relaxed"),
Ordering::Release => println!("release"),
Ordering::Acquire => println!("acquire"),
//~^ ERROR: this match arm has an identical body to the `_` wildcard arm
_ => repeat(),
}
}
}

View File

@ -1,7 +1,6 @@
#![feature(non_exhaustive_omitted_patterns_lint)]
#![warn(clippy::match_same_arms)]
#![no_main]
//@no-rustfix
use std::sync::atomic::Ordering; // #[non_exhaustive] enum
fn repeat() -> ! {

View File

@ -1,12 +1,13 @@
error: this match arm has an identical body to the `_` wildcard arm
--> tests/ui/match_same_arms_non_exhaustive.rs:45:9
--> tests/ui/match_same_arms_non_exhaustive.rs:44:9
|
LL | Ordering::AcqRel | Ordering::SeqCst => repeat(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try removing the arm
LL | / Ordering::AcqRel | Ordering::SeqCst => repeat(),
LL | |
| |________^ help: try removing the arm
|
= help: or try changing either arm body
note: `_` wildcard arm here
--> tests/ui/match_same_arms_non_exhaustive.rs:47:9
--> tests/ui/match_same_arms_non_exhaustive.rs:46:9
|
LL | _ => repeat(),
| ^^^^^^^^^^^^^
@ -14,14 +15,15 @@ LL | _ => repeat(),
= help: to override `-D warnings` add `#[allow(clippy::match_same_arms)]`
error: this match arm has an identical body to the `_` wildcard arm
--> tests/ui/match_same_arms_non_exhaustive.rs:59:13
--> tests/ui/match_same_arms_non_exhaustive.rs:58:13
|
LL | Ordering::AcqRel | Ordering::SeqCst => repeat(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try removing the arm
LL | / Ordering::AcqRel | Ordering::SeqCst => repeat(),
LL | |
| |____________^ help: try removing the arm
|
= help: or try changing either arm body
note: `_` wildcard arm here
--> tests/ui/match_same_arms_non_exhaustive.rs:61:13
--> tests/ui/match_same_arms_non_exhaustive.rs:60:13
|
LL | _ => repeat(),
| ^^^^^^^^^^^^^

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