fix: Removing all unused imports removes used imports for imports used for Derive macros
Signed-off-by: Hayashi Mikihiro <34ttrweoewiwe28@gmail.com>
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index c5d37ca..3a91050 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -97,7 +97,8 @@
diagnostics::*,
has_source::HasSource,
semantics::{
- PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits,
+ PathResolution, PathResolutionPerNs, Semantics, SemanticsImpl, SemanticsScope, TypeInfo,
+ VisibleTraits,
},
};
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 327fe8a..7ee7261 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -103,6 +103,26 @@
}
}
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub struct PathResolutionPerNs {
+ pub type_ns: Option<PathResolution>,
+ pub value_ns: Option<PathResolution>,
+ pub macro_ns: Option<PathResolution>,
+}
+
+impl PathResolutionPerNs {
+ pub fn new(
+ type_ns: Option<PathResolution>,
+ value_ns: Option<PathResolution>,
+ macro_ns: Option<PathResolution>,
+ ) -> Self {
+ PathResolutionPerNs { type_ns, value_ns, macro_ns }
+ }
+ pub fn take_path(&self) -> Option<PathResolution> {
+ self.type_ns.or(self.value_ns).or(self.macro_ns)
+ }
+}
+
#[derive(Debug)]
pub struct TypeInfo {
/// The original type of the expression or pattern.
@@ -1606,6 +1626,10 @@
self.resolve_path_with_subst(path).map(|(it, _)| it)
}
+ pub fn resolve_path_per_ns(&self, path: &ast::Path) -> Option<PathResolutionPerNs> {
+ self.analyze(path.syntax())?.resolve_hir_path_per_ns(self.db, path)
+ }
+
pub fn resolve_path_with_subst(
&self,
path: &ast::Path,
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 42da7b9..610770d 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -10,7 +10,9 @@
use crate::{
Adt, AssocItem, BindingMode, BuiltinAttr, BuiltinType, Callable, Const, DeriveHelper, Field,
Function, GenericSubstitution, Local, Macro, ModuleDef, Static, Struct, ToolModule, Trait,
- TraitAlias, TupleField, Type, TypeAlias, Variant, db::HirDatabase, semantics::PathResolution,
+ TraitAlias, TupleField, Type, TypeAlias, Variant,
+ db::HirDatabase,
+ semantics::{PathResolution, PathResolutionPerNs},
};
use either::Either;
use hir_def::{
@@ -1159,7 +1161,9 @@
prefer_value_ns,
name_hygiene(db, InFile::new(self.file_id, path.syntax())),
Some(&store),
- )?;
+ false,
+ )
+ .take_path()?;
let subst = (|| {
let parent = parent()?;
let ty = if let Some(expr) = ast::Expr::cast(parent.clone()) {
@@ -1209,6 +1213,26 @@
}
}
+ pub(crate) fn resolve_hir_path_per_ns(
+ &self,
+ db: &dyn HirDatabase,
+ path: &ast::Path,
+ ) -> Option<PathResolutionPerNs> {
+ let mut collector = ExprCollector::new(db, self.resolver.module(), self.file_id);
+ let hir_path =
+ collector.lower_path(path.clone(), &mut ExprCollector::impl_trait_error_allocator)?;
+ let store = collector.store.finish();
+ Some(resolve_hir_path_(
+ db,
+ &self.resolver,
+ &hir_path,
+ false,
+ name_hygiene(db, InFile::new(self.file_id, path.syntax())),
+ Some(&store),
+ true,
+ ))
+ }
+
pub(crate) fn record_literal_missing_fields(
&self,
db: &'db dyn HirDatabase,
@@ -1532,7 +1556,7 @@
hygiene: HygieneId,
store: Option<&ExpressionStore>,
) -> Option<PathResolution> {
- resolve_hir_path_(db, resolver, path, false, hygiene, store)
+ resolve_hir_path_(db, resolver, path, false, hygiene, store, false).take_path()
}
#[inline]
@@ -1554,7 +1578,8 @@
prefer_value_ns: bool,
hygiene: HygieneId,
store: Option<&ExpressionStore>,
-) -> Option<PathResolution> {
+ resolve_per_ns: bool,
+) -> PathResolutionPerNs {
let types = || {
let (ty, unresolved) = match path.type_anchor() {
Some(type_ref) => resolver.generic_def().and_then(|def| {
@@ -1635,9 +1660,31 @@
.map(|(def, _)| PathResolution::Def(ModuleDef::Macro(def.into())))
};
- if prefer_value_ns { values().or_else(types) } else { types().or_else(values) }
- .or_else(items)
- .or_else(macros)
+ if resolve_per_ns {
+ PathResolutionPerNs {
+ type_ns: types().or_else(items),
+ value_ns: values(),
+ macro_ns: macros(),
+ }
+ } else {
+ let res = if prefer_value_ns {
+ values()
+ .map(|value_ns| PathResolutionPerNs::new(None, Some(value_ns), None))
+ .unwrap_or_else(|| PathResolutionPerNs::new(types(), None, None))
+ } else {
+ types()
+ .map(|type_ns| PathResolutionPerNs::new(Some(type_ns), None, None))
+ .unwrap_or_else(|| PathResolutionPerNs::new(None, values(), None))
+ };
+
+ if res.take_path().is_some() {
+ res
+ } else if let Some(type_ns) = items() {
+ PathResolutionPerNs::new(Some(type_ns), None, None)
+ } else {
+ PathResolutionPerNs::new(None, None, macros())
+ }
+ }
}
fn resolve_hir_value_path(
diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs
index 1baf814..fb96882 100644
--- a/crates/ide-assists/src/handlers/remove_unused_imports.rs
+++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs
@@ -1,6 +1,9 @@
use std::collections::hash_map::Entry;
-use hir::{FileRange, InFile, InRealFile, Module, ModuleSource};
+use hir::{
+ FileRange, InFile, InRealFile, Module, ModuleDef, ModuleSource, PathResolution,
+ PathResolutionPerNs,
+};
use ide_db::text_edit::TextRange;
use ide_db::{
FxHashMap, RootDatabase,
@@ -77,49 +80,52 @@
};
// Get the actual definition associated with this use item.
- let res = match ctx.sema.resolve_path(&path) {
- Some(x) => x,
- None => {
+ let res = match ctx.sema.resolve_path_per_ns(&path) {
+ Some(x) if x.take_path().is_some() => x,
+ Some(_) | None => {
return None;
}
};
+ match res {
+ PathResolutionPerNs { type_ns: Some(type_ns), .. } if u.star_token().is_some() => {
+ // Check if any of the children of this module are used
+ let def_mod = match type_ns {
+ PathResolution::Def(ModuleDef::Module(module)) => module,
+ _ => return None,
+ };
- let def = match res {
- hir::PathResolution::Def(d) => Definition::from(d),
- _ => return None,
- };
-
- if u.star_token().is_some() {
- // Check if any of the children of this module are used
- let def_mod = match def {
- Definition::Module(module) => module,
- _ => return None,
- };
-
- if !def_mod
- .scope(ctx.db(), Some(use_module))
- .iter()
- .filter_map(|(_, x)| match x {
- hir::ScopeDef::ModuleDef(d) => Some(Definition::from(*d)),
- _ => None,
- })
- .any(|d| used_once_in_scope(ctx, d, u.rename(), scope))
- {
- return Some(u);
+ if !def_mod
+ .scope(ctx.db(), Some(use_module))
+ .iter()
+ .filter_map(|(_, x)| match x {
+ hir::ScopeDef::ModuleDef(d) => Some(Definition::from(*d)),
+ _ => None,
+ })
+ .any(|d| used_once_in_scope(ctx, d, u.rename(), scope))
+ {
+ Some(u)
+ } else {
+ None
+ }
}
- } else if let Definition::Trait(ref t) = def {
- // If the trait or any item is used.
- if !std::iter::once((def, u.rename()))
- .chain(t.items(ctx.db()).into_iter().map(|item| (item.into(), None)))
- .any(|(d, rename)| used_once_in_scope(ctx, d, rename, scope))
- {
- return Some(u);
+ PathResolutionPerNs {
+ type_ns: Some(PathResolution::Def(ModuleDef::Trait(ref t))),
+ value_ns,
+ macro_ns,
+ } => {
+ // If the trait or any item is used.
+ if is_trait_unused_in_scope(ctx, &u, scope, t) {
+ let path = [value_ns, macro_ns];
+ is_path_unused_in_scope(ctx, &u, scope, &path).then_some(u)
+ } else {
+ None
+ }
}
- } else if !used_once_in_scope(ctx, def, u.rename(), scope) {
- return Some(u);
+ PathResolutionPerNs { type_ns, value_ns, macro_ns } => {
+ let path = [type_ns, value_ns, macro_ns];
+ is_path_unused_in_scope(ctx, &u, scope, &path).then_some(u)
+ }
}
-
- None
})
.peekable();
@@ -141,6 +147,33 @@
}
}
+fn is_path_unused_in_scope(
+ ctx: &AssistContext<'_>,
+ u: &ast::UseTree,
+ scope: &mut Vec<SearchScope>,
+ path: &[Option<PathResolution>],
+) -> bool {
+ !path
+ .iter()
+ .filter_map(|path| *path)
+ .filter_map(|res| match res {
+ PathResolution::Def(d) => Some(Definition::from(d)),
+ _ => None,
+ })
+ .any(|def| used_once_in_scope(ctx, def, u.rename(), scope))
+}
+
+fn is_trait_unused_in_scope(
+ ctx: &AssistContext<'_>,
+ u: &ast::UseTree,
+ scope: &mut Vec<SearchScope>,
+ t: &hir::Trait,
+) -> bool {
+ !std::iter::once((Definition::Trait(*t), u.rename()))
+ .chain(t.items(ctx.db()).into_iter().map(|item| (item.into(), None)))
+ .any(|(d, rename)| used_once_in_scope(ctx, d, rename, scope))
+}
+
fn used_once_in_scope(
ctx: &AssistContext<'_>,
def: Definition,
@@ -1012,4 +1045,110 @@
"#,
);
}
+
+ #[test]
+ fn test_unused_macro() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+//- /foo.rs crate:foo
+#[macro_export]
+macro_rules! m { () => {} }
+
+//- /main.rs crate:main deps:foo
+use foo::m;$0
+fn main() {}
+"#,
+ r#"
+fn main() {}
+"#,
+ );
+
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+//- /foo.rs crate:foo
+#[macro_export]
+macro_rules! m { () => {} }
+
+//- /main.rs crate:main deps:foo
+use foo::m;$0
+fn main() {
+ m!();
+}
+"#,
+ );
+
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+//- /foo.rs crate:foo
+#[macro_export]
+macro_rules! m { () => {} }
+
+//- /bar.rs crate:bar deps:foo
+pub use foo::m;
+fn m() {}
+
+
+//- /main.rs crate:main deps:bar
+use bar::m;$0
+fn main() {
+ m!();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_conflict_derive_macro() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+//- /bar.rs crate:bar
+pub use proc_macros::DeriveIdentity;
+pub trait DeriveIdentity {}
+
+//- /main.rs crate:main deps:bar
+$0use bar::DeriveIdentity;$0
+#[derive(DeriveIdentity)]
+struct S;
+"#,
+ );
+
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+//- /bar.rs crate:bar
+pub use proc_macros::DeriveIdentity;
+pub fn DeriveIdentity() {}
+
+//- /main.rs crate:main deps:bar
+$0use bar::DeriveIdentity;$0
+#[derive(DeriveIdentity)]
+struct S;
+"#,
+ );
+
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+//- /bar.rs crate:bar
+pub use proc_macros::DeriveIdentity;
+pub fn DeriveIdentity() {}
+
+//- /main.rs crate:main deps:bar
+$0use bar::DeriveIdentity;$0
+fn main() {
+ DeriveIdentity();
+}
+"#,
+ );
+ }
}