summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-completion
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-completion')
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/Cargo.toml33
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions.rs691
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs380
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs93
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs116
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs61
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs74
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs947
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs280
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs108
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs43
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs407
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs196
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs130
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs133
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs1160
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs237
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs341
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs354
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs185
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs616
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs311
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs369
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs189
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs246
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs120
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs41
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/config.rs41
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context.rs639
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs1293
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs413
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/item.rs637
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/lib.rs247
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render.rs1910
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs33
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs671
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs191
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs270
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs193
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs57
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs77
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs96
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs214
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests.rs305
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs1016
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs672
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs1232
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs274
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs154
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs247
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs716
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs131
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs133
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs229
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs895
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs671
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs384
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs90
58 files changed, 21992 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml
new file mode 100644
index 000000000..8c9d6b228
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "ide-completion"
+version = "0.0.0"
+description = "TBD"
+license = "MIT OR Apache-2.0"
+edition = "2021"
+rust-version = "1.57"
+
+[lib]
+doctest = false
+
+[dependencies]
+cov-mark = "2.0.0-pre.1"
+itertools = "0.10.3"
+
+once_cell = "1.12.0"
+smallvec = "1.9.0"
+
+stdx = { path = "../stdx", version = "0.0.0" }
+syntax = { path = "../syntax", version = "0.0.0" }
+text-edit = { path = "../text-edit", version = "0.0.0" }
+base-db = { path = "../base-db", version = "0.0.0" }
+ide-db = { path = "../ide-db", version = "0.0.0" }
+profile = { path = "../profile", version = "0.0.0" }
+
+# completions crate should depend only on the top-level `hir` package. if you need
+# something from some `hir-xxx` subpackage, reexport the API via `hir`.
+hir = { path = "../hir", version = "0.0.0" }
+
+[dev-dependencies]
+expect-test = "1.4.0"
+
+test-utils = { path = "../test-utils" }
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs
new file mode 100644
index 000000000..72579e602
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs
@@ -0,0 +1,691 @@
+//! This module defines an accumulator for completions which are going to be presented to user.
+
+pub(crate) mod attribute;
+pub(crate) mod dot;
+pub(crate) mod expr;
+pub(crate) mod extern_abi;
+pub(crate) mod field;
+pub(crate) mod flyimport;
+pub(crate) mod fn_param;
+pub(crate) mod format_string;
+pub(crate) mod item_list;
+pub(crate) mod keyword;
+pub(crate) mod lifetime;
+pub(crate) mod mod_;
+pub(crate) mod pattern;
+pub(crate) mod postfix;
+pub(crate) mod record;
+pub(crate) mod snippet;
+pub(crate) mod r#type;
+pub(crate) mod use_;
+pub(crate) mod vis;
+
+use std::iter;
+
+use hir::{known, ScopeDef};
+use ide_db::{imports::import_assets::LocatedImport, SymbolKind};
+use syntax::ast;
+
+use crate::{
+ context::{
+ DotAccess, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind,
+ PathCompletionCtx, PathKind, PatternContext, TypeLocation, Visible,
+ },
+ item::Builder,
+ render::{
+ const_::render_const,
+ function::{render_fn, render_method},
+ literal::{render_struct_literal, render_variant_lit},
+ macro_::render_macro,
+ pattern::{render_struct_pat, render_variant_pat},
+ render_field, render_path_resolution, render_pattern_resolution, render_tuple_field,
+ type_alias::{render_type_alias, render_type_alias_with_eq},
+ union_literal::render_union_literal,
+ RenderContext,
+ },
+ CompletionContext, CompletionItem, CompletionItemKind,
+};
+
+/// Represents an in-progress set of completions being built.
+#[derive(Debug, Default)]
+pub struct Completions {
+ buf: Vec<CompletionItem>,
+}
+
+impl From<Completions> for Vec<CompletionItem> {
+ fn from(val: Completions) -> Self {
+ val.buf
+ }
+}
+
+impl Builder {
+ /// Convenience method, which allows to add a freshly created completion into accumulator
+ /// without binding it to the variable.
+ pub(crate) fn add_to(self, acc: &mut Completions) {
+ acc.add(self.build())
+ }
+}
+
+impl Completions {
+ fn add(&mut self, item: CompletionItem) {
+ self.buf.push(item)
+ }
+
+ fn add_opt(&mut self, item: Option<CompletionItem>) {
+ if let Some(item) = item {
+ self.buf.push(item)
+ }
+ }
+
+ pub(crate) fn add_all<I>(&mut self, items: I)
+ where
+ I: IntoIterator,
+ I::Item: Into<CompletionItem>,
+ {
+ items.into_iter().for_each(|item| self.add(item.into()))
+ }
+
+ pub(crate) fn add_keyword(&mut self, ctx: &CompletionContext<'_>, keyword: &'static str) {
+ let item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), keyword);
+ item.add_to(self);
+ }
+
+ pub(crate) fn add_nameref_keywords_with_colon(&mut self, ctx: &CompletionContext<'_>) {
+ ["self::", "crate::"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
+
+ if ctx.depth_from_crate_root > 0 {
+ self.add_keyword(ctx, "super::");
+ }
+ }
+
+ pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext<'_>) {
+ ["self", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
+
+ if ctx.depth_from_crate_root > 0 {
+ self.add_keyword(ctx, "super");
+ }
+ }
+
+ pub(crate) fn add_super_keyword(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ super_chain_len: Option<usize>,
+ ) {
+ if let Some(len) = super_chain_len {
+ if len > 0 && len < ctx.depth_from_crate_root {
+ self.add_keyword(ctx, "super::");
+ }
+ }
+ }
+
+ pub(crate) fn add_keyword_snippet_expr(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ incomplete_let: bool,
+ kw: &str,
+ snippet: &str,
+ ) {
+ let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
+
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ if incomplete_let && snippet.ends_with('}') {
+ // complete block expression snippets with a trailing semicolon, if inside an incomplete let
+ cov_mark::hit!(let_semi);
+ item.insert_snippet(cap, format!("{};", snippet));
+ } else {
+ item.insert_snippet(cap, snippet);
+ }
+ }
+ None => {
+ item.insert_text(if snippet.contains('$') { kw } else { snippet });
+ }
+ };
+ item.add_to(self);
+ }
+
+ pub(crate) fn add_keyword_snippet(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ kw: &str,
+ snippet: &str,
+ ) {
+ let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
+
+ match ctx.config.snippet_cap {
+ Some(cap) => item.insert_snippet(cap, snippet),
+ None => item.insert_text(if snippet.contains('$') { kw } else { snippet }),
+ };
+ item.add_to(self);
+ }
+
+ pub(crate) fn add_crate_roots(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ ) {
+ ctx.process_all_names(&mut |name, res| match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {
+ self.add_module(ctx, path_ctx, m, name);
+ }
+ _ => (),
+ });
+ }
+
+ pub(crate) fn add_path_resolution(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: hir::Name,
+ resolution: hir::ScopeDef,
+ ) {
+ let is_private_editable = match ctx.def_is_visible(&resolution) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_path_resolution(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ path_ctx,
+ local_name,
+ resolution,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_pattern_resolution(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ local_name: hir::Name,
+ resolution: hir::ScopeDef,
+ ) {
+ let is_private_editable = match ctx.def_is_visible(&resolution) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_pattern_resolution(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ pattern_ctx,
+ local_name,
+ resolution,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_enum_variants(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ e: hir::Enum,
+ ) {
+ e.variants(ctx.db)
+ .into_iter()
+ .for_each(|variant| self.add_enum_variant(ctx, path_ctx, variant, None));
+ }
+
+ pub(crate) fn add_module(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ module: hir::Module,
+ local_name: hir::Name,
+ ) {
+ self.add_path_resolution(
+ ctx,
+ path_ctx,
+ local_name,
+ hir::ScopeDef::ModuleDef(module.into()),
+ );
+ }
+
+ pub(crate) fn add_macro(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ mac: hir::Macro,
+ local_name: hir::Name,
+ ) {
+ let is_private_editable = match ctx.is_visible(&mac) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_macro(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ path_ctx,
+ local_name,
+ mac,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_function(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ func: hir::Function,
+ local_name: Option<hir::Name>,
+ ) {
+ let is_private_editable = match ctx.is_visible(&func) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_fn(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ path_ctx,
+ local_name,
+ func,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_method(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ func: hir::Function,
+ receiver: Option<hir::Name>,
+ local_name: Option<hir::Name>,
+ ) {
+ let is_private_editable = match ctx.is_visible(&func) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_method(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ dot_access,
+ receiver,
+ local_name,
+ func,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_method_with_import(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ func: hir::Function,
+ import: LocatedImport,
+ ) {
+ let is_private_editable = match ctx.is_visible(&func) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add(
+ render_method(
+ RenderContext::new(ctx)
+ .private_editable(is_private_editable)
+ .import_to_add(Some(import)),
+ dot_access,
+ None,
+ None,
+ func,
+ )
+ .build(),
+ );
+ }
+
+ pub(crate) fn add_const(&mut self, ctx: &CompletionContext<'_>, konst: hir::Const) {
+ let is_private_editable = match ctx.is_visible(&konst) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add_opt(render_const(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ konst,
+ ));
+ }
+
+ pub(crate) fn add_type_alias(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ type_alias: hir::TypeAlias,
+ ) {
+ let is_private_editable = match ctx.is_visible(&type_alias) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ self.add_opt(render_type_alias(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ type_alias,
+ ));
+ }
+
+ pub(crate) fn add_type_alias_with_eq(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ type_alias: hir::TypeAlias,
+ ) {
+ self.add_opt(render_type_alias_with_eq(RenderContext::new(ctx), type_alias));
+ }
+
+ pub(crate) fn add_qualified_enum_variant(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ variant: hir::Variant,
+ path: hir::ModPath,
+ ) {
+ if let Some(builder) =
+ render_variant_lit(RenderContext::new(ctx), path_ctx, None, variant, Some(path))
+ {
+ self.add(builder.build());
+ }
+ }
+
+ pub(crate) fn add_enum_variant(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ variant: hir::Variant,
+ local_name: Option<hir::Name>,
+ ) {
+ if let PathCompletionCtx { kind: PathKind::Pat { pat_ctx }, .. } = path_ctx {
+ cov_mark::hit!(enum_variant_pattern_path);
+ self.add_variant_pat(ctx, pat_ctx, Some(path_ctx), variant, local_name);
+ return;
+ }
+
+ if let Some(builder) =
+ render_variant_lit(RenderContext::new(ctx), path_ctx, local_name, variant, None)
+ {
+ self.add(builder.build());
+ }
+ }
+
+ pub(crate) fn add_field(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ receiver: Option<hir::Name>,
+ field: hir::Field,
+ ty: &hir::Type,
+ ) {
+ let is_private_editable = match ctx.is_visible(&field) {
+ Visible::Yes => false,
+ Visible::Editable => true,
+ Visible::No => return,
+ };
+ let item = render_field(
+ RenderContext::new(ctx).private_editable(is_private_editable),
+ dot_access,
+ receiver,
+ field,
+ ty,
+ );
+ self.add(item);
+ }
+
+ pub(crate) fn add_struct_literal(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ strukt: hir::Struct,
+ path: Option<hir::ModPath>,
+ local_name: Option<hir::Name>,
+ ) {
+ if let Some(builder) =
+ render_struct_literal(RenderContext::new(ctx), path_ctx, strukt, path, local_name)
+ {
+ self.add(builder.build());
+ }
+ }
+
+ pub(crate) fn add_union_literal(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ un: hir::Union,
+ path: Option<hir::ModPath>,
+ local_name: Option<hir::Name>,
+ ) {
+ let item = render_union_literal(RenderContext::new(ctx), un, path, local_name);
+ self.add_opt(item);
+ }
+
+ pub(crate) fn add_tuple_field(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ receiver: Option<hir::Name>,
+ field: usize,
+ ty: &hir::Type,
+ ) {
+ let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty);
+ self.add(item);
+ }
+
+ pub(crate) fn add_lifetime(&mut self, ctx: &CompletionContext<'_>, name: hir::Name) {
+ CompletionItem::new(SymbolKind::LifetimeParam, ctx.source_range(), name.to_smol_str())
+ .add_to(self)
+ }
+
+ pub(crate) fn add_label(&mut self, ctx: &CompletionContext<'_>, name: hir::Name) {
+ CompletionItem::new(SymbolKind::Label, ctx.source_range(), name.to_smol_str()).add_to(self)
+ }
+
+ pub(crate) fn add_variant_pat(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ path_ctx: Option<&PathCompletionCtx>,
+ variant: hir::Variant,
+ local_name: Option<hir::Name>,
+ ) {
+ self.add_opt(render_variant_pat(
+ RenderContext::new(ctx),
+ pattern_ctx,
+ path_ctx,
+ variant,
+ local_name.clone(),
+ None,
+ ));
+ }
+
+ pub(crate) fn add_qualified_variant_pat(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ variant: hir::Variant,
+ path: hir::ModPath,
+ ) {
+ let path = Some(&path);
+ self.add_opt(render_variant_pat(
+ RenderContext::new(ctx),
+ pattern_ctx,
+ None,
+ variant,
+ None,
+ path,
+ ));
+ }
+
+ pub(crate) fn add_struct_pat(
+ &mut self,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ strukt: hir::Struct,
+ local_name: Option<hir::Name>,
+ ) {
+ self.add_opt(render_struct_pat(RenderContext::new(ctx), pattern_ctx, strukt, local_name));
+ }
+}
+
+/// Calls the callback for each variant of the provided enum with the path to the variant.
+/// Skips variants that are visible with single segment paths.
+fn enum_variants_with_paths(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ enum_: hir::Enum,
+ impl_: &Option<ast::Impl>,
+ cb: impl Fn(&mut Completions, &CompletionContext<'_>, hir::Variant, hir::ModPath),
+) {
+ let variants = enum_.variants(ctx.db);
+
+ if let Some(impl_) = impl_.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) {
+ if impl_.self_ty(ctx.db).as_adt() == Some(hir::Adt::Enum(enum_)) {
+ for &variant in &variants {
+ let self_path = hir::ModPath::from_segments(
+ hir::PathKind::Plain,
+ iter::once(known::SELF_TYPE).chain(iter::once(variant.name(ctx.db))),
+ );
+ cb(acc, ctx, variant, self_path);
+ }
+ }
+ }
+
+ for variant in variants {
+ if let Some(path) = ctx.module.find_use_path(ctx.db, hir::ModuleDef::from(variant)) {
+ // Variants with trivial paths are already added by the existing completion logic,
+ // so we should avoid adding these twice
+ if path.segments().len() > 1 {
+ cb(acc, ctx, variant, path);
+ }
+ }
+ }
+}
+
+pub(super) fn complete_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ NameContext { name, kind }: &NameContext,
+) {
+ match kind {
+ NameKind::Const => {
+ item_list::trait_impl::complete_trait_impl_const(acc, ctx, name);
+ }
+ NameKind::Function => {
+ item_list::trait_impl::complete_trait_impl_fn(acc, ctx, name);
+ }
+ NameKind::IdentPat(pattern_ctx) => {
+ if ctx.token.kind() != syntax::T![_] {
+ complete_patterns(acc, ctx, pattern_ctx)
+ }
+ }
+ NameKind::Module(mod_under_caret) => {
+ mod_::complete_mod(acc, ctx, mod_under_caret);
+ }
+ NameKind::TypeAlias => {
+ item_list::trait_impl::complete_trait_impl_type_alias(acc, ctx, name);
+ }
+ NameKind::RecordField => {
+ field::complete_field_list_record_variant(acc, ctx);
+ }
+ NameKind::ConstParam
+ | NameKind::Enum
+ | NameKind::MacroDef
+ | NameKind::MacroRules
+ | NameKind::Rename
+ | NameKind::SelfParam
+ | NameKind::Static
+ | NameKind::Struct
+ | NameKind::Trait
+ | NameKind::TypeParam
+ | NameKind::Union
+ | NameKind::Variant => (),
+ }
+}
+
+pub(super) fn complete_name_ref(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ NameRefContext { nameref, kind }: &NameRefContext,
+) {
+ match kind {
+ NameRefKind::Path(path_ctx) => {
+ flyimport::import_on_the_fly_path(acc, ctx, path_ctx);
+
+ match &path_ctx.kind {
+ PathKind::Expr { expr_ctx } => {
+ expr::complete_expr_path(acc, ctx, path_ctx, expr_ctx);
+
+ dot::complete_undotted_self(acc, ctx, path_ctx, expr_ctx);
+ item_list::complete_item_list_in_expr(acc, ctx, path_ctx, expr_ctx);
+ record::complete_record_expr_func_update(acc, ctx, path_ctx, expr_ctx);
+ snippet::complete_expr_snippet(acc, ctx, path_ctx, expr_ctx);
+ }
+ PathKind::Type { location } => {
+ r#type::complete_type_path(acc, ctx, path_ctx, location);
+
+ match location {
+ TypeLocation::TupleField => {
+ field::complete_field_list_tuple_variant(acc, ctx, path_ctx);
+ }
+ TypeLocation::TypeAscription(ascription) => {
+ r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription);
+ }
+ TypeLocation::GenericArgList(_)
+ | TypeLocation::TypeBound
+ | TypeLocation::ImplTarget
+ | TypeLocation::ImplTrait
+ | TypeLocation::Other => (),
+ }
+ }
+ PathKind::Attr { attr_ctx } => {
+ attribute::complete_attribute_path(acc, ctx, path_ctx, attr_ctx);
+ }
+ PathKind::Derive { existing_derives } => {
+ attribute::complete_derive_path(acc, ctx, path_ctx, existing_derives);
+ }
+ PathKind::Item { kind } => {
+ item_list::complete_item_list(acc, ctx, path_ctx, kind);
+
+ snippet::complete_item_snippet(acc, ctx, path_ctx, kind);
+ if let ItemListKind::TraitImpl(impl_) = kind {
+ item_list::trait_impl::complete_trait_impl_item_by_name(
+ acc, ctx, path_ctx, nameref, impl_,
+ );
+ }
+ }
+ PathKind::Pat { .. } => {
+ pattern::complete_pattern_path(acc, ctx, path_ctx);
+ }
+ PathKind::Vis { has_in_token } => {
+ vis::complete_vis_path(acc, ctx, path_ctx, has_in_token);
+ }
+ PathKind::Use => {
+ use_::complete_use_path(acc, ctx, path_ctx, nameref);
+ }
+ }
+ }
+ NameRefKind::DotAccess(dot_access) => {
+ flyimport::import_on_the_fly_dot(acc, ctx, dot_access);
+ dot::complete_dot(acc, ctx, dot_access);
+ postfix::complete_postfix(acc, ctx, dot_access);
+ }
+ NameRefKind::Keyword(item) => {
+ keyword::complete_for_and_where(acc, ctx, item);
+ }
+ NameRefKind::RecordExpr { dot_prefix, expr } => {
+ record::complete_record_expr_fields(acc, ctx, expr, dot_prefix);
+ }
+ NameRefKind::Pattern(pattern_ctx) => complete_patterns(acc, ctx, pattern_ctx),
+ }
+}
+
+fn complete_patterns(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) {
+ flyimport::import_on_the_fly_pat(acc, ctx, pattern_ctx);
+ fn_param::complete_fn_param(acc, ctx, pattern_ctx);
+ pattern::complete_pattern(acc, ctx, pattern_ctx);
+ record::complete_record_pattern_fields(acc, ctx, pattern_ctx);
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
new file mode 100644
index 000000000..d9fe94cb4
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs
@@ -0,0 +1,380 @@
+//! Completion for (built-in) attributes, derives and lints.
+//!
+//! This module uses a bit of static metadata to provide completions for builtin-in attributes and lints.
+
+use ide_db::{
+ generated::lints::{
+ Lint, CLIPPY_LINTS, CLIPPY_LINT_GROUPS, DEFAULT_LINTS, FEATURES, RUSTDOC_LINTS,
+ },
+ syntax_helpers::node_ext::parse_tt_as_comma_sep_paths,
+ FxHashMap, SymbolKind,
+};
+use itertools::Itertools;
+use once_cell::sync::Lazy;
+use syntax::{
+ ast::{self, AttrKind},
+ AstNode, SyntaxKind, T,
+};
+
+use crate::{
+ context::{AttrCtx, CompletionContext, PathCompletionCtx, Qualified},
+ item::CompletionItem,
+ Completions,
+};
+
+mod cfg;
+mod derive;
+mod lint;
+mod repr;
+
+pub(crate) use self::derive::complete_derive_path;
+
+/// Complete inputs to known builtin attributes as well as derive attributes
+pub(crate) fn complete_known_attribute_input(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ &colon_prefix: &bool,
+ fake_attribute_under_caret: &ast::Attr,
+) -> Option<()> {
+ let attribute = fake_attribute_under_caret;
+ let name_ref = match attribute.path() {
+ Some(p) => Some(p.as_single_name_ref()?),
+ None => None,
+ };
+ let (path, tt) = name_ref.zip(attribute.token_tree())?;
+ if tt.l_paren_token().is_none() {
+ return None;
+ }
+
+ match path.text().as_str() {
+ "repr" => repr::complete_repr(acc, ctx, tt),
+ "feature" => {
+ lint::complete_lint(acc, ctx, colon_prefix, &parse_tt_as_comma_sep_paths(tt)?, FEATURES)
+ }
+ "allow" | "warn" | "deny" | "forbid" => {
+ let existing_lints = parse_tt_as_comma_sep_paths(tt)?;
+
+ let lints: Vec<Lint> = CLIPPY_LINT_GROUPS
+ .iter()
+ .map(|g| &g.lint)
+ .chain(DEFAULT_LINTS)
+ .chain(CLIPPY_LINTS)
+ .chain(RUSTDOC_LINTS)
+ .cloned()
+ .collect();
+
+ lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
+ }
+ "cfg" => cfg::complete_cfg(acc, ctx),
+ _ => (),
+ }
+ Some(())
+}
+
+pub(crate) fn complete_attribute_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ &AttrCtx { kind, annotated_item_kind }: &AttrCtx,
+) {
+ let is_inner = kind == AttrKind::Inner;
+
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
+ match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ }
+ }
+ return;
+ }
+ // fresh use tree with leading colon2, only show crate roots
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ // only show modules in a fresh UseTree
+ Qualified::No => {
+ ctx.process_all_names(&mut |name, def| match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_attr(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ }
+
+ let attributes = annotated_item_kind.and_then(|kind| {
+ if ast::Expr::can_cast(kind) {
+ Some(EXPR_ATTRIBUTES)
+ } else {
+ KIND_TO_ATTRIBUTES.get(&kind).copied()
+ }
+ });
+
+ let add_completion = |attr_completion: &AttrCompletion| {
+ let mut item =
+ CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), attr_completion.label);
+
+ if let Some(lookup) = attr_completion.lookup {
+ item.lookup_by(lookup);
+ }
+
+ if let Some((snippet, cap)) = attr_completion.snippet.zip(ctx.config.snippet_cap) {
+ item.insert_snippet(cap, snippet);
+ }
+
+ if is_inner || !attr_completion.prefer_inner {
+ item.add_to(acc);
+ }
+ };
+
+ match attributes {
+ Some(applicable) => applicable
+ .iter()
+ .flat_map(|name| ATTRIBUTES.binary_search_by(|attr| attr.key().cmp(name)).ok())
+ .flat_map(|idx| ATTRIBUTES.get(idx))
+ .for_each(add_completion),
+ None if is_inner => ATTRIBUTES.iter().for_each(add_completion),
+ None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion),
+ }
+}
+
+struct AttrCompletion {
+ label: &'static str,
+ lookup: Option<&'static str>,
+ snippet: Option<&'static str>,
+ prefer_inner: bool,
+}
+
+impl AttrCompletion {
+ fn key(&self) -> &'static str {
+ self.lookup.unwrap_or(self.label)
+ }
+
+ const fn prefer_inner(self) -> AttrCompletion {
+ AttrCompletion { prefer_inner: true, ..self }
+ }
+}
+
+const fn attr(
+ label: &'static str,
+ lookup: Option<&'static str>,
+ snippet: Option<&'static str>,
+) -> AttrCompletion {
+ AttrCompletion { label, lookup, snippet, prefer_inner: false }
+}
+
+macro_rules! attrs {
+ // attributes applicable to all items
+ [@ { item $($tt:tt)* } {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" })
+ };
+ // attributes applicable to all adts
+ [@ { adt $($tt:tt)* } {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" })
+ };
+ // attributes applicable to all linkable things aka functions/statics
+ [@ { linkable $($tt:tt)* } {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" })
+ };
+ // error fallback for nicer error message
+ [@ { $ty:ident $($tt:tt)* } {$($acc:tt)*}] => {
+ compile_error!(concat!("unknown attr subtype ", stringify!($ty)))
+ };
+ // general push down accumulation
+ [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => {
+ attrs!(@ { $($tt)* } { $($acc)*, $lit })
+ };
+ [@ {$($tt:tt)+} {$($tt2:tt)*}] => {
+ compile_error!(concat!("Unexpected input ", stringify!($($tt)+)))
+ };
+ // final output construction
+ [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ };
+ // starting matcher
+ [$($tt:tt),*] => {
+ attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" })
+ };
+}
+
+#[rustfmt::skip]
+static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| {
+ use SyntaxKind::*;
+ [
+ (
+ SOURCE_FILE,
+ attrs!(
+ item,
+ "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std",
+ "recursion_limit", "type_length_limit", "windows_subsystem"
+ ),
+ ),
+ (MODULE, attrs!(item, "macro_use", "no_implicit_prelude", "path")),
+ (ITEM_LIST, attrs!(item, "no_implicit_prelude")),
+ (MACRO_RULES, attrs!(item, "macro_export", "macro_use")),
+ (MACRO_DEF, attrs!(item)),
+ (EXTERN_CRATE, attrs!(item, "macro_use", "no_link")),
+ (USE, attrs!(item)),
+ (TYPE_ALIAS, attrs!(item)),
+ (STRUCT, attrs!(item, adt, "non_exhaustive")),
+ (ENUM, attrs!(item, adt, "non_exhaustive")),
+ (UNION, attrs!(item, adt)),
+ (CONST, attrs!(item)),
+ (
+ FN,
+ attrs!(
+ item, linkable,
+ "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro",
+ "proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature",
+ "test", "track_caller"
+ ),
+ ),
+ (STATIC, attrs!(item, linkable, "global_allocator", "used")),
+ (TRAIT, attrs!(item, "must_use")),
+ (IMPL, attrs!(item, "automatically_derived")),
+ (ASSOC_ITEM_LIST, attrs!(item)),
+ (EXTERN_BLOCK, attrs!(item, "link")),
+ (EXTERN_ITEM_LIST, attrs!(item, "link")),
+ (MACRO_CALL, attrs!()),
+ (SELF_PARAM, attrs!()),
+ (PARAM, attrs!()),
+ (RECORD_FIELD, attrs!()),
+ (VARIANT, attrs!("non_exhaustive")),
+ (TYPE_PARAM, attrs!()),
+ (CONST_PARAM, attrs!()),
+ (LIFETIME_PARAM, attrs!()),
+ (LET_STMT, attrs!()),
+ (EXPR_STMT, attrs!()),
+ (LITERAL, attrs!()),
+ (RECORD_EXPR_FIELD_LIST, attrs!()),
+ (RECORD_EXPR_FIELD, attrs!()),
+ (MATCH_ARM_LIST, attrs!()),
+ (MATCH_ARM, attrs!()),
+ (IDENT_PAT, attrs!()),
+ (RECORD_PAT_FIELD, attrs!()),
+ ]
+ .into_iter()
+ .collect()
+});
+const EXPR_ATTRIBUTES: &[&str] = attrs!();
+
+/// <https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index>
+// Keep these sorted for the binary search!
+const ATTRIBUTES: &[AttrCompletion] = &[
+ attr("allow(…)", Some("allow"), Some("allow(${0:lint})")),
+ attr("automatically_derived", None, None),
+ attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")),
+ attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")),
+ attr("cold", None, None),
+ attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#))
+ .prefer_inner(),
+ attr("deny(…)", Some("deny"), Some("deny(${0:lint})")),
+ attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)),
+ attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)),
+ attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)),
+ attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)),
+ attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)),
+ attr(
+ r#"export_name = "…""#,
+ Some("export_name"),
+ Some(r#"export_name = "${0:exported_symbol_name}""#),
+ ),
+ attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(),
+ attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")),
+ attr("global_allocator", None, None),
+ attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)),
+ attr("inline", Some("inline"), Some("inline")),
+ attr("link", None, None),
+ attr(r#"link_name = "…""#, Some("link_name"), Some(r#"link_name = "${0:symbol_name}""#)),
+ attr(
+ r#"link_section = "…""#,
+ Some("link_section"),
+ Some(r#"link_section = "${0:section_name}""#),
+ ),
+ attr("macro_export", None, None),
+ attr("macro_use", None, None),
+ attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)),
+ attr("no_implicit_prelude", None, None).prefer_inner(),
+ attr("no_link", None, None).prefer_inner(),
+ attr("no_main", None, None).prefer_inner(),
+ attr("no_mangle", None, None),
+ attr("no_std", None, None).prefer_inner(),
+ attr("non_exhaustive", None, None),
+ attr("panic_handler", None, None),
+ attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)),
+ attr("proc_macro", None, None),
+ attr("proc_macro_attribute", None, None),
+ attr("proc_macro_derive(…)", Some("proc_macro_derive"), Some("proc_macro_derive(${0:Trait})")),
+ attr(
+ r#"recursion_limit = "…""#,
+ Some("recursion_limit"),
+ Some(r#"recursion_limit = "${0:128}""#),
+ )
+ .prefer_inner(),
+ attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
+ attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
+ attr(
+ r#"target_feature(enable = "…")"#,
+ Some("target_feature"),
+ Some(r#"target_feature(enable = "${0:feature}")"#),
+ ),
+ attr("test", None, None),
+ attr("track_caller", None, None),
+ attr("type_length_limit = …", Some("type_length_limit"), Some("type_length_limit = ${0:128}"))
+ .prefer_inner(),
+ attr("used", None, None),
+ attr("warn(…)", Some("warn"), Some("warn(${0:lint})")),
+ attr(
+ r#"windows_subsystem = "…""#,
+ Some("windows_subsystem"),
+ Some(r#"windows_subsystem = "${0:subsystem}""#),
+ )
+ .prefer_inner(),
+];
+
+fn parse_comma_sep_expr(input: ast::TokenTree) -> Option<Vec<ast::Expr>> {
+ let r_paren = input.r_paren_token()?;
+ let tokens = input
+ .syntax()
+ .children_with_tokens()
+ .skip(1)
+ .take_while(|it| it.as_token() != Some(&r_paren));
+ let input_expressions = tokens.group_by(|tok| tok.kind() == T![,]);
+ Some(
+ input_expressions
+ .into_iter()
+ .filter_map(|(is_sep, group)| (!is_sep).then(|| group))
+ .filter_map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
+ .collect::<Vec<ast::Expr>>(),
+ )
+}
+
+#[test]
+fn attributes_are_sorted() {
+ let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key());
+ let mut prev = attrs.next().unwrap();
+
+ attrs.for_each(|next| {
+ assert!(
+ prev < next,
+ r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#,
+ prev,
+ next
+ );
+ prev = next;
+ });
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs
new file mode 100644
index 000000000..311060143
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs
@@ -0,0 +1,93 @@
+//! Completion for cfg
+
+use std::iter;
+
+use ide_db::SymbolKind;
+use itertools::Itertools;
+use syntax::SyntaxKind;
+
+use crate::{completions::Completions, context::CompletionContext, CompletionItem};
+
+pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext<'_>) {
+ let add_completion = |item: &str| {
+ let mut completion = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), item);
+ completion.insert_text(format!(r#""{}""#, item));
+ acc.add(completion.build());
+ };
+
+ let previous = iter::successors(ctx.original_token.prev_token(), |t| {
+ (matches!(t.kind(), SyntaxKind::EQ) || t.kind().is_trivia())
+ .then(|| t.prev_token())
+ .flatten()
+ })
+ .find(|t| matches!(t.kind(), SyntaxKind::IDENT));
+
+ match previous.as_ref().map(|p| p.text()) {
+ Some("target_arch") => KNOWN_ARCH.iter().copied().for_each(add_completion),
+ Some("target_env") => KNOWN_ENV.iter().copied().for_each(add_completion),
+ Some("target_os") => KNOWN_OS.iter().copied().for_each(add_completion),
+ Some("target_vendor") => KNOWN_VENDOR.iter().copied().for_each(add_completion),
+ Some("target_endian") => ["little", "big"].into_iter().for_each(add_completion),
+ Some(name) => ctx.krate.potential_cfg(ctx.db).get_cfg_values(name).cloned().for_each(|s| {
+ let insert_text = format!(r#""{}""#, s);
+ let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
+ item.insert_text(insert_text);
+
+ acc.add(item.build());
+ }),
+ None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().unique().for_each(|s| {
+ let item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
+ acc.add(item.build());
+ }),
+ };
+}
+
+const KNOWN_ARCH: [&str; 19] = [
+ "aarch64",
+ "arm",
+ "avr",
+ "hexagon",
+ "mips",
+ "mips64",
+ "msp430",
+ "nvptx64",
+ "powerpc",
+ "powerpc64",
+ "riscv32",
+ "riscv64",
+ "s390x",
+ "sparc",
+ "sparc64",
+ "wasm32",
+ "wasm64",
+ "x86",
+ "x86_64",
+];
+
+const KNOWN_ENV: [&str; 7] = ["eabihf", "gnu", "gnueabihf", "msvc", "relibc", "sgx", "uclibc"];
+
+const KNOWN_OS: [&str; 20] = [
+ "cuda",
+ "dragonfly",
+ "emscripten",
+ "freebsd",
+ "fuchsia",
+ "haiku",
+ "hermit",
+ "illumos",
+ "l4re",
+ "linux",
+ "netbsd",
+ "none",
+ "openbsd",
+ "psp",
+ "redox",
+ "solaris",
+ "uefi",
+ "unknown",
+ "vxworks",
+ "windows",
+];
+
+const KNOWN_VENDOR: [&str; 8] =
+ ["apple", "fortanix", "nvidia", "pc", "sony", "unknown", "wrs", "uwp"];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs
new file mode 100644
index 000000000..793c22630
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs
@@ -0,0 +1,116 @@
+//! Completion for derives
+use hir::{HasAttrs, ScopeDef};
+use ide_db::SymbolKind;
+use itertools::Itertools;
+use syntax::SmolStr;
+
+use crate::{
+ context::{CompletionContext, ExistingDerives, PathCompletionCtx, Qualified},
+ item::CompletionItem,
+ Completions,
+};
+
+pub(crate) fn complete_derive_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ existing_derives: &ExistingDerives,
+) {
+ let core = ctx.famous_defs().core();
+
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
+ match def {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
+ if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
+ {
+ acc.add_macro(ctx, path_ctx, mac, name)
+ }
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ }
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ // only show modules in a fresh UseTree
+ Qualified::No => {
+ ctx.process_all_names(&mut |name, def| {
+ let mac = match def {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac))
+ if !existing_derives.contains(&mac) && mac.is_derive(ctx.db) =>
+ {
+ mac
+ }
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ return acc.add_module(ctx, path_ctx, m, name);
+ }
+ _ => return,
+ };
+
+ match (core, mac.module(ctx.db).krate()) {
+ // show derive dependencies for `core`/`std` derives
+ (Some(core), mac_krate) if core == mac_krate => {}
+ _ => return acc.add_macro(ctx, path_ctx, mac, name),
+ };
+
+ let name_ = name.to_smol_str();
+ let find = DEFAULT_DERIVE_DEPENDENCIES
+ .iter()
+ .find(|derive_completion| derive_completion.label == name_);
+
+ match find {
+ Some(derive_completion) => {
+ let mut components = vec![derive_completion.label];
+ components.extend(derive_completion.dependencies.iter().filter(
+ |&&dependency| {
+ !existing_derives
+ .iter()
+ .map(|it| it.name(ctx.db))
+ .any(|it| it.to_smol_str() == dependency)
+ },
+ ));
+ let lookup = components.join(", ");
+ let label = Itertools::intersperse(components.into_iter().rev(), ", ");
+
+ let mut item = CompletionItem::new(
+ SymbolKind::Derive,
+ ctx.source_range(),
+ SmolStr::from_iter(label),
+ );
+ if let Some(docs) = mac.docs(ctx.db) {
+ item.documentation(docs);
+ }
+ item.lookup_by(lookup);
+ item.add_to(acc);
+ }
+ None => acc.add_macro(ctx, path_ctx, mac, name),
+ }
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ }
+}
+
+struct DeriveDependencies {
+ label: &'static str,
+ dependencies: &'static [&'static str],
+}
+
+/// Standard Rust derives that have dependencies
+/// (the dependencies are needed so that the main derive don't break the compilation when added)
+const DEFAULT_DERIVE_DEPENDENCIES: &[DeriveDependencies] = &[
+ DeriveDependencies { label: "Copy", dependencies: &["Clone"] },
+ DeriveDependencies { label: "Eq", dependencies: &["PartialEq"] },
+ DeriveDependencies { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] },
+ DeriveDependencies { label: "PartialOrd", dependencies: &["PartialEq"] },
+];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs
new file mode 100644
index 000000000..967f6ddd9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs
@@ -0,0 +1,61 @@
+//! Completion for lints
+use ide_db::{generated::lints::Lint, SymbolKind};
+use syntax::ast;
+
+use crate::{context::CompletionContext, item::CompletionItem, Completions};
+
+pub(super) fn complete_lint(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ is_qualified: bool,
+ existing_lints: &[ast::Path],
+ lints_completions: &[Lint],
+) {
+ for &Lint { label, description } in lints_completions {
+ let (qual, name) = {
+ // FIXME: change `Lint`'s label to not store a path in it but split the prefix off instead?
+ let mut parts = label.split("::");
+ let ns_or_label = match parts.next() {
+ Some(it) => it,
+ None => continue,
+ };
+ let label = parts.next();
+ match label {
+ Some(label) => (Some(ns_or_label), label),
+ None => (None, ns_or_label),
+ }
+ };
+ if qual.is_none() && is_qualified {
+ // qualified completion requested, but this lint is unqualified
+ continue;
+ }
+ let lint_already_annotated = existing_lints
+ .iter()
+ .filter_map(|path| {
+ let q = path.qualifier();
+ if q.as_ref().and_then(|it| it.qualifier()).is_some() {
+ return None;
+ }
+ Some((q.and_then(|it| it.as_single_name_ref()), path.segment()?.name_ref()?))
+ })
+ .any(|(q, name_ref)| {
+ let qualifier_matches = match (q, qual) {
+ (None, None) => true,
+ (None, Some(_)) => false,
+ (Some(_), None) => false,
+ (Some(q), Some(ns)) => q.text() == ns,
+ };
+ qualifier_matches && name_ref.text() == name
+ });
+ if lint_already_annotated {
+ continue;
+ }
+ let label = match qual {
+ Some(qual) if !is_qualified => format!("{}::{}", qual, name),
+ _ => name.to_owned(),
+ };
+ let mut item = CompletionItem::new(SymbolKind::Attribute, ctx.source_range(), label);
+ item.documentation(hir::Documentation::new(description.to_owned()));
+ item.add_to(acc)
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs
new file mode 100644
index 000000000..a29417133
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs
@@ -0,0 +1,74 @@
+//! Completion for representations.
+
+use ide_db::SymbolKind;
+use syntax::ast;
+
+use crate::{context::CompletionContext, item::CompletionItem, Completions};
+
+pub(super) fn complete_repr(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ input: ast::TokenTree,
+) {
+ if let Some(existing_reprs) = super::parse_comma_sep_expr(input) {
+ for &ReprCompletion { label, snippet, lookup, collides } in REPR_COMPLETIONS {
+ let repr_already_annotated = existing_reprs
+ .iter()
+ .filter_map(|expr| match expr {
+ ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
+ ast::Expr::CallExpr(call) => match call.expr()? {
+ ast::Expr::PathExpr(path) => path.path()?.as_single_name_ref(),
+ _ => None,
+ },
+ _ => None,
+ })
+ .any(|it| {
+ let text = it.text();
+ lookup.unwrap_or(label) == text || collides.contains(&text.as_str())
+ });
+ if repr_already_annotated {
+ continue;
+ }
+
+ let mut item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), label);
+ if let Some(lookup) = lookup {
+ item.lookup_by(lookup);
+ }
+ if let Some((snippet, cap)) = snippet.zip(ctx.config.snippet_cap) {
+ item.insert_snippet(cap, snippet);
+ }
+ item.add_to(acc);
+ }
+ }
+}
+
+struct ReprCompletion {
+ label: &'static str,
+ snippet: Option<&'static str>,
+ lookup: Option<&'static str>,
+ collides: &'static [&'static str],
+}
+
+const fn attr(label: &'static str, collides: &'static [&'static str]) -> ReprCompletion {
+ ReprCompletion { label, snippet: None, lookup: None, collides }
+}
+
+#[rustfmt::skip]
+const REPR_COMPLETIONS: &[ReprCompletion] = &[
+ ReprCompletion { label: "align($0)", snippet: Some("align($0)"), lookup: Some("align"), collides: &["transparent", "packed"] },
+ attr("packed", &["transparent", "align"]),
+ attr("transparent", &["C", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("C", &["transparent"]),
+ attr("u8", &["transparent", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u16", &["transparent", "u8", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u32", &["transparent", "u8", "u16", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u64", &["transparent", "u8", "u16", "u32", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("u128", &["transparent", "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("usize", &["transparent", "u8", "u16", "u32", "u64", "u128", "i8", "i16", "i32", "i64", "i128", "isize"]),
+ attr("i8", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i16", "i32", "i64", "i128", "isize"]),
+ attr("i16", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i32", "i64", "i128", "isize"]),
+ attr("i32", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i64", "i128", "isize"]),
+ attr("i64", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i128", "isize"]),
+ attr("i28", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "isize"]),
+ attr("isize", &["transparent", "u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128"]),
+];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs
new file mode 100644
index 000000000..cf40ca489
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs
@@ -0,0 +1,947 @@
+//! Completes references after dot (fields and method calls).
+
+use ide_db::FxHashSet;
+
+use crate::{
+ context::{CompletionContext, DotAccess, DotAccessKind, ExprCtx, PathCompletionCtx, Qualified},
+ CompletionItem, CompletionItemKind, Completions,
+};
+
+/// Complete dot accesses, i.e. fields or methods.
+pub(crate) fn complete_dot(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+) {
+ let receiver_ty = match dot_access {
+ DotAccess { receiver_ty: Some(receiver_ty), .. } => &receiver_ty.original,
+ _ => return,
+ };
+
+ // Suggest .await syntax for types that implement Future trait
+ if receiver_ty.impls_future(ctx.db) {
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), "await");
+ item.detail("expr.await");
+ item.add_to(acc);
+ }
+
+ if let DotAccessKind::Method { .. } = dot_access.kind {
+ cov_mark::hit!(test_no_struct_field_completion_for_method_call);
+ } else {
+ complete_fields(
+ acc,
+ ctx,
+ &receiver_ty,
+ |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
+ |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
+ );
+ }
+ complete_methods(ctx, &receiver_ty, |func| acc.add_method(ctx, dot_access, func, None, None));
+}
+
+pub(crate) fn complete_undotted_self(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ if !ctx.config.enable_self_on_the_fly {
+ return;
+ }
+ if !path_ctx.is_trivial_path() {
+ return;
+ }
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ let self_param = match expr_ctx {
+ ExprCtx { self_param: Some(self_param), .. } => self_param,
+ _ => return,
+ };
+
+ let ty = self_param.ty(ctx.db);
+ complete_fields(
+ acc,
+ ctx,
+ &ty,
+ |acc, field, ty| {
+ acc.add_field(
+ ctx,
+ &DotAccess {
+ receiver: None,
+ receiver_ty: None,
+ kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
+ },
+ Some(hir::known::SELF_PARAM),
+ field,
+ &ty,
+ )
+ },
+ |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty),
+ );
+ complete_methods(ctx, &ty, |func| {
+ acc.add_method(
+ ctx,
+ &DotAccess {
+ receiver: None,
+ receiver_ty: None,
+ kind: DotAccessKind::Method { has_parens: false },
+ },
+ func,
+ Some(hir::known::SELF_PARAM),
+ None,
+ )
+ });
+}
+
+fn complete_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ receiver: &hir::Type,
+ mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type),
+ mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type),
+) {
+ for receiver in receiver.autoderef(ctx.db) {
+ for (field, ty) in receiver.fields(ctx.db) {
+ named_field(acc, field, ty);
+ }
+ for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() {
+ // Tuple fields are always public (tuple struct fields are handled above).
+ tuple_index(acc, i, ty);
+ }
+ }
+}
+
+fn complete_methods(
+ ctx: &CompletionContext<'_>,
+ receiver: &hir::Type,
+ mut f: impl FnMut(hir::Function),
+) {
+ let mut seen_methods = FxHashSet::default();
+ receiver.iterate_method_candidates(
+ ctx.db,
+ &ctx.scope,
+ &ctx.traits_in_scope(),
+ Some(ctx.module),
+ None,
+ |func| {
+ if func.self_param(ctx.db).is_some() && seen_methods.insert(func.name(ctx.db)) {
+ f(func);
+ }
+ None::<()>
+ },
+ );
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{
+ check_edit, completion_list_no_kw, completion_list_no_kw_with_private_editable,
+ };
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ fn check_with_private_editable(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list_no_kw_with_private_editable(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn test_struct_field_and_method_completion() {
+ check(
+ r#"
+struct S { foo: u32 }
+impl S {
+ fn bar(&self) {}
+}
+fn foo(s: S) { s.$0 }
+"#,
+ expect![[r#"
+ fd foo u32
+ me bar() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_struct_field_completion_self() {
+ check(
+ r#"
+struct S { the_field: (u32,) }
+impl S {
+ fn foo(self) { self.$0 }
+}
+"#,
+ expect![[r#"
+ fd the_field (u32,)
+ me foo() fn(self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_struct_field_completion_autoderef() {
+ check(
+ r#"
+struct A { the_field: (u32, i32) }
+impl A {
+ fn foo(&self) { self.$0 }
+}
+"#,
+ expect![[r#"
+ fd the_field (u32, i32)
+ me foo() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_no_struct_field_completion_for_method_call() {
+ cov_mark::check!(test_no_struct_field_completion_for_method_call);
+ check(
+ r#"
+struct A { the_field: u32 }
+fn foo(a: A) { a.$0() }
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn test_visibility_filtering() {
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd pub_field u32
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd pub_field u32
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A(
+ i32,
+ pub f64,
+ );
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd 1 f64
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me pub_method() fn(&self)
+ "#]],
+ );
+ check(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me pub_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_visibility_filtering_with_private_editable_enabled() {
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd crate_field u32
+ fd private_field u32
+ fd pub_field u32
+ fd super_field u32
+ "#]],
+ );
+
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A {
+ private_field: u32,
+ pub pub_field: u32,
+ pub(crate) crate_field: u32,
+ pub(super) super_field: u32,
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd pub_field u32
+ "#]],
+ );
+
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub mod m {
+ pub struct A(
+ i32,
+ pub f64,
+ );
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::m::A) { a.$0 }
+"#,
+ expect![[r#"
+ fd 1 f64
+ "#]],
+ );
+
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:local
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me crate_method() fn(&self)
+ me private_method() fn(&self)
+ me pub_method() fn(&self)
+ "#]],
+ );
+ check_with_private_editable(
+ r#"
+//- /lib.rs crate:lib new_source_root:library
+pub struct A {}
+mod m {
+ impl super::A {
+ fn private_method(&self) {}
+ pub(crate) fn crate_method(&self) {}
+ pub fn pub_method(&self) {}
+ }
+}
+//- /main.rs crate:main deps:lib new_source_root:local
+fn foo(a: lib::A) { a.$0 }
+"#,
+ expect![[r#"
+ me pub_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_local_impls() {
+ check(
+ r#"
+//- /lib.rs crate:lib
+pub struct A {}
+mod m {
+ impl super::A {
+ pub fn pub_module_method(&self) {}
+ }
+ fn f() {
+ impl super::A {
+ pub fn pub_foreign_local_method(&self) {}
+ }
+ }
+}
+//- /main.rs crate:main deps:lib
+fn foo(a: lib::A) {
+ impl lib::A {
+ fn local_method(&self) {}
+ }
+ a.$0
+}
+"#,
+ expect![[r#"
+ me local_method() fn(&self)
+ me pub_module_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_doc_hidden_filtering() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:dep
+fn foo(a: dep::A) { a.$0 }
+//- /dep.rs crate:dep
+pub struct A {
+ #[doc(hidden)]
+ pub hidden_field: u32,
+ pub pub_field: u32,
+}
+
+impl A {
+ pub fn pub_method(&self) {}
+
+ #[doc(hidden)]
+ pub fn hidden_method(&self) {}
+}
+ "#,
+ expect![[r#"
+ fd pub_field u32
+ me pub_method() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_union_field_completion() {
+ check(
+ r#"
+union U { field: u8, other: u16 }
+fn foo(u: U) { u.$0 }
+"#,
+ expect![[r#"
+ fd field u8
+ fd other u16
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_method_completion_only_fitting_impls() {
+ check(
+ r#"
+struct A<T> {}
+impl A<u32> {
+ fn the_method(&self) {}
+}
+impl A<i32> {
+ fn the_other_method(&self) {}
+}
+fn foo(a: A<u32>) { a.$0 }
+"#,
+ expect![[r#"
+ me the_method() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn test_trait_method_completion() {
+ check(
+ r#"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl Trait for A {}
+fn foo(a: A) { a.$0 }
+"#,
+ expect![[r#"
+ me the_method() (as Trait) fn(&self)
+ "#]],
+ );
+ check_edit(
+ "the_method",
+ r#"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl Trait for A {}
+fn foo(a: A) { a.$0 }
+"#,
+ r#"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl Trait for A {}
+fn foo(a: A) { a.the_method()$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn test_trait_method_completion_deduplicated() {
+ check(
+ r"
+struct A {}
+trait Trait { fn the_method(&self); }
+impl<T> Trait for T {}
+fn foo(a: &A) { a.$0 }
+",
+ expect![[r#"
+ me the_method() (as Trait) fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_trait_method_from_other_module() {
+ check(
+ r"
+struct A {}
+mod m {
+ pub trait Trait { fn the_method(&self); }
+}
+use m::Trait;
+impl Trait for A {}
+fn foo(a: A) { a.$0 }
+",
+ expect![[r#"
+ me the_method() (as Trait) fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_no_non_self_method() {
+ check(
+ r#"
+struct A {}
+impl A {
+ fn the_method() {}
+}
+fn foo(a: A) {
+ a.$0
+}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn test_tuple_field_completion() {
+ check(
+ r#"
+fn foo() {
+ let b = (0, 3.14);
+ b.$0
+}
+"#,
+ expect![[r#"
+ fd 0 i32
+ fd 1 f64
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_tuple_struct_field_completion() {
+ check(
+ r#"
+struct S(i32, f64);
+fn foo() {
+ let b = S(0, 3.14);
+ b.$0
+}
+"#,
+ expect![[r#"
+ fd 0 i32
+ fd 1 f64
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_tuple_field_inference() {
+ check(
+ r#"
+pub struct S;
+impl S { pub fn blah(&self) {} }
+
+struct T(S);
+
+impl T {
+ fn foo(&self) {
+ // FIXME: This doesn't work without the trailing `a` as `0.` is a float
+ self.0.a$0
+ }
+}
+"#,
+ expect![[r#"
+ me blah() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_completion_works_in_consts() {
+ check(
+ r#"
+struct A { the_field: u32 }
+const X: u32 = {
+ A { the_field: 92 }.$0
+};
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_in_simple_macro_1() {
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct A { the_field: u32 }
+fn foo(a: A) {
+ m!(a.x$0)
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_in_simple_macro_2() {
+ // this doesn't work yet because the macro doesn't expand without the token -- maybe it can be fixed with better recovery
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct A { the_field: u32 }
+fn foo(a: A) {
+ m!(a.$0)
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn works_in_simple_macro_recursive_1() {
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct A { the_field: u32 }
+fn foo(a: A) {
+ m!(m!(m!(a.x$0)))
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_expansion_resilient() {
+ check(
+ r#"
+macro_rules! d {
+ () => {};
+ ($val:expr) => {
+ match $val { tmp => { tmp } }
+ };
+ // Trailing comma with single argument is ignored
+ ($val:expr,) => { $crate::d!($val) };
+ ($($val:expr),+ $(,)?) => {
+ ($($crate::d!($val)),+,)
+ };
+}
+struct A { the_field: u32 }
+fn foo(a: A) {
+ d!(a.$0)
+}
+"#,
+ expect![[r#"
+ fd the_field u32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_method_completion_issue_3547() {
+ check(
+ r#"
+struct HashSet<T> {}
+impl<T> HashSet<T> {
+ pub fn the_method(&self) {}
+}
+fn foo() {
+ let s: HashSet<_>;
+ s.$0
+}
+"#,
+ expect![[r#"
+ me the_method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_method_call_when_receiver_is_a_macro_call() {
+ check(
+ r#"
+struct S;
+impl S { fn foo(&self) {} }
+macro_rules! make_s { () => { S }; }
+fn main() { make_s!().f$0; }
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ "#]],
+ )
+ }
+
+ #[test]
+ fn completes_after_macro_call_in_submodule() {
+ check(
+ r#"
+macro_rules! empty {
+ () => {};
+}
+
+mod foo {
+ #[derive(Debug, Default)]
+ struct Template2 {}
+
+ impl Template2 {
+ fn private(&self) {}
+ }
+ fn baz() {
+ let goo: Template2 = Template2 {};
+ empty!();
+ goo.$0
+ }
+}
+ "#,
+ expect![[r#"
+ me private() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn issue_8931() {
+ check(
+ r#"
+//- minicore: fn
+struct S;
+
+struct Foo;
+impl Foo {
+ fn foo(&self) -> &[u8] { loop {} }
+}
+
+impl S {
+ fn indented(&mut self, f: impl FnOnce(&mut Self)) {
+ }
+
+ fn f(&mut self, v: Foo) {
+ self.indented(|this| v.$0)
+ }
+}
+ "#,
+ expect![[r#"
+ me foo() fn(&self) -> &[u8]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_bare_fields_and_methods_in_methods() {
+ check(
+ r#"
+struct Foo { field: i32 }
+
+impl Foo { fn foo(&self) { $0 } }"#,
+ expect![[r#"
+ fd self.field i32
+ lc self &Foo
+ sp Self
+ st Foo
+ bt u32
+ me self.foo() fn(&self)
+ "#]],
+ );
+ check(
+ r#"
+struct Foo(i32);
+
+impl Foo { fn foo(&mut self) { $0 } }"#,
+ expect![[r#"
+ fd self.0 i32
+ lc self &mut Foo
+ sp Self
+ st Foo
+ bt u32
+ me self.foo() fn(&mut self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn macro_completion_after_dot() {
+ check(
+ r#"
+macro_rules! m {
+ ($e:expr) => { $e };
+}
+
+struct Completable;
+
+impl Completable {
+ fn method(&self) {}
+}
+
+fn f() {
+ let c = Completable;
+ m!(c.$0);
+}
+ "#,
+ expect![[r#"
+ me method() fn(&self)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn completes_method_call_when_receiver_type_has_errors_issue_10297() {
+ check(
+ r#"
+//- minicore: iterator, sized
+struct Vec<T>;
+impl<T> IntoIterator for Vec<T> {
+ type Item = ();
+ type IntoIter = ();
+ fn into_iter(self);
+}
+fn main() {
+ let x: Vec<_>;
+ x.$0;
+}
+"#,
+ expect![[r#"
+ me into_iter() (as IntoIterator) fn(self) -> <Self as IntoIterator>::IntoIter
+ "#]],
+ )
+ }
+
+ #[test]
+ fn postfix_drop_completion() {
+ cov_mark::check!(postfix_drop_completion);
+ check_edit(
+ "drop",
+ r#"
+//- minicore: drop
+struct Vec<T>(T);
+impl<T> Drop for Vec<T> {
+ fn drop(&mut self) {}
+}
+fn main() {
+ let x = Vec(0u32)
+ x.$0;
+}
+"#,
+ r"
+struct Vec<T>(T);
+impl<T> Drop for Vec<T> {
+ fn drop(&mut self) {}
+}
+fn main() {
+ let x = Vec(0u32)
+ drop($0x);
+}
+",
+ )
+ }
+
+ #[test]
+ fn issue_12484() {
+ check(
+ r#"
+//- minicore: sized
+trait SizeUser {
+ type Size;
+}
+trait Closure: SizeUser {}
+trait Encrypt: SizeUser {
+ fn encrypt(self, _: impl Closure<Size = Self::Size>);
+}
+fn test(thing: impl Encrypt) {
+ thing.$0;
+}
+ "#,
+ expect![[r#"
+ me encrypt(…) (as Encrypt) fn(self, impl Closure<Size = <Self as SizeUser>::Size>)
+ "#]],
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs
new file mode 100644
index 000000000..5d0ddaaf2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs
@@ -0,0 +1,280 @@
+//! Completion of names from the current scope in expression position.
+
+use hir::ScopeDef;
+
+use crate::{
+ context::{ExprCtx, PathCompletionCtx, Qualified},
+ CompletionContext, Completions,
+};
+
+pub(crate) fn complete_expr_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ let _p = profile::span("complete_expr_path");
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+
+ let &ExprCtx {
+ in_block_expr,
+ in_loop_body,
+ after_if_expr,
+ in_condition,
+ incomplete_let,
+ ref ref_expr_parent,
+ ref is_func_update,
+ ref innermost_ret_ty,
+ ref impl_,
+ in_match_guard,
+ ..
+ } = expr_ctx;
+
+ let wants_mut_token =
+ ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false);
+
+ let scope_def_applicable = |def| match def {
+ ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) | ScopeDef::Label(_) => false,
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
+ _ => true,
+ };
+
+ let add_assoc_item = |acc: &mut Completions, item| match item {
+ hir::AssocItem::Function(func) => acc.add_function(ctx, path_ctx, func, None),
+ hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
+ hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
+ };
+
+ match qualified {
+ Qualified::TypeAnchor { ty: None, trait_: None } => ctx
+ .traits_in_scope()
+ .iter()
+ .flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
+ .for_each(|item| add_assoc_item(acc, item)),
+ Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
+ trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
+ }
+ Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
+ if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+ cov_mark::hit!(completes_variant_through_alias);
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // 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::<()>
+ });
+ }
+ Qualified::With { resolution: None, .. } => {}
+ Qualified::With { resolution: Some(resolution), .. } => {
+ // 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 {
+ if scope_def_applicable(def) {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ }
+ }
+ hir::PathResolution::Def(
+ def @ (hir::ModuleDef::Adt(_)
+ | hir::ModuleDef::TypeAlias(_)
+ | hir::ModuleDef::BuiltinType(_)),
+ ) => {
+ let ty = match def {
+ hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
+ hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
+ hir::ModuleDef::BuiltinType(builtin) => {
+ cov_mark::hit!(completes_primitive_assoc_const);
+ builtin.ty(ctx.db)
+ }
+ _ => return,
+ };
+
+ if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+ cov_mark::hit!(completes_variant_through_alias);
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
+ // (where AssocType is defined on a trait, not an inherent impl)
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // 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, 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() {
+ cov_mark::hit!(completes_variant_through_self);
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+ }
+ _ => (),
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No => {
+ acc.add_nameref_keywords_with_colon(ctx);
+ if let Some(adt) =
+ ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
+ {
+ let self_ty = (|| ctx.sema.to_def(impl_.as_ref()?)?.self_ty(ctx.db).as_adt())();
+ let complete_self = self_ty == Some(adt);
+
+ match adt {
+ hir::Adt::Struct(strukt) => {
+ let path = ctx
+ .module
+ .find_use_path(ctx.db, hir::ModuleDef::from(strukt))
+ .filter(|it| it.len() > 1);
+
+ acc.add_struct_literal(ctx, path_ctx, strukt, path, None);
+
+ if complete_self {
+ acc.add_struct_literal(
+ ctx,
+ path_ctx,
+ strukt,
+ None,
+ Some(hir::known::SELF_TYPE),
+ );
+ }
+ }
+ hir::Adt::Union(un) => {
+ let path = ctx
+ .module
+ .find_use_path(ctx.db, hir::ModuleDef::from(un))
+ .filter(|it| it.len() > 1);
+
+ acc.add_union_literal(ctx, un, path, None);
+ if complete_self {
+ acc.add_union_literal(ctx, un, None, Some(hir::known::SELF_TYPE));
+ }
+ }
+ hir::Adt::Enum(e) => {
+ super::enum_variants_with_paths(
+ acc,
+ ctx,
+ e,
+ impl_,
+ |acc, ctx, variant, path| {
+ acc.add_qualified_enum_variant(ctx, path_ctx, variant, path)
+ },
+ );
+ }
+ }
+ }
+ ctx.process_all_names(&mut |name, def| match def {
+ ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
+ let assocs = t.items_with_supertraits(ctx.db);
+ match &*assocs {
+ // traits with no assoc items are unusable as expressions since
+ // there is no associated item path that can be constructed with them
+ [] => (),
+ // FIXME: Render the assoc item with the trait qualified
+ &[_item] => acc.add_path_resolution(ctx, path_ctx, name, def),
+ // FIXME: Append `::` to the thing here, since a trait on its own won't work
+ [..] => acc.add_path_resolution(ctx, path_ctx, name, def),
+ }
+ }
+ _ if scope_def_applicable(def) => acc.add_path_resolution(ctx, path_ctx, name, def),
+ _ => (),
+ });
+
+ if is_func_update.is_none() {
+ let mut add_keyword =
+ |kw, snippet| acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet);
+
+ if !in_block_expr {
+ add_keyword("unsafe", "unsafe {\n $0\n}");
+ }
+ add_keyword("match", "match $1 {\n $0\n}");
+ add_keyword("while", "while $1 {\n $0\n}");
+ add_keyword("while let", "while let $1 = $2 {\n $0\n}");
+ add_keyword("loop", "loop {\n $0\n}");
+ if in_match_guard {
+ add_keyword("if", "if $0");
+ } else {
+ add_keyword("if", "if $1 {\n $0\n}");
+ }
+ add_keyword("if let", "if let $1 = $2 {\n $0\n}");
+ add_keyword("for", "for $1 in $2 {\n $0\n}");
+ add_keyword("true", "true");
+ add_keyword("false", "false");
+
+ if in_condition || in_block_expr {
+ add_keyword("let", "let");
+ }
+
+ if after_if_expr {
+ add_keyword("else", "else {\n $0\n}");
+ add_keyword("else if", "else if $1 {\n $0\n}");
+ }
+
+ if wants_mut_token {
+ add_keyword("mut", "mut ");
+ }
+
+ if in_loop_body {
+ if in_block_expr {
+ add_keyword("continue", "continue;");
+ add_keyword("break", "break;");
+ } else {
+ add_keyword("continue", "continue");
+ add_keyword("break", "break");
+ }
+ }
+
+ if let Some(ty) = innermost_ret_ty {
+ add_keyword(
+ "return",
+ match (in_block_expr, ty.is_unit()) {
+ (true, true) => "return ;",
+ (true, false) => "return;",
+ (false, true) => "return $0",
+ (false, false) => "return",
+ },
+ );
+ }
+ }
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs
new file mode 100644
index 000000000..4e89ef696
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs
@@ -0,0 +1,108 @@
+//! Completes function abi strings.
+use syntax::{
+ ast::{self, IsString},
+ AstNode, AstToken,
+};
+
+use crate::{
+ completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind,
+};
+
+// Most of these are feature gated, we should filter/add feature gate completions once we have them.
+const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[
+ "Rust",
+ "C",
+ "C-unwind",
+ "cdecl",
+ "stdcall",
+ "stdcall-unwind",
+ "fastcall",
+ "vectorcall",
+ "thiscall",
+ "thiscall-unwind",
+ "aapcs",
+ "win64",
+ "sysv64",
+ "ptx-kernel",
+ "msp430-interrupt",
+ "x86-interrupt",
+ "amdgpu-kernel",
+ "efiapi",
+ "avr-interrupt",
+ "avr-non-blocking-interrupt",
+ "C-cmse-nonsecure-call",
+ "wasm",
+ "system",
+ "system-unwind",
+ "rust-intrinsic",
+ "rust-call",
+ "platform-intrinsic",
+ "unadjusted",
+];
+
+pub(crate) fn complete_extern_abi(
+ acc: &mut Completions,
+ _ctx: &CompletionContext<'_>,
+ expanded: &ast::String,
+) -> Option<()> {
+ if !expanded.syntax().parent().map_or(false, |it| ast::Abi::can_cast(it.kind())) {
+ return None;
+ }
+ let abi_str = expanded;
+ let source_range = abi_str.text_range_between_quotes()?;
+ for &abi in SUPPORTED_CALLING_CONVENTIONS {
+ CompletionItem::new(CompletionItemKind::Keyword, source_range, abi).add_to(acc);
+ }
+ Some(())
+}
+
+#[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 only_completes_in_string_literals() {
+ check(
+ r#"
+$0 fn foo {}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn requires_extern_prefix() {
+ check(
+ r#"
+"$0" fn foo {}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn works() {
+ check(
+ r#"
+extern "$0" fn foo {}
+"#,
+ expect![[]],
+ );
+ check_edit(
+ "Rust",
+ r#"
+extern "$0" fn foo {}
+"#,
+ r#"
+extern "Rust" fn foo {}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs
new file mode 100644
index 000000000..870df63b7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs
@@ -0,0 +1,43 @@
+//! Completion of field list position.
+
+use crate::{
+ context::{PathCompletionCtx, Qualified},
+ CompletionContext, Completions,
+};
+
+pub(crate) fn complete_field_list_tuple_variant(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+) {
+ if ctx.qualifier_ctx.vis_node.is_some() {
+ return;
+ }
+ match path_ctx {
+ PathCompletionCtx {
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ has_type_args: false,
+ ..
+ } => {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+ add_keyword("pub(crate)", "pub(crate)");
+ add_keyword("pub(super)", "pub(super)");
+ add_keyword("pub", "pub");
+ }
+ _ => (),
+ }
+}
+
+pub(crate) fn complete_field_list_record_variant(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+) {
+ if ctx.qualifier_ctx.vis_node.is_none() {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+ add_keyword("pub(crate)", "pub(crate)");
+ add_keyword("pub(super)", "pub(super)");
+ add_keyword("pub", "pub");
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs
new file mode 100644
index 000000000..f04cc15d7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs
@@ -0,0 +1,407 @@
+//! See [`import_on_the_fly`].
+use hir::{ItemInNs, ModuleDef};
+use ide_db::imports::{
+ import_assets::{ImportAssets, LocatedImport},
+ insert_use::ImportScope,
+};
+use itertools::Itertools;
+use syntax::{
+ ast::{self},
+ AstNode, SyntaxNode, T,
+};
+
+use crate::{
+ context::{
+ CompletionContext, DotAccess, PathCompletionCtx, PathKind, PatternContext, Qualified,
+ TypeLocation,
+ },
+ render::{render_resolution_with_import, render_resolution_with_import_pat, RenderContext},
+};
+
+use super::Completions;
+
+// Feature: Completion With Autoimport
+//
+// When completing names in the current scope, proposes additional imports from other modules or crates,
+// if they can be qualified in the scope, and their name contains all symbols from the completion input.
+//
+// To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
+// If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the containing is checked case-insensitively.
+//
+// ```
+// fn main() {
+// pda$0
+// }
+// # pub mod std { pub mod marker { pub struct PhantomData { } } }
+// ```
+// ->
+// ```
+// use std::marker::PhantomData;
+//
+// fn main() {
+// PhantomData
+// }
+// # pub mod std { pub mod marker { pub struct PhantomData { } } }
+// ```
+//
+// Also completes associated items, that require trait imports.
+// If any unresolved and/or partially-qualified path precedes the input, it will be taken into account.
+// Currently, only the imports with their import path ending with the whole qualifier will be proposed
+// (no fuzzy matching for qualifier).
+//
+// ```
+// mod foo {
+// pub mod bar {
+// pub struct Item;
+//
+// impl Item {
+// pub const TEST_ASSOC: usize = 3;
+// }
+// }
+// }
+//
+// fn main() {
+// bar::Item::TEST_A$0
+// }
+// ```
+// ->
+// ```
+// use foo::bar;
+//
+// mod foo {
+// pub mod bar {
+// pub struct Item;
+//
+// impl Item {
+// pub const TEST_ASSOC: usize = 3;
+// }
+// }
+// }
+//
+// fn main() {
+// bar::Item::TEST_ASSOC
+// }
+// ```
+//
+// NOTE: currently, if an assoc item comes from a trait that's not currently imported, and it also has an unresolved and/or partially-qualified path,
+// no imports will be proposed.
+//
+// .Fuzzy search details
+//
+// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
+// (i.e. in `HashMap` in the `std::collections::HashMap` path).
+// For the same reasons, avoids searching for any path imports for inputs with their length less than 2 symbols
+// (but shows all associated items for any input length).
+//
+// .Import configuration
+//
+// It is possible to configure how use-trees are merged with the `imports.granularity.group` setting.
+// Mimics the corresponding behavior of the `Auto Import` feature.
+//
+// .LSP and performance implications
+//
+// The feature is enabled only if the LSP client supports LSP protocol version 3.16+ and reports the `additionalTextEdits`
+// (case-sensitive) resolve client capability in its client capabilities.
+// This way the server is able to defer the costly computations, doing them for a selected completion item only.
+// For clients with no such support, all edits have to be calculated on the completion request, including the fuzzy search completion ones,
+// which might be slow ergo the feature is automatically disabled.
+//
+// .Feature toggle
+//
+// The feature can be forcefully turned off in the settings with the `rust-analyzer.completion.autoimport.enable` flag.
+// Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corresponding
+// capability enabled.
+pub(crate) fn import_on_the_fly_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+) -> Option<()> {
+ if !ctx.config.enable_imports_on_the_fly {
+ return None;
+ }
+ let qualified = match path_ctx {
+ PathCompletionCtx {
+ kind:
+ PathKind::Expr { .. }
+ | PathKind::Type { .. }
+ | PathKind::Attr { .. }
+ | PathKind::Derive { .. }
+ | PathKind::Item { .. }
+ | PathKind::Pat { .. },
+ qualified,
+ ..
+ } => qualified,
+ _ => return None,
+ };
+ let potential_import_name = import_name(ctx);
+ let qualifier = match qualified {
+ Qualified::With { path, .. } => Some(path.clone()),
+ _ => None,
+ };
+ let import_assets = import_assets_for_path(ctx, &potential_import_name, qualifier.clone())?;
+
+ import_on_the_fly(
+ acc,
+ ctx,
+ path_ctx,
+ import_assets,
+ qualifier.map(|it| it.syntax().clone()).or_else(|| ctx.original_token.parent())?,
+ potential_import_name,
+ )
+}
+
+pub(crate) fn import_on_the_fly_pat(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) -> Option<()> {
+ if !ctx.config.enable_imports_on_the_fly {
+ return None;
+ }
+ if let PatternContext { record_pat: Some(_), .. } = pattern_ctx {
+ return None;
+ }
+
+ let potential_import_name = import_name(ctx);
+ let import_assets = import_assets_for_path(ctx, &potential_import_name, None)?;
+
+ import_on_the_fly_pat_(
+ acc,
+ ctx,
+ pattern_ctx,
+ import_assets,
+ ctx.original_token.parent()?,
+ potential_import_name,
+ )
+}
+
+pub(crate) fn import_on_the_fly_dot(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+) -> Option<()> {
+ if !ctx.config.enable_imports_on_the_fly {
+ return None;
+ }
+ let receiver = dot_access.receiver.as_ref()?;
+ let ty = dot_access.receiver_ty.as_ref()?;
+ let potential_import_name = import_name(ctx);
+ let import_assets = ImportAssets::for_fuzzy_method_call(
+ ctx.module,
+ ty.original.clone(),
+ potential_import_name.clone(),
+ receiver.syntax().clone(),
+ )?;
+
+ import_on_the_fly_method(
+ acc,
+ ctx,
+ dot_access,
+ import_assets,
+ receiver.syntax().clone(),
+ potential_import_name,
+ )
+}
+
+fn import_on_the_fly(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { kind, .. }: &PathCompletionCtx,
+ import_assets: ImportAssets,
+ position: SyntaxNode,
+ potential_import_name: String,
+) -> Option<()> {
+ let _p = profile::span("import_on_the_fly").detail(|| potential_import_name.clone());
+
+ if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
+ return None;
+ }
+
+ let ns_filter = |import: &LocatedImport| {
+ match (kind, import.original_item) {
+ // Aren't handled in flyimport
+ (PathKind::Vis { .. } | PathKind::Use, _) => false,
+ // modules are always fair game
+ (_, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
+ // and so are macros(except for attributes)
+ (
+ PathKind::Expr { .. }
+ | PathKind::Type { .. }
+ | PathKind::Item { .. }
+ | PathKind::Pat { .. },
+ ItemInNs::Macros(mac),
+ ) => mac.is_fn_like(ctx.db),
+ (PathKind::Item { .. }, ..) => false,
+
+ (PathKind::Expr { .. }, ItemInNs::Types(_) | ItemInNs::Values(_)) => true,
+
+ (PathKind::Pat { .. }, ItemInNs::Types(_)) => true,
+ (PathKind::Pat { .. }, ItemInNs::Values(def)) => {
+ matches!(def, hir::ModuleDef::Const(_))
+ }
+
+ (PathKind::Type { location }, ItemInNs::Types(ty)) => {
+ if matches!(location, TypeLocation::TypeBound) {
+ matches!(ty, ModuleDef::Trait(_))
+ } else {
+ true
+ }
+ }
+ (PathKind::Type { .. }, ItemInNs::Values(_)) => false,
+
+ (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(ctx.db),
+ (PathKind::Attr { .. }, _) => false,
+
+ (PathKind::Derive { existing_derives }, ItemInNs::Macros(mac)) => {
+ mac.is_derive(ctx.db) && !existing_derives.contains(&mac)
+ }
+ (PathKind::Derive { .. }, _) => false,
+ }
+ };
+ let user_input_lowercased = potential_import_name.to_lowercase();
+
+ acc.add_all(
+ import_assets
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .into_iter()
+ .filter(ns_filter)
+ .filter(|import| {
+ !ctx.is_item_hidden(&import.item_to_import)
+ && !ctx.is_item_hidden(&import.original_item)
+ })
+ .sorted_by_key(|located_import| {
+ compute_fuzzy_completion_order_key(
+ &located_import.import_path,
+ &user_input_lowercased,
+ )
+ })
+ .filter_map(|import| {
+ render_resolution_with_import(RenderContext::new(ctx), path_ctx, import)
+ })
+ .map(|builder| builder.build()),
+ );
+ Some(())
+}
+
+fn import_on_the_fly_pat_(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+ import_assets: ImportAssets,
+ position: SyntaxNode,
+ potential_import_name: String,
+) -> Option<()> {
+ let _p = profile::span("import_on_the_fly_pat").detail(|| potential_import_name.clone());
+
+ if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
+ return None;
+ }
+
+ let ns_filter = |import: &LocatedImport| match import.original_item {
+ ItemInNs::Macros(mac) => mac.is_fn_like(ctx.db),
+ ItemInNs::Types(_) => true,
+ ItemInNs::Values(def) => matches!(def, hir::ModuleDef::Const(_)),
+ };
+ let user_input_lowercased = potential_import_name.to_lowercase();
+
+ acc.add_all(
+ import_assets
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .into_iter()
+ .filter(ns_filter)
+ .filter(|import| {
+ !ctx.is_item_hidden(&import.item_to_import)
+ && !ctx.is_item_hidden(&import.original_item)
+ })
+ .sorted_by_key(|located_import| {
+ compute_fuzzy_completion_order_key(
+ &located_import.import_path,
+ &user_input_lowercased,
+ )
+ })
+ .filter_map(|import| {
+ render_resolution_with_import_pat(RenderContext::new(ctx), pattern_ctx, import)
+ })
+ .map(|builder| builder.build()),
+ );
+ Some(())
+}
+
+fn import_on_the_fly_method(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+ import_assets: ImportAssets,
+ position: SyntaxNode,
+ potential_import_name: String,
+) -> Option<()> {
+ let _p = profile::span("import_on_the_fly_method").detail(|| potential_import_name.clone());
+
+ if ImportScope::find_insert_use_container(&position, &ctx.sema).is_none() {
+ return None;
+ }
+
+ let user_input_lowercased = potential_import_name.to_lowercase();
+
+ import_assets
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .into_iter()
+ .filter(|import| {
+ !ctx.is_item_hidden(&import.item_to_import)
+ && !ctx.is_item_hidden(&import.original_item)
+ })
+ .sorted_by_key(|located_import| {
+ compute_fuzzy_completion_order_key(&located_import.import_path, &user_input_lowercased)
+ })
+ .for_each(|import| match import.original_item {
+ ItemInNs::Values(hir::ModuleDef::Function(f)) => {
+ acc.add_method_with_import(ctx, dot_access, f, import);
+ }
+ _ => (),
+ });
+ Some(())
+}
+
+fn import_name(ctx: &CompletionContext<'_>) -> String {
+ let token_kind = ctx.token.kind();
+ if matches!(token_kind, T![.] | T![::]) {
+ String::new()
+ } else {
+ ctx.token.to_string()
+ }
+}
+
+fn import_assets_for_path(
+ ctx: &CompletionContext<'_>,
+ potential_import_name: &str,
+ qualifier: Option<ast::Path>,
+) -> Option<ImportAssets> {
+ let fuzzy_name_length = potential_import_name.len();
+ let mut assets_for_path = ImportAssets::for_fuzzy_path(
+ ctx.module,
+ qualifier,
+ potential_import_name.to_owned(),
+ &ctx.sema,
+ ctx.token.parent()?,
+ )?;
+ if fuzzy_name_length < 3 {
+ cov_mark::hit!(flyimport_exact_on_short_path);
+ assets_for_path.path_fuzzy_name_to_exact(false);
+ }
+ Some(assets_for_path)
+}
+
+fn compute_fuzzy_completion_order_key(
+ proposed_mod_path: &hir::ModPath,
+ user_input_lowercased: &str,
+) -> usize {
+ cov_mark::hit!(certain_fuzzy_order_test);
+ let import_name = match proposed_mod_path.segments().last() {
+ Some(name) => name.to_smol_str().to_lowercase(),
+ None => return usize::MAX,
+ };
+ match import_name.match_indices(user_input_lowercased).next() {
+ Some((first_matching_index, _)) => first_matching_index,
+ None => usize::MAX,
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs
new file mode 100644
index 000000000..f0ecc595a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs
@@ -0,0 +1,196 @@
+//! See [`complete_fn_param`].
+
+use hir::HirDisplay;
+use ide_db::FxHashMap;
+use syntax::{
+ algo,
+ ast::{self, HasModuleItem},
+ match_ast, AstNode, Direction, SyntaxKind, TextRange, TextSize,
+};
+
+use crate::{
+ context::{ParamContext, ParamKind, PatternContext},
+ CompletionContext, CompletionItem, CompletionItemKind, Completions,
+};
+
+// FIXME: Make this a submodule of [`pattern`]
+/// Complete repeated parameters, both name and type. For example, if all
+/// functions in a file have a `spam: &mut Spam` parameter, a completion with
+/// `spam: &mut Spam` insert text/label will be suggested.
+///
+/// Also complete parameters for closure or local functions from the surrounding defined locals.
+pub(crate) fn complete_fn_param(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) -> Option<()> {
+ let (ParamContext { param_list, kind, .. }, impl_) = match pattern_ctx {
+ PatternContext { param_ctx: Some(kind), impl_, .. } => (kind, impl_),
+ _ => return None,
+ };
+
+ let comma_wrapper = comma_wrapper(ctx);
+ let mut add_new_item_to_acc = |label: &str| {
+ let mk_item = |label: &str, range: TextRange| {
+ CompletionItem::new(CompletionItemKind::Binding, range, label)
+ };
+ let item = match &comma_wrapper {
+ Some((fmt, range)) => mk_item(&fmt(label), *range),
+ None => mk_item(label, ctx.source_range()),
+ };
+ // Completion lookup is omitted intentionally here.
+ // See the full discussion: https://github.com/rust-lang/rust-analyzer/issues/12073
+ item.add_to(acc)
+ };
+
+ match kind {
+ ParamKind::Function(function) => {
+ fill_fn_params(ctx, function, param_list, impl_, add_new_item_to_acc);
+ }
+ ParamKind::Closure(closure) => {
+ let stmt_list = closure.syntax().ancestors().find_map(ast::StmtList::cast)?;
+ params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
+ add_new_item_to_acc(&format!("{name}: {ty}"));
+ });
+ }
+ }
+
+ Some(())
+}
+
+fn fill_fn_params(
+ ctx: &CompletionContext<'_>,
+ function: &ast::Fn,
+ param_list: &ast::ParamList,
+ impl_: &Option<ast::Impl>,
+ mut add_new_item_to_acc: impl FnMut(&str),
+) {
+ let mut file_params = FxHashMap::default();
+
+ let mut extract_params = |f: ast::Fn| {
+ f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
+ if let Some(pat) = param.pat() {
+ // FIXME: We should be able to turn these into SmolStr without having to allocate a String
+ let whole_param = param.syntax().text().to_string();
+ let binding = pat.syntax().text().to_string();
+ file_params.entry(whole_param).or_insert(binding);
+ }
+ });
+ };
+
+ for node in ctx.token.parent_ancestors() {
+ match_ast! {
+ match node {
+ ast::SourceFile(it) => it.items().filter_map(|item| match item {
+ ast::Item::Fn(it) => Some(it),
+ _ => None,
+ }).for_each(&mut extract_params),
+ ast::ItemList(it) => it.items().filter_map(|item| match item {
+ ast::Item::Fn(it) => Some(it),
+ _ => None,
+ }).for_each(&mut extract_params),
+ ast::AssocItemList(it) => it.assoc_items().filter_map(|item| match item {
+ ast::AssocItem::Fn(it) => Some(it),
+ _ => None,
+ }).for_each(&mut extract_params),
+ _ => continue,
+ }
+ };
+ }
+
+ if let Some(stmt_list) = function.syntax().parent().and_then(ast::StmtList::cast) {
+ params_from_stmt_list_scope(ctx, stmt_list, |name, ty| {
+ file_params.entry(format!("{name}: {ty}")).or_insert(name.to_string());
+ });
+ }
+ remove_duplicated(&mut file_params, param_list.params());
+ let self_completion_items = ["self", "&self", "mut self", "&mut self"];
+ if should_add_self_completions(ctx.token.text_range().start(), param_list, impl_) {
+ self_completion_items.into_iter().for_each(|self_item| add_new_item_to_acc(self_item));
+ }
+
+ file_params.keys().for_each(|whole_param| add_new_item_to_acc(whole_param));
+}
+
+fn params_from_stmt_list_scope(
+ ctx: &CompletionContext<'_>,
+ stmt_list: ast::StmtList,
+ mut cb: impl FnMut(hir::Name, String),
+) {
+ let syntax_node = match stmt_list.syntax().last_child() {
+ Some(it) => it,
+ None => return,
+ };
+ if let Some(scope) =
+ ctx.sema.scope_at_offset(stmt_list.syntax(), syntax_node.text_range().end())
+ {
+ let module = scope.module().into();
+ scope.process_all_names(&mut |name, def| {
+ if let hir::ScopeDef::Local(local) = def {
+ if let Ok(ty) = local.ty(ctx.db).display_source_code(ctx.db, module) {
+ cb(name, ty);
+ }
+ }
+ });
+ }
+}
+
+fn remove_duplicated(
+ file_params: &mut FxHashMap<String, String>,
+ fn_params: ast::AstChildren<ast::Param>,
+) {
+ fn_params.for_each(|param| {
+ let whole_param = param.syntax().text().to_string();
+ file_params.remove(&whole_param);
+
+ match param.pat() {
+ // remove suggestions for patterns that already exist
+ // if the type is missing we are checking the current param to be completed
+ // in which case this would find itself removing the suggestions due to itself
+ Some(pattern) if param.ty().is_some() => {
+ let binding = pattern.syntax().text().to_string();
+ file_params.retain(|_, v| v != &binding);
+ }
+ _ => (),
+ }
+ })
+}
+
+fn should_add_self_completions(
+ cursor: TextSize,
+ param_list: &ast::ParamList,
+ impl_: &Option<ast::Impl>,
+) -> bool {
+ if impl_.is_none() || param_list.self_param().is_some() {
+ return false;
+ }
+ match param_list.params().next() {
+ Some(first) => first.pat().map_or(false, |pat| pat.syntax().text_range().contains(cursor)),
+ None => true,
+ }
+}
+
+fn comma_wrapper(ctx: &CompletionContext<'_>) -> Option<(impl Fn(&str) -> String, TextRange)> {
+ let param = ctx.token.parent_ancestors().find(|node| node.kind() == SyntaxKind::PARAM)?;
+
+ let next_token_kind = {
+ let t = param.last_token()?.next_token()?;
+ let t = algo::skip_whitespace_token(t, Direction::Next)?;
+ t.kind()
+ };
+ let prev_token_kind = {
+ let t = param.first_token()?.prev_token()?;
+ let t = algo::skip_whitespace_token(t, Direction::Prev)?;
+ t.kind()
+ };
+
+ let has_trailing_comma =
+ matches!(next_token_kind, SyntaxKind::COMMA | SyntaxKind::R_PAREN | SyntaxKind::PIPE);
+ let trailing = if has_trailing_comma { "" } else { "," };
+
+ let has_leading_comma =
+ matches!(prev_token_kind, SyntaxKind::COMMA | SyntaxKind::L_PAREN | SyntaxKind::PIPE);
+ let leading = if has_leading_comma { "" } else { ", " };
+
+ Some((move |label: &_| (format!("{}{}{}", leading, label, trailing)), param.text_range()))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs
new file mode 100644
index 000000000..038bdb427
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs
@@ -0,0 +1,130 @@
+//! Completes identifiers in format string literals.
+
+use ide_db::syntax_helpers::format_string::is_format_string;
+use itertools::Itertools;
+use syntax::{ast, AstToken, TextRange, TextSize};
+
+use crate::{context::CompletionContext, CompletionItem, CompletionItemKind, Completions};
+
+/// Complete identifiers in format strings.
+pub(crate) fn format_string(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ original: &ast::String,
+ expanded: &ast::String,
+) {
+ if !is_format_string(&expanded) {
+ return;
+ }
+ let cursor = ctx.position.offset;
+ let lit_start = ctx.original_token.text_range().start();
+ let cursor_in_lit = cursor - lit_start;
+
+ let prefix = &original.text()[..cursor_in_lit.into()];
+ let braces = prefix.char_indices().rev().skip_while(|&(_, c)| c.is_alphanumeric()).next_tuple();
+ let brace_offset = match braces {
+ // escaped brace
+ Some(((_, '{'), (_, '{'))) => return,
+ Some(((idx, '{'), _)) => lit_start + TextSize::from(idx as u32 + 1),
+ _ => return,
+ };
+
+ let source_range = TextRange::new(brace_offset, cursor);
+ ctx.locals.iter().for_each(|(name, _)| {
+ CompletionItem::new(CompletionItemKind::Binding, source_range, name.to_smol_str())
+ .add_to(acc);
+ })
+}
+
+#[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 works_when_wrapped() {
+ check(
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+macro_rules! print {
+ ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
+}
+fn main() {
+ let foobar = 1;
+ print!("f$0");
+}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn no_completion_without_brace() {
+ check(
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("f$0");
+}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn completes_locals() {
+ check_edit(
+ "foobar",
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{f$0");
+}
+"#,
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{foobar");
+}
+"#,
+ );
+ check_edit(
+ "foobar",
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{$0");
+}
+"#,
+ r#"
+macro_rules! format_args {
+ ($lit:literal $(tt:tt)*) => { 0 },
+}
+fn main() {
+ let foobar = 1;
+ format_args!("{foobar");
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs
new file mode 100644
index 000000000..60d05ae46
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs
@@ -0,0 +1,133 @@
+//! Completion of paths and keywords at item list position.
+
+use crate::{
+ context::{ExprCtx, ItemListKind, PathCompletionCtx, Qualified},
+ CompletionContext, Completions,
+};
+
+pub(crate) mod trait_impl;
+
+pub(crate) fn complete_item_list_in_expr(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ if !expr_ctx.in_block_expr {
+ return;
+ }
+ if !path_ctx.is_trivial_path() {
+ return;
+ }
+ add_keywords(acc, ctx, None);
+}
+
+pub(crate) fn complete_item_list(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ kind: &ItemListKind,
+) {
+ let _p = profile::span("complete_item_list");
+ if path_ctx.is_trivial_path() {
+ add_keywords(acc, ctx, Some(kind));
+ }
+
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
+ match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ }
+ }
+
+ acc.add_super_keyword(ctx, *super_chain_len);
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No if ctx.qualifier_ctx.none() => {
+ ctx.process_all_names(&mut |name, def| match def {
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(m)) if m.is_fn_like(ctx.db) => {
+ acc.add_macro(ctx, path_ctx, m, name)
+ }
+ hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) => {
+ acc.add_module(ctx, path_ctx, m, name)
+ }
+ _ => (),
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::No | Qualified::With { .. } => {}
+ }
+}
+
+fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option<&ItemListKind>) {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+
+ let in_item_list = matches!(kind, Some(ItemListKind::SourceFile | ItemListKind::Module) | None);
+ let in_assoc_non_trait_impl = matches!(kind, Some(ItemListKind::Impl | ItemListKind::Trait));
+ let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock));
+ let in_trait = matches!(kind, Some(ItemListKind::Trait));
+ let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl(_)));
+ let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl));
+ let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
+ let in_block = matches!(kind, None);
+
+ if !in_trait_impl {
+ if ctx.qualifier_ctx.unsafe_tok.is_some() {
+ if in_item_list || in_assoc_non_trait_impl {
+ add_keyword("fn", "fn $1($2) {\n $0\n}");
+ }
+ if in_item_list {
+ add_keyword("trait", "trait $1 {\n $0\n}");
+ if no_qualifiers {
+ add_keyword("impl", "impl $1 {\n $0\n}");
+ }
+ }
+ return;
+ }
+
+ if in_item_list {
+ add_keyword("enum", "enum $1 {\n $0\n}");
+ add_keyword("mod", "mod $0");
+ add_keyword("static", "static $0");
+ add_keyword("struct", "struct $0");
+ add_keyword("trait", "trait $1 {\n $0\n}");
+ add_keyword("union", "union $1 {\n $0\n}");
+ add_keyword("use", "use $0");
+ if no_qualifiers {
+ add_keyword("impl", "impl $1 {\n $0\n}");
+ }
+ }
+
+ if !in_trait && !in_block && no_qualifiers {
+ add_keyword("pub(crate)", "pub(crate)");
+ add_keyword("pub(super)", "pub(super)");
+ add_keyword("pub", "pub");
+ }
+
+ if in_extern_block {
+ add_keyword("fn", "fn $1($2);");
+ } else {
+ if !in_inherent_impl {
+ if !in_trait {
+ add_keyword("extern", "extern $0");
+ }
+ add_keyword("type", "type $0");
+ }
+
+ add_keyword("fn", "fn $1($2) {\n $0\n}");
+ add_keyword("unsafe", "unsafe");
+ add_keyword("const", "const $0");
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
new file mode 100644
index 000000000..e9256803c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -0,0 +1,1160 @@
+//! Completion for associated items in a trait implementation.
+//!
+//! This module adds the completion items related to implementing associated
+//! items within an `impl Trait for Struct` block. The current context node
+//! must be within either a `FN`, `TYPE_ALIAS`, or `CONST` node
+//! and an direct child of an `IMPL`.
+//!
+//! # Examples
+//!
+//! Considering the following trait `impl`:
+//!
+//! ```ignore
+//! trait SomeTrait {
+//! fn foo();
+//! }
+//!
+//! impl SomeTrait for () {
+//! fn f$0
+//! }
+//! ```
+//!
+//! may result in the completion of the following method:
+//!
+//! ```ignore
+//! # trait SomeTrait {
+//! # fn foo();
+//! # }
+//!
+//! impl SomeTrait for () {
+//! fn foo() {}$0
+//! }
+//! ```
+
+use hir::{self, HasAttrs};
+use ide_db::{
+ path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node,
+ traits::get_missing_assoc_items, SymbolKind,
+};
+use syntax::{
+ ast::{self, edit_in_place::AttrsOwnerEdit},
+ AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
+};
+use text_edit::TextEdit;
+
+use crate::{
+ context::PathCompletionCtx, CompletionContext, CompletionItem, CompletionItemKind,
+ CompletionRelevance, Completions,
+};
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+enum ImplCompletionKind {
+ All,
+ Fn,
+ TypeAlias,
+ Const,
+}
+
+pub(crate) fn complete_trait_impl_const(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::Const)
+}
+
+pub(crate) fn complete_trait_impl_type_alias(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::TypeAlias)
+}
+
+pub(crate) fn complete_trait_impl_fn(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+) -> Option<()> {
+ complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::Fn)
+}
+
+fn complete_trait_impl_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ name: &Option<ast::Name>,
+ kind: ImplCompletionKind,
+) -> Option<()> {
+ let token = ctx.token.clone();
+ let item = match name {
+ Some(name) => name.syntax().parent(),
+ None => if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token }
+ .parent(),
+ }?;
+ complete_trait_impl(
+ acc,
+ ctx,
+ kind,
+ replacement_range(ctx, &item),
+ // item -> ASSOC_ITEM_LIST -> IMPL
+ &ast::Impl::cast(item.parent()?.parent()?)?,
+ );
+ Some(())
+}
+
+pub(crate) fn complete_trait_impl_item_by_name(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ name_ref: &Option<ast::NameRef>,
+ impl_: &Option<ast::Impl>,
+) {
+ if !path_ctx.is_trivial_path() {
+ return;
+ }
+ if let Some(impl_) = impl_ {
+ complete_trait_impl(
+ acc,
+ ctx,
+ ImplCompletionKind::All,
+ match name_ref {
+ Some(name) => name.syntax().text_range(),
+ None => ctx.source_range(),
+ },
+ impl_,
+ );
+ }
+}
+
+fn complete_trait_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ kind: ImplCompletionKind,
+ replacement_range: TextRange,
+ impl_def: &ast::Impl,
+) {
+ if let Some(hir_impl) = ctx.sema.to_def(impl_def) {
+ get_missing_assoc_items(&ctx.sema, impl_def).into_iter().for_each(|item| {
+ use self::ImplCompletionKind::*;
+ match (item, kind) {
+ (hir::AssocItem::Function(func), All | Fn) => {
+ add_function_impl(acc, ctx, replacement_range, func, hir_impl)
+ }
+ (hir::AssocItem::TypeAlias(type_alias), All | TypeAlias) => {
+ add_type_alias_impl(acc, ctx, replacement_range, type_alias)
+ }
+ (hir::AssocItem::Const(const_), All | Const) => {
+ add_const_impl(acc, ctx, replacement_range, const_, hir_impl)
+ }
+ _ => {}
+ }
+ });
+ }
+}
+
+fn add_function_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ func: hir::Function,
+ impl_def: hir::Impl,
+) {
+ let fn_name = func.name(ctx.db);
+
+ let label = format!(
+ "fn {}({})",
+ fn_name,
+ if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
+ );
+
+ let completion_kind = if func.has_self_param(ctx.db) {
+ CompletionItemKind::Method
+ } else {
+ CompletionItemKind::SymbolKind(SymbolKind::Function)
+ };
+
+ let mut item = CompletionItem::new(completion_kind, replacement_range, label);
+ item.lookup_by(format!("fn {}", fn_name))
+ .set_documentation(func.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+
+ if let Some(source) = ctx.sema.source(func) {
+ let assoc_item = ast::AssocItem::Fn(source.value);
+ if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
+ let transformed_fn = match transformed_item {
+ ast::AssocItem::Fn(func) => func,
+ _ => unreachable!(),
+ };
+
+ let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
+ match ctx.config.snippet_cap {
+ Some(cap) => {
+ let snippet = format!("{} {{\n $0\n}}", function_decl);
+ item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet));
+ }
+ None => {
+ let header = format!("{} {{", function_decl);
+ item.text_edit(TextEdit::replace(replacement_range, header));
+ }
+ };
+ item.add_to(acc);
+ }
+ }
+}
+
+/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
+fn get_transformed_assoc_item(
+ ctx: &CompletionContext<'_>,
+ assoc_item: ast::AssocItem,
+ impl_def: hir::Impl,
+) -> Option<ast::AssocItem> {
+ let assoc_item = assoc_item.clone_for_update();
+ let trait_ = impl_def.trait_(ctx.db)?;
+ let source_scope = &ctx.sema.scope_for_def(trait_);
+ let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?;
+ let transform = PathTransform::trait_impl(
+ target_scope,
+ source_scope,
+ trait_,
+ ctx.sema.source(impl_def)?.value,
+ );
+
+ transform.apply(assoc_item.syntax());
+ if let ast::AssocItem::Fn(func) = &assoc_item {
+ func.remove_attrs_and_docs();
+ }
+ Some(assoc_item)
+}
+
+fn add_type_alias_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ type_alias: hir::TypeAlias,
+) {
+ let alias_name = type_alias.name(ctx.db);
+ let (alias_name, escaped_name) = (alias_name.to_smol_str(), alias_name.escaped().to_smol_str());
+
+ let label = format!("type {} =", alias_name);
+ let replacement = format!("type {} = ", escaped_name);
+
+ let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label);
+ item.lookup_by(format!("type {}", alias_name))
+ .set_documentation(type_alias.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+ match ctx.config.snippet_cap {
+ Some(cap) => item
+ .snippet_edit(cap, TextEdit::replace(replacement_range, format!("{}$0;", replacement))),
+ None => item.text_edit(TextEdit::replace(replacement_range, replacement)),
+ };
+ item.add_to(acc);
+}
+
+fn add_const_impl(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ const_: hir::Const,
+ impl_def: hir::Impl,
+) {
+ let const_name = const_.name(ctx.db).map(|n| n.to_smol_str());
+
+ if let Some(const_name) = const_name {
+ if let Some(source) = ctx.sema.source(const_) {
+ let assoc_item = ast::AssocItem::Const(source.value);
+ if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
+ let transformed_const = match transformed_item {
+ ast::AssocItem::Const(const_) => const_,
+ _ => unreachable!(),
+ };
+
+ let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro());
+ let replacement = format!("{} ", label);
+
+ let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label);
+ item.lookup_by(format!("const {}", const_name))
+ .set_documentation(const_.docs(ctx.db))
+ .set_relevance(CompletionRelevance {
+ is_item_from_trait: true,
+ ..Default::default()
+ });
+ match ctx.config.snippet_cap {
+ Some(cap) => item.snippet_edit(
+ cap,
+ TextEdit::replace(replacement_range, format!("{}$0;", replacement)),
+ ),
+ None => item.text_edit(TextEdit::replace(replacement_range, replacement)),
+ };
+ item.add_to(acc);
+ }
+ }
+ }
+}
+
+fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> String {
+ const_.remove_attrs_and_docs();
+ let const_ = if needs_whitespace {
+ insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
+ } else {
+ const_.syntax().clone()
+ };
+
+ let start = const_.text_range().start();
+ let const_end = const_.text_range().end();
+
+ let end = const_
+ .children_with_tokens()
+ .find(|s| s.kind() == T![;] || s.kind() == T![=])
+ .map_or(const_end, |f| f.text_range().start());
+
+ let len = end - start;
+ let range = TextRange::new(0.into(), len);
+
+ let syntax = const_.text().slice(range).to_string();
+
+ format!("{} =", syntax.trim_end())
+}
+
+fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
+ node.remove_attrs_and_docs();
+
+ let node = if needs_whitespace {
+ insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
+ } else {
+ node.syntax().clone()
+ };
+
+ let start = node.text_range().start();
+ let end = node.text_range().end();
+
+ let end = node
+ .last_child_or_token()
+ .filter(|s| s.kind() == T![;] || s.kind() == SyntaxKind::BLOCK_EXPR)
+ .map_or(end, |f| f.text_range().start());
+
+ let len = end - start;
+ let range = TextRange::new(0.into(), len);
+
+ let syntax = node.text().slice(range).to_string();
+
+ syntax.trim_end().to_owned()
+}
+
+fn replacement_range(ctx: &CompletionContext<'_>, item: &SyntaxNode) -> TextRange {
+ let first_child = item
+ .children_with_tokens()
+ .find(|child| {
+ !matches!(child.kind(), SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR)
+ })
+ .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
+
+ TextRange::new(first_child.text_range().start(), ctx.source_range().end())
+}
+
+#[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 no_completion_inside_fn() {
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ t$0
+ }
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ fn t$0
+ }
+}
+",
+ expect![[""]],
+ );
+
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ fn $0
+ }
+}
+",
+ expect![[""]],
+ );
+
+ // https://github.com/rust-lang/rust-analyzer/pull/5976#issuecomment-692332191
+ check(
+ r"
+trait Test { fn test(); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test() {
+ foo.$0
+ }
+}
+",
+ expect![[r#""#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(_: i32); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test(t$0)
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ bn &mut self
+ bn &self
+ bn mut self
+ bn self
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { fn test(_: fn()); fn test2(); }
+struct T;
+
+impl Test for T {
+ fn test(f: fn $0)
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_completion_inside_const() {
+ check(
+ r"
+trait Test { const TEST: fn(); const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: fn $0
+}
+",
+ expect![[r#""#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: T$0
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = f$0
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ t$0
+ };
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ fn $0
+ };
+}
+",
+ expect![[""]],
+ );
+
+ check(
+ r"
+trait Test { const TEST: u32; const TEST2: u32; type Test; fn test(); }
+struct T;
+
+impl Test for T {
+ const TEST: u32 = {
+ fn t$0
+ };
+}
+",
+ expect![[""]],
+ );
+ }
+
+ #[test]
+ fn no_completion_inside_type() {
+ check(
+ r"
+trait Test { type Test; type Test2; fn test(); }
+struct T;
+
+impl Test for T {
+ type Test = T$0;
+}
+",
+ expect![[r#"
+ sp Self
+ st T
+ tt Test
+ bt u32
+ "#]],
+ );
+
+ check(
+ r"
+trait Test { type Test; type Test2; fn test(); }
+struct T;
+
+impl Test for T {
+ type Test = fn $0;
+}
+",
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn name_ref_single_function() {
+ check_edit(
+ "fn test",
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ t$0
+}
+"#,
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn test() {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_function() {
+ check_edit(
+ "fn test",
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn t$0
+}
+"#,
+ r#"
+trait Test {
+ fn test();
+}
+struct T;
+
+impl Test for T {
+ fn test() {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn generic_fn() {
+ check_edit(
+ "fn foo",
+ r#"
+trait Test {
+ fn foo<T>();
+}
+struct T;
+
+impl Test for T {
+ fn f$0
+}
+"#,
+ r#"
+trait Test {
+ fn foo<T>();
+}
+struct T;
+
+impl Test for T {
+ fn foo<T>() {
+ $0
+}
+}
+"#,
+ );
+ check_edit(
+ "fn foo",
+ r#"
+trait Test {
+ fn foo<T>() where T: Into<String>;
+}
+struct T;
+
+impl Test for T {
+ fn f$0
+}
+"#,
+ r#"
+trait Test {
+ fn foo<T>() where T: Into<String>;
+}
+struct T;
+
+impl Test for T {
+ fn foo<T>() where T: Into<String> {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn associated_type() {
+ check_edit(
+ "type SomeType",
+ r#"
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type S$0
+}
+"#,
+ "
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type SomeType = $0;\n\
+}
+",
+ );
+ check_edit(
+ "type SomeType",
+ r#"
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type$0
+}
+"#,
+ "
+trait Test {
+ type SomeType;
+}
+
+impl Test for () {
+ type SomeType = $0;\n\
+}
+",
+ );
+ }
+
+ #[test]
+ fn associated_const() {
+ check_edit(
+ "const SOME_CONST",
+ r#"
+trait Test {
+ const SOME_CONST: u16;
+}
+
+impl Test for () {
+ const S$0
+}
+"#,
+ "
+trait Test {
+ const SOME_CONST: u16;
+}
+
+impl Test for () {
+ const SOME_CONST: u16 = $0;\n\
+}
+",
+ );
+
+ check_edit(
+ "const SOME_CONST",
+ r#"
+trait Test {
+ const SOME_CONST: u16 = 92;
+}
+
+impl Test for () {
+ const S$0
+}
+"#,
+ "
+trait Test {
+ const SOME_CONST: u16 = 92;
+}
+
+impl Test for () {
+ const SOME_CONST: u16 = $0;\n\
+}
+",
+ );
+ }
+
+ #[test]
+ fn complete_without_name() {
+ let test = |completion: &str, hint: &str, completed: &str, next_sibling: &str| {
+ check_edit(
+ completion,
+ &format!(
+ r#"
+trait Test {{
+ type Foo;
+ const CONST: u16;
+ fn bar();
+}}
+struct T;
+
+impl Test for T {{
+ {}
+ {}
+}}
+"#,
+ hint, next_sibling
+ ),
+ &format!(
+ r#"
+trait Test {{
+ type Foo;
+ const CONST: u16;
+ fn bar();
+}}
+struct T;
+
+impl Test for T {{
+ {}
+ {}
+}}
+"#,
+ completed, next_sibling
+ ),
+ )
+ };
+
+ // Enumerate some possible next siblings.
+ for next_sibling in &[
+ "",
+ "fn other_fn() {}", // `const $0 fn` -> `const fn`
+ "type OtherType = i32;",
+ "const OTHER_CONST: i32 = 0;",
+ "async fn other_fn() {}",
+ "unsafe fn other_fn() {}",
+ "default fn other_fn() {}",
+ "default type OtherType = i32;",
+ "default const OTHER_CONST: i32 = 0;",
+ ] {
+ test("fn bar", "fn $0", "fn bar() {\n $0\n}", next_sibling);
+ test("type Foo", "type $0", "type Foo = $0;", next_sibling);
+ test("const CONST", "const $0", "const CONST: u16 = $0;", next_sibling);
+ }
+ }
+
+ #[test]
+ fn snippet_does_not_overwrite_comment_or_attr() {
+ let test = |completion: &str, hint: &str, completed: &str| {
+ check_edit(
+ completion,
+ &format!(
+ r#"
+trait Foo {{
+ type Type;
+ fn function();
+ const CONST: i32 = 0;
+}}
+struct T;
+
+impl Foo for T {{
+ // Comment
+ #[bar]
+ {}
+}}
+"#,
+ hint
+ ),
+ &format!(
+ r#"
+trait Foo {{
+ type Type;
+ fn function();
+ const CONST: i32 = 0;
+}}
+struct T;
+
+impl Foo for T {{
+ // Comment
+ #[bar]
+ {}
+}}
+"#,
+ completed
+ ),
+ )
+ };
+ test("fn function", "fn f$0", "fn function() {\n $0\n}");
+ test("type Type", "type T$0", "type Type = $0;");
+ test("const CONST", "const C$0", "const CONST: i32 = $0;");
+ }
+
+ #[test]
+ fn generics_are_inlined_in_return_type() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function() -> T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function() -> T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function() -> u32 {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_parameter() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function(bar: T);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function(bar: T);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function(bar: u32) {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_when_part_of_other_types() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T> {
+ fn function(bar: Vec<T>);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ fn function(bar: Vec<T>);
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function(bar: Vec<u32>) {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_complex() {
+ check_edit(
+ "fn function",
+ r#"
+trait Foo<T, U, V> {
+ fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
+}
+struct Bar;
+
+impl Foo<u32, Vec<usize>, u8> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait Foo<T, U, V> {
+ fn function(bar: Vec<T>, baz: U) -> Arc<Vec<V>>;
+}
+struct Bar;
+
+impl Foo<u32, Vec<usize>, u8> for Bar {
+ fn function(bar: Vec<u32>, baz: Vec<usize>) -> Arc<Vec<u8>> {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_associated_const() {
+ check_edit(
+ "const BAR",
+ r#"
+trait Foo<T> {
+ const BAR: T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ const B$0
+}
+"#,
+ r#"
+trait Foo<T> {
+ const BAR: T;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ const BAR: u32 = $0;
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn generics_are_inlined_in_where_clause() {
+ check_edit(
+ "fn function",
+ r#"
+trait SomeTrait<T> {}
+
+trait Foo<T> {
+ fn function()
+ where Self: SomeTrait<T>;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn f$0
+}
+"#,
+ r#"
+trait SomeTrait<T> {}
+
+trait Foo<T> {
+ fn function()
+ where Self: SomeTrait<T>;
+}
+struct Bar;
+
+impl Foo<u32> for Bar {
+ fn function()
+ where Self: SomeTrait<u32> {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn works_directly_in_impl() {
+ check(
+ r#"
+trait Tr {
+ fn required();
+}
+
+impl Tr for () {
+ $0
+}
+"#,
+ expect![[r#"
+ fn fn required()
+ "#]],
+ );
+ check(
+ r#"
+trait Tr {
+ fn provided() {}
+ fn required();
+}
+
+impl Tr for () {
+ fn provided() {}
+ $0
+}
+"#,
+ expect![[r#"
+ fn fn required()
+ "#]],
+ );
+ }
+
+ #[test]
+ fn fixes_up_macro_generated() {
+ check_edit(
+ "fn foo",
+ r#"
+macro_rules! noop {
+ ($($item: item)*) => {
+ $($item)*
+ }
+}
+
+noop! {
+ trait Foo {
+ fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
+ }
+}
+
+struct Test;
+
+impl Foo for Test {
+ $0
+}
+"#,
+ r#"
+macro_rules! noop {
+ ($($item: item)*) => {
+ $($item)*
+ }
+}
+
+noop! {
+ trait Foo {
+ fn foo(&mut self, bar: i64, baz: &mut u32) -> Result<(), u32>;
+ }
+}
+
+struct Test;
+
+impl Foo for Test {
+ fn foo(&mut self,bar:i64,baz: &mut u32) -> Result<(),u32> {
+ $0
+}
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs
new file mode 100644
index 000000000..3989a451b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs
@@ -0,0 +1,237 @@
+//! Completes `where` and `for` keywords.
+
+use syntax::ast::{self, Item};
+
+use crate::{CompletionContext, Completions};
+
+pub(crate) fn complete_for_and_where(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ keyword_item: &ast::Item,
+) {
+ let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
+
+ match keyword_item {
+ Item::Impl(it) => {
+ if it.for_token().is_none() && it.trait_().is_none() && it.self_ty().is_some() {
+ add_keyword("for", "for");
+ }
+ add_keyword("where", "where");
+ }
+ Item::Enum(_)
+ | Item::Fn(_)
+ | Item::Struct(_)
+ | Item::Trait(_)
+ | Item::TypeAlias(_)
+ | Item::Union(_) => {
+ add_keyword("where", "where");
+ }
+ _ => (),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+ }
+
+ #[test]
+ fn test_else_edit_after_if() {
+ check_edit(
+ "else",
+ r#"fn quux() { if true { () } $0 }"#,
+ r#"fn quux() { if true { () } else {
+ $0
+} }"#,
+ );
+ }
+
+ #[test]
+ fn test_keywords_after_unsafe_in_block_expr() {
+ check(
+ r"fn my_fn() { unsafe $0 }",
+ expect![[r#"
+ kw fn
+ kw impl
+ kw trait
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_completion_await_impls_future() {
+ check(
+ r#"
+//- minicore: future
+use core::future::*;
+struct A {}
+impl Future for A {}
+fn foo(a: A) { a.$0 }
+"#,
+ expect![[r#"
+ kw await expr.await
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ );
+
+ check(
+ r#"
+//- minicore: future
+use std::future::*;
+fn foo() {
+ let a = async {};
+ a.$0
+}
+"#,
+ expect![[r#"
+ kw await expr.await
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+ }
+
+ #[test]
+ fn let_semi() {
+ cov_mark::check!(let_semi);
+ check_edit(
+ "match",
+ r#"
+fn main() { let x = $0 }
+"#,
+ r#"
+fn main() { let x = match $1 {
+ $0
+}; }
+"#,
+ );
+
+ check_edit(
+ "if",
+ r#"
+fn main() {
+ let x = $0
+ let y = 92;
+}
+"#,
+ r#"
+fn main() {
+ let x = if $1 {
+ $0
+};
+ let y = 92;
+}
+"#,
+ );
+
+ check_edit(
+ "loop",
+ r#"
+fn main() {
+ let x = $0
+ bar();
+}
+"#,
+ r#"
+fn main() {
+ let x = loop {
+ $0
+};
+ bar();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn if_completion_in_match_guard() {
+ check_edit(
+ "if",
+ r"
+fn main() {
+ match () {
+ () $0
+ }
+}
+",
+ r"
+fn main() {
+ match () {
+ () if $0
+ }
+}
+",
+ )
+ }
+
+ #[test]
+ fn if_completion_in_match_arm_expr() {
+ check_edit(
+ "if",
+ r"
+fn main() {
+ match () {
+ () => $0
+ }
+}
+",
+ r"
+fn main() {
+ match () {
+ () => if $1 {
+ $0
+}
+ }
+}
+",
+ )
+ }
+
+ #[test]
+ fn if_completion_in_match_arm_expr_block() {
+ check_edit(
+ "if",
+ r"
+fn main() {
+ match () {
+ () => {
+ $0
+ }
+ }
+}
+",
+ r"
+fn main() {
+ match () {
+ () => {
+ if $1 {
+ $0
+}
+ }
+ }
+}
+",
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs
new file mode 100644
index 000000000..3b79def63
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs
@@ -0,0 +1,341 @@
+//! Completes lifetimes and labels.
+//!
+//! These completions work a bit differently in that they are only shown when what the user types
+//! has a `'` preceding it, as our fake syntax tree is invalid otherwise (due to us not inserting
+//! a lifetime but an ident for obvious reasons).
+//! Due to this all the tests for lifetimes and labels live in this module for the time being as
+//! there is no value in lifting these out into the outline module test since they will either not
+//! show up for normal completions, or they won't show completions other than lifetimes depending
+//! on the fixture input.
+use hir::{known, ScopeDef};
+use syntax::{ast, TokenText};
+
+use crate::{
+ completions::Completions,
+ context::{CompletionContext, LifetimeContext, LifetimeKind},
+};
+
+/// Completes lifetimes.
+pub(crate) fn complete_lifetime(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ lifetime_ctx: &LifetimeContext,
+) {
+ let (lp, lifetime) = match lifetime_ctx {
+ LifetimeContext { kind: LifetimeKind::Lifetime, lifetime } => (None, lifetime),
+ LifetimeContext {
+ kind: LifetimeKind::LifetimeParam { is_decl: false, param },
+ lifetime,
+ } => (Some(param), lifetime),
+ _ => return,
+ };
+ let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) {
+ (Some(lt), Some(lp)) if lp == lt.clone() => return,
+ (Some(_), Some(lp)) => Some(lp),
+ _ => None,
+ };
+ let param_lifetime = param_lifetime.as_ref().map(ast::Lifetime::text);
+ let param_lifetime = param_lifetime.as_ref().map(TokenText::as_str);
+
+ ctx.process_all_names_raw(&mut |name, res| {
+ if matches!(
+ res,
+ ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_))
+ if param_lifetime != Some(&*name.to_smol_str())
+ ) {
+ acc.add_lifetime(ctx, name);
+ }
+ });
+ if param_lifetime.is_none() {
+ acc.add_lifetime(ctx, known::STATIC_LIFETIME);
+ }
+}
+
+/// Completes labels.
+pub(crate) fn complete_label(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ lifetime_ctx: &LifetimeContext,
+) {
+ if !matches!(lifetime_ctx, LifetimeContext { kind: LifetimeKind::LabelRef, .. }) {
+ return;
+ }
+ ctx.process_all_names_raw(&mut |name, res| {
+ if let ScopeDef::Label(_) = res {
+ acc.add_label(ctx, name);
+ }
+ });
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::{check_edit, completion_list};
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn check_lifetime_edit() {
+ check_edit(
+ "'lifetime",
+ r#"
+fn func<'lifetime>(foo: &'li$0) {}
+"#,
+ r#"
+fn func<'lifetime>(foo: &'lifetime) {}
+"#,
+ );
+ cov_mark::check!(completes_if_lifetime_without_idents);
+ check_edit(
+ "'lifetime",
+ r#"
+fn func<'lifetime>(foo: &'$0) {}
+"#,
+ r#"
+fn func<'lifetime>(foo: &'lifetime) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_ref() {
+ check(
+ r#"
+fn foo<'lifetime>(foo: &'a$0 usize) {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_ref_missing_ty() {
+ check(
+ r#"
+fn foo<'lifetime>(foo: &'a$0) {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+ #[test]
+ fn complete_lifetime_in_self_ref() {
+ check(
+ r#"
+struct Foo;
+impl<'impl> Foo {
+ fn foo<'func>(&'a$0 self) {}
+}
+"#,
+ expect![[r#"
+ lt 'func
+ lt 'impl
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_arg_list() {
+ check(
+ r#"
+struct Foo<'lt>;
+fn foo<'lifetime>(_: Foo<'a$0>) {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_where_pred() {
+ check(
+ r#"
+fn foo2<'lifetime, T>() where 'a$0 {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_ty_bound() {
+ check(
+ r#"
+fn foo2<'lifetime, T>() where T: 'a$0 {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ check(
+ r#"
+fn foo2<'lifetime, T>() where T: Trait<'a$0> {}
+"#,
+ expect![[r#"
+ lt 'lifetime
+ lt 'static
+ "#]],
+ );
+ }
+
+ #[test]
+ fn dont_complete_lifetime_in_assoc_ty_bound() {
+ check(
+ r#"
+fn foo2<'lifetime, T>() where T: Trait<Item = 'a$0> {}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn complete_lifetime_in_param_list() {
+ check(
+ r#"
+fn foo<'$0>() {}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+fn foo<'a$0>() {}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+fn foo<'footime, 'lifetime: 'a$0>() {}
+"#,
+ expect![[r#"
+ lt 'footime
+ "#]],
+ );
+ }
+
+ #[test]
+ fn check_label_edit() {
+ check_edit(
+ "'label",
+ r#"
+fn foo() {
+ 'label: loop {
+ break '$0
+ }
+}
+"#,
+ r#"
+fn foo() {
+ 'label: loop {
+ break 'label
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_label_in_loop() {
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ break '$0
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ continue '$0
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_block_nested() {
+ check(
+ r#"
+fn foo() {
+ 'foop: {
+ 'baap: {
+ break '$0
+ }
+ }
+}
+"#,
+ expect![[r#"
+ lb 'baap
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_loop_with_value() {
+ check(
+ r#"
+fn foo() {
+ 'foop: loop {
+ break '$0 i32;
+ }
+}
+"#,
+ expect![[r#"
+ lb 'foop
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_while_cond() {
+ check(
+ r#"
+fn foo() {
+ 'outer: while { 'inner: loop { break '$0 } } {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ lb 'outer
+ "#]],
+ );
+ }
+
+ #[test]
+ fn complete_label_in_for_iterable() {
+ check(
+ r#"
+fn foo() {
+ 'outer: for _ in [{ 'inner: loop { break '$0 } }] {}
+}
+"#,
+ expect![[r#"
+ lb 'inner
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs
new file mode 100644
index 000000000..9c975b929
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs
@@ -0,0 +1,354 @@
+//! Completes mod declarations.
+
+use std::iter;
+
+use hir::{Module, ModuleSource};
+use ide_db::{
+ base_db::{SourceDatabaseExt, VfsPath},
+ FxHashSet, RootDatabase, SymbolKind,
+};
+use syntax::{ast, AstNode, SyntaxKind};
+
+use crate::{context::CompletionContext, CompletionItem, Completions};
+
+/// Complete mod declaration, i.e. `mod $0;`
+pub(crate) fn complete_mod(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ mod_under_caret: &ast::Module,
+) -> Option<()> {
+ if mod_under_caret.item_list().is_some() {
+ return None;
+ }
+
+ let _p = profile::span("completion::complete_mod");
+
+ let mut current_module = ctx.module;
+ // For `mod $0`, `ctx.module` is its parent, but for `mod f$0`, it's `mod f` itself, but we're
+ // interested in its parent.
+ if ctx.original_token.kind() == SyntaxKind::IDENT {
+ if let Some(module) =
+ ctx.original_token.parent_ancestors().nth(1).and_then(ast::Module::cast)
+ {
+ match ctx.sema.to_def(&module) {
+ Some(module) if module == current_module => {
+ if let Some(parent) = current_module.parent(ctx.db) {
+ current_module = parent;
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ let module_definition_file =
+ current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
+ let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
+ let directory_to_look_for_submodules = directory_to_look_for_submodules(
+ current_module,
+ ctx.db,
+ source_root.path_for_file(&module_definition_file)?,
+ )?;
+
+ let existing_mod_declarations = current_module
+ .children(ctx.db)
+ .filter_map(|module| Some(module.name(ctx.db)?.to_string()))
+ .collect::<FxHashSet<_>>();
+
+ let module_declaration_file =
+ current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
+ module_declaration_source_file.file_id.original_file(ctx.db)
+ });
+
+ source_root
+ .iter()
+ .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
+ .filter(|submodule_candidate_file| {
+ Some(submodule_candidate_file) != module_declaration_file.as_ref()
+ })
+ .filter_map(|submodule_file| {
+ let submodule_path = source_root.path_for_file(&submodule_file)?;
+ let directory_with_submodule = submodule_path.parent()?;
+ let (name, ext) = submodule_path.name_and_extension()?;
+ if ext != Some("rs") {
+ return None;
+ }
+ match name {
+ "lib" | "main" => None,
+ "mod" => {
+ if directory_with_submodule.parent()? == directory_to_look_for_submodules {
+ match directory_with_submodule.name_and_extension()? {
+ (directory_name, None) => Some(directory_name.to_owned()),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+ file_name if directory_with_submodule == directory_to_look_for_submodules => {
+ Some(file_name.to_owned())
+ }
+ _ => None,
+ }
+ })
+ .filter(|name| !existing_mod_declarations.contains(name))
+ .for_each(|submodule_name| {
+ let mut label = submodule_name;
+ if mod_under_caret.semicolon_token().is_none() {
+ label.push(';');
+ }
+ let item = CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label);
+ item.add_to(acc)
+ });
+
+ Some(())
+}
+
+fn directory_to_look_for_submodules(
+ module: Module,
+ db: &RootDatabase,
+ module_file_path: &VfsPath,
+) -> Option<VfsPath> {
+ let directory_with_module_path = module_file_path.parent()?;
+ let (name, ext) = module_file_path.name_and_extension()?;
+ if ext != Some("rs") {
+ return None;
+ }
+ let base_directory = match name {
+ "mod" | "lib" | "main" => Some(directory_with_module_path),
+ regular_rust_file_name => {
+ if matches!(
+ (
+ directory_with_module_path
+ .parent()
+ .as_ref()
+ .and_then(|path| path.name_and_extension()),
+ directory_with_module_path.name_and_extension(),
+ ),
+ (Some(("src", None)), Some(("bin", None)))
+ ) {
+ // files in /src/bin/ can import each other directly
+ Some(directory_with_module_path)
+ } else {
+ directory_with_module_path.join(regular_rust_file_name)
+ }
+ }
+ }?;
+
+ module_chain_to_containing_module_file(module, db)
+ .into_iter()
+ .filter_map(|module| module.name(db))
+ .try_fold(base_directory, |path, name| path.join(&name.to_smol_str()))
+}
+
+fn module_chain_to_containing_module_file(
+ current_module: Module,
+ db: &RootDatabase,
+) -> Vec<Module> {
+ let mut path =
+ iter::successors(Some(current_module), |current_module| current_module.parent(db))
+ .take_while(|current_module| {
+ matches!(current_module.definition_source(db).value, ModuleSource::Module(_))
+ })
+ .collect::<Vec<_>>();
+ path.reverse();
+ path
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::tests::completion_list;
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn lib_module_completion() {
+ check(
+ r#"
+//- /lib.rs
+mod $0
+//- /foo.rs
+fn foo() {}
+//- /foo/ignored_foo.rs
+fn ignored_foo() {}
+//- /bar/mod.rs
+fn bar() {}
+//- /bar/ignored_bar.rs
+fn ignored_bar() {}
+"#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_module_completion_with_module_body() {
+ check(
+ r#"
+//- /lib.rs
+mod $0 {
+
+}
+//- /foo.rs
+fn foo() {}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn main_module_completion() {
+ check(
+ r#"
+//- /main.rs
+mod $0
+//- /foo.rs
+fn foo() {}
+//- /foo/ignored_foo.rs
+fn ignored_foo() {}
+//- /bar/mod.rs
+fn bar() {}
+//- /bar/ignored_bar.rs
+fn ignored_bar() {}
+"#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+
+ #[test]
+ fn main_test_module_completion() {
+ check(
+ r#"
+//- /main.rs
+mod tests {
+ mod $0;
+}
+//- /tests/foo.rs
+fn foo() {}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+ }
+
+ #[test]
+ fn directly_nested_module_completion() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+mod $0;
+//- /foo/bar.rs
+fn bar() {}
+//- /foo/bar/ignored_bar.rs
+fn ignored_bar() {}
+//- /foo/baz/mod.rs
+fn baz() {}
+//- /foo/moar/ignored_moar.rs
+fn ignored_moar() {}
+"#,
+ expect![[r#"
+ md bar
+ md baz
+ "#]],
+ );
+ }
+
+ #[test]
+ fn nested_in_source_module_completion() {
+ check(
+ r#"
+//- /lib.rs
+mod foo;
+//- /foo.rs
+mod bar {
+ mod $0
+}
+//- /foo/bar/baz.rs
+fn baz() {}
+"#,
+ expect![[r#"
+ md baz;
+ "#]],
+ );
+ }
+
+ // FIXME binary modules are not supported in tests properly
+ // Binary modules are a bit special, they allow importing the modules from `/src/bin`
+ // and that's why are good to test two things:
+ // * no cycles are allowed in mod declarations
+ // * no modules from the parent directory are proposed
+ // Unfortunately, binary modules support is in cargo not rustc,
+ // hence the test does not work now
+ //
+ // #[test]
+ // fn regular_bin_module_completion() {
+ // check(
+ // r#"
+ // //- /src/bin.rs
+ // fn main() {}
+ // //- /src/bin/foo.rs
+ // mod $0
+ // //- /src/bin/bar.rs
+ // fn bar() {}
+ // //- /src/bin/bar/bar_ignored.rs
+ // fn bar_ignored() {}
+ // "#,
+ // expect![[r#"
+ // md bar;
+ // "#]],foo
+ // );
+ // }
+
+ #[test]
+ fn already_declared_bin_module_completion_omitted() {
+ check(
+ r#"
+//- /src/bin.rs crate:main
+fn main() {}
+//- /src/bin/foo.rs
+mod $0
+//- /src/bin/bar.rs
+mod foo;
+fn bar() {}
+//- /src/bin/bar/bar_ignored.rs
+fn bar_ignored() {}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn name_partially_typed() {
+ check(
+ r#"
+//- /lib.rs
+mod f$0
+//- /foo.rs
+fn foo() {}
+//- /foo/ignored_foo.rs
+fn ignored_foo() {}
+//- /bar/mod.rs
+fn bar() {}
+//- /bar/ignored_bar.rs
+fn ignored_bar() {}
+"#,
+ expect![[r#"
+ md bar;
+ md foo;
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs
new file mode 100644
index 000000000..71d2d9d43
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs
@@ -0,0 +1,185 @@
+//! Completes constants and paths in unqualified patterns.
+
+use hir::{db::DefDatabase, AssocItem, ScopeDef};
+use syntax::ast::Pat;
+
+use crate::{
+ context::{PathCompletionCtx, PatternContext, PatternRefutability, Qualified},
+ CompletionContext, Completions,
+};
+
+/// Completes constants and paths in unqualified patterns.
+pub(crate) fn complete_pattern(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) {
+ match pattern_ctx.parent_pat.as_ref() {
+ Some(Pat::RangePat(_) | Pat::BoxPat(_)) => (),
+ Some(Pat::RefPat(r)) => {
+ if r.mut_token().is_none() {
+ acc.add_keyword(ctx, "mut");
+ }
+ }
+ _ => {
+ let tok = ctx.token.text_range().start();
+ match (pattern_ctx.ref_token.as_ref(), pattern_ctx.mut_token.as_ref()) {
+ (None, None) => {
+ acc.add_keyword(ctx, "ref");
+ acc.add_keyword(ctx, "mut");
+ }
+ (None, Some(m)) if tok < m.text_range().start() => {
+ acc.add_keyword(ctx, "ref");
+ }
+ (Some(r), None) if tok > r.text_range().end() => {
+ acc.add_keyword(ctx, "mut");
+ }
+ _ => (),
+ }
+ }
+ }
+
+ if pattern_ctx.record_pat.is_some() {
+ return;
+ }
+
+ let refutable = pattern_ctx.refutability == PatternRefutability::Refutable;
+ let single_variant_enum = |enum_: hir::Enum| ctx.db.enum_data(enum_.into()).variants.len() == 1;
+
+ if let Some(hir::Adt::Enum(e)) =
+ ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
+ {
+ if refutable || single_variant_enum(e) {
+ super::enum_variants_with_paths(
+ acc,
+ ctx,
+ e,
+ &pattern_ctx.impl_,
+ |acc, ctx, variant, path| {
+ acc.add_qualified_variant_pat(ctx, pattern_ctx, variant, path);
+ },
+ );
+ }
+ }
+
+ // FIXME: ideally, we should look at the type we are matching against and
+ // suggest variants + auto-imports
+ ctx.process_all_names(&mut |name, res| {
+ let add_simple_path = match res {
+ hir::ScopeDef::ModuleDef(def) => match def {
+ hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
+ acc.add_struct_pat(ctx, pattern_ctx, strukt, Some(name.clone()));
+ true
+ }
+ hir::ModuleDef::Variant(variant)
+ if refutable || single_variant_enum(variant.parent_enum(ctx.db)) =>
+ {
+ acc.add_variant_pat(ctx, pattern_ctx, None, variant, Some(name.clone()));
+ true
+ }
+ hir::ModuleDef::Adt(hir::Adt::Enum(e)) => refutable || single_variant_enum(e),
+ hir::ModuleDef::Const(..) => refutable,
+ hir::ModuleDef::Module(..) => true,
+ hir::ModuleDef::Macro(mac) => mac.is_fn_like(ctx.db),
+ _ => false,
+ },
+ hir::ScopeDef::ImplSelfType(impl_) => match impl_.self_ty(ctx.db).as_adt() {
+ Some(hir::Adt::Struct(strukt)) => {
+ acc.add_struct_pat(ctx, pattern_ctx, strukt, Some(name.clone()));
+ true
+ }
+ Some(hir::Adt::Enum(e)) => refutable || single_variant_enum(e),
+ Some(hir::Adt::Union(_)) => true,
+ _ => false,
+ },
+ ScopeDef::GenericParam(hir::GenericParam::ConstParam(_)) => true,
+ ScopeDef::GenericParam(_)
+ | ScopeDef::AdtSelfType(_)
+ | ScopeDef::Local(_)
+ | ScopeDef::Label(_)
+ | ScopeDef::Unknown => false,
+ };
+ if add_simple_path {
+ acc.add_pattern_resolution(ctx, pattern_ctx, name, res);
+ }
+ });
+}
+
+pub(crate) fn complete_pattern_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+) {
+ match qualified {
+ Qualified::With { resolution: Some(resolution), super_chain_len, .. } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ 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 {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
+ mac.is_fn_like(ctx.db)
+ }
+ ScopeDef::ModuleDef(_) => true,
+ _ => false,
+ };
+
+ if add_resolution {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ }
+ }
+ res => {
+ let ty = match res {
+ hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
+ hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Struct(s))) => {
+ s.ty(ctx.db)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
+ e.ty(ctx.db)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Union(u))) => {
+ u.ty(ctx.db)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::BuiltinType(ty)) => ty.ty(ctx.db),
+ _ => return,
+ };
+
+ if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
+ acc.add_enum_variants(ctx, path_ctx, e);
+ }
+
+ ctx.iterate_path_candidates(&ty, |item| match item {
+ AssocItem::TypeAlias(ta) => acc.add_type_alias(ctx, ta),
+ AssocItem::Const(c) => acc.add_const(ctx, c),
+ _ => {}
+ });
+ }
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No => {
+ // this will only be hit if there are brackets or braces, otherwise this will be parsed as an ident pattern
+ ctx.process_all_names(&mut |name, res| {
+ // FIXME: we should check what kind of pattern we are in and filter accordingly
+ let add_completion = match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
+ ScopeDef::ModuleDef(hir::ModuleDef::Adt(_)) => true,
+ ScopeDef::ModuleDef(hir::ModuleDef::Variant(_)) => true,
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) => true,
+ ScopeDef::ImplSelfType(_) => true,
+ _ => false,
+ };
+ if add_completion {
+ acc.add_path_resolution(ctx, path_ctx, name, res);
+ }
+ });
+
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
new file mode 100644
index 000000000..9a891cea2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
@@ -0,0 +1,616 @@
+//! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
+
+mod format_like;
+
+use hir::{Documentation, HasAttrs};
+use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap};
+use syntax::{
+ ast::{self, AstNode, AstToken},
+ SyntaxKind::{EXPR_STMT, STMT_LIST},
+ TextRange, TextSize,
+};
+use text_edit::TextEdit;
+
+use crate::{
+ completions::postfix::format_like::add_format_like_completions,
+ context::{CompletionContext, DotAccess, DotAccessKind},
+ item::{Builder, CompletionRelevancePostfixMatch},
+ CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
+};
+
+pub(crate) fn complete_postfix(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_access: &DotAccess,
+) {
+ if !ctx.config.enable_postfix_completions {
+ return;
+ }
+
+ let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match dot_access {
+ DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. } => (
+ it,
+ &ty.original,
+ match *kind {
+ DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
+ receiver_is_ambiguous_float_literal
+ }
+ DotAccessKind::Method { .. } => false,
+ },
+ ),
+ _ => return,
+ };
+
+ let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
+
+ let cap = match ctx.config.snippet_cap {
+ Some(it) => it,
+ None => return,
+ };
+
+ let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
+ Some(it) => it,
+ None => return,
+ };
+
+ if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop() {
+ if receiver_ty.impls_trait(ctx.db, drop_trait, &[]) {
+ if let &[hir::AssocItem::Function(drop_fn)] = &*drop_trait.items(ctx.db) {
+ cov_mark::hit!(postfix_drop_completion);
+ // FIXME: check that `drop` is in scope, use fully qualified path if it isn't/if shadowed
+ let mut item = postfix_snippet(
+ "drop",
+ "fn drop(&mut self)",
+ &format!("drop($0{})", receiver_text),
+ );
+ item.set_documentation(drop_fn.docs(ctx.db));
+ item.add_to(acc);
+ }
+ }
+ }
+
+ if !ctx.config.snippets.is_empty() {
+ add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
+ }
+
+ let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
+ if let Some(try_enum) = &try_enum {
+ match try_enum {
+ TryEnum::Result => {
+ postfix_snippet(
+ "ifl",
+ "if let Ok {}",
+ &format!("if let Ok($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+
+ postfix_snippet(
+ "while",
+ "while let Ok {}",
+ &format!("while let Ok($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ TryEnum::Option => {
+ postfix_snippet(
+ "ifl",
+ "if let Some {}",
+ &format!("if let Some($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+
+ postfix_snippet(
+ "while",
+ "while let Some {}",
+ &format!("while let Some($1) = {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ }
+ } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
+ postfix_snippet("if", "if expr {}", &format!("if {} {{\n $0\n}}", receiver_text))
+ .add_to(acc);
+ postfix_snippet(
+ "while",
+ "while expr {}",
+ &format!("while {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ postfix_snippet("not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
+ } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() {
+ if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
+ postfix_snippet(
+ "for",
+ "for ele in expr {}",
+ &format!("for ele in {} {{\n $0\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ }
+
+ postfix_snippet("ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
+ postfix_snippet("refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
+
+ // The rest of the postfix completions create an expression that moves an argument,
+ // so it's better to consider references now to avoid breaking the compilation
+ let dot_receiver = include_references(dot_receiver);
+ let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal);
+ let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
+ Some(it) => it,
+ None => return,
+ };
+
+ match try_enum {
+ Some(try_enum) => match try_enum {
+ TryEnum::Result => {
+ postfix_snippet(
+ "match",
+ "match expr {}",
+ &format!("match {} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ TryEnum::Option => {
+ postfix_snippet(
+ "match",
+ "match expr {}",
+ &format!(
+ "match {} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}",
+ receiver_text
+ ),
+ )
+ .add_to(acc);
+ }
+ },
+ None => {
+ postfix_snippet(
+ "match",
+ "match expr {}",
+ &format!("match {} {{\n ${{1:_}} => {{$0}},\n}}", receiver_text),
+ )
+ .add_to(acc);
+ }
+ }
+
+ postfix_snippet("box", "Box::new(expr)", &format!("Box::new({})", receiver_text)).add_to(acc);
+ postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); // fixme
+ postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{})", receiver_text)).add_to(acc);
+ postfix_snippet("call", "function(expr)", &format!("${{1}}({})", receiver_text)).add_to(acc);
+
+ if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
+ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
+ postfix_snippet("let", "let", &format!("let $0 = {};", receiver_text)).add_to(acc);
+ postfix_snippet("letm", "let mut", &format!("let mut $0 = {};", receiver_text))
+ .add_to(acc);
+ }
+ }
+
+ if let ast::Expr::Literal(literal) = dot_receiver.clone() {
+ if let Some(literal_text) = ast::String::cast(literal.token()) {
+ add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
+ }
+ }
+}
+
+fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
+ let text = if receiver_is_ambiguous_float_literal {
+ let text = receiver.syntax().text();
+ let without_dot = ..text.len() - TextSize::of('.');
+ text.slice(without_dot).to_string()
+ } else {
+ receiver.to_string()
+ };
+
+ // The receiver texts should be interpreted as-is, as they are expected to be
+ // normal Rust expressions. We escape '\' and '$' so they don't get treated as
+ // snippet-specific constructs.
+ //
+ // Note that we don't need to escape the other characters that can be escaped,
+ // because they wouldn't be treated as snippet-specific constructs without '$'.
+ text.replace('\\', "\\\\").replace('$', "\\$")
+}
+
+fn include_references(initial_element: &ast::Expr) -> ast::Expr {
+ let mut resulting_element = initial_element.clone();
+ while let Some(parent_ref_element) =
+ resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
+ {
+ resulting_element = ast::Expr::from(parent_ref_element);
+ }
+ resulting_element
+}
+
+fn build_postfix_snippet_builder<'ctx>(
+ ctx: &'ctx CompletionContext<'_>,
+ cap: SnippetCap,
+ receiver: &'ctx ast::Expr,
+) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
+ let receiver_syntax = receiver.syntax();
+ let receiver_range = ctx.sema.original_range_opt(receiver_syntax)?.range;
+ if ctx.source_range().end() < receiver_range.start() {
+ // This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping.
+ return None;
+ }
+ let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
+
+ // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that
+ // can't be annotated for the closure, hence fix it by constructing it without the Option first
+ fn build<'ctx>(
+ ctx: &'ctx CompletionContext<'_>,
+ cap: SnippetCap,
+ delete_range: TextRange,
+ ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
+ move |label, detail, snippet| {
+ let edit = TextEdit::replace(delete_range, snippet.to_string());
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+ item.detail(detail).snippet_edit(cap, edit);
+ let postfix_match = if ctx.original_token.text() == label {
+ cov_mark::hit!(postfix_exact_match_is_high_priority);
+ Some(CompletionRelevancePostfixMatch::Exact)
+ } else {
+ cov_mark::hit!(postfix_inexact_match_is_low_priority);
+ Some(CompletionRelevancePostfixMatch::NonExact)
+ };
+ let relevance = CompletionRelevance { postfix_match, ..Default::default() };
+ item.set_relevance(relevance);
+ item
+ }
+ }
+ Some(build(ctx, cap, delete_range))
+}
+
+fn add_custom_postfix_completions(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
+ receiver_text: &str,
+) -> Option<()> {
+ if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
+ return None;
+ }
+ ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(
+ |(trigger, snippet)| {
+ let imports = match snippet.imports(ctx) {
+ Some(imports) => imports,
+ None => return,
+ };
+ let body = snippet.postfix_snippet(receiver_text);
+ let mut builder =
+ postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
+ builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
+ for import in imports.into_iter() {
+ builder.add_import(import);
+ }
+ builder.add_to(acc);
+ },
+ );
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::{expect, Expect};
+
+ use crate::{
+ tests::{check_edit, check_edit_with_config, completion_list, TEST_CONFIG},
+ CompletionConfig, Snippet,
+ };
+
+ fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+ }
+
+ #[test]
+ fn postfix_completion_works_for_trivial_path_expression() {
+ check(
+ r#"
+fn main() {
+ let bar = true;
+ bar.$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_completion_works_for_function_calln() {
+ check(
+ r#"
+fn foo(elt: bool) -> bool {
+ !elt
+}
+
+fn main() {
+ let bar = true;
+ foo(bar.$0)
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_type_filtering() {
+ check(
+ r#"
+fn main() {
+ let bar: u8 = 12;
+ bar.$0
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+ }
+
+ #[test]
+ fn let_middle_block() {
+ check(
+ r#"
+fn main() {
+ baz.l$0
+ res
+}
+"#,
+ expect![[r#"
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn if if expr {}
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn not !expr
+ sn ref &expr
+ sn refm &mut expr
+ sn while while expr {}
+ "#]],
+ );
+ }
+
+ #[test]
+ fn option_iflet() {
+ check_edit(
+ "ifl",
+ r#"
+//- minicore: option
+fn main() {
+ let bar = Some(true);
+ bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = Some(true);
+ if let Some($1) = bar {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn result_match() {
+ check_edit(
+ "match",
+ r#"
+//- minicore: result
+fn main() {
+ let bar = Ok(true);
+ bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = Ok(true);
+ match bar {
+ Ok(${1:_}) => {$2},
+ Err(${3:_}) => {$0},
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn postfix_completion_works_for_ambiguous_float_literal() {
+ check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
+ }
+
+ #[test]
+ fn works_in_simple_macro() {
+ check_edit(
+ "dbg",
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn main() {
+ let bar: u8 = 12;
+ m!(bar.d$0)
+}
+"#,
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn main() {
+ let bar: u8 = 12;
+ m!(dbg!(bar))
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn postfix_completion_for_references() {
+ check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
+ check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
+ check_edit(
+ "ifl",
+ r#"
+//- minicore: option
+fn main() {
+ let bar = &Some(true);
+ bar.$0
+}
+"#,
+ r#"
+fn main() {
+ let bar = &Some(true);
+ if let Some($1) = bar {
+ $0
+}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn custom_postfix_completion() {
+ let config = CompletionConfig {
+ snippets: vec![Snippet::new(
+ &[],
+ &["break".into()],
+ &["ControlFlow::Break(${receiver})".into()],
+ "",
+ &["core::ops::ControlFlow".into()],
+ crate::SnippetScope::Expr,
+ )
+ .unwrap()],
+ ..TEST_CONFIG
+ };
+
+ check_edit_with_config(
+ config.clone(),
+ "break",
+ r#"
+//- minicore: try
+fn main() { 42.$0 }
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break(42) }
+"#,
+ );
+
+ // The receiver texts should be escaped, see comments in `get_receiver_text()`
+ // for detail.
+ //
+ // Note that the last argument is what *lsp clients would see* rather than
+ // what users would see. Unescaping happens thereafter.
+ check_edit_with_config(
+ config.clone(),
+ "break",
+ r#"
+//- minicore: try
+fn main() { '\\'.$0 }
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break('\\\\') }
+"#,
+ );
+
+ check_edit_with_config(
+ config.clone(),
+ "break",
+ r#"
+//- minicore: try
+fn main() {
+ match true {
+ true => "${1:placeholder}",
+ false => "\$",
+ }.$0
+}
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() {
+ ControlFlow::Break(match true {
+ true => "\${1:placeholder}",
+ false => "\\\$",
+ })
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn postfix_completion_for_format_like_strings() {
+ check_edit(
+ "format",
+ r#"fn main() { "{some_var:?}".$0 }"#,
+ r#"fn main() { format!("{:?}", some_var) }"#,
+ );
+ check_edit(
+ "panic",
+ r#"fn main() { "Panic with {a}".$0 }"#,
+ r#"fn main() { panic!("Panic with {}", a) }"#,
+ );
+ check_edit(
+ "println",
+ r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
+ r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
+ );
+ check_edit(
+ "loge",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::error!("{}", 2+2) }"#,
+ );
+ check_edit(
+ "logt",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::trace!("{}", 2+2) }"#,
+ );
+ check_edit(
+ "logd",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::debug!("{}", 2+2) }"#,
+ );
+ check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
+ check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
+ check_edit(
+ "loge",
+ r#"fn main() { "{2+2}".$0 }"#,
+ r#"fn main() { log::error!("{}", 2+2) }"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs
new file mode 100644
index 000000000..6b94347e0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs
@@ -0,0 +1,311 @@
+// Feature: Format String Completion
+//
+// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
+//
+// The following postfix snippets are available:
+//
+// * `format` -> `format!(...)`
+// * `panic` -> `panic!(...)`
+// * `println` -> `println!(...)`
+// * `log`:
+// ** `logd` -> `log::debug!(...)`
+// ** `logt` -> `log::trace!(...)`
+// ** `logi` -> `log::info!(...)`
+// ** `logw` -> `log::warn!(...)`
+// ** `loge` -> `log::error!(...)`
+//
+// image::https://user-images.githubusercontent.com/48062697/113020656-b560f500-917a-11eb-87de-02991f61beb8.gif[]
+
+use ide_db::SnippetCap;
+use syntax::ast::{self, AstToken};
+
+use crate::{
+ completions::postfix::build_postfix_snippet_builder, context::CompletionContext, Completions,
+};
+
+/// Mapping ("postfix completion item" => "macro to use")
+static KINDS: &[(&str, &str)] = &[
+ ("format", "format!"),
+ ("panic", "panic!"),
+ ("println", "println!"),
+ ("eprintln", "eprintln!"),
+ ("logd", "log::debug!"),
+ ("logt", "log::trace!"),
+ ("logi", "log::info!"),
+ ("logw", "log::warn!"),
+ ("loge", "log::error!"),
+];
+
+pub(crate) fn add_format_like_completions(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ dot_receiver: &ast::Expr,
+ cap: SnippetCap,
+ receiver_text: &ast::String,
+) {
+ let input = match string_literal_contents(receiver_text) {
+ // It's not a string literal, do not parse input.
+ Some(input) => input,
+ None => return,
+ };
+
+ let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
+ Some(it) => it,
+ None => return,
+ };
+ let mut parser = FormatStrParser::new(input);
+
+ if parser.parse().is_ok() {
+ for (label, macro_name) in KINDS {
+ let snippet = parser.to_suggestion(macro_name);
+
+ postfix_snippet(label, macro_name, &snippet).add_to(acc);
+ }
+ }
+}
+
+/// Checks whether provided item is a string literal.
+fn string_literal_contents(item: &ast::String) -> Option<String> {
+ let item = item.text();
+ if item.len() >= 2 && item.starts_with('\"') && item.ends_with('\"') {
+ return Some(item[1..item.len() - 1].to_owned());
+ }
+
+ None
+}
+
+/// Parser for a format-like string. It is more allowing in terms of string contents,
+/// as we expect variable placeholders to be filled with expressions.
+#[derive(Debug)]
+pub(crate) struct FormatStrParser {
+ input: String,
+ output: String,
+ extracted_expressions: Vec<String>,
+ state: State,
+ parsed: bool,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum State {
+ NotExpr,
+ MaybeExpr,
+ Expr,
+ MaybeIncorrect,
+ FormatOpts,
+}
+
+impl FormatStrParser {
+ pub(crate) fn new(input: String) -> Self {
+ Self {
+ input,
+ output: String::new(),
+ extracted_expressions: Vec::new(),
+ state: State::NotExpr,
+ parsed: false,
+ }
+ }
+
+ pub(crate) fn parse(&mut self) -> Result<(), ()> {
+ let mut current_expr = String::new();
+
+ let mut placeholder_id = 1;
+
+ // Count of open braces inside of an expression.
+ // We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
+ // "{MyStruct { val_a: 0, val_b: 1 }}".
+ let mut inexpr_open_count = 0;
+
+ // We need to escape '\' and '$'. See the comments on `get_receiver_text()` for detail.
+ let mut chars = self.input.chars().peekable();
+ while let Some(chr) = chars.next() {
+ match (self.state, chr) {
+ (State::NotExpr, '{') => {
+ self.output.push(chr);
+ self.state = State::MaybeExpr;
+ }
+ (State::NotExpr, '}') => {
+ self.output.push(chr);
+ self.state = State::MaybeIncorrect;
+ }
+ (State::NotExpr, _) => {
+ if matches!(chr, '\\' | '$') {
+ self.output.push('\\');
+ }
+ self.output.push(chr);
+ }
+ (State::MaybeIncorrect, '}') => {
+ // It's okay, we met "}}".
+ self.output.push(chr);
+ self.state = State::NotExpr;
+ }
+ (State::MaybeIncorrect, _) => {
+ // Error in the string.
+ return Err(());
+ }
+ (State::MaybeExpr, '{') => {
+ self.output.push(chr);
+ self.state = State::NotExpr;
+ }
+ (State::MaybeExpr, '}') => {
+ // This is an empty sequence '{}'. Replace it with placeholder.
+ self.output.push(chr);
+ self.extracted_expressions.push(format!("${}", placeholder_id));
+ placeholder_id += 1;
+ self.state = State::NotExpr;
+ }
+ (State::MaybeExpr, _) => {
+ if matches!(chr, '\\' | '$') {
+ current_expr.push('\\');
+ }
+ current_expr.push(chr);
+ self.state = State::Expr;
+ }
+ (State::Expr, '}') => {
+ if inexpr_open_count == 0 {
+ self.output.push(chr);
+ self.extracted_expressions.push(current_expr.trim().into());
+ current_expr = String::new();
+ self.state = State::NotExpr;
+ } else {
+ // We're closing one brace met before inside of the expression.
+ current_expr.push(chr);
+ inexpr_open_count -= 1;
+ }
+ }
+ (State::Expr, ':') if chars.peek().copied() == Some(':') => {
+ // path seperator
+ current_expr.push_str("::");
+ chars.next();
+ }
+ (State::Expr, ':') => {
+ if inexpr_open_count == 0 {
+ // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
+ self.output.push(chr);
+ self.extracted_expressions.push(current_expr.trim().into());
+ current_expr = String::new();
+ self.state = State::FormatOpts;
+ } else {
+ // We're inside of braced expression, assume that it's a struct field name/value delimeter.
+ current_expr.push(chr);
+ }
+ }
+ (State::Expr, '{') => {
+ current_expr.push(chr);
+ inexpr_open_count += 1;
+ }
+ (State::Expr, _) => {
+ if matches!(chr, '\\' | '$') {
+ current_expr.push('\\');
+ }
+ current_expr.push(chr);
+ }
+ (State::FormatOpts, '}') => {
+ self.output.push(chr);
+ self.state = State::NotExpr;
+ }
+ (State::FormatOpts, _) => {
+ if matches!(chr, '\\' | '$') {
+ self.output.push('\\');
+ }
+ self.output.push(chr);
+ }
+ }
+ }
+
+ if self.state != State::NotExpr {
+ return Err(());
+ }
+
+ self.parsed = true;
+ Ok(())
+ }
+
+ pub(crate) fn to_suggestion(&self, macro_name: &str) -> String {
+ assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
+
+ let expressions_as_string = self.extracted_expressions.join(", ");
+ format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use expect_test::{expect, Expect};
+
+ fn check(input: &str, expect: &Expect) {
+ let mut parser = FormatStrParser::new((*input).to_owned());
+ let outcome_repr = if parser.parse().is_ok() {
+ // Parsing should be OK, expected repr is "string; expr_1, expr_2".
+ if parser.extracted_expressions.is_empty() {
+ parser.output
+ } else {
+ format!("{}; {}", parser.output, parser.extracted_expressions.join(", "))
+ }
+ } else {
+ // Parsing should fail, expected repr is "-".
+ "-".to_owned()
+ };
+
+ expect.assert_eq(&outcome_repr);
+ }
+
+ #[test]
+ fn format_str_parser() {
+ let test_vector = &[
+ ("no expressions", expect![["no expressions"]]),
+ (r"no expressions with \$0$1", expect![r"no expressions with \\\$0\$1"]),
+ ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
+ ("{expr:?}", expect![["{:?}; expr"]]),
+ ("{expr:1$}", expect![[r"{:1\$}; expr"]]),
+ ("{$0}", expect![[r"{}; \$0"]]),
+ ("{malformed", expect![["-"]]),
+ ("malformed}", expect![["-"]]),
+ ("{{correct", expect![["{{correct"]]),
+ ("correct}}", expect![["correct}}"]]),
+ ("{correct}}}", expect![["{}}}; correct"]]),
+ ("{correct}}}}}", expect![["{}}}}}; correct"]]),
+ ("{incorrect}}", expect![["-"]]),
+ ("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]),
+ ("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]),
+ (
+ "{SomeStruct { val_a: 0, val_b: 1 }}",
+ expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]],
+ ),
+ ("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]),
+ (
+ "{SomeStruct { val_a: 0, val_b: 1 }:?}",
+ expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
+ ),
+ ("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
+ ("{strsim::jaro_winkle(a)}", expect![["{}; strsim::jaro_winkle(a)"]]),
+ ("{foo::bar::baz()}", expect![["{}; foo::bar::baz()"]]),
+ ("{foo::bar():?}", expect![["{:?}; foo::bar()"]]),
+ ];
+
+ for (input, output) in test_vector {
+ check(input, output)
+ }
+ }
+
+ #[test]
+ fn test_into_suggestion() {
+ let test_vector = &[
+ ("println!", "{}", r#"println!("{}", $1)"#),
+ ("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
+ (
+ "log::info!",
+ "{} {expr} {} {2 + 2}",
+ r#"log::info!("{} {} {} {}", $1, expr, $2, 2 + 2)"#,
+ ),
+ ("format!", "{expr:?}", r#"format!("{:?}", expr)"#),
+ ];
+
+ for (kind, input, output) in test_vector {
+ let mut parser = FormatStrParser::new((*input).to_owned());
+ parser.parse().expect("Parsing must succeed");
+
+ assert_eq!(&parser.to_suggestion(*kind), output);
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs
new file mode 100644
index 000000000..1c9042390
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs
@@ -0,0 +1,369 @@
+//! Complete fields in record literals and patterns.
+use ide_db::SymbolKind;
+use syntax::ast::{self, Expr};
+
+use crate::{
+ context::{DotAccess, DotAccessKind, ExprCtx, PathCompletionCtx, PatternContext, Qualified},
+ CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
+ CompletionRelevancePostfixMatch, Completions,
+};
+
+pub(crate) fn complete_record_pattern_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ pattern_ctx: &PatternContext,
+) {
+ if let PatternContext { record_pat: Some(record_pat), .. } = pattern_ctx {
+ complete_fields(acc, ctx, ctx.sema.record_pattern_missing_fields(record_pat));
+ }
+}
+
+pub(crate) fn complete_record_expr_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ record_expr: &ast::RecordExpr,
+ &dot_prefix: &bool,
+) {
+ let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
+
+ let missing_fields = match ty.as_ref().and_then(|t| t.original.as_adt()) {
+ Some(hir::Adt::Union(un)) => {
+ // ctx.sema.record_literal_missing_fields will always return
+ // an empty Vec on a union literal. This is normally
+ // reasonable, but here we'd like to present the full list
+ // of fields if the literal is empty.
+ let were_fields_specified =
+ record_expr.record_expr_field_list().and_then(|fl| fl.fields().next()).is_some();
+
+ match were_fields_specified {
+ false => un.fields(ctx.db).into_iter().map(|f| (f, f.ty(ctx.db))).collect(),
+ true => return,
+ }
+ }
+ _ => {
+ let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
+ add_default_update(acc, ctx, ty, &missing_fields);
+ if dot_prefix {
+ let mut item =
+ CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), "..");
+ item.insert_text(".");
+ item.add_to(acc);
+ return;
+ }
+ missing_fields
+ }
+ };
+ complete_fields(acc, ctx, missing_fields);
+}
+
+// FIXME: This should probably be part of complete_path_expr
+pub(crate) fn complete_record_expr_func_update(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ expr_ctx: &ExprCtx,
+) {
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ if let ExprCtx { is_func_update: Some(record_expr), .. } = expr_ctx {
+ let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
+
+ match ty.as_ref().and_then(|t| t.original.as_adt()) {
+ Some(hir::Adt::Union(_)) => (),
+ _ => {
+ let missing_fields = ctx.sema.record_literal_missing_fields(record_expr);
+ add_default_update(acc, ctx, ty, &missing_fields);
+ }
+ };
+ }
+}
+
+fn add_default_update(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ ty: Option<hir::TypeInfo>,
+ missing_fields: &[(hir::Field, hir::Type)],
+) {
+ let default_trait = ctx.famous_defs().core_default_Default();
+ let impl_default_trait = default_trait
+ .zip(ty.as_ref())
+ .map_or(false, |(default_trait, ty)| ty.original.impls_trait(ctx.db, default_trait, &[]));
+ if impl_default_trait && !missing_fields.is_empty() {
+ // FIXME: This should make use of scope_def like completions so we get all the other goodies
+ let completion_text = "..Default::default()";
+ let mut item = CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
+ let completion_text =
+ completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text);
+ item.insert_text(completion_text).set_relevance(CompletionRelevance {
+ postfix_match: Some(CompletionRelevancePostfixMatch::Exact),
+ ..Default::default()
+ });
+ item.add_to(acc);
+ }
+}
+
+fn complete_fields(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ missing_fields: Vec<(hir::Field, hir::Type)>,
+) {
+ for (field, ty) in missing_fields {
+ acc.add_field(
+ ctx,
+ &DotAccess {
+ receiver: None,
+ receiver_ty: None,
+ kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
+ },
+ None,
+ field,
+ &ty,
+ );
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_edit;
+
+ #[test]
+ fn literal_struct_completion_edit() {
+ check_edit(
+ "FooDesc {…}",
+ r#"
+struct FooDesc { pub bar: bool }
+
+fn create_foo(foo_desc: &FooDesc) -> () { () }
+
+fn baz() {
+ let foo = create_foo(&$0);
+}
+ "#,
+ r#"
+struct FooDesc { pub bar: bool }
+
+fn create_foo(foo_desc: &FooDesc) -> () { () }
+
+fn baz() {
+ let foo = create_foo(&FooDesc { bar: ${1:()} }$0);
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn literal_struct_impl_self_completion() {
+ check_edit(
+ "Self {…}",
+ r#"
+struct Foo {
+ bar: u64,
+}
+
+impl Foo {
+ fn new() -> Foo {
+ Self$0
+ }
+}
+ "#,
+ r#"
+struct Foo {
+ bar: u64,
+}
+
+impl Foo {
+ fn new() -> Foo {
+ Self { bar: ${1:()} }$0
+ }
+}
+ "#,
+ );
+
+ check_edit(
+ "Self(…)",
+ r#"
+mod submod {
+ pub struct Foo(pub u64);
+}
+
+impl submod::Foo {
+ fn new() -> submod::Foo {
+ Self$0
+ }
+}
+ "#,
+ r#"
+mod submod {
+ pub struct Foo(pub u64);
+}
+
+impl submod::Foo {
+ fn new() -> submod::Foo {
+ Self(${1:()})$0
+ }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn literal_struct_completion_from_sub_modules() {
+ check_edit(
+ "submod::Struct {…}",
+ r#"
+mod submod {
+ pub struct Struct {
+ pub a: u64,
+ }
+}
+
+fn f() -> submod::Struct {
+ Stru$0
+}
+ "#,
+ r#"
+mod submod {
+ pub struct Struct {
+ pub a: u64,
+ }
+}
+
+fn f() -> submod::Struct {
+ submod::Struct { a: ${1:()} }$0
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn literal_struct_complexion_module() {
+ check_edit(
+ "FooDesc {…}",
+ r#"
+mod _69latrick {
+ pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
+ pub fn create_foo(foo_desc: &FooDesc) -> () { () }
+}
+
+fn baz() {
+ use _69latrick::*;
+
+ let foo = create_foo(&$0);
+}
+ "#,
+ r#"
+mod _69latrick {
+ pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, pub bar: bool }
+ pub fn create_foo(foo_desc: &FooDesc) -> () { () }
+}
+
+fn baz() {
+ use _69latrick::*;
+
+ let foo = create_foo(&FooDesc { six: ${1:()}, neuf: ${2:()}, bar: ${3:()} }$0);
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn default_completion_edit() {
+ check_edit(
+ "..Default::default()",
+ r#"
+//- minicore: default
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ .$0
+ };
+}
+"#,
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..Default::default()
+ };
+}
+"#,
+ );
+ check_edit(
+ "..Default::default()",
+ r#"
+//- minicore: default
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ $0
+ };
+}
+"#,
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..Default::default()
+ };
+}
+"#,
+ );
+ check_edit(
+ "..Default::default()",
+ r#"
+//- minicore: default
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..$0
+ };
+}
+"#,
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+impl Default for Struct {
+ fn default() -> Self {}
+}
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ ..Default::default()
+ };
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs
new file mode 100644
index 000000000..66adb4286
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs
@@ -0,0 +1,189 @@
+//! This file provides snippet completions, like `pd` => `eprintln!(...)`.
+
+use hir::Documentation;
+use ide_db::{imports::insert_use::ImportScope, SnippetCap};
+
+use crate::{
+ context::{ExprCtx, ItemListKind, PathCompletionCtx, Qualified},
+ item::Builder,
+ CompletionContext, CompletionItem, CompletionItemKind, Completions, SnippetScope,
+};
+
+pub(crate) fn complete_expr_snippet(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ &ExprCtx { in_block_expr, .. }: &ExprCtx,
+) {
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+
+ let cap = match ctx.config.snippet_cap {
+ Some(it) => it,
+ None => return,
+ };
+
+ if !ctx.config.snippets.is_empty() {
+ add_custom_completions(acc, ctx, cap, SnippetScope::Expr);
+ }
+
+ if in_block_expr {
+ snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
+ snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
+ let item = snippet(
+ ctx,
+ cap,
+ "macro_rules",
+ "\
+macro_rules! $1 {
+ ($2) => {
+ $0
+ };
+}",
+ );
+ item.add_to(acc);
+ }
+}
+
+pub(crate) fn complete_item_snippet(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ kind: &ItemListKind,
+) {
+ if !matches!(path_ctx.qualified, Qualified::No) {
+ return;
+ }
+ if !ctx.qualifier_ctx.none() {
+ return;
+ }
+ let cap = match ctx.config.snippet_cap {
+ Some(it) => it,
+ None => return,
+ };
+
+ if !ctx.config.snippets.is_empty() {
+ add_custom_completions(acc, ctx, cap, SnippetScope::Item);
+ }
+
+ // Test-related snippets shouldn't be shown in blocks.
+ if let ItemListKind::SourceFile | ItemListKind::Module = kind {
+ let mut item = snippet(
+ ctx,
+ cap,
+ "tmod (Test module)",
+ "\
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn ${1:test_name}() {
+ $0
+ }
+}",
+ );
+ item.lookup_by("tmod");
+ item.add_to(acc);
+
+ let mut item = snippet(
+ ctx,
+ cap,
+ "tfn (Test function)",
+ "\
+#[test]
+fn ${1:feature}() {
+ $0
+}",
+ );
+ item.lookup_by("tfn");
+ item.add_to(acc);
+
+ let item = snippet(
+ ctx,
+ cap,
+ "macro_rules",
+ "\
+macro_rules! $1 {
+ ($2) => {
+ $0
+ };
+}",
+ );
+ item.add_to(acc);
+ }
+}
+
+fn snippet(ctx: &CompletionContext<'_>, cap: SnippetCap, label: &str, snippet: &str) -> Builder {
+ let mut item = CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
+ item.insert_snippet(cap, snippet);
+ item
+}
+
+fn add_custom_completions(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ cap: SnippetCap,
+ scope: SnippetScope,
+) -> Option<()> {
+ if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
+ return None;
+ }
+ ctx.config.prefix_snippets().filter(|(_, snip)| snip.scope == scope).for_each(
+ |(trigger, snip)| {
+ let imports = match snip.imports(ctx) {
+ Some(imports) => imports,
+ None => return,
+ };
+ let body = snip.snippet();
+ let mut builder = snippet(ctx, cap, trigger, &body);
+ builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
+ for import in imports.into_iter() {
+ builder.add_import(import);
+ }
+ builder.set_detail(snip.description.clone());
+ builder.add_to(acc);
+ },
+ );
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ tests::{check_edit_with_config, TEST_CONFIG},
+ CompletionConfig, Snippet,
+ };
+
+ #[test]
+ fn custom_snippet_completion() {
+ check_edit_with_config(
+ CompletionConfig {
+ snippets: vec![Snippet::new(
+ &["break".into()],
+ &[],
+ &["ControlFlow::Break(())".into()],
+ "",
+ &["core::ops::ControlFlow".into()],
+ crate::SnippetScope::Expr,
+ )
+ .unwrap()],
+ ..TEST_CONFIG
+ },
+ "break",
+ r#"
+//- minicore: try
+fn main() { $0 }
+"#,
+ r#"
+use core::ops::ControlFlow;
+
+fn main() { ControlFlow::Break(()) }
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs
new file mode 100644
index 000000000..8f9db2f94
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs
@@ -0,0 +1,246 @@
+//! Completion of names from the current scope in type position.
+
+use hir::{HirDisplay, ScopeDef};
+use syntax::{ast, AstNode, SyntaxKind};
+
+use crate::{
+ context::{PathCompletionCtx, Qualified, TypeAscriptionTarget, TypeLocation},
+ render::render_type_inference,
+ CompletionContext, Completions,
+};
+
+pub(crate) fn complete_type_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ location: &TypeLocation,
+) {
+ let _p = profile::span("complete_type_path");
+
+ let scope_def_applicable = |def| {
+ use hir::{GenericParam::*, ModuleDef::*};
+ match def {
+ ScopeDef::GenericParam(LifetimeParam(_)) | ScopeDef::Label(_) => false,
+ // no values in type places
+ ScopeDef::ModuleDef(Function(_) | Variant(_) | Static(_)) | ScopeDef::Local(_) => false,
+ // unless its a constant in a generic arg list position
+ ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => {
+ matches!(location, TypeLocation::GenericArgList(_))
+ }
+ ScopeDef::ImplSelfType(_) => {
+ !matches!(location, TypeLocation::ImplTarget | TypeLocation::ImplTrait)
+ }
+ // Don't suggest attribute macros and derives.
+ ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db),
+ // Type things are fine
+ ScopeDef::ModuleDef(BuiltinType(_) | Adt(_) | Module(_) | Trait(_) | TypeAlias(_))
+ | ScopeDef::AdtSelfType(_)
+ | ScopeDef::Unknown
+ | ScopeDef::GenericParam(TypeParam(_)) => true,
+ }
+ };
+
+ let add_assoc_item = |acc: &mut Completions, item| match item {
+ hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArgList(_)) => {
+ acc.add_const(ctx, ct)
+ }
+ hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
+ hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
+ };
+
+ match qualified {
+ Qualified::TypeAnchor { ty: None, trait_: None } => ctx
+ .traits_in_scope()
+ .iter()
+ .flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
+ .for_each(|item| add_assoc_item(acc, item)),
+ Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
+ trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
+ }
+ Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // 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::<()>
+ });
+ }
+ Qualified::With { resolution: None, .. } => {}
+ Qualified::With { resolution: Some(resolution), .. } => {
+ // 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 {
+ if scope_def_applicable(def) {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ }
+ }
+ hir::PathResolution::Def(
+ def @ (hir::ModuleDef::Adt(_)
+ | hir::ModuleDef::TypeAlias(_)
+ | hir::ModuleDef::BuiltinType(_)),
+ ) => {
+ let ty = match def {
+ hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
+ hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
+ hir::ModuleDef::BuiltinType(builtin) => builtin.ty(ctx.db),
+ _ => return,
+ };
+
+ // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
+ // (where AssocType is defined on a trait, not an inherent impl)
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+
+ // 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, 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,
+ };
+
+ ctx.iterate_path_candidates(&ty, |item| {
+ add_assoc_item(acc, item);
+ });
+ }
+ _ => (),
+ }
+ }
+ Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
+ Qualified::No => {
+ match location {
+ TypeLocation::TypeBound => {
+ acc.add_nameref_keywords_with_colon(ctx);
+ ctx.process_all_names(&mut |name, res| {
+ let add_resolution = match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => {
+ mac.is_fn_like(ctx.db)
+ }
+ ScopeDef::ModuleDef(
+ hir::ModuleDef::Trait(_) | hir::ModuleDef::Module(_),
+ ) => true,
+ _ => false,
+ };
+ if add_resolution {
+ acc.add_path_resolution(ctx, path_ctx, name, res);
+ }
+ });
+ return;
+ }
+ TypeLocation::GenericArgList(Some(arg_list)) => {
+ let in_assoc_type_arg = ctx
+ .original_token
+ .parent_ancestors()
+ .any(|node| node.kind() == SyntaxKind::ASSOC_TYPE_ARG);
+
+ if !in_assoc_type_arg {
+ if let Some(path_seg) =
+ arg_list.syntax().parent().and_then(ast::PathSegment::cast)
+ {
+ if path_seg
+ .syntax()
+ .ancestors()
+ .find_map(ast::TypeBound::cast)
+ .is_some()
+ {
+ if let Some(hir::PathResolution::Def(hir::ModuleDef::Trait(
+ trait_,
+ ))) = ctx.sema.resolve_path(&path_seg.parent_path())
+ {
+ let arg_idx = arg_list
+ .generic_args()
+ .filter(|arg| {
+ arg.syntax().text_range().end()
+ < ctx.original_token.text_range().start()
+ })
+ .count();
+
+ let n_required_params =
+ trait_.type_or_const_param_count(ctx.sema.db, true);
+ if arg_idx >= n_required_params {
+ trait_
+ .items_with_supertraits(ctx.sema.db)
+ .into_iter()
+ .for_each(|it| {
+ if let hir::AssocItem::TypeAlias(alias) = it {
+ cov_mark::hit!(
+ complete_assoc_type_in_generics_list
+ );
+ acc.add_type_alias_with_eq(ctx, alias);
+ }
+ });
+
+ let n_params =
+ trait_.type_or_const_param_count(ctx.sema.db, false);
+ if arg_idx >= n_params {
+ return; // only show assoc types
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ _ => {}
+ };
+
+ acc.add_nameref_keywords_with_colon(ctx);
+ ctx.process_all_names(&mut |name, def| {
+ if scope_def_applicable(def) {
+ acc.add_path_resolution(ctx, path_ctx, name, def);
+ }
+ });
+ }
+ }
+}
+
+pub(crate) fn complete_ascribed_type(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ ascription: &TypeAscriptionTarget,
+) -> Option<()> {
+ if !path_ctx.is_trivial_path() {
+ return None;
+ }
+ let x = match ascription {
+ TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => {
+ ctx.sema.type_of_pat(pat.as_ref()?)
+ }
+ TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => {
+ ctx.sema.type_of_expr(exp.as_ref()?)
+ }
+ }?
+ .adjusted();
+ let ty_string = x.display_source_code(ctx.db, ctx.module.into()).ok()?;
+ acc.add(render_type_inference(ty_string, ctx));
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs
new file mode 100644
index 000000000..2555c34aa
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs
@@ -0,0 +1,120 @@
+//! Completion for use trees
+
+use hir::ScopeDef;
+use ide_db::{FxHashSet, SymbolKind};
+use syntax::{ast, AstNode};
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx, Qualified},
+ item::Builder,
+ CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
+};
+
+pub(crate) fn complete_use_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, use_tree_parent, .. }: &PathCompletionCtx,
+ name_ref: &Option<ast::NameRef>,
+) {
+ match qualified {
+ Qualified::With { path, resolution: Some(resolution), super_chain_len } => {
+ acc.add_super_keyword(ctx, *super_chain_len);
+
+ // only show `self` in a new use-tree when the qualifier doesn't end in self
+ let not_preceded_by_self = *use_tree_parent
+ && !matches!(
+ path.segment().and_then(|it| it.kind()),
+ Some(ast::PathSegmentKind::SelfKw)
+ );
+ if not_preceded_by_self {
+ acc.add_keyword(ctx, "self");
+ }
+
+ let mut already_imported_names = FxHashSet::default();
+ if let Some(list) = ctx.token.parent_ancestors().find_map(ast::UseTreeList::cast) {
+ let use_tree = list.parent_use_tree();
+ if use_tree.path().as_ref() == Some(path) {
+ for tree in list.use_trees().filter(|tree| tree.is_simple_path()) {
+ if let Some(name) = tree.path().and_then(|path| path.as_single_name_ref()) {
+ already_imported_names.insert(name.to_string());
+ }
+ }
+ }
+ }
+
+ match resolution {
+ hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
+ let module_scope = module.scope(ctx.db, Some(ctx.module));
+ let unknown_is_current = |name: &hir::Name| {
+ matches!(
+ name_ref,
+ Some(name_ref) if name_ref.syntax().text() == name.to_smol_str().as_str()
+ )
+ };
+ for (name, def) in module_scope {
+ let is_name_already_imported = name
+ .as_text()
+ .map_or(false, |text| already_imported_names.contains(text.as_str()));
+
+ let add_resolution = match def {
+ ScopeDef::Unknown if unknown_is_current(&name) => {
+ // for `use self::foo$0`, don't suggest `foo` as a completion
+ cov_mark::hit!(dont_complete_current_use);
+ continue;
+ }
+ ScopeDef::ModuleDef(_) | ScopeDef::Unknown => true,
+ _ => false,
+ };
+
+ if add_resolution {
+ let mut builder = Builder::from_resolution(ctx, path_ctx, name, def);
+ builder.set_relevance(CompletionRelevance {
+ is_name_already_imported,
+ ..Default::default()
+ });
+ acc.add(builder.build());
+ }
+ }
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
+ cov_mark::hit!(enum_plain_qualified_use_tree);
+ acc.add_enum_variants(ctx, path_ctx, *e);
+ }
+ _ => {}
+ }
+ }
+ // fresh use tree with leading colon2, only show crate roots
+ Qualified::Absolute => {
+ cov_mark::hit!(use_tree_crate_roots_only);
+ acc.add_crate_roots(ctx, path_ctx);
+ }
+ // only show modules and non-std enum in a fresh UseTree
+ Qualified::No => {
+ cov_mark::hit!(unqualified_path_selected_only);
+ ctx.process_all_names(&mut |name, res| {
+ match res {
+ ScopeDef::ModuleDef(hir::ModuleDef::Module(module)) => {
+ acc.add_module(ctx, path_ctx, module, name);
+ }
+ ScopeDef::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => {
+ // exclude prelude enum
+ let is_builtin =
+ res.krate(ctx.db).map_or(false, |krate| krate.is_builtin(ctx.db));
+
+ if !is_builtin {
+ let item = CompletionItem::new(
+ CompletionItemKind::SymbolKind(SymbolKind::Enum),
+ ctx.source_range(),
+ format!("{}::", e.name(ctx.db)),
+ );
+ acc.add(item.build());
+ }
+ }
+ _ => {}
+ };
+ });
+ acc.add_nameref_keywords_with_colon(ctx);
+ }
+ Qualified::TypeAnchor { .. } | Qualified::With { resolution: None, .. } => {}
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs
new file mode 100644
index 000000000..5e6cf4bf9
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs
@@ -0,0 +1,41 @@
+//! Completion for visibility specifiers.
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx, Qualified},
+ Completions,
+};
+
+pub(crate) fn complete_vis_path(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
+ &has_in_token: &bool,
+) {
+ match qualified {
+ Qualified::With {
+ resolution: Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))),
+ super_chain_len,
+ ..
+ } => {
+ // Try completing next child module of the path that is still a parent of the current module
+ let next_towards_current =
+ ctx.module.path_to_root(ctx.db).into_iter().take_while(|it| it != module).last();
+ if let Some(next) = next_towards_current {
+ if let Some(name) = next.name(ctx.db) {
+ cov_mark::hit!(visibility_qualified);
+ acc.add_module(ctx, path_ctx, next, name);
+ }
+ }
+
+ acc.add_super_keyword(ctx, *super_chain_len);
+ }
+ Qualified::Absolute | Qualified::TypeAnchor { .. } | Qualified::With { .. } => {}
+ Qualified::No => {
+ if !has_in_token {
+ cov_mark::hit!(kw_completion_in);
+ acc.add_keyword(ctx, "in");
+ }
+ acc.add_nameref_keywords(ctx);
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/config.rs b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
new file mode 100644
index 000000000..80d6af281
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/config.rs
@@ -0,0 +1,41 @@
+//! Settings for tweaking completion.
+//!
+//! The fun thing here is `SnippetCap` -- this type can only be created in this
+//! module, and we use to statically check that we only produce snippet
+//! completions if we are allowed to.
+
+use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap};
+
+use crate::snippet::Snippet;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct CompletionConfig {
+ pub enable_postfix_completions: bool,
+ pub enable_imports_on_the_fly: bool,
+ pub enable_self_on_the_fly: bool,
+ pub enable_private_editable: bool,
+ pub callable: Option<CallableSnippets>,
+ pub snippet_cap: Option<SnippetCap>,
+ pub insert_use: InsertUseConfig,
+ pub snippets: Vec<Snippet>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum CallableSnippets {
+ FillArguments,
+ AddParentheses,
+}
+
+impl CompletionConfig {
+ pub fn postfix_snippets(&self) -> impl Iterator<Item = (&str, &Snippet)> {
+ self.snippets
+ .iter()
+ .flat_map(|snip| snip.postfix_triggers.iter().map(move |trigger| (&**trigger, snip)))
+ }
+
+ pub fn prefix_snippets(&self) -> impl Iterator<Item = (&str, &Snippet)> {
+ self.snippets
+ .iter()
+ .flat_map(|snip| snip.prefix_triggers.iter().map(move |trigger| (&**trigger, snip)))
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
new file mode 100644
index 000000000..e35f79d2b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs
@@ -0,0 +1,639 @@
+//! See `CompletionContext` structure.
+
+mod analysis;
+#[cfg(test)]
+mod tests;
+
+use std::iter;
+
+use base_db::SourceDatabaseExt;
+use hir::{
+ HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo,
+};
+use ide_db::{
+ base_db::{FilePosition, SourceDatabase},
+ famous_defs::FamousDefs,
+ FxHashMap, FxHashSet, RootDatabase,
+};
+use syntax::{
+ ast::{self, AttrKind, NameOrNameRef},
+ AstNode,
+ SyntaxKind::{self, *},
+ SyntaxToken, TextRange, TextSize,
+};
+use text_edit::Indel;
+
+use crate::CompletionConfig;
+
+const COMPLETION_MARKER: &str = "intellijRulezz";
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum PatternRefutability {
+ Refutable,
+ Irrefutable,
+}
+
+#[derive(Debug)]
+pub(crate) enum Visible {
+ Yes,
+ Editable,
+ No,
+}
+
+/// Existing qualifiers for the thing we are currently completing.
+#[derive(Debug, Default)]
+pub(super) struct QualifierCtx {
+ pub(super) unsafe_tok: Option<SyntaxToken>,
+ pub(super) vis_node: Option<ast::Visibility>,
+}
+
+impl QualifierCtx {
+ pub(super) fn none(&self) -> bool {
+ self.unsafe_tok.is_none() && self.vis_node.is_none()
+ }
+}
+
+/// The state of the path we are currently completing.
+#[derive(Debug)]
+pub(crate) struct PathCompletionCtx {
+ /// If this is a call with () already there (or {} in case of record patterns)
+ pub(super) has_call_parens: bool,
+ /// If this has a macro call bang !
+ pub(super) has_macro_bang: bool,
+ /// The qualifier of the current path.
+ pub(super) qualified: Qualified,
+ /// The parent of the path we are completing.
+ pub(super) parent: Option<ast::Path>,
+ /// The path of which we are completing the segment
+ pub(super) path: ast::Path,
+ pub(super) kind: PathKind,
+ /// Whether the path segment has type args or not.
+ pub(super) has_type_args: bool,
+ /// Whether the qualifier comes from a use tree parent or not
+ pub(crate) use_tree_parent: bool,
+}
+
+impl PathCompletionCtx {
+ pub(super) fn is_trivial_path(&self) -> bool {
+ matches!(
+ self,
+ PathCompletionCtx {
+ has_call_parens: false,
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ has_type_args: false,
+ ..
+ }
+ )
+ }
+}
+
+/// The kind of path we are completing right now.
+#[derive(Debug, PartialEq, Eq)]
+pub(super) enum PathKind {
+ Expr {
+ expr_ctx: ExprCtx,
+ },
+ Type {
+ location: TypeLocation,
+ },
+ Attr {
+ attr_ctx: AttrCtx,
+ },
+ Derive {
+ existing_derives: ExistingDerives,
+ },
+ /// Path in item position, that is inside an (Assoc)ItemList
+ Item {
+ kind: ItemListKind,
+ },
+ Pat {
+ pat_ctx: PatternContext,
+ },
+ Vis {
+ has_in_token: bool,
+ },
+ Use,
+}
+
+pub(crate) type ExistingDerives = FxHashSet<hir::Macro>;
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) struct AttrCtx {
+ pub(crate) kind: AttrKind,
+ pub(crate) annotated_item_kind: Option<SyntaxKind>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub(crate) struct ExprCtx {
+ pub(crate) in_block_expr: bool,
+ pub(crate) in_loop_body: bool,
+ pub(crate) after_if_expr: bool,
+ /// Whether this expression is the direct condition of an if or while expression
+ pub(crate) in_condition: bool,
+ pub(crate) incomplete_let: bool,
+ pub(crate) ref_expr_parent: Option<ast::RefExpr>,
+ pub(crate) is_func_update: Option<ast::RecordExpr>,
+ pub(crate) self_param: Option<hir::SelfParam>,
+ pub(crate) innermost_ret_ty: Option<hir::Type>,
+ pub(crate) impl_: Option<ast::Impl>,
+ /// Whether this expression occurs in match arm guard position: before the
+ /// fat arrow token
+ pub(crate) in_match_guard: bool,
+}
+
+/// Original file ast nodes
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum TypeLocation {
+ TupleField,
+ TypeAscription(TypeAscriptionTarget),
+ GenericArgList(Option<ast::GenericArgList>),
+ TypeBound,
+ ImplTarget,
+ ImplTrait,
+ Other,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum TypeAscriptionTarget {
+ Let(Option<ast::Pat>),
+ FnParam(Option<ast::Pat>),
+ RetType(Option<ast::Expr>),
+ Const(Option<ast::Expr>),
+}
+
+/// The kind of item list a [`PathKind::Item`] belongs to.
+#[derive(Debug, PartialEq, Eq)]
+pub(super) enum ItemListKind {
+ SourceFile,
+ Module,
+ Impl,
+ TraitImpl(Option<ast::Impl>),
+ Trait,
+ ExternBlock,
+}
+
+#[derive(Debug)]
+pub(super) enum Qualified {
+ No,
+ With {
+ path: ast::Path,
+ resolution: Option<PathResolution>,
+ /// How many `super` segments are present in the path
+ ///
+ /// This would be None, if path is not solely made of
+ /// `super` segments, e.g.
+ ///
+ /// ```rust
+ /// use super::foo;
+ /// ```
+ ///
+ /// Otherwise it should be Some(count of `super`)
+ super_chain_len: Option<usize>,
+ },
+ /// <_>::
+ TypeAnchor {
+ ty: Option<hir::Type>,
+ trait_: Option<hir::Trait>,
+ },
+ /// Whether the path is an absolute path
+ Absolute,
+}
+
+/// The state of the pattern we are completing.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) struct PatternContext {
+ pub(super) refutability: PatternRefutability,
+ pub(super) param_ctx: Option<ParamContext>,
+ pub(super) has_type_ascription: bool,
+ pub(super) parent_pat: Option<ast::Pat>,
+ pub(super) ref_token: Option<SyntaxToken>,
+ pub(super) mut_token: Option<SyntaxToken>,
+ /// The record pattern this name or ref is a field of
+ pub(super) record_pat: Option<ast::RecordPat>,
+ pub(super) impl_: Option<ast::Impl>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) struct ParamContext {
+ pub(super) param_list: ast::ParamList,
+ pub(super) param: ast::Param,
+ pub(super) kind: ParamKind,
+}
+
+/// The state of the lifetime we are completing.
+#[derive(Debug)]
+pub(super) struct LifetimeContext {
+ pub(super) lifetime: Option<ast::Lifetime>,
+ pub(super) kind: LifetimeKind,
+}
+
+/// The kind of lifetime we are completing.
+#[derive(Debug)]
+pub(super) enum LifetimeKind {
+ LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
+ Lifetime,
+ LabelRef,
+ LabelDef,
+}
+
+/// The state of the name we are completing.
+#[derive(Debug)]
+pub(super) struct NameContext {
+ #[allow(dead_code)]
+ pub(super) name: Option<ast::Name>,
+ pub(super) kind: NameKind,
+}
+
+/// The kind of the name we are completing.
+#[derive(Debug)]
+#[allow(dead_code)]
+pub(super) enum NameKind {
+ Const,
+ ConstParam,
+ Enum,
+ Function,
+ IdentPat(PatternContext),
+ MacroDef,
+ MacroRules,
+ /// Fake node
+ Module(ast::Module),
+ RecordField,
+ Rename,
+ SelfParam,
+ Static,
+ Struct,
+ Trait,
+ TypeAlias,
+ TypeParam,
+ Union,
+ Variant,
+}
+
+/// The state of the NameRef we are completing.
+#[derive(Debug)]
+pub(super) struct NameRefContext {
+ /// NameRef syntax in the original file
+ pub(super) nameref: Option<ast::NameRef>,
+ pub(super) kind: NameRefKind,
+}
+
+/// The kind of the NameRef we are completing.
+#[derive(Debug)]
+pub(super) enum NameRefKind {
+ Path(PathCompletionCtx),
+ DotAccess(DotAccess),
+ /// Position where we are only interested in keyword completions
+ Keyword(ast::Item),
+ /// The record expression this nameref is a field of and whether a dot precedes the completion identifier.
+ RecordExpr {
+ dot_prefix: bool,
+ expr: ast::RecordExpr,
+ },
+ Pattern(PatternContext),
+}
+
+/// The identifier we are currently completing.
+#[derive(Debug)]
+pub(super) enum CompletionAnalysis {
+ Name(NameContext),
+ NameRef(NameRefContext),
+ Lifetime(LifetimeContext),
+ /// The string the cursor is currently inside
+ String {
+ /// original token
+ original: ast::String,
+ /// fake token
+ expanded: Option<ast::String>,
+ },
+ /// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)`
+ UnexpandedAttrTT {
+ colon_prefix: bool,
+ fake_attribute_under_caret: Option<ast::Attr>,
+ },
+}
+
+/// Information about the field or method access we are completing.
+#[derive(Debug)]
+pub(super) struct DotAccess {
+ pub(super) receiver: Option<ast::Expr>,
+ pub(super) receiver_ty: Option<TypeInfo>,
+ pub(super) kind: DotAccessKind,
+}
+
+#[derive(Debug)]
+pub(super) enum DotAccessKind {
+ Field {
+ /// True if the receiver is an integer and there is no ident in the original file after it yet
+ /// like `0.$0`
+ receiver_is_ambiguous_float_literal: bool,
+ },
+ Method {
+ has_parens: bool,
+ },
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub(crate) enum ParamKind {
+ Function(ast::Fn),
+ Closure(ast::ClosureExpr),
+}
+
+/// `CompletionContext` is created early during completion to figure out, where
+/// exactly is the cursor, syntax-wise.
+#[derive(Debug)]
+pub(crate) struct CompletionContext<'a> {
+ pub(super) sema: Semantics<'a, RootDatabase>,
+ pub(super) scope: SemanticsScope<'a>,
+ pub(super) db: &'a RootDatabase,
+ pub(super) config: &'a CompletionConfig,
+ pub(super) position: FilePosition,
+
+ /// The token before the cursor, in the original file.
+ pub(super) original_token: SyntaxToken,
+ /// The token before the cursor, in the macro-expanded file.
+ pub(super) token: SyntaxToken,
+ /// The crate of the current file.
+ pub(super) krate: hir::Crate,
+ /// The module of the `scope`.
+ pub(super) module: hir::Module,
+
+ /// The expected name of what we are completing.
+ /// This is usually the parameter name of the function argument we are completing.
+ pub(super) expected_name: Option<NameOrNameRef>,
+ /// The expected type of what we are completing.
+ pub(super) expected_type: Option<Type>,
+
+ pub(super) qualifier_ctx: QualifierCtx,
+
+ pub(super) locals: FxHashMap<Name, Local>,
+
+ /// The module depth of the current module of the cursor position.
+ /// - crate-root
+ /// - mod foo
+ /// - mod bar
+ /// Here depth will be 2
+ pub(super) depth_from_crate_root: usize,
+}
+
+impl<'a> CompletionContext<'a> {
+ /// The range of the identifier that is being completed.
+ pub(crate) fn source_range(&self) -> TextRange {
+ // check kind of macro-expanded token, but use range of original token
+ let kind = self.token.kind();
+ match kind {
+ CHAR => {
+ // assume we are completing a lifetime but the user has only typed the '
+ cov_mark::hit!(completes_if_lifetime_without_idents);
+ TextRange::at(self.original_token.text_range().start(), TextSize::from(1))
+ }
+ IDENT | LIFETIME_IDENT | UNDERSCORE => self.original_token.text_range(),
+ _ if kind.is_keyword() => self.original_token.text_range(),
+ _ => TextRange::empty(self.position.offset),
+ }
+ }
+
+ pub(crate) fn famous_defs(&self) -> FamousDefs<'_, '_> {
+ FamousDefs(&self.sema, self.krate)
+ }
+
+ /// Checks if an item is visible and not `doc(hidden)` at the completion site.
+ pub(crate) fn def_is_visible(&self, item: &ScopeDef) -> Visible {
+ match item {
+ ScopeDef::ModuleDef(def) => match def {
+ hir::ModuleDef::Module(it) => self.is_visible(it),
+ hir::ModuleDef::Function(it) => self.is_visible(it),
+ hir::ModuleDef::Adt(it) => self.is_visible(it),
+ hir::ModuleDef::Variant(it) => self.is_visible(it),
+ hir::ModuleDef::Const(it) => self.is_visible(it),
+ hir::ModuleDef::Static(it) => self.is_visible(it),
+ hir::ModuleDef::Trait(it) => self.is_visible(it),
+ hir::ModuleDef::TypeAlias(it) => self.is_visible(it),
+ hir::ModuleDef::Macro(it) => self.is_visible(it),
+ hir::ModuleDef::BuiltinType(_) => Visible::Yes,
+ },
+ ScopeDef::GenericParam(_)
+ | ScopeDef::ImplSelfType(_)
+ | ScopeDef::AdtSelfType(_)
+ | ScopeDef::Local(_)
+ | ScopeDef::Label(_)
+ | ScopeDef::Unknown => Visible::Yes,
+ }
+ }
+
+ /// Checks if an item is visible and not `doc(hidden)` at the completion site.
+ pub(crate) fn is_visible<I>(&self, item: &I) -> Visible
+ where
+ I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
+ {
+ let vis = item.visibility(self.db);
+ let attrs = item.attrs(self.db);
+ self.is_visible_impl(&vis, &attrs, item.krate(self.db))
+ }
+
+ /// Check if an item is `#[doc(hidden)]`.
+ pub(crate) fn is_item_hidden(&self, item: &hir::ItemInNs) -> bool {
+ let attrs = item.attrs(self.db);
+ let krate = item.krate(self.db);
+ match (attrs, krate) {
+ (Some(attrs), Some(krate)) => self.is_doc_hidden(&attrs, krate),
+ _ => false,
+ }
+ }
+
+ /// Whether the given trait is an operator trait or not.
+ pub(crate) fn is_ops_trait(&self, trait_: hir::Trait) -> bool {
+ match trait_.attrs(self.db).lang() {
+ Some(lang) => OP_TRAIT_LANG_NAMES.contains(&lang.as_str()),
+ None => false,
+ }
+ }
+
+ /// Returns the traits in scope, with the [`Drop`] trait removed.
+ pub(crate) fn traits_in_scope(&self) -> hir::VisibleTraits {
+ let mut traits_in_scope = self.scope.visible_traits();
+ if let Some(drop) = self.famous_defs().core_ops_Drop() {
+ traits_in_scope.0.remove(&drop.into());
+ }
+ traits_in_scope
+ }
+
+ pub(crate) fn iterate_path_candidates(
+ &self,
+ ty: &hir::Type,
+ mut cb: impl FnMut(hir::AssocItem),
+ ) {
+ let mut seen = FxHashSet::default();
+ ty.iterate_path_candidates(
+ self.db,
+ &self.scope,
+ &self.traits_in_scope(),
+ Some(self.module),
+ None,
+ |item| {
+ // We might iterate candidates of a trait multiple times here, so deduplicate
+ // them.
+ if seen.insert(item) {
+ cb(item)
+ }
+ None::<()>
+ },
+ );
+ }
+
+ /// A version of [`SemanticsScope::process_all_names`] that filters out `#[doc(hidden)]` items.
+ pub(crate) fn process_all_names(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
+ let _p = profile::span("CompletionContext::process_all_names");
+ self.scope.process_all_names(&mut |name, def| {
+ if self.is_scope_def_hidden(def) {
+ return;
+ }
+
+ f(name, def);
+ });
+ }
+
+ pub(crate) fn process_all_names_raw(&self, f: &mut dyn FnMut(Name, ScopeDef)) {
+ let _p = profile::span("CompletionContext::process_all_names_raw");
+ self.scope.process_all_names(&mut |name, def| f(name, def));
+ }
+
+ fn is_scope_def_hidden(&self, scope_def: ScopeDef) -> bool {
+ if let (Some(attrs), Some(krate)) = (scope_def.attrs(self.db), scope_def.krate(self.db)) {
+ return self.is_doc_hidden(&attrs, krate);
+ }
+
+ false
+ }
+
+ fn is_visible_impl(
+ &self,
+ vis: &hir::Visibility,
+ attrs: &hir::Attrs,
+ defining_crate: hir::Crate,
+ ) -> Visible {
+ if !vis.is_visible_from(self.db, self.module.into()) {
+ if !self.config.enable_private_editable {
+ return Visible::No;
+ }
+ // If the definition location is editable, also show private items
+ let root_file = defining_crate.root_file(self.db);
+ let source_root_id = self.db.file_source_root(root_file);
+ let is_editable = !self.db.source_root(source_root_id).is_library;
+ return if is_editable { Visible::Editable } else { Visible::No };
+ }
+
+ if self.is_doc_hidden(attrs, defining_crate) {
+ Visible::No
+ } else {
+ Visible::Yes
+ }
+ }
+
+ fn is_doc_hidden(&self, attrs: &hir::Attrs, defining_crate: hir::Crate) -> bool {
+ // `doc(hidden)` items are only completed within the defining crate.
+ self.krate != defining_crate && attrs.has_doc_hidden()
+ }
+}
+
+// CompletionContext construction
+impl<'a> CompletionContext<'a> {
+ pub(super) fn new(
+ db: &'a RootDatabase,
+ position @ FilePosition { file_id, offset }: FilePosition,
+ config: &'a CompletionConfig,
+ ) -> Option<(CompletionContext<'a>, CompletionAnalysis)> {
+ let _p = profile::span("CompletionContext::new");
+ let sema = Semantics::new(db);
+
+ let original_file = sema.parse(file_id);
+
+ // Insert a fake ident to get a valid parse tree. We will use this file
+ // to determine context, though the original_file will be used for
+ // actual completion.
+ let file_with_fake_ident = {
+ let parse = db.parse(file_id);
+ let edit = Indel::insert(offset, COMPLETION_MARKER.to_string());
+ parse.reparse(&edit).tree()
+ };
+ let fake_ident_token =
+ file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?;
+
+ let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
+ let token = sema.descend_into_macros_single(original_token.clone());
+
+ // adjust for macro input, this still fails if there is no token written yet
+ let scope_offset = if original_token == token { offset } else { token.text_range().end() };
+ let scope = sema.scope_at_offset(&token.parent()?, scope_offset)?;
+
+ let krate = scope.krate();
+ let module = scope.module();
+
+ let mut locals = FxHashMap::default();
+ scope.process_all_names(&mut |name, scope| {
+ if let ScopeDef::Local(local) = scope {
+ locals.insert(name, local);
+ }
+ });
+
+ let depth_from_crate_root = iter::successors(module.parent(db), |m| m.parent(db)).count();
+
+ let mut ctx = CompletionContext {
+ sema,
+ scope,
+ db,
+ config,
+ position,
+ original_token,
+ token,
+ krate,
+ module,
+ expected_name: None,
+ expected_type: None,
+ qualifier_ctx: Default::default(),
+ locals,
+ depth_from_crate_root,
+ };
+ let ident_ctx = ctx.expand_and_analyze(
+ original_file.syntax().clone(),
+ file_with_fake_ident.syntax().clone(),
+ offset,
+ fake_ident_token,
+ )?;
+ Some((ctx, ident_ctx))
+ }
+}
+
+const OP_TRAIT_LANG_NAMES: &[&str] = &[
+ "add_assign",
+ "add",
+ "bitand_assign",
+ "bitand",
+ "bitor_assign",
+ "bitor",
+ "bitxor_assign",
+ "bitxor",
+ "deref_mut",
+ "deref",
+ "div_assign",
+ "div",
+ "eq",
+ "fn_mut",
+ "fn_once",
+ "fn",
+ "index_mut",
+ "index",
+ "mul_assign",
+ "mul",
+ "neg",
+ "not",
+ "partial_ord",
+ "rem_assign",
+ "rem",
+ "shl_assign",
+ "shl",
+ "shr_assign",
+ "shr",
+ "sub",
+];
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
new file mode 100644
index 000000000..22ec7cead
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
@@ -0,0 +1,1293 @@
+//! Module responsible for analyzing the code surrounding the cursor for completion.
+use std::iter;
+
+use hir::{Semantics, Type, TypeInfo};
+use ide_db::{active_parameter::ActiveParameter, RootDatabase};
+use syntax::{
+ algo::{find_node_at_offset, non_trivia_sibling},
+ ast::{self, AttrKind, HasArgList, HasLoopBody, HasName, NameOrNameRef},
+ match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
+ SyntaxToken, TextRange, TextSize, T,
+};
+
+use crate::context::{
+ AttrCtx, CompletionAnalysis, CompletionContext, DotAccess, DotAccessKind, ExprCtx,
+ ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, NameRefContext,
+ NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathKind, PatternContext,
+ PatternRefutability, Qualified, QualifierCtx, TypeAscriptionTarget, TypeLocation,
+ COMPLETION_MARKER,
+};
+
+impl<'a> CompletionContext<'a> {
+ /// Expand attributes and macro calls at the current cursor position for both the original file
+ /// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original
+ /// and speculative states stay in sync.
+ pub(super) fn expand_and_analyze(
+ &mut self,
+ mut original_file: SyntaxNode,
+ mut speculative_file: SyntaxNode,
+ mut offset: TextSize,
+ mut fake_ident_token: SyntaxToken,
+ ) -> Option<CompletionAnalysis> {
+ let _p = profile::span("CompletionContext::expand_and_fill");
+ let mut derive_ctx = None;
+
+ 'expansion: loop {
+ let parent_item =
+ |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
+ let ancestor_items = iter::successors(
+ Option::zip(
+ find_node_at_offset::<ast::Item>(&original_file, offset),
+ find_node_at_offset::<ast::Item>(&speculative_file, offset),
+ ),
+ |(a, b)| parent_item(a).zip(parent_item(b)),
+ );
+
+ // first try to expand attributes as these are always the outermost macro calls
+ 'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items {
+ match (
+ self.sema.expand_attr_macro(&actual_item),
+ self.sema.speculative_expand_attr_macro(
+ &actual_item,
+ &item_with_fake_ident,
+ fake_ident_token.clone(),
+ ),
+ ) {
+ // maybe parent items have attributes, so continue walking the ancestors
+ (None, None) => continue 'ancestors,
+ // successful expansions
+ (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
+ let new_offset = fake_mapped_token.text_range().start();
+ if new_offset > actual_expansion.text_range().end() {
+ // offset outside of bounds from the original expansion,
+ // stop here to prevent problems from happening
+ break 'expansion;
+ }
+ original_file = actual_expansion;
+ speculative_file = fake_expansion;
+ fake_ident_token = fake_mapped_token;
+ offset = new_offset;
+ continue 'expansion;
+ }
+ // exactly one expansion failed, inconsistent state so stop expanding completely
+ _ => break 'expansion,
+ }
+ }
+
+ // No attributes have been expanded, so look for macro_call! token trees or derive token trees
+ let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
+ Some(it) => it,
+ None => break 'expansion,
+ };
+ let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
+ Some(it) => it,
+ None => break 'expansion,
+ };
+
+ // Expand pseudo-derive expansion
+ if let (Some(orig_attr), Some(spec_attr)) = (
+ orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+ spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
+ ) {
+ if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
+ self.sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
+ self.sema.speculative_expand_derive_as_pseudo_attr_macro(
+ &orig_attr,
+ &spec_attr,
+ fake_ident_token.clone(),
+ ),
+ ) {
+ derive_ctx = Some((
+ actual_expansion,
+ fake_expansion,
+ fake_mapped_token.text_range().start(),
+ orig_attr,
+ ));
+ }
+ // at this point we won't have any more successful expansions, so stop
+ break 'expansion;
+ }
+
+ // Expand fn-like macro calls
+ if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
+ orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+ spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+ ) {
+ let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
+ let mac_call_path1 =
+ macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());
+
+ // inconsistent state, stop expanding
+ if mac_call_path0 != mac_call_path1 {
+ break 'expansion;
+ }
+ let speculative_args = match macro_call_with_fake_ident.token_tree() {
+ Some(tt) => tt,
+ None => break 'expansion,
+ };
+
+ match (
+ self.sema.expand(&actual_macro_call),
+ self.sema.speculative_expand(
+ &actual_macro_call,
+ &speculative_args,
+ fake_ident_token.clone(),
+ ),
+ ) {
+ // successful expansions
+ (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) => {
+ let new_offset = fake_mapped_token.text_range().start();
+ if new_offset > actual_expansion.text_range().end() {
+ // offset outside of bounds from the original expansion,
+ // stop here to prevent problems from happening
+ break 'expansion;
+ }
+ original_file = actual_expansion;
+ speculative_file = fake_expansion;
+ fake_ident_token = fake_mapped_token;
+ offset = new_offset;
+ continue 'expansion;
+ }
+ // at least on expansion failed, we won't have anything to expand from this point
+ // onwards so break out
+ _ => break 'expansion,
+ }
+ }
+
+ // none of our states have changed so stop the loop
+ break 'expansion;
+ }
+
+ self.analyze(&original_file, speculative_file, offset, derive_ctx)
+ }
+
+ /// Calculate the expected type and name of the cursor position.
+ fn expected_type_and_name(
+ &self,
+ name_like: &ast::NameLike,
+ ) -> (Option<Type>, Option<NameOrNameRef>) {
+ let mut node = match self.token.parent() {
+ Some(it) => it,
+ None => return (None, None),
+ };
+
+ let strip_refs = |mut ty: Type| match name_like {
+ ast::NameLike::NameRef(n) => {
+ let p = match n.syntax().parent() {
+ Some(it) => it,
+ None => return ty,
+ };
+ let top_syn = match_ast! {
+ match p {
+ ast::FieldExpr(e) => e
+ .syntax()
+ .ancestors()
+ .map_while(ast::FieldExpr::cast)
+ .last()
+ .map(|it| it.syntax().clone()),
+ ast::PathSegment(e) => e
+ .syntax()
+ .ancestors()
+ .skip(1)
+ .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
+ .find_map(ast::PathExpr::cast)
+ .map(|it| it.syntax().clone()),
+ _ => None
+ }
+ };
+ let top_syn = match top_syn {
+ Some(it) => it,
+ None => return ty,
+ };
+ for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) {
+ cov_mark::hit!(expected_type_fn_param_ref);
+ ty = ty.strip_reference();
+ }
+ ty
+ }
+ _ => ty,
+ };
+
+ loop {
+ break match_ast! {
+ match node {
+ ast::LetStmt(it) => {
+ cov_mark::hit!(expected_type_let_with_leading_char);
+ cov_mark::hit!(expected_type_let_without_leading_char);
+ let ty = it.pat()
+ .and_then(|pat| self.sema.type_of_pat(&pat))
+ .or_else(|| it.initializer().and_then(|it| self.sema.type_of_expr(&it)))
+ .map(TypeInfo::original);
+ let name = match it.pat() {
+ Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
+ Some(_) | None => None,
+ };
+
+ (ty, name)
+ },
+ ast::LetExpr(it) => {
+ cov_mark::hit!(expected_type_if_let_without_leading_char);
+ let ty = it.pat()
+ .and_then(|pat| self.sema.type_of_pat(&pat))
+ .or_else(|| it.expr().and_then(|it| self.sema.type_of_expr(&it)))
+ .map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::ArgList(_) => {
+ cov_mark::hit!(expected_type_fn_param);
+ ActiveParameter::at_token(
+ &self.sema,
+ self.token.clone(),
+ ).map(|ap| {
+ let name = ap.ident().map(NameOrNameRef::Name);
+
+ let ty = strip_refs(ap.ty);
+ (Some(ty), name)
+ })
+ .unwrap_or((None, None))
+ },
+ ast::RecordExprFieldList(it) => {
+ // wouldn't try {} be nice...
+ (|| {
+ if self.token.kind() == T![..]
+ || self.token.prev_token().map(|t| t.kind()) == Some(T![..])
+ {
+ cov_mark::hit!(expected_type_struct_func_update);
+ let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?;
+ let ty = self.sema.type_of_expr(&record_expr.into())?;
+ Some((
+ Some(ty.original),
+ None
+ ))
+ } else {
+ cov_mark::hit!(expected_type_struct_field_without_leading_char);
+ let expr_field = self.token.prev_sibling_or_token()?
+ .into_node()
+ .and_then(ast::RecordExprField::cast)?;
+ let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?;
+ Some((
+ Some(ty),
+ expr_field.field_name().map(NameOrNameRef::NameRef),
+ ))
+ }
+ })().unwrap_or((None, None))
+ },
+ ast::RecordExprField(it) => {
+ if let Some(expr) = it.expr() {
+ cov_mark::hit!(expected_type_struct_field_with_leading_char);
+ (
+ self.sema.type_of_expr(&expr).map(TypeInfo::original),
+ it.field_name().map(NameOrNameRef::NameRef),
+ )
+ } else {
+ cov_mark::hit!(expected_type_struct_field_followed_by_comma);
+ let ty = self.sema.resolve_record_field(&it)
+ .map(|(_, _, ty)| ty);
+ (
+ ty,
+ it.field_name().map(NameOrNameRef::NameRef),
+ )
+ }
+ },
+ // match foo { $0 }
+ // match foo { ..., pat => $0 }
+ ast::MatchExpr(it) => {
+ let on_arrow = previous_non_trivia_token(self.token.clone()).map_or(false, |it| T![=>] == it.kind());
+
+ let ty = if on_arrow {
+ // match foo { ..., pat => $0 }
+ cov_mark::hit!(expected_type_match_arm_body_without_leading_char);
+ cov_mark::hit!(expected_type_match_arm_body_with_leading_char);
+ self.sema.type_of_expr(&it.into())
+ } else {
+ // match foo { $0 }
+ cov_mark::hit!(expected_type_match_arm_without_leading_char);
+ it.expr().and_then(|e| self.sema.type_of_expr(&e))
+ }.map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::IfExpr(it) => {
+ let ty = it.condition()
+ .and_then(|e| self.sema.type_of_expr(&e))
+ .map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::IdentPat(it) => {
+ cov_mark::hit!(expected_type_if_let_with_leading_char);
+ cov_mark::hit!(expected_type_match_arm_with_leading_char);
+ let ty = self.sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original);
+ (ty, None)
+ },
+ ast::Fn(it) => {
+ cov_mark::hit!(expected_type_fn_ret_with_leading_char);
+ cov_mark::hit!(expected_type_fn_ret_without_leading_char);
+ let def = self.sema.to_def(&it);
+ (def.map(|def| def.ret_type(self.db)), None)
+ },
+ ast::ClosureExpr(it) => {
+ let ty = self.sema.type_of_expr(&it.into());
+ ty.and_then(|ty| ty.original.as_callable(self.db))
+ .map(|c| (Some(c.return_type()), None))
+ .unwrap_or((None, None))
+ },
+ ast::ParamList(_) => (None, None),
+ ast::Stmt(_) => (None, None),
+ ast::Item(_) => (None, None),
+ _ => {
+ match node.parent() {
+ Some(n) => {
+ node = n;
+ continue;
+ },
+ None => (None, None),
+ }
+ },
+ }
+ };
+ }
+ }
+
+ /// Fill the completion context, this is what does semantic reasoning about the surrounding context
+ /// of the completion location.
+ fn analyze(
+ &mut self,
+ original_file: &SyntaxNode,
+ file_with_fake_ident: SyntaxNode,
+ offset: TextSize,
+ derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
+ ) -> Option<CompletionAnalysis> {
+ let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased()?;
+ let syntax_element = NodeOrToken::Token(fake_ident_token);
+ if is_in_token_of_for_loop(syntax_element.clone()) {
+ // for pat $0
+ // there is nothing to complete here except `in` keyword
+ // don't bother populating the context
+ // FIXME: the completion calculations should end up good enough
+ // such that this special case becomes unnecessary
+ return None;
+ }
+
+ // Overwrite the path kind for derives
+ if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
+ if let Some(ast::NameLike::NameRef(name_ref)) =
+ find_node_at_offset(&file_with_fake_ident, offset)
+ {
+ let parent = name_ref.syntax().parent()?;
+ let (mut nameref_ctx, _) =
+ Self::classify_name_ref(&self.sema, &original_file, name_ref, parent)?;
+ if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind {
+ path_ctx.kind = PathKind::Derive {
+ existing_derives: self
+ .sema
+ .resolve_derive_macro(&origin_attr)
+ .into_iter()
+ .flatten()
+ .flatten()
+ .collect(),
+ };
+ }
+ return Some(CompletionAnalysis::NameRef(nameref_ctx));
+ }
+ return None;
+ }
+
+ let name_like = match find_node_at_offset(&file_with_fake_ident, offset) {
+ Some(it) => it,
+ None => {
+ let analysis =
+ if let Some(original) = ast::String::cast(self.original_token.clone()) {
+ CompletionAnalysis::String {
+ original,
+ expanded: ast::String::cast(self.token.clone()),
+ }
+ } else {
+ // Fix up trailing whitespace problem
+ // #[attr(foo = $0
+ let token =
+ syntax::algo::skip_trivia_token(self.token.clone(), Direction::Prev)?;
+ let p = token.parent()?;
+ if p.kind() == SyntaxKind::TOKEN_TREE
+ && p.ancestors().any(|it| it.kind() == SyntaxKind::META)
+ {
+ let colon_prefix = previous_non_trivia_token(self.token.clone())
+ .map_or(false, |it| T![:] == it.kind());
+ CompletionAnalysis::UnexpandedAttrTT {
+ fake_attribute_under_caret: syntax_element
+ .ancestors()
+ .find_map(ast::Attr::cast),
+ colon_prefix,
+ }
+ } else {
+ return None;
+ }
+ };
+ return Some(analysis);
+ }
+ };
+ (self.expected_type, self.expected_name) = self.expected_type_and_name(&name_like);
+ let analysis = match name_like {
+ ast::NameLike::Lifetime(lifetime) => CompletionAnalysis::Lifetime(
+ Self::classify_lifetime(&self.sema, original_file, lifetime)?,
+ ),
+ ast::NameLike::NameRef(name_ref) => {
+ let parent = name_ref.syntax().parent()?;
+ let (nameref_ctx, qualifier_ctx) =
+ Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone())?;
+
+ self.qualifier_ctx = qualifier_ctx;
+ CompletionAnalysis::NameRef(nameref_ctx)
+ }
+ ast::NameLike::Name(name) => {
+ let name_ctx = Self::classify_name(&self.sema, original_file, name)?;
+ CompletionAnalysis::Name(name_ctx)
+ }
+ };
+ Some(analysis)
+ }
+
+ fn classify_lifetime(
+ _sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ lifetime: ast::Lifetime,
+ ) -> Option<LifetimeContext> {
+ let parent = lifetime.syntax().parent()?;
+ if parent.kind() == SyntaxKind::ERROR {
+ return None;
+ }
+
+ let kind = match_ast! {
+ match parent {
+ ast::LifetimeParam(param) => LifetimeKind::LifetimeParam {
+ is_decl: param.lifetime().as_ref() == Some(&lifetime),
+ param
+ },
+ ast::BreakExpr(_) => LifetimeKind::LabelRef,
+ ast::ContinueExpr(_) => LifetimeKind::LabelRef,
+ ast::Label(_) => LifetimeKind::LabelDef,
+ _ => LifetimeKind::Lifetime,
+ }
+ };
+ let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start());
+
+ Some(LifetimeContext { lifetime, kind })
+ }
+
+ fn classify_name(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ name: ast::Name,
+ ) -> Option<NameContext> {
+ let parent = name.syntax().parent()?;
+ let kind = match_ast! {
+ match parent {
+ ast::Const(_) => NameKind::Const,
+ ast::ConstParam(_) => NameKind::ConstParam,
+ ast::Enum(_) => NameKind::Enum,
+ ast::Fn(_) => NameKind::Function,
+ ast::IdentPat(bind_pat) => {
+ let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into());
+ if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
+ pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat());
+ }
+
+ NameKind::IdentPat(pat_ctx)
+ },
+ ast::MacroDef(_) => NameKind::MacroDef,
+ ast::MacroRules(_) => NameKind::MacroRules,
+ ast::Module(module) => NameKind::Module(module),
+ ast::RecordField(_) => NameKind::RecordField,
+ ast::Rename(_) => NameKind::Rename,
+ ast::SelfParam(_) => NameKind::SelfParam,
+ ast::Static(_) => NameKind::Static,
+ ast::Struct(_) => NameKind::Struct,
+ ast::Trait(_) => NameKind::Trait,
+ ast::TypeAlias(_) => NameKind::TypeAlias,
+ ast::TypeParam(_) => NameKind::TypeParam,
+ ast::Union(_) => NameKind::Union,
+ ast::Variant(_) => NameKind::Variant,
+ _ => return None,
+ }
+ };
+ let name = find_node_at_offset(&original_file, name.syntax().text_range().start());
+ Some(NameContext { name, kind })
+ }
+
+ fn classify_name_ref(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ name_ref: ast::NameRef,
+ parent: SyntaxNode,
+ ) -> Option<(NameRefContext, QualifierCtx)> {
+ let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
+
+ let make_res =
+ |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default());
+
+ if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
+ let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone())
+ .map_or(false, |it| T![.] == it.kind());
+
+ return find_node_in_file_compensated(
+ sema,
+ original_file,
+ &record_field.parent_record_lit(),
+ )
+ .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix })
+ .map(make_res);
+ }
+ if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
+ let kind = NameRefKind::Pattern(PatternContext {
+ param_ctx: None,
+ has_type_ascription: false,
+ ref_token: None,
+ mut_token: None,
+ record_pat: find_node_in_file_compensated(
+ sema,
+ original_file,
+ &record_field.parent_record_pat(),
+ ),
+ ..pattern_context_for(
+ sema,
+ original_file,
+ record_field.parent_record_pat().clone().into(),
+ )
+ });
+ return Some(make_res(kind));
+ }
+
+ let segment = match_ast! {
+ match parent {
+ ast::PathSegment(segment) => segment,
+ ast::FieldExpr(field) => {
+ let receiver = find_opt_node_in_file(original_file, field.expr());
+ let receiver_is_ambiguous_float_literal = match &receiver {
+ Some(ast::Expr::Literal(l)) => matches! {
+ l.kind(),
+ ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.'))
+ },
+ _ => false,
+ };
+ let kind = NameRefKind::DotAccess(DotAccess {
+ receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
+ kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
+ receiver
+ });
+ return Some(make_res(kind));
+ },
+ ast::MethodCallExpr(method) => {
+ let receiver = find_opt_node_in_file(original_file, method.receiver());
+ let kind = NameRefKind::DotAccess(DotAccess {
+ receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
+ kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
+ receiver
+ });
+ return Some(make_res(kind));
+ },
+ _ => return None,
+ }
+ };
+
+ let path = segment.parent_path();
+ let mut path_ctx = PathCompletionCtx {
+ has_call_parens: false,
+ has_macro_bang: false,
+ qualified: Qualified::No,
+ parent: None,
+ path: path.clone(),
+ kind: PathKind::Item { kind: ItemListKind::SourceFile },
+ has_type_args: false,
+ use_tree_parent: false,
+ };
+
+ let is_in_block = |it: &SyntaxNode| {
+ it.parent()
+ .map(|node| {
+ ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind())
+ })
+ .unwrap_or(false)
+ };
+ let func_update_record = |syn: &SyntaxNode| {
+ if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
+ find_node_in_file_compensated(sema, original_file, &record_expr)
+ } else {
+ None
+ }
+ };
+ let after_if_expr = |node: SyntaxNode| {
+ let prev_expr = (|| {
+ let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
+ ast::ExprStmt::cast(prev_sibling)?.expr()
+ })();
+ matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
+ };
+
+ // We do not want to generate path completions when we are sandwiched between an item decl signature and its body.
+ // ex. trait Foo $0 {}
+ // in these cases parser recovery usually kicks in for our inserted identifier, causing it
+ // to either be parsed as an ExprStmt or a MacroCall, depending on whether it is in a block
+ // expression or an item list.
+ // The following code checks if the body is missing, if it is we either cut off the body
+ // from the item or it was missing in the first place
+ let inbetween_body_and_decl_check = |node: SyntaxNode| {
+ if let Some(NodeOrToken::Node(n)) =
+ syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev)
+ {
+ if let Some(item) = ast::Item::cast(n) {
+ let is_inbetween = match &item {
+ ast::Item::Const(it) => it.body().is_none(),
+ ast::Item::Enum(it) => it.variant_list().is_none(),
+ ast::Item::ExternBlock(it) => it.extern_item_list().is_none(),
+ ast::Item::Fn(it) => it.body().is_none(),
+ ast::Item::Impl(it) => it.assoc_item_list().is_none(),
+ ast::Item::Module(it) => it.item_list().is_none(),
+ ast::Item::Static(it) => it.body().is_none(),
+ ast::Item::Struct(it) => it.field_list().is_none(),
+ ast::Item::Trait(it) => it.assoc_item_list().is_none(),
+ ast::Item::TypeAlias(it) => it.ty().is_none(),
+ ast::Item::Union(it) => it.record_field_list().is_none(),
+ _ => false,
+ };
+ if is_inbetween {
+ return Some(item);
+ }
+ }
+ }
+ None
+ };
+
+ let type_location = |node: &SyntaxNode| {
+ let parent = node.parent()?;
+ let res = match_ast! {
+ match parent {
+ ast::Const(it) => {
+ let name = find_opt_node_in_file(original_file, it.name())?;
+ let original = ast::Const::cast(name.syntax().parent()?)?;
+ TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
+ },
+ ast::RetType(it) => {
+ if it.thin_arrow_token().is_none() {
+ return None;
+ }
+ let parent = match ast::Fn::cast(parent.parent()?) {
+ Some(x) => x.param_list(),
+ None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
+ };
+
+ let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
+ TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
+ match parent {
+ ast::ClosureExpr(it) => {
+ it.body()
+ },
+ ast::Fn(it) => {
+ it.body().map(ast::Expr::BlockExpr)
+ },
+ _ => return None,
+ }
+ }))
+ },
+ ast::Param(it) => {
+ if it.colon_token().is_none() {
+ return None;
+ }
+ TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat())))
+ },
+ ast::LetStmt(it) => {
+ if it.colon_token().is_none() {
+ return None;
+ }
+ TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat())))
+ },
+ ast::Impl(it) => {
+ match it.trait_() {
+ Some(t) if t.syntax() == node => TypeLocation::ImplTrait,
+ _ => match it.self_ty() {
+ Some(t) if t.syntax() == node => TypeLocation::ImplTarget,
+ _ => return None,
+ },
+ }
+ },
+ ast::TypeBound(_) => TypeLocation::TypeBound,
+ // is this case needed?
+ ast::TypeBoundList(_) => TypeLocation::TypeBound,
+ ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))),
+ // is this case needed?
+ ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))),
+ ast::TupleField(_) => TypeLocation::TupleField,
+ _ => return None,
+ }
+ };
+ Some(res)
+ };
+
+ let is_in_condition = |it: &ast::Expr| {
+ (|| {
+ let parent = it.syntax().parent()?;
+ if let Some(expr) = ast::WhileExpr::cast(parent.clone()) {
+ Some(expr.condition()? == *it)
+ } else if let Some(expr) = ast::IfExpr::cast(parent) {
+ Some(expr.condition()? == *it)
+ } else {
+ None
+ }
+ })()
+ .unwrap_or(false)
+ };
+
+ let make_path_kind_expr = |expr: ast::Expr| {
+ let it = expr.syntax();
+ let in_block_expr = is_in_block(it);
+ let in_loop_body = is_in_loop_body(it);
+ let after_if_expr = after_if_expr(it.clone());
+ let ref_expr_parent =
+ path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast);
+ let (innermost_ret_ty, self_param) = {
+ let find_ret_ty = |it: SyntaxNode| {
+ if let Some(item) = ast::Item::cast(it.clone()) {
+ match item {
+ ast::Item::Fn(f) => {
+ Some(sema.to_def(&f).map(|it| it.ret_type(sema.db)))
+ }
+ ast::Item::MacroCall(_) => None,
+ _ => Some(None),
+ }
+ } else {
+ let expr = ast::Expr::cast(it)?;
+ let callable = match expr {
+ // FIXME
+ // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b),
+ ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr),
+ _ => return None,
+ };
+ Some(
+ callable
+ .and_then(|c| c.adjusted().as_callable(sema.db))
+ .map(|it| it.return_type()),
+ )
+ }
+ };
+ let find_fn_self_param = |it| match it {
+ ast::Item::Fn(fn_) => {
+ Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db)))
+ }
+ ast::Item::MacroCall(_) => None,
+ _ => Some(None),
+ };
+
+ match find_node_in_file_compensated(sema, original_file, &expr) {
+ Some(it) => {
+ let innermost_ret_ty = sema
+ .ancestors_with_macros(it.syntax().clone())
+ .find_map(find_ret_ty)
+ .flatten();
+
+ let self_param = sema
+ .ancestors_with_macros(it.syntax().clone())
+ .filter_map(ast::Item::cast)
+ .find_map(find_fn_self_param)
+ .flatten();
+ (innermost_ret_ty, self_param)
+ }
+ None => (None, None),
+ }
+ };
+ let is_func_update = func_update_record(it);
+ let in_condition = is_in_condition(&expr);
+ let incomplete_let = it
+ .parent()
+ .and_then(ast::LetStmt::cast)
+ .map_or(false, |it| it.semicolon_token().is_none());
+ let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax());
+
+ let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) {
+ Some(arm) => arm
+ .fat_arrow_token()
+ .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()),
+ None => false,
+ };
+
+ PathKind::Expr {
+ expr_ctx: ExprCtx {
+ in_block_expr,
+ in_loop_body,
+ after_if_expr,
+ in_condition,
+ ref_expr_parent,
+ is_func_update,
+ innermost_ret_ty,
+ self_param,
+ incomplete_let,
+ impl_,
+ in_match_guard,
+ },
+ }
+ };
+ let make_path_kind_type = |ty: ast::Type| {
+ let location = type_location(ty.syntax());
+ PathKind::Type { location: location.unwrap_or(TypeLocation::Other) }
+ };
+
+ let mut kind_macro_call = |it: ast::MacroCall| {
+ path_ctx.has_macro_bang = it.excl_token().is_some();
+ let parent = it.syntax().parent()?;
+ // Any path in an item list will be treated as a macro call by the parser
+ let kind = match_ast! {
+ match parent {
+ ast::MacroExpr(expr) => make_path_kind_expr(expr.into()),
+ ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())},
+ ast::MacroType(ty) => make_path_kind_type(ty.into()),
+ ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module },
+ ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() {
+ Some(it) => match_ast! {
+ match it {
+ ast::Trait(_) => ItemListKind::Trait,
+ ast::Impl(it) => if it.trait_().is_some() {
+ ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
+ } else {
+ ItemListKind::Impl
+ },
+ _ => return None
+ }
+ },
+ None => return None,
+ } },
+ ast::ExternItemList(_) => PathKind::Item { kind: ItemListKind::ExternBlock },
+ ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
+ _ => return None,
+ }
+ };
+ Some(kind)
+ };
+ let make_path_kind_attr = |meta: ast::Meta| {
+ let attr = meta.parent_attr()?;
+ let kind = attr.kind();
+ let attached = attr.syntax().parent()?;
+ let is_trailing_outer_attr = kind != AttrKind::Inner
+ && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next)
+ .is_none();
+ let annotated_item_kind =
+ if is_trailing_outer_attr { None } else { Some(attached.kind()) };
+ Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind } })
+ };
+
+ // Infer the path kind
+ let parent = path.syntax().parent()?;
+ let kind = match_ast! {
+ match parent {
+ ast::PathType(it) => make_path_kind_type(it.into()),
+ ast::PathExpr(it) => {
+ if let Some(p) = it.syntax().parent() {
+ if ast::ExprStmt::can_cast(p.kind()) {
+ if let Some(kind) = inbetween_body_and_decl_check(p) {
+ return Some(make_res(NameRefKind::Keyword(kind)));
+ }
+ }
+ }
+
+ path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
+
+ make_path_kind_expr(it.into())
+ },
+ ast::TupleStructPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::RecordPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::PathPat(it) => {
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
+ },
+ ast::MacroCall(it) => {
+ // A macro call in this position is usually a result of parsing recovery, so check that
+ if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
+ return Some(make_res(NameRefKind::Keyword(kind)));
+ }
+
+ kind_macro_call(it)?
+ },
+ ast::Meta(meta) => make_path_kind_attr(meta)?,
+ ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
+ ast::UseTree(_) => PathKind::Use,
+ // completing inside a qualifier
+ ast::Path(parent) => {
+ path_ctx.parent = Some(parent.clone());
+ let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::PathType(it) => make_path_kind_type(it.into()),
+ ast::PathExpr(it) => {
+ path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
+
+ make_path_kind_expr(it.into())
+ },
+ ast::TupleStructPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::RecordPat(it) => {
+ path_ctx.has_call_parens = true;
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
+ },
+ ast::PathPat(it) => {
+ PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
+ },
+ ast::MacroCall(it) => {
+ kind_macro_call(it)?
+ },
+ ast::Meta(meta) => make_path_kind_attr(meta)?,
+ ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
+ ast::UseTree(_) => PathKind::Use,
+ ast::RecordExpr(it) => make_path_kind_expr(it.into()),
+ _ => return None,
+ }
+ }
+ },
+ ast::RecordExpr(it) => make_path_kind_expr(it.into()),
+ _ => return None,
+ }
+ };
+
+ path_ctx.kind = kind;
+ path_ctx.has_type_args = segment.generic_arg_list().is_some();
+
+ // calculate the qualifier context
+ if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
+ path_ctx.use_tree_parent = use_tree_parent;
+ if !use_tree_parent && segment.coloncolon_token().is_some() {
+ path_ctx.qualified = Qualified::Absolute;
+ } else {
+ let qualifier = qualifier
+ .segment()
+ .and_then(|it| find_node_in_file(original_file, &it))
+ .map(|it| it.parent_path());
+ if let Some(qualifier) = qualifier {
+ let type_anchor = match qualifier.segment().and_then(|it| it.kind()) {
+ Some(ast::PathSegmentKind::Type {
+ type_ref: Some(type_ref),
+ trait_ref,
+ }) if qualifier.qualifier().is_none() => Some((type_ref, trait_ref)),
+ _ => None,
+ };
+
+ path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor {
+ let ty = match ty {
+ ast::Type::InferType(_) => None,
+ ty => sema.resolve_type(&ty),
+ };
+ let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?));
+ Qualified::TypeAnchor { ty, trait_ }
+ } else {
+ let res = sema.resolve_path(&qualifier);
+
+ // For understanding how and why super_chain_len is calculated the way it
+ // is check the documentation at it's definition
+ let mut segment_count = 0;
+ let super_count =
+ iter::successors(Some(qualifier.clone()), |p| p.qualifier())
+ .take_while(|p| {
+ p.segment()
+ .and_then(|s| {
+ segment_count += 1;
+ s.super_token()
+ })
+ .is_some()
+ })
+ .count();
+
+ let super_chain_len =
+ if segment_count > super_count { None } else { Some(super_count) };
+
+ Qualified::With { path: qualifier, resolution: res, super_chain_len }
+ }
+ };
+ }
+ } else if let Some(segment) = path.segment() {
+ if segment.coloncolon_token().is_some() {
+ path_ctx.qualified = Qualified::Absolute;
+ }
+ }
+
+ let mut qualifier_ctx = QualifierCtx::default();
+ if path_ctx.is_trivial_path() {
+ // fetch the full expression that may have qualifiers attached to it
+ let top_node = match path_ctx.kind {
+ PathKind::Expr { expr_ctx: ExprCtx { in_block_expr: true, .. } } => {
+ parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| {
+ let parent = p.parent()?;
+ if ast::StmtList::can_cast(parent.kind()) {
+ Some(p)
+ } else if ast::ExprStmt::can_cast(parent.kind()) {
+ Some(parent)
+ } else {
+ None
+ }
+ })
+ }
+ PathKind::Item { .. } => {
+ parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind()))
+ }
+ _ => None,
+ };
+ if let Some(top) = top_node {
+ if let Some(NodeOrToken::Node(error_node)) =
+ syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev)
+ {
+ if error_node.kind() == SyntaxKind::ERROR {
+ qualifier_ctx.unsafe_tok = error_node
+ .children_with_tokens()
+ .filter_map(NodeOrToken::into_token)
+ .find(|it| it.kind() == T![unsafe]);
+ qualifier_ctx.vis_node =
+ error_node.children().find_map(ast::Visibility::cast);
+ }
+ }
+
+ if let PathKind::Item { .. } = path_ctx.kind {
+ if qualifier_ctx.none() {
+ if let Some(t) = top.first_token() {
+ if let Some(prev) = t
+ .prev_token()
+ .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev))
+ {
+ if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) {
+ // This was inferred to be an item position path, but it seems
+ // to be part of some other broken node which leaked into an item
+ // list
+ return None;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx))
+ }
+}
+
+fn pattern_context_for(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ pat: ast::Pat,
+) -> PatternContext {
+ let mut param_ctx = None;
+ let (refutability, has_type_ascription) =
+ pat
+ .syntax()
+ .ancestors()
+ .skip_while(|it| ast::Pat::can_cast(it.kind()))
+ .next()
+ .map_or((PatternRefutability::Irrefutable, false), |node| {
+ let refutability = match_ast! {
+ match node {
+ ast::LetStmt(let_) => return (PatternRefutability::Irrefutable, let_.ty().is_some()),
+ ast::Param(param) => {
+ let has_type_ascription = param.ty().is_some();
+ param_ctx = (|| {
+ let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?;
+ let param_list = find_node_in_file_compensated(sema, original_file, &fake_param_list)?;
+ let param_list_owner = param_list.syntax().parent()?;
+ let kind = match_ast! {
+ match param_list_owner {
+ ast::ClosureExpr(closure) => ParamKind::Closure(closure),
+ ast::Fn(fn_) => ParamKind::Function(fn_),
+ _ => return None,
+ }
+ };
+ Some(ParamContext {
+ param_list, param, kind
+ })
+ })();
+ return (PatternRefutability::Irrefutable, has_type_ascription)
+ },
+ ast::MatchArm(_) => PatternRefutability::Refutable,
+ ast::LetExpr(_) => PatternRefutability::Refutable,
+ ast::ForExpr(_) => PatternRefutability::Irrefutable,
+ _ => PatternRefutability::Irrefutable,
+ }
+ };
+ (refutability, false)
+ });
+ let (ref_token, mut_token) = match &pat {
+ ast::Pat::IdentPat(it) => (it.ref_token(), it.mut_token()),
+ _ => (None, None),
+ };
+
+ PatternContext {
+ refutability,
+ param_ctx,
+ has_type_ascription,
+ parent_pat: pat.syntax().parent().and_then(ast::Pat::cast),
+ mut_token,
+ ref_token,
+ record_pat: None,
+ impl_: fetch_immediate_impl(sema, original_file, pat.syntax()),
+ }
+}
+
+fn fetch_immediate_impl(
+ sema: &Semantics<'_, RootDatabase>,
+ original_file: &SyntaxNode,
+ node: &SyntaxNode,
+) -> Option<ast::Impl> {
+ let mut ancestors = ancestors_in_file_compensated(sema, original_file, node)?
+ .filter_map(ast::Item::cast)
+ .filter(|it| !matches!(it, ast::Item::MacroCall(_)));
+
+ match ancestors.next()? {
+ ast::Item::Const(_) | ast::Item::Fn(_) | ast::Item::TypeAlias(_) => (),
+ ast::Item::Impl(it) => return Some(it),
+ _ => return None,
+ }
+ match ancestors.next()? {
+ ast::Item::Impl(it) => Some(it),
+ _ => None,
+ }
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range.
+/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
+fn find_opt_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: Option<N>) -> Option<N> {
+ find_node_in_file(syntax, &node?)
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range.
+/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
+fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
+ let syntax_range = syntax.text_range();
+ let range = node.syntax().text_range();
+ let intersection = range.intersect(syntax_range)?;
+ syntax.covering_element(intersection).ancestors().find_map(N::cast)
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
+/// for the offset introduced by the fake ident.
+/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
+fn find_node_in_file_compensated<N: AstNode>(
+ sema: &Semantics<'_, RootDatabase>,
+ in_file: &SyntaxNode,
+ node: &N,
+) -> Option<N> {
+ ancestors_in_file_compensated(sema, in_file, node.syntax())?.find_map(N::cast)
+}
+
+fn ancestors_in_file_compensated<'sema>(
+ sema: &'sema Semantics<'_, RootDatabase>,
+ in_file: &SyntaxNode,
+ node: &SyntaxNode,
+) -> Option<impl Iterator<Item = SyntaxNode> + 'sema> {
+ let syntax_range = in_file.text_range();
+ let range = node.text_range();
+ let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?;
+ if end < range.start() {
+ return None;
+ }
+ let range = TextRange::new(range.start(), end);
+ // our inserted ident could cause `range` to go outside of the original syntax, so cap it
+ let intersection = range.intersect(syntax_range)?;
+ let node = match in_file.covering_element(intersection) {
+ NodeOrToken::Node(node) => node,
+ NodeOrToken::Token(tok) => tok.parent()?,
+ };
+ Some(sema.ancestors_with_macros(node))
+}
+
+/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
+/// for the offset introduced by the fake ident..
+/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
+fn find_opt_node_in_file_compensated<N: AstNode>(
+ sema: &Semantics<'_, RootDatabase>,
+ syntax: &SyntaxNode,
+ node: Option<N>,
+) -> Option<N> {
+ find_node_in_file_compensated(sema, syntax, &node?)
+}
+
+fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
+ if let Some(qual) = path.qualifier() {
+ return Some((qual, false));
+ }
+ let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?;
+ let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?;
+ Some((use_tree.path()?, true))
+}
+
+pub(crate) fn is_in_token_of_for_loop(element: SyntaxElement) -> bool {
+ // oh my ...
+ (|| {
+ let syntax_token = element.into_token()?;
+ let range = syntax_token.text_range();
+ let for_expr = syntax_token.parent_ancestors().find_map(ast::ForExpr::cast)?;
+
+ // check if the current token is the `in` token of a for loop
+ if let Some(token) = for_expr.in_token() {
+ return Some(syntax_token == token);
+ }
+ let pat = for_expr.pat()?;
+ if range.end() < pat.syntax().text_range().end() {
+ // if we are inside or before the pattern we can't be at the `in` token position
+ return None;
+ }
+ let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
+ Some(match next_sibl {
+ // the loop body is some node, if our token is at the start we are at the `in` position,
+ // otherwise we could be in a recovered expression, we don't wanna ruin completions there
+ syntax::NodeOrToken::Node(n) => n.text_range().start() == range.start(),
+ // the loop body consists of a single token, if we are this we are certainly at the `in` token position
+ syntax::NodeOrToken::Token(t) => t == syntax_token,
+ })
+ })()
+ .unwrap_or(false)
+}
+
+#[test]
+fn test_for_is_prev2() {
+ crate::tests::check_pattern_is_applicable(r"fn __() { for i i$0 }", is_in_token_of_for_loop);
+}
+
+pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
+ node.ancestors()
+ .take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
+ .find_map(|it| {
+ let loop_body = match_ast! {
+ match it {
+ ast::ForExpr(it) => it.loop_body(),
+ ast::WhileExpr(it) => it.loop_body(),
+ ast::LoopExpr(it) => it.loop_body(),
+ _ => None,
+ }
+ };
+ loop_body.filter(|it| it.syntax().text_range().contains_range(node.text_range()))
+ })
+ .is_some()
+}
+
+fn previous_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> {
+ let mut token = match e.into() {
+ SyntaxElement::Node(n) => n.first_token()?,
+ SyntaxElement::Token(t) => t,
+ }
+ .prev_token();
+ while let Some(inner) = token {
+ if !inner.kind().is_trivia() {
+ return Some(inner);
+ } else {
+ token = inner.prev_token();
+ }
+ }
+ None
+}
+
+fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
+ let mut e = ele.next_sibling_or_token();
+ while let Some(inner) = e {
+ if !inner.kind().is_trivia() {
+ return Some(inner);
+ } else {
+ e = inner.next_sibling_or_token();
+ }
+ }
+ None
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs
new file mode 100644
index 000000000..50845b388
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs
@@ -0,0 +1,413 @@
+use expect_test::{expect, Expect};
+use hir::HirDisplay;
+
+use crate::{
+ context::CompletionContext,
+ tests::{position, TEST_CONFIG},
+};
+
+fn check_expected_type_and_name(ra_fixture: &str, expect: Expect) {
+ let (db, pos) = position(ra_fixture);
+ let config = TEST_CONFIG;
+ let (completion_context, _analysis) = CompletionContext::new(&db, pos, &config).unwrap();
+
+ let ty = completion_context
+ .expected_type
+ .map(|t| t.display_test(&db).to_string())
+ .unwrap_or("?".to_owned());
+
+ let name =
+ completion_context.expected_name.map_or_else(|| "?".to_owned(), |name| name.to_string());
+
+ expect.assert_eq(&format!("ty: {}, name: {}", ty, name));
+}
+
+#[test]
+fn expected_type_let_without_leading_char() {
+ cov_mark::check!(expected_type_let_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let x: u32 = $0;
+}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_let_with_leading_char() {
+ cov_mark::check!(expected_type_let_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let x: u32 = c$0;
+}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_let_pat() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let x$0 = 0u32;
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ let $0 = 0u32;
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_fn_param() {
+ cov_mark::check!(expected_type_fn_param);
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar($0); }
+fn bar(x: u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(c$0); }
+fn bar(x: u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_fn_param_ref() {
+ cov_mark::check!(expected_type_fn_param_ref);
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&$0); }
+fn bar(x: &u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&mut $0); }
+fn bar(x: &mut u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(& c$0); }
+fn bar(x: &u32) {}
+ "#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&mut c$0); }
+fn bar(x: &mut u32) {}
+"#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+fn foo() { bar(&c$0); }
+fn bar(x: &u32) {}
+ "#,
+ expect![[r#"ty: u32, name: x"#]],
+ );
+}
+
+#[test]
+fn expected_type_struct_field_without_leading_char() {
+ cov_mark::check!(expected_type_struct_field_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo { a: u32 }
+fn foo() {
+ Foo { a: $0 };
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ )
+}
+
+#[test]
+fn expected_type_struct_field_followed_by_comma() {
+ cov_mark::check!(expected_type_struct_field_followed_by_comma);
+ check_expected_type_and_name(
+ r#"
+struct Foo { a: u32 }
+fn foo() {
+ Foo { a: $0, };
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ )
+}
+
+#[test]
+fn expected_type_generic_struct_field() {
+ check_expected_type_and_name(
+ r#"
+struct Foo<T> { a: T }
+fn foo() -> Foo<u32> {
+ Foo { a: $0 }
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ )
+}
+
+#[test]
+fn expected_type_struct_field_with_leading_char() {
+ cov_mark::check!(expected_type_struct_field_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo { a: u32 }
+fn foo() {
+ Foo { a: c$0 };
+}
+"#,
+ expect![[r#"ty: u32, name: a"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_arm_without_leading_char() {
+ cov_mark::check!(expected_type_match_arm_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum E { X }
+fn foo() {
+ match E::X { $0 }
+}
+"#,
+ expect![[r#"ty: E, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_arm_with_leading_char() {
+ cov_mark::check!(expected_type_match_arm_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum E { X }
+fn foo() {
+ match E::X { c$0 }
+}
+"#,
+ expect![[r#"ty: E, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_arm_body_without_leading_char() {
+ cov_mark::check!(expected_type_match_arm_body_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo;
+enum E { X }
+fn foo() -> Foo {
+ match E::X { E::X => $0 }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_match_body_arm_with_leading_char() {
+ cov_mark::check!(expected_type_match_arm_body_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+struct Foo;
+enum E { X }
+fn foo() -> Foo {
+ match E::X { E::X => c$0 }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_if_let_without_leading_char() {
+ cov_mark::check!(expected_type_if_let_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum Foo { Bar, Baz, Quux }
+
+fn foo() {
+ let f = Foo::Quux;
+ if let $0 = f { }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_if_let_with_leading_char() {
+ cov_mark::check!(expected_type_if_let_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+enum Foo { Bar, Baz, Quux }
+
+fn foo() {
+ let f = Foo::Quux;
+ if let c$0 = f { }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_fn_ret_without_leading_char() {
+ cov_mark::check!(expected_type_fn_ret_without_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() -> u32 {
+ $0
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_fn_ret_with_leading_char() {
+ cov_mark::check!(expected_type_fn_ret_with_leading_char);
+ check_expected_type_and_name(
+ r#"
+fn foo() -> u32 {
+ c$0
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_fn_ret_fn_ref_fully_typed() {
+ check_expected_type_and_name(
+ r#"
+fn foo() -> u32 {
+ foo$0
+}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ )
+}
+
+#[test]
+fn expected_type_closure_param_return() {
+ // FIXME: make this work with `|| $0`
+ check_expected_type_and_name(
+ r#"
+//- minicore: fn
+fn foo() {
+ bar(|| a$0);
+}
+
+fn bar(f: impl FnOnce() -> u32) {}
+"#,
+ expect![[r#"ty: u32, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_generic_function() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ bar::<u32>($0);
+}
+
+fn bar<T>(t: T) {}
+"#,
+ expect![[r#"ty: u32, name: t"#]],
+ );
+}
+
+#[test]
+fn expected_type_generic_method() {
+ check_expected_type_and_name(
+ r#"
+fn foo() {
+ S(1u32).bar($0);
+}
+
+struct S<T>(T);
+impl<T> S<T> {
+ fn bar(self, t: T) {}
+}
+"#,
+ expect![[r#"ty: u32, name: t"#]],
+ );
+}
+
+#[test]
+fn expected_type_functional_update() {
+ cov_mark::check!(expected_type_struct_func_update);
+ check_expected_type_and_name(
+ r#"
+struct Foo { field: u32 }
+fn foo() {
+ Foo {
+ ..$0
+ }
+}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_param_pat() {
+ check_expected_type_and_name(
+ r#"
+struct Foo { field: u32 }
+fn foo(a$0: Foo) {}
+"#,
+ expect![[r#"ty: Foo, name: ?"#]],
+ );
+ check_expected_type_and_name(
+ r#"
+struct Foo { field: u32 }
+fn foo($0: Foo) {}
+"#,
+ // FIXME make this work, currently fails due to pattern recovery eating the `:`
+ expect![[r#"ty: ?, name: ?"#]],
+ );
+}
+
+#[test]
+fn expected_type_ref_prefix_on_field() {
+ check_expected_type_and_name(
+ r#"
+fn foo(_: &mut i32) {}
+struct S {
+ field: i32,
+}
+
+fn main() {
+ let s = S {
+ field: 100,
+ };
+ foo(&mut s.f$0);
+}
+"#,
+ expect!["ty: i32, name: ?"],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
new file mode 100644
index 000000000..27c3ccb35
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs
@@ -0,0 +1,637 @@
+//! See `CompletionItem` structure.
+
+use std::fmt;
+
+use hir::{Documentation, Mutability};
+use ide_db::{imports::import_assets::LocatedImport, SnippetCap, SymbolKind};
+use smallvec::SmallVec;
+use stdx::{impl_from, never};
+use syntax::{SmolStr, TextRange, TextSize};
+use text_edit::TextEdit;
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx},
+ render::{render_path_resolution, RenderContext},
+};
+
+/// `CompletionItem` describes a single completion variant in the editor pop-up.
+/// It is basically a POD with various properties. To construct a
+/// `CompletionItem`, use `new` method and the `Builder` struct.
+#[derive(Clone)]
+pub struct CompletionItem {
+ /// Label in the completion pop up which identifies completion.
+ label: SmolStr,
+ /// Range of identifier that is being completed.
+ ///
+ /// It should be used primarily for UI, but we also use this to convert
+ /// generic TextEdit into LSP's completion edit (see conv.rs).
+ ///
+ /// `source_range` must contain the completion offset. `text_edit` should
+ /// start with what `source_range` points to, or VSCode will filter out the
+ /// completion silently.
+ source_range: TextRange,
+ /// What happens when user selects this item.
+ ///
+ /// Typically, replaces `source_range` with new identifier.
+ text_edit: TextEdit,
+ is_snippet: bool,
+
+ /// What item (struct, function, etc) are we completing.
+ kind: CompletionItemKind,
+
+ /// Lookup is used to check if completion item indeed can complete current
+ /// ident.
+ ///
+ /// That is, in `foo.bar$0` lookup of `abracadabra` will be accepted (it
+ /// contains `bar` sub sequence), and `quux` will rejected.
+ lookup: Option<SmolStr>,
+
+ /// Additional info to show in the UI pop up.
+ detail: Option<String>,
+ documentation: Option<Documentation>,
+
+ /// Whether this item is marked as deprecated
+ deprecated: bool,
+
+ /// If completing a function call, ask the editor to show parameter popup
+ /// after completion.
+ trigger_call_info: bool,
+
+ /// We use this to sort completion. Relevance records facts like "do the
+ /// types align precisely?". We can't sort by relevances directly, they are
+ /// only partially ordered.
+ ///
+ /// Note that Relevance ignores fuzzy match score. We compute Relevance for
+ /// all possible items, and then separately build an ordered completion list
+ /// based on relevance and fuzzy matching with the already typed identifier.
+ relevance: CompletionRelevance,
+
+ /// Indicates that a reference or mutable reference to this variable is a
+ /// possible match.
+ ref_match: Option<(Mutability, TextSize)>,
+
+ /// The import data to add to completion's edits.
+ import_to_add: SmallVec<[LocatedImport; 1]>,
+}
+
+// We use custom debug for CompletionItem to make snapshot tests more readable.
+impl fmt::Debug for CompletionItem {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let mut s = f.debug_struct("CompletionItem");
+ s.field("label", &self.label()).field("source_range", &self.source_range());
+ if self.text_edit().len() == 1 {
+ let atom = &self.text_edit().iter().next().unwrap();
+ s.field("delete", &atom.delete);
+ s.field("insert", &atom.insert);
+ } else {
+ s.field("text_edit", &self.text_edit);
+ }
+ s.field("kind", &self.kind());
+ if self.lookup() != self.label() {
+ s.field("lookup", &self.lookup());
+ }
+ if let Some(detail) = self.detail() {
+ s.field("detail", &detail);
+ }
+ if let Some(documentation) = self.documentation() {
+ s.field("documentation", &documentation);
+ }
+ if self.deprecated {
+ s.field("deprecated", &true);
+ }
+
+ if self.relevance != CompletionRelevance::default() {
+ s.field("relevance", &self.relevance);
+ }
+
+ if let Some((mutability, offset)) = &self.ref_match {
+ s.field("ref_match", &format!("&{}@{offset:?}", mutability.as_keyword_for_ref()));
+ }
+ if self.trigger_call_info {
+ s.field("trigger_call_info", &true);
+ }
+ s.finish()
+ }
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
+pub struct CompletionRelevance {
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// fn f(spam: String) {}
+ /// fn main {
+ /// let spam = 92;
+ /// f($0) // name of local matches the name of param
+ /// }
+ /// ```
+ pub exact_name_match: bool,
+ /// See CompletionRelevanceTypeMatch doc comments for cases where this is set.
+ pub type_match: Option<CompletionRelevanceTypeMatch>,
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// fn foo(a: u32) {
+ /// let b = 0;
+ /// $0 // `a` and `b` are local
+ /// }
+ /// ```
+ pub is_local: bool,
+ /// This is set when trait items are completed in an impl of that trait.
+ pub is_item_from_trait: bool,
+ /// This is set when an import is suggested whose name is already imported.
+ pub is_name_already_imported: bool,
+ /// This is set for completions that will insert a `use` item.
+ pub requires_import: bool,
+ /// Set for method completions of the `core::ops` and `core::cmp` family.
+ pub is_op_method: bool,
+ /// Set for item completions that are private but in the workspace.
+ pub is_private_editable: bool,
+ /// Set for postfix snippet item completions
+ pub postfix_match: Option<CompletionRelevancePostfixMatch>,
+ /// This is set for type inference results
+ pub is_definite: bool,
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum CompletionRelevanceTypeMatch {
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// enum Option<T> { Some(T), None }
+ /// fn f(a: Option<u32>) {}
+ /// fn main {
+ /// f(Option::N$0) // type `Option<T>` could unify with `Option<u32>`
+ /// }
+ /// ```
+ CouldUnify,
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// fn f(spam: String) {}
+ /// fn main {
+ /// let foo = String::new();
+ /// f($0) // type of local matches the type of param
+ /// }
+ /// ```
+ Exact,
+}
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum CompletionRelevancePostfixMatch {
+ /// Set in cases when item is postfix, but not exact
+ NonExact,
+ /// This is set in cases like these:
+ ///
+ /// ```
+ /// (a > b).not$0
+ /// ```
+ ///
+ /// Basically, we want to guarantee that postfix snippets always takes
+ /// precedence over everything else.
+ Exact,
+}
+
+impl CompletionRelevance {
+ /// Provides a relevance score. Higher values are more relevant.
+ ///
+ /// The absolute value of the relevance score is not meaningful, for
+ /// example a value of 0 doesn't mean "not relevant", rather
+ /// it means "least relevant". The score value should only be used
+ /// for relative ordering.
+ ///
+ /// See is_relevant if you need to make some judgement about score
+ /// in an absolute sense.
+ pub fn score(self) -> u32 {
+ let mut score = 0;
+ let CompletionRelevance {
+ exact_name_match,
+ type_match,
+ is_local,
+ is_item_from_trait,
+ is_name_already_imported,
+ requires_import,
+ is_op_method,
+ is_private_editable,
+ postfix_match,
+ is_definite,
+ } = self;
+
+ // lower rank private things
+ if !is_private_editable {
+ score += 1;
+ }
+ // lower rank trait op methods
+ if !is_op_method {
+ score += 10;
+ }
+ // lower rank for conflicting import names
+ if !is_name_already_imported {
+ score += 1;
+ }
+ // lower rank for items that don't need an import
+ if !requires_import {
+ score += 1;
+ }
+ if exact_name_match {
+ score += 10;
+ }
+ score += match postfix_match {
+ Some(CompletionRelevancePostfixMatch::Exact) => 100,
+ Some(CompletionRelevancePostfixMatch::NonExact) => 0,
+ None => 3,
+ };
+ score += match type_match {
+ Some(CompletionRelevanceTypeMatch::Exact) => 8,
+ Some(CompletionRelevanceTypeMatch::CouldUnify) => 3,
+ None => 0,
+ };
+ // slightly prefer locals
+ if is_local {
+ score += 1;
+ }
+ if is_item_from_trait {
+ score += 1;
+ }
+ if is_definite {
+ score += 10;
+ }
+ score
+ }
+
+ /// Returns true when the score for this threshold is above
+ /// some threshold such that we think it is especially likely
+ /// to be relevant.
+ pub fn is_relevant(&self) -> bool {
+ self.score() > 0
+ }
+}
+
+/// The type of the completion item.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum CompletionItemKind {
+ SymbolKind(SymbolKind),
+ Binding,
+ BuiltinType,
+ InferredType,
+ Keyword,
+ Method,
+ Snippet,
+ UnresolvedReference,
+}
+
+impl_from!(SymbolKind for CompletionItemKind);
+
+impl CompletionItemKind {
+ #[cfg(test)]
+ pub(crate) fn tag(&self) -> &'static str {
+ match self {
+ CompletionItemKind::SymbolKind(kind) => match kind {
+ SymbolKind::Attribute => "at",
+ SymbolKind::BuiltinAttr => "ba",
+ SymbolKind::Const => "ct",
+ SymbolKind::ConstParam => "cp",
+ SymbolKind::Derive => "de",
+ SymbolKind::DeriveHelper => "dh",
+ SymbolKind::Enum => "en",
+ SymbolKind::Field => "fd",
+ SymbolKind::Function => "fn",
+ SymbolKind::Impl => "im",
+ SymbolKind::Label => "lb",
+ SymbolKind::LifetimeParam => "lt",
+ SymbolKind::Local => "lc",
+ SymbolKind::Macro => "ma",
+ SymbolKind::Module => "md",
+ SymbolKind::SelfParam => "sp",
+ SymbolKind::SelfType => "sy",
+ SymbolKind::Static => "sc",
+ SymbolKind::Struct => "st",
+ SymbolKind::ToolModule => "tm",
+ SymbolKind::Trait => "tt",
+ SymbolKind::TypeAlias => "ta",
+ SymbolKind::TypeParam => "tp",
+ SymbolKind::Union => "un",
+ SymbolKind::ValueParam => "vp",
+ SymbolKind::Variant => "ev",
+ },
+ CompletionItemKind::Binding => "bn",
+ CompletionItemKind::BuiltinType => "bt",
+ CompletionItemKind::InferredType => "it",
+ CompletionItemKind::Keyword => "kw",
+ CompletionItemKind::Method => "me",
+ CompletionItemKind::Snippet => "sn",
+ CompletionItemKind::UnresolvedReference => "??",
+ }
+ }
+}
+
+impl CompletionItem {
+ pub(crate) fn new(
+ kind: impl Into<CompletionItemKind>,
+ source_range: TextRange,
+ label: impl Into<SmolStr>,
+ ) -> Builder {
+ let label = label.into();
+ Builder {
+ source_range,
+ label,
+ insert_text: None,
+ is_snippet: false,
+ trait_name: None,
+ detail: None,
+ documentation: None,
+ lookup: None,
+ kind: kind.into(),
+ text_edit: None,
+ deprecated: false,
+ trigger_call_info: false,
+ relevance: CompletionRelevance::default(),
+ ref_match: None,
+ imports_to_add: Default::default(),
+ }
+ }
+
+ /// What user sees in pop-up in the UI.
+ pub fn label(&self) -> &str {
+ &self.label
+ }
+ pub fn source_range(&self) -> TextRange {
+ self.source_range
+ }
+
+ pub fn text_edit(&self) -> &TextEdit {
+ &self.text_edit
+ }
+ /// Whether `text_edit` is a snippet (contains `$0` markers).
+ pub fn is_snippet(&self) -> bool {
+ self.is_snippet
+ }
+
+ /// Short one-line additional information, like a type
+ pub fn detail(&self) -> Option<&str> {
+ self.detail.as_deref()
+ }
+ /// A doc-comment
+ pub fn documentation(&self) -> Option<Documentation> {
+ self.documentation.clone()
+ }
+ /// What string is used for filtering.
+ pub fn lookup(&self) -> &str {
+ self.lookup.as_deref().unwrap_or(&self.label)
+ }
+
+ pub fn kind(&self) -> CompletionItemKind {
+ self.kind
+ }
+
+ pub fn deprecated(&self) -> bool {
+ self.deprecated
+ }
+
+ pub fn relevance(&self) -> CompletionRelevance {
+ self.relevance
+ }
+
+ pub fn trigger_call_info(&self) -> bool {
+ self.trigger_call_info
+ }
+
+ pub fn ref_match(&self) -> Option<(Mutability, TextSize, CompletionRelevance)> {
+ // Relevance of the ref match should be the same as the original
+ // match, but with exact type match set because self.ref_match
+ // is only set if there is an exact type match.
+ let mut relevance = self.relevance;
+ relevance.type_match = Some(CompletionRelevanceTypeMatch::Exact);
+
+ self.ref_match.map(|(mutability, offset)| (mutability, offset, relevance))
+ }
+
+ pub fn imports_to_add(&self) -> &[LocatedImport] {
+ &self.import_to_add
+ }
+}
+
+/// A helper to make `CompletionItem`s.
+#[must_use]
+#[derive(Clone)]
+pub(crate) struct Builder {
+ source_range: TextRange,
+ imports_to_add: SmallVec<[LocatedImport; 1]>,
+ trait_name: Option<SmolStr>,
+ label: SmolStr,
+ insert_text: Option<String>,
+ is_snippet: bool,
+ detail: Option<String>,
+ documentation: Option<Documentation>,
+ lookup: Option<SmolStr>,
+ kind: CompletionItemKind,
+ text_edit: Option<TextEdit>,
+ deprecated: bool,
+ trigger_call_info: bool,
+ relevance: CompletionRelevance,
+ ref_match: Option<(Mutability, TextSize)>,
+}
+
+impl Builder {
+ pub(crate) fn from_resolution(
+ ctx: &CompletionContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: hir::Name,
+ resolution: hir::ScopeDef,
+ ) -> Self {
+ render_path_resolution(RenderContext::new(ctx), path_ctx, local_name, resolution)
+ }
+
+ pub(crate) fn build(self) -> CompletionItem {
+ let _p = profile::span("item::Builder::build");
+
+ let mut label = self.label;
+ let mut lookup = self.lookup;
+ let insert_text = self.insert_text.unwrap_or_else(|| label.to_string());
+
+ if let [import_edit] = &*self.imports_to_add {
+ // snippets can have multiple imports, but normal completions only have up to one
+ if let Some(original_path) = import_edit.original_path.as_ref() {
+ lookup = lookup.or_else(|| Some(label.clone()));
+ label = SmolStr::from(format!("{} (use {})", label, original_path));
+ }
+ } else if let Some(trait_name) = self.trait_name {
+ label = SmolStr::from(format!("{} (as {})", label, trait_name));
+ }
+
+ let text_edit = match self.text_edit {
+ Some(it) => it,
+ None => TextEdit::replace(self.source_range, insert_text),
+ };
+
+ CompletionItem {
+ source_range: self.source_range,
+ label,
+ text_edit,
+ is_snippet: self.is_snippet,
+ detail: self.detail,
+ documentation: self.documentation,
+ lookup,
+ kind: self.kind,
+ deprecated: self.deprecated,
+ trigger_call_info: self.trigger_call_info,
+ relevance: self.relevance,
+ ref_match: self.ref_match,
+ import_to_add: self.imports_to_add,
+ }
+ }
+ pub(crate) fn lookup_by(&mut self, lookup: impl Into<SmolStr>) -> &mut Builder {
+ self.lookup = Some(lookup.into());
+ self
+ }
+ pub(crate) fn label(&mut self, label: impl Into<SmolStr>) -> &mut Builder {
+ self.label = label.into();
+ self
+ }
+ pub(crate) fn trait_name(&mut self, trait_name: SmolStr) -> &mut Builder {
+ self.trait_name = Some(trait_name);
+ self
+ }
+ pub(crate) fn insert_text(&mut self, insert_text: impl Into<String>) -> &mut Builder {
+ self.insert_text = Some(insert_text.into());
+ self
+ }
+ pub(crate) fn insert_snippet(
+ &mut self,
+ cap: SnippetCap,
+ snippet: impl Into<String>,
+ ) -> &mut Builder {
+ let _ = cap;
+ self.is_snippet = true;
+ self.insert_text(snippet)
+ }
+ pub(crate) fn text_edit(&mut self, edit: TextEdit) -> &mut Builder {
+ self.text_edit = Some(edit);
+ self
+ }
+ pub(crate) fn snippet_edit(&mut self, _cap: SnippetCap, edit: TextEdit) -> &mut Builder {
+ self.is_snippet = true;
+ self.text_edit(edit)
+ }
+ pub(crate) fn detail(&mut self, detail: impl Into<String>) -> &mut Builder {
+ self.set_detail(Some(detail))
+ }
+ pub(crate) fn set_detail(&mut self, detail: Option<impl Into<String>>) -> &mut Builder {
+ self.detail = detail.map(Into::into);
+ if let Some(detail) = &self.detail {
+ if never!(detail.contains('\n'), "multiline detail:\n{}", detail) {
+ self.detail = Some(detail.splitn(2, '\n').next().unwrap().to_string());
+ }
+ }
+ self
+ }
+ #[allow(unused)]
+ pub(crate) fn documentation(&mut self, docs: Documentation) -> &mut Builder {
+ self.set_documentation(Some(docs))
+ }
+ pub(crate) fn set_documentation(&mut self, docs: Option<Documentation>) -> &mut Builder {
+ self.documentation = docs.map(Into::into);
+ self
+ }
+ pub(crate) fn set_deprecated(&mut self, deprecated: bool) -> &mut Builder {
+ self.deprecated = deprecated;
+ self
+ }
+ pub(crate) fn set_relevance(&mut self, relevance: CompletionRelevance) -> &mut Builder {
+ self.relevance = relevance;
+ self
+ }
+ pub(crate) fn trigger_call_info(&mut self) -> &mut Builder {
+ self.trigger_call_info = true;
+ self
+ }
+ pub(crate) fn add_import(&mut self, import_to_add: LocatedImport) -> &mut Builder {
+ self.imports_to_add.push(import_to_add);
+ self
+ }
+ pub(crate) fn ref_match(&mut self, mutability: Mutability, offset: TextSize) -> &mut Builder {
+ self.ref_match = Some((mutability, offset));
+ self
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use itertools::Itertools;
+ use test_utils::assert_eq_text;
+
+ use super::{
+ CompletionRelevance, CompletionRelevancePostfixMatch, CompletionRelevanceTypeMatch,
+ };
+
+ /// Check that these are CompletionRelevance are sorted in ascending order
+ /// by their relevance score.
+ ///
+ /// We want to avoid making assertions about the absolute score of any
+ /// item, but we do want to assert whether each is >, <, or == to the
+ /// others.
+ ///
+ /// If provided vec![vec![a], vec![b, c], vec![d]], then this will assert:
+ /// a.score < b.score == c.score < d.score
+ fn check_relevance_score_ordered(expected_relevance_order: Vec<Vec<CompletionRelevance>>) {
+ let expected = format!("{:#?}", &expected_relevance_order);
+
+ let actual_relevance_order = expected_relevance_order
+ .into_iter()
+ .flatten()
+ .map(|r| (r.score(), r))
+ .sorted_by_key(|(score, _r)| *score)
+ .fold(
+ (u32::MIN, vec![vec![]]),
+ |(mut currently_collecting_score, mut out), (score, r)| {
+ if currently_collecting_score == score {
+ out.last_mut().unwrap().push(r);
+ } else {
+ currently_collecting_score = score;
+ out.push(vec![r]);
+ }
+ (currently_collecting_score, out)
+ },
+ )
+ .1;
+
+ let actual = format!("{:#?}", &actual_relevance_order);
+
+ assert_eq_text!(&expected, &actual);
+ }
+
+ #[test]
+ fn relevance_score() {
+ use CompletionRelevance as Cr;
+ let default = Cr::default();
+ // This test asserts that the relevance score for these items is ascending, and
+ // that any items in the same vec have the same score.
+ let expected_relevance_order = vec![
+ vec![],
+ vec![Cr { is_op_method: true, is_private_editable: true, ..default }],
+ vec![Cr { is_op_method: true, ..default }],
+ vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::NonExact), ..default }],
+ vec![Cr { is_private_editable: true, ..default }],
+ vec![default],
+ vec![Cr { is_local: true, ..default }],
+ vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::CouldUnify), ..default }],
+ vec![Cr { type_match: Some(CompletionRelevanceTypeMatch::Exact), ..default }],
+ vec![Cr { exact_name_match: true, ..default }],
+ vec![Cr { exact_name_match: true, is_local: true, ..default }],
+ vec![Cr {
+ exact_name_match: true,
+ type_match: Some(CompletionRelevanceTypeMatch::Exact),
+ ..default
+ }],
+ vec![Cr {
+ exact_name_match: true,
+ type_match: Some(CompletionRelevanceTypeMatch::Exact),
+ is_local: true,
+ ..default
+ }],
+ vec![Cr { postfix_match: Some(CompletionRelevancePostfixMatch::Exact), ..default }],
+ ];
+
+ check_relevance_score_ordered(expected_relevance_order);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
new file mode 100644
index 000000000..ae1a440d0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs
@@ -0,0 +1,247 @@
+//! `completions` crate provides utilities for generating completions of user input.
+
+#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
+
+mod completions;
+mod config;
+mod context;
+mod item;
+mod render;
+
+#[cfg(test)]
+mod tests;
+mod snippet;
+
+use ide_db::{
+ base_db::FilePosition,
+ helpers::mod_path_to_ast,
+ imports::{
+ import_assets::NameToImport,
+ insert_use::{self, ImportScope},
+ },
+ items_locator, RootDatabase,
+};
+use syntax::algo;
+use text_edit::TextEdit;
+
+use crate::{
+ completions::Completions,
+ context::{
+ CompletionAnalysis, CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx,
+ PathKind,
+ },
+};
+
+pub use crate::{
+ config::{CallableSnippets, CompletionConfig},
+ item::{
+ CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
+ },
+ snippet::{Snippet, SnippetScope},
+};
+
+//FIXME: split the following feature into fine-grained features.
+
+// Feature: Magic Completions
+//
+// In addition to usual reference completion, rust-analyzer provides some ✨magic✨
+// completions as well:
+//
+// Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor
+// is placed at the appropriate position. Even though `if` is easy to type, you
+// still want to complete it, to get ` { }` for free! `return` is inserted with a
+// space or `;` depending on the return type of the function.
+//
+// When completing a function call, `()` are automatically inserted. If a function
+// takes arguments, the cursor is positioned inside the parenthesis.
+//
+// There are postfix completions, which can be triggered by typing something like
+// `foo().if`. The word after `.` determines postfix completion. Possible variants are:
+//
+// - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result`
+// - `expr.match` -> `match expr {}`
+// - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result`
+// - `expr.ref` -> `&expr`
+// - `expr.refm` -> `&mut expr`
+// - `expr.let` -> `let $0 = expr;`
+// - `expr.letm` -> `let mut $0 = expr;`
+// - `expr.not` -> `!expr`
+// - `expr.dbg` -> `dbg!(expr)`
+// - `expr.dbgr` -> `dbg!(&expr)`
+// - `expr.call` -> `(expr)`
+//
+// There also snippet completions:
+//
+// .Expressions
+// - `pd` -> `eprintln!(" = {:?}", );`
+// - `ppd` -> `eprintln!(" = {:#?}", );`
+//
+// .Items
+// - `tfn` -> `#[test] fn feature(){}`
+// - `tmod` ->
+// ```rust
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+//
+// #[test]
+// fn test_name() {}
+// }
+// ```
+//
+// And the auto import completions, enabled with the `rust-analyzer.completion.autoimport.enable` setting and the corresponding LSP client capabilities.
+// Those are the additional completion options with automatic `use` import and options from all project importable items,
+// fuzzy matched against the completion input.
+//
+// image::https://user-images.githubusercontent.com/48062697/113020667-b72ab880-917a-11eb-8778-716cf26a0eb3.gif[]
+
+/// Main entry point for completion. We run completion as a two-phase process.
+///
+/// First, we look at the position and collect a so-called `CompletionContext.
+/// This is a somewhat messy process, because, during completion, syntax tree is
+/// incomplete and can look really weird.
+///
+/// Once the context is collected, we run a series of completion routines which
+/// look at the context and produce completion items. One subtlety about this
+/// phase is that completion engine should not filter by the substring which is
+/// already present, it should give all possible variants for the identifier at
+/// the caret. In other words, for
+///
+/// ```no_run
+/// fn f() {
+/// let foo = 92;
+/// let _ = bar$0
+/// }
+/// ```
+///
+/// `foo` *should* be present among the completion variants. Filtering by
+/// identifier prefix/fuzzy match should be done higher in the stack, together
+/// with ordering of completions (currently this is done by the client).
+///
+/// # Speculative Completion Problem
+///
+/// There's a curious unsolved problem in the current implementation. Often, you
+/// want to compute completions on a *slightly different* text document.
+///
+/// In the simplest case, when the code looks like `let x = `, you want to
+/// insert a fake identifier to get a better syntax tree: `let x = complete_me`.
+///
+/// We do this in `CompletionContext`, and it works OK-enough for *syntax*
+/// analysis. However, we might want to, eg, ask for the type of `complete_me`
+/// variable, and that's where our current infrastructure breaks down. salsa
+/// doesn't allow such "phantom" inputs.
+///
+/// Another case where this would be instrumental is macro expansion. We want to
+/// insert a fake ident and re-expand code. There's `expand_speculative` as a
+/// work-around for this.
+///
+/// A different use-case is completion of injection (examples and links in doc
+/// comments). When computing completion for a path in a doc-comment, you want
+/// to inject a fake path expression into the item being documented and complete
+/// that.
+///
+/// IntelliJ has CodeFragment/Context infrastructure for that. You can create a
+/// temporary PSI node, and say that the context ("parent") of this node is some
+/// existing node. Asking for, eg, type of this `CodeFragment` node works
+/// correctly, as the underlying infrastructure makes use of contexts to do
+/// analysis.
+pub fn completions(
+ db: &RootDatabase,
+ config: &CompletionConfig,
+ position: FilePosition,
+ trigger_character: Option<char>,
+) -> Option<Vec<CompletionItem>> {
+ let (ctx, analysis) = &CompletionContext::new(db, position, config)?;
+ let mut completions = Completions::default();
+
+ // prevent `(` from triggering unwanted completion noise
+ if trigger_character == Some('(') {
+ if let CompletionAnalysis::NameRef(NameRefContext { kind, .. }) = &analysis {
+ if let NameRefKind::Path(
+ path_ctx @ PathCompletionCtx { kind: PathKind::Vis { has_in_token }, .. },
+ ) = kind
+ {
+ completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token);
+ }
+ }
+ // prevent `(` from triggering unwanted completion noise
+ return Some(completions.into());
+ }
+
+ {
+ let acc = &mut completions;
+
+ match &analysis {
+ CompletionAnalysis::Name(name_ctx) => completions::complete_name(acc, ctx, name_ctx),
+ CompletionAnalysis::NameRef(name_ref_ctx) => {
+ completions::complete_name_ref(acc, ctx, name_ref_ctx)
+ }
+ CompletionAnalysis::Lifetime(lifetime_ctx) => {
+ completions::lifetime::complete_label(acc, ctx, lifetime_ctx);
+ completions::lifetime::complete_lifetime(acc, ctx, lifetime_ctx);
+ }
+ CompletionAnalysis::String { original, expanded: Some(expanded) } => {
+ completions::extern_abi::complete_extern_abi(acc, ctx, expanded);
+ completions::format_string::format_string(acc, ctx, original, expanded);
+ }
+ CompletionAnalysis::UnexpandedAttrTT {
+ colon_prefix,
+ fake_attribute_under_caret: Some(attr),
+ } => {
+ completions::attribute::complete_known_attribute_input(
+ acc,
+ ctx,
+ colon_prefix,
+ attr,
+ );
+ }
+ CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
+ }
+ }
+
+ Some(completions.into())
+}
+
+/// Resolves additional completion data at the position given.
+/// This is used for import insertion done via completions like flyimport and custom user snippets.
+pub fn resolve_completion_edits(
+ db: &RootDatabase,
+ config: &CompletionConfig,
+ FilePosition { file_id, offset }: FilePosition,
+ imports: impl IntoIterator<Item = (String, String)>,
+) -> Option<Vec<TextEdit>> {
+ let _p = profile::span("resolve_completion_edits");
+ let sema = hir::Semantics::new(db);
+
+ let original_file = sema.parse(file_id);
+ let original_token =
+ syntax::AstNode::syntax(&original_file).token_at_offset(offset).left_biased()?;
+ let position_for_import = &original_token.parent()?;
+ let scope = ImportScope::find_insert_use_container(position_for_import, &sema)?;
+
+ let current_module = sema.scope(position_for_import)?.module();
+ let current_crate = current_module.krate();
+ let new_ast = scope.clone_for_update();
+ let mut import_insert = TextEdit::builder();
+
+ imports.into_iter().for_each(|(full_import_path, imported_name)| {
+ let items_with_name = items_locator::items_with_name(
+ &sema,
+ current_crate,
+ NameToImport::exact_case_sensitive(imported_name),
+ items_locator::AssocItemSearch::Include,
+ Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()),
+ );
+ let import = items_with_name
+ .filter_map(|candidate| {
+ current_module.find_use_path_prefixed(db, candidate, config.insert_use.prefix_kind)
+ })
+ .find(|mod_path| mod_path.to_string() == full_import_path);
+ if let Some(import_path) = import {
+ insert_use::insert_use(&new_ast, mod_path_to_ast(&import_path), &config.insert_use);
+ }
+ });
+
+ algo::diff(scope.as_syntax_node(), new_ast.as_syntax_node()).into_text_edit(&mut import_insert);
+ Some(vec![import_insert.finish()])
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
new file mode 100644
index 000000000..946134b0f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs
@@ -0,0 +1,1910 @@
+//! `render` module provides utilities for rendering completion suggestions
+//! into code pieces that will be presented to user.
+
+pub(crate) mod macro_;
+pub(crate) mod function;
+pub(crate) mod const_;
+pub(crate) mod pattern;
+pub(crate) mod type_alias;
+pub(crate) mod variant;
+pub(crate) mod union_literal;
+pub(crate) mod literal;
+
+use hir::{AsAssocItem, HasAttrs, HirDisplay, ScopeDef};
+use ide_db::{
+ helpers::item_name, imports::import_assets::LocatedImport, RootDatabase, SnippetCap, SymbolKind,
+};
+use syntax::{AstNode, SmolStr, SyntaxKind, TextRange};
+
+use crate::{
+ context::{DotAccess, PathCompletionCtx, PathKind, PatternContext},
+ item::{Builder, CompletionRelevanceTypeMatch},
+ 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.
+#[derive(Debug, Clone)]
+pub(crate) struct RenderContext<'a> {
+ completion: &'a CompletionContext<'a>,
+ is_private_editable: bool,
+ import_to_add: Option<LocatedImport>,
+}
+
+impl<'a> RenderContext<'a> {
+ pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> {
+ RenderContext { completion, is_private_editable: false, import_to_add: None }
+ }
+
+ pub(crate) fn private_editable(mut self, private_editable: bool) -> Self {
+ self.is_private_editable = private_editable;
+ self
+ }
+
+ pub(crate) fn import_to_add(mut self, import_to_add: Option<LocatedImport>) -> Self {
+ self.import_to_add = import_to_add;
+ self
+ }
+
+ fn snippet_cap(&self) -> Option<SnippetCap> {
+ self.completion.config.snippet_cap
+ }
+
+ fn db(&self) -> &'a RootDatabase {
+ self.completion.db
+ }
+
+ fn source_range(&self) -> TextRange {
+ self.completion.source_range()
+ }
+
+ fn completion_relevance(&self) -> CompletionRelevance {
+ CompletionRelevance {
+ is_private_editable: self.is_private_editable,
+ requires_import: self.import_to_add.is_some(),
+ ..Default::default()
+ }
+ }
+
+ 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()
+ }
+
+ fn is_deprecated_assoc_item(&self, as_assoc_item: impl AsAssocItem) -> bool {
+ let db = self.db();
+ let assoc = match as_assoc_item.as_assoc_item(db) {
+ Some(assoc) => assoc,
+ None => return false,
+ };
+
+ let is_assoc_deprecated = match assoc {
+ hir::AssocItem::Function(it) => self.is_deprecated(it),
+ hir::AssocItem::Const(it) => self.is_deprecated(it),
+ hir::AssocItem::TypeAlias(it) => self.is_deprecated(it),
+ };
+ is_assoc_deprecated
+ || assoc
+ .containing_trait_or_trait_impl(db)
+ .map(|trait_| self.is_deprecated(trait_))
+ .unwrap_or(false)
+ }
+
+ // FIXME: remove this
+ fn docs(&self, def: impl HasAttrs) -> Option<hir::Documentation> {
+ def.docs(self.db())
+ }
+}
+
+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());
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let mut item = CompletionItem::new(
+ SymbolKind::Field,
+ ctx.source_range(),
+ field_with_receiver(receiver.as_ref(), &name),
+ );
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(ctx.completion, ty),
+ exact_name_match: compute_exact_name_match(ctx.completion, name.as_str()),
+ ..CompletionRelevance::default()
+ });
+ item.detail(ty.display(ctx.db()).to_string())
+ .set_documentation(field.docs(ctx.db()))
+ .set_deprecated(is_deprecated)
+ .lookup_by(name.clone());
+ 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>,
+ field: usize,
+ ty: &hir::Type,
+) -> CompletionItem {
+ let mut item = CompletionItem::new(
+ SymbolKind::Field,
+ ctx.source_range(),
+ 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_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_path(ctx, path_ctx, local_name, None, resolution)
+}
+
+pub(crate) fn render_pattern_resolution(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ local_name: hir::Name,
+ resolution: ScopeDef,
+) -> Builder {
+ 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 = 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)?,
+ })
+}
+
+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_path(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ 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(ctx, path_ctx, local_name, mac);
+ }
+ ScopeDef::ModuleDef(Function(func)) => {
+ let ctx = ctx.import_to_add(import_to_add);
+ return render_fn(ctx, path_ctx, Some(local_name), func);
+ }
+ ScopeDef::ModuleDef(Variant(var)) => {
+ let ctx = ctx.clone().import_to_add(import_to_add.clone());
+ if let Some(item) =
+ render_variant_lit(ctx, path_ctx, Some(local_name.clone()), var, None)
+ {
+ return item;
+ }
+ }
+ _ => (),
+ }
+
+ 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.escaped().is_escaped() {
+ item.insert_text(local_name.escaped().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.escaped()));
+ }
+ }
+ }
+ 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,
+ import_to_add: Option<LocatedImport>,
+ resolution: ScopeDef,
+) -> Builder {
+ let _p = profile::span("render_resolution");
+
+ let db = ctx.db();
+ let ctx = ctx.import_to_add(import_to_add);
+ let kind = res_to_kind(resolution);
+
+ let mut item = CompletionItem::new(kind, ctx.source_range(), local_name.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::ModuleDef(Macro(_)) => CompletionItemKind::SymbolKind(SymbolKind::Macro),
+ ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::SymbolKind(SymbolKind::Module),
+ ScopeDef::ModuleDef(Adt(adt)) => CompletionItemKind::SymbolKind(match adt {
+ hir::Adt::Struct(_) => SymbolKind::Struct,
+ hir::Adt::Union(_) => SymbolKind::Union,
+ hir::Adt::Enum(_) => SymbolKind::Enum,
+ }),
+ ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::SymbolKind(SymbolKind::Const),
+ ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::SymbolKind(SymbolKind::Static),
+ ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::SymbolKind(SymbolKind::Trait),
+ ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::SymbolKind(SymbolKind::TypeAlias),
+ ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType,
+ ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param {
+ hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam,
+ hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam,
+ hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam,
+ }),
+ ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local),
+ ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label),
+ ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => {
+ CompletionItemKind::SymbolKind(SymbolKind::SelfParam)
+ }
+ }
+}
+
+fn scope_def_docs(db: &RootDatabase, resolution: ScopeDef) -> Option<hir::Documentation> {
+ use hir::ModuleDef::*;
+ match resolution {
+ ScopeDef::ModuleDef(Module(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Adt(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Variant(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Const(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Static(it)) => it.docs(db),
+ ScopeDef::ModuleDef(Trait(it)) => it.docs(db),
+ ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(db),
+ _ => None,
+ }
+}
+
+fn scope_def_is_deprecated(ctx: &RenderContext<'_>, resolution: ScopeDef) -> bool {
+ match resolution {
+ ScopeDef::ModuleDef(it) => ctx.is_deprecated_assoc_item(it),
+ ScopeDef::GenericParam(it) => ctx.is_deprecated(it),
+ ScopeDef::AdtSelfType(it) => ctx.is_deprecated(it),
+ _ => false,
+ }
+}
+
+fn compute_type_match(
+ ctx: &CompletionContext<'_>,
+ completion_ty: &hir::Type,
+) -> Option<CompletionRelevanceTypeMatch> {
+ let expected_type = ctx.expected_type.as_ref()?;
+
+ // We don't ever consider unit type to be an exact type match, since
+ // nearly always this is not meaningful to the user.
+ if expected_type.is_unit() {
+ return None;
+ }
+
+ if completion_ty == expected_type {
+ Some(CompletionRelevanceTypeMatch::Exact)
+ } else if expected_type.could_unify_with(ctx.db, completion_ty) {
+ Some(CompletionRelevanceTypeMatch::CouldUnify)
+ } else {
+ None
+ }
+}
+
+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<'_>,
+ completion_ty: &hir::Type,
+) -> Option<hir::Mutability> {
+ let expected_type = ctx.expected_type.as_ref()?;
+ if completion_ty != expected_type {
+ let expected_type_without_ref = expected_type.remove_ref()?;
+ if completion_ty.autoderef(ctx.db).any(|deref_ty| deref_ty == expected_type_without_ref) {
+ cov_mark::hit!(suggest_ref);
+ let mutability = if expected_type.is_mutable_reference() {
+ hir::Mutability::Mut
+ } else {
+ hir::Mutability::Shared
+ };
+ return Some(mutability);
+ };
+ }
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use std::cmp;
+
+ use expect_test::{expect, Expect};
+ use ide_db::SymbolKind;
+ use itertools::Itertools;
+
+ use crate::{
+ item::CompletionRelevanceTypeMatch,
+ tests::{check_edit, do_completion, get_all_items, TEST_CONFIG},
+ CompletionItem, CompletionItemKind, CompletionRelevance, CompletionRelevancePostfixMatch,
+ };
+
+ #[track_caller]
+ fn check(ra_fixture: &str, kind: impl Into<CompletionItemKind>, expect: Expect) {
+ let actual = do_completion(ra_fixture, kind.into());
+ expect.assert_debug_eq(&actual);
+ }
+
+ #[track_caller]
+ fn check_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
+ let actual: Vec<_> =
+ kinds.iter().flat_map(|&kind| do_completion(ra_fixture, kind)).collect();
+ expect.assert_debug_eq(&actual);
+ }
+
+ #[track_caller]
+ fn check_relevance_for_kinds(ra_fixture: &str, kinds: &[CompletionItemKind], expect: Expect) {
+ 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, None);
+ actual.retain(|it| it.kind() != CompletionItemKind::Snippet);
+ actual.retain(|it| it.kind() != CompletionItemKind::Keyword);
+ actual.retain(|it| it.kind() != CompletionItemKind::BuiltinType);
+ actual.sort_by_key(|it| cmp::Reverse(it.relevance().score()));
+ check_relevance_(actual, expect);
+ }
+
+ #[track_caller]
+ fn check_relevance_(actual: Vec<CompletionItem>, expect: Expect) {
+ let actual = actual
+ .into_iter()
+ .flat_map(|it| {
+ let mut items = vec![];
+
+ let tag = it.kind().tag();
+ let relevance = display_relevance(it.relevance());
+ items.push(format!("{} {} {}\n", tag, it.label(), relevance));
+
+ if let Some((mutability, _offset, relevance)) = it.ref_match() {
+ let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label());
+ let relevance = display_relevance(relevance);
+
+ items.push(format!("{} {} {}\n", tag, label, relevance));
+ }
+
+ items
+ })
+ .collect::<String>();
+
+ expect.assert_eq(&actual);
+
+ fn display_relevance(relevance: CompletionRelevance) -> String {
+ let relevance_factors = vec![
+ (relevance.type_match == Some(CompletionRelevanceTypeMatch::Exact), "type"),
+ (
+ relevance.type_match == Some(CompletionRelevanceTypeMatch::CouldUnify),
+ "type_could_unify",
+ ),
+ (relevance.exact_name_match, "name"),
+ (relevance.is_local, "local"),
+ (
+ relevance.postfix_match == Some(CompletionRelevancePostfixMatch::Exact),
+ "snippet",
+ ),
+ (relevance.is_op_method, "op_method"),
+ (relevance.requires_import, "requires_import"),
+ ]
+ .into_iter()
+ .filter_map(|(cond, desc)| if cond { Some(desc) } else { None })
+ .join("+");
+
+ format!("[{}]", relevance_factors)
+ }
+ }
+
+ #[test]
+ fn enum_detail_includes_record_fields() {
+ check(
+ r#"
+enum Foo { Foo { x: i32, y: i32 } }
+
+fn main() { Foo::Fo$0 }
+"#,
+ SymbolKind::Variant,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "Foo {…}",
+ source_range: 54..56,
+ delete: 54..56,
+ insert: "Foo { x: ${1:()}, y: ${2:()} }$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "Foo { x: i32, y: i32 }",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_detail_includes_tuple_fields() {
+ check(
+ r#"
+enum Foo { Foo (i32, i32) }
+
+fn main() { Foo::Fo$0 }
+"#,
+ SymbolKind::Variant,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "Foo(…)",
+ source_range: 46..48,
+ delete: 46..48,
+ insert: "Foo(${1:()}, ${2:()})$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "Foo(i32, i32)",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn fn_detail_includes_args_and_return_type() {
+ check(
+ r#"
+fn foo<T>(a: u32, b: u32, t: T) -> (u32, T) { (a, t) }
+
+fn main() { fo$0 }
+"#,
+ SymbolKind::Function,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "foo(…)",
+ source_range: 68..70,
+ delete: 68..70,
+ insert: "foo(${1:a}, ${2:b}, ${3:t})$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "foo",
+ detail: "fn(u32, u32, T) -> (u32, T)",
+ trigger_call_info: true,
+ },
+ CompletionItem {
+ label: "main()",
+ source_range: 68..70,
+ delete: 68..70,
+ insert: "main()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "main",
+ detail: "fn()",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_detail_just_name_for_unit() {
+ check(
+ r#"
+enum Foo { Foo }
+
+fn main() { Foo::Fo$0 }
+"#,
+ SymbolKind::Variant,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "Foo",
+ source_range: 35..37,
+ delete: 35..37,
+ insert: "Foo$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "Foo",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn lookup_enums_by_two_qualifiers() {
+ check_kinds(
+ r#"
+mod m {
+ pub enum Spam { Foo, Bar(i32) }
+}
+fn main() { let _: m::Spam = S$0 }
+"#,
+ &[
+ CompletionItemKind::SymbolKind(SymbolKind::Function),
+ CompletionItemKind::SymbolKind(SymbolKind::Module),
+ CompletionItemKind::SymbolKind(SymbolKind::Variant),
+ ],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "main()",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "main()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "main",
+ detail: "fn()",
+ },
+ CompletionItem {
+ label: "m",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "m",
+ kind: SymbolKind(
+ Module,
+ ),
+ },
+ CompletionItem {
+ label: "m::Spam::Bar(…)",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "m::Spam::Bar(${1:()})$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ lookup: "Spam::Bar(…)",
+ detail: "m::Spam::Bar(i32)",
+ relevance: CompletionRelevance {
+ exact_name_match: false,
+ type_match: Some(
+ Exact,
+ ),
+ is_local: false,
+ is_item_from_trait: false,
+ is_name_already_imported: false,
+ requires_import: false,
+ is_op_method: false,
+ is_private_editable: false,
+ postfix_match: None,
+ is_definite: false,
+ },
+ },
+ CompletionItem {
+ label: "m::Spam::Foo",
+ source_range: 75..76,
+ delete: 75..76,
+ insert: "m::Spam::Foo$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ lookup: "Spam::Foo",
+ detail: "m::Spam::Foo",
+ relevance: CompletionRelevance {
+ exact_name_match: false,
+ type_match: Some(
+ Exact,
+ ),
+ is_local: false,
+ is_item_from_trait: false,
+ is_name_already_imported: false,
+ requires_import: false,
+ is_op_method: false,
+ is_private_editable: false,
+ postfix_match: None,
+ is_definite: false,
+ },
+ },
+ ]
+ "#]],
+ )
+ }
+
+ #[test]
+ fn sets_deprecated_flag_in_items() {
+ check(
+ r#"
+#[deprecated]
+fn something_deprecated() {}
+
+fn main() { som$0 }
+"#,
+ SymbolKind::Function,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "main()",
+ source_range: 56..59,
+ delete: 56..59,
+ insert: "main()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "main",
+ detail: "fn()",
+ },
+ CompletionItem {
+ label: "something_deprecated()",
+ source_range: 56..59,
+ delete: 56..59,
+ insert: "something_deprecated()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "something_deprecated",
+ detail: "fn()",
+ deprecated: true,
+ },
+ ]
+ "#]],
+ );
+
+ check(
+ r#"
+struct A { #[deprecated] the_field: u32 }
+fn foo() { A { the$0 } }
+"#,
+ SymbolKind::Field,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "the_field",
+ source_range: 57..60,
+ delete: 57..60,
+ insert: "the_field",
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "u32",
+ deprecated: true,
+ relevance: CompletionRelevance {
+ exact_name_match: false,
+ type_match: Some(
+ CouldUnify,
+ ),
+ is_local: false,
+ is_item_from_trait: false,
+ is_name_already_imported: false,
+ requires_import: false,
+ is_op_method: false,
+ is_private_editable: false,
+ postfix_match: None,
+ is_definite: false,
+ },
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn renders_docs() {
+ check_kinds(
+ r#"
+struct S {
+ /// Field docs
+ foo:
+}
+impl S {
+ /// Method docs
+ fn bar(self) { self.$0 }
+}"#,
+ &[CompletionItemKind::Method, CompletionItemKind::SymbolKind(SymbolKind::Field)],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "bar()",
+ source_range: 94..94,
+ delete: 94..94,
+ insert: "bar()$0",
+ kind: Method,
+ lookup: "bar",
+ detail: "fn(self)",
+ documentation: Documentation(
+ "Method docs",
+ ),
+ },
+ CompletionItem {
+ label: "foo",
+ source_range: 94..94,
+ delete: 94..94,
+ insert: "foo",
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "{unknown}",
+ documentation: Documentation(
+ "Field docs",
+ ),
+ },
+ ]
+ "#]],
+ );
+
+ check_kinds(
+ r#"
+use self::my$0;
+
+/// mod docs
+mod my { }
+
+/// enum docs
+enum E {
+ /// variant docs
+ V
+}
+use self::E::*;
+"#,
+ &[
+ CompletionItemKind::SymbolKind(SymbolKind::Module),
+ CompletionItemKind::SymbolKind(SymbolKind::Variant),
+ CompletionItemKind::SymbolKind(SymbolKind::Enum),
+ ],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "my",
+ source_range: 10..12,
+ delete: 10..12,
+ insert: "my",
+ kind: SymbolKind(
+ Module,
+ ),
+ documentation: Documentation(
+ "mod docs",
+ ),
+ },
+ CompletionItem {
+ label: "V",
+ source_range: 10..12,
+ delete: 10..12,
+ insert: "V$0",
+ kind: SymbolKind(
+ Variant,
+ ),
+ detail: "V",
+ documentation: Documentation(
+ "variant docs",
+ ),
+ },
+ CompletionItem {
+ label: "E",
+ source_range: 10..12,
+ delete: 10..12,
+ insert: "E",
+ kind: SymbolKind(
+ Enum,
+ ),
+ documentation: Documentation(
+ "enum docs",
+ ),
+ },
+ ]
+ "#]],
+ )
+ }
+
+ #[test]
+ fn dont_render_attrs() {
+ check(
+ r#"
+struct S;
+impl S {
+ #[inline]
+ fn the_method(&self) { }
+}
+fn foo(s: S) { s.$0 }
+"#,
+ CompletionItemKind::Method,
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "the_method()",
+ source_range: 81..81,
+ delete: 81..81,
+ insert: "the_method()$0",
+ kind: Method,
+ lookup: "the_method",
+ detail: "fn(&self)",
+ },
+ ]
+ "#]],
+ )
+ }
+
+ #[test]
+ fn no_call_parens_if_fn_ptr_needed() {
+ cov_mark::check!(no_call_parens_if_fn_ptr_needed);
+ check_edit(
+ "foo",
+ r#"
+fn foo(foo: u8, bar: u8) {}
+struct ManualVtable { f: fn(u8, u8) }
+
+fn main() -> ManualVtable {
+ ManualVtable { f: f$0 }
+}
+"#,
+ r#"
+fn foo(foo: u8, bar: u8) {}
+struct ManualVtable { f: fn(u8, u8) }
+
+fn main() -> ManualVtable {
+ ManualVtable { f: foo }
+}
+"#,
+ );
+ check_edit(
+ "type",
+ r#"
+struct RawIdentTable { r#type: u32 }
+
+fn main() -> RawIdentTable {
+ RawIdentTable { t$0: 42 }
+}
+"#,
+ r#"
+struct RawIdentTable { r#type: u32 }
+
+fn main() -> RawIdentTable {
+ RawIdentTable { r#type: 42 }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn no_parens_in_use_item() {
+ check_edit(
+ "foo",
+ r#"
+mod m { pub fn foo() {} }
+use crate::m::f$0;
+"#,
+ r#"
+mod m { pub fn foo() {} }
+use crate::m::foo;
+"#,
+ );
+ }
+
+ #[test]
+ fn no_parens_in_call() {
+ check_edit(
+ "foo",
+ r#"
+fn foo(x: i32) {}
+fn main() { f$0(); }
+"#,
+ r#"
+fn foo(x: i32) {}
+fn main() { foo(); }
+"#,
+ );
+ check_edit(
+ "foo",
+ r#"
+struct Foo;
+impl Foo { fn foo(&self){} }
+fn f(foo: &Foo) { foo.f$0(); }
+"#,
+ r#"
+struct Foo;
+impl Foo { fn foo(&self){} }
+fn f(foo: &Foo) { foo.foo(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn inserts_angle_brackets_for_generics() {
+ cov_mark::check!(inserts_angle_brackets_for_generics);
+ check_edit(
+ "Vec",
+ r#"
+struct Vec<T> {}
+fn foo(xs: Ve$0)
+"#,
+ r#"
+struct Vec<T> {}
+fn foo(xs: Vec<$0>)
+"#,
+ );
+ check_edit(
+ "Vec",
+ r#"
+type Vec<T> = (T,);
+fn foo(xs: Ve$0)
+"#,
+ r#"
+type Vec<T> = (T,);
+fn foo(xs: Vec<$0>)
+"#,
+ );
+ check_edit(
+ "Vec",
+ r#"
+struct Vec<T = i128> {}
+fn foo(xs: Ve$0)
+"#,
+ r#"
+struct Vec<T = i128> {}
+fn foo(xs: Vec)
+"#,
+ );
+ check_edit(
+ "Vec",
+ r#"
+struct Vec<T> {}
+fn foo(xs: Ve$0<i128>)
+"#,
+ r#"
+struct Vec<T> {}
+fn foo(xs: Vec<i128>)
+"#,
+ );
+ }
+
+ #[test]
+ fn active_param_relevance() {
+ check_relevance(
+ r#"
+struct S { foo: i64, bar: u32, baz: u32 }
+fn test(bar: u32) { }
+fn foo(s: S) { test(s.$0) }
+"#,
+ expect![[r#"
+ fd bar [type+name]
+ fd baz [type]
+ fd foo []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn record_field_relevances() {
+ check_relevance(
+ r#"
+struct A { foo: i64, bar: u32, baz: u32 }
+struct B { x: (), y: f32, bar: u32 }
+fn foo(a: A) { B { bar: a.$0 }; }
+"#,
+ expect![[r#"
+ fd bar [type+name]
+ fd baz [type]
+ fd foo []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn record_field_and_call_relevances() {
+ check_relevance(
+ r#"
+struct A { foo: i64, bar: u32, baz: u32 }
+struct B { x: (), y: f32, bar: u32 }
+fn f(foo: i64) { }
+fn foo(a: A) { B { bar: f(a.$0) }; }
+"#,
+ expect![[r#"
+ fd foo [type+name]
+ fd bar []
+ fd baz []
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct A { foo: i64, bar: u32, baz: u32 }
+struct B { x: (), y: f32, bar: u32 }
+fn f(foo: i64) { }
+fn foo(a: A) { f(B { bar: a.$0 }); }
+"#,
+ expect![[r#"
+ fd bar [type+name]
+ fd baz [type]
+ fd foo []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn prioritize_exact_ref_match() {
+ check_relevance(
+ r#"
+struct WorldSnapshot { _f: () };
+fn go(world: &WorldSnapshot) { go(w$0) }
+"#,
+ expect![[r#"
+ lc world [type+name+local]
+ st WorldSnapshot {…} []
+ st &WorldSnapshot {…} [type]
+ st WorldSnapshot []
+ fn go(…) []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn too_many_arguments() {
+ cov_mark::check!(too_many_arguments);
+ check_relevance(
+ r#"
+struct Foo;
+fn f(foo: &Foo) { f(foo, w$0) }
+"#,
+ expect![[r#"
+ lc foo [local]
+ st Foo []
+ fn f(…) []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn score_fn_type_and_name_match() {
+ check_relevance(
+ r#"
+struct A { bar: u8 }
+fn baz() -> u8 { 0 }
+fn bar() -> u8 { 0 }
+fn f() { A { bar: b$0 }; }
+"#,
+ expect![[r#"
+ fn bar() [type+name]
+ fn baz() [type]
+ st A []
+ fn f() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn score_method_type_and_name_match() {
+ check_relevance(
+ r#"
+fn baz(aaa: u32){}
+struct Foo;
+impl Foo {
+fn aaa(&self) -> u32 { 0 }
+fn bbb(&self) -> u32 { 0 }
+fn ccc(&self) -> u64 { 0 }
+}
+fn f() {
+ baz(Foo.$0
+}
+"#,
+ expect![[r#"
+ me aaa() [type+name]
+ me bbb() [type]
+ me ccc() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn score_method_name_match_only() {
+ check_relevance(
+ r#"
+fn baz(aaa: u32){}
+struct Foo;
+impl Foo {
+fn aaa(&self) -> u64 { 0 }
+}
+fn f() {
+ baz(Foo.$0
+}
+"#,
+ expect![[r#"
+ me aaa() [name]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn suggest_ref_mut() {
+ cov_mark::check!(suggest_ref);
+ check_relevance(
+ r#"
+struct S;
+fn foo(s: &mut S) {}
+fn main() {
+ let mut s = S;
+ foo($0);
+}
+ "#,
+ expect![[r#"
+ lc s [name+local]
+ lc &mut s [type+name+local]
+ st S []
+ st &mut S [type]
+ st S []
+ fn foo(…) []
+ fn main() []
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct S;
+fn foo(s: &mut S) {}
+fn main() {
+ let mut s = S;
+ foo(&mut $0);
+}
+ "#,
+ expect![[r#"
+ lc s [type+name+local]
+ st S [type]
+ st S []
+ fn foo(…) []
+ fn main() []
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct S;
+fn foo(s: &mut S) {}
+fn main() {
+ let mut ssss = S;
+ foo(&mut s$0);
+}
+ "#,
+ expect![[r#"
+ lc ssss [type+local]
+ st S [type]
+ st S []
+ fn foo(…) []
+ fn main() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn suggest_deref() {
+ check_relevance(
+ r#"
+//- minicore: deref
+struct S;
+struct T(S);
+
+impl core::ops::Deref for T {
+ type Target = S;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn foo(s: &S) {}
+
+fn main() {
+ let t = T(S);
+ let m = 123;
+
+ foo($0);
+}
+ "#,
+ expect![[r#"
+ lc m [local]
+ lc t [local]
+ lc &t [type+local]
+ st S []
+ st &S [type]
+ st S []
+ st T []
+ fn foo(…) []
+ fn main() []
+ md core []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn suggest_deref_mut() {
+ check_relevance(
+ r#"
+//- minicore: deref_mut
+struct S;
+struct T(S);
+
+impl core::ops::Deref for T {
+ type Target = S;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl core::ops::DerefMut for T {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+fn foo(s: &mut S) {}
+
+fn main() {
+ let t = T(S);
+ let m = 123;
+
+ foo($0);
+}
+ "#,
+ expect![[r#"
+ lc m [local]
+ lc t [local]
+ lc &mut t [type+local]
+ st S []
+ st &mut S [type]
+ st S []
+ st T []
+ fn foo(…) []
+ fn main() []
+ md core []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn locals() {
+ check_relevance(
+ r#"
+fn foo(bar: u32) {
+ let baz = 0;
+
+ f$0
+}
+"#,
+ expect![[r#"
+ lc baz [local]
+ lc bar [local]
+ fn foo(…) []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_owned() {
+ check_relevance(
+ r#"
+enum Foo { A, B }
+fn foo() {
+ bar($0);
+}
+fn bar(t: Foo) {}
+"#,
+ expect![[r#"
+ ev Foo::A [type]
+ ev Foo::B [type]
+ en Foo []
+ fn bar(…) []
+ fn foo() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn enum_ref() {
+ check_relevance(
+ r#"
+enum Foo { A, B }
+fn foo() {
+ bar($0);
+}
+fn bar(t: &Foo) {}
+"#,
+ expect![[r#"
+ ev Foo::A []
+ ev &Foo::A [type]
+ ev Foo::B []
+ ev &Foo::B [type]
+ en Foo []
+ fn bar(…) []
+ fn foo() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn suggest_deref_fn_ret() {
+ check_relevance(
+ r#"
+//- minicore: deref
+struct S;
+struct T(S);
+
+impl core::ops::Deref for T {
+ type Target = S;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+fn foo(s: &S) {}
+fn bar() -> T {}
+
+fn main() {
+ foo($0);
+}
+"#,
+ expect![[r#"
+ st S []
+ st &S [type]
+ st S []
+ st T []
+ fn bar() []
+ fn &bar() [type]
+ fn foo(…) []
+ fn main() []
+ md core []
+ "#]],
+ )
+ }
+
+ #[test]
+ fn op_function_relevances() {
+ check_relevance(
+ r#"
+#[lang = "sub"]
+trait Sub {
+ fn sub(self, other: Self) -> Self { self }
+}
+impl Sub for u32 {}
+fn foo(a: u32) { a.$0 }
+"#,
+ expect![[r#"
+ me sub(…) (as Sub) [op_method]
+ "#]],
+ );
+ check_relevance(
+ r#"
+struct Foo;
+impl Foo {
+ fn new() -> Self {}
+}
+#[lang = "eq"]
+pub trait PartialEq<Rhs: ?Sized = Self> {
+ fn eq(&self, other: &Rhs) -> bool;
+ fn ne(&self, other: &Rhs) -> bool;
+}
+
+impl PartialEq for Foo {}
+fn main() {
+ Foo::$0
+}
+"#,
+ expect![[r#"
+ fn new() []
+ me eq(…) (as PartialEq) [op_method]
+ me ne(…) (as PartialEq) [op_method]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn struct_field_method_ref() {
+ check_kinds(
+ r#"
+struct Foo { bar: u32 }
+impl Foo { fn baz(&self) -> u32 { 0 } }
+
+fn foo(f: Foo) { let _: &u32 = f.b$0 }
+"#,
+ &[CompletionItemKind::Method, CompletionItemKind::SymbolKind(SymbolKind::Field)],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "baz()",
+ source_range: 98..99,
+ delete: 98..99,
+ insert: "baz()$0",
+ kind: Method,
+ lookup: "baz",
+ detail: "fn(&self) -> u32",
+ ref_match: "&@96",
+ },
+ CompletionItem {
+ label: "bar",
+ source_range: 98..99,
+ delete: 98..99,
+ insert: "bar",
+ kind: SymbolKind(
+ Field,
+ ),
+ detail: "u32",
+ ref_match: "&@96",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn qualified_path_ref() {
+ check_kinds(
+ r#"
+struct S;
+
+struct T;
+impl T {
+ fn foo() -> S {}
+}
+
+fn bar(s: &S) {}
+
+fn main() {
+ bar(T::$0);
+}
+"#,
+ &[CompletionItemKind::SymbolKind(SymbolKind::Function)],
+ expect![[r#"
+ [
+ CompletionItem {
+ label: "foo()",
+ source_range: 95..95,
+ delete: 95..95,
+ insert: "foo()$0",
+ kind: SymbolKind(
+ Function,
+ ),
+ lookup: "foo",
+ detail: "fn() -> S",
+ ref_match: "&@92",
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn generic_enum() {
+ check_relevance(
+ r#"
+enum Foo<T> { A(T), B }
+// bar() should not be an exact type match
+// because the generic parameters are different
+fn bar() -> Foo<u8> { Foo::B }
+// FIXME baz() should be an exact type match
+// because the types could unify, but it currently
+// is not. This is due to the T here being
+// TyKind::Placeholder rather than TyKind::Missing.
+fn baz<T>() -> Foo<T> { Foo::B }
+fn foo() {
+ let foo: Foo<u32> = Foo::B;
+ let _: Foo<u32> = f$0;
+}
+"#,
+ expect![[r#"
+ lc foo [type+local]
+ ev Foo::A(…) [type_could_unify]
+ ev Foo::B [type_could_unify]
+ fn foo() []
+ en Foo []
+ fn bar() []
+ fn baz() []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_exact_match_is_high_priority() {
+ cov_mark::check!(postfix_exact_match_is_high_priority);
+ check_relevance_for_kinds(
+ r#"
+mod ops {
+ pub trait Not {
+ type Output;
+ fn not(self) -> Self::Output;
+ }
+
+ impl Not for bool {
+ type Output = bool;
+ fn not(self) -> bool { if self { false } else { true }}
+ }
+}
+
+fn main() {
+ let _: bool = (9 > 2).not$0;
+}
+ "#,
+ &[CompletionItemKind::Snippet, CompletionItemKind::Method],
+ expect![[r#"
+ sn not [snippet]
+ me not() (use ops::Not) [type_could_unify+requires_import]
+ sn if []
+ sn while []
+ sn ref []
+ sn refm []
+ sn match []
+ sn box []
+ sn dbg []
+ sn dbgr []
+ sn call []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn postfix_inexact_match_is_low_priority() {
+ cov_mark::check!(postfix_inexact_match_is_low_priority);
+ check_relevance_for_kinds(
+ r#"
+struct S;
+impl S {
+ fn f(&self) {}
+}
+fn main() {
+ S.$0
+}
+ "#,
+ &[CompletionItemKind::Snippet, CompletionItemKind::Method],
+ expect![[r#"
+ me f() []
+ sn ref []
+ sn refm []
+ sn match []
+ sn box []
+ sn dbg []
+ sn dbgr []
+ sn call []
+ sn let []
+ sn letm []
+ "#]],
+ );
+ }
+
+ #[test]
+ fn flyimport_reduced_relevance() {
+ check_relevance(
+ r#"
+mod std {
+ pub mod io {
+ pub trait BufRead {}
+ pub struct BufReader;
+ pub struct BufWriter;
+ }
+}
+struct Buffer;
+
+fn f() {
+ Buf$0
+}
+"#,
+ expect![[r#"
+ 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
+ }
+}
+"#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs
new file mode 100644
index 000000000..a810eef18
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs
@@ -0,0 +1,33 @@
+//! Renderer for `const` fields.
+
+use hir::{AsAssocItem, HirDisplay};
+use ide_db::SymbolKind;
+
+use crate::{item::CompletionItem, render::RenderContext};
+
+pub(crate) fn render_const(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem> {
+ let _p = profile::span("render_const");
+ render(ctx, const_)
+}
+
+fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option<CompletionItem> {
+ let db = ctx.db();
+ let name = const_.name(db)?;
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let detail = const_.display(db).to_string();
+
+ let mut item = CompletionItem::new(SymbolKind::Const, ctx.source_range(), name.clone());
+ item.set_documentation(ctx.docs(const_))
+ .set_deprecated(ctx.is_deprecated(const_) || ctx.is_deprecated_assoc_item(const_))
+ .detail(detail)
+ .set_relevance(ctx.completion_relevance());
+
+ if let Some(actm) = const_.as_assoc_item(db) {
+ if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
+ item.trait_name(trt.name(db).to_smol_str());
+ }
+ }
+ item.insert_text(escaped_name);
+
+ Some(item.build())
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
new file mode 100644
index 000000000..4b5535718
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs
@@ -0,0 +1,671 @@
+//! Renderer for function calls.
+
+use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
+use ide_db::{SnippetCap, SymbolKind};
+use itertools::Itertools;
+use stdx::{format_to, to_lower_snake_case};
+use syntax::{AstNode, SmolStr};
+
+use crate::{
+ context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
+ item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
+ render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
+ CallableSnippets,
+};
+
+#[derive(Debug)]
+enum FuncKind<'ctx> {
+ Function(&'ctx PathCompletionCtx),
+ Method(&'ctx DotAccess, Option<hir::Name>),
+}
+
+pub(crate) fn render_fn(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: Option<hir::Name>,
+ func: hir::Function,
+) -> Builder {
+ let _p = profile::span("render_fn");
+ render(ctx, local_name, func, FuncKind::Function(path_ctx))
+}
+
+pub(crate) fn render_method(
+ ctx: RenderContext<'_>,
+ dot_access: &DotAccess,
+ receiver: Option<hir::Name>,
+ local_name: Option<hir::Name>,
+ func: hir::Function,
+) -> Builder {
+ let _p = profile::span("render_method");
+ render(ctx, local_name, func, FuncKind::Method(dot_access, receiver))
+}
+
+fn render(
+ ctx @ RenderContext { completion, .. }: RenderContext<'_>,
+ local_name: Option<hir::Name>,
+ func: hir::Function,
+ func_kind: FuncKind<'_>,
+) -> Builder {
+ let db = completion.db;
+
+ let name = local_name.unwrap_or_else(|| func.name(db));
+
+ let (call, escaped_call) = match &func_kind {
+ FuncKind::Method(_, Some(receiver)) => (
+ format!("{}.{}", receiver, &name).into(),
+ format!("{}.{}", receiver.escaped(), name.escaped()).into(),
+ ),
+ _ => (name.to_smol_str(), name.escaped().to_smol_str()),
+ };
+ let mut item = CompletionItem::new(
+ if func.self_param(db).is_some() {
+ CompletionItemKind::Method
+ } else {
+ CompletionItemKind::SymbolKind(SymbolKind::Function)
+ },
+ ctx.source_range(),
+ call.clone(),
+ );
+
+ let ret_type = func.ret_type(db);
+ let is_op_method = func
+ .as_assoc_item(ctx.db())
+ .and_then(|trait_| trait_.containing_trait_or_trait_impl(ctx.db()))
+ .map_or(false, |trait_| completion.is_ops_trait(trait_));
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(completion, &ret_type),
+ exact_name_match: compute_exact_name_match(completion, &call),
+ is_op_method,
+ ..ctx.completion_relevance()
+ });
+
+ if let Some(ref_match) = compute_ref_match(completion, &ret_type) {
+ match func_kind {
+ FuncKind::Function(path_ctx) => {
+ item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
+ }
+ FuncKind::Method(DotAccess { receiver: Some(receiver), .. }, _) => {
+ if let Some(original_expr) = completion.sema.original_ast_node(receiver.clone()) {
+ item.ref_match(ref_match, original_expr.syntax().text_range().start());
+ }
+ }
+ _ => (),
+ }
+ }
+
+ item.set_documentation(ctx.docs(func))
+ .set_deprecated(ctx.is_deprecated(func) || ctx.is_deprecated_assoc_item(func))
+ .detail(detail(db, func))
+ .lookup_by(name.to_smol_str());
+
+ match ctx.completion.config.snippet_cap {
+ Some(cap) => {
+ let complete_params = match func_kind {
+ FuncKind::Function(PathCompletionCtx {
+ kind: PathKind::Expr { .. },
+ has_call_parens: false,
+ ..
+ }) => Some(false),
+ FuncKind::Method(
+ DotAccess {
+ kind:
+ DotAccessKind::Method { has_parens: false } | DotAccessKind::Field { .. },
+ ..
+ },
+ _,
+ ) => Some(true),
+ _ => None,
+ };
+ if let Some(has_dot_receiver) = complete_params {
+ if let Some((self_param, params)) =
+ params(ctx.completion, func, &func_kind, has_dot_receiver)
+ {
+ add_call_parens(
+ &mut item,
+ completion,
+ cap,
+ call,
+ escaped_call,
+ self_param,
+ params,
+ );
+ }
+ }
+ }
+ _ => (),
+ };
+
+ match ctx.import_to_add {
+ Some(import_to_add) => {
+ item.add_import(import_to_add);
+ }
+ None => {
+ if let Some(actm) = func.as_assoc_item(db) {
+ if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
+ item.trait_name(trt.name(db).to_smol_str());
+ }
+ }
+ }
+ }
+ item
+}
+
+pub(super) fn add_call_parens<'b>(
+ builder: &'b mut Builder,
+ ctx: &CompletionContext<'_>,
+ cap: SnippetCap,
+ name: SmolStr,
+ escaped_name: SmolStr,
+ self_param: Option<hir::SelfParam>,
+ params: Vec<hir::Param>,
+) -> &'b mut Builder {
+ cov_mark::hit!(inserts_parens_for_function_calls);
+
+ let (snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
+ (format!("{}()$0", escaped_name), "()")
+ } else {
+ builder.trigger_call_info();
+ let snippet = if let Some(CallableSnippets::FillArguments) = ctx.config.callable {
+ let offset = if self_param.is_some() { 2 } else { 1 };
+ let function_params_snippet =
+ params.iter().enumerate().format_with(", ", |(index, param), f| {
+ match param.name(ctx.db) {
+ Some(n) => {
+ let smol_str = n.to_smol_str();
+ let text = smol_str.as_str().trim_start_matches('_');
+ let ref_ = ref_of_param(ctx, text, param.ty());
+ f(&format_args!("${{{}:{}{}}}", index + offset, ref_, text))
+ }
+ None => {
+ let name = match param.ty().as_adt() {
+ None => "_".to_string(),
+ Some(adt) => adt
+ .name(ctx.db)
+ .as_text()
+ .map(|s| to_lower_snake_case(s.as_str()))
+ .unwrap_or_else(|| "_".to_string()),
+ };
+ f(&format_args!("${{{}:{}}}", index + offset, name))
+ }
+ }
+ });
+ match self_param {
+ Some(self_param) => {
+ format!(
+ "{}(${{1:{}}}{}{})$0",
+ escaped_name,
+ self_param.display(ctx.db),
+ if params.is_empty() { "" } else { ", " },
+ function_params_snippet
+ )
+ }
+ None => {
+ format!("{}({})$0", escaped_name, function_params_snippet)
+ }
+ }
+ } else {
+ cov_mark::hit!(suppress_arg_snippets);
+ format!("{}($0)", escaped_name)
+ };
+
+ (snippet, "(…)")
+ };
+ builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
+}
+
+fn ref_of_param(ctx: &CompletionContext<'_>, arg: &str, ty: &hir::Type) -> &'static str {
+ if let Some(derefed_ty) = ty.remove_ref() {
+ for (name, local) in ctx.locals.iter() {
+ if name.as_text().as_deref() == Some(arg) {
+ return if local.ty(ctx.db) == derefed_ty {
+ if ty.is_mutable_reference() {
+ "&mut "
+ } else {
+ "&"
+ }
+ } else {
+ ""
+ };
+ }
+ }
+ }
+ ""
+}
+
+fn detail(db: &dyn HirDatabase, func: hir::Function) -> String {
+ let mut ret_ty = func.ret_type(db);
+ let mut detail = String::new();
+
+ if func.is_const(db) {
+ format_to!(detail, "const ");
+ }
+ if func.is_async(db) {
+ format_to!(detail, "async ");
+ if let Some(async_ret) = func.async_ret_type(db) {
+ ret_ty = async_ret;
+ }
+ }
+ if func.is_unsafe_to_call(db) {
+ format_to!(detail, "unsafe ");
+ }
+
+ format_to!(detail, "fn({})", params_display(db, func));
+ if !ret_ty.is_unit() {
+ format_to!(detail, " -> {}", ret_ty.display(db));
+ }
+ detail
+}
+
+fn params_display(db: &dyn HirDatabase, func: hir::Function) -> String {
+ if let Some(self_param) = func.self_param(db) {
+ let assoc_fn_params = func.assoc_fn_params(db);
+ let params = assoc_fn_params
+ .iter()
+ .skip(1) // skip the self param because we are manually handling that
+ .map(|p| p.ty().display(db));
+ format!(
+ "{}{}",
+ self_param.display(db),
+ params.format_with("", |display, f| {
+ f(&", ")?;
+ f(&display)
+ })
+ )
+ } else {
+ let assoc_fn_params = func.assoc_fn_params(db);
+ assoc_fn_params.iter().map(|p| p.ty().display(db)).join(", ")
+ }
+}
+
+fn params(
+ ctx: &CompletionContext<'_>,
+ func: hir::Function,
+ func_kind: &FuncKind<'_>,
+ has_dot_receiver: bool,
+) -> Option<(Option<hir::SelfParam>, Vec<hir::Param>)> {
+ if ctx.config.callable.is_none() {
+ return None;
+ }
+
+ // Don't add parentheses if the expected type is some function reference.
+ if let Some(ty) = &ctx.expected_type {
+ // FIXME: check signature matches?
+ if ty.is_fn() {
+ cov_mark::hit!(no_call_parens_if_fn_ptr_needed);
+ return None;
+ }
+ }
+
+ let self_param = if has_dot_receiver || matches!(func_kind, FuncKind::Method(_, Some(_))) {
+ None
+ } else {
+ func.self_param(ctx.db)
+ };
+ Some((self_param, func.params_without_self(ctx.db)))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ tests::{check_edit, check_edit_with_config, TEST_CONFIG},
+ CallableSnippets, CompletionConfig,
+ };
+
+ #[test]
+ fn inserts_parens_for_function_calls() {
+ cov_mark::check!(inserts_parens_for_function_calls);
+ check_edit(
+ "no_args",
+ r#"
+fn no_args() {}
+fn main() { no_$0 }
+"#,
+ r#"
+fn no_args() {}
+fn main() { no_args()$0 }
+"#,
+ );
+
+ check_edit(
+ "with_args",
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_$0 }
+"#,
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_args(${1:x}, ${2:y})$0 }
+"#,
+ );
+
+ check_edit(
+ "foo",
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn bar(s: &S) { s.f$0 }
+"#,
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn bar(s: &S) { s.foo()$0 }
+"#,
+ );
+
+ check_edit(
+ "foo",
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {}
+}
+fn bar(s: &S) {
+ s.f$0
+}
+"#,
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {}
+}
+fn bar(s: &S) {
+ s.foo(${1:x})$0
+}
+"#,
+ );
+
+ check_edit(
+ "foo",
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {
+ $0
+ }
+}
+"#,
+ r#"
+struct S {}
+impl S {
+ fn foo(&self, x: i32) {
+ self.foo(${1:x})$0
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn parens_for_method_call_as_assoc_fn() {
+ check_edit(
+ "foo",
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn main() { S::f$0 }
+"#,
+ r#"
+struct S;
+impl S {
+ fn foo(&self) {}
+}
+fn main() { S::foo(${1:&self})$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn suppress_arg_snippets() {
+ cov_mark::check!(suppress_arg_snippets);
+ check_edit_with_config(
+ CompletionConfig { callable: Some(CallableSnippets::AddParentheses), ..TEST_CONFIG },
+ "with_args",
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_$0 }
+"#,
+ r#"
+fn with_args(x: i32, y: String) {}
+fn main() { with_args($0) }
+"#,
+ );
+ }
+
+ #[test]
+ fn strips_underscores_from_args() {
+ check_edit(
+ "foo",
+ r#"
+fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
+fn main() { f$0 }
+"#,
+ r#"
+fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
+fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
+"#,
+ );
+ }
+
+ #[test]
+ fn insert_ref_when_matching_local_in_scope() {
+ check_edit(
+ "ref_arg",
+ r#"
+struct Foo {}
+fn ref_arg(x: &Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_ar$0
+}
+"#,
+ r#"
+struct Foo {}
+fn ref_arg(x: &Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_arg(${1:&x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn insert_mut_ref_when_matching_local_in_scope() {
+ check_edit(
+ "ref_arg",
+ r#"
+struct Foo {}
+fn ref_arg(x: &mut Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_ar$0
+}
+"#,
+ r#"
+struct Foo {}
+fn ref_arg(x: &mut Foo) {}
+fn main() {
+ let x = Foo {};
+ ref_arg(${1:&mut x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn insert_ref_when_matching_local_in_scope_for_method() {
+ check_edit(
+ "apply_foo",
+ r#"
+struct Foo {}
+struct Bar {}
+impl Bar {
+ fn apply_foo(&self, x: &Foo) {}
+}
+
+fn main() {
+ let x = Foo {};
+ let y = Bar {};
+ y.$0
+}
+"#,
+ r#"
+struct Foo {}
+struct Bar {}
+impl Bar {
+ fn apply_foo(&self, x: &Foo) {}
+}
+
+fn main() {
+ let x = Foo {};
+ let y = Bar {};
+ y.apply_foo(${1:&x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trim_mut_keyword_in_func_completion() {
+ check_edit(
+ "take_mutably",
+ r#"
+fn take_mutably(mut x: &i32) {}
+
+fn main() {
+ take_m$0
+}
+"#,
+ r#"
+fn take_mutably(mut x: &i32) {}
+
+fn main() {
+ take_mutably(${1:x})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_pattern_args_with_type_name_if_adt() {
+ check_edit(
+ "qux",
+ r#"
+struct Foo {
+ bar: i32
+}
+
+fn qux(Foo { bar }: Foo) {
+ println!("{}", bar);
+}
+
+fn main() {
+ qu$0
+}
+"#,
+ r#"
+struct Foo {
+ bar: i32
+}
+
+fn qux(Foo { bar }: Foo) {
+ println!("{}", bar);
+}
+
+fn main() {
+ qux(${1:foo})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_fn_param() {
+ // has mut kw
+ check_edit(
+ "mut bar: u32",
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: (), mut ba$0)
+"#,
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: (), mut bar: u32)
+"#,
+ );
+
+ // has type param
+ check_edit(
+ "mut bar: u32",
+ r#"
+fn g(foo: (), mut ba$0: u32)
+fn f(foo: (), mut bar: u32) {}
+"#,
+ r#"
+fn g(foo: (), mut bar: u32)
+fn f(foo: (), mut bar: u32) {}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_fn_mut_param_add_comma() {
+ // add leading and trailing comma
+ check_edit(
+ ", mut bar: u32,",
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: ()mut ba$0 baz: ())
+"#,
+ r#"
+fn f(foo: (), mut bar: u32) {}
+fn g(foo: (), mut bar: u32, baz: ())
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_fn_mut_param_has_attribute() {
+ check_edit(
+ r#"#[baz = "qux"] mut bar: u32"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), mut ba$0)
+"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut bar: u32)
+"#,
+ );
+
+ check_edit(
+ r#"#[baz = "qux"] mut bar: u32"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut ba$0)
+"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut bar: u32)
+"#,
+ );
+
+ check_edit(
+ r#", #[baz = "qux"] mut bar: u32"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: ()#[baz = "qux"] mut ba$0)
+"#,
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut bar: u32)
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs
new file mode 100644
index 000000000..91a253f8f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs
@@ -0,0 +1,191 @@
+//! Renderer for `enum` variants.
+
+use hir::{db::HirDatabase, Documentation, HasAttrs, StructKind};
+use ide_db::SymbolKind;
+use syntax::AstNode;
+
+use crate::{
+ context::{CompletionContext, PathCompletionCtx, PathKind},
+ item::{Builder, CompletionItem},
+ render::{
+ compute_ref_match, compute_type_match,
+ variant::{
+ format_literal_label, render_record_lit, render_tuple_lit, visible_fields,
+ RenderedLiteral,
+ },
+ RenderContext,
+ },
+ CompletionItemKind, CompletionRelevance,
+};
+
+pub(crate) fn render_variant_lit(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ local_name: Option<hir::Name>,
+ variant: hir::Variant,
+ path: Option<hir::ModPath>,
+) -> Option<Builder> {
+ let _p = profile::span("render_enum_variant");
+ let db = ctx.db();
+
+ let name = local_name.unwrap_or_else(|| variant.name(db));
+ render(ctx, path_ctx, Variant::EnumVariant(variant), name, path)
+}
+
+pub(crate) fn render_struct_literal(
+ ctx: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ strukt: hir::Struct,
+ path: Option<hir::ModPath>,
+ local_name: Option<hir::Name>,
+) -> Option<Builder> {
+ let _p = profile::span("render_struct_literal");
+ let db = ctx.db();
+
+ let name = local_name.unwrap_or_else(|| strukt.name(db));
+ render(ctx, path_ctx, Variant::Struct(strukt), name, path)
+}
+
+fn render(
+ ctx @ RenderContext { completion, .. }: RenderContext<'_>,
+ path_ctx: &PathCompletionCtx,
+ thing: Variant,
+ name: hir::Name,
+ path: Option<hir::ModPath>,
+) -> Option<Builder> {
+ let db = completion.db;
+ let mut kind = thing.kind(db);
+ let should_add_parens = match &path_ctx {
+ PathCompletionCtx { has_call_parens: true, .. } => false,
+ PathCompletionCtx { kind: PathKind::Use | PathKind::Type { .. }, .. } => false,
+ _ => true,
+ };
+
+ let fields = thing.fields(completion)?;
+ let (qualified_name, short_qualified_name, qualified) = match path {
+ Some(path) => {
+ let short = hir::ModPath::from_segments(
+ hir::PathKind::Plain,
+ path.segments().iter().skip(path.segments().len().saturating_sub(2)).cloned(),
+ );
+ (path, short, true)
+ }
+ None => (name.clone().into(), name.into(), false),
+ };
+ let (qualified_name, escaped_qualified_name) =
+ (qualified_name.to_string(), qualified_name.escaped().to_string());
+ let snippet_cap = ctx.snippet_cap();
+
+ let mut rendered = match kind {
+ StructKind::Tuple if should_add_parens => {
+ render_tuple_lit(db, snippet_cap, &fields, &escaped_qualified_name)
+ }
+ StructKind::Record if should_add_parens => {
+ render_record_lit(db, snippet_cap, &fields, &escaped_qualified_name)
+ }
+ _ => RenderedLiteral {
+ literal: escaped_qualified_name.clone(),
+ detail: escaped_qualified_name.clone(),
+ },
+ };
+
+ if snippet_cap.is_some() {
+ rendered.literal.push_str("$0");
+ }
+
+ // only show name in label if not adding parens
+ if !should_add_parens {
+ kind = StructKind::Unit;
+ }
+
+ let mut item = CompletionItem::new(
+ CompletionItemKind::SymbolKind(thing.symbol_kind()),
+ ctx.source_range(),
+ format_literal_label(&qualified_name, kind),
+ );
+
+ item.detail(rendered.detail);
+
+ match snippet_cap {
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, rendered.literal),
+ None => item.insert_text(rendered.literal),
+ };
+
+ if qualified {
+ item.lookup_by(format_literal_label(&short_qualified_name.to_string(), kind));
+ }
+ item.set_documentation(thing.docs(db)).set_deprecated(thing.is_deprecated(&ctx));
+
+ let ty = thing.ty(db);
+ item.set_relevance(CompletionRelevance {
+ type_match: compute_type_match(ctx.completion, &ty),
+ ..ctx.completion_relevance()
+ });
+ if let Some(ref_match) = compute_ref_match(completion, &ty) {
+ item.ref_match(ref_match, path_ctx.path.syntax().text_range().start());
+ }
+
+ if let Some(import_to_add) = ctx.import_to_add {
+ item.add_import(import_to_add);
+ }
+ Some(item)
+}
+
+#[derive(Clone, Copy)]
+enum Variant {
+ Struct(hir::Struct),
+ EnumVariant(hir::Variant),
+}
+
+impl Variant {
+ fn fields(self, ctx: &CompletionContext<'_>) -> Option<Vec<hir::Field>> {
+ let fields = match self {
+ Variant::Struct(it) => it.fields(ctx.db),
+ Variant::EnumVariant(it) => it.fields(ctx.db),
+ };
+ let (visible_fields, fields_omitted) = match self {
+ Variant::Struct(it) => visible_fields(ctx, &fields, it)?,
+ Variant::EnumVariant(it) => visible_fields(ctx, &fields, it)?,
+ };
+ if !fields_omitted {
+ Some(visible_fields)
+ } else {
+ None
+ }
+ }
+
+ fn kind(self, db: &dyn HirDatabase) -> StructKind {
+ match self {
+ Variant::Struct(it) => it.kind(db),
+ Variant::EnumVariant(it) => it.kind(db),
+ }
+ }
+
+ fn symbol_kind(self) -> SymbolKind {
+ match self {
+ Variant::Struct(_) => SymbolKind::Struct,
+ Variant::EnumVariant(_) => SymbolKind::Variant,
+ }
+ }
+
+ fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
+ match self {
+ Variant::Struct(it) => it.docs(db),
+ Variant::EnumVariant(it) => it.docs(db),
+ }
+ }
+
+ fn is_deprecated(self, ctx: &RenderContext<'_>) -> bool {
+ match self {
+ Variant::Struct(it) => ctx.is_deprecated(it),
+ Variant::EnumVariant(it) => ctx.is_deprecated(it),
+ }
+ }
+
+ fn ty(self, db: &dyn HirDatabase) -> hir::Type {
+ match self {
+ Variant::Struct(it) => it.ty(db),
+ Variant::EnumVariant(it) => it.parent_enum(db).ty(db),
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs
new file mode 100644
index 000000000..ca2269f13
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs
@@ -0,0 +1,270 @@
+//! Renderer for macro invocations.
+
+use hir::{Documentation, HirDisplay};
+use ide_db::SymbolKind;
+use syntax::SmolStr;
+
+use crate::{
+ context::{PathCompletionCtx, PathKind, PatternContext},
+ item::{Builder, CompletionItem},
+ render::RenderContext,
+};
+
+pub(crate) fn render_macro(
+ ctx: RenderContext<'_>,
+ PathCompletionCtx { kind, has_macro_bang, has_call_parens, .. }: &PathCompletionCtx,
+
+ name: hir::Name,
+ macro_: hir::Macro,
+) -> Builder {
+ let _p = profile::span("render_macro");
+ render(ctx, *kind == PathKind::Use, *has_macro_bang, *has_call_parens, name, macro_)
+}
+
+pub(crate) fn render_macro_pat(
+ ctx: RenderContext<'_>,
+ _pattern_ctx: &PatternContext,
+ name: hir::Name,
+ macro_: hir::Macro,
+) -> Builder {
+ let _p = profile::span("render_macro");
+ render(ctx, false, false, false, name, macro_)
+}
+
+fn render(
+ ctx @ RenderContext { completion, .. }: RenderContext<'_>,
+ is_use_path: bool,
+ has_macro_bang: bool,
+ has_call_parens: bool,
+ name: hir::Name,
+ macro_: hir::Macro,
+) -> Builder {
+ let source_range = if ctx.is_immediately_after_macro_bang() {
+ cov_mark::hit!(completes_macro_call_if_cursor_at_bang_token);
+ completion.token.parent().map_or_else(|| ctx.source_range(), |it| it.text_range())
+ } else {
+ ctx.source_range()
+ };
+
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let docs = ctx.docs(macro_);
+ let docs_str = docs.as_ref().map(Documentation::as_str).unwrap_or_default();
+ let is_fn_like = macro_.is_fn_like(completion.db);
+ let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") };
+
+ let needs_bang = is_fn_like && !is_use_path && !has_macro_bang;
+
+ let mut item = CompletionItem::new(
+ SymbolKind::from(macro_.kind(completion.db)),
+ source_range,
+ label(&ctx, needs_bang, bra, ket, &name),
+ );
+ item.set_deprecated(ctx.is_deprecated(macro_))
+ .detail(macro_.display(completion.db).to_string())
+ .set_documentation(docs)
+ .set_relevance(ctx.completion_relevance());
+
+ match ctx.snippet_cap() {
+ Some(cap) if needs_bang && !has_call_parens => {
+ let snippet = format!("{}!{}$0{}", escaped_name, bra, ket);
+ let lookup = banged_name(&name);
+ item.insert_snippet(cap, snippet).lookup_by(lookup);
+ }
+ _ if needs_bang => {
+ item.insert_text(banged_name(&escaped_name)).lookup_by(banged_name(&name));
+ }
+ _ => {
+ cov_mark::hit!(dont_insert_macro_call_parens_unncessary);
+ item.insert_text(escaped_name);
+ }
+ };
+ if let Some(import_to_add) = ctx.import_to_add {
+ item.add_import(import_to_add);
+ }
+
+ item
+}
+
+fn label(
+ ctx: &RenderContext<'_>,
+ needs_bang: bool,
+ bra: &str,
+ ket: &str,
+ name: &SmolStr,
+) -> SmolStr {
+ if needs_bang {
+ if ctx.snippet_cap().is_some() {
+ SmolStr::from_iter([&*name, "!", bra, "…", ket])
+ } else {
+ banged_name(name)
+ }
+ } else {
+ name.clone()
+ }
+}
+
+fn banged_name(name: &str) -> SmolStr {
+ SmolStr::from_iter([name, "!"])
+}
+
+fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) {
+ let mut votes = [0, 0, 0];
+ for (idx, s) in docs.match_indices(&macro_name) {
+ let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
+ // Ensure to match the full word
+ if after.starts_with('!')
+ && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric())
+ {
+ // It may have spaces before the braces like `foo! {}`
+ match after[1..].chars().find(|&c| !c.is_whitespace()) {
+ Some('{') => votes[0] += 1,
+ Some('[') => votes[1] += 1,
+ Some('(') => votes[2] += 1,
+ _ => {}
+ }
+ }
+ }
+
+ // Insert a space before `{}`.
+ // We prefer the last one when some votes equal.
+ let (_vote, (bra, ket)) = votes
+ .iter()
+ .zip(&[(" {", "}"), ("[", "]"), ("(", ")")])
+ .max_by_key(|&(&vote, _)| vote)
+ .unwrap();
+ (*bra, *ket)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::check_edit;
+
+ #[test]
+ fn dont_insert_macro_call_parens_unncessary() {
+ cov_mark::check!(dont_insert_macro_call_parens_unncessary);
+ check_edit(
+ "frobnicate",
+ r#"
+//- /main.rs crate:main deps:foo
+use foo::$0;
+//- /foo/lib.rs crate:foo
+#[macro_export]
+macro_rules! frobnicate { () => () }
+"#,
+ r#"
+use foo::frobnicate;
+"#,
+ );
+
+ check_edit(
+ "frobnicate",
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() { frob$0!(); }
+"#,
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() { frobnicate!(); }
+"#,
+ );
+ }
+
+ #[test]
+ fn add_bang_to_parens() {
+ check_edit(
+ "frobnicate!",
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() {
+ frob$0()
+}
+"#,
+ r#"
+macro_rules! frobnicate { () => () }
+fn main() {
+ frobnicate!()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn guesses_macro_braces() {
+ check_edit(
+ "vec!",
+ r#"
+/// Creates a [`Vec`] containing the arguments.
+///
+/// ```
+/// let v = vec![1, 2, 3];
+/// assert_eq!(v[0], 1);
+/// assert_eq!(v[1], 2);
+/// assert_eq!(v[2], 3);
+/// ```
+macro_rules! vec { () => {} }
+
+fn main() { v$0 }
+"#,
+ r#"
+/// Creates a [`Vec`] containing the arguments.
+///
+/// ```
+/// let v = vec![1, 2, 3];
+/// assert_eq!(v[0], 1);
+/// assert_eq!(v[1], 2);
+/// assert_eq!(v[2], 3);
+/// ```
+macro_rules! vec { () => {} }
+
+fn main() { vec![$0] }
+"#,
+ );
+
+ check_edit(
+ "foo!",
+ r#"
+/// Foo
+///
+/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
+/// call as `let _=foo! { hello world };`
+macro_rules! foo { () => {} }
+fn main() { $0 }
+"#,
+ r#"
+/// Foo
+///
+/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
+/// call as `let _=foo! { hello world };`
+macro_rules! foo { () => {} }
+fn main() { foo! {$0} }
+"#,
+ )
+ }
+
+ #[test]
+ fn completes_macro_call_if_cursor_at_bang_token() {
+ // Regression test for https://github.com/rust-lang/rust-analyzer/issues/9904
+ cov_mark::check!(completes_macro_call_if_cursor_at_bang_token);
+ check_edit(
+ "foo!",
+ r#"
+macro_rules! foo {
+ () => {}
+}
+
+fn main() {
+ foo!$0
+}
+"#,
+ r#"
+macro_rules! foo {
+ () => {}
+}
+
+fn main() {
+ foo!($0)
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs
new file mode 100644
index 000000000..34a384f2f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs
@@ -0,0 +1,193 @@
+//! Renderer for patterns.
+
+use hir::{db::HirDatabase, HasAttrs, Name, StructKind};
+use ide_db::SnippetCap;
+use itertools::Itertools;
+use syntax::SmolStr;
+
+use crate::{
+ context::{ParamContext, ParamKind, PathCompletionCtx, PatternContext},
+ render::{
+ variant::{format_literal_label, visible_fields},
+ RenderContext,
+ },
+ CompletionItem, CompletionItemKind,
+};
+
+pub(crate) fn render_struct_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ strukt: hir::Struct,
+ local_name: Option<Name>,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_struct_pat");
+
+ let fields = strukt.fields(ctx.db());
+ let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, strukt)?;
+
+ if visible_fields.is_empty() {
+ // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
+ return None;
+ }
+
+ let name = local_name.unwrap_or_else(|| strukt.name(ctx.db()));
+ let (name, escaped_name) = (name.to_smol_str(), name.escaped().to_smol_str());
+ let kind = strukt.kind(ctx.db());
+ let label = format_literal_label(name.as_str(), kind);
+ let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?;
+
+ Some(build_completion(ctx, label, pat, strukt))
+}
+
+pub(crate) fn render_variant_pat(
+ ctx: RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ path_ctx: Option<&PathCompletionCtx>,
+ variant: hir::Variant,
+ local_name: Option<Name>,
+ path: Option<&hir::ModPath>,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_variant_pat");
+
+ let fields = variant.fields(ctx.db());
+ let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?;
+
+ let (name, escaped_name) = match path {
+ Some(path) => (path.to_string().into(), path.escaped().to_string().into()),
+ None => {
+ let name = local_name.unwrap_or_else(|| variant.name(ctx.db()));
+ (name.to_smol_str(), name.escaped().to_smol_str())
+ }
+ };
+
+ let (label, pat) = match path_ctx {
+ Some(PathCompletionCtx { has_call_parens: true, .. }) => (name, escaped_name.to_string()),
+ _ => {
+ let kind = variant.kind(ctx.db());
+ let label = format_literal_label(name.as_str(), kind);
+ let pat = render_pat(
+ &ctx,
+ pattern_ctx,
+ &escaped_name,
+ kind,
+ &visible_fields,
+ fields_omitted,
+ )?;
+ (label, pat)
+ }
+ };
+
+ Some(build_completion(ctx, label, pat, variant))
+}
+
+fn build_completion(
+ ctx: RenderContext<'_>,
+ label: SmolStr,
+ pat: String,
+ def: impl HasAttrs + Copy,
+) -> CompletionItem {
+ let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label);
+ item.set_documentation(ctx.docs(def))
+ .set_deprecated(ctx.is_deprecated(def))
+ .detail(&pat)
+ .set_relevance(ctx.completion_relevance());
+ match ctx.snippet_cap() {
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, pat),
+ None => item.insert_text(pat),
+ };
+ item.build()
+}
+
+fn render_pat(
+ ctx: &RenderContext<'_>,
+ pattern_ctx: &PatternContext,
+ name: &str,
+ kind: StructKind,
+ fields: &[hir::Field],
+ fields_omitted: bool,
+) -> Option<String> {
+ let mut pat = match kind {
+ StructKind::Tuple => render_tuple_as_pat(ctx.snippet_cap(), fields, name, fields_omitted),
+ StructKind::Record => {
+ render_record_as_pat(ctx.db(), ctx.snippet_cap(), fields, name, fields_omitted)
+ }
+ StructKind::Unit => name.to_string(),
+ };
+
+ let needs_ascription = matches!(
+ pattern_ctx,
+ PatternContext {
+ param_ctx: Some(ParamContext { kind: ParamKind::Function(_), .. }),
+ has_type_ascription: false,
+ ..
+ }
+ );
+ if needs_ascription {
+ pat.push(':');
+ pat.push(' ');
+ pat.push_str(name);
+ }
+ if ctx.snippet_cap().is_some() {
+ pat.push_str("$0");
+ }
+ Some(pat)
+}
+
+fn render_record_as_pat(
+ db: &dyn HirDatabase,
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ name: &str,
+ fields_omitted: bool,
+) -> String {
+ let fields = fields.iter();
+ match snippet_cap {
+ Some(_) => {
+ format!(
+ "{name} {{ {}{} }}",
+ fields.enumerate().format_with(", ", |(idx, field), f| {
+ f(&format_args!("{}${}", field.name(db).escaped(), idx + 1))
+ }),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ None => {
+ format!(
+ "{name} {{ {}{} }}",
+ fields.map(|field| field.name(db).escaped().to_smol_str()).format(", "),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ }
+}
+
+fn render_tuple_as_pat(
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ name: &str,
+ fields_omitted: bool,
+) -> String {
+ let fields = fields.iter();
+ match snippet_cap {
+ Some(_) => {
+ format!(
+ "{name}({}{})",
+ fields
+ .enumerate()
+ .format_with(", ", |(idx, _), f| { f(&format_args!("${}", idx + 1)) }),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ None => {
+ format!(
+ "{name}({}{})",
+ fields.enumerate().map(|(idx, _)| idx).format(", "),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+ }
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs
new file mode 100644
index 000000000..f1b23c76e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs
@@ -0,0 +1,57 @@
+//! Renderer for type aliases.
+
+use hir::{AsAssocItem, HirDisplay};
+use ide_db::SymbolKind;
+use syntax::SmolStr;
+
+use crate::{item::CompletionItem, render::RenderContext};
+
+pub(crate) fn render_type_alias(
+ ctx: RenderContext<'_>,
+ type_alias: hir::TypeAlias,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_type_alias");
+ render(ctx, type_alias, false)
+}
+
+pub(crate) fn render_type_alias_with_eq(
+ ctx: RenderContext<'_>,
+ type_alias: hir::TypeAlias,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_type_alias_with_eq");
+ render(ctx, type_alias, true)
+}
+
+fn render(
+ ctx: RenderContext<'_>,
+ type_alias: hir::TypeAlias,
+ with_eq: bool,
+) -> Option<CompletionItem> {
+ let db = ctx.db();
+
+ let name = type_alias.name(db);
+ let (name, escaped_name) = if with_eq {
+ (
+ SmolStr::from_iter([&name.to_smol_str(), " = "]),
+ SmolStr::from_iter([&name.escaped().to_smol_str(), " = "]),
+ )
+ } else {
+ (name.to_smol_str(), name.escaped().to_smol_str())
+ };
+ let detail = type_alias.display(db).to_string();
+
+ let mut item = CompletionItem::new(SymbolKind::TypeAlias, ctx.source_range(), name.clone());
+ item.set_documentation(ctx.docs(type_alias))
+ .set_deprecated(ctx.is_deprecated(type_alias) || ctx.is_deprecated_assoc_item(type_alias))
+ .detail(detail)
+ .set_relevance(ctx.completion_relevance());
+
+ if let Some(actm) = type_alias.as_assoc_item(db) {
+ if let Some(trt) = actm.containing_trait_or_trait_impl(db) {
+ item.trait_name(trt.name(db).to_smol_str());
+ }
+ }
+ item.insert_text(escaped_name);
+
+ Some(item.build())
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs
new file mode 100644
index 000000000..9c9540a9b
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs
@@ -0,0 +1,77 @@
+//! Renderer for `union` literals.
+
+use hir::{HirDisplay, Name, StructKind};
+use ide_db::SymbolKind;
+use itertools::Itertools;
+
+use crate::{
+ render::{
+ variant::{format_literal_label, visible_fields},
+ RenderContext,
+ },
+ CompletionItem, CompletionItemKind,
+};
+
+pub(crate) fn render_union_literal(
+ ctx: RenderContext<'_>,
+ un: hir::Union,
+ path: Option<hir::ModPath>,
+ local_name: Option<Name>,
+) -> Option<CompletionItem> {
+ let name = local_name.unwrap_or_else(|| un.name(ctx.db()));
+
+ let (qualified_name, escaped_qualified_name) = match path {
+ Some(p) => (p.to_string(), p.escaped().to_string()),
+ None => (name.to_string(), name.escaped().to_string()),
+ };
+
+ let mut item = CompletionItem::new(
+ CompletionItemKind::SymbolKind(SymbolKind::Union),
+ ctx.source_range(),
+ format_literal_label(&name.to_smol_str(), StructKind::Record),
+ );
+
+ let fields = un.fields(ctx.db());
+ let (fields, fields_omitted) = visible_fields(ctx.completion, &fields, un)?;
+
+ if fields.is_empty() {
+ return None;
+ }
+
+ let literal = if ctx.snippet_cap().is_some() {
+ format!(
+ "{} {{ ${{1|{}|}}: ${{2:()}} }}$0",
+ escaped_qualified_name,
+ fields.iter().map(|field| field.name(ctx.db()).escaped().to_smol_str()).format(",")
+ )
+ } else {
+ format!(
+ "{} {{ {} }}",
+ escaped_qualified_name,
+ fields.iter().format_with(", ", |field, f| {
+ f(&format_args!("{}: ()", field.name(ctx.db()).escaped()))
+ })
+ )
+ };
+
+ let detail = format!(
+ "{} {{ {}{} }}",
+ qualified_name,
+ fields.iter().format_with(", ", |field, f| {
+ f(&format_args!("{}: {}", field.name(ctx.db()), field.ty(ctx.db()).display(ctx.db())))
+ }),
+ if fields_omitted { ", .." } else { "" }
+ );
+
+ item.set_documentation(ctx.docs(un))
+ .set_deprecated(ctx.is_deprecated(un))
+ .detail(&detail)
+ .set_relevance(ctx.completion_relevance());
+
+ match ctx.snippet_cap() {
+ Some(snippet_cap) => item.insert_snippet(snippet_cap, literal),
+ None => item.insert_text(literal),
+ };
+
+ Some(item.build())
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs
new file mode 100644
index 000000000..003a0c11e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs
@@ -0,0 +1,96 @@
+//! Code common to structs, unions, and enum variants.
+
+use crate::context::CompletionContext;
+use hir::{db::HirDatabase, HasAttrs, HasCrate, HasVisibility, HirDisplay, StructKind};
+use ide_db::SnippetCap;
+use itertools::Itertools;
+use syntax::SmolStr;
+
+/// A rendered struct, union, or enum variant, split into fields for actual
+/// auto-completion (`literal`, using `field: ()`) and display in the
+/// completions menu (`detail`, using `field: type`).
+pub(crate) struct RenderedLiteral {
+ pub(crate) literal: String,
+ pub(crate) detail: String,
+}
+
+/// Render a record type (or sub-type) to a `RenderedCompound`. Use `None` for
+/// the `name` argument for an anonymous type.
+pub(crate) fn render_record_lit(
+ db: &dyn HirDatabase,
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ path: &str,
+) -> RenderedLiteral {
+ let completions = fields.iter().enumerate().format_with(", ", |(idx, field), f| {
+ if snippet_cap.is_some() {
+ f(&format_args!("{}: ${{{}:()}}", field.name(db).escaped(), idx + 1))
+ } else {
+ f(&format_args!("{}: ()", field.name(db).escaped()))
+ }
+ });
+
+ let types = fields.iter().format_with(", ", |field, f| {
+ f(&format_args!("{}: {}", field.name(db), field.ty(db).display(db)))
+ });
+
+ RenderedLiteral {
+ literal: format!("{} {{ {} }}", path, completions),
+ detail: format!("{} {{ {} }}", path, types),
+ }
+}
+
+/// Render a tuple type (or sub-type) to a `RenderedCompound`. Use `None` for
+/// the `name` argument for an anonymous type.
+pub(crate) fn render_tuple_lit(
+ db: &dyn HirDatabase,
+ snippet_cap: Option<SnippetCap>,
+ fields: &[hir::Field],
+ path: &str,
+) -> RenderedLiteral {
+ let completions = fields.iter().enumerate().format_with(", ", |(idx, _), f| {
+ if snippet_cap.is_some() {
+ f(&format_args!("${{{}:()}}", idx + 1))
+ } else {
+ f(&format_args!("()"))
+ }
+ });
+
+ let types = fields.iter().format_with(", ", |field, f| f(&field.ty(db).display(db)));
+
+ RenderedLiteral {
+ literal: format!("{}({})", path, completions),
+ detail: format!("{}({})", path, types),
+ }
+}
+
+/// Find all the visible fields in a given list. Returns the list of visible
+/// fields, plus a boolean for whether the list is comprehensive (contains no
+/// private fields and its item is not marked `#[non_exhaustive]`).
+pub(crate) fn visible_fields(
+ ctx: &CompletionContext<'_>,
+ fields: &[hir::Field],
+ item: impl HasAttrs + HasCrate + Copy,
+) -> Option<(Vec<hir::Field>, bool)> {
+ let module = ctx.module;
+ let n_fields = fields.len();
+ let fields = fields
+ .iter()
+ .filter(|field| field.is_visible_from(ctx.db, module))
+ .copied()
+ .collect::<Vec<_>>();
+ let has_invisible_field = n_fields - fields.len() > 0;
+ let is_foreign_non_exhaustive = item.attrs(ctx.db).by_key("non_exhaustive").exists()
+ && item.krate(ctx.db) != module.krate();
+ let fields_omitted = has_invisible_field || is_foreign_non_exhaustive;
+ Some((fields, fields_omitted))
+}
+
+/// Format a struct, etc. literal option for display in the completions menu.
+pub(crate) fn format_literal_label(name: &str, kind: StructKind) -> SmolStr {
+ match kind {
+ StructKind::Tuple => SmolStr::from_iter([name, "(…)"]),
+ StructKind::Record => SmolStr::from_iter([name, " {…}"]),
+ StructKind::Unit => name.into(),
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs
new file mode 100644
index 000000000..dc1039fa6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs
@@ -0,0 +1,214 @@
+//! User (postfix)-snippet definitions.
+//!
+//! Actual logic is implemented in [`crate::completions::postfix`] and [`crate::completions::snippet`] respectively.
+
+// Feature: User Snippet Completions
+//
+// rust-analyzer allows the user to define custom (postfix)-snippets that may depend on items to be accessible for the current scope to be applicable.
+//
+// A custom snippet can be defined by adding it to the `rust-analyzer.completion.snippets.custom` object respectively.
+//
+// [source,json]
+// ----
+// {
+// "rust-analyzer.completion.snippets.custom": {
+// "thread spawn": {
+// "prefix": ["spawn", "tspawn"],
+// "body": [
+// "thread::spawn(move || {",
+// "\t$0",
+// "});",
+// ],
+// "description": "Insert a thread::spawn call",
+// "requires": "std::thread",
+// "scope": "expr",
+// }
+// }
+// }
+// ----
+//
+// In the example above:
+//
+// * `"thread spawn"` is the name of the snippet.
+//
+// * `prefix` defines one or more trigger words that will trigger the snippets completion.
+// Using `postfix` will instead create a postfix snippet.
+//
+// * `body` is one or more lines of content joined via newlines for the final output.
+//
+// * `description` is an optional description of the snippet, if unset the snippet name will be used.
+//
+// * `requires` is an optional list of item paths that have to be resolvable in the current crate where the completion is rendered.
+// On failure of resolution the snippet won't be applicable, otherwise the snippet will insert an import for the items on insertion if
+// the items aren't yet in scope.
+//
+// * `scope` is an optional filter for when the snippet should be applicable. Possible values are:
+// ** for Snippet-Scopes: `expr`, `item` (default: `item`)
+// ** for Postfix-Snippet-Scopes: `expr`, `type` (default: `expr`)
+//
+// The `body` field also has access to placeholders as visible in the example as `$0`.
+// These placeholders take the form of `$number` or `${number:placeholder_text}` which can be traversed as tabstop in ascending order starting from 1,
+// with `$0` being a special case that always comes last.
+//
+// There is also a special placeholder, `${receiver}`, which will be replaced by the receiver expression for postfix snippets, or a `$0` tabstop in case of normal snippets.
+// This replacement for normal snippets allows you to reuse a snippet for both post- and prefix in a single definition.
+//
+// For the VSCode editor, rust-analyzer also ships with a small set of defaults which can be removed
+// by overwriting the settings object mentioned above, the defaults are:
+// [source,json]
+// ----
+// {
+// "Arc::new": {
+// "postfix": "arc",
+// "body": "Arc::new(${receiver})",
+// "requires": "std::sync::Arc",
+// "description": "Put the expression into an `Arc`",
+// "scope": "expr"
+// },
+// "Rc::new": {
+// "postfix": "rc",
+// "body": "Rc::new(${receiver})",
+// "requires": "std::rc::Rc",
+// "description": "Put the expression into an `Rc`",
+// "scope": "expr"
+// },
+// "Box::pin": {
+// "postfix": "pinbox",
+// "body": "Box::pin(${receiver})",
+// "requires": "std::boxed::Box",
+// "description": "Put the expression into a pinned `Box`",
+// "scope": "expr"
+// },
+// "Ok": {
+// "postfix": "ok",
+// "body": "Ok(${receiver})",
+// "description": "Wrap the expression in a `Result::Ok`",
+// "scope": "expr"
+// },
+// "Err": {
+// "postfix": "err",
+// "body": "Err(${receiver})",
+// "description": "Wrap the expression in a `Result::Err`",
+// "scope": "expr"
+// },
+// "Some": {
+// "postfix": "some",
+// "body": "Some(${receiver})",
+// "description": "Wrap the expression in an `Option::Some`",
+// "scope": "expr"
+// }
+// }
+// ----
+
+use ide_db::imports::import_assets::LocatedImport;
+use itertools::Itertools;
+use syntax::{ast, AstNode, GreenNode, SyntaxNode};
+
+use crate::context::CompletionContext;
+
+/// A snippet scope describing where a snippet may apply to.
+/// These may differ slightly in meaning depending on the snippet trigger.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum SnippetScope {
+ Item,
+ Expr,
+ Type,
+}
+
+/// A user supplied snippet.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Snippet {
+ pub postfix_triggers: Box<[Box<str>]>,
+ pub prefix_triggers: Box<[Box<str>]>,
+ pub scope: SnippetScope,
+ pub description: Option<Box<str>>,
+ snippet: String,
+ // These are `ast::Path`'s but due to SyntaxNodes not being Send we store these
+ // and reconstruct them on demand instead. This is cheaper than reparsing them
+ // from strings
+ requires: Box<[GreenNode]>,
+}
+
+impl Snippet {
+ pub fn new(
+ prefix_triggers: &[String],
+ postfix_triggers: &[String],
+ snippet: &[String],
+ description: &str,
+ requires: &[String],
+ scope: SnippetScope,
+ ) -> Option<Self> {
+ if prefix_triggers.is_empty() && postfix_triggers.is_empty() {
+ return None;
+ }
+ let (requires, snippet, description) = validate_snippet(snippet, description, requires)?;
+ Some(Snippet {
+ // Box::into doesn't work as that has a Copy bound 😒
+ postfix_triggers: postfix_triggers.iter().map(String::as_str).map(Into::into).collect(),
+ prefix_triggers: prefix_triggers.iter().map(String::as_str).map(Into::into).collect(),
+ scope,
+ snippet,
+ description,
+ requires,
+ })
+ }
+
+ /// Returns [`None`] if the required items do not resolve.
+ pub(crate) fn imports(&self, ctx: &CompletionContext<'_>) -> Option<Vec<LocatedImport>> {
+ import_edits(ctx, &self.requires)
+ }
+
+ pub fn snippet(&self) -> String {
+ self.snippet.replace("${receiver}", "$0")
+ }
+
+ pub fn postfix_snippet(&self, receiver: &str) -> String {
+ self.snippet.replace("${receiver}", receiver)
+ }
+}
+
+fn import_edits(ctx: &CompletionContext<'_>, requires: &[GreenNode]) -> Option<Vec<LocatedImport>> {
+ let resolve = |import: &GreenNode| {
+ let path = ast::Path::cast(SyntaxNode::new_root(import.clone()))?;
+ let item = match ctx.scope.speculative_resolve(&path)? {
+ hir::PathResolution::Def(def) => def.into(),
+ _ => return None,
+ };
+ let path =
+ ctx.module.find_use_path_prefixed(ctx.db, item, ctx.config.insert_use.prefix_kind)?;
+ Some((path.len() > 1).then(|| LocatedImport::new(path.clone(), item, item, None)))
+ };
+ let mut res = Vec::with_capacity(requires.len());
+ for import in requires {
+ match resolve(import) {
+ Some(first) => res.extend(first),
+ None => return None,
+ }
+ }
+ Some(res)
+}
+
+fn validate_snippet(
+ snippet: &[String],
+ description: &str,
+ requires: &[String],
+) -> Option<(Box<[GreenNode]>, String, Option<Box<str>>)> {
+ let mut imports = Vec::with_capacity(requires.len());
+ for path in requires.iter() {
+ let use_path = ast::SourceFile::parse(&format!("use {};", path))
+ .syntax_node()
+ .descendants()
+ .find_map(ast::Path::cast)?;
+ if use_path.syntax().text() != path.as_str() {
+ return None;
+ }
+ let green = use_path.syntax().green().into_owned();
+ imports.push(green);
+ }
+ let snippet = snippet.iter().join("\n");
+ let description = (!description.is_empty())
+ .then(|| description.split_once('\n').map_or(description, |(it, _)| it))
+ .map(ToOwned::to_owned)
+ .map(Into::into);
+ Some((imports.into_boxed_slice(), snippet, description))
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
new file mode 100644
index 000000000..cf826648d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests.rs
@@ -0,0 +1,305 @@
+//! Tests and test utilities for completions.
+//!
+//! Most tests live in this module or its submodules. The tests in these submodules are "location"
+//! oriented, that is they try to check completions for something like type position, param position
+//! etc.
+//! Tests that are more orientated towards specific completion types like visibility checks of path
+//! completions or `check_edit` tests usually live in their respective completion modules instead.
+//! This gives this test module and its submodules here the main purpose of giving the developer an
+//! overview of whats being completed where, not how.
+
+mod attribute;
+mod expression;
+mod flyimport;
+mod fn_param;
+mod item_list;
+mod item;
+mod pattern;
+mod predicate;
+mod proc_macros;
+mod record;
+mod special;
+mod type_pos;
+mod use_tree;
+mod visibility;
+
+use hir::{db::DefDatabase, PrefixKind, Semantics};
+use ide_db::{
+ base_db::{fixture::ChangeFixture, FileLoader, FilePosition},
+ imports::insert_use::{ImportGranularity, InsertUseConfig},
+ RootDatabase, SnippetCap,
+};
+use itertools::Itertools;
+use stdx::{format_to, trim_indent};
+use syntax::{AstNode, NodeOrToken, SyntaxElement};
+use test_utils::assert_eq_text;
+
+use crate::{
+ resolve_completion_edits, CallableSnippets, CompletionConfig, CompletionItem,
+ CompletionItemKind,
+};
+
+/// Lots of basic item definitions
+const BASE_ITEMS_FIXTURE: &str = r#"
+enum Enum { TupleV(u32), RecordV { field: u32 }, UnitV }
+use self::Enum::TupleV;
+mod module {}
+
+trait Trait {}
+static STATIC: Unit = Unit;
+const CONST: Unit = Unit;
+struct Record { field: u32 }
+struct Tuple(u32);
+struct Unit;
+#[macro_export]
+macro_rules! makro {}
+#[rustc_builtin_macro]
+pub macro Clone {}
+fn function() {}
+union Union { field: i32 }
+"#;
+
+pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
+ enable_postfix_completions: true,
+ enable_imports_on_the_fly: true,
+ enable_self_on_the_fly: true,
+ enable_private_editable: false,
+ callable: Some(CallableSnippets::FillArguments),
+ snippet_cap: SnippetCap::new(true),
+ insert_use: InsertUseConfig {
+ granularity: ImportGranularity::Crate,
+ prefix_kind: PrefixKind::Plain,
+ enforce_granularity: true,
+ group: true,
+ skip_glob_imports: true,
+ },
+ snippets: Vec::new(),
+};
+
+pub(crate) fn completion_list(ra_fixture: &str) -> String {
+ completion_list_with_config(TEST_CONFIG, ra_fixture, true, None)
+}
+
+pub(crate) fn completion_list_no_kw(ra_fixture: &str) -> String {
+ completion_list_with_config(TEST_CONFIG, ra_fixture, false, None)
+}
+
+pub(crate) fn completion_list_no_kw_with_private_editable(ra_fixture: &str) -> String {
+ let mut config = TEST_CONFIG.clone();
+ config.enable_private_editable = true;
+ completion_list_with_config(config, ra_fixture, false, None)
+}
+
+pub(crate) fn completion_list_with_trigger_character(
+ ra_fixture: &str,
+ trigger_character: Option<char>,
+) -> String {
+ completion_list_with_config(TEST_CONFIG, ra_fixture, true, trigger_character)
+}
+
+fn completion_list_with_config(
+ config: CompletionConfig,
+ ra_fixture: &str,
+ include_keywords: bool,
+ trigger_character: Option<char>,
+) -> String {
+ // filter out all but one builtintype completion for smaller test outputs
+ let items = get_all_items(config, ra_fixture, trigger_character);
+ let items = items
+ .into_iter()
+ .filter(|it| it.kind() != CompletionItemKind::BuiltinType || it.label() == "u32")
+ .filter(|it| include_keywords || it.kind() != CompletionItemKind::Keyword)
+ .filter(|it| include_keywords || it.kind() != CompletionItemKind::Snippet)
+ .sorted_by_key(|it| (it.kind(), it.label().to_owned(), it.detail().map(ToOwned::to_owned)))
+ .collect();
+ render_completion_list(items)
+}
+
+/// Creates analysis from a multi-file fixture, returns positions marked with $0.
+pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
+ let change_fixture = ChangeFixture::parse(ra_fixture);
+ let mut database = RootDatabase::default();
+ database.set_enable_proc_attr_macros(true);
+ database.apply_change(change_fixture.change);
+ let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
+ let offset = range_or_offset.expect_offset();
+ (database, FilePosition { file_id, offset })
+}
+
+pub(crate) fn do_completion(code: &str, kind: CompletionItemKind) -> Vec<CompletionItem> {
+ do_completion_with_config(TEST_CONFIG, code, kind)
+}
+
+pub(crate) fn do_completion_with_config(
+ config: CompletionConfig,
+ code: &str,
+ kind: CompletionItemKind,
+) -> Vec<CompletionItem> {
+ get_all_items(config, code, None)
+ .into_iter()
+ .filter(|c| c.kind() == kind)
+ .sorted_by(|l, r| l.label().cmp(r.label()))
+ .collect()
+}
+
+fn render_completion_list(completions: Vec<CompletionItem>) -> String {
+ fn monospace_width(s: &str) -> usize {
+ s.chars().count()
+ }
+ let label_width =
+ completions.iter().map(|it| monospace_width(it.label())).max().unwrap_or_default().min(22);
+ completions
+ .into_iter()
+ .map(|it| {
+ let tag = it.kind().tag();
+ let var_name = format!("{} {}", tag, it.label());
+ let mut buf = var_name;
+ if let Some(detail) = it.detail() {
+ let width = label_width.saturating_sub(monospace_width(it.label()));
+ format_to!(buf, "{:width$} {}", "", detail, width = width);
+ }
+ if it.deprecated() {
+ format_to!(buf, " DEPRECATED");
+ }
+ format_to!(buf, "\n");
+ buf
+ })
+ .collect()
+}
+
+#[track_caller]
+pub(crate) fn check_edit(what: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
+ check_edit_with_config(TEST_CONFIG, what, ra_fixture_before, ra_fixture_after)
+}
+
+#[track_caller]
+pub(crate) fn check_edit_with_config(
+ config: CompletionConfig,
+ what: &str,
+ ra_fixture_before: &str,
+ ra_fixture_after: &str,
+) {
+ let ra_fixture_after = trim_indent(ra_fixture_after);
+ let (db, position) = position(ra_fixture_before);
+ let completions: Vec<CompletionItem> =
+ crate::completions(&db, &config, position, None).unwrap().into();
+ let (completion,) = completions
+ .iter()
+ .filter(|it| it.lookup() == what)
+ .collect_tuple()
+ .unwrap_or_else(|| panic!("can't find {:?} completion in {:#?}", what, completions));
+ let mut actual = db.file_text(position.file_id).to_string();
+
+ let mut combined_edit = completion.text_edit().to_owned();
+
+ resolve_completion_edits(
+ &db,
+ &config,
+ position,
+ completion.imports_to_add().iter().filter_map(|import_edit| {
+ let import_path = &import_edit.import_path;
+ let import_name = import_path.segments().last()?;
+ Some((import_path.to_string(), import_name.to_string()))
+ }),
+ )
+ .into_iter()
+ .flatten()
+ .for_each(|text_edit| {
+ combined_edit.union(text_edit).expect(
+ "Failed to apply completion resolve changes: change ranges overlap, but should not",
+ )
+ });
+
+ combined_edit.apply(&mut actual);
+ assert_eq_text!(&ra_fixture_after, &actual)
+}
+
+pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) {
+ let (db, pos) = position(code);
+
+ let sema = Semantics::new(&db);
+ let original_file = sema.parse(pos.file_id);
+ let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap();
+ assert!(check(NodeOrToken::Token(token)));
+}
+
+pub(crate) fn get_all_items(
+ config: CompletionConfig,
+ code: &str,
+ trigger_character: Option<char>,
+) -> Vec<CompletionItem> {
+ let (db, position) = position(code);
+ let res = crate::completions(&db, &config, position, trigger_character)
+ .map_or_else(Vec::default, Into::into);
+ // validate
+ res.iter().for_each(|it| {
+ let sr = it.source_range();
+ assert!(
+ sr.contains_inclusive(position.offset),
+ "source range {sr:?} does not contain the offset {:?} of the completion request: {it:?}",
+ position.offset
+ );
+ });
+ res
+}
+
+#[test]
+fn test_no_completions_required() {
+ assert_eq!(completion_list(r#"fn foo() { for i i$0 }"#), String::new());
+}
+
+#[test]
+fn regression_10042() {
+ completion_list(
+ r#"
+macro_rules! preset {
+ ($($x:ident)&&*) => {
+ {
+ let mut v = Vec::new();
+ $(
+ v.push($x.into());
+ )*
+ v
+ }
+ };
+}
+
+fn foo() {
+ preset!(foo$0);
+}
+"#,
+ );
+}
+
+#[test]
+fn no_completions_in_comments() {
+ assert_eq!(
+ completion_list(
+ r#"
+fn test() {
+let x = 2; // A comment$0
+}
+"#,
+ ),
+ String::new(),
+ );
+ assert_eq!(
+ completion_list(
+ r#"
+/*
+Some multi-line comment$0
+*/
+"#,
+ ),
+ String::new(),
+ );
+ assert_eq!(
+ completion_list(
+ r#"
+/// Some doc comment
+/// let test$0 = 1
+"#,
+ ),
+ String::new(),
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
new file mode 100644
index 000000000..1578ba2c3
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs
@@ -0,0 +1,1016 @@
+//! Completion tests for attributes.
+use expect_test::{expect, Expect};
+
+use crate::tests::{check_edit, completion_list};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn proc_macros() {
+ check(
+ r#"
+//- proc_macros: identity
+#[$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at repr(…)
+ at warn(…)
+ md proc_macros
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn proc_macros_on_comment() {
+ check(
+ r#"
+//- proc_macros: identity
+/// $0
+#[proc_macros::identity]
+struct Foo;
+"#,
+ expect![[r#""#]],
+ )
+}
+
+#[test]
+fn proc_macros_qualified() {
+ check(
+ r#"
+//- proc_macros: identity
+#[proc_macros::$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at identity proc_macro identity
+ "#]],
+ )
+}
+
+#[test]
+fn inside_nested_attr() {
+ check(r#"#[cfg($0)]"#, expect![[]])
+}
+
+#[test]
+fn with_existing_attr() {
+ check(
+ r#"#[no_mangle] #[$0] mcall!();"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at forbid(…)
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn attr_on_source_file() {
+ check(
+ r#"#![$0]"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at crate_name = ""
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at feature(…)
+ at forbid(…)
+ at must_use
+ at no_implicit_prelude
+ at no_main
+ at no_mangle
+ at no_std
+ at recursion_limit = "…"
+ at type_length_limit = …
+ at warn(…)
+ at windows_subsystem = "…"
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_module() {
+ check(
+ r#"#[$0] mod foo;"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at macro_use
+ at must_use
+ at no_mangle
+ at path = "…"
+ at warn(…)
+ kw crate::
+ kw self::
+ kw super::
+ "#]],
+ );
+ check(
+ r#"mod foo {#![$0]}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_implicit_prelude
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ kw super::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_macro_rules() {
+ check(
+ r#"#[$0] macro_rules! foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at macro_export
+ at macro_use
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_macro_def() {
+ check(
+ r#"#[$0] macro foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_extern_crate() {
+ check(
+ r#"#[$0] extern crate foo;"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at macro_use
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_use() {
+ check(
+ r#"#[$0] use foo;"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_type_alias() {
+ check(
+ r#"#[$0] type foo = ();"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_struct() {
+ check(
+ r#"
+//- minicore:derive
+#[$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at derive macro derive
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at repr(…)
+ at warn(…)
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_enum() {
+ check(
+ r#"#[$0] enum Foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at repr(…)
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_const() {
+ check(
+ r#"#[$0] const FOO: () = ();"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_static() {
+ check(
+ r#"#[$0] static FOO: () = ()"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at export_name = "…"
+ at forbid(…)
+ at global_allocator
+ at link_name = "…"
+ at link_section = "…"
+ at must_use
+ at no_mangle
+ at used
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_trait() {
+ check(
+ r#"#[$0] trait Foo {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_impl() {
+ check(
+ r#"#[$0] impl () {}"#,
+ expect![[r#"
+ at allow(…)
+ at automatically_derived
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"impl () {#![$0]}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_extern_block() {
+ check(
+ r#"#[$0] extern {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at link
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"extern {#![$0]}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at forbid(…)
+ at link
+ at must_use
+ at no_mangle
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_variant() {
+ check(
+ r#"enum Foo { #[$0] Bar }"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at deny(…)
+ at forbid(…)
+ at non_exhaustive
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_on_fn() {
+ check(
+ r#"#[$0] fn main() {}"#,
+ expect![[r#"
+ at allow(…)
+ at cfg(…)
+ at cfg_attr(…)
+ at cold
+ at deny(…)
+ at deprecated
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at export_name = "…"
+ at forbid(…)
+ at ignore = "…"
+ at inline
+ at link_name = "…"
+ at link_section = "…"
+ at must_use
+ at must_use
+ at no_mangle
+ at panic_handler
+ at proc_macro
+ at proc_macro_attribute
+ at proc_macro_derive(…)
+ at should_panic
+ at target_feature(enable = "…")
+ at test
+ at track_caller
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn attr_in_source_file_end() {
+ check(
+ r#"#[$0]"#,
+ expect![[r#"
+ at allow(…)
+ at automatically_derived
+ at cfg(…)
+ at cfg_attr(…)
+ at cold
+ at deny(…)
+ at deprecated
+ at derive(…)
+ at doc = "…"
+ at doc(alias = "…")
+ at doc(hidden)
+ at export_name = "…"
+ at forbid(…)
+ at global_allocator
+ at ignore = "…"
+ at inline
+ at link
+ at link_name = "…"
+ at link_section = "…"
+ at macro_export
+ at macro_use
+ at must_use
+ at no_mangle
+ at non_exhaustive
+ at panic_handler
+ at path = "…"
+ at proc_macro
+ at proc_macro_attribute
+ at proc_macro_derive(…)
+ at repr(…)
+ at should_panic
+ at target_feature(enable = "…")
+ at test
+ at track_caller
+ at used
+ at warn(…)
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+mod cfg {
+ use super::*;
+
+ #[test]
+ fn cfg_target_endian() {
+ check(
+ r#"#[cfg(target_endian = $0"#,
+ expect![[r#"
+ ba big
+ ba little
+ "#]],
+ );
+ }
+}
+
+mod derive {
+ use super::*;
+
+ fn check_derive(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn no_completion_for_incorrect_derive() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive{$0)] struct Test;
+"#,
+ expect![[]],
+ )
+ }
+
+ #[test]
+ fn empty_derive() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive($0)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de PartialEq macro PartialEq
+ de PartialEq, Eq
+ de PartialEq, Eq, PartialOrd, Ord
+ de PartialEq, PartialOrd
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_with_input_before() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive(serde::Serialize, PartialEq, $0)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de Eq
+ de Eq, PartialOrd, Ord
+ de PartialOrd
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ )
+ }
+
+ #[test]
+ fn derive_with_input_after() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive($0 serde::Serialize, PartialEq)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de Eq
+ de Eq, PartialOrd, Ord
+ de PartialOrd
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_with_existing_derives() {
+ check_derive(
+ r#"
+//- minicore: derive, copy, clone, ord, eq, default, fmt
+#[derive(PartialEq, Eq, Or$0)] struct Test;
+"#,
+ expect![[r#"
+ de Clone macro Clone
+ de Clone, Copy
+ de Default macro Default
+ de PartialOrd
+ de PartialOrd, Ord
+ md core
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_flyimport() {
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+#[derive(der$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity (use proc_macros::DeriveIdentity) proc_macro DeriveIdentity
+ md core
+ md proc_macros
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+use proc_macros::DeriveIdentity;
+#[derive(der$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity proc_macro DeriveIdentity
+ md core
+ md proc_macros
+ kw crate::
+ kw self::
+ "#]],
+ );
+ }
+
+ #[test]
+ fn derive_flyimport_edit() {
+ check_edit(
+ "DeriveIdentity",
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive
+#[derive(der$0)] struct Test;
+"#,
+ r#"
+use proc_macros::DeriveIdentity;
+
+#[derive(DeriveIdentity)] struct Test;
+"#,
+ );
+ }
+
+ #[test]
+ fn qualified() {
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive, copy, clone
+#[derive(proc_macros::$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity proc_macro DeriveIdentity
+ "#]],
+ );
+ check_derive(
+ r#"
+//- proc_macros: derive_identity
+//- minicore: derive, copy, clone
+#[derive(proc_macros::C$0)] struct Test;
+"#,
+ expect![[r#"
+ de DeriveIdentity proc_macro DeriveIdentity
+ "#]],
+ );
+ }
+}
+
+mod lint {
+ use super::*;
+
+ #[test]
+ fn lint_empty() {
+ check_edit(
+ "deprecated",
+ r#"#[allow($0)] struct Test;"#,
+ r#"#[allow(deprecated)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_with_existing() {
+ check_edit(
+ "deprecated",
+ r#"#[allow(keyword_idents, $0)] struct Test;"#,
+ r#"#[allow(keyword_idents, deprecated)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_qualified() {
+ check_edit(
+ "deprecated",
+ r#"#[allow(keyword_idents, $0)] struct Test;"#,
+ r#"#[allow(keyword_idents, deprecated)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_feature() {
+ check_edit(
+ "box_syntax",
+ r#"#[feature(box_$0)] struct Test;"#,
+ r#"#[feature(box_syntax)] struct Test;"#,
+ )
+ }
+
+ #[test]
+ fn lint_clippy_unqualified() {
+ check_edit(
+ "clippy::as_conversions",
+ r#"#[allow($0)] struct Test;"#,
+ r#"#[allow(clippy::as_conversions)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_clippy_qualified() {
+ check_edit(
+ "as_conversions",
+ r#"#[allow(clippy::$0)] struct Test;"#,
+ r#"#[allow(clippy::as_conversions)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_rustdoc_unqualified() {
+ check_edit(
+ "rustdoc::bare_urls",
+ r#"#[allow($0)] struct Test;"#,
+ r#"#[allow(rustdoc::bare_urls)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_rustdoc_qualified() {
+ check_edit(
+ "bare_urls",
+ r#"#[allow(rustdoc::$0)] struct Test;"#,
+ r#"#[allow(rustdoc::bare_urls)] struct Test;"#,
+ );
+ }
+
+ #[test]
+ fn lint_unclosed() {
+ check_edit(
+ "deprecated",
+ r#"#[allow(dep$0 struct Test;"#,
+ r#"#[allow(deprecated struct Test;"#,
+ );
+ check_edit(
+ "bare_urls",
+ r#"#[allow(rustdoc::$0 struct Test;"#,
+ r#"#[allow(rustdoc::bare_urls struct Test;"#,
+ );
+ }
+}
+
+mod repr {
+ use super::*;
+
+ fn check_repr(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+ }
+
+ #[test]
+ fn no_completion_for_incorrect_repr() {
+ check_repr(r#"#[repr{$0)] struct Test;"#, expect![[]])
+ }
+
+ #[test]
+ fn empty() {
+ check_repr(
+ r#"#[repr($0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba align($0)
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba packed
+ ba transparent
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn transparent() {
+ check_repr(r#"#[repr(transparent, $0)] struct Test;"#, expect![[r#""#]]);
+ }
+
+ #[test]
+ fn align() {
+ check_repr(
+ r#"#[repr(align(1), $0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba transparent
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn packed() {
+ check_repr(
+ r#"#[repr(packed, $0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba transparent
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn c() {
+ check_repr(
+ r#"#[repr(C, $0)] struct Test;"#,
+ expect![[r#"
+ ba align($0)
+ ba i16
+ ba i28
+ ba i32
+ ba i64
+ ba i8
+ ba isize
+ ba packed
+ ba u128
+ ba u16
+ ba u32
+ ba u64
+ ba u8
+ ba usize
+ "#]],
+ );
+ }
+
+ #[test]
+ fn prim() {
+ check_repr(
+ r#"#[repr(usize, $0)] struct Test;"#,
+ expect![[r#"
+ ba C
+ ba align($0)
+ ba packed
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs
new file mode 100644
index 000000000..925081ebf
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs
@@ -0,0 +1,672 @@
+//! Completion tests for expressions.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+fn check_empty(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn complete_literal_struct_with_a_private_field() {
+ // `FooDesc.bar` is private, the completion should not be triggered.
+ check(
+ r#"
+mod _69latrick {
+ pub struct FooDesc { pub six: bool, pub neuf: Vec<String>, bar: bool }
+ pub fn create_foo(foo_desc: &FooDesc) -> () { () }
+}
+
+fn baz() {
+ use _69latrick::*;
+
+ let foo = create_foo(&$0);
+}
+ "#,
+ // This should not contain `FooDesc {…}`.
+ expect![[r#"
+ ct CONST
+ en Enum
+ fn baz() fn()
+ fn create_foo(…) fn(&FooDesc)
+ fn function() fn()
+ ma makro!(…) macro_rules! makro
+ md _69latrick
+ md module
+ sc STATIC
+ st FooDesc
+ st Record
+ st Tuple
+ st Unit
+ un Union
+ ev TupleV(…) TupleV(u32)
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw mut
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ )
+}
+
+#[test]
+fn completes_various_bindings() {
+ check_empty(
+ r#"
+fn func(param0 @ (param1, param2): (i32, i32)) {
+ let letlocal = 92;
+ if let ifletlocal = 100 {
+ match 0 {
+ matcharm => 1 + $0,
+ otherwise => (),
+ }
+ }
+ let letlocal2 = 44;
+}
+"#,
+ expect![[r#"
+ fn func(…) fn((i32, i32))
+ lc ifletlocal i32
+ lc letlocal i32
+ lc matcharm i32
+ lc param0 (i32, i32)
+ lc param1 i32
+ lc param2 i32
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+}
+
+#[test]
+fn completes_all_the_things_in_fn_body() {
+ check(
+ r#"
+use non_existant::Unresolved;
+mod qualified { pub enum Enum { Variant } }
+
+impl Unit {
+ fn foo<'lifetime, TypeParam, const CONST_PARAM: usize>(self) {
+ fn local_func() {}
+ $0
+ }
+}
+"#,
+ // `self` is in here twice, once as the module, once as the local
+ expect![[r#"
+ ct CONST
+ cp CONST_PARAM
+ en Enum
+ fn function() fn()
+ fn local_func() fn()
+ lc self Unit
+ ma makro!(…) macro_rules! makro
+ md module
+ md qualified
+ sp Self
+ sc STATIC
+ st Record
+ st Tuple
+ st Unit
+ tp TypeParam
+ un Union
+ ev TupleV(…) TupleV(u32)
+ bt u32
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ me self.foo() fn(self)
+ sn macro_rules
+ sn pd
+ sn ppd
+ ?? Unresolved
+ "#]],
+ );
+ check(
+ r#"
+use non_existant::Unresolved;
+mod qualified { pub enum Enum { Variant } }
+
+impl Unit {
+ fn foo<'lifetime, TypeParam, const CONST_PARAM: usize>(self) {
+ fn local_func() {}
+ self::$0
+ }
+}
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ fn function() fn()
+ ma makro!(…) macro_rules! makro
+ md module
+ md qualified
+ sc STATIC
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ ev TupleV(…) TupleV(u32)
+ ?? Unresolved
+ "#]],
+ );
+}
+
+#[test]
+fn complete_in_block() {
+ check_empty(
+ r#"
+ fn foo() {
+ if true {
+ $0
+ }
+ }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ )
+}
+
+#[test]
+fn complete_after_if_expr() {
+ check_empty(
+ r#"
+ fn foo() {
+ if true {}
+ $0
+ }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw const
+ kw crate::
+ kw else
+ kw else if
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ )
+}
+
+#[test]
+fn complete_in_match_arm() {
+ check_empty(
+ r#"
+ fn foo() {
+ match () {
+ () => $0
+ }
+ }
+"#,
+ expect![[r#"
+ fn foo() fn()
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ )
+}
+
+#[test]
+fn completes_in_loop_ctx() {
+ check_empty(
+ r"fn my() { loop { $0 } }",
+ expect![[r#"
+ fn my() fn()
+ bt u32
+ kw break
+ kw const
+ kw continue
+ kw crate::
+ kw enum
+ kw extern
+ kw false
+ kw fn
+ kw for
+ kw if
+ kw if let
+ kw impl
+ kw let
+ kw loop
+ kw match
+ kw mod
+ kw return
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw true
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ kw while
+ kw while let
+ sn macro_rules
+ sn pd
+ sn ppd
+ "#]],
+ );
+}
+
+#[test]
+fn completes_in_let_initializer() {
+ check_empty(
+ r#"fn main() { let _ = $0 }"#,
+ expect![[r#"
+ fn main() fn()
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ )
+}
+
+#[test]
+fn struct_initializer_field_expr() {
+ check_empty(
+ r#"
+struct Foo {
+ pub f: i32,
+}
+fn foo() {
+ Foo {
+ f: $0
+ }
+}
+"#,
+ expect![[r#"
+ fn foo() fn()
+ st Foo
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+}
+
+#[test]
+fn shadowing_shows_single_completion() {
+ cov_mark::check!(shadowing_shows_single_completion);
+
+ check_empty(
+ r#"
+fn foo() {
+ let bar = 92;
+ {
+ let bar = 62;
+ drop($0)
+ }
+}
+"#,
+ expect![[r#"
+ fn foo() fn()
+ lc bar i32
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+}
+
+#[test]
+fn in_macro_expr_frag() {
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn quux(x: i32) {
+ m!($0);
+}
+"#,
+ expect![[r#"
+ fn quux(…) fn(i32)
+ lc x i32
+ ma m!(…) macro_rules! m
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+ check_empty(
+ r"
+macro_rules! m { ($e:expr) => { $e } }
+fn quux(x: i32) {
+ m!(x$0);
+}
+",
+ expect![[r#"
+ fn quux(…) fn(i32)
+ lc x i32
+ ma m!(…) macro_rules! m
+ bt u32
+ kw crate::
+ kw false
+ kw for
+ kw if
+ kw if let
+ kw loop
+ kw match
+ kw return
+ kw self::
+ kw true
+ kw unsafe
+ kw while
+ kw while let
+ "#]],
+ );
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+fn quux(x: i32) {
+ let y = 92;
+ m!(x$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn enum_qualified() {
+ check(
+ r#"
+impl Enum {
+ type AssocType = ();
+ const ASSOC_CONST: () = ();
+ fn assoc_fn() {}
+}
+fn func() {
+ Enum::$0
+}
+"#,
+ expect![[r#"
+ ct ASSOC_CONST const ASSOC_CONST: ()
+ fn assoc_fn() fn()
+ ta AssocType type AssocType = ()
+ ev RecordV {…} RecordV { field: u32 }
+ ev TupleV(…) TupleV(u32)
+ ev UnitV UnitV
+ "#]],
+ );
+}
+
+#[test]
+fn ty_qualified_no_drop() {
+ check_empty(
+ r#"
+//- minicore: drop
+struct Foo;
+impl Drop for Foo {
+ fn drop(&mut self) {}
+}
+fn func() {
+ Foo::$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn with_parens() {
+ check_empty(
+ r#"
+enum Enum {
+ Variant()
+}
+impl Enum {
+ fn variant() -> Self { Enum::Variant() }
+}
+fn func() {
+ Enum::$0()
+}
+"#,
+ expect![[r#"
+ fn variant fn() -> Enum
+ ev Variant Variant
+ "#]],
+ );
+}
+
+#[test]
+fn detail_impl_trait_in_return_position() {
+ check_empty(
+ r"
+//- minicore: sized
+trait Trait<T> {}
+fn foo<U>() -> impl Trait<U> {}
+fn main() {
+ self::$0
+}
+",
+ expect![[r#"
+ fn foo() fn() -> impl Trait<U>
+ fn main() fn()
+ tt Trait
+ "#]],
+ );
+}
+
+#[test]
+fn detail_async_fn() {
+ check_empty(
+ r#"
+//- minicore: future, sized
+trait Trait<T> {}
+async fn foo() -> u8 {}
+async fn bar<U>() -> impl Trait<U> {}
+fn main() {
+ self::$0
+}
+"#,
+ expect![[r#"
+ fn bar() async fn() -> impl Trait<U>
+ fn foo() async fn() -> u8
+ fn main() fn()
+ tt Trait
+ "#]],
+ );
+}
+
+#[test]
+fn detail_impl_trait_in_argument_position() {
+ check_empty(
+ r"
+//- minicore: sized
+trait Trait<T> {}
+struct Foo;
+impl Foo {
+ fn bar<U>(_: impl Trait<U>) {}
+}
+fn main() {
+ Foo::$0
+}
+",
+ expect![[r"
+ fn bar(…) fn(impl Trait<U>)
+ "]],
+ );
+}
+
+#[test]
+fn complete_record_expr_path() {
+ check(
+ r#"
+struct Zulu;
+impl Zulu {
+ fn test() -> Self { }
+}
+fn boi(val: Zulu) { }
+fn main() {
+ boi(Zulu:: $0 {});
+}
+"#,
+ expect![[r#"
+ fn test() fn() -> Zulu
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
new file mode 100644
index 000000000..0bba7f245
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs
@@ -0,0 +1,1232 @@
+use expect_test::{expect, Expect};
+
+use crate::{
+ context::{CompletionAnalysis, NameContext, NameKind, NameRefKind},
+ tests::{check_edit, check_edit_with_config, TEST_CONFIG},
+};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let config = TEST_CONFIG;
+ let (db, position) = crate::tests::position(ra_fixture);
+ let (ctx, analysis) = crate::context::CompletionContext::new(&db, position, &config).unwrap();
+
+ let mut acc = crate::completions::Completions::default();
+ if let CompletionAnalysis::Name(NameContext { kind: NameKind::IdentPat(pat_ctx), .. }) =
+ &analysis
+ {
+ crate::completions::flyimport::import_on_the_fly_pat(&mut acc, &ctx, pat_ctx);
+ }
+ if let CompletionAnalysis::NameRef(name_ref_ctx) = &analysis {
+ match &name_ref_ctx.kind {
+ NameRefKind::Path(path) => {
+ crate::completions::flyimport::import_on_the_fly_path(&mut acc, &ctx, path);
+ }
+ NameRefKind::DotAccess(dot_access) => {
+ crate::completions::flyimport::import_on_the_fly_dot(&mut acc, &ctx, dot_access);
+ }
+ NameRefKind::Pattern(pattern) => {
+ crate::completions::flyimport::import_on_the_fly_pat(&mut acc, &ctx, pattern);
+ }
+ _ => (),
+ }
+ }
+
+ expect.assert_eq(&super::render_completion_list(Vec::from(acc)));
+}
+
+#[test]
+fn function_fuzzy_completion() {
+ check_edit(
+ "stdin",
+ r#"
+//- /lib.rs crate:dep
+pub mod io {
+ pub fn stdin() {}
+};
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ stdi$0
+}
+"#,
+ r#"
+use dep::io::stdin;
+
+fn main() {
+ stdin()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn macro_fuzzy_completion() {
+ check_edit(
+ "macro_with_curlies!",
+ r#"
+//- /lib.rs crate:dep
+/// Please call me as macro_with_curlies! {}
+#[macro_export]
+macro_rules! macro_with_curlies {
+ () => {}
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ curli$0
+}
+"#,
+ r#"
+use dep::macro_with_curlies;
+
+fn main() {
+ macro_with_curlies! {$0}
+}
+"#,
+ );
+}
+
+#[test]
+fn struct_fuzzy_completion() {
+ check_edit(
+ "ThirdStruct",
+ r#"
+//- /lib.rs crate:dep
+pub struct FirstStruct;
+pub mod some_module {
+ pub struct SecondStruct;
+ pub struct ThirdStruct;
+}
+
+//- /main.rs crate:main deps:dep
+use dep::{FirstStruct, some_module::SecondStruct};
+
+fn main() {
+ this$0
+}
+"#,
+ r#"
+use dep::{FirstStruct, some_module::{SecondStruct, ThirdStruct}};
+
+fn main() {
+ ThirdStruct
+}
+"#,
+ );
+}
+
+#[test]
+fn short_paths_are_ignored() {
+ cov_mark::check!(flyimport_exact_on_short_path);
+
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub struct Bar;
+pub struct Rcar;
+pub struct Rc;
+pub mod some_module {
+ pub struct Bar;
+ pub struct Rcar;
+ pub struct Rc;
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ rc$0
+}
+"#,
+ expect![[r#"
+ st Rc (use dep::Rc)
+ st Rc (use dep::some_module::Rc)
+ "#]],
+ );
+}
+
+#[test]
+fn fuzzy_completions_come_in_specific_order() {
+ cov_mark::check!(certain_fuzzy_order_test);
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub struct FirstStruct;
+pub mod some_module {
+ // already imported, omitted
+ pub struct SecondStruct;
+ // does not contain all letters from the query, omitted
+ pub struct UnrelatedOne;
+ // contains all letters from the query, but not in sequence, displayed last
+ pub struct ThiiiiiirdStruct;
+ // contains all letters from the query, but not in the beginning, displayed second
+ pub struct AfterThirdStruct;
+ // contains all letters from the query in the begginning, displayed first
+ pub struct ThirdStruct;
+}
+
+//- /main.rs crate:main deps:dep
+use dep::{FirstStruct, some_module::SecondStruct};
+
+fn main() {
+ hir$0
+}
+"#,
+ expect![[r#"
+ st ThirdStruct (use dep::some_module::ThirdStruct)
+ st AfterThirdStruct (use dep::some_module::AfterThirdStruct)
+ st ThiiiiiirdStruct (use dep::some_module::ThiiiiiirdStruct)
+ "#]],
+ );
+}
+
+#[test]
+fn trait_function_fuzzy_completion() {
+ let fixture = r#"
+ //- /lib.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+ }
+
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::wei$0
+ }
+ "#;
+
+ check(
+ fixture,
+ expect![[r#"
+ fn weird_function() (use dep::test_mod::TestTrait) fn()
+ "#]],
+ );
+
+ check_edit(
+ "weird_function",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestStruct::weird_function()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn trait_const_fuzzy_completion() {
+ let fixture = r#"
+ //- /lib.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+ }
+
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ dep::test_mod::TestStruct::spe$0
+ }
+ "#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct SPECIAL_CONST (use dep::test_mod::TestTrait)
+ "#]],
+ );
+
+ check_edit(
+ "SPECIAL_CONST",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestStruct::SPECIAL_CONST
+}
+"#,
+ );
+}
+
+#[test]
+fn trait_method_fuzzy_completion() {
+ let fixture = r#"
+ //- /lib.rs crate:dep
+ pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+ }
+
+ //- /main.rs crate:main deps:dep
+ fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.ran$0
+ }
+ "#;
+
+ check(
+ fixture,
+ expect![[r#"
+ me random_method() (use dep::test_mod::TestTrait) fn(&self)
+ "#]],
+ );
+
+ check_edit(
+ "random_method",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.random_method()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn trait_method_from_alias() {
+ let fixture = r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ fn random_method();
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ fn random_method() {}
+ }
+ pub type TestAlias = TestStruct;
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestAlias::ran$0
+}
+"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ fn random_method() (use dep::test_mod::TestTrait) fn()
+ "#]],
+ );
+
+ check_edit(
+ "random_method",
+ fixture,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestAlias::random_method()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn no_trait_type_fuzzy_completion() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::hum$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn does_not_propose_names_in_scope() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+use dep::test_mod::TestStruct;
+fn main() {
+ TestSt$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn does_not_propose_traits_in_scope() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+use dep::test_mod::{TestStruct, TestTrait};
+fn main() {
+ dep::test_mod::TestStruct::hum$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn blanket_trait_impl_import() {
+ check_edit(
+ "another_function",
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ pub struct TestStruct {}
+ pub trait TestTrait {
+ fn another_function();
+ }
+ impl<T> TestTrait for T {
+ fn another_function() {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::ano$0
+}
+"#,
+ r#"
+use dep::test_mod::TestTrait;
+
+fn main() {
+ dep::test_mod::TestStruct::another_function()$0
+}
+"#,
+ );
+}
+
+#[test]
+fn zero_input_deprecated_assoc_item_completion() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ #[deprecated]
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ let test_struct = dep::test_mod::TestStruct {};
+ test_struct.$0
+}
+ "#,
+ expect![[r#"
+ me random_method() (use dep::test_mod::TestTrait) fn(&self) DEPRECATED
+ "#]],
+ );
+
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod test_mod {
+ #[deprecated]
+ pub trait TestTrait {
+ const SPECIAL_CONST: u8;
+ type HumbleType;
+ fn weird_function();
+ fn random_method(&self);
+ }
+ pub struct TestStruct {}
+ impl TestTrait for TestStruct {
+ const SPECIAL_CONST: u8 = 42;
+ type HumbleType = ();
+ fn weird_function() {}
+ fn random_method(&self) {}
+ }
+}
+
+//- /main.rs crate:main deps:dep
+fn main() {
+ dep::test_mod::TestStruct::$0
+}
+"#,
+ expect![[r#"
+ fn weird_function() (use dep::test_mod::TestTrait) fn() DEPRECATED
+ ct SPECIAL_CONST (use dep::test_mod::TestTrait) DEPRECATED
+ "#]],
+ );
+}
+
+#[test]
+fn no_completions_in_use_statements() {
+ check(
+ r#"
+//- /lib.rs crate:dep
+pub mod io {
+ pub fn stdin() {}
+};
+
+//- /main.rs crate:main deps:dep
+use stdi$0
+
+fn main() {}
+"#,
+ expect![[]],
+ );
+}
+
+#[test]
+fn prefix_config_usage() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use crate::foo::bar;
+
+fn main() {
+ Ite$0
+}"#;
+ let mut config = TEST_CONFIG;
+
+ config.insert_use.prefix_kind = hir::PrefixKind::ByCrate;
+ check_edit_with_config(
+ config.clone(),
+ "Item",
+ fixture,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use crate::foo::bar::{self, Item};
+
+fn main() {
+ Item
+}"#,
+ );
+
+ config.insert_use.prefix_kind = hir::PrefixKind::BySelf;
+ check_edit_with_config(
+ config.clone(),
+ "Item",
+ fixture,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use crate::foo::bar;
+
+use self::foo::bar::Item;
+
+fn main() {
+ Item
+}"#,
+ );
+
+ config.insert_use.prefix_kind = hir::PrefixKind::Plain;
+ check_edit_with_config(
+ config,
+ "Item",
+ fixture,
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+ }
+}
+
+use foo::bar::Item;
+
+use crate::foo::bar;
+
+fn main() {
+ Item
+}"#,
+ );
+}
+
+#[test]
+fn unresolved_qualifier() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct Item;
+ }
+ }
+}
+
+fn main() {
+ bar::baz::Ite$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ st Item (use foo::bar::baz::Item)
+ "#]],
+ );
+
+ check_edit(
+ "Item",
+ fixture,
+ r#"
+ use foo::bar;
+
+ mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct Item;
+ }
+ }
+ }
+
+ fn main() {
+ bar::baz::Item
+ }"#,
+ );
+}
+
+#[test]
+fn unresolved_assoc_item_container() {
+ let fixture = r#"
+mod foo {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+}
+
+fn main() {
+ Item::TEST_A$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct TEST_ASSOC (use foo::Item)
+ "#]],
+ );
+
+ check_edit(
+ "TEST_ASSOC",
+ fixture,
+ r#"
+use foo::Item;
+
+mod foo {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+}
+
+fn main() {
+ Item::TEST_ASSOC
+}"#,
+ );
+}
+
+#[test]
+fn unresolved_assoc_item_container_with_path() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_A$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct TEST_ASSOC (use foo::bar::Item)
+ "#]],
+ );
+
+ check_edit(
+ "TEST_ASSOC",
+ fixture,
+ r#"
+use foo::bar;
+
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_ASSOC
+}"#,
+ );
+}
+
+#[test]
+fn fuzzy_unresolved_path() {
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::ASS$0
+}"#,
+ expect![[]],
+ )
+}
+
+#[test]
+fn unqualified_assoc_items_are_omitted() {
+ check(
+ r#"
+mod something {
+ pub trait BaseTrait {
+ fn test_function() -> i32;
+ }
+
+ pub struct Item1;
+ pub struct Item2;
+
+ impl BaseTrait for Item1 {
+ fn test_function() -> i32 {
+ 1
+ }
+ }
+
+ impl BaseTrait for Item2 {
+ fn test_function() -> i32 {
+ 2
+ }
+ }
+}
+
+fn main() {
+ test_f$0
+}"#,
+ expect![[]],
+ )
+}
+
+#[test]
+fn case_matters() {
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ TES$0
+}"#,
+ expect![[r#"
+ ct TEST_CONST (use foo::TEST_CONST)
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ tes$0
+}"#,
+ expect![[r#"
+ ct TEST_CONST (use foo::TEST_CONST)
+ fn test_function() (use foo::test_function) fn() -> i32
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ Te$0
+}"#,
+ expect![[]],
+ );
+}
+
+#[test]
+fn no_fuzzy_during_fields_of_record_lit_syntax() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo {
+ some_field: i32,
+}
+fn main() {
+ let _ = Foo { so$0 };
+}
+"#,
+ expect![[]],
+ );
+}
+
+#[test]
+fn fuzzy_after_fields_of_record_lit_syntax() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo {
+ some_field: i32,
+}
+fn main() {
+ let _ = Foo { some_field: som$0 };
+}
+"#,
+ expect![[r#"
+ fn some_fn() (use m::some_fn) fn() -> i32
+ "#]],
+ );
+}
+
+#[test]
+fn no_flyimports_in_traits_and_impl_declarations() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+trait Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo;
+impl Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo;
+trait Bar {}
+impl Bar for Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn no_inherent_candidates_proposed() {
+ check(
+ r#"
+mod baz {
+ pub trait DefDatabase {
+ fn method1(&self);
+ }
+ pub trait HirDatabase: DefDatabase {
+ fn method2(&self);
+ }
+}
+
+mod bar {
+ fn test(db: &dyn crate::baz::HirDatabase) {
+ db.metho$0
+ }
+}
+ "#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+mod baz {
+ pub trait DefDatabase {
+ fn method1(&self);
+ }
+ pub trait HirDatabase: DefDatabase {
+ fn method2(&self);
+ }
+}
+
+mod bar {
+ fn test(db: &impl crate::baz::HirDatabase) {
+ db.metho$0
+ }
+}
+"#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+mod baz {
+ pub trait DefDatabase {
+ fn method1(&self);
+ }
+ pub trait HirDatabase: DefDatabase {
+ fn method2(&self);
+ }
+}
+
+mod bar {
+ fn test<T: crate::baz::HirDatabase>(db: T) {
+ db.metho$0
+ }
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn respects_doc_hidden() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:dep
+fn f() {
+ ().fro$0
+}
+
+//- /dep.rs crate:dep
+#[doc(hidden)]
+pub trait Private {
+ fn frob(&self) {}
+}
+
+impl<T> Private for T {}
+ "#,
+ expect![[r#""#]],
+ );
+ check(
+ r#"
+//- /lib.rs crate:lib deps:dep
+fn f() {
+ ().fro$0
+}
+
+//- /dep.rs crate:dep
+pub trait Private {
+ #[doc(hidden)]
+ fn frob(&self) {}
+}
+
+impl<T> Private for T {}
+ "#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn regression_9760() {
+ check(
+ r#"
+struct Struct;
+fn main() {}
+
+mod mud {
+ fn func() {
+ let struct_instance = Stru$0
+ }
+}
+"#,
+ expect![[r#"
+ st Struct (use crate::Struct)
+ "#]],
+ );
+}
+
+#[test]
+fn flyimport_pattern() {
+ check(
+ r#"
+mod module {
+ pub struct FooStruct {}
+ pub const FooConst: () = ();
+ pub fn foo_fun() {}
+}
+fn function() {
+ let foo$0
+}
+"#,
+ expect![[r#"
+ ct FooConst (use module::FooConst)
+ st FooStruct (use module::FooStruct)
+ "#]],
+ );
+}
+
+#[test]
+fn flyimport_item_name() {
+ check(
+ r#"
+mod module {
+ pub struct Struct;
+}
+struct Str$0
+ "#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn flyimport_rename() {
+ check(
+ r#"
+mod module {
+ pub struct Struct;
+}
+use self as Str$0;
+ "#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn flyimport_enum_variant() {
+ check(
+ r#"
+mod foo {
+ pub struct Barbara;
+}
+
+enum Foo {
+ Barba$0()
+}
+}"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub struct Barbara;
+}
+
+enum Foo {
+ Barba(Barba$0)
+}
+}"#,
+ expect![[r#"
+ st Barbara (use foo::Barbara)
+ "#]],
+ )
+}
+
+#[test]
+fn flyimport_attribute() {
+ check(
+ r#"
+//- proc_macros:identity
+#[ide$0]
+struct Foo;
+"#,
+ expect![[r#"
+ at identity (use proc_macros::identity) proc_macro identity
+ "#]],
+ );
+ check_edit(
+ "identity",
+ r#"
+//- proc_macros:identity
+#[ide$0]
+struct Foo;
+"#,
+ r#"
+use proc_macros::identity;
+
+#[identity]
+struct Foo;
+"#,
+ );
+}
+
+#[test]
+fn flyimport_in_type_bound_omits_types() {
+ check(
+ r#"
+mod module {
+ pub struct CompletemeStruct;
+ pub type CompletemeType = ();
+ pub enum CompletemeEnum {}
+ pub trait CompletemeTrait {}
+}
+
+fn f<T>() where T: Comp$0
+"#,
+ expect![[r#"
+ tt CompletemeTrait (use module::CompletemeTrait)
+ "#]],
+ );
+}
+
+#[test]
+fn flyimport_source_file() {
+ check(
+ r#"
+//- /main.rs crate:main deps:dep
+def$0
+//- /lib.rs crate:dep
+#[macro_export]
+macro_rules! define_struct {
+ () => {
+ pub struct Foo;
+ };
+}
+"#,
+ expect![[r#"
+ ma define_struct!(…) (use dep::define_struct) macro_rules! define_struct
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs
new file mode 100644
index 000000000..cce74604c
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs
@@ -0,0 +1,274 @@
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, completion_list_with_trigger_character};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+fn check_with_trigger_character(ra_fixture: &str, trigger_character: char, expect: Expect) {
+ let actual = completion_list_with_trigger_character(ra_fixture, Some(trigger_character));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn only_param() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: usize) {}
+fn baz(file$0) {}
+"#,
+ expect![[r#"
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn last_param() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: usize) {}
+fn baz(foo: (), file$0) {}
+"#,
+ expect![[r#"
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn first_param() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: usize) {}
+fn baz(file$0 id: u32) {}
+"#,
+ expect![[r#"
+ bn file_id: usize,
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn repeated_param_name() {
+ check(
+ r#"
+fn foo(file_id: usize) {}
+fn bar(file_id: u32, $0) {}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+
+ check(
+ r#"
+fn f(#[foo = "bar"] baz: u32,) {}
+fn g(baz: (), ba$0)
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn trait_param() {
+ check(
+ r#"
+pub(crate) trait SourceRoot {
+ pub fn contains(file_id: usize) -> bool;
+ pub fn syntax(file$0)
+}
+"#,
+ expect![[r#"
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn in_inner_function() {
+ check(
+ r#"
+fn outer(text: &str) {
+ fn inner($0)
+}
+"#,
+ expect![[r#"
+ bn text: &str
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn trigger_by_l_paren() {
+ check_with_trigger_character(
+ r#"
+fn foo($0)
+"#,
+ '(',
+ expect![[]],
+ )
+}
+
+#[test]
+fn shows_non_ident_pat_param() {
+ check(
+ r#"
+struct Bar { bar: u32 }
+fn foo(Bar { bar }: Bar) {}
+fn foo2($0) {}
+"#,
+ expect![[r#"
+ st Bar
+ bn Bar { bar }: Bar
+ bn Bar {…} Bar { bar$1 }: Bar$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn in_impl_only_param() {
+ check(
+ r#"
+struct A {}
+
+impl A {
+ fn foo(file_id: usize) {}
+ fn new($0) {}
+}
+"#,
+ expect![[r#"
+ sp Self
+ st A
+ bn &mut self
+ bn &self
+ bn file_id: usize
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn in_impl_after_self() {
+ check(
+ r#"
+struct A {}
+
+impl A {
+ fn foo(file_id: usize) {}
+ fn new(self, $0) {}
+}
+"#,
+ expect![[r#"
+ sp Self
+ st A
+ bn file_id: usize
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+// doesn't complete qux due to there being no expression after
+// see source_analyzer::adjust comment
+#[test]
+fn local_fn_shows_locals_for_params() {
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ fn inner($0) {}
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ bn bar: i32
+ bn baz: i32
+ bn foo: i32
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn closure_shows_locals_for_params() {
+ check(
+ r#"
+fn outer() {
+ let foo = 3;
+ {
+ let bar = 3;
+ |$0| {};
+ let baz = 3;
+ let qux = 3;
+ }
+ let fez = 3;
+}
+"#,
+ expect![[r#"
+ bn bar: i32
+ bn baz: i32
+ bn foo: i32
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_fully_equal() {
+ check(
+ r#"
+fn foo(bar: u32) {}
+fn bar(bar$0) {}
+"#,
+ expect![[r#"
+ bn bar: u32
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_for_params_with_attributes() {
+ check(
+ r#"
+fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
+fn g(foo: (), #[baz = "qux"] mut ba$0)
+"#,
+ expect![[r##"
+ bn #[baz = "qux"] mut bar: u32
+ "##]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs
new file mode 100644
index 000000000..409413c1d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs
@@ -0,0 +1,154 @@
+//! Completion tests for item specifics overall.
+//!
+//! Except for use items which are tested in [super::use_tree] and mod declarations with are tested
+//! in [crate::completions::mod_].
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn target_type_or_trait_in_impl_block() {
+ check(
+ r#"
+impl Tra$0
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn target_type_in_trait_impl_block() {
+ check(
+ r#"
+impl Trait for Str$0
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn after_trait_name_in_trait_def() {
+ check(
+ r"trait A $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+}
+
+#[test]
+fn after_target_name_in_impl() {
+ check(
+ r"impl Trait $0",
+ expect![[r#"
+ kw for
+ kw where
+ "#]],
+ );
+ check(
+ r"impl Trait f$0",
+ expect![[r#"
+ kw for
+ kw where
+ "#]],
+ );
+ check(
+ r"impl Trait for Type $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+}
+
+#[test]
+fn completes_where() {
+ check(
+ r"struct Struct $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"struct Struct $0 {}",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ // FIXME: This shouldn't be completed here
+ check(
+ r"struct Struct $0 ()",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"fn func() $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"enum Enum $0",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"enum Enum $0 {}",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+ check(
+ r"trait Trait $0 {}",
+ expect![[r#"
+ kw where
+ "#]],
+ );
+}
+
+#[test]
+fn before_record_field() {
+ check(
+ r#"
+struct Foo {
+ $0
+ pub f: i32,
+}
+"#,
+ expect![[r#"
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ "#]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs
new file mode 100644
index 000000000..5076c6e86
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs
@@ -0,0 +1,247 @@
+//! Completion tests for item list position.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn in_mod_item_list() {
+ check(
+ r#"mod tests { $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw fn
+ kw impl
+ kw mod
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw static
+ kw struct
+ kw super::
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ sn macro_rules
+ sn tfn (Test function)
+ sn tmod (Test module)
+ "#]],
+ )
+}
+
+#[test]
+fn in_source_file_item_list() {
+ check(
+ r#"$0"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw fn
+ kw impl
+ kw mod
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ sn macro_rules
+ sn tfn (Test function)
+ sn tmod (Test module)
+ "#]],
+ )
+}
+
+#[test]
+fn in_item_list_after_attr() {
+ check(
+ r#"#[attr] $0"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw enum
+ kw extern
+ kw fn
+ kw impl
+ kw mod
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw static
+ kw struct
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ sn macro_rules
+ sn tfn (Test function)
+ sn tmod (Test module)
+ "#]],
+ )
+}
+
+#[test]
+fn in_qualified_path() {
+ check(
+ r#"crate::$0"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ "#]],
+ )
+}
+
+#[test]
+fn after_unsafe_token() {
+ check(
+ r#"unsafe $0"#,
+ expect![[r#"
+ kw fn
+ kw impl
+ kw trait
+ "#]],
+ );
+}
+
+#[test]
+fn after_visibility() {
+ check(
+ r#"pub $0"#,
+ expect![[r#"
+ kw const
+ kw enum
+ kw extern
+ kw fn
+ kw mod
+ kw static
+ kw struct
+ kw trait
+ kw type
+ kw union
+ kw unsafe
+ kw use
+ "#]],
+ );
+}
+
+#[test]
+fn after_visibility_unsafe() {
+ check(
+ r#"pub unsafe $0"#,
+ expect![[r#"
+ kw fn
+ kw trait
+ "#]],
+ );
+}
+
+#[test]
+fn in_impl_assoc_item_list() {
+ check(
+ r#"impl Struct { $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw unsafe
+ "#]],
+ )
+}
+
+#[test]
+fn in_impl_assoc_item_list_after_attr() {
+ check(
+ r#"impl Struct { #[attr] $0 }"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ kw unsafe
+ "#]],
+ )
+}
+
+#[test]
+fn in_trait_assoc_item_list() {
+ check(
+ r"trait Foo { $0 }",
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ kw const
+ kw crate::
+ kw fn
+ kw self::
+ kw type
+ kw unsafe
+ "#]],
+ );
+}
+
+#[test]
+fn in_trait_impl_assoc_item_list() {
+ check(
+ r#"
+trait Test {
+ type Type0;
+ type Type1;
+ const CONST0: ();
+ const CONST1: ();
+ fn function0();
+ fn function1();
+}
+
+impl Test for () {
+ type Type0 = ();
+ const CONST0: () = ();
+ fn function0() {}
+ $0
+}
+"#,
+ expect![[r#"
+ ct const CONST1: () =
+ fn fn function1()
+ ma makro!(…) macro_rules! makro
+ md module
+ ta type Type1 =
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs
new file mode 100644
index 000000000..30ddbe2dc
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs
@@ -0,0 +1,716 @@
+//! Completion tests for pattern position.
+use expect_test::{expect, Expect};
+
+use crate::tests::{check_edit, completion_list, BASE_ITEMS_FIXTURE};
+
+fn check_empty(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}\n{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn wildcard() {
+ check(
+ r#"
+fn quux() {
+ let _$0
+}
+"#,
+ expect![""],
+ );
+}
+
+#[test]
+fn ident_rebind_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let en$0 @ x
+}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn ident_ref_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let ref en$0
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+ check_empty(
+ r#"
+fn quux() {
+ let ref en$0 @ x
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+}
+
+#[test]
+fn ident_ref_mut_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let ref mut en$0
+}
+"#,
+ expect![[r#""#]],
+ );
+ check_empty(
+ r#"
+fn quux() {
+ let ref mut en$0 @ x
+}
+"#,
+ expect![[r#""#]],
+ );
+}
+
+#[test]
+fn ref_pat() {
+ check_empty(
+ r#"
+fn quux() {
+ let &en$0
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+ check_empty(
+ r#"
+fn quux() {
+ let &mut en$0
+}
+"#,
+ expect![[r#""#]],
+ );
+ check_empty(
+ r#"
+fn foo() {
+ for &$0 in () {}
+}
+"#,
+ expect![[r#"
+ kw mut
+ "#]],
+ );
+}
+
+#[test]
+fn refutable() {
+ check(
+ r#"
+fn foo() {
+ if let a$0
+}
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ ev TupleV
+ bn Record {…} Record { field$1 }$0
+ bn Tuple(…) Tuple($1)$0
+ bn TupleV(…) TupleV($1)$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn irrefutable() {
+ check(
+ r#"
+enum SingleVariantEnum {
+ Variant
+}
+use SingleVariantEnum::Variant;
+fn foo() {
+ let a$0
+}
+"#,
+ expect![[r#"
+ en SingleVariantEnum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ ev Variant
+ bn Record {…} Record { field$1 }$0
+ bn Tuple(…) Tuple($1)$0
+ bn Variant Variant$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn in_param() {
+ check(
+ r#"
+fn foo(a$0) {
+}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ bn Record {…} Record { field$1 }: Record$0
+ bn Tuple(…) Tuple($1): Tuple$0
+ kw mut
+ kw ref
+ "#]],
+ );
+ check(
+ r#"
+fn foo(a$0: Tuple) {
+}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ bn Record {…} Record { field$1 }$0
+ bn Tuple(…) Tuple($1)$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn only_fn_like_macros() {
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+
+#[rustc_builtin_macro]
+macro Clone {}
+
+fn foo() {
+ let x$0
+}
+"#,
+ expect![[r#"
+ ma m!(…) macro_rules! m
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn in_simple_macro_call() {
+ check_empty(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+enum E { X }
+
+fn foo() {
+ m!(match E::X { a$0 })
+}
+"#,
+ expect![[r#"
+ en E
+ ma m!(…) macro_rules! m
+ bn E::X E::X$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn omits_private_fields_pat() {
+ check_empty(
+ r#"
+mod foo {
+ pub struct Record { pub field: i32, _field: i32 }
+ pub struct Tuple(pub u32, u32);
+ pub struct Invisible(u32, u32);
+}
+use foo::*;
+
+fn outer() {
+ if let a$0
+}
+"#,
+ expect![[r#"
+ md foo
+ st Invisible
+ st Record
+ st Tuple
+ bn Record {…} Record { field$1, .. }$0
+ bn Tuple(…) Tuple($1, ..)$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_self_pats() {
+ check_empty(
+ r#"
+struct Foo(i32);
+impl Foo {
+ fn foo() {
+ match Foo(0) {
+ a$0
+ }
+ }
+}
+ "#,
+ expect![[r#"
+ sp Self
+ st Foo
+ bn Foo(…) Foo($1)$0
+ bn Self(…) Self($1)$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn enum_qualified() {
+ check(
+ r#"
+impl Enum {
+ type AssocType = ();
+ const ASSOC_CONST: () = ();
+ fn assoc_fn() {}
+}
+fn func() {
+ if let Enum::$0 = unknown {}
+}
+"#,
+ expect![[r#"
+ ct ASSOC_CONST const ASSOC_CONST: ()
+ bn RecordV {…} RecordV { field$1 }$0
+ bn TupleV(…) TupleV($1)$0
+ bn UnitV UnitV$0
+ "#]],
+ );
+}
+
+#[test]
+fn completes_in_record_field_pat() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn outer(Foo { bar: $0 }: Foo) {}
+"#,
+ expect![[r#"
+ st Bar
+ st Foo
+ bn Bar(…) Bar($1)$0
+ bn Foo {…} Foo { bar$1 }$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn skips_in_record_field_pat_name() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn outer(Foo { bar$0 }: Foo) {}
+"#,
+ expect![[r#"
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_in_fn_param() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn foo($0) {}
+"#,
+ expect![[r#"
+ st Bar
+ st Foo
+ bn Bar(…) Bar($1): Bar$0
+ bn Foo {…} Foo { bar$1 }: Foo$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_in_closure_param() {
+ check_empty(
+ r#"
+struct Foo { bar: Bar }
+struct Bar(u32);
+fn foo() {
+ |$0| {};
+}
+"#,
+ expect![[r#"
+ st Bar
+ st Foo
+ bn Bar(…) Bar($1)$0
+ bn Foo {…} Foo { bar$1 }$0
+ kw mut
+ kw ref
+ "#]],
+ )
+}
+
+#[test]
+fn completes_no_delims_if_existing() {
+ check_empty(
+ r#"
+struct Bar(u32);
+fn foo() {
+ match Bar(0) {
+ B$0(b) => {}
+ }
+}
+"#,
+ expect![[r#"
+ st Bar
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Foo { bar: u32 }
+fn foo() {
+ match (Foo { bar: 0 }) {
+ F$0 { bar } => {}
+ }
+}
+"#,
+ expect![[r#"
+ st Foo
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check_empty(
+ r#"
+enum Enum {
+ TupleVariant(u32)
+}
+fn foo() {
+ match Enum::TupleVariant(0) {
+ Enum::T$0(b) => {}
+ }
+}
+"#,
+ expect![[r#"
+ bn TupleVariant TupleVariant
+ "#]],
+ );
+ check_empty(
+ r#"
+enum Enum {
+ RecordVariant { field: u32 }
+}
+fn foo() {
+ match (Enum::RecordVariant { field: 0 }) {
+ Enum::RecordV$0 { field } => {}
+ }
+}
+"#,
+ expect![[r#"
+ bn RecordVariant RecordVariant
+ "#]],
+ );
+}
+
+#[test]
+fn completes_enum_variant_pat() {
+ cov_mark::check!(enum_variant_pattern_path);
+ check_edit(
+ "RecordVariant {…}",
+ r#"
+enum Enum {
+ RecordVariant { field: u32 }
+}
+fn foo() {
+ match (Enum::RecordVariant { field: 0 }) {
+ Enum::RecordV$0
+ }
+}
+"#,
+ r#"
+enum Enum {
+ RecordVariant { field: u32 }
+}
+fn foo() {
+ match (Enum::RecordVariant { field: 0 }) {
+ Enum::RecordVariant { field$1 }$0
+ }
+}
+"#,
+ );
+}
+
+#[test]
+fn completes_enum_variant_pat_escape() {
+ cov_mark::check!(enum_variant_pattern_path);
+ check_empty(
+ r#"
+enum Enum {
+ A,
+ B { r#type: i32 },
+ r#type,
+ r#struct { r#type: i32 },
+}
+fn foo() {
+ match (Enum::A) {
+ $0
+ }
+}
+"#,
+ expect![[r#"
+ en Enum
+ bn Enum::A Enum::A$0
+ bn Enum::B {…} Enum::B { r#type$1 }$0
+ bn Enum::struct {…} Enum::r#struct { r#type$1 }$0
+ bn Enum::type Enum::r#type$0
+ kw mut
+ kw ref
+ "#]],
+ );
+
+ check_empty(
+ r#"
+enum Enum {
+ A,
+ B { r#type: i32 },
+ r#type,
+ r#struct { r#type: i32 },
+}
+fn foo() {
+ match (Enum::A) {
+ Enum::$0
+ }
+}
+"#,
+ expect![[r#"
+ bn A A$0
+ bn B {…} B { r#type$1 }$0
+ bn struct {…} r#struct { r#type$1 }$0
+ bn type r#type$0
+ "#]],
+ );
+}
+
+#[test]
+fn completes_associated_const() {
+ check_empty(
+ r#"
+#[derive(PartialEq, Eq)]
+struct Ty(u8);
+
+impl Ty {
+ const ABC: Self = Self(0);
+}
+
+fn f(t: Ty) {
+ match t {
+ Ty::$0 => {}
+ _ => {}
+ }
+}
+"#,
+ expect![[r#"
+ ct ABC const ABC: Self
+ "#]],
+ );
+
+ check_empty(
+ r#"
+enum MyEnum {}
+
+impl MyEnum {
+ pub const A: i32 = 123;
+ pub const B: i32 = 456;
+}
+
+fn f(e: MyEnum) {
+ match e {
+ MyEnum::$0 => {}
+ _ => {}
+ }
+}
+"#,
+ expect![[r#"
+ ct A pub const A: i32
+ ct B pub const B: i32
+ "#]],
+ );
+
+ check_empty(
+ r#"
+union U {
+ i: i32,
+ f: f32,
+}
+
+impl U {
+ pub const C: i32 = 123;
+ pub const D: i32 = 456;
+}
+
+fn f(u: U) {
+ match u {
+ U::$0 => {}
+ _ => {}
+ }
+}
+"#,
+ expect![[r#"
+ ct C pub const C: i32
+ ct D pub const D: i32
+ "#]],
+ );
+
+ check_empty(
+ r#"
+#[lang = "u32"]
+impl u32 {
+ pub const MIN: Self = 0;
+}
+
+fn f(v: u32) {
+ match v {
+ u32::$0
+ }
+}
+ "#,
+ expect![[r#"
+ ct MIN pub const MIN: Self
+ "#]],
+ );
+}
+
+#[test]
+fn in_method_param() {
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo($0)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn &mut self
+ bn &self
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo(s$0)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn &mut self
+ bn &self
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo(s$0, foo: u8)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn &mut self
+ bn &self
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ bn mut self
+ bn self
+ kw mut
+ kw ref
+ "#]],
+ );
+ check_empty(
+ r#"
+struct Ty(u8);
+
+impl Ty {
+ fn foo(foo: u8, b$0)
+}
+"#,
+ expect![[r#"
+ sp Self
+ st Ty
+ bn Self(…) Self($1): Self$0
+ bn Ty(…) Ty($1): Ty$0
+ kw mut
+ kw ref
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs
new file mode 100644
index 000000000..a8676e2f2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs
@@ -0,0 +1,131 @@
+//! Completion tests for predicates and bounds.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}\n{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn predicate_start() {
+ // FIXME: `for` kw
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where $0 {}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn bound_for_type_pred() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where T: $0 {}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ tt Trait
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn bound_for_lifetime_pred() {
+ // FIXME: should only show lifetimes here, that is we shouldn't get any completions here when not typing
+ // a `'`
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where 'lt: $0 {}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ tt Trait
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn bound_for_for_pred() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where for<'a> T: $0 {}
+"#,
+ expect![[r#"
+ ma makro!(…) macro_rules! makro
+ md module
+ tt Trait
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn param_list_for_for_pred() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> where for<'a> $0 {}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn pred_on_fn_in_impl() {
+ check(
+ r#"
+impl Record {
+ fn method(self) where $0 {}
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs
new file mode 100644
index 000000000..9eae6f849
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs
@@ -0,0 +1,133 @@
+//! Completion tests for expressions.
+use expect_test::{expect, Expect};
+
+use crate::tests::completion_list;
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn complete_dot_in_attr() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::identity]
+fn main() {
+ Foo.$0
+}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr2() {
+ check(
+ r#"
+//- proc_macros: identity
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::identity]
+fn main() {
+ Foo.f$0
+}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr_input() {
+ check(
+ r#"
+//- proc_macros: input_replace
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::input_replace(
+ fn suprise() {
+ Foo.$0
+ }
+)]
+fn main() {}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
+
+#[test]
+fn complete_dot_in_attr_input2() {
+ check(
+ r#"
+//- proc_macros: input_replace
+pub struct Foo;
+impl Foo {
+ fn foo(&self) {}
+}
+
+#[proc_macros::input_replace(
+ fn suprise() {
+ Foo.f$0
+ }
+)]
+fn main() {}
+"#,
+ expect![[r#"
+ me foo() fn(&self)
+ sn box Box::new(expr)
+ sn call function(expr)
+ sn dbg dbg!(expr)
+ sn dbgr dbg!(&expr)
+ sn let let
+ sn letm let mut
+ sn match match expr {}
+ sn ref &expr
+ sn refm &mut expr
+ "#]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs
new file mode 100644
index 000000000..f6accc68e
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs
@@ -0,0 +1,229 @@
+use expect_test::{expect, Expect};
+
+use crate::tests::completion_list;
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual);
+}
+
+#[test]
+fn without_default_impl() {
+ check(
+ r#"
+struct Struct { foo: u32, bar: usize }
+
+fn foo() {
+ let other = Struct {
+ foo: 5,
+ $0
+ };
+}
+"#,
+ expect![[r#"
+ fd bar usize
+ "#]],
+ );
+}
+
+#[test]
+fn record_pattern_field() {
+ check(
+ r#"
+struct Struct { foo: u32, bar: u32 }
+
+fn foo(s: Struct) {
+ match s {
+ Struct { foo, $0: 92 } => (),
+ }
+}
+"#,
+ expect![[r#"
+ fd bar u32
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn pattern_enum_variant() {
+ check(
+ r#"
+enum Enum { Variant { foo: u32, bar: u32 } }
+fn foo(e: Enum) {
+ match e {
+ Enum::Variant { foo, $0 } => (),
+ }
+}
+"#,
+ expect![[r#"
+ fd bar u32
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn record_literal_field_in_macro() {
+ check(
+ r#"
+macro_rules! m { ($e:expr) => { $e } }
+struct Struct { field: u32 }
+fn foo() {
+ m!(Struct { fie$0 })
+}
+"#,
+ expect![[r#"
+ fd field u32
+ "#]],
+ );
+}
+
+#[test]
+fn record_pattern_field_in_macro() {
+ check(
+ r"
+macro_rules! m { ($e:expr) => { $e } }
+struct Struct { field: u32 }
+
+fn foo(f: Struct) {
+ m!(match f {
+ Struct { f$0: 92 } => (),
+ })
+}
+",
+ expect![[r#"
+ fd field u32
+ kw mut
+ kw ref
+ "#]],
+ );
+}
+
+#[test]
+fn functional_update() {
+ // FIXME: This should filter out all completions that do not have the type `Foo`
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, $0 }
+}
+"#,
+ expect![[r#"
+ fd ..Default::default()
+ fd foo1 u32
+ fd foo2 u32
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, .$0 }
+}
+"#,
+ expect![[r#"
+ fd ..Default::default()
+ sn ..
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, ..$0 }
+}
+"#,
+ expect![[r#"
+ fd ..Default::default()
+ fn main() fn()
+ lc foo Foo
+ lc thing i32
+ md core
+ st Foo
+ st Foo {…} Foo { foo1: u32, foo2: u32 }
+ tt Default
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"
+//- minicore:default
+struct Foo { foo1: u32, foo2: u32 }
+impl Default for Foo {
+ fn default() -> Self { loop {} }
+}
+
+fn main() {
+ let thing = 1;
+ let foo = Foo { foo1: 0, foo2: 0 };
+ let foo2 = Foo { thing, ..Default::$0 }
+}
+"#,
+ expect![[r#"
+ fn default() (as Default) fn() -> Self
+ "#]],
+ );
+}
+
+#[test]
+fn empty_union_literal() {
+ check(
+ r#"
+union Union { foo: u32, bar: f32 }
+
+fn foo() {
+ let other = Union {
+ $0
+ };
+}
+ "#,
+ expect![[r#"
+ fd bar f32
+ fd foo u32
+ "#]],
+ )
+}
+
+#[test]
+fn dont_suggest_additional_union_fields() {
+ check(
+ r#"
+union Union { foo: u32, bar: f32 }
+
+fn foo() {
+ let other = Union {
+ foo: 1,
+ $0
+ };
+}
+ "#,
+ expect![[r#""#]],
+ )
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
new file mode 100644
index 000000000..033dc99c2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs
@@ -0,0 +1,895 @@
+//! Tests that don't fit into a specific category.
+
+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 completes_if_prefix_is_keyword() {
+ check_edit(
+ "wherewolf",
+ r#"
+fn main() {
+ let wherewolf = 92;
+ drop(where$0)
+}
+"#,
+ r#"
+fn main() {
+ let wherewolf = 92;
+ drop(wherewolf)
+}
+"#,
+ )
+}
+
+/// Regression test for issue #6091.
+#[test]
+fn correctly_completes_module_items_prefixed_with_underscore() {
+ check_edit(
+ "_alpha",
+ r#"
+fn main() {
+ _$0
+}
+fn _alpha() {}
+"#,
+ r#"
+fn main() {
+ _alpha()$0
+}
+fn _alpha() {}
+"#,
+ )
+}
+
+#[test]
+fn completes_prelude() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn foo() { let x: $0 }
+
+//- /std/lib.rs crate:std
+pub mod prelude {
+ pub mod rust_2018 {
+ pub struct Option;
+ }
+}
+"#,
+ expect![[r#"
+ md std
+ st Option
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn completes_prelude_macros() {
+ check(
+ r#"
+//- /main.rs crate:main deps:std
+fn f() {$0}
+
+//- /std/lib.rs crate:std
+pub mod prelude {
+ pub mod rust_2018 {
+ pub use crate::concat;
+ }
+}
+
+mod macros {
+ #[rustc_builtin_macro]
+ #[macro_export]
+ macro_rules! concat { }
+}
+"#,
+ expect![[r#"
+ fn f() fn()
+ ma concat!(…) macro_rules! concat
+ md std
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn completes_std_prelude_if_core_is_defined() {
+ check(
+ r#"
+//- /main.rs crate:main deps:core,std
+fn foo() { let x: $0 }
+
+//- /core/lib.rs crate:core
+pub mod prelude {
+ pub mod rust_2018 {
+ pub struct Option;
+ }
+}
+
+//- /std/lib.rs crate:std deps:core
+pub mod prelude {
+ pub mod rust_2018 {
+ pub struct String;
+ }
+}
+"#,
+ expect![[r#"
+ md core
+ md std
+ st String
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn respects_doc_hidden() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:std
+fn f() {
+ format_$0
+}
+
+//- /std.rs crate:std
+#[doc(hidden)]
+#[macro_export]
+macro_rules! format_args_nl {
+ () => {}
+}
+
+pub mod prelude {
+ pub mod rust_2018 {}
+}
+ "#,
+ expect![[r#"
+ fn f() fn()
+ md std
+ bt u32
+ "#]],
+ );
+}
+
+#[test]
+fn respects_doc_hidden_in_assoc_item_list() {
+ check(
+ r#"
+//- /lib.rs crate:lib deps:std
+struct S;
+impl S {
+ format_$0
+}
+
+//- /std.rs crate:std
+#[doc(hidden)]
+#[macro_export]
+macro_rules! format_args_nl {
+ () => {}
+}
+
+pub mod prelude {
+ pub mod rust_2018 {}
+}
+ "#,
+ expect![[r#"
+ md std
+ "#]],
+ );
+}
+
+#[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#"
+ ct PUBLIC_CONST pub const PUBLIC_CONST: u32
+ fn public_method() fn()
+ 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#"
+ ct C2 (as Sub) const C2: ()
+ ct CONST (as Super) const CONST: u8
+ fn func() (as Super) fn()
+ fn subfunc() (as Sub) fn()
+ ta SubTy (as Sub) type SubTy
+ ta Ty (as Super) type Ty
+ me method(…) (as Super) fn(&self)
+ me submethod(…) (as Sub) 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#"
+ ct C2 (as Sub) const C2: ()
+ ct CONST (as Super) const CONST: u8
+ fn func() (as Super) fn()
+ fn subfunc() (as Sub) fn()
+ ta SubTy (as Sub) type SubTy
+ ta Ty (as Super) type Ty
+ me method(…) (as Super) fn(&self)
+ 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 bar() fn()
+ fn foo() 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 {
+ pub fn wrong_fn() {}
+ pub const WRONG_CONST: u32 = 1;
+ pub 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 {
+ pub fn wrong_fn() {}
+ pub const WRONG_CONST: u32 = 1;
+ pub 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 {
+ pub fn wrong_fn() {}
+ pub const WRONG_CONST: u32 = 1;
+ pub 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 foo() fn()
+ fn main() fn()
+ "#]],
+ );
+}
+
+#[test]
+fn function_mod_share_name() {
+ check(
+ r#"
+fn foo() { self::m::$0 }
+
+mod m {
+ pub mod z {}
+ pub fn z() {}
+}
+"#,
+ expect![[r#"
+ fn z() fn()
+ md z
+ "#]],
+ );
+}
+
+#[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() {
+ cov_mark::check!(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_non_exhaustive_variant_within_the_defining_crate() {
+ check(
+ r#"
+enum Foo {
+ #[non_exhaustive]
+ Bar,
+ Baz,
+}
+
+fn foo(self) {
+ Foo::$0
+}
+"#,
+ expect![[r#"
+ ev Bar Bar
+ ev Baz Baz
+ "#]],
+ );
+
+ check(
+ r#"
+//- /main.rs crate:main deps:e
+fn foo(self) {
+ e::Foo::$0
+}
+
+//- /e.rs crate:e
+enum Foo {
+ #[non_exhaustive]
+ Bar,
+ Baz,
+}
+"#,
+ expect![[r#"
+ ev Baz Baz
+ "#]],
+ );
+}
+
+#[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_hidden2() {
+ 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() {
+ 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
+ "#]],
+ );
+}
+
+#[test]
+fn type_anchor_type() {
+ check(
+ r#"
+trait Foo {
+ fn foo() -> Self;
+}
+struct Bar;
+impl Bar {
+ fn bar() {}
+}
+impl Foo for Bar {
+ fn foo() -> {
+ Bar
+ }
+}
+fn bar() -> Bar {
+ <Bar>::$0
+}
+"#,
+ expect![[r#"
+ fn bar() fn()
+ fn foo() (as Foo) fn() -> Self
+ "#]],
+ );
+}
+
+#[test]
+fn type_anchor_type_trait() {
+ check(
+ r#"
+trait Foo {
+ fn foo() -> Self;
+}
+struct Bar;
+impl Bar {
+ fn bar() {}
+}
+impl Foo for Bar {
+ fn foo() -> {
+ Bar
+ }
+}
+fn bar() -> Bar {
+ <Bar as Foo>::$0
+}
+"#,
+ expect![[r#"
+ fn foo() (as Foo) fn() -> Self
+ "#]],
+ );
+}
+
+#[test]
+fn completes_fn_in_pub_trait_generated_by_macro() {
+ check(
+ r#"
+mod other_mod {
+ macro_rules! make_method {
+ ($name:ident) => {
+ fn $name(&self) {}
+ };
+ }
+
+ pub trait MyTrait {
+ make_method! { by_macro }
+ fn not_by_macro(&self) {}
+ }
+
+ pub struct Foo {}
+
+ impl MyTrait for Foo {}
+}
+
+fn main() {
+ use other_mod::{Foo, MyTrait};
+ let f = Foo {};
+ f.$0
+}
+"#,
+ expect![[r#"
+ me by_macro() (as MyTrait) fn(&self)
+ me not_by_macro() (as MyTrait) fn(&self)
+ "#]],
+ )
+}
+
+#[test]
+fn completes_fn_in_pub_trait_generated_by_recursive_macro() {
+ check(
+ r#"
+mod other_mod {
+ macro_rules! make_method {
+ ($name:ident) => {
+ fn $name(&self) {}
+ };
+ }
+
+ macro_rules! make_trait {
+ () => {
+ pub trait MyTrait {
+ make_method! { by_macro }
+ fn not_by_macro(&self) {}
+ }
+ }
+ }
+
+ make_trait!();
+
+ pub struct Foo {}
+
+ impl MyTrait for Foo {}
+}
+
+fn main() {
+ use other_mod::{Foo, MyTrait};
+ let f = Foo {};
+ f.$0
+}
+"#,
+ expect![[r#"
+ me by_macro() (as MyTrait) fn(&self)
+ me not_by_macro() (as MyTrait) fn(&self)
+ "#]],
+ )
+}
+
+#[test]
+fn completes_const_in_pub_trait_generated_by_macro() {
+ check(
+ r#"
+mod other_mod {
+ macro_rules! make_const {
+ ($name:ident) => {
+ const $name: u8 = 1;
+ };
+ }
+
+ pub trait MyTrait {
+ make_const! { by_macro }
+ }
+
+ pub struct Foo {}
+
+ impl MyTrait for Foo {}
+}
+
+fn main() {
+ use other_mod::{Foo, MyTrait};
+ let f = Foo {};
+ Foo::$0
+}
+"#,
+ expect![[r#"
+ ct by_macro (as MyTrait) pub const by_macro: u8
+ "#]],
+ )
+}
+
+#[test]
+fn completes_locals_from_macros() {
+ check(
+ r#"
+
+macro_rules! x {
+ ($x:ident, $expr:expr) => {
+ let $x = 0;
+ $expr
+ };
+}
+fn main() {
+ x! {
+ foobar, {
+ f$0
+ }
+ };
+}
+"#,
+ expect![[r#"
+ fn main() fn()
+ lc foobar i32
+ ma x!(…) macro_rules! x
+ bt u32
+ "#]],
+ )
+}
+
+#[test]
+fn regression_12644() {
+ check(
+ r#"
+macro_rules! __rust_force_expr {
+ ($e:expr) => {
+ $e
+ };
+}
+macro_rules! vec {
+ ($elem:expr) => {
+ __rust_force_expr!($elem)
+ };
+}
+
+struct Struct;
+impl Struct {
+ fn foo(self) {}
+}
+
+fn f() {
+ vec![Struct].$0;
+}
+"#,
+ expect![[r#"
+ me foo() fn(self)
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs
new file mode 100644
index 000000000..f0b7726c5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs
@@ -0,0 +1,671 @@
+//! Completion tests for type position.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, BASE_ITEMS_FIXTURE};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(&format!("{}\n{}", BASE_ITEMS_FIXTURE, ra_fixture));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn record_field_ty() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize> {
+ f: $0
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn tuple_struct_field() {
+ check(
+ r#"
+struct Foo<'lt, T, const C: usize>(f$0);
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw pub
+ kw pub(crate)
+ kw pub(super)
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn fn_return_type() {
+ check(
+ r#"
+fn x<'lt, T, const C: usize>() -> $0
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn fn_return_type_no_local_items() {
+ check(
+ r#"
+fn foo() -> B$0 {
+ struct Bar;
+ enum Baz {}
+ union Bax {
+ i: i32,
+ f: f32
+ }
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it ()
+ kw crate::
+ kw self::
+ "#]],
+ )
+}
+
+#[test]
+fn inferred_type_const() {
+ check(
+ r#"
+struct Foo<T>(T);
+const FOO: $0 = Foo(2);
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it Foo<i32>
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_closure_param() {
+ check(
+ r#"
+fn f1(f: fn(i32) -> i32) {}
+fn f2() {
+ f1(|x: $0);
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it i32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_closure_return() {
+ check(
+ r#"
+fn f1(f: fn(u64) -> u64) {}
+fn f2() {
+ f1(|x| -> $0 {
+ x + 5
+ });
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it u64
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_fn_return() {
+ check(
+ r#"
+fn f2(x: u64) -> $0 {
+ x + 5
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it u64
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_fn_param() {
+ check(
+ r#"
+fn f1(x: i32) {}
+fn f2(x: $0) {
+ f1(x);
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ bt u32
+ it i32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_not_in_the_scope() {
+ check(
+ r#"
+mod a {
+ pub struct Foo<T>(T);
+ pub fn x() -> Foo<Foo<i32>> {
+ Foo(Foo(2))
+ }
+}
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let foo: $0 = a::x();
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md a
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ it a::Foo<a::Foo<i32>>
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn inferred_type_let() {
+ check(
+ r#"
+struct Foo<T>(T);
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let foo: $0 = Foo(2);
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Foo<…>
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ it Foo<i32>
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn body_type_pos() {
+ check(
+ r#"
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let _: $0;
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"
+fn foo<'lt, T, const C: usize>() {
+ let local = ();
+ let _: self::$0;
+}
+"#,
+ expect![[r#"
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ un Union
+ "#]],
+ );
+}
+
+#[test]
+fn completes_types_and_const_in_arg_list() {
+ cov_mark::check!(complete_assoc_type_in_generics_list);
+ check(
+ r#"
+trait Trait1 {
+ type Super;
+}
+trait Trait2: Trait1 {
+ type Foo;
+}
+
+fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
+"#,
+ expect![[r#"
+ ta Foo = (as Trait2) type Foo
+ ta Super = (as Trait1) type Super
+ "#]],
+ );
+ check(
+ r#"
+trait Trait1 {
+ type Super;
+}
+trait Trait2<T>: Trait1 {
+ type Foo;
+}
+
+fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
+"#,
+ expect![[r#"
+ ct CONST
+ cp CONST_PARAM
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tt Trait1
+ tt Trait2
+ tp T
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+ check(
+ r#"
+trait Trait2 {
+ type Foo;
+}
+
+fn foo<'lt, T: Trait2<self::$0>, const CONST_PARAM: usize>(_: T) {}
+ "#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt Trait
+ tt Trait2
+ un Union
+ "#]],
+ );
+}
+
+#[test]
+fn no_assoc_completion_outside_type_bounds() {
+ check(
+ r#"
+struct S;
+trait Tr<T> {
+ type Ty;
+}
+
+impl Tr<$0
+ "#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ sp Self
+ st Record
+ st S
+ st Tuple
+ st Unit
+ tt Tr
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn enum_qualified() {
+ check(
+ r#"
+impl Enum {
+ type AssocType = ();
+ const ASSOC_CONST: () = ();
+ fn assoc_fn() {}
+}
+fn func(_: Enum::$0) {}
+"#,
+ expect![[r#"
+ ta AssocType type AssocType = ()
+ "#]],
+ );
+}
+
+#[test]
+fn completes_type_parameter_or_associated_type() {
+ check(
+ r#"
+trait MyTrait<T, U> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u8, I$0
+"#,
+ expect![[r#"
+ ta Item1 = (as MyTrait) type Item1
+ ta Item2 = (as MyTrait) type Item2
+ "#]],
+ );
+}
+
+#[test]
+fn completes_type_parameter_or_associated_type_with_default_value() {
+ check(
+ r#"
+trait MyTrait<T, U = u8> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U = u8> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u$0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ ta Item1 = (as MyTrait) type Item1
+ ta Item2 = (as MyTrait) type Item2
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait<T, U = u8> {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<u8, u8, I$0
+"#,
+ expect![[r#"
+ ta Item1 = (as MyTrait) type Item1
+ ta Item2 = (as MyTrait) type Item2
+ "#]],
+ );
+}
+
+#[test]
+fn completes_types_after_associated_type() {
+ check(
+ r#"
+trait MyTrait {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<Item1 = $0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+
+ check(
+ r#"
+trait MyTrait {
+ type Item1;
+ type Item2;
+};
+
+fn f(t: impl MyTrait<Item1 = u8, Item2 = $0
+"#,
+ expect![[r#"
+ ct CONST
+ en Enum
+ ma makro!(…) macro_rules! makro
+ md module
+ st Record
+ st Tuple
+ st Unit
+ tt MyTrait
+ tt Trait
+ un Union
+ bt u32
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs
new file mode 100644
index 000000000..037d7dce5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs
@@ -0,0 +1,384 @@
+//! Completion tests for use trees.
+use expect_test::{expect, Expect};
+
+use crate::tests::completion_list;
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn use_tree_start() {
+ cov_mark::check!(unqualified_path_selected_only);
+ check(
+ r#"
+//- /lib.rs crate:main deps:other_crate
+use f$0
+
+struct Foo;
+enum FooBar {
+ Foo,
+ Bar
+}
+mod foo {}
+//- /other_crate/lib.rs crate:other_crate
+// nothing here
+"#,
+ expect![[r#"
+ en FooBar::
+ md foo
+ md other_crate
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn use_tree_start_abs() {
+ cov_mark::check!(use_tree_crate_roots_only);
+ check(
+ r#"
+//- /lib.rs crate:main deps:other_crate
+use ::f$0
+
+struct Foo;
+mod foo {}
+//- /other_crate/lib.rs crate:other_crate
+// nothing here
+"#,
+ expect![[r#"
+ md other_crate
+ "#]],
+ );
+}
+
+#[test]
+fn dont_complete_current_use() {
+ cov_mark::check!(dont_complete_current_use);
+ check(r#"use self::foo$0;"#, expect![[r#""#]]);
+ check(
+ r#"
+mod foo { pub struct S; }
+use self::{foo::*, bar$0};
+"#,
+ expect![[r#"
+ md foo
+ st S
+ "#]],
+ );
+}
+
+#[test]
+fn nested_use_tree() {
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct FooBar;
+ }
+}
+use foo::{bar::$0}
+"#,
+ expect![[r#"
+ st FooBar
+ "#]],
+ );
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct FooBar;
+ }
+}
+use foo::{$0}
+"#,
+ expect![[r#"
+ md bar
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn deeply_nested_use_tree() {
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct FooBarBaz;
+ }
+ }
+}
+use foo::{bar::{baz::$0}}
+"#,
+ expect![[r#"
+ st FooBarBaz
+ "#]],
+ );
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct FooBarBaz;
+ }
+ }
+}
+use foo::{bar::{$0}}
+"#,
+ expect![[r#"
+ md baz
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn plain_qualified_use_tree() {
+ check(
+ r#"
+use foo::$0
+
+mod foo {
+ struct Private;
+ pub struct Foo;
+ macro_rules! foo_ { {} => {} }
+ pub use foo_ as foo;
+}
+struct Bar;
+"#,
+ expect![[r#"
+ ma foo macro_rules! foo_
+ st Foo
+ "#]],
+ );
+}
+
+#[test]
+fn enum_plain_qualified_use_tree() {
+ cov_mark::check!(enum_plain_qualified_use_tree);
+ check(
+ r#"
+use Foo::$0
+
+enum Foo {
+ UnitVariant,
+ TupleVariant(),
+ RecordVariant {},
+}
+impl Foo {
+ const CONST: () = ()
+ fn func() {}
+}
+"#,
+ expect![[r#"
+ ev RecordVariant RecordVariant
+ ev TupleVariant TupleVariant
+ ev UnitVariant UnitVariant
+ "#]],
+ );
+}
+
+#[test]
+fn self_qualified_use_tree() {
+ check(
+ r#"
+use self::$0
+
+mod foo {}
+struct Bar;
+"#,
+ expect![[r#"
+ md foo
+ st Bar
+ "#]],
+ );
+}
+
+#[test]
+fn super_qualified_use_tree() {
+ check(
+ r#"
+mod bar {
+ use super::$0
+}
+
+mod foo {}
+struct Bar;
+"#,
+ expect![[r#"
+ md bar
+ md foo
+ st Bar
+ "#]],
+ );
+}
+
+#[test]
+fn super_super_qualified_use_tree() {
+ check(
+ r#"
+mod a {
+ const A: usize = 0;
+ mod b {
+ const B: usize = 0;
+ mod c { use super::super::$0 }
+ }
+}
+"#,
+ expect![[r#"
+ ct A
+ md b
+ kw super::
+ "#]],
+ );
+}
+
+#[test]
+fn crate_qualified_use_tree() {
+ check(
+ r#"
+use crate::$0
+
+mod foo {}
+struct Bar;
+"#,
+ expect![[r#"
+ md foo
+ st Bar
+ "#]],
+ );
+}
+
+#[test]
+fn extern_crate_qualified_use_tree() {
+ check(
+ r#"
+//- /lib.rs crate:main deps:other_crate
+use other_crate::$0
+//- /other_crate/lib.rs crate:other_crate
+pub struct Foo;
+pub mod foo {}
+"#,
+ expect![[r#"
+ md foo
+ st Foo
+ "#]],
+ );
+}
+
+#[test]
+fn pub_use_tree() {
+ check(
+ r#"
+pub struct X;
+pub mod bar {}
+pub use $0;
+"#,
+ expect![[r#"
+ md bar
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn pub_suggest_use_tree_super_acc_to_depth_in_tree() {
+ // https://github.com/rust-lang/rust-analyzer/issues/12439
+ // Check discussion in https://github.com/rust-lang/rust-analyzer/pull/12447
+
+ check(
+ r#"
+mod foo {
+ mod bar {
+ pub use super::$0;
+ }
+}
+"#,
+ expect![[r#"
+ md bar
+ kw super::
+ "#]],
+ );
+
+ // Not suggest super when at crate root
+ check(
+ r#"
+mod foo {
+ mod bar {
+ pub use super::super::$0;
+ }
+}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ use $0;
+}
+"#,
+ expect![[r#"
+ kw crate::
+ kw self::
+ kw super::
+ "#]],
+ );
+
+ // Not suggest super after another kw in path ( here it is foo1 )
+ check(
+ r#"
+mod foo {
+ mod bar {
+ use super::super::foo1::$0;
+ }
+}
+
+mod foo1 {
+ pub mod bar1 {}
+}
+"#,
+ expect![[r#"
+ md bar1
+ "#]],
+ );
+}
+
+#[test]
+fn use_tree_braces_at_start() {
+ check(
+ r#"
+struct X;
+mod bar {}
+use {$0};
+"#,
+ expect![[r#"
+ md bar
+ kw crate::
+ kw self::
+ "#]],
+ );
+}
+
+#[test]
+fn impl_prefix_does_not_add_fn_snippet() {
+ // regression test for 7222
+ check(
+ r#"
+mod foo {
+ pub fn bar(x: u32) {}
+}
+use self::foo::impl$0
+"#,
+ expect![[r#"
+ fn bar fn(u32)
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs
new file mode 100644
index 000000000..c18d6e66d
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs
@@ -0,0 +1,90 @@
+//! Completion tests for visibility modifiers.
+use expect_test::{expect, Expect};
+
+use crate::tests::{completion_list, completion_list_with_trigger_character};
+
+fn check(ra_fixture: &str, expect: Expect) {
+ let actual = completion_list(ra_fixture);
+ expect.assert_eq(&actual)
+}
+
+fn check_with_trigger_character(ra_fixture: &str, trigger_character: char, expect: Expect) {
+ let actual = completion_list_with_trigger_character(ra_fixture, Some(trigger_character));
+ expect.assert_eq(&actual)
+}
+
+#[test]
+fn empty_pub() {
+ cov_mark::check!(kw_completion_in);
+ check_with_trigger_character(
+ r#"
+pub($0)
+"#,
+ '(',
+ expect![[r#"
+ kw crate
+ kw in
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn after_in_kw() {
+ check(
+ r#"
+pub(in $0)
+"#,
+ expect![[r#"
+ kw crate
+ kw self
+ "#]],
+ );
+}
+
+#[test]
+fn qualified() {
+ cov_mark::check!(visibility_qualified);
+ check(
+ r#"
+mod foo {
+ pub(in crate::$0)
+}
+
+mod bar {}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+ check(
+ r#"
+mod qux {
+ mod foo {
+ pub(in crate::$0)
+ }
+ mod baz {}
+}
+
+mod bar {}
+"#,
+ expect![[r#"
+ md qux
+ "#]],
+ );
+ check(
+ r#"
+mod qux {
+ mod foo {
+ pub(in crate::qux::$0)
+ }
+ mod baz {}
+}
+
+mod bar {}
+"#,
+ expect![[r#"
+ md foo
+ "#]],
+ );
+}