diff options
33 files changed, 775 insertions, 253 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index b43d61d0e..c2b68a853 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -50,7 +50,6 @@ use hir_def::{ | |||
50 | per_ns::PerNs, | 50 | per_ns::PerNs, |
51 | resolver::{HasResolver, Resolver}, | 51 | resolver::{HasResolver, Resolver}, |
52 | src::HasSource as _, | 52 | src::HasSource as _, |
53 | type_ref::TraitRef, | ||
54 | AdtId, AssocContainerId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, | 53 | AdtId, AssocContainerId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, |
55 | DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, ImplId, LifetimeParamId, | 54 | DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, ImplId, LifetimeParamId, |
56 | LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId, | 55 | LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId, |
@@ -1797,9 +1796,11 @@ impl Impl { | |||
1797 | } | 1796 | } |
1798 | 1797 | ||
1799 | // FIXME: the return type is wrong. This should be a hir version of | 1798 | // FIXME: the return type is wrong. This should be a hir version of |
1800 | // `TraitRef` (ie, resolved `TypeRef`). | 1799 | // `TraitRef` (to account for parameters and qualifiers) |
1801 | pub fn trait_(self, db: &dyn HirDatabase) -> Option<TraitRef> { | 1800 | pub fn trait_(self, db: &dyn HirDatabase) -> Option<Trait> { |
1802 | db.impl_data(self.id).target_trait.as_deref().cloned() | 1801 | let trait_ref = db.impl_trait(self.id)?.skip_binders().clone(); |
1802 | let id = hir_ty::from_chalk_trait_id(trait_ref.trait_id); | ||
1803 | Some(Trait { id }) | ||
1803 | } | 1804 | } |
1804 | 1805 | ||
1805 | pub fn self_ty(self, db: &dyn HirDatabase) -> Type { | 1806 | pub fn self_ty(self, db: &dyn HirDatabase) -> Type { |
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index c7f2c02e4..2d08a7704 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs | |||
@@ -117,6 +117,16 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { | |||
117 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { | 117 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { |
118 | self.imp.expand(macro_call) | 118 | self.imp.expand(macro_call) |
119 | } | 119 | } |
120 | |||
121 | /// If `item` has an attribute macro attached to it, expands it. | ||
122 | pub fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> { | ||
123 | self.imp.expand_attr_macro(item) | ||
124 | } | ||
125 | |||
126 | pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool { | ||
127 | self.imp.is_attr_macro_call(item) | ||
128 | } | ||
129 | |||
120 | pub fn speculative_expand( | 130 | pub fn speculative_expand( |
121 | &self, | 131 | &self, |
122 | actual_macro_call: &ast::MacroCall, | 132 | actual_macro_call: &ast::MacroCall, |
@@ -332,6 +342,22 @@ impl<'db> SemanticsImpl<'db> { | |||
332 | Some(node) | 342 | Some(node) |
333 | } | 343 | } |
334 | 344 | ||
345 | fn expand_attr_macro(&self, item: &ast::Item) -> Option<SyntaxNode> { | ||
346 | let sa = self.analyze(item.syntax()); | ||
347 | let src = InFile::new(sa.file_id, item.clone()); | ||
348 | let macro_call_id = self.with_ctx(|ctx| ctx.item_to_macro_call(src))?; | ||
349 | let file_id = macro_call_id.as_file(); | ||
350 | let node = self.db.parse_or_expand(file_id)?; | ||
351 | self.cache(node.clone(), file_id); | ||
352 | Some(node) | ||
353 | } | ||
354 | |||
355 | fn is_attr_macro_call(&self, item: &ast::Item) -> bool { | ||
356 | let sa = self.analyze(item.syntax()); | ||
357 | let src = InFile::new(sa.file_id, item.clone()); | ||
358 | self.with_ctx(|ctx| ctx.item_to_macro_call(src).is_some()) | ||
359 | } | ||
360 | |||
335 | fn speculative_expand( | 361 | fn speculative_expand( |
336 | &self, | 362 | &self, |
337 | actual_macro_call: &ast::MacroCall, | 363 | actual_macro_call: &ast::MacroCall, |
@@ -362,25 +388,57 @@ impl<'db> SemanticsImpl<'db> { | |||
362 | 388 | ||
363 | let token = successors(Some(InFile::new(sa.file_id, token)), |token| { | 389 | let token = successors(Some(InFile::new(sa.file_id, token)), |token| { |
364 | self.db.unwind_if_cancelled(); | 390 | self.db.unwind_if_cancelled(); |
365 | let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; | 391 | |
366 | let tt = macro_call.token_tree()?; | 392 | for node in token.value.ancestors() { |
367 | if !tt.syntax().text_range().contains_range(token.value.text_range()) { | 393 | match_ast! { |
368 | return None; | 394 | match node { |
369 | } | 395 | ast::MacroCall(macro_call) => { |
370 | let file_id = sa.expand(self.db, token.with_value(¯o_call))?; | 396 | let tt = macro_call.token_tree()?; |
371 | let token = self | 397 | if !tt.syntax().text_range().contains_range(token.value.text_range()) { |
372 | .expansion_info_cache | 398 | return None; |
373 | .borrow_mut() | 399 | } |
374 | .entry(file_id) | 400 | let file_id = sa.expand(self.db, token.with_value(¯o_call))?; |
375 | .or_insert_with(|| file_id.expansion_info(self.db.upcast())) | 401 | let token = self |
376 | .as_ref()? | 402 | .expansion_info_cache |
377 | .map_token_down(token.as_ref())?; | 403 | .borrow_mut() |
378 | 404 | .entry(file_id) | |
379 | if let Some(parent) = token.value.parent() { | 405 | .or_insert_with(|| file_id.expansion_info(self.db.upcast())) |
380 | self.cache(find_root(&parent), token.file_id); | 406 | .as_ref()? |
407 | .map_token_down(token.as_ref())?; | ||
408 | |||
409 | if let Some(parent) = token.value.parent() { | ||
410 | self.cache(find_root(&parent), token.file_id); | ||
411 | } | ||
412 | |||
413 | return Some(token); | ||
414 | }, | ||
415 | ast::Item(item) => { | ||
416 | match self.with_ctx(|ctx| ctx.item_to_macro_call(token.with_value(item))) { | ||
417 | Some(call_id) => { | ||
418 | let file_id = call_id.as_file(); | ||
419 | let token = self | ||
420 | .expansion_info_cache | ||
421 | .borrow_mut() | ||
422 | .entry(file_id) | ||
423 | .or_insert_with(|| file_id.expansion_info(self.db.upcast())) | ||
424 | .as_ref()? | ||
425 | .map_token_down(token.as_ref())?; | ||
426 | |||
427 | if let Some(parent) = token.value.parent() { | ||
428 | self.cache(find_root(&parent), token.file_id); | ||
429 | } | ||
430 | |||
431 | return Some(token); | ||
432 | } | ||
433 | None => {} | ||
434 | } | ||
435 | }, | ||
436 | _ => {} | ||
437 | } | ||
438 | } | ||
381 | } | 439 | } |
382 | 440 | ||
383 | Some(token) | 441 | None |
384 | }) | 442 | }) |
385 | .last() | 443 | .last() |
386 | .unwrap(); | 444 | .unwrap(); |
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 9a5a2255f..22e196196 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs | |||
@@ -10,7 +10,7 @@ use hir_def::{ | |||
10 | ImplId, LifetimeParamId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, | 10 | ImplId, LifetimeParamId, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, |
11 | UnionId, VariantId, | 11 | UnionId, VariantId, |
12 | }; | 12 | }; |
13 | use hir_expand::{name::AsName, AstId, MacroDefKind}; | 13 | use hir_expand::{name::AsName, AstId, MacroCallId, MacroDefKind}; |
14 | use rustc_hash::FxHashMap; | 14 | use rustc_hash::FxHashMap; |
15 | use smallvec::SmallVec; | 15 | use smallvec::SmallVec; |
16 | use stdx::impl_from; | 16 | use stdx::impl_from; |
@@ -145,16 +145,25 @@ impl SourceToDefCtx<'_, '_> { | |||
145 | Some((container, label_id)) | 145 | Some((container, label_id)) |
146 | } | 146 | } |
147 | 147 | ||
148 | pub(super) fn item_to_macro_call(&mut self, src: InFile<ast::Item>) -> Option<MacroCallId> { | ||
149 | let map = self.dyn_map(src.as_ref())?; | ||
150 | map[keys::ATTR_MACRO].get(&src).copied() | ||
151 | } | ||
152 | |||
148 | fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>( | 153 | fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>( |
149 | &mut self, | 154 | &mut self, |
150 | src: InFile<Ast>, | 155 | src: InFile<Ast>, |
151 | key: Key<Ast, ID>, | 156 | key: Key<Ast, ID>, |
152 | ) -> Option<ID> { | 157 | ) -> Option<ID> { |
153 | let container = self.find_container(src.as_ref().map(|it| it.syntax()))?; | 158 | self.dyn_map(src.as_ref())?[key].get(&src).copied() |
159 | } | ||
160 | |||
161 | fn dyn_map<Ast: AstNode + 'static>(&mut self, src: InFile<&Ast>) -> Option<&DynMap> { | ||
162 | let container = self.find_container(src.map(|it| it.syntax()))?; | ||
154 | let db = self.db; | 163 | let db = self.db; |
155 | let dyn_map = | 164 | let dyn_map = |
156 | &*self.cache.entry(container).or_insert_with(|| container.child_by_source(db)); | 165 | &*self.cache.entry(container).or_insert_with(|| container.child_by_source(db)); |
157 | dyn_map[key].get(&src).copied() | 166 | Some(dyn_map) |
158 | } | 167 | } |
159 | 168 | ||
160 | pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> { | 169 | pub(super) fn type_param_to_def(&mut self, src: InFile<ast::TypeParam>) -> Option<TypeParamId> { |
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs index b7e72b790..3886b6c04 100644 --- a/crates/hir_def/src/attr.rs +++ b/crates/hir_def/src/attr.rs | |||
@@ -36,8 +36,8 @@ use crate::{ | |||
36 | pub struct Documentation(String); | 36 | pub struct Documentation(String); |
37 | 37 | ||
38 | impl Documentation { | 38 | impl Documentation { |
39 | pub fn new(s: impl Into<String>) -> Self { | 39 | pub fn new(s: String) -> Self { |
40 | Documentation(s.into()) | 40 | Documentation(s) |
41 | } | 41 | } |
42 | 42 | ||
43 | pub fn as_str(&self) -> &str { | 43 | pub fn as_str(&self) -> &str { |
diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs index f2e809ca9..f22383e22 100644 --- a/crates/hir_def/src/child_by_source.rs +++ b/crates/hir_def/src/child_by_source.rs | |||
@@ -85,6 +85,10 @@ impl ChildBySource for ItemScope { | |||
85 | res[keys::CONST].insert(src, konst); | 85 | res[keys::CONST].insert(src, konst); |
86 | }); | 86 | }); |
87 | self.impls().for_each(|imp| add_impl(db, res, imp)); | 87 | self.impls().for_each(|imp| add_impl(db, res, imp)); |
88 | self.attr_macro_invocs().for_each(|(ast_id, call_id)| { | ||
89 | let item = ast_id.with_value(ast_id.to_node(db.upcast())); | ||
90 | res[keys::ATTR_MACRO].insert(item, call_id); | ||
91 | }); | ||
88 | 92 | ||
89 | fn add_module_def(db: &dyn DefDatabase, map: &mut DynMap, item: ModuleDefId) { | 93 | fn add_module_def(db: &dyn DefDatabase, map: &mut DynMap, item: ModuleDefId) { |
90 | match item { | 94 | match item { |
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs index 9014468ea..0f74f050d 100644 --- a/crates/hir_def/src/item_scope.rs +++ b/crates/hir_def/src/item_scope.rs | |||
@@ -4,11 +4,11 @@ | |||
4 | use std::collections::hash_map::Entry; | 4 | use std::collections::hash_map::Entry; |
5 | 5 | ||
6 | use base_db::CrateId; | 6 | use base_db::CrateId; |
7 | use hir_expand::name::Name; | 7 | use hir_expand::{name::Name, AstId, MacroCallId, MacroDefKind}; |
8 | use hir_expand::MacroDefKind; | ||
9 | use once_cell::sync::Lazy; | 8 | use once_cell::sync::Lazy; |
10 | use rustc_hash::{FxHashMap, FxHashSet}; | 9 | use rustc_hash::{FxHashMap, FxHashSet}; |
11 | use stdx::format_to; | 10 | use stdx::format_to; |
11 | use syntax::ast; | ||
12 | 12 | ||
13 | use crate::{ | 13 | use crate::{ |
14 | db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId, | 14 | db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ConstId, ImplId, |
@@ -53,6 +53,7 @@ pub struct ItemScope { | |||
53 | // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will | 53 | // FIXME: Macro shadowing in one module is not properly handled. Non-item place macros will |
54 | // be all resolved to the last one defined if shadowing happens. | 54 | // be all resolved to the last one defined if shadowing happens. |
55 | legacy_macros: FxHashMap<Name, MacroDefId>, | 55 | legacy_macros: FxHashMap<Name, MacroDefId>, |
56 | attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>, | ||
56 | } | 57 | } |
57 | 58 | ||
58 | pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { | 59 | pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { |
@@ -169,6 +170,16 @@ impl ItemScope { | |||
169 | self.legacy_macros.insert(name, mac); | 170 | self.legacy_macros.insert(name, mac); |
170 | } | 171 | } |
171 | 172 | ||
173 | pub(crate) fn add_attr_macro_invoc(&mut self, item: AstId<ast::Item>, call: MacroCallId) { | ||
174 | self.attr_macros.insert(item, call); | ||
175 | } | ||
176 | |||
177 | pub(crate) fn attr_macro_invocs( | ||
178 | &self, | ||
179 | ) -> impl Iterator<Item = (AstId<ast::Item>, MacroCallId)> + '_ { | ||
180 | self.attr_macros.iter().map(|(k, v)| (*k, *v)) | ||
181 | } | ||
182 | |||
172 | pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { | 183 | pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { |
173 | self.unnamed_trait_imports.get(&tr).copied() | 184 | self.unnamed_trait_imports.get(&tr).copied() |
174 | } | 185 | } |
@@ -307,6 +318,7 @@ impl ItemScope { | |||
307 | unnamed_consts, | 318 | unnamed_consts, |
308 | unnamed_trait_imports, | 319 | unnamed_trait_imports, |
309 | legacy_macros, | 320 | legacy_macros, |
321 | attr_macros, | ||
310 | } = self; | 322 | } = self; |
311 | types.shrink_to_fit(); | 323 | types.shrink_to_fit(); |
312 | values.shrink_to_fit(); | 324 | values.shrink_to_fit(); |
@@ -317,6 +329,7 @@ impl ItemScope { | |||
317 | unnamed_consts.shrink_to_fit(); | 329 | unnamed_consts.shrink_to_fit(); |
318 | unnamed_trait_imports.shrink_to_fit(); | 330 | unnamed_trait_imports.shrink_to_fit(); |
319 | legacy_macros.shrink_to_fit(); | 331 | legacy_macros.shrink_to_fit(); |
332 | attr_macros.shrink_to_fit(); | ||
320 | } | 333 | } |
321 | } | 334 | } |
322 | 335 | ||
diff --git a/crates/hir_def/src/keys.rs b/crates/hir_def/src/keys.rs index 89b3ed868..688cd9fcf 100644 --- a/crates/hir_def/src/keys.rs +++ b/crates/hir_def/src/keys.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use std::marker::PhantomData; | 3 | use std::marker::PhantomData; |
4 | 4 | ||
5 | use hir_expand::{InFile, MacroDefId}; | 5 | use hir_expand::{InFile, MacroCallId, MacroDefId}; |
6 | use rustc_hash::FxHashMap; | 6 | use rustc_hash::FxHashMap; |
7 | use syntax::{ast, AstNode, AstPtr}; | 7 | use syntax::{ast, AstNode, AstPtr}; |
8 | 8 | ||
@@ -32,6 +32,7 @@ pub const LIFETIME_PARAM: Key<ast::LifetimeParam, LifetimeParamId> = Key::new(); | |||
32 | pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new(); | 32 | pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new(); |
33 | 33 | ||
34 | pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new(); | 34 | pub const MACRO: Key<ast::MacroCall, MacroDefId> = Key::new(); |
35 | pub const ATTR_MACRO: Key<ast::Item, MacroCallId> = Key::new(); | ||
35 | 36 | ||
36 | /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are | 37 | /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are |
37 | /// equal if they point to exactly the same object. | 38 | /// equal if they point to exactly the same object. |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index d0b1db5d1..d019ba3a9 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -1112,6 +1112,11 @@ impl DefCollector<'_> { | |||
1112 | return false; | 1112 | return false; |
1113 | } | 1113 | } |
1114 | } | 1114 | } |
1115 | |||
1116 | self.def_map.modules[directive.module_id] | ||
1117 | .scope | ||
1118 | .add_attr_macro_invoc(ast_id.ast_id, call_id); | ||
1119 | |||
1115 | resolved.push((directive.module_id, call_id, directive.depth)); | 1120 | resolved.push((directive.module_id, call_id, directive.depth)); |
1116 | res = ReachedFixedPoint::No; | 1121 | res = ReachedFixedPoint::No; |
1117 | return false; | 1122 | return false; |
diff --git a/crates/hir_expand/src/input.rs b/crates/hir_expand/src/input.rs index 40116a479..82dc7f326 100644 --- a/crates/hir_expand/src/input.rs +++ b/crates/hir_expand/src/input.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | //! Macro input conditioning. | 1 | //! Macro input conditioning. |
2 | 2 | ||
3 | use syntax::{ | 3 | use syntax::{ |
4 | ast::{self, AttrsOwner}, | 4 | ast::{self, make, AttrsOwner}, |
5 | AstNode, SyntaxNode, | 5 | AstNode, SyntaxNode, |
6 | }; | 6 | }; |
7 | 7 | ||
@@ -61,7 +61,9 @@ fn remove_attr_invoc(item: ast::Item, attr_index: usize) -> ast::Item { | |||
61 | .attrs() | 61 | .attrs() |
62 | .nth(attr_index) | 62 | .nth(attr_index) |
63 | .unwrap_or_else(|| panic!("cannot find attribute #{}", attr_index)); | 63 | .unwrap_or_else(|| panic!("cannot find attribute #{}", attr_index)); |
64 | attr.syntax().detach(); | 64 | let syntax_index = attr.syntax().index(); |
65 | let ws = make::tokens::whitespace(&" ".repeat(u32::from(attr.syntax().text().len()) as usize)); | ||
66 | item.syntax().splice_children(syntax_index..syntax_index + 1, vec![ws.into()]); | ||
65 | item | 67 | item |
66 | } | 68 | } |
67 | 69 | ||
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index e0d01fa96..3f38e2145 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs | |||
@@ -3,8 +3,7 @@ use std::iter; | |||
3 | use hir::Semantics; | 3 | use hir::Semantics; |
4 | use ide_db::RootDatabase; | 4 | use ide_db::RootDatabase; |
5 | use syntax::{ | 5 | use syntax::{ |
6 | algo::find_node_at_offset, ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, | 6 | ast, match_ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxKind::*, SyntaxNode, WalkEvent, T, |
7 | SyntaxNode, WalkEvent, T, | ||
8 | }; | 7 | }; |
9 | 8 | ||
10 | use crate::FilePosition; | 9 | use crate::FilePosition; |
@@ -28,16 +27,37 @@ pub struct ExpandedMacro { | |||
28 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 27 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
29 | let sema = Semantics::new(db); | 28 | let sema = Semantics::new(db); |
30 | let file = sema.parse(position.file_id); | 29 | let file = sema.parse(position.file_id); |
31 | let mac = find_node_at_offset::<ast::MacroCall>(file.syntax(), position.offset)?; | ||
32 | let name = mac.path()?.segment()?.name_ref()?; | ||
33 | 30 | ||
34 | let expanded = expand_macro_recur(&sema, &mac)?; | 31 | let tok = file.syntax().token_at_offset(position.offset).left_biased()?; |
32 | let mut expanded = None; | ||
33 | let mut name = None; | ||
34 | for node in tok.ancestors() { | ||
35 | match_ast! { | ||
36 | match node { | ||
37 | ast::MacroCall(mac) => { | ||
38 | name = Some(mac.path()?.segment()?.name_ref()?.to_string()); | ||
39 | expanded = expand_macro_recur(&sema, &mac); | ||
40 | break; | ||
41 | }, | ||
42 | ast::Item(item) => { | ||
43 | // FIXME: add the macro name | ||
44 | // FIXME: make this recursive too | ||
45 | name = Some("?".to_string()); | ||
46 | expanded = sema.expand_attr_macro(&item); | ||
47 | if expanded.is_some() { | ||
48 | break; | ||
49 | } | ||
50 | }, | ||
51 | _ => {} | ||
52 | } | ||
53 | } | ||
54 | } | ||
35 | 55 | ||
36 | // FIXME: | 56 | // FIXME: |
37 | // macro expansion may lose all white space information | 57 | // macro expansion may lose all white space information |
38 | // But we hope someday we can use ra_fmt for that | 58 | // But we hope someday we can use ra_fmt for that |
39 | let expansion = insert_whitespaces(expanded); | 59 | let expansion = insert_whitespaces(expanded?); |
40 | Some(ExpandedMacro { name: name.to_string(), expansion }) | 60 | Some(ExpandedMacro { name: name?, expansion }) |
41 | } | 61 | } |
42 | 62 | ||
43 | fn expand_macro_recur( | 63 | fn expand_macro_recur( |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index b0bfd646e..2d36c34e9 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,10 +1,10 @@ | |||
1 | use std::convert::TryInto; | 1 | use std::convert::TryInto; |
2 | 2 | ||
3 | use either::Either; | 3 | use either::Either; |
4 | use hir::{InFile, Semantics}; | 4 | use hir::{AsAssocItem, InFile, ModuleDef, Semantics}; |
5 | use ide_db::{ | 5 | use ide_db::{ |
6 | base_db::{AnchoredPath, FileId, FileLoader}, | 6 | base_db::{AnchoredPath, FileId, FileLoader}, |
7 | defs::{NameClass, NameRefClass}, | 7 | defs::{Definition, NameClass, NameRefClass}, |
8 | RootDatabase, | 8 | RootDatabase, |
9 | }; | 9 | }; |
10 | use syntax::{ | 10 | use syntax::{ |
@@ -57,7 +57,8 @@ pub(crate) fn goto_definition( | |||
57 | }, | 57 | }, |
58 | ast::Name(name) => { | 58 | ast::Name(name) => { |
59 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); | 59 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); |
60 | def.try_to_nav(sema.db) | 60 | try_find_trait_item_definition(&sema.db, &def) |
61 | .or_else(|| def.try_to_nav(sema.db)) | ||
61 | }, | 62 | }, |
62 | ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) { | 63 | ast::Lifetime(lt) => if let Some(name_class) = NameClass::classify_lifetime(&sema, <) { |
63 | let def = name_class.referenced_or_defined(sema.db); | 64 | let def = name_class.referenced_or_defined(sema.db); |
@@ -99,6 +100,34 @@ fn try_lookup_include_path( | |||
99 | }) | 100 | }) |
100 | } | 101 | } |
101 | 102 | ||
103 | /// finds the trait definition of an impl'd item | ||
104 | /// e.g. | ||
105 | /// ```rust | ||
106 | /// trait A { fn a(); } | ||
107 | /// struct S; | ||
108 | /// impl A for S { fn a(); } // <-- on this function, will get the location of a() in the trait | ||
109 | /// ``` | ||
110 | fn try_find_trait_item_definition(db: &RootDatabase, def: &Definition) -> Option<NavigationTarget> { | ||
111 | let name = def.name(db)?; | ||
112 | let assoc = match def { | ||
113 | Definition::ModuleDef(ModuleDef::Function(f)) => f.as_assoc_item(db), | ||
114 | Definition::ModuleDef(ModuleDef::Const(c)) => c.as_assoc_item(db), | ||
115 | Definition::ModuleDef(ModuleDef::TypeAlias(ty)) => ty.as_assoc_item(db), | ||
116 | _ => None, | ||
117 | }?; | ||
118 | |||
119 | let imp = match assoc.container(db) { | ||
120 | hir::AssocItemContainer::Impl(imp) => imp, | ||
121 | _ => return None, | ||
122 | }; | ||
123 | |||
124 | let trait_ = imp.trait_(db)?; | ||
125 | trait_ | ||
126 | .items(db) | ||
127 | .iter() | ||
128 | .find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten()) | ||
129 | } | ||
130 | |||
102 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 131 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
103 | return tokens.max_by_key(priority); | 132 | return tokens.max_by_key(priority); |
104 | fn priority(n: &SyntaxToken) -> usize { | 133 | fn priority(n: &SyntaxToken) -> usize { |
@@ -1262,4 +1291,58 @@ fn main() { | |||
1262 | "#, | 1291 | "#, |
1263 | ); | 1292 | ); |
1264 | } | 1293 | } |
1294 | |||
1295 | #[test] | ||
1296 | fn goto_def_of_trait_impl_fn() { | ||
1297 | check( | ||
1298 | r#" | ||
1299 | trait Twait { | ||
1300 | fn a(); | ||
1301 | // ^ | ||
1302 | } | ||
1303 | |||
1304 | struct Stwuct; | ||
1305 | |||
1306 | impl Twait for Stwuct { | ||
1307 | fn a$0(); | ||
1308 | } | ||
1309 | "#, | ||
1310 | ); | ||
1311 | } | ||
1312 | |||
1313 | #[test] | ||
1314 | fn goto_def_of_trait_impl_const() { | ||
1315 | check( | ||
1316 | r#" | ||
1317 | trait Twait { | ||
1318 | const NOMS: bool; | ||
1319 | // ^^^^ | ||
1320 | } | ||
1321 | |||
1322 | struct Stwuct; | ||
1323 | |||
1324 | impl Twait for Stwuct { | ||
1325 | const NOMS$0: bool = true; | ||
1326 | } | ||
1327 | "#, | ||
1328 | ); | ||
1329 | } | ||
1330 | |||
1331 | #[test] | ||
1332 | fn goto_def_of_trait_impl_type_alias() { | ||
1333 | check( | ||
1334 | r#" | ||
1335 | trait Twait { | ||
1336 | type IsBad; | ||
1337 | // ^^^^^ | ||
1338 | } | ||
1339 | |||
1340 | struct Stwuct; | ||
1341 | |||
1342 | impl Twait for Stwuct { | ||
1343 | type IsBad$0 = !; | ||
1344 | } | ||
1345 | "#, | ||
1346 | ); | ||
1347 | } | ||
1265 | } | 1348 | } |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 79c2f4a1e..b03f1c71f 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -192,6 +192,7 @@ fn traverse( | |||
192 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); | 192 | let mut bindings_shadow_count: FxHashMap<Name, u32> = FxHashMap::default(); |
193 | 193 | ||
194 | let mut current_macro_call: Option<ast::MacroCall> = None; | 194 | let mut current_macro_call: Option<ast::MacroCall> = None; |
195 | let mut current_attr_macro_call = None; | ||
195 | let mut current_macro: Option<ast::Macro> = None; | 196 | let mut current_macro: Option<ast::Macro> = None; |
196 | let mut macro_highlighter = MacroHighlighter::default(); | 197 | let mut macro_highlighter = MacroHighlighter::default(); |
197 | let mut inside_attribute = false; | 198 | let mut inside_attribute = false; |
@@ -227,6 +228,19 @@ fn traverse( | |||
227 | } | 228 | } |
228 | _ => (), | 229 | _ => (), |
229 | } | 230 | } |
231 | match event.clone().map(|it| it.into_node().and_then(ast::Item::cast)) { | ||
232 | WalkEvent::Enter(Some(item)) => { | ||
233 | if sema.is_attr_macro_call(&item) { | ||
234 | current_attr_macro_call = Some(item); | ||
235 | } | ||
236 | } | ||
237 | WalkEvent::Leave(Some(item)) => { | ||
238 | if current_attr_macro_call == Some(item) { | ||
239 | current_attr_macro_call = None; | ||
240 | } | ||
241 | } | ||
242 | _ => (), | ||
243 | } | ||
230 | 244 | ||
231 | match event.clone().map(|it| it.into_node().and_then(ast::Macro::cast)) { | 245 | match event.clone().map(|it| it.into_node().and_then(ast::Macro::cast)) { |
232 | WalkEvent::Enter(Some(mac)) => { | 246 | WalkEvent::Enter(Some(mac)) => { |
@@ -286,6 +300,22 @@ fn traverse( | |||
286 | } | 300 | } |
287 | None => token.into(), | 301 | None => token.into(), |
288 | } | 302 | } |
303 | } else if current_attr_macro_call.is_some() { | ||
304 | let token = match element.clone().into_token() { | ||
305 | Some(it) => it, | ||
306 | _ => continue, | ||
307 | }; | ||
308 | let token = sema.descend_into_macros(token.clone()); | ||
309 | match token.parent() { | ||
310 | Some(parent) => { | ||
311 | // We only care Name and Name_ref | ||
312 | match (token.kind(), parent.kind()) { | ||
313 | (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), | ||
314 | _ => token.into(), | ||
315 | } | ||
316 | } | ||
317 | None => token.into(), | ||
318 | } | ||
289 | } else { | 319 | } else { |
290 | element.clone() | 320 | element.clone() |
291 | }; | 321 | }; |
diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 70949ca35..fc5a17f05 100644 --- a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs | |||
@@ -1,3 +1,4 @@ | |||
1 | use either::Either; | ||
1 | use ide_db::defs::{Definition, NameRefClass}; | 2 | use ide_db::defs::{Definition, NameRefClass}; |
2 | use syntax::{ | 3 | use syntax::{ |
3 | ast::{self, AstNode, GenericParamsOwner, VisibilityOwner}, | 4 | ast::{self, AstNode, GenericParamsOwner, VisibilityOwner}, |
@@ -8,7 +9,7 @@ use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, | |||
8 | 9 | ||
9 | // Assist: convert_tuple_struct_to_named_struct | 10 | // Assist: convert_tuple_struct_to_named_struct |
10 | // | 11 | // |
11 | // Converts tuple struct to struct with named fields. | 12 | // Converts tuple struct to struct with named fields, and analogously for tuple enum variants. |
12 | // | 13 | // |
13 | // ``` | 14 | // ``` |
14 | // struct Point$0(f32, f32); | 15 | // struct Point$0(f32, f32); |
@@ -49,14 +50,21 @@ pub(crate) fn convert_tuple_struct_to_named_struct( | |||
49 | acc: &mut Assists, | 50 | acc: &mut Assists, |
50 | ctx: &AssistContext, | 51 | ctx: &AssistContext, |
51 | ) -> Option<()> { | 52 | ) -> Option<()> { |
52 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | 53 | let strukt = ctx |
53 | let tuple_fields = match strukt.field_list()? { | 54 | .find_node_at_offset::<ast::Struct>() |
55 | .map(Either::Left) | ||
56 | .or_else(|| ctx.find_node_at_offset::<ast::Variant>().map(Either::Right))?; | ||
57 | let field_list = strukt.as_ref().either(|s| s.field_list(), |v| v.field_list())?; | ||
58 | let tuple_fields = match field_list { | ||
54 | ast::FieldList::TupleFieldList(it) => it, | 59 | ast::FieldList::TupleFieldList(it) => it, |
55 | ast::FieldList::RecordFieldList(_) => return None, | 60 | ast::FieldList::RecordFieldList(_) => return None, |
56 | }; | 61 | }; |
57 | let strukt_def = ctx.sema.to_def(&strukt)?; | 62 | let strukt_def = match &strukt { |
63 | Either::Left(s) => Either::Left(ctx.sema.to_def(s)?), | ||
64 | Either::Right(v) => Either::Right(ctx.sema.to_def(v)?), | ||
65 | }; | ||
66 | let target = strukt.as_ref().either(|s| s.syntax(), |v| v.syntax()).text_range(); | ||
58 | 67 | ||
59 | let target = strukt.syntax().text_range(); | ||
60 | acc.add( | 68 | acc.add( |
61 | AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite), | 69 | AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite), |
62 | "Convert to named struct", | 70 | "Convert to named struct", |
@@ -73,7 +81,7 @@ pub(crate) fn convert_tuple_struct_to_named_struct( | |||
73 | fn edit_struct_def( | 81 | fn edit_struct_def( |
74 | ctx: &AssistContext, | 82 | ctx: &AssistContext, |
75 | edit: &mut AssistBuilder, | 83 | edit: &mut AssistBuilder, |
76 | strukt: &ast::Struct, | 84 | strukt: &Either<ast::Struct, ast::Variant>, |
77 | tuple_fields: ast::TupleFieldList, | 85 | tuple_fields: ast::TupleFieldList, |
78 | names: Vec<ast::Name>, | 86 | names: Vec<ast::Name>, |
79 | ) { | 87 | ) { |
@@ -86,27 +94,40 @@ fn edit_struct_def( | |||
86 | 94 | ||
87 | edit.edit_file(ctx.frange.file_id); | 95 | edit.edit_file(ctx.frange.file_id); |
88 | 96 | ||
89 | if let Some(w) = strukt.where_clause() { | 97 | if let Either::Left(strukt) = strukt { |
90 | edit.delete(w.syntax().text_range()); | 98 | if let Some(w) = strukt.where_clause() { |
91 | edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); | 99 | edit.delete(w.syntax().text_range()); |
92 | edit.insert(tuple_fields_text_range.start(), w.syntax().text()); | 100 | edit.insert( |
93 | edit.insert(tuple_fields_text_range.start(), ","); | 101 | tuple_fields_text_range.start(), |
94 | edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text()); | 102 | ast::make::tokens::single_newline().text(), |
103 | ); | ||
104 | edit.insert(tuple_fields_text_range.start(), w.syntax().text()); | ||
105 | edit.insert(tuple_fields_text_range.start(), ","); | ||
106 | edit.insert( | ||
107 | tuple_fields_text_range.start(), | ||
108 | ast::make::tokens::single_newline().text(), | ||
109 | ); | ||
110 | } else { | ||
111 | edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); | ||
112 | } | ||
113 | strukt.semicolon_token().map(|t| edit.delete(t.text_range())); | ||
95 | } else { | 114 | } else { |
96 | edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); | 115 | edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text()); |
97 | } | 116 | } |
98 | 117 | ||
99 | edit.replace(tuple_fields_text_range, record_fields.to_string()); | 118 | edit.replace(tuple_fields_text_range, record_fields.to_string()); |
100 | strukt.semicolon_token().map(|t| edit.delete(t.text_range())); | ||
101 | } | 119 | } |
102 | 120 | ||
103 | fn edit_struct_references( | 121 | fn edit_struct_references( |
104 | ctx: &AssistContext, | 122 | ctx: &AssistContext, |
105 | edit: &mut AssistBuilder, | 123 | edit: &mut AssistBuilder, |
106 | strukt: hir::Struct, | 124 | strukt: Either<hir::Struct, hir::Variant>, |
107 | names: &[ast::Name], | 125 | names: &[ast::Name], |
108 | ) { | 126 | ) { |
109 | let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt))); | 127 | let strukt_def = match strukt { |
128 | Either::Left(s) => Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(s))), | ||
129 | Either::Right(v) => Definition::ModuleDef(hir::ModuleDef::Variant(v)), | ||
130 | }; | ||
110 | let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); | 131 | let usages = strukt_def.usages(&ctx.sema).include_self_refs().all(); |
111 | 132 | ||
112 | let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { | 133 | let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> { |
@@ -513,4 +534,305 @@ where | |||
513 | "#, | 534 | "#, |
514 | ); | 535 | ); |
515 | } | 536 | } |
537 | #[test] | ||
538 | fn not_applicable_other_than_tuple_variant() { | ||
539 | check_assist_not_applicable( | ||
540 | convert_tuple_struct_to_named_struct, | ||
541 | r#"enum Enum { Variant$0 { value: usize } };"#, | ||
542 | ); | ||
543 | check_assist_not_applicable( | ||
544 | convert_tuple_struct_to_named_struct, | ||
545 | r#"enum Enum { Variant$0 }"#, | ||
546 | ); | ||
547 | } | ||
548 | |||
549 | #[test] | ||
550 | fn convert_simple_variant() { | ||
551 | check_assist( | ||
552 | convert_tuple_struct_to_named_struct, | ||
553 | r#" | ||
554 | enum A { | ||
555 | $0Variant(usize), | ||
556 | } | ||
557 | |||
558 | impl A { | ||
559 | fn new(value: usize) -> A { | ||
560 | A::Variant(value) | ||
561 | } | ||
562 | |||
563 | fn new_with_default() -> A { | ||
564 | A::new(Default::default()) | ||
565 | } | ||
566 | |||
567 | fn value(self) -> usize { | ||
568 | match self { | ||
569 | A::Variant(value) => value, | ||
570 | } | ||
571 | } | ||
572 | }"#, | ||
573 | r#" | ||
574 | enum A { | ||
575 | Variant { field1: usize }, | ||
576 | } | ||
577 | |||
578 | impl A { | ||
579 | fn new(value: usize) -> A { | ||
580 | A::Variant { field1: value } | ||
581 | } | ||
582 | |||
583 | fn new_with_default() -> A { | ||
584 | A::new(Default::default()) | ||
585 | } | ||
586 | |||
587 | fn value(self) -> usize { | ||
588 | match self { | ||
589 | A::Variant { field1: value } => value, | ||
590 | } | ||
591 | } | ||
592 | }"#, | ||
593 | ); | ||
594 | } | ||
595 | |||
596 | #[test] | ||
597 | fn convert_variant_referenced_via_self_kw() { | ||
598 | check_assist( | ||
599 | convert_tuple_struct_to_named_struct, | ||
600 | r#" | ||
601 | enum A { | ||
602 | $0Variant(usize), | ||
603 | } | ||
604 | |||
605 | impl A { | ||
606 | fn new(value: usize) -> A { | ||
607 | Self::Variant(value) | ||
608 | } | ||
609 | |||
610 | fn new_with_default() -> A { | ||
611 | Self::new(Default::default()) | ||
612 | } | ||
613 | |||
614 | fn value(self) -> usize { | ||
615 | match self { | ||
616 | Self::Variant(value) => value, | ||
617 | } | ||
618 | } | ||
619 | }"#, | ||
620 | r#" | ||
621 | enum A { | ||
622 | Variant { field1: usize }, | ||
623 | } | ||
624 | |||
625 | impl A { | ||
626 | fn new(value: usize) -> A { | ||
627 | Self::Variant { field1: value } | ||
628 | } | ||
629 | |||
630 | fn new_with_default() -> A { | ||
631 | Self::new(Default::default()) | ||
632 | } | ||
633 | |||
634 | fn value(self) -> usize { | ||
635 | match self { | ||
636 | Self::Variant { field1: value } => value, | ||
637 | } | ||
638 | } | ||
639 | }"#, | ||
640 | ); | ||
641 | } | ||
642 | |||
643 | #[test] | ||
644 | fn convert_destructured_variant() { | ||
645 | check_assist( | ||
646 | convert_tuple_struct_to_named_struct, | ||
647 | r#" | ||
648 | enum A { | ||
649 | $0Variant(usize), | ||
650 | } | ||
651 | |||
652 | impl A { | ||
653 | fn into_inner(self) -> usize { | ||
654 | let A::Variant(first) = self; | ||
655 | first | ||
656 | } | ||
657 | |||
658 | fn into_inner_via_self(self) -> usize { | ||
659 | let Self::Variant(first) = self; | ||
660 | first | ||
661 | } | ||
662 | }"#, | ||
663 | r#" | ||
664 | enum A { | ||
665 | Variant { field1: usize }, | ||
666 | } | ||
667 | |||
668 | impl A { | ||
669 | fn into_inner(self) -> usize { | ||
670 | let A::Variant { field1: first } = self; | ||
671 | first | ||
672 | } | ||
673 | |||
674 | fn into_inner_via_self(self) -> usize { | ||
675 | let Self::Variant { field1: first } = self; | ||
676 | first | ||
677 | } | ||
678 | }"#, | ||
679 | ); | ||
680 | } | ||
681 | |||
682 | #[test] | ||
683 | fn convert_variant_with_wrapped_references() { | ||
684 | check_assist( | ||
685 | convert_tuple_struct_to_named_struct, | ||
686 | r#" | ||
687 | enum Inner { | ||
688 | $0Variant(usize), | ||
689 | } | ||
690 | enum Outer { | ||
691 | Variant(Inner), | ||
692 | } | ||
693 | |||
694 | impl Outer { | ||
695 | fn new() -> Self { | ||
696 | Self::Variant(Inner::Variant(42)) | ||
697 | } | ||
698 | |||
699 | fn into_inner_destructed(self) -> u32 { | ||
700 | let Outer::Variant(Inner::Variant(x)) = self; | ||
701 | x | ||
702 | } | ||
703 | }"#, | ||
704 | r#" | ||
705 | enum Inner { | ||
706 | Variant { field1: usize }, | ||
707 | } | ||
708 | enum Outer { | ||
709 | Variant(Inner), | ||
710 | } | ||
711 | |||
712 | impl Outer { | ||
713 | fn new() -> Self { | ||
714 | Self::Variant(Inner::Variant { field1: 42 }) | ||
715 | } | ||
716 | |||
717 | fn into_inner_destructed(self) -> u32 { | ||
718 | let Outer::Variant(Inner::Variant { field1: x }) = self; | ||
719 | x | ||
720 | } | ||
721 | }"#, | ||
722 | ); | ||
723 | |||
724 | check_assist( | ||
725 | convert_tuple_struct_to_named_struct, | ||
726 | r#" | ||
727 | enum Inner { | ||
728 | Variant(usize), | ||
729 | } | ||
730 | enum Outer { | ||
731 | $0Variant(Inner), | ||
732 | } | ||
733 | |||
734 | impl Outer { | ||
735 | fn new() -> Self { | ||
736 | Self::Variant(Inner::Variant(42)) | ||
737 | } | ||
738 | |||
739 | fn into_inner_destructed(self) -> u32 { | ||
740 | let Outer::Variant(Inner::Variant(x)) = self; | ||
741 | x | ||
742 | } | ||
743 | }"#, | ||
744 | r#" | ||
745 | enum Inner { | ||
746 | Variant(usize), | ||
747 | } | ||
748 | enum Outer { | ||
749 | Variant { field1: Inner }, | ||
750 | } | ||
751 | |||
752 | impl Outer { | ||
753 | fn new() -> Self { | ||
754 | Self::Variant { field1: Inner::Variant(42) } | ||
755 | } | ||
756 | |||
757 | fn into_inner_destructed(self) -> u32 { | ||
758 | let Outer::Variant { field1: Inner::Variant(x) } = self; | ||
759 | x | ||
760 | } | ||
761 | }"#, | ||
762 | ); | ||
763 | } | ||
764 | |||
765 | #[test] | ||
766 | fn convert_variant_with_multi_file_references() { | ||
767 | check_assist( | ||
768 | convert_tuple_struct_to_named_struct, | ||
769 | r#" | ||
770 | //- /main.rs | ||
771 | struct Inner; | ||
772 | enum A { | ||
773 | $0Variant(Inner), | ||
774 | } | ||
775 | |||
776 | mod foo; | ||
777 | |||
778 | //- /foo.rs | ||
779 | use crate::{A, Inner}; | ||
780 | fn f() { | ||
781 | let a = A::Variant(Inner); | ||
782 | } | ||
783 | "#, | ||
784 | r#" | ||
785 | //- /main.rs | ||
786 | struct Inner; | ||
787 | enum A { | ||
788 | Variant { field1: Inner }, | ||
789 | } | ||
790 | |||
791 | mod foo; | ||
792 | |||
793 | //- /foo.rs | ||
794 | use crate::{A, Inner}; | ||
795 | fn f() { | ||
796 | let a = A::Variant { field1: Inner }; | ||
797 | } | ||
798 | "#, | ||
799 | ); | ||
800 | } | ||
801 | |||
802 | #[test] | ||
803 | fn convert_directly_used_variant() { | ||
804 | check_assist( | ||
805 | convert_tuple_struct_to_named_struct, | ||
806 | r#" | ||
807 | //- /main.rs | ||
808 | struct Inner; | ||
809 | enum A { | ||
810 | $0Variant(Inner), | ||
811 | } | ||
812 | |||
813 | mod foo; | ||
814 | |||
815 | //- /foo.rs | ||
816 | use crate::{A::Variant, Inner}; | ||
817 | fn f() { | ||
818 | let a = Variant(Inner); | ||
819 | } | ||
820 | "#, | ||
821 | r#" | ||
822 | //- /main.rs | ||
823 | struct Inner; | ||
824 | enum A { | ||
825 | Variant { field1: Inner }, | ||
826 | } | ||
827 | |||
828 | mod foo; | ||
829 | |||
830 | //- /foo.rs | ||
831 | use crate::{A::Variant, Inner}; | ||
832 | fn f() { | ||
833 | let a = Variant { field1: Inner }; | ||
834 | } | ||
835 | "#, | ||
836 | ); | ||
837 | } | ||
516 | } | 838 | } |
diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index ffdcdc930..7a4d71e91 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs | |||
@@ -6,7 +6,6 @@ pub(crate) mod flyimport; | |||
6 | pub(crate) mod fn_param; | 6 | pub(crate) mod fn_param; |
7 | pub(crate) mod keyword; | 7 | pub(crate) mod keyword; |
8 | pub(crate) mod lifetime; | 8 | pub(crate) mod lifetime; |
9 | pub(crate) mod macro_in_item_position; | ||
10 | pub(crate) mod mod_; | 9 | pub(crate) mod mod_; |
11 | pub(crate) mod pattern; | 10 | pub(crate) mod pattern; |
12 | pub(crate) mod postfix; | 11 | pub(crate) mod postfix; |
diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index e0a7021fd..8ad57a069 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs | |||
@@ -4,7 +4,7 @@ use either::Either; | |||
4 | use hir::{HasVisibility, ScopeDef}; | 4 | use hir::{HasVisibility, ScopeDef}; |
5 | use rustc_hash::FxHashSet; | 5 | use rustc_hash::FxHashSet; |
6 | 6 | ||
7 | use crate::{context::CompletionContext, Completions}; | 7 | use crate::{context::CompletionContext, patterns::ImmediateLocation, Completions}; |
8 | 8 | ||
9 | /// Complete dot accesses, i.e. fields or methods. | 9 | /// Complete dot accesses, i.e. fields or methods. |
10 | pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { |
@@ -18,7 +18,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | |||
18 | _ => return, | 18 | _ => return, |
19 | }; | 19 | }; |
20 | 20 | ||
21 | if ctx.is_call { | 21 | if matches!(ctx.completion_location, Some(ImmediateLocation::MethodCall { .. })) { |
22 | cov_mark::hit!(test_no_struct_field_completion_for_method_call); | 22 | cov_mark::hit!(test_no_struct_field_completion_for_method_call); |
23 | } else { | 23 | } else { |
24 | complete_fields(ctx, &receiver_ty, |field, ty| match field { | 24 | complete_fields(ctx, &receiver_ty, |field, ty| match field { |
@@ -33,7 +33,7 @@ fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { | |||
33 | if !ctx.config.enable_self_on_the_fly { | 33 | if !ctx.config.enable_self_on_the_fly { |
34 | return; | 34 | return; |
35 | } | 35 | } |
36 | if !ctx.is_trivial_path || ctx.is_path_disallowed() { | 36 | if !ctx.is_trivial_path() || ctx.is_path_disallowed() { |
37 | return; | 37 | return; |
38 | } | 38 | } |
39 | ctx.scope.process_all_names(&mut |name, def| { | 39 | ctx.scope.process_all_names(&mut |name, def| { |
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index d72bf13d3..7bf47bf75 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs | |||
@@ -161,13 +161,13 @@ pub(crate) fn position_for_import<'a>( | |||
161 | ) -> Option<&'a SyntaxNode> { | 161 | ) -> Option<&'a SyntaxNode> { |
162 | Some(match import_candidate { | 162 | Some(match import_candidate { |
163 | Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(), | 163 | Some(ImportCandidate::Path(_)) => ctx.name_ref_syntax.as_ref()?.syntax(), |
164 | Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual.as_ref()?.syntax(), | 164 | Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual()?.syntax(), |
165 | Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver()?.syntax(), | 165 | Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver()?.syntax(), |
166 | None => ctx | 166 | None => ctx |
167 | .name_ref_syntax | 167 | .name_ref_syntax |
168 | .as_ref() | 168 | .as_ref() |
169 | .map(|name_ref| name_ref.syntax()) | 169 | .map(|name_ref| name_ref.syntax()) |
170 | .or_else(|| ctx.path_qual.as_ref().map(|path| path.syntax())) | 170 | .or_else(|| ctx.path_qual().map(|path| path.syntax())) |
171 | .or_else(|| ctx.dot_receiver().map(|expr| expr.syntax()))?, | 171 | .or_else(|| ctx.dot_receiver().map(|expr| expr.syntax()))?, |
172 | }) | 172 | }) |
173 | } | 173 | } |
@@ -190,7 +190,7 @@ fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAs | |||
190 | }; | 190 | }; |
191 | let assets_for_path = ImportAssets::for_fuzzy_path( | 191 | let assets_for_path = ImportAssets::for_fuzzy_path( |
192 | current_module, | 192 | current_module, |
193 | ctx.path_qual.clone(), | 193 | ctx.path_qual().cloned(), |
194 | fuzzy_name, | 194 | fuzzy_name, |
195 | &ctx.sema, | 195 | &ctx.sema, |
196 | approximate_node, | 196 | approximate_node, |
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index 1a7a484a4..ba13d3707 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs | |||
@@ -5,8 +5,8 @@ use std::iter; | |||
5 | use syntax::{SyntaxKind, T}; | 5 | use syntax::{SyntaxKind, T}; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind, | 8 | context::PathCompletionContext, patterns::ImmediateLocation, CompletionContext, CompletionItem, |
9 | CompletionKind, Completions, | 9 | CompletionItemKind, CompletionKind, Completions, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { | 12 | pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { |
@@ -19,11 +19,12 @@ pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC | |||
19 | }; | 19 | }; |
20 | 20 | ||
21 | if ctx.use_item_syntax.is_some() { | 21 | if ctx.use_item_syntax.is_some() { |
22 | if ctx.path_qual.is_none() { | 22 | let qual = ctx.path_qual(); |
23 | if qual.is_none() { | ||
23 | kw_completion("crate::").add_to(acc); | 24 | kw_completion("crate::").add_to(acc); |
24 | } | 25 | } |
25 | kw_completion("self").add_to(acc); | 26 | kw_completion("self").add_to(acc); |
26 | if iter::successors(ctx.path_qual.clone(), |p| p.qualifier()) | 27 | if iter::successors(qual.cloned(), |p| p.qualifier()) |
27 | .all(|p| p.segment().and_then(|s| s.super_token()).is_some()) | 28 | .all(|p| p.segment().and_then(|s| s.super_token()).is_some()) |
28 | { | 29 | { |
29 | kw_completion("super::").add_to(acc); | 30 | kw_completion("super::").add_to(acc); |
@@ -127,8 +128,15 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
127 | add_keyword("mut", "mut "); | 128 | add_keyword("mut", "mut "); |
128 | } | 129 | } |
129 | 130 | ||
130 | if ctx.in_loop_body { | 131 | let (can_be_stmt, in_loop_body) = match ctx.path_context { |
131 | if ctx.can_be_stmt { | 132 | Some(PathCompletionContext { |
133 | is_trivial_path: true, can_be_stmt, in_loop_body, .. | ||
134 | }) => (can_be_stmt, in_loop_body), | ||
135 | _ => return, | ||
136 | }; | ||
137 | |||
138 | if in_loop_body { | ||
139 | if can_be_stmt { | ||
132 | add_keyword("continue", "continue;"); | 140 | add_keyword("continue", "continue;"); |
133 | add_keyword("break", "break;"); | 141 | add_keyword("break", "break;"); |
134 | } else { | 142 | } else { |
@@ -137,9 +145,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
137 | } | 145 | } |
138 | } | 146 | } |
139 | 147 | ||
140 | if !ctx.is_trivial_path { | ||
141 | return; | ||
142 | } | ||
143 | let fn_def = match &ctx.function_def { | 148 | let fn_def = match &ctx.function_def { |
144 | Some(it) => it, | 149 | Some(it) => it, |
145 | None => return, | 150 | None => return, |
@@ -147,7 +152,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
147 | 152 | ||
148 | add_keyword( | 153 | add_keyword( |
149 | "return", | 154 | "return", |
150 | match (ctx.can_be_stmt, fn_def.ret_type().is_some()) { | 155 | match (can_be_stmt, fn_def.ret_type().is_some()) { |
151 | (true, true) => "return $0;", | 156 | (true, true) => "return $0;", |
152 | (true, false) => "return;", | 157 | (true, false) => "return;", |
153 | (false, true) => "return $0", | 158 | (false, true) => "return $0", |
diff --git a/crates/ide_completion/src/completions/macro_in_item_position.rs b/crates/ide_completion/src/completions/macro_in_item_position.rs deleted file mode 100644 index 781b96ff1..000000000 --- a/crates/ide_completion/src/completions/macro_in_item_position.rs +++ /dev/null | |||
@@ -1,48 +0,0 @@ | |||
1 | //! Completes macro invocations used in item position. | ||
2 | |||
3 | use crate::{CompletionContext, Completions}; | ||
4 | |||
5 | // Ideally this should be removed and moved into `(un)qualified_path` respectively | ||
6 | pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { | ||
7 | // Show only macros in top level. | ||
8 | if !ctx.expects_item() { | ||
9 | return; | ||
10 | } | ||
11 | |||
12 | ctx.scope.process_all_names(&mut |name, res| { | ||
13 | if let hir::ScopeDef::MacroDef(mac) = res { | ||
14 | acc.add_macro(ctx, Some(name.clone()), mac); | ||
15 | } | ||
16 | // FIXME: This should be done in qualified_path/unqualified_path instead? | ||
17 | if let hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { | ||
18 | acc.add_resolution(ctx, name, &res); | ||
19 | } | ||
20 | }) | ||
21 | } | ||
22 | |||
23 | #[cfg(test)] | ||
24 | mod tests { | ||
25 | use expect_test::{expect, Expect}; | ||
26 | |||
27 | use crate::{test_utils::completion_list, CompletionKind}; | ||
28 | |||
29 | fn check(ra_fixture: &str, expect: Expect) { | ||
30 | let actual = completion_list(ra_fixture, CompletionKind::Reference); | ||
31 | expect.assert_eq(&actual) | ||
32 | } | ||
33 | |||
34 | #[test] | ||
35 | fn completes_macros_as_item() { | ||
36 | check( | ||
37 | r#" | ||
38 | macro_rules! foo { () => {} } | ||
39 | fn foo() {} | ||
40 | |||
41 | $0 | ||
42 | "#, | ||
43 | expect![[r#" | ||
44 | ma foo!(…) macro_rules! foo | ||
45 | "#]], | ||
46 | ) | ||
47 | } | ||
48 | } | ||
diff --git a/crates/ide_completion/src/completions/postfix.rs b/crates/ide_completion/src/completions/postfix.rs index 86bbb58e2..86eb21714 100644 --- a/crates/ide_completion/src/completions/postfix.rs +++ b/crates/ide_completion/src/completions/postfix.rs | |||
@@ -24,7 +24,7 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
24 | } | 24 | } |
25 | 25 | ||
26 | let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.completion_location { | 26 | let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.completion_location { |
27 | Some(ImmediateLocation::MethodCall { receiver: Some(it) }) => (it, false), | 27 | Some(ImmediateLocation::MethodCall { receiver: Some(it), .. }) => (it, false), |
28 | Some(ImmediateLocation::FieldAccess { | 28 | Some(ImmediateLocation::FieldAccess { |
29 | receiver: Some(it), | 29 | receiver: Some(it), |
30 | receiver_is_ambiguous_float_literal, | 30 | receiver_is_ambiguous_float_literal, |
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index de58ce1cd..d58745fb4 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs | |||
@@ -7,11 +7,11 @@ use syntax::AstNode; | |||
7 | use crate::{CompletionContext, Completions}; | 7 | use crate::{CompletionContext, Completions}; |
8 | 8 | ||
9 | pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 9 | pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
10 | if ctx.is_path_disallowed() || ctx.expects_item() { | 10 | if ctx.is_path_disallowed() { |
11 | return; | 11 | return; |
12 | } | 12 | } |
13 | let path = match &ctx.path_qual { | 13 | let path = match ctx.path_qual() { |
14 | Some(path) => path.clone(), | 14 | Some(path) => path, |
15 | None => return, | 15 | None => return, |
16 | }; | 16 | }; |
17 | 17 | ||
@@ -20,7 +20,8 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
20 | None => return, | 20 | None => return, |
21 | }; | 21 | }; |
22 | let context_module = ctx.scope.module(); | 22 | let context_module = ctx.scope.module(); |
23 | if ctx.expects_assoc_item() { | 23 | |
24 | if ctx.expects_item() || ctx.expects_assoc_item() { | ||
24 | if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { | 25 | if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { |
25 | let module_scope = module.scope(ctx.db, context_module); | 26 | let module_scope = module.scope(ctx.db, context_module); |
26 | for (name, def) in module_scope { | 27 | for (name, def) in module_scope { |
@@ -631,17 +632,17 @@ impl MyStruct { | |||
631 | "#, | 632 | "#, |
632 | expect![[r##" | 633 | expect![[r##" |
633 | md bar | 634 | md bar |
634 | ma foo! #[macro_export] macro_rules! foo | 635 | ma foo!(…) #[macro_export] macro_rules! foo |
635 | "##]], | 636 | "##]], |
636 | ); | 637 | ); |
637 | } | 638 | } |
638 | 639 | ||
639 | #[test] | 640 | #[test] |
640 | #[ignore] // FIXME doesn't complete anything atm | ||
641 | fn completes_in_item_list() { | 641 | fn completes_in_item_list() { |
642 | check( | 642 | check( |
643 | r#" | 643 | r#" |
644 | struct MyStruct {} | 644 | struct MyStruct {} |
645 | #[macro_export] | ||
645 | macro_rules! foo {} | 646 | macro_rules! foo {} |
646 | mod bar {} | 647 | mod bar {} |
647 | 648 | ||
@@ -649,7 +650,7 @@ crate::$0 | |||
649 | "#, | 650 | "#, |
650 | expect![[r#" | 651 | expect![[r#" |
651 | md bar | 652 | md bar |
652 | ma foo! macro_rules! foo | 653 | ma foo!(…) #[macro_export] macro_rules! foo |
653 | "#]], | 654 | "#]], |
654 | ) | 655 | ) |
655 | } | 656 | } |
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs index 6e6a6eb92..b9862de67 100644 --- a/crates/ide_completion/src/completions/snippet.rs +++ b/crates/ide_completion/src/completions/snippet.rs | |||
@@ -3,8 +3,8 @@ | |||
3 | use ide_db::helpers::SnippetCap; | 3 | use ide_db::helpers::SnippetCap; |
4 | 4 | ||
5 | use crate::{ | 5 | use crate::{ |
6 | item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, | 6 | context::PathCompletionContext, item::Builder, CompletionContext, CompletionItem, |
7 | Completions, | 7 | CompletionItemKind, CompletionKind, Completions, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder { | 10 | fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) -> Builder { |
@@ -14,15 +14,21 @@ fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str) | |||
14 | } | 14 | } |
15 | 15 | ||
16 | pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { | 16 | pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { |
17 | if !(ctx.is_trivial_path && ctx.function_def.is_some()) { | 17 | if ctx.function_def.is_none() { |
18 | return; | 18 | return; |
19 | } | 19 | } |
20 | |||
21 | let can_be_stmt = match ctx.path_context { | ||
22 | Some(PathCompletionContext { is_trivial_path: true, can_be_stmt, .. }) => can_be_stmt, | ||
23 | _ => return, | ||
24 | }; | ||
25 | |||
20 | let cap = match ctx.config.snippet_cap { | 26 | let cap = match ctx.config.snippet_cap { |
21 | Some(it) => it, | 27 | Some(it) => it, |
22 | None => return, | 28 | None => return, |
23 | }; | 29 | }; |
24 | 30 | ||
25 | if ctx.can_be_stmt { | 31 | if can_be_stmt { |
26 | snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); | 32 | snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); |
27 | snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); | 33 | snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); |
28 | } | 34 | } |
diff --git a/crates/ide_completion/src/completions/trait_impl.rs b/crates/ide_completion/src/completions/trait_impl.rs index 968c0254d..a60e5f43c 100644 --- a/crates/ide_completion/src/completions/trait_impl.rs +++ b/crates/ide_completion/src/completions/trait_impl.rs | |||
@@ -34,20 +34,13 @@ | |||
34 | use hir::{self, HasAttrs, HasSource}; | 34 | use hir::{self, HasAttrs, HasSource}; |
35 | use ide_db::{traits::get_missing_assoc_items, SymbolKind}; | 35 | use ide_db::{traits::get_missing_assoc_items, SymbolKind}; |
36 | use syntax::{ | 36 | use syntax::{ |
37 | ast::{self, edit, Impl}, | 37 | ast::{self, edit}, |
38 | display::function_declaration, | 38 | display::function_declaration, |
39 | AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T, | 39 | AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, T, |
40 | }; | 40 | }; |
41 | use text_edit::TextEdit; | 41 | use text_edit::TextEdit; |
42 | 42 | ||
43 | use crate::{ | 43 | use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; |
44 | CompletionContext, | ||
45 | CompletionItem, | ||
46 | CompletionItemKind, | ||
47 | CompletionKind, | ||
48 | Completions, | ||
49 | // display::function_declaration, | ||
50 | }; | ||
51 | 44 | ||
52 | #[derive(Debug, PartialEq, Eq)] | 45 | #[derive(Debug, PartialEq, Eq)] |
53 | enum ImplCompletionKind { | 46 | enum ImplCompletionKind { |
@@ -58,7 +51,7 @@ enum ImplCompletionKind { | |||
58 | } | 51 | } |
59 | 52 | ||
60 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { | 53 | pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { |
61 | if let Some((kind, trigger, impl_def)) = completion_match(ctx) { | 54 | if let Some((kind, trigger, impl_def)) = completion_match(ctx.token.clone()) { |
62 | get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| match item { | 55 | get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| match item { |
63 | hir::AssocItem::Function(fn_item) | 56 | hir::AssocItem::Function(fn_item) |
64 | if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn => | 57 | if kind == ImplCompletionKind::All || kind == ImplCompletionKind::Fn => |
@@ -80,8 +73,7 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext | |||
80 | } | 73 | } |
81 | } | 74 | } |
82 | 75 | ||
83 | fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, SyntaxNode, Impl)> { | 76 | fn completion_match(mut token: SyntaxToken) -> Option<(ImplCompletionKind, SyntaxNode, ast::Impl)> { |
84 | let mut token = ctx.token.clone(); | ||
85 | // For keyword without name like `impl .. { fn $0 }`, the current position is inside | 77 | // For keyword without name like `impl .. { fn $0 }`, the current position is inside |
86 | // the whitespace token, which is outside `FN` syntax node. | 78 | // the whitespace token, which is outside `FN` syntax node. |
87 | // We need to follow the previous token in this case. | 79 | // We need to follow the previous token in this case. |
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index bd955aa85..8b22933e0 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs | |||
@@ -5,26 +5,25 @@ use hir::ScopeDef; | |||
5 | use crate::{CompletionContext, Completions}; | 5 | use crate::{CompletionContext, Completions}; |
6 | 6 | ||
7 | pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { | 7 | pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) { |
8 | if !ctx.is_trivial_path { | 8 | if ctx.is_path_disallowed() || !ctx.is_trivial_path() { |
9 | return; | ||
10 | } | ||
11 | if ctx.is_path_disallowed() || ctx.expects_item() { | ||
12 | return; | 9 | return; |
13 | } | 10 | } |
14 | 11 | ||
15 | if ctx.expects_assoc_item() { | 12 | if ctx.expects_item() || ctx.expects_assoc_item() { |
16 | ctx.scope.process_all_names(&mut |name, def| { | 13 | // only show macros in {Assoc}ItemList |
17 | if let ScopeDef::MacroDef(macro_def) = def { | 14 | ctx.scope.process_all_names(&mut |name, res| { |
18 | acc.add_macro(ctx, Some(name.clone()), macro_def); | 15 | if let hir::ScopeDef::MacroDef(mac) = res { |
16 | acc.add_macro(ctx, Some(name.clone()), mac); | ||
19 | } | 17 | } |
20 | if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = def { | 18 | if let hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { |
21 | acc.add_resolution(ctx, name, &def); | 19 | acc.add_resolution(ctx, name, &res); |
22 | } | 20 | } |
23 | }); | 21 | }); |
24 | return; | 22 | return; |
25 | } | 23 | } |
26 | 24 | ||
27 | if ctx.expects_use_tree() { | 25 | if ctx.expects_use_tree() { |
26 | // only show modules in a fresh UseTree | ||
28 | cov_mark::hit!(only_completes_modules_in_import); | 27 | cov_mark::hit!(only_completes_modules_in_import); |
29 | ctx.scope.process_all_names(&mut |name, res| { | 28 | ctx.scope.process_all_names(&mut |name, res| { |
30 | if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { | 29 | if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { |
@@ -693,12 +692,11 @@ impl MyStruct { | |||
693 | "#, | 692 | "#, |
694 | expect![[r#" | 693 | expect![[r#" |
695 | md bar | 694 | md bar |
696 | ma foo! macro_rules! foo | 695 | ma foo!(…) macro_rules! foo |
697 | "#]], | 696 | "#]], |
698 | ) | 697 | ) |
699 | } | 698 | } |
700 | 699 | ||
701 | // FIXME: The completions here currently come from `macro_in_item_position`, but they shouldn't | ||
702 | #[test] | 700 | #[test] |
703 | fn completes_in_item_list() { | 701 | fn completes_in_item_list() { |
704 | check( | 702 | check( |
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index cb4f08e53..6177caa12 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -29,6 +29,29 @@ pub(crate) enum PatternRefutability { | |||
29 | Irrefutable, | 29 | Irrefutable, |
30 | } | 30 | } |
31 | 31 | ||
32 | #[derive(Debug)] | ||
33 | pub(crate) struct PathCompletionContext { | ||
34 | /// If this is a call with () already there | ||
35 | call_kind: Option<CallKind>, | ||
36 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | ||
37 | pub(super) is_trivial_path: bool, | ||
38 | /// If not a trivial path, the prefix (qualifier). | ||
39 | pub(super) path_qual: Option<ast::Path>, | ||
40 | pub(super) is_path_type: bool, | ||
41 | pub(super) has_type_args: bool, | ||
42 | /// `true` if we are a statement or a last expr in the block. | ||
43 | pub(super) can_be_stmt: bool, | ||
44 | /// `true` if we expect an expression at the cursor position. | ||
45 | pub(super) is_expr: bool, | ||
46 | pub(super) in_loop_body: bool, | ||
47 | } | ||
48 | |||
49 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
50 | pub(crate) enum CallKind { | ||
51 | Pat, | ||
52 | Mac, | ||
53 | Expr, | ||
54 | } | ||
32 | /// `CompletionContext` is created early during completion to figure out, where | 55 | /// `CompletionContext` is created early during completion to figure out, where |
33 | /// exactly is the cursor, syntax-wise. | 56 | /// exactly is the cursor, syntax-wise. |
34 | #[derive(Debug)] | 57 | #[derive(Debug)] |
@@ -45,14 +68,13 @@ pub(crate) struct CompletionContext<'a> { | |||
45 | pub(super) krate: Option<hir::Crate>, | 68 | pub(super) krate: Option<hir::Crate>, |
46 | pub(super) expected_name: Option<NameOrNameRef>, | 69 | pub(super) expected_name: Option<NameOrNameRef>, |
47 | pub(super) expected_type: Option<Type>, | 70 | pub(super) expected_type: Option<Type>, |
48 | pub(super) name_ref_syntax: Option<ast::NameRef>, | ||
49 | |||
50 | pub(super) use_item_syntax: Option<ast::Use>, | ||
51 | 71 | ||
52 | /// The parent function of the cursor position if it exists. | 72 | /// The parent function of the cursor position if it exists. |
53 | pub(super) function_def: Option<ast::Fn>, | 73 | pub(super) function_def: Option<ast::Fn>, |
54 | /// The parent impl of the cursor position if it exists. | 74 | /// The parent impl of the cursor position if it exists. |
55 | pub(super) impl_def: Option<ast::Impl>, | 75 | pub(super) impl_def: Option<ast::Impl>, |
76 | pub(super) name_ref_syntax: Option<ast::NameRef>, | ||
77 | pub(super) use_item_syntax: Option<ast::Use>, | ||
56 | 78 | ||
57 | // potentially set if we are completing a lifetime | 79 | // potentially set if we are completing a lifetime |
58 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | 80 | pub(super) lifetime_syntax: Option<ast::Lifetime>, |
@@ -67,29 +89,12 @@ pub(crate) struct CompletionContext<'a> { | |||
67 | pub(super) completion_location: Option<ImmediateLocation>, | 89 | pub(super) completion_location: Option<ImmediateLocation>, |
68 | pub(super) prev_sibling: Option<ImmediatePrevSibling>, | 90 | pub(super) prev_sibling: Option<ImmediatePrevSibling>, |
69 | pub(super) attribute_under_caret: Option<ast::Attr>, | 91 | pub(super) attribute_under_caret: Option<ast::Attr>, |
92 | pub(super) previous_token: Option<SyntaxToken>, | ||
70 | 93 | ||
71 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 94 | pub(super) path_context: Option<PathCompletionContext>, |
72 | pub(super) active_parameter: Option<ActiveParameter>, | 95 | pub(super) active_parameter: Option<ActiveParameter>, |
73 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | ||
74 | pub(super) is_trivial_path: bool, | ||
75 | /// If not a trivial path, the prefix (qualifier). | ||
76 | pub(super) path_qual: Option<ast::Path>, | ||
77 | /// `true` if we are a statement or a last expr in the block. | ||
78 | pub(super) can_be_stmt: bool, | ||
79 | /// `true` if we expect an expression at the cursor position. | ||
80 | pub(super) is_expr: bool, | ||
81 | /// If this is a call (method or function) in particular, i.e. the () are already there. | ||
82 | pub(super) is_call: bool, | ||
83 | /// Like `is_call`, but for tuple patterns. | ||
84 | pub(super) is_pattern_call: bool, | ||
85 | /// If this is a macro call, i.e. the () are already there. | ||
86 | pub(super) is_macro_call: bool, | ||
87 | pub(super) is_path_type: bool, | ||
88 | pub(super) has_type_args: bool, | ||
89 | pub(super) locals: Vec<(String, Local)>, | 96 | pub(super) locals: Vec<(String, Local)>, |
90 | 97 | ||
91 | pub(super) previous_token: Option<SyntaxToken>, | ||
92 | pub(super) in_loop_body: bool, | ||
93 | pub(super) incomplete_let: bool, | 98 | pub(super) incomplete_let: bool, |
94 | 99 | ||
95 | no_completion_required: bool, | 100 | no_completion_required: bool, |
@@ -136,36 +141,27 @@ impl<'a> CompletionContext<'a> { | |||
136 | original_token, | 141 | original_token, |
137 | token, | 142 | token, |
138 | krate, | 143 | krate, |
139 | lifetime_allowed: false, | ||
140 | expected_name: None, | 144 | expected_name: None, |
141 | expected_type: None, | 145 | expected_type: None, |
146 | function_def: None, | ||
147 | impl_def: None, | ||
142 | name_ref_syntax: None, | 148 | name_ref_syntax: None, |
149 | use_item_syntax: None, | ||
143 | lifetime_syntax: None, | 150 | lifetime_syntax: None, |
144 | lifetime_param_syntax: None, | 151 | lifetime_param_syntax: None, |
145 | function_def: None, | 152 | lifetime_allowed: false, |
146 | use_item_syntax: None, | ||
147 | impl_def: None, | ||
148 | active_parameter: ActiveParameter::at(db, position), | ||
149 | is_label_ref: false, | 153 | is_label_ref: false, |
150 | is_param: false, | ||
151 | is_pat_or_const: None, | 154 | is_pat_or_const: None, |
152 | is_trivial_path: false, | 155 | is_param: false, |
153 | path_qual: None, | ||
154 | can_be_stmt: false, | ||
155 | is_expr: false, | ||
156 | is_call: false, | ||
157 | is_pattern_call: false, | ||
158 | is_macro_call: false, | ||
159 | is_path_type: false, | ||
160 | has_type_args: false, | ||
161 | previous_token: None, | ||
162 | in_loop_body: false, | ||
163 | completion_location: None, | 156 | completion_location: None, |
164 | prev_sibling: None, | 157 | prev_sibling: None, |
165 | no_completion_required: false, | ||
166 | incomplete_let: false, | ||
167 | attribute_under_caret: None, | 158 | attribute_under_caret: None, |
159 | previous_token: None, | ||
160 | path_context: None, | ||
161 | active_parameter: ActiveParameter::at(db, position), | ||
168 | locals, | 162 | locals, |
163 | incomplete_let: false, | ||
164 | no_completion_required: false, | ||
169 | }; | 165 | }; |
170 | 166 | ||
171 | let mut original_file = original_file.syntax().clone(); | 167 | let mut original_file = original_file.syntax().clone(); |
@@ -250,14 +246,14 @@ impl<'a> CompletionContext<'a> { | |||
250 | pub(crate) fn has_dot_receiver(&self) -> bool { | 246 | pub(crate) fn has_dot_receiver(&self) -> bool { |
251 | matches!( | 247 | matches!( |
252 | &self.completion_location, | 248 | &self.completion_location, |
253 | Some(ImmediateLocation::FieldAccess { receiver, .. }) | Some(ImmediateLocation::MethodCall { receiver }) | 249 | Some(ImmediateLocation::FieldAccess { receiver, .. }) | Some(ImmediateLocation::MethodCall { receiver,.. }) |
254 | if receiver.is_some() | 250 | if receiver.is_some() |
255 | ) | 251 | ) |
256 | } | 252 | } |
257 | 253 | ||
258 | pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> { | 254 | pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> { |
259 | match &self.completion_location { | 255 | match &self.completion_location { |
260 | Some(ImmediateLocation::MethodCall { receiver }) | 256 | Some(ImmediateLocation::MethodCall { receiver, .. }) |
261 | | Some(ImmediateLocation::FieldAccess { receiver, .. }) => receiver.as_ref(), | 257 | | Some(ImmediateLocation::FieldAccess { receiver, .. }) => receiver.as_ref(), |
262 | _ => None, | 258 | _ => None, |
263 | } | 259 | } |
@@ -275,11 +271,6 @@ impl<'a> CompletionContext<'a> { | |||
275 | matches!(self.completion_location, Some(ImmediateLocation::ItemList)) | 271 | matches!(self.completion_location, Some(ImmediateLocation::ItemList)) |
276 | } | 272 | } |
277 | 273 | ||
278 | // fn expects_value(&self) -> bool { | ||
279 | pub(crate) fn expects_expression(&self) -> bool { | ||
280 | self.is_expr | ||
281 | } | ||
282 | |||
283 | pub(crate) fn has_block_expr_parent(&self) -> bool { | 274 | pub(crate) fn has_block_expr_parent(&self) -> bool { |
284 | matches!(self.completion_location, Some(ImmediateLocation::BlockExpr)) | 275 | matches!(self.completion_location, Some(ImmediateLocation::BlockExpr)) |
285 | } | 276 | } |
@@ -316,6 +307,22 @@ impl<'a> CompletionContext<'a> { | |||
316 | ) || self.attribute_under_caret.is_some() | 307 | ) || self.attribute_under_caret.is_some() |
317 | } | 308 | } |
318 | 309 | ||
310 | pub(crate) fn expects_expression(&self) -> bool { | ||
311 | self.path_context.as_ref().map_or(false, |it| it.is_expr) | ||
312 | } | ||
313 | |||
314 | pub(crate) fn path_call_kind(&self) -> Option<CallKind> { | ||
315 | self.path_context.as_ref().and_then(|it| it.call_kind) | ||
316 | } | ||
317 | |||
318 | pub(crate) fn is_trivial_path(&self) -> bool { | ||
319 | self.path_context.as_ref().map_or(false, |it| it.is_trivial_path) | ||
320 | } | ||
321 | |||
322 | pub(crate) fn path_qual(&self) -> Option<&ast::Path> { | ||
323 | self.path_context.as_ref().and_then(|it| it.path_qual.as_ref()) | ||
324 | } | ||
325 | |||
319 | fn fill_impl_def(&mut self) { | 326 | fn fill_impl_def(&mut self) { |
320 | self.impl_def = self | 327 | self.impl_def = self |
321 | .sema | 328 | .sema |
@@ -441,7 +448,6 @@ impl<'a> CompletionContext<'a> { | |||
441 | let for_is_prev2 = for_is_prev2(syntax_element.clone()); | 448 | let for_is_prev2 = for_is_prev2(syntax_element.clone()); |
442 | (fn_is_prev && !inside_impl_trait_block) || for_is_prev2 | 449 | (fn_is_prev && !inside_impl_trait_block) || for_is_prev2 |
443 | }; | 450 | }; |
444 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
445 | 451 | ||
446 | self.incomplete_let = | 452 | self.incomplete_let = |
447 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { | 453 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { |
@@ -549,10 +555,6 @@ impl<'a> CompletionContext<'a> { | |||
549 | self.name_ref_syntax = | 555 | self.name_ref_syntax = |
550 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); | 556 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); |
551 | 557 | ||
552 | if matches!(self.completion_location, Some(ImmediateLocation::ItemList)) { | ||
553 | return; | ||
554 | } | ||
555 | |||
556 | self.use_item_syntax = | 558 | self.use_item_syntax = |
557 | self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast); | 559 | self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast); |
558 | 560 | ||
@@ -568,22 +570,34 @@ impl<'a> CompletionContext<'a> { | |||
568 | }; | 570 | }; |
569 | 571 | ||
570 | if let Some(segment) = ast::PathSegment::cast(parent) { | 572 | if let Some(segment) = ast::PathSegment::cast(parent) { |
573 | let path_ctx = self.path_context.get_or_insert(PathCompletionContext { | ||
574 | call_kind: None, | ||
575 | is_trivial_path: false, | ||
576 | path_qual: None, | ||
577 | has_type_args: false, | ||
578 | is_path_type: false, | ||
579 | can_be_stmt: false, | ||
580 | is_expr: false, | ||
581 | in_loop_body: false, | ||
582 | }); | ||
583 | path_ctx.in_loop_body = is_in_loop_body(name_ref.syntax()); | ||
571 | let path = segment.parent_path(); | 584 | let path = segment.parent_path(); |
572 | self.is_call = path | ||
573 | .syntax() | ||
574 | .parent() | ||
575 | .and_then(ast::PathExpr::cast) | ||
576 | .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) | ||
577 | .is_some(); | ||
578 | self.is_macro_call = path.syntax().parent().and_then(ast::MacroCall::cast).is_some(); | ||
579 | self.is_pattern_call = | ||
580 | path.syntax().parent().and_then(ast::TupleStructPat::cast).is_some(); | ||
581 | 585 | ||
582 | self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | 586 | if let Some(p) = path.syntax().parent() { |
583 | self.has_type_args = segment.generic_arg_list().is_some(); | 587 | path_ctx.call_kind = match_ast! { |
588 | match p { | ||
589 | ast::PathExpr(it) => it.syntax().parent().and_then(ast::CallExpr::cast).map(|_| CallKind::Expr), | ||
590 | ast::MacroCall(it) => it.excl_token().and(Some(CallKind::Mac)), | ||
591 | ast::TupleStructPat(_it) => Some(CallKind::Pat), | ||
592 | _ => None | ||
593 | } | ||
594 | }; | ||
595 | } | ||
596 | path_ctx.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); | ||
597 | path_ctx.has_type_args = segment.generic_arg_list().is_some(); | ||
584 | 598 | ||
585 | if let Some(path) = path_or_use_tree_qualifier(&path) { | 599 | if let Some(path) = path_or_use_tree_qualifier(&path) { |
586 | self.path_qual = path | 600 | path_ctx.path_qual = path |
587 | .segment() | 601 | .segment() |
588 | .and_then(|it| { | 602 | .and_then(|it| { |
589 | find_node_with_range::<ast::PathSegment>( | 603 | find_node_with_range::<ast::PathSegment>( |
@@ -601,11 +615,11 @@ impl<'a> CompletionContext<'a> { | |||
601 | } | 615 | } |
602 | } | 616 | } |
603 | 617 | ||
604 | self.is_trivial_path = true; | 618 | path_ctx.is_trivial_path = true; |
605 | 619 | ||
606 | // Find either enclosing expr statement (thing with `;`) or a | 620 | // Find either enclosing expr statement (thing with `;`) or a |
607 | // block. If block, check that we are the last expr. | 621 | // block. If block, check that we are the last expr. |
608 | self.can_be_stmt = name_ref | 622 | path_ctx.can_be_stmt = name_ref |
609 | .syntax() | 623 | .syntax() |
610 | .ancestors() | 624 | .ancestors() |
611 | .find_map(|node| { | 625 | .find_map(|node| { |
@@ -621,10 +635,8 @@ impl<'a> CompletionContext<'a> { | |||
621 | None | 635 | None |
622 | }) | 636 | }) |
623 | .unwrap_or(false); | 637 | .unwrap_or(false); |
624 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); | 638 | path_ctx.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); |
625 | } | 639 | } |
626 | self.is_call |= | ||
627 | matches!(self.completion_location, Some(ImmediateLocation::MethodCall { .. })); | ||
628 | } | 640 | } |
629 | } | 641 | } |
630 | 642 | ||
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 6fb38f50d..18983aa01 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -158,7 +158,6 @@ pub fn completions( | |||
158 | completions::record::complete_record(&mut acc, &ctx); | 158 | completions::record::complete_record(&mut acc, &ctx); |
159 | completions::pattern::complete_pattern(&mut acc, &ctx); | 159 | completions::pattern::complete_pattern(&mut acc, &ctx); |
160 | completions::postfix::complete_postfix(&mut acc, &ctx); | 160 | completions::postfix::complete_postfix(&mut acc, &ctx); |
161 | completions::macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); | ||
162 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); | 161 | completions::trait_impl::complete_trait_impl(&mut acc, &ctx); |
163 | completions::mod_::complete_mod(&mut acc, &ctx); | 162 | completions::mod_::complete_mod(&mut acc, &ctx); |
164 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); | 163 | completions::flyimport::import_on_the_fly(&mut acc, &ctx); |
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index 080898aef..ee87bf461 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs | |||
@@ -4,7 +4,7 @@ use hir::Semantics; | |||
4 | use ide_db::RootDatabase; | 4 | use ide_db::RootDatabase; |
5 | use syntax::{ | 5 | use syntax::{ |
6 | algo::non_trivia_sibling, | 6 | algo::non_trivia_sibling, |
7 | ast::{self, LoopBodyOwner}, | 7 | ast::{self, ArgListOwner, LoopBodyOwner}, |
8 | match_ast, AstNode, Direction, SyntaxElement, | 8 | match_ast, AstNode, Direction, SyntaxElement, |
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, T, | 10 | SyntaxNode, SyntaxToken, TextRange, TextSize, T, |
@@ -39,6 +39,7 @@ pub(crate) enum ImmediateLocation { | |||
39 | // Original file ast node | 39 | // Original file ast node |
40 | MethodCall { | 40 | MethodCall { |
41 | receiver: Option<ast::Expr>, | 41 | receiver: Option<ast::Expr>, |
42 | has_parens: bool, | ||
42 | }, | 43 | }, |
43 | // Original file ast node | 44 | // Original file ast node |
44 | FieldAccess { | 45 | FieldAccess { |
@@ -204,6 +205,7 @@ pub(crate) fn determine_location( | |||
204 | .receiver() | 205 | .receiver() |
205 | .map(|e| e.syntax().text_range()) | 206 | .map(|e| e.syntax().text_range()) |
206 | .and_then(|r| find_node_with_range(original_file, r)), | 207 | .and_then(|r| find_node_with_range(original_file, r)), |
208 | has_parens: it.arg_list().map_or(false, |it| it.l_paren_token().is_some()) | ||
207 | }, | 209 | }, |
208 | _ => return None, | 210 | _ => return None, |
209 | } | 211 | } |
@@ -270,9 +272,8 @@ fn test_for_is_prev2() { | |||
270 | check_pattern_is_applicable(r"for i i$0", for_is_prev2); | 272 | check_pattern_is_applicable(r"for i i$0", for_is_prev2); |
271 | } | 273 | } |
272 | 274 | ||
273 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | 275 | pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool { |
274 | element | 276 | node.ancestors() |
275 | .ancestors() | ||
276 | .take_while(|it| it.kind() != FN && it.kind() != CLOSURE_EXPR) | 277 | .take_while(|it| it.kind() != FN && it.kind() != CLOSURE_EXPR) |
277 | .find_map(|it| { | 278 | .find_map(|it| { |
278 | let loop_body = match_ast! { | 279 | let loop_body = match_ast! { |
@@ -283,7 +284,7 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | |||
283 | _ => None, | 284 | _ => None, |
284 | } | 285 | } |
285 | }; | 286 | }; |
286 | loop_body.filter(|it| it.syntax().text_range().contains_range(element.text_range())) | 287 | loop_body.filter(|it| it.syntax().text_range().contains_range(node.text_range())) |
287 | }) | 288 | }) |
288 | .is_some() | 289 | .is_some() |
289 | } | 290 | } |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index a49a60711..750694432 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -275,8 +275,12 @@ impl<'a> Render<'a> { | |||
275 | }; | 275 | }; |
276 | 276 | ||
277 | // Add `<>` for generic types | 277 | // Add `<>` for generic types |
278 | if self.ctx.completion.is_path_type | 278 | if self |
279 | && !self.ctx.completion.has_type_args | 279 | .ctx |
280 | .completion | ||
281 | .path_context | ||
282 | .as_ref() | ||
283 | .map_or(false, |it| it.is_path_type && !it.has_type_args) | ||
280 | && self.ctx.completion.config.add_call_parenthesis | 284 | && self.ctx.completion.config.add_call_parenthesis |
281 | { | 285 | { |
282 | if let Some(cap) = self.ctx.snippet_cap() { | 286 | if let Some(cap) = self.ctx.snippet_cap() { |
diff --git a/crates/ide_completion/src/render/builder_ext.rs b/crates/ide_completion/src/render/builder_ext.rs index 6d062b3b9..c54752d30 100644 --- a/crates/ide_completion/src/render/builder_ext.rs +++ b/crates/ide_completion/src/render/builder_ext.rs | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | 4 | ||
5 | use crate::{item::Builder, CompletionContext}; | 5 | use crate::{context::CallKind, item::Builder, patterns::ImmediateLocation, CompletionContext}; |
6 | 6 | ||
7 | #[derive(Debug)] | 7 | #[derive(Debug)] |
8 | pub(super) enum Params { | 8 | pub(super) enum Params { |
@@ -32,10 +32,12 @@ impl Builder { | |||
32 | cov_mark::hit!(no_parens_in_use_item); | 32 | cov_mark::hit!(no_parens_in_use_item); |
33 | return false; | 33 | return false; |
34 | } | 34 | } |
35 | if ctx.is_pattern_call { | 35 | if matches!(ctx.path_call_kind(), Some(CallKind::Expr) | Some(CallKind::Pat)) |
36 | return false; | 36 | | matches!( |
37 | } | 37 | ctx.completion_location, |
38 | if ctx.is_call { | 38 | Some(ImmediateLocation::MethodCall { has_parens: true, .. }) |
39 | ) | ||
40 | { | ||
39 | return false; | 41 | return false; |
40 | } | 42 | } |
41 | 43 | ||
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs index 0dfba8acc..429d937c8 100644 --- a/crates/ide_completion/src/render/macro_.rs +++ b/crates/ide_completion/src/render/macro_.rs | |||
@@ -5,6 +5,7 @@ use ide_db::SymbolKind; | |||
5 | use syntax::display::macro_label; | 5 | use syntax::display::macro_label; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | context::CallKind, | ||
8 | item::{CompletionItem, CompletionKind, ImportEdit}, | 9 | item::{CompletionItem, CompletionKind, ImportEdit}, |
9 | render::RenderContext, | 10 | render::RenderContext, |
10 | }; | 11 | }; |
@@ -68,7 +69,8 @@ impl<'a> MacroRender<'a> { | |||
68 | } | 69 | } |
69 | 70 | ||
70 | fn needs_bang(&self) -> bool { | 71 | fn needs_bang(&self) -> bool { |
71 | self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call | 72 | self.ctx.completion.use_item_syntax.is_none() |
73 | && !matches!(self.ctx.completion.path_call_kind(), Some(CallKind::Mac)) | ||
72 | } | 74 | } |
73 | 75 | ||
74 | fn label(&self) -> String { | 76 | fn label(&self) -> String { |
diff --git a/crates/ide_db/src/call_info.rs b/crates/ide_db/src/call_info.rs index bad277a95..933bcad55 100644 --- a/crates/ide_db/src/call_info.rs +++ b/crates/ide_db/src/call_info.rs | |||
@@ -223,9 +223,8 @@ impl FnCallNode { | |||
223 | ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, | 223 | ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, |
224 | _ => return None, | 224 | _ => return None, |
225 | }), | 225 | }), |
226 | |||
227 | FnCallNode::MethodCallExpr(call_expr) => { | 226 | FnCallNode::MethodCallExpr(call_expr) => { |
228 | call_expr.syntax().children().filter_map(ast::NameRef::cast).next() | 227 | call_expr.syntax().children().find_map(ast::NameRef::cast) |
229 | } | 228 | } |
230 | } | 229 | } |
231 | } | 230 | } |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 70511c5ca..40dd0da3e 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -1510,7 +1510,7 @@ fn show_ref_command_link( | |||
1510 | snap: &GlobalStateSnapshot, | 1510 | snap: &GlobalStateSnapshot, |
1511 | position: &FilePosition, | 1511 | position: &FilePosition, |
1512 | ) -> Option<lsp_ext::CommandLinkGroup> { | 1512 | ) -> Option<lsp_ext::CommandLinkGroup> { |
1513 | if snap.config.hover().implementations { | 1513 | if snap.config.hover().references { |
1514 | if let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) { | 1514 | if let Some(ref_search_res) = snap.analysis.find_all_refs(*position, None).unwrap_or(None) { |
1515 | let uri = to_proto::url(snap, position.file_id); | 1515 | let uri = to_proto::url(snap, position.file_id); |
1516 | let line_index = snap.file_line_index(position.file_id).ok()?; | 1516 | let line_index = snap.file_line_index(position.file_id).ok()?; |
diff --git a/docs/dev/README.md b/docs/dev/README.md index 16b23adc6..e81f1e74c 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -1,6 +1,6 @@ | |||
1 | # Contributing Quick Start | 1 | # Contributing Quick Start |
2 | 2 | ||
3 | Rust Analyzer is an ordinary Rust project, which is organized as a Cargo workspace, builds on stable and doesn't depend on C libraries. | 3 | rust-analyzer is an ordinary Rust project, which is organized as a Cargo workspace, builds on stable and doesn't depend on C libraries. |
4 | So, just | 4 | So, just |
5 | 5 | ||
6 | ``` | 6 | ``` |
@@ -9,18 +9,18 @@ $ cargo test | |||
9 | 9 | ||
10 | should be enough to get you started! | 10 | should be enough to get you started! |
11 | 11 | ||
12 | To learn more about how rust-analyzer works, see [./architecture.md](./architecture.md) document. | 12 | To learn more about how rust-analyzer works, see [./architecture.md](./architecture.md). |
13 | It also explains the high-level layout of the source code. | 13 | It also explains the high-level layout of the source code. |
14 | Do skim through that document. | 14 | Do skim through that document. |
15 | 15 | ||
16 | We also publish rustdoc docs to pages: https://rust-analyzer.github.io/rust-analyzer/ide/. | 16 | We also publish rustdoc docs to pages: https://rust-analyzer.github.io/rust-analyzer/ide/. |
17 | Note though, that internal documentation is very incomplete. | 17 | Note though, that the internal documentation is very incomplete. |
18 | 18 | ||
19 | Various organizational and process issues are discussed in this document. | 19 | Various organizational and process issues are discussed in this document. |
20 | 20 | ||
21 | # Getting in Touch | 21 | # Getting in Touch |
22 | 22 | ||
23 | Rust Analyzer is a part of [RLS-2.0 working | 23 | rust-analyzer is a part of the [RLS-2.0 working |
24 | group](https://github.com/rust-lang/compiler-team/tree/6a769c13656c0a6959ebc09e7b1f7c09b86fb9c0/working-groups/rls-2.0). | 24 | group](https://github.com/rust-lang/compiler-team/tree/6a769c13656c0a6959ebc09e7b1f7c09b86fb9c0/working-groups/rls-2.0). |
25 | Discussion happens in this Zulip stream: | 25 | Discussion happens in this Zulip stream: |
26 | 26 | ||
@@ -33,7 +33,7 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer | |||
33 | * [E-has-instructions](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-has-instructions) | 33 | * [E-has-instructions](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-has-instructions) |
34 | issues have links to the code in question and tests. | 34 | issues have links to the code in question and tests. |
35 | * [Broken Window](https://github.com/rust-analyzer/rust-analyzer/issues?q=is:issue+is:open+label:%22Broken+Window%22) | 35 | * [Broken Window](https://github.com/rust-analyzer/rust-analyzer/issues?q=is:issue+is:open+label:%22Broken+Window%22) |
36 | are issues which are not critical by themselves, but which should be fixed ASAP regardless, to avoid accumulation of technical debt. | 36 | are issues which are not necessarily critical by themselves, but which should be fixed ASAP regardless, to avoid accumulation of technical debt. |
37 | * [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), | 37 | * [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), |
38 | [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium), | 38 | [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium), |
39 | [E-hard](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-hard), | 39 | [E-hard](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-hard), |
@@ -42,7 +42,9 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer | |||
42 | * [S-actionable](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-actionable) and | 42 | * [S-actionable](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-actionable) and |
43 | [S-unactionable](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-unactionable) | 43 | [S-unactionable](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-unactionable) |
44 | specify if there are concrete steps to resolve or advance an issue. Roughly, actionable issues need only work to be fixed, | 44 | specify if there are concrete steps to resolve or advance an issue. Roughly, actionable issues need only work to be fixed, |
45 | while unactionable ones are effectively wont-fix. Each triaged issue should have one of these labels. | 45 | while unactionable ones are blocked either on user feedback (providing a reproducible example), or on larger architectural |
46 | work or decisions. This classification is descriptive, not prescriptive, and might be wrong: Any unactionable issue might have a simple fix that we missed. | ||
47 | Each triaged issue should have one of these labels. | ||
46 | * [fun](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3Afun) | 48 | * [fun](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3Afun) |
47 | is for cool, but probably hard stuff. | 49 | is for cool, but probably hard stuff. |
48 | * [Design](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%Design) | 50 | * [Design](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%Design) |
@@ -206,7 +208,7 @@ Release process is handled by `release`, `dist` and `promote` xtasks, `release` | |||
206 | ./rust-rust-analyzer # Note the name! | 208 | ./rust-rust-analyzer # Note the name! |
207 | ``` | 209 | ``` |
208 | 210 | ||
209 | Additionally, it assumes that remote for `rust-analyzer` is called `upstream` (I use `origin` to point to my fork). | 211 | Additionally, it assumes that the remote for `rust-analyzer` is called `upstream` (I use `origin` to point to my fork). |
210 | 212 | ||
211 | `release` calls the GitHub API calls to scrape pull request comments and categorize them in the changelog. | 213 | `release` calls the GitHub API calls to scrape pull request comments and categorize them in the changelog. |
212 | This step uses the `curl` and `jq` applications, which need to be available in `PATH`. | 214 | This step uses the `curl` and `jq` applications, which need to be available in `PATH`. |
diff --git a/xtask/src/codegen/gen_lint_completions.rs b/xtask/src/codegen/gen_lint_completions.rs index 7d78d0d04..3b54b2489 100644 --- a/xtask/src/codegen/gen_lint_completions.rs +++ b/xtask/src/codegen/gen_lint_completions.rs | |||
@@ -27,7 +27,7 @@ pub(crate) fn generate_lint_completions() -> Result<()> { | |||
27 | generate_feature_descriptor(&mut contents, "./target/rust/src/doc/unstable-book/src".into())?; | 27 | generate_feature_descriptor(&mut contents, "./target/rust/src/doc/unstable-book/src".into())?; |
28 | contents.push('\n'); | 28 | contents.push('\n'); |
29 | 29 | ||
30 | cmd!("curl http://rust-lang.github.io/rust-clippy/master/lints.json --output ./target/clippy_lints.json").run()?; | 30 | cmd!("curl https://rust-lang.github.io/rust-clippy/master/lints.json --output ./target/clippy_lints.json").run()?; |
31 | generate_descriptor_clippy(&mut contents, &Path::new("./target/clippy_lints.json"))?; | 31 | generate_descriptor_clippy(&mut contents, &Path::new("./target/clippy_lints.json"))?; |
32 | let contents = reformat(&contents)?; | 32 | let contents = reformat(&contents)?; |
33 | 33 | ||