use ide_db::{
helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
};
-use syntax::{SmolStr, SyntaxKind, TextRange};
+use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
use crate::{
- context::{PathCompletionCtx, PathKind},
+ context::{DotAccess, PathCompletionCtx, PathKind, PatternContext},
item::{Builder, CompletionRelevanceTypeMatch},
- render::{function::render_fn, literal::render_variant_lit, macro_::render_macro},
+ render::{
+ function::render_fn,
+ literal::render_variant_lit,
+ macro_::{render_macro, render_macro_pat},
+ },
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
};
/// Interface for data and methods required for items rendering.
}
}
+ fn is_immediately_after_macro_bang(&self) -> bool {
+ self.completion.token.kind() == SyntaxKind::BANG
+ && self
+ .completion
+ .token
+ .parent()
+ .map_or(false, |it| it.kind() == SyntaxKind::MACRO_CALL)
+ }
+
fn is_deprecated(&self, def: impl HasAttrs) -> bool {
let attrs = def.attrs(self.db());
attrs.by_key("deprecated").exists()
pub(crate) fn render_field(
ctx: RenderContext<'_>,
+ dot_access: &DotAccess,
receiver: Option<hir::Name>,
field: hir::Field,
ty: &hir::Type,
) -> CompletionItem {
let is_deprecated = ctx.is_deprecated(field);
- let name = field.name(ctx.db()).to_smol_str();
+ let name = field.name(ctx.db());
+ let (name, escaped_name) = (name.unescaped().to_smol_str(), name.to_smol_str());
let mut item = CompletionItem::new(
SymbolKind::Field,
ctx.source_range(),
- receiver.map_or_else(|| name.clone(), |receiver| format!("{}.{}", receiver, name).into()),
+ field_with_receiver(receiver.as_ref(), &name),
);
item.set_relevance(CompletionRelevance {
type_match: compute_type_match(ctx.completion, ty),
.set_documentation(field.docs(ctx.db()))
.set_deprecated(is_deprecated)
.lookup_by(name.clone());
- let is_keyword = SyntaxKind::from_keyword(name.as_str()).is_some();
- if is_keyword && !matches!(name.as_str(), "self" | "crate" | "super" | "Self") {
- item.insert_text(format!("r#{}", name));
- }
- if let Some(_ref_match) = compute_ref_match(ctx.completion, ty) {
- // FIXME
- // For now we don't properly calculate the edits for ref match
- // completions on struct fields, so we've disabled them. See #8058.
+ item.insert_text(field_with_receiver(receiver.as_ref(), &escaped_name));
+ if let Some(receiver) = &dot_access.receiver {
+ if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) {
+ if let Some(ref_match) = compute_ref_match(ctx.completion, ty) {
+ item.ref_match(ref_match, original.syntax().text_range().start());
+ }
+ }
}
item.build()
}
+fn field_with_receiver(receiver: Option<&hir::Name>, field_name: &str) -> SmolStr {
+ receiver
+ .map_or_else(|| field_name.into(), |receiver| format!("{}.{}", receiver, field_name).into())
+}
+
pub(crate) fn render_tuple_field(
ctx: RenderContext<'_>,
receiver: Option<hir::Name>,
let mut item = CompletionItem::new(
SymbolKind::Field,
ctx.source_range(),
- receiver.map_or_else(|| field.to_string(), |receiver| format!("{}.{}", receiver, field)),
+ field_with_receiver(receiver.as_ref(), &field.to_string()),
);
item.detail(ty.display(ctx.db()).to_string()).lookup_by(field.to_string());
item.build()
}
-pub(crate) fn render_resolution(
+pub(crate) fn render_type_inference(
+ ty_string: String,
+ ctx: &CompletionContext<'_>,
+) -> CompletionItem {
+ let mut builder =
+ CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string);
+ builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() });
+ builder.build()
+}
+
+pub(crate) fn render_path_resolution(
ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
local_name: hir::Name,
resolution: ScopeDef,
) -> Builder {
- render_resolution_(ctx, local_name, None, resolution)
+ render_resolution_path(ctx, path_ctx, local_name, None, resolution)
}
-pub(crate) fn render_resolution_simple(
+pub(crate) fn render_pattern_resolution(
ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
local_name: hir::Name,
resolution: ScopeDef,
) -> Builder {
- render_resolution_simple_(ctx, local_name, None, resolution)
+ render_resolution_pat(ctx, pattern_ctx, local_name, None, resolution)
}
pub(crate) fn render_resolution_with_import(
ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ import_edit: LocatedImport,
+) -> Option<Builder> {
+ let resolution = ScopeDef::from(import_edit.original_item);
+ let local_name = scope_def_to_name(resolution, &ctx, &import_edit)?;
+
+ Some(render_resolution_path(ctx, path_ctx, local_name, Some(import_edit), resolution))
+}
+
+pub(crate) fn render_resolution_with_import_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
import_edit: LocatedImport,
) -> Option<Builder> {
let resolution = ScopeDef::from(import_edit.original_item);
- let local_name = match resolution {
+ let local_name = scope_def_to_name(resolution, &ctx, &import_edit)?;
+ Some(render_resolution_pat(ctx, pattern_ctx, local_name, Some(import_edit), resolution))
+}
+
+fn scope_def_to_name(
+ resolution: ScopeDef,
+ ctx: &RenderContext<'_>,
+ import_edit: &LocatedImport,
+) -> Option<hir::Name> {
+ Some(match resolution {
ScopeDef::ModuleDef(hir::ModuleDef::Function(f)) => f.name(ctx.completion.db),
ScopeDef::ModuleDef(hir::ModuleDef::Const(c)) => c.name(ctx.completion.db)?,
ScopeDef::ModuleDef(hir::ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db),
_ => item_name(ctx.db(), import_edit.original_item)?,
- };
- Some(render_resolution_(ctx, local_name, Some(import_edit), resolution))
+ })
}
-pub(crate) fn render_type_inference(ty_string: String, ctx: &CompletionContext) -> CompletionItem {
- let mut builder =
- CompletionItem::new(CompletionItemKind::InferredType, ctx.source_range(), ty_string);
- builder.set_relevance(CompletionRelevance { is_definite: true, ..Default::default() });
- builder.build()
+fn render_resolution_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ local_name: hir::Name,
+ import_to_add: Option<LocatedImport>,
+ resolution: ScopeDef,
+) -> Builder {
+ let _p = profile::span("render_resolution");
+ use hir::ModuleDef::*;
+
+ match resolution {
+ ScopeDef::ModuleDef(Macro(mac)) => {
+ let ctx = ctx.import_to_add(import_to_add);
+ return render_macro_pat(ctx, pattern_ctx, local_name, mac);
+ }
+ _ => (),
+ }
+
+ render_resolution_simple_(ctx, &local_name, import_to_add, resolution)
}
-fn render_resolution_(
+fn render_resolution_path(
ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
local_name: hir::Name,
import_to_add: Option<LocatedImport>,
resolution: ScopeDef,
match resolution {
ScopeDef::ModuleDef(Macro(mac)) => {
let ctx = ctx.import_to_add(import_to_add);
- return render_macro(ctx, local_name, mac);
+ return render_macro(ctx, path_ctx, local_name, mac);
}
ScopeDef::ModuleDef(Function(func)) => {
let ctx = ctx.import_to_add(import_to_add);
- return render_fn(ctx, Some(local_name), func);
+ return render_fn(ctx, path_ctx, Some(local_name), func);
}
- ScopeDef::ModuleDef(Variant(var)) if ctx.completion.pattern_ctx.is_none() => {
+ ScopeDef::ModuleDef(Variant(var)) => {
let ctx = ctx.clone().import_to_add(import_to_add.clone());
- if let Some(item) = render_variant_lit(ctx, Some(local_name.clone()), var, None) {
+ if let Some(item) =
+ render_variant_lit(ctx, path_ctx, Some(local_name.clone()), var, None)
+ {
return item;
}
}
_ => (),
}
- render_resolution_simple_(ctx, local_name, import_to_add, resolution)
+
+ let completion = ctx.completion;
+ let cap = ctx.snippet_cap();
+ let db = completion.db;
+ let config = completion.config;
+
+ let name = local_name.to_smol_str();
+ let mut item = render_resolution_simple_(ctx, &local_name, import_to_add, resolution);
+ if local_name.is_escaped() {
+ item.insert_text(local_name.to_smol_str());
+ }
+ // Add `<>` for generic types
+ let type_path_no_ty_args = matches!(
+ path_ctx,
+ PathCompletionCtx { kind: PathKind::Type { .. }, has_type_args: false, .. }
+ ) && config.callable.is_some();
+ if type_path_no_ty_args {
+ if let Some(cap) = cap {
+ let has_non_default_type_params = match resolution {
+ ScopeDef::ModuleDef(hir::ModuleDef::Adt(it)) => it.has_non_default_type_params(db),
+ ScopeDef::ModuleDef(hir::ModuleDef::TypeAlias(it)) => {
+ it.has_non_default_type_params(db)
+ }
+ _ => false,
+ };
+
+ if has_non_default_type_params {
+ cov_mark::hit!(inserts_angle_brackets_for_generics);
+ item.lookup_by(name.clone())
+ .label(SmolStr::from_iter([&name, "<…>"]))
+ .trigger_call_info()
+ .insert_snippet(cap, format!("{}<$0>", local_name));
+ }
+ }
+ }
+ if let ScopeDef::Local(local) = resolution {
+ let ty = local.ty(db);
+ if !ty.is_unknown() {
+ item.detail(ty.display(db).to_string());
+ }
+
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(completion, &ty),
+ exact_name_match: compute_exact_name_match(completion, &name),
+ is_local: true,
+ ..CompletionRelevance::default()
+ });
+
+ if let Some(ref_match) = compute_ref_match(completion, &ty) {
+ item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
+ }
+ };
+ item
}
fn render_resolution_simple_(
ctx: RenderContext<'_>,
- local_name: hir::Name,
+ local_name: &hir::Name,
import_to_add: Option<LocatedImport>,
resolution: ScopeDef,
) -> Builder {
let _p = profile::span("render_resolution");
- use hir::ModuleDef::*;
let db = ctx.db();
let ctx = ctx.import_to_add(import_to_add);
- let kind = match resolution {
+ let kind = res_to_kind(resolution);
+
+ let mut item =
+ CompletionItem::new(kind, ctx.source_range(), local_name.unescaped().to_smol_str());
+ item.set_relevance(ctx.completion_relevance())
+ .set_documentation(scope_def_docs(db, resolution))
+ .set_deprecated(scope_def_is_deprecated(&ctx, resolution));
+
+ if let Some(import_to_add) = ctx.import_to_add {
+ item.add_import(import_to_add);
+ }
+ item
+}
+
+fn res_to_kind(resolution: ScopeDef) -> CompletionItemKind {
+ use hir::ModuleDef::*;
+ match resolution {
ScopeDef::Unknown => CompletionItemKind::UnresolvedReference,
ScopeDef::ModuleDef(Function(_)) => CompletionItemKind::SymbolKind(SymbolKind::Function),
ScopeDef::ModuleDef(Variant(_)) => CompletionItemKind::SymbolKind(SymbolKind::Variant),
ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => {
CompletionItemKind::SymbolKind(SymbolKind::SelfParam)
}
- };
-
- let local_name = local_name.to_smol_str();
- let mut item = CompletionItem::new(kind, ctx.source_range(), local_name.clone());
- item.set_relevance(ctx.completion_relevance());
- if let ScopeDef::Local(local) = resolution {
- let ty = local.ty(db);
- if !ty.is_unknown() {
- item.detail(ty.display(db).to_string());
- }
-
- item.set_relevance(CompletionRelevance {
- type_match: compute_type_match(ctx.completion, &ty),
- exact_name_match: compute_exact_name_match(ctx.completion, &local_name),
- is_local: true,
- ..CompletionRelevance::default()
- });
-
- if let Some(ref_match) = compute_ref_match(ctx.completion, &ty) {
- item.ref_match(ref_match);
- }
- };
-
- // Add `<>` for generic types
- let type_path_no_ty_args = matches!(
- ctx.completion.path_context,
- Some(PathCompletionCtx { kind: Some(PathKind::Type), has_type_args: false, .. })
- ) && ctx.completion.config.add_call_parenthesis;
- if type_path_no_ty_args {
- if let Some(cap) = ctx.snippet_cap() {
- let has_non_default_type_params = match resolution {
- ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(db),
- ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(db),
- _ => false,
- };
- if has_non_default_type_params {
- cov_mark::hit!(inserts_angle_brackets_for_generics);
- item.lookup_by(local_name.clone())
- .label(SmolStr::from_iter([&local_name, "<…>"]))
- .trigger_call_info()
- .insert_snippet(cap, format!("{}<$0>", local_name));
- }
- }
}
- item.set_documentation(scope_def_docs(db, resolution))
- .set_deprecated(scope_def_is_deprecated(&ctx, resolution));
-
- if let Some(import_to_add) = ctx.import_to_add {
- item.add_import(import_to_add);
- }
- item
}
fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option<hir::Documentation> {
}
fn compute_type_match(
- ctx: &CompletionContext,
+ ctx: &CompletionContext<'_>,
completion_ty: &hir::Type,
) -> Option<CompletionRelevanceTypeMatch> {
let expected_type = ctx.expected_type.as_ref()?;
}
}
-fn compute_exact_name_match(ctx: &CompletionContext, completion_name: &str) -> bool {
+fn compute_exact_name_match(ctx: &CompletionContext<'_>, completion_name: &str) -> bool {
ctx.expected_name.as_ref().map_or(false, |name| name.text() == completion_name)
}
fn compute_ref_match(
- ctx: &CompletionContext,
+ ctx: &CompletionContext<'_>,
completion_ty: &hir::Type,
) -> Option<hir::Mutability> {
let expected_type = ctx.expected_type.as_ref()?;
#[track_caller]
fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
- let mut actual = get_all_items(TEST_CONFIG, ra_fixture);
+ let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
actual.retain(|it| kinds.contains(&it.kind()));
actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
check_relevance_(actual, expect);
#[track_caller]
fn check_relevance(ra_fixture: &str, expect: Expect) {
- let mut actual = get_all_items(TEST_CONFIG, ra_fixture);
+ let mut actual = get_all_items(TEST_CONFIG, ra_fixture, None);
actual.retain(|it| it.kind() != CompletionItemKind::Snippet);
actual.retain(|it| it.kind() != CompletionItemKind::Keyword);
actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType);
let relevance = display_relevance(it.relevance());
items.push(format!("{} {} {}\n", tag, it.label(), relevance));
- if let Some((mutability, relevance)) = it.ref_match() {
+ if let Some((mutability, _offset, relevance)) = it.ref_match() {
let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label());
let relevance = display_relevance(relevance);
kind: SymbolKind(
Variant,
),
+ lookup: "Foo{}",
detail: "Foo { x: i32, y: i32 }",
},
]
kind: SymbolKind(
Variant,
),
+ lookup: "Foo()",
detail: "Foo(i32, i32)",
},
]
kind: SymbolKind(
Variant,
),
- lookup: "Spam::Bar(…)",
+ lookup: "Spam::Bar()",
detail: "m::Spam::Bar(i32)",
relevance: CompletionRelevance {
exact_name_match: false,
#[test]
fn no_parens_in_use_item() {
- cov_mark::check!(no_parens_in_use_item);
check_edit(
"foo",
r#"
"#,
expect![[r#"
lc world [type+name+local]
+ st WorldSnapshot {…} []
+ st &WorldSnapshot {…} [type]
st WorldSnapshot []
fn go(…) []
"#]],
lc s [name+local]
lc &mut s [type+name+local]
st S []
- fn main() []
+ st &mut S [type]
+ st S []
fn foo(…) []
+ fn main() []
"#]],
);
check_relevance(
lc s [type+name+local]
st S [type]
st S []
- fn main() []
fn foo(…) []
+ fn main() []
"#]],
);
check_relevance(
lc ssss [type+local]
st S [type]
st S []
- fn main() []
fn foo(…) []
+ fn main() []
"#]],
);
}
lc m [local]
lc t [local]
lc &t [type+local]
- st T []
st S []
- fn main() []
+ st &S [type]
+ st S []
+ st T []
fn foo(…) []
+ fn main() []
md core []
- tt Sized []
"#]],
)
}
lc m [local]
lc t [local]
lc &mut t [type+local]
- st T []
st S []
- fn main() []
+ st &mut S [type]
+ st S []
+ st T []
fn foo(…) []
+ fn main() []
md core []
- tt Sized []
"#]],
)
}
}
"#,
expect![[r#"
- st T []
st S []
- fn main() []
+ st &S [type]
+ st S []
+ st T []
fn bar() []
fn &bar() [type]
fn foo(…) []
+ fn main() []
md core []
- tt Sized []
"#]],
)
}
fn foo(f: Foo) { let _: &u32 = f.b$0 }
"#,
&[CompletionItemKind::Method, CompletionItemKind::SymbolKind(SymbolKind::Field)],
- // FIXME
- // Ideally we'd also suggest &f.bar and &f.baz() as exact
- // type matches. See #8058.
expect![[r#"
[
CompletionItem {
kind: Method,
lookup: "baz",
detail: "fn(&self) -> u32",
+ ref_match: "&@96",
},
CompletionItem {
label: "bar",
Field,
),
detail: "u32",
+ ref_match: "&@96",
},
]
"#]],
#[test]
fn qualified_path_ref() {
- // disabled right now because it doesn't render correctly, #8058
check_kinds(
r#"
struct S;
),
lookup: "foo",
detail: "fn() -> S",
+ ref_match: "&@92",
},
]
"#]],
ev Foo::B [type_could_unify]
fn foo() []
en Foo []
- fn baz() []
fn bar() []
+ fn baz() []
"#]],
);
}
}
"#,
expect![[r#"
- md std []
st Buffer []
fn f() []
+ md std []
tt BufRead (use std::io::BufRead) [requires_import]
st BufReader (use std::io::BufReader) [requires_import]
st BufWriter (use std::io::BufWriter) [requires_import]
"#]],
);
}
+
+ #[test]
+ fn completes_struct_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+mod m { pub struct r#type {} }
+fn main() {
+ let r#type = m::t$0;
+}
+"#,
+ r#"
+mod m { pub struct r#type {} }
+fn main() {
+ let r#type = m::r#type;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_fn_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+mod m { pub fn r#type {} }
+fn main() {
+ m::t$0
+}
+"#,
+ r#"
+mod m { pub fn r#type {} }
+fn main() {
+ m::r#type()$0
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_macro_with_raw_identifier() {
+ check_edit(
+ "let!",
+ r#"
+macro_rules! r#let { () => {} }
+fn main() {
+ $0
+}
+"#,
+ r#"
+macro_rules! r#let { () => {} }
+fn main() {
+ r#let!($0)
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_variant_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+enum A { r#type }
+fn main() {
+ let a = A::t$0
+}
+"#,
+ r#"
+enum A { r#type }
+fn main() {
+ let a = A::r#type$0
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_field_with_raw_identifier() {
+ check_edit(
+ "fn",
+ r#"
+mod r#type {
+ pub struct r#struct {
+ pub r#fn: u32
+ }
+}
+
+fn main() {
+ let a = r#type::r#struct {};
+ a.$0
+}
+"#,
+ r#"
+mod r#type {
+ pub struct r#struct {
+ pub r#fn: u32
+ }
+}
+
+fn main() {
+ let a = r#type::r#struct {};
+ a.r#fn
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_const_with_raw_identifier() {
+ check_edit(
+ "type",
+ r#"
+struct r#struct {}
+impl r#struct { pub const r#type: u8 = 1; }
+fn main() {
+ r#struct::t$0
+}
+"#,
+ r#"
+struct r#struct {}
+impl r#struct { pub const r#type: u8 = 1; }
+fn main() {
+ r#struct::r#type
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_type_alias_with_raw_identifier() {
+ check_edit(
+ "type type",
+ r#"
+struct r#struct {}
+trait r#trait { type r#type; }
+impl r#trait for r#struct { type t$0 }
+"#,
+ r#"
+struct r#struct {}
+trait r#trait { type r#type; }
+impl r#trait for r#struct { type r#type = $0; }
+"#,
+ )
+ }
+
+ #[test]
+ fn field_access_includes_self() {
+ check_edit(
+ "length",
+ r#"
+struct S {
+ length: i32
+}
+
+impl S {
+ fn some_fn(&self) {
+ let l = len$0
+ }
+}
+"#,
+ r#"
+struct S {
+ length: i32
+}
+
+impl S {
+ fn some_fn(&self) {
+ let l = self.length
+ }
+}
+"#,
+ )
+ }
}