Auto merge of #151501 - flip1995:clippy-subtree-update, r=Manishearth

Clippy subtree update

r? Manishearth 

`Cargo.lock` update due to Clippy version bump.
This commit is contained in:
bors 2026-01-23 14:57:31 +00:00
commit d222ddc4d9
150 changed files with 6339 additions and 2115 deletions

View file

@ -630,7 +630,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
[[package]]
name = "clippy"
version = "0.1.94"
version = "0.1.95"
dependencies = [
"anstream",
"askama",
@ -657,7 +657,7 @@ dependencies = [
[[package]]
name = "clippy_config"
version = "0.1.94"
version = "0.1.95"
dependencies = [
"clippy_utils",
"itertools",
@ -681,7 +681,7 @@ dependencies = [
[[package]]
name = "clippy_lints"
version = "0.1.94"
version = "0.1.95"
dependencies = [
"arrayvec",
"cargo_metadata 0.18.1",
@ -713,7 +713,7 @@ dependencies = [
[[package]]
name = "clippy_utils"
version = "0.1.94"
version = "0.1.95"
dependencies = [
"arrayvec",
"itertools",
@ -1117,7 +1117,7 @@ dependencies = [
[[package]]
name = "declare_clippy_lint"
version = "0.1.94"
version = "0.1.95"
[[package]]
name = "derive-where"

View file

@ -148,7 +148,7 @@ impl CommandProfiler {
})
.collect();
entries.sort_by(|a, b| b.2.cmp(&a.2));
entries.sort_by_key(|e| std::cmp::Reverse(e.2));
let total_bootstrap_duration = start_time.elapsed();

View file

@ -6,7 +6,66 @@ document.
## Unreleased / Beta / In Rust Nightly
[d9fb15c...master](https://github.com/rust-lang/rust-clippy/compare/d9fb15c...master)
[92b4b68...master](https://github.com/rust-lang/rust-clippy/compare/92b4b68...master)
## Rust 1.93
Current stable, released 2026-01-22
[View all 96 merged pull requests](https://github.com/rust-lang/rust-clippy/pulls?q=merged%3A2025-10-17T15%3A48%3A11Z..2025-11-28T19%3A22%3A54Z+base%3Amaster)
### New Lints
* Added [`doc_paragraphs_missing_punctuation`] to `restriction`
[#15758](https://github.com/rust-lang/rust-clippy/pull/15758)
### Moves and Deprecations
* Renamed [`needless_if`] to [`needless_ifs`]
[#15961](https://github.com/rust-lang/rust-clippy/pull/15961)
* Renamed [`empty_enum`] to [`empty_enums`]
[#15912](https://github.com/rust-lang/rust-clippy/pull/15912)
### Enhancements
* [`result_large_err`] added `large_error_ignored` configuration
[#15697](https://github.com/rust-lang/rust-clippy/pull/15697)
* [`explicit_deref_methods`] don't lint in `impl Deref(Mut)`
[#16113](https://github.com/rust-lang/rust-clippy/pull/16113)
* [`missing_docs_in_private_items`] don't lint items in bodies and automatically derived impls;
better detect when things are accessible from the crate root; lint unnameable items which are
accessible outside the crate
[#14741](https://github.com/rust-lang/rust-clippy/pull/14741)
* [`unnecessary_unwrap`] and [`panicking_unwrap`] lint field accesses
[#15949](https://github.com/rust-lang/rust-clippy/pull/15949)
* [`ok_expect`] add autofix
[#15867](https://github.com/rust-lang/rust-clippy/pull/15867)
* [`let_and_return`] disallow _any_ text between let and return
[#16006](https://github.com/rust-lang/rust-clippy/pull/16006)
* [`needless_collect`] extend to lint more cases
[#14361](https://github.com/rust-lang/rust-clippy/pull/14361)
* [`needless_doctest_main`] and [`test_attr_in_doctest`] now handle whitespace in language tags
[#15967](https://github.com/rust-lang/rust-clippy/pull/15967)
* [`search_is_some`] now fixes code spanning multiple lines
[#15902](https://github.com/rust-lang/rust-clippy/pull/15902)
* [`unnecessary_find_map`] and [`unnecessary_filter_map`] make diagnostic spans more precise
[#15929](https://github.com/rust-lang/rust-clippy/pull/15929)
* [`precedence`] warn about ambiguity when a closure is used as a method call receiver
[#14421](https://github.com/rust-lang/rust-clippy/pull/14421)
* [`match_as_ref`] suggest `as_ref` when the reference needs to be cast; improve diagnostics
[#15934](https://github.com/rust-lang/rust-clippy/pull/15934)
[#15928](https://github.com/rust-lang/rust-clippy/pull/15928)
### False Positive Fixes
* [`single_range_in_vec_init`] fix FP for explicit `Range`
[#16043](https://github.com/rust-lang/rust-clippy/pull/16043)
* [`mod_module_files`] fix false positive for integration tests in workspace crates
[#16048](https://github.com/rust-lang/rust-clippy/pull/16048)
* [`replace_box`] fix FP when the box is moved
[#15984](https://github.com/rust-lang/rust-clippy/pull/15984)
* [`len_zero`] fix FP on unstable methods
[#15894](https://github.com/rust-lang/rust-clippy/pull/15894)
## Rust 1.92
@ -6406,6 +6465,7 @@ Released 2018-09-13
[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
[`duplicated_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicated_attributes
[`duration_suboptimal_units`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_suboptimal_units
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
[`eager_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#eager_transmute
[`elidable_lifetime_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#elidable_lifetime_names
@ -6607,6 +6667,7 @@ Released 2018-09-13
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits
[`manual_c_str_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_c_str_literals
[`manual_checked_ops`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_checked_ops
[`manual_clamp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_clamp
[`manual_contains`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_contains
[`manual_dangling_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_dangling_ptr
@ -6652,6 +6713,7 @@ Released 2018-09-13
[`manual_string_new`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new
[`manual_strip`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip
[`manual_swap`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_swap
[`manual_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_take
[`manual_try_fold`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold
[`manual_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or
[`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default

View file

@ -1,6 +1,6 @@
[package]
name = "clippy"
version = "0.1.94"
version = "0.1.95"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -905,6 +905,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
* [`manual_split_once`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_split_once)
* [`manual_str_repeat`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_str_repeat)
* [`manual_strip`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_strip)
* [`manual_take`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_take)
* [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold)
* [`map_clone`](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone)
* [`map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or)

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_config"
version = "0.1.94"
version = "0.1.95"
edition = "2024"
publish = false

View file

@ -780,6 +780,7 @@ define_Conf! {
manual_split_once,
manual_str_repeat,
manual_strip,
manual_take,
manual_try_fold,
map_clone,
map_unwrap_or,

View file

@ -6,10 +6,7 @@
unused_lifetimes,
unused_qualifications
)]
#![allow(
clippy::must_use_candidate,
clippy::missing_panics_doc,
)]
#![allow(clippy::must_use_candidate, clippy::missing_panics_doc)]
#![deny(clippy::derive_deserialize_allowing_unknown)]
extern crate rustc_data_structures;

View file

@ -1,4 +1,4 @@
use crate::parse::{DeprecatedLint, Lint, ParseCx};
use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint};
use crate::update_lints::generate_lint_files;
use crate::utils::{UpdateMode, Version};
use std::ffi::OsStr;
@ -61,6 +61,58 @@ pub fn deprecate<'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'cx str,
}
}
pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) {
let mut lints = cx.find_lint_decls();
let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints();
let Some(lint) = lints.iter().find(|l| l.name == old_name) else {
eprintln!("error: failed to find lint `{old_name}`");
return;
};
let old_name_prefixed = cx.str_buf.with(|buf| {
buf.extend(["clippy::", old_name]);
cx.arena.alloc_str(buf)
});
for lint in &mut renamed_lints {
if lint.new_name == old_name_prefixed {
lint.new_name = new_name;
}
}
match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) {
Ok(_) => {
println!("`{old_name}` is already deprecated");
return;
},
Err(idx) => renamed_lints.insert(
idx,
RenamedLint {
old_name: old_name_prefixed,
new_name,
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
},
),
}
let mod_path = {
let mut mod_path = PathBuf::from(format!("clippy_lints/src/{}", lint.module));
if mod_path.is_dir() {
mod_path = mod_path.join("mod");
}
mod_path.set_extension("rs");
mod_path
};
if remove_lint_declaration(old_name, &mod_path, &mut lints).unwrap_or(false) {
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("info: `{old_name}` has successfully been uplifted");
println!("note: you must run `cargo uitest` to update the test results");
} else {
eprintln!("error: lint not found");
}
}
fn remove_lint_declaration(name: &str, path: &Path, lints: &mut Vec<Lint<'_>>) -> io::Result<bool> {
fn remove_lint(name: &str, lints: &mut Vec<Lint<'_>>) {
lints.iter().position(|l| l.name == name).map(|pos| lints.remove(pos));

View file

@ -1,5 +1,5 @@
use crate::parse::cursor::{self, Capture, Cursor};
use crate::parse::{ParseCx, RenamedLint};
use crate::parse::{DeprecatedLint, Lint, ParseCx, RenamedLint};
use crate::update_lints::generate_lint_files;
use crate::utils::{
ErrAction, FileUpdater, UpdateMode, UpdateStatus, Version, delete_dir_if_exists, delete_file_if_exists,
@ -10,6 +10,96 @@ use std::ffi::OsString;
use std::fs;
use std::path::Path;
/// Runs the `deprecate` command
///
/// This does the following:
/// * Adds an entry to `deprecated_lints.rs`.
/// * Removes the lint declaration (and the entire file if applicable)
///
/// # Panics
///
/// If a file path could not read from or written to
pub fn deprecate<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, name: &'env str, reason: &'env str) {
let mut lints = cx.find_lint_decls();
let (mut deprecated_lints, renamed_lints) = cx.read_deprecated_lints();
let Some(lint_idx) = lints.iter().position(|l| l.name == name) else {
eprintln!("error: failed to find lint `{name}`");
return;
};
let prefixed_name = cx.str_buf.with(|buf| {
buf.extend(["clippy::", name]);
cx.arena.alloc_str(buf)
});
match deprecated_lints.binary_search_by(|x| x.name.cmp(prefixed_name)) {
Ok(_) => {
println!("`{name}` is already deprecated");
return;
},
Err(idx) => deprecated_lints.insert(
idx,
DeprecatedLint {
name: prefixed_name,
reason,
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
},
),
}
remove_lint_declaration(lint_idx, &mut lints, &mut FileUpdater::default());
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("info: `{name}` has successfully been deprecated");
println!("note: you must run `cargo uitest` to update the test results");
}
pub fn uplift<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) {
let mut lints = cx.find_lint_decls();
let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints();
let Some(lint_idx) = lints.iter().position(|l| l.name == old_name) else {
eprintln!("error: failed to find lint `{old_name}`");
return;
};
let old_name_prefixed = cx.str_buf.with(|buf| {
buf.extend(["clippy::", old_name]);
cx.arena.alloc_str(buf)
});
for lint in &mut renamed_lints {
if lint.new_name == old_name_prefixed {
lint.new_name = new_name;
}
}
match renamed_lints.binary_search_by(|x| x.old_name.cmp(old_name_prefixed)) {
Ok(_) => {
println!("`{old_name}` is already deprecated");
return;
},
Err(idx) => renamed_lints.insert(
idx,
RenamedLint {
old_name: old_name_prefixed,
new_name,
version: cx.str_buf.alloc_display(cx.arena, clippy_version.rust_display()),
},
),
}
let mut updater = FileUpdater::default();
let remove_mod = remove_lint_declaration(lint_idx, &mut lints, &mut updater);
let mut update_fn = uplift_update_fn(old_name, new_name, remove_mod);
for e in walk_dir_no_dot_or_target(".") {
let e = expect_action(e, ErrAction::Read, ".");
if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
updater.update_file(e.path(), &mut update_fn);
}
}
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
println!("info: `{old_name}` has successfully been uplifted as `{new_name}`");
println!("note: you must run `cargo uitest` to update the test results");
}
/// Runs the `rename_lint` command.
///
/// This does the following:
@ -25,8 +115,7 @@ use std::path::Path;
/// * If either lint name has a prefix
/// * If `old_name` doesn't name an existing lint.
/// * If `old_name` names a deprecated or renamed lint.
#[expect(clippy::too_many_lines)]
pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str, new_name: &'cx str, uplift: bool) {
pub fn rename<'cx, 'env: 'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'env str, new_name: &'env str) {
let mut updater = FileUpdater::default();
let mut lints = cx.find_lint_decls();
let (deprecated_lints, mut renamed_lints) = cx.read_deprecated_lints();
@ -34,20 +123,15 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str
let Ok(lint_idx) = lints.binary_search_by(|x| x.name.cmp(old_name)) else {
panic!("could not find lint `{old_name}`");
};
let lint = &lints[lint_idx];
let old_name_prefixed = cx.str_buf.with(|buf| {
buf.extend(["clippy::", old_name]);
cx.arena.alloc_str(buf)
});
let new_name_prefixed = if uplift {
new_name
} else {
cx.str_buf.with(|buf| {
buf.extend(["clippy::", new_name]);
cx.arena.alloc_str(buf)
})
};
let new_name_prefixed = cx.str_buf.with(|buf| {
buf.extend(["clippy::", new_name]);
cx.arena.alloc_str(buf)
});
for lint in &mut renamed_lints {
if lint.new_name == old_name_prefixed {
@ -71,37 +155,8 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str
},
}
// Some tests are named `lint_name_suffix` which should also be renamed,
// but we can't do that if the renamed lint's name overlaps with another
// lint. e.g. renaming 'foo' to 'bar' when a lint 'foo_bar' also exists.
let change_prefixed_tests = lints.get(lint_idx + 1).is_none_or(|l| !l.name.starts_with(old_name));
let mut mod_edit = ModEdit::None;
if uplift {
let is_unique_mod = lints[..lint_idx].iter().any(|l| l.module == lint.module)
|| lints[lint_idx + 1..].iter().any(|l| l.module == lint.module);
if is_unique_mod {
if delete_file_if_exists(lint.path.as_ref()) {
mod_edit = ModEdit::Delete;
}
} else {
updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus {
let mut start = &src[..lint.declaration_range.start];
if start.ends_with("\n\n") {
start = &start[..start.len() - 1];
}
let mut end = &src[lint.declaration_range.end..];
if end.starts_with("\n\n") {
end = &end[1..];
}
dst.push_str(start);
dst.push_str(end);
UpdateStatus::Changed
});
}
delete_test_files(old_name, change_prefixed_tests);
lints.remove(lint_idx);
} else if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() {
let mut rename_mod = false;
if lints.binary_search_by(|x| x.name.cmp(new_name)).is_err() {
let lint = &mut lints[lint_idx];
if lint.module.ends_with(old_name)
&& lint
@ -112,7 +167,7 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str
let mut new_path = lint.path.with_file_name(new_name).into_os_string();
new_path.push(".rs");
if try_rename_file(lint.path.as_ref(), new_path.as_ref()) {
mod_edit = ModEdit::Rename;
rename_mod = true;
}
lint.module = cx.str_buf.with(|buf| {
@ -121,7 +176,16 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str
cx.arena.alloc_str(buf)
});
}
rename_test_files(old_name, new_name, change_prefixed_tests);
rename_test_files(
old_name,
new_name,
&lints[lint_idx + 1..]
.iter()
.map(|l| l.name)
.take_while(|&n| n.starts_with(old_name))
.collect::<Vec<_>>(),
);
lints[lint_idx].name = new_name;
lints.sort_by(|lhs, rhs| lhs.name.cmp(rhs.name));
} else {
@ -130,7 +194,7 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str
return;
}
let mut update_fn = file_update_fn(old_name, new_name, mod_edit);
let mut update_fn = rename_update_fn(old_name, new_name, rename_mod);
for e in walk_dir_no_dot_or_target(".") {
let e = expect_action(e, ErrAction::Read, ".");
if e.path().as_os_str().as_encoded_bytes().ends_with(b".rs") {
@ -139,67 +203,85 @@ pub fn rename<'cx>(cx: ParseCx<'cx>, clippy_version: Version, old_name: &'cx str
}
generate_lint_files(UpdateMode::Change, &lints, &deprecated_lints, &renamed_lints);
if uplift {
println!("Uplifted `clippy::{old_name}` as `{new_name}`");
if matches!(mod_edit, ModEdit::None) {
println!("Only the rename has been registered, the code will need to be edited manually");
} else {
println!("All the lint's code has been deleted");
println!("Make sure to inspect the results as some things may have been missed");
}
} else {
println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`");
println!("All code referencing the old name has been updated");
println!("Make sure to inspect the results as some things may have been missed");
}
println!("Renamed `clippy::{old_name}` to `clippy::{new_name}`");
println!("All code referencing the old name has been updated");
println!("Make sure to inspect the results as some things may have been missed");
println!("note: `cargo uibless` still needs to be run to update the test results");
}
#[derive(Clone, Copy)]
enum ModEdit {
None,
Delete,
Rename,
/// Removes a lint's declaration and test files. Returns whether the module containing the
/// lint was deleted.
fn remove_lint_declaration(lint_idx: usize, lints: &mut Vec<Lint<'_>>, updater: &mut FileUpdater) -> bool {
let lint = lints.remove(lint_idx);
let delete_mod = if lints.iter().all(|l| l.module != lint.module) {
delete_file_if_exists(lint.path.as_ref())
} else {
updater.update_file(&lint.path, &mut |_, src, dst| -> UpdateStatus {
let mut start = &src[..lint.declaration_range.start];
if start.ends_with("\n\n") {
start = &start[..start.len() - 1];
}
let mut end = &src[lint.declaration_range.end..];
if end.starts_with("\n\n") {
end = &end[1..];
}
dst.push_str(start);
dst.push_str(end);
UpdateStatus::Changed
});
false
};
delete_test_files(
lint.name,
&lints[lint_idx..]
.iter()
.map(|l| l.name)
.take_while(|&n| n.starts_with(lint.name))
.collect::<Vec<_>>(),
);
delete_mod
}
fn collect_ui_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) {
fn collect_ui_test_names(lint: &str, ignored_prefixes: &[&str], dst: &mut Vec<(OsString, bool)>) {
for e in fs::read_dir("tests/ui").expect("error reading `tests/ui`") {
let e = e.expect("error reading `tests/ui`");
let name = e.file_name();
if let Some((name_only, _)) = name.as_encoded_bytes().split_once(|&x| x == b'.') {
if name_only.starts_with(lint.as_bytes()) && (rename_prefixed || name_only.len() == lint.len()) {
dst.push((name, true));
}
} else if name.as_encoded_bytes().starts_with(lint.as_bytes()) && (rename_prefixed || name.len() == lint.len())
if name.as_encoded_bytes().starts_with(lint.as_bytes())
&& !ignored_prefixes
.iter()
.any(|&pre| name.as_encoded_bytes().starts_with(pre.as_bytes()))
&& let Ok(ty) = e.file_type()
&& (ty.is_file() || ty.is_dir())
{
dst.push((name, ty.is_file()));
}
}
}
fn collect_ui_toml_test_names(lint: &str, ignored_prefixes: &[&str], dst: &mut Vec<(OsString, bool)>) {
for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") {
let e = e.expect("error reading `tests/ui-toml`");
let name = e.file_name();
if name.as_encoded_bytes().starts_with(lint.as_bytes())
&& !ignored_prefixes
.iter()
.any(|&pre| name.as_encoded_bytes().starts_with(pre.as_bytes()))
&& e.file_type().is_ok_and(|ty| ty.is_dir())
{
dst.push((name, false));
}
}
}
fn collect_ui_toml_test_names(lint: &str, rename_prefixed: bool, dst: &mut Vec<(OsString, bool)>) {
if rename_prefixed {
for e in fs::read_dir("tests/ui-toml").expect("error reading `tests/ui-toml`") {
let e = e.expect("error reading `tests/ui-toml`");
let name = e.file_name();
if name.as_encoded_bytes().starts_with(lint.as_bytes()) && e.file_type().is_ok_and(|ty| ty.is_dir()) {
dst.push((name, false));
}
}
} else {
dst.push((lint.into(), false));
}
}
/// Renames all test files for the given lint.
///
/// If `rename_prefixed` is `true` this will also rename tests which have the lint name as a prefix.
fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) {
let mut tests = Vec::new();
/// Renames all test files for the given lint where the file name does not start with any
/// of the given prefixes.
fn rename_test_files(old_name: &str, new_name: &str, ignored_prefixes: &[&str]) {
let mut tests: Vec<(OsString, bool)> = Vec::new();
let mut old_buf = OsString::from("tests/ui/");
let mut new_buf = OsString::from("tests/ui/");
collect_ui_test_names(old_name, rename_prefixed, &mut tests);
collect_ui_test_names(old_name, ignored_prefixes, &mut tests);
for &(ref name, is_file) in &tests {
old_buf.push(name);
new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]);
@ -217,7 +299,7 @@ fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) {
new_buf.truncate("tests/ui".len());
old_buf.push("-toml/");
new_buf.push("-toml/");
collect_ui_toml_test_names(old_name, rename_prefixed, &mut tests);
collect_ui_toml_test_names(old_name, ignored_prefixes, &mut tests);
for (name, _) in &tests {
old_buf.push(name);
new_buf.extend([new_name.as_ref(), name.slice_encoded_bytes(old_name.len()..)]);
@ -227,11 +309,13 @@ fn rename_test_files(old_name: &str, new_name: &str, rename_prefixed: bool) {
}
}
fn delete_test_files(lint: &str, rename_prefixed: bool) {
/// Deletes all test files for the given lint where the file name does not start with any
/// of the given prefixes.
fn delete_test_files(lint: &str, ignored_prefixes: &[&str]) {
let mut tests = Vec::new();
let mut buf = OsString::from("tests/ui/");
collect_ui_test_names(lint, rename_prefixed, &mut tests);
collect_ui_test_names(lint, ignored_prefixes, &mut tests);
for &(ref name, is_file) in &tests {
buf.push(name);
if is_file {
@ -246,7 +330,7 @@ fn delete_test_files(lint: &str, rename_prefixed: bool) {
buf.push("-toml/");
tests.clear();
collect_ui_toml_test_names(lint, rename_prefixed, &mut tests);
collect_ui_toml_test_names(lint, ignored_prefixes, &mut tests);
for (name, _) in &tests {
buf.push(name);
delete_dir_if_exists(buf.as_ref());
@ -271,12 +355,50 @@ fn snake_to_pascal(s: &str) -> String {
String::from_utf8(dst).unwrap()
}
#[expect(clippy::too_many_lines)]
fn file_update_fn<'a, 'b>(
/// Creates an update function which replaces all instances of `clippy::old_name` with
/// `new_name`.
fn uplift_update_fn<'a>(
old_name: &'a str,
new_name: &'b str,
mod_edit: ModEdit,
) -> impl use<'a, 'b> + FnMut(&Path, &str, &mut String) -> UpdateStatus {
new_name: &'a str,
remove_mod: bool,
) -> impl use<'a> + FnMut(&Path, &str, &mut String) -> UpdateStatus {
move |_, src, dst| {
let mut copy_pos = 0u32;
let mut changed = false;
let mut cursor = Cursor::new(src);
while let Some(ident) = cursor.find_any_ident() {
match cursor.get_text(ident) {
"mod"
if remove_mod && cursor.match_all(&[cursor::Pat::Ident(old_name), cursor::Pat::Semi], &mut []) =>
{
dst.push_str(&src[copy_pos as usize..ident.pos as usize]);
dst.push_str(new_name);
copy_pos = cursor.pos();
if src[copy_pos as usize..].starts_with('\n') {
copy_pos += 1;
}
changed = true;
},
"clippy" if cursor.match_all(&[cursor::Pat::DoubleColon, cursor::Pat::Ident(old_name)], &mut []) => {
dst.push_str(&src[copy_pos as usize..ident.pos as usize]);
dst.push_str(new_name);
copy_pos = cursor.pos();
changed = true;
},
_ => {},
}
}
dst.push_str(&src[copy_pos as usize..]);
UpdateStatus::from_changed(changed)
}
}
fn rename_update_fn<'a>(
old_name: &'a str,
new_name: &'a str,
rename_mod: bool,
) -> impl use<'a> + FnMut(&Path, &str, &mut String) -> UpdateStatus {
let old_name_pascal = snake_to_pascal(old_name);
let new_name_pascal = snake_to_pascal(new_name);
let old_name_upper = old_name.to_ascii_uppercase();
@ -307,34 +429,15 @@ fn file_update_fn<'a, 'b>(
},
// mod lint_name
"mod" => {
if !matches!(mod_edit, ModEdit::None)
&& let Some(pos) = cursor.find_ident(old_name)
{
match mod_edit {
ModEdit::Rename => {
dst.push_str(&src[copy_pos as usize..pos as usize]);
dst.push_str(new_name);
copy_pos = cursor.pos();
changed = true;
},
ModEdit::Delete if cursor.match_pat(cursor::Pat::Semi) => {
let mut start = &src[copy_pos as usize..match_start as usize];
if start.ends_with("\n\n") {
start = &start[..start.len() - 1];
}
dst.push_str(start);
copy_pos = cursor.pos();
if src[copy_pos as usize..].starts_with("\n\n") {
copy_pos += 1;
}
changed = true;
},
ModEdit::Delete | ModEdit::None => {},
}
if rename_mod && let Some(pos) = cursor.match_ident(old_name) {
dst.push_str(&src[copy_pos as usize..pos as usize]);
dst.push_str(new_name);
copy_pos = cursor.pos();
changed = true;
}
},
// lint_name::
name if matches!(mod_edit, ModEdit::Rename) && name == old_name => {
name if rename_mod && name == old_name => {
let name_end = cursor.pos();
if cursor.match_pat(cursor::Pat::DoubleColon) {
dst.push_str(&src[copy_pos as usize..match_start as usize]);

View file

@ -23,13 +23,12 @@ extern crate rustc_arena;
extern crate rustc_driver;
extern crate rustc_lexer;
pub mod deprecate_lint;
pub mod dogfood;
pub mod edit_lints;
pub mod fmt;
pub mod lint;
pub mod new_lint;
pub mod release;
pub mod rename_lint;
pub mod serve;
pub mod setup;
pub mod sync;

View file

@ -4,8 +4,8 @@
use clap::{Args, Parser, Subcommand};
use clippy_dev::{
ClippyInfo, UpdateMode, deprecate_lint, dogfood, fmt, lint, new_lint, new_parse_cx, release, rename_lint, serve,
setup, sync, update_lints,
ClippyInfo, UpdateMode, dogfood, edit_lints, fmt, lint, new_lint, new_parse_cx, release, serve, setup, sync,
update_lints,
};
use std::env;
@ -74,21 +74,14 @@ fn main() {
},
DevCommand::Serve { port, lint } => serve::run(port, lint),
DevCommand::Lint { path, edition, args } => lint::run(&path, &edition, args.iter()),
DevCommand::RenameLint {
old_name,
new_name,
uplift,
} => new_parse_cx(|cx| {
rename_lint::rename(
cx,
clippy.version,
&old_name,
new_name.as_ref().unwrap_or(&old_name),
uplift,
);
DevCommand::RenameLint { old_name, new_name } => new_parse_cx(|cx| {
edit_lints::rename(cx, clippy.version, &old_name, &new_name);
}),
DevCommand::Uplift { old_name, new_name } => new_parse_cx(|cx| {
edit_lints::uplift(cx, clippy.version, &old_name, new_name.as_deref().unwrap_or(&old_name));
}),
DevCommand::Deprecate { name, reason } => {
new_parse_cx(|cx| deprecate_lint::deprecate(cx, clippy.version, &name, &reason));
new_parse_cx(|cx| edit_lints::deprecate(cx, clippy.version, &name, &reason));
},
DevCommand::Sync(SyncCommand { subcommand }) => match subcommand {
SyncSubcommand::UpdateNightly => sync::update_nightly(),
@ -243,15 +236,9 @@ enum DevCommand {
/// The name of the lint to rename
#[arg(value_parser = lint_name)]
old_name: String,
#[arg(
required_unless_present = "uplift",
value_parser = lint_name,
)]
#[arg(value_parser = lint_name)]
/// The new name of the lint
new_name: Option<String>,
#[arg(long)]
/// This lint will be uplifted into rustc
uplift: bool,
new_name: String,
},
/// Deprecate the given lint
Deprecate {
@ -266,6 +253,15 @@ enum DevCommand {
Sync(SyncCommand),
/// Manage Clippy releases
Release(ReleaseCommand),
/// Marks a lint as uplifted into rustc and removes its code
Uplift {
/// The name of the lint to uplift
#[arg(value_parser = lint_name)]
old_name: String,
/// The name of the lint in rustc
#[arg(value_parser = lint_name)]
new_name: Option<String>,
},
}
#[derive(Args)]

View file

@ -219,6 +219,22 @@ impl<'txt> Cursor<'txt> {
}
}
/// Consume the returns the position of the next non-whitespace token if it's an
/// identifier. Returns `None` otherwise.
pub fn match_ident(&mut self, s: &str) -> Option<u32> {
loop {
match self.next_token.kind {
TokenKind::Ident if s == self.peek_text() => {
let pos = self.pos;
self.step();
return Some(pos);
},
TokenKind::Whitespace => self.step(),
_ => return None,
}
}
}
/// Continually attempt to match the pattern on subsequent tokens until a match is
/// found. Returns whether the pattern was successfully matched.
///

View file

@ -54,7 +54,7 @@ pub fn run(port: u16, lint: Option<String>) -> ! {
}
// Delay to avoid updating the metadata too aggressively.
thread::sleep(Duration::from_millis(1000));
thread::sleep(Duration::from_secs(1));
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
version = "0.1.94"
version = "0.1.95"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"

View file

@ -468,6 +468,10 @@ declare_clippy_lint! {
/// #[ignore = "Some good reason"]
/// fn test() {}
/// ```
///
/// ### Note
/// Clippy can only lint compiled code. For this lint to trigger, you must configure `cargo clippy`
/// to include test compilation, for instance, by using flags such as `--tests` or `--all-targets`.
#[clippy::version = "1.88.0"]
pub IGNORE_WITHOUT_REASON,
pedantic,

View file

@ -145,6 +145,12 @@ declare_clippy_lint! {
/// let _ = i32::try_from(u32::MAX).ok();
/// ```
///
/// If the wrapping is intended, you can use:
/// ```no_run
/// let _ = u32::MAX.cast_signed();
/// let _ = (-1i32).cast_unsigned();
/// ```
///
#[clippy::version = "pre 1.29.0"]
pub CAST_POSSIBLE_WRAP,
pedantic,

View file

@ -17,10 +17,6 @@ declare_clippy_lint! {
/// `if` is not guaranteed to be exhaustive and conditionals can get
/// repetitive
///
/// ### Known problems
/// The match statement may be slower due to the compiler
/// not inlining the call to cmp. See issue [#5354](https://github.com/rust-lang/rust-clippy/issues/5354)
///
/// ### Example
/// ```rust,ignore
/// # fn a() {}

View file

@ -135,6 +135,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::drop_forget_ref::FORGET_NON_DROP_INFO,
crate::drop_forget_ref::MEM_FORGET_INFO,
crate::duplicate_mod::DUPLICATE_MOD_INFO,
crate::duration_suboptimal_units::DURATION_SUBOPTIMAL_UNITS_INFO,
crate::else_if_without_else::ELSE_IF_WITHOUT_ELSE_INFO,
crate::empty_drop::EMPTY_DROP_INFO,
crate::empty_enums::EMPTY_ENUMS_INFO,
@ -296,6 +297,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::manual_assert::MANUAL_ASSERT_INFO,
crate::manual_async_fn::MANUAL_ASYNC_FN_INFO,
crate::manual_bits::MANUAL_BITS_INFO,
crate::manual_checked_ops::MANUAL_CHECKED_OPS_INFO,
crate::manual_clamp::MANUAL_CLAMP_INFO,
crate::manual_float_methods::MANUAL_IS_FINITE_INFO,
crate::manual_float_methods::MANUAL_IS_INFINITE_INFO,
@ -315,6 +317,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO,
crate::manual_take::MANUAL_TAKE_INFO,
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
crate::map_unit_fn::RESULT_MAP_UNIT_FN_INFO,
crate::match_result_ok::MATCH_RESULT_OK_INFO,

View file

@ -105,7 +105,7 @@ fn check_struct<'tcx>(
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind
&& let Some(PathSegment { args, .. }) = p.segments.last()
{
let args = args.map(|a| a.args).unwrap_or(&[]);
let args = args.map(|a| a.args).unwrap_or_default();
// ty_args contains the generic parameters of the type declaration, while args contains the
// arguments used at instantiation time. If both len are not equal, it means that some

View file

@ -692,6 +692,12 @@ declare_clippy_lint! {
/// ///
/// /// It was chosen by a fair dice roll.
/// ```
///
/// ### Terminal punctuation marks
/// This lint treats these characters as end markers: '.', '?', '!', '…' and ':'.
///
/// The colon is not exactly a terminal punctuation mark, but this is required for paragraphs that
/// introduce a table or a list for example.
#[clippy::version = "1.93.0"]
pub DOC_PARAGRAPHS_MISSING_PUNCTUATION,
restriction,

View file

@ -0,0 +1,204 @@
use std::ops::ControlFlow;
use clippy_config::Conf;
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::sym;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath, RustcVersion};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::TyCtxt;
use rustc_session::impl_lint_pass;
use rustc_span::Symbol;
declare_clippy_lint! {
/// ### What it does
///
/// Checks for instances where a `std::time::Duration` is constructed using a smaller time unit
/// when the value could be expressed more clearly using a larger unit.
///
/// ### Why is this bad?
///
/// Using a smaller unit for a duration that is evenly divisible by a larger unit reduces
/// readability. Readers have to mentally convert values, which can be error-prone and makes
/// the code less clear.
///
/// ### Example
/// ```
/// use std::time::Duration;
///
/// let dur = Duration::from_millis(5_000);
/// let dur = Duration::from_secs(180);
/// let dur = Duration::from_mins(10 * 60);
/// ```
///
/// Use instead:
/// ```
/// use std::time::Duration;
///
/// let dur = Duration::from_secs(5);
/// let dur = Duration::from_mins(3);
/// let dur = Duration::from_hours(10);
/// ```
#[clippy::version = "1.95.0"]
pub DURATION_SUBOPTIMAL_UNITS,
pedantic,
"constructing a `Duration` using a smaller unit when a larger unit would be more readable"
}
impl_lint_pass!(DurationSuboptimalUnits => [DURATION_SUBOPTIMAL_UNITS]);
pub struct DurationSuboptimalUnits {
msrv: Msrv,
units: Vec<Unit>,
}
impl DurationSuboptimalUnits {
pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self {
// The order of the units matters, as they are walked top to bottom
let mut units = UNITS.to_vec();
if tcx.features().enabled(sym::duration_constructors) {
units.extend(EXTENDED_UNITS);
}
Self { msrv: conf.msrv, units }
}
}
impl LateLintPass<'_> for DurationSuboptimalUnits {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) {
if !expr.span.in_external_macro(cx.sess().source_map())
// Check if a function on std::time::Duration is called
&& let ExprKind::Call(func, [arg]) = expr.kind
&& let ExprKind::Path(QPath::TypeRelative(func_ty, func_name)) = func.kind
&& cx
.typeck_results()
.node_type(func_ty.hir_id)
.is_diag_item(cx, sym::Duration)
// We intentionally don't want to evaluate referenced constants, as we don't want to
// recommend a literal value over using constants:
//
// let dur = Duration::from_secs(SIXTY);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Duration::from_mins(1)`
&& let Some(Constant::Int(value)) = ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt())
&& let value = u64::try_from(value).expect("All Duration::from_<time-unit> constructors take a u64")
// There is no need to promote e.g. 0 seconds to 0 hours
&& value != 0
&& let Some((promoted_constructor, promoted_value)) = self.promote(cx, func_name.ident.name, value)
{
span_lint_and_then(
cx,
DURATION_SUBOPTIMAL_UNITS,
expr.span,
"constructing a `Duration` using a smaller unit when a larger unit would be more readable",
|diag| {
let suggestions = vec![
(func_name.ident.span, promoted_constructor.to_string()),
(arg.span, promoted_value.to_string()),
];
diag.multipart_suggestion_verbose(
format!("try using {promoted_constructor}"),
suggestions,
Applicability::MachineApplicable,
);
},
);
}
}
}
impl DurationSuboptimalUnits {
/// Tries to promote the given constructor and value to a bigger time unit and returns the
/// promoted constructor name and value.
///
/// Returns [`None`] in case no promotion could be done.
fn promote(&self, cx: &LateContext<'_>, constructor_name: Symbol, value: u64) -> Option<(Symbol, u64)> {
let (best_unit, best_value) = self
.units
.iter()
.skip_while(|unit| unit.constructor_name != constructor_name)
.skip(1)
.try_fold(
(constructor_name, value),
|(current_unit, current_value), bigger_unit| {
if let Some(bigger_value) = current_value.div_exact(u64::from(bigger_unit.factor))
&& bigger_unit.stable_since.is_none_or(|v| self.msrv.meets(cx, v))
{
ControlFlow::Continue((bigger_unit.constructor_name, bigger_value))
} else {
// We have to break early, as we can't skip versions, as they are needed to
// correctly calculate the promoted value.
ControlFlow::Break((current_unit, current_value))
}
},
)
.into_value();
(best_unit != constructor_name).then_some((best_unit, best_value))
}
}
#[derive(Clone, Copy)]
struct Unit {
/// Name of the constructor on [`Duration`](std::time::Duration) to construct it from the given
/// unit, e.g. [`Duration::from_secs`](std::time::Duration::from_secs)
constructor_name: Symbol,
/// The increase factor over the previous (smaller) unit
factor: u16,
/// In what rustc version stable support for this constructor was added.
/// We do not need to track the version stable support in const contexts was added, as the const
/// stabilization was done in an ascending order of the time unites, so it's always valid to
/// promote a const constructor.
stable_since: Option<RustcVersion>,
}
/// Time unit constructors available on stable. The order matters!
const UNITS: [Unit; 6] = [
Unit {
constructor_name: sym::from_nanos,
// The value doesn't matter, as there is no previous unit
factor: 0,
stable_since: Some(msrvs::DURATION_FROM_NANOS_MICROS),
},
Unit {
constructor_name: sym::from_micros,
factor: 1_000,
stable_since: Some(msrvs::DURATION_FROM_NANOS_MICROS),
},
Unit {
constructor_name: sym::from_millis,
factor: 1_000,
stable_since: Some(msrvs::DURATION_FROM_MILLIS_SECS),
},
Unit {
constructor_name: sym::from_secs,
factor: 1_000,
stable_since: Some(msrvs::DURATION_FROM_MILLIS_SECS),
},
Unit {
constructor_name: sym::from_mins,
factor: 60,
stable_since: Some(msrvs::DURATION_FROM_MINUTES_HOURS),
},
Unit {
constructor_name: sym::from_hours,
factor: 60,
stable_since: Some(msrvs::DURATION_FROM_MINUTES_HOURS),
},
];
/// Time unit constructors behind the `duration_constructors` feature. The order matters!
const EXTENDED_UNITS: [Unit; 2] = [
Unit {
constructor_name: sym::from_days,
factor: 24,
stable_since: None,
},
Unit {
constructor_name: sym::from_weeks,
factor: 7,
stable_since: None,
},
];

View file

@ -1,761 +0,0 @@
use clippy_utils::consts::Constant::{F32, F64, Int};
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::{
eq_expr_value, get_parent_expr, has_ambiguous_literal_in_expr, higher, is_in_const_context, is_no_std_crate,
numeric_literal, peel_blocks, sugg, sym,
};
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::SyntaxContext;
use rustc_span::source_map::Spanned;
use std::f32::consts as f32_consts;
use std::f64::consts as f64_consts;
use sugg::Sugg;
declare_clippy_lint! {
/// ### What it does
/// Looks for floating-point expressions that
/// can be expressed using built-in methods to improve accuracy
/// at the cost of performance.
///
/// ### Why is this bad?
/// Negatively impacts accuracy.
///
/// ### Example
/// ```no_run
/// let a = 3f32;
/// let _ = a.powf(1.0 / 3.0);
/// let _ = (1.0 + a).ln();
/// let _ = a.exp() - 1.0;
/// ```
///
/// Use instead:
/// ```no_run
/// let a = 3f32;
/// let _ = a.cbrt();
/// let _ = a.ln_1p();
/// let _ = a.exp_m1();
/// ```
#[clippy::version = "1.43.0"]
pub IMPRECISE_FLOPS,
nursery,
"usage of imprecise floating point operations"
}
declare_clippy_lint! {
/// ### What it does
/// Looks for floating-point expressions that
/// can be expressed using built-in methods to improve both
/// accuracy and performance.
///
/// ### Why is this bad?
/// Negatively impacts accuracy and performance.
///
/// ### Example
/// ```no_run
/// use std::f32::consts::E;
///
/// let a = 3f32;
/// let _ = (2f32).powf(a);
/// let _ = E.powf(a);
/// let _ = a.powf(1.0 / 2.0);
/// let _ = a.log(2.0);
/// let _ = a.log(10.0);
/// let _ = a.log(E);
/// let _ = a.powf(2.0);
/// let _ = a * 2.0 + 4.0;
/// let _ = if a < 0.0 {
/// -a
/// } else {
/// a
/// };
/// let _ = if a < 0.0 {
/// a
/// } else {
/// -a
/// };
/// ```
///
/// is better expressed as
///
/// ```no_run
/// use std::f32::consts::E;
///
/// let a = 3f32;
/// let _ = a.exp2();
/// let _ = a.exp();
/// let _ = a.sqrt();
/// let _ = a.log2();
/// let _ = a.log10();
/// let _ = a.ln();
/// let _ = a.powi(2);
/// let _ = a.mul_add(2.0, 4.0);
/// let _ = a.abs();
/// let _ = -a.abs();
/// ```
#[clippy::version = "1.43.0"]
pub SUBOPTIMAL_FLOPS,
nursery,
"usage of sub-optimal floating point operations"
}
declare_lint_pass!(FloatingPointArithmetic => [
IMPRECISE_FLOPS,
SUBOPTIMAL_FLOPS
]);
// Returns the specialized log method for a given base if base is constant
// and is one of 2, 10 and e
fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> {
if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) {
if F32(2.0) == value || F64(2.0) == value {
return Some("log2");
} else if F32(10.0) == value || F64(10.0) == value {
return Some("log10");
} else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
return Some("ln");
}
}
None
}
// Adds type suffixes and parenthesis to method receivers if necessary
fn prepare_receiver_sugg<'a>(cx: &LateContext<'_>, mut expr: &'a Expr<'a>) -> Sugg<'a> {
let mut suggestion = Sugg::hir(cx, expr, "..");
if let ExprKind::Unary(UnOp::Neg, inner_expr) = &expr.kind {
expr = inner_expr;
}
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind()
// if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous
&& let ExprKind::Lit(lit) = &expr.kind
&& let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node
{
let op = format!(
"{suggestion}{}{}",
// Check for float literals without numbers following the decimal
// separator such as `2.` and adds a trailing zero
if sym.as_str().ends_with('.') { "0" } else { "" },
float_ty.name_str()
)
.into();
suggestion = match suggestion {
Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op),
};
}
suggestion.maybe_paren()
}
fn check_log_base(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) {
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"logarithm for bases 2, 10 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", Sugg::hir(cx, receiver, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}
}
// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and
// suggest usage of `(x + (y - 1)).ln_1p()` instead
fn check_ln1p(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Add, ..
},
lhs,
rhs,
) = receiver.kind
{
let ecx = ConstEvalCtxt::new(cx);
let recv = match (ecx.eval(lhs), ecx.eval(rhs)) {
(Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs,
(_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs,
_ => return,
};
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"ln(1 + x) can be computed more accurately",
"consider using",
format!("{}.ln_1p()", prepare_receiver_sugg(cx, recv)),
Applicability::MachineApplicable,
);
}
}
// Returns an integer if the float constant is a whole number and it can be
// converted to an integer without loss of precision. For now we only check
// ranges [-16777215, 16777216) for type f32 as whole number floats outside
// this range are lossy and ambiguous.
#[expect(clippy::cast_possible_truncation)]
fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
match value {
F32(num) if num.fract() == 0.0 => {
if (-16_777_215.0..16_777_216.0).contains(num) {
Some(num.round() as i32)
} else {
None
}
},
F64(num) if num.fract() == 0.0 => {
if (-2_147_483_648.0..2_147_483_648.0).contains(num) {
Some(num.round() as i32)
} else {
None
}
},
_ => None,
}
}
fn check_powf(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
// Check receiver
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver)
&& let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
Some("exp")
} else if F32(2.0) == value || F64(2.0) == value {
Some("exp2")
} else {
None
}
{
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"exponent for bases 2 and e can be computed more accurately",
"consider using",
format!("{}.{method}()", prepare_receiver_sugg(cx, &args[0])),
Applicability::MachineApplicable,
);
}
// Check argument
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) {
let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
(
SUBOPTIMAL_FLOPS,
"square-root of a number can be computed more efficiently and accurately",
format!("{}.sqrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
)
} else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
(
IMPRECISE_FLOPS,
"cube-root of a number can be computed more accurately",
format!("{}.cbrt()", Sugg::hir(cx, receiver, "..").maybe_paren()),
)
} else if let Some(exponent) = get_integer_from_float_constant(&value) {
(
SUBOPTIMAL_FLOPS,
"exponentiation with integer powers can be computed more efficiently",
format!(
"{}.powi({})",
Sugg::hir(cx, receiver, "..").maybe_paren(),
numeric_literal::format(&exponent.to_string(), None, false)
),
)
} else {
return;
};
span_lint_and_sugg(
cx,
lint,
expr.span,
help,
"consider using",
suggestion,
Applicability::MachineApplicable,
);
}
}
fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0])
&& value == Int(2)
&& let Some(parent) = get_parent_expr(cx, expr)
{
if let Some(grandparent) = get_parent_expr(cx, parent)
&& let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind
&& method.name == sym::sqrt
&& detect_hypot(cx, receiver).is_some()
{
return;
}
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = parent.kind
{
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
// Negate expr if original code has subtraction and expr is on the right side
let maybe_neg_sugg = |expr, hir_id| {
let sugg = Sugg::hir(cx, expr, "..");
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
-sugg
} else {
sugg
}
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
parent.span,
"multiply and add expressions can be calculated more efficiently and accurately",
"consider using",
format!(
"{}.mul_add({}, {})",
Sugg::hir(cx, receiver, "..").maybe_paren(),
maybe_neg_sugg(receiver, expr.hir_id),
maybe_neg_sugg(other_addend, other_addend.hir_id),
),
Applicability::MachineApplicable,
);
}
}
}
fn detect_hypot(cx: &LateContext<'_>, receiver: &Expr<'_>) -> Option<String> {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Add, ..
},
add_lhs,
add_rhs,
) = receiver.kind
{
// check if expression of the form x * x + y * y
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lmul_lhs,
lmul_rhs,
) = add_lhs.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
rmul_lhs,
rmul_rhs,
) = add_rhs.kind
&& eq_expr_value(cx, lmul_lhs, lmul_rhs)
&& eq_expr_value(cx, rmul_lhs, rmul_rhs)
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, lmul_lhs, "..").maybe_paren(),
Sugg::hir(cx, rmul_lhs, "..")
));
}
// check if expression of the form x.powi(2) + y.powi(2)
if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = &add_lhs.kind
&& let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = &add_rhs.kind
&& lmethod.name == sym::powi
&& rmethod.name == sym::powi
&& let ecx = ConstEvalCtxt::new(cx)
&& let Some(lvalue) = ecx.eval(largs_1)
&& let Some(rvalue) = ecx.eval(rargs_1)
&& Int(2) == lvalue
&& Int(2) == rvalue
{
return Some(format!(
"{}.hypot({})",
Sugg::hir(cx, largs_0, "..").maybe_paren(),
Sugg::hir(cx, rargs_0, "..")
));
}
}
None
}
fn check_hypot(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
if let Some(message) = detect_hypot(cx, receiver) {
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"hypotenuse can be computed more accurately",
"consider using",
message,
Applicability::MachineApplicable,
);
}
}
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
// and suggest usage of `x.exp_m1() - (y - 1)` instead
fn check_expm1(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
},
lhs,
rhs,
) = expr.kind
&& let ExprKind::MethodCall(path, self_arg, [], _) = &lhs.kind
&& path.ident.name == sym::exp
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& let Some(value) = ConstEvalCtxt::new(cx).eval(rhs)
&& (F32(1.0) == value || F64(1.0) == value)
&& cx.typeck_results().expr_ty(self_arg).is_floating_point()
{
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!("{}.exp_m1()", Sugg::hir(cx, self_arg, "..").maybe_paren()),
Applicability::MachineApplicable,
);
}
}
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lhs,
rhs,
) = &expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
{
return Some((lhs, rhs));
}
None
}
fn check_mul_add(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = &expr.kind
{
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind
&& method.name == sym::sqrt
&& detect_hypot(cx, receiver).is_some()
{
return;
}
let maybe_neg_sugg = |expr| {
let sugg = Sugg::hir(cx, expr, "..");
if let BinOpKind::Sub = op { -sugg } else { sugg }
};
let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs)
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
{
(inner_lhs, Sugg::hir(cx, inner_rhs, ".."), maybe_neg_sugg(rhs))
} else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs)
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
{
(inner_lhs, maybe_neg_sugg(inner_rhs), Sugg::hir(cx, lhs, ".."))
} else {
return;
};
// Check if any variable in the expression has an ambiguous type (could be f32 or f64)
// see: https://github.com/rust-lang/rust-clippy/issues/14897
if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _)))
&& has_ambiguous_literal_in_expr(cx, recv)
{
return;
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"multiply and add expressions can be calculated more efficiently and accurately",
"consider using",
format!("{}.mul_add({arg1}, {arg2})", prepare_receiver_sugg(cx, recv)),
Applicability::MachineApplicable,
);
}
}
/// Returns true iff expr is an expression which tests whether or not
/// test is positive or an expression which tests whether or not test
/// is nonnegative.
/// Used for check-custom-abs function below
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
_ => false,
}
} else {
false
}
}
/// See [`is_testing_positive`]
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
_ => false,
}
} else {
false
}
}
/// Returns true iff expr is some zero literal
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) {
Some(Int(i)) => i == 0,
Some(F32(f)) => f == 0.0,
Some(F64(f)) => f == 0.0,
_ => false,
}
}
/// If the two expressions are negations of each other, then it returns
/// a tuple, in which the first element is true iff expr1 is the
/// positive expressions, and the second element is the positive
/// one of the two expressions
/// If the two expressions are not negations of each other, then it
/// returns None.
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = &expr1.kind
&& eq_expr_value(cx, expr1_negated, expr2)
{
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = &expr2.kind
&& eq_expr_value(cx, expr1, expr2_negated)
{
return Some((true, expr1));
}
None
}
fn check_custom_abs(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(expr)
&& let if_body_expr = peel_blocks(then)
&& let else_body_expr = peel_blocks(r#else)
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
{
let positive_abs_sugg = (
"manual implementation of `abs` method",
format!("{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
);
let negative_abs_sugg = (
"manual implementation of negation of `abs` method",
format!("-{}.abs()", Sugg::hir(cx, body, "..").maybe_paren()),
);
let sugg = if is_testing_positive(cx, cond, body) {
if if_expr_positive {
positive_abs_sugg
} else {
negative_abs_sugg
}
} else if is_testing_negative(cx, cond, body) {
if if_expr_positive {
negative_abs_sugg
} else {
positive_abs_sugg
}
} else {
return;
};
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
sugg.0,
"try",
sugg.1,
Applicability::MachineApplicable,
);
}
}
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind
&& let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind
{
return method_a.name == method_b.name
&& args_a.len() == args_b.len()
&& (matches!(method_a.name, sym::ln | sym::log2 | sym::log10)
|| method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
}
false
}
fn check_log_division(cx: &LateContext<'_>, expr: &Expr<'_>) {
// check if expression of the form x.logN() / y.logN()
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
lhs,
rhs,
) = &expr.kind
&& are_same_base_logs(cx, lhs, rhs)
&& let ExprKind::MethodCall(_, largs_self, ..) = &lhs.kind
&& let ExprKind::MethodCall(_, rargs_self, ..) = &rhs.kind
{
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"log base can be expressed more clearly",
"consider using",
format!(
"{}.log({})",
Sugg::hir(cx, largs_self, "..").maybe_paren(),
Sugg::hir(cx, rargs_self, ".."),
),
Applicability::MachineApplicable,
);
}
}
fn check_radians(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
div_lhs,
div_rhs,
) = &expr.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
mul_lhs,
mul_rhs,
) = &div_lhs.kind
&& let ecx = ConstEvalCtxt::new(cx)
&& let Some(rvalue) = ecx.eval(div_rhs)
&& let Some(lvalue) = ecx.eval(mul_rhs)
{
// TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
&& (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
{
let mut proposal = format!("{}.to_degrees()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_degrees()", Sugg::hir(cx, mul_lhs, ".."));
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to degrees can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
} else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue)
&& (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
{
let mut proposal = format!("{}.to_radians()", Sugg::hir(cx, mul_lhs, "..").maybe_paren());
if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
if value.as_str().ends_with('.') {
proposal = format!("{}0_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
} else {
proposal = format!("{}_f64.to_radians()", Sugg::hir(cx, mul_lhs, ".."));
}
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to radians can be done more accurately",
"consider using",
proposal,
Applicability::MachineApplicable,
);
}
}
}
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// All of these operations are currently not const and are in std.
if is_in_const_context(cx) {
return;
}
if let ExprKind::MethodCall(path, receiver, args, _) = &expr.kind {
let recv_ty = cx.typeck_results().expr_ty(receiver);
if recv_ty.is_floating_point() && !is_no_std_crate(cx) && cx.ty_based_def(expr).opt_parent(cx).is_impl(cx) {
match path.ident.name {
sym::ln => check_ln1p(cx, expr, receiver),
sym::log => check_log_base(cx, expr, receiver, args),
sym::powf => check_powf(cx, expr, receiver, args),
sym::powi => check_powi(cx, expr, receiver, args),
sym::sqrt => check_hypot(cx, expr, receiver),
_ => {},
}
}
} else {
if !is_no_std_crate(cx) {
check_expm1(cx, expr);
check_mul_add(cx, expr);
check_custom_abs(cx, expr);
check_log_division(cx, expr);
}
check_radians(cx, expr);
}
}
}

View file

@ -0,0 +1,100 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::{F32, F64, Int};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{eq_expr_value, higher, peel_blocks};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
use rustc_span::SyntaxContext;
use rustc_span::source_map::Spanned;
use super::SUBOPTIMAL_FLOPS;
/// Returns true iff expr is an expression which tests whether or not
/// test is positive or an expression which tests whether or not test
/// is nonnegative.
/// Used for check-custom-abs function below
fn is_testing_positive(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
_ => false,
}
} else {
false
}
}
/// See [`is_testing_positive`]
fn is_testing_negative(cx: &LateContext<'_>, expr: &Expr<'_>, test: &Expr<'_>) -> bool {
if let ExprKind::Binary(Spanned { node: op, .. }, left, right) = expr.kind {
match op {
BinOpKind::Gt | BinOpKind::Ge => is_zero(cx, left, expr.span.ctxt()) && eq_expr_value(cx, right, test),
BinOpKind::Lt | BinOpKind::Le => is_zero(cx, right, expr.span.ctxt()) && eq_expr_value(cx, left, test),
_ => false,
}
} else {
false
}
}
/// Returns true iff expr is some zero literal
fn is_zero(cx: &LateContext<'_>, expr: &Expr<'_>, ctxt: SyntaxContext) -> bool {
match ConstEvalCtxt::new(cx).eval_local(expr, ctxt) {
Some(Int(i)) => i == 0,
Some(F32(f)) => f == 0.0,
Some(F64(f)) => f == 0.0,
_ => false,
}
}
/// If the two expressions are negations of each other, then it returns
/// a tuple, in which the first element is true iff expr1 is the
/// positive expressions, and the second element is the positive
/// one of the two expressions
/// If the two expressions are not negations of each other, then it
/// returns None.
fn are_negated<'a>(cx: &LateContext<'_>, expr1: &'a Expr<'a>, expr2: &'a Expr<'a>) -> Option<(bool, &'a Expr<'a>)> {
if let ExprKind::Unary(UnOp::Neg, expr1_negated) = expr1.kind
&& eq_expr_value(cx, expr1_negated, expr2)
{
return Some((false, expr2));
}
if let ExprKind::Unary(UnOp::Neg, expr2_negated) = expr2.kind
&& eq_expr_value(cx, expr1, expr2_negated)
{
return Some((true, expr1));
}
None
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let Some(higher::If {
cond,
then,
r#else: Some(r#else),
}) = higher::If::hir(expr)
&& let if_body_expr = peel_blocks(then)
&& let else_body_expr = peel_blocks(r#else)
&& let Some((if_expr_positive, body)) = are_negated(cx, if_body_expr, else_body_expr)
{
let sugg_positive_abs = if is_testing_positive(cx, cond, body) {
if_expr_positive
} else if is_testing_negative(cx, cond, body) {
!if_expr_positive
} else {
return;
};
let mut app = Applicability::MachineApplicable;
let body = Sugg::hir_with_applicability(cx, body, "_", &mut app).maybe_paren();
let sugg = if sugg_positive_abs {
("manual implementation of `abs` method", format!("{body}.abs()"))
} else {
#[rustfmt::skip]
("manual implementation of negation of `abs` method", format!("-{body}.abs()"))
};
span_lint_and_sugg(cx, SUBOPTIMAL_FLOPS, expr.span, sugg.0, "try", sugg.1, app);
}
}

View file

@ -0,0 +1,42 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::{F32, F64};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use clippy_utils::sym;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::IMPRECISE_FLOPS;
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
// and suggest usage of `x.exp_m1() - (y - 1)` instead
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Sub, ..
},
lhs,
rhs,
) = expr.kind
&& let ExprKind::MethodCall(path, self_arg, [], _) = lhs.kind
&& path.ident.name == sym::exp
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& let Some(value) = ConstEvalCtxt::new(cx).eval(rhs)
&& (F32(1.0) == value || F64(1.0) == value)
&& cx.typeck_results().expr_ty(self_arg).is_floating_point()
{
span_lint_and_then(
cx,
IMPRECISE_FLOPS,
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
|diag| {
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_applicability(cx, self_arg, "_", &mut app).maybe_paren();
diag.span_suggestion(expr.span, "consider using", format!("{recv}.exp_m1()"), app);
},
);
}
}

View file

@ -0,0 +1,82 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::Int;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{eq_expr_value, sym};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::IMPRECISE_FLOPS;
pub(super) fn detect(cx: &LateContext<'_>, receiver: &Expr<'_>, app: &mut Applicability) -> Option<String> {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Add, ..
},
add_lhs,
add_rhs,
) = receiver.kind
{
// check if expression of the form x * x + y * y
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lmul_lhs,
lmul_rhs,
) = add_lhs.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
rmul_lhs,
rmul_rhs,
) = add_rhs.kind
&& eq_expr_value(cx, lmul_lhs, lmul_rhs)
&& eq_expr_value(cx, rmul_lhs, rmul_rhs)
{
return Some(format!(
"{}.hypot({})",
Sugg::hir_with_applicability(cx, lmul_lhs, "_", app).maybe_paren(),
Sugg::hir_with_applicability(cx, rmul_lhs, "_", app)
));
}
// check if expression of the form x.powi(2) + y.powi(2)
if let ExprKind::MethodCall(PathSegment { ident: lmethod, .. }, largs_0, [largs_1, ..], _) = add_lhs.kind
&& let ExprKind::MethodCall(PathSegment { ident: rmethod, .. }, rargs_0, [rargs_1, ..], _) = add_rhs.kind
&& lmethod.name == sym::powi
&& rmethod.name == sym::powi
&& let ecx = ConstEvalCtxt::new(cx)
&& let Some(lvalue) = ecx.eval(largs_1)
&& let Some(rvalue) = ecx.eval(rargs_1)
&& Int(2) == lvalue
&& Int(2) == rvalue
{
return Some(format!(
"{}.hypot({})",
Sugg::hir_with_applicability(cx, largs_0, "_", app).maybe_paren(),
Sugg::hir_with_applicability(cx, rargs_0, "_", app)
));
}
}
None
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
let mut app = Applicability::MachineApplicable;
if let Some(message) = detect(cx, receiver, &mut app) {
span_lint_and_sugg(
cx,
IMPRECISE_FLOPS,
expr.span,
"hypotenuse can be computed more accurately",
"consider using",
message,
app,
);
}
}

View file

@ -0,0 +1,42 @@
use clippy_utils::sugg::Sugg;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, UnOp};
use rustc_lint::LateContext;
use rustc_middle::ty;
// Adds type suffixes and parenthesis to method receivers if necessary
pub(super) fn prepare_receiver_sugg<'a>(
cx: &LateContext<'_>,
mut expr: &'a Expr<'a>,
app: &mut Applicability,
) -> Sugg<'a> {
let mut suggestion = Sugg::hir_with_applicability(cx, expr, "_", app);
if let ExprKind::Unary(UnOp::Neg, inner_expr) = expr.kind {
expr = inner_expr;
}
if let ty::Float(float_ty) = cx.typeck_results().expr_ty(expr).kind()
// if the expression is a float literal and it is unsuffixed then
// add a suffix so the suggestion is valid and unambiguous
&& let ExprKind::Lit(lit) = expr.kind
&& let ast::LitKind::Float(sym, ast::LitFloatType::Unsuffixed) = lit.node
{
let op = format!(
"{suggestion}{}{}",
// Check for float literals without numbers following the decimal
// separator such as `2.` and adds a trailing zero
if sym.as_str().ends_with('.') { "0" } else { "" },
float_ty.name_str()
)
.into();
suggestion = match suggestion {
Sugg::MaybeParen(_) | Sugg::UnOp(UnOp::Neg, _) => Sugg::MaybeParen(op),
_ => Sugg::NonParen(op),
};
}
suggestion.maybe_paren()
}

View file

@ -0,0 +1,41 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::{F32, F64};
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::IMPRECISE_FLOPS;
// TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and
// suggest usage of `(x + (y - 1)).ln_1p()` instead
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Add, ..
},
lhs,
rhs,
) = receiver.kind
{
let ecx = ConstEvalCtxt::new(cx);
let recv = match (ecx.eval(lhs), ecx.eval(rhs)) {
(Some(value), _) if F32(1.0) == value || F64(1.0) == value => rhs,
(_, Some(value)) if F32(1.0) == value || F64(1.0) == value => lhs,
_ => return,
};
span_lint_and_then(
cx,
IMPRECISE_FLOPS,
expr.span,
"ln(1 + x) can be computed more accurately",
|diag| {
let mut app = Applicability::MachineApplicable;
let recv = super::lib::prepare_receiver_sugg(cx, recv, &mut app);
diag.span_suggestion(expr.span, "consider using", format!("{recv}.ln_1p()"), app);
},
);
}
}

View file

@ -0,0 +1,44 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::{F32, F64};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::SyntaxContext;
use std::f32::consts as f32_consts;
use std::f64::consts as f64_consts;
use super::SUBOPTIMAL_FLOPS;
// Returns the specialized log method for a given base if base is constant
// and is one of 2, 10 and e
fn get_specialized_log_method(cx: &LateContext<'_>, base: &Expr<'_>, ctxt: SyntaxContext) -> Option<&'static str> {
if let Some(value) = ConstEvalCtxt::new(cx).eval_local(base, ctxt) {
if F32(2.0) == value || F64(2.0) == value {
return Some("log2");
} else if F32(10.0) == value || F64(10.0) == value {
return Some("log10");
} else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
return Some("ln");
}
}
None
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some(method) = get_specialized_log_method(cx, &args[0], expr.span.ctxt()) {
span_lint_and_then(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"logarithm for bases 2, 10 and e can be computed more accurately",
|diag| {
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren();
diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app);
},
);
}
}

View file

@ -0,0 +1,52 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{eq_expr_value, sym};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::SUBOPTIMAL_FLOPS;
fn are_same_base_logs(cx: &LateContext<'_>, expr_a: &Expr<'_>, expr_b: &Expr<'_>) -> bool {
if let ExprKind::MethodCall(PathSegment { ident: method_a, .. }, _, args_a, _) = expr_a.kind
&& let ExprKind::MethodCall(PathSegment { ident: method_b, .. }, _, args_b, _) = expr_b.kind
{
return method_a.name == method_b.name
&& args_a.len() == args_b.len()
&& (matches!(method_a.name, sym::ln | sym::log2 | sym::log10)
|| method_a.name == sym::log && args_a.len() == 1 && eq_expr_value(cx, &args_a[0], &args_b[0]));
}
false
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
// check if expression of the form x.logN() / y.logN()
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
lhs,
rhs,
) = expr.kind
&& are_same_base_logs(cx, lhs, rhs)
&& let ExprKind::MethodCall(_, largs_self, ..) = lhs.kind
&& let ExprKind::MethodCall(_, rargs_self, ..) = rhs.kind
{
let mut app = Applicability::MachineApplicable;
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"log base can be expressed more clearly",
"consider using",
format!(
"{}.log({})",
Sugg::hir_with_applicability(cx, largs_self, "_", &mut app).maybe_paren(),
Sugg::hir_with_applicability(cx, rargs_self, "_", &mut app),
),
app,
);
}
}

View file

@ -0,0 +1,141 @@
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::{is_in_const_context, is_no_std_crate, sym};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
mod custom_abs;
mod expm1;
mod hypot;
mod lib;
mod ln1p;
mod log_base;
mod log_division;
mod mul_add;
mod powf;
mod powi;
mod radians;
declare_clippy_lint! {
/// ### What it does
/// Looks for floating-point expressions that
/// can be expressed using built-in methods to improve accuracy
/// at the cost of performance.
///
/// ### Why is this bad?
/// Negatively impacts accuracy.
///
/// ### Example
/// ```no_run
/// let a = 3f32;
/// let _ = a.powf(1.0 / 3.0);
/// let _ = (1.0 + a).ln();
/// let _ = a.exp() - 1.0;
/// ```
///
/// Use instead:
/// ```no_run
/// let a = 3f32;
/// let _ = a.cbrt();
/// let _ = a.ln_1p();
/// let _ = a.exp_m1();
/// ```
#[clippy::version = "1.43.0"]
pub IMPRECISE_FLOPS,
nursery,
"usage of imprecise floating point operations"
}
declare_clippy_lint! {
/// ### What it does
/// Looks for floating-point expressions that
/// can be expressed using built-in methods to improve both
/// accuracy and performance.
///
/// ### Why is this bad?
/// Negatively impacts accuracy and performance.
///
/// ### Example
/// ```no_run
/// use std::f32::consts::E;
///
/// let a = 3f32;
/// let _ = (2f32).powf(a);
/// let _ = E.powf(a);
/// let _ = a.powf(1.0 / 2.0);
/// let _ = a.log(2.0);
/// let _ = a.log(10.0);
/// let _ = a.log(E);
/// let _ = a.powf(2.0);
/// let _ = a * 2.0 + 4.0;
/// let _ = if a < 0.0 {
/// -a
/// } else {
/// a
/// };
/// let _ = if a < 0.0 {
/// a
/// } else {
/// -a
/// };
/// ```
///
/// is better expressed as
///
/// ```no_run
/// use std::f32::consts::E;
///
/// let a = 3f32;
/// let _ = a.exp2();
/// let _ = a.exp();
/// let _ = a.sqrt();
/// let _ = a.log2();
/// let _ = a.log10();
/// let _ = a.ln();
/// let _ = a.powi(2);
/// let _ = a.mul_add(2.0, 4.0);
/// let _ = a.abs();
/// let _ = -a.abs();
/// ```
#[clippy::version = "1.43.0"]
pub SUBOPTIMAL_FLOPS,
nursery,
"usage of sub-optimal floating point operations"
}
declare_lint_pass!(FloatingPointArithmetic => [
IMPRECISE_FLOPS,
SUBOPTIMAL_FLOPS
]);
impl<'tcx> LateLintPass<'tcx> for FloatingPointArithmetic {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// All of these operations are currently not const and are in std.
if is_in_const_context(cx) {
return;
}
if let ExprKind::MethodCall(path, receiver, args, _) = expr.kind {
let recv_ty = cx.typeck_results().expr_ty(receiver);
if recv_ty.is_floating_point() && !is_no_std_crate(cx) && cx.ty_based_def(expr).opt_parent(cx).is_impl(cx) {
match path.ident.name {
sym::ln => ln1p::check(cx, expr, receiver),
sym::log => log_base::check(cx, expr, receiver, args),
sym::powf => powf::check(cx, expr, receiver, args),
sym::powi => powi::check(cx, expr, receiver, args),
sym::sqrt => hypot::check(cx, expr, receiver),
_ => {},
}
}
} else {
if !is_no_std_crate(cx) {
expm1::check(cx, expr);
mul_add::check(cx, expr);
custom_abs::check(cx, expr);
log_division::check(cx, expr);
}
radians::check(cx, expr);
}
}
}

View file

@ -0,0 +1,94 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, has_ambiguous_literal_in_expr, sym};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::SUBOPTIMAL_FLOPS;
fn is_float_mul_expr<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<(&'a Expr<'a>, &'a Expr<'a>)> {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
lhs,
rhs,
) = expr.kind
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
{
return Some((lhs, rhs));
}
None
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = &expr.kind
{
if let Some(parent) = get_parent_expr(cx, expr)
&& let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = parent.kind
&& method.name == sym::sqrt
// we don't care about the applicability as this is an early-return condition
&& super::hypot::detect(cx, receiver, &mut Applicability::Unspecified).is_some()
{
return;
}
let maybe_neg_sugg = |expr, app: &mut _| {
let sugg = Sugg::hir_with_applicability(cx, expr, "_", app);
if let BinOpKind::Sub = op { -sugg } else { sugg }
};
let mut app = Applicability::MachineApplicable;
let (recv, arg1, arg2) = if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, lhs)
&& cx.typeck_results().expr_ty(rhs).is_floating_point()
{
(
inner_lhs,
Sugg::hir_with_applicability(cx, inner_rhs, "_", &mut app),
maybe_neg_sugg(rhs, &mut app),
)
} else if let Some((inner_lhs, inner_rhs)) = is_float_mul_expr(cx, rhs)
&& cx.typeck_results().expr_ty(lhs).is_floating_point()
{
(
inner_lhs,
maybe_neg_sugg(inner_rhs, &mut app),
Sugg::hir_with_applicability(cx, lhs, "_", &mut app),
)
} else {
return;
};
// Check if any variable in the expression has an ambiguous type (could be f32 or f64)
// see: https://github.com/rust-lang/rust-clippy/issues/14897
if (matches!(recv.kind, ExprKind::Path(_)) || matches!(recv.kind, ExprKind::Call(_, _)))
&& has_ambiguous_literal_in_expr(cx, recv)
{
return;
}
span_lint_and_sugg(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"multiply and add expressions can be calculated more efficiently and accurately",
"consider using",
format!(
"{}.mul_add({arg1}, {arg2})",
super::lib::prepare_receiver_sugg(cx, recv, &mut app)
),
app,
);
}
}

View file

@ -0,0 +1,94 @@
use clippy_utils::consts::Constant::{F32, F64};
use clippy_utils::consts::{ConstEvalCtxt, Constant};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::numeric_literal;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use std::f32::consts as f32_consts;
use std::f64::consts as f64_consts;
use super::{IMPRECISE_FLOPS, SUBOPTIMAL_FLOPS};
// Returns an integer if the float constant is a whole number and it can be
// converted to an integer without loss of precision. For now we only check
// ranges [-16777215, 16777216) for type f32 as whole number floats outside
// this range are lossy and ambiguous.
#[expect(clippy::cast_possible_truncation)]
fn get_integer_from_float_constant(value: &Constant) -> Option<i32> {
match value {
F32(num) if num.fract() == 0.0 => {
if (-16_777_215.0..16_777_216.0).contains(num) {
Some(num.round() as i32)
} else {
None
}
},
F64(num) if num.fract() == 0.0 => {
if (-2_147_483_648.0..2_147_483_648.0).contains(num) {
Some(num.round() as i32)
} else {
None
}
},
_ => None,
}
}
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
// Check receiver
if let Some(value) = ConstEvalCtxt::new(cx).eval(receiver)
&& let Some(method) = if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
Some("exp")
} else if F32(2.0) == value || F64(2.0) == value {
Some("exp2")
} else {
None
}
{
span_lint_and_then(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"exponent for bases 2 and e can be computed more accurately",
|diag| {
let mut app = Applicability::MachineApplicable;
let recv = super::lib::prepare_receiver_sugg(cx, &args[0], &mut app);
diag.span_suggestion(expr.span, "consider using", format!("{recv}.{method}()"), app);
},
);
}
// Check argument
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0]) {
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren();
let (lint, help, suggestion) = if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
(
SUBOPTIMAL_FLOPS,
"square-root of a number can be computed more efficiently and accurately",
format!("{recv}.sqrt()"),
)
} else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
(
IMPRECISE_FLOPS,
"cube-root of a number can be computed more accurately",
format!("{recv}.cbrt()"),
)
} else if let Some(exponent) = get_integer_from_float_constant(&value) {
(
SUBOPTIMAL_FLOPS,
"exponentiation with integer powers can be computed more efficiently",
format!(
"{recv}.powi({})",
numeric_literal::format(&exponent.to_string(), None, false)
),
)
} else {
return;
};
span_lint_and_sugg(cx, lint, expr.span, help, "consider using", suggestion, app);
}
}

View file

@ -0,0 +1,70 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::Int;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, sym};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PathSegment};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use super::SUBOPTIMAL_FLOPS;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, receiver: &Expr<'_>, args: &[Expr<'_>]) {
if let Some(value) = ConstEvalCtxt::new(cx).eval(&args[0])
&& value == Int(2)
&& let Some(parent) = get_parent_expr(cx, expr)
{
if let Some(grandparent) = get_parent_expr(cx, parent)
&& let ExprKind::MethodCall(PathSegment { ident: method, .. }, receiver, ..) = grandparent.kind
&& method.name == sym::sqrt
// we don't care about the applicability as this is an early-return condition
&& super::hypot::detect(cx, receiver, &mut Applicability::Unspecified).is_some()
{
return;
}
if let ExprKind::Binary(
Spanned {
node: op @ (BinOpKind::Add | BinOpKind::Sub),
..
},
lhs,
rhs,
) = parent.kind
{
span_lint_and_then(
cx,
SUBOPTIMAL_FLOPS,
parent.span,
"multiply and add expressions can be calculated more efficiently and accurately",
|diag| {
let other_addend = if lhs.hir_id == expr.hir_id { rhs } else { lhs };
// Negate expr if original code has subtraction and expr is on the right side
let maybe_neg_sugg = |expr, hir_id, app: &mut _| {
let sugg = Sugg::hir_with_applicability(cx, expr, "_", app);
if matches!(op, BinOpKind::Sub) && hir_id == rhs.hir_id {
-sugg
} else {
sugg
}
};
let mut app = Applicability::MachineApplicable;
diag.span_suggestion(
parent.span,
"consider using",
format!(
"{}.mul_add({}, {})",
Sugg::hir_with_applicability(cx, receiver, "_", &mut app).maybe_paren(),
maybe_neg_sugg(receiver, expr.hir_id, &mut app),
maybe_neg_sugg(other_addend, other_addend.hir_id, &mut app),
),
app,
);
},
);
}
}
}

View file

@ -0,0 +1,89 @@
use clippy_utils::consts::ConstEvalCtxt;
use clippy_utils::consts::Constant::{F32, F64};
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use rustc_ast::ast;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Spanned;
use std::f32::consts as f32_consts;
use std::f64::consts as f64_consts;
use super::SUBOPTIMAL_FLOPS;
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::Binary(
Spanned {
node: BinOpKind::Div, ..
},
div_lhs,
div_rhs,
) = expr.kind
&& let ExprKind::Binary(
Spanned {
node: BinOpKind::Mul, ..
},
mul_lhs,
mul_rhs,
) = div_lhs.kind
&& let ecx = ConstEvalCtxt::new(cx)
&& let Some(rvalue) = ecx.eval(div_rhs)
&& let Some(lvalue) = ecx.eval(mul_rhs)
{
// TODO: also check for constant values near PI/180 or 180/PI
if (F32(f32_consts::PI) == rvalue || F64(f64_consts::PI) == rvalue)
&& (F32(180_f32) == lvalue || F64(180_f64) == lvalue)
{
span_lint_and_then(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to degrees can be done more accurately",
|diag| {
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app);
let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
if value.as_str().ends_with('.') {
format!("{recv}0_f64.to_degrees()")
} else {
format!("{recv}_f64.to_degrees()")
}
} else {
format!("{}.to_degrees()", recv.maybe_paren())
};
diag.span_suggestion(expr.span, "consider using", proposal, app);
},
);
} else if (F32(180_f32) == rvalue || F64(180_f64) == rvalue)
&& (F32(f32_consts::PI) == lvalue || F64(f64_consts::PI) == lvalue)
{
span_lint_and_then(
cx,
SUBOPTIMAL_FLOPS,
expr.span,
"conversion to radians can be done more accurately",
|diag| {
let mut app = Applicability::MachineApplicable;
let recv = Sugg::hir_with_applicability(cx, mul_lhs, "num", &mut app);
let proposal = if let ExprKind::Lit(literal) = mul_lhs.kind
&& let ast::LitKind::Float(ref value, float_type) = literal.node
&& float_type == ast::LitFloatType::Unsuffixed
{
if value.as_str().ends_with('.') {
format!("{recv}0_f64.to_radians()")
} else {
format!("{recv}_f64.to_radians()")
}
} else {
format!("{}.to_radians()", recv.maybe_paren())
};
diag.span_suggestion(expr.span, "consider using", proposal, app);
},
);
}
}
}

View file

@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::SpanRangeExt;
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind};
use rustc_ast::token;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg;
use rustc_ast::ast::{BinOpKind, Expr, ExprKind, LitKind, UnOp};
use rustc_data_structures::packed::Pu128;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
@ -35,132 +35,125 @@ declare_clippy_lint! {
declare_lint_pass!(IntPlusOne => [INT_PLUS_ONE]);
// cases:
// BinOpKind::Ge
// LeOrGe::Ge
// x >= y + 1
// x - 1 >= y
//
// BinOpKind::Le
// LeOrGe::Le
// x + 1 <= y
// x <= y - 1
#[derive(Copy, Clone)]
enum Side {
Lhs,
Rhs,
enum LeOrGe {
Le,
Ge,
}
impl TryFrom<BinOpKind> for LeOrGe {
type Error = ();
fn try_from(value: BinOpKind) -> Result<Self, Self::Error> {
match value {
BinOpKind::Le => Ok(Self::Le),
BinOpKind::Ge => Ok(Self::Ge),
_ => Err(()),
}
}
}
impl IntPlusOne {
#[expect(clippy::cast_sign_loss)]
fn check_lit(token_lit: token::Lit, target_value: i128) -> bool {
if let Ok(LitKind::Int(value, ..)) = LitKind::from_token_lit(token_lit) {
return value == (target_value as u128);
fn is_one(expr: &Expr) -> bool {
if let ExprKind::Lit(token_lit) = expr.kind
&& matches!(LitKind::from_token_lit(token_lit), Ok(LitKind::Int(Pu128(1), ..)))
{
return true;
}
false
}
fn check_binop(cx: &EarlyContext<'_>, binop: BinOpKind, lhs: &Expr, rhs: &Expr) -> Option<String> {
match (binop, &lhs.kind, &rhs.kind) {
// case where `x - 1 >= ...` or `-1 + x >= ...`
(BinOpKind::Ge, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) => {
match (lhskind.node, &lhslhs.kind, &lhsrhs.kind) {
// `-1 + x`
(BinOpKind::Add, ExprKind::Lit(lit), _) if Self::check_lit(*lit, -1) => {
Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
},
// `x - 1`
(BinOpKind::Sub, _, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
},
_ => None,
}
},
// case where `... >= y + 1` or `... >= 1 + y`
(BinOpKind::Ge, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) if rhskind.node == BinOpKind::Add => {
match (&rhslhs.kind, &rhsrhs.kind) {
// `y + 1` and `1 + y`
(ExprKind::Lit(lit), _) if Self::check_lit(*lit, 1) => {
Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
},
(_, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
},
_ => None,
}
},
// case where `x + 1 <= ...` or `1 + x <= ...`
(BinOpKind::Le, ExprKind::Binary(lhskind, lhslhs, lhsrhs), _) if lhskind.node == BinOpKind::Add => {
match (&lhslhs.kind, &lhsrhs.kind) {
// `1 + x` and `x + 1`
(ExprKind::Lit(lit), _) if Self::check_lit(*lit, 1) => {
Self::generate_recommendation(cx, binop, lhsrhs, rhs, Side::Lhs)
},
(_, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
Self::generate_recommendation(cx, binop, lhslhs, rhs, Side::Lhs)
},
_ => None,
}
},
// case where `... >= y - 1` or `... >= -1 + y`
(BinOpKind::Le, _, ExprKind::Binary(rhskind, rhslhs, rhsrhs)) => {
match (rhskind.node, &rhslhs.kind, &rhsrhs.kind) {
// `-1 + y`
(BinOpKind::Add, ExprKind::Lit(lit), _) if Self::check_lit(*lit, -1) => {
Self::generate_recommendation(cx, binop, rhsrhs, lhs, Side::Rhs)
},
// `y - 1`
(BinOpKind::Sub, _, ExprKind::Lit(lit)) if Self::check_lit(*lit, 1) => {
Self::generate_recommendation(cx, binop, rhslhs, lhs, Side::Rhs)
},
_ => None,
}
},
_ => None,
fn is_neg_one(expr: &Expr) -> bool {
if let ExprKind::Unary(UnOp::Neg, expr) = &expr.kind {
Self::is_one(expr)
} else {
false
}
}
fn generate_recommendation(
cx: &EarlyContext<'_>,
binop: BinOpKind,
node: &Expr,
other_side: &Expr,
side: Side,
) -> Option<String> {
let binop_string = match binop {
BinOpKind::Ge => ">",
BinOpKind::Le => "<",
_ => return None,
};
if let Some(snippet) = node.span.get_source_text(cx)
&& let Some(other_side_snippet) = other_side.span.get_source_text(cx)
/// Checks whether `expr` is `x + 1` or `1 + x`, and if so, returns `x`
fn as_x_plus_one(expr: &Expr) -> Option<&Expr> {
if let ExprKind::Binary(op, lhs, rhs) = &expr.kind
&& op.node == BinOpKind::Add
{
let rec = match side {
Side::Lhs => Some(format!("{snippet} {binop_string} {other_side_snippet}")),
Side::Rhs => Some(format!("{other_side_snippet} {binop_string} {snippet}")),
};
return rec;
if Self::is_one(rhs) {
// x + 1
return Some(lhs);
} else if Self::is_one(lhs) {
// 1 + x
return Some(rhs);
}
}
None
}
fn emit_warning(cx: &EarlyContext<'_>, block: &Expr, recommendation: String) {
span_lint_and_sugg(
/// Checks whether `expr` is `x - 1` or `-1 + x`, and if so, returns `x`
fn as_x_minus_one(expr: &Expr) -> Option<&Expr> {
if let ExprKind::Binary(op, lhs, rhs) = &expr.kind {
if op.node == BinOpKind::Sub && Self::is_one(rhs) {
// x - 1
return Some(lhs);
} else if op.node == BinOpKind::Add && Self::is_neg_one(lhs) {
// -1 + x
return Some(rhs);
}
}
None
}
fn check_binop<'tcx>(le_or_ge: LeOrGe, lhs: &'tcx Expr, rhs: &'tcx Expr) -> Option<(&'tcx Expr, &'tcx Expr)> {
match le_or_ge {
LeOrGe::Ge => {
// case where `x - 1 >= ...` or `-1 + x >= ...`
(Self::as_x_minus_one(lhs).map(|new_lhs| (new_lhs, rhs)))
// case where `... >= y + 1` or `... >= 1 + y`
.or_else(|| Self::as_x_plus_one(rhs).map(|new_rhs| (lhs, new_rhs)))
},
LeOrGe::Le => {
// case where `x + 1 <= ...` or `1 + x <= ...`
(Self::as_x_plus_one(lhs).map(|new_lhs| (new_lhs, rhs)))
// case where `... <= y - 1` or `... <= -1 + y`
.or_else(|| Self::as_x_minus_one(rhs).map(|new_rhs| (lhs, new_rhs)))
},
}
}
fn emit_warning(cx: &EarlyContext<'_>, expr: &Expr, new_lhs: &Expr, le_or_ge: LeOrGe, new_rhs: &Expr) {
span_lint_and_then(
cx,
INT_PLUS_ONE,
block.span,
expr.span,
"unnecessary `>= y + 1` or `x - 1 >=`",
"change it to",
recommendation,
Applicability::MachineApplicable, // snippet
|diag| {
let mut app = Applicability::MachineApplicable;
let ctxt = expr.span.ctxt();
let new_lhs = sugg::Sugg::ast(cx, new_lhs, "_", ctxt, &mut app);
let new_rhs = sugg::Sugg::ast(cx, new_rhs, "_", ctxt, &mut app);
let new_binop = match le_or_ge {
LeOrGe::Ge => BinOpKind::Gt,
LeOrGe::Le => BinOpKind::Lt,
};
let rec = sugg::make_binop(new_binop, &new_lhs, &new_rhs);
diag.span_suggestion(expr.span, "change it to", rec, app);
},
);
}
}
impl EarlyLintPass for IntPlusOne {
fn check_expr(&mut self, cx: &EarlyContext<'_>, item: &Expr) {
if let ExprKind::Binary(ref kind, ref lhs, ref rhs) = item.kind
&& let Some(rec) = Self::check_binop(cx, kind.node, lhs, rhs)
fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
if let ExprKind::Binary(binop, lhs, rhs) = &expr.kind
&& let Ok(le_or_ge) = LeOrGe::try_from(binop.node)
&& let Some((new_lhs, new_rhs)) = Self::check_binop(le_or_ge, lhs, rhs)
{
Self::emit_warning(cx, item, rec);
Self::emit_warning(cx, expr, new_lhs, le_or_ge, new_rhs);
}
}
}

View file

@ -1,10 +1,12 @@
#![feature(box_patterns)]
#![feature(macro_metavar_expr_concat)]
#![feature(control_flow_into_value)]
#![feature(exact_div)]
#![feature(f128)]
#![feature(f16)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(iter_partition_in_place)]
#![feature(macro_metavar_expr_concat)]
#![feature(never_type)]
#![feature(rustc_private)]
#![feature(stmt_expr_attributes)]
@ -110,6 +112,7 @@ mod doc;
mod double_parens;
mod drop_forget_ref;
mod duplicate_mod;
mod duration_suboptimal_units;
mod else_if_without_else;
mod empty_drop;
mod empty_enums;
@ -195,6 +198,7 @@ mod manual_abs_diff;
mod manual_assert;
mod manual_async_fn;
mod manual_bits;
mod manual_checked_ops;
mod manual_clamp;
mod manual_float_methods;
mod manual_hash_one;
@ -213,6 +217,7 @@ mod manual_rotate;
mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
mod manual_take;
mod map_unit_fn;
mod match_result_ok;
mod matches;
@ -712,7 +717,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::<unused_async::UnusedAsync>::default()),
Box::new(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))),
Box::new(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))),
Box::new(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)),
Box::new(move |_| Box::new(strlen_on_c_strings::StrlenOnCStrings::new(conf))),
Box::new(move |_| Box::new(self_named_constructors::SelfNamedConstructors)),
Box::new(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)),
Box::new(move |_| Box::new(manual_assert::ManualAssert)),
@ -854,6 +859,9 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)),
Box::new(move |tcx| Box::new(duration_suboptimal_units::DurationSuboptimalUnits::new(tcx, conf))),
Box::new(move |_| Box::new(manual_take::ManualTake::new(conf))),
Box::new(|_| Box::new(manual_checked_ops::ManualCheckedOps)),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::trait_ref_of_method;
use clippy_utils::{is_from_proc_macro, trait_ref_of_method};
use itertools::Itertools;
use rustc_ast::visit::{try_visit, walk_list};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
@ -149,9 +149,12 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
..
} = item.kind
{
check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv);
check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv, || {
is_from_proc_macro(cx, item)
});
} else if let ItemKind::Impl(impl_) = &item.kind
&& !item.span.from_expansion()
&& !is_from_proc_macro(cx, item)
{
report_extra_impl_lifetimes(cx, impl_);
}
@ -169,6 +172,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
item.span,
report_extra_lifetimes,
self.msrv,
|| is_from_proc_macro(cx, item),
);
}
}
@ -179,7 +183,17 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
TraitFn::Required(sig) => (None, Some(sig)),
TraitFn::Provided(id) => (Some(id), None),
};
check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true, self.msrv);
check_fn_inner(
cx,
sig,
body,
trait_sig,
item.generics,
item.span,
true,
self.msrv,
|| is_from_proc_macro(cx, item),
);
}
}
}
@ -194,6 +208,7 @@ fn check_fn_inner<'tcx>(
span: Span,
report_extra_lifetimes: bool,
msrv: Msrv,
is_from_proc_macro: impl FnOnce() -> bool,
) {
if span.in_external_macro(cx.sess().source_map()) || has_where_lifetimes(cx, generics) {
return;
@ -245,10 +260,19 @@ fn check_fn_inner<'tcx>(
}
}
if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params, msrv) {
if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) {
return;
}
let elidable = could_use_elision(cx, sig.decl, body, trait_sig, generics.params, msrv);
let has_elidable_lts = elidable
.as_ref()
.is_some_and(|(_, usages)| !usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)));
// Only check is_from_proc_macro if we're about to emit a lint (it's an expensive check)
if (has_elidable_lts || report_extra_lifetimes) && is_from_proc_macro() {
return;
}
if let Some((elidable_lts, usages)) = elidable
&& has_elidable_lts
{
// async functions have usages whose spans point at the lifetime declaration which messes up
// suggestions
let include_suggestions = !sig.header.is_async();

View file

@ -40,7 +40,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_blo
let_info,
Some(if_let.if_then),
);
} else if els.and_then(|x| x.expr).is_some_and(is_simple_break_expr)
} else if els.is_some_and(is_simple_break_block)
&& let Some((pat, _)) = let_info
{
could_be_while_let(cx, expr, pat, init, has_trailing_exprs, let_info, None);
@ -61,17 +61,23 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, loop_blo
}
}
/// Returns `true` if expr contains a single break expression without a label or sub-expression,
/// possibly embedded in blocks.
fn is_simple_break_expr(e: &Expr<'_>) -> bool {
if let ExprKind::Block(b, _) = e.kind {
match (b.stmts, b.expr) {
([s], None) => matches!(s.kind, StmtKind::Expr(e) | StmtKind::Semi(e) if is_simple_break_expr(e)),
([], Some(e)) => is_simple_break_expr(e),
_ => false,
}
} else {
matches!(e.kind, ExprKind::Break(dest, None) if dest.label.is_none())
/// Checks if `block` contains a single unlabeled `break` expression or statement, possibly embedded
/// inside other blocks.
fn is_simple_break_block(block: &Block<'_>) -> bool {
match (block.stmts, block.expr) {
([s], None) => matches!(s.kind, StmtKind::Expr(e) | StmtKind::Semi(e) if is_simple_break_expr(e)),
([], Some(e)) => is_simple_break_expr(e),
_ => false,
}
}
/// Checks if `expr` contains a single unlabeled `break` expression or statement, possibly embedded
/// inside other blocks.
fn is_simple_break_expr(expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Block(b, _) => is_simple_break_block(b),
ExprKind::Break(dest, None) => dest.label.is_none(),
_ => false,
}
}

View file

@ -0,0 +1,170 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::{Descend, for_each_expr_without_closures};
use clippy_utils::{SpanlessEq, is_integer_literal};
use rustc_hir::{AssignOpKind, BinOpKind, Block, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use std::ops::ControlFlow;
declare_clippy_lint! {
/// ### What it does
/// Detects manual zero checks before dividing integers, such as `if x != 0 { y / x }`.
///
/// ### Why is this bad?
/// `checked_div` already handles the zero case and makes the intent clearer while avoiding a
/// panic from a manual division.
///
/// ### Example
/// ```no_run
/// # let (a, b) = (10u32, 5u32);
/// if b != 0 {
/// let result = a / b;
/// println!("{result}");
/// }
/// ```
/// Use instead:
/// ```no_run
/// # let (a, b) = (10u32, 5u32);
/// if let Some(result) = a.checked_div(b) {
/// println!("{result}");
/// }
/// ```
#[clippy::version = "1.95.0"]
pub MANUAL_CHECKED_OPS,
complexity,
"manual zero checks before dividing integers"
}
declare_lint_pass!(ManualCheckedOps => [MANUAL_CHECKED_OPS]);
#[derive(Copy, Clone)]
enum NonZeroBranch {
Then,
Else,
}
impl LateLintPass<'_> for ManualCheckedOps {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::If(cond, then, r#else) = expr.kind
&& !expr.span.from_expansion()
&& let Some((divisor, branch)) = divisor_from_condition(cond)
// This lint is intended for unsigned integers only.
//
// For signed integers, the most direct refactor to `checked_div` is often not
// semantically equivalent to the original guard. For example, `rhs > 0` deliberately
// excludes negative divisors, while `checked_div` would return `Some` for `rhs = -2`.
// Also, `checked_div` can return `None` for `MIN / -1`, which requires additional
// handling beyond the zero check.
&& is_unsigned_integer(cx, divisor)
&& let Some(block) = branch_block(then, r#else, branch)
{
let mut eq = SpanlessEq::new(cx).deny_side_effects().paths_by_resolution();
if !eq.eq_expr(divisor, divisor) {
return;
}
let mut division_spans = Vec::new();
let mut first_use = None;
let found_early_use = for_each_expr_without_closures(block, |e| {
if let ExprKind::Binary(binop, lhs, rhs) = e.kind
&& binop.node == BinOpKind::Div
&& eq.eq_expr(rhs, divisor)
&& is_unsigned_integer(cx, lhs)
{
match first_use {
None => first_use = Some(UseKind::Division),
Some(UseKind::Other) => return ControlFlow::Break(()),
Some(UseKind::Division) => {},
}
division_spans.push(e.span);
ControlFlow::<(), _>::Continue(Descend::No)
} else if let ExprKind::AssignOp(op, lhs, rhs) = e.kind
&& op.node == AssignOpKind::DivAssign
&& eq.eq_expr(rhs, divisor)
&& is_unsigned_integer(cx, lhs)
{
match first_use {
None => first_use = Some(UseKind::Division),
Some(UseKind::Other) => return ControlFlow::Break(()),
Some(UseKind::Division) => {},
}
division_spans.push(e.span);
ControlFlow::<(), _>::Continue(Descend::No)
} else if eq.eq_expr(e, divisor) {
if first_use.is_none() {
first_use = Some(UseKind::Other);
return ControlFlow::Break(());
}
ControlFlow::<(), _>::Continue(Descend::Yes)
} else {
ControlFlow::<(), _>::Continue(Descend::Yes)
}
});
if found_early_use.is_some() || first_use != Some(UseKind::Division) || division_spans.is_empty() {
return;
}
span_lint_and_then(cx, MANUAL_CHECKED_OPS, cond.span, "manual checked division", |diag| {
diag.span_label(cond.span, "check performed here");
if let Some((first, rest)) = division_spans.split_first() {
diag.span_label(*first, "division performed here");
if !rest.is_empty() {
diag.span_labels(rest.to_vec(), "... and here");
}
}
diag.help("consider using `checked_div`");
});
}
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum UseKind {
Division,
Other,
}
fn divisor_from_condition<'tcx>(cond: &'tcx Expr<'tcx>) -> Option<(&'tcx Expr<'tcx>, NonZeroBranch)> {
let ExprKind::Binary(binop, lhs, rhs) = cond.kind else {
return None;
};
match binop.node {
BinOpKind::Ne | BinOpKind::Lt if is_zero(lhs) => Some((rhs, NonZeroBranch::Then)),
BinOpKind::Ne | BinOpKind::Gt if is_zero(rhs) => Some((lhs, NonZeroBranch::Then)),
BinOpKind::Eq if is_zero(lhs) => Some((rhs, NonZeroBranch::Else)),
BinOpKind::Eq if is_zero(rhs) => Some((lhs, NonZeroBranch::Else)),
_ => None,
}
}
fn branch_block<'tcx>(
then: &'tcx Expr<'tcx>,
r#else: Option<&'tcx Expr<'tcx>>,
branch: NonZeroBranch,
) -> Option<&'tcx Block<'tcx>> {
match branch {
NonZeroBranch::Then => match then.kind {
ExprKind::Block(block, _) => Some(block),
_ => None,
},
NonZeroBranch::Else => match r#else?.kind {
ExprKind::Block(block, _) => Some(block),
_ => None,
},
}
}
fn is_zero(expr: &Expr<'_>) -> bool {
is_integer_literal(expr, 0)
}
fn is_unsigned_integer(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
matches!(cx.typeck_results().expr_ty(expr).peel_refs().kind(), ty::Uint(_))
}

View file

@ -0,0 +1,114 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{MEM_TAKE, Msrv};
use clippy_utils::source::snippet_with_context;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Detects manual re-implementations of `std::mem::take`.
///
/// ### Why is this bad?
/// Because the function call is shorter and easier to read.
///
/// ### Known issues
/// Currently the lint only detects cases involving `bool`s.
///
/// ### Example
/// ```no_run
/// let mut x = true;
/// let _ = if x {
/// x = false;
/// true
/// } else {
/// false
/// };
/// ```
/// Use instead:
/// ```no_run
/// let mut x = true;
/// let _ = std::mem::take(&mut x);
/// ```
#[clippy::version = "1.94.0"]
pub MANUAL_TAKE,
complexity,
"manual `mem::take` implementation"
}
pub struct ManualTake {
msrv: Msrv,
}
impl ManualTake {
pub fn new(conf: &'static Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl_lint_pass!(ManualTake => [MANUAL_TAKE]);
impl LateLintPass<'_> for ManualTake {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if let ExprKind::If(cond, then, Some(otherwise)) = expr.kind
&& let ExprKind::Path(_) = cond.kind
&& let ExprKind::Block(
Block {
stmts: [stmt],
expr: Some(then_expr),
..
},
..,
) = then.kind
&& let ExprKind::Block(
Block {
stmts: [],
expr: Some(else_expr),
..
},
..,
) = otherwise.kind
&& let StmtKind::Semi(assignment) = stmt.kind
&& let ExprKind::Assign(mut_c, possible_false, _) = assignment.kind
&& let ExprKind::Path(_) = mut_c.kind
&& !expr.span.in_external_macro(cx.sess().source_map())
&& let Some(std_or_core) = clippy_utils::std_or_core(cx)
&& self.msrv.meets(cx, MEM_TAKE)
&& clippy_utils::SpanlessEq::new(cx).eq_expr(cond, mut_c)
&& Some(false) == as_const_bool(possible_false)
&& let Some(then_bool) = as_const_bool(then_expr)
&& let Some(else_bool) = as_const_bool(else_expr)
&& then_bool != else_bool
{
span_lint_and_then(
cx,
MANUAL_TAKE,
expr.span,
"manual implementation of `mem::take`",
|diag| {
let mut app = Applicability::MachineApplicable;
let negate = if then_bool { "" } else { "!" };
let taken = snippet_with_context(cx, cond.span, expr.span.ctxt(), "_", &mut app).0;
diag.span_suggestion_verbose(
expr.span,
"use",
format!("{negate}{std_or_core}::mem::take(&mut {taken})"),
app,
);
},
);
}
}
}
fn as_const_bool(e: &Expr<'_>) -> Option<bool> {
if let ExprKind::Lit(lit) = e.kind
&& let LitKind::Bool(b) = lit.node
{
Some(b)
} else {
None
}
}

View file

@ -1,12 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::{snippet, snippet_with_applicability};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use clippy_utils::{get_parent_expr, sym};
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Closure, Expr, ExprKind, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty;
@ -14,7 +15,37 @@ use rustc_span::{Span, Symbol};
use super::MANUAL_IS_VARIANT_AND;
pub(super) fn check(
#[derive(Clone, Copy, PartialEq)]
enum Flavor {
Option,
Result,
}
impl Flavor {
fn new(cx: &LateContext<'_>, def_id: DefId) -> Option<Self> {
match cx.tcx.get_diagnostic_name(def_id)? {
sym::Option => Some(Self::Option),
sym::Result => Some(Self::Result),
_ => None,
}
}
const fn diag_sym(self) -> Symbol {
match self {
Self::Option => sym::Option,
Self::Result => sym::Result,
}
}
const fn positive_variant_name(self) -> Symbol {
match self {
Self::Option => sym::Some,
Self::Result => sym::Ok,
}
}
}
pub(super) fn check_map_unwrap_or_default(
cx: &LateContext<'_>,
expr: &Expr<'_>,
map_recv: &Expr<'_>,
@ -30,11 +61,13 @@ pub(super) fn check(
}
// 2. the caller of `map()` is neither `Option` nor `Result`
let is_option = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Option);
let is_result = cx.typeck_results().expr_ty(map_recv).is_diag_item(cx, sym::Result);
if !is_option && !is_result {
let Some(flavor) = (cx.typeck_results())
.expr_ty(map_recv)
.opt_def_id()
.and_then(|did| Flavor::new(cx, did))
else {
return;
}
};
// 3. the caller of `unwrap_or_default` is neither `Option<bool>` nor `Result<bool, _>`
if !cx.typeck_results().expr_ty(expr).is_bool() {
@ -46,44 +79,23 @@ pub(super) fn check(
return;
}
let lint_msg = if is_option {
"called `map(<f>).unwrap_or_default()` on an `Option` value"
} else {
"called `map(<f>).unwrap_or_default()` on a `Result` value"
let lint_span = expr.span.with_lo(map_span.lo());
let lint_msg = match flavor {
Flavor::Option => "called `map(<f>).unwrap_or_default()` on an `Option` value",
Flavor::Result => "called `map(<f>).unwrap_or_default()` on a `Result` value",
};
let suggestion = if is_option { "is_some_and" } else { "is_ok_and" };
span_lint_and_sugg(
cx,
MANUAL_IS_VARIANT_AND,
expr.span.with_lo(map_span.lo()),
lint_msg,
"use",
format!("{}({})", suggestion, snippet(cx, map_arg.span, "..")),
Applicability::MachineApplicable,
);
}
span_lint_and_then(cx, MANUAL_IS_VARIANT_AND, lint_span, lint_msg, |diag| {
let method = match flavor {
Flavor::Option => "is_some_and",
Flavor::Result => "is_ok_and",
};
#[derive(Clone, Copy, PartialEq)]
enum Flavor {
Option,
Result,
}
let mut app = Applicability::MachineApplicable;
let map_arg_snippet = snippet_with_applicability(cx, map_arg.span, "_", &mut app);
impl Flavor {
const fn symbol(self) -> Symbol {
match self {
Self::Option => sym::Option,
Self::Result => sym::Result,
}
}
const fn positive(self) -> Symbol {
match self {
Self::Option => sym::Some,
Self::Result => sym::Ok,
}
}
diag.span_suggestion(lint_span, "use", format!("{method}({map_arg_snippet})"), app);
});
}
#[derive(Clone, Copy, PartialEq)]
@ -178,7 +190,7 @@ fn emit_lint<'tcx>(
cx,
MANUAL_IS_VARIANT_AND,
span,
format!("called `.map() {op} {pos}()`", pos = flavor.positive(),),
format!("called `.map() {op} {pos}()`", pos = flavor.positive_variant_name()),
"use",
format!(
"{inversion}{recv}.{method}({body})",
@ -195,24 +207,23 @@ pub(super) fn check_map(cx: &LateContext<'_>, expr: &Expr<'_>) {
&& op.span.eq_ctxt(expr.span)
&& let Ok(op) = Op::try_from(op.node)
{
// Check `left` and `right` expression in any order, and for `Option` and `Result`
// Check `left` and `right` expression in any order
for (expr1, expr2) in [(left, right), (right, left)] {
for flavor in [Flavor::Option, Flavor::Result] {
if let ExprKind::Call(call, [arg]) = expr1.kind
&& let ExprKind::Lit(lit) = arg.kind
&& let LitKind::Bool(bool_cst) = lit.node
&& let ExprKind::Path(QPath::Resolved(_, path)) = call.kind
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res
&& let ty = cx.typeck_results().expr_ty(expr1)
&& let ty::Adt(adt, args) = ty.kind()
&& cx.tcx.is_diagnostic_item(flavor.symbol(), adt.did())
&& args.type_at(0).is_bool()
&& let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind
&& cx.typeck_results().expr_ty(recv).is_diag_item(cx, flavor.symbol())
&& let Ok(map_func) = MapFunc::try_from(map_expr)
{
return emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv);
}
if let ExprKind::Call(call, [arg]) = expr1.kind
&& let ExprKind::Lit(lit) = arg.kind
&& let LitKind::Bool(bool_cst) = lit.node
&& let ExprKind::Path(QPath::Resolved(_, path)) = call.kind
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), _) = path.res
&& let ExprKind::MethodCall(_, recv, [map_expr], _) = expr2.kind
&& let ty = cx.typeck_results().expr_ty(expr1)
&& let ty::Adt(adt, args) = ty.kind()
&& let Some(flavor) = Flavor::new(cx, adt.did())
&& args.type_at(0).is_bool()
&& cx.typeck_results().expr_ty(recv).is_diag_item(cx, flavor.diag_sym())
&& let Ok(map_func) = MapFunc::try_from(map_expr)
{
emit_lint(cx, parent_expr.span, op, flavor, bool_cst, map_func, recv);
return;
}
}
}

View file

@ -1,71 +1,210 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet;
use clippy_utils::usage::mutated_variables;
use clippy_utils::res::{MaybeDef as _, MaybeResPath as _};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_copy;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{Visitor, walk_expr, walk_path};
use rustc_hir::{ExprKind, HirId, LangItem, Node, PatKind, Path, QPath};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
use rustc_middle::hir::nested_filter;
use rustc_span::{Span, sym};
use std::ops::ControlFlow;
use super::MAP_UNWRAP_OR;
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
///
/// Returns true if the lint was emitted
/// lint use of `map().unwrap_or()` for `Option`s and `Result`s
#[expect(clippy::too_many_arguments)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
recv: &'tcx hir::Expr<'_>,
map_arg: &'tcx hir::Expr<'_>,
unwrap_arg: &'tcx hir::Expr<'_>,
expr: &rustc_hir::Expr<'_>,
recv: &rustc_hir::Expr<'_>,
map_arg: &'tcx rustc_hir::Expr<'_>,
unwrap_recv: &rustc_hir::Expr<'_>,
unwrap_arg: &'tcx rustc_hir::Expr<'_>,
map_span: Span,
msrv: Msrv,
) -> bool {
// lint if the caller of `map()` is an `Option` or a `Result`.
let is_option = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option);
let is_result = cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result);
) {
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
let recv_ty_kind = match recv_ty.opt_diag_name(cx) {
Some(sym::Option) => sym::Option,
Some(sym::Result) if msrv.meets(cx, msrvs::RESULT_MAP_OR) => sym::Result,
_ => return,
};
if is_result && !msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) {
return false;
}
let unwrap_arg_ty = cx.typeck_results().expr_ty(unwrap_arg);
if !is_copy(cx, unwrap_arg_ty) {
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
// borrowck errors, see #10579 for one such instance.
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
// ```
// let x = vec![1, 2];
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
// ```
// This compiles, but changing it to `map_or` will produce a compile error:
// ```
// let x = vec![1, 2];
// x.get(0..1).map_or(x, |s| s.to_vec())
// ^ moving `x` here
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
// ```
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
// before the call to `unwrap_or`.
if is_option || is_result {
// Don't make a suggestion that may fail to compile due to mutably borrowing
// the same variable twice.
let map_mutated_vars = mutated_variables(recv, cx);
let unwrap_mutated_vars = mutated_variables(unwrap_arg, cx);
if let (Some(map_mutated_vars), Some(unwrap_mutated_vars)) = (map_mutated_vars, unwrap_mutated_vars) {
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
return false;
}
} else {
return false;
}
// lint message
let msg = if is_option {
"called `map(<f>).unwrap_or_else(<g>)` on an `Option` value"
} else {
"called `map(<f>).unwrap_or_else(<g>)` on a `Result` value"
let mut unwrap_visitor = UnwrapVisitor {
cx,
identifiers: FxHashSet::default(),
};
// get snippets for args to map() and unwrap_or_else()
let map_snippet = snippet(cx, map_arg.span, "..");
let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
// lint, with note if both map() and unwrap_or_else() have the same span
if map_arg.span.eq_ctxt(unwrap_arg.span) {
let var_snippet = snippet(cx, recv.span, "..");
span_lint_and_sugg(
cx,
MAP_UNWRAP_OR,
expr.span,
msg,
"try",
format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"),
Applicability::MachineApplicable,
);
return true;
unwrap_visitor.visit_expr(unwrap_arg);
let mut reference_visitor = ReferenceVisitor {
cx,
identifiers: unwrap_visitor.identifiers,
unwrap_or_span: unwrap_arg.span,
};
let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
// Visit the body, and return if we've found a reference
if reference_visitor.visit_body(body).is_break() {
return;
}
}
false
if !unwrap_arg.span.eq_ctxt(map_span) {
return;
}
let mut applicability = Applicability::MachineApplicable;
// get snippet for unwrap_or()
let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
// lint message
let suggest_kind = if recv_ty_kind == sym::Option
&& unwrap_arg
.basic_res()
.ctor_parent(cx)
.is_lang_item(cx, LangItem::OptionNone)
{
SuggestedKind::AndThen
}
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
else if matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
if matches!(lit.node, rustc_ast::LitKind::Bool(false)))
&& msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND)
{
SuggestedKind::IsVariantAnd
} else {
SuggestedKind::Other
};
let arg = match suggest_kind {
SuggestedKind::AndThen => "None",
SuggestedKind::IsVariantAnd => "false",
SuggestedKind::Other => "<a>",
};
let suggest = match (suggest_kind, recv_ty_kind) {
(SuggestedKind::AndThen, _) => "and_then(<f>)",
(SuggestedKind::IsVariantAnd, sym::Result) => "is_ok_and(<f>)",
(SuggestedKind::IsVariantAnd, sym::Option) => "is_some_and(<f>)",
_ => "map_or(<a>, <f>)",
};
let msg = format!(
"called `map(<f>).unwrap_or({arg})` on {} `{recv_ty_kind}` value",
if recv_ty_kind == sym::Option { "an" } else { "a" }
);
span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
let map_arg_span = map_arg.span;
let mut suggestion = vec![
(
map_span,
String::from(match (suggest_kind, recv_ty_kind) {
(SuggestedKind::AndThen, _) => "and_then",
(SuggestedKind::IsVariantAnd, sym::Result) => "is_ok_and",
(SuggestedKind::IsVariantAnd, sym::Option) => "is_some_and",
(SuggestedKind::Other, _)
if unwrap_arg_ty.peel_refs().is_array()
&& cx.typeck_results().expr_ty_adjusted(unwrap_arg).peel_refs().is_slice() =>
{
return;
},
_ => "map_or",
}),
),
(expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
];
if matches!(suggest_kind, SuggestedKind::Other) {
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
}
diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability);
});
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum SuggestedKind {
AndThen,
IsVariantAnd,
Other,
}
struct UnwrapVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<HirId>,
}
impl<'tcx> Visitor<'tcx> for UnwrapVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) {
if let Res::Local(local_id) = path.res
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
{
self.identifiers.insert(local_id);
}
walk_path(self, path);
}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}
struct ReferenceVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<HirId>,
unwrap_or_span: Span,
}
impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::All;
type Result = ControlFlow<()>;
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) -> ControlFlow<()> {
// If we haven't found a reference yet, check if this references
// one of the locals that was moved in the `unwrap_or` argument.
// We are only interested in exprs that appear before the `unwrap_or` call.
if expr.span < self.unwrap_or_span
&& let ExprKind::Path(ref path) = expr.kind
&& let QPath::Resolved(_, path) = path
&& let Res::Local(local_id) = path.res
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
&& self.identifiers.contains(&local_id)
{
return ControlFlow::Break(());
}
walk_expr(self, expr)
}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}

View file

@ -0,0 +1,68 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef as _;
use clippy_utils::source::snippet;
use clippy_utils::sym;
use clippy_utils::usage::mutated_variables;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use super::MAP_UNWRAP_OR;
/// lint use of `map().unwrap_or_else()` for `Option`s and `Result`s
///
/// Is part of the `map_unwrap_or` lint, split into separate files for readability.
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx hir::Expr<'_>,
recv: &'tcx hir::Expr<'_>,
map_arg: &'tcx hir::Expr<'_>,
unwrap_arg: &'tcx hir::Expr<'_>,
msrv: Msrv,
) -> bool {
let recv_ty = cx.typeck_results().expr_ty(recv).peel_refs();
let recv_ty_kind = match recv_ty.opt_diag_name(cx) {
Some(sym::Option) => sym::Option,
Some(sym::Result) if msrv.meets(cx, msrvs::RESULT_MAP_OR_ELSE) => sym::Result,
_ => return false,
};
// Don't make a suggestion that may fail to compile due to mutably borrowing
// the same variable twice.
let Some(map_mutated_vars) = mutated_variables(recv, cx) else {
return false;
};
let Some(unwrap_mutated_vars) = mutated_variables(unwrap_arg, cx) else {
return false;
};
if map_mutated_vars.intersection(&unwrap_mutated_vars).next().is_some() {
return false;
}
// lint message
let msg = if recv_ty_kind == sym::Option {
"called `map(<f>).unwrap_or_else(<g>)` on an `Option` value"
} else {
"called `map(<f>).unwrap_or_else(<g>)` on a `Result` value"
};
// get snippets for args to map() and unwrap_or_else()
let map_snippet = snippet(cx, map_arg.span, "..");
let unwrap_snippet = snippet(cx, unwrap_arg.span, "..");
// lint, with note if both map() and unwrap_or_else() have the same span
if map_arg.span.eq_ctxt(unwrap_arg.span) {
let var_snippet = snippet(cx, recv.span, "..");
span_lint_and_sugg(
cx,
MAP_UNWRAP_OR,
expr.span,
msg,
"try",
format!("{var_snippet}.map_or_else({unwrap_snippet}, {map_snippet})"),
Applicability::MachineApplicable,
);
return true;
}
false
}

View file

@ -74,6 +74,7 @@ mod map_err_ignore;
mod map_flatten;
mod map_identity;
mod map_unwrap_or;
mod map_unwrap_or_else;
mod map_with_unused_argument_over_ranges;
mod mut_mutex_lock;
mod needless_as_bytes;
@ -89,7 +90,6 @@ mod open_options;
mod option_as_ref_cloned;
mod option_as_ref_deref;
mod option_map_or_none;
mod option_map_unwrap_or;
mod or_fun_call;
mod or_then_unwrap;
mod path_buf_push_overwrite;
@ -3933,7 +3933,8 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where f is a function or closure that returns the `bool` type.
/// Checks for usage of `option.map(f).unwrap_or_default()` and `result.map(f).unwrap_or_default()` where `f` is a function or closure that returns the `bool` type.
///
/// Also checks for equality comparisons like `option.map(f) == Some(true)` and `result.map(f) == Ok(true)`.
///
/// ### Why is this bad?
@ -5531,10 +5532,10 @@ impl Methods {
stable_sort_primitive::check(cx, expr, recv);
},
(sym::sort_by, [arg]) => {
unnecessary_sort_by::check(cx, expr, recv, arg, false);
unnecessary_sort_by::check(cx, expr, call_span, arg, false);
},
(sym::sort_unstable_by, [arg]) => {
unnecessary_sort_by::check(cx, expr, recv, arg, true);
unnecessary_sort_by::check(cx, expr, call_span, arg, true);
},
(sym::split, [arg]) => {
str_split::check(cx, expr, recv, arg);
@ -5576,7 +5577,7 @@ impl Methods {
unnecessary_fallible_conversions::check_method(cx, expr);
},
(sym::to_owned, []) => {
if !suspicious_to_owned::check(cx, expr, recv) {
if !suspicious_to_owned::check(cx, expr, span) {
implicit_clone::check(cx, name, expr, recv);
}
},
@ -5607,7 +5608,7 @@ impl Methods {
manual_saturating_arithmetic::check_unwrap_or(cx, expr, lhs, rhs, u_arg, arith);
},
Some((sym::map, m_recv, [m_arg], span, _)) => {
option_map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
map_unwrap_or::check(cx, expr, m_recv, m_arg, recv, u_arg, span, self.msrv);
},
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(
@ -5629,7 +5630,7 @@ impl Methods {
manual_saturating_arithmetic::check_sub_unwrap_or_default(cx, expr, lhs, rhs);
},
Some((sym::map, m_recv, [arg], span, _)) => {
manual_is_variant_and::check(cx, expr, m_recv, arg, span, self.msrv);
manual_is_variant_and::check_map_unwrap_or_default(cx, expr, m_recv, arg, span, self.msrv);
},
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(
@ -5648,7 +5649,7 @@ impl Methods {
(sym::unwrap_or_else, [u_arg]) => {
match method_call(recv) {
Some((sym::map, recv, [map_arg], _, _))
if map_unwrap_or::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
if map_unwrap_or_else::check(cx, expr, recv, map_arg, u_arg, self.msrv) => {},
Some((then_method @ (sym::then | sym::then_some), t_recv, [t_arg], _, _)) => {
obfuscated_if_else::check(
cx,

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::ops::ControlFlow;
use super::NEEDLESS_COLLECT;
@ -16,8 +17,8 @@ use rustc_hir::{
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
use rustc_span::Span;
use rustc_span::symbol::Ident;
use rustc_span::{Span, Symbol};
const NEEDLESS_COLLECT_MSG: &str = "avoid using `collect()` when not needed";
@ -104,16 +105,19 @@ pub(super) fn check<'tcx>(
Node::LetStmt(l) => {
if let PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None) = l.pat.kind
&& let ty = cx.typeck_results().expr_ty(collect_expr)
&& matches!(
ty.opt_diag_name(cx),
Some(sym::Vec | sym::VecDeque | sym::BinaryHeap | sym::LinkedList)
)
&& let Some(extra_spec) = ty.opt_diag_name(cx).and_then(ExtraFunctionSpec::new)
&& let iter_ty = cx.typeck_results().expr_ty(iter_expr)
&& let Some(block) = get_enclosing_block(cx, l.hir_id)
&& let Some(iter_calls) = detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty))
&& let Some((iter_calls, extra_calls)) =
detect_iter_and_into_iters(block, id, cx, get_captured_ids(cx, iter_ty), extra_spec)
&& let [iter_call] = &*iter_calls
{
let mut used_count_visitor = UsedCountVisitor { cx, id, count: 0 };
let mut used_count_visitor = UsedCountVisitor {
cx,
id,
extra_spec,
count: 0,
};
walk_block(&mut used_count_visitor, block);
if used_count_visitor.count > 1 {
return;
@ -135,11 +139,24 @@ pub(super) fn check<'tcx>(
span,
NEEDLESS_COLLECT_MSG,
|diag| {
let iter_replacement =
format!("{}{}", Sugg::hir(cx, iter_expr, ".."), iter_call.get_iter_method(cx));
let iter_snippet = Sugg::hir(cx, iter_expr, "..");
let mut iter_replacement = iter_snippet.to_string();
for extra in &extra_calls {
iter_replacement = extra.apply_iter_method(cx, &iter_replacement);
}
iter_replacement.push_str(&iter_call.get_iter_method(cx));
let mut remove_suggestions = vec![(l.span, String::new())];
remove_suggestions.extend(
extra_calls
.iter()
.flat_map(|extra| extra.span().map(|s| (s, String::new()))),
);
remove_suggestions.push((iter_call.span, iter_replacement));
diag.multipart_suggestion(
iter_call.get_suggestion_text(),
vec![(l.span, String::new()), (iter_call.span, iter_replacement)],
remove_suggestions,
Applicability::MaybeIncorrect,
);
},
@ -272,6 +289,7 @@ struct IterFunction {
func: IterFunctionKind,
span: Span,
}
impl IterFunction {
fn get_iter_method(&self, cx: &LateContext<'_>) -> String {
match &self.func {
@ -288,6 +306,7 @@ impl IterFunction {
},
}
}
fn get_suggestion_text(&self) -> &'static str {
match &self.func {
IterFunctionKind::IntoIter(_) => {
@ -305,6 +324,7 @@ impl IterFunction {
}
}
}
enum IterFunctionKind {
IntoIter(HirId),
Len,
@ -312,16 +332,119 @@ enum IterFunctionKind {
Contains(Span),
}
struct ExtraFunctionSpan {
/// Span of the function call
func_span: Span,
/// Span of the argument
arg_span: Span,
}
enum ExtraFunction {
Push {
back: Vec<ExtraFunctionSpan>,
front: Vec<ExtraFunctionSpan>,
},
Extend(ExtraFunctionSpan),
}
impl ExtraFunction {
fn apply_iter_method(&self, cx: &LateContext<'_>, inner: &str) -> String {
match &self {
ExtraFunction::Push { back, front } => {
let back_sugg = back
.iter()
.map(|span| snippet(cx, span.arg_span, ".."))
.intersperse(Cow::Borrowed(", "))
.collect::<String>();
let front = front
.iter()
.map(|span| snippet(cx, span.arg_span, ".."))
.intersperse(Cow::Borrowed(", "))
.collect::<String>();
match (front.is_empty(), back_sugg.is_empty()) {
(true, true) => inner.to_string(),
(true, false) => format!("{inner}.chain([{back_sugg}])"),
(false, true) => format!("[{front}].into_iter().chain({inner})"),
(false, false) => format!("[{front}].into_iter().chain({inner}).chain([{back_sugg}])"),
}
},
ExtraFunction::Extend(span) => {
let s = snippet(cx, span.arg_span, "..");
format!("{inner}.chain({s})")
},
}
}
fn span(&self) -> Box<dyn Iterator<Item = Span> + '_> {
match &self {
ExtraFunction::Push { back, front } => Box::new(
back.iter()
.map(|s| s.func_span)
.chain(front.iter().map(|s| s.func_span)),
),
ExtraFunction::Extend(span) => Box::new(std::iter::once(span.func_span)),
}
}
}
#[derive(Clone, Copy)]
struct ExtraFunctionPushSpec {
back: Option<Symbol>,
front: Option<Symbol>,
}
#[derive(Clone, Copy)]
struct ExtraFunctionSpec {
push_symbol: ExtraFunctionPushSpec,
extend_symbol: Option<Symbol>,
}
impl ExtraFunctionSpec {
fn new(target: Symbol) -> Option<Self> {
match target {
sym::Vec => Some(ExtraFunctionSpec {
push_symbol: ExtraFunctionPushSpec {
back: Some(sym::push),
front: None,
},
extend_symbol: Some(sym::extend),
}),
sym::VecDeque | sym::LinkedList => Some(ExtraFunctionSpec {
push_symbol: ExtraFunctionPushSpec {
back: Some(sym::push_back),
front: Some(sym::push_front),
},
extend_symbol: Some(sym::extend),
}),
sym::BinaryHeap => Some(ExtraFunctionSpec {
push_symbol: ExtraFunctionPushSpec {
back: None,
front: None,
},
extend_symbol: None,
}),
_ => None,
}
}
fn is_extra_function(self, name: Symbol) -> bool {
self.push_symbol.back == Some(name) || self.push_symbol.front == Some(name) || self.extend_symbol == Some(name)
}
}
struct IterFunctionVisitor<'a, 'tcx> {
illegal_mutable_capture_ids: HirIdSet,
current_mutably_captured_ids: HirIdSet,
cx: &'a LateContext<'tcx>,
uses: Vec<Option<IterFunction>>,
extras: Vec<ExtraFunction>,
extra_spec: ExtraFunctionSpec,
hir_id_uses_map: FxHashMap<HirId, usize>,
current_statement_hir_id: Option<HirId>,
seen_other: bool,
target: HirId,
}
impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
for (expr, hir_id) in block.stmts.iter().filter_map(get_expr_and_hir_id_from_stmt) {
@ -341,6 +464,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
}
}
#[expect(clippy::too_many_lines)]
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
// Check function calls on our collection
if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind {
@ -384,6 +508,53 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
func: IterFunctionKind::Contains(args[0].span),
span: expr.span,
})),
name if let is_push_back = self.extra_spec.push_symbol.back.is_some_and(|sym| name == sym)
&& (is_push_back || self.extra_spec.push_symbol.front.is_some_and(|sym| name == sym))
&& self.uses.is_empty() =>
{
let span = get_span_of_expr_or_parent_stmt(self.cx, expr);
match self.extras.last_mut() {
Some(ExtraFunction::Push { back, .. }) if is_push_back => {
back.push(ExtraFunctionSpan {
func_span: span,
arg_span: args[0].span,
});
},
Some(ExtraFunction::Push { front, .. }) => {
front.push(ExtraFunctionSpan {
func_span: span,
arg_span: args[0].span,
});
},
_ if is_push_back => {
self.extras.push(ExtraFunction::Push {
back: vec![ExtraFunctionSpan {
func_span: span,
arg_span: args[0].span,
}],
front: Vec::new(),
});
},
_ => {
self.extras.push(ExtraFunction::Push {
back: Vec::new(),
front: vec![ExtraFunctionSpan {
func_span: span,
arg_span: args[0].span,
}],
});
},
}
},
name if self.extra_spec.extend_symbol.is_some_and(|sym| name == sym)
&& self.uses.is_empty() =>
{
let span = get_span_of_expr_or_parent_stmt(self.cx, expr);
self.extras.push(ExtraFunction::Extend(ExtraFunctionSpan {
func_span: span,
arg_span: args[0].span,
}));
},
_ => {
self.seen_other = true;
if let Some(hir_id) = self.current_statement_hir_id {
@ -421,6 +592,16 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> {
}
}
/// If parent of the `expr` is a statement, return the span of the statement, otherwise return the
/// span of the expression.
fn get_span_of_expr_or_parent_stmt<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Span {
if let Node::Stmt(stmt) = cx.tcx.parent_hir_node(expr.hir_id) {
stmt.span
} else {
expr.span
}
}
enum LoopKind<'tcx> {
Conditional(&'tcx Expr<'tcx>),
Loop,
@ -468,6 +649,7 @@ fn get_expr_and_hir_id_from_stmt<'v>(stmt: &'v Stmt<'v>) -> Option<(&'v Expr<'v>
struct UsedCountVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
id: HirId,
extra_spec: ExtraFunctionSpec,
count: usize,
}
@ -475,11 +657,20 @@ impl<'tcx> Visitor<'tcx> for UsedCountVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
if expr.res_local_id() == Some(self.id) {
self.count += 1;
} else {
if expr.res_local_id() != Some(self.id) {
walk_expr(self, expr);
return;
}
let parent = self.cx.tcx.parent_hir_node(expr.hir_id);
if let Node::Expr(expr) = parent
&& let ExprKind::MethodCall(method_name, _, _, _) = &expr.kind
&& self.extra_spec.is_extra_function(method_name.ident.name)
{
return;
}
self.count += 1;
}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
@ -494,12 +685,15 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
id: HirId,
cx: &'a LateContext<'tcx>,
captured_ids: HirIdSet,
) -> Option<Vec<IterFunction>> {
extra_spec: ExtraFunctionSpec,
) -> Option<(Vec<IterFunction>, Vec<ExtraFunction>)> {
let mut visitor = IterFunctionVisitor {
illegal_mutable_capture_ids: captured_ids,
current_mutably_captured_ids: HirIdSet::default(),
cx,
uses: Vec::new(),
extras: Vec::new(),
extra_spec,
hir_id_uses_map: FxHashMap::default(),
current_statement_hir_id: None,
seen_other: false,
@ -509,7 +703,7 @@ fn detect_iter_and_into_iters<'tcx: 'a, 'a>(
if visitor.seen_other {
None
} else {
Some(visitor.uses.into_iter().flatten().collect())
Some((visitor.uses.into_iter().flatten().collect(), visitor.extras))
}
}

View file

@ -1,180 +0,0 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_copy;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::intravisit::{Visitor, walk_path};
use rustc_hir::{ExprKind, HirId, Node, PatKind, Path, QPath};
use rustc_lint::LateContext;
use rustc_middle::hir::nested_filter;
use rustc_span::{Span, sym};
use std::ops::ControlFlow;
use super::MAP_UNWRAP_OR;
/// lint use of `map().unwrap_or()` for `Option`s
#[expect(clippy::too_many_arguments)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &rustc_hir::Expr<'_>,
recv: &rustc_hir::Expr<'_>,
map_arg: &'tcx rustc_hir::Expr<'_>,
unwrap_recv: &rustc_hir::Expr<'_>,
unwrap_arg: &'tcx rustc_hir::Expr<'_>,
map_span: Span,
msrv: Msrv,
) {
// lint if the caller of `map()` is an `Option`
if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Option) {
if !is_copy(cx, cx.typeck_results().expr_ty(unwrap_arg)) {
// Replacing `.map(<f>).unwrap_or(<a>)` with `.map_or(<a>, <f>)` can sometimes lead to
// borrowck errors, see #10579 for one such instance.
// In particular, if `a` causes a move and `f` references that moved binding, then we cannot lint:
// ```
// let x = vec![1, 2];
// x.get(0..1).map(|s| s.to_vec()).unwrap_or(x);
// ```
// This compiles, but changing it to `map_or` will produce a compile error:
// ```
// let x = vec![1, 2];
// x.get(0..1).map_or(x, |s| s.to_vec())
// ^ moving `x` here
// ^^^^^^^^^^^ while it is borrowed here (and later used in the closure)
// ```
// So, we have to check that `a` is not referenced anywhere (even outside of the `.map` closure!)
// before the call to `unwrap_or`.
let mut unwrap_visitor = UnwrapVisitor {
cx,
identifiers: FxHashSet::default(),
};
unwrap_visitor.visit_expr(unwrap_arg);
let mut reference_visitor = ReferenceVisitor {
cx,
identifiers: unwrap_visitor.identifiers,
unwrap_or_span: unwrap_arg.span,
};
let body = cx.tcx.hir_body_owned_by(cx.tcx.hir_enclosing_body_owner(expr.hir_id));
// Visit the body, and return if we've found a reference
if reference_visitor.visit_body(body).is_break() {
return;
}
}
if !unwrap_arg.span.eq_ctxt(map_span) {
return;
}
// is_some_and is stabilised && `unwrap_or` argument is false; suggest `is_some_and` instead
let suggest_is_some_and = matches!(&unwrap_arg.kind, ExprKind::Lit(lit)
if matches!(lit.node, rustc_ast::LitKind::Bool(false)))
&& msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND);
let mut applicability = Applicability::MachineApplicable;
// get snippet for unwrap_or()
let unwrap_snippet = snippet_with_applicability(cx, unwrap_arg.span, "..", &mut applicability);
// lint message
// comparing the snippet from source to raw text ("None") below is safe
// because we already have checked the type.
let arg = if unwrap_snippet == "None" {
"None"
} else if suggest_is_some_and {
"false"
} else {
"<a>"
};
let unwrap_snippet_none = unwrap_snippet == "None";
let suggest = if unwrap_snippet_none {
"and_then(<f>)"
} else if suggest_is_some_and {
"is_some_and(<f>)"
} else {
"map_or(<a>, <f>)"
};
let msg = format!("called `map(<f>).unwrap_or({arg})` on an `Option` value");
span_lint_and_then(cx, MAP_UNWRAP_OR, expr.span, msg, |diag| {
let map_arg_span = map_arg.span;
let mut suggestion = vec![
(
map_span,
String::from(if unwrap_snippet_none {
"and_then"
} else if suggest_is_some_and {
"is_some_and"
} else {
"map_or"
}),
),
(expr.span.with_lo(unwrap_recv.span.hi()), String::new()),
];
if !unwrap_snippet_none && !suggest_is_some_and {
suggestion.push((map_arg_span.with_hi(map_arg_span.lo()), format!("{unwrap_snippet}, ")));
}
diag.multipart_suggestion(format!("use `{suggest}` instead"), suggestion, applicability);
});
}
}
struct UnwrapVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<HirId>,
}
impl<'tcx> Visitor<'tcx> for UnwrapVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::All;
fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) {
if let Res::Local(local_id) = path.res
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
{
self.identifiers.insert(local_id);
}
walk_path(self, path);
}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}
struct ReferenceVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
identifiers: FxHashSet<HirId>,
unwrap_or_span: Span,
}
impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::All;
type Result = ControlFlow<()>;
fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) -> ControlFlow<()> {
// If we haven't found a reference yet, check if this references
// one of the locals that was moved in the `unwrap_or` argument.
// We are only interested in exprs that appear before the `unwrap_or` call.
if expr.span < self.unwrap_or_span
&& let ExprKind::Path(ref path) = expr.kind
&& let QPath::Resolved(_, path) = path
&& let Res::Local(local_id) = path.res
&& let Node::Pat(pat) = self.cx.tcx.hir_node(local_id)
&& let PatKind::Binding(_, local_id, ..) = pat.kind
&& self.identifiers.contains(&local_id)
{
return ControlFlow::Break(());
}
rustc_hir::intravisit::walk_expr(self, expr)
}
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}

View file

@ -1,15 +1,14 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_context;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_middle::ty::print::with_forced_trimmed_paths;
use rustc_span::sym;
use rustc_span::{Span, sym};
use super::SUSPICIOUS_TO_OWNED;
pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -> bool {
pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, method_span: Span) -> bool {
if cx
.typeck_results()
.type_dependent_def_id(expr.hir_id)
@ -18,28 +17,22 @@ pub fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) -
&& let input_type = cx.typeck_results().expr_ty(expr)
&& input_type.is_diag_item(cx, sym::Cow)
{
let mut app = Applicability::MaybeIncorrect;
let recv_snip = snippet_with_context(cx, recv.span, expr.span.ctxt(), "..", &mut app).0;
let app = Applicability::MaybeIncorrect;
span_lint_and_then(
cx,
SUSPICIOUS_TO_OWNED,
expr.span,
with_forced_trimmed_paths!(format!(
"this `to_owned` call clones the {input_type} itself and does not cause the {input_type} contents to become owned"
"this `to_owned` call clones the `{input_type}` itself and does not cause its contents to become owned"
)),
|diag| {
diag.span_suggestion(
expr.span,
"depending on intent, either make the Cow an Owned variant",
format!("{recv_snip}.into_owned()"),
app,
);
diag.span_suggestion(
expr.span,
"or clone the Cow itself",
format!("{recv_snip}.clone()"),
method_span,
"depending on intent, either make the `Cow` an `Owned` variant",
"into_owned".to_string(),
app,
);
diag.span_suggestion(method_span, "or clone the `Cow` itself", "clone".to_string(), app);
},
);
return true;

View file

@ -8,7 +8,7 @@ use clippy_utils::sugg::{Sugg, make_binop};
use clippy_utils::ty::{implements_trait, is_copy};
use clippy_utils::visitors::is_local_used;
use clippy_utils::{get_parent_expr, is_from_proc_macro};
use rustc_ast::LitKind::Bool;
use rustc_ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, PatKind};
use rustc_lint::LateContext;
@ -48,13 +48,14 @@ pub(super) fn check<'a>(
let ExprKind::Lit(def_kind) = def.kind else {
return;
};
let recv_ty = cx.typeck_results().expr_ty_adjusted(recv);
let Bool(def_bool) = def_kind.node else {
let LitKind::Bool(def_bool) = def_kind.node else {
return;
};
let typeck = cx.typeck_results();
let recv_ty = typeck.expr_ty_adjusted(recv);
let variant = match recv_ty.opt_diag_name(cx) {
Some(sym::Option) => Variant::Some,
Some(sym::Result) => Variant::Ok,
@ -63,12 +64,12 @@ pub(super) fn check<'a>(
let ext_def_span = def.span.until(map.span);
let (sugg, method, applicability) = if cx.typeck_results().expr_adjustments(recv).is_empty()
let (sugg, method, applicability): (_, Cow<'_, _>, _) = if typeck.expr_adjustments(recv).is_empty()
&& let ExprKind::Closure(map_closure) = map.kind
&& let closure_body = cx.tcx.hir_body(map_closure.body)
&& let closure_body_value = closure_body.value.peel_blocks()
&& let ExprKind::Binary(op, l, r) = closure_body_value.kind
&& let Some(param) = closure_body.params.first()
&& let [param] = closure_body.params
&& let PatKind::Binding(_, hir_id, _, _) = param.pat.kind
// checking that map_or is one of the following:
// .map_or(false, |x| x == y)
@ -78,14 +79,13 @@ pub(super) fn check<'a>(
&& ((BinOpKind::Eq == op.node && !def_bool) || (BinOpKind::Ne == op.node && def_bool))
&& let non_binding_location = if l.res_local_id() == Some(hir_id) { r } else { l }
&& switch_to_eager_eval(cx, non_binding_location)
// if its both then that's a strange edge case and
// if it's both then that's a strange edge case and
// we can just ignore it, since by default clippy will error on this
&& (l.res_local_id() == Some(hir_id)) != (r.res_local_id() == Some(hir_id))
&& !is_local_used(cx, non_binding_location, hir_id)
&& let typeck_results = cx.typeck_results()
&& let l_ty = typeck_results.expr_ty(l)
&& l_ty == typeck_results.expr_ty(r)
&& let Some(partial_eq) = cx.tcx.get_diagnostic_item(sym::PartialEq)
&& let l_ty = typeck.expr_ty(l)
&& l_ty == typeck.expr_ty(r)
&& let Some(partial_eq) = cx.tcx.lang_items().eq_trait()
&& implements_trait(cx, recv_ty, partial_eq, &[recv_ty.into()])
&& is_copy(cx, l_ty)
{
@ -97,12 +97,12 @@ pub(super) fn check<'a>(
// being converted to `Some(5) == Some(5).then(|| 1)` isn't
// the same thing
let mut app = Applicability::MachineApplicable;
let inner_non_binding = Sugg::NonParen(Cow::Owned(format!(
"{wrap}({})",
Sugg::hir(cx, non_binding_location, "")
Sugg::hir_with_applicability(cx, non_binding_location, "", &mut app)
)));
let mut app = Applicability::MachineApplicable;
let binop = make_binop(
op.node,
&Sugg::hir_with_applicability(cx, recv, "..", &mut app),
@ -126,18 +126,18 @@ pub(super) fn check<'a>(
}
.into_string();
(vec![(expr.span, sugg)], "a standard comparison", app)
(vec![(expr.span, sugg)], "a standard comparison".into(), app)
} else if !def_bool && msrv.meets(cx, msrvs::OPTION_RESULT_IS_VARIANT_AND) {
let suggested_name = variant.method_name();
(
vec![(method_span, suggested_name.into()), (ext_def_span, String::default())],
suggested_name,
vec![(method_span, suggested_name.into()), (ext_def_span, String::new())],
format!("`{suggested_name}`").into(),
Applicability::MachineApplicable,
)
} else if def_bool && matches!(variant, Variant::Some) && msrv.meets(cx, msrvs::IS_NONE_OR) {
(
vec![(method_span, "is_none_or".into()), (ext_def_span, String::default())],
"is_none_or",
vec![(method_span, "is_none_or".into()), (ext_def_span, String::new())],
"`is_none_or`".into(),
Applicability::MachineApplicable,
)
} else {

View file

@ -1,47 +1,51 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::std_or_core;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::implements_trait;
use clippy_utils::ty::{implements_trait, is_copy, peel_n_ty_refs};
use rustc_data_structures::fx::FxHashMap;
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_span::sym;
use rustc_middle::ty::{self, GenericArgKind, Ty};
use rustc_span::symbol::Ident;
use rustc_span::{Span, sym};
use std::iter;
use std::ops::Not;
use super::UNNECESSARY_SORT_BY;
enum LintTrigger {
Sort(SortDetection),
Sort,
SortByKey(SortByKeyDetection),
}
struct SortDetection {
vec_name: String,
}
struct SortByKeyDetection {
vec_name: String,
closure_arg: String,
closure_arg: Span,
closure_body: String,
reverse: bool,
applicability: Applicability,
}
/// Detect if the two expressions are mirrored (identical, except one
/// contains a and the other replaces it with b)
fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool {
match (&a_expr.kind, &b_expr.kind) {
fn mirrored_exprs(
a_expr: &Expr<'_>,
b_expr: &Expr<'_>,
binding_map: &BindingMap,
binding_source: BindingSource,
) -> bool {
match (a_expr.kind, b_expr.kind) {
// Two arrays with mirrored contents
(ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => {
iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
},
(ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => iter::zip(left_exprs, right_exprs)
.all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source)),
// The two exprs are function calls.
// Check to see that the function itself and its arguments are mirrored
(ExprKind::Call(left_expr, left_args), ExprKind::Call(right_expr, right_args)) => {
mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
&& iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
mirrored_exprs(left_expr, right_expr, binding_map, binding_source)
&& iter::zip(left_args, right_args)
.all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source))
},
// The two exprs are method calls.
// Check to see that the function is the same and the arguments and receivers are mirrored
@ -50,116 +54,219 @@ fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident
ExprKind::MethodCall(right_segment, right_receiver, right_args, _),
) => {
left_segment.ident == right_segment.ident
&& iter::zip(*left_args, *right_args).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
&& mirrored_exprs(left_receiver, a_ident, right_receiver, b_ident)
&& iter::zip(left_args, right_args)
.all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source))
&& mirrored_exprs(left_receiver, right_receiver, binding_map, binding_source)
},
// Two tuples with mirrored contents
(ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => {
iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
},
(ExprKind::Tup(left_exprs), ExprKind::Tup(right_exprs)) => iter::zip(left_exprs, right_exprs)
.all(|(left, right)| mirrored_exprs(left, right, binding_map, binding_source)),
// Two binary ops, which are the same operation and which have mirrored arguments
(ExprKind::Binary(left_op, left_left, left_right), ExprKind::Binary(right_op, right_left, right_right)) => {
left_op.node == right_op.node
&& mirrored_exprs(left_left, a_ident, right_left, b_ident)
&& mirrored_exprs(left_right, a_ident, right_right, b_ident)
&& mirrored_exprs(left_left, right_left, binding_map, binding_source)
&& mirrored_exprs(left_right, right_right, binding_map, binding_source)
},
// Two unary ops, which are the same operation and which have the same argument
(ExprKind::Unary(left_op, left_expr), ExprKind::Unary(right_op, right_expr)) => {
left_op == right_op && mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
left_op == right_op && mirrored_exprs(left_expr, right_expr, binding_map, binding_source)
},
// The two exprs are literals of some kind
(ExprKind::Lit(left_lit), ExprKind::Lit(right_lit)) => left_lit.node == right_lit.node,
(ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(left, a_ident, right, b_ident),
(ExprKind::Cast(left, _), ExprKind::Cast(right, _)) => mirrored_exprs(left, right, binding_map, binding_source),
(ExprKind::DropTemps(left_block), ExprKind::DropTemps(right_block)) => {
mirrored_exprs(left_block, a_ident, right_block, b_ident)
mirrored_exprs(left_block, right_block, binding_map, binding_source)
},
(ExprKind::Field(left_expr, left_ident), ExprKind::Field(right_expr, right_ident)) => {
left_ident.name == right_ident.name && mirrored_exprs(left_expr, a_ident, right_expr, right_ident)
left_ident.name == right_ident.name && mirrored_exprs(left_expr, right_expr, binding_map, binding_source)
},
// Two paths: either one is a and the other is b, or they're identical to each other
(
ExprKind::Path(QPath::Resolved(
_,
Path {
&Path {
segments: left_segments,
..
},
)),
ExprKind::Path(QPath::Resolved(
_,
Path {
&Path {
segments: right_segments,
..
},
)),
) => {
(iter::zip(*left_segments, *right_segments).all(|(left, right)| left.ident == right.ident)
&& left_segments
.iter()
.all(|seg| &seg.ident != a_ident && &seg.ident != b_ident))
(iter::zip(left_segments, right_segments).all(|(left, right)| left.ident == right.ident)
&& left_segments.iter().all(|seg| {
!binding_map.contains_key(&BindingKey {
ident: seg.ident,
source: BindingSource::Left,
}) && !binding_map.contains_key(&BindingKey {
ident: seg.ident,
source: BindingSource::Right,
})
}))
|| (left_segments.len() == 1
&& &left_segments[0].ident == a_ident
&& right_segments.len() == 1
&& &right_segments[0].ident == b_ident)
&& binding_map
.get(&BindingKey {
ident: left_segments[0].ident,
source: binding_source,
})
.is_some_and(|value| value.mirrored.ident == right_segments[0].ident))
},
// Matching expressions, but one or both is borrowed
(
ExprKind::AddrOf(left_kind, Mutability::Not, left_expr),
ExprKind::AddrOf(right_kind, Mutability::Not, right_expr),
) => left_kind == right_kind && mirrored_exprs(left_expr, a_ident, right_expr, b_ident),
(_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => mirrored_exprs(a_expr, a_ident, right_expr, b_ident),
(ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => mirrored_exprs(left_expr, a_ident, b_expr, b_ident),
) => left_kind == right_kind && mirrored_exprs(left_expr, right_expr, binding_map, binding_source),
(_, ExprKind::AddrOf(_, Mutability::Not, right_expr)) => {
mirrored_exprs(a_expr, right_expr, binding_map, binding_source)
},
(ExprKind::AddrOf(_, Mutability::Not, left_expr), _) => {
mirrored_exprs(left_expr, b_expr, binding_map, binding_source)
},
_ => false,
}
}
fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Expr<'_>) -> Option<LintTrigger> {
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
enum BindingSource {
Left,
Right,
}
impl Not for BindingSource {
type Output = BindingSource;
fn not(self) -> Self::Output {
match self {
BindingSource::Left => BindingSource::Right,
BindingSource::Right => BindingSource::Left,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct BindingKey {
/// The identifier of the binding.
ident: Ident,
/// The source of the binding.
source: BindingSource,
}
struct BindingValue {
/// The mirrored binding.
mirrored: BindingKey,
/// The number of refs the binding is wrapped in.
n_refs: usize,
}
/// A map from binding info to the number of refs the binding is wrapped in.
type BindingMap = FxHashMap<BindingKey, BindingValue>;
/// Extract the binding pairs, if the two patterns are mirrored. The pats are assumed to be used in
/// closure inputs and thus irrefutable.
fn mapping_of_mirrored_pats(a_pat: &Pat<'_>, b_pat: &Pat<'_>) -> Option<BindingMap> {
fn mapping_of_mirrored_pats_inner(
a_pat: &Pat<'_>,
b_pat: &Pat<'_>,
mapping: &mut BindingMap,
n_refs: usize,
) -> bool {
match (&a_pat.kind, &b_pat.kind) {
(PatKind::Tuple(a_pats, a_dots), PatKind::Tuple(b_pats, b_dots)) => {
a_dots == b_dots
&& a_pats.len() == b_pats.len()
&& iter::zip(a_pats.iter(), b_pats.iter())
.all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs))
},
(PatKind::Binding(_, _, a_ident, _), PatKind::Binding(_, _, b_ident, _)) => {
let a_key = BindingKey {
ident: *a_ident,
source: BindingSource::Left,
};
let b_key = BindingKey {
ident: *b_ident,
source: BindingSource::Right,
};
let a_value = BindingValue {
mirrored: b_key,
n_refs,
};
let b_value = BindingValue {
mirrored: a_key,
n_refs,
};
mapping.insert(a_key, a_value);
mapping.insert(b_key, b_value);
true
},
(PatKind::Wild, PatKind::Wild) => true,
(PatKind::TupleStruct(_, a_pats, a_dots), PatKind::TupleStruct(_, b_pats, b_dots)) => {
a_dots == b_dots
&& a_pats.len() == b_pats.len()
&& iter::zip(a_pats.iter(), b_pats.iter())
.all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs))
},
(PatKind::Struct(_, a_fields, a_rest), PatKind::Struct(_, b_fields, b_rest)) => {
a_rest == b_rest
&& a_fields.len() == b_fields.len()
&& iter::zip(a_fields.iter(), b_fields.iter()).all(|(a_field, b_field)| {
a_field.ident == b_field.ident
&& mapping_of_mirrored_pats_inner(a_field.pat, b_field.pat, mapping, n_refs)
})
},
(PatKind::Ref(a_inner, _, _), PatKind::Ref(b_inner, _, _)) => {
mapping_of_mirrored_pats_inner(a_inner, b_inner, mapping, n_refs + 1)
},
(PatKind::Slice(a_elems, None, a_rest), PatKind::Slice(b_elems, None, b_rest)) => {
a_elems.len() == b_elems.len()
&& iter::zip(a_elems.iter(), b_elems.iter())
.all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs))
&& a_rest.len() == b_rest.len()
&& iter::zip(a_rest.iter(), b_rest.iter())
.all(|(a, b)| mapping_of_mirrored_pats_inner(a, b, mapping, n_refs))
},
_ => false,
}
}
let mut mapping = FxHashMap::default();
if mapping_of_mirrored_pats_inner(a_pat, b_pat, &mut mapping, 0) {
return Some(mapping);
}
None
}
fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>) -> Option<LintTrigger> {
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
&& cx.tcx.type_of(impl_id).instantiate_identity().is_slice()
&& let ExprKind::Closure(&Closure { body, .. }) = arg.kind
&& let closure_body = cx.tcx.hir_body(body)
&& let &[
Param {
pat:
Pat {
kind: PatKind::Binding(_, _, left_ident, _),
..
},
..
},
Param {
pat:
Pat {
kind: PatKind::Binding(_, _, right_ident, _),
..
},
..
},
] = &closure_body.params
&& let &[Param { pat: l_pat, .. }, Param { pat: r_pat, .. }] = closure_body.params
&& let Some(binding_map) = mapping_of_mirrored_pats(l_pat, r_pat)
&& let ExprKind::MethodCall(method_path, left_expr, [right_expr], _) = closure_body.value.kind
&& method_path.ident.name == sym::cmp
&& cx
.ty_based_def(closure_body.value)
.opt_parent(cx)
.is_diag_item(cx, sym::Ord)
&& let Some(ord_trait) = cx.tcx.get_diagnostic_item(sym::Ord)
&& cx.ty_based_def(closure_body.value).opt_parent(cx).opt_def_id() == Some(ord_trait)
{
let (closure_body, closure_arg, reverse) = if mirrored_exprs(left_expr, left_ident, right_expr, right_ident) {
(
Sugg::hir(cx, left_expr, "..").to_string(),
left_ident.name.to_string(),
false,
)
} else if mirrored_exprs(left_expr, right_ident, right_expr, left_ident) {
(
Sugg::hir(cx, left_expr, "..").to_string(),
right_ident.name.to_string(),
true,
)
let (closure_body, closure_arg, reverse) =
if mirrored_exprs(left_expr, right_expr, &binding_map, BindingSource::Left) {
(left_expr, l_pat.span, false)
} else if mirrored_exprs(left_expr, right_expr, &binding_map, BindingSource::Right) {
(left_expr, r_pat.span, true)
} else {
return None;
};
let mut applicability = if reverse {
Applicability::MaybeIncorrect
} else {
return None;
Applicability::MachineApplicable
};
let vec_name = Sugg::hir(cx, recv, "..").to_string();
if let ExprKind::Path(QPath::Resolved(
_,
@ -167,22 +274,53 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp
segments: [PathSegment { ident: left_name, .. }],
..
},
)) = &left_expr.kind
&& left_name == left_ident
&& cx
.tcx
.get_diagnostic_item(sym::Ord)
.is_some_and(|id| implements_trait(cx, cx.typeck_results().expr_ty(left_expr), id, &[]))
)) = left_expr.kind
{
return Some(LintTrigger::Sort(SortDetection { vec_name }));
if let PatKind::Binding(_, _, left_ident, _) = l_pat.kind
&& *left_name == left_ident
&& implements_trait(cx, cx.typeck_results().expr_ty(left_expr), ord_trait, &[])
{
return Some(LintTrigger::Sort);
}
let mut left_expr_ty = cx.typeck_results().expr_ty(left_expr);
let left_ident_n_refs = binding_map
.get(&BindingKey {
ident: *left_name,
source: BindingSource::Left,
})
.map_or(0, |value| value.n_refs);
// Peel off the outer-most ref which is introduced by the closure, if it is not already peeled
// by the pattern
if left_ident_n_refs == 0 {
(left_expr_ty, _) = peel_n_ty_refs(left_expr_ty, 1);
}
if !reverse && is_copy(cx, left_expr_ty) {
let mut closure_body =
snippet_with_applicability(cx, closure_body.span, "_", &mut applicability).to_string();
if left_ident_n_refs == 0 {
closure_body = format!("*{closure_body}");
}
return Some(LintTrigger::SortByKey(SortByKeyDetection {
closure_arg,
closure_body,
reverse,
applicability,
}));
}
}
if !expr_borrows(cx, left_expr) {
let left_expr_ty = cx.typeck_results().expr_ty(left_expr);
if !expr_borrows(left_expr_ty)
// Don't lint if the closure is accessing non-Copy fields
&& (!expr_is_field_access(left_expr) || is_copy(cx, left_expr_ty))
{
let closure_body = Sugg::hir_with_applicability(cx, closure_body, "_", &mut applicability).to_string();
return Some(LintTrigger::SortByKey(SortByKeyDetection {
vec_name,
closure_arg,
closure_body,
reverse,
applicability,
}));
}
}
@ -190,61 +328,75 @@ fn detect_lint(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, arg: &Exp
None
}
fn expr_borrows(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
let ty = cx.typeck_results().expr_ty(expr);
fn expr_borrows(ty: Ty<'_>) -> bool {
matches!(ty.kind(), ty::Ref(..)) || ty.walk().any(|arg| matches!(arg.kind(), GenericArgKind::Lifetime(_)))
}
fn expr_is_field_access(expr: &Expr<'_>) -> bool {
match expr.kind {
ExprKind::Field(_, _) => true,
ExprKind::AddrOf(_, Mutability::Not, inner) => expr_is_field_access(inner),
_ => false,
}
}
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &'tcx Expr<'_>,
recv: &'tcx Expr<'_>,
call_span: Span,
arg: &'tcx Expr<'_>,
is_unstable: bool,
) {
match detect_lint(cx, expr, recv, arg) {
match detect_lint(cx, expr, arg) {
Some(LintTrigger::SortByKey(trigger)) => {
let method = if is_unstable {
"sort_unstable_by_key"
} else {
"sort_by_key"
};
span_lint_and_sugg(
let Some(std_or_core) = std_or_core(cx) else {
// To make it this far the crate has to reference diagnostic items defined in core. Either this is
// the `core` crate, there's an `extern crate core` somewhere, or another crate is defining the
// diagnostic items. It's fine to not lint in all those cases even if we might be able to.
return;
};
span_lint_and_then(
cx,
UNNECESSARY_SORT_BY,
expr.span,
format!("consider using `{method}`"),
"try",
format!(
"{}.{}(|{}| {})",
trigger.vec_name,
method,
trigger.closure_arg,
if let Some(std_or_core) = std_or_core(cx)
&& trigger.reverse
{
format!("{}::cmp::Reverse({})", std_or_core, trigger.closure_body)
|diag| {
let mut app = trigger.applicability;
let closure_body = if trigger.reverse {
format!("{std_or_core}::cmp::Reverse({})", trigger.closure_body)
} else {
trigger.closure_body
},
),
if trigger.reverse {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
let closure_arg = snippet_with_applicability(cx, trigger.closure_arg, "_", &mut app);
diag.span_suggestion_verbose(
call_span,
"try",
format!("{method}(|{closure_arg}| {closure_body})"),
app,
);
},
);
},
Some(LintTrigger::Sort(trigger)) => {
Some(LintTrigger::Sort) => {
let method = if is_unstable { "sort_unstable" } else { "sort" };
span_lint_and_sugg(
span_lint_and_then(
cx,
UNNECESSARY_SORT_BY,
expr.span,
format!("consider using `{method}`"),
"try",
format!("{}.{}()", trigger.vec_name, method),
Applicability::MachineApplicable,
|diag| {
diag.span_suggestion_verbose(
call_span,
"try",
format!("{method}()"),
Applicability::MachineApplicable,
);
},
);
},
None => {},

View file

@ -1,6 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_lint_allowed;
use clippy_utils::macros::span_is_local;
use clippy_utils::source::snippet_opt;
use rustc_hir::def_id::DefIdSet;
use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
@ -84,7 +85,14 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods {
cx.tcx.def_span(item.owner_id),
format!("missing trait method provided by default: `{}`", assoc.name()),
|diag| {
diag.span_help(cx.tcx.def_span(assoc.def_id), "implement the method");
if assoc.def_id.is_local() {
diag.span_help(cx.tcx.def_span(assoc.def_id), "implement the method");
} else if let Some(snippet) = snippet_opt(cx, of_trait.trait_ref.path.span) {
diag.help(format!(
"implement the missing `{}` method of the `{snippet}` trait",
assoc.name(),
));
}
},
);
}

View file

@ -1,9 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::higher;
use clippy_utils::source::{indent_of, snippet_block, snippet_with_context};
use rustc_ast::Label;
use rustc_errors::Applicability;
use rustc_hir::{Block, Expr, ExprKind, LoopSource, StmtKind};
use rustc_hir::{Block, Expr, ExprKind, HirId, LoopSource, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::{ExpnKind, Span};
@ -336,14 +336,9 @@ fn emit_warning(cx: &LateContext<'_>, data: &LintData<'_>, header: &str, typ: Li
data.if_expr,
),
};
span_lint_and_help(
cx,
NEEDLESS_CONTINUE,
expr.span,
message,
None,
format!("{header}\n{snip}"),
);
span_lint_hir_and_then(cx, NEEDLESS_CONTINUE, expr.hir_id, expr.span, message, |diag| {
diag.help(format!("{header}\n{snip}"));
});
}
fn suggestion_snippet_for_continue_inside_if(cx: &LateContext<'_>, data: &LintData<'_>) -> String {
@ -424,11 +419,11 @@ fn suggestion_snippet_for_continue_inside_else(cx: &LateContext<'_>, data: &Lint
fn check_last_stmt_in_expr<F>(cx: &LateContext<'_>, inner_expr: &Expr<'_>, func: &F)
where
F: Fn(Option<&Label>, Span),
F: Fn(HirId, Option<&Label>, Span),
{
match inner_expr.kind {
ExprKind::Continue(continue_label) => {
func(continue_label.label.as_ref(), inner_expr.span);
func(inner_expr.hir_id, continue_label.label.as_ref(), inner_expr.span);
},
ExprKind::If(_, then_block, else_block) if let ExprKind::Block(then_block, _) = then_block.kind => {
check_last_stmt_in_block(cx, then_block, func);
@ -454,7 +449,7 @@ where
fn check_last_stmt_in_block<F>(cx: &LateContext<'_>, b: &Block<'_>, func: &F)
where
F: Fn(Option<&Label>, Span),
F: Fn(HirId, Option<&Label>, Span),
{
if let Some(expr) = b.expr {
check_last_stmt_in_expr(cx, expr, func);
@ -470,15 +465,17 @@ where
fn check_and_warn(cx: &LateContext<'_>, expr: &Expr<'_>) {
with_loop_block(expr, |loop_block, label| {
let p = |continue_label: Option<&Label>, span: Span| {
let p = |continue_hir_id, continue_label: Option<&Label>, span: Span| {
if compare_labels(label, continue_label) {
span_lint_and_help(
span_lint_hir_and_then(
cx,
NEEDLESS_CONTINUE,
continue_hir_id,
span,
MSG_REDUNDANT_CONTINUE_EXPRESSION,
None,
DROP_CONTINUE_EXPRESSION_MSG,
|diag| {
diag.help(DROP_CONTINUE_EXPRESSION_MSG);
},
);
}
};

View file

@ -33,7 +33,10 @@ impl ArithmeticSideEffects {
allowed_binary.extend([
("f32", FxHashSet::from_iter(["f32"])),
("f64", FxHashSet::from_iter(["f64"])),
("std::string::String", FxHashSet::from_iter(["str"])),
(
"std::string::String",
FxHashSet::from_iter(["str", "std::string::String"]),
),
]);
for (lhs, rhs) in &conf.arithmetic_side_effects_allowed_binary {
allowed_binary.entry(lhs).or_default().insert(rhs);

View file

@ -39,6 +39,18 @@ pub(super) fn check(cx: &LateContext<'_>, op: BinOpKind, lhs: &Expr<'_>, rhs: &E
| (BinOpKind::And, BinOpKind::Ge, BinOpKind::Le) => {
"=="
},
// x != y && x >= y => x > y
(BinOpKind::And, BinOpKind::Ne, BinOpKind::Ge)
// x >= y && x != y => x > y
| (BinOpKind::And, BinOpKind::Ge, BinOpKind::Ne) => {
">"
},
// x != y && x <= y => x < y
(BinOpKind::And, BinOpKind::Ne, BinOpKind::Le)
// x <= y && x != y => x < y
| (BinOpKind::And, BinOpKind::Le, BinOpKind::Ne) => {
"<"
},
_ => return,
};

View file

@ -47,6 +47,10 @@ declare_clippy_lint! {
/// }
/// }
/// ```
///
/// ### Note
/// Clippy can only lint compiled code. For this lint to trigger, you must configure `cargo clippy`
/// to include test compilation, for instance, by using flags such as `--tests` or `--all-targets`.
#[clippy::version = "1.88.0"]
pub REDUNDANT_TEST_PREFIX,
restriction,

View file

@ -2,6 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::MaybeResPath;
use clippy_utils::source::{indent_of, snippet};
use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym};
use rustc_ast::BindingMode;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
@ -89,13 +90,14 @@ impl<'tcx> LateLintPass<'tcx> for SignificantDropTightening<'tcx> {
|diag| {
match apa.counter {
0 | 1 => {},
2 => {
2 if let Some(last_method_span) = apa.last_method_span => {
let indent = " ".repeat(indent_of(cx, apa.last_stmt_span).unwrap_or(0));
let init_method = snippet(cx, apa.first_method_span, "..");
let usage_method = snippet(cx, apa.last_method_span, "..");
let stmt = if let Some(last_bind_ident) = apa.last_bind_ident {
let usage_method = snippet(cx, last_method_span, "..");
let stmt = if let Some((binding_mode, last_bind_ident)) = apa.last_bind_ident {
format!(
"\n{indent}let {} = {init_method}.{usage_method};",
"\n{indent}let {}{} = {init_method}.{usage_method};",
binding_mode.prefix_str(),
snippet(cx, last_bind_ident.span, ".."),
)
} else {
@ -310,13 +312,13 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> {
};
match self.ap.curr_stmt.kind {
hir::StmtKind::Let(local) => {
if let hir::PatKind::Binding(_, _, ident, _) = local.pat.kind {
apa.last_bind_ident = Some(ident);
if let hir::PatKind::Binding(binding_mode, _, ident, _) = local.pat.kind {
apa.last_bind_ident = Some((binding_mode, ident));
}
if let Some(local_init) = local.init
&& let hir::ExprKind::MethodCall(_, _, _, span) = local_init.kind
{
apa.last_method_span = span;
apa.last_method_span = Some(span);
}
},
hir::StmtKind::Semi(semi_expr) => {
@ -326,7 +328,7 @@ impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> {
return;
}
if let hir::ExprKind::MethodCall(_, _, _, span) = semi_expr.kind {
apa.last_method_span = span;
apa.last_method_span = Some(span);
}
},
_ => {},
@ -385,9 +387,9 @@ struct AuxParamsAttr {
/// The last visited binding or variable span within a block that had any referenced inner type
/// marked with `#[has_significant_drop]`.
last_bind_ident: Option<Ident>,
last_bind_ident: Option<(BindingMode, Ident)>,
/// Similar to `last_bind_span` but encompasses the right-hand method call.
last_method_span: Span,
last_method_span: Option<Span>,
/// Similar to `last_bind_span` but encompasses the whole contained statement.
last_stmt_span: Span,
}
@ -403,7 +405,7 @@ impl Default for AuxParamsAttr {
first_method_span: DUMMY_SP,
first_stmt_span: DUMMY_SP,
last_bind_ident: None,
last_method_span: DUMMY_SP,
last_method_span: None,
last_stmt_span: DUMMY_SP,
}
}

View file

@ -150,7 +150,9 @@ impl SlowVectorInit {
&& func.ty_rel_def(cx).is_diag_item(cx, sym::vec_with_capacity)
{
Some(InitializedSize::Initialized(len_expr))
} else if matches!(expr.kind, ExprKind::Call(func, []) if func.ty_rel_def(cx).is_diag_item(cx, sym::vec_new)) {
} else if let ExprKind::Call(func, []) = expr.kind
&& func.ty_rel_def(cx).is_diag_item(cx, sym::vec_new)
{
Some(InitializedSize::Uninitialized)
} else {
None

View file

@ -1,4 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::res::MaybeDef;
use clippy_utils::source::snippet_with_context;
use clippy_utils::visitors::is_expr_unsafe;
@ -6,12 +8,12 @@ use clippy_utils::{match_libc_symbol, sym};
use rustc_errors::Applicability;
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, LangItem, Node, UnsafeSource};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
declare_clippy_lint! {
/// ### What it does
/// Checks for usage of `libc::strlen` on a `CString` or `CStr` value,
/// and suggest calling `as_bytes().len()` or `to_bytes().len()` respectively instead.
/// and suggest calling `count_bytes()` instead.
///
/// ### Why is this bad?
/// libc::strlen is an unsafe function, which we don't need to call
@ -27,15 +29,25 @@ declare_clippy_lint! {
/// ```rust, no_run
/// use std::ffi::CString;
/// let cstring = CString::new("foo").expect("CString::new failed");
/// let len = cstring.as_bytes().len();
/// let len = cstring.count_bytes();
/// ```
#[clippy::version = "1.55.0"]
pub STRLEN_ON_C_STRINGS,
complexity,
"using `libc::strlen` on a `CString` or `CStr` value, while `as_bytes().len()` or `to_bytes().len()` respectively can be used instead"
"using `libc::strlen` on a `CString` or `CStr` value, while `count_bytes()` can be used instead"
}
declare_lint_pass!(StrlenOnCStrings => [STRLEN_ON_C_STRINGS]);
pub struct StrlenOnCStrings {
msrv: Msrv,
}
impl StrlenOnCStrings {
pub fn new(conf: &Conf) -> Self {
Self { msrv: conf.msrv }
}
}
impl_lint_pass!(StrlenOnCStrings => [STRLEN_ON_C_STRINGS]);
impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
@ -47,7 +59,21 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings {
&& let ExprKind::MethodCall(path, self_arg, [], _) = recv.kind
&& !recv.span.from_expansion()
&& path.ident.name == sym::as_ptr
&& let typeck = cx.typeck_results()
&& typeck
.expr_ty_adjusted(self_arg)
.peel_refs()
.is_lang_item(cx, LangItem::CStr)
{
let ty = typeck.expr_ty(self_arg).peel_refs();
let ty_kind = if ty.is_diag_item(cx, sym::cstring_type) {
"`CString` value"
} else if ty.is_lang_item(cx, LangItem::CStr) {
"`CStr` value"
} else {
"type that dereferences to `CStr`"
};
let ctxt = expr.span.ctxt();
let span = match cx.tcx.parent_hir_node(expr.hir_id) {
Node::Block(&Block {
@ -58,25 +84,23 @@ impl<'tcx> LateLintPass<'tcx> for StrlenOnCStrings {
_ => expr.span,
};
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
let mut app = Applicability::MachineApplicable;
let val_name = snippet_with_context(cx, self_arg.span, ctxt, "..", &mut app).0;
let method_name = if ty.is_diag_item(cx, sym::cstring_type) {
"as_bytes"
} else if ty.is_lang_item(cx, LangItem::CStr) {
"to_bytes"
} else {
return;
};
span_lint_and_sugg(
span_lint_and_then(
cx,
STRLEN_ON_C_STRINGS,
span,
"using `libc::strlen` on a `CString` or `CStr` value",
"try",
format!("{val_name}.{method_name}().len()"),
app,
format!("using `libc::strlen` on a {ty_kind}"),
|diag| {
let mut app = Applicability::MachineApplicable;
let val_name = snippet_with_context(cx, self_arg.span, ctxt, "_", &mut app).0;
let suggestion = if self.msrv.meets(cx, msrvs::CSTR_COUNT_BYTES) {
format!("{val_name}.count_bytes()")
} else {
format!("{val_name}.to_bytes().len()")
};
diag.span_suggestion(span, "use", suggestion, app);
},
);
}
}

View file

@ -42,6 +42,18 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, arg: &'t
return true;
}
// Catching:
// `std::mem::transmute(std::ptr::without_provenance::<i32>(0))`
// `std::mem::transmute(std::ptr::without_provenance_mut::<i32>(0))`
if let ExprKind::Call(func1, [arg1]) = arg.kind
&& (func1.basic_res().is_diag_item(cx, sym::ptr_without_provenance)
|| func1.basic_res().is_diag_item(cx, sym::ptr_without_provenance_mut))
&& is_integer_const(cx, arg1, 0)
{
span_lint(cx, TRANSMUTING_NULL, expr.span, LINT_MSG);
return true;
}
// Catching:
// `std::mem::transmute({ 0 as *const u64 })` and similar const blocks
if let ExprKind::Block(block, _) = arg.kind

View file

@ -115,6 +115,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
&& !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
&& !is_unsafe_from_proc_macro(cx, block.span)
&& !block_has_safety_comment(cx, block.span, self.accept_comment_above_attributes)
&& !block_has_inner_safety_comment(cx, block.span)
&& !block_parents_have_safety_comment(
self.accept_comment_above_statement,
self.accept_comment_above_attributes,
@ -839,6 +840,23 @@ fn text_has_safety_comment(
}
}
}
// Check for a comment that appears after other code on the same line (e.g., `let x = // SAFETY:`)
// This handles cases in macros where the comment is on the same line as preceding code.
// We only check the first (immediate preceding) line for this pattern.
// Only whitespace is allowed between the comment marker and `SAFETY:`.
if let Some(comment_start) = [line.find("//"), line.find("/*")].into_iter().flatten().min()
&& let after_marker = &line[comment_start + 2..] // skip marker
&& let trimmed = after_marker.trim_start() // skip whitespace
&& trimmed.get(..7).is_some_and(|s| s.eq_ignore_ascii_case("SAFETY:"))
{
let safety_offset = 2 + (after_marker.len() - trimmed.len());
return HasSafetyComment::Yes(
start_pos
+ BytePos(u32::try_from(line_start).unwrap())
+ BytePos(u32::try_from(comment_start + safety_offset).unwrap()),
false,
);
}
// No line comments; look for the start of a block comment.
// This will only find them if they are at the start of a line.
let (mut line_start, mut line) = (line_start, line);
@ -894,3 +912,20 @@ fn is_const_or_static(node: &Node<'_>) -> bool {
})
)
}
fn block_has_inner_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
let source_map = cx.sess().source_map();
if let Ok(src) = source_map.span_to_snippet(span)
&& let Some(after_brace) = src
.strip_prefix("unsafe")
.and_then(|s| s.trim_start().strip_prefix('{'))
&& let Some(comment) = after_brace
.trim_start()
.strip_prefix("//")
.or_else(|| after_brace.trim_start().strip_prefix("/*"))
{
comment.trim_start().to_ascii_uppercase().starts_with("SAFETY:")
} else {
false
}
}

View file

@ -152,7 +152,12 @@ fn insert_necessary_parens(pat: &mut Pat) {
walk_pat(self, pat);
let target = match &mut pat.kind {
// `i @ a | b`, `box a | b`, and `& mut? a | b`.
Ident(.., Some(p)) | Box(p) | Ref(p, _, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p,
Ident(.., Some(p)) | Box(p) | Ref(p, _, _)
if let Or(ps) = &p.kind
&& ps.len() > 1 =>
{
p
},
// `&(mut x)`
Ref(p, Pinnedness::Not, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p,
_ => return,

View file

@ -365,7 +365,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
format!("useless conversion to the same type: `{b}`"),
"consider removing `.into_iter()`",
sugg,
Applicability::MachineApplicable, // snippet
applicability,
);
}
}

View file

@ -325,7 +325,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
ConstArgKind::Infer(..) => chain!(self, "let ConstArgKind::Infer(..) = {const_arg}.kind"),
ConstArgKind::Error(..) => chain!(self, "let ConstArgKind::Error(..) = {const_arg}.kind"),
ConstArgKind::Tup(..) => chain!(self, "let ConstArgKind::Tup(..) = {const_arg}.kind"),
ConstArgKind::Literal(..) => chain!(self, "let ConstArgKind::Literal(..) = {const_arg}.kind")
ConstArgKind::Literal(..) => chain!(self, "let ConstArgKind::Literal(..) = {const_arg}.kind"),
}
}

View file

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt, sym};
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
use std::borrow::{Borrow, Cow};
@ -88,33 +88,42 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
&& let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind
&& let ExprKind::Path(..) = recv.kind
{
let and_then_snippets =
get_and_then_snippets(cx, call_cx.span, call_lint.span, call_sp.span, call_msg.span);
let mut app = Applicability::MachineApplicable;
let expr_ctxt = expr.span.ctxt();
let and_then_snippets = get_and_then_snippets(
cx,
expr_ctxt,
call_cx.span,
call_lint.span,
call_sp.span,
call_msg.span,
&mut app,
);
let mut sle = SpanlessEq::new(cx).deny_side_effects();
match ps.ident.as_str() {
"span_suggestion" if sle.eq_expr(call_sp, &span_call_args[0]) => {
suggest_suggestion(
cx,
expr,
&and_then_snippets,
&span_suggestion_snippets(cx, span_call_args),
);
match ps.ident.name {
sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => {
let snippets = span_suggestion_snippets(cx, expr_ctxt, span_call_args, &mut app);
suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app);
},
"span_help" if sle.eq_expr(call_sp, &span_call_args[0]) => {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => {
let help_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true, app);
},
"span_note" if sle.eq_expr(call_sp, &span_call_args[0]) => {
let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => {
let note_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true, app);
},
"help" => {
let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
sym::help => {
let help_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false, app);
},
"note" => {
let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
sym::note => {
let note_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false, app);
},
_ => (),
}
@ -122,24 +131,26 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
}
}
struct AndThenSnippets<'a> {
cx: Cow<'a, str>,
lint: Cow<'a, str>,
span: Cow<'a, str>,
msg: Cow<'a, str>,
struct AndThenSnippets {
cx: Cow<'static, str>,
lint: Cow<'static, str>,
span: Cow<'static, str>,
msg: Cow<'static, str>,
}
fn get_and_then_snippets(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
cx_span: Span,
lint_span: Span,
span_span: Span,
msg_span: Span,
) -> AndThenSnippets<'static> {
let cx_snippet = snippet(cx, cx_span, "cx");
let lint_snippet = snippet(cx, lint_span, "..");
let span_snippet = snippet(cx, span_span, "span");
let msg_snippet = snippet(cx, msg_span, r#""...""#);
app: &mut Applicability,
) -> AndThenSnippets {
let cx_snippet = snippet_with_applicability(cx, cx_span, "cx", app);
let lint_snippet = snippet_with_applicability(cx, lint_span, "..", app);
let span_snippet = snippet_with_applicability(cx, span_span, "span", app);
let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0;
AndThenSnippets {
cx: cx_snippet,
@ -149,19 +160,22 @@ fn get_and_then_snippets(
}
}
struct SpanSuggestionSnippets<'a> {
help: Cow<'a, str>,
sugg: Cow<'a, str>,
applicability: Cow<'a, str>,
struct SpanSuggestionSnippets {
help: Cow<'static, str>,
sugg: Cow<'static, str>,
applicability: Cow<'static, str>,
}
fn span_suggestion_snippets<'a, 'hir>(
fn span_suggestion_snippets<'hir>(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
span_call_args: &'hir [Expr<'hir>],
) -> SpanSuggestionSnippets<'a> {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
let sugg_snippet = snippet(cx, span_call_args[2].span, "..");
let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable");
app: &mut Applicability,
) -> SpanSuggestionSnippets {
let help_snippet = snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, app).0;
let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, expr_ctxt, "..", app).0;
let applicability_snippet =
snippet_with_applicability(cx, span_call_args[3].span, "Applicability::MachineApplicable", app);
SpanSuggestionSnippets {
help: help_snippet,
@ -173,8 +187,9 @@ fn span_suggestion_snippets<'a, 'hir>(
fn suggest_suggestion(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
and_then_snippets: &AndThenSnippets,
span_suggestion_snippets: &SpanSuggestionSnippets,
app: Applicability,
) {
span_lint_and_sugg(
cx,
@ -192,16 +207,17 @@ fn suggest_suggestion(
span_suggestion_snippets.sugg,
span_suggestion_snippets.applicability
),
Applicability::MachineApplicable,
app,
);
}
fn suggest_help(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
and_then_snippets: &AndThenSnippets,
help: &str,
with_span: bool,
app: Applicability,
) {
let option_span = if with_span {
format!("Some({})", and_then_snippets.span)
@ -219,16 +235,17 @@ fn suggest_help(
"span_lint_and_help({}, {}, {}, {}, {}, {help})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span,
),
Applicability::MachineApplicable,
app,
);
}
fn suggest_note(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
and_then_snippets: &AndThenSnippets,
note: &str,
with_span: bool,
app: Applicability,
) {
let note_span = if with_span {
format!("Some({})", and_then_snippets.span)
@ -246,6 +263,6 @@ fn suggest_note(
"span_lint_and_note({}, {}, {}, {}, {note_span}, {note})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg,
),
Applicability::MachineApplicable,
app,
);
}

View file

@ -2,7 +2,7 @@
#![allow(
clippy::missing_docs_in_private_items,
clippy::must_use_candidate,
clippy::symbol_as_str,
clippy::symbol_as_str
)]
#![warn(
trivial_casts,
@ -29,7 +29,7 @@ extern crate rustc_session;
extern crate rustc_span;
mod almost_standard_lint_formulation;
mod collapsible_calls;
mod collapsible_span_lint_calls;
mod derive_deserialize_allowing_unknown;
mod internal_paths;
mod lint_without_lint_pass;
@ -46,7 +46,7 @@ use rustc_lint::{Lint, LintStore};
static LINTS: &[&Lint] = &[
almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION,
collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
collapsible_span_lint_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN,
lint_without_lint_pass::DEFAULT_LINT,
lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE,
@ -67,7 +67,7 @@ pub fn register_lints(store: &mut LintStore) {
store.register_early_pass(|| Box::new(unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths));
store.register_early_pass(|| Box::new(produce_ice::ProduceIce));
store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls));
store.register_late_pass(|_| Box::new(collapsible_span_lint_calls::CollapsibleCalls));
store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown));
store.register_late_pass(|_| Box::<symbols::Symbols>::default());
store.register_late_pass(|_| Box::<lint_without_lint_pass::LintWithoutLintPass>::default());

View file

@ -1,6 +1,6 @@
[package]
name = "clippy_utils"
version = "0.1.94"
version = "0.1.95"
edition = "2024"
description = "Helpful tools for writing lints, provided as they are used in Clippy"
repository = "https://github.com/rust-lang/rust-clippy"

View file

@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain:
<!-- begin autogenerated nightly -->
```
nightly-2026-01-08
nightly-2026-01-22
```
<!-- end autogenerated nightly -->

View file

@ -159,7 +159,10 @@ impl LimitStack {
}
pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) {
let stack = &mut self.stack;
parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val)));
parse_attrs(sess, attrs, name, |val| {
let popped = stack.pop();
debug_assert_eq!(popped, Some(val));
});
}
}

View file

@ -505,7 +505,7 @@ impl HirEqInterExpr<'_, '_, '_> {
(ExprKind::Block(l, _), ExprKind::Block(r, _)) => self.eq_block(l, r),
(ExprKind::Binary(l_op, ll, lr), ExprKind::Binary(r_op, rl, rr)) => {
l_op.node == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
|| swap_binop(l_op.node, ll, lr).is_some_and(|(l_op, ll, lr)| {
|| swap_binop(self.inner.cx, l_op.node, ll, lr).is_some_and(|(l_op, ll, lr)| {
l_op == r_op.node && self.eq_expr(ll, rl) && self.eq_expr(lr, rr)
})
},
@ -667,7 +667,7 @@ impl HirEqInterExpr<'_, '_, '_> {
match (&left.kind, &right.kind) {
(ConstArgKind::Tup(l_t), ConstArgKind::Tup(r_t)) => {
l_t.len() == r_t.len() && l_t.iter().zip(*r_t).all(|(l_c, r_c)| self.eq_const_arg(*l_c, *r_c))
l_t.len() == r_t.len() && l_t.iter().zip(*r_t).all(|(l_c, r_c)| self.eq_const_arg(l_c, r_c))
},
(ConstArgKind::Path(l_p), ConstArgKind::Path(r_p)) => self.eq_qpath(l_p, r_p),
(ConstArgKind::Anon(l_an), ConstArgKind::Anon(r_an)) => self.eq_body(l_an.body, r_an.body),
@ -689,9 +689,12 @@ impl HirEqInterExpr<'_, '_, '_> {
(ConstArgKind::Literal(kind_l), ConstArgKind::Literal(kind_r)) => kind_l == kind_r,
(ConstArgKind::Array(l_arr), ConstArgKind::Array(r_arr)) => {
l_arr.elems.len() == r_arr.elems.len()
&& l_arr.elems.iter().zip(r_arr.elems.iter())
.all(|(l_elem, r_elem)| self.eq_const_arg(l_elem, r_elem))
}
&& l_arr
.elems
.iter()
.zip(r_arr.elems.iter())
.all(|(l_elem, r_elem)| self.eq_const_arg(l_elem, r_elem))
},
// Use explicit match for now since ConstArg is undergoing flux.
(
ConstArgKind::Path(..)
@ -955,26 +958,35 @@ fn reduce_exprkind<'hir>(cx: &LateContext<'_>, kind: &'hir ExprKind<'hir>) -> &'
}
fn swap_binop<'a>(
cx: &LateContext<'_>,
binop: BinOpKind,
lhs: &'a Expr<'a>,
rhs: &'a Expr<'a>,
) -> Option<(BinOpKind, &'a Expr<'a>, &'a Expr<'a>)> {
match binop {
BinOpKind::Add | BinOpKind::Eq | BinOpKind::Ne | BinOpKind::BitAnd | BinOpKind::BitXor | BinOpKind::BitOr => {
Some((binop, rhs, lhs))
},
// `==` and `!=`, are commutative
BinOpKind::Eq | BinOpKind::Ne => Some((binop, rhs, lhs)),
// Comparisons can be reversed
BinOpKind::Lt => Some((BinOpKind::Gt, rhs, lhs)),
BinOpKind::Le => Some((BinOpKind::Ge, rhs, lhs)),
BinOpKind::Ge => Some((BinOpKind::Le, rhs, lhs)),
BinOpKind::Gt => Some((BinOpKind::Lt, rhs, lhs)),
BinOpKind::Mul // Not always commutative, e.g. with matrices. See issue #5698
| BinOpKind::Shl
| BinOpKind::Shr
| BinOpKind::Rem
| BinOpKind::Sub
| BinOpKind::Div
// Non-commutative operators
BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Rem | BinOpKind::Sub | BinOpKind::Div => None,
// We know that those operators are commutative for primitive types,
// and we don't assume anything for other types
BinOpKind::Mul
| BinOpKind::Add
| BinOpKind::And
| BinOpKind::Or => None,
| BinOpKind::Or
| BinOpKind::BitAnd
| BinOpKind::BitXor
| BinOpKind::BitOr => cx
.typeck_results()
.expr_ty_adjusted(lhs)
.peel_refs()
.is_primitive()
.then_some((binop, rhs, lhs)),
}
}
@ -1564,7 +1576,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
match &const_arg.kind {
ConstArgKind::Tup(tup) => {
for arg in *tup {
self.hash_const_arg(*arg);
self.hash_const_arg(arg);
}
},
ConstArgKind::Path(path) => self.hash_qpath(path),

View file

@ -6,11 +6,7 @@
#![feature(assert_matches)]
#![feature(unwrap_infallible)]
#![recursion_limit = "512"]
#![allow(
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::must_use_candidate,
)]
#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)]
#![warn(
trivial_casts,
trivial_numeric_casts,
@ -2028,12 +2024,12 @@ pub fn is_expr_temporary_value(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
}
pub fn std_or_core(cx: &LateContext<'_>) -> Option<&'static str> {
if !is_no_std_crate(cx) {
Some("std")
} else if !is_no_core_crate(cx) {
if is_no_core_crate(cx) {
None
} else if is_no_std_crate(cx) {
Some("core")
} else {
None
Some("std")
}
}

View file

@ -23,6 +23,7 @@ macro_rules! msrv_aliases {
// names may refer to stabilized feature flags or library items
msrv_aliases! {
1,91,0 { DURATION_FROM_MINUTES_HOURS }
1,88,0 { LET_CHAINS }
1,87,0 { OS_STR_DISPLAY, INT_MIDPOINT, CONST_CHAR_IS_DIGIT, UNSIGNED_IS_MULTIPLE_OF, INTEGER_SIGN_CAST }
1,85,0 { UINT_FLOAT_MIDPOINT, CONST_SIZE_OF_VAL }
@ -31,7 +32,7 @@ msrv_aliases! {
1,82,0 { IS_NONE_OR, REPEAT_N, RAW_REF_OP, SPECIALIZED_TO_STRING_FOR_REFS }
1,81,0 { LINT_REASONS_STABILIZATION, ERROR_IN_CORE, EXPLICIT_SELF_TYPE_ELISION, DURATION_ABS_DIFF }
1,80,0 { BOX_INTO_ITER, LAZY_CELL }
1,79,0 { CONST_BLOCKS }
1,79,0 { CONST_BLOCKS, CSTR_COUNT_BYTES }
1,77,0 { C_STR_LITERALS }
1,76,0 { PTR_FROM_REF, OPTION_RESULT_INSPECT }
1,75,0 { OPTION_AS_SLICE }
@ -61,7 +62,7 @@ msrv_aliases! {
1,45,0 { STR_STRIP_PREFIX }
1,43,0 { LOG2_10, LOG10_2, NUMERIC_ASSOCIATED_CONSTANTS }
1,42,0 { MATCHES_MACRO, SLICE_PATTERNS, PTR_SLICE_RAW_PARTS }
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR_ELSE }
1,41,0 { RE_REBALANCING_COHERENCE, RESULT_MAP_OR, RESULT_MAP_OR_ELSE }
1,40,0 { MEM_TAKE, NON_EXHAUSTIVE, OPTION_AS_DEREF }
1,38,0 { POINTER_CAST, REM_EUCLID }
1,37,0 { TYPE_ALIAS_ENUM_VARIANTS }
@ -69,12 +70,12 @@ msrv_aliases! {
1,35,0 { OPTION_COPIED, RANGE_CONTAINS }
1,34,0 { TRY_FROM }
1,33,0 { UNDERSCORE_IMPORTS }
1,32,0 { CONST_IS_POWER_OF_TWO }
1,32,0 { CONST_IS_POWER_OF_TWO, CONST_DURATION_FROM_NANOS_MICROS_MILLIS_SECS }
1,31,0 { OPTION_REPLACE }
1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES }
1,29,0 { ITER_FLATTEN }
1,28,0 { FROM_BOOL, REPEAT_WITH, SLICE_FROM_REF }
1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND }
1,27,0 { ITERATOR_TRY_FOLD, DOUBLE_ENDED_ITERATOR_RFIND, DURATION_FROM_NANOS_MICROS }
1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN, POINTER_ADD_SUB_METHODS }
1,24,0 { IS_ASCII_DIGIT, PTR_NULL }
1,18,0 { HASH_MAP_RETAIN, HASH_SET_RETAIN }
@ -82,6 +83,7 @@ msrv_aliases! {
1,16,0 { STR_REPEAT, RESULT_UNWRAP_OR_DEFAULT }
1,15,0 { MAYBE_BOUND_IN_WHERE }
1,13,0 { QUESTION_MARK_OPERATOR }
1,3,0 { DURATION_FROM_MILLIS_SECS }
}
/// `#[clippy::msrv]` attributes are rarely used outside of Clippy's test suite, as a basic

View file

@ -139,6 +139,7 @@ generate! {
disallowed_types,
drain,
dump,
duration_constructors,
ends_with,
enum_glob_use,
enumerate,
@ -163,12 +164,20 @@ generate! {
from_be_bytes,
from_bytes_with_nul,
from_bytes_with_nul_unchecked,
from_days,
from_hours,
from_le_bytes,
from_micros,
from_millis,
from_mins,
from_nanos,
from_ne_bytes,
from_ptr,
from_raw,
from_raw_parts,
from_secs,
from_str_radix,
from_weeks,
fs,
fuse,
futures_util,
@ -179,6 +188,7 @@ generate! {
get_unchecked,
get_unchecked_mut,
has_significant_drop,
help,
hidden_glob_reexports,
hygiene,
ilog,
@ -271,6 +281,8 @@ generate! {
powi,
product,
push,
push_back,
push_front,
push_str,
read,
read_exact,
@ -325,7 +337,10 @@ generate! {
sort,
sort_by,
sort_unstable_by,
span_help,
span_lint_and_then,
span_note,
span_suggestion,
split,
split_at,
split_at_checked,

View file

@ -441,6 +441,21 @@ pub fn peel_and_count_ty_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize, Option<Mutabili
(ty, count, mutbl)
}
/// Peels off `n` references on the type. Returns the underlying type and, if any references
/// were removed, whether the pointer is ultimately mutable or not.
pub fn peel_n_ty_refs(mut ty: Ty<'_>, n: usize) -> (Ty<'_>, Option<Mutability>) {
let mut mutbl = None;
for _ in 0..n {
if let ty::Ref(_, dest_ty, m) = ty.kind() {
ty = *dest_ty;
mutbl.replace(mutbl.map_or(*m, |mutbl: Mutability| mutbl.min(*m)));
} else {
break;
}
}
(ty, mutbl)
}
/// Checks whether `a` and `b` are same types having same `Const` generic args, but ignores
/// lifetimes.
///
@ -818,7 +833,7 @@ impl AdtVariantInfo {
.enumerate()
.map(|(i, f)| (i, approx_ty_size(cx, f.ty(cx.tcx, subst))))
.collect::<Vec<_>>();
fields_size.sort_by(|(_, a_size), (_, b_size)| a_size.cmp(b_size));
fields_size.sort_by_key(|(_, a_size)| *a_size);
Self {
ind: i,
@ -827,7 +842,7 @@ impl AdtVariantInfo {
}
})
.collect::<Vec<_>>();
variants_size.sort_by(|a, b| b.size.cmp(&a.size));
variants_size.sort_by_key(|b| std::cmp::Reverse(b.size));
variants_size
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "declare_clippy_lint"
version = "0.1.94"
version = "0.1.95"
edition = "2024"
repository = "https://github.com/rust-lang/rust-clippy"
license = "MIT OR Apache-2.0"

View file

@ -281,8 +281,7 @@ impl CrateWithSource {
CrateSource::Path { path } => {
fn is_cache_dir(entry: &DirEntry) -> bool {
fs::read(entry.path().join("CACHEDIR.TAG"))
.map(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
.unwrap_or(false)
.is_ok_and(|x| x.starts_with(b"Signature: 8a477f597d28d172789f06886806bc55"))
}
// copy path into the dest_crate_root but skip directories that contain a CACHEDIR.TAG file.

View file

@ -1,6 +1,6 @@
[toolchain]
# begin autogenerated nightly
channel = "nightly-2026-01-08"
channel = "nightly-2026-01-22"
# end autogenerated nightly
components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"]
profile = "minimal"

View file

@ -30,11 +30,10 @@ use rustc_span::symbol::Symbol;
use std::env;
use std::fs::read_to_string;
use std::io::Write as _;
use std::path::Path;
use std::process::exit;
use anstream::println;
/// If a command-line option matches `find_arg`, then apply the predicate `pred` on its value. If
/// true, then return it. The parameter is assumed to be either `--arg=value` or `--arg value`.
fn arg_value<'a>(args: &'a [String], find_arg: &str, pred: impl Fn(&str) -> bool) -> Option<&'a str> {
@ -184,7 +183,9 @@ impl rustc_driver::Callbacks for ClippyCallbacks {
}
fn display_help() {
println!("{}", help_message());
if writeln!(&mut anstream::stdout().lock(), "{}", help_message()).is_err() {
exit(rustc_driver::EXIT_FAILURE);
}
}
const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/new?template=ice.yml";
@ -251,8 +252,10 @@ pub fn main() {
if orig_args.iter().any(|a| a == "--version" || a == "-V") {
let version_info = rustc_tools_util::get_version_info!();
println!("{version_info}");
exit(0);
match writeln!(&mut anstream::stdout().lock(), "{version_info}") {
Ok(()) => exit(rustc_driver::EXIT_SUCCESS),
Err(_) => exit(rustc_driver::EXIT_FAILURE),
}
}
// Setting RUSTC_WRAPPER causes Cargo to pass 'rustc' as the first argument.

View file

@ -4,19 +4,24 @@
// warn on lints, that are included in `rust-lang/rust`s bootstrap
#![warn(rust_2018_idioms, unused_lifetimes)]
use std::env;
use std::path::PathBuf;
use std::process::{self, Command};
extern crate rustc_driver;
use anstream::println;
use std::env;
use std::io::Write as _;
use std::path::PathBuf;
use std::process::{self, Command, exit};
fn show_help() {
println!("{}", help_message());
if writeln!(&mut anstream::stdout().lock(), "{}", help_message()).is_err() {
exit(rustc_driver::EXIT_FAILURE);
}
}
fn show_version() {
let version_info = rustc_tools_util::get_version_info!();
println!("{version_info}");
if writeln!(&mut anstream::stdout().lock(), "{version_info}").is_err() {
exit(rustc_driver::EXIT_FAILURE);
}
}
pub fn main() {

View file

@ -50,6 +50,15 @@ impl EarlyLintPass for Pass {
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
db.help(help_msg).help(help_msg);
});
// Issue #15880
#[expect(clippy::disallowed_names)]
let foo = "foo";
span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable);
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}"));
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}"));
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}"));
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}"));
}
}

View file

@ -65,6 +65,35 @@ impl EarlyLintPass for Pass {
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
db.help(help_msg).help(help_msg);
});
// Issue #15880
#[expect(clippy::disallowed_names)]
let foo = "foo";
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_suggestion(
expr.span,
format!("try using {foo}"),
format!("{foo}.use"),
Applicability::MachineApplicable,
);
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_help(expr.span, format!("try using {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.help(format!("try using {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_note(expr.span, format!("required because of {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.note(format!("required because of {foo}"));
});
}
}

View file

@ -49,5 +49,53 @@ LL | | db.note(note_msg);
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)`
error: aborting due to 5 previous errors
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:72:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_suggestion(
LL | | expr.span,
... |
LL | | );
LL | | });
| |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable)`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:81:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_help(expr.span, format!("try using {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:85:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.help(format!("try using {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:89:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_note(expr.span, format!("required because of {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:93:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.note(format!("required because of {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}"))`
error: aborting due to 10 previous errors

View file

@ -1,15 +1,39 @@
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:270:19
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:244:22
|
LL | let _x = unsafe { 1 };
| ^^^^^^^^^^^^
...
LL | t!();
| ---- in this macro invocation
|
= help: consider adding a safety comment on the preceding line
= note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]`
= note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:256:13
|
LL | unsafe { 1 };
| ^^^^^^^^^^^^
...
LL | t!();
| ---- in this macro invocation
|
= help: consider adding a safety comment on the preceding line
= note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:337:19
|
LL | /* Safety: */ unsafe {}
| ^^^^^^^^^
|
= help: consider adding a safety comment on the preceding line
= note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]`
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:275:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:342:5
|
LL | unsafe {}
| ^^^^^^^^^
@ -17,7 +41,7 @@ LL | unsafe {}
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:14
|
LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
| ^^^^^^^^^^^^^
@ -25,7 +49,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:29
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:29
|
LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
| ^^^^^^^^^^^^^
@ -33,7 +57,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:48
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:48
|
LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
| ^^^^^^^^^^^^^
@ -41,7 +65,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:18
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:18
|
LL | let _ = (42, unsafe {}, "test", unsafe {});
| ^^^^^^^^^
@ -49,7 +73,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {});
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:37
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:37
|
LL | let _ = (42, unsafe {}, "test", unsafe {});
| ^^^^^^^^^
@ -57,7 +81,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {});
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:293:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:14
|
LL | let _ = *unsafe { &42 };
| ^^^^^^^^^^^^^^
@ -65,7 +89,7 @@ LL | let _ = *unsafe { &42 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:299:19
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:366:19
|
LL | let _ = match unsafe {} {
| ^^^^^^^^^
@ -73,7 +97,7 @@ LL | let _ = match unsafe {} {
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:306:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:14
|
LL | let _ = &unsafe {};
| ^^^^^^^^^
@ -81,7 +105,7 @@ LL | let _ = &unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:311:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:378:14
|
LL | let _ = [unsafe {}; 5];
| ^^^^^^^^^
@ -89,7 +113,7 @@ LL | let _ = [unsafe {}; 5];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:316:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:383:13
|
LL | let _ = unsafe {};
| ^^^^^^^^^
@ -97,7 +121,7 @@ LL | let _ = unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:327:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:394:8
|
LL | t!(unsafe {});
| ^^^^^^^^^
@ -105,7 +129,7 @@ LL | t!(unsafe {});
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:334:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:401:13
|
LL | unsafe {}
| ^^^^^^^^^
@ -117,7 +141,7 @@ LL | t!();
= note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:343:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:410:5
|
LL | unsafe {} // SAFETY:
| ^^^^^^^^^
@ -125,7 +149,7 @@ LL | unsafe {} // SAFETY:
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:349:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:416:5
|
LL | unsafe {
| ^^^^^^^^
@ -133,7 +157,7 @@ LL | unsafe {
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:427:5
|
LL | unsafe {};
| ^^^^^^^^^
@ -141,7 +165,7 @@ LL | unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:365:20
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:432:20
|
LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -149,7 +173,7 @@ LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:440:5
|
LL | unsafe impl A for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -157,7 +181,7 @@ LL | unsafe impl A for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:381:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:448:9
|
LL | unsafe impl B for (u32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -165,7 +189,7 @@ LL | unsafe impl B for (u32) {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:403:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:470:13
|
LL | unsafe impl T for $t {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -177,7 +201,7 @@ LL | no_safety_comment!(());
= note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13
|
LL | unsafe impl T for $t {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -189,7 +213,7 @@ LL | no_safety_comment!(());
= note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:439:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:506:5
|
LL | unsafe impl T for (i32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -197,7 +221,7 @@ LL | unsafe impl T for (i32) {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13
|
LL | unsafe impl T for $t {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -209,7 +233,7 @@ LL | no_safety_comment!(u32);
= note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:446:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:513:5
|
LL | unsafe impl T for (bool) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -217,7 +241,7 @@ LL | unsafe impl T for (bool) {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:493:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:560:5
|
LL | unsafe impl NoComment for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -225,7 +249,7 @@ LL | unsafe impl NoComment for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:498:19
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:565:19
|
LL | /* SAFETY: */ unsafe impl InlineComment for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -233,7 +257,7 @@ LL | /* SAFETY: */ unsafe impl InlineComment for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:503:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:570:5
|
LL | unsafe impl TrailingComment for () {} // SAFETY:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -241,13 +265,13 @@ LL | unsafe impl TrailingComment for () {} // SAFETY:
= help: consider adding a safety comment on the preceding line
error: constant has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:508:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:575:5
|
LL | const BIG_NUMBER: i32 = 1000000;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:574:8
|
LL | // SAFETY:
| ^^^^^^^
@ -255,7 +279,7 @@ LL | // SAFETY:
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]`
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:510:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:577:5
|
LL | unsafe impl Interference for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -263,7 +287,7 @@ LL | unsafe impl Interference for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:518:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:585:5
|
LL | unsafe impl ImplInFn for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -271,7 +295,7 @@ LL | unsafe impl ImplInFn for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:528:1
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:595:1
|
LL | unsafe impl CrateRoot for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -279,7 +303,7 @@ LL | unsafe impl CrateRoot for () {}
= help: consider adding a safety comment on the preceding line
error: statement has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:543:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:610:5
|
LL | / let _ = {
LL | |
@ -289,13 +313,13 @@ LL | | };
| |______^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:609:8
|
LL | // SAFETY: this is more than one level away, so it should warn
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:545:12
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:612:12
|
LL | if unsafe { true } {
| ^^^^^^^^^^^^^^^
@ -303,7 +327,7 @@ LL | if unsafe { true } {
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:549:23
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:616:23
|
LL | let bar = unsafe {};
| ^^^^^^^^^
@ -311,7 +335,7 @@ LL | let bar = unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:638:52
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:705:52
|
LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 };
| ^^^^^^^^^^^^
@ -319,7 +343,7 @@ LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:647:41
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:714:41
|
LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 };
| ^^^^^^^^^^^^
@ -327,7 +351,7 @@ LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:657:42
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:724:42
|
LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 };
| ^^^^^^^^^^^^
@ -335,7 +359,7 @@ LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:662:40
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:729:40
|
LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 };
| ^^^^^^^^^^^^
@ -343,136 +367,136 @@ LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 };
= help: consider adding a safety comment on the preceding line
error: constant has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:701:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:768:5
|
LL | const UNIX_EPOCH_JULIAN_DAY: i32 =
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:699:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:766:8
|
LL | // SAFETY: fail ONLY if `accept-comment-above-attribute = false`
| ^^^^^^^
error: statement has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:721:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:788:5
|
LL | _ = bar();
| ^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:720:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:8
|
LL | // SAFETY: unnecessary_safety_comment triggers here
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: module has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:741:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:808:5
|
LL | mod x {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:807:8
|
LL | // SAFETY: ...
| ^^^^^^^
error: module has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:746:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:813:5
|
LL | mod y {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:744:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:811:8
|
LL | // SAFETY: ...
| ^^^^^^^
error: module has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:751:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:818:5
|
LL | mod z {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:750:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:817:8
|
LL | // SAFETY: ...
| ^^^^^^^
error: module has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:759:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:826:5
|
LL | mod y {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:757:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:824:8
|
LL | // SAFETY: ...
| ^^^^^^^
error: statement has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:774:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:841:9
|
LL | let x = 34;
| ^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:772:12
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:839:12
|
LL | // SAFETY: ...
| ^^^^^^^^^^^
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:848:5
|
LL | unsafe fn unsafe_comment() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider changing the `safety` comment for a `# Safety` doc comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:847:8
|
LL | // SAFETY: Bla
| ^^^^^^^
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:854:5
|
LL | unsafe fn unsafe_block_comment() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider changing the `safety` comment for a `# Safety` doc comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:852:8
|
LL | SAFETY: Bla
| ^^^^^^^
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:858:5
|
LL | fn safe_comment() {}
| ^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:857:8
|
LL | // SAFETY: Bla
| ^^^^^^^
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:862:5
|
LL | fn safe_doc_comment() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:861:9
|
LL | /// SAFETY: Bla
| ^^^^^^^
error: aborting due to 50 previous errors
error: aborting due to 52 previous errors

View file

@ -1,15 +1,39 @@
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:270:19
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:244:22
|
LL | let _x = unsafe { 1 };
| ^^^^^^^^^^^^
...
LL | t!();
| ---- in this macro invocation
|
= help: consider adding a safety comment on the preceding line
= note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]`
= note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:256:13
|
LL | unsafe { 1 };
| ^^^^^^^^^^^^
...
LL | t!();
| ---- in this macro invocation
|
= help: consider adding a safety comment on the preceding line
= note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:337:19
|
LL | /* Safety: */ unsafe {}
| ^^^^^^^^^
|
= help: consider adding a safety comment on the preceding line
= note: `-D clippy::undocumented-unsafe-blocks` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::undocumented_unsafe_blocks)]`
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:275:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:342:5
|
LL | unsafe {}
| ^^^^^^^^^
@ -17,7 +41,7 @@ LL | unsafe {}
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:14
|
LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
| ^^^^^^^^^^^^^
@ -25,7 +49,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:29
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:29
|
LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
| ^^^^^^^^^^^^^
@ -33,7 +57,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:280:48
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:347:48
|
LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
| ^^^^^^^^^^^^^
@ -41,7 +65,7 @@ LL | let _ = [unsafe { 14 }, unsafe { 15 }, 42, unsafe { 16 }];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:18
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:18
|
LL | let _ = (42, unsafe {}, "test", unsafe {});
| ^^^^^^^^^
@ -49,7 +73,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {});
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:287:37
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:354:37
|
LL | let _ = (42, unsafe {}, "test", unsafe {});
| ^^^^^^^^^
@ -57,7 +81,7 @@ LL | let _ = (42, unsafe {}, "test", unsafe {});
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:293:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:14
|
LL | let _ = *unsafe { &42 };
| ^^^^^^^^^^^^^^
@ -65,7 +89,7 @@ LL | let _ = *unsafe { &42 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:299:19
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:366:19
|
LL | let _ = match unsafe {} {
| ^^^^^^^^^
@ -73,7 +97,7 @@ LL | let _ = match unsafe {} {
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:306:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:14
|
LL | let _ = &unsafe {};
| ^^^^^^^^^
@ -81,7 +105,7 @@ LL | let _ = &unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:311:14
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:378:14
|
LL | let _ = [unsafe {}; 5];
| ^^^^^^^^^
@ -89,7 +113,7 @@ LL | let _ = [unsafe {}; 5];
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:316:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:383:13
|
LL | let _ = unsafe {};
| ^^^^^^^^^
@ -97,7 +121,7 @@ LL | let _ = unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:327:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:394:8
|
LL | t!(unsafe {});
| ^^^^^^^^^
@ -105,7 +129,7 @@ LL | t!(unsafe {});
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:334:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:401:13
|
LL | unsafe {}
| ^^^^^^^^^
@ -117,7 +141,7 @@ LL | t!();
= note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:343:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:410:5
|
LL | unsafe {} // SAFETY:
| ^^^^^^^^^
@ -125,7 +149,7 @@ LL | unsafe {} // SAFETY:
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:349:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:416:5
|
LL | unsafe {
| ^^^^^^^^
@ -133,7 +157,7 @@ LL | unsafe {
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:360:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:427:5
|
LL | unsafe {};
| ^^^^^^^^^
@ -141,7 +165,7 @@ LL | unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:365:20
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:432:20
|
LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -149,7 +173,7 @@ LL | println!("{}", unsafe { String::from_utf8_unchecked(vec![]) });
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:373:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:440:5
|
LL | unsafe impl A for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -157,7 +181,7 @@ LL | unsafe impl A for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:381:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:448:9
|
LL | unsafe impl B for (u32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -165,7 +189,7 @@ LL | unsafe impl B for (u32) {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:403:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:470:13
|
LL | unsafe impl T for $t {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -177,7 +201,7 @@ LL | no_safety_comment!(());
= note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13
|
LL | unsafe impl T for $t {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -189,7 +213,7 @@ LL | no_safety_comment!(());
= note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:439:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:506:5
|
LL | unsafe impl T for (i32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -197,7 +221,7 @@ LL | unsafe impl T for (i32) {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:429:13
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:496:13
|
LL | unsafe impl T for $t {}
| ^^^^^^^^^^^^^^^^^^^^^^^
@ -209,7 +233,7 @@ LL | no_safety_comment!(u32);
= note: this error originates in the macro `no_safety_comment` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:446:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:513:5
|
LL | unsafe impl T for (bool) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -217,7 +241,7 @@ LL | unsafe impl T for (bool) {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:493:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:560:5
|
LL | unsafe impl NoComment for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -225,7 +249,7 @@ LL | unsafe impl NoComment for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:498:19
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:565:19
|
LL | /* SAFETY: */ unsafe impl InlineComment for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -233,7 +257,7 @@ LL | /* SAFETY: */ unsafe impl InlineComment for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:503:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:570:5
|
LL | unsafe impl TrailingComment for () {} // SAFETY:
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -241,13 +265,13 @@ LL | unsafe impl TrailingComment for () {} // SAFETY:
= help: consider adding a safety comment on the preceding line
error: constant has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:508:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:575:5
|
LL | const BIG_NUMBER: i32 = 1000000;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:507:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:574:8
|
LL | // SAFETY:
| ^^^^^^^
@ -255,7 +279,7 @@ LL | // SAFETY:
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_safety_comment)]`
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:510:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:577:5
|
LL | unsafe impl Interference for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -263,7 +287,7 @@ LL | unsafe impl Interference for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:518:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:585:5
|
LL | unsafe impl ImplInFn for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -271,7 +295,7 @@ LL | unsafe impl ImplInFn for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe impl missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:528:1
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:595:1
|
LL | unsafe impl CrateRoot for () {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -279,7 +303,7 @@ LL | unsafe impl CrateRoot for () {}
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:539:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:606:9
|
LL | unsafe {};
| ^^^^^^^^^
@ -287,7 +311,7 @@ LL | unsafe {};
= help: consider adding a safety comment on the preceding line
error: statement has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:543:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:610:5
|
LL | / let _ = {
LL | |
@ -297,13 +321,13 @@ LL | | };
| |______^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:542:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:609:8
|
LL | // SAFETY: this is more than one level away, so it should warn
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:545:12
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:612:12
|
LL | if unsafe { true } {
| ^^^^^^^^^^^^^^^
@ -311,7 +335,7 @@ LL | if unsafe { true } {
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:549:23
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:616:23
|
LL | let bar = unsafe {};
| ^^^^^^^^^
@ -319,7 +343,7 @@ LL | let bar = unsafe {};
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:568:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:635:9
|
LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -327,7 +351,7 @@ LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:573:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:640:9
|
LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -335,7 +359,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line()
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:578:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:645:9
|
LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -343,7 +367,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line()
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:585:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:652:5
|
LL | unsafe {}
| ^^^^^^^^^
@ -351,7 +375,7 @@ LL | unsafe {}
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:590:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:657:5
|
LL | unsafe {
| ^^^^^^^^
@ -359,7 +383,7 @@ LL | unsafe {
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:598:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:665:9
|
LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -367,7 +391,7 @@ LL | unsafe { a_function_with_a_very_long_name_to_break_the_line() };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:604:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:671:9
|
LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -375,7 +399,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line()
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:611:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:678:9
|
LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line() };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -383,7 +407,7 @@ LL | unsafe { a_const_function_with_a_very_long_name_to_break_the_line()
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:617:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:684:5
|
LL | unsafe {}
| ^^^^^^^^^
@ -391,7 +415,7 @@ LL | unsafe {}
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:638:52
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:705:52
|
LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 };
| ^^^^^^^^^^^^
@ -399,7 +423,7 @@ LL | const NO_SAFETY_IN_TRAIT_BUT_IN_IMPL: u8 = unsafe { 0 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:647:41
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:714:41
|
LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 };
| ^^^^^^^^^^^^
@ -407,7 +431,7 @@ LL | const NO_SAFETY_IN_TRAIT: i32 = unsafe { 1 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:657:42
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:724:42
|
LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 };
| ^^^^^^^^^^^^
@ -415,7 +439,7 @@ LL | const HAS_SAFETY_IN_TRAIT: i32 = unsafe { 3 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:662:40
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:729:40
|
LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 };
| ^^^^^^^^^^^^
@ -423,7 +447,7 @@ LL | const NO_SAFETY_IN_IMPL: i32 = unsafe { 1 };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:673:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:9
|
LL | unsafe { here_is_another_variable_with_long_name };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -431,7 +455,7 @@ LL | unsafe { here_is_another_variable_with_long_name };
= help: consider adding a safety comment on the preceding line
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:702:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:769:9
|
LL | unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.into_julian_day_just_make_this_line_longer();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -439,19 +463,19 @@ LL | unsafe { Date::__from_ordinal_date_unchecked(1970, 1) }.into_julian
= help: consider adding a safety comment on the preceding line
error: statement has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:721:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:788:5
|
LL | _ = bar();
| ^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:720:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:8
|
LL | // SAFETY: unnecessary_safety_comment triggers here
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:735:12
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:802:12
|
LL | return unsafe { h() };
| ^^^^^^^^^^^^^^
@ -459,31 +483,31 @@ LL | return unsafe { h() };
= help: consider adding a safety comment on the preceding line
error: module has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:741:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:808:5
|
LL | mod x {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:740:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:807:8
|
LL | // SAFETY: ...
| ^^^^^^^
error: module has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:751:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:818:5
|
LL | mod z {}
| ^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:750:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:817:8
|
LL | // SAFETY: ...
| ^^^^^^^
error: unsafe block missing a safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:766:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:833:9
|
LL | unsafe {}
| ^^^^^^^^^
@ -491,52 +515,52 @@ LL | unsafe {}
= help: consider adding a safety comment on the preceding line
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:848:5
|
LL | unsafe fn unsafe_comment() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider changing the `safety` comment for a `# Safety` doc comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:847:8
|
LL | // SAFETY: Bla
| ^^^^^^^
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:854:5
|
LL | unsafe fn unsafe_block_comment() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider changing the `safety` comment for a `# Safety` doc comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:852:8
|
LL | SAFETY: Bla
| ^^^^^^^
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:858:5
|
LL | fn safe_comment() {}
| ^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:8
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:857:8
|
LL | // SAFETY: Bla
| ^^^^^^^
error: function has unnecessary safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:862:5
|
LL | fn safe_doc_comment() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: consider removing the safety comment
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:9
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:861:9
|
LL | /// SAFETY: Bla
| ^^^^^^^
error: aborting due to 60 previous errors
error: aborting due to 62 previous errors

View file

@ -201,6 +201,66 @@ fn comment_macro_def() {
t!();
}
fn comment_macro_def_with_let() {
macro_rules! t {
() => {
let _x =
// SAFETY: here be exactly one dragon
unsafe { 1 };
};
}
t!();
}
#[rustfmt::skip]
fn comment_macro_def_with_let_same_line() {
macro_rules! t {
() => {
let _x =// SAFETY: same line comment
unsafe { 1 };
};
}
t!();
}
fn inner_comment_macro_def_with_let() {
macro_rules! t {
() => {
let _x = unsafe {
// SAFETY: inside the block
1
};
};
}
t!();
}
fn no_comment_macro_def_with_let() {
macro_rules! t {
() => {
let _x = unsafe { 1 };
//~^ undocumented_unsafe_blocks
};
}
t!();
}
fn prefixed_safety_comment_macro_def_with_let() {
macro_rules! t {
() => {
let _x =// not a SAFETY: comment, should lint
unsafe { 1 };
//~^ undocumented_unsafe_blocks
};
}
t!();
}
fn non_ascii_comment() {
// ॐ᧻໒ SaFeTy: ௵∰
unsafe {};
@ -263,6 +323,13 @@ fn in_closure(x: *const u32) {
let _ = || unsafe { *x };
}
fn inner_block_comment_block_style(x: *const u32) {
let _ = unsafe {
/* SAFETY: block comment inside */
*x
};
}
// Invalid comments
#[rustfmt::skip]

View file

@ -174,7 +174,6 @@ pub fn hard_coded_allowed() {
let _ = Saturating(0u32) + Saturating(0u32);
let _ = String::new() + "";
let _ = String::new() + &String::new();
//~^ arithmetic_side_effects
let _ = Wrapping(0u32) + Wrapping(0u32);
let saturating: Saturating<u32> = Saturating(0u32);

View file

@ -14,784 +14,778 @@ LL | let _ = 1f128 + 1f128;
| ^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:176:13
|
LL | let _ = String::new() + &String::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:312:5
--> tests/ui/arithmetic_side_effects.rs:311:5
|
LL | _n += 1;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:314:5
--> tests/ui/arithmetic_side_effects.rs:313:5
|
LL | _n += &1;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:316:5
--> tests/ui/arithmetic_side_effects.rs:315:5
|
LL | _n -= 1;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:318:5
--> tests/ui/arithmetic_side_effects.rs:317:5
|
LL | _n -= &1;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:320:5
--> tests/ui/arithmetic_side_effects.rs:319:5
|
LL | _n /= 0;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:322:5
--> tests/ui/arithmetic_side_effects.rs:321:5
|
LL | _n /= &0;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:324:5
--> tests/ui/arithmetic_side_effects.rs:323:5
|
LL | _n %= 0;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:326:5
--> tests/ui/arithmetic_side_effects.rs:325:5
|
LL | _n %= &0;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:328:5
--> tests/ui/arithmetic_side_effects.rs:327:5
|
LL | _n *= 2;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:330:5
--> tests/ui/arithmetic_side_effects.rs:329:5
|
LL | _n *= &2;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:332:5
--> tests/ui/arithmetic_side_effects.rs:331:5
|
LL | _n += -1;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:334:5
--> tests/ui/arithmetic_side_effects.rs:333:5
|
LL | _n += &-1;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:336:5
--> tests/ui/arithmetic_side_effects.rs:335:5
|
LL | _n -= -1;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:338:5
--> tests/ui/arithmetic_side_effects.rs:337:5
|
LL | _n -= &-1;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:340:5
--> tests/ui/arithmetic_side_effects.rs:339:5
|
LL | _n /= -0;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:342:5
--> tests/ui/arithmetic_side_effects.rs:341:5
|
LL | _n /= &-0;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:344:5
--> tests/ui/arithmetic_side_effects.rs:343:5
|
LL | _n %= -0;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:346:5
--> tests/ui/arithmetic_side_effects.rs:345:5
|
LL | _n %= &-0;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:348:5
--> tests/ui/arithmetic_side_effects.rs:347:5
|
LL | _n *= -2;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:350:5
--> tests/ui/arithmetic_side_effects.rs:349:5
|
LL | _n *= &-2;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:352:5
--> tests/ui/arithmetic_side_effects.rs:351:5
|
LL | _custom += Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:354:5
--> tests/ui/arithmetic_side_effects.rs:353:5
|
LL | _custom += &Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:356:5
--> tests/ui/arithmetic_side_effects.rs:355:5
|
LL | _custom -= Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:358:5
--> tests/ui/arithmetic_side_effects.rs:357:5
|
LL | _custom -= &Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:360:5
--> tests/ui/arithmetic_side_effects.rs:359:5
|
LL | _custom /= Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:362:5
--> tests/ui/arithmetic_side_effects.rs:361:5
|
LL | _custom /= &Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:364:5
--> tests/ui/arithmetic_side_effects.rs:363:5
|
LL | _custom %= Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:366:5
--> tests/ui/arithmetic_side_effects.rs:365:5
|
LL | _custom %= &Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:368:5
--> tests/ui/arithmetic_side_effects.rs:367:5
|
LL | _custom *= Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:370:5
--> tests/ui/arithmetic_side_effects.rs:369:5
|
LL | _custom *= &Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:372:5
--> tests/ui/arithmetic_side_effects.rs:371:5
|
LL | _custom >>= Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:374:5
--> tests/ui/arithmetic_side_effects.rs:373:5
|
LL | _custom >>= &Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:376:5
--> tests/ui/arithmetic_side_effects.rs:375:5
|
LL | _custom <<= Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:378:5
--> tests/ui/arithmetic_side_effects.rs:377:5
|
LL | _custom <<= &Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:380:5
--> tests/ui/arithmetic_side_effects.rs:379:5
|
LL | _custom += -Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:382:5
--> tests/ui/arithmetic_side_effects.rs:381:5
|
LL | _custom += &-Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:384:5
--> tests/ui/arithmetic_side_effects.rs:383:5
|
LL | _custom -= -Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:386:5
--> tests/ui/arithmetic_side_effects.rs:385:5
|
LL | _custom -= &-Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:388:5
--> tests/ui/arithmetic_side_effects.rs:387:5
|
LL | _custom /= -Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:390:5
--> tests/ui/arithmetic_side_effects.rs:389:5
|
LL | _custom /= &-Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:392:5
--> tests/ui/arithmetic_side_effects.rs:391:5
|
LL | _custom %= -Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:394:5
--> tests/ui/arithmetic_side_effects.rs:393:5
|
LL | _custom %= &-Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:396:5
--> tests/ui/arithmetic_side_effects.rs:395:5
|
LL | _custom *= -Custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:398:5
--> tests/ui/arithmetic_side_effects.rs:397:5
|
LL | _custom *= &-Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:400:5
--> tests/ui/arithmetic_side_effects.rs:399:5
|
LL | _custom >>= -Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:402:5
--> tests/ui/arithmetic_side_effects.rs:401:5
|
LL | _custom >>= &-Custom;
| ^^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:404:5
--> tests/ui/arithmetic_side_effects.rs:403:5
|
LL | _custom <<= -Custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:406:5
--> tests/ui/arithmetic_side_effects.rs:405:5
|
LL | _custom <<= &-Custom;
| ^^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:410:10
--> tests/ui/arithmetic_side_effects.rs:409:10
|
LL | _n = _n + 1;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:412:10
--> tests/ui/arithmetic_side_effects.rs:411:10
|
LL | _n = _n + &1;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:414:10
--> tests/ui/arithmetic_side_effects.rs:413:10
|
LL | _n = 1 + _n;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:416:10
--> tests/ui/arithmetic_side_effects.rs:415:10
|
LL | _n = &1 + _n;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:418:10
--> tests/ui/arithmetic_side_effects.rs:417:10
|
LL | _n = _n - 1;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:420:10
--> tests/ui/arithmetic_side_effects.rs:419:10
|
LL | _n = _n - &1;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:422:10
--> tests/ui/arithmetic_side_effects.rs:421:10
|
LL | _n = 1 - _n;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:424:10
--> tests/ui/arithmetic_side_effects.rs:423:10
|
LL | _n = &1 - _n;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:426:10
--> tests/ui/arithmetic_side_effects.rs:425:10
|
LL | _n = _n / 0;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:428:10
--> tests/ui/arithmetic_side_effects.rs:427:10
|
LL | _n = _n / &0;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:430:10
--> tests/ui/arithmetic_side_effects.rs:429:10
|
LL | _n = _n % 0;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:432:10
--> tests/ui/arithmetic_side_effects.rs:431:10
|
LL | _n = _n % &0;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:434:10
--> tests/ui/arithmetic_side_effects.rs:433:10
|
LL | _n = _n * 2;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:436:10
--> tests/ui/arithmetic_side_effects.rs:435:10
|
LL | _n = _n * &2;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:438:10
--> tests/ui/arithmetic_side_effects.rs:437:10
|
LL | _n = 2 * _n;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:440:10
--> tests/ui/arithmetic_side_effects.rs:439:10
|
LL | _n = &2 * _n;
| ^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:442:10
--> tests/ui/arithmetic_side_effects.rs:441:10
|
LL | _n = 23 + &85;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:444:10
--> tests/ui/arithmetic_side_effects.rs:443:10
|
LL | _n = &23 + 85;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:446:10
--> tests/ui/arithmetic_side_effects.rs:445:10
|
LL | _n = &23 + &85;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:448:15
--> tests/ui/arithmetic_side_effects.rs:447:15
|
LL | _custom = _custom + _custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:450:15
--> tests/ui/arithmetic_side_effects.rs:449:15
|
LL | _custom = _custom + &_custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:452:15
--> tests/ui/arithmetic_side_effects.rs:451:15
|
LL | _custom = Custom + _custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:454:15
--> tests/ui/arithmetic_side_effects.rs:453:15
|
LL | _custom = &Custom + _custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:456:15
--> tests/ui/arithmetic_side_effects.rs:455:15
|
LL | _custom = _custom - Custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:458:15
--> tests/ui/arithmetic_side_effects.rs:457:15
|
LL | _custom = _custom - &Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:460:15
--> tests/ui/arithmetic_side_effects.rs:459:15
|
LL | _custom = Custom - _custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:462:15
--> tests/ui/arithmetic_side_effects.rs:461:15
|
LL | _custom = &Custom - _custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:464:15
--> tests/ui/arithmetic_side_effects.rs:463:15
|
LL | _custom = _custom / Custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:466:15
--> tests/ui/arithmetic_side_effects.rs:465:15
|
LL | _custom = _custom / &Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:468:15
--> tests/ui/arithmetic_side_effects.rs:467:15
|
LL | _custom = _custom % Custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:470:15
--> tests/ui/arithmetic_side_effects.rs:469:15
|
LL | _custom = _custom % &Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:472:15
--> tests/ui/arithmetic_side_effects.rs:471:15
|
LL | _custom = _custom * Custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:474:15
--> tests/ui/arithmetic_side_effects.rs:473:15
|
LL | _custom = _custom * &Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:476:15
--> tests/ui/arithmetic_side_effects.rs:475:15
|
LL | _custom = Custom * _custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:478:15
--> tests/ui/arithmetic_side_effects.rs:477:15
|
LL | _custom = &Custom * _custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:480:15
--> tests/ui/arithmetic_side_effects.rs:479:15
|
LL | _custom = Custom + &Custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:482:15
--> tests/ui/arithmetic_side_effects.rs:481:15
|
LL | _custom = &Custom + Custom;
| ^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:484:15
--> tests/ui/arithmetic_side_effects.rs:483:15
|
LL | _custom = &Custom + &Custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:486:15
--> tests/ui/arithmetic_side_effects.rs:485:15
|
LL | _custom = _custom >> _custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:488:15
--> tests/ui/arithmetic_side_effects.rs:487:15
|
LL | _custom = _custom >> &_custom;
| ^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:490:15
--> tests/ui/arithmetic_side_effects.rs:489:15
|
LL | _custom = Custom << _custom;
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:492:15
--> tests/ui/arithmetic_side_effects.rs:491:15
|
LL | _custom = &Custom << _custom;
| ^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:496:23
--> tests/ui/arithmetic_side_effects.rs:495:23
|
LL | _n.saturating_div(0);
| ^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:498:21
--> tests/ui/arithmetic_side_effects.rs:497:21
|
LL | _n.wrapping_div(0);
| ^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:500:21
--> tests/ui/arithmetic_side_effects.rs:499:21
|
LL | _n.wrapping_rem(0);
| ^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:502:28
--> tests/ui/arithmetic_side_effects.rs:501:28
|
LL | _n.wrapping_rem_euclid(0);
| ^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:505:23
--> tests/ui/arithmetic_side_effects.rs:504:23
|
LL | _n.saturating_div(_n);
| ^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:507:21
--> tests/ui/arithmetic_side_effects.rs:506:21
|
LL | _n.wrapping_div(_n);
| ^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:509:21
--> tests/ui/arithmetic_side_effects.rs:508:21
|
LL | _n.wrapping_rem(_n);
| ^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:511:28
--> tests/ui/arithmetic_side_effects.rs:510:28
|
LL | _n.wrapping_rem_euclid(_n);
| ^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:514:23
--> tests/ui/arithmetic_side_effects.rs:513:23
|
LL | _n.saturating_div(*Box::new(_n));
| ^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:518:10
--> tests/ui/arithmetic_side_effects.rs:517:10
|
LL | _n = -_n;
| ^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:520:10
--> tests/ui/arithmetic_side_effects.rs:519:10
|
LL | _n = -&_n;
| ^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:522:15
--> tests/ui/arithmetic_side_effects.rs:521:15
|
LL | _custom = -_custom;
| ^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:524:15
--> tests/ui/arithmetic_side_effects.rs:523:15
|
LL | _custom = -&_custom;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:526:9
--> tests/ui/arithmetic_side_effects.rs:525:9
|
LL | _ = -*Box::new(_n);
| ^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:536:5
--> tests/ui/arithmetic_side_effects.rs:535:5
|
LL | 1 + i;
| ^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:538:5
--> tests/ui/arithmetic_side_effects.rs:537:5
|
LL | i * 2;
| ^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:540:5
--> tests/ui/arithmetic_side_effects.rs:539:5
|
LL | 1 % i / 2;
| ^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:542:5
--> tests/ui/arithmetic_side_effects.rs:541:5
|
LL | i - 2 + 2 - i;
| ^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:544:5
--> tests/ui/arithmetic_side_effects.rs:543:5
|
LL | -i;
| ^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:556:5
--> tests/ui/arithmetic_side_effects.rs:555:5
|
LL | i += 1;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:558:5
--> tests/ui/arithmetic_side_effects.rs:557:5
|
LL | i -= 1;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:560:5
--> tests/ui/arithmetic_side_effects.rs:559:5
|
LL | i *= 2;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:563:5
--> tests/ui/arithmetic_side_effects.rs:562:5
|
LL | i /= 0;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:566:5
--> tests/ui/arithmetic_side_effects.rs:565:5
|
LL | i /= var1;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:568:5
--> tests/ui/arithmetic_side_effects.rs:567:5
|
LL | i /= var2;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:571:5
--> tests/ui/arithmetic_side_effects.rs:570:5
|
LL | i %= 0;
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:574:5
--> tests/ui/arithmetic_side_effects.rs:573:5
|
LL | i %= var1;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:576:5
--> tests/ui/arithmetic_side_effects.rs:575:5
|
LL | i %= var2;
| ^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:587:5
--> tests/ui/arithmetic_side_effects.rs:586:5
|
LL | 10 / a
| ^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:642:9
--> tests/ui/arithmetic_side_effects.rs:641:9
|
LL | x / maybe_zero
| ^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:647:9
--> tests/ui/arithmetic_side_effects.rs:646:9
|
LL | x % maybe_zero
| ^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:659:5
--> tests/ui/arithmetic_side_effects.rs:658:5
|
LL | one.add_assign(1);
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:664:5
--> tests/ui/arithmetic_side_effects.rs:663:5
|
LL | one.sub_assign(1);
| ^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:685:5
--> tests/ui/arithmetic_side_effects.rs:684:5
|
LL | one.add(&one);
| ^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:687:5
--> tests/ui/arithmetic_side_effects.rs:686:5
|
LL | Box::new(one).add(one);
| ^^^^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:696:13
--> tests/ui/arithmetic_side_effects.rs:695:13
|
LL | let _ = u128::MAX + u128::from(1u8);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:713:13
--> tests/ui/arithmetic_side_effects.rs:712:13
|
LL | let _ = u128::MAX * u128::from(1u8);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:736:33
--> tests/ui/arithmetic_side_effects.rs:735:33
|
LL | let _ = Duration::from_secs(86400 * Foo::from(1));
| ^^^^^^^^^^^^^^^^^^^^
error: arithmetic operation that can potentially result in unexpected side-effects
--> tests/ui/arithmetic_side_effects.rs:742:33
--> tests/ui/arithmetic_side_effects.rs:741:33
|
LL | let _ = Duration::from_secs(86400 * shift(1));
| ^^^^^^^^^^^^^^^^
error: aborting due to 132 previous errors
error: aborting due to 131 previous errors

View file

@ -472,3 +472,28 @@ mod issue14422 {
return;
}
}
#[clippy::cognitive_complexity = "1"]
mod attribute_stacking {
fn bad() {
//~^ cognitive_complexity
if true {
println!("a");
}
}
#[clippy::cognitive_complexity = "2"]
fn ok() {
if true {
println!("a");
}
}
// should revert to cognitive_complexity = "1"
fn bad_again() {
//~^ cognitive_complexity
if true {
println!("a");
}
}
}

View file

@ -176,5 +176,21 @@ LL | fn bar() {
|
= help: you could split it up into multiple smaller functions
error: aborting due to 22 previous errors
error: the function has a cognitive complexity of (2/1)
--> tests/ui/cognitive_complexity.rs:478:8
|
LL | fn bad() {
| ^^^
|
= help: you could split it up into multiple smaller functions
error: the function has a cognitive complexity of (2/1)
--> tests/ui/cognitive_complexity.rs:493:8
|
LL | fn bad_again() {
| ^^^^^^^^^
|
= help: you could split it up into multiple smaller functions
error: aborting due to 24 previous errors

View file

@ -1,12 +1,11 @@
//@check-pass
//@ignore-target: apple
#![feature(rustc_attrs)]
#[warn(clippy::main_recursion)]
#[allow(unconditional_recursion)]
#[rustc_main]
fn a() {
println!("Hello, World!");
a();
//~^ main_recursion
}
fn main() {}

View file

@ -0,0 +1,12 @@
error: recursing into entrypoint `a`
--> tests/ui/crate_level_checks/entrypoint_recursion.rs:7:5
|
LL | a();
| ^
|
= help: consider using another function for this recursion
= note: `-D clippy::main-recursion` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::main_recursion)]`
error: aborting due to 1 previous error

View file

@ -0,0 +1,13 @@
//@check-pass
//@compile-flags: -Cpanic=abort
#![no_std]
#[warn(clippy::main_recursion)]
#[allow(unconditional_recursion)]
fn main() {
main();
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}

View file

@ -35,4 +35,20 @@ fn main() {
//~^ double_comparisons
// do something
}
if x < y {
//~^ double_comparisons
// do something
}
if x < y {
//~^ double_comparisons
// do something
}
if x > y {
//~^ double_comparisons
// do something
}
if x > y {
//~^ double_comparisons
// do something
}
}

View file

@ -35,4 +35,20 @@ fn main() {
//~^ double_comparisons
// do something
}
if x != y && x <= y {
//~^ double_comparisons
// do something
}
if x <= y && x != y {
//~^ double_comparisons
// do something
}
if x != y && x >= y {
//~^ double_comparisons
// do something
}
if x >= y && x != y {
//~^ double_comparisons
// do something
}
}

View file

@ -49,5 +49,29 @@ error: this binary expression can be simplified
LL | if x >= y && x <= y {
| ^^^^^^^^^^^^^^^^ help: try: `x == y`
error: aborting due to 8 previous errors
error: this binary expression can be simplified
--> tests/ui/double_comparison.rs:38:8
|
LL | if x != y && x <= y {
| ^^^^^^^^^^^^^^^^ help: try: `x < y`
error: this binary expression can be simplified
--> tests/ui/double_comparison.rs:42:8
|
LL | if x <= y && x != y {
| ^^^^^^^^^^^^^^^^ help: try: `x < y`
error: this binary expression can be simplified
--> tests/ui/double_comparison.rs:46:8
|
LL | if x != y && x >= y {
| ^^^^^^^^^^^^^^^^ help: try: `x > y`
error: this binary expression can be simplified
--> tests/ui/double_comparison.rs:50:8
|
LL | if x >= y && x != y {
| ^^^^^^^^^^^^^^^^ help: try: `x > y`
error: aborting due to 12 previous errors

View file

@ -0,0 +1,91 @@
//@aux-build:proc_macros.rs
#![warn(clippy::duration_suboptimal_units)]
use std::time::Duration;
const SIXTY: u64 = 60;
macro_rules! mac {
(slow_rythm) => {
3600
};
(duration) => {
Duration::from_mins(5)
//~^ duration_suboptimal_units
};
(arg => $e:expr) => {
Duration::from_secs($e)
};
}
fn main() {
let dur = Duration::from_secs(0);
let dur = Duration::from_secs(42);
let dur = Duration::from_hours(3);
let dur = Duration::from_mins(1);
//~^ duration_suboptimal_units
let dur = Duration::from_mins(3);
//~^ duration_suboptimal_units
let dur = Duration::from_mins(10);
//~^ duration_suboptimal_units
let dur = Duration::from_hours(24);
//~^ duration_suboptimal_units
let dur = Duration::from_secs(5);
//~^ duration_suboptimal_units
let dur = Duration::from_hours(13);
//~^ duration_suboptimal_units
// Constants are intentionally not resolved, as we don't want to recommend a literal value over
// using constants.
let dur = Duration::from_secs(SIXTY);
// Technically it would be nice to use Duration::from_mins(SIXTY) here, but that is a follow-up
let dur = Duration::from_secs(SIXTY * 60);
const {
let dur = Duration::from_secs(0);
let dur = Duration::from_secs(5);
//~^ duration_suboptimal_units
let dur = Duration::from_mins(3);
//~^ duration_suboptimal_units
let dur = Duration::from_hours(24);
//~^ duration_suboptimal_units
let dur = Duration::from_secs(SIXTY);
}
// Qualified Durations must be kept
std::time::Duration::from_mins(1);
//~^ duration_suboptimal_units
// We lint in normal macros
assert_eq!(Duration::from_hours(1), Duration::from_mins(6));
//~^ duration_suboptimal_units
// We lint in normal macros (marker is in macro itself)
let dur = mac!(duration);
// We don't lint in macros if duration comes from outside
let dur = mac!(arg => 3600);
// We don't lint in external macros
let dur = proc_macros::external! { Duration::from_secs(3_600) };
// We don't lint values coming from macros
let dur = Duration::from_secs(mac!(slow_rythm));
}
mod my_duration {
struct Duration {}
impl Duration {
pub const fn from_secs(_secs: u64) -> Self {
Self {}
}
}
fn test() {
// Only suggest the change for std::time::Duration, not for other Duration structs
let dur = Duration::from_secs(60);
}
}

View file

@ -0,0 +1,91 @@
//@aux-build:proc_macros.rs
#![warn(clippy::duration_suboptimal_units)]
use std::time::Duration;
const SIXTY: u64 = 60;
macro_rules! mac {
(slow_rythm) => {
3600
};
(duration) => {
Duration::from_secs(300)
//~^ duration_suboptimal_units
};
(arg => $e:expr) => {
Duration::from_secs($e)
};
}
fn main() {
let dur = Duration::from_secs(0);
let dur = Duration::from_secs(42);
let dur = Duration::from_hours(3);
let dur = Duration::from_secs(60);
//~^ duration_suboptimal_units
let dur = Duration::from_secs(180);
//~^ duration_suboptimal_units
let dur = Duration::from_secs(10 * 60);
//~^ duration_suboptimal_units
let dur = Duration::from_mins(24 * 60);
//~^ duration_suboptimal_units
let dur = Duration::from_millis(5_000);
//~^ duration_suboptimal_units
let dur = Duration::from_nanos(13 * 60 * 60 * 1_000 * 1_000 * 1_000);
//~^ duration_suboptimal_units
// Constants are intentionally not resolved, as we don't want to recommend a literal value over
// using constants.
let dur = Duration::from_secs(SIXTY);
// Technically it would be nice to use Duration::from_mins(SIXTY) here, but that is a follow-up
let dur = Duration::from_secs(SIXTY * 60);
const {
let dur = Duration::from_secs(0);
let dur = Duration::from_millis(5_000);
//~^ duration_suboptimal_units
let dur = Duration::from_secs(180);
//~^ duration_suboptimal_units
let dur = Duration::from_mins(24 * 60);
//~^ duration_suboptimal_units
let dur = Duration::from_secs(SIXTY);
}
// Qualified Durations must be kept
std::time::Duration::from_secs(60);
//~^ duration_suboptimal_units
// We lint in normal macros
assert_eq!(Duration::from_secs(3_600), Duration::from_mins(6));
//~^ duration_suboptimal_units
// We lint in normal macros (marker is in macro itself)
let dur = mac!(duration);
// We don't lint in macros if duration comes from outside
let dur = mac!(arg => 3600);
// We don't lint in external macros
let dur = proc_macros::external! { Duration::from_secs(3_600) };
// We don't lint values coming from macros
let dur = Duration::from_secs(mac!(slow_rythm));
}
mod my_duration {
struct Duration {}
impl Duration {
pub const fn from_secs(_secs: u64) -> Self {
Self {}
}
}
fn test() {
// Only suggest the change for std::time::Duration, not for other Duration structs
let dur = Duration::from_secs(60);
}
}

View file

@ -0,0 +1,152 @@
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:26:15
|
LL | let dur = Duration::from_secs(60);
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::duration-suboptimal-units` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::duration_suboptimal_units)]`
help: try using from_mins
|
LL - let dur = Duration::from_secs(60);
LL + let dur = Duration::from_mins(1);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:28:15
|
LL | let dur = Duration::from_secs(180);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_mins
|
LL - let dur = Duration::from_secs(180);
LL + let dur = Duration::from_mins(3);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:30:15
|
LL | let dur = Duration::from_secs(10 * 60);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_mins
|
LL - let dur = Duration::from_secs(10 * 60);
LL + let dur = Duration::from_mins(10);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:32:15
|
LL | let dur = Duration::from_mins(24 * 60);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_hours
|
LL - let dur = Duration::from_mins(24 * 60);
LL + let dur = Duration::from_hours(24);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:34:15
|
LL | let dur = Duration::from_millis(5_000);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_secs
|
LL - let dur = Duration::from_millis(5_000);
LL + let dur = Duration::from_secs(5);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:36:15
|
LL | let dur = Duration::from_nanos(13 * 60 * 60 * 1_000 * 1_000 * 1_000);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_hours
|
LL - let dur = Duration::from_nanos(13 * 60 * 60 * 1_000 * 1_000 * 1_000);
LL + let dur = Duration::from_hours(13);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:47:19
|
LL | let dur = Duration::from_millis(5_000);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_secs
|
LL - let dur = Duration::from_millis(5_000);
LL + let dur = Duration::from_secs(5);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:49:19
|
LL | let dur = Duration::from_secs(180);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_mins
|
LL - let dur = Duration::from_secs(180);
LL + let dur = Duration::from_mins(3);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:51:19
|
LL | let dur = Duration::from_mins(24 * 60);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_hours
|
LL - let dur = Duration::from_mins(24 * 60);
LL + let dur = Duration::from_hours(24);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:58:5
|
LL | std::time::Duration::from_secs(60);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_mins
|
LL - std::time::Duration::from_secs(60);
LL + std::time::Duration::from_mins(1);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:62:16
|
LL | assert_eq!(Duration::from_secs(3_600), Duration::from_mins(6));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_hours
|
LL - assert_eq!(Duration::from_secs(3_600), Duration::from_mins(6));
LL + assert_eq!(Duration::from_hours(1), Duration::from_mins(6));
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units.rs:13:9
|
LL | Duration::from_secs(300)
| ^^^^^^^^^^^^^^^^^^^^^^^^
...
LL | let dur = mac!(duration);
| -------------- in this macro invocation
|
= note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info)
help: try using from_mins
|
LL - Duration::from_secs(300)
LL + Duration::from_mins(5)
|
error: aborting due to 12 previous errors

View file

@ -0,0 +1,17 @@
#![warn(clippy::duration_suboptimal_units)]
// The duration_constructors feature enables `Duration::from_days` and `Duration::from_weeks`, so we
// should suggest them
#![feature(duration_constructors)]
use std::time::Duration;
fn main() {
let dur = Duration::from_mins(1);
//~^ duration_suboptimal_units
let dur = Duration::from_days(1);
//~^ duration_suboptimal_units
let dur = Duration::from_weeks(13);
//~^ duration_suboptimal_units
}

View file

@ -0,0 +1,17 @@
#![warn(clippy::duration_suboptimal_units)]
// The duration_constructors feature enables `Duration::from_days` and `Duration::from_weeks`, so we
// should suggest them
#![feature(duration_constructors)]
use std::time::Duration;
fn main() {
let dur = Duration::from_secs(60);
//~^ duration_suboptimal_units
let dur = Duration::from_hours(24);
//~^ duration_suboptimal_units
let dur = Duration::from_nanos(13 * 7 * 24 * 60 * 60 * 1_000 * 1_000 * 1_000);
//~^ duration_suboptimal_units
}

View file

@ -0,0 +1,40 @@
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units_days_weeks.rs:9:15
|
LL | let dur = Duration::from_secs(60);
| ^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `-D clippy::duration-suboptimal-units` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::duration_suboptimal_units)]`
help: try using from_mins
|
LL - let dur = Duration::from_secs(60);
LL + let dur = Duration::from_mins(1);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units_days_weeks.rs:12:15
|
LL | let dur = Duration::from_hours(24);
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_days
|
LL - let dur = Duration::from_hours(24);
LL + let dur = Duration::from_days(1);
|
error: constructing a `Duration` using a smaller unit when a larger unit would be more readable
--> tests/ui/duration_suboptimal_units_days_weeks.rs:15:15
|
LL | let dur = Duration::from_nanos(13 * 7 * 24 * 60 * 60 * 1_000 * 1_000 * 1_000);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: try using from_weeks
|
LL - let dur = Duration::from_nanos(13 * 7 * 24 * 60 * 60 * 1_000 * 1_000 * 1_000);
LL + let dur = Duration::from_weeks(13);
|
error: aborting due to 3 previous errors

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