From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- .../rust-analyzer/crates/ide-completion/Cargo.toml | 33 + .../crates/ide-completion/src/completions.rs | 691 +++++++ .../ide-completion/src/completions/attribute.rs | 380 ++++ .../src/completions/attribute/cfg.rs | 93 + .../src/completions/attribute/derive.rs | 116 ++ .../src/completions/attribute/lint.rs | 61 + .../src/completions/attribute/repr.rs | 74 + .../crates/ide-completion/src/completions/dot.rs | 947 ++++++++++ .../crates/ide-completion/src/completions/expr.rs | 280 +++ .../ide-completion/src/completions/extern_abi.rs | 108 ++ .../crates/ide-completion/src/completions/field.rs | 43 + .../ide-completion/src/completions/flyimport.rs | 407 +++++ .../ide-completion/src/completions/fn_param.rs | 196 ++ .../src/completions/format_string.rs | 130 ++ .../ide-completion/src/completions/item_list.rs | 133 ++ .../src/completions/item_list/trait_impl.rs | 1160 ++++++++++++ .../ide-completion/src/completions/keyword.rs | 237 +++ .../ide-completion/src/completions/lifetime.rs | 341 ++++ .../crates/ide-completion/src/completions/mod_.rs | 354 ++++ .../ide-completion/src/completions/pattern.rs | 185 ++ .../ide-completion/src/completions/postfix.rs | 616 +++++++ .../src/completions/postfix/format_like.rs | 311 ++++ .../ide-completion/src/completions/record.rs | 369 ++++ .../ide-completion/src/completions/snippet.rs | 189 ++ .../crates/ide-completion/src/completions/type.rs | 246 +++ .../crates/ide-completion/src/completions/use_.rs | 120 ++ .../crates/ide-completion/src/completions/vis.rs | 41 + .../crates/ide-completion/src/config.rs | 41 + .../crates/ide-completion/src/context.rs | 639 +++++++ .../crates/ide-completion/src/context/analysis.rs | 1293 +++++++++++++ .../crates/ide-completion/src/context/tests.rs | 413 +++++ .../crates/ide-completion/src/item.rs | 637 +++++++ .../rust-analyzer/crates/ide-completion/src/lib.rs | 247 +++ .../crates/ide-completion/src/render.rs | 1910 ++++++++++++++++++++ .../crates/ide-completion/src/render/const_.rs | 33 + .../crates/ide-completion/src/render/function.rs | 671 +++++++ .../crates/ide-completion/src/render/literal.rs | 191 ++ .../crates/ide-completion/src/render/macro_.rs | 270 +++ .../crates/ide-completion/src/render/pattern.rs | 193 ++ .../crates/ide-completion/src/render/type_alias.rs | 57 + .../ide-completion/src/render/union_literal.rs | 77 + .../crates/ide-completion/src/render/variant.rs | 96 + .../crates/ide-completion/src/snippet.rs | 214 +++ .../crates/ide-completion/src/tests.rs | 305 ++++ .../crates/ide-completion/src/tests/attribute.rs | 1016 +++++++++++ .../crates/ide-completion/src/tests/expression.rs | 672 +++++++ .../crates/ide-completion/src/tests/flyimport.rs | 1232 +++++++++++++ .../crates/ide-completion/src/tests/fn_param.rs | 274 +++ .../crates/ide-completion/src/tests/item.rs | 154 ++ .../crates/ide-completion/src/tests/item_list.rs | 247 +++ .../crates/ide-completion/src/tests/pattern.rs | 716 ++++++++ .../crates/ide-completion/src/tests/predicate.rs | 131 ++ .../crates/ide-completion/src/tests/proc_macros.rs | 133 ++ .../crates/ide-completion/src/tests/record.rs | 229 +++ .../crates/ide-completion/src/tests/special.rs | 895 +++++++++ .../crates/ide-completion/src/tests/type_pos.rs | 671 +++++++ .../crates/ide-completion/src/tests/use_tree.rs | 384 ++++ .../crates/ide-completion/src/tests/visibility.rs | 90 + 58 files changed, 21992 insertions(+) create mode 100644 src/tools/rust-analyzer/crates/ide-completion/Cargo.toml create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/cfg.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/derive.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/lint.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/attribute/repr.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/expr.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/extern_abi.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/field.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/fn_param.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/format_string.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/keyword.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/lifetime.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/pattern.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix/format_like.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/record.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/snippet.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/use_.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/completions/vis.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/config.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/context.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/item.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/lib.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/const_.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/literal.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/type_alias.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/union_literal.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/render/variant.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/snippet.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/expression.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/flyimport.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/fn_param.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/item.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/predicate.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/proc_macros.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/type_pos.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/use_tree.rs create mode 100644 src/tools/rust-analyzer/crates/ide-completion/src/tests/visibility.rs (limited to 'src/tools/rust-analyzer/crates/ide-completion') 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, +} + +impl From for Vec { + 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) { + if let Some(item) = item { + self.buf.push(item) + } + } + + pub(crate) fn add_all(&mut self, items: I) + where + I: IntoIterator, + I::Item: Into, + { + 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, + ) { + 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, + ) { + 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, + local_name: Option, + ) { + 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, + ) { + 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, + 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, + local_name: Option, + ) { + 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, + local_name: Option, + ) { + 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, + 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, + ) { + 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, + ) { + 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, + 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 = 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> = 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!(); + +/// +// 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> { + 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::>(), + ) +} + +#[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 {} +impl A { + fn the_method(&self) {} +} +impl A { + fn the_other_method(&self) {} +} +fn foo(a: A) { 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 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 {} +impl HashSet { + 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; +impl IntoIterator for Vec { + type Item = (); + type IntoIter = (); + fn into_iter(self); +} +fn main() { + let x: Vec<_>; + x.$0; +} +"#, + expect![[r#" + me into_iter() (as IntoIterator) fn(self) -> ::IntoIter + "#]], + ) + } + + #[test] + fn postfix_drop_completion() { + cov_mark::check!(postfix_drop_completion); + check_edit( + "drop", + r#" +//- minicore: drop +struct Vec(T); +impl Drop for Vec { + fn drop(&mut self) {} +} +fn main() { + let x = Vec(0u32) + x.$0; +} +"#, + r" +struct Vec(T); +impl Drop for Vec { + 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); +} +fn test(thing: impl Encrypt) { + thing.$0; +} + "#, + expect![[r#" + me encrypt(…) (as Encrypt) fn(self, impl Closure::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 `::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, +) -> Option { + 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, + 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, + fn_params: ast::AstChildren, +) { + 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, +) -> 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, +) -> 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, +) -> Option<()> { + complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::TypeAlias) +} + +pub(crate) fn complete_trait_impl_fn( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + name: &Option, +) -> Option<()> { + complete_trait_impl_name(acc, ctx, name, ImplCompletionKind::Fn) +} + +fn complete_trait_impl_name( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + name: &Option, + 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, + impl_: &Option, +) { + 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 { + 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(); +} +struct T; + +impl Test for T { + fn f$0 +} +"#, + r#" +trait Test { + fn foo(); +} +struct T; + +impl Test for T { + fn foo() { + $0 +} +} +"#, + ); + check_edit( + "fn foo", + r#" +trait Test { + fn foo() where T: Into; +} +struct T; + +impl Test for T { + fn f$0 +} +"#, + r#" +trait Test { + fn foo() where T: Into; +} +struct T; + +impl Test for T { + fn foo() where T: Into { + $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 { + fn function() -> T; +} +struct Bar; + +impl Foo for Bar { + fn f$0 +} +"#, + r#" +trait Foo { + fn function() -> T; +} +struct Bar; + +impl Foo for Bar { + fn function() -> u32 { + $0 +} +} +"#, + ) + } + + #[test] + fn generics_are_inlined_in_parameter() { + check_edit( + "fn function", + r#" +trait Foo { + fn function(bar: T); +} +struct Bar; + +impl Foo for Bar { + fn f$0 +} +"#, + r#" +trait Foo { + fn function(bar: T); +} +struct Bar; + +impl Foo for Bar { + fn function(bar: u32) { + $0 +} +} +"#, + ) + } + + #[test] + fn generics_are_inlined_when_part_of_other_types() { + check_edit( + "fn function", + r#" +trait Foo { + fn function(bar: Vec); +} +struct Bar; + +impl Foo for Bar { + fn f$0 +} +"#, + r#" +trait Foo { + fn function(bar: Vec); +} +struct Bar; + +impl Foo for Bar { + fn function(bar: Vec) { + $0 +} +} +"#, + ) + } + + #[test] + fn generics_are_inlined_complex() { + check_edit( + "fn function", + r#" +trait Foo { + fn function(bar: Vec, baz: U) -> Arc>; +} +struct Bar; + +impl Foo, u8> for Bar { + fn f$0 +} +"#, + r#" +trait Foo { + fn function(bar: Vec, baz: U) -> Arc>; +} +struct Bar; + +impl Foo, u8> for Bar { + fn function(bar: Vec, baz: Vec) -> Arc> { + $0 +} +} +"#, + ) + } + + #[test] + fn generics_are_inlined_in_associated_const() { + check_edit( + "const BAR", + r#" +trait Foo { + const BAR: T; +} +struct Bar; + +impl Foo for Bar { + const B$0 +} +"#, + r#" +trait Foo { + const BAR: T; +} +struct Bar; + +impl Foo for Bar { + const BAR: u32 = $0; +} +"#, + ) + } + + #[test] + fn generics_are_inlined_in_where_clause() { + check_edit( + "fn function", + r#" +trait SomeTrait {} + +trait Foo { + fn function() + where Self: SomeTrait; +} +struct Bar; + +impl Foo for Bar { + fn f$0 +} +"#, + r#" +trait SomeTrait {} + +trait Foo { + fn function() + where Self: SomeTrait; +} +struct Bar; + +impl Foo for Bar { + fn function() + where Self: SomeTrait { + $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 {} +"#, + 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::>(); + + 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 { + 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 { + 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::>(); + 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 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 { + 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, + 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, + 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, 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, 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 `::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, +) { + 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, + pub snippet_cap: Option, + pub insert_use: InsertUseConfig, + pub snippets: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CallableSnippets { + FillArguments, + AddParentheses, +} + +impl CompletionConfig { + pub fn postfix_snippets(&self) -> impl Iterator { + self.snippets + .iter() + .flat_map(|snip| snip.postfix_triggers.iter().map(move |trigger| (&**trigger, snip))) + } + + pub fn prefix_snippets(&self) -> impl Iterator { + 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, + pub(super) vis_node: Option, +} + +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, + /// 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; + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct AttrCtx { + pub(crate) kind: AttrKind, + pub(crate) annotated_item_kind: Option, +} + +#[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, + pub(crate) is_func_update: Option, + pub(crate) self_param: Option, + pub(crate) innermost_ret_ty: Option, + pub(crate) impl_: Option, + /// 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), + TypeBound, + ImplTarget, + ImplTrait, + Other, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum TypeAscriptionTarget { + Let(Option), + FnParam(Option), + RetType(Option), + Const(Option), +} + +/// The kind of item list a [`PathKind::Item`] belongs to. +#[derive(Debug, PartialEq, Eq)] +pub(super) enum ItemListKind { + SourceFile, + Module, + Impl, + TraitImpl(Option), + Trait, + ExternBlock, +} + +#[derive(Debug)] +pub(super) enum Qualified { + No, + With { + path: ast::Path, + resolution: Option, + /// 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, + }, + /// <_>:: + TypeAnchor { + ty: Option, + trait_: Option, + }, + /// 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, + pub(super) has_type_ascription: bool, + pub(super) parent_pat: Option, + pub(super) ref_token: Option, + pub(super) mut_token: Option, + /// The record pattern this name or ref is a field of + pub(super) record_pat: Option, + pub(super) impl_: Option, +} + +#[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, + 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, + 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, + 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, + }, + /// 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, + }, +} + +/// Information about the field or method access we are completing. +#[derive(Debug)] +pub(super) struct DotAccess { + pub(super) receiver: Option, + pub(super) receiver_ty: Option, + 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, + /// The expected type of what we are completing. + pub(super) expected_type: Option, + + pub(super) qualifier_ctx: QualifierCtx, + + pub(super) locals: FxHashMap, + + /// 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(&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 { + 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::(&original_file, offset), + find_node_at_offset::(&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::(&original_file, offset) { + Some(it) => it, + None => break 'expansion, + }; + let spec_tt = match find_node_at_offset::(&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, Option) { + 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 { + 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 { + 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 { + 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 { + 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(syntax: &SyntaxNode, node: Option) -> Option { + 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(syntax: &SyntaxNode, node: &N) -> Option { + 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( + sema: &Semantics<'_, RootDatabase>, + in_file: &SyntaxNode, + node: &N, +) -> Option { + 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 + '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( + sema: &Semantics<'_, RootDatabase>, + syntax: &SyntaxNode, + node: Option, +) -> Option { + 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) -> Option { + 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 { + 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 { a: T } +fn foo() -> Foo { + 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::($0); +} + +fn bar(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); +impl S { + 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, + + /// Additional info to show in the UI pop up. + detail: Option, + documentation: Option, + + /// 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, + /// 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, + /// 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 { Some(T), None } + /// fn f(a: Option) {} + /// fn main { + /// f(Option::N$0) // type `Option` could unify with `Option` + /// } + /// ``` + 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, + source_range: TextRange, + label: impl Into, + ) -> 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 { + 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, + label: SmolStr, + insert_text: Option, + is_snippet: bool, + detail: Option, + documentation: Option, + lookup: Option, + kind: CompletionItemKind, + text_edit: Option, + 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) -> &mut Builder { + self.lookup = Some(lookup.into()); + self + } + pub(crate) fn label(&mut self, label: impl Into) -> &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) -> &mut Builder { + self.insert_text = Some(insert_text.into()); + self + } + pub(crate) fn insert_snippet( + &mut self, + cap: SnippetCap, + snippet: impl Into, + ) -> &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) -> &mut Builder { + self.set_detail(Some(detail)) + } + pub(crate) fn set_detail(&mut self, detail: Option>) -> &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) -> &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>) { + 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, +) -> Option> { + 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, +) -> Option> { + 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, +} + +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) -> Self { + self.import_to_add = import_to_add; + self + } + + fn snippet_cap(&self) -> Option { + 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 { + def.docs(self.db()) + } +} + +pub(crate) fn render_field( + ctx: RenderContext<'_>, + dot_access: &DotAccess, + receiver: Option, + 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, + 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 { + 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 { + 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 { + 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, + 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, + 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, + 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 { + 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 { + 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 { + 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, 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, 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::(); + + 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(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 {} +fn foo(xs: Ve$0) +"#, + r#" +struct Vec {} +fn foo(xs: Vec<$0>) +"#, + ); + check_edit( + "Vec", + r#" +type Vec = (T,); +fn foo(xs: Ve$0) +"#, + r#" +type Vec = (T,); +fn foo(xs: Vec<$0>) +"#, + ); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve$0) +"#, + r#" +struct Vec {} +fn foo(xs: Vec) +"#, + ); + check_edit( + "Vec", + r#" +struct Vec {} +fn foo(xs: Ve$0) +"#, + r#" +struct Vec {} +fn foo(xs: Vec) +"#, + ); + } + + #[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 { + 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 { A(T), B } +// bar() should not be an exact type match +// because the generic parameters are different +fn bar() -> Foo { 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() -> Foo { Foo::B } +fn foo() { + let foo: Foo = Foo::B; + let _: Foo = 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 { + let _p = profile::span("render_const"); + render(ctx, const_) +} + +fn render(ctx: RenderContext<'_>, const_: hir::Const) -> Option { + 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), +} + +pub(crate) fn render_fn( + ctx: RenderContext<'_>, + path_ctx: &PathCompletionCtx, + local_name: Option, + 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, + local_name: Option, + 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, + 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, + params: Vec, +) -> &'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, Vec)> { + 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, + variant: hir::Variant, + path: Option, +) -> Option { + 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, + local_name: Option, +) -> Option { + 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, +) -> Option { + 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> { + 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 { + 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(¯o_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, +) -> Option { + 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, + path: Option<&hir::ModPath>, +) -> Option { + 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 { + 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, + 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, + 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 { + 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 { + 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 { + 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, + local_name: Option, +) -> Option { + 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, + 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, + 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, 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::>(); + 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]>, + pub prefix_triggers: Box<[Box]>, + pub scope: SnippetScope, + pub description: Option>, + 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 { + 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> { + 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> { + 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>)> { + 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, +) -> 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, +) -> 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 { + do_completion_with_config(TEST_CONFIG, code, kind) +} + +pub(crate) fn do_completion_with_config( + config: CompletionConfig, + code: &str, + kind: CompletionItemKind, +) -> Vec { + 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) -> 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 = + 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, +) -> Vec { + 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, 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 {} +fn foo() -> impl Trait {} +fn main() { + self::$0 +} +", + expect![[r#" + fn foo() fn() -> impl Trait + fn main() fn() + tt Trait + "#]], + ); +} + +#[test] +fn detail_async_fn() { + check_empty( + r#" +//- minicore: future, sized +trait Trait {} +async fn foo() -> u8 {} +async fn bar() -> impl Trait {} +fn main() { + self::$0 +} +"#, + expect![[r#" + fn bar() async fn() -> impl Trait + 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 {} +struct Foo; +impl Foo { + fn bar(_: impl Trait) {} +} +fn main() { + Foo::$0 +} +", + expect![[r" + fn bar(…) fn(impl Trait) + "]], + ); +} + +#[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 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(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 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 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() 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 _ = ::$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::$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); +impl Super for Wrap {} +impl Sub for Wrap { + 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 {} + +impl HashMap { + pub fn new() -> HashMap { } +} +fn foo() { + HashMap::$0 +} +"#, + expect![[r#" + fn new() fn() -> HashMap + "#]], + ); +} + +#[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 { + ::$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 { + ::$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); +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 + 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); + pub fn x() -> Foo> { + 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> + kw crate:: + kw self:: + "#]], + ); +} + +#[test] +fn inferred_type_let() { + check( + r#" +struct Foo(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 + 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: 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, 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 { + 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 { + type Item1; + type Item2; +}; + +fn f(t: impl MyTrait { + type Item1; + type Item2; +}; + +fn f(t: impl MyTrait { + type Item1; + type Item2; +}; + +fn f(t: impl MyTrait { + type Item1; + type Item2; +}; + +fn f(t: impl MyTrait { + type Item1; + type Item2; +}; + +fn f(t: impl MyTrait { + type Item1; + type Item2; +}; + +fn f(t: impl MyTrait {} } + 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 + "#]], + ); +} -- cgit v1.2.3