Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-completion/src/completions/qualified_path.rs')
| -rw-r--r-- | crates/ide-completion/src/completions/qualified_path.rs | 685 |
1 files changed, 685 insertions, 0 deletions
diff --git a/crates/ide-completion/src/completions/qualified_path.rs b/crates/ide-completion/src/completions/qualified_path.rs new file mode 100644 index 0000000000..2c2fe524ef --- /dev/null +++ b/crates/ide-completion/src/completions/qualified_path.rs @@ -0,0 +1,685 @@ +//! Completion of paths, i.e. `some::prefix::$0`. + +use hir::{ScopeDef, Trait}; +use ide_db::FxHashSet; +use syntax::ast; + +use crate::{ + completions::module_or_fn_macro, + context::{PathCompletionCtx, PathKind}, + patterns::ImmediateLocation, + CompletionContext, Completions, +}; + +pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { + if ctx.is_path_disallowed() || ctx.has_impl_or_trait_prev_sibling() { + return; + } + if ctx.pattern_ctx.is_some() { + return; + } + let (qualifier, kind) = match ctx.path_context { + // let ... else, syntax would come in really handy here right now + Some(PathCompletionCtx { qualifier: Some(ref qualifier), kind, .. }) => (qualifier, kind), + _ => return, + }; + let traits_in_scope = |ctx: &CompletionContext| { + let mut traits_in_scope = ctx.scope.visible_traits(); + if let Some(drop) = ctx.famous_defs().core_ops_Drop() { + traits_in_scope.remove(&drop.into()); + } + traits_in_scope + }; + + // special case `<_>::$0` as this doesn't resolve to anything. + if qualifier.path.qualifier().is_none() { + if matches!( + qualifier.path.segment().and_then(|it| it.kind()), + Some(ast::PathSegmentKind::Type { + type_ref: Some(ast::Type::InferType(_)), + trait_ref: None, + }) + ) { + cov_mark::hit!(completion_type_anchor_empty); + traits_in_scope(ctx) + .into_iter() + .flat_map(|it| Trait::from(it).items(ctx.sema.db)) + .for_each(|item| add_assoc_item(acc, ctx, item)); + return; + } + } + + let resolution = match &qualifier.resolution { + Some(res) => res, + None => return, + }; + + match ctx.completion_location { + Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => { + if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { + for (name, def) in module.scope(ctx.db, Some(ctx.module)) { + if let Some(def) = module_or_fn_macro(ctx.db, def) { + acc.add_resolution(ctx, name, def); + } + } + } + return; + } + _ => (), + } + + match kind { + Some( + PathKind::Pat + | PathKind::Attr { .. } + | PathKind::Vis { .. } + | PathKind::Use + | PathKind::Derive, + ) => { + return; + } + _ => { + // Add associated types on type parameters and `Self`. + ctx.scope.assoc_type_shorthand_candidates(resolution, |_, alias| { + acc.add_type_alias(ctx, alias); + None::<()> + }); + } + } + + match resolution { + hir::PathResolution::Def(hir::ModuleDef::Module(module)) => { + let module_scope = module.scope(ctx.db, Some(ctx.module)); + for (name, def) in module_scope { + let add_resolution = match def { + // Don't suggest attribute macros and derives. + ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db), + // no values in type places + ScopeDef::ModuleDef( + hir::ModuleDef::Function(_) + | hir::ModuleDef::Variant(_) + | hir::ModuleDef::Static(_), + ) + | ScopeDef::Local(_) => !ctx.expects_type(), + // unless its a constant in a generic arg list position + ScopeDef::ModuleDef(hir::ModuleDef::Const(_)) => { + !ctx.expects_type() || ctx.expects_generic_arg() + } + _ => true, + }; + + if add_resolution { + acc.add_resolution(ctx, name, def); + } + } + } + hir::PathResolution::Def( + def @ (hir::ModuleDef::Adt(_) + | hir::ModuleDef::TypeAlias(_) + | hir::ModuleDef::BuiltinType(_)), + ) => { + if let &hir::ModuleDef::Adt(hir::Adt::Enum(e)) = def { + add_enum_variants(acc, ctx, e); + } + let ty = match def { + hir::ModuleDef::Adt(adt) => adt.ty(ctx.db), + hir::ModuleDef::TypeAlias(a) => { + let ty = a.ty(ctx.db); + if let Some(hir::Adt::Enum(e)) = ty.as_adt() { + cov_mark::hit!(completes_variant_through_alias); + add_enum_variants(acc, ctx, e); + } + ty + } + hir::ModuleDef::BuiltinType(builtin) => { + cov_mark::hit!(completes_primitive_assoc_const); + builtin.ty(ctx.db) + } + _ => unreachable!(), + }; + + // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType. + // (where AssocType is defined on a trait, not an inherent impl) + + let traits_in_scope = traits_in_scope(ctx); + ty.iterate_path_candidates( + ctx.db, + &ctx.scope, + &traits_in_scope, + Some(ctx.module), + None, + |item| { + add_assoc_item(acc, ctx, item); + None::<()> + }, + ); + + // Iterate assoc types separately + ty.iterate_assoc_items(ctx.db, ctx.krate, |item| { + if let hir::AssocItem::TypeAlias(ty) = item { + acc.add_type_alias(ctx, ty) + } + None::<()> + }); + } + hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => { + // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`. + for item in t.items(ctx.db) { + add_assoc_item(acc, ctx, item); + } + } + hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => { + let ty = match resolution { + hir::PathResolution::TypeParam(param) => param.ty(ctx.db), + hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db), + _ => return, + }; + + if let Some(hir::Adt::Enum(e)) = ty.as_adt() { + add_enum_variants(acc, ctx, e); + } + let traits_in_scope = traits_in_scope(ctx); + let mut seen = FxHashSet::default(); + ty.iterate_path_candidates( + ctx.db, + &ctx.scope, + &traits_in_scope, + Some(ctx.module), + None, + |item| { + // We might iterate candidates of a trait multiple times here, so deduplicate + // them. + if seen.insert(item) { + add_assoc_item(acc, ctx, item); + } + None::<()> + }, + ); + } + _ => {} + } +} + +fn add_assoc_item(acc: &mut Completions, ctx: &CompletionContext, item: hir::AssocItem) { + match item { + hir::AssocItem::Function(func) if !ctx.expects_type() => acc.add_function(ctx, func, None), + hir::AssocItem::Const(ct) if !ctx.expects_type() || ctx.expects_generic_arg() => { + acc.add_const(ctx, ct) + } + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + _ => (), + } +} + +fn add_enum_variants(acc: &mut Completions, ctx: &CompletionContext, e: hir::Enum) { + if ctx.expects_type() { + return; + } + e.variants(ctx.db).into_iter().for_each(|variant| acc.add_enum_variant(ctx, variant, None)); +} + +#[cfg(test)] +mod tests { + use expect_test::{expect, Expect}; + + use crate::tests::{check_edit, completion_list_no_kw}; + + fn check(ra_fixture: &str, expect: Expect) { + let actual = completion_list_no_kw(ra_fixture); + expect.assert_eq(&actual); + } + + #[test] + fn associated_item_visibility() { + check( + r#" +//- /lib.rs crate:lib new_source_root:library +pub struct S; + +impl S { + pub fn public_method() { } + fn private_method() { } + pub type PublicType = u32; + type PrivateType = u32; + pub const PUBLIC_CONST: u32 = 1; + const PRIVATE_CONST: u32 = 1; +} + +//- /main.rs crate:main deps:lib new_source_root:local +fn foo() { let _ = lib::S::$0 } +"#, + expect![[r#" + fn public_method() fn() + ct PUBLIC_CONST pub const PUBLIC_CONST: u32 + ta PublicType pub type PublicType = u32 + "#]], + ); + } + + #[test] + fn completes_union_associated_method() { + check( + r#" +union U {}; +impl U { fn m() { } } + +fn foo() { let _ = U::$0 } +"#, + expect![[r#" + fn m() fn() + "#]], + ); + } + + #[test] + fn completes_trait_associated_method_1() { + check( + r#" +trait Trait { fn m(); } + +fn foo() { let _ = Trait::$0 } +"#, + expect![[r#" + fn m() (as Trait) fn() + "#]], + ); + } + + #[test] + fn completes_trait_associated_method_2() { + check( + r#" +trait Trait { fn m(); } + +struct S; +impl Trait for S {} + +fn foo() { let _ = S::$0 } +"#, + expect![[r#" + fn m() (as Trait) fn() + "#]], + ); + } + + #[test] + fn completes_trait_associated_method_3() { + check( + r#" +trait Trait { fn m(); } + +struct S; +impl Trait for S {} + +fn foo() { let _ = <S as Trait>::$0 } +"#, + expect![[r#" + fn m() (as Trait) fn() + "#]], + ); + } + + #[test] + fn completes_ty_param_assoc_ty() { + check( + r#" +trait Super { + type Ty; + const CONST: u8; + fn func() {} + fn method(&self) {} +} + +trait Sub: Super { + type SubTy; + const C2: (); + fn subfunc() {} + fn submethod(&self) {} +} + +fn foo<T: Sub>() { T::$0 } +"#, + expect![[r#" + ta SubTy (as Sub) type SubTy + ta Ty (as Super) type Ty + ct C2 (as Sub) const C2: () + fn subfunc() (as Sub) fn() + me submethod(…) (as Sub) fn(&self) + ct CONST (as Super) const CONST: u8 + fn func() (as Super) fn() + me method(…) (as Super) fn(&self) + "#]], + ); + } + + #[test] + fn completes_self_param_assoc_ty() { + check( + r#" +trait Super { + type Ty; + const CONST: u8 = 0; + fn func() {} + fn method(&self) {} +} + +trait Sub: Super { + type SubTy; + const C2: () = (); + fn subfunc() {} + fn submethod(&self) {} +} + +struct Wrap<T>(T); +impl<T> Super for Wrap<T> {} +impl<T> Sub for Wrap<T> { + fn subfunc() { + // Should be able to assume `Self: Sub + Super` + Self::$0 + } +} +"#, + expect![[r#" + ta SubTy (as Sub) type SubTy + ta Ty (as Super) type Ty + ct CONST (as Super) const CONST: u8 + fn func() (as Super) fn() + me method(…) (as Super) fn(&self) + ct C2 (as Sub) const C2: () + fn subfunc() (as Sub) fn() + me submethod(…) (as Sub) fn(&self) + "#]], + ); + } + + #[test] + fn completes_type_alias() { + check( + r#" +struct S; +impl S { fn foo() {} } +type T = S; +impl T { fn bar() {} } + +fn main() { T::$0; } +"#, + expect![[r#" + fn foo() fn() + fn bar() fn() + "#]], + ); + } + + #[test] + fn completes_qualified_macros() { + check( + r#" +#[macro_export] +macro_rules! foo { () => {} } + +fn main() { let _ = crate::$0 } +"#, + expect![[r#" + fn main() fn() + ma foo!(…) macro_rules! foo + "#]], + ); + } + + #[test] + fn does_not_complete_non_fn_macros() { + check( + r#" +mod m { + #[rustc_builtin_macro] + pub macro Clone {} +} + +fn f() {m::$0} +"#, + expect![[r#""#]], + ); + check( + r#" +mod m { + #[rustc_builtin_macro] + pub macro bench {} +} + +fn f() {m::$0} +"#, + expect![[r#""#]], + ); + } + + #[test] + fn completes_reexported_items_under_correct_name() { + check( + r#" +fn foo() { self::m::$0 } + +mod m { + pub use super::p::wrong_fn as right_fn; + pub use super::p::WRONG_CONST as RIGHT_CONST; + pub use super::p::WrongType as RightType; +} +mod p { + fn wrong_fn() {} + const WRONG_CONST: u32 = 1; + struct WrongType {}; +} +"#, + expect![[r#" + ct RIGHT_CONST + fn right_fn() fn() + st RightType + "#]], + ); + + check_edit( + "RightType", + r#" +fn foo() { self::m::$0 } + +mod m { + pub use super::p::wrong_fn as right_fn; + pub use super::p::WRONG_CONST as RIGHT_CONST; + pub use super::p::WrongType as RightType; +} +mod p { + fn wrong_fn() {} + const WRONG_CONST: u32 = 1; + struct WrongType {}; +} +"#, + r#" +fn foo() { self::m::RightType } + +mod m { + pub use super::p::wrong_fn as right_fn; + pub use super::p::WRONG_CONST as RIGHT_CONST; + pub use super::p::WrongType as RightType; +} +mod p { + fn wrong_fn() {} + const WRONG_CONST: u32 = 1; + struct WrongType {}; +} +"#, + ); + } + + #[test] + fn completes_in_simple_macro_call() { + check( + r#" +macro_rules! m { ($e:expr) => { $e } } +fn main() { m!(self::f$0); } +fn foo() {} +"#, + expect![[r#" + fn main() fn() + fn foo() fn() + "#]], + ); + } + + #[test] + fn function_mod_share_name() { + check( + r#" +fn foo() { self::m::$0 } + +mod m { + pub mod z {} + pub fn z() {} +} +"#, + expect![[r#" + md z + fn z() fn() + "#]], + ); + } + + #[test] + fn completes_hashmap_new() { + check( + r#" +struct RandomState; +struct HashMap<K, V, S = RandomState> {} + +impl<K, V> HashMap<K, V, RandomState> { + pub fn new() -> HashMap<K, V, RandomState> { } +} +fn foo() { + HashMap::$0 +} +"#, + expect![[r#" + fn new() fn() -> HashMap<K, V, RandomState> + "#]], + ); + } + + #[test] + fn completes_variant_through_self() { + check( + r#" +enum Foo { + Bar, + Baz, +} + +impl Foo { + fn foo(self) { + Self::$0 + } +} +"#, + expect![[r#" + ev Bar Bar + ev Baz Baz + me foo(…) fn(self) + "#]], + ); + } + + #[test] + fn completes_primitive_assoc_const() { + cov_mark::check!(completes_primitive_assoc_const); + check( + r#" +//- /lib.rs crate:lib deps:core +fn f() { + u8::$0 +} + +//- /core.rs crate:core +#[lang = "u8"] +impl u8 { + pub const MAX: Self = 255; + + pub fn func(self) {} +} +"#, + expect![[r#" + ct MAX pub const MAX: Self + me func(…) fn(self) + "#]], + ); + } + + #[test] + fn completes_variant_through_alias() { + cov_mark::check!(completes_variant_through_alias); + check( + r#" +enum Foo { + Bar +} +type Foo2 = Foo; +fn main() { + Foo2::$0 +} +"#, + expect![[r#" + ev Bar Bar + "#]], + ); + } + + #[test] + fn respects_doc_hidden() { + cov_mark::check!(qualified_path_doc_hidden); + check( + r#" +//- /lib.rs crate:lib deps:dep +fn f() { + dep::$0 +} + +//- /dep.rs crate:dep +#[doc(hidden)] +#[macro_export] +macro_rules! m { + () => {} +} + +#[doc(hidden)] +pub fn f() {} + +#[doc(hidden)] +pub struct S; + +#[doc(hidden)] +pub mod m {} + "#, + expect![[r#""#]], + ) + } + + #[test] + fn type_anchor_empty() { + cov_mark::check!(completion_type_anchor_empty); + check( + r#" +trait Foo { + fn foo() -> Self; +} +struct Bar; +impl Foo for Bar { + fn foo() -> { + Bar + } +} +fn bar() -> Bar { + <_>::$0 +} +"#, + expect![[r#" + fn foo() (as Foo) fn() -> Self + "#]], + ) + } +} |