rust/crates/ra_ide/src/completion/complete_path.rs
bors[bot] beb4f49541
Merge #3513
3513: Completion in macros r=matklad a=flodiebold

I experimented a bit with completion in macros. It's kind of working, but there are a lot of rough edges.

 - I'm trying to expand the macro call with the inserted fake token. This requires some hacky additions on the HIR level to be able to do "hypothetical" expansions. There should probably be a nicer API for this, if we want to do it this way. I'm not sure whether it's worth it, because we still can't do a lot if the original macro call didn't expand in nearly the same way. E.g. if we have something like `println!("", x<|>)` the expansions will look the same and everything is fine; but in that case we could maybe have achieved the same result in a simpler way. If we have something like `m!(<|>)` where `m!()` doesn't even expand or expands to something very different, we don't really know what to do anyway.
 - Relatedly, there are a lot of cases where this doesn't work because either the original call or the hypothetical call doesn't expand. E.g. if we have `m!(x.<|>)` the original token tree doesn't parse as an expression; if we have `m!(match x { <|> })` the hypothetical token tree doesn't parse. It would be nice if we could have better error recovery in these cases.

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
2020-03-09 08:56:58 +00:00

971 lines
26 KiB
Rust

//! Completion of paths, i.e. `some::prefix::<|>`.
use hir::{Adt, HasVisibility, PathResolution, ScopeDef};
use ra_syntax::AstNode;
use test_utils::tested_by;
use crate::completion::{CompletionContext, Completions};
pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
let path = match &ctx.path_prefix {
Some(path) => path.clone(),
_ => return,
};
let def = match ctx.scope().resolve_hir_path(&path) {
Some(PathResolution::Def(def)) => def,
_ => return,
};
let context_module = ctx.scope().module();
match def {
hir::ModuleDef::Module(module) => {
let module_scope = module.scope(ctx.db, context_module);
for (name, def) in module_scope {
if ctx.use_item_syntax.is_some() {
if let ScopeDef::Unknown = def {
if let Some(name_ref) = ctx.name_ref_syntax.as_ref() {
if name_ref.syntax().text() == name.to_string().as_str() {
// for `use self::foo<|>`, don't suggest `foo` as a completion
tested_by!(dont_complete_current_use);
continue;
}
}
}
}
acc.add_resolution(ctx, name.to_string(), &def);
}
}
hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => {
if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
for variant in e.variants(ctx.db) {
acc.add_enum_variant(ctx, variant);
}
}
let ty = match def {
hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
_ => unreachable!(),
};
// Iterate assoc types separately
// FIXME: complete T::AssocType
let krate = ctx.krate;
if let Some(krate) = krate {
let traits_in_scope = ctx.scope().traits_in_scope();
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
return None;
}
match item {
hir::AssocItem::Function(func) => {
if !func.has_self_param(ctx.db) {
acc.add_function(ctx, func);
}
}
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
}
None::<()>
});
ty.iterate_impl_items(ctx.db, krate, |item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
return None;
}
match item {
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {}
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
}
None::<()>
});
}
}
hir::ModuleDef::Trait(t) => {
for item in t.items(ctx.db) {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
continue;
}
match item {
hir::AssocItem::Function(func) => {
if !func.has_self_param(ctx.db) {
acc.add_function(ctx, func);
}
}
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
}
}
}
_ => {}
};
}
#[cfg(test)]
mod tests {
use test_utils::covers;
use crate::completion::{do_completion, CompletionItem, CompletionKind};
use insta::assert_debug_snapshot;
fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
do_completion(code, CompletionKind::Reference)
}
#[test]
fn dont_complete_current_use() {
covers!(dont_complete_current_use);
let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference);
assert!(completions.is_empty());
}
#[test]
fn dont_complete_current_use_in_braces_with_glob() {
let completions = do_completion(
r"
mod foo { pub struct S; }
use self::{foo::*, bar<|>};
",
CompletionKind::Reference,
);
assert_eq!(completions.len(), 2);
}
#[test]
fn dont_complete_primitive_in_use() {
let completions = do_completion(r"use self::<|>;", CompletionKind::BuiltinType);
assert!(completions.is_empty());
}
#[test]
fn dont_complete_primitive_in_module_scope() {
let completions = do_completion(r"fn foo() { self::<|> }", CompletionKind::BuiltinType);
assert!(completions.is_empty());
}
#[test]
fn completes_primitives() {
let completions =
do_completion(r"fn main() { let _: <|> = 92; }", CompletionKind::BuiltinType);
assert_eq!(completions.len(), 17);
}
#[test]
fn completes_mod_with_docs() {
assert_debug_snapshot!(
do_reference_completion(
r"
use self::my<|>;
/// Some simple
/// docs describing `mod my`.
mod my {
struct Bar;
}
"
),
@r###"
[
CompletionItem {
label: "my",
source_range: [27; 29),
delete: [27; 29),
insert: "my",
kind: Module,
documentation: Documentation(
"Some simple\ndocs describing `mod my`.",
),
},
]
"###
);
}
#[test]
fn path_visibility() {
assert_debug_snapshot!(
do_reference_completion(
r"
use self::my::<|>;
mod my {
struct Bar;
pub struct Foo;
pub use Bar as PublicBar;
}
"
),
@r###"
[
CompletionItem {
label: "Foo",
source_range: [31; 31),
delete: [31; 31),
insert: "Foo",
kind: Struct,
},
CompletionItem {
label: "PublicBar",
source_range: [31; 31),
delete: [31; 31),
insert: "PublicBar",
kind: Struct,
},
]
"###
);
}
#[test]
fn completes_use_item_starting_with_self() {
assert_debug_snapshot!(
do_reference_completion(
r"
use self::m::<|>;
mod m {
pub struct Bar;
}
"
),
@r###"
[
CompletionItem {
label: "Bar",
source_range: [30; 30),
delete: [30; 30),
insert: "Bar",
kind: Struct,
},
]
"###
);
}
#[test]
fn completes_use_item_starting_with_crate() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
mod foo;
struct Spam;
//- /foo.rs
use crate::Sp<|>
"
),
@r###"
[
CompletionItem {
label: "Spam",
source_range: [11; 13),
delete: [11; 13),
insert: "Spam",
kind: Struct,
},
CompletionItem {
label: "foo",
source_range: [11; 13),
delete: [11; 13),
insert: "foo",
kind: Module,
},
]
"###
);
}
#[test]
fn completes_nested_use_tree() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
mod foo;
struct Spam;
//- /foo.rs
use crate::{Sp<|>};
"
),
@r###"
[
CompletionItem {
label: "Spam",
source_range: [12; 14),
delete: [12; 14),
insert: "Spam",
kind: Struct,
},
CompletionItem {
label: "foo",
source_range: [12; 14),
delete: [12; 14),
insert: "foo",
kind: Module,
},
]
"###
);
}
#[test]
fn completes_deeply_nested_use_tree() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
mod foo;
pub mod bar {
pub mod baz {
pub struct Spam;
}
}
//- /foo.rs
use crate::{bar::{baz::Sp<|>}};
"
),
@r###"
[
CompletionItem {
label: "Spam",
source_range: [23; 25),
delete: [23; 25),
insert: "Spam",
kind: Struct,
},
]
"###
);
}
#[test]
fn completes_enum_variant() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
/// An enum
enum E {
/// Foo Variant
Foo,
/// Bar Variant with i32
Bar(i32)
}
fn foo() { let _ = E::<|> }
"
),
@r###"
[
CompletionItem {
label: "Bar",
source_range: [116; 116),
delete: [116; 116),
insert: "Bar",
kind: EnumVariant,
detail: "(i32)",
documentation: Documentation(
"Bar Variant with i32",
),
},
CompletionItem {
label: "Foo",
source_range: [116; 116),
delete: [116; 116),
insert: "Foo",
kind: EnumVariant,
detail: "()",
documentation: Documentation(
"Foo Variant",
),
},
]
"###
);
}
#[test]
fn completes_enum_variant_with_details() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
struct S { field: u32 }
/// An enum
enum E {
/// Foo Variant (empty)
Foo,
/// Bar Variant with i32 and u32
Bar(i32, u32),
///
S(S),
}
fn foo() { let _ = E::<|> }
"
),
@r###"
[
CompletionItem {
label: "Bar",
source_range: [180; 180),
delete: [180; 180),
insert: "Bar",
kind: EnumVariant,
detail: "(i32, u32)",
documentation: Documentation(
"Bar Variant with i32 and u32",
),
},
CompletionItem {
label: "Foo",
source_range: [180; 180),
delete: [180; 180),
insert: "Foo",
kind: EnumVariant,
detail: "()",
documentation: Documentation(
"Foo Variant (empty)",
),
},
CompletionItem {
label: "S",
source_range: [180; 180),
delete: [180; 180),
insert: "S",
kind: EnumVariant,
detail: "(S)",
documentation: Documentation(
"",
),
},
]
"###
);
}
#[test]
fn completes_struct_associated_method() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
/// A Struct
struct S;
impl S {
/// An associated method
fn m() { }
}
fn foo() { let _ = S::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [100; 100),
delete: [100; 100),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"An associated method",
),
},
]
"###
);
}
#[test]
fn completes_struct_associated_const() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
/// A Struct
struct S;
impl S {
/// An associated const
const C: i32 = 42;
}
fn foo() { let _ = S::<|> }
"
),
@r###"
[
CompletionItem {
label: "C",
source_range: [107; 107),
delete: [107; 107),
insert: "C",
kind: Const,
detail: "const C: i32 = 42;",
documentation: Documentation(
"An associated const",
),
},
]
"###
);
}
#[test]
fn completes_struct_associated_type() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
/// A Struct
struct S;
impl S {
/// An associated type
type T = i32;
}
fn foo() { let _ = S::<|> }
"
),
@r###"
[
CompletionItem {
label: "T",
source_range: [101; 101),
delete: [101; 101),
insert: "T",
kind: TypeAlias,
detail: "type T = i32;",
documentation: Documentation(
"An associated type",
),
},
]
"###
);
}
#[test]
fn associated_item_visibility() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
struct S;
mod m {
impl super::S {
pub(super) fn public_method() { }
fn private_method() { }
pub(super) type PublicType = u32;
type PrivateType = u32;
pub(super) const PUBLIC_CONST: u32 = 1;
const PRIVATE_CONST: u32 = 1;
}
}
fn foo() { let _ = S::<|> }
"
),
@r###"
[
CompletionItem {
label: "PUBLIC_CONST",
source_range: [302; 302),
delete: [302; 302),
insert: "PUBLIC_CONST",
kind: Const,
detail: "pub(super) const PUBLIC_CONST: u32 = 1;",
},
CompletionItem {
label: "PublicType",
source_range: [302; 302),
delete: [302; 302),
insert: "PublicType",
kind: TypeAlias,
detail: "pub(super) type PublicType = u32;",
},
CompletionItem {
label: "public_method()",
source_range: [302; 302),
delete: [302; 302),
insert: "public_method()$0",
kind: Function,
lookup: "public_method",
detail: "pub(super) fn public_method()",
},
]
"###
);
}
#[test]
fn completes_enum_associated_method() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
/// An enum
enum S {};
impl S {
/// An associated method
fn m() { }
}
fn foo() { let _ = S::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [100; 100),
delete: [100; 100),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"An associated method",
),
},
]
"###
);
}
#[test]
fn completes_union_associated_method() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
/// A union
union U {};
impl U {
/// An associated method
fn m() { }
}
fn foo() { let _ = U::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [101; 101),
delete: [101; 101),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"An associated method",
),
},
]
"###
);
}
#[test]
fn completes_use_paths_across_crates() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /main.rs
use foo::<|>;
//- /foo/lib.rs
pub mod bar {
pub struct S;
}
"
),
@r###"
[
CompletionItem {
label: "bar",
source_range: [9; 9),
delete: [9; 9),
insert: "bar",
kind: Module,
},
]
"###
);
}
#[test]
fn completes_trait_associated_method_1() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Trait {
/// A trait method
fn m();
}
fn foo() { let _ = Trait::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [73; 73),
delete: [73; 73),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"A trait method",
),
},
]
"###
);
}
#[test]
fn completes_trait_associated_method_2() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Trait {
/// A trait method
fn m();
}
struct S;
impl Trait for S {}
fn foo() { let _ = S::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [99; 99),
delete: [99; 99),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"A trait method",
),
},
]
"###
);
}
#[test]
fn completes_trait_associated_method_3() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Trait {
/// A trait method
fn m();
}
struct S;
impl Trait for S {}
fn foo() { let _ = <S as Trait>::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [110; 110),
delete: [110; 110),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"A trait method",
),
},
]
"###
);
}
#[test]
fn completes_type_alias() {
assert_debug_snapshot!(
do_reference_completion(
"
struct S;
impl S { fn foo() {} }
type T = S;
impl T { fn bar() {} }
fn main() {
T::<|>;
}
"
),
@r###"
[
CompletionItem {
label: "bar()",
source_range: [185; 185),
delete: [185; 185),
insert: "bar()$0",
kind: Function,
lookup: "bar",
detail: "fn bar()",
},
CompletionItem {
label: "foo()",
source_range: [185; 185),
delete: [185; 185),
insert: "foo()$0",
kind: Function,
lookup: "foo",
detail: "fn foo()",
},
]
"###
);
}
#[test]
fn completes_qualified_macros() {
assert_debug_snapshot!(
do_reference_completion(
"
#[macro_export]
macro_rules! foo {
() => {}
}
fn main() {
let _ = crate::<|>
}
"
),
@r###"
[
CompletionItem {
label: "foo!",
source_range: [179; 179),
delete: [179; 179),
insert: "foo!($0)",
kind: Macro,
detail: "#[macro_export]\nmacro_rules! foo",
},
CompletionItem {
label: "main()",
source_range: [179; 179),
delete: [179; 179),
insert: "main()$0",
kind: Function,
lookup: "main",
detail: "fn main()",
},
]
"###
);
}
#[test]
fn completes_reexported_items_under_correct_name() {
assert_debug_snapshot!(
do_reference_completion(
r"
fn foo() {
self::m::<|>
}
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###"
[
CompletionItem {
label: "RIGHT_CONST",
source_range: [57; 57),
delete: [57; 57),
insert: "RIGHT_CONST",
kind: Const,
},
CompletionItem {
label: "RightType",
source_range: [57; 57),
delete: [57; 57),
insert: "RightType",
kind: Struct,
},
CompletionItem {
label: "right_fn()",
source_range: [57; 57),
delete: [57; 57),
insert: "right_fn()$0",
kind: Function,
lookup: "right_fn",
detail: "fn wrong_fn()",
},
]
"###
);
}
#[test]
fn completes_in_simple_macro_call() {
let completions = do_reference_completion(
r#"
macro_rules! m { ($e:expr) => { $e } }
fn main() { m!(self::f<|>); }
fn foo() {}
"#,
);
assert_debug_snapshot!(completions, @r###"
[
CompletionItem {
label: "foo()",
source_range: [93; 94),
delete: [93; 94),
insert: "foo()$0",
kind: Function,
lookup: "foo",
detail: "fn foo()",
},
CompletionItem {
label: "main()",
source_range: [93; 94),
delete: [93; 94),
insert: "main()$0",
kind: Function,
lookup: "main",
detail: "fn main()",
},
]
"###);
}
}