@@ -5,26 +5,30 @@ use clippy_utils::{
55use rustc_ast:: LitKind ;
66use rustc_errors:: Applicability ;
77use rustc_hir:: def_id:: DefId ;
8- use rustc_hir:: * ;
8+ use rustc_hir:: { Expr , ExprKind , QPath } ;
99use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
1010use rustc_middle:: { lint:: in_external_macro, ty} ;
1111use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1212use rustc_span:: { sym, Symbol } ;
1313
1414declare_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>) ->
138164fn 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 {
0 commit comments