diff options
Diffstat (limited to 'crates/ide_completion/src')
25 files changed, 2195 insertions, 1077 deletions
diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 78154bf3e..ffdcdc930 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs | |||
@@ -18,7 +18,7 @@ pub(crate) mod unqualified_path; | |||
18 | 18 | ||
19 | use std::iter; | 19 | use std::iter; |
20 | 20 | ||
21 | use hir::{known, ModPath, ScopeDef, Type}; | 21 | use hir::known; |
22 | use ide_db::SymbolKind; | 22 | use ide_db::SymbolKind; |
23 | 23 | ||
24 | use crate::{ | 24 | use crate::{ |
@@ -69,13 +69,25 @@ impl Completions { | |||
69 | items.into_iter().for_each(|item| self.add(item.into())) | 69 | items.into_iter().for_each(|item| self.add(item.into())) |
70 | } | 70 | } |
71 | 71 | ||
72 | pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { | 72 | pub(crate) fn add_field( |
73 | let item = render_field(RenderContext::new(ctx), field, ty); | 73 | &mut self, |
74 | ctx: &CompletionContext, | ||
75 | receiver: Option<hir::Name>, | ||
76 | field: hir::Field, | ||
77 | ty: &hir::Type, | ||
78 | ) { | ||
79 | let item = render_field(RenderContext::new(ctx), receiver, field, ty); | ||
74 | self.add(item); | 80 | self.add(item); |
75 | } | 81 | } |
76 | 82 | ||
77 | pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { | 83 | pub(crate) fn add_tuple_field( |
78 | let item = render_tuple_field(RenderContext::new(ctx), field, ty); | 84 | &mut self, |
85 | ctx: &CompletionContext, | ||
86 | receiver: Option<hir::Name>, | ||
87 | field: usize, | ||
88 | ty: &hir::Type, | ||
89 | ) { | ||
90 | let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty); | ||
79 | self.add(item); | 91 | self.add(item); |
80 | } | 92 | } |
81 | 93 | ||
@@ -89,8 +101,8 @@ impl Completions { | |||
89 | pub(crate) fn add_resolution( | 101 | pub(crate) fn add_resolution( |
90 | &mut self, | 102 | &mut self, |
91 | ctx: &CompletionContext, | 103 | ctx: &CompletionContext, |
92 | local_name: String, | 104 | local_name: hir::Name, |
93 | resolution: &ScopeDef, | 105 | resolution: &hir::ScopeDef, |
94 | ) { | 106 | ) { |
95 | if let Some(item) = render_resolution(RenderContext::new(ctx), local_name, resolution) { | 107 | if let Some(item) = render_resolution(RenderContext::new(ctx), local_name, resolution) { |
96 | self.add(item); | 108 | self.add(item); |
@@ -100,7 +112,7 @@ impl Completions { | |||
100 | pub(crate) fn add_macro( | 112 | pub(crate) fn add_macro( |
101 | &mut self, | 113 | &mut self, |
102 | ctx: &CompletionContext, | 114 | ctx: &CompletionContext, |
103 | name: Option<String>, | 115 | name: Option<hir::Name>, |
104 | macro_: hir::MacroDef, | 116 | macro_: hir::MacroDef, |
105 | ) { | 117 | ) { |
106 | let name = match name { | 118 | let name = match name { |
@@ -116,7 +128,7 @@ impl Completions { | |||
116 | &mut self, | 128 | &mut self, |
117 | ctx: &CompletionContext, | 129 | ctx: &CompletionContext, |
118 | func: hir::Function, | 130 | func: hir::Function, |
119 | local_name: Option<String>, | 131 | local_name: Option<hir::Name>, |
120 | ) { | 132 | ) { |
121 | if let Some(item) = render_fn(RenderContext::new(ctx), None, local_name, func) { | 133 | if let Some(item) = render_fn(RenderContext::new(ctx), None, local_name, func) { |
122 | self.add(item) | 134 | self.add(item) |
@@ -127,9 +139,11 @@ impl Completions { | |||
127 | &mut self, | 139 | &mut self, |
128 | ctx: &CompletionContext, | 140 | ctx: &CompletionContext, |
129 | func: hir::Function, | 141 | func: hir::Function, |
130 | local_name: Option<String>, | 142 | receiver: Option<hir::Name>, |
143 | local_name: Option<hir::Name>, | ||
131 | ) { | 144 | ) { |
132 | if let Some(item) = render_method(RenderContext::new(ctx), None, local_name, func) { | 145 | if let Some(item) = render_method(RenderContext::new(ctx), None, receiver, local_name, func) |
146 | { | ||
133 | self.add(item) | 147 | self.add(item) |
134 | } | 148 | } |
135 | } | 149 | } |
@@ -149,7 +163,7 @@ impl Completions { | |||
149 | &mut self, | 163 | &mut self, |
150 | ctx: &CompletionContext, | 164 | ctx: &CompletionContext, |
151 | variant: hir::Variant, | 165 | variant: hir::Variant, |
152 | path: ModPath, | 166 | path: hir::ModPath, |
153 | ) { | 167 | ) { |
154 | if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, None, Some(path)) { | 168 | if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, None, Some(path)) { |
155 | self.add(item); | 169 | self.add(item); |
@@ -183,7 +197,7 @@ impl Completions { | |||
183 | &mut self, | 197 | &mut self, |
184 | ctx: &CompletionContext, | 198 | ctx: &CompletionContext, |
185 | variant: hir::Variant, | 199 | variant: hir::Variant, |
186 | path: ModPath, | 200 | path: hir::ModPath, |
187 | ) { | 201 | ) { |
188 | let item = render_variant(RenderContext::new(ctx), None, None, variant, Some(path)); | 202 | let item = render_variant(RenderContext::new(ctx), None, None, variant, Some(path)); |
189 | self.add(item); | 203 | self.add(item); |
@@ -193,7 +207,7 @@ impl Completions { | |||
193 | &mut self, | 207 | &mut self, |
194 | ctx: &CompletionContext, | 208 | ctx: &CompletionContext, |
195 | variant: hir::Variant, | 209 | variant: hir::Variant, |
196 | local_name: Option<String>, | 210 | local_name: Option<hir::Name>, |
197 | ) { | 211 | ) { |
198 | let item = render_variant(RenderContext::new(ctx), None, local_name, variant, None); | 212 | let item = render_variant(RenderContext::new(ctx), None, local_name, variant, None); |
199 | self.add(item); | 213 | self.add(item); |
diff --git a/crates/ide_completion/src/completions/attribute.rs b/crates/ide_completion/src/completions/attribute.rs index b1505c74b..c48bb9e66 100644 --- a/crates/ide_completion/src/completions/attribute.rs +++ b/crates/ide_completion/src/completions/attribute.rs | |||
@@ -3,9 +3,9 @@ | |||
3 | //! This module uses a bit of static metadata to provide completions | 3 | //! This module uses a bit of static metadata to provide completions |
4 | //! for built-in attributes. | 4 | //! for built-in attributes. |
5 | 5 | ||
6 | use itertools::Itertools; | 6 | use once_cell::sync::Lazy; |
7 | use rustc_hash::FxHashSet; | 7 | use rustc_hash::{FxHashMap, FxHashSet}; |
8 | use syntax::{ast, AstNode, T}; | 8 | use syntax::{algo::non_trivia_sibling, ast, AstNode, Direction, NodeOrToken, SyntaxKind, T}; |
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{ |
11 | context::CompletionContext, | 11 | context::CompletionContext, |
@@ -14,33 +14,45 @@ use crate::{ | |||
14 | Completions, | 14 | Completions, |
15 | }; | 15 | }; |
16 | 16 | ||
17 | pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 17 | mod derive; |
18 | if ctx.mod_declaration_under_caret.is_some() { | 18 | mod lint; |
19 | return None; | 19 | pub(crate) use self::lint::LintCompletion; |
20 | } | ||
21 | 20 | ||
21 | pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | ||
22 | let attribute = ctx.attribute_under_caret.as_ref()?; | 22 | let attribute = ctx.attribute_under_caret.as_ref()?; |
23 | match (attribute.path(), attribute.token_tree()) { | 23 | match (attribute.path().and_then(|p| p.as_single_name_ref()), attribute.token_tree()) { |
24 | (Some(path), Some(token_tree)) => { | 24 | (Some(path), Some(token_tree)) => match path.text().as_str() { |
25 | let path = path.syntax().text(); | 25 | "derive" => derive::complete_derive(acc, ctx, token_tree), |
26 | if path == "derive" { | 26 | "feature" => lint::complete_lint(acc, ctx, token_tree, FEATURES), |
27 | complete_derive(acc, ctx, token_tree) | 27 | "allow" | "warn" | "deny" | "forbid" => { |
28 | } else if path == "feature" { | 28 | lint::complete_lint(acc, ctx, token_tree.clone(), lint::DEFAULT_LINT_COMPLETIONS); |
29 | complete_lint(acc, ctx, token_tree, FEATURES) | 29 | lint::complete_lint(acc, ctx, token_tree, CLIPPY_LINTS); |
30 | } else if path == "allow" || path == "warn" || path == "deny" || path == "forbid" { | ||
31 | complete_lint(acc, ctx, token_tree.clone(), DEFAULT_LINT_COMPLETIONS); | ||
32 | complete_lint(acc, ctx, token_tree, CLIPPY_LINTS); | ||
33 | } | 30 | } |
34 | } | 31 | _ => (), |
35 | (_, Some(_token_tree)) => {} | 32 | }, |
36 | _ => complete_attribute_start(acc, ctx, attribute), | 33 | (None, Some(_)) => (), |
34 | _ => complete_new_attribute(acc, ctx, attribute), | ||
37 | } | 35 | } |
38 | Some(()) | 36 | Some(()) |
39 | } | 37 | } |
40 | 38 | ||
41 | fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { | 39 | fn complete_new_attribute(acc: &mut Completions, ctx: &CompletionContext, attribute: &ast::Attr) { |
42 | let is_inner = attribute.kind() == ast::AttrKind::Inner; | 40 | let is_inner = attribute.kind() == ast::AttrKind::Inner; |
43 | for attr_completion in ATTRIBUTES.iter().filter(|compl| is_inner || !compl.prefer_inner) { | 41 | let attribute_annotated_item_kind = |
42 | attribute.syntax().parent().map(|it| it.kind()).filter(|_| { | ||
43 | is_inner | ||
44 | // If we got nothing coming after the attribute it could be anything so filter it the kind out | ||
45 | || non_trivia_sibling(attribute.syntax().clone().into(), Direction::Next).is_some() | ||
46 | }); | ||
47 | let attributes = attribute_annotated_item_kind.and_then(|kind| { | ||
48 | if ast::Expr::can_cast(kind) { | ||
49 | Some(EXPR_ATTRIBUTES) | ||
50 | } else { | ||
51 | KIND_TO_ATTRIBUTES.get(&kind).copied() | ||
52 | } | ||
53 | }); | ||
54 | |||
55 | let add_completion = |attr_completion: &AttrCompletion| { | ||
44 | let mut item = CompletionItem::new( | 56 | let mut item = CompletionItem::new( |
45 | CompletionKind::Attribute, | 57 | CompletionKind::Attribute, |
46 | ctx.source_range(), | 58 | ctx.source_range(), |
@@ -56,9 +68,19 @@ fn complete_attribute_start(acc: &mut Completions, ctx: &CompletionContext, attr | |||
56 | item.insert_snippet(cap, snippet); | 68 | item.insert_snippet(cap, snippet); |
57 | } | 69 | } |
58 | 70 | ||
59 | if attribute.kind() == ast::AttrKind::Inner || !attr_completion.prefer_inner { | 71 | if is_inner || !attr_completion.prefer_inner { |
60 | acc.add(item.build()); | 72 | acc.add(item.build()); |
61 | } | 73 | } |
74 | }; | ||
75 | |||
76 | match attributes { | ||
77 | Some(applicable) => applicable | ||
78 | .iter() | ||
79 | .flat_map(|name| ATTRIBUTES.binary_search_by(|attr| attr.key().cmp(name)).ok()) | ||
80 | .flat_map(|idx| ATTRIBUTES.get(idx)) | ||
81 | .for_each(add_completion), | ||
82 | None if is_inner => ATTRIBUTES.iter().for_each(add_completion), | ||
83 | None => ATTRIBUTES.iter().filter(|compl| !compl.prefer_inner).for_each(add_completion), | ||
62 | } | 84 | } |
63 | } | 85 | } |
64 | 86 | ||
@@ -70,6 +92,10 @@ struct AttrCompletion { | |||
70 | } | 92 | } |
71 | 93 | ||
72 | impl AttrCompletion { | 94 | impl AttrCompletion { |
95 | fn key(&self) -> &'static str { | ||
96 | self.lookup.unwrap_or(self.label) | ||
97 | } | ||
98 | |||
73 | const fn prefer_inner(self) -> AttrCompletion { | 99 | const fn prefer_inner(self) -> AttrCompletion { |
74 | AttrCompletion { prefer_inner: true, ..self } | 100 | AttrCompletion { prefer_inner: true, ..self } |
75 | } | 101 | } |
@@ -83,30 +109,122 @@ const fn attr( | |||
83 | AttrCompletion { label, lookup, snippet, prefer_inner: false } | 109 | AttrCompletion { label, lookup, snippet, prefer_inner: false } |
84 | } | 110 | } |
85 | 111 | ||
112 | macro_rules! attrs { | ||
113 | // attributes applicable to all items | ||
114 | [@ { item $($tt:tt)* } {$($acc:tt)*}] => { | ||
115 | attrs!(@ { $($tt)* } { $($acc)*, "deprecated", "doc", "dochidden", "docalias", "must_use", "no_mangle" }) | ||
116 | }; | ||
117 | // attributes applicable to all adts | ||
118 | [@ { adt $($tt:tt)* } {$($acc:tt)*}] => { | ||
119 | attrs!(@ { $($tt)* } { $($acc)*, "derive", "repr" }) | ||
120 | }; | ||
121 | // attributes applicable to all linkable things aka functions/statics | ||
122 | [@ { linkable $($tt:tt)* } {$($acc:tt)*}] => { | ||
123 | attrs!(@ { $($tt)* } { $($acc)*, "export_name", "link_name", "link_section" }) | ||
124 | }; | ||
125 | // error fallback for nicer error message | ||
126 | [@ { $ty:ident $($tt:tt)* } {$($acc:tt)*}] => { | ||
127 | compile_error!(concat!("unknown attr subtype ", stringify!($ty))) | ||
128 | }; | ||
129 | // general push down accumulation | ||
130 | [@ { $lit:literal $($tt:tt)*} {$($acc:tt)*}] => { | ||
131 | attrs!(@ { $($tt)* } { $($acc)*, $lit }) | ||
132 | }; | ||
133 | [@ {$($tt:tt)+} {$($tt2:tt)*}] => { | ||
134 | compile_error!(concat!("Unexpected input ", stringify!($($tt)+))) | ||
135 | }; | ||
136 | // final output construction | ||
137 | [@ {} {$($tt:tt)*}] => { &[$($tt)*] as _ }; | ||
138 | // starting matcher | ||
139 | [$($tt:tt),*] => { | ||
140 | attrs!(@ { $($tt)* } { "allow", "cfg", "cfg_attr", "deny", "forbid", "warn" }) | ||
141 | }; | ||
142 | } | ||
143 | |||
144 | #[rustfmt::skip] | ||
145 | static KIND_TO_ATTRIBUTES: Lazy<FxHashMap<SyntaxKind, &[&str]>> = Lazy::new(|| { | ||
146 | use SyntaxKind::*; | ||
147 | std::array::IntoIter::new([ | ||
148 | ( | ||
149 | SOURCE_FILE, | ||
150 | attrs!( | ||
151 | item, | ||
152 | "crate_name", "feature", "no_implicit_prelude", "no_main", "no_std", | ||
153 | "recursion_limit", "type_length_limit", "windows_subsystem" | ||
154 | ), | ||
155 | ), | ||
156 | (MODULE, attrs!(item, "no_implicit_prelude", "path")), | ||
157 | (ITEM_LIST, attrs!(item, "no_implicit_prelude")), | ||
158 | (MACRO_RULES, attrs!(item, "macro_export", "macro_use")), | ||
159 | (MACRO_DEF, attrs!(item)), | ||
160 | (EXTERN_CRATE, attrs!(item, "macro_use", "no_link")), | ||
161 | (USE, attrs!(item)), | ||
162 | (TYPE_ALIAS, attrs!(item)), | ||
163 | (STRUCT, attrs!(item, adt, "non_exhaustive")), | ||
164 | (ENUM, attrs!(item, adt, "non_exhaustive")), | ||
165 | (UNION, attrs!(item, adt)), | ||
166 | (CONST, attrs!(item)), | ||
167 | ( | ||
168 | FN, | ||
169 | attrs!( | ||
170 | item, linkable, | ||
171 | "cold", "ignore", "inline", "must_use", "panic_handler", "proc_macro", | ||
172 | "proc_macro_derive", "proc_macro_attribute", "should_panic", "target_feature", | ||
173 | "test", "track_caller" | ||
174 | ), | ||
175 | ), | ||
176 | (STATIC, attrs!(item, linkable, "global_allocator", "used")), | ||
177 | (TRAIT, attrs!(item, "must_use")), | ||
178 | (IMPL, attrs!(item, "automatically_derived")), | ||
179 | (ASSOC_ITEM_LIST, attrs!(item)), | ||
180 | (EXTERN_BLOCK, attrs!(item, "link")), | ||
181 | (EXTERN_ITEM_LIST, attrs!(item, "link")), | ||
182 | (MACRO_CALL, attrs!()), | ||
183 | (SELF_PARAM, attrs!()), | ||
184 | (PARAM, attrs!()), | ||
185 | (RECORD_FIELD, attrs!()), | ||
186 | (VARIANT, attrs!("non_exhaustive")), | ||
187 | (TYPE_PARAM, attrs!()), | ||
188 | (CONST_PARAM, attrs!()), | ||
189 | (LIFETIME_PARAM, attrs!()), | ||
190 | (LET_STMT, attrs!()), | ||
191 | (EXPR_STMT, attrs!()), | ||
192 | (LITERAL, attrs!()), | ||
193 | (RECORD_EXPR_FIELD_LIST, attrs!()), | ||
194 | (RECORD_EXPR_FIELD, attrs!()), | ||
195 | (MATCH_ARM_LIST, attrs!()), | ||
196 | (MATCH_ARM, attrs!()), | ||
197 | (IDENT_PAT, attrs!()), | ||
198 | (RECORD_PAT_FIELD, attrs!()), | ||
199 | ]) | ||
200 | .collect() | ||
201 | }); | ||
202 | const EXPR_ATTRIBUTES: &[&str] = attrs!(); | ||
203 | |||
86 | /// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index | 204 | /// https://doc.rust-lang.org/reference/attributes.html#built-in-attributes-index |
205 | // Keep these sorted for the binary search! | ||
87 | const ATTRIBUTES: &[AttrCompletion] = &[ | 206 | const ATTRIBUTES: &[AttrCompletion] = &[ |
88 | attr("allow(…)", Some("allow"), Some("allow(${0:lint})")), | 207 | attr("allow(…)", Some("allow"), Some("allow(${0:lint})")), |
89 | attr("automatically_derived", None, None), | 208 | attr("automatically_derived", None, None), |
90 | attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")), | ||
91 | attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")), | 209 | attr("cfg(…)", Some("cfg"), Some("cfg(${0:predicate})")), |
210 | attr("cfg_attr(…)", Some("cfg_attr"), Some("cfg_attr(${1:predicate}, ${0:attr})")), | ||
92 | attr("cold", None, None), | 211 | attr("cold", None, None), |
93 | attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#)) | 212 | attr(r#"crate_name = """#, Some("crate_name"), Some(r#"crate_name = "${0:crate_name}""#)) |
94 | .prefer_inner(), | 213 | .prefer_inner(), |
95 | attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), | 214 | attr("deny(…)", Some("deny"), Some("deny(${0:lint})")), |
96 | attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)), | 215 | attr(r#"deprecated"#, Some("deprecated"), Some(r#"deprecated"#)), |
97 | attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), | 216 | attr("derive(…)", Some("derive"), Some(r#"derive(${0:Debug})"#)), |
217 | attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), | ||
218 | attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)), | ||
219 | attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)), | ||
98 | attr( | 220 | attr( |
99 | r#"export_name = "…""#, | 221 | r#"export_name = "…""#, |
100 | Some("export_name"), | 222 | Some("export_name"), |
101 | Some(r#"export_name = "${0:exported_symbol_name}""#), | 223 | Some(r#"export_name = "${0:exported_symbol_name}""#), |
102 | ), | 224 | ), |
103 | attr(r#"doc(alias = "…")"#, Some("docalias"), Some(r#"doc(alias = "${0:docs}")"#)), | ||
104 | attr(r#"doc = "…""#, Some("doc"), Some(r#"doc = "${0:docs}""#)), | ||
105 | attr(r#"doc(hidden)"#, Some("dochidden"), Some(r#"doc(hidden)"#)), | ||
106 | attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(), | 225 | attr("feature(…)", Some("feature"), Some("feature(${0:flag})")).prefer_inner(), |
107 | attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")), | 226 | attr("forbid(…)", Some("forbid"), Some("forbid(${0:lint})")), |
108 | // FIXME: resolve through macro resolution? | 227 | attr("global_allocator", None, None), |
109 | attr("global_allocator", None, None).prefer_inner(), | ||
110 | attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)), | 228 | attr(r#"ignore = "…""#, Some("ignore"), Some(r#"ignore = "${0:reason}""#)), |
111 | attr("inline", Some("inline"), Some("inline")), | 229 | attr("inline", Some("inline"), Some("inline")), |
112 | attr("link", None, None), | 230 | attr("link", None, None), |
@@ -119,13 +237,13 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
119 | attr("macro_export", None, None), | 237 | attr("macro_export", None, None), |
120 | attr("macro_use", None, None), | 238 | attr("macro_use", None, None), |
121 | attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)), | 239 | attr(r#"must_use"#, Some("must_use"), Some(r#"must_use"#)), |
122 | attr("no_link", None, None).prefer_inner(), | ||
123 | attr("no_implicit_prelude", None, None).prefer_inner(), | 240 | attr("no_implicit_prelude", None, None).prefer_inner(), |
241 | attr("no_link", None, None).prefer_inner(), | ||
124 | attr("no_main", None, None).prefer_inner(), | 242 | attr("no_main", None, None).prefer_inner(), |
125 | attr("no_mangle", None, None), | 243 | attr("no_mangle", None, None), |
126 | attr("no_std", None, None).prefer_inner(), | 244 | attr("no_std", None, None).prefer_inner(), |
127 | attr("non_exhaustive", None, None), | 245 | attr("non_exhaustive", None, None), |
128 | attr("panic_handler", None, None).prefer_inner(), | 246 | attr("panic_handler", None, None), |
129 | attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)), | 247 | attr(r#"path = "…""#, Some("path"), Some(r#"path ="${0:path}""#)), |
130 | attr("proc_macro", None, None), | 248 | attr("proc_macro", None, None), |
131 | attr("proc_macro_attribute", None, None), | 249 | attr("proc_macro_attribute", None, None), |
@@ -153,381 +271,539 @@ const ATTRIBUTES: &[AttrCompletion] = &[ | |||
153 | .prefer_inner(), | 271 | .prefer_inner(), |
154 | ]; | 272 | ]; |
155 | 273 | ||
156 | fn complete_derive(acc: &mut Completions, ctx: &CompletionContext, derive_input: ast::TokenTree) { | 274 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Option<FxHashSet<String>> { |
157 | if let Ok(existing_derives) = parse_comma_sep_input(derive_input) { | 275 | let (l_paren, r_paren) = derive_input.l_paren_token().zip(derive_input.r_paren_token())?; |
158 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | 276 | let mut input_derives = FxHashSet::default(); |
159 | .iter() | 277 | let mut tokens = derive_input |
160 | .filter(|completion| !existing_derives.contains(completion.label)) | 278 | .syntax() |
161 | { | 279 | .children_with_tokens() |
162 | let mut components = vec![derive_completion.label]; | 280 | .filter_map(NodeOrToken::into_token) |
163 | components.extend( | 281 | .skip_while(|token| token != &l_paren) |
164 | derive_completion | 282 | .skip(1) |
165 | .dependencies | 283 | .take_while(|token| token != &r_paren) |
166 | .iter() | 284 | .peekable(); |
167 | .filter(|&&dependency| !existing_derives.contains(dependency)), | 285 | let mut input = String::new(); |
168 | ); | 286 | while tokens.peek().is_some() { |
169 | let lookup = components.join(", "); | 287 | for token in tokens.by_ref().take_while(|t| t.kind() != T![,]) { |
170 | let label = components.iter().rev().join(", "); | 288 | input.push_str(token.text()); |
171 | let mut item = | ||
172 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label); | ||
173 | item.lookup_by(lookup).kind(CompletionItemKind::Attribute); | ||
174 | item.add_to(acc); | ||
175 | } | 289 | } |
176 | 290 | ||
177 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | 291 | if !input.is_empty() { |
178 | let mut item = CompletionItem::new( | 292 | input_derives.insert(input.trim().to_owned()); |
179 | CompletionKind::Attribute, | ||
180 | ctx.source_range(), | ||
181 | custom_derive_name, | ||
182 | ); | ||
183 | item.kind(CompletionItemKind::Attribute); | ||
184 | item.add_to(acc); | ||
185 | } | 293 | } |
186 | } | ||
187 | } | ||
188 | |||
189 | fn complete_lint( | ||
190 | acc: &mut Completions, | ||
191 | ctx: &CompletionContext, | ||
192 | derive_input: ast::TokenTree, | ||
193 | lints_completions: &[LintCompletion], | ||
194 | ) { | ||
195 | if let Ok(existing_lints) = parse_comma_sep_input(derive_input) { | ||
196 | for lint_completion in lints_completions | ||
197 | .into_iter() | ||
198 | .filter(|completion| !existing_lints.contains(completion.label)) | ||
199 | { | ||
200 | let mut item = CompletionItem::new( | ||
201 | CompletionKind::Attribute, | ||
202 | ctx.source_range(), | ||
203 | lint_completion.label, | ||
204 | ); | ||
205 | item.kind(CompletionItemKind::Attribute).detail(lint_completion.description); | ||
206 | item.add_to(acc) | ||
207 | } | ||
208 | } | ||
209 | } | ||
210 | |||
211 | fn parse_comma_sep_input(derive_input: ast::TokenTree) -> Result<FxHashSet<String>, ()> { | ||
212 | match (derive_input.left_delimiter_token(), derive_input.right_delimiter_token()) { | ||
213 | (Some(left_paren), Some(right_paren)) | ||
214 | if left_paren.kind() == T!['('] && right_paren.kind() == T![')'] => | ||
215 | { | ||
216 | let mut input_derives = FxHashSet::default(); | ||
217 | let mut current_derive = String::new(); | ||
218 | for token in derive_input | ||
219 | .syntax() | ||
220 | .children_with_tokens() | ||
221 | .filter_map(|token| token.into_token()) | ||
222 | .skip_while(|token| token != &left_paren) | ||
223 | .skip(1) | ||
224 | .take_while(|token| token != &right_paren) | ||
225 | { | ||
226 | if T![,] == token.kind() { | ||
227 | if !current_derive.is_empty() { | ||
228 | input_derives.insert(current_derive); | ||
229 | current_derive = String::new(); | ||
230 | } | ||
231 | } else { | ||
232 | current_derive.push_str(token.text().trim()); | ||
233 | } | ||
234 | } | ||
235 | 294 | ||
236 | if !current_derive.is_empty() { | 295 | input.clear(); |
237 | input_derives.insert(current_derive); | ||
238 | } | ||
239 | Ok(input_derives) | ||
240 | } | ||
241 | _ => Err(()), | ||
242 | } | 296 | } |
243 | } | ||
244 | |||
245 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
246 | let mut result = FxHashSet::default(); | ||
247 | ctx.scope.process_all_names(&mut |name, scope_def| { | ||
248 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
249 | // FIXME kind() doesn't check whether proc-macro is a derive | ||
250 | if mac.kind() == hir::MacroKind::Derive || mac.kind() == hir::MacroKind::ProcMacro { | ||
251 | result.insert(name.to_string()); | ||
252 | } | ||
253 | } | ||
254 | }); | ||
255 | result | ||
256 | } | ||
257 | |||
258 | struct DeriveCompletion { | ||
259 | label: &'static str, | ||
260 | dependencies: &'static [&'static str], | ||
261 | } | ||
262 | |||
263 | /// Standard Rust derives and the information about their dependencies | ||
264 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
265 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
266 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
267 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
268 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
269 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
270 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
271 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
272 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
273 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
274 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
275 | ]; | ||
276 | 297 | ||
277 | pub(crate) struct LintCompletion { | 298 | Some(input_derives) |
278 | pub(crate) label: &'static str, | ||
279 | pub(crate) description: &'static str, | ||
280 | } | 299 | } |
281 | 300 | ||
282 | #[rustfmt::skip] | ||
283 | const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[ | ||
284 | LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# }, | ||
285 | LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# }, | ||
286 | LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# }, | ||
287 | LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# }, | ||
288 | LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# }, | ||
289 | LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# }, | ||
290 | LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# }, | ||
291 | LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# }, | ||
292 | LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# }, | ||
293 | LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# }, | ||
294 | LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# }, | ||
295 | LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# }, | ||
296 | LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# }, | ||
297 | LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# }, | ||
298 | LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# }, | ||
299 | LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# }, | ||
300 | LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# }, | ||
301 | LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# }, | ||
302 | LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# }, | ||
303 | LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# }, | ||
304 | LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# }, | ||
305 | LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# }, | ||
306 | LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# }, | ||
307 | LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# }, | ||
308 | LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# }, | ||
309 | LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# }, | ||
310 | LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# }, | ||
311 | LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# }, | ||
312 | LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# }, | ||
313 | LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# }, | ||
314 | LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# }, | ||
315 | LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# }, | ||
316 | LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# }, | ||
317 | LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# }, | ||
318 | LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# }, | ||
319 | LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# }, | ||
320 | LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# }, | ||
321 | LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# }, | ||
322 | LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# }, | ||
323 | LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# }, | ||
324 | LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# }, | ||
325 | LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# }, | ||
326 | LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# }, | ||
327 | LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# }, | ||
328 | LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# }, | ||
329 | LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# }, | ||
330 | LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# }, | ||
331 | LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# }, | ||
332 | LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# }, | ||
333 | LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# }, | ||
334 | LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# }, | ||
335 | LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# }, | ||
336 | LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# }, | ||
337 | LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# }, | ||
338 | LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# }, | ||
339 | LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# }, | ||
340 | LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# }, | ||
341 | LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# }, | ||
342 | LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# }, | ||
343 | LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# }, | ||
344 | LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# }, | ||
345 | LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# }, | ||
346 | LintCompletion { label: "path_statements", description: r#"path statements with no effect"# }, | ||
347 | LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# }, | ||
348 | LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# }, | ||
349 | LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# }, | ||
350 | LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# }, | ||
351 | LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# }, | ||
352 | LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# }, | ||
353 | LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# }, | ||
354 | LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# }, | ||
355 | LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# }, | ||
356 | LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# }, | ||
357 | LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# }, | ||
358 | LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# }, | ||
359 | LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# }, | ||
360 | LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# }, | ||
361 | LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# }, | ||
362 | LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# }, | ||
363 | LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# }, | ||
364 | LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# }, | ||
365 | LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# }, | ||
366 | LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# }, | ||
367 | LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# }, | ||
368 | LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# }, | ||
369 | LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# }, | ||
370 | LintCompletion { label: "unused_imports", description: r#"imports that are never used"# }, | ||
371 | LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# }, | ||
372 | LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# }, | ||
373 | LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# }, | ||
374 | LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# }, | ||
375 | LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# }, | ||
376 | LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# }, | ||
377 | LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# }, | ||
378 | LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# }, | ||
379 | LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# }, | ||
380 | LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# }, | ||
381 | LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# }, | ||
382 | LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# }, | ||
383 | LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# }, | ||
384 | LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# }, | ||
385 | LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# }, | ||
386 | LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# }, | ||
387 | LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# }, | ||
388 | LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# }, | ||
389 | LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# }, | ||
390 | LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# }, | ||
391 | LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# }, | ||
392 | LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# }, | ||
393 | LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# }, | ||
394 | LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# }, | ||
395 | LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# }, | ||
396 | LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# }, | ||
397 | LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# }, | ||
398 | LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# }, | ||
399 | ]; | ||
400 | |||
401 | #[cfg(test)] | 301 | #[cfg(test)] |
402 | mod tests { | 302 | mod tests { |
303 | use super::*; | ||
304 | |||
403 | use expect_test::{expect, Expect}; | 305 | use expect_test::{expect, Expect}; |
404 | 306 | ||
405 | use crate::{test_utils::completion_list, CompletionKind}; | 307 | use crate::{test_utils::completion_list, CompletionKind}; |
406 | 308 | ||
309 | #[test] | ||
310 | fn attributes_are_sorted() { | ||
311 | let mut attrs = ATTRIBUTES.iter().map(|attr| attr.key()); | ||
312 | let mut prev = attrs.next().unwrap(); | ||
313 | |||
314 | attrs.for_each(|next| { | ||
315 | assert!( | ||
316 | prev < next, | ||
317 | r#"ATTRIBUTES array is not sorted, "{}" should come after "{}""#, | ||
318 | prev, | ||
319 | next | ||
320 | ); | ||
321 | prev = next; | ||
322 | }); | ||
323 | } | ||
324 | |||
407 | fn check(ra_fixture: &str, expect: Expect) { | 325 | fn check(ra_fixture: &str, expect: Expect) { |
408 | let actual = completion_list(ra_fixture, CompletionKind::Attribute); | 326 | let actual = completion_list(ra_fixture, CompletionKind::Attribute); |
409 | expect.assert_eq(&actual); | 327 | expect.assert_eq(&actual); |
410 | } | 328 | } |
411 | 329 | ||
412 | #[test] | 330 | #[test] |
413 | fn empty_derive_completion() { | 331 | fn test_attribute_completion_inside_nested_attr() { |
332 | check(r#"#[cfg($0)]"#, expect![[]]) | ||
333 | } | ||
334 | |||
335 | #[test] | ||
336 | fn test_attribute_completion_with_existing_attr() { | ||
414 | check( | 337 | check( |
415 | r#" | 338 | r#"#[no_mangle] #[$0] mcall!();"#, |
416 | #[derive($0)] | ||
417 | struct Test {} | ||
418 | "#, | ||
419 | expect![[r#" | 339 | expect![[r#" |
420 | at Clone | 340 | at allow(…) |
421 | at Clone, Copy | 341 | at cfg(…) |
422 | at Debug | 342 | at cfg_attr(…) |
423 | at Default | 343 | at deny(…) |
424 | at Hash | 344 | at forbid(…) |
425 | at PartialEq | 345 | at warn(…) |
426 | at PartialEq, Eq | 346 | "#]], |
427 | at PartialEq, PartialOrd | 347 | ) |
428 | at PartialEq, Eq, PartialOrd, Ord | 348 | } |
349 | |||
350 | #[test] | ||
351 | fn complete_attribute_on_source_file() { | ||
352 | check( | ||
353 | r#"#![$0]"#, | ||
354 | expect![[r#" | ||
355 | at allow(…) | ||
356 | at cfg(…) | ||
357 | at cfg_attr(…) | ||
358 | at deny(…) | ||
359 | at forbid(…) | ||
360 | at warn(…) | ||
361 | at deprecated | ||
362 | at doc = "…" | ||
363 | at doc(hidden) | ||
364 | at doc(alias = "…") | ||
365 | at must_use | ||
366 | at no_mangle | ||
367 | at crate_name = "" | ||
368 | at feature(…) | ||
369 | at no_implicit_prelude | ||
370 | at no_main | ||
371 | at no_std | ||
372 | at recursion_limit = … | ||
373 | at type_length_limit = … | ||
374 | at windows_subsystem = "…" | ||
429 | "#]], | 375 | "#]], |
430 | ); | 376 | ); |
431 | } | 377 | } |
432 | 378 | ||
433 | #[test] | 379 | #[test] |
434 | fn no_completion_for_incorrect_derive() { | 380 | fn complete_attribute_on_module() { |
435 | check( | 381 | check( |
436 | r#" | 382 | r#"#[$0] mod foo;"#, |
437 | #[derive{$0)] | 383 | expect![[r#" |
438 | struct Test {} | 384 | at allow(…) |
439 | "#, | 385 | at cfg(…) |
440 | expect![[r#""#]], | 386 | at cfg_attr(…) |
441 | ) | 387 | at deny(…) |
388 | at forbid(…) | ||
389 | at warn(…) | ||
390 | at deprecated | ||
391 | at doc = "…" | ||
392 | at doc(hidden) | ||
393 | at doc(alias = "…") | ||
394 | at must_use | ||
395 | at no_mangle | ||
396 | at path = "…" | ||
397 | "#]], | ||
398 | ); | ||
399 | check( | ||
400 | r#"mod foo {#![$0]}"#, | ||
401 | expect![[r#" | ||
402 | at allow(…) | ||
403 | at cfg(…) | ||
404 | at cfg_attr(…) | ||
405 | at deny(…) | ||
406 | at forbid(…) | ||
407 | at warn(…) | ||
408 | at deprecated | ||
409 | at doc = "…" | ||
410 | at doc(hidden) | ||
411 | at doc(alias = "…") | ||
412 | at must_use | ||
413 | at no_mangle | ||
414 | at no_implicit_prelude | ||
415 | "#]], | ||
416 | ); | ||
442 | } | 417 | } |
443 | 418 | ||
444 | #[test] | 419 | #[test] |
445 | fn derive_with_input_completion() { | 420 | fn complete_attribute_on_macro_rules() { |
446 | check( | 421 | check( |
447 | r#" | 422 | r#"#[$0] macro_rules! foo {}"#, |
448 | #[derive(serde::Serialize, PartialEq, $0)] | ||
449 | struct Test {} | ||
450 | "#, | ||
451 | expect![[r#" | 423 | expect![[r#" |
452 | at Clone | 424 | at allow(…) |
453 | at Clone, Copy | 425 | at cfg(…) |
454 | at Debug | 426 | at cfg_attr(…) |
455 | at Default | 427 | at deny(…) |
456 | at Hash | 428 | at forbid(…) |
457 | at Eq | 429 | at warn(…) |
458 | at PartialOrd | 430 | at deprecated |
459 | at Eq, PartialOrd, Ord | 431 | at doc = "…" |
432 | at doc(hidden) | ||
433 | at doc(alias = "…") | ||
434 | at must_use | ||
435 | at no_mangle | ||
436 | at macro_export | ||
437 | at macro_use | ||
460 | "#]], | 438 | "#]], |
461 | ) | 439 | ); |
462 | } | 440 | } |
463 | 441 | ||
464 | #[test] | 442 | #[test] |
465 | fn test_attribute_completion() { | 443 | fn complete_attribute_on_macro_def() { |
466 | check( | 444 | check( |
467 | r#"#[$0]"#, | 445 | r#"#[$0] macro foo {}"#, |
468 | expect![[r#" | 446 | expect![[r#" |
469 | at allow(…) | 447 | at allow(…) |
470 | at automatically_derived | 448 | at cfg(…) |
471 | at cfg_attr(…) | 449 | at cfg_attr(…) |
450 | at deny(…) | ||
451 | at forbid(…) | ||
452 | at warn(…) | ||
453 | at deprecated | ||
454 | at doc = "…" | ||
455 | at doc(hidden) | ||
456 | at doc(alias = "…") | ||
457 | at must_use | ||
458 | at no_mangle | ||
459 | "#]], | ||
460 | ); | ||
461 | } | ||
462 | |||
463 | #[test] | ||
464 | fn complete_attribute_on_extern_crate() { | ||
465 | check( | ||
466 | r#"#[$0] extern crate foo;"#, | ||
467 | expect![[r#" | ||
468 | at allow(…) | ||
472 | at cfg(…) | 469 | at cfg(…) |
473 | at cold | 470 | at cfg_attr(…) |
474 | at deny(…) | 471 | at deny(…) |
472 | at forbid(…) | ||
473 | at warn(…) | ||
475 | at deprecated | 474 | at deprecated |
475 | at doc = "…" | ||
476 | at doc(hidden) | ||
477 | at doc(alias = "…") | ||
478 | at must_use | ||
479 | at no_mangle | ||
480 | at macro_use | ||
481 | "#]], | ||
482 | ); | ||
483 | } | ||
484 | |||
485 | #[test] | ||
486 | fn complete_attribute_on_use() { | ||
487 | check( | ||
488 | r#"#[$0] use foo;"#, | ||
489 | expect![[r#" | ||
490 | at allow(…) | ||
491 | at cfg(…) | ||
492 | at cfg_attr(…) | ||
493 | at deny(…) | ||
494 | at forbid(…) | ||
495 | at warn(…) | ||
496 | at deprecated | ||
497 | at doc = "…" | ||
498 | at doc(hidden) | ||
499 | at doc(alias = "…") | ||
500 | at must_use | ||
501 | at no_mangle | ||
502 | "#]], | ||
503 | ); | ||
504 | } | ||
505 | |||
506 | #[test] | ||
507 | fn complete_attribute_on_type_alias() { | ||
508 | check( | ||
509 | r#"#[$0] type foo = ();"#, | ||
510 | expect![[r#" | ||
511 | at allow(…) | ||
512 | at cfg(…) | ||
513 | at cfg_attr(…) | ||
514 | at deny(…) | ||
515 | at forbid(…) | ||
516 | at warn(…) | ||
517 | at deprecated | ||
518 | at doc = "…" | ||
519 | at doc(hidden) | ||
520 | at doc(alias = "…") | ||
521 | at must_use | ||
522 | at no_mangle | ||
523 | "#]], | ||
524 | ); | ||
525 | } | ||
526 | |||
527 | #[test] | ||
528 | fn complete_attribute_on_struct() { | ||
529 | check( | ||
530 | r#"#[$0] struct Foo;"#, | ||
531 | expect![[r#" | ||
532 | at allow(…) | ||
533 | at cfg(…) | ||
534 | at cfg_attr(…) | ||
535 | at deny(…) | ||
536 | at forbid(…) | ||
537 | at warn(…) | ||
538 | at deprecated | ||
539 | at doc = "…" | ||
540 | at doc(hidden) | ||
541 | at doc(alias = "…") | ||
542 | at must_use | ||
543 | at no_mangle | ||
476 | at derive(…) | 544 | at derive(…) |
477 | at export_name = "…" | 545 | at repr(…) |
546 | at non_exhaustive | ||
547 | "#]], | ||
548 | ); | ||
549 | } | ||
550 | |||
551 | #[test] | ||
552 | fn complete_attribute_on_enum() { | ||
553 | check( | ||
554 | r#"#[$0] enum Foo {}"#, | ||
555 | expect![[r#" | ||
556 | at allow(…) | ||
557 | at cfg(…) | ||
558 | at cfg_attr(…) | ||
559 | at deny(…) | ||
560 | at forbid(…) | ||
561 | at warn(…) | ||
562 | at deprecated | ||
563 | at doc = "…" | ||
564 | at doc(hidden) | ||
478 | at doc(alias = "…") | 565 | at doc(alias = "…") |
566 | at must_use | ||
567 | at no_mangle | ||
568 | at derive(…) | ||
569 | at repr(…) | ||
570 | at non_exhaustive | ||
571 | "#]], | ||
572 | ); | ||
573 | } | ||
574 | |||
575 | #[test] | ||
576 | fn complete_attribute_on_const() { | ||
577 | check( | ||
578 | r#"#[$0] const FOO: () = ();"#, | ||
579 | expect![[r#" | ||
580 | at allow(…) | ||
581 | at cfg(…) | ||
582 | at cfg_attr(…) | ||
583 | at deny(…) | ||
584 | at forbid(…) | ||
585 | at warn(…) | ||
586 | at deprecated | ||
479 | at doc = "…" | 587 | at doc = "…" |
480 | at doc(hidden) | 588 | at doc(hidden) |
589 | at doc(alias = "…") | ||
590 | at must_use | ||
591 | at no_mangle | ||
592 | "#]], | ||
593 | ); | ||
594 | } | ||
595 | |||
596 | #[test] | ||
597 | fn complete_attribute_on_static() { | ||
598 | check( | ||
599 | r#"#[$0] static FOO: () = ()"#, | ||
600 | expect![[r#" | ||
601 | at allow(…) | ||
602 | at cfg(…) | ||
603 | at cfg_attr(…) | ||
604 | at deny(…) | ||
481 | at forbid(…) | 605 | at forbid(…) |
482 | at ignore = "…" | 606 | at warn(…) |
483 | at inline | 607 | at deprecated |
484 | at link | 608 | at doc = "…" |
609 | at doc(hidden) | ||
610 | at doc(alias = "…") | ||
611 | at must_use | ||
612 | at no_mangle | ||
613 | at export_name = "…" | ||
485 | at link_name = "…" | 614 | at link_name = "…" |
486 | at link_section = "…" | 615 | at link_section = "…" |
487 | at macro_export | 616 | at global_allocator |
488 | at macro_use | 617 | at used |
618 | "#]], | ||
619 | ); | ||
620 | } | ||
621 | |||
622 | #[test] | ||
623 | fn complete_attribute_on_trait() { | ||
624 | check( | ||
625 | r#"#[$0] trait Foo {}"#, | ||
626 | expect![[r#" | ||
627 | at allow(…) | ||
628 | at cfg(…) | ||
629 | at cfg_attr(…) | ||
630 | at deny(…) | ||
631 | at forbid(…) | ||
632 | at warn(…) | ||
633 | at deprecated | ||
634 | at doc = "…" | ||
635 | at doc(hidden) | ||
636 | at doc(alias = "…") | ||
637 | at must_use | ||
638 | at no_mangle | ||
639 | at must_use | ||
640 | "#]], | ||
641 | ); | ||
642 | } | ||
643 | |||
644 | #[test] | ||
645 | fn complete_attribute_on_impl() { | ||
646 | check( | ||
647 | r#"#[$0] impl () {}"#, | ||
648 | expect![[r#" | ||
649 | at allow(…) | ||
650 | at cfg(…) | ||
651 | at cfg_attr(…) | ||
652 | at deny(…) | ||
653 | at forbid(…) | ||
654 | at warn(…) | ||
655 | at deprecated | ||
656 | at doc = "…" | ||
657 | at doc(hidden) | ||
658 | at doc(alias = "…") | ||
659 | at must_use | ||
660 | at no_mangle | ||
661 | at automatically_derived | ||
662 | "#]], | ||
663 | ); | ||
664 | check( | ||
665 | r#"impl () {#![$0]}"#, | ||
666 | expect![[r#" | ||
667 | at allow(…) | ||
668 | at cfg(…) | ||
669 | at cfg_attr(…) | ||
670 | at deny(…) | ||
671 | at forbid(…) | ||
672 | at warn(…) | ||
673 | at deprecated | ||
674 | at doc = "…" | ||
675 | at doc(hidden) | ||
676 | at doc(alias = "…") | ||
489 | at must_use | 677 | at must_use |
490 | at no_mangle | 678 | at no_mangle |
679 | "#]], | ||
680 | ); | ||
681 | } | ||
682 | |||
683 | #[test] | ||
684 | fn complete_attribute_on_extern_block() { | ||
685 | check( | ||
686 | r#"#[$0] extern {}"#, | ||
687 | expect![[r#" | ||
688 | at allow(…) | ||
689 | at cfg(…) | ||
690 | at cfg_attr(…) | ||
691 | at deny(…) | ||
692 | at forbid(…) | ||
693 | at warn(…) | ||
694 | at deprecated | ||
695 | at doc = "…" | ||
696 | at doc(hidden) | ||
697 | at doc(alias = "…") | ||
698 | at must_use | ||
699 | at no_mangle | ||
700 | at link | ||
701 | "#]], | ||
702 | ); | ||
703 | check( | ||
704 | r#"extern {#![$0]}"#, | ||
705 | expect![[r#" | ||
706 | at allow(…) | ||
707 | at cfg(…) | ||
708 | at cfg_attr(…) | ||
709 | at deny(…) | ||
710 | at forbid(…) | ||
711 | at warn(…) | ||
712 | at deprecated | ||
713 | at doc = "…" | ||
714 | at doc(hidden) | ||
715 | at doc(alias = "…") | ||
716 | at must_use | ||
717 | at no_mangle | ||
718 | at link | ||
719 | "#]], | ||
720 | ); | ||
721 | } | ||
722 | |||
723 | #[test] | ||
724 | fn complete_attribute_on_variant() { | ||
725 | check( | ||
726 | r#"enum Foo { #[$0] Bar }"#, | ||
727 | expect![[r#" | ||
728 | at allow(…) | ||
729 | at cfg(…) | ||
730 | at cfg_attr(…) | ||
731 | at deny(…) | ||
732 | at forbid(…) | ||
733 | at warn(…) | ||
491 | at non_exhaustive | 734 | at non_exhaustive |
492 | at path = "…" | 735 | "#]], |
736 | ); | ||
737 | } | ||
738 | |||
739 | #[test] | ||
740 | fn complete_attribute_on_fn() { | ||
741 | check( | ||
742 | r#"#[$0] fn main() {}"#, | ||
743 | expect![[r#" | ||
744 | at allow(…) | ||
745 | at cfg(…) | ||
746 | at cfg_attr(…) | ||
747 | at deny(…) | ||
748 | at forbid(…) | ||
749 | at warn(…) | ||
750 | at deprecated | ||
751 | at doc = "…" | ||
752 | at doc(hidden) | ||
753 | at doc(alias = "…") | ||
754 | at must_use | ||
755 | at no_mangle | ||
756 | at export_name = "…" | ||
757 | at link_name = "…" | ||
758 | at link_section = "…" | ||
759 | at cold | ||
760 | at ignore = "…" | ||
761 | at inline | ||
762 | at must_use | ||
763 | at panic_handler | ||
493 | at proc_macro | 764 | at proc_macro |
494 | at proc_macro_attribute | ||
495 | at proc_macro_derive(…) | 765 | at proc_macro_derive(…) |
496 | at repr(…) | 766 | at proc_macro_attribute |
497 | at should_panic | 767 | at should_panic |
498 | at target_feature = "…" | 768 | at target_feature = "…" |
499 | at test | 769 | at test |
500 | at track_caller | 770 | at track_caller |
501 | at used | ||
502 | at warn(…) | ||
503 | "#]], | 771 | "#]], |
504 | ) | 772 | ); |
505 | } | 773 | } |
506 | 774 | ||
507 | #[test] | 775 | #[test] |
508 | fn test_attribute_completion_inside_nested_attr() { | 776 | fn complete_attribute_on_expr() { |
509 | check(r#"#[cfg($0)]"#, expect![[]]) | 777 | check( |
778 | r#"fn main() { #[$0] foo() }"#, | ||
779 | expect![[r#" | ||
780 | at allow(…) | ||
781 | at cfg(…) | ||
782 | at cfg_attr(…) | ||
783 | at deny(…) | ||
784 | at forbid(…) | ||
785 | at warn(…) | ||
786 | "#]], | ||
787 | ); | ||
510 | } | 788 | } |
511 | 789 | ||
512 | #[test] | 790 | #[test] |
513 | fn test_inner_attribute_completion() { | 791 | fn complete_attribute_in_source_file_end() { |
514 | check( | 792 | check( |
515 | r"#![$0]", | 793 | r#"#[$0]"#, |
516 | expect![[r#" | 794 | expect![[r#" |
517 | at allow(…) | 795 | at allow(…) |
518 | at automatically_derived | 796 | at automatically_derived |
519 | at cfg_attr(…) | ||
520 | at cfg(…) | 797 | at cfg(…) |
798 | at cfg_attr(…) | ||
521 | at cold | 799 | at cold |
522 | at crate_name = "" | ||
523 | at deny(…) | 800 | at deny(…) |
524 | at deprecated | 801 | at deprecated |
525 | at derive(…) | 802 | at derive(…) |
526 | at export_name = "…" | ||
527 | at doc(alias = "…") | ||
528 | at doc = "…" | 803 | at doc = "…" |
804 | at doc(alias = "…") | ||
529 | at doc(hidden) | 805 | at doc(hidden) |
530 | at feature(…) | 806 | at export_name = "…" |
531 | at forbid(…) | 807 | at forbid(…) |
532 | at global_allocator | 808 | at global_allocator |
533 | at ignore = "…" | 809 | at ignore = "…" |
@@ -538,27 +814,20 @@ struct Test {} | |||
538 | at macro_export | 814 | at macro_export |
539 | at macro_use | 815 | at macro_use |
540 | at must_use | 816 | at must_use |
541 | at no_link | ||
542 | at no_implicit_prelude | ||
543 | at no_main | ||
544 | at no_mangle | 817 | at no_mangle |
545 | at no_std | ||
546 | at non_exhaustive | 818 | at non_exhaustive |
547 | at panic_handler | 819 | at panic_handler |
548 | at path = "…" | 820 | at path = "…" |
549 | at proc_macro | 821 | at proc_macro |
550 | at proc_macro_attribute | 822 | at proc_macro_attribute |
551 | at proc_macro_derive(…) | 823 | at proc_macro_derive(…) |
552 | at recursion_limit = … | ||
553 | at repr(…) | 824 | at repr(…) |
554 | at should_panic | 825 | at should_panic |
555 | at target_feature = "…" | 826 | at target_feature = "…" |
556 | at test | 827 | at test |
557 | at track_caller | 828 | at track_caller |
558 | at type_length_limit = … | ||
559 | at used | 829 | at used |
560 | at warn(…) | 830 | at warn(…) |
561 | at windows_subsystem = "…" | ||
562 | "#]], | 831 | "#]], |
563 | ); | 832 | ); |
564 | } | 833 | } |
diff --git a/crates/ide_completion/src/completions/attribute/derive.rs b/crates/ide_completion/src/completions/attribute/derive.rs new file mode 100644 index 000000000..0bc3eab98 --- /dev/null +++ b/crates/ide_completion/src/completions/attribute/derive.rs | |||
@@ -0,0 +1,147 @@ | |||
1 | //! Completion for derives | ||
2 | use itertools::Itertools; | ||
3 | use rustc_hash::FxHashSet; | ||
4 | use syntax::ast; | ||
5 | |||
6 | use crate::{ | ||
7 | context::CompletionContext, | ||
8 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
9 | Completions, | ||
10 | }; | ||
11 | |||
12 | pub(super) fn complete_derive( | ||
13 | acc: &mut Completions, | ||
14 | ctx: &CompletionContext, | ||
15 | derive_input: ast::TokenTree, | ||
16 | ) { | ||
17 | if let Some(existing_derives) = super::parse_comma_sep_input(derive_input) { | ||
18 | for derive_completion in DEFAULT_DERIVE_COMPLETIONS | ||
19 | .iter() | ||
20 | .filter(|completion| !existing_derives.contains(completion.label)) | ||
21 | { | ||
22 | let mut components = vec![derive_completion.label]; | ||
23 | components.extend( | ||
24 | derive_completion | ||
25 | .dependencies | ||
26 | .iter() | ||
27 | .filter(|&&dependency| !existing_derives.contains(dependency)), | ||
28 | ); | ||
29 | let lookup = components.join(", "); | ||
30 | let label = components.iter().rev().join(", "); | ||
31 | let mut item = | ||
32 | CompletionItem::new(CompletionKind::Attribute, ctx.source_range(), label); | ||
33 | item.lookup_by(lookup).kind(CompletionItemKind::Attribute); | ||
34 | item.add_to(acc); | ||
35 | } | ||
36 | |||
37 | for custom_derive_name in get_derive_names_in_scope(ctx).difference(&existing_derives) { | ||
38 | let mut item = CompletionItem::new( | ||
39 | CompletionKind::Attribute, | ||
40 | ctx.source_range(), | ||
41 | custom_derive_name, | ||
42 | ); | ||
43 | item.kind(CompletionItemKind::Attribute); | ||
44 | item.add_to(acc); | ||
45 | } | ||
46 | } | ||
47 | } | ||
48 | |||
49 | fn get_derive_names_in_scope(ctx: &CompletionContext) -> FxHashSet<String> { | ||
50 | let mut result = FxHashSet::default(); | ||
51 | ctx.scope.process_all_names(&mut |name, scope_def| { | ||
52 | if let hir::ScopeDef::MacroDef(mac) = scope_def { | ||
53 | if mac.kind() == hir::MacroKind::Derive { | ||
54 | result.insert(name.to_string()); | ||
55 | } | ||
56 | } | ||
57 | }); | ||
58 | result | ||
59 | } | ||
60 | |||
61 | struct DeriveCompletion { | ||
62 | label: &'static str, | ||
63 | dependencies: &'static [&'static str], | ||
64 | } | ||
65 | |||
66 | /// Standard Rust derives and the information about their dependencies | ||
67 | /// (the dependencies are needed so that the main derive don't break the compilation when added) | ||
68 | const DEFAULT_DERIVE_COMPLETIONS: &[DeriveCompletion] = &[ | ||
69 | DeriveCompletion { label: "Clone", dependencies: &[] }, | ||
70 | DeriveCompletion { label: "Copy", dependencies: &["Clone"] }, | ||
71 | DeriveCompletion { label: "Debug", dependencies: &[] }, | ||
72 | DeriveCompletion { label: "Default", dependencies: &[] }, | ||
73 | DeriveCompletion { label: "Hash", dependencies: &[] }, | ||
74 | DeriveCompletion { label: "PartialEq", dependencies: &[] }, | ||
75 | DeriveCompletion { label: "Eq", dependencies: &["PartialEq"] }, | ||
76 | DeriveCompletion { label: "PartialOrd", dependencies: &["PartialEq"] }, | ||
77 | DeriveCompletion { label: "Ord", dependencies: &["PartialOrd", "Eq", "PartialEq"] }, | ||
78 | ]; | ||
79 | |||
80 | #[cfg(test)] | ||
81 | mod tests { | ||
82 | use expect_test::{expect, Expect}; | ||
83 | |||
84 | use crate::{test_utils::completion_list, CompletionKind}; | ||
85 | |||
86 | fn check(ra_fixture: &str, expect: Expect) { | ||
87 | let actual = completion_list(ra_fixture, CompletionKind::Attribute); | ||
88 | expect.assert_eq(&actual); | ||
89 | } | ||
90 | |||
91 | #[test] | ||
92 | fn no_completion_for_incorrect_derive() { | ||
93 | check(r#"#[derive{$0)] struct Test;"#, expect![[]]) | ||
94 | } | ||
95 | |||
96 | #[test] | ||
97 | fn empty_derive() { | ||
98 | check( | ||
99 | r#"#[derive($0)] struct Test;"#, | ||
100 | expect![[r#" | ||
101 | at Clone | ||
102 | at Clone, Copy | ||
103 | at Debug | ||
104 | at Default | ||
105 | at Hash | ||
106 | at PartialEq | ||
107 | at PartialEq, Eq | ||
108 | at PartialEq, PartialOrd | ||
109 | at PartialEq, Eq, PartialOrd, Ord | ||
110 | "#]], | ||
111 | ); | ||
112 | } | ||
113 | |||
114 | #[test] | ||
115 | fn derive_with_input() { | ||
116 | check( | ||
117 | r#"#[derive(serde::Serialize, PartialEq, $0)] struct Test;"#, | ||
118 | expect![[r#" | ||
119 | at Clone | ||
120 | at Clone, Copy | ||
121 | at Debug | ||
122 | at Default | ||
123 | at Hash | ||
124 | at Eq | ||
125 | at PartialOrd | ||
126 | at Eq, PartialOrd, Ord | ||
127 | "#]], | ||
128 | ) | ||
129 | } | ||
130 | |||
131 | #[test] | ||
132 | fn derive_with_input2() { | ||
133 | check( | ||
134 | r#"#[derive($0 serde::Serialize, PartialEq)] struct Test;"#, | ||
135 | expect![[r#" | ||
136 | at Clone | ||
137 | at Clone, Copy | ||
138 | at Debug | ||
139 | at Default | ||
140 | at Hash | ||
141 | at Eq | ||
142 | at PartialOrd | ||
143 | at Eq, PartialOrd, Ord | ||
144 | "#]], | ||
145 | ) | ||
146 | } | ||
147 | } | ||
diff --git a/crates/ide_completion/src/completions/attribute/lint.rs b/crates/ide_completion/src/completions/attribute/lint.rs new file mode 100644 index 000000000..403630dce --- /dev/null +++ b/crates/ide_completion/src/completions/attribute/lint.rs | |||
@@ -0,0 +1,187 @@ | |||
1 | //! Completion for lints | ||
2 | use syntax::ast; | ||
3 | |||
4 | use crate::{ | ||
5 | context::CompletionContext, | ||
6 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
7 | Completions, | ||
8 | }; | ||
9 | |||
10 | pub(super) fn complete_lint( | ||
11 | acc: &mut Completions, | ||
12 | ctx: &CompletionContext, | ||
13 | derive_input: ast::TokenTree, | ||
14 | lints_completions: &[LintCompletion], | ||
15 | ) { | ||
16 | if let Some(existing_lints) = super::parse_comma_sep_input(derive_input) { | ||
17 | for lint_completion in lints_completions | ||
18 | .into_iter() | ||
19 | .filter(|completion| !existing_lints.contains(completion.label)) | ||
20 | { | ||
21 | let mut item = CompletionItem::new( | ||
22 | CompletionKind::Attribute, | ||
23 | ctx.source_range(), | ||
24 | lint_completion.label, | ||
25 | ); | ||
26 | item.kind(CompletionItemKind::Attribute).detail(lint_completion.description); | ||
27 | item.add_to(acc) | ||
28 | } | ||
29 | } | ||
30 | } | ||
31 | |||
32 | pub(crate) struct LintCompletion { | ||
33 | pub(crate) label: &'static str, | ||
34 | pub(crate) description: &'static str, | ||
35 | } | ||
36 | |||
37 | #[rustfmt::skip] | ||
38 | pub(super) const DEFAULT_LINT_COMPLETIONS: &[LintCompletion] = &[ | ||
39 | LintCompletion { label: "absolute_paths_not_starting_with_crate", description: r#"fully qualified paths that start with a module name instead of `crate`, `self`, or an extern crate name"# }, | ||
40 | LintCompletion { label: "anonymous_parameters", description: r#"detects anonymous parameters"# }, | ||
41 | LintCompletion { label: "box_pointers", description: r#"use of owned (Box type) heap memory"# }, | ||
42 | LintCompletion { label: "deprecated_in_future", description: r#"detects use of items that will be deprecated in a future version"# }, | ||
43 | LintCompletion { label: "elided_lifetimes_in_paths", description: r#"hidden lifetime parameters in types are deprecated"# }, | ||
44 | LintCompletion { label: "explicit_outlives_requirements", description: r#"outlives requirements can be inferred"# }, | ||
45 | LintCompletion { label: "indirect_structural_match", description: r#"pattern with const indirectly referencing non-structural-match type"# }, | ||
46 | LintCompletion { label: "keyword_idents", description: r#"detects edition keywords being used as an identifier"# }, | ||
47 | LintCompletion { label: "macro_use_extern_crate", description: r#"the `#[macro_use]` attribute is now deprecated in favor of using macros via the module system"# }, | ||
48 | LintCompletion { label: "meta_variable_misuse", description: r#"possible meta-variable misuse at macro definition"# }, | ||
49 | LintCompletion { label: "missing_copy_implementations", description: r#"detects potentially-forgotten implementations of `Copy`"# }, | ||
50 | LintCompletion { label: "missing_crate_level_docs", description: r#"detects crates with no crate-level documentation"# }, | ||
51 | LintCompletion { label: "missing_debug_implementations", description: r#"detects missing implementations of Debug"# }, | ||
52 | LintCompletion { label: "missing_docs", description: r#"detects missing documentation for public members"# }, | ||
53 | LintCompletion { label: "missing_doc_code_examples", description: r#"detects publicly-exported items without code samples in their documentation"# }, | ||
54 | LintCompletion { label: "non_ascii_idents", description: r#"detects non-ASCII identifiers"# }, | ||
55 | LintCompletion { label: "private_doc_tests", description: r#"detects code samples in docs of private items not documented by rustdoc"# }, | ||
56 | LintCompletion { label: "single_use_lifetimes", description: r#"detects lifetime parameters that are only used once"# }, | ||
57 | LintCompletion { label: "trivial_casts", description: r#"detects trivial casts which could be removed"# }, | ||
58 | LintCompletion { label: "trivial_numeric_casts", description: r#"detects trivial casts of numeric types which could be removed"# }, | ||
59 | LintCompletion { label: "unaligned_references", description: r#"detects unaligned references to fields of packed structs"# }, | ||
60 | LintCompletion { label: "unreachable_pub", description: r#"`pub` items not reachable from crate root"# }, | ||
61 | LintCompletion { label: "unsafe_code", description: r#"usage of `unsafe` code"# }, | ||
62 | LintCompletion { label: "unsafe_op_in_unsafe_fn", description: r#"unsafe operations in unsafe functions without an explicit unsafe block are deprecated"# }, | ||
63 | LintCompletion { label: "unstable_features", description: r#"enabling unstable features (deprecated. do not use)"# }, | ||
64 | LintCompletion { label: "unused_crate_dependencies", description: r#"crate dependencies that are never used"# }, | ||
65 | LintCompletion { label: "unused_extern_crates", description: r#"extern crates that are never used"# }, | ||
66 | LintCompletion { label: "unused_import_braces", description: r#"unnecessary braces around an imported item"# }, | ||
67 | LintCompletion { label: "unused_lifetimes", description: r#"detects lifetime parameters that are never used"# }, | ||
68 | LintCompletion { label: "unused_qualifications", description: r#"detects unnecessarily qualified names"# }, | ||
69 | LintCompletion { label: "unused_results", description: r#"unused result of an expression in a statement"# }, | ||
70 | LintCompletion { label: "variant_size_differences", description: r#"detects enums with widely varying variant sizes"# }, | ||
71 | LintCompletion { label: "array_into_iter", description: r#"detects calling `into_iter` on arrays"# }, | ||
72 | LintCompletion { label: "asm_sub_register", description: r#"using only a subset of a register for inline asm inputs"# }, | ||
73 | LintCompletion { label: "bare_trait_objects", description: r#"suggest using `dyn Trait` for trait objects"# }, | ||
74 | LintCompletion { label: "bindings_with_variant_name", description: r#"detects pattern bindings with the same name as one of the matched variants"# }, | ||
75 | LintCompletion { label: "cenum_impl_drop_cast", description: r#"a C-like enum implementing Drop is cast"# }, | ||
76 | LintCompletion { label: "clashing_extern_declarations", description: r#"detects when an extern fn has been declared with the same name but different types"# }, | ||
77 | LintCompletion { label: "coherence_leak_check", description: r#"distinct impls distinguished only by the leak-check code"# }, | ||
78 | LintCompletion { label: "confusable_idents", description: r#"detects visually confusable pairs between identifiers"# }, | ||
79 | LintCompletion { label: "dead_code", description: r#"detect unused, unexported items"# }, | ||
80 | LintCompletion { label: "deprecated", description: r#"detects use of deprecated items"# }, | ||
81 | LintCompletion { label: "ellipsis_inclusive_range_patterns", description: r#"`...` range patterns are deprecated"# }, | ||
82 | LintCompletion { label: "exported_private_dependencies", description: r#"public interface leaks type from a private dependency"# }, | ||
83 | LintCompletion { label: "illegal_floating_point_literal_pattern", description: r#"floating-point literals cannot be used in patterns"# }, | ||
84 | LintCompletion { label: "improper_ctypes", description: r#"proper use of libc types in foreign modules"# }, | ||
85 | LintCompletion { label: "improper_ctypes_definitions", description: r#"proper use of libc types in foreign item definitions"# }, | ||
86 | LintCompletion { label: "incomplete_features", description: r#"incomplete features that may function improperly in some or all cases"# }, | ||
87 | LintCompletion { label: "inline_no_sanitize", description: r#"detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`"# }, | ||
88 | LintCompletion { label: "intra_doc_link_resolution_failure", description: r#"failures in resolving intra-doc link targets"# }, | ||
89 | LintCompletion { label: "invalid_codeblock_attributes", description: r#"codeblock attribute looks a lot like a known one"# }, | ||
90 | LintCompletion { label: "invalid_value", description: r#"an invalid value is being created (such as a NULL reference)"# }, | ||
91 | LintCompletion { label: "irrefutable_let_patterns", description: r#"detects irrefutable patterns in if-let and while-let statements"# }, | ||
92 | LintCompletion { label: "late_bound_lifetime_arguments", description: r#"detects generic lifetime arguments in path segments with late bound lifetime parameters"# }, | ||
93 | LintCompletion { label: "mixed_script_confusables", description: r#"detects Unicode scripts whose mixed script confusables codepoints are solely used"# }, | ||
94 | LintCompletion { label: "mutable_borrow_reservation_conflict", description: r#"reservation of a two-phased borrow conflicts with other shared borrows"# }, | ||
95 | LintCompletion { label: "non_camel_case_types", description: r#"types, variants, traits and type parameters should have camel case names"# }, | ||
96 | LintCompletion { label: "non_shorthand_field_patterns", description: r#"using `Struct { x: x }` instead of `Struct { x }` in a pattern"# }, | ||
97 | LintCompletion { label: "non_snake_case", description: r#"variables, methods, functions, lifetime parameters and modules should have snake case names"# }, | ||
98 | LintCompletion { label: "non_upper_case_globals", description: r#"static constants should have uppercase identifiers"# }, | ||
99 | LintCompletion { label: "no_mangle_generic_items", description: r#"generic items must be mangled"# }, | ||
100 | LintCompletion { label: "overlapping_patterns", description: r#"detects overlapping patterns"# }, | ||
101 | LintCompletion { label: "path_statements", description: r#"path statements with no effect"# }, | ||
102 | LintCompletion { label: "private_in_public", description: r#"detect private items in public interfaces not caught by the old implementation"# }, | ||
103 | LintCompletion { label: "proc_macro_derive_resolution_fallback", description: r#"detects proc macro derives using inaccessible names from parent modules"# }, | ||
104 | LintCompletion { label: "redundant_semicolons", description: r#"detects unnecessary trailing semicolons"# }, | ||
105 | LintCompletion { label: "renamed_and_removed_lints", description: r#"lints that have been renamed or removed"# }, | ||
106 | LintCompletion { label: "safe_packed_borrows", description: r#"safe borrows of fields of packed structs were erroneously allowed"# }, | ||
107 | LintCompletion { label: "stable_features", description: r#"stable features found in `#[feature]` directive"# }, | ||
108 | LintCompletion { label: "trivial_bounds", description: r#"these bounds don't depend on an type parameters"# }, | ||
109 | LintCompletion { label: "type_alias_bounds", description: r#"bounds in type aliases are not enforced"# }, | ||
110 | LintCompletion { label: "tyvar_behind_raw_pointer", description: r#"raw pointer to an inference variable"# }, | ||
111 | LintCompletion { label: "uncommon_codepoints", description: r#"detects uncommon Unicode codepoints in identifiers"# }, | ||
112 | LintCompletion { label: "unconditional_recursion", description: r#"functions that cannot return without calling themselves"# }, | ||
113 | LintCompletion { label: "unknown_lints", description: r#"unrecognized lint attribute"# }, | ||
114 | LintCompletion { label: "unnameable_test_items", description: r#"detects an item that cannot be named being marked as `#[test_case]`"# }, | ||
115 | LintCompletion { label: "unreachable_code", description: r#"detects unreachable code paths"# }, | ||
116 | LintCompletion { label: "unreachable_patterns", description: r#"detects unreachable patterns"# }, | ||
117 | LintCompletion { label: "unstable_name_collisions", description: r#"detects name collision with an existing but unstable method"# }, | ||
118 | LintCompletion { label: "unused_allocation", description: r#"detects unnecessary allocations that can be eliminated"# }, | ||
119 | LintCompletion { label: "unused_assignments", description: r#"detect assignments that will never be read"# }, | ||
120 | LintCompletion { label: "unused_attributes", description: r#"detects attributes that were not used by the compiler"# }, | ||
121 | LintCompletion { label: "unused_braces", description: r#"unnecessary braces around an expression"# }, | ||
122 | LintCompletion { label: "unused_comparisons", description: r#"comparisons made useless by limits of the types involved"# }, | ||
123 | LintCompletion { label: "unused_doc_comments", description: r#"detects doc comments that aren't used by rustdoc"# }, | ||
124 | LintCompletion { label: "unused_features", description: r#"unused features found in crate-level `#[feature]` directives"# }, | ||
125 | LintCompletion { label: "unused_imports", description: r#"imports that are never used"# }, | ||
126 | LintCompletion { label: "unused_labels", description: r#"detects labels that are never used"# }, | ||
127 | LintCompletion { label: "unused_macros", description: r#"detects macros that were not used"# }, | ||
128 | LintCompletion { label: "unused_must_use", description: r#"unused result of a type flagged as `#[must_use]`"# }, | ||
129 | LintCompletion { label: "unused_mut", description: r#"detect mut variables which don't need to be mutable"# }, | ||
130 | LintCompletion { label: "unused_parens", description: r#"`if`, `match`, `while` and `return` do not need parentheses"# }, | ||
131 | LintCompletion { label: "unused_unsafe", description: r#"unnecessary use of an `unsafe` block"# }, | ||
132 | LintCompletion { label: "unused_variables", description: r#"detect variables which are not used in any way"# }, | ||
133 | LintCompletion { label: "warnings", description: r#"mass-change the level for lints which produce warnings"# }, | ||
134 | LintCompletion { label: "where_clauses_object_safety", description: r#"checks the object safety of where clauses"# }, | ||
135 | LintCompletion { label: "while_true", description: r#"suggest using `loop { }` instead of `while true { }`"# }, | ||
136 | LintCompletion { label: "ambiguous_associated_items", description: r#"ambiguous associated items"# }, | ||
137 | LintCompletion { label: "arithmetic_overflow", description: r#"arithmetic operation overflows"# }, | ||
138 | LintCompletion { label: "conflicting_repr_hints", description: r#"conflicts between `#[repr(..)]` hints that were previously accepted and used in practice"# }, | ||
139 | LintCompletion { label: "const_err", description: r#"constant evaluation detected erroneous expression"# }, | ||
140 | LintCompletion { label: "ill_formed_attribute_input", description: r#"ill-formed attribute inputs that were previously accepted and used in practice"# }, | ||
141 | LintCompletion { label: "incomplete_include", description: r#"trailing content in included file"# }, | ||
142 | LintCompletion { label: "invalid_type_param_default", description: r#"type parameter default erroneously allowed in invalid location"# }, | ||
143 | LintCompletion { label: "macro_expanded_macro_exports_accessed_by_absolute_paths", description: r#"macro-expanded `macro_export` macros from the current crate cannot be referred to by absolute paths"# }, | ||
144 | LintCompletion { label: "missing_fragment_specifier", description: r#"detects missing fragment specifiers in unused `macro_rules!` patterns"# }, | ||
145 | LintCompletion { label: "mutable_transmutes", description: r#"mutating transmuted &mut T from &T may cause undefined behavior"# }, | ||
146 | LintCompletion { label: "no_mangle_const_items", description: r#"const items will not have their symbols exported"# }, | ||
147 | LintCompletion { label: "order_dependent_trait_objects", description: r#"trait-object types were treated as different depending on marker-trait order"# }, | ||
148 | LintCompletion { label: "overflowing_literals", description: r#"literal out of range for its type"# }, | ||
149 | LintCompletion { label: "patterns_in_fns_without_body", description: r#"patterns in functions without body were erroneously allowed"# }, | ||
150 | LintCompletion { label: "pub_use_of_private_extern_crate", description: r#"detect public re-exports of private extern crates"# }, | ||
151 | LintCompletion { label: "soft_unstable", description: r#"a feature gate that doesn't break dependent crates"# }, | ||
152 | LintCompletion { label: "unconditional_panic", description: r#"operation will cause a panic at runtime"# }, | ||
153 | LintCompletion { label: "unknown_crate_types", description: r#"unknown crate type found in `#[crate_type]` directive"# }, | ||
154 | ]; | ||
155 | |||
156 | #[cfg(test)] | ||
157 | mod tests { | ||
158 | |||
159 | use crate::test_utils::check_edit; | ||
160 | |||
161 | #[test] | ||
162 | fn check_empty() { | ||
163 | check_edit( | ||
164 | "deprecated", | ||
165 | r#"#[allow($0)] struct Test;"#, | ||
166 | r#"#[allow(deprecated)] struct Test;"#, | ||
167 | ) | ||
168 | } | ||
169 | |||
170 | #[test] | ||
171 | fn check_with_existing() { | ||
172 | check_edit( | ||
173 | "deprecated", | ||
174 | r#"#[allow(keyword_idents, $0)] struct Test;"#, | ||
175 | r#"#[allow(keyword_idents, deprecated)] struct Test;"#, | ||
176 | ) | ||
177 | } | ||
178 | |||
179 | #[test] | ||
180 | fn check_qualified() { | ||
181 | check_edit( | ||
182 | "deprecated", | ||
183 | r#"#[allow(keyword_idents, $0)] struct Test;"#, | ||
184 | r#"#[allow(keyword_idents, deprecated)] struct Test;"#, | ||
185 | ) | ||
186 | } | ||
187 | } | ||
diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index fd9738743..302c9ccbd 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | //! Completes references after dot (fields and method calls). | 1 | //! Completes references after dot (fields and method calls). |
2 | 2 | ||
3 | use hir::{HasVisibility, Type}; | 3 | use either::Either; |
4 | use hir::{HasVisibility, ScopeDef}; | ||
4 | use rustc_hash::FxHashSet; | 5 | use rustc_hash::FxHashSet; |
5 | 6 | ||
6 | use crate::{context::CompletionContext, Completions}; | 7 | use crate::{context::CompletionContext, Completions}; |
@@ -9,7 +10,7 @@ use crate::{context::CompletionContext, Completions}; | |||
9 | pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | 10 | pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { |
10 | let dot_receiver = match &ctx.dot_receiver { | 11 | let dot_receiver = match &ctx.dot_receiver { |
11 | Some(expr) => expr, | 12 | Some(expr) => expr, |
12 | _ => return, | 13 | _ => return complete_undotted_self(acc, ctx), |
13 | }; | 14 | }; |
14 | 15 | ||
15 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { | 16 | let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { |
@@ -20,12 +21,43 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | |||
20 | if ctx.is_call { | 21 | if ctx.is_call { |
21 | cov_mark::hit!(test_no_struct_field_completion_for_method_call); | 22 | cov_mark::hit!(test_no_struct_field_completion_for_method_call); |
22 | } else { | 23 | } else { |
23 | complete_fields(acc, ctx, &receiver_ty); | 24 | complete_fields(ctx, &receiver_ty, |field, ty| match field { |
25 | Either::Left(field) => acc.add_field(ctx, None, field, &ty), | ||
26 | Either::Right(tuple_idx) => acc.add_tuple_field(ctx, None, tuple_idx, &ty), | ||
27 | }); | ||
24 | } | 28 | } |
25 | complete_methods(acc, ctx, &receiver_ty); | 29 | complete_methods(ctx, &receiver_ty, |func| acc.add_method(ctx, func, None, None)); |
30 | } | ||
31 | |||
32 | fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { | ||
33 | if !ctx.is_trivial_path || !ctx.config.enable_self_on_the_fly { | ||
34 | return; | ||
35 | } | ||
36 | ctx.scope.process_all_names(&mut |name, def| { | ||
37 | if let ScopeDef::Local(local) = &def { | ||
38 | if local.is_self(ctx.db) { | ||
39 | let ty = local.ty(ctx.db); | ||
40 | complete_fields(ctx, &ty, |field, ty| match field { | ||
41 | either::Either::Left(field) => { | ||
42 | acc.add_field(ctx, Some(name.clone()), field, &ty) | ||
43 | } | ||
44 | either::Either::Right(tuple_idx) => { | ||
45 | acc.add_tuple_field(ctx, Some(name.clone()), tuple_idx, &ty) | ||
46 | } | ||
47 | }); | ||
48 | complete_methods(ctx, &ty, |func| { | ||
49 | acc.add_method(ctx, func, Some(name.clone()), None) | ||
50 | }); | ||
51 | } | ||
52 | } | ||
53 | }); | ||
26 | } | 54 | } |
27 | 55 | ||
28 | fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { | 56 | fn complete_fields( |
57 | ctx: &CompletionContext, | ||
58 | receiver: &hir::Type, | ||
59 | mut f: impl FnMut(Either<hir::Field, usize>, hir::Type), | ||
60 | ) { | ||
29 | for receiver in receiver.autoderef(ctx.db) { | 61 | for receiver in receiver.autoderef(ctx.db) { |
30 | for (field, ty) in receiver.fields(ctx.db) { | 62 | for (field, ty) in receiver.fields(ctx.db) { |
31 | if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { | 63 | if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { |
@@ -33,16 +65,20 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Ty | |||
33 | // field is editable, we should show the completion | 65 | // field is editable, we should show the completion |
34 | continue; | 66 | continue; |
35 | } | 67 | } |
36 | acc.add_field(ctx, field, &ty); | 68 | f(Either::Left(field), ty); |
37 | } | 69 | } |
38 | for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { | 70 | for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { |
39 | // FIXME: Handle visibility | 71 | // FIXME: Handle visibility |
40 | acc.add_tuple_field(ctx, i, &ty); | 72 | f(Either::Right(i), ty); |
41 | } | 73 | } |
42 | } | 74 | } |
43 | } | 75 | } |
44 | 76 | ||
45 | fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { | 77 | fn complete_methods( |
78 | ctx: &CompletionContext, | ||
79 | receiver: &hir::Type, | ||
80 | mut f: impl FnMut(hir::Function), | ||
81 | ) { | ||
46 | if let Some(krate) = ctx.krate { | 82 | if let Some(krate) = ctx.krate { |
47 | let mut seen_methods = FxHashSet::default(); | 83 | let mut seen_methods = FxHashSet::default(); |
48 | let traits_in_scope = ctx.scope.traits_in_scope(); | 84 | let traits_in_scope = ctx.scope.traits_in_scope(); |
@@ -51,7 +87,7 @@ fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &T | |||
51 | && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) | 87 | && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) |
52 | && seen_methods.insert(func.name(ctx.db)) | 88 | && seen_methods.insert(func.name(ctx.db)) |
53 | { | 89 | { |
54 | acc.add_method(ctx, func, None); | 90 | f(func); |
55 | } | 91 | } |
56 | None::<()> | 92 | None::<()> |
57 | }); | 93 | }); |
@@ -484,4 +520,34 @@ impl S { | |||
484 | "#]], | 520 | "#]], |
485 | ); | 521 | ); |
486 | } | 522 | } |
523 | |||
524 | #[test] | ||
525 | fn completes_bare_fields_and_methods_in_methods() { | ||
526 | check( | ||
527 | r#" | ||
528 | struct Foo { field: i32 } | ||
529 | |||
530 | impl Foo { fn foo(&self) { $0 } }"#, | ||
531 | expect![[r#" | ||
532 | lc self &Foo | ||
533 | sp Self | ||
534 | st Foo | ||
535 | fd self.field i32 | ||
536 | me self.foo() fn(&self) | ||
537 | "#]], | ||
538 | ); | ||
539 | check( | ||
540 | r#" | ||
541 | struct Foo(i32); | ||
542 | |||
543 | impl Foo { fn foo(&mut self) { $0 } }"#, | ||
544 | expect![[r#" | ||
545 | lc self &mut Foo | ||
546 | sp Self | ||
547 | st Foo | ||
548 | fd self.0 i32 | ||
549 | me self.foo() fn(&mut self) | ||
550 | "#]], | ||
551 | ); | ||
552 | } | ||
487 | } | 553 | } |
diff --git a/crates/ide_completion/src/completions/flyimport.rs b/crates/ide_completion/src/completions/flyimport.rs index 9d5b61562..df27e7a84 100644 --- a/crates/ide_completion/src/completions/flyimport.rs +++ b/crates/ide_completion/src/completions/flyimport.rs | |||
@@ -111,11 +111,9 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) | |||
111 | return None; | 111 | return None; |
112 | } | 112 | } |
113 | if ctx.use_item_syntax.is_some() | 113 | if ctx.use_item_syntax.is_some() |
114 | || ctx.attribute_under_caret.is_some() | 114 | || ctx.is_path_disallowed() |
115 | || ctx.mod_declaration_under_caret.is_some() | 115 | || ctx.expects_item() |
116 | || ctx.record_lit_syntax.is_some() | 116 | || ctx.expects_assoc_item() |
117 | || ctx.has_trait_parent | ||
118 | || ctx.has_impl_parent | ||
119 | { | 117 | { |
120 | return None; | 118 | return None; |
121 | } | 119 | } |
diff --git a/crates/ide_completion/src/completions/fn_param.rs b/crates/ide_completion/src/completions/fn_param.rs index 0ea558489..cb90e8a3e 100644 --- a/crates/ide_completion/src/completions/fn_param.rs +++ b/crates/ide_completion/src/completions/fn_param.rs | |||
@@ -128,4 +128,19 @@ fn outer(text: String) { | |||
128 | "#]], | 128 | "#]], |
129 | ) | 129 | ) |
130 | } | 130 | } |
131 | |||
132 | #[test] | ||
133 | fn completes_non_ident_pat_param() { | ||
134 | check( | ||
135 | r#" | ||
136 | struct Bar { bar: u32 } | ||
137 | |||
138 | fn foo(Bar { bar }: Bar) {} | ||
139 | fn foo2($0) {} | ||
140 | "#, | ||
141 | expect![[r#" | ||
142 | bn Bar { bar }: Bar | ||
143 | "#]], | ||
144 | ) | ||
145 | } | ||
131 | } | 146 | } |
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index 61b667104..0d035c611 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs | |||
@@ -2,9 +2,12 @@ | |||
2 | 2 | ||
3 | use std::iter; | 3 | use std::iter; |
4 | 4 | ||
5 | use syntax::SyntaxKind; | 5 | use syntax::{SyntaxKind, T}; |
6 | 6 | ||
7 | use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; | 7 | use crate::{ |
8 | patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind, | ||
9 | CompletionKind, Completions, | ||
10 | }; | ||
8 | 11 | ||
9 | 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) { |
10 | // complete keyword "crate" in use stmt | 13 | // complete keyword "crate" in use stmt |
@@ -44,111 +47,105 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
44 | cov_mark::hit!(no_keyword_completion_in_comments); | 47 | cov_mark::hit!(no_keyword_completion_in_comments); |
45 | return; | 48 | return; |
46 | } | 49 | } |
47 | if ctx.record_lit_syntax.is_some() { | 50 | if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) { |
48 | cov_mark::hit!(no_keyword_completion_in_record_lit); | 51 | cov_mark::hit!(no_keyword_completion_in_record_lit); |
49 | return; | 52 | return; |
50 | } | 53 | } |
54 | let mut add_keyword = |kw, snippet| add_keyword(ctx, acc, kw, snippet); | ||
51 | 55 | ||
52 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; | 56 | let expects_assoc_item = ctx.expects_assoc_item(); |
53 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { | 57 | let has_block_expr_parent = ctx.has_block_expr_parent(); |
54 | add_keyword(ctx, acc, "where", "where "); | 58 | let expects_item = ctx.expects_item(); |
59 | |||
60 | if ctx.has_impl_or_trait_prev_sibling() { | ||
61 | add_keyword("where", "where "); | ||
55 | return; | 62 | return; |
56 | } | 63 | } |
57 | if ctx.unsafe_is_prev { | 64 | if ctx.previous_token_is(T![unsafe]) { |
58 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { | 65 | if expects_item || expects_assoc_item || has_block_expr_parent { |
59 | add_keyword(ctx, acc, "fn", "fn $0() {}") | 66 | add_keyword("fn", "fn $1($2) {\n $0\n}") |
60 | } | 67 | } |
61 | 68 | ||
62 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | 69 | if expects_item || has_block_expr_parent { |
63 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | 70 | add_keyword("trait", "trait $1 {\n $0\n}"); |
64 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | 71 | add_keyword("impl", "impl $1 {\n $0\n}"); |
65 | } | 72 | } |
66 | 73 | ||
67 | return; | 74 | return; |
68 | } | 75 | } |
69 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | ||
70 | { | ||
71 | add_keyword(ctx, acc, "fn", "fn $0() {}"); | ||
72 | } | ||
73 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
74 | add_keyword(ctx, acc, "use", "use "); | ||
75 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
76 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
77 | } | ||
78 | 76 | ||
79 | if ctx.has_item_list_or_source_file_parent { | 77 | if expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_record_field() { |
80 | add_keyword(ctx, acc, "enum", "enum $0 {}"); | 78 | add_keyword("pub(crate)", "pub(crate) "); |
81 | add_keyword(ctx, acc, "struct", "struct $0"); | 79 | add_keyword("pub", "pub "); |
82 | add_keyword(ctx, acc, "union", "union $0 {}"); | ||
83 | } | 80 | } |
84 | 81 | ||
85 | if ctx.is_expr { | 82 | if expects_item || expects_assoc_item || has_block_expr_parent { |
86 | add_keyword(ctx, acc, "match", "match $0 {}"); | 83 | add_keyword("unsafe", "unsafe "); |
87 | add_keyword(ctx, acc, "while", "while $0 {}"); | 84 | add_keyword("fn", "fn $1($2) {\n $0\n}"); |
88 | add_keyword(ctx, acc, "while let", "while let $1 = $0 {}"); | 85 | add_keyword("const", "const $0"); |
89 | add_keyword(ctx, acc, "loop", "loop {$0}"); | 86 | add_keyword("type", "type $0"); |
90 | add_keyword(ctx, acc, "if", "if $0 {}"); | ||
91 | add_keyword(ctx, acc, "if let", "if let $1 = $0 {}"); | ||
92 | add_keyword(ctx, acc, "for", "for $1 in $0 {}"); | ||
93 | } | 87 | } |
94 | 88 | ||
95 | if ctx.if_is_prev || ctx.block_expr_parent { | 89 | if expects_item || has_block_expr_parent { |
96 | add_keyword(ctx, acc, "let", "let "); | 90 | add_keyword("use", "use $0"); |
91 | add_keyword("impl", "impl $1 {\n $0\n}"); | ||
92 | add_keyword("trait", "trait $1 {\n $0\n}"); | ||
93 | add_keyword("static", "static $0"); | ||
94 | add_keyword("extern", "extern $0"); | ||
95 | add_keyword("mod", "mod $0"); | ||
97 | } | 96 | } |
98 | 97 | ||
99 | if ctx.after_if { | 98 | if expects_item { |
100 | add_keyword(ctx, acc, "else", "else {$0}"); | 99 | add_keyword("enum", "enum $1 {\n $0\n}"); |
101 | add_keyword(ctx, acc, "else if", "else if $0 {}"); | 100 | add_keyword("struct", "struct $0"); |
102 | } | 101 | add_keyword("union", "union $1 {\n $0\n}"); |
103 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | ||
104 | add_keyword(ctx, acc, "mod", "mod $0"); | ||
105 | } | 102 | } |
106 | if ctx.bind_pat_parent || ctx.ref_pat_parent { | 103 | |
107 | add_keyword(ctx, acc, "mut", "mut "); | 104 | if ctx.expects_expression() { |
105 | if !has_block_expr_parent { | ||
106 | add_keyword("unsafe", "unsafe {\n $0\n}"); | ||
107 | } | ||
108 | add_keyword("match", "match $1 {\n $0\n}"); | ||
109 | add_keyword("while", "while $1 {\n $0\n}"); | ||
110 | add_keyword("while let", "while let $1 = $2 {\n $0\n}"); | ||
111 | add_keyword("loop", "loop {\n $0\n}"); | ||
112 | add_keyword("if", "if $1 {\n $0\n}"); | ||
113 | add_keyword("if let", "if let $1 = $2 {\n $0\n}"); | ||
114 | add_keyword("for", "for $1 in $2 {\n $0\n}"); | ||
108 | } | 115 | } |
109 | if ctx.has_item_list_or_source_file_parent || has_trait_or_impl_parent || ctx.block_expr_parent | 116 | |
110 | { | 117 | if ctx.previous_token_is(T![if]) || ctx.previous_token_is(T![while]) || has_block_expr_parent { |
111 | add_keyword(ctx, acc, "const", "const "); | 118 | add_keyword("let", "let "); |
112 | add_keyword(ctx, acc, "type", "type "); | ||
113 | } | 119 | } |
114 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | 120 | |
115 | add_keyword(ctx, acc, "static", "static "); | 121 | if ctx.after_if() { |
116 | }; | 122 | add_keyword("else", "else {\n $0\n}"); |
117 | if (ctx.has_item_list_or_source_file_parent) || ctx.block_expr_parent { | 123 | add_keyword("else if", "else if $1 {\n $0\n}"); |
118 | add_keyword(ctx, acc, "extern", "extern "); | ||
119 | } | 124 | } |
120 | if ctx.has_item_list_or_source_file_parent | 125 | |
121 | || has_trait_or_impl_parent | 126 | if ctx.expects_ident_pat_or_ref_expr() { |
122 | || ctx.block_expr_parent | 127 | add_keyword("mut", "mut "); |
123 | || ctx.is_match_arm | ||
124 | { | ||
125 | add_keyword(ctx, acc, "unsafe", "unsafe "); | ||
126 | } | 128 | } |
129 | |||
127 | if ctx.in_loop_body { | 130 | if ctx.in_loop_body { |
128 | if ctx.can_be_stmt { | 131 | if ctx.can_be_stmt { |
129 | add_keyword(ctx, acc, "continue", "continue;"); | 132 | add_keyword("continue", "continue;"); |
130 | add_keyword(ctx, acc, "break", "break;"); | 133 | add_keyword("break", "break;"); |
131 | } else { | 134 | } else { |
132 | add_keyword(ctx, acc, "continue", "continue"); | 135 | add_keyword("continue", "continue"); |
133 | add_keyword(ctx, acc, "break", "break"); | 136 | add_keyword("break", "break"); |
134 | } | 137 | } |
135 | } | 138 | } |
136 | if ctx.has_item_list_or_source_file_parent || ctx.has_impl_parent | ctx.has_field_list_parent { | ||
137 | add_keyword(ctx, acc, "pub(crate)", "pub(crate) "); | ||
138 | add_keyword(ctx, acc, "pub", "pub "); | ||
139 | } | ||
140 | 139 | ||
141 | if !ctx.is_trivial_path { | 140 | if !ctx.is_trivial_path { |
142 | return; | 141 | return; |
143 | } | 142 | } |
144 | let fn_def = match &ctx.function_syntax { | 143 | let fn_def = match &ctx.function_def { |
145 | Some(it) => it, | 144 | Some(it) => it, |
146 | None => return, | 145 | None => return, |
147 | }; | 146 | }; |
148 | 147 | ||
149 | add_keyword( | 148 | add_keyword( |
150 | ctx, | ||
151 | acc, | ||
152 | "return", | 149 | "return", |
153 | match (ctx.can_be_stmt, fn_def.ret_type().is_some()) { | 150 | match (ctx.can_be_stmt, fn_def.ret_type().is_some()) { |
154 | (true, true) => "return $0;", | 151 | (true, true) => "return $0;", |
@@ -165,15 +162,12 @@ fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet | |||
165 | 162 | ||
166 | match ctx.config.snippet_cap { | 163 | match ctx.config.snippet_cap { |
167 | Some(cap) => { | 164 | Some(cap) => { |
168 | let tmp; | 165 | if snippet.ends_with('}') && ctx.incomplete_let { |
169 | let snippet = if snippet.ends_with('}') && ctx.incomplete_let { | ||
170 | cov_mark::hit!(let_semi); | 166 | cov_mark::hit!(let_semi); |
171 | tmp = format!("{};", snippet); | 167 | item.insert_snippet(cap, format!("{};", snippet)); |
172 | &tmp | ||
173 | } else { | 168 | } else { |
174 | snippet | 169 | item.insert_snippet(cap, snippet); |
175 | }; | 170 | } |
176 | item.insert_snippet(cap, snippet); | ||
177 | } | 171 | } |
178 | None => { | 172 | None => { |
179 | item.insert_text(if snippet.contains('$') { kw } else { snippet }); | 173 | item.insert_text(if snippet.contains('$') { kw } else { snippet }); |
@@ -236,21 +230,21 @@ mod tests { | |||
236 | check( | 230 | check( |
237 | r"m$0", | 231 | r"m$0", |
238 | expect![[r#" | 232 | expect![[r#" |
233 | kw pub(crate) | ||
234 | kw pub | ||
235 | kw unsafe | ||
239 | kw fn | 236 | kw fn |
237 | kw const | ||
238 | kw type | ||
240 | kw use | 239 | kw use |
241 | kw impl | 240 | kw impl |
242 | kw trait | 241 | kw trait |
242 | kw static | ||
243 | kw extern | ||
244 | kw mod | ||
243 | kw enum | 245 | kw enum |
244 | kw struct | 246 | kw struct |
245 | kw union | 247 | kw union |
246 | kw mod | ||
247 | kw const | ||
248 | kw type | ||
249 | kw static | ||
250 | kw extern | ||
251 | kw unsafe | ||
252 | kw pub(crate) | ||
253 | kw pub | ||
254 | "#]], | 248 | "#]], |
255 | ); | 249 | ); |
256 | } | 250 | } |
@@ -260,10 +254,16 @@ mod tests { | |||
260 | check( | 254 | check( |
261 | r"fn quux() { $0 }", | 255 | r"fn quux() { $0 }", |
262 | expect![[r#" | 256 | expect![[r#" |
257 | kw unsafe | ||
263 | kw fn | 258 | kw fn |
259 | kw const | ||
260 | kw type | ||
264 | kw use | 261 | kw use |
265 | kw impl | 262 | kw impl |
266 | kw trait | 263 | kw trait |
264 | kw static | ||
265 | kw extern | ||
266 | kw mod | ||
267 | kw match | 267 | kw match |
268 | kw while | 268 | kw while |
269 | kw while let | 269 | kw while let |
@@ -272,12 +272,6 @@ mod tests { | |||
272 | kw if let | 272 | kw if let |
273 | kw for | 273 | kw for |
274 | kw let | 274 | kw let |
275 | kw mod | ||
276 | kw const | ||
277 | kw type | ||
278 | kw static | ||
279 | kw extern | ||
280 | kw unsafe | ||
281 | kw return | 275 | kw return |
282 | "#]], | 276 | "#]], |
283 | ); | 277 | ); |
@@ -288,10 +282,16 @@ mod tests { | |||
288 | check( | 282 | check( |
289 | r"fn quux() { if true { $0 } }", | 283 | r"fn quux() { if true { $0 } }", |
290 | expect![[r#" | 284 | expect![[r#" |
285 | kw unsafe | ||
291 | kw fn | 286 | kw fn |
287 | kw const | ||
288 | kw type | ||
292 | kw use | 289 | kw use |
293 | kw impl | 290 | kw impl |
294 | kw trait | 291 | kw trait |
292 | kw static | ||
293 | kw extern | ||
294 | kw mod | ||
295 | kw match | 295 | kw match |
296 | kw while | 296 | kw while |
297 | kw while let | 297 | kw while let |
@@ -300,12 +300,6 @@ mod tests { | |||
300 | kw if let | 300 | kw if let |
301 | kw for | 301 | kw for |
302 | kw let | 302 | kw let |
303 | kw mod | ||
304 | kw const | ||
305 | kw type | ||
306 | kw static | ||
307 | kw extern | ||
308 | kw unsafe | ||
309 | kw return | 303 | kw return |
310 | "#]], | 304 | "#]], |
311 | ); | 305 | ); |
@@ -316,10 +310,16 @@ mod tests { | |||
316 | check( | 310 | check( |
317 | r#"fn quux() { if true { () } $0 }"#, | 311 | r#"fn quux() { if true { () } $0 }"#, |
318 | expect![[r#" | 312 | expect![[r#" |
313 | kw unsafe | ||
319 | kw fn | 314 | kw fn |
315 | kw const | ||
316 | kw type | ||
320 | kw use | 317 | kw use |
321 | kw impl | 318 | kw impl |
322 | kw trait | 319 | kw trait |
320 | kw static | ||
321 | kw extern | ||
322 | kw mod | ||
323 | kw match | 323 | kw match |
324 | kw while | 324 | kw while |
325 | kw while let | 325 | kw while let |
@@ -330,19 +330,15 @@ mod tests { | |||
330 | kw let | 330 | kw let |
331 | kw else | 331 | kw else |
332 | kw else if | 332 | kw else if |
333 | kw mod | ||
334 | kw const | ||
335 | kw type | ||
336 | kw static | ||
337 | kw extern | ||
338 | kw unsafe | ||
339 | kw return | 333 | kw return |
340 | "#]], | 334 | "#]], |
341 | ); | 335 | ); |
342 | check_edit( | 336 | check_edit( |
343 | "else", | 337 | "else", |
344 | r#"fn quux() { if true { () } $0 }"#, | 338 | r#"fn quux() { if true { () } $0 }"#, |
345 | r#"fn quux() { if true { () } else {$0} }"#, | 339 | r#"fn quux() { if true { () } else { |
340 | $0 | ||
341 | } }"#, | ||
346 | ); | 342 | ); |
347 | } | 343 | } |
348 | 344 | ||
@@ -355,6 +351,7 @@ fn quux() -> i32 { | |||
355 | } | 351 | } |
356 | "#, | 352 | "#, |
357 | expect![[r#" | 353 | expect![[r#" |
354 | kw unsafe | ||
358 | kw match | 355 | kw match |
359 | kw while | 356 | kw while |
360 | kw while let | 357 | kw while let |
@@ -362,7 +359,6 @@ fn quux() -> i32 { | |||
362 | kw if | 359 | kw if |
363 | kw if let | 360 | kw if let |
364 | kw for | 361 | kw for |
365 | kw unsafe | ||
366 | kw return | 362 | kw return |
367 | "#]], | 363 | "#]], |
368 | ); | 364 | ); |
@@ -373,10 +369,10 @@ fn quux() -> i32 { | |||
373 | check( | 369 | check( |
374 | r"trait My { $0 }", | 370 | r"trait My { $0 }", |
375 | expect![[r#" | 371 | expect![[r#" |
372 | kw unsafe | ||
376 | kw fn | 373 | kw fn |
377 | kw const | 374 | kw const |
378 | kw type | 375 | kw type |
379 | kw unsafe | ||
380 | "#]], | 376 | "#]], |
381 | ); | 377 | ); |
382 | } | 378 | } |
@@ -386,12 +382,27 @@ fn quux() -> i32 { | |||
386 | check( | 382 | check( |
387 | r"impl My { $0 }", | 383 | r"impl My { $0 }", |
388 | expect![[r#" | 384 | expect![[r#" |
385 | kw pub(crate) | ||
386 | kw pub | ||
387 | kw unsafe | ||
389 | kw fn | 388 | kw fn |
390 | kw const | 389 | kw const |
391 | kw type | 390 | kw type |
392 | kw unsafe | 391 | "#]], |
392 | ); | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn test_keywords_in_impl_def_with_attr() { | ||
397 | check( | ||
398 | r"impl My { #[foo] $0 }", | ||
399 | expect![[r#" | ||
393 | kw pub(crate) | 400 | kw pub(crate) |
394 | kw pub | 401 | kw pub |
402 | kw unsafe | ||
403 | kw fn | ||
404 | kw const | ||
405 | kw type | ||
395 | "#]], | 406 | "#]], |
396 | ); | 407 | ); |
397 | } | 408 | } |
@@ -401,10 +412,16 @@ fn quux() -> i32 { | |||
401 | check( | 412 | check( |
402 | r"fn my() { loop { $0 } }", | 413 | r"fn my() { loop { $0 } }", |
403 | expect![[r#" | 414 | expect![[r#" |
415 | kw unsafe | ||
404 | kw fn | 416 | kw fn |
417 | kw const | ||
418 | kw type | ||
405 | kw use | 419 | kw use |
406 | kw impl | 420 | kw impl |
407 | kw trait | 421 | kw trait |
422 | kw static | ||
423 | kw extern | ||
424 | kw mod | ||
408 | kw match | 425 | kw match |
409 | kw while | 426 | kw while |
410 | kw while let | 427 | kw while let |
@@ -413,12 +430,6 @@ fn quux() -> i32 { | |||
413 | kw if let | 430 | kw if let |
414 | kw for | 431 | kw for |
415 | kw let | 432 | kw let |
416 | kw mod | ||
417 | kw const | ||
418 | kw type | ||
419 | kw static | ||
420 | kw extern | ||
421 | kw unsafe | ||
422 | kw continue | 433 | kw continue |
423 | kw break | 434 | kw break |
424 | kw return | 435 | kw return |
@@ -565,6 +576,7 @@ pub mod future { | |||
565 | check( | 576 | check( |
566 | r#"fn main() { let _ = $0 }"#, | 577 | r#"fn main() { let _ = $0 }"#, |
567 | expect![[r#" | 578 | expect![[r#" |
579 | kw unsafe | ||
568 | kw match | 580 | kw match |
569 | kw while | 581 | kw while |
570 | kw while let | 582 | kw while let |
@@ -625,6 +637,7 @@ fn foo() { | |||
625 | } | 637 | } |
626 | "#, | 638 | "#, |
627 | expect![[r#" | 639 | expect![[r#" |
640 | kw unsafe | ||
628 | kw match | 641 | kw match |
629 | kw while | 642 | kw while |
630 | kw while let | 643 | kw while let |
@@ -646,7 +659,9 @@ fn foo() { | |||
646 | fn main() { let x = $0 } | 659 | fn main() { let x = $0 } |
647 | "#, | 660 | "#, |
648 | r#" | 661 | r#" |
649 | fn main() { let x = match $0 {}; } | 662 | fn main() { let x = match $1 { |
663 | $0 | ||
664 | }; } | ||
650 | "#, | 665 | "#, |
651 | ); | 666 | ); |
652 | 667 | ||
@@ -660,7 +675,9 @@ fn main() { | |||
660 | "#, | 675 | "#, |
661 | r#" | 676 | r#" |
662 | fn main() { | 677 | fn main() { |
663 | let x = if $0 {}; | 678 | let x = if $1 { |
679 | $0 | ||
680 | }; | ||
664 | let y = 92; | 681 | let y = 92; |
665 | } | 682 | } |
666 | "#, | 683 | "#, |
@@ -676,7 +693,9 @@ fn main() { | |||
676 | "#, | 693 | "#, |
677 | r#" | 694 | r#" |
678 | fn main() { | 695 | fn main() { |
679 | let x = loop {$0}; | 696 | let x = loop { |
697 | $0 | ||
698 | }; | ||
680 | bar(); | 699 | bar(); |
681 | } | 700 | } |
682 | "#, | 701 | "#, |
diff --git a/crates/ide_completion/src/completions/lifetime.rs b/crates/ide_completion/src/completions/lifetime.rs index 5eeddf7a4..8ccccb646 100644 --- a/crates/ide_completion/src/completions/lifetime.rs +++ b/crates/ide_completion/src/completions/lifetime.rs | |||
@@ -8,19 +8,23 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) | |||
8 | if !ctx.lifetime_allowed { | 8 | if !ctx.lifetime_allowed { |
9 | return; | 9 | return; |
10 | } | 10 | } |
11 | let lp_string; | ||
11 | let param_lifetime = match ( | 12 | let param_lifetime = match ( |
12 | &ctx.lifetime_syntax, | 13 | &ctx.lifetime_syntax, |
13 | ctx.lifetime_param_syntax.as_ref().and_then(|lp| lp.lifetime()), | 14 | ctx.lifetime_param_syntax.as_ref().and_then(|lp| lp.lifetime()), |
14 | ) { | 15 | ) { |
15 | (Some(lt), Some(lp)) if lp == lt.clone() => return, | 16 | (Some(lt), Some(lp)) if lp == lt.clone() => return, |
16 | (Some(_), Some(lp)) => Some(lp.to_string()), | 17 | (Some(_), Some(lp)) => { |
18 | lp_string = lp.to_string(); | ||
19 | Some(&*lp_string) | ||
20 | } | ||
17 | _ => None, | 21 | _ => None, |
18 | }; | 22 | }; |
19 | 23 | ||
20 | ctx.scope.process_all_names(&mut |name, res| { | 24 | ctx.scope.process_all_names(&mut |name, res| { |
21 | if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res { | 25 | if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res { |
22 | if param_lifetime != Some(name.to_string()) { | 26 | if param_lifetime != Some(&*name.to_string()) { |
23 | acc.add_resolution(ctx, name.to_string(), &res); | 27 | acc.add_resolution(ctx, name, &res); |
24 | } | 28 | } |
25 | } | 29 | } |
26 | }); | 30 | }); |
@@ -36,7 +40,7 @@ pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) { | |||
36 | } | 40 | } |
37 | ctx.scope.process_all_names(&mut |name, res| { | 41 | ctx.scope.process_all_names(&mut |name, res| { |
38 | if let ScopeDef::Label(_) = res { | 42 | if let ScopeDef::Label(_) = res { |
39 | acc.add_resolution(ctx, name.to_string(), &res); | 43 | acc.add_resolution(ctx, name, &res); |
40 | } | 44 | } |
41 | }); | 45 | }); |
42 | } | 46 | } |
diff --git a/crates/ide_completion/src/completions/macro_in_item_position.rs b/crates/ide_completion/src/completions/macro_in_item_position.rs index 2be299ac2..202e71215 100644 --- a/crates/ide_completion/src/completions/macro_in_item_position.rs +++ b/crates/ide_completion/src/completions/macro_in_item_position.rs | |||
@@ -2,15 +2,22 @@ | |||
2 | 2 | ||
3 | use crate::{CompletionContext, Completions}; | 3 | use crate::{CompletionContext, Completions}; |
4 | 4 | ||
5 | // Ideally this should be removed and moved into `(un)qualified_path` respectively | ||
5 | pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { | 6 | pub(crate) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { |
6 | // Show only macros in top level. | 7 | // Show only macros in top level. |
7 | if ctx.is_new_item { | 8 | if !ctx.is_new_item { |
8 | ctx.scope.process_all_names(&mut |name, res| { | 9 | return; |
9 | if let hir::ScopeDef::MacroDef(mac) = res { | ||
10 | acc.add_macro(ctx, Some(name.to_string()), mac); | ||
11 | } | ||
12 | }) | ||
13 | } | 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 | }) | ||
14 | } | 21 | } |
15 | 22 | ||
16 | #[cfg(test)] | 23 | #[cfg(test)] |
diff --git a/crates/ide_completion/src/completions/mod_.rs b/crates/ide_completion/src/completions/mod_.rs index 4f9415736..6a5746fb9 100644 --- a/crates/ide_completion/src/completions/mod_.rs +++ b/crates/ide_completion/src/completions/mod_.rs | |||
@@ -9,14 +9,14 @@ use ide_db::{ | |||
9 | }; | 9 | }; |
10 | use rustc_hash::FxHashSet; | 10 | use rustc_hash::FxHashSet; |
11 | 11 | ||
12 | use crate::CompletionItem; | 12 | use crate::{patterns::ImmediateLocation, CompletionItem}; |
13 | 13 | ||
14 | use crate::{context::CompletionContext, item::CompletionKind, Completions}; | 14 | use crate::{context::CompletionContext, item::CompletionKind, Completions}; |
15 | 15 | ||
16 | /// Complete mod declaration, i.e. `mod $0 ;` | 16 | /// Complete mod declaration, i.e. `mod $0 ;` |
17 | pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 17 | pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
18 | let mod_under_caret = match &ctx.mod_declaration_under_caret { | 18 | let mod_under_caret = match &ctx.completion_location { |
19 | Some(mod_under_caret) if mod_under_caret.item_list().is_none() => mod_under_caret, | 19 | Some(ImmediateLocation::ModDeclaration(mod_under_caret)) => mod_under_caret, |
20 | _ => return None, | 20 | _ => return None, |
21 | }; | 21 | }; |
22 | 22 | ||
diff --git a/crates/ide_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs index 8dc9ab73c..8a728c67e 100644 --- a/crates/ide_completion/src/completions/pattern.rs +++ b/crates/ide_completion/src/completions/pattern.rs | |||
@@ -1,17 +1,15 @@ | |||
1 | //! Completes constants and paths in patterns. | 1 | //! Completes constants and paths in patterns. |
2 | 2 | ||
3 | use crate::{CompletionContext, Completions}; | 3 | use crate::{context::PatternRefutability, CompletionContext, Completions}; |
4 | 4 | ||
5 | /// Completes constants and paths in patterns. | 5 | /// Completes constants and paths in patterns. |
6 | pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | 6 | pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { |
7 | if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_pat_binding) { | 7 | let refutable = match ctx.is_pat_or_const { |
8 | return; | 8 | Some(it) => it == PatternRefutability::Refutable, |
9 | } | 9 | None => return, |
10 | if ctx.record_pat_syntax.is_some() { | 10 | }; |
11 | return; | ||
12 | } | ||
13 | 11 | ||
14 | if !ctx.is_irrefutable_pat_binding { | 12 | if refutable { |
15 | if let Some(hir::Adt::Enum(e)) = | 13 | if let Some(hir::Adt::Enum(e)) = |
16 | ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) | 14 | ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) |
17 | { | 15 | { |
@@ -31,14 +29,14 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
31 | acc.add_struct_pat(ctx, *strukt, Some(name.clone())); | 29 | acc.add_struct_pat(ctx, *strukt, Some(name.clone())); |
32 | true | 30 | true |
33 | } | 31 | } |
34 | hir::ModuleDef::Variant(variant) if !ctx.is_irrefutable_pat_binding => { | 32 | hir::ModuleDef::Variant(variant) if refutable => { |
35 | acc.add_variant_pat(ctx, *variant, Some(name.clone())); | 33 | acc.add_variant_pat(ctx, *variant, Some(name.clone())); |
36 | true | 34 | true |
37 | } | 35 | } |
38 | hir::ModuleDef::Adt(hir::Adt::Enum(..)) | 36 | hir::ModuleDef::Adt(hir::Adt::Enum(..)) |
39 | | hir::ModuleDef::Variant(..) | 37 | | hir::ModuleDef::Variant(..) |
40 | | hir::ModuleDef::Const(..) | 38 | | hir::ModuleDef::Const(..) |
41 | | hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding, | 39 | | hir::ModuleDef::Module(..) => refutable, |
42 | _ => false, | 40 | _ => false, |
43 | }, | 41 | }, |
44 | hir::ScopeDef::MacroDef(_) => true, | 42 | hir::ScopeDef::MacroDef(_) => true, |
@@ -47,13 +45,13 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { | |||
47 | acc.add_struct_pat(ctx, strukt, Some(name.clone())); | 45 | acc.add_struct_pat(ctx, strukt, Some(name.clone())); |
48 | true | 46 | true |
49 | } | 47 | } |
50 | Some(hir::Adt::Enum(_)) => !ctx.is_irrefutable_pat_binding, | 48 | Some(hir::Adt::Enum(_)) => refutable, |
51 | _ => true, | 49 | _ => true, |
52 | }, | 50 | }, |
53 | _ => false, | 51 | _ => false, |
54 | }; | 52 | }; |
55 | if add_resolution { | 53 | if add_resolution { |
56 | acc.add_resolution(ctx, name.to_string(), &res); | 54 | acc.add_resolution(ctx, name, &res); |
57 | } | 55 | } |
58 | }); | 56 | }); |
59 | } | 57 | } |
@@ -402,4 +400,31 @@ impl Foo { | |||
402 | "#]], | 400 | "#]], |
403 | ) | 401 | ) |
404 | } | 402 | } |
403 | |||
404 | #[test] | ||
405 | fn completes_in_record_field_pat() { | ||
406 | check_snippet( | ||
407 | r#" | ||
408 | struct Foo { bar: Bar } | ||
409 | struct Bar(u32); | ||
410 | fn outer(Foo { bar: $0 }: Foo) {} | ||
411 | "#, | ||
412 | expect![[r#" | ||
413 | bn Foo Foo { bar$1 }$0 | ||
414 | bn Bar Bar($1)$0 | ||
415 | "#]], | ||
416 | ) | ||
417 | } | ||
418 | |||
419 | #[test] | ||
420 | fn skips_in_record_field_pat_name() { | ||
421 | check_snippet( | ||
422 | r#" | ||
423 | struct Foo { bar: Bar } | ||
424 | struct Bar(u32); | ||
425 | fn outer(Foo { bar$0 }: Foo) {} | ||
426 | "#, | ||
427 | expect![[r#""#]], | ||
428 | ) | ||
429 | } | ||
405 | } | 430 | } |
diff --git a/crates/ide_completion/src/completions/qualified_path.rs b/crates/ide_completion/src/completions/qualified_path.rs index eedb44873..de58ce1cd 100644 --- a/crates/ide_completion/src/completions/qualified_path.rs +++ b/crates/ide_completion/src/completions/qualified_path.rs | |||
@@ -1,27 +1,39 @@ | |||
1 | //! Completion of paths, i.e. `some::prefix::$0`. | 1 | //! Completion of paths, i.e. `some::prefix::$0`. |
2 | 2 | ||
3 | use hir::{Adt, HasVisibility, PathResolution, ScopeDef}; | 3 | use hir::HasVisibility; |
4 | use rustc_hash::FxHashSet; | 4 | use rustc_hash::FxHashSet; |
5 | use syntax::AstNode; | 5 | use syntax::AstNode; |
6 | 6 | ||
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() { | ||
11 | return; | ||
12 | } | ||
10 | let path = match &ctx.path_qual { | 13 | let path = match &ctx.path_qual { |
11 | Some(path) => path.clone(), | 14 | Some(path) => path.clone(), |
12 | None => return, | 15 | None => return, |
13 | }; | 16 | }; |
14 | 17 | ||
15 | if ctx.attribute_under_caret.is_some() || ctx.mod_declaration_under_caret.is_some() { | ||
16 | return; | ||
17 | } | ||
18 | |||
19 | let context_module = ctx.scope.module(); | ||
20 | |||
21 | let resolution = match ctx.sema.resolve_path(&path) { | 18 | let resolution = match ctx.sema.resolve_path(&path) { |
22 | Some(res) => res, | 19 | Some(res) => res, |
23 | None => return, | 20 | None => return, |
24 | }; | 21 | }; |
22 | let context_module = ctx.scope.module(); | ||
23 | if ctx.expects_assoc_item() { | ||
24 | if let hir::PathResolution::Def(hir::ModuleDef::Module(module)) = resolution { | ||
25 | let module_scope = module.scope(ctx.db, context_module); | ||
26 | for (name, def) in module_scope { | ||
27 | if let hir::ScopeDef::MacroDef(macro_def) = def { | ||
28 | acc.add_macro(ctx, Some(name.clone()), macro_def); | ||
29 | } | ||
30 | if let hir::ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = def { | ||
31 | acc.add_resolution(ctx, name, &def); | ||
32 | } | ||
33 | } | ||
34 | } | ||
35 | return; | ||
36 | } | ||
25 | 37 | ||
26 | // Add associated types on type parameters and `Self`. | 38 | // Add associated types on type parameters and `Self`. |
27 | resolution.assoc_type_shorthand_candidates(ctx.db, |_, alias| { | 39 | resolution.assoc_type_shorthand_candidates(ctx.db, |_, alias| { |
@@ -30,11 +42,11 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
30 | }); | 42 | }); |
31 | 43 | ||
32 | match resolution { | 44 | match resolution { |
33 | PathResolution::Def(hir::ModuleDef::Module(module)) => { | 45 | hir::PathResolution::Def(hir::ModuleDef::Module(module)) => { |
34 | let module_scope = module.scope(ctx.db, context_module); | 46 | let module_scope = module.scope(ctx.db, context_module); |
35 | for (name, def) in module_scope { | 47 | for (name, def) in module_scope { |
36 | if ctx.use_item_syntax.is_some() { | 48 | if ctx.use_item_syntax.is_some() { |
37 | if let ScopeDef::Unknown = def { | 49 | if let hir::ScopeDef::Unknown = def { |
38 | if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { | 50 | if let Some(name_ref) = ctx.name_ref_syntax.as_ref() { |
39 | if name_ref.syntax().text() == name.to_string().as_str() { | 51 | if name_ref.syntax().text() == name.to_string().as_str() { |
40 | // for `use self::foo$0`, don't suggest `foo` as a completion | 52 | // for `use self::foo$0`, don't suggest `foo` as a completion |
@@ -45,20 +57,20 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
45 | } | 57 | } |
46 | } | 58 | } |
47 | 59 | ||
48 | acc.add_resolution(ctx, name.to_string(), &def); | 60 | acc.add_resolution(ctx, name, &def); |
49 | } | 61 | } |
50 | } | 62 | } |
51 | PathResolution::Def(def @ hir::ModuleDef::Adt(_)) | 63 | hir::PathResolution::Def(def @ hir::ModuleDef::Adt(_)) |
52 | | PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) | 64 | | hir::PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) |
53 | | PathResolution::Def(def @ hir::ModuleDef::BuiltinType(_)) => { | 65 | | hir::PathResolution::Def(def @ hir::ModuleDef::BuiltinType(_)) => { |
54 | if let hir::ModuleDef::Adt(Adt::Enum(e)) = def { | 66 | if let hir::ModuleDef::Adt(hir::Adt::Enum(e)) = def { |
55 | add_enum_variants(ctx, acc, e); | 67 | add_enum_variants(ctx, acc, e); |
56 | } | 68 | } |
57 | let ty = match def { | 69 | let ty = match def { |
58 | hir::ModuleDef::Adt(adt) => adt.ty(ctx.db), | 70 | hir::ModuleDef::Adt(adt) => adt.ty(ctx.db), |
59 | hir::ModuleDef::TypeAlias(a) => { | 71 | hir::ModuleDef::TypeAlias(a) => { |
60 | let ty = a.ty(ctx.db); | 72 | let ty = a.ty(ctx.db); |
61 | if let Some(Adt::Enum(e)) = ty.as_adt() { | 73 | if let Some(hir::Adt::Enum(e)) = ty.as_adt() { |
62 | cov_mark::hit!(completes_variant_through_alias); | 74 | cov_mark::hit!(completes_variant_through_alias); |
63 | add_enum_variants(ctx, acc, e); | 75 | add_enum_variants(ctx, acc, e); |
64 | } | 76 | } |
@@ -105,7 +117,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
105 | }); | 117 | }); |
106 | } | 118 | } |
107 | } | 119 | } |
108 | PathResolution::Def(hir::ModuleDef::Trait(t)) => { | 120 | hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => { |
109 | // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`. | 121 | // Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`. |
110 | for item in t.items(ctx.db) { | 122 | for item in t.items(ctx.db) { |
111 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { | 123 | if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { |
@@ -118,15 +130,15 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon | |||
118 | } | 130 | } |
119 | } | 131 | } |
120 | } | 132 | } |
121 | PathResolution::TypeParam(_) | PathResolution::SelfType(_) => { | 133 | hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => { |
122 | if let Some(krate) = ctx.krate { | 134 | if let Some(krate) = ctx.krate { |
123 | let ty = match resolution { | 135 | let ty = match resolution { |
124 | PathResolution::TypeParam(param) => param.ty(ctx.db), | 136 | hir::PathResolution::TypeParam(param) => param.ty(ctx.db), |
125 | PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db), | 137 | hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db), |
126 | _ => return, | 138 | _ => return, |
127 | }; | 139 | }; |
128 | 140 | ||
129 | if let Some(Adt::Enum(e)) = ty.as_adt() { | 141 | if let Some(hir::Adt::Enum(e)) = ty.as_adt() { |
130 | add_enum_variants(ctx, acc, e); | 142 | add_enum_variants(ctx, acc, e); |
131 | } | 143 | } |
132 | 144 | ||
@@ -596,7 +608,7 @@ fn main() { T::$0; } | |||
596 | macro_rules! foo { () => {} } | 608 | macro_rules! foo { () => {} } |
597 | 609 | ||
598 | fn main() { let _ = crate::$0 } | 610 | fn main() { let _ = crate::$0 } |
599 | "#, | 611 | "#, |
600 | expect![[r##" | 612 | expect![[r##" |
601 | fn main() fn() | 613 | fn main() fn() |
602 | ma foo!(…) #[macro_export] macro_rules! foo | 614 | ma foo!(…) #[macro_export] macro_rules! foo |
@@ -605,6 +617,44 @@ fn main() { let _ = crate::$0 } | |||
605 | } | 617 | } |
606 | 618 | ||
607 | #[test] | 619 | #[test] |
620 | fn completes_in_assoc_item_list() { | ||
621 | check( | ||
622 | r#" | ||
623 | #[macro_export] | ||
624 | macro_rules! foo { () => {} } | ||
625 | mod bar {} | ||
626 | |||
627 | struct MyStruct {} | ||
628 | impl MyStruct { | ||
629 | crate::$0 | ||
630 | } | ||
631 | "#, | ||
632 | expect![[r##" | ||
633 | md bar | ||
634 | ma foo! #[macro_export] macro_rules! foo | ||
635 | "##]], | ||
636 | ); | ||
637 | } | ||
638 | |||
639 | #[test] | ||
640 | #[ignore] // FIXME doesn't complete anything atm | ||
641 | fn completes_in_item_list() { | ||
642 | check( | ||
643 | r#" | ||
644 | struct MyStruct {} | ||
645 | macro_rules! foo {} | ||
646 | mod bar {} | ||
647 | |||
648 | crate::$0 | ||
649 | "#, | ||
650 | expect![[r#" | ||
651 | md bar | ||
652 | ma foo! macro_rules! foo | ||
653 | "#]], | ||
654 | ) | ||
655 | } | ||
656 | |||
657 | #[test] | ||
608 | fn test_super_super_completion() { | 658 | fn test_super_super_completion() { |
609 | check( | 659 | check( |
610 | r#" | 660 | r#" |
diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs index 2f95b8687..0ac47cdbe 100644 --- a/crates/ide_completion/src/completions/record.rs +++ b/crates/ide_completion/src/completions/record.rs | |||
@@ -2,41 +2,44 @@ | |||
2 | use ide_db::{helpers::FamousDefs, SymbolKind}; | 2 | use ide_db::{helpers::FamousDefs, SymbolKind}; |
3 | use syntax::ast::Expr; | 3 | use syntax::ast::Expr; |
4 | 4 | ||
5 | use crate::{item::CompletionKind, CompletionContext, CompletionItem, Completions}; | 5 | use crate::{ |
6 | item::CompletionKind, patterns::ImmediateLocation, CompletionContext, CompletionItem, | ||
7 | Completions, | ||
8 | }; | ||
6 | 9 | ||
7 | pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 10 | pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
8 | let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { | 11 | let missing_fields = match &ctx.completion_location { |
9 | (None, None) => return None, | 12 | Some(ImmediateLocation::RecordExpr(record_expr)) => { |
10 | (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), | 13 | let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone())); |
11 | (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), | ||
12 | (_, Some(record_lit)) => { | ||
13 | let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_lit.clone())); | ||
14 | let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default(); | 14 | let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default(); |
15 | let impl_default_trait = default_trait | 15 | let impl_default_trait = default_trait |
16 | .and_then(|default_trait| ty.map(|ty| ty.impls_trait(ctx.db, default_trait, &[]))) | 16 | .zip(ty) |
17 | .unwrap_or(false); | 17 | .map_or(false, |(default_trait, ty)| ty.impls_trait(ctx.db, default_trait, &[])); |
18 | 18 | ||
19 | let missing_fields = ctx.sema.record_literal_missing_fields(record_lit); | 19 | let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); |
20 | if impl_default_trait && !missing_fields.is_empty() { | 20 | if impl_default_trait && !missing_fields.is_empty() { |
21 | let completion_text = "..Default::default()"; | 21 | let completion_text = "..Default::default()"; |
22 | let completion_text = completion_text | ||
23 | .strip_prefix(ctx.token.to_string().as_str()) | ||
24 | .unwrap_or(completion_text); | ||
25 | let mut item = CompletionItem::new( | 22 | let mut item = CompletionItem::new( |
26 | CompletionKind::Snippet, | 23 | CompletionKind::Snippet, |
27 | ctx.source_range(), | 24 | ctx.source_range(), |
28 | "..Default::default()", | 25 | completion_text, |
29 | ); | 26 | ); |
27 | let completion_text = | ||
28 | completion_text.strip_prefix(ctx.token.text()).unwrap_or(completion_text); | ||
30 | item.insert_text(completion_text).kind(SymbolKind::Field); | 29 | item.insert_text(completion_text).kind(SymbolKind::Field); |
31 | item.add_to(acc); | 30 | item.add_to(acc); |
32 | } | 31 | } |
33 | 32 | ||
34 | missing_fields | 33 | missing_fields |
35 | } | 34 | } |
35 | Some(ImmediateLocation::RecordPat(record_pat)) => { | ||
36 | ctx.sema.record_pattern_missing_fields(record_pat) | ||
37 | } | ||
38 | _ => return None, | ||
36 | }; | 39 | }; |
37 | 40 | ||
38 | for (field, ty) in missing_fields { | 41 | for (field, ty) in missing_fields { |
39 | acc.add_field(ctx, field, &ty); | 42 | acc.add_field(ctx, None, field, &ty); |
40 | } | 43 | } |
41 | 44 | ||
42 | Some(()) | 45 | Some(()) |
@@ -108,8 +111,6 @@ fn process(f: S) { | |||
108 | check_snippet( | 111 | check_snippet( |
109 | test_code, | 112 | test_code, |
110 | expect![[r#" | 113 | expect![[r#" |
111 | sn pd | ||
112 | sn ppd | ||
113 | fd ..Default::default() | 114 | fd ..Default::default() |
114 | "#]], | 115 | "#]], |
115 | ); | 116 | ); |
@@ -179,13 +180,7 @@ fn process(f: S) { | |||
179 | "#]], | 180 | "#]], |
180 | ); | 181 | ); |
181 | 182 | ||
182 | check_snippet( | 183 | check_snippet(test_code, expect![[r#""#]]); |
183 | test_code, | ||
184 | expect![[r#" | ||
185 | sn pd | ||
186 | sn ppd | ||
187 | "#]], | ||
188 | ); | ||
189 | } | 184 | } |
190 | 185 | ||
191 | #[test] | 186 | #[test] |
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs index 7f7830976..defc25b00 100644 --- a/crates/ide_completion/src/completions/snippet.rs +++ b/crates/ide_completion/src/completions/snippet.rs | |||
@@ -14,7 +14,7 @@ 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_syntax.is_some()) { | 17 | if !(ctx.is_trivial_path && ctx.function_def.is_some()) { |
18 | return; | 18 | return; |
19 | } | 19 | } |
20 | let cap = match ctx.config.snippet_cap { | 20 | let cap = match ctx.config.snippet_cap { |
@@ -22,8 +22,10 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte | |||
22 | None => return, | 22 | None => return, |
23 | }; | 23 | }; |
24 | 24 | ||
25 | snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); | 25 | if ctx.can_be_stmt { |
26 | snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); | 26 | snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); |
27 | snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); | ||
28 | } | ||
27 | } | 29 | } |
28 | 30 | ||
29 | pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { | 31 | pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { |
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index 7875500c1..20188a7dd 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs | |||
@@ -1,7 +1,6 @@ | |||
1 | //! Completion of names from the current scope, e.g. locals and imported items. | 1 | //! Completion of names from the current scope, e.g. locals and imported items. |
2 | 2 | ||
3 | use hir::ScopeDef; | 3 | use hir::ScopeDef; |
4 | use syntax::AstNode; | ||
5 | 4 | ||
6 | use crate::{CompletionContext, Completions}; | 5 | use crate::{CompletionContext, Completions}; |
7 | 6 | ||
@@ -9,11 +8,29 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
9 | if !ctx.is_trivial_path { | 8 | if !ctx.is_trivial_path { |
10 | return; | 9 | return; |
11 | } | 10 | } |
12 | if ctx.record_lit_syntax.is_some() | 11 | if ctx.is_path_disallowed() || ctx.expects_item() { |
13 | || ctx.record_pat_syntax.is_some() | 12 | return; |
14 | || ctx.attribute_under_caret.is_some() | 13 | } |
15 | || ctx.mod_declaration_under_caret.is_some() | 14 | |
16 | { | 15 | if ctx.expects_assoc_item() { |
16 | ctx.scope.process_all_names(&mut |name, def| { | ||
17 | if let ScopeDef::MacroDef(macro_def) = def { | ||
18 | acc.add_macro(ctx, Some(name.clone()), macro_def); | ||
19 | } | ||
20 | if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = def { | ||
21 | acc.add_resolution(ctx, name, &def); | ||
22 | } | ||
23 | }); | ||
24 | return; | ||
25 | } | ||
26 | |||
27 | if ctx.expects_use_tree() { | ||
28 | cov_mark::hit!(only_completes_modules_in_import); | ||
29 | ctx.scope.process_all_names(&mut |name, res| { | ||
30 | if let ScopeDef::ModuleDef(hir::ModuleDef::Module(_)) = res { | ||
31 | acc.add_resolution(ctx, name, &res); | ||
32 | } | ||
33 | }); | ||
17 | return; | 34 | return; |
18 | } | 35 | } |
19 | 36 | ||
@@ -30,15 +47,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
30 | cov_mark::hit!(skip_lifetime_completion); | 47 | cov_mark::hit!(skip_lifetime_completion); |
31 | return; | 48 | return; |
32 | } | 49 | } |
33 | if ctx.use_item_syntax.is_some() { | 50 | acc.add_resolution(ctx, name, &res); |
34 | if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) { | ||
35 | if name_ref.syntax().text() == name.to_string().as_str() { | ||
36 | cov_mark::hit!(self_fulfilling_completion); | ||
37 | return; | ||
38 | } | ||
39 | } | ||
40 | } | ||
41 | acc.add_resolution(ctx, name.to_string(), &res); | ||
42 | }); | 51 | }); |
43 | } | 52 | } |
44 | 53 | ||
@@ -61,15 +70,17 @@ mod tests { | |||
61 | } | 70 | } |
62 | 71 | ||
63 | #[test] | 72 | #[test] |
64 | fn self_fulfilling_completion() { | 73 | fn only_completes_modules_in_import() { |
65 | cov_mark::check!(self_fulfilling_completion); | 74 | cov_mark::check!(only_completes_modules_in_import); |
66 | check( | 75 | check( |
67 | r#" | 76 | r#" |
68 | use foo$0 | 77 | use f$0 |
69 | use std::collections; | 78 | |
79 | struct Foo; | ||
80 | mod foo {} | ||
70 | "#, | 81 | "#, |
71 | expect![[r#" | 82 | expect![[r#" |
72 | ?? collections | 83 | md foo |
73 | "#]], | 84 | "#]], |
74 | ); | 85 | ); |
75 | } | 86 | } |
@@ -86,7 +97,7 @@ fn quux(x: Option<Enum>) { | |||
86 | } | 97 | } |
87 | } | 98 | } |
88 | "#, | 99 | "#, |
89 | expect![[""]], | 100 | expect![[r#""#]], |
90 | ); | 101 | ); |
91 | } | 102 | } |
92 | 103 | ||
@@ -102,7 +113,7 @@ fn quux(x: Option<Enum>) { | |||
102 | } | 113 | } |
103 | } | 114 | } |
104 | "#, | 115 | "#, |
105 | expect![[""]], | 116 | expect![[r#""#]], |
106 | ); | 117 | ); |
107 | } | 118 | } |
108 | 119 | ||
@@ -651,7 +662,7 @@ fn f() {} | |||
651 | } | 662 | } |
652 | 663 | ||
653 | #[test] | 664 | #[test] |
654 | fn completes_type_or_trait_in_impl_block() { | 665 | fn completes_target_type_or_trait_in_impl_block() { |
655 | check( | 666 | check( |
656 | r#" | 667 | r#" |
657 | trait MyTrait {} | 668 | trait MyTrait {} |
@@ -666,4 +677,41 @@ impl My$0 | |||
666 | "#]], | 677 | "#]], |
667 | ) | 678 | ) |
668 | } | 679 | } |
680 | |||
681 | #[test] | ||
682 | fn completes_in_assoc_item_list() { | ||
683 | check( | ||
684 | r#" | ||
685 | macro_rules! foo {} | ||
686 | mod bar {} | ||
687 | |||
688 | struct MyStruct {} | ||
689 | impl MyStruct { | ||
690 | $0 | ||
691 | } | ||
692 | "#, | ||
693 | expect![[r#" | ||
694 | md bar | ||
695 | ma foo! macro_rules! foo | ||
696 | "#]], | ||
697 | ) | ||
698 | } | ||
699 | |||
700 | // FIXME: The completions here currently come from `macro_in_item_position`, but they shouldn't | ||
701 | #[test] | ||
702 | fn completes_in_item_list() { | ||
703 | check( | ||
704 | r#" | ||
705 | struct MyStruct {} | ||
706 | macro_rules! foo {} | ||
707 | mod bar {} | ||
708 | |||
709 | $0 | ||
710 | "#, | ||
711 | expect![[r#" | ||
712 | md bar | ||
713 | ma foo!(…) macro_rules! foo | ||
714 | "#]], | ||
715 | ) | ||
716 | } | ||
669 | } | 717 | } |
diff --git a/crates/ide_completion/src/config.rs b/crates/ide_completion/src/config.rs index d70ed6c1c..c300ce887 100644 --- a/crates/ide_completion/src/config.rs +++ b/crates/ide_completion/src/config.rs | |||
@@ -10,6 +10,7 @@ use ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap}; | |||
10 | pub struct CompletionConfig { | 10 | pub struct CompletionConfig { |
11 | pub enable_postfix_completions: bool, | 11 | pub enable_postfix_completions: bool, |
12 | pub enable_imports_on_the_fly: bool, | 12 | pub enable_imports_on_the_fly: bool, |
13 | pub enable_self_on_the_fly: bool, | ||
13 | pub add_call_parenthesis: bool, | 14 | pub add_call_parenthesis: bool, |
14 | pub add_call_argument_snippets: bool, | 15 | pub add_call_argument_snippets: bool, |
15 | pub snippet_cap: Option<SnippetCap>, | 16 | pub snippet_cap: Option<SnippetCap>, |
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 787eb2fd3..7c46c815d 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -1,29 +1,34 @@ | |||
1 | //! See `CompletionContext` structure. | 1 | //! See `CompletionContext` structure. |
2 | 2 | ||
3 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; | 3 | use hir::{Local, ScopeDef, Semantics, SemanticsScope, Type}; |
4 | use ide_db::base_db::{FilePosition, SourceDatabase}; | 4 | use ide_db::{ |
5 | use ide_db::{call_info::ActiveParameter, RootDatabase}; | 5 | base_db::{FilePosition, SourceDatabase}, |
6 | call_info::ActiveParameter, | ||
7 | RootDatabase, | ||
8 | }; | ||
6 | use syntax::{ | 9 | use syntax::{ |
7 | algo::find_node_at_offset, | 10 | algo::find_node_at_offset, |
8 | ast::{self, NameOrNameRef, NameOwner}, | 11 | ast::{self, NameOrNameRef, NameOwner}, |
9 | match_ast, AstNode, NodeOrToken, | 12 | match_ast, AstNode, NodeOrToken, |
10 | SyntaxKind::*, | 13 | SyntaxKind::{self, *}, |
11 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 14 | SyntaxNode, SyntaxToken, TextRange, TextSize, T, |
12 | }; | 15 | }; |
13 | |||
14 | use text_edit::Indel; | 16 | use text_edit::Indel; |
15 | 17 | ||
16 | use crate::{ | 18 | use crate::{ |
17 | patterns::{ | 19 | patterns::{ |
18 | fn_is_prev, for_is_prev2, has_bind_pat_parent, has_block_expr_parent, | 20 | determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block, |
19 | has_field_list_parent, has_impl_as_prev_sibling, has_impl_parent, | 21 | is_in_loop_body, previous_token, ImmediateLocation, ImmediatePrevSibling, |
20 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, | ||
21 | has_trait_parent, if_is_prev, inside_impl_trait_block, is_in_loop_body, is_match_arm, | ||
22 | unsafe_is_prev, | ||
23 | }, | 22 | }, |
24 | CompletionConfig, | 23 | CompletionConfig, |
25 | }; | 24 | }; |
26 | 25 | ||
26 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
27 | pub(crate) enum PatternRefutability { | ||
28 | Refutable, | ||
29 | Irrefutable, | ||
30 | } | ||
31 | |||
27 | /// `CompletionContext` is created early during completion to figure out, where | 32 | /// `CompletionContext` is created early during completion to figure out, where |
28 | /// exactly is the cursor, syntax-wise. | 33 | /// exactly is the cursor, syntax-wise. |
29 | #[derive(Debug)] | 34 | #[derive(Debug)] |
@@ -41,28 +46,34 @@ pub(crate) struct CompletionContext<'a> { | |||
41 | pub(super) expected_name: Option<NameOrNameRef>, | 46 | pub(super) expected_name: Option<NameOrNameRef>, |
42 | pub(super) expected_type: Option<Type>, | 47 | pub(super) expected_type: Option<Type>, |
43 | pub(super) name_ref_syntax: Option<ast::NameRef>, | 48 | pub(super) name_ref_syntax: Option<ast::NameRef>, |
44 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | 49 | |
45 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, | ||
46 | pub(super) function_syntax: Option<ast::Fn>, | ||
47 | pub(super) use_item_syntax: Option<ast::Use>, | 50 | pub(super) use_item_syntax: Option<ast::Use>, |
48 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | 51 | |
49 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | 52 | /// The parent function of the cursor position if it exists. |
50 | pub(super) record_field_syntax: Option<ast::RecordExprField>, | 53 | pub(super) function_def: Option<ast::Fn>, |
54 | /// The parent impl of the cursor position if it exists. | ||
51 | pub(super) impl_def: Option<ast::Impl>, | 55 | pub(super) impl_def: Option<ast::Impl>, |
56 | |||
57 | // potentially set if we are completing a lifetime | ||
58 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | ||
59 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, | ||
52 | pub(super) lifetime_allowed: bool, | 60 | pub(super) lifetime_allowed: bool, |
61 | pub(super) is_label_ref: bool, | ||
62 | |||
63 | // potentially set if we are completing a name | ||
64 | pub(super) is_pat_or_const: Option<PatternRefutability>, | ||
65 | pub(super) is_param: bool, | ||
66 | |||
67 | pub(super) completion_location: Option<ImmediateLocation>, | ||
68 | pub(super) prev_sibling: Option<ImmediatePrevSibling>, | ||
69 | pub(super) attribute_under_caret: Option<ast::Attr>, | ||
70 | |||
53 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 71 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
54 | pub(super) active_parameter: Option<ActiveParameter>, | 72 | pub(super) active_parameter: Option<ActiveParameter>, |
55 | pub(super) is_param: bool, | ||
56 | pub(super) is_label_ref: bool, | ||
57 | /// If a name-binding or reference to a const in a pattern. | ||
58 | /// Irrefutable patterns (like let) are excluded. | ||
59 | pub(super) is_pat_binding_or_const: bool, | ||
60 | pub(super) is_irrefutable_pat_binding: bool, | ||
61 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. | 73 | /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. |
62 | pub(super) is_trivial_path: bool, | 74 | pub(super) is_trivial_path: bool, |
63 | /// If not a trivial path, the prefix (qualifier). | 75 | /// If not a trivial path, the prefix (qualifier). |
64 | pub(super) path_qual: Option<ast::Path>, | 76 | pub(super) path_qual: Option<ast::Path>, |
65 | pub(super) after_if: bool, | ||
66 | /// `true` if we are a statement or a last expr in the block. | 77 | /// `true` if we are a statement or a last expr in the block. |
67 | pub(super) can_be_stmt: bool, | 78 | pub(super) can_be_stmt: bool, |
68 | /// `true` if we expect an expression at the cursor position. | 79 | /// `true` if we expect an expression at the cursor position. |
@@ -80,26 +91,13 @@ pub(crate) struct CompletionContext<'a> { | |||
80 | pub(super) is_macro_call: bool, | 91 | pub(super) is_macro_call: bool, |
81 | pub(super) is_path_type: bool, | 92 | pub(super) is_path_type: bool, |
82 | pub(super) has_type_args: bool, | 93 | pub(super) has_type_args: bool, |
83 | pub(super) attribute_under_caret: Option<ast::Attr>, | 94 | pub(super) locals: Vec<(String, Local)>, |
84 | pub(super) mod_declaration_under_caret: Option<ast::Module>, | 95 | |
85 | pub(super) unsafe_is_prev: bool, | 96 | pub(super) previous_token: Option<SyntaxToken>, |
86 | pub(super) if_is_prev: bool, | ||
87 | pub(super) block_expr_parent: bool, | ||
88 | pub(super) bind_pat_parent: bool, | ||
89 | pub(super) ref_pat_parent: bool, | ||
90 | pub(super) in_loop_body: bool, | 97 | pub(super) in_loop_body: bool, |
91 | pub(super) has_trait_parent: bool, | ||
92 | pub(super) has_impl_parent: bool, | ||
93 | pub(super) inside_impl_trait_block: bool, | ||
94 | pub(super) has_field_list_parent: bool, | ||
95 | pub(super) trait_as_prev_sibling: bool, | ||
96 | pub(super) impl_as_prev_sibling: bool, | ||
97 | pub(super) is_match_arm: bool, | ||
98 | pub(super) has_item_list_or_source_file_parent: bool, | ||
99 | pub(super) for_is_prev2: bool, | ||
100 | pub(super) fn_is_prev: bool, | ||
101 | pub(super) incomplete_let: bool, | 98 | pub(super) incomplete_let: bool, |
102 | pub(super) locals: Vec<(String, Local)>, | 99 | |
100 | no_completion_required: bool, | ||
103 | } | 101 | } |
104 | 102 | ||
105 | impl<'a> CompletionContext<'a> { | 103 | impl<'a> CompletionContext<'a> { |
@@ -149,20 +147,15 @@ impl<'a> CompletionContext<'a> { | |||
149 | name_ref_syntax: None, | 147 | name_ref_syntax: None, |
150 | lifetime_syntax: None, | 148 | lifetime_syntax: None, |
151 | lifetime_param_syntax: None, | 149 | lifetime_param_syntax: None, |
152 | function_syntax: None, | 150 | function_def: None, |
153 | use_item_syntax: None, | 151 | use_item_syntax: None, |
154 | record_lit_syntax: None, | ||
155 | record_pat_syntax: None, | ||
156 | record_field_syntax: None, | ||
157 | impl_def: None, | 152 | impl_def: None, |
158 | active_parameter: ActiveParameter::at(db, position), | 153 | active_parameter: ActiveParameter::at(db, position), |
159 | is_label_ref: false, | 154 | is_label_ref: false, |
160 | is_param: false, | 155 | is_param: false, |
161 | is_pat_binding_or_const: false, | 156 | is_pat_or_const: None, |
162 | is_irrefutable_pat_binding: false, | ||
163 | is_trivial_path: false, | 157 | is_trivial_path: false, |
164 | path_qual: None, | 158 | path_qual: None, |
165 | after_if: false, | ||
166 | can_be_stmt: false, | 159 | can_be_stmt: false, |
167 | is_expr: false, | 160 | is_expr: false, |
168 | is_new_item: false, | 161 | is_new_item: false, |
@@ -173,69 +166,56 @@ impl<'a> CompletionContext<'a> { | |||
173 | is_macro_call: false, | 166 | is_macro_call: false, |
174 | is_path_type: false, | 167 | is_path_type: false, |
175 | has_type_args: false, | 168 | has_type_args: false, |
176 | attribute_under_caret: None, | 169 | previous_token: None, |
177 | mod_declaration_under_caret: None, | ||
178 | unsafe_is_prev: false, | ||
179 | if_is_prev: false, | ||
180 | block_expr_parent: false, | ||
181 | bind_pat_parent: false, | ||
182 | ref_pat_parent: false, | ||
183 | in_loop_body: false, | 170 | in_loop_body: false, |
184 | has_trait_parent: false, | 171 | completion_location: None, |
185 | has_impl_parent: false, | 172 | prev_sibling: None, |
186 | inside_impl_trait_block: false, | 173 | no_completion_required: false, |
187 | has_field_list_parent: false, | ||
188 | trait_as_prev_sibling: false, | ||
189 | impl_as_prev_sibling: false, | ||
190 | is_match_arm: false, | ||
191 | has_item_list_or_source_file_parent: false, | ||
192 | for_is_prev2: false, | ||
193 | fn_is_prev: false, | ||
194 | incomplete_let: false, | 174 | incomplete_let: false, |
175 | attribute_under_caret: None, | ||
195 | locals, | 176 | locals, |
196 | }; | 177 | }; |
197 | 178 | ||
198 | let mut original_file = original_file.syntax().clone(); | 179 | let mut original_file = original_file.syntax().clone(); |
199 | let mut hypothetical_file = file_with_fake_ident.syntax().clone(); | 180 | let mut speculative_file = file_with_fake_ident.syntax().clone(); |
200 | let mut offset = position.offset; | 181 | let mut offset = position.offset; |
201 | let mut fake_ident_token = fake_ident_token; | 182 | let mut fake_ident_token = fake_ident_token; |
202 | 183 | ||
203 | // Are we inside a macro call? | 184 | // Are we inside a macro call? |
204 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( | 185 | while let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = ( |
205 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), | 186 | find_node_at_offset::<ast::MacroCall>(&original_file, offset), |
206 | find_node_at_offset::<ast::MacroCall>(&hypothetical_file, offset), | 187 | find_node_at_offset::<ast::MacroCall>(&speculative_file, offset), |
207 | ) { | 188 | ) { |
208 | if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) | 189 | if actual_macro_call.path().as_ref().map(|s| s.syntax().text()) |
209 | != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) | 190 | != macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text()) |
210 | { | 191 | { |
211 | break; | 192 | break; |
212 | } | 193 | } |
213 | let hypothetical_args = match macro_call_with_fake_ident.token_tree() { | 194 | let speculative_args = match macro_call_with_fake_ident.token_tree() { |
214 | Some(tt) => tt, | 195 | Some(tt) => tt, |
215 | None => break, | 196 | None => break, |
216 | }; | 197 | }; |
217 | if let (Some(actual_expansion), Some(hypothetical_expansion)) = ( | 198 | if let (Some(actual_expansion), Some(speculative_expansion)) = ( |
218 | ctx.sema.expand(&actual_macro_call), | 199 | ctx.sema.expand(&actual_macro_call), |
219 | ctx.sema.speculative_expand( | 200 | ctx.sema.speculative_expand( |
220 | &actual_macro_call, | 201 | &actual_macro_call, |
221 | &hypothetical_args, | 202 | &speculative_args, |
222 | fake_ident_token, | 203 | fake_ident_token, |
223 | ), | 204 | ), |
224 | ) { | 205 | ) { |
225 | let new_offset = hypothetical_expansion.1.text_range().start(); | 206 | let new_offset = speculative_expansion.1.text_range().start(); |
226 | if new_offset > actual_expansion.text_range().end() { | 207 | if new_offset > actual_expansion.text_range().end() { |
227 | break; | 208 | break; |
228 | } | 209 | } |
229 | original_file = actual_expansion; | 210 | original_file = actual_expansion; |
230 | hypothetical_file = hypothetical_expansion.0; | 211 | speculative_file = speculative_expansion.0; |
231 | fake_ident_token = hypothetical_expansion.1; | 212 | fake_ident_token = speculative_expansion.1; |
232 | offset = new_offset; | 213 | offset = new_offset; |
233 | } else { | 214 | } else { |
234 | break; | 215 | break; |
235 | } | 216 | } |
236 | } | 217 | } |
237 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | 218 | ctx.fill(&original_file, speculative_file, offset); |
238 | ctx.fill(&original_file, hypothetical_file, offset); | ||
239 | Some(ctx) | 219 | Some(ctx) |
240 | } | 220 | } |
241 | 221 | ||
@@ -245,7 +225,7 @@ impl<'a> CompletionContext<'a> { | |||
245 | /// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names. | 225 | /// Exception for this case is `impl Trait for Foo`, where we would like to hint trait method names. |
246 | /// - `for _ i$0` -- obviously, it'll be "in" keyword. | 226 | /// - `for _ i$0` -- obviously, it'll be "in" keyword. |
247 | pub(crate) fn no_completion_required(&self) -> bool { | 227 | pub(crate) fn no_completion_required(&self) -> bool { |
248 | (self.fn_is_prev && !self.inside_impl_trait_block) || self.for_is_prev2 | 228 | self.no_completion_required |
249 | } | 229 | } |
250 | 230 | ||
251 | /// The range of the identifier that is being completed. | 231 | /// The range of the identifier that is being completed. |
@@ -264,33 +244,67 @@ impl<'a> CompletionContext<'a> { | |||
264 | } | 244 | } |
265 | } | 245 | } |
266 | 246 | ||
267 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | 247 | pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool { |
268 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | 248 | self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind) |
269 | let syntax_element = NodeOrToken::Token(fake_ident_token); | 249 | } |
270 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | 250 | |
271 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | 251 | pub(crate) fn expects_assoc_item(&self) -> bool { |
272 | self.if_is_prev = if_is_prev(syntax_element.clone()); | 252 | matches!( |
273 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | 253 | self.completion_location, |
274 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | 254 | Some(ImmediateLocation::Trait) | Some(ImmediateLocation::Impl) |
275 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | 255 | ) |
276 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | 256 | } |
277 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | 257 | |
278 | self.inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | 258 | pub(crate) fn expects_use_tree(&self) -> bool { |
279 | self.has_field_list_parent = has_field_list_parent(syntax_element.clone()); | 259 | matches!(self.completion_location, Some(ImmediateLocation::Use)) |
280 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | 260 | } |
281 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | 261 | |
282 | self.is_match_arm = is_match_arm(syntax_element.clone()); | 262 | pub(crate) fn expects_non_trait_assoc_item(&self) -> bool { |
283 | self.has_item_list_or_source_file_parent = | 263 | matches!(self.completion_location, Some(ImmediateLocation::Impl)) |
284 | has_item_list_or_source_file_parent(syntax_element.clone()); | 264 | } |
285 | self.mod_declaration_under_caret = | 265 | |
286 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) | 266 | pub(crate) fn expects_item(&self) -> bool { |
287 | .filter(|module| module.item_list().is_none()); | 267 | matches!(self.completion_location, Some(ImmediateLocation::ItemList)) |
288 | self.for_is_prev2 = for_is_prev2(syntax_element.clone()); | 268 | } |
289 | self.fn_is_prev = fn_is_prev(syntax_element.clone()); | 269 | |
290 | self.incomplete_let = | 270 | pub(crate) fn expects_expression(&self) -> bool { |
291 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { | 271 | self.is_expr |
292 | it.syntax().text_range().end() == syntax_element.text_range().end() | 272 | } |
293 | }); | 273 | |
274 | pub(crate) fn has_block_expr_parent(&self) -> bool { | ||
275 | matches!(self.completion_location, Some(ImmediateLocation::BlockExpr)) | ||
276 | } | ||
277 | |||
278 | pub(crate) fn expects_ident_pat_or_ref_expr(&self) -> bool { | ||
279 | matches!( | ||
280 | self.completion_location, | ||
281 | Some(ImmediateLocation::IdentPat) | Some(ImmediateLocation::RefExpr) | ||
282 | ) | ||
283 | } | ||
284 | |||
285 | pub(crate) fn expect_record_field(&self) -> bool { | ||
286 | matches!(self.completion_location, Some(ImmediateLocation::RecordField)) | ||
287 | } | ||
288 | |||
289 | pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool { | ||
290 | matches!( | ||
291 | self.prev_sibling, | ||
292 | Some(ImmediatePrevSibling::ImplDefType) | Some(ImmediatePrevSibling::TraitDefName) | ||
293 | ) | ||
294 | } | ||
295 | |||
296 | pub(crate) fn after_if(&self) -> bool { | ||
297 | matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr)) | ||
298 | } | ||
299 | |||
300 | pub(crate) fn is_path_disallowed(&self) -> bool { | ||
301 | matches!( | ||
302 | self.completion_location, | ||
303 | Some(ImmediateLocation::Attribute(_)) | ||
304 | | Some(ImmediateLocation::ModDeclaration(_)) | ||
305 | | Some(ImmediateLocation::RecordPat(_)) | ||
306 | | Some(ImmediateLocation::RecordExpr(_)) | ||
307 | ) || self.attribute_under_caret.is_some() | ||
294 | } | 308 | } |
295 | 309 | ||
296 | fn fill_impl_def(&mut self) { | 310 | fn fill_impl_def(&mut self) { |
@@ -337,25 +351,24 @@ impl<'a> CompletionContext<'a> { | |||
337 | }, | 351 | }, |
338 | ast::RecordExprFieldList(_it) => { | 352 | ast::RecordExprFieldList(_it) => { |
339 | cov_mark::hit!(expected_type_struct_field_without_leading_char); | 353 | cov_mark::hit!(expected_type_struct_field_without_leading_char); |
340 | self.token.prev_sibling_or_token() | 354 | // wouldn't try {} be nice... |
341 | .and_then(|se| se.into_node()) | 355 | (|| { |
342 | .and_then(|node| ast::RecordExprField::cast(node)) | 356 | let expr_field = self.token.prev_sibling_or_token()? |
343 | .and_then(|rf| self.sema.resolve_record_field(&rf).zip(Some(rf))) | 357 | .into_node() |
344 | .map(|(f, rf)|( | 358 | .and_then(|node| ast::RecordExprField::cast(node))?; |
345 | Some(f.0.ty(self.db)), | 359 | let (_, _, ty) = self.sema.resolve_record_field(&expr_field)?; |
346 | rf.field_name().map(NameOrNameRef::NameRef), | 360 | Some(( |
361 | Some(ty), | ||
362 | expr_field.field_name().map(NameOrNameRef::NameRef), | ||
347 | )) | 363 | )) |
348 | .unwrap_or((None, None)) | 364 | })().unwrap_or((None, None)) |
349 | }, | 365 | }, |
350 | ast::RecordExprField(it) => { | 366 | ast::RecordExprField(it) => { |
351 | cov_mark::hit!(expected_type_struct_field_with_leading_char); | 367 | cov_mark::hit!(expected_type_struct_field_with_leading_char); |
352 | self.sema | 368 | ( |
353 | .resolve_record_field(&it) | 369 | it.expr().as_ref().and_then(|e| self.sema.type_of_expr(e)), |
354 | .map(|f|( | 370 | it.field_name().map(NameOrNameRef::NameRef), |
355 | Some(f.0.ty(self.db)), | 371 | ) |
356 | it.field_name().map(NameOrNameRef::NameRef), | ||
357 | )) | ||
358 | .unwrap_or((None, None)) | ||
359 | }, | 372 | }, |
360 | ast::MatchExpr(it) => { | 373 | ast::MatchExpr(it) => { |
361 | cov_mark::hit!(expected_type_match_arm_without_leading_char); | 374 | cov_mark::hit!(expected_type_match_arm_without_leading_char); |
@@ -382,6 +395,12 @@ impl<'a> CompletionContext<'a> { | |||
382 | let def = self.sema.to_def(&it); | 395 | let def = self.sema.to_def(&it); |
383 | (def.map(|def| def.ret_type(self.db)), None) | 396 | (def.map(|def| def.ret_type(self.db)), None) |
384 | }, | 397 | }, |
398 | ast::ClosureExpr(it) => { | ||
399 | let ty = self.sema.type_of_expr(&it.into()); | ||
400 | ty.and_then(|ty| ty.as_callable(self.db)) | ||
401 | .map(|c| (Some(c.return_type()), None)) | ||
402 | .unwrap_or((None, None)) | ||
403 | }, | ||
385 | ast::Stmt(_it) => (None, None), | 404 | ast::Stmt(_it) => (None, None), |
386 | _ => { | 405 | _ => { |
387 | match node.parent() { | 406 | match node.parent() { |
@@ -403,71 +422,43 @@ impl<'a> CompletionContext<'a> { | |||
403 | file_with_fake_ident: SyntaxNode, | 422 | file_with_fake_ident: SyntaxNode, |
404 | offset: TextSize, | 423 | offset: TextSize, |
405 | ) { | 424 | ) { |
425 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
426 | let syntax_element = NodeOrToken::Token(fake_ident_token); | ||
427 | self.previous_token = previous_token(syntax_element.clone()); | ||
428 | self.attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast); | ||
429 | self.no_completion_required = { | ||
430 | let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | ||
431 | let fn_is_prev = self.previous_token_is(T![fn]); | ||
432 | let for_is_prev2 = for_is_prev2(syntax_element.clone()); | ||
433 | (fn_is_prev && !inside_impl_trait_block) || for_is_prev2 | ||
434 | }; | ||
435 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
436 | |||
437 | self.incomplete_let = | ||
438 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { | ||
439 | it.syntax().text_range().end() == syntax_element.text_range().end() | ||
440 | }); | ||
441 | |||
406 | let (expected_type, expected_name) = self.expected_type_and_name(); | 442 | let (expected_type, expected_name) = self.expected_type_and_name(); |
407 | self.expected_type = expected_type; | 443 | self.expected_type = expected_type; |
408 | self.expected_name = expected_name; | 444 | self.expected_name = expected_name; |
409 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | ||
410 | |||
411 | if let Some(lifetime) = find_node_at_offset::<ast::Lifetime>(&file_with_fake_ident, offset) | ||
412 | { | ||
413 | self.classify_lifetime(original_file, lifetime, offset); | ||
414 | } | ||
415 | |||
416 | // First, let's try to complete a reference to some declaration. | ||
417 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) { | ||
418 | // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. | ||
419 | // See RFC#1685. | ||
420 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
421 | self.is_param = true; | ||
422 | return; | ||
423 | } | ||
424 | // FIXME: remove this (V) duplication and make the check more precise | ||
425 | if name_ref.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | ||
426 | self.record_pat_syntax = | ||
427 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
428 | } | ||
429 | self.classify_name_ref(original_file, name_ref, offset); | ||
430 | } | ||
431 | |||
432 | // Otherwise, see if this is a declaration. We can use heuristics to | ||
433 | // suggest declaration names, see `CompletionKind::Magic`. | ||
434 | if let Some(name) = find_node_at_offset::<ast::Name>(&file_with_fake_ident, offset) { | ||
435 | if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::IdentPat::cast) { | ||
436 | self.is_pat_binding_or_const = true; | ||
437 | if bind_pat.at_token().is_some() | ||
438 | || bind_pat.ref_token().is_some() | ||
439 | || bind_pat.mut_token().is_some() | ||
440 | { | ||
441 | self.is_pat_binding_or_const = false; | ||
442 | } | ||
443 | if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { | ||
444 | self.is_pat_binding_or_const = false; | ||
445 | } | ||
446 | if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| { | ||
447 | match_ast! { | ||
448 | match node { | ||
449 | ast::LetStmt(it) => Some(it.pat()), | ||
450 | ast::Param(it) => Some(it.pat()), | ||
451 | _ => None, | ||
452 | } | ||
453 | } | ||
454 | }) { | ||
455 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) { | ||
456 | self.is_pat_binding_or_const = false; | ||
457 | self.is_irrefutable_pat_binding = true; | ||
458 | } | ||
459 | } | ||
460 | 445 | ||
461 | self.fill_impl_def(); | 446 | let name_like = match find_node_at_offset(&&file_with_fake_ident, offset) { |
447 | Some(it) => it, | ||
448 | None => return, | ||
449 | }; | ||
450 | self.completion_location = | ||
451 | determine_location(&self.sema, original_file, offset, &name_like); | ||
452 | self.prev_sibling = determine_prev_sibling(&name_like); | ||
453 | match name_like { | ||
454 | ast::NameLike::Lifetime(lifetime) => { | ||
455 | self.classify_lifetime(original_file, lifetime, offset); | ||
462 | } | 456 | } |
463 | if is_node::<ast::Param>(name.syntax()) { | 457 | ast::NameLike::NameRef(name_ref) => { |
464 | self.is_param = true; | 458 | self.classify_name_ref(original_file, name_ref); |
465 | return; | ||
466 | } | 459 | } |
467 | // FIXME: remove this (^) duplication and make the check more precise | 460 | ast::NameLike::Name(name) => { |
468 | if name.syntax().ancestors().find_map(ast::RecordPatFieldList::cast).is_some() { | 461 | self.classify_name(name); |
469 | self.record_pat_syntax = | ||
470 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
471 | } | 462 | } |
472 | } | 463 | } |
473 | } | 464 | } |
@@ -501,22 +492,55 @@ impl<'a> CompletionContext<'a> { | |||
501 | } | 492 | } |
502 | } | 493 | } |
503 | 494 | ||
504 | fn classify_name_ref( | 495 | fn classify_name(&mut self, name: ast::Name) { |
505 | &mut self, | 496 | if let Some(bind_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { |
506 | original_file: &SyntaxNode, | 497 | self.is_pat_or_const = Some(PatternRefutability::Refutable); |
507 | name_ref: ast::NameRef, | 498 | // if any of these is here our bind pat can't be a const pat anymore |
508 | offset: TextSize, | 499 | let complex_ident_pat = bind_pat.at_token().is_some() |
509 | ) { | 500 | || bind_pat.ref_token().is_some() |
510 | self.name_ref_syntax = | 501 | || bind_pat.mut_token().is_some(); |
511 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); | 502 | if complex_ident_pat { |
512 | let name_range = name_ref.syntax().text_range(); | 503 | self.is_pat_or_const = None; |
513 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | 504 | } else { |
514 | self.record_lit_syntax = | 505 | let irrefutable_pat = bind_pat.syntax().ancestors().find_map(|node| { |
515 | self.sema.find_node_at_offset_with_macros(original_file, offset); | 506 | match_ast! { |
507 | match node { | ||
508 | ast::LetStmt(it) => Some(it.pat()), | ||
509 | ast::Param(it) => Some(it.pat()), | ||
510 | _ => None, | ||
511 | } | ||
512 | } | ||
513 | }); | ||
514 | if let Some(Some(pat)) = irrefutable_pat { | ||
515 | // This check is here since we could be inside a pattern in the initializer expression of the let statement. | ||
516 | if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) { | ||
517 | self.is_pat_or_const = Some(PatternRefutability::Irrefutable); | ||
518 | } | ||
519 | } | ||
520 | |||
521 | let is_name_in_field_pat = bind_pat | ||
522 | .syntax() | ||
523 | .parent() | ||
524 | .and_then(ast::RecordPatField::cast) | ||
525 | .map_or(false, |pat_field| pat_field.name_ref().is_none()); | ||
526 | if is_name_in_field_pat { | ||
527 | self.is_pat_or_const = None; | ||
528 | } | ||
529 | } | ||
530 | |||
531 | self.fill_impl_def(); | ||
516 | } | 532 | } |
517 | 533 | ||
534 | self.is_param |= is_node::<ast::Param>(name.syntax()); | ||
535 | } | ||
536 | |||
537 | fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) { | ||
518 | self.fill_impl_def(); | 538 | self.fill_impl_def(); |
519 | 539 | ||
540 | self.name_ref_syntax = | ||
541 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); | ||
542 | |||
543 | let name_range = name_ref.syntax().text_range(); | ||
520 | let top_node = name_ref | 544 | let top_node = name_ref |
521 | .syntax() | 545 | .syntax() |
522 | .ancestors() | 546 | .ancestors() |
@@ -524,31 +548,20 @@ impl<'a> CompletionContext<'a> { | |||
524 | .last() | 548 | .last() |
525 | .unwrap(); | 549 | .unwrap(); |
526 | 550 | ||
527 | match top_node.parent().map(|it| it.kind()) { | 551 | if matches!(top_node.parent().map(|it| it.kind()), Some(SOURCE_FILE) | Some(ITEM_LIST)) { |
528 | Some(SOURCE_FILE) | Some(ITEM_LIST) => { | 552 | self.is_new_item = true; |
529 | self.is_new_item = true; | 553 | return; |
530 | return; | ||
531 | } | ||
532 | _ => (), | ||
533 | } | 554 | } |
534 | 555 | ||
535 | self.use_item_syntax = | 556 | self.use_item_syntax = |
536 | self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast); | 557 | self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast); |
537 | 558 | ||
538 | self.function_syntax = self | 559 | self.function_def = self |
539 | .sema | 560 | .sema |
540 | .token_ancestors_with_macros(self.token.clone()) | 561 | .token_ancestors_with_macros(self.token.clone()) |
541 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) | 562 | .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) |
542 | .find_map(ast::Fn::cast); | 563 | .find_map(ast::Fn::cast); |
543 | 564 | ||
544 | self.record_field_syntax = self | ||
545 | .sema | ||
546 | .token_ancestors_with_macros(self.token.clone()) | ||
547 | .take_while(|it| { | ||
548 | it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR | ||
549 | }) | ||
550 | .find_map(ast::RecordExprField::cast); | ||
551 | |||
552 | let parent = match name_ref.syntax().parent() { | 565 | let parent = match name_ref.syntax().parent() { |
553 | Some(it) => it, | 566 | Some(it) => it, |
554 | None => return, | 567 | None => return, |
@@ -609,18 +622,8 @@ impl<'a> CompletionContext<'a> { | |||
609 | }) | 622 | }) |
610 | .unwrap_or(false); | 623 | .unwrap_or(false); |
611 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); | 624 | self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); |
612 | |||
613 | if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { | ||
614 | if let Some(if_expr) = | ||
615 | self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off) | ||
616 | { | ||
617 | if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start() | ||
618 | { | ||
619 | self.after_if = true; | ||
620 | } | ||
621 | } | ||
622 | } | ||
623 | } | 625 | } |
626 | |||
624 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | 627 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { |
625 | // The receiver comes before the point of insertion of the fake | 628 | // The receiver comes before the point of insertion of the fake |
626 | // ident, so it should have the same range in the non-modified file | 629 | // ident, so it should have the same range in the non-modified file |
@@ -638,6 +641,7 @@ impl<'a> CompletionContext<'a> { | |||
638 | false | 641 | false |
639 | }; | 642 | }; |
640 | } | 643 | } |
644 | |||
641 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { | 645 | if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { |
642 | // As above | 646 | // As above |
643 | self.dot_receiver = method_call_expr | 647 | self.dot_receiver = method_call_expr |
@@ -785,6 +789,19 @@ fn foo() { | |||
785 | } | 789 | } |
786 | 790 | ||
787 | #[test] | 791 | #[test] |
792 | fn expected_type_generic_struct_field() { | ||
793 | check_expected_type_and_name( | ||
794 | r#" | ||
795 | struct Foo<T> { a: T } | ||
796 | fn foo() -> Foo<u32> { | ||
797 | Foo { a: $0 } | ||
798 | } | ||
799 | "#, | ||
800 | expect![[r#"ty: u32, name: a"#]], | ||
801 | ) | ||
802 | } | ||
803 | |||
804 | #[test] | ||
788 | fn expected_type_struct_field_with_leading_char() { | 805 | fn expected_type_struct_field_with_leading_char() { |
789 | cov_mark::check!(expected_type_struct_field_with_leading_char); | 806 | cov_mark::check!(expected_type_struct_field_with_leading_char); |
790 | check_expected_type_and_name( | 807 | check_expected_type_and_name( |
@@ -895,4 +912,52 @@ fn foo() -> u32 { | |||
895 | expect![[r#"ty: u32, name: ?"#]], | 912 | expect![[r#"ty: u32, name: ?"#]], |
896 | ) | 913 | ) |
897 | } | 914 | } |
915 | |||
916 | #[test] | ||
917 | fn expected_type_closure_param_return() { | ||
918 | // FIXME: make this work with `|| $0` | ||
919 | check_expected_type_and_name( | ||
920 | r#" | ||
921 | fn foo() { | ||
922 | bar(|| a$0); | ||
923 | } | ||
924 | |||
925 | fn bar(f: impl FnOnce() -> u32) {} | ||
926 | #[lang = "fn_once"] | ||
927 | trait FnOnce { type Output; } | ||
928 | "#, | ||
929 | expect![[r#"ty: u32, name: ?"#]], | ||
930 | ); | ||
931 | } | ||
932 | |||
933 | #[test] | ||
934 | fn expected_type_generic_function() { | ||
935 | check_expected_type_and_name( | ||
936 | r#" | ||
937 | fn foo() { | ||
938 | bar::<u32>($0); | ||
939 | } | ||
940 | |||
941 | fn bar<T>(t: T) {} | ||
942 | "#, | ||
943 | expect![[r#"ty: u32, name: t"#]], | ||
944 | ); | ||
945 | } | ||
946 | |||
947 | #[test] | ||
948 | fn expected_type_generic_method() { | ||
949 | check_expected_type_and_name( | ||
950 | r#" | ||
951 | fn foo() { | ||
952 | S(1u32).bar($0); | ||
953 | } | ||
954 | |||
955 | struct S<T>(T); | ||
956 | impl<T> S<T> { | ||
957 | fn bar(self, t: T) {} | ||
958 | } | ||
959 | "#, | ||
960 | expect![[r#"ty: u32, name: t"#]], | ||
961 | ); | ||
962 | } | ||
898 | } | 963 | } |
diff --git a/crates/ide_completion/src/lib.rs b/crates/ide_completion/src/lib.rs index 645349215..1152a9850 100644 --- a/crates/ide_completion/src/lib.rs +++ b/crates/ide_completion/src/lib.rs | |||
@@ -107,7 +107,7 @@ pub use crate::{ | |||
107 | /// identifier prefix/fuzzy match should be done higher in the stack, together | 107 | /// identifier prefix/fuzzy match should be done higher in the stack, together |
108 | /// with ordering of completions (currently this is done by the client). | 108 | /// with ordering of completions (currently this is done by the client). |
109 | /// | 109 | /// |
110 | /// # Hypothetical Completion Problem | 110 | /// # Speculative Completion Problem |
111 | /// | 111 | /// |
112 | /// There's a curious unsolved problem in the current implementation. Often, you | 112 | /// There's a curious unsolved problem in the current implementation. Often, you |
113 | /// want to compute completions on a *slightly different* text document. | 113 | /// want to compute completions on a *slightly different* text document. |
@@ -121,7 +121,7 @@ pub use crate::{ | |||
121 | /// doesn't allow such "phantom" inputs. | 121 | /// doesn't allow such "phantom" inputs. |
122 | /// | 122 | /// |
123 | /// Another case where this would be instrumental is macro expansion. We want to | 123 | /// Another case where this would be instrumental is macro expansion. We want to |
124 | /// insert a fake ident and re-expand code. There's `expand_hypothetical` as a | 124 | /// insert a fake ident and re-expand code. There's `expand_speculative` as a |
125 | /// work-around for this. | 125 | /// work-around for this. |
126 | /// | 126 | /// |
127 | /// A different use-case is completion of injection (examples and links in doc | 127 | /// A different use-case is completion of injection (examples and links in doc |
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index d82564381..26516046b 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs | |||
@@ -1,38 +1,197 @@ | |||
1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context | 1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context |
2 | 2 | ||
3 | use hir::Semantics; | ||
4 | use ide_db::RootDatabase; | ||
3 | use syntax::{ | 5 | use syntax::{ |
4 | algo::non_trivia_sibling, | 6 | algo::non_trivia_sibling, |
5 | ast::{self, LoopBodyOwner}, | 7 | ast::{self, LoopBodyOwner}, |
6 | match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, | 8 | match_ast, AstNode, Direction, SyntaxElement, |
7 | SyntaxKind::*, | 9 | SyntaxKind::*, |
8 | SyntaxNode, SyntaxToken, T, | 10 | SyntaxNode, SyntaxToken, TextSize, T, |
9 | }; | 11 | }; |
10 | 12 | ||
11 | #[cfg(test)] | 13 | #[cfg(test)] |
12 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; | 14 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; |
13 | 15 | ||
14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | 16 | /// Direct parent container of the cursor position |
15 | not_same_range_ancestor(element) | 17 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
16 | .filter(|it| it.kind() == ASSOC_ITEM_LIST) | 18 | pub(crate) enum ImmediatePrevSibling { |
17 | .and_then(|it| it.parent()) | 19 | IfExpr, |
18 | .filter(|it| it.kind() == TRAIT) | 20 | TraitDefName, |
19 | .is_some() | 21 | ImplDefType, |
20 | } | 22 | } |
21 | #[test] | 23 | |
22 | fn test_has_trait_parent() { | 24 | /// Direct parent container of the cursor position |
23 | check_pattern_is_applicable(r"trait A { f$0 }", has_trait_parent); | 25 | #[derive(Clone, Debug, PartialEq, Eq)] |
26 | pub(crate) enum ImmediateLocation { | ||
27 | Use, | ||
28 | Impl, | ||
29 | Trait, | ||
30 | RecordField, | ||
31 | RefExpr, | ||
32 | IdentPat, | ||
33 | BlockExpr, | ||
34 | ItemList, | ||
35 | // Fake file ast node | ||
36 | Attribute(ast::Attr), | ||
37 | // Fake file ast node | ||
38 | ModDeclaration(ast::Module), | ||
39 | // Original file ast node | ||
40 | /// The record expr of the field name we are completing | ||
41 | RecordExpr(ast::RecordExpr), | ||
42 | // Original file ast node | ||
43 | /// The record pat of the field name we are completing | ||
44 | RecordPat(ast::RecordPat), | ||
24 | } | 45 | } |
25 | 46 | ||
26 | pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | 47 | pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { |
27 | not_same_range_ancestor(element) | 48 | let node = match name_like { |
28 | .filter(|it| it.kind() == ASSOC_ITEM_LIST) | 49 | ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref), |
29 | .and_then(|it| it.parent()) | 50 | ast::NameLike::Name(n) => n.syntax().clone(), |
30 | .filter(|it| it.kind() == IMPL) | 51 | ast::NameLike::Lifetime(lt) => lt.syntax().clone(), |
31 | .is_some() | 52 | }; |
53 | let node = match node.parent().and_then(ast::MacroCall::cast) { | ||
54 | // When a path is being typed after the name of a trait/type of an impl it is being | ||
55 | // parsed as a macro, so when the trait/impl has a block following it an we are between the | ||
56 | // name and block the macro will attach the block to itself so maximizing fails to take | ||
57 | // that into account | ||
58 | // FIXME path expr and statement have a similar problem with attrs | ||
59 | Some(call) | ||
60 | if call.excl_token().is_none() | ||
61 | && call.token_tree().map_or(false, |t| t.l_curly_token().is_some()) | ||
62 | && call.semicolon_token().is_none() => | ||
63 | { | ||
64 | call.syntax().clone() | ||
65 | } | ||
66 | _ => node, | ||
67 | }; | ||
68 | let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?; | ||
69 | let res = match_ast! { | ||
70 | match prev_sibling { | ||
71 | ast::ExprStmt(it) => { | ||
72 | let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone(); | ||
73 | match_ast! { | ||
74 | match node { | ||
75 | ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr, | ||
76 | _ => return None, | ||
77 | } | ||
78 | } | ||
79 | }, | ||
80 | ast::Trait(it) => if it.assoc_item_list().is_none() { | ||
81 | ImmediatePrevSibling::TraitDefName | ||
82 | } else { | ||
83 | return None | ||
84 | }, | ||
85 | ast::Impl(it) => if it.assoc_item_list().is_none() | ||
86 | && (it.for_token().is_none() || it.self_ty().is_some()) { | ||
87 | ImmediatePrevSibling::ImplDefType | ||
88 | } else { | ||
89 | return None | ||
90 | }, | ||
91 | _ => return None, | ||
92 | } | ||
93 | }; | ||
94 | Some(res) | ||
32 | } | 95 | } |
33 | #[test] | 96 | |
34 | fn test_has_impl_parent() { | 97 | pub(crate) fn determine_location( |
35 | check_pattern_is_applicable(r"impl A { f$0 }", has_impl_parent); | 98 | sema: &Semantics<RootDatabase>, |
99 | original_file: &SyntaxNode, | ||
100 | offset: TextSize, | ||
101 | name_like: &ast::NameLike, | ||
102 | ) -> Option<ImmediateLocation> { | ||
103 | let node = match name_like { | ||
104 | ast::NameLike::NameRef(name_ref) => { | ||
105 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | ||
106 | return sema | ||
107 | .find_node_at_offset_with_macros(original_file, offset) | ||
108 | .map(ImmediateLocation::RecordExpr); | ||
109 | } | ||
110 | if ast::RecordPatField::for_field_name_ref(&name_ref).is_some() { | ||
111 | return sema | ||
112 | .find_node_at_offset_with_macros(original_file, offset) | ||
113 | .map(ImmediateLocation::RecordPat); | ||
114 | } | ||
115 | maximize_name_ref(name_ref) | ||
116 | } | ||
117 | ast::NameLike::Name(name) => { | ||
118 | if ast::RecordPatField::for_field_name(&name).is_some() { | ||
119 | return sema | ||
120 | .find_node_at_offset_with_macros(original_file, offset) | ||
121 | .map(ImmediateLocation::RecordPat); | ||
122 | } | ||
123 | name.syntax().clone() | ||
124 | } | ||
125 | ast::NameLike::Lifetime(lt) => lt.syntax().clone(), | ||
126 | }; | ||
127 | |||
128 | let parent = match node.parent() { | ||
129 | Some(parent) => match ast::MacroCall::cast(parent.clone()) { | ||
130 | // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. | ||
131 | // This is usually fine as the node expansion code above already accounts for that with | ||
132 | // the ancestors call, but there is one exception to this which is that when an attribute | ||
133 | // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ. | ||
134 | // FIXME path expr and statement have a similar problem | ||
135 | Some(call) | ||
136 | if call.excl_token().is_none() | ||
137 | && call.token_tree().is_none() | ||
138 | && call.semicolon_token().is_none() => | ||
139 | { | ||
140 | call.syntax().parent()? | ||
141 | } | ||
142 | _ => parent, | ||
143 | }, | ||
144 | // SourceFile | ||
145 | None => { | ||
146 | return match node.kind() { | ||
147 | MACRO_ITEMS | SOURCE_FILE => Some(ImmediateLocation::ItemList), | ||
148 | _ => None, | ||
149 | } | ||
150 | } | ||
151 | }; | ||
152 | |||
153 | let res = match_ast! { | ||
154 | match parent { | ||
155 | ast::IdentPat(_it) => ImmediateLocation::IdentPat, | ||
156 | ast::Use(_it) => ImmediateLocation::Use, | ||
157 | ast::BlockExpr(_it) => ImmediateLocation::BlockExpr, | ||
158 | ast::SourceFile(_it) => ImmediateLocation::ItemList, | ||
159 | ast::ItemList(_it) => ImmediateLocation::ItemList, | ||
160 | ast::RefExpr(_it) => ImmediateLocation::RefExpr, | ||
161 | ast::RecordField(_it) => ImmediateLocation::RecordField, | ||
162 | ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) { | ||
163 | Some(IMPL) => ImmediateLocation::Impl, | ||
164 | Some(TRAIT) => ImmediateLocation::Trait, | ||
165 | _ => return None, | ||
166 | }, | ||
167 | ast::Module(it) => if it.item_list().is_none() { | ||
168 | ImmediateLocation::ModDeclaration(it) | ||
169 | } else { | ||
170 | return None | ||
171 | }, | ||
172 | ast::Attr(it) => ImmediateLocation::Attribute(it), | ||
173 | _ => return None, | ||
174 | } | ||
175 | }; | ||
176 | Some(res) | ||
177 | } | ||
178 | |||
179 | fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode { | ||
180 | // Maximize a nameref to its enclosing path if its the last segment of said path | ||
181 | if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { | ||
182 | let p = segment.parent_path(); | ||
183 | if p.parent_path().is_none() { | ||
184 | if let Some(it) = p | ||
185 | .syntax() | ||
186 | .ancestors() | ||
187 | .take_while(|it| it.text_range() == p.syntax().text_range()) | ||
188 | .last() | ||
189 | { | ||
190 | return it; | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | name_ref.syntax().clone() | ||
36 | } | 195 | } |
37 | 196 | ||
38 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { | 197 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { |
@@ -53,98 +212,8 @@ fn test_inside_impl_trait_block() { | |||
53 | check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); | 212 | check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); |
54 | } | 213 | } |
55 | 214 | ||
56 | pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool { | 215 | pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> { |
57 | not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some() | 216 | element.into_token().and_then(|it| previous_non_trivia_token(it)) |
58 | } | ||
59 | #[test] | ||
60 | fn test_has_field_list_parent() { | ||
61 | check_pattern_is_applicable(r"struct Foo { f$0 }", has_field_list_parent); | ||
62 | check_pattern_is_applicable(r"struct Foo { f$0 pub f: i32}", has_field_list_parent); | ||
63 | } | ||
64 | |||
65 | pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { | ||
66 | not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() | ||
67 | } | ||
68 | #[test] | ||
69 | fn test_has_block_expr_parent() { | ||
70 | check_pattern_is_applicable(r"fn my_fn() { let a = 2; f$0 }", has_block_expr_parent); | ||
71 | } | ||
72 | |||
73 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | ||
74 | element.ancestors().any(|it| it.kind() == IDENT_PAT) | ||
75 | } | ||
76 | #[test] | ||
77 | fn test_has_bind_pat_parent() { | ||
78 | check_pattern_is_applicable(r"fn my_fn(m$0) {}", has_bind_pat_parent); | ||
79 | check_pattern_is_applicable(r"fn my_fn() { let m$0 }", has_bind_pat_parent); | ||
80 | } | ||
81 | |||
82 | pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { | ||
83 | not_same_range_ancestor(element) | ||
84 | .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) | ||
85 | .is_some() | ||
86 | } | ||
87 | #[test] | ||
88 | fn test_has_ref_parent() { | ||
89 | check_pattern_is_applicable(r"fn my_fn(&m$0) {}", has_ref_parent); | ||
90 | check_pattern_is_applicable(r"fn my() { let &m$0 }", has_ref_parent); | ||
91 | } | ||
92 | |||
93 | pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { | ||
94 | let ancestor = not_same_range_ancestor(element); | ||
95 | if !ancestor.is_some() { | ||
96 | return true; | ||
97 | } | ||
98 | ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some() | ||
99 | } | ||
100 | #[test] | ||
101 | fn test_has_item_list_or_source_file_parent() { | ||
102 | check_pattern_is_applicable(r"i$0", has_item_list_or_source_file_parent); | ||
103 | check_pattern_is_applicable(r"mod foo { f$0 }", has_item_list_or_source_file_parent); | ||
104 | } | ||
105 | |||
106 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | ||
107 | not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() | ||
108 | && previous_sibling_or_ancestor_sibling(element) | ||
109 | .and_then(|it| it.into_token()) | ||
110 | .filter(|it| it.kind() == FAT_ARROW) | ||
111 | .is_some() | ||
112 | } | ||
113 | #[test] | ||
114 | fn test_is_match_arm() { | ||
115 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm); | ||
116 | } | ||
117 | |||
118 | pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { | ||
119 | element | ||
120 | .into_token() | ||
121 | .and_then(|it| previous_non_trivia_token(it)) | ||
122 | .filter(|it| it.kind() == T![unsafe]) | ||
123 | .is_some() | ||
124 | } | ||
125 | #[test] | ||
126 | fn test_unsafe_is_prev() { | ||
127 | check_pattern_is_applicable(r"unsafe i$0", unsafe_is_prev); | ||
128 | } | ||
129 | |||
130 | pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | ||
131 | element | ||
132 | .into_token() | ||
133 | .and_then(|it| previous_non_trivia_token(it)) | ||
134 | .filter(|it| it.kind() == T![if]) | ||
135 | .is_some() | ||
136 | } | ||
137 | |||
138 | pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool { | ||
139 | element | ||
140 | .into_token() | ||
141 | .and_then(|it| previous_non_trivia_token(it)) | ||
142 | .filter(|it| it.kind() == T![fn]) | ||
143 | .is_some() | ||
144 | } | ||
145 | #[test] | ||
146 | fn test_fn_is_prev() { | ||
147 | check_pattern_is_applicable(r"fn l$0", fn_is_prev); | ||
148 | } | 217 | } |
149 | 218 | ||
150 | /// Check if the token previous to the previous one is `for`. | 219 | /// Check if the token previous to the previous one is `for`. |
@@ -162,55 +231,22 @@ fn test_for_is_prev2() { | |||
162 | check_pattern_is_applicable(r"for i i$0", for_is_prev2); | 231 | check_pattern_is_applicable(r"for i i$0", for_is_prev2); |
163 | } | 232 | } |
164 | 233 | ||
165 | #[test] | ||
166 | fn test_if_is_prev() { | ||
167 | check_pattern_is_applicable(r"if l$0", if_is_prev); | ||
168 | } | ||
169 | |||
170 | pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { | ||
171 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT).is_some() | ||
172 | } | ||
173 | #[test] | ||
174 | fn test_has_trait_as_prev_sibling() { | ||
175 | check_pattern_is_applicable(r"trait A w$0 {}", has_trait_as_prev_sibling); | ||
176 | } | ||
177 | |||
178 | pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { | ||
179 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL).is_some() | ||
180 | } | ||
181 | #[test] | ||
182 | fn test_has_impl_as_prev_sibling() { | ||
183 | check_pattern_is_applicable(r"impl A w$0 {}", has_impl_as_prev_sibling); | ||
184 | } | ||
185 | |||
186 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | 234 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { |
187 | for node in element.ancestors() { | ||
188 | if node.kind() == FN || node.kind() == CLOSURE_EXPR { | ||
189 | break; | ||
190 | } | ||
191 | let loop_body = match_ast! { | ||
192 | match node { | ||
193 | ast::ForExpr(it) => it.loop_body(), | ||
194 | ast::WhileExpr(it) => it.loop_body(), | ||
195 | ast::LoopExpr(it) => it.loop_body(), | ||
196 | _ => None, | ||
197 | } | ||
198 | }; | ||
199 | if let Some(body) = loop_body { | ||
200 | if body.syntax().text_range().contains_range(element.text_range()) { | ||
201 | return true; | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | false | ||
206 | } | ||
207 | |||
208 | fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | ||
209 | element | 235 | element |
210 | .ancestors() | 236 | .ancestors() |
211 | .take_while(|it| it.text_range() == element.text_range()) | 237 | .take_while(|it| it.kind() != FN && it.kind() != CLOSURE_EXPR) |
212 | .last() | 238 | .find_map(|it| { |
213 | .and_then(|it| it.parent()) | 239 | let loop_body = match_ast! { |
240 | match it { | ||
241 | ast::ForExpr(it) => it.loop_body(), | ||
242 | ast::WhileExpr(it) => it.loop_body(), | ||
243 | ast::LoopExpr(it) => it.loop_body(), | ||
244 | _ => None, | ||
245 | } | ||
246 | }; | ||
247 | loop_body.filter(|it| it.syntax().text_range().contains_range(element.text_range())) | ||
248 | }) | ||
249 | .is_some() | ||
214 | } | 250 | } |
215 | 251 | ||
216 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | 252 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { |
@@ -225,17 +261,119 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | |||
225 | None | 261 | None |
226 | } | 262 | } |
227 | 263 | ||
228 | fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { | 264 | #[cfg(test)] |
229 | let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); | 265 | mod tests { |
230 | if let Some(sibling) = token_sibling { | 266 | use syntax::algo::find_node_at_offset; |
231 | Some(sibling) | 267 | |
232 | } else { | 268 | use crate::test_utils::position; |
233 | // if not trying to find first ancestor which has such a sibling | 269 | |
234 | let range = element.text_range(); | 270 | use super::*; |
235 | let top_node = element.ancestors().take_while(|it| it.text_range() == range).last()?; | 271 | |
236 | let prev_sibling_node = top_node.ancestors().find(|it| { | 272 | fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) { |
237 | non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() | 273 | let (db, pos) = position(code); |
238 | })?; | 274 | |
239 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | 275 | let sema = Semantics::new(&db); |
276 | let original_file = sema.parse(pos.file_id); | ||
277 | |||
278 | let name_like = find_node_at_offset(original_file.syntax(), pos.offset).unwrap(); | ||
279 | assert_eq!( | ||
280 | determine_location(&sema, original_file.syntax(), pos.offset, &name_like), | ||
281 | loc.into() | ||
282 | ); | ||
283 | } | ||
284 | |||
285 | fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) { | ||
286 | check_pattern_is_applicable(code, |e| { | ||
287 | let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike"); | ||
288 | assert_eq!(determine_prev_sibling(name), sibling.into()); | ||
289 | true | ||
290 | }); | ||
291 | } | ||
292 | |||
293 | #[test] | ||
294 | fn test_trait_loc() { | ||
295 | check_location(r"trait A { f$0 }", ImmediateLocation::Trait); | ||
296 | check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait); | ||
297 | check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait); | ||
298 | check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait); | ||
299 | check_location(r"trait A$0 {}", None); | ||
300 | check_location(r"trait A { fn f$0 }", None); | ||
301 | } | ||
302 | |||
303 | #[test] | ||
304 | fn test_impl_loc() { | ||
305 | check_location(r"impl A { f$0 }", ImmediateLocation::Impl); | ||
306 | check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl); | ||
307 | check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl); | ||
308 | check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl); | ||
309 | check_location(r"impl A$0 {}", None); | ||
310 | check_location(r"impl A { fn f$0 }", None); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_use_loc() { | ||
315 | check_location(r"use f$0", ImmediateLocation::Use); | ||
316 | check_location(r"use f$0;", ImmediateLocation::Use); | ||
317 | check_location(r"use f::{f$0}", None); | ||
318 | check_location(r"use {f$0}", None); | ||
319 | } | ||
320 | |||
321 | #[test] | ||
322 | fn test_record_field_loc() { | ||
323 | check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField); | ||
324 | check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField); | ||
325 | check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
329 | fn test_block_expr_loc() { | ||
330 | check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr); | ||
331 | check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::BlockExpr); | ||
332 | } | ||
333 | |||
334 | #[test] | ||
335 | fn test_ident_pat_loc() { | ||
336 | check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat); | ||
337 | check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat); | ||
338 | check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat); | ||
339 | check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat); | ||
340 | } | ||
341 | |||
342 | #[test] | ||
343 | fn test_ref_expr_loc() { | ||
344 | check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); | ||
345 | } | ||
346 | |||
347 | #[test] | ||
348 | fn test_item_list_loc() { | ||
349 | check_location(r"i$0", ImmediateLocation::ItemList); | ||
350 | check_location(r"#[attr] i$0", ImmediateLocation::ItemList); | ||
351 | check_location(r"fn f() {} i$0", ImmediateLocation::ItemList); | ||
352 | check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList); | ||
353 | check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList); | ||
354 | check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList); | ||
355 | check_location(r"mod foo$0 {}", None); | ||
356 | } | ||
357 | |||
358 | #[test] | ||
359 | fn test_impl_prev_sibling() { | ||
360 | check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType); | ||
361 | check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType); | ||
362 | check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType); | ||
363 | check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType); | ||
364 | check_prev_sibling(r"impl A for w$0 {}", None); | ||
365 | check_prev_sibling(r"impl A for w$0", None); | ||
366 | } | ||
367 | |||
368 | #[test] | ||
369 | fn test_trait_prev_sibling() { | ||
370 | check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName); | ||
371 | check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName); | ||
372 | } | ||
373 | |||
374 | #[test] | ||
375 | fn test_if_expr_prev_sibling() { | ||
376 | check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr); | ||
377 | check_prev_sibling(r"fn foo() { if true {}; w$0", None); | ||
240 | } | 378 | } |
241 | } | 379 | } |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 6b04ee164..a49a60711 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -10,9 +10,7 @@ pub(crate) mod type_alias; | |||
10 | 10 | ||
11 | mod builder_ext; | 11 | mod builder_ext; |
12 | 12 | ||
13 | use hir::{ | 13 | use hir::{AsAssocItem, HasAttrs, HirDisplay}; |
14 | AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, | ||
15 | }; | ||
16 | use ide_db::{ | 14 | use ide_db::{ |
17 | helpers::{item_name, SnippetCap}, | 15 | helpers::{item_name, SnippetCap}, |
18 | RootDatabase, SymbolKind, | 16 | RootDatabase, SymbolKind, |
@@ -21,31 +19,32 @@ use syntax::TextRange; | |||
21 | 19 | ||
22 | use crate::{ | 20 | use crate::{ |
23 | item::{CompletionRelevanceTypeMatch, ImportEdit}, | 21 | item::{CompletionRelevanceTypeMatch, ImportEdit}, |
22 | render::{enum_variant::render_variant, function::render_fn, macro_::render_macro}, | ||
24 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionRelevance, | 23 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, CompletionRelevance, |
25 | }; | 24 | }; |
26 | 25 | ||
27 | use crate::render::{enum_variant::render_variant, function::render_fn, macro_::render_macro}; | ||
28 | |||
29 | pub(crate) fn render_field<'a>( | 26 | pub(crate) fn render_field<'a>( |
30 | ctx: RenderContext<'a>, | 27 | ctx: RenderContext<'a>, |
28 | receiver: Option<hir::Name>, | ||
31 | field: hir::Field, | 29 | field: hir::Field, |
32 | ty: &Type, | 30 | ty: &hir::Type, |
33 | ) -> CompletionItem { | 31 | ) -> CompletionItem { |
34 | Render::new(ctx).add_field(field, ty) | 32 | Render::new(ctx).render_field(receiver, field, ty) |
35 | } | 33 | } |
36 | 34 | ||
37 | pub(crate) fn render_tuple_field<'a>( | 35 | pub(crate) fn render_tuple_field<'a>( |
38 | ctx: RenderContext<'a>, | 36 | ctx: RenderContext<'a>, |
37 | receiver: Option<hir::Name>, | ||
39 | field: usize, | 38 | field: usize, |
40 | ty: &Type, | 39 | ty: &hir::Type, |
41 | ) -> CompletionItem { | 40 | ) -> CompletionItem { |
42 | Render::new(ctx).add_tuple_field(field, ty) | 41 | Render::new(ctx).render_tuple_field(receiver, field, ty) |
43 | } | 42 | } |
44 | 43 | ||
45 | pub(crate) fn render_resolution<'a>( | 44 | pub(crate) fn render_resolution<'a>( |
46 | ctx: RenderContext<'a>, | 45 | ctx: RenderContext<'a>, |
47 | local_name: String, | 46 | local_name: hir::Name, |
48 | resolution: &ScopeDef, | 47 | resolution: &hir::ScopeDef, |
49 | ) -> Option<CompletionItem> { | 48 | ) -> Option<CompletionItem> { |
50 | Render::new(ctx).render_resolution(local_name, None, resolution) | 49 | Render::new(ctx).render_resolution(local_name, None, resolution) |
51 | } | 50 | } |
@@ -54,12 +53,12 @@ pub(crate) fn render_resolution_with_import<'a>( | |||
54 | ctx: RenderContext<'a>, | 53 | ctx: RenderContext<'a>, |
55 | import_edit: ImportEdit, | 54 | import_edit: ImportEdit, |
56 | ) -> Option<CompletionItem> { | 55 | ) -> Option<CompletionItem> { |
57 | let resolution = ScopeDef::from(import_edit.import.original_item); | 56 | let resolution = hir::ScopeDef::from(import_edit.import.original_item); |
58 | let local_name = match resolution { | 57 | let local_name = match resolution { |
59 | ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), | 58 | hir::ScopeDef::ModuleDef(hir::ModuleDef::Function(f)) => f.name(ctx.completion.db), |
60 | ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), | 59 | hir::ScopeDef::ModuleDef(hir::ModuleDef::Const(c)) => c.name(ctx.completion.db)?, |
61 | ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), | 60 | hir::ScopeDef::ModuleDef(hir::ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db), |
62 | _ => item_name(ctx.db(), import_edit.import.original_item)?.to_string(), | 61 | _ => item_name(ctx.db(), import_edit.import.original_item)?, |
63 | }; | 62 | }; |
64 | Render::new(ctx).render_resolution(local_name, Some(import_edit), &resolution).map( | 63 | Render::new(ctx).render_resolution(local_name, Some(import_edit), &resolution).map( |
65 | |mut item| { | 64 | |mut item| { |
@@ -113,7 +112,7 @@ impl<'a> RenderContext<'a> { | |||
113 | || assoc.containing_trait(db).map(|trait_| self.is_deprecated(trait_)).unwrap_or(false) | 112 | || assoc.containing_trait(db).map(|trait_| self.is_deprecated(trait_)).unwrap_or(false) |
114 | } | 113 | } |
115 | 114 | ||
116 | fn docs(&self, node: impl HasAttrs) -> Option<Documentation> { | 115 | fn docs(&self, node: impl HasAttrs) -> Option<hir::Documentation> { |
117 | node.docs(self.db()) | 116 | node.docs(self.db()) |
118 | } | 117 | } |
119 | } | 118 | } |
@@ -129,13 +128,18 @@ impl<'a> Render<'a> { | |||
129 | Render { ctx } | 128 | Render { ctx } |
130 | } | 129 | } |
131 | 130 | ||
132 | fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem { | 131 | fn render_field( |
132 | &self, | ||
133 | receiver: Option<hir::Name>, | ||
134 | field: hir::Field, | ||
135 | ty: &hir::Type, | ||
136 | ) -> CompletionItem { | ||
133 | let is_deprecated = self.ctx.is_deprecated(field); | 137 | let is_deprecated = self.ctx.is_deprecated(field); |
134 | let name = field.name(self.ctx.db()); | 138 | let name = field.name(self.ctx.db()).to_string(); |
135 | let mut item = CompletionItem::new( | 139 | let mut item = CompletionItem::new( |
136 | CompletionKind::Reference, | 140 | CompletionKind::Reference, |
137 | self.ctx.source_range(), | 141 | self.ctx.source_range(), |
138 | name.to_string(), | 142 | receiver.map_or_else(|| name.clone(), |receiver| format!("{}.{}", receiver, name)), |
139 | ); | 143 | ); |
140 | item.kind(SymbolKind::Field) | 144 | item.kind(SymbolKind::Field) |
141 | .detail(ty.display(self.ctx.db()).to_string()) | 145 | .detail(ty.display(self.ctx.db()).to_string()) |
@@ -144,7 +148,7 @@ impl<'a> Render<'a> { | |||
144 | 148 | ||
145 | item.set_relevance(CompletionRelevance { | 149 | item.set_relevance(CompletionRelevance { |
146 | type_match: compute_type_match(self.ctx.completion, ty), | 150 | type_match: compute_type_match(self.ctx.completion, ty), |
147 | exact_name_match: compute_exact_name_match(self.ctx.completion, name.to_string()), | 151 | exact_name_match: compute_exact_name_match(self.ctx.completion, &name), |
148 | ..CompletionRelevance::default() | 152 | ..CompletionRelevance::default() |
149 | }); | 153 | }); |
150 | 154 | ||
@@ -157,11 +161,17 @@ impl<'a> Render<'a> { | |||
157 | item.build() | 161 | item.build() |
158 | } | 162 | } |
159 | 163 | ||
160 | fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { | 164 | fn render_tuple_field( |
165 | &self, | ||
166 | receiver: Option<hir::Name>, | ||
167 | field: usize, | ||
168 | ty: &hir::Type, | ||
169 | ) -> CompletionItem { | ||
161 | let mut item = CompletionItem::new( | 170 | let mut item = CompletionItem::new( |
162 | CompletionKind::Reference, | 171 | CompletionKind::Reference, |
163 | self.ctx.source_range(), | 172 | self.ctx.source_range(), |
164 | field.to_string(), | 173 | receiver |
174 | .map_or_else(|| field.to_string(), |receiver| format!("{}.{}", receiver, field)), | ||
165 | ); | 175 | ); |
166 | 176 | ||
167 | item.kind(SymbolKind::Field).detail(ty.display(self.ctx.db()).to_string()); | 177 | item.kind(SymbolKind::Field).detail(ty.display(self.ctx.db()).to_string()); |
@@ -171,74 +181,82 @@ impl<'a> Render<'a> { | |||
171 | 181 | ||
172 | fn render_resolution( | 182 | fn render_resolution( |
173 | self, | 183 | self, |
174 | local_name: String, | 184 | local_name: hir::Name, |
175 | import_to_add: Option<ImportEdit>, | 185 | import_to_add: Option<ImportEdit>, |
176 | resolution: &ScopeDef, | 186 | resolution: &hir::ScopeDef, |
177 | ) -> Option<CompletionItem> { | 187 | ) -> Option<CompletionItem> { |
178 | let _p = profile::span("render_resolution"); | 188 | let _p = profile::span("render_resolution"); |
179 | use hir::ModuleDef::*; | 189 | use hir::ModuleDef::*; |
180 | 190 | ||
181 | let completion_kind = match resolution { | 191 | let completion_kind = match resolution { |
182 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, | 192 | hir::ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, |
183 | _ => CompletionKind::Reference, | 193 | _ => CompletionKind::Reference, |
184 | }; | 194 | }; |
185 | 195 | ||
186 | let kind = match resolution { | 196 | let kind = match resolution { |
187 | ScopeDef::ModuleDef(Function(func)) => { | 197 | hir::ScopeDef::ModuleDef(Function(func)) => { |
188 | return render_fn(self.ctx, import_to_add, Some(local_name), *func); | 198 | return render_fn(self.ctx, import_to_add, Some(local_name), *func); |
189 | } | 199 | } |
190 | ScopeDef::ModuleDef(Variant(_)) | 200 | hir::ScopeDef::ModuleDef(Variant(_)) |
191 | if self.ctx.completion.is_pat_binding_or_const | 201 | if self.ctx.completion.is_pat_or_const.is_some() => |
192 | | self.ctx.completion.is_irrefutable_pat_binding => | ||
193 | { | 202 | { |
194 | CompletionItemKind::SymbolKind(SymbolKind::Variant) | 203 | CompletionItemKind::SymbolKind(SymbolKind::Variant) |
195 | } | 204 | } |
196 | ScopeDef::ModuleDef(Variant(var)) => { | 205 | hir::ScopeDef::ModuleDef(Variant(var)) => { |
197 | let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None); | 206 | let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None); |
198 | return Some(item); | 207 | return Some(item); |
199 | } | 208 | } |
200 | ScopeDef::MacroDef(mac) => { | 209 | hir::ScopeDef::MacroDef(mac) => { |
201 | let item = render_macro(self.ctx, import_to_add, local_name, *mac); | 210 | let item = render_macro(self.ctx, import_to_add, local_name, *mac); |
202 | return item; | 211 | return item; |
203 | } | 212 | } |
204 | 213 | ||
205 | ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::SymbolKind(SymbolKind::Module), | 214 | hir::ScopeDef::ModuleDef(Module(..)) => { |
206 | ScopeDef::ModuleDef(Adt(adt)) => CompletionItemKind::SymbolKind(match adt { | 215 | CompletionItemKind::SymbolKind(SymbolKind::Module) |
216 | } | ||
217 | hir::ScopeDef::ModuleDef(Adt(adt)) => CompletionItemKind::SymbolKind(match adt { | ||
207 | hir::Adt::Struct(_) => SymbolKind::Struct, | 218 | hir::Adt::Struct(_) => SymbolKind::Struct, |
208 | hir::Adt::Union(_) => SymbolKind::Union, | 219 | hir::Adt::Union(_) => SymbolKind::Union, |
209 | hir::Adt::Enum(_) => SymbolKind::Enum, | 220 | hir::Adt::Enum(_) => SymbolKind::Enum, |
210 | }), | 221 | }), |
211 | ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::SymbolKind(SymbolKind::Const), | 222 | hir::ScopeDef::ModuleDef(Const(..)) => { |
212 | ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::SymbolKind(SymbolKind::Static), | 223 | CompletionItemKind::SymbolKind(SymbolKind::Const) |
213 | ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::SymbolKind(SymbolKind::Trait), | 224 | } |
214 | ScopeDef::ModuleDef(TypeAlias(..)) => { | 225 | hir::ScopeDef::ModuleDef(Static(..)) => { |
226 | CompletionItemKind::SymbolKind(SymbolKind::Static) | ||
227 | } | ||
228 | hir::ScopeDef::ModuleDef(Trait(..)) => { | ||
229 | CompletionItemKind::SymbolKind(SymbolKind::Trait) | ||
230 | } | ||
231 | hir::ScopeDef::ModuleDef(TypeAlias(..)) => { | ||
215 | CompletionItemKind::SymbolKind(SymbolKind::TypeAlias) | 232 | CompletionItemKind::SymbolKind(SymbolKind::TypeAlias) |
216 | } | 233 | } |
217 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, | 234 | hir::ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, |
218 | ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param { | 235 | hir::ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param { |
219 | hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam, | 236 | hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam, |
220 | hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam, | 237 | hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam, |
221 | hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, | 238 | hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, |
222 | }), | 239 | }), |
223 | ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), | 240 | hir::ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), |
224 | ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label), | 241 | hir::ScopeDef::Label(..) => CompletionItemKind::SymbolKind(SymbolKind::Label), |
225 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { | 242 | hir::ScopeDef::AdtSelfType(..) | hir::ScopeDef::ImplSelfType(..) => { |
226 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) | 243 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) |
227 | } | 244 | } |
228 | ScopeDef::Unknown => { | 245 | hir::ScopeDef::Unknown => { |
229 | let mut item = CompletionItem::new( | 246 | let mut item = CompletionItem::new( |
230 | CompletionKind::Reference, | 247 | CompletionKind::Reference, |
231 | self.ctx.source_range(), | 248 | self.ctx.source_range(), |
232 | local_name, | 249 | local_name.to_string(), |
233 | ); | 250 | ); |
234 | item.kind(CompletionItemKind::UnresolvedReference).add_import(import_to_add); | 251 | item.kind(CompletionItemKind::UnresolvedReference).add_import(import_to_add); |
235 | return Some(item.build()); | 252 | return Some(item.build()); |
236 | } | 253 | } |
237 | }; | 254 | }; |
238 | 255 | ||
256 | let local_name = local_name.to_string(); | ||
239 | let mut item = | 257 | let mut item = |
240 | CompletionItem::new(completion_kind, self.ctx.source_range(), local_name.clone()); | 258 | CompletionItem::new(completion_kind, self.ctx.source_range(), local_name.clone()); |
241 | if let ScopeDef::Local(local) = resolution { | 259 | if let hir::ScopeDef::Local(local) = resolution { |
242 | let ty = local.ty(self.ctx.db()); | 260 | let ty = local.ty(self.ctx.db()); |
243 | if !ty.is_unknown() { | 261 | if !ty.is_unknown() { |
244 | item.detail(ty.display(self.ctx.db()).to_string()); | 262 | item.detail(ty.display(self.ctx.db()).to_string()); |
@@ -263,8 +281,10 @@ impl<'a> Render<'a> { | |||
263 | { | 281 | { |
264 | if let Some(cap) = self.ctx.snippet_cap() { | 282 | if let Some(cap) = self.ctx.snippet_cap() { |
265 | let has_non_default_type_params = match resolution { | 283 | let has_non_default_type_params = match resolution { |
266 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(self.ctx.db()), | 284 | hir::ScopeDef::ModuleDef(Adt(it)) => { |
267 | ScopeDef::ModuleDef(TypeAlias(it)) => { | 285 | it.has_non_default_type_params(self.ctx.db()) |
286 | } | ||
287 | hir::ScopeDef::ModuleDef(TypeAlias(it)) => { | ||
268 | it.has_non_default_type_params(self.ctx.db()) | 288 | it.has_non_default_type_params(self.ctx.db()) |
269 | } | 289 | } |
270 | _ => false, | 290 | _ => false, |
@@ -284,26 +304,26 @@ impl<'a> Render<'a> { | |||
284 | Some(item.build()) | 304 | Some(item.build()) |
285 | } | 305 | } |
286 | 306 | ||
287 | fn docs(&self, resolution: &ScopeDef) -> Option<Documentation> { | 307 | fn docs(&self, resolution: &hir::ScopeDef) -> Option<hir::Documentation> { |
288 | use hir::ModuleDef::*; | 308 | use hir::ModuleDef::*; |
289 | match resolution { | 309 | match resolution { |
290 | ScopeDef::ModuleDef(Module(it)) => it.docs(self.ctx.db()), | 310 | hir::ScopeDef::ModuleDef(Module(it)) => it.docs(self.ctx.db()), |
291 | ScopeDef::ModuleDef(Adt(it)) => it.docs(self.ctx.db()), | 311 | hir::ScopeDef::ModuleDef(Adt(it)) => it.docs(self.ctx.db()), |
292 | ScopeDef::ModuleDef(Variant(it)) => it.docs(self.ctx.db()), | 312 | hir::ScopeDef::ModuleDef(Variant(it)) => it.docs(self.ctx.db()), |
293 | ScopeDef::ModuleDef(Const(it)) => it.docs(self.ctx.db()), | 313 | hir::ScopeDef::ModuleDef(Const(it)) => it.docs(self.ctx.db()), |
294 | ScopeDef::ModuleDef(Static(it)) => it.docs(self.ctx.db()), | 314 | hir::ScopeDef::ModuleDef(Static(it)) => it.docs(self.ctx.db()), |
295 | ScopeDef::ModuleDef(Trait(it)) => it.docs(self.ctx.db()), | 315 | hir::ScopeDef::ModuleDef(Trait(it)) => it.docs(self.ctx.db()), |
296 | ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(self.ctx.db()), | 316 | hir::ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(self.ctx.db()), |
297 | _ => None, | 317 | _ => None, |
298 | } | 318 | } |
299 | } | 319 | } |
300 | 320 | ||
301 | fn is_deprecated(&self, resolution: &ScopeDef) -> bool { | 321 | fn is_deprecated(&self, resolution: &hir::ScopeDef) -> bool { |
302 | match resolution { | 322 | match resolution { |
303 | ScopeDef::ModuleDef(it) => self.ctx.is_deprecated_assoc_item(*it), | 323 | hir::ScopeDef::ModuleDef(it) => self.ctx.is_deprecated_assoc_item(*it), |
304 | ScopeDef::MacroDef(it) => self.ctx.is_deprecated(*it), | 324 | hir::ScopeDef::MacroDef(it) => self.ctx.is_deprecated(*it), |
305 | ScopeDef::GenericParam(it) => self.ctx.is_deprecated(*it), | 325 | hir::ScopeDef::GenericParam(it) => self.ctx.is_deprecated(*it), |
306 | ScopeDef::AdtSelfType(it) => self.ctx.is_deprecated(*it), | 326 | hir::ScopeDef::AdtSelfType(it) => self.ctx.is_deprecated(*it), |
307 | _ => false, | 327 | _ => false, |
308 | } | 328 | } |
309 | } | 329 | } |
@@ -330,21 +350,23 @@ fn compute_type_match( | |||
330 | } | 350 | } |
331 | } | 351 | } |
332 | 352 | ||
333 | fn compute_exact_name_match(ctx: &CompletionContext, completion_name: impl Into<String>) -> bool { | 353 | fn compute_exact_name_match(ctx: &CompletionContext, completion_name: &str) -> bool { |
334 | let completion_name = completion_name.into(); | ||
335 | ctx.expected_name.as_ref().map_or(false, |name| name.text() == completion_name) | 354 | ctx.expected_name.as_ref().map_or(false, |name| name.text() == completion_name) |
336 | } | 355 | } |
337 | 356 | ||
338 | fn compute_ref_match(ctx: &CompletionContext, completion_ty: &hir::Type) -> Option<Mutability> { | 357 | fn compute_ref_match( |
358 | ctx: &CompletionContext, | ||
359 | completion_ty: &hir::Type, | ||
360 | ) -> Option<hir::Mutability> { | ||
339 | let expected_type = ctx.expected_type.as_ref()?; | 361 | let expected_type = ctx.expected_type.as_ref()?; |
340 | if completion_ty != expected_type { | 362 | if completion_ty != expected_type { |
341 | let expected_type_without_ref = expected_type.remove_ref()?; | 363 | let expected_type_without_ref = expected_type.remove_ref()?; |
342 | if completion_ty.autoderef(ctx.db).any(|deref_ty| deref_ty == expected_type_without_ref) { | 364 | if completion_ty.autoderef(ctx.db).any(|deref_ty| deref_ty == expected_type_without_ref) { |
343 | cov_mark::hit!(suggest_ref); | 365 | cov_mark::hit!(suggest_ref); |
344 | let mutability = if expected_type.is_mutable_reference() { | 366 | let mutability = if expected_type.is_mutable_reference() { |
345 | Mutability::Mut | 367 | hir::Mutability::Mut |
346 | } else { | 368 | } else { |
347 | Mutability::Shared | 369 | hir::Mutability::Shared |
348 | }; | 370 | }; |
349 | return Some(mutability); | 371 | return Some(mutability); |
350 | }; | 372 | }; |
@@ -667,6 +689,13 @@ fn foo() { A { the$0 } } | |||
667 | ), | 689 | ), |
668 | detail: "u32", | 690 | detail: "u32", |
669 | deprecated: true, | 691 | deprecated: true, |
692 | relevance: CompletionRelevance { | ||
693 | exact_name_match: false, | ||
694 | type_match: Some( | ||
695 | CouldUnify, | ||
696 | ), | ||
697 | is_local: false, | ||
698 | }, | ||
670 | }, | 699 | }, |
671 | ] | 700 | ] |
672 | "#]], | 701 | "#]], |
diff --git a/crates/ide_completion/src/render/enum_variant.rs b/crates/ide_completion/src/render/enum_variant.rs index 0c0c71134..28f056e77 100644 --- a/crates/ide_completion/src/render/enum_variant.rs +++ b/crates/ide_completion/src/render/enum_variant.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | //! Renderer for `enum` variants. | 1 | //! Renderer for `enum` variants. |
2 | 2 | ||
3 | use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; | 3 | use std::iter; |
4 | |||
5 | use hir::{HasAttrs, HirDisplay}; | ||
4 | use ide_db::SymbolKind; | 6 | use ide_db::SymbolKind; |
5 | use itertools::Itertools; | 7 | use itertools::Itertools; |
6 | 8 | ||
@@ -13,9 +15,9 @@ use crate::{ | |||
13 | pub(crate) fn render_variant<'a>( | 15 | pub(crate) fn render_variant<'a>( |
14 | ctx: RenderContext<'a>, | 16 | ctx: RenderContext<'a>, |
15 | import_to_add: Option<ImportEdit>, | 17 | import_to_add: Option<ImportEdit>, |
16 | local_name: Option<String>, | 18 | local_name: Option<hir::Name>, |
17 | variant: hir::Variant, | 19 | variant: hir::Variant, |
18 | path: Option<ModPath>, | 20 | path: Option<hir::ModPath>, |
19 | ) -> CompletionItem { | 21 | ) -> CompletionItem { |
20 | let _p = profile::span("render_enum_variant"); | 22 | let _p = profile::span("render_enum_variant"); |
21 | EnumRender::new(ctx, local_name, variant, path).render(import_to_add) | 23 | EnumRender::new(ctx, local_name, variant, path).render(import_to_add) |
@@ -24,42 +26,45 @@ pub(crate) fn render_variant<'a>( | |||
24 | #[derive(Debug)] | 26 | #[derive(Debug)] |
25 | struct EnumRender<'a> { | 27 | struct EnumRender<'a> { |
26 | ctx: RenderContext<'a>, | 28 | ctx: RenderContext<'a>, |
27 | name: String, | 29 | name: hir::Name, |
28 | variant: hir::Variant, | 30 | variant: hir::Variant, |
29 | path: Option<ModPath>, | 31 | path: Option<hir::ModPath>, |
30 | qualified_name: String, | 32 | qualified_name: hir::ModPath, |
31 | short_qualified_name: String, | 33 | short_qualified_name: hir::ModPath, |
32 | variant_kind: StructKind, | 34 | variant_kind: hir::StructKind, |
33 | } | 35 | } |
34 | 36 | ||
35 | impl<'a> EnumRender<'a> { | 37 | impl<'a> EnumRender<'a> { |
36 | fn new( | 38 | fn new( |
37 | ctx: RenderContext<'a>, | 39 | ctx: RenderContext<'a>, |
38 | local_name: Option<String>, | 40 | local_name: Option<hir::Name>, |
39 | variant: hir::Variant, | 41 | variant: hir::Variant, |
40 | path: Option<ModPath>, | 42 | path: Option<hir::ModPath>, |
41 | ) -> EnumRender<'a> { | 43 | ) -> EnumRender<'a> { |
42 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db()).to_string()); | 44 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db())); |
43 | let variant_kind = variant.kind(ctx.db()); | 45 | let variant_kind = variant.kind(ctx.db()); |
44 | 46 | ||
45 | let (qualified_name, short_qualified_name) = match &path { | 47 | let (qualified_name, short_qualified_name) = match &path { |
46 | Some(path) => { | 48 | Some(path) => { |
47 | let full = path.to_string(); | 49 | let short = hir::ModPath::from_segments( |
48 | let segments = path.segments(); | 50 | hir::PathKind::Plain, |
49 | let short = segments[segments.len().saturating_sub(2)..].iter().join("::"); | 51 | path.segments().iter().skip(path.segments().len().saturating_sub(2)).cloned(), |
50 | (full, short) | 52 | ); |
53 | (path.clone(), short) | ||
51 | } | 54 | } |
52 | None => (name.to_string(), name.to_string()), | 55 | None => ( |
56 | hir::ModPath::from_segments(hir::PathKind::Plain, iter::once(name.clone())), | ||
57 | hir::ModPath::from_segments(hir::PathKind::Plain, iter::once(name.clone())), | ||
58 | ), | ||
53 | }; | 59 | }; |
54 | 60 | ||
55 | EnumRender { ctx, name, variant, path, qualified_name, short_qualified_name, variant_kind } | 61 | EnumRender { ctx, name, variant, path, qualified_name, short_qualified_name, variant_kind } |
56 | } | 62 | } |
57 | |||
58 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { | 63 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { |
59 | let mut item = CompletionItem::new( | 64 | let mut item = CompletionItem::new( |
60 | CompletionKind::Reference, | 65 | CompletionKind::Reference, |
61 | self.ctx.source_range(), | 66 | self.ctx.source_range(), |
62 | self.qualified_name.clone(), | 67 | self.qualified_name.to_string(), |
63 | ); | 68 | ); |
64 | item.kind(SymbolKind::Variant) | 69 | item.kind(SymbolKind::Variant) |
65 | .set_documentation(self.variant.docs(self.ctx.db())) | 70 | .set_documentation(self.variant.docs(self.ctx.db())) |
@@ -67,12 +72,16 @@ impl<'a> EnumRender<'a> { | |||
67 | .add_import(import_to_add) | 72 | .add_import(import_to_add) |
68 | .detail(self.detail()); | 73 | .detail(self.detail()); |
69 | 74 | ||
70 | if self.variant_kind == StructKind::Tuple { | 75 | if self.variant_kind == hir::StructKind::Tuple { |
71 | cov_mark::hit!(inserts_parens_for_tuple_enums); | 76 | cov_mark::hit!(inserts_parens_for_tuple_enums); |
72 | let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len()); | 77 | let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len()); |
73 | item.add_call_parens(self.ctx.completion, self.short_qualified_name, params); | 78 | item.add_call_parens( |
79 | self.ctx.completion, | ||
80 | self.short_qualified_name.to_string(), | ||
81 | params, | ||
82 | ); | ||
74 | } else if self.path.is_some() { | 83 | } else if self.path.is_some() { |
75 | item.lookup_by(self.short_qualified_name); | 84 | item.lookup_by(self.short_qualified_name.to_string()); |
76 | } | 85 | } |
77 | 86 | ||
78 | let ty = self.variant.parent_enum(self.ctx.completion.db).ty(self.ctx.completion.db); | 87 | let ty = self.variant.parent_enum(self.ctx.completion.db).ty(self.ctx.completion.db); |
@@ -96,11 +105,11 @@ impl<'a> EnumRender<'a> { | |||
96 | .map(|field| (field.name(self.ctx.db()), field.ty(self.ctx.db()))); | 105 | .map(|field| (field.name(self.ctx.db()), field.ty(self.ctx.db()))); |
97 | 106 | ||
98 | match self.variant_kind { | 107 | match self.variant_kind { |
99 | StructKind::Tuple | StructKind::Unit => format!( | 108 | hir::StructKind::Tuple | hir::StructKind::Unit => format!( |
100 | "({})", | 109 | "({})", |
101 | detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ") | 110 | detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ") |
102 | ), | 111 | ), |
103 | StructKind::Record => format!( | 112 | hir::StructKind::Record => format!( |
104 | "{{ {} }}", | 113 | "{{ {} }}", |
105 | detail_types | 114 | detail_types |
106 | .map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string())) | 115 | .map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string())) |
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs index d681e2c91..3ec77ca0f 100644 --- a/crates/ide_completion/src/render/function.rs +++ b/crates/ide_completion/src/render/function.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! Renderer for function calls. | 1 | //! Renderer for function calls. |
2 | 2 | ||
3 | use hir::{HasSource, HirDisplay, Type}; | 3 | use hir::{HasSource, HirDisplay}; |
4 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
5 | use itertools::Itertools; | 5 | use itertools::Itertools; |
6 | use syntax::ast::Fn; | 6 | use syntax::ast::Fn; |
@@ -16,27 +16,29 @@ use crate::{ | |||
16 | pub(crate) fn render_fn<'a>( | 16 | pub(crate) fn render_fn<'a>( |
17 | ctx: RenderContext<'a>, | 17 | ctx: RenderContext<'a>, |
18 | import_to_add: Option<ImportEdit>, | 18 | import_to_add: Option<ImportEdit>, |
19 | local_name: Option<String>, | 19 | local_name: Option<hir::Name>, |
20 | fn_: hir::Function, | 20 | fn_: hir::Function, |
21 | ) -> Option<CompletionItem> { | 21 | ) -> Option<CompletionItem> { |
22 | let _p = profile::span("render_fn"); | 22 | let _p = profile::span("render_fn"); |
23 | Some(FunctionRender::new(ctx, local_name, fn_, false)?.render(import_to_add)) | 23 | Some(FunctionRender::new(ctx, None, local_name, fn_, false)?.render(import_to_add)) |
24 | } | 24 | } |
25 | 25 | ||
26 | pub(crate) fn render_method<'a>( | 26 | pub(crate) fn render_method<'a>( |
27 | ctx: RenderContext<'a>, | 27 | ctx: RenderContext<'a>, |
28 | import_to_add: Option<ImportEdit>, | 28 | import_to_add: Option<ImportEdit>, |
29 | local_name: Option<String>, | 29 | receiver: Option<hir::Name>, |
30 | local_name: Option<hir::Name>, | ||
30 | fn_: hir::Function, | 31 | fn_: hir::Function, |
31 | ) -> Option<CompletionItem> { | 32 | ) -> Option<CompletionItem> { |
32 | let _p = profile::span("render_method"); | 33 | let _p = profile::span("render_method"); |
33 | Some(FunctionRender::new(ctx, local_name, fn_, true)?.render(import_to_add)) | 34 | Some(FunctionRender::new(ctx, receiver, local_name, fn_, true)?.render(import_to_add)) |
34 | } | 35 | } |
35 | 36 | ||
36 | #[derive(Debug)] | 37 | #[derive(Debug)] |
37 | struct FunctionRender<'a> { | 38 | struct FunctionRender<'a> { |
38 | ctx: RenderContext<'a>, | 39 | ctx: RenderContext<'a>, |
39 | name: String, | 40 | name: String, |
41 | receiver: Option<hir::Name>, | ||
40 | func: hir::Function, | 42 | func: hir::Function, |
41 | ast_node: Fn, | 43 | ast_node: Fn, |
42 | is_method: bool, | 44 | is_method: bool, |
@@ -45,18 +47,22 @@ struct FunctionRender<'a> { | |||
45 | impl<'a> FunctionRender<'a> { | 47 | impl<'a> FunctionRender<'a> { |
46 | fn new( | 48 | fn new( |
47 | ctx: RenderContext<'a>, | 49 | ctx: RenderContext<'a>, |
48 | local_name: Option<String>, | 50 | receiver: Option<hir::Name>, |
51 | local_name: Option<hir::Name>, | ||
49 | fn_: hir::Function, | 52 | fn_: hir::Function, |
50 | is_method: bool, | 53 | is_method: bool, |
51 | ) -> Option<FunctionRender<'a>> { | 54 | ) -> Option<FunctionRender<'a>> { |
52 | let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string()); | 55 | let name = local_name.unwrap_or_else(|| fn_.name(ctx.db())).to_string(); |
53 | let ast_node = fn_.source(ctx.db())?.value; | 56 | let ast_node = fn_.source(ctx.db())?.value; |
54 | 57 | ||
55 | Some(FunctionRender { ctx, name, func: fn_, ast_node, is_method }) | 58 | Some(FunctionRender { ctx, name, receiver, func: fn_, ast_node, is_method }) |
56 | } | 59 | } |
57 | 60 | ||
58 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { | 61 | fn render(mut self, import_to_add: Option<ImportEdit>) -> CompletionItem { |
59 | let params = self.params(); | 62 | let params = self.params(); |
63 | if let Some(receiver) = &self.receiver { | ||
64 | self.name = format!("{}.{}", receiver, &self.name) | ||
65 | } | ||
60 | let mut item = CompletionItem::new( | 66 | let mut item = CompletionItem::new( |
61 | CompletionKind::Reference, | 67 | CompletionKind::Reference, |
62 | self.ctx.source_range(), | 68 | self.ctx.source_range(), |
@@ -74,7 +80,7 @@ impl<'a> FunctionRender<'a> { | |||
74 | let ret_type = self.func.ret_type(self.ctx.db()); | 80 | let ret_type = self.func.ret_type(self.ctx.db()); |
75 | item.set_relevance(CompletionRelevance { | 81 | item.set_relevance(CompletionRelevance { |
76 | type_match: compute_type_match(self.ctx.completion, &ret_type), | 82 | type_match: compute_type_match(self.ctx.completion, &ret_type), |
77 | exact_name_match: compute_exact_name_match(self.ctx.completion, self.name.clone()), | 83 | exact_name_match: compute_exact_name_match(self.ctx.completion, &self.name), |
78 | ..CompletionRelevance::default() | 84 | ..CompletionRelevance::default() |
79 | }); | 85 | }); |
80 | 86 | ||
@@ -129,7 +135,7 @@ impl<'a> FunctionRender<'a> { | |||
129 | format!("-> {}", ret_ty.display(self.ctx.db())) | 135 | format!("-> {}", ret_ty.display(self.ctx.db())) |
130 | } | 136 | } |
131 | 137 | ||
132 | fn add_arg(&self, arg: &str, ty: &Type) -> String { | 138 | fn add_arg(&self, arg: &str, ty: &hir::Type) -> String { |
133 | if let Some(derefed_ty) = ty.remove_ref() { | 139 | if let Some(derefed_ty) = ty.remove_ref() { |
134 | for (name, local) in self.ctx.completion.locals.iter() { | 140 | for (name, local) in self.ctx.completion.locals.iter() { |
135 | if name == arg && local.ty(self.ctx.db()) == derefed_ty { | 141 | if name == arg && local.ty(self.ctx.db()) == derefed_ty { |
@@ -148,7 +154,7 @@ impl<'a> FunctionRender<'a> { | |||
148 | }; | 154 | }; |
149 | 155 | ||
150 | let mut params_pats = Vec::new(); | 156 | let mut params_pats = Vec::new(); |
151 | let params_ty = if self.ctx.completion.dot_receiver.is_some() { | 157 | let params_ty = if self.ctx.completion.dot_receiver.is_some() || self.receiver.is_some() { |
152 | self.func.method_params(self.ctx.db()).unwrap_or_default() | 158 | self.func.method_params(self.ctx.db()).unwrap_or_default() |
153 | } else { | 159 | } else { |
154 | if let Some(s) = ast_params.self_param() { | 160 | if let Some(s) = ast_params.self_param() { |
@@ -255,6 +261,26 @@ fn bar(s: &S) { | |||
255 | } | 261 | } |
256 | "#, | 262 | "#, |
257 | ); | 263 | ); |
264 | |||
265 | check_edit( | ||
266 | "self.foo", | ||
267 | r#" | ||
268 | struct S {} | ||
269 | impl S { | ||
270 | fn foo(&self, x: i32) { | ||
271 | $0 | ||
272 | } | ||
273 | } | ||
274 | "#, | ||
275 | r#" | ||
276 | struct S {} | ||
277 | impl S { | ||
278 | fn foo(&self, x: i32) { | ||
279 | self.foo(${1:x})$0 | ||
280 | } | ||
281 | } | ||
282 | "#, | ||
283 | ); | ||
258 | } | 284 | } |
259 | 285 | ||
260 | #[test] | 286 | #[test] |
diff --git a/crates/ide_completion/src/render/macro_.rs b/crates/ide_completion/src/render/macro_.rs index 7578ad50b..0dfba8acc 100644 --- a/crates/ide_completion/src/render/macro_.rs +++ b/crates/ide_completion/src/render/macro_.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | //! Renderer for macro invocations. | 1 | //! Renderer for macro invocations. |
2 | 2 | ||
3 | use hir::{Documentation, HasSource}; | 3 | use hir::HasSource; |
4 | use ide_db::SymbolKind; | 4 | use ide_db::SymbolKind; |
5 | use syntax::display::macro_label; | 5 | use syntax::display::macro_label; |
6 | 6 | ||
@@ -12,7 +12,7 @@ use crate::{ | |||
12 | pub(crate) fn render_macro<'a>( | 12 | pub(crate) fn render_macro<'a>( |
13 | ctx: RenderContext<'a>, | 13 | ctx: RenderContext<'a>, |
14 | import_to_add: Option<ImportEdit>, | 14 | import_to_add: Option<ImportEdit>, |
15 | name: String, | 15 | name: hir::Name, |
16 | macro_: hir::MacroDef, | 16 | macro_: hir::MacroDef, |
17 | ) -> Option<CompletionItem> { | 17 | ) -> Option<CompletionItem> { |
18 | let _p = profile::span("render_macro"); | 18 | let _p = profile::span("render_macro"); |
@@ -24,13 +24,14 @@ struct MacroRender<'a> { | |||
24 | ctx: RenderContext<'a>, | 24 | ctx: RenderContext<'a>, |
25 | name: String, | 25 | name: String, |
26 | macro_: hir::MacroDef, | 26 | macro_: hir::MacroDef, |
27 | docs: Option<Documentation>, | 27 | docs: Option<hir::Documentation>, |
28 | bra: &'static str, | 28 | bra: &'static str, |
29 | ket: &'static str, | 29 | ket: &'static str, |
30 | } | 30 | } |
31 | 31 | ||
32 | impl<'a> MacroRender<'a> { | 32 | impl<'a> MacroRender<'a> { |
33 | fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { | 33 | fn new(ctx: RenderContext<'a>, name: hir::Name, macro_: hir::MacroDef) -> MacroRender<'a> { |
34 | let name = name.to_string(); | ||
34 | let docs = ctx.docs(macro_); | 35 | let docs = ctx.docs(macro_); |
35 | let docs_str = docs.as_ref().map_or("", |s| s.as_str()); | 36 | let docs_str = docs.as_ref().map_or("", |s| s.as_str()); |
36 | let (bra, ket) = guess_macro_braces(&name, docs_str); | 37 | let (bra, ket) = guess_macro_braces(&name, docs_str); |
@@ -74,7 +75,11 @@ impl<'a> MacroRender<'a> { | |||
74 | if self.needs_bang() && self.ctx.snippet_cap().is_some() { | 75 | if self.needs_bang() && self.ctx.snippet_cap().is_some() { |
75 | format!("{}!{}…{}", self.name, self.bra, self.ket) | 76 | format!("{}!{}…{}", self.name, self.bra, self.ket) |
76 | } else { | 77 | } else { |
77 | self.banged_name() | 78 | if self.macro_.kind() == hir::MacroKind::Derive { |
79 | self.name.to_string() | ||
80 | } else { | ||
81 | self.banged_name() | ||
82 | } | ||
78 | } | 83 | } |
79 | } | 84 | } |
80 | 85 | ||
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs index 37be575e5..b0a4b2026 100644 --- a/crates/ide_completion/src/test_utils.rs +++ b/crates/ide_completion/src/test_utils.rs | |||
@@ -12,13 +12,14 @@ use ide_db::{ | |||
12 | use itertools::Itertools; | 12 | use itertools::Itertools; |
13 | use stdx::{format_to, trim_indent}; | 13 | use stdx::{format_to, trim_indent}; |
14 | use syntax::{AstNode, NodeOrToken, SyntaxElement}; | 14 | use syntax::{AstNode, NodeOrToken, SyntaxElement}; |
15 | use test_utils::{assert_eq_text, RangeOrOffset}; | 15 | use test_utils::assert_eq_text; |
16 | 16 | ||
17 | use crate::{item::CompletionKind, CompletionConfig, CompletionItem}; | 17 | use crate::{item::CompletionKind, CompletionConfig, CompletionItem}; |
18 | 18 | ||
19 | pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { | 19 | pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { |
20 | enable_postfix_completions: true, | 20 | enable_postfix_completions: true, |
21 | enable_imports_on_the_fly: true, | 21 | enable_imports_on_the_fly: true, |
22 | enable_self_on_the_fly: true, | ||
22 | add_call_parenthesis: true, | 23 | add_call_parenthesis: true, |
23 | add_call_argument_snippets: true, | 24 | add_call_argument_snippets: true, |
24 | snippet_cap: SnippetCap::new(true), | 25 | snippet_cap: SnippetCap::new(true), |
@@ -36,10 +37,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) { | |||
36 | let mut database = RootDatabase::default(); | 37 | let mut database = RootDatabase::default(); |
37 | database.apply_change(change_fixture.change); | 38 | database.apply_change(change_fixture.change); |
38 | let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); | 39 | let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); |
39 | let offset = match range_or_offset { | 40 | let offset = range_or_offset.expect_offset(); |
40 | RangeOrOffset::Range(_) => panic!(), | ||
41 | RangeOrOffset::Offset(it) => it, | ||
42 | }; | ||
43 | (database, FilePosition { file_id, offset }) | 41 | (database, FilePosition { file_id, offset }) |
44 | } | 42 | } |
45 | 43 | ||
@@ -52,10 +50,11 @@ pub(crate) fn do_completion_with_config( | |||
52 | code: &str, | 50 | code: &str, |
53 | kind: CompletionKind, | 51 | kind: CompletionKind, |
54 | ) -> Vec<CompletionItem> { | 52 | ) -> Vec<CompletionItem> { |
55 | let mut kind_completions: Vec<CompletionItem> = | 53 | get_all_items(config, code) |
56 | get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect(); | 54 | .into_iter() |
57 | kind_completions.sort_by(|l, r| l.label().cmp(r.label())); | 55 | .filter(|c| c.completion_kind == kind) |
58 | kind_completions | 56 | .sorted_by(|l, r| l.label().cmp(r.label())) |
57 | .collect() | ||
59 | } | 58 | } |
60 | 59 | ||
61 | pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { | 60 | pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { |
@@ -132,7 +131,7 @@ pub(crate) fn check_edit_with_config( | |||
132 | assert_eq_text!(&ra_fixture_after, &actual) | 131 | assert_eq_text!(&ra_fixture_after, &actual) |
133 | } | 132 | } |
134 | 133 | ||
135 | pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { | 134 | pub(crate) fn check_pattern_is_applicable(code: &str, check: impl FnOnce(SyntaxElement) -> bool) { |
136 | let (db, pos) = position(code); | 135 | let (db, pos) = position(code); |
137 | 136 | ||
138 | let sema = Semantics::new(&db); | 137 | let sema = Semantics::new(&db); |