Skip to content

Commit 3e33a75

Browse files
committed
Refactor and add description, rewrite is_path_constructor
1 parent 9c56f21 commit 3e33a75

File tree

3 files changed

+64
-29
lines changed

3 files changed

+64
-29
lines changed

clippy_lints/src/bare_dos_device_names.rs

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,30 @@ use clippy_utils::{
55
use rustc_ast::LitKind;
66
use rustc_errors::Applicability;
77
use rustc_hir::def_id::DefId;
8-
use rustc_hir::*;
8+
use rustc_hir::{Expr, ExprKind, QPath};
99
use rustc_lint::{LateContext, LateLintPass, LintContext};
1010
use rustc_middle::{lint::in_external_macro, ty};
1111
use rustc_session::{declare_lint_pass, declare_tool_lint};
1212
use rustc_span::{sym, Symbol};
1313

1414
declare_clippy_lint! {
1515
/// ### What it does
16-
/// TODO please do soon
16+
/// Checks for paths implicitly referring to DOS devices.
1717
///
1818
/// ### Why is this bad?
19-
/// TODO please
19+
/// This will lead to unexpected path transformations on Windows. Usually, the programmer will
20+
/// have intended to refer to a file/folder instead.
2021
///
2122
/// ### Example
22-
/// ```rust
23-
/// // TODO
23+
/// ```rust,ignore
24+
/// let _ = PathBuf::from("CON");
2425
/// ```
2526
/// Use instead:
26-
/// ```rust
27-
/// // TODO
27+
/// ```rust,ignore
28+
/// // If this was unintended:
29+
/// let _ = PathBuf::from("./CON");
30+
/// // To silence the lint:
31+
/// let _ = PathBuf::from(r"\\.\CON");
2832
/// ```
2933
#[clippy::version = "1.72.0"]
3034
pub BARE_DOS_DEVICE_NAMES,
@@ -73,7 +77,7 @@ impl<'tcx> LateLintPass<'tcx> for BareDosDeviceNames {
7377
| "prn"
7478
)
7579
&& let Some(parent) = get_parent_expr(cx, expr)
76-
&& (is_path_buf_from_or_path_new(cx, parent) || is_path_ty(cx, expr, parent))
80+
&& (is_path_constructor(cx, parent) || is_path_ty(cx, expr, parent))
7781
&& !is_from_proc_macro(cx, expr)
7882
{
7983
span_lint_and_then(
@@ -86,7 +90,8 @@ impl<'tcx> LateLintPass<'tcx> for BareDosDeviceNames {
8690
diag.span_suggestion_verbose(
8791
expr.span,
8892
"if this is intended, try",
89-
format!(r#""\\.\{str_sym}""#),
93+
// FIXME: I have zero clue why it normalizes this. `\` -> `/`
94+
format!(r#"r"\\.\{str_sym}"\"#),
9095
Applicability::MaybeIncorrect,
9196
);
9297

@@ -103,22 +108,43 @@ impl<'tcx> LateLintPass<'tcx> for BareDosDeviceNames {
103108
}
104109
}
105110

106-
/// Gets whether the `Expr` is an argument to `Path::new` or `PathBuf::from`. The caller must
107-
/// provide the parent `Expr`, for performance's sake.
111+
/// Gets whether the `Expr` is an argument to path type constructors. The caller must provide the
112+
/// parent `Expr`, for performance's sake.
108113
///
109-
/// TODO: We can likely refactor this like we did with `LINTED_TRAITS`.
110-
fn is_path_buf_from_or_path_new(cx: &LateContext<'_>, parent: &Expr<'_>) -> bool {
114+
/// We can't use `is_path_ty` as these take `AsRef<OsStr>` or similar.
115+
///
116+
/// TODO: Should we lint `OsStr` too, in `is_path_ty`? I personally don't think so.
117+
fn is_path_constructor(cx: &LateContext<'_>, parent: &Expr<'_>) -> bool {
118+
enum DefPathOrTyAndName {
119+
/// Something from `clippy_utils::paths`.
120+
DefPath(&'static [&'static str]),
121+
/// The type's name and the method's name. The type must be a diagnostic item and not its
122+
/// constructor.
123+
///
124+
/// Currently, this is only used for `PathBuf::from`. `PathBuf::from` is unfortunately
125+
/// tricky, as all we end up having for `match_def_path` is `core::convert::From::from`,
126+
/// not `std::path::PathBuf::from`. Basically useless.
127+
TyAndName((Symbol, Symbol)),
128+
}
129+
// Provides no additional clarity
130+
use DefPathOrTyAndName::{DefPath, TyAndName};
131+
132+
const LINTED_METHODS: &[DefPathOrTyAndName] = &[DefPath(&PATH_NEW), TyAndName((sym::PathBuf, sym::from))];
133+
111134
if let ExprKind::Call(path, _) = parent.kind
112135
&& let ExprKind::Path(qpath) = path.kind
113136
&& let QPath::TypeRelative(ty, last_segment) = qpath
114137
&& let Some(call_def_id) = path_res(cx, path).opt_def_id()
115138
&& let Some(ty_def_id) = path_res(cx, ty).opt_def_id()
116-
&& (match_def_path(cx, call_def_id, &PATH_NEW)
117-
// `PathBuf::from` is unfortunately tricky, as all we end up having for `match_def_path`
118-
// is `core::convert::From::from`, not `std::path::PathBuf::from`. Basically useless.
119-
|| cx.tcx.is_diagnostic_item(sym::PathBuf, ty_def_id) && last_segment.ident.as_str() == "from")
120139
{
121-
return true;
140+
return LINTED_METHODS.iter().any(|method| {
141+
match method {
142+
DefPath(path) => match_def_path(cx, call_def_id, path),
143+
TyAndName((ty_name, method_name)) => {
144+
cx.tcx.is_diagnostic_item(*ty_name, ty_def_id) && last_segment.ident.name == *method_name
145+
},
146+
}
147+
});
122148
}
123149

124150
false
@@ -138,8 +164,15 @@ fn get_def_id_and_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) ->
138164
fn is_path_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, parent: &'tcx Expr<'tcx>) -> bool {
139165
const LINTED_TRAITS: &[(Symbol, Symbol)] = &[
140166
(sym::AsRef, sym::Path),
141-
(sym::Into, sym::PathBuf),
167+
(sym::AsMut, sym::Path),
168+
// Basically useless, but let's lint these anyway
169+
(sym::AsRef, sym::PathBuf),
170+
(sym::AsMut, sym::PathBuf),
142171
(sym::Into, sym::Path),
172+
(sym::Into, sym::PathBuf),
173+
// Never seen `From` used in a generic context before, but let's lint these anyway
174+
(sym::From, sym::Path),
175+
(sym::From, sym::PathBuf),
143176
// TODO: Let's add more traits here.
144177
];
145178

@@ -168,7 +201,7 @@ fn is_path_ty<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, parent: &'tc
168201
{
169202
if let ty::ClauseKind::Trait(trit) = predicate
170203
&& trit.trait_ref.self_ty() == arg_ty
171-
// I believe `0` is always `Self`, so `T` or `impl <trait>`
204+
// I believe `0` is always `Self`, i.e., `T` or `impl <trait>` so get `1` instead
172205
&& let [_, subst] = trit.trait_ref.substs.as_slice()
173206
&& let Some(as_ref_ty) = subst.as_type()
174207
{

tests/ui/bare_dos_device_names.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ fn a<T: AsRef<Path>>(t: T) {}
1313

1414
fn b(t: impl AsRef<Path>) {}
1515

16+
// TODO: More tests for traits.
17+
1618
fn main() {
1719
a("con");
1820
b("conin$");

tests/ui/bare_dos_device_names.stderr

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
11
error: this path refers to a DOS device
2-
--> $DIR/bare_dos_device_names.rs:17:7
2+
--> $DIR/bare_dos_device_names.rs:19:7
33
|
44
LL | a("con");
55
| ^^^^^
66
|
77
= note: `-D clippy::bare-dos-device-names` implied by `-D warnings`
88
help: if this is intended, try
99
|
10-
LL | a("//./con");
11-
| ~~~~~~~~~
10+
LL | a(r"//./con"/);
11+
| ~~~~~~~~~~~
1212
help: if this was intended to point to a file or folder, try
1313
|
1414
LL | a("./con");
1515
| ~~~~~~~
1616

1717
error: this path refers to a DOS device
18-
--> $DIR/bare_dos_device_names.rs:18:7
18+
--> $DIR/bare_dos_device_names.rs:20:7
1919
|
2020
LL | b("conin$");
2121
| ^^^^^^^^
2222
|
2323
help: if this is intended, try
2424
|
25-
LL | b("//./conin$");
26-
| ~~~~~~~~~~~~
25+
LL | b(r"//./conin$"/);
26+
| ~~~~~~~~~~~~~~
2727
help: if this was intended to point to a file or folder, try
2828
|
2929
LL | b("./conin$");
3030
| ~~~~~~~~~~
3131

3232
error: this path refers to a DOS device
33-
--> $DIR/bare_dos_device_names.rs:19:16
33+
--> $DIR/bare_dos_device_names.rs:21:16
3434
|
3535
LL | File::open("conin$");
3636
| ^^^^^^^^
3737
|
3838
help: if this is intended, try
3939
|
40-
LL | File::open("//./conin$");
41-
| ~~~~~~~~~~~~
40+
LL | File::open(r"//./conin$"/);
41+
| ~~~~~~~~~~~~~~
4242
help: if this was intended to point to a file or folder, try
4343
|
4444
LL | File::open("./conin$");

0 commit comments

Comments
 (0)