diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 05f06f97fd..6be44532d0 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -2247,6 +2247,11 @@ impl<'db> SemanticsScope<'db> { } } + /// Checks if a trait is in scope, either because of an import or because we're in an impl of it. + pub fn can_use_trait_methods(&self, t: Trait) -> bool { + self.resolver.traits_in_scope(self.db).contains(&t.id) + } + /// Resolve a path as-if it was written at the given scope. This is /// necessary a heuristic, as it doesn't take hygiene into account. pub fn speculative_resolve(&self, ast_path: &ast::Path) -> Option { diff --git a/crates/ide-assists/src/handlers/unqualify_method_call.rs b/crates/ide-assists/src/handlers/unqualify_method_call.rs index 1f89a3d5f1..a58b1da621 100644 --- a/crates/ide-assists/src/handlers/unqualify_method_call.rs +++ b/crates/ide-assists/src/handlers/unqualify_method_call.rs @@ -1,3 +1,4 @@ +use hir::AsAssocItem; use syntax::{ TextRange, ast::{self, AstNode, HasArgList, prec::ExprPrecedence}, @@ -43,6 +44,7 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) let qualifier = path.qualifier()?; let method_name = path.segment()?.name_ref()?; + let scope = ctx.sema.scope(path.syntax())?; let res = ctx.sema.resolve_path(&path)?; let hir::PathResolution::Def(hir::ModuleDef::Function(fun)) = res else { return None }; if !fun.has_self_param(ctx.sema.db) { @@ -78,7 +80,14 @@ pub(crate) fn unqualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) edit.insert(close, ")"); } edit.replace(replace_comma, format!(".{method_name}(")); - add_import(qualifier, ctx, edit); + + if let Some(fun) = fun.as_assoc_item(ctx.db()) + && let Some(trait_) = fun.container_or_implemented_trait(ctx.db()) + && !scope.can_use_trait_methods(trait_) + { + // Only add an import for trait methods that are not already imported. + add_import(qualifier, ctx, edit); + } }, ) } @@ -235,4 +244,111 @@ impl S { fn assoc(S: S, S: S) {} } fn f() { S::assoc$0(S, S); }"#, ); } + + #[test] + fn inherent_method() { + check_assist( + unqualify_method_call, + r#" +mod foo { + pub struct Bar; + impl Bar { + pub fn bar(self) {} + } +} + +fn baz() { + foo::Bar::b$0ar(foo::Bar); +} + "#, + r#" +mod foo { + pub struct Bar; + impl Bar { + pub fn bar(self) {} + } +} + +fn baz() { + foo::Bar.bar(); +} + "#, + ); + } + + #[test] + fn trait_method_in_impl() { + check_assist( + unqualify_method_call, + r#" +mod foo { + pub trait Bar { + pub fn bar(self) {} + } +} + +struct Baz; +impl foo::Bar for Baz { + fn bar(self) { + foo::Bar::b$0ar(Baz); + } +} + "#, + r#" +mod foo { + pub trait Bar { + pub fn bar(self) {} + } +} + +struct Baz; +impl foo::Bar for Baz { + fn bar(self) { + Baz.bar(); + } +} + "#, + ); + } + + #[test] + fn trait_method_already_imported() { + check_assist( + unqualify_method_call, + r#" +mod foo { + pub struct Foo; + pub trait Bar { + pub fn bar(self) {} + } + impl Bar for Foo { + pub fn bar(self) {} + } +} + +use foo::Bar; + +fn baz() { + foo::Bar::b$0ar(foo::Foo); +} + "#, + r#" +mod foo { + pub struct Foo; + pub trait Bar { + pub fn bar(self) {} + } + impl Bar for Foo { + pub fn bar(self) {} + } +} + +use foo::Bar; + +fn baz() { + foo::Foo.bar(); +} + "#, + ); + } }