1
1
use clippy_config:: Conf ;
2
2
use clippy_utils:: diagnostics:: span_lint;
3
- use clippy_utils:: source :: snippet_opt ;
3
+ use clippy_utils:: is_from_proc_macro ;
4
4
use rustc_data_structures:: fx:: FxHashSet ;
5
5
use rustc_hir:: def:: { DefKind , Res } ;
6
6
use rustc_hir:: def_id:: { DefId , CRATE_DEF_INDEX } ;
7
7
use rustc_hir:: { HirId , ItemKind , Node , Path } ;
8
8
use rustc_lint:: { LateContext , LateLintPass } ;
9
9
use rustc_session:: impl_lint_pass;
10
10
use rustc_span:: symbol:: kw;
11
+ use rustc_span:: Symbol ;
11
12
12
13
declare_clippy_lint ! {
13
14
/// ### What it does
@@ -24,6 +25,13 @@ declare_clippy_lint! {
24
25
/// Note: One exception to this is code from macro expansion - this does not lint such cases, as
25
26
/// using absolute paths is the proper way of referencing items in one.
26
27
///
28
+ /// ### Known issues
29
+ ///
30
+ /// There are currently a few cases which are not caught by this lint:
31
+ /// * Macro calls. e.g. `path::to::macro!()`
32
+ /// * Derive macros. e.g. `#[derive(path::to::macro)]`
33
+ /// * Attribute macros. e.g. `#[path::to::macro]`
34
+ ///
27
35
/// ### Example
28
36
/// ```no_run
29
37
/// let x = std::f64::consts::PI;
@@ -48,63 +56,66 @@ impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]);
48
56
49
57
pub struct AbsolutePaths {
50
58
pub absolute_paths_max_segments : u64 ,
51
- pub absolute_paths_allowed_crates : & ' static FxHashSet < String > ,
59
+ pub absolute_paths_allowed_crates : FxHashSet < Symbol > ,
52
60
}
53
61
54
62
impl AbsolutePaths {
55
63
pub fn new ( conf : & ' static Conf ) -> Self {
56
64
Self {
57
65
absolute_paths_max_segments : conf. absolute_paths_max_segments ,
58
- absolute_paths_allowed_crates : & conf. absolute_paths_allowed_crates ,
66
+ absolute_paths_allowed_crates : conf
67
+ . absolute_paths_allowed_crates
68
+ . iter ( )
69
+ . map ( |x| Symbol :: intern ( x) )
70
+ . collect ( ) ,
59
71
}
60
72
}
61
73
}
62
74
63
- impl LateLintPass < ' _ > for AbsolutePaths {
75
+ impl < ' tcx > LateLintPass < ' tcx > for AbsolutePaths {
64
76
// We should only lint `QPath::Resolved`s, but since `Path` is only used in `Resolved` and `UsePath`
65
77
// we don't need to use a visitor or anything as we can just check if the `Node` for `hir_id` isn't
66
78
// a `Use`
67
- #[ expect( clippy:: cast_possible_truncation) ]
68
- fn check_path ( & mut self , cx : & LateContext < ' _ > , path : & Path < ' _ > , hir_id : HirId ) {
69
- let Self {
70
- absolute_paths_max_segments,
71
- absolute_paths_allowed_crates,
72
- } = self ;
73
-
74
- if !path. span . from_expansion ( )
75
- && let node = cx. tcx . hir_node ( hir_id)
76
- && !matches ! ( node, Node :: Item ( item) if matches!( item. kind, ItemKind :: Use ( _, _) ) )
77
- && let [ first, rest @ ..] = path. segments
78
- // Handle `::std`
79
- && let ( segment, len) = if first. ident . name == kw:: PathRoot {
80
- // Indexing is fine as `PathRoot` must be followed by another segment. `len() - 1`
81
- // is fine here for the same reason
82
- ( & rest[ 0 ] , path. segments . len ( ) - 1 )
83
- } else {
84
- ( first, path. segments . len ( ) )
85
- }
86
- && len > * absolute_paths_max_segments as usize
87
- && let Some ( segment_snippet) = snippet_opt ( cx, segment. ident . span )
88
- && segment_snippet == segment. ident . as_str ( )
89
- {
90
- let is_abs_external =
91
- matches ! ( segment. res, Res :: Def ( DefKind :: Mod , DefId { index, .. } ) if index == CRATE_DEF_INDEX ) ;
92
- let is_abs_crate = segment. ident . name == kw:: Crate ;
93
-
94
- if is_abs_external && absolute_paths_allowed_crates. contains ( segment. ident . name . as_str ( ) )
95
- || is_abs_crate && absolute_paths_allowed_crates. contains ( "crate" )
79
+ fn check_path ( & mut self , cx : & LateContext < ' tcx > , path : & Path < ' tcx > , hir_id : HirId ) {
80
+ let segments = match path. segments {
81
+ [ ] | [ _] => return ,
82
+ // Don't count enum variants and trait items as part of the length.
83
+ [ rest @ .., _]
84
+ if let [ .., s] = rest
85
+ && matches ! ( s. res, Res :: Def ( DefKind :: Enum | DefKind :: Trait | DefKind :: TraitAlias , _) ) =>
86
+ {
87
+ rest
88
+ } ,
89
+ path => path,
90
+ } ;
91
+ if let [ s1, s2, ..] = segments
92
+ && let has_root = s1. ident . name == kw:: PathRoot
93
+ && let first = if has_root { s2 } else { s1 }
94
+ && let len = segments. len ( ) - usize:: from ( has_root)
95
+ && len as u64 > self . absolute_paths_max_segments
96
+ && let crate_name = if let Res :: Def ( DefKind :: Mod , DefId { index, .. } ) = first. res
97
+ && index == CRATE_DEF_INDEX
96
98
{
99
+ // `other_crate::foo` or `::other_crate::foo`
100
+ first. ident . name
101
+ } else if first. ident . name == kw:: Crate || has_root {
102
+ // `::foo` or `crate::foo`
103
+ kw:: Crate
104
+ } else {
97
105
return ;
98
106
}
99
-
100
- if is_abs_external || is_abs_crate {
101
- span_lint (
102
- cx,
103
- ABSOLUTE_PATHS ,
104
- path. span ,
105
- "consider bringing this path into scope with the `use` keyword" ,
106
- ) ;
107
- }
107
+ && !path. span . from_expansion ( )
108
+ && let node = cx. tcx . hir_node ( hir_id)
109
+ && !matches ! ( node, Node :: Item ( item) if matches!( item. kind, ItemKind :: Use ( ..) ) )
110
+ && !self . absolute_paths_allowed_crates . contains ( & crate_name)
111
+ && !is_from_proc_macro ( cx, path)
112
+ {
113
+ span_lint (
114
+ cx,
115
+ ABSOLUTE_PATHS ,
116
+ path. span ,
117
+ "consider bringing this path into scope with the `use` keyword" ,
118
+ ) ;
108
119
}
109
120
}
110
121
}
0 commit comments