Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New lint to detect &std::path::MAIN_SEPARATOR.to_string() #10483

Merged
merged 1 commit into from
Mar 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4662,6 +4662,7 @@ Released 2018-09-13
[`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed
[`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check
[`manual_let_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_let_else
[`manual_main_separator_str`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_main_separator_str
[`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::manual_clamp::MANUAL_CLAMP_INFO,
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
crate::manual_retain::MANUAL_RETAIN_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ mod manual_bits;
mod manual_clamp;
mod manual_is_ascii_check;
mod manual_let_else;
mod manual_main_separator_str;
mod manual_non_exhaustive;
mod manual_rem_euclid;
mod manual_retain;
Expand Down Expand Up @@ -936,6 +937,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::new(redundant_async_block::RedundantAsyncBlock));
store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv())));
// add lints here, do not remove this comment, it's used in `new_lint`
}

Expand Down
72 changes: 72 additions & 0 deletions clippy_lints/src/manual_main_separator_str.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::{is_trait_method, match_def_path, paths, peel_hir_expr_refs};
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::*;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;

declare_clippy_lint! {
/// ### What it does
/// Checks for references on `std::path::MAIN_SEPARATOR.to_string()` used
/// to build a `&str`.
///
/// ### Why is this bad?
/// There exists a `std::path::MAIN_SEPARATOR_STR` which does not require
/// an extra memory allocation.
///
/// ### Example
/// ```rust
/// let s: &str = &std::path::MAIN_SEPARATOR.to_string();
/// ```
/// Use instead:
/// ```rust
/// let s: &str = std::path::MAIN_SEPARATOR_STR;
/// ```
#[clippy::version = "1.70.0"]
pub MANUAL_MAIN_SEPARATOR_STR,
complexity,
"`&std::path::MAIN_SEPARATOR.to_string()` can be replaced by `std::path::MAIN_SEPARATOR_STR`"
}

pub struct ManualMainSeparatorStr {
msrv: Msrv,
}

impl ManualMainSeparatorStr {
#[must_use]
pub fn new(msrv: Msrv) -> Self {
Self { msrv }
}
}

impl_lint_pass!(ManualMainSeparatorStr => [MANUAL_MAIN_SEPARATOR_STR]);

impl LateLintPass<'_> for ManualMainSeparatorStr {
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
if self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR) &&
let (target, _) = peel_hir_expr_refs(expr) &&
is_trait_method(cx, target, sym::ToString) &&
let ExprKind::MethodCall(path, receiver, &[], _) = target.kind &&
path.ident.name == sym::to_string &&
let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind &&
let Res::Def(DefKind::Const, receiver_def_id) = path.res &&
match_def_path(cx, receiver_def_id, &paths::PATH_MAIN_SEPARATOR) &&
let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind() &&
ty.is_str()
{
span_lint_and_sugg(
cx,
MANUAL_MAIN_SEPARATOR_STR,
expr.span,
"taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String`",
"replace with",
"std::path::MAIN_SEPARATOR_STR".to_owned(),
Applicability::MachineApplicable,
);
}
}
}
1 change: 1 addition & 0 deletions clippy_utils/src/msrvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ macro_rules! msrv_aliases {

// names may refer to stabilized feature flags or library items
msrv_aliases! {
1,68,0 { PATH_MAIN_SEPARATOR_STR }
1,65,0 { LET_ELSE }
1,62,0 { BOOL_THEN_SOME, DEFAULT_ENUM_ATTRIBUTE }
1,58,0 { FORMAT_ARGS_CAPTURE, PATTERN_TRAIT_CHAR_ARRAY }
Expand Down
1 change: 1 addition & 0 deletions clippy_utils/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub const PARKING_LOT_MUTEX_GUARD: [&str; 3] = ["lock_api", "mutex", "MutexGuard
pub const PARKING_LOT_RWLOCK_READ_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockReadGuard"];
pub const PARKING_LOT_RWLOCK_WRITE_GUARD: [&str; 3] = ["lock_api", "rwlock", "RwLockWriteGuard"];
pub const PATH_BUF_AS_PATH: [&str; 4] = ["std", "path", "PathBuf", "as_path"];
pub const PATH_MAIN_SEPARATOR: [&str; 3] = ["std", "path", "MAIN_SEPARATOR"];
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
pub const PEEKABLE: [&str; 5] = ["core", "iter", "adapters", "peekable", "Peekable"];
pub const PERMISSIONS: [&str; 3] = ["std", "fs", "Permissions"];
Expand Down
39 changes: 39 additions & 0 deletions tests/ui/manual_main_separator_str.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// run-rustfix

#![allow(unused)]
#![warn(clippy::manual_main_separator_str)]

use std::path::MAIN_SEPARATOR;

fn len(s: &str) -> usize {
s.len()
}

struct U<'a> {
f: &'a str,
g: &'a String,
}

struct V<T> {
f: T,
}

fn main() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about lets without a given type? Or other expressions that are not direct local bindings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added new tests, such as initializing a struct field with this expression or passing it as a function argument.

I've also made it work when it is a method receiver (see the test with encode_utf16()), without an explicit &.

// Should lint
let _: &str = std::path::MAIN_SEPARATOR_STR;
let _ = len(std::path::MAIN_SEPARATOR_STR);
let _: Vec<u16> = std::path::MAIN_SEPARATOR_STR.encode_utf16().collect();

// Should lint for field `f` only
let _ = U {
f: std::path::MAIN_SEPARATOR_STR,
g: &MAIN_SEPARATOR.to_string(),
};

// Should not lint
let _: &String = &MAIN_SEPARATOR.to_string();
let _ = &MAIN_SEPARATOR.to_string();
let _ = V {
f: &MAIN_SEPARATOR.to_string(),
};
}
39 changes: 39 additions & 0 deletions tests/ui/manual_main_separator_str.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// run-rustfix

#![allow(unused)]
#![warn(clippy::manual_main_separator_str)]

use std::path::MAIN_SEPARATOR;

fn len(s: &str) -> usize {
s.len()
}

struct U<'a> {
f: &'a str,
g: &'a String,
}

struct V<T> {
f: T,
}

fn main() {
// Should lint
let _: &str = &MAIN_SEPARATOR.to_string();
let _ = len(&MAIN_SEPARATOR.to_string());
let _: Vec<u16> = MAIN_SEPARATOR.to_string().encode_utf16().collect();

// Should lint for field `f` only
let _ = U {
f: &MAIN_SEPARATOR.to_string(),
g: &MAIN_SEPARATOR.to_string(),
};

// Should not lint
let _: &String = &MAIN_SEPARATOR.to_string();
let _ = &MAIN_SEPARATOR.to_string();
let _ = V {
f: &MAIN_SEPARATOR.to_string(),
};
}
28 changes: 28 additions & 0 deletions tests/ui/manual_main_separator_str.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String`
--> $DIR/manual_main_separator_str.rs:23:19
|
LL | let _: &str = &MAIN_SEPARATOR.to_string();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR`
|
= note: `-D clippy::manual-main-separator-str` implied by `-D warnings`

error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String`
--> $DIR/manual_main_separator_str.rs:24:17
|
LL | let _ = len(&MAIN_SEPARATOR.to_string());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR`

error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String`
--> $DIR/manual_main_separator_str.rs:25:23
|
LL | let _: Vec<u16> = MAIN_SEPARATOR.to_string().encode_utf16().collect();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR`

error: taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String`
--> $DIR/manual_main_separator_str.rs:29:12
|
LL | f: &MAIN_SEPARATOR.to_string(),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `std::path::MAIN_SEPARATOR_STR`

error: aborting due to 4 previous errors