diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index 54c8969e..d45adb31 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -28,13 +28,13 @@ pub use crate::{ ids::{AssocItemId, ItemLoc}, in_file::InFile, name::Name, - name_resolution::PerNs, + name_resolution::{Namespace, PerNs}, path::{Path, PathKind}, primitive_type::{FloatBitness, IntBitness, Signedness}, resolve::{resolver_for_expr, resolver_for_scope, Resolver, TypeNs, ValueNs}, ty::{ lower::CallableDef, FloatTy, InferenceResult, IntTy, ResolveBitness, Substitution, Ty, - TyKind, + TyKind, TypableDef, }, visibility::{HasVisibility, Visibility}, }; diff --git a/crates/mun_hir/src/semantics.rs b/crates/mun_hir/src/semantics.rs index 61026f9e..49a326e5 100644 --- a/crates/mun_hir/src/semantics.rs +++ b/crates/mun_hir/src/semantics.rs @@ -109,6 +109,7 @@ impl<'db> Semantics<'db> { return SourceAnalyzer::new_for_body(self.db, def, node, offset) } SourceToDefContainer::ModuleId(id) => id.resolver(self.db.upcast()), + SourceToDefContainer::Impl(id) => id.resolver(self.db.upcast()), }; SourceAnalyzer::new_for_resolver(resolver, node) diff --git a/crates/mun_hir/src/semantics/source_to_def.rs b/crates/mun_hir/src/semantics/source_to_def.rs index 407e67f6..6255ae25 100644 --- a/crates/mun_hir/src/semantics/source_to_def.rs +++ b/crates/mun_hir/src/semantics/source_to_def.rs @@ -4,9 +4,9 @@ use rustc_hash::FxHashMap; use crate::{ code_model::src::HasSource, - ids::{DefWithBodyId, FunctionId, ItemDefinitionId, Lookup, StructId, TypeAliasId}, + ids::{DefWithBodyId, FunctionId, ImplId, ItemDefinitionId, Lookup, StructId, TypeAliasId}, item_scope::ItemScope, - DefDatabase, HirDatabase, InFile, + AssocItemId, DefDatabase, HirDatabase, InFile, }; pub(super) type SourceToDefCache = FxHashMap; @@ -24,25 +24,33 @@ impl SourceToDefContext<'_, '_> { &mut self, src: InFile<&SyntaxNode>, ) -> Option { - for container in std::iter::successors(Some(src.cloned()), move |node| { + let mut ancestors = std::iter::successors(Some(src.cloned()), move |node| { node.value.parent().map(|parent| node.with_value(parent)) }) - .skip(1) - { - let res: SourceToDefContainer = match_ast! { - match (container.value) { - ast::FunctionDef(it) => { - let def = self.fn_to_def(container.with_value(it))?; - DefWithBodyId::from(def).into() - }, - _ => continue, - } - }; - return Some(res); - } + .skip(1); - let def = self.file_to_def(src.file_id)?; - Some(def.into()) + ancestors.find_map(|node| self.container_to_def(node)) + } + + /// Find the container associated with the given ast node. + fn container_to_def(&mut self, container: InFile) -> Option { + Some(match_ast! { + match (container.value) { + ast::FunctionDef(it) => { + let def = self.fn_to_def(container.with_value(it))?; + SourceToDefContainer::DefWithBodyId(def.into()) + }, + ast::Impl(it) => { + let def = self.impl_to_def(container.with_value(it))?; + SourceToDefContainer::Impl(def) + }, + ast::SourceFile(_) => { + let def = self.file_to_def(container.file_id)?; + SourceToDefContainer::ModuleId(def) + }, + _ => return None, + } + }) } /// Find the `FunctionId` associated with the specified syntax tree node. @@ -56,22 +64,30 @@ impl SourceToDefContext<'_, '_> { def_map.functions.get(&src).copied() } + /// Find the `ImplId` associated with the specified syntax tree node. + fn impl_to_def(&mut self, src: InFile) -> Option { + let container = self.find_container(src.as_ref().map(AstNode::syntax))?; + let db = self.db; + let def_map = &*self + .cache + .entry(container) + .or_insert_with(|| container.source_to_def_map(db)); + def_map.impls.get(&src).copied() + } + /// Finds the `ModuleId` associated with the specified `file` fn file_to_def(&self, file_id: FileId) -> Option { let source_root_id = self.db.file_source_root(file_id); let packages = self.db.packages(); - let result = packages + let package_id = packages .iter() - .filter(|package_id| packages[*package_id].source_root == source_root_id) - .find_map(|package_id| { - let module_tree = self.db.module_tree(package_id); - let module_id = module_tree.module_for_file(file_id)?; - Some(ModuleId { - package: package_id, - local_id: module_id, - }) - }); - result + .find(|package_id| packages[*package_id].source_root == source_root_id)?; + let module_tree = self.db.module_tree(package_id); + let module_id = module_tree.module_for_file(file_id)?; + Some(ModuleId { + package: package_id, + local_id: module_id, + }) } } @@ -80,6 +96,7 @@ impl SourceToDefContext<'_, '_> { pub(crate) enum SourceToDefContainer { DefWithBodyId(DefWithBodyId), ModuleId(ModuleId), + Impl(ImplId), } impl From for SourceToDefContainer { @@ -99,6 +116,7 @@ impl SourceToDefContainer { match self { SourceToDefContainer::DefWithBodyId(id) => id.source_to_def_map(db), SourceToDefContainer::ModuleId(id) => id.source_to_def_map(db), + SourceToDefContainer::Impl(id) => id.source_to_def_map(db), } } } @@ -148,6 +166,27 @@ impl SourceToDef for ItemScope { self.declarations() .for_each(|item| add_module_def(db.upcast(), &mut result, item)); + self.impls().for_each(|id| { + let src = id.lookup(db.upcast()).source(db.upcast()); + result.impls.insert(src, id); + }); + + result + } +} + +impl SourceToDef for ImplId { + fn source_to_def_map(&self, db: &dyn HirDatabase) -> SourceToDefMap { + let mut result = SourceToDefMap::default(); + let impl_items = db.impl_data(*self); + for &assoc_item in &impl_items.items { + match assoc_item { + AssocItemId::FunctionId(id) => { + let src = id.lookup(db.upcast()).source(db.upcast()); + result.functions.insert(src, id); + } + } + } result } } @@ -156,6 +195,7 @@ impl SourceToDef for ItemScope { #[derive(Default)] pub(crate) struct SourceToDefMap { functions: FxHashMap, FunctionId>, + impls: FxHashMap, ImplId>, structs: FxHashMap, StructId>, type_aliases: FxHashMap, TypeAliasId>, } diff --git a/crates/mun_hir/src/source_analyzer.rs b/crates/mun_hir/src/source_analyzer.rs index 25aff395..8b8e78fd 100644 --- a/crates/mun_hir/src/source_analyzer.rs +++ b/crates/mun_hir/src/source_analyzer.rs @@ -14,6 +14,7 @@ use crate::{ /// A `SourceAnalyzer` is a wrapper which exposes the HIR API in terms of the /// original source file. It's useful to query things from the syntax. +#[derive(Debug)] pub(crate) struct SourceAnalyzer { /// The file for which this analyzer was constructed pub(crate) file_id: FileId, diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index 925c50da..c3263718 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -8,8 +8,9 @@ use std::{fmt, iter::FromIterator, mem, ops::Deref, sync::Arc}; pub(crate) use infer::infer_query; pub use infer::InferenceResult; +pub use lower::TypableDef; pub(crate) use lower::{ - callable_item_sig, fn_sig_for_fn, type_for_def, type_for_impl_self, CallableDef, TypableDef, + callable_item_sig, fn_sig_for_fn, type_for_def, type_for_impl_self, CallableDef, }; pub use primitives::{FloatTy, IntTy}; pub use resolve::ResolveBitness; diff --git a/crates/mun_language_server/src/completion/dot.rs b/crates/mun_language_server/src/completion/dot.rs index 3a583328..f93f3a64 100644 --- a/crates/mun_language_server/src/completion/dot.rs +++ b/crates/mun_language_server/src/completion/dot.rs @@ -91,4 +91,34 @@ mod tests { Some(CompletionKind::Reference) )); } + + #[test] + fn test_param() { + insta::assert_snapshot!(completion_string( + r#" + struct Foo { bar: i32 } + + fn foo(bar: Foo) { + bar.$0 + } + "#, + Some(CompletionKind::Reference) + )); + } + + #[test] + fn test_self() { + insta::assert_snapshot!(completion_string( + r#" + struct Foo { bar: i32 } + + impl Foo { + fn foo(self) { + self.$0 + } + } + "#, + Some(CompletionKind::Reference) + )); + } } diff --git a/crates/mun_language_server/src/completion/expr.rs b/crates/mun_language_server/src/completion/expr.rs index c6f70426..56d9d94a 100644 --- a/crates/mun_language_server/src/completion/expr.rs +++ b/crates/mun_language_server/src/completion/expr.rs @@ -14,18 +14,25 @@ pub(super) fn complete_expr_path( ) { match qualified { Qualified::With { - resolution: Some(PathResolution::Def(ModuleDef::Struct(s))), + resolution: Some(resolution), .. } => { - let ty = s.ty(ctx.db); - MethodResolutionCtx::new(ctx.db, ty.clone()) - .with_association(AssociationMode::WithoutSelf) - .collect(|item, _visible| { - match item { - AssocItemId::FunctionId(f) => result.add_function(ctx, f.into(), None), - }; - None::<()> - }); + let ty = match resolution { + PathResolution::Def(ModuleDef::Struct(st)) => Some(st.ty(ctx.db)), + PathResolution::SelfType(imp) => Some(imp.self_ty(ctx.db)), + _ => None, + }; + + if let Some(ty) = ty { + MethodResolutionCtx::new(ctx.db, ty) + .with_association(AssociationMode::WithoutSelf) + .collect(|item, _visible| { + match item { + AssocItemId::FunctionId(f) => result.add_function(ctx, f.into(), None), + }; + None::<()> + }); + } } Qualified::No => { // Iterate over all items in the current scope and add completions for them @@ -74,4 +81,49 @@ mod tests { Some(CompletionKind::Reference) )); } + + #[test] + fn test_parameter() { + insta::assert_snapshot!(completion_string( + r#" + fn bar() { + let a = 0; + foo(f$0) + } + "#, + Some(CompletionKind::Reference) + )); + } + + #[test] + fn test_associated_self() { + insta::assert_snapshot!(completion_string( + r#" + struct Foo; + + impl Foo { + fn foo() { + Self::$0 + } + } + "#, + Some(CompletionKind::Reference) + )); + } + + #[test] + fn test_complete_self() { + insta::assert_snapshot!(completion_string( + r#" + struct Foo; + + impl Foo { + fn foo(self) { + $0 + } + } + "#, + Some(CompletionKind::Reference) + )); + } } diff --git a/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__dot__tests__param.snap b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__dot__tests__param.snap new file mode 100644 index 00000000..52fee917 --- /dev/null +++ b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__dot__tests__param.snap @@ -0,0 +1,5 @@ +--- +source: crates/mun_language_server/src/completion/dot.rs +expression: "completion_string(r#\"\n struct Foo { bar: i32 }\n\n fn foo(bar: Foo) {\n bar.$0\n }\n \"#,\nSome(CompletionKind::Reference))" +--- +fd bar i32 diff --git a/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__dot__tests__self.snap b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__dot__tests__self.snap new file mode 100644 index 00000000..6ed1e8c3 --- /dev/null +++ b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__dot__tests__self.snap @@ -0,0 +1,5 @@ +--- +source: crates/mun_language_server/src/completion/dot.rs +expression: "completion_string(r#\"\n struct Foo { bar: i32 }\n\n impl Foo {\n fn foo(self) {\n self.$0\n }\n }\n \"#,\nSome(CompletionKind::Reference))" +--- +fd bar i32 diff --git a/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__associated_self.snap b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__associated_self.snap new file mode 100644 index 00000000..9e367a49 --- /dev/null +++ b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__associated_self.snap @@ -0,0 +1,5 @@ +--- +source: crates/mun_language_server/src/completion/expr.rs +expression: "completion_string(r#\"\n struct Foo;\n\n impl Foo {\n fn foo() {\n Self::$0\n }\n }\n \"#,\nSome(CompletionKind::Reference))" +--- +fn foo -> () diff --git a/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__complete_self.snap b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__complete_self.snap new file mode 100644 index 00000000..e4578ec9 --- /dev/null +++ b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__complete_self.snap @@ -0,0 +1,7 @@ +--- +source: crates/mun_language_server/src/completion/expr.rs +expression: "completion_string(r#\"\n struct Foo;\n\n impl Foo {\n fn foo(self) {\n $0\n }\n }\n \"#,\nSome(CompletionKind::Reference))" +--- +lc self Foo +sp Self +st Foo diff --git a/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__parameter.snap b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__parameter.snap new file mode 100644 index 00000000..72fb9472 --- /dev/null +++ b/crates/mun_language_server/src/completion/snapshots/mun_language_server__completion__expr__tests__parameter.snap @@ -0,0 +1,6 @@ +--- +source: crates/mun_language_server/src/completion/expr.rs +expression: "completion_string(r#\"\n fn bar() {\n let a = 0;\n foo(f$0)\n }\n \"#,\nSome(CompletionKind::Reference))" +--- +lc a i32 +fn bar -> ()