11use clippy_config:: Conf ;
22use clippy_utils:: diagnostics:: span_lint;
3- use clippy_utils:: source :: snippet_opt ;
3+ use clippy_utils:: is_from_proc_macro ;
44use rustc_data_structures:: fx:: FxHashSet ;
55use rustc_hir:: def:: { DefKind , Res } ;
66use rustc_hir:: def_id:: { DefId , CRATE_DEF_INDEX } ;
77use rustc_hir:: { HirId , ItemKind , Node , Path } ;
88use rustc_lint:: { LateContext , LateLintPass } ;
99use rustc_session:: impl_lint_pass;
1010use rustc_span:: symbol:: kw;
11+ use rustc_span:: Symbol ;
1112
1213declare_clippy_lint ! {
1314 /// ### What it does
@@ -24,6 +25,13 @@ declare_clippy_lint! {
2425 /// Note: One exception to this is code from macro expansion - this does not lint such cases, as
2526 /// using absolute paths is the proper way of referencing items in one.
2627 ///
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+ ///
2735 /// ### Example
2836 /// ```no_run
2937 /// let x = std::f64::consts::PI;
@@ -48,63 +56,66 @@ impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]);
4856
4957pub struct AbsolutePaths {
5058 pub absolute_paths_max_segments : u64 ,
51- pub absolute_paths_allowed_crates : & ' static FxHashSet < String > ,
59+ pub absolute_paths_allowed_crates : FxHashSet < Symbol > ,
5260}
5361
5462impl AbsolutePaths {
5563 pub fn new ( conf : & ' static Conf ) -> Self {
5664 Self {
5765 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 ( ) ,
5971 }
6072 }
6173}
6274
63- impl LateLintPass < ' _ > for AbsolutePaths {
75+ impl < ' tcx > LateLintPass < ' tcx > for AbsolutePaths {
6476 // We should only lint `QPath::Resolved`s, but since `Path` is only used in `Resolved` and `UsePath`
6577 // we don't need to use a visitor or anything as we can just check if the `Node` for `hir_id` isn't
6678 // 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
9698 {
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 {
97105 return ;
98106 }
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+ ) ;
108119 }
109120 }
110121}
0 commit comments