about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_feature/src/unstable.rs2
-rw-r--r--compiler/rustc_lint/messages.ftl2
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/lints.rs4
-rw-r--r--compiler/rustc_lint/src/passes.rs4
-rw-r--r--compiler/rustc_lint/src/unqualified_local_imports.rs85
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--tests/ui/feature-gates/feature-gate-unqualified-local-imports.rs6
-rw-r--r--tests/ui/feature-gates/feature-gate-unqualified-local-imports.stderr13
-rw-r--r--tests/ui/lint/unqualified_local_imports.rs38
-rw-r--r--tests/ui/lint/unqualified_local_imports.stderr14
11 files changed, 172 insertions, 0 deletions
diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs
index 0b09e9fbb85..63b4b272f76 100644
--- a/compiler/rustc_feature/src/unstable.rs
+++ b/compiler/rustc_feature/src/unstable.rs
@@ -227,6 +227,8 @@ declare_features! (
     (internal, staged_api, "1.0.0", None),
     /// Added for testing unstable lints; perma-unstable.
     (internal, test_unstable_lint, "1.60.0", None),
+    /// Helps with formatting for `group_imports = "StdExternalCrate"`.
+    (unstable, unqualified_local_imports, "CURRENT_RUSTC_VERSION", None),
     /// Use for stable + negative coherence and strict coherence depending on trait's
     /// rustc_strict_coherence value.
     (unstable, with_negative_coherence, "1.60.0", None),
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index e71c5676ce4..83e97537c15 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -899,6 +899,8 @@ lint_unnameable_test_items = cannot test inner items
 lint_unnecessary_qualification = unnecessary qualification
     .suggestion = remove the unnecessary path segments
 
+lint_unqualified_local_imports = `use` of a local item without leading `self::`, `super::`, or `crate::`
+
 lint_unsafe_attr_outside_unsafe = unsafe attribute used without unsafe
     .label = usage of unsafe attribute
 lint_unsafe_attr_outside_unsafe_suggestion = wrap the attribute in `unsafe(...)`
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 652a40dada8..c74cb866f21 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -86,6 +86,7 @@ mod tail_expr_drop_order;
 mod traits;
 mod types;
 mod unit_bindings;
+mod unqualified_local_imports;
 mod unused;
 
 use async_closures::AsyncClosureUsage;
@@ -126,6 +127,7 @@ use tail_expr_drop_order::TailExprDropOrder;
 use traits::*;
 use types::*;
 use unit_bindings::*;
+use unqualified_local_imports::*;
 use unused::*;
 
 #[rustfmt::skip]
@@ -249,6 +251,7 @@ late_lint_methods!(
             TailExprDropOrder: TailExprDropOrder,
             IfLetRescope: IfLetRescope::default(),
             StaticMutRefs: StaticMutRefs,
+            UnqualifiedLocalImports: UnqualifiedLocalImports,
         ]
     ]
 );
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 76002cc8425..6979d56844a 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -3093,3 +3093,7 @@ pub(crate) enum MutRefSugg {
         span: Span,
     },
 }
+
+#[derive(LintDiagnostic)]
+#[diag(lint_unqualified_local_imports)]
+pub(crate) struct UnqualifiedLocalImportsDiag {}
diff --git a/compiler/rustc_lint/src/passes.rs b/compiler/rustc_lint/src/passes.rs
index 17ec58c7957..a1d436e0d3d 100644
--- a/compiler/rustc_lint/src/passes.rs
+++ b/compiler/rustc_lint/src/passes.rs
@@ -14,6 +14,8 @@ macro_rules! late_lint_methods {
             fn check_mod(a: &'tcx rustc_hir::Mod<'tcx>, b: rustc_hir::HirId);
             fn check_foreign_item(a: &'tcx rustc_hir::ForeignItem<'tcx>);
             fn check_item(a: &'tcx rustc_hir::Item<'tcx>);
+            /// This is called *after* recursing into the item
+            /// (in contrast to `check_item`, which is checked before).
             fn check_item_post(a: &'tcx rustc_hir::Item<'tcx>);
             fn check_local(a: &'tcx rustc_hir::LetStmt<'tcx>);
             fn check_block(a: &'tcx rustc_hir::Block<'tcx>);
@@ -135,6 +137,8 @@ macro_rules! early_lint_methods {
             fn check_crate(a: &rustc_ast::Crate);
             fn check_crate_post(a: &rustc_ast::Crate);
             fn check_item(a: &rustc_ast::Item);
+            /// This is called *after* recursing into the item
+            /// (in contrast to `check_item`, which is checked before).
             fn check_item_post(a: &rustc_ast::Item);
             fn check_local(a: &rustc_ast::Local);
             fn check_block(a: &rustc_ast::Block);
diff --git a/compiler/rustc_lint/src/unqualified_local_imports.rs b/compiler/rustc_lint/src/unqualified_local_imports.rs
new file mode 100644
index 00000000000..bea01a33bd6
--- /dev/null
+++ b/compiler/rustc_lint/src/unqualified_local_imports.rs
@@ -0,0 +1,85 @@
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{self as hir};
+use rustc_session::{declare_lint, declare_lint_pass};
+use rustc_span::symbol::kw;
+
+use crate::{LateContext, LateLintPass, LintContext, lints};
+
+declare_lint! {
+    /// The `unqualified_local_imports` lint checks for `use` items that import a local item using a
+    /// path that does not start with `self::`, `super::`, or `crate::`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,edition2018
+    /// #![warn(unqualified_local_imports)]
+    ///
+    /// mod localmod {
+    ///     pub struct S;
+    /// }
+    ///
+    /// use localmod::S;
+    /// # // We have to actually use `S`, or else the `unused` warnings suppress the lint we care about.
+    /// # pub fn main() {
+    /// #     let _x = S;
+    /// # }
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// This lint is meant to be used with the (unstable) rustfmt setting `group_imports = "StdExternalCrate"`.
+    /// That setting makes rustfmt group `self::`, `super::`, and `crate::` imports separately from those
+    /// refering to other crates. However, rustfmt cannot know whether `use c::S;` refers to a local module `c`
+    /// or an external crate `c`, so it always gets categorized as an import from another crate.
+    /// To ensure consistent grouping of imports from the local crate, all local imports must
+    /// start with `self::`, `super::`, or `crate::`. This lint can be used to enforce that style.
+    pub UNQUALIFIED_LOCAL_IMPORTS,
+    Allow,
+    "`use` of a local item without leading `self::`, `super::`, or `crate::`",
+    @feature_gate = unqualified_local_imports;
+}
+
+declare_lint_pass!(UnqualifiedLocalImports => [UNQUALIFIED_LOCAL_IMPORTS]);
+
+impl<'tcx> LateLintPass<'tcx> for UnqualifiedLocalImports {
+    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
+        let hir::ItemKind::Use(path, _kind) = item.kind else { return };
+        // `path` has three resolutions for the type, module, value namespaces.
+        // Check if any of them qualifies: local crate, and not a macro.
+        // (Macros can't be imported any other way so we don't complain about them.)
+        let is_local_import = |res: &Res| {
+            matches!(
+                res,
+                hir::def::Res::Def(def_kind, def_id)
+                    if def_id.is_local() && !matches!(def_kind, DefKind::Macro(_)),
+            )
+        };
+        if !path.res.iter().any(is_local_import) {
+            return;
+        }
+        // So this does refer to something local. Let's check whether it starts with `self`,
+        // `super`, or `crate`. If the path is empty, that means we have a `use *`, which is
+        // equivalent to `use crate::*` so we don't fire the lint in that case.
+        let Some(first_seg) = path.segments.first() else { return };
+        if matches!(first_seg.ident.name, kw::SelfLower | kw::Super | kw::Crate) {
+            return;
+        }
+
+        let encl_item_id = cx.tcx.hir().get_parent_item(item.hir_id());
+        let encl_item = cx.tcx.hir_node_by_def_id(encl_item_id.def_id);
+        if encl_item.fn_kind().is_some() {
+            // `use` in a method -- don't lint, that leads to too many undesirable lints
+            // when a function imports all variants of an enum.
+            return;
+        }
+
+        // This `use` qualifies for our lint!
+        cx.emit_span_lint(
+            UNQUALIFIED_LOCAL_IMPORTS,
+            first_seg.ident.span,
+            lints::UnqualifiedLocalImportsDiag {},
+        );
+    }
+}
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index cfe990a225f..8f226b26bef 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -2058,6 +2058,7 @@ symbols! {
         unmarked_api,
         unnamed_fields,
         unpin,
+        unqualified_local_imports,
         unreachable,
         unreachable_2015,
         unreachable_2015_macro,
diff --git a/tests/ui/feature-gates/feature-gate-unqualified-local-imports.rs b/tests/ui/feature-gates/feature-gate-unqualified-local-imports.rs
new file mode 100644
index 00000000000..29929e40f89
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-unqualified-local-imports.rs
@@ -0,0 +1,6 @@
+//@ check-pass
+
+#![allow(unqualified_local_imports)]
+//~^ WARNING unknown lint: `unqualified_local_imports`
+
+fn main() {}
diff --git a/tests/ui/feature-gates/feature-gate-unqualified-local-imports.stderr b/tests/ui/feature-gates/feature-gate-unqualified-local-imports.stderr
new file mode 100644
index 00000000000..22cd3bf4c6f
--- /dev/null
+++ b/tests/ui/feature-gates/feature-gate-unqualified-local-imports.stderr
@@ -0,0 +1,13 @@
+warning: unknown lint: `unqualified_local_imports`
+  --> $DIR/feature-gate-unqualified-local-imports.rs:3:1
+   |
+LL | #![allow(unqualified_local_imports)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: the `unqualified_local_imports` lint is unstable
+   = help: add `#![feature(unqualified_local_imports)]` to the crate attributes to enable
+   = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
+   = note: `#[warn(unknown_lints)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/lint/unqualified_local_imports.rs b/tests/ui/lint/unqualified_local_imports.rs
new file mode 100644
index 00000000000..9de71471342
--- /dev/null
+++ b/tests/ui/lint/unqualified_local_imports.rs
@@ -0,0 +1,38 @@
+//@compile-flags: --edition 2018
+#![feature(unqualified_local_imports)]
+#![deny(unqualified_local_imports)]
+
+mod localmod {
+    pub struct S;
+    pub struct T;
+}
+
+// Not a local import, so no lint.
+use std::cell::Cell;
+
+// Implicitly local import, gets lint.
+use localmod::S; //~ERROR: unqualified
+
+// Explicitly local import, no lint.
+use self::localmod::T;
+
+macro_rules! mymacro {
+    ($cond:expr) => {
+        if !$cond {
+            continue;
+        }
+    };
+}
+// Macro import: no lint, as there is no other way to write it.
+pub(crate) use mymacro;
+
+#[allow(unused)]
+enum LocalEnum {
+    VarA,
+    VarB,
+}
+
+fn main() {
+    // Import in a function, no lint.
+    use LocalEnum::*;
+}
diff --git a/tests/ui/lint/unqualified_local_imports.stderr b/tests/ui/lint/unqualified_local_imports.stderr
new file mode 100644
index 00000000000..81d12f55949
--- /dev/null
+++ b/tests/ui/lint/unqualified_local_imports.stderr
@@ -0,0 +1,14 @@
+error: `use` of a local item without leading `self::`, `super::`, or `crate::`
+  --> $DIR/unqualified_local_imports.rs:14:5
+   |
+LL | use localmod::S;
+   |     ^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/unqualified_local_imports.rs:3:9
+   |
+LL | #![deny(unqualified_local_imports)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+