diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index d94f55e32627..1e949771ea01 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -481,7 +481,7 @@ impl HirDisplay for Module { // FIXME: Module doesn't have visibility saved in data. match self.name(f.db) { Some(name) => write!(f, "mod {}", name), - None if self.crate_root(f.db) == *self => match self.krate().display_name(f.db) { + None if self.is_crate_root(f.db) => match self.krate().display_name(f.db) { Some(name) => write!(f, "extern crate {}", name), None => write!(f, "extern crate {{unknown}}"), }, diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 918cadc8696d..032da5f50af6 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -452,6 +452,11 @@ impl Module { Module { id: def_map.module_id(def_map.root()) } } + pub fn is_crate_root(self, db: &dyn HirDatabase) -> bool { + let def_map = db.crate_def_map(self.id.krate()); + def_map.root() == self.id.local_id + } + /// Iterates over all child modules. pub fn children(self, db: &dyn HirDatabase) -> impl Iterator { let def_map = self.id.def_map(db.upcast()); diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 9ac88e260c40..243ba63b8a0a 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -949,12 +949,15 @@ impl<'db> SemanticsImpl<'db> { })?; match res { - Either::Left(path) => resolve_hir_path( - self.db, - &self.scope(derive.syntax()).resolver, - &Path::from_known_path(path, []), - ) - .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))), + Either::Left(path) => { + let len = path.len(); + resolve_hir_path( + self.db, + &self.scope(derive.syntax()).resolver, + &Path::from_known_path(path, vec![None; len]), + ) + .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))) + } Either::Right(derive) => derive .map(|call| MacroDef { id: self.db.lookup_intern_macro_call(call).def }) .map(PathResolution::Macro), diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs index fc81b88db3f1..a6141174c837 100644 --- a/crates/hir_def/src/path.rs +++ b/crates/hir_def/src/path.rs @@ -92,7 +92,9 @@ impl Path { path: ModPath, generic_args: impl Into>]>>, ) -> Path { - Path { type_anchor: None, mod_path: Interned::new(path), generic_args: generic_args.into() } + let generic_args = generic_args.into(); + assert_eq!(path.len(), generic_args.len()); + Path { type_anchor: None, mod_path: Interned::new(path), generic_args } } pub fn kind(&self) -> &PathKind { diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index a6c91f9d27e4..173380654e0c 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs @@ -253,6 +253,7 @@ pub enum PointerCast { /// Go from a mut raw pointer to a const raw pointer. MutToConstPointer, + #[allow(dead_code)] /// Go from `*const [T; N]` to `*const T` ArrayToPointer, diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 92074b3e10d6..9c65efdb1022 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -4,6 +4,7 @@ pub(crate) mod attribute; pub(crate) mod dot; pub(crate) mod flyimport; pub(crate) mod fn_param; +pub(crate) mod format_string; pub(crate) mod keyword; pub(crate) mod lifetime; pub(crate) mod mod_; @@ -14,7 +15,8 @@ pub(crate) mod record; pub(crate) mod snippet; pub(crate) mod trait_impl; pub(crate) mod unqualified_path; -pub(crate) mod format_string; +pub(crate) mod use_; +pub(crate) mod vis; use std::iter; @@ -97,6 +99,19 @@ impl Completions { item.add_to(self); } + pub(crate) fn add_nameref_keywords(&mut self, ctx: &CompletionContext) { + ["self::", "super::", "crate::"].into_iter().for_each(|kw| self.add_keyword(ctx, kw)); + } + + pub(crate) fn add_crate_roots(&mut self, ctx: &CompletionContext) { + ctx.process_all_names(&mut |name, res| match res { + ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => { + self.add_resolution(ctx, name, res); + } + _ => (), + }); + } + pub(crate) fn add_resolution( &mut self, ctx: &CompletionContext, diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs index 6b6759ebfd95..cb45d9de03fc 100644 --- a/crates/ide_completion/src/completions/attribute.rs +++ b/crates/ide_completion/src/completions/attribute.rs @@ -1,8 +1,6 @@ -//! Completion for attributes +//! Completion for (built-in) attributes, derives and lints. //! -//! This module uses a bit of static metadata to provide completions -//! for built-in attributes. -//! Non-built-in attribute (excluding derives attributes) completions are done in [`super::unqualified_path`]. +//! This module uses a bit of static metadata to provide completions for builtin-in attributes and lints. use ide_db::{ helpers::{ @@ -16,62 +14,107 @@ use ide_db::{ use itertools::Itertools; use once_cell::sync::Lazy; use rustc_hash::FxHashMap; -use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, T}; +use syntax::{ + ast::{self, AttrKind}, + AstNode, SyntaxKind, T, +}; -use crate::{context::CompletionContext, item::CompletionItem, Completions}; +use crate::{ + completions::module_or_attr, + context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + item::CompletionItem, + Completions, +}; mod cfg; mod derive; mod lint; mod repr; -pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { +/// Complete inputs to known builtin attributes as well as derive attributes +pub(crate) fn complete_known_attribute_input( + acc: &mut Completions, + ctx: &CompletionContext, +) -> Option<()> { let attribute = ctx.fake_attribute_under_caret.as_ref()?; let name_ref = match attribute.path() { Some(p) => Some(p.as_single_name_ref()?), None => None, }; - match (name_ref, attribute.token_tree()) { - (Some(path), Some(tt)) if tt.l_paren_token().is_some() => match path.text().as_str() { - "repr" => repr::complete_repr(acc, ctx, tt), - "derive" => derive::complete_derive(acc, ctx, ctx.attr.as_ref()?), - "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES), - "allow" | "warn" | "deny" | "forbid" => { - let existing_lints = parse_tt_as_comma_sep_paths(tt)?; + let (path, tt) = name_ref.zip(attribute.token_tree())?; + if tt.l_paren_token().is_none() { + return None; + } - let lints: Vec = CLIPPY_LINT_GROUPS - .iter() - .map(|g| &g.lint) - .chain(DEFAULT_LINTS.iter()) - .chain(CLIPPY_LINTS.iter()) - .chain(RUSTDOC_LINTS) - .cloned() - .collect(); + match path.text().as_str() { + "repr" => repr::complete_repr(acc, ctx, tt), + "derive" => derive::complete_derive(acc, ctx, ctx.attr.as_ref()?), + "feature" => lint::complete_lint(acc, ctx, &parse_tt_as_comma_sep_paths(tt)?, FEATURES), + "allow" | "warn" | "deny" | "forbid" => { + let existing_lints = parse_tt_as_comma_sep_paths(tt)?; - lint::complete_lint(acc, ctx, &existing_lints, &lints); - } - "cfg" => { - cfg::complete_cfg(acc, ctx); - } - _ => (), - }, - (_, Some(_)) => (), - (_, None) if attribute.expr().is_some() => (), - (_, None) => complete_new_attribute(acc, ctx, attribute), + 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, &existing_lints, &lints); + } + "cfg" => { + cfg::complete_cfg(acc, ctx); + } + _ => (), } Some(()) } -// FIXME?: Move this functionality to (un)qualified_path, make this module work solely for builtin/known attributes for their inputs? -fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { - let is_inner = attribute.kind() == ast::AttrKind::Inner; - let attribute_annotated_item_kind = - attribute.syntax().parent().map(|it| it.kind()).filter(|_| { - is_inner - // If we got nothing coming after the attribute it could be anything so filter it the kind out - || non_trivia_sibling(attribute.syntax().clone().into(), Direction::Next).is_some() - }); - let attributes = attribute_annotated_item_kind.and_then(|kind| { +pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) { + let (is_absolute_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context { + Some(PathCompletionCtx { + kind: Some(PathKind::Attr { kind, annotated_item_kind }), + is_absolute_path, + ref qualifier, + .. + }) => (is_absolute_path, qualifier, kind == AttrKind::Inner, annotated_item_kind), + _ => return, + }; + + match qualifier { + Some(PathQualifierCtx { resolution, is_super_chain, .. }) => { + if *is_super_chain { + acc.add_keyword(ctx, "super::"); + } + + let module = match resolution { + Some(hir::PathResolution::Def(hir::ModuleDef::Module(it))) => it, + _ => return, + }; + + for (name, def) in module.scope(ctx.db, ctx.module) { + if let Some(def) = module_or_attr(def) { + acc.add_resolution(ctx, name, def); + } + } + return; + } + // fresh use tree with leading colon2, only show crate roots + None if is_absolute_path => acc.add_crate_roots(ctx), + // only show modules in a fresh UseTree + None => { + ctx.process_all_names(&mut |name, def| { + if let Some(def) = module_or_attr(def) { + acc.add_resolution(ctx, name, def); + } + }); + acc.add_nameref_keywords(ctx); + } + } + + let attributes = annotated_item_kind.and_then(|kind| { if ast::Expr::can_cast(kind) { Some(EXPR_ATTRIBUTES) } else { diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index b8bab941706f..4a34b0f7e562 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs @@ -32,7 +32,7 @@ fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { if !ctx.config.enable_self_on_the_fly { return; } - if !ctx.is_trivial_path() || ctx.is_path_disallowed() || !ctx.expects_expression() { + if ctx.is_non_trivial_path() || ctx.is_path_disallowed() || !ctx.expects_expression() { return; } if let Some(func) = ctx.function_def.as_ref().and_then(|fn_| ctx.sema.to_def(fn_)) { diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index 8ca4634be25d..723abd62aef5 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs @@ -171,8 +171,8 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) (PathKind::Type, ItemInNs::Types(_)) => true, (PathKind::Type, ItemInNs::Values(_)) => false, - (PathKind::Attr, ItemInNs::Macros(mac)) => mac.is_attr(), - (PathKind::Attr, _) => false, + (PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(), + (PathKind::Attr { .. }, _) => false, } }; diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index 6b013c912257..4704e842e6a3 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs @@ -5,7 +5,7 @@ use syntax::{SyntaxKind, T}; use crate::{ - context::{PathCompletionContext, PathKind}, + context::{PathCompletionCtx, PathKind}, patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind, Completions, }; @@ -27,6 +27,9 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte cov_mark::hit!(no_keyword_completion_in_non_trivial_path); return; } + if ctx.pattern_ctx.is_some() { + return; + } let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet); @@ -34,11 +37,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte let has_block_expr_parent = ctx.has_block_expr_parent(); let expects_item = ctx.expects_item(); - if let Some(PathKind::Vis { has_in_token }) = ctx.path_kind() { - if !has_in_token { - cov_mark::hit!(kw_completion_in); - add_keyword("in", "in"); - } + if let Some(PathKind::Vis { .. }) = ctx.path_kind() { return; } if ctx.has_impl_or_trait_prev_sibling() { @@ -121,14 +120,14 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte add_keyword("else if", "else if $1 {\n $0\n}"); } - if ctx.expects_ident_pat_or_ref_expr() { + if ctx.expects_ident_ref_expr() { add_keyword("mut", "mut "); } let (can_be_stmt, in_loop_body) = match ctx.path_context { - Some(PathCompletionContext { - is_trivial_path: true, can_be_stmt, in_loop_body, .. - }) => (can_be_stmt, in_loop_body), + Some(PathCompletionCtx { is_absolute_path: false, can_be_stmt, in_loop_body, .. }) => { + (can_be_stmt, in_loop_body) + } _ => return, }; diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs index a140ca4239bf..f1b4fa720591 100644 --- a/crates/ide_completion/src/completions/pattern.rs +++ b/crates/ide_completion/src/completions/pattern.rs @@ -1,20 +1,52 @@ //! Completes constants and paths in unqualified patterns. -use hir::db::DefDatabase; +use hir::{db::DefDatabase, AssocItem, ScopeDef}; +use rustc_hash::FxHashSet; +use syntax::ast::Pat; use crate::{ - context::{PatternContext, PatternRefutability}, + context::{PathCompletionCtx, PathQualifierCtx, PatternRefutability}, CompletionContext, Completions, }; /// Completes constants and paths in unqualified patterns. pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { - let refutable = match ctx.pattern_ctx { - Some(PatternContext { refutability, .. }) if ctx.path_context.is_none() => { - refutability == PatternRefutability::Refutable - } + let patctx = match &ctx.pattern_ctx { + Some(ctx) => ctx, _ => return, }; + let refutable = patctx.refutability == PatternRefutability::Refutable; + + if let Some(path_ctx) = &ctx.path_context { + pattern_path_completion(acc, ctx, path_ctx); + return; + } + + match patctx.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 (patctx.ref_token.as_ref(), patctx.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"); + } + _ => (), + } + } + } + let single_variant_enum = |enum_: hir::Enum| ctx.db.enum_data(enum_.into()).variants.len() == 1; if let Some(hir::Adt::Enum(e)) = @@ -63,3 +95,92 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { } }); } + +fn pattern_path_completion( + acc: &mut Completions, + ctx: &CompletionContext, + PathCompletionCtx { qualifier, is_absolute_path, .. }: &PathCompletionCtx, +) { + match qualifier { + Some(PathQualifierCtx { resolution, is_super_chain, .. }) => { + if *is_super_chain { + acc.add_keyword(ctx, "super::"); + } + + let resolution = match resolution { + Some(it) => it, + None => return, + }; + + match resolution { + hir::PathResolution::Def(hir::ModuleDef::Module(module)) => { + let module_scope = module.scope(ctx.db, ctx.module); + for (name, def) in module_scope { + let add_resolution = match def { + ScopeDef::MacroDef(m) if m.is_fn_like() => true, + ScopeDef::ModuleDef(_) => true, + _ => false, + }; + + if add_resolution { + acc.add_resolution(ctx, name, def); + } + } + } + hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => { + cov_mark::hit!(enum_plain_qualified_use_tree); + e.variants(ctx.db) + .into_iter() + .for_each(|variant| acc.add_enum_variant(ctx, variant, None)); + } + res @ (hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_)) => { + if let Some(krate) = ctx.krate { + let ty = match res { + 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() { + e.variants(ctx.db) + .into_iter() + .for_each(|variant| acc.add_enum_variant(ctx, variant, None)); + } + + let traits_in_scope = ctx.scope.visible_traits(); + let mut seen = FxHashSet::default(); + ty.iterate_path_candidates( + ctx.db, + krate, + &traits_in_scope, + ctx.module, + None, + |_ty, item| { + // Note associated consts cannot be referenced in patterns + if let AssocItem::TypeAlias(ta) = item { + // We might iterate candidates of a trait multiple times here, so deduplicate them. + if seen.insert(item) { + acc.add_type_alias(ctx, ta); + } + } + None::<()> + }, + ); + } + } + _ => {} + } + } + // qualifier can only be none here if we are in a TuplePat or RecordPat in which case special characters have to follow the path + None if *is_absolute_path => acc.add_crate_roots(ctx), + None => { + cov_mark::hit!(unqualified_path_only_modules_in_import); + ctx.process_all_names(&mut |name, res| { + if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { + acc.add_resolution(ctx, name, res); + } + }); + acc.add_nameref_keywords(ctx); + } + } +} diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index 2c6899ff0cd9..cf78f7c1adf4 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs @@ -1,14 +1,12 @@ //! Completion of paths, i.e. `some::prefix::$0`. -use std::iter; - use hir::{ScopeDef, Trait}; use rustc_hash::FxHashSet; -use syntax::{ast, AstNode}; +use syntax::ast; use crate::{ - completions::{module_or_attr, module_or_fn_macro}, - context::{PathCompletionContext, PathKind}, + completions::module_or_fn_macro, + context::{PathCompletionCtx, PathKind}, patterns::ImmediateLocation, CompletionContext, Completions, }; @@ -17,21 +15,19 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon if ctx.is_path_disallowed() || ctx.has_impl_or_trait_prev_sibling() { return; } - let (path, use_tree_parent, kind) = match ctx.path_context { + if ctx.pattern_ctx.is_some() { + return; + } + let (qualifier, kind) = match ctx.path_context { // let ... else, syntax would come in really handy here right now - Some(PathCompletionContext { - qualifier: Some(ref qualifier), - use_tree_parent, - kind, - .. - }) => (qualifier, use_tree_parent, kind), + Some(PathCompletionCtx { qualifier: Some(ref qualifier), kind, .. }) => (qualifier, kind), _ => return, }; // special case `<_>::$0` as this doesn't resolve to anything. - if path.qualifier().is_none() { + if qualifier.path.qualifier().is_none() { if matches!( - path.segment().and_then(|it| it.kind()), + qualifier.path.segment().and_then(|it| it.kind()), Some(ast::PathSegmentKind::Type { type_ref: Some(ast::Type::InferType(_)), trait_ref: None, @@ -47,17 +43,15 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon } } - let resolution = match ctx.sema.resolve_path(path) { + let resolution = match &qualifier.resolution { Some(res) => res, None => return, }; - let context_module = ctx.module; - match ctx.completion_location { Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => { if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { - for (name, def) in module.scope(ctx.db, context_module) { + for (name, def) in module.scope(ctx.db, ctx.module) { if let Some(def) = module_or_fn_macro(def) { acc.add_resolution(ctx, name, def); } @@ -69,78 +63,22 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon } match kind { - // Complete next child module that comes after the qualified module which is still our parent - Some(PathKind::Vis { .. }) => { - if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { - if let Some(current_module) = ctx.module { - let next_towards_current = current_module - .path_to_root(ctx.db) - .into_iter() - .take_while(|&it| it != module) - .next(); - if let Some(next) = next_towards_current { - if let Some(name) = next.name(ctx.db) { - cov_mark::hit!(visibility_qualified); - acc.add_resolution(ctx, name, ScopeDef::ModuleDef(next.into())); - } - } - } - } - return; - } - Some(PathKind::Attr) => { - if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { - for (name, def) in module.scope(ctx.db, context_module) { - if let Some(def) = module_or_attr(def) { - acc.add_resolution(ctx, name, def); - } - } - } + Some(PathKind::Pat | PathKind::Attr { .. } | PathKind::Vis { .. } | PathKind::Use) => { return; } - Some(PathKind::Use) => { - if iter::successors(Some(path.clone()), |p| p.qualifier()) - .all(|p| p.segment().and_then(|s| s.super_token()).is_some()) - { - acc.add_keyword(ctx, "super::"); - } - // only show `self` in a new use-tree when the qualifier doesn't end in self - if use_tree_parent - && !matches!( - path.segment().and_then(|it| it.kind()), - Some(ast::PathSegmentKind::SelfKw) - ) - { - acc.add_keyword(ctx, "self"); - } + _ => { + // Add associated types on type parameters and `Self`. + ctx.scope.assoc_type_shorthand_candidates(&resolution, |_, alias| { + acc.add_type_alias(ctx, alias); + None::<()> + }); } - _ => (), - } - - if !matches!(kind, Some(PathKind::Pat)) { - // 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, context_module); + let module_scope = module.scope(ctx.db, ctx.module); for (name, def) in module_scope { - if let Some(PathKind::Use) = kind { - if let ScopeDef::Unknown = def { - if let Some(ast::NameLike::NameRef(name_ref)) = ctx.name_syntax.as_ref() { - if name_ref.syntax().text() == name.to_smol_str().as_str() { - // for `use self::foo$0`, don't suggest `foo` as a completion - cov_mark::hit!(dont_complete_current_use); - continue; - } - } - } - } - let add_resolution = match def { // Don't suggest attribute macros and derives. ScopeDef::MacroDef(mac) => mac.is_fn_like(), @@ -168,7 +106,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | hir::ModuleDef::TypeAlias(_) | hir::ModuleDef::BuiltinType(_)), ) => { - if let hir::ModuleDef::Adt(hir::Adt::Enum(e)) = def { + if let &hir::ModuleDef::Adt(hir::Adt::Enum(e)) = def { add_enum_variants(acc, ctx, e); } let ty = match def { @@ -622,18 +560,6 @@ fn foo() { ); } - #[test] - fn dont_complete_attr() { - check( - r#" -mod foo { pub struct Foo; } -#[foo::$0] -fn f() {} -"#, - expect![[""]], - ); - } - #[test] fn completes_variant_through_self() { check( diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs index 02f711f51d86..9a2b9c2fa408 100644 --- a/crates/ide_completion/src/completions/snippet.rs +++ b/crates/ide_completion/src/completions/snippet.rs @@ -5,7 +5,7 @@ use ide_db::helpers::{insert_use::ImportScope, SnippetCap}; use syntax::T; use crate::{ - context::PathCompletionContext, item::Builder, CompletionContext, CompletionItem, + context::PathCompletionCtx, item::Builder, CompletionContext, CompletionItem, CompletionItemKind, Completions, SnippetScope, }; @@ -21,7 +21,9 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte } let can_be_stmt = match ctx.path_context { - Some(PathCompletionContext { is_trivial_path: true, can_be_stmt, .. }) => can_be_stmt, + Some(PathCompletionCtx { + is_absolute_path: false, qualifier: None, can_be_stmt, .. + }) => can_be_stmt, _ => return, }; diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index 7e06b074ce21..cca2785e2dd4 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs @@ -4,8 +4,8 @@ use hir::ScopeDef; use syntax::{ast, AstNode}; use crate::{ - completions::{module_or_attr, module_or_fn_macro}, - context::{PathCompletionContext, PathKind}, + completions::module_or_fn_macro, + context::{PathCompletionCtx, PathKind}, patterns::ImmediateLocation, CompletionContext, Completions, }; @@ -15,37 +15,22 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC if ctx.is_path_disallowed() || ctx.has_impl_or_trait_prev_sibling() { return; } - let kind = match ctx.path_context { - Some(PathCompletionContext { is_trivial_path: true, kind, .. }) => kind, + match ctx.path_context { + Some(PathCompletionCtx { + kind: + Some( + PathKind::Vis { .. } + | PathKind::Attr { .. } + | PathKind::Use { .. } + | PathKind::Pat, + ), + .. + }) => return, + Some(PathCompletionCtx { is_absolute_path: false, qualifier: None, .. }) => (), _ => return, - }; - - if let Some(PathKind::Use) = kind { - // only show modules in a fresh UseTree - cov_mark::hit!(unqualified_path_only_modules_in_import); - ctx.process_all_names(&mut |name, res| { - if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { - acc.add_resolution(ctx, name, res); - } - }); - - ["self::", "super::", "crate::"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw)); - return; } - ["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw)); - match kind { - Some(PathKind::Vis { .. }) => return, - Some(PathKind::Attr) => { - ctx.process_all_names(&mut |name, def| { - if let Some(def) = module_or_attr(def) { - acc.add_resolution(ctx, name, def); - } - }); - return; - } - _ => (), - } + ["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw)); match &ctx.completion_location { Some(ImmediateLocation::ItemList | ImmediateLocation::Trait | ImmediateLocation::Impl) => { diff --git a/crates/ide_completion/src/completions/use_.rs b/crates/ide_completion/src/completions/use_.rs new file mode 100644 index 000000000000..6f980845c7db --- /dev/null +++ b/crates/ide_completion/src/completions/use_.rs @@ -0,0 +1,95 @@ +//! Completion for use trees + +use hir::ScopeDef; +use syntax::{ast, AstNode}; + +use crate::{ + context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + Completions, +}; + +pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) { + let (is_absolute_path, qualifier) = match ctx.path_context { + Some(PathCompletionCtx { + kind: Some(PathKind::Use), + is_absolute_path, + ref qualifier, + .. + }) => (is_absolute_path, qualifier), + _ => return, + }; + + match qualifier { + Some(PathQualifierCtx { path, resolution, is_super_chain, use_tree_parent }) => { + if *is_super_chain { + acc.add_keyword(ctx, "super::"); + } + // 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 resolution = match resolution { + Some(it) => it, + None => return, + }; + + match resolution { + hir::PathResolution::Def(hir::ModuleDef::Module(module)) => { + let module_scope = module.scope(ctx.db, ctx.module); + let unknown_is_current = |name: &hir::Name| { + matches!( + ctx.name_syntax.as_ref(), + Some(ast::NameLike::NameRef(name_ref)) + if name_ref.syntax().text() == name.to_smol_str().as_str() + ) + }; + for (name, def) in module_scope { + 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::MacroDef(_) | ScopeDef::Unknown => { + true + } + _ => false, + }; + + if add_resolution { + acc.add_resolution(ctx, name, def); + } + } + } + hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e))) => { + cov_mark::hit!(enum_plain_qualified_use_tree); + e.variants(ctx.db) + .into_iter() + .for_each(|variant| acc.add_enum_variant(ctx, variant, None)); + } + _ => {} + } + } + // fresh use tree with leading colon2, only show crate roots + None if is_absolute_path => { + cov_mark::hit!(use_tree_crate_roots_only); + acc.add_crate_roots(ctx); + } + // only show modules in a fresh UseTree + None => { + cov_mark::hit!(unqualified_path_only_modules_in_import); + ctx.process_all_names(&mut |name, res| { + if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { + acc.add_resolution(ctx, name, res); + } + }); + acc.add_nameref_keywords(ctx); + } + } +} diff --git a/crates/ide_completion/src/completions/vis.rs b/crates/ide_completion/src/completions/vis.rs new file mode 100644 index 000000000000..9cf96326588c --- /dev/null +++ b/crates/ide_completion/src/completions/vis.rs @@ -0,0 +1,52 @@ +//! Completion for visibility specifiers. + +use hir::ScopeDef; + +use crate::{ + context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + Completions, +}; + +pub(crate) fn complete_vis(acc: &mut Completions, ctx: &CompletionContext) { + let (is_absolute_path, qualifier, has_in_token) = match ctx.path_context { + Some(PathCompletionCtx { + kind: Some(PathKind::Vis { has_in_token }), + is_absolute_path, + ref qualifier, + .. + }) => (is_absolute_path, qualifier, has_in_token), + _ => return, + }; + + match qualifier { + Some(PathQualifierCtx { resolution, is_super_chain, .. }) => { + if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = resolution { + if let Some(current_module) = ctx.module { + let next_towards_current = current_module + .path_to_root(ctx.db) + .into_iter() + .take_while(|it| it != module) + .next(); + if let Some(next) = next_towards_current { + if let Some(name) = next.name(ctx.db) { + cov_mark::hit!(visibility_qualified); + acc.add_resolution(ctx, name, ScopeDef::ModuleDef(next.into())); + } + } + } + } + + if *is_super_chain { + acc.add_keyword(ctx, "super::"); + } + } + None if !is_absolute_path => { + if !has_in_token { + cov_mark::hit!(kw_completion_in); + acc.add_keyword(ctx, "in"); + } + ["self", "super", "crate"].into_iter().for_each(|kw| acc.add_keyword(ctx, kw)); + } + _ => {} + } +} diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index ab55d9cc04a6..d711215491c1 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -3,7 +3,9 @@ use std::iter; use base_db::SourceDatabaseExt; -use hir::{HasAttrs, Local, Name, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo}; +use hir::{ + HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo, +}; use ide_db::{ active_parameter::ActiveParameter, base_db::{FilePosition, SourceDatabase}, @@ -11,8 +13,8 @@ use ide_db::{ RootDatabase, }; use syntax::{ - algo::find_node_at_offset, - ast::{self, HasName, NameOrNameRef}, + algo::{find_node_at_offset, non_trivia_sibling}, + ast::{self, AttrKind, HasName, NameOrNameRef}, match_ast, AstNode, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, TextRange, TextSize, T, @@ -34,6 +36,7 @@ pub(crate) enum PatternRefutability { Refutable, Irrefutable, } + pub(crate) enum Visible { Yes, Editable, @@ -44,7 +47,7 @@ pub(crate) enum Visible { pub(super) enum PathKind { Expr, Type, - Attr, + Attr { kind: AttrKind, annotated_item_kind: Option }, Mac, Pat, Vis { has_in_token: bool }, @@ -52,18 +55,13 @@ pub(super) enum PathKind { } #[derive(Debug)] -pub(crate) struct PathCompletionContext { +pub(crate) struct PathCompletionCtx { /// If this is a call with () already there has_call_parens: bool, - /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. - pub(super) is_trivial_path: bool, - /// If not a trivial path, the prefix (qualifier). - pub(super) qualifier: Option, - #[allow(dead_code)] - /// If not a trivial path, the suffix (parent). - pub(super) parent: Option, - /// Whether the qualifier comes from a use tree parent or not - pub(super) use_tree_parent: bool, + /// Whether this path stars with a `::`. + pub(super) is_absolute_path: bool, + /// The qualifier of the current path if it exists. + pub(super) qualifier: Option, pub(super) kind: Option, /// Whether the path segment has type args or not. pub(super) has_type_args: bool, @@ -72,11 +70,24 @@ pub(crate) struct PathCompletionContext { pub(super) in_loop_body: bool, } +#[derive(Debug)] +pub(crate) struct PathQualifierCtx { + pub(crate) path: ast::Path, + pub(crate) resolution: Option, + /// Whether this path consists solely of `super` segments + pub(crate) is_super_chain: bool, + /// Whether the qualifier comes from a use tree parent or not + pub(crate) use_tree_parent: bool, +} + #[derive(Debug)] pub(super) struct PatternContext { pub(super) refutability: PatternRefutability, pub(super) param_ctx: Option<(ast::ParamList, ast::Param, ParamKind)>, pub(super) has_type_ascription: bool, + pub(super) parent_pat: Option, + pub(super) ref_token: Option, + pub(super) mut_token: Option, } #[derive(Debug)] @@ -129,7 +140,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) lifetime_ctx: Option, pub(super) pattern_ctx: Option, - pub(super) path_context: Option, + pub(super) path_context: Option, pub(super) locals: Vec<(Name, Local)>, @@ -211,11 +222,8 @@ impl<'a> CompletionContext<'a> { matches!(self.completion_location, Some(ImmediateLocation::StmtList)) } - pub(crate) fn expects_ident_pat_or_ref_expr(&self) -> bool { - matches!( - self.completion_location, - Some(ImmediateLocation::IdentPat | ImmediateLocation::RefExpr) - ) + pub(crate) fn expects_ident_ref_expr(&self) -> bool { + matches!(self.completion_location, Some(ImmediateLocation::RefExpr)) } pub(crate) fn expect_field(&self) -> bool { @@ -262,27 +270,29 @@ impl<'a> CompletionContext<'a> { } pub(crate) fn expects_expression(&self) -> bool { - matches!(self.path_context, Some(PathCompletionContext { kind: Some(PathKind::Expr), .. })) + matches!(self.path_context, Some(PathCompletionCtx { kind: Some(PathKind::Expr), .. })) } pub(crate) fn expects_type(&self) -> bool { - matches!(self.path_context, Some(PathCompletionContext { kind: Some(PathKind::Type), .. })) + matches!(self.path_context, Some(PathCompletionCtx { kind: Some(PathKind::Type), .. })) } pub(crate) fn path_is_call(&self) -> bool { self.path_context.as_ref().map_or(false, |it| it.has_call_parens) } - pub(crate) fn is_trivial_path(&self) -> bool { - matches!(self.path_context, Some(PathCompletionContext { is_trivial_path: true, .. })) - } - pub(crate) fn is_non_trivial_path(&self) -> bool { - matches!(self.path_context, Some(PathCompletionContext { is_trivial_path: false, .. })) + matches!( + self.path_context, + Some( + PathCompletionCtx { is_absolute_path: true, .. } + | PathCompletionCtx { qualifier: Some(_), .. } + ) + ) } pub(crate) fn path_qual(&self) -> Option<&ast::Path> { - self.path_context.as_ref().and_then(|it| it.qualifier.as_ref()) + self.path_context.as_ref().and_then(|it| it.qualifier.as_ref().map(|it| &it.path)) } pub(crate) fn path_kind(&self) -> Option { @@ -779,37 +789,33 @@ impl<'a> CompletionContext<'a> { if is_name_in_field_pat { return None; } - if !bind_pat.is_simple_ident() { - return None; - } Some(pattern_context_for(original_file, bind_pat.into())) } fn classify_name_ref( - _sema: &Semantics, + sema: &Semantics, original_file: &SyntaxNode, name_ref: ast::NameRef, - ) -> Option<(PathCompletionContext, Option)> { + ) -> Option<(PathCompletionCtx, Option)> { let parent = name_ref.syntax().parent()?; let segment = ast::PathSegment::cast(parent)?; let path = segment.parent_path(); - let mut path_ctx = PathCompletionContext { + let mut path_ctx = PathCompletionCtx { has_call_parens: false, - is_trivial_path: false, + is_absolute_path: false, qualifier: None, - parent: None, has_type_args: false, can_be_stmt: false, in_loop_body: false, - use_tree_parent: false, kind: None, }; let mut pat_ctx = None; path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax()); - path_ctx.kind = path.syntax().ancestors().find_map(|it| { - match_ast! { + path_ctx.kind = path.syntax().ancestors().find_map(|it| { + // using Option> as extra controlflow + let kind = match_ast! { match it { ast::PathType(_) => Some(PathKind::Type), ast::PathExpr(it) => { @@ -830,32 +836,57 @@ impl<'a> CompletionContext<'a> { Some(PathKind::Pat) }, ast::MacroCall(it) => it.excl_token().and(Some(PathKind::Mac)), - ast::Meta(_) => Some(PathKind::Attr), + ast::Meta(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 { + kind, + annotated_item_kind, + }) + })(), ast::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }), ast::UseTree(_) => Some(PathKind::Use), - _ => None, + _ => return None, } - } - }); + }; + Some(kind) + }).flatten(); path_ctx.has_type_args = segment.generic_arg_list().is_some(); if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) { - path_ctx.use_tree_parent = use_tree_parent; - path_ctx.qualifier = path + if !use_tree_parent { + path_ctx.is_absolute_path = + path.top_path().segment().map_or(false, |it| it.coloncolon_token().is_some()); + } + + let path = path .segment() .and_then(|it| find_node_in_file(original_file, &it)) .map(|it| it.parent_path()); + path_ctx.qualifier = path.map(|path| { + let res = sema.resolve_path(&path); + let is_super_chain = iter::successors(Some(path.clone()), |p| p.qualifier()) + .all(|p| p.segment().and_then(|s| s.super_token()).is_some()); + PathQualifierCtx { path, resolution: res, is_super_chain, use_tree_parent } + }); return Some((path_ctx, pat_ctx)); } if let Some(segment) = path.segment() { if segment.coloncolon_token().is_some() { + path_ctx.is_absolute_path = true; return Some((path_ctx, pat_ctx)); } } - path_ctx.is_trivial_path = true; - // Find either enclosing expr statement (thing with `;`) or a // block. If block, check that we are the last expr. path_ctx.can_be_stmt = name_ref @@ -915,7 +946,18 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont }; (refutability, false) }); - PatternContext { refutability, param_ctx: is_param, has_type_ascription } + let (ref_token, mut_token) = match &pat { + ast::Pat::IdentPat(it) => (it.ref_token(), it.mut_token()), + _ => (None, None), + }; + PatternContext { + refutability, + param_ctx: is_param, + has_type_ascription, + parent_pat: pat.syntax().parent().and_then(ast::Pat::cast), + mut_token, + ref_token, + } } fn find_node_in_file(syntax: &SyntaxNode, node: &N) -> Option { @@ -946,7 +988,7 @@ fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> { } 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)?; - use_tree.path().zip(Some(true)) + Some((use_tree.path()?, true)) } fn has_ref(token: &SyntaxToken) -> bool { diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index a2217af493df..f0cd125973a2 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs @@ -151,7 +151,10 @@ pub fn completions( } let mut acc = Completions::default(); + completions::attribute::complete_known_attribute_input(&mut acc, &ctx); completions::attribute::complete_attribute(&mut acc, &ctx); + completions::use_::complete_use_tree(&mut acc, &ctx); + completions::vis::complete_vis(&mut acc, &ctx); completions::fn_param::complete_fn_param(&mut acc, &ctx); completions::keyword::complete_expr_keyword(&mut acc, &ctx); completions::snippet::complete_expr_snippet(&mut acc, &ctx); diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 6be265112383..027fb5352715 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -19,7 +19,7 @@ use ide_db::{ use syntax::{SmolStr, SyntaxKind, TextRange}; use crate::{ - context::{PathCompletionContext, PathKind}, + context::{PathCompletionCtx, PathKind}, item::{CompletionRelevanceTypeMatch, ImportEdit}, render::{enum_variant::render_variant, function::render_fn, macro_::render_macro}, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, @@ -234,7 +234,7 @@ fn render_resolution_( // Add `<>` for generic types let type_path_no_ty_args = matches!( ctx.completion.path_context, - Some(PathCompletionContext { kind: Some(PathKind::Type), has_type_args: false, .. }) + Some(PathCompletionCtx { kind: Some(PathKind::Type), has_type_args: false, .. }) ) && ctx.completion.config.add_call_parenthesis; if type_path_no_ty_args { if let Some(cap) = ctx.snippet_cap() { diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs index 2d2a1b867a6f..5a9c48a3278c 100644 --- a/crates/ide_completion/src/tests/attribute.rs +++ b/crates/ide_completion/src/tests/attribute.rs @@ -17,6 +17,10 @@ fn proc_macros() { struct Foo; "#, expect![[r#" + md proc_macros + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -32,10 +36,6 @@ struct Foo; at derive(…) at repr(…) at non_exhaustive - kw self - kw super - kw crate - md proc_macros "#]], ) } @@ -78,15 +78,15 @@ fn with_existing_attr() { check( r#"#[no_mangle] #[$0] mcall!();"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) at deny(…) at forbid(…) at warn(…) - kw self - kw super - kw crate "#]], ) } @@ -96,6 +96,9 @@ fn attr_on_source_file() { check( r#"#![$0]"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -116,9 +119,6 @@ fn attr_on_source_file() { at recursion_limit = "…" at type_length_limit = … at windows_subsystem = "…" - kw self - kw super - kw crate "#]], ); } @@ -128,6 +128,9 @@ fn attr_on_module() { check( r#"#[$0] mod foo;"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -142,14 +145,14 @@ fn attr_on_module() { at no_mangle at macro_use at path = "…" - kw self - kw super - kw crate "#]], ); check( r#"mod foo {#![$0]}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -163,9 +166,6 @@ fn attr_on_module() { at must_use at no_mangle at no_implicit_prelude - kw self - kw super - kw crate "#]], ); } @@ -175,6 +175,9 @@ fn attr_on_macro_rules() { check( r#"#[$0] macro_rules! foo {}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -189,9 +192,6 @@ fn attr_on_macro_rules() { at no_mangle at macro_export at macro_use - kw self - kw super - kw crate "#]], ); } @@ -201,6 +201,9 @@ fn attr_on_macro_def() { check( r#"#[$0] macro foo {}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -213,9 +216,6 @@ fn attr_on_macro_def() { at doc(alias = "…") at must_use at no_mangle - kw self - kw super - kw crate "#]], ); } @@ -225,6 +225,9 @@ fn attr_on_extern_crate() { check( r#"#[$0] extern crate foo;"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -238,9 +241,6 @@ fn attr_on_extern_crate() { at must_use at no_mangle at macro_use - kw self - kw super - kw crate "#]], ); } @@ -250,6 +250,9 @@ fn attr_on_use() { check( r#"#[$0] use foo;"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -262,9 +265,6 @@ fn attr_on_use() { at doc(alias = "…") at must_use at no_mangle - kw self - kw super - kw crate "#]], ); } @@ -274,6 +274,9 @@ fn attr_on_type_alias() { check( r#"#[$0] type foo = ();"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -286,9 +289,6 @@ fn attr_on_type_alias() { at doc(alias = "…") at must_use at no_mangle - kw self - kw super - kw crate "#]], ); } @@ -302,6 +302,11 @@ fn attr_on_struct() { struct Foo; "#, expect![[r#" + md core + at derive pub macro derive + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -317,11 +322,6 @@ struct Foo; at derive(…) at repr(…) at non_exhaustive - kw self - kw super - kw crate - md core - at derive pub macro derive "#]], ); } @@ -331,6 +331,9 @@ fn attr_on_enum() { check( r#"#[$0] enum Foo {}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -346,9 +349,6 @@ fn attr_on_enum() { at derive(…) at repr(…) at non_exhaustive - kw self - kw super - kw crate "#]], ); } @@ -358,6 +358,9 @@ fn attr_on_const() { check( r#"#[$0] const FOO: () = ();"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -370,9 +373,6 @@ fn attr_on_const() { at doc(alias = "…") at must_use at no_mangle - kw self - kw super - kw crate "#]], ); } @@ -382,6 +382,9 @@ fn attr_on_static() { check( r#"#[$0] static FOO: () = ()"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -399,9 +402,6 @@ fn attr_on_static() { at link_section = "…" at global_allocator at used - kw self - kw super - kw crate "#]], ); } @@ -411,6 +411,9 @@ fn attr_on_trait() { check( r#"#[$0] trait Foo {}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -424,9 +427,6 @@ fn attr_on_trait() { at must_use at no_mangle at must_use - kw self - kw super - kw crate "#]], ); } @@ -436,6 +436,9 @@ fn attr_on_impl() { check( r#"#[$0] impl () {}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -449,14 +452,14 @@ fn attr_on_impl() { at must_use at no_mangle at automatically_derived - kw self - kw super - kw crate "#]], ); check( r#"impl () {#![$0]}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -469,9 +472,6 @@ fn attr_on_impl() { at doc(alias = "…") at must_use at no_mangle - kw self - kw super - kw crate "#]], ); } @@ -481,6 +481,9 @@ fn attr_on_extern_block() { check( r#"#[$0] extern {}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -494,14 +497,14 @@ fn attr_on_extern_block() { at must_use at no_mangle at link - kw self - kw super - kw crate "#]], ); check( r#"extern {#![$0]}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -515,9 +518,6 @@ fn attr_on_extern_block() { at must_use at no_mangle at link - kw self - kw super - kw crate "#]], ); } @@ -527,6 +527,9 @@ fn attr_on_variant() { check( r#"enum Foo { #[$0] Bar }"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -534,9 +537,6 @@ fn attr_on_variant() { at forbid(…) at warn(…) at non_exhaustive - kw self - kw super - kw crate "#]], ); } @@ -546,6 +546,9 @@ fn attr_on_fn() { check( r#"#[$0] fn main() {}"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) @@ -573,9 +576,6 @@ fn attr_on_fn() { at target_feature = "…" at test at track_caller - kw self - kw super - kw crate "#]], ); } @@ -586,15 +586,15 @@ fn attr_on_expr() { check( r#"fn main() { #[$0] foo() }"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at cfg(…) at cfg_attr(…) at deny(…) at forbid(…) at warn(…) - kw self - kw super - kw crate "#]], ); } @@ -604,6 +604,9 @@ fn attr_in_source_file_end() { check( r#"#[$0]"#, expect![[r#" + kw self:: + kw super:: + kw crate:: at allow(…) at automatically_derived at cfg(…) @@ -640,9 +643,6 @@ fn attr_in_source_file_end() { at track_caller at used at warn(…) - kw self - kw super - kw crate "#]], ); } diff --git a/crates/ide_completion/src/tests/fn_param.rs b/crates/ide_completion/src/tests/fn_param.rs index 662fbe309bcf..779ec0c3a766 100644 --- a/crates/ide_completion/src/tests/fn_param.rs +++ b/crates/ide_completion/src/tests/fn_param.rs @@ -17,6 +17,7 @@ fn baz(file$0) {} "#, expect![[r#" bn file_id: usize + kw ref kw mut "#]], ); @@ -32,6 +33,7 @@ fn baz(foo: (), file$0) {} "#, expect![[r#" bn file_id: usize + kw ref kw mut "#]], ); @@ -47,6 +49,7 @@ fn baz(file$0 id: u32) {} "#, expect![[r#" bn file_id: usize, + kw ref kw mut "#]], ); @@ -60,6 +63,7 @@ fn foo(file_id: usize) {} fn bar(file_id: u32, $0) {} "#, expect![[r#" + kw ref kw mut "#]], ); @@ -76,6 +80,7 @@ pub(crate) trait SourceRoot { "#, expect![[r#" bn file_id: usize + kw ref kw mut "#]], ); @@ -91,6 +96,7 @@ fn outer(text: &str) { "#, expect![[r#" bn text: &str + kw ref kw mut "#]], ) @@ -106,6 +112,7 @@ fn foo2($0) {} "#, expect![[r#" bn Bar { bar }: Bar + kw ref kw mut bn Bar Bar { bar$1 }: Bar$0 st Bar @@ -130,6 +137,7 @@ impl A { bn mut self bn &mut self bn file_id: usize + kw ref kw mut sp Self st A @@ -150,6 +158,7 @@ impl A { "#, expect![[r#" bn file_id: usize + kw ref kw mut sp Self st A @@ -178,6 +187,7 @@ fn outer() { bn foo: i32 bn baz: i32 bn bar: i32 + kw ref kw mut "#]], ) @@ -202,6 +212,22 @@ fn outer() { bn baz: i32 bn bar: i32 bn foo: i32 + kw ref + kw mut + "#]], + ) +} + +#[test] +fn completes_fully_equal() { + check( + r#" +fn foo(bar: u32) {} +fn bar(bar$0) {} +"#, + expect![[r#" + bn bar: u32 + kw ref kw mut "#]], ) diff --git a/crates/ide_completion/src/tests/pattern.rs b/crates/ide_completion/src/tests/pattern.rs index 7f437e1a077a..fe532576729d 100644 --- a/crates/ide_completion/src/tests/pattern.rs +++ b/crates/ide_completion/src/tests/pattern.rs @@ -22,6 +22,7 @@ fn quux() { } "#, expect![[r#" + kw ref kw mut "#]], ); @@ -53,16 +54,13 @@ fn quux() { #[test] fn ident_ref_mut_pat() { - // FIXME mut is already here, don't complete it again check_empty( r#" fn quux() { let ref mut en$0 } "#, - expect![[r#" - kw mut - "#]], + expect![[r#""#]], ); check_empty( r#" @@ -70,9 +68,7 @@ fn quux() { let ref mut en$0 @ x } "#, - expect![[r#" - kw mut - "#]], + expect![[r#""#]], ); } @@ -88,16 +84,13 @@ fn quux() { kw mut "#]], ); - // FIXME mut is already here, don't complete it again check_empty( r#" fn quux() { let &mut en$0 } "#, - expect![[r#" - kw mut - "#]], + expect![[r#""#]], ); } @@ -110,6 +103,7 @@ fn foo() { } "#, expect![[r##" + kw ref kw mut en Enum bn Record Record { field$1 }$0 @@ -139,6 +133,7 @@ fn foo() { } "#, expect![[r##" + kw ref kw mut bn Record Record { field$1 }$0 st Record @@ -160,6 +155,7 @@ fn foo(a$0) { } "#, expect![[r##" + kw ref kw mut bn Record Record { field$1 }: Record$0 st Record @@ -175,6 +171,7 @@ fn foo(a$0: Tuple) { } "#, expect![[r##" + kw ref kw mut bn Record Record { field$1 }$0 st Record @@ -200,6 +197,7 @@ fn foo() { } "#, expect![[r#" + kw ref kw mut ma m!(…) macro_rules! m "#]], @@ -218,6 +216,7 @@ fn foo() { } "#, expect![[r#" + kw ref kw mut ev E::X () en E @@ -242,6 +241,7 @@ fn outer() { } "#, expect![[r#" + kw ref kw mut bn Record Record { field$1, .. }$0 st Record @@ -267,6 +267,7 @@ impl Foo { } "#, expect![[r#" + kw ref kw mut bn Self Self($1)$0 sp Self @@ -278,7 +279,6 @@ impl Foo { #[test] fn enum_qualified() { - // FIXME: Don't show functions, they aren't patterns check( r#" impl Enum { @@ -291,12 +291,9 @@ fn func() { } "#, expect![[r#" - ev TupleV(…) (u32) - ev RecordV {field: u32} - ev UnitV () - ct ASSOC_CONST const ASSOC_CONST: () - fn assoc_fn() fn() - ta AssocType type AssocType = () + ev TupleV(…) (u32) + ev RecordV {field: u32} + ev UnitV () "#]], ); } @@ -310,6 +307,7 @@ struct Bar(u32); fn outer(Foo { bar: $0 }: Foo) {} "#, expect![[r#" + kw ref kw mut bn Foo Foo { bar$1 }$0 st Foo @@ -340,6 +338,7 @@ struct Bar(u32); fn foo($0) {} "#, expect![[r#" + kw ref kw mut bn Foo Foo { bar$1 }: Foo$0 st Foo @@ -360,6 +359,7 @@ fn foo() { } "#, expect![[r#" + kw ref kw mut bn Foo Foo { bar$1 }$0 st Foo @@ -368,17 +368,3 @@ fn foo() { "#]], ) } - -#[test] -fn completes_fully_equal() { - check_empty( - r#" -fn foo(bar: u32) {} -fn bar(bar$0) {} -"#, - expect![[r#" - bn bar: u32 - kw mut - "#]], - ) -} diff --git a/crates/ide_completion/src/tests/use_tree.rs b/crates/ide_completion/src/tests/use_tree.rs index 5f20e342e02d..73cb83957f6a 100644 --- a/crates/ide_completion/src/tests/use_tree.rs +++ b/crates/ide_completion/src/tests/use_tree.rs @@ -31,6 +31,25 @@ mod foo {} ); } +#[test] +fn use_tree_start_abs() { + cov_mark::check!(use_tree_crate_roots_only); + check( + r#" +//- /lib.rs crate:main deps:other_crate +use ::f$0 + +struct Foo; +mod foo {} +//- /other_crate/lib.rs crate:other_crate +// nothing here +"#, + expect![[r#" + md other_crate + "#]], + ); +} + #[test] fn dont_complete_current_use() { cov_mark::check!(dont_complete_current_use); @@ -134,6 +153,25 @@ struct Bar; ); } +#[test] +fn enum_plain_qualified_use_tree() { + cov_mark::check!(enum_plain_qualified_use_tree); + check( + r#" +use Foo::$0 + +enum Foo { Variant } +impl Foo { + const CONST: () = () + fn func() {} +} +"#, + expect![[r#" + ev Variant () + "#]], + ); +} + #[test] fn self_qualified_use_tree() { check( diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index dcfc2b7bfc7a..7ea2bc63f25c 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs @@ -223,7 +223,7 @@ impl Definition { // def is crate root // FIXME: We don't do searches for crates currently, as a crate does not actually have a single name if let &Definition::Module(module) = self { - if module.crate_root(db) == module { + if module.is_crate_root(db) { return SearchScope::reverse_dependencies(db, module.krate()); } } @@ -378,7 +378,7 @@ impl<'a> FindUsages<'a> { let name = match self.def { // special case crate modules as these do not have a proper name - Definition::Module(module) if module.crate_root(self.sema.db) == module => { + Definition::Module(module) if module.is_crate_root(self.sema.db) => { // FIXME: This assumes the crate name is always equal to its display name when it really isn't module .krate() @@ -460,7 +460,7 @@ impl<'a> FindUsages<'a> { Definition::Module(module) => { let scope = search_scope.intersection(&SearchScope::module(self.sema.db, module)); - let is_crate_root = module.crate_root(self.sema.db) == module; + let is_crate_root = module.is_crate_root(self.sema.db); for (text, file_id, search_range) in scope_files(sema, &scope) { let tree = Lazy::new(move || sema.parse(file_id).syntax().clone()); diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 7211c77e880b..067e13ee14dd 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -119,7 +119,7 @@ impl From for ast::Item { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum AttrKind { Inner, Outer,