Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b052acf

Browse files
committedSep 22, 2024
add unqualified_local_imports lint
1 parent 6ce3767 commit b052acf

11 files changed

+172
-0
lines changed
 

‎compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ declare_features! (
227227
(internal, staged_api, "1.0.0", None),
228228
/// Added for testing unstable lints; perma-unstable.
229229
(internal, test_unstable_lint, "1.60.0", None),
230+
/// Helps with formatting for `group_imports = "StdExternalCrate"`.
231+
(unstable, unqualified_local_imports, "CURRENT_RUSTC_VERSION", None),
230232
/// Use for stable + negative coherence and strict coherence depending on trait's
231233
/// rustc_strict_coherence value.
232234
(unstable, with_negative_coherence, "1.60.0", None),

‎compiler/rustc_lint/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,8 @@ lint_unnameable_test_items = cannot test inner items
899899
lint_unnecessary_qualification = unnecessary qualification
900900
.suggestion = remove the unnecessary path segments
901901
902+
lint_unqualified_local_imports = `use` of a local item without leading `self::`, `super::`, or `crate::`
903+
902904
lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
903905
.label = usage of unsafe attribute
904906
lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`

‎compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ mod tail_expr_drop_order;
8686
mod traits;
8787
mod types;
8888
mod unit_bindings;
89+
mod unqualified_local_imports;
8990
mod unused;
9091

9192
use async_closures::AsyncClosureUsage;
@@ -126,6 +127,7 @@ use tail_expr_drop_order::TailExprDropOrder;
126127
use traits::*;
127128
use types::*;
128129
use unit_bindings::*;
130+
use unqualified_local_imports::*;
129131
use unused::*;
130132

131133
#[rustfmt::skip]
@@ -249,6 +251,7 @@ late_lint_methods!(
249251
TailExprDropOrder: TailExprDropOrder,
250252
IfLetRescope: IfLetRescope::default(),
251253
StaticMutRefs: StaticMutRefs,
254+
UnqualifiedLocalImports: UnqualifiedLocalImports,
252255
]
253256
]
254257
);

‎compiler/rustc_lint/src/lints.rs

+4
Original file line numberDiff line numberDiff line change
@@ -3093,3 +3093,7 @@ pub(crate) enum MutRefSugg {
30933093
span: Span,
30943094
},
30953095
}
3096+
3097+
#[derive(LintDiagnostic)]
3098+
#[diag(lint_unqualified_local_imports)]
3099+
pub(crate) struct UnqualifiedLocalImportsDiag {}

‎compiler/rustc_lint/src/passes.rs

+4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ macro_rules! late_lint_methods {
1414
fn check_mod(a: &'tcx rustc_hir::Mod<'tcx>, b: rustc_hir::HirId);
1515
fn check_foreign_item(a: &'tcx rustc_hir::ForeignItem<'tcx>);
1616
fn check_item(a: &'tcx rustc_hir::Item<'tcx>);
17+
/// This is called *after* recursing into the item
18+
/// (in contrast to `check_item`, which is checked before).
1719
fn check_item_post(a: &'tcx rustc_hir::Item<'tcx>);
1820
fn check_local(a: &'tcx rustc_hir::LetStmt<'tcx>);
1921
fn check_block(a: &'tcx rustc_hir::Block<'tcx>);
@@ -135,6 +137,8 @@ macro_rules! early_lint_methods {
135137
fn check_crate(a: &rustc_ast::Crate);
136138
fn check_crate_post(a: &rustc_ast::Crate);
137139
fn check_item(a: &rustc_ast::Item);
140+
/// This is called *after* recursing into the item
141+
/// (in contrast to `check_item`, which is checked before).
138142
fn check_item_post(a: &rustc_ast::Item);
139143
fn check_local(a: &rustc_ast::Local);
140144
fn check_block(a: &rustc_ast::Block);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
use rustc_hir::def::{DefKind, Res};
2+
use rustc_hir::{self as hir};
3+
use rustc_session::{declare_lint, declare_lint_pass};
4+
use rustc_span::symbol::kw;
5+
6+
use crate::{lints, LateContext, LateLintPass, LintContext};
7+
8+
declare_lint! {
9+
/// The `unqualified_local_imports` lint checks for `use` items that import a local item using a
10+
/// path that does not start with `self::`, `super::`, or `crate::`.
11+
///
12+
/// ### Example
13+
///
14+
/// ```rust,edition2018
15+
/// #![warn(unqualified_local_imports)]
16+
///
17+
/// mod localmod {
18+
/// pub struct S;
19+
/// }
20+
///
21+
/// use localmod::S;
22+
/// # // We have to actually use `S`, or else the `unused` warnings suppress the lint we care about.
23+
/// # pub fn main() {
24+
/// # let _x = S;
25+
/// # }
26+
/// ```
27+
///
28+
/// {{produces}}
29+
///
30+
/// ### Explanation
31+
///
32+
/// This lint is meant to be used with the (unstable) rustfmt setting `group_imports = "StdExternalCrate"`.
33+
/// That setting makes rustfmt group `self::`, `super::`, and `crate::` imports separately from those
34+
/// refering to other crates. However, rustfmt cannot know whether `use c::S;` refers to a local module `c`
35+
/// or an external crate `c`, so it always gets categorized as an import from another crate.
36+
/// To ensure consistent grouping of imports from the local crate, all local imports must
37+
/// start with `self::`, `super::`, or `crate::`. This lint can be used to enforce that style.
38+
pub UNQUALIFIED_LOCAL_IMPORTS,
39+
Allow,
40+
"`use` of a local item without leading `self::`, `super::`, or `crate::`",
41+
@feature_gate = unqualified_local_imports;
42+
}
43+
44+
declare_lint_pass!(UnqualifiedLocalImports => [UNQUALIFIED_LOCAL_IMPORTS]);
45+
46+
impl<'tcx> LateLintPass<'tcx> for UnqualifiedLocalImports {
47+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
48+
let hir::ItemKind::Use(path, _kind) = item.kind else { return };
49+
// `path` has three resolutions for the type, module, value namespaces.
50+
// Check if any of them qualifies: local crate, and not a macro.
51+
// (Macros can't be imported any other way so we don't complain about them.)
52+
let is_local_import = |res: &Res| {
53+
matches!(
54+
res,
55+
hir::def::Res::Def(def_kind, def_id)
56+
if def_id.is_local() && !matches!(def_kind, DefKind::Macro(_)),
57+
)
58+
};
59+
if !path.res.iter().any(is_local_import) {
60+
return;
61+
}
62+
// So this does refer to something local. Let's check whether it starts with `self`,
63+
// `super`, or `crate`. If the path is empty, that means we have a `use *`, which is
64+
// equivalent to `use crate::*` so we don't fire the lint in that case.
65+
let Some(first_seg) = path.segments.first() else { return };
66+
if matches!(first_seg.ident.name, kw::SelfLower | kw::Super | kw::Crate) {
67+
return;
68+
}
69+
70+
let encl_item_id = cx.tcx.hir().get_parent_item(item.hir_id());
71+
let encl_item = cx.tcx.hir_node_by_def_id(encl_item_id.def_id);
72+
if encl_item.fn_kind().is_some() {
73+
// `use` in a method -- don't lint, that leads to too many undesirable lints
74+
// when a function imports all variants of an enum.
75+
return;
76+
}
77+
78+
// This `use` qualifies for our lint!
79+
cx.emit_span_lint(
80+
UNQUALIFIED_LOCAL_IMPORTS,
81+
first_seg.ident.span,
82+
lints::UnqualifiedLocalImportsDiag {},
83+
);
84+
}
85+
}

‎compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,7 @@ symbols! {
20582058
unmarked_api,
20592059
unnamed_fields,
20602060
unpin,
2061+
unqualified_local_imports,
20612062
unreachable,
20622063
unreachable_2015,
20632064
unreachable_2015_macro,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//@ check-pass
2+
3+
#![allow(unqualified_local_imports)]
4+
//~^ WARNING unknown lint: `unqualified_local_imports`
5+
6+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
warning: unknown lint: `unqualified_local_imports`
2+
--> $DIR/feature-gate-unqualified-local-imports.rs:3:1
3+
|
4+
LL | #![allow(unqualified_local_imports)]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: the `unqualified_local_imports` lint is unstable
8+
= help: add `#![feature(unqualified_local_imports)]` to the crate attributes to enable
9+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
10+
= note: `#[warn(unknown_lints)]` on by default
11+
12+
warning: 1 warning emitted
13+
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//@compile-flags: --edition 2018
2+
#![feature(unqualified_local_imports)]
3+
#![deny(unqualified_local_imports)]
4+
5+
mod localmod {
6+
pub struct S;
7+
pub struct T;
8+
}
9+
10+
// Not a local import, so no lint.
11+
use std::cell::Cell;
12+
13+
// Implicitly local import, gets lint.
14+
use localmod::S; //~ERROR: unqualified
15+
16+
// Explicitly local import, no lint.
17+
use self::localmod::T;
18+
19+
macro_rules! mymacro {
20+
($cond:expr) => {
21+
if !$cond {
22+
continue;
23+
}
24+
};
25+
}
26+
// Macro import: no lint, as there is no other way to write it.
27+
pub(crate) use mymacro;
28+
29+
#[allow(unused)]
30+
enum LocalEnum {
31+
VarA,
32+
VarB,
33+
}
34+
35+
fn main() {
36+
// Import in a function, no lint.
37+
use LocalEnum::*;
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: `use` of a local item without leading `self::`, `super::`, or `crate::`
2+
--> $DIR/unqualified_local_imports.rs:14:5
3+
|
4+
LL | use localmod::S;
5+
| ^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/unqualified_local_imports.rs:3:9
9+
|
10+
LL | #![deny(unqualified_local_imports)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to 1 previous error
14+

0 commit comments

Comments
 (0)
Please sign in to comment.