Skip to content

Commit 58df1e6

Browse files
committed
Auto merge of rust-lang#11003 - Centri3:absolute_path, r=Alexendoo,blyxyas
New lint [`absolute_paths`] Closes rust-lang#10568 Maybe we should make the max segments allowed a configuration option? I see quite a bit of 3-segment paths in clippy, and while I think only really `<mod/type>::<item>` or `<item>` should be (usually) used but anything above may be too widespread 😕 PS, despite this being "max segments allowed" it only lints if it's absolute, as is the point of the lint, e.g., `std::io::ErrorKind::etc` is linted but `io::ErrorKind::NotFound::etc` isn't changelog: New lint [`absolute_paths`]
2 parents d2c9047 + 9cf1509 commit 58df1e6

File tree

18 files changed

+365
-5
lines changed

18 files changed

+365
-5
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -4680,6 +4680,7 @@ Released 2018-09-13
46804680

46814681
<!-- lint disable no-unused-definitions -->
46824682
<!-- begin autogenerated links to lint list -->
4683+
[`absolute_paths`]: https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths
46834684
[`absurd_extreme_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#absurd_extreme_comparisons
46844685
[`alloc_instead_of_core`]: https://rust-lang.github.io/rust-clippy/master/index.html#alloc_instead_of_core
46854686
[`allow_attributes`]: https://rust-lang.github.io/rust-clippy/master/index.html#allow_attributes
@@ -5467,4 +5468,6 @@ Released 2018-09-13
54675468
[`accept-comment-above-statement`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-statement
54685469
[`accept-comment-above-attributes`]: https://doc.rust-lang.org/clippy/lint_configuration.html#accept-comment-above-attributes
54695470
[`allow-one-hash-in-raw-strings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#allow-one-hash-in-raw-strings
5471+
[`absolute-paths-max-segments`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-max-segments
5472+
[`absolute-paths-allowed-crates`]: https://doc.rust-lang.org/clippy/lint_configuration.html#absolute-paths-allowed-crates
54705473
<!-- end autogenerated links to configuration documentation -->

book/src/lint_configuration.md

+21
Original file line numberDiff line numberDiff line change
@@ -730,3 +730,24 @@ Whether to allow `r#""#` when `r""` can be used
730730
* [`unnecessary_raw_string_hashes`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_raw_string_hashes)
731731

732732

733+
## `absolute-paths-max-segments`
734+
The maximum number of segments a path can have before being linted, anything above this will
735+
be linted.
736+
737+
**Default Value:** `2` (`u64`)
738+
739+
---
740+
**Affected lints:**
741+
* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths)
742+
743+
744+
## `absolute-paths-allowed-crates`
745+
Which crates to allow absolute paths from
746+
747+
**Default Value:** `{}` (`rustc_data_structures::fx::FxHashSet<String>`)
748+
749+
---
750+
**Affected lints:**
751+
* [`absolute_paths`](https://rust-lang.github.io/rust-clippy/master/index.html#absolute_paths)
752+
753+

clippy_dev/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub fn clippy_project_root() -> PathBuf {
5151
for path in current_dir.ancestors() {
5252
let result = std::fs::read_to_string(path.join("Cargo.toml"));
5353
if let Err(err) = &result {
54-
if err.kind() == std::io::ErrorKind::NotFound {
54+
if err.kind() == io::ErrorKind::NotFound {
5555
continue;
5656
}
5757
}

clippy_lints/src/absolute_paths.rs

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::source::snippet_opt;
3+
use rustc_data_structures::fx::FxHashSet;
4+
use rustc_hir::def::{DefKind, Res};
5+
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
6+
use rustc_hir::{HirId, ItemKind, Node, Path};
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_session::{declare_tool_lint, impl_lint_pass};
9+
use rustc_span::symbol::kw;
10+
11+
declare_clippy_lint! {
12+
/// ### What it does
13+
/// Checks for usage of items through absolute paths, like `std::env::current_dir`.
14+
///
15+
/// ### Why is this bad?
16+
/// Many codebases have their own style when it comes to importing, but one that is seldom used
17+
/// is using absolute paths *everywhere*. This is generally considered unidiomatic, and you
18+
/// should add a `use` statement.
19+
///
20+
/// The default maximum segments (2) is pretty strict, you may want to increase this in
21+
/// `clippy.toml`.
22+
///
23+
/// Note: One exception to this is code from macro expansion - this does not lint such cases, as
24+
/// using absolute paths is the proper way of referencing items in one.
25+
///
26+
/// ### Example
27+
/// ```rust
28+
/// let x = std::f64::consts::PI;
29+
/// ```
30+
/// Use any of the below instead, or anything else:
31+
/// ```rust
32+
/// use std::f64;
33+
/// use std::f64::consts;
34+
/// use std::f64::consts::PI;
35+
/// let x = f64::consts::PI;
36+
/// let x = consts::PI;
37+
/// let x = PI;
38+
/// use std::f64::consts as f64_consts;
39+
/// let x = f64_consts::PI;
40+
/// ```
41+
#[clippy::version = "1.73.0"]
42+
pub ABSOLUTE_PATHS,
43+
restriction,
44+
"checks for usage of an item without a `use` statement"
45+
}
46+
impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]);
47+
48+
pub struct AbsolutePaths {
49+
pub absolute_paths_max_segments: u64,
50+
pub absolute_paths_allowed_crates: FxHashSet<String>,
51+
}
52+
53+
impl LateLintPass<'_> for AbsolutePaths {
54+
// We should only lint `QPath::Resolved`s, but since `Path` is only used in `Resolved` and `UsePath`
55+
// we don't need to use a visitor or anything as we can just check if the `Node` for `hir_id` isn't
56+
// a `Use`
57+
#[expect(clippy::cast_possible_truncation)]
58+
fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
59+
let Self {
60+
absolute_paths_max_segments,
61+
absolute_paths_allowed_crates,
62+
} = self;
63+
64+
if !path.span.from_expansion()
65+
&& let Some(node) = cx.tcx.hir().find(hir_id)
66+
&& !matches!(node, Node::Item(item) if matches!(item.kind, ItemKind::Use(_, _)))
67+
&& let [first, rest @ ..] = path.segments
68+
// Handle `::std`
69+
&& let (segment, len) = if first.ident.name == kw::PathRoot {
70+
// Indexing is fine as `PathRoot` must be followed by another segment. `len() - 1`
71+
// is fine here for the same reason
72+
(&rest[0], path.segments.len() - 1)
73+
} else {
74+
(first, path.segments.len())
75+
}
76+
&& len > *absolute_paths_max_segments as usize
77+
&& let Some(segment_snippet) = snippet_opt(cx, segment.ident.span)
78+
&& segment_snippet == segment.ident.as_str()
79+
{
80+
let is_abs_external =
81+
matches!(segment.res, Res::Def(DefKind::Mod, DefId { index, .. }) if index == CRATE_DEF_INDEX);
82+
let is_abs_crate = segment.ident.name == kw::Crate;
83+
84+
if is_abs_external && absolute_paths_allowed_crates.contains(segment.ident.name.as_str())
85+
|| is_abs_crate && absolute_paths_allowed_crates.contains("crate")
86+
{
87+
return;
88+
}
89+
90+
if is_abs_external || is_abs_crate {
91+
span_lint(
92+
cx,
93+
ABSOLUTE_PATHS,
94+
path.span,
95+
"consider bringing this path into scope with the `use` keyword",
96+
);
97+
}
98+
}
99+
}
100+
}

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
3737
crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
3838
#[cfg(feature = "internal")]
3939
crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
40+
crate::absolute_paths::ABSOLUTE_PATHS_INFO,
4041
crate::allow_attributes::ALLOW_ATTRIBUTES_INFO,
4142
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
4243
crate::approx_const::APPROX_CONSTANT_INFO,

clippy_lints/src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ mod declared_lints;
6565
mod renamed_lints;
6666

6767
// begin lints modules, do not remove this comment, it’s used in `update_lints`
68+
mod absolute_paths;
6869
mod allow_attributes;
6970
mod almost_complete_range;
7071
mod approx_const;
@@ -1082,6 +1083,14 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10821083
store.register_late_pass(|_| Box::new(manual_float_methods::ManualFloatMethods));
10831084
store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes));
10841085
store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError));
1086+
let absolute_paths_max_segments = conf.absolute_paths_max_segments;
1087+
let absolute_paths_allowed_crates = conf.absolute_paths_allowed_crates.clone();
1088+
store.register_late_pass(move |_| {
1089+
Box::new(absolute_paths::AbsolutePaths {
1090+
absolute_paths_max_segments,
1091+
absolute_paths_allowed_crates: absolute_paths_allowed_crates.clone(),
1092+
})
1093+
});
10851094
// add lints here, do not remove this comment, it's used in `new_lint`
10861095
}
10871096

clippy_lints/src/lifetimes.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use rustc_hir::{
1515
PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate,
1616
};
1717
use rustc_lint::{LateContext, LateLintPass, LintContext};
18+
use rustc_middle::hir::map::Map;
1819
use rustc_middle::hir::nested_filter as middle_nested_filter;
1920
use rustc_middle::lint::in_external_macro;
2021
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -620,7 +621,7 @@ impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F>
620621
where
621622
F: NestedFilter<'tcx>,
622623
{
623-
type Map = rustc_middle::hir::map::Map<'tcx>;
624+
type Map = Map<'tcx>;
624625
type NestedFilter = F;
625626

626627
// for lifetimes as parameters of generics

clippy_lints/src/redundant_static_lifetimes.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_ast::ast::{ConstItem, Item, ItemKind, StaticItem, Ty, TyKind};
55
use rustc_errors::Applicability;
66
use rustc_lint::{EarlyContext, EarlyLintPass};
77
use rustc_session::{declare_tool_lint, impl_lint_pass};
8+
use rustc_span::symbol::kw;
89

910
declare_clippy_lint! {
1011
/// ### What it does
@@ -64,7 +65,7 @@ impl RedundantStaticLifetimes {
6465
if let Some(lifetime) = *optional_lifetime {
6566
match borrow_type.ty.kind {
6667
TyKind::Path(..) | TyKind::Slice(..) | TyKind::Array(..) | TyKind::Tup(..) => {
67-
if lifetime.ident.name == rustc_span::symbol::kw::StaticLifetime {
68+
if lifetime.ident.name == kw::StaticLifetime {
6869
let snip = snippet(cx, borrow_type.ty.span, "<type>");
6970
let sugg = format!("&{}{snip}", borrow_type.mutbl.prefix_str());
7071
span_lint_and_then(

clippy_lints/src/utils/conf.rs

+10
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,16 @@ define_Conf! {
551551
///
552552
/// Whether to allow `r#""#` when `r""` can be used
553553
(allow_one_hash_in_raw_strings: bool = false),
554+
/// Lint: ABSOLUTE_PATHS.
555+
///
556+
/// The maximum number of segments a path can have before being linted, anything above this will
557+
/// be linted.
558+
(absolute_paths_max_segments: u64 = 2),
559+
/// Lint: ABSOLUTE_PATHS.
560+
///
561+
/// Which crates to allow absolute paths from
562+
(absolute_paths_allowed_crates: rustc_data_structures::fx::FxHashSet<String> =
563+
rustc_data_structures::fx::FxHashSet::default()),
554564
}
555565

556566
/// Search for the configuration file.

clippy_utils/src/mir/possible_origin.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
4444
let lhs = place.local;
4545
match rvalue {
4646
// Only consider `&mut`, which can modify origin place
47-
mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
47+
mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, borrowed) |
4848
// _2: &mut _;
4949
// _3 = move _2
5050
mir::Rvalue::Use(mir::Operand::Move(borrowed)) |

clippy_utils/src/usage.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::visitors::{for_each_expr, for_each_expr_with_closures, Descend};
22
use core::ops::ControlFlow;
3+
use hir::def::Res;
34
use rustc_hir::intravisit::{self, Visitor};
45
use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Node};
56
use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId};
@@ -127,7 +128,7 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for BindingUsageFinder<'a, 'tcx> {
127128
}
128129

129130
fn visit_path(&mut self, path: &hir::Path<'tcx>, _: hir::HirId) {
130-
if let hir::def::Res::Local(id) = path.res {
131+
if let Res::Local(id) = path.res {
131132
if self.binding_ids.contains(&id) {
132133
self.usage_found = true;
133134
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: consider bringing this path into scope with the `use` keyword
2+
--> $DIR/absolute_paths.rs:40:5
3+
|
4+
LL | std::f32::MAX;
5+
| ^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::absolute-paths` implied by `-D warnings`
8+
9+
error: consider bringing this path into scope with the `use` keyword
10+
--> $DIR/absolute_paths.rs:41:5
11+
|
12+
LL | core::f32::MAX;
13+
| ^^^^^^^^^^^^^^
14+
15+
error: consider bringing this path into scope with the `use` keyword
16+
--> $DIR/absolute_paths.rs:42:5
17+
|
18+
LL | ::core::f32::MAX;
19+
| ^^^^^^^^^^^^^^^^
20+
21+
error: consider bringing this path into scope with the `use` keyword
22+
--> $DIR/absolute_paths.rs:58:5
23+
|
24+
LL | ::std::f32::MAX;
25+
| ^^^^^^^^^^^^^^^
26+
27+
error: aborting due to 4 previous errors
28+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
error: consider bringing this path into scope with the `use` keyword
2+
--> $DIR/absolute_paths.rs:40:5
3+
|
4+
LL | std::f32::MAX;
5+
| ^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::absolute-paths` implied by `-D warnings`
8+
9+
error: consider bringing this path into scope with the `use` keyword
10+
--> $DIR/absolute_paths.rs:41:5
11+
|
12+
LL | core::f32::MAX;
13+
| ^^^^^^^^^^^^^^
14+
15+
error: consider bringing this path into scope with the `use` keyword
16+
--> $DIR/absolute_paths.rs:42:5
17+
|
18+
LL | ::core::f32::MAX;
19+
| ^^^^^^^^^^^^^^^^
20+
21+
error: consider bringing this path into scope with the `use` keyword
22+
--> $DIR/absolute_paths.rs:43:5
23+
|
24+
LL | crate::a::b::c::C;
25+
| ^^^^^^^^^^^^^^^^^
26+
27+
error: consider bringing this path into scope with the `use` keyword
28+
--> $DIR/absolute_paths.rs:44:5
29+
|
30+
LL | crate::a::b::c::d::e::f::F;
31+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
32+
33+
error: consider bringing this path into scope with the `use` keyword
34+
--> $DIR/absolute_paths.rs:45:5
35+
|
36+
LL | crate::a::A;
37+
| ^^^^^^^^^^^
38+
39+
error: consider bringing this path into scope with the `use` keyword
40+
--> $DIR/absolute_paths.rs:46:5
41+
|
42+
LL | crate::a::b::B;
43+
| ^^^^^^^^^^^^^^
44+
45+
error: consider bringing this path into scope with the `use` keyword
46+
--> $DIR/absolute_paths.rs:47:5
47+
|
48+
LL | crate::a::b::c::C::ZERO;
49+
| ^^^^^^^^^^^^^^^^^
50+
51+
error: consider bringing this path into scope with the `use` keyword
52+
--> $DIR/absolute_paths.rs:48:5
53+
|
54+
LL | helper::b::c::d::e::f();
55+
| ^^^^^^^^^^^^^^^^^^^^^
56+
57+
error: consider bringing this path into scope with the `use` keyword
58+
--> $DIR/absolute_paths.rs:49:5
59+
|
60+
LL | ::helper::b::c::d::e::f();
61+
| ^^^^^^^^^^^^^^^^^^^^^^^
62+
63+
error: consider bringing this path into scope with the `use` keyword
64+
--> $DIR/absolute_paths.rs:58:5
65+
|
66+
LL | ::std::f32::MAX;
67+
| ^^^^^^^^^^^^^^^
68+
69+
error: aborting due to 11 previous errors
70+

0 commit comments

Comments
 (0)