Skip to content

Commit

Permalink
add unqualified_local_imports lint
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Jul 24, 2024
1 parent 6106b05 commit 0b9e562
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 0 deletions.
2 changes: 2 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,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(...)`
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ mod shadowed_into_iter;
mod traits;
mod types;
mod unit_bindings;
mod unqualified_local_imports;
mod unused;

pub use shadowed_into_iter::{ARRAY_INTO_ITER, BOXED_SLICE_INTO_ITER};
Expand Down Expand Up @@ -118,6 +119,7 @@ use shadowed_into_iter::ShadowedIntoIter;
use traits::*;
use types::*;
use unit_bindings::*;
use unqualified_local_imports::*;
use unused::*;

pub use builtin::{MissingDoc, SoftLints};
Expand Down Expand Up @@ -234,6 +236,7 @@ late_lint_methods!(
AsyncFnInTrait: AsyncFnInTrait,
NonLocalDefinitions: NonLocalDefinitions::default(),
ImplTraitOvercaptures: ImplTraitOvercaptures,
UnqualifiedLocalImports: UnqualifiedLocalImports,
]
]
);
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2953,3 +2953,7 @@ pub struct UnsafeAttrOutsideUnsafeSuggestion {
pub struct OutOfScopeMacroCalls {
pub path: String,
}

#[derive(LintDiagnostic)]
#[diag(lint_unqualified_local_imports)]
pub struct UnqualifiedLocalImportsDiag {}
4 changes: 4 additions & 0 deletions compiler/rustc_lint/src/passes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>);
Expand Down Expand Up @@ -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);
Expand Down
64 changes: 64 additions & 0 deletions compiler/rustc_lint/src/unqualified_local_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use rustc_hir as hir;
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::symbol::kw;

use crate::{lints, LateContext, LateLintPass, LintContext};

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;
/// ```
///
/// {{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::`"
}

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.
// However, it shouldn't be possible for those to be in different crates so we only check the first.
let Some(hir::def::Res::Def(_def_kind, def_id)) = path.res.first() else { return };
if !def_id.is_local() {
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;
}

// This `use` qualifies for our lint!
cx.emit_span_lint(
UNQUALIFIED_LOCAL_IMPORTS,
first_seg.ident.span,
lints::UnqualifiedLocalImportsDiag {},
);
}
}
17 changes: 17 additions & 0 deletions tests/ui/lint/unqualified_local_imports.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#![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;

fn main() {}
14 changes: 14 additions & 0 deletions tests/ui/lint/unqualified_local_imports.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: `use` of a local item without leading `self::`, `super::`, or `crate::`
--> $DIR/unqualified_local_imports.rs:12:5
|
LL | use localmod::S;
| ^^^^^^^^
|
note: the lint level is defined here
--> $DIR/unqualified_local_imports.rs:1:9
|
LL | #![deny(unqualified_local_imports)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

0 comments on commit 0b9e562

Please sign in to comment.