diff options
-rw-r--r-- | crates/completion/src/completions.rs | 1371 | ||||
-rw-r--r-- | crates/completion/src/context.rs | 13 | ||||
-rw-r--r-- | crates/completion/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/completion/src/render.rs | 848 | ||||
-rw-r--r-- | crates/completion/src/render/builder_ext.rs | 94 | ||||
-rw-r--r-- | crates/completion/src/render/const_.rs | 55 | ||||
-rw-r--r-- | crates/completion/src/render/enum_variant.rs | 180 | ||||
-rw-r--r-- | crates/completion/src/render/function.rs | 303 | ||||
-rw-r--r-- | crates/completion/src/render/macro_.rs | 216 | ||||
-rw-r--r-- | crates/completion/src/render/type_alias.rs | 55 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/mod_resolution.rs | 17 | ||||
-rw-r--r-- | crates/hir_ty/src/diagnostics/decl_check.rs | 126 | ||||
-rw-r--r-- | editors/code/rust.tmGrammar.json | 62 |
13 files changed, 1953 insertions, 1388 deletions
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index d5fb85b79..75dbb1a23 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs | |||
@@ -14,14 +14,16 @@ pub(crate) mod macro_in_item_position; | |||
14 | pub(crate) mod trait_impl; | 14 | pub(crate) mod trait_impl; |
15 | pub(crate) mod mod_; | 15 | pub(crate) mod mod_; |
16 | 16 | ||
17 | use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; | 17 | use hir::{ModPath, ScopeDef, Type}; |
18 | use itertools::Itertools; | ||
19 | use syntax::{ast::NameOwner, display::*}; | ||
20 | use test_utils::mark; | ||
21 | 18 | ||
22 | use crate::{ | 19 | use crate::{ |
23 | item::Builder, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, | 20 | item::Builder, |
24 | CompletionScore, RootDatabase, | 21 | render::{ |
22 | const_::render_const, enum_variant::render_enum_variant, function::render_fn, | ||
23 | macro_::render_macro, render_field, render_resolution, render_tuple_field, | ||
24 | type_alias::render_type_alias, RenderContext, | ||
25 | }, | ||
26 | CompletionContext, CompletionItem, | ||
25 | }; | 27 | }; |
26 | 28 | ||
27 | /// Represents an in-progress set of completions being built. | 29 | /// Represents an in-progress set of completions being built. |
@@ -58,27 +60,13 @@ impl Completions { | |||
58 | } | 60 | } |
59 | 61 | ||
60 | pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { | 62 | pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &Type) { |
61 | let is_deprecated = is_deprecated(field, ctx.db); | 63 | let item = render_field(RenderContext::new(ctx), field, ty); |
62 | let name = field.name(ctx.db); | 64 | self.add(item); |
63 | let mut item = | ||
64 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) | ||
65 | .kind(CompletionItemKind::Field) | ||
66 | .detail(ty.display(ctx.db).to_string()) | ||
67 | .set_documentation(field.docs(ctx.db)) | ||
68 | .set_deprecated(is_deprecated); | ||
69 | |||
70 | if let Some(score) = compute_score(ctx, &ty, &name.to_string()) { | ||
71 | item = item.set_score(score); | ||
72 | } | ||
73 | |||
74 | item.add_to(self); | ||
75 | } | 65 | } |
76 | 66 | ||
77 | pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { | 67 | pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { |
78 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string()) | 68 | let item = render_tuple_field(RenderContext::new(ctx), field, ty); |
79 | .kind(CompletionItemKind::Field) | 69 | self.add(item); |
80 | .detail(ty.display(ctx.db).to_string()) | ||
81 | .add_to(self); | ||
82 | } | 70 | } |
83 | 71 | ||
84 | pub(crate) fn add_resolution( | 72 | pub(crate) fn add_resolution( |
@@ -87,100 +75,9 @@ impl Completions { | |||
87 | local_name: String, | 75 | local_name: String, |
88 | resolution: &ScopeDef, | 76 | resolution: &ScopeDef, |
89 | ) { | 77 | ) { |
90 | use hir::ModuleDef::*; | 78 | if let Some(item) = render_resolution(RenderContext::new(ctx), local_name, resolution) { |
91 | 79 | self.add(item); | |
92 | let completion_kind = match resolution { | ||
93 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, | ||
94 | _ => CompletionKind::Reference, | ||
95 | }; | ||
96 | |||
97 | let kind = match resolution { | ||
98 | ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, | ||
99 | ScopeDef::ModuleDef(Function(func)) => { | ||
100 | self.add_function(ctx, *func, Some(local_name)); | ||
101 | return; | ||
102 | } | ||
103 | ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, | ||
104 | // FIXME: add CompletionItemKind::Union | ||
105 | ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, | ||
106 | ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, | ||
107 | |||
108 | ScopeDef::ModuleDef(EnumVariant(var)) => { | ||
109 | self.add_enum_variant(ctx, *var, Some(local_name)); | ||
110 | return; | ||
111 | } | ||
112 | ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, | ||
113 | ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, | ||
114 | ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, | ||
115 | ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, | ||
116 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, | ||
117 | ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, | ||
118 | ScopeDef::Local(..) => CompletionItemKind::Binding, | ||
119 | // (does this need its own kind?) | ||
120 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, | ||
121 | ScopeDef::MacroDef(mac) => { | ||
122 | self.add_macro(ctx, Some(local_name), *mac); | ||
123 | return; | ||
124 | } | ||
125 | ScopeDef::Unknown => { | ||
126 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), local_name) | ||
127 | .kind(CompletionItemKind::UnresolvedReference) | ||
128 | .add_to(self); | ||
129 | return; | ||
130 | } | ||
131 | }; | ||
132 | |||
133 | let docs = match resolution { | ||
134 | ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db), | ||
135 | ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db), | ||
136 | ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db), | ||
137 | ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db), | ||
138 | ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db), | ||
139 | ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db), | ||
140 | ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db), | ||
141 | _ => None, | ||
142 | }; | ||
143 | |||
144 | let mut item = CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone()); | ||
145 | if let ScopeDef::Local(local) = resolution { | ||
146 | let ty = local.ty(ctx.db); | ||
147 | if !ty.is_unknown() { | ||
148 | item = item.detail(ty.display(ctx.db).to_string()); | ||
149 | } | ||
150 | }; | ||
151 | |||
152 | let mut ref_match = None; | ||
153 | if let ScopeDef::Local(local) = resolution { | ||
154 | if let Some((active_name, active_type)) = ctx.active_name_and_type() { | ||
155 | let ty = local.ty(ctx.db); | ||
156 | if let Some(score) = | ||
157 | compute_score_from_active(&active_type, &active_name, &ty, &local_name) | ||
158 | { | ||
159 | item = item.set_score(score); | ||
160 | } | ||
161 | ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name); | ||
162 | } | ||
163 | } | 80 | } |
164 | |||
165 | // Add `<>` for generic types | ||
166 | if ctx.is_path_type && !ctx.has_type_args && ctx.config.add_call_parenthesis { | ||
167 | if let Some(cap) = ctx.config.snippet_cap { | ||
168 | let has_non_default_type_params = match resolution { | ||
169 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), | ||
170 | ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), | ||
171 | _ => false, | ||
172 | }; | ||
173 | if has_non_default_type_params { | ||
174 | mark::hit!(inserts_angle_brackets_for_generics); | ||
175 | item = item | ||
176 | .lookup_by(local_name.clone()) | ||
177 | .label(format!("{}<…>", local_name)) | ||
178 | .insert_snippet(cap, format!("{}<$0>", local_name)); | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | |||
183 | item.kind(kind).set_documentation(docs).set_ref_match(ref_match).add_to(self) | ||
184 | } | 81 | } |
185 | 82 | ||
186 | pub(crate) fn add_macro( | 83 | pub(crate) fn add_macro( |
@@ -189,50 +86,13 @@ impl Completions { | |||
189 | name: Option<String>, | 86 | name: Option<String>, |
190 | macro_: hir::MacroDef, | 87 | macro_: hir::MacroDef, |
191 | ) { | 88 | ) { |
192 | // FIXME: Currently proc-macro do not have ast-node, | ||
193 | // such that it does not have source | ||
194 | if macro_.is_proc_macro() { | ||
195 | return; | ||
196 | } | ||
197 | |||
198 | let name = match name { | 89 | let name = match name { |
199 | Some(it) => it, | 90 | Some(it) => it, |
200 | None => return, | 91 | None => return, |
201 | }; | 92 | }; |
202 | 93 | if let Some(item) = render_macro(RenderContext::new(ctx), name, macro_) { | |
203 | let ast_node = macro_.source(ctx.db).value; | 94 | self.add(item); |
204 | let detail = macro_label(&ast_node); | 95 | } |
205 | |||
206 | let docs = macro_.docs(ctx.db); | ||
207 | |||
208 | let mut builder = CompletionItem::new( | ||
209 | CompletionKind::Reference, | ||
210 | ctx.source_range(), | ||
211 | &format!("{}!", name), | ||
212 | ) | ||
213 | .kind(CompletionItemKind::Macro) | ||
214 | .set_documentation(docs.clone()) | ||
215 | .set_deprecated(is_deprecated(macro_, ctx.db)) | ||
216 | .detail(detail); | ||
217 | |||
218 | let needs_bang = ctx.use_item_syntax.is_none() && !ctx.is_macro_call; | ||
219 | builder = match ctx.config.snippet_cap { | ||
220 | Some(cap) if needs_bang => { | ||
221 | let docs = docs.as_ref().map_or("", |s| s.as_str()); | ||
222 | let (bra, ket) = guess_macro_braces(&name, docs); | ||
223 | builder | ||
224 | .insert_snippet(cap, format!("{}!{}$0{}", name, bra, ket)) | ||
225 | .label(format!("{}!{}…{}", name, bra, ket)) | ||
226 | .lookup_by(format!("{}!", name)) | ||
227 | } | ||
228 | None if needs_bang => builder.insert_text(format!("{}!", name)), | ||
229 | _ => { | ||
230 | mark::hit!(dont_insert_macro_call_parens_unncessary); | ||
231 | builder.insert_text(name) | ||
232 | } | ||
233 | }; | ||
234 | |||
235 | self.add(builder.build()); | ||
236 | } | 96 | } |
237 | 97 | ||
238 | pub(crate) fn add_function( | 98 | pub(crate) fn add_function( |
@@ -241,82 +101,20 @@ impl Completions { | |||
241 | func: hir::Function, | 101 | func: hir::Function, |
242 | local_name: Option<String>, | 102 | local_name: Option<String>, |
243 | ) { | 103 | ) { |
244 | fn add_arg(arg: &str, ty: &Type, ctx: &CompletionContext) -> String { | 104 | let item = render_fn(RenderContext::new(ctx), local_name, func); |
245 | if let Some(derefed_ty) = ty.remove_ref() { | 105 | self.add(item) |
246 | for (name, local) in ctx.locals.iter() { | ||
247 | if name == arg && local.ty(ctx.db) == derefed_ty { | ||
248 | return (if ty.is_mutable_reference() { "&mut " } else { "&" }).to_string() | ||
249 | + &arg.to_string(); | ||
250 | } | ||
251 | } | ||
252 | } | ||
253 | arg.to_string() | ||
254 | }; | ||
255 | let name = local_name.unwrap_or_else(|| func.name(ctx.db).to_string()); | ||
256 | let ast_node = func.source(ctx.db).value; | ||
257 | |||
258 | let mut builder = | ||
259 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) | ||
260 | .kind(if func.self_param(ctx.db).is_some() { | ||
261 | CompletionItemKind::Method | ||
262 | } else { | ||
263 | CompletionItemKind::Function | ||
264 | }) | ||
265 | .set_documentation(func.docs(ctx.db)) | ||
266 | .set_deprecated(is_deprecated(func, ctx.db)) | ||
267 | .detail(function_declaration(&ast_node)); | ||
268 | |||
269 | let params_ty = func.params(ctx.db); | ||
270 | let params = ast_node | ||
271 | .param_list() | ||
272 | .into_iter() | ||
273 | .flat_map(|it| it.params()) | ||
274 | .zip(params_ty) | ||
275 | .flat_map(|(it, param_ty)| { | ||
276 | if let Some(pat) = it.pat() { | ||
277 | let name = pat.to_string(); | ||
278 | let arg = name.trim_start_matches("mut ").trim_start_matches('_'); | ||
279 | return Some(add_arg(arg, param_ty.ty(), ctx)); | ||
280 | } | ||
281 | None | ||
282 | }) | ||
283 | .collect(); | ||
284 | |||
285 | builder = builder.add_call_parens(ctx, name, Params::Named(params)); | ||
286 | |||
287 | self.add(builder.build()) | ||
288 | } | 106 | } |
289 | 107 | ||
290 | pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { | 108 | pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { |
291 | let ast_node = constant.source(ctx.db).value; | 109 | if let Some(item) = render_const(RenderContext::new(ctx), constant) { |
292 | let name = match ast_node.name() { | 110 | self.add(item); |
293 | Some(name) => name, | 111 | } |
294 | _ => return, | ||
295 | }; | ||
296 | let detail = const_label(&ast_node); | ||
297 | |||
298 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) | ||
299 | .kind(CompletionItemKind::Const) | ||
300 | .set_documentation(constant.docs(ctx.db)) | ||
301 | .set_deprecated(is_deprecated(constant, ctx.db)) | ||
302 | .detail(detail) | ||
303 | .add_to(self); | ||
304 | } | 112 | } |
305 | 113 | ||
306 | pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { | 114 | pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { |
307 | let type_def = type_alias.source(ctx.db).value; | 115 | if let Some(item) = render_type_alias(RenderContext::new(ctx), type_alias) { |
308 | let name = match type_def.name() { | 116 | self.add(item) |
309 | Some(name) => name, | 117 | } |
310 | _ => return, | ||
311 | }; | ||
312 | let detail = type_label(&type_def); | ||
313 | |||
314 | CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) | ||
315 | .kind(CompletionItemKind::TypeAlias) | ||
316 | .set_documentation(type_alias.docs(ctx.db)) | ||
317 | .set_deprecated(is_deprecated(type_alias, ctx.db)) | ||
318 | .detail(detail) | ||
319 | .add_to(self); | ||
320 | } | 118 | } |
321 | 119 | ||
322 | pub(crate) fn add_qualified_enum_variant( | 120 | pub(crate) fn add_qualified_enum_variant( |
@@ -325,7 +123,8 @@ impl Completions { | |||
325 | variant: hir::EnumVariant, | 123 | variant: hir::EnumVariant, |
326 | path: ModPath, | 124 | path: ModPath, |
327 | ) { | 125 | ) { |
328 | self.add_enum_variant_impl(ctx, variant, None, Some(path)) | 126 | let item = render_enum_variant(RenderContext::new(ctx), None, variant, Some(path)); |
127 | self.add(item); | ||
329 | } | 128 | } |
330 | 129 | ||
331 | pub(crate) fn add_enum_variant( | 130 | pub(crate) fn add_enum_variant( |
@@ -334,1119 +133,7 @@ impl Completions { | |||
334 | variant: hir::EnumVariant, | 133 | variant: hir::EnumVariant, |
335 | local_name: Option<String>, | 134 | local_name: Option<String>, |
336 | ) { | 135 | ) { |
337 | self.add_enum_variant_impl(ctx, variant, local_name, None) | 136 | let item = render_enum_variant(RenderContext::new(ctx), local_name, variant, None); |
338 | } | 137 | self.add(item); |
339 | |||
340 | fn add_enum_variant_impl( | ||
341 | &mut self, | ||
342 | ctx: &CompletionContext, | ||
343 | variant: hir::EnumVariant, | ||
344 | local_name: Option<String>, | ||
345 | path: Option<ModPath>, | ||
346 | ) { | ||
347 | let is_deprecated = is_deprecated(variant, ctx.db); | ||
348 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); | ||
349 | let (qualified_name, short_qualified_name) = match &path { | ||
350 | Some(path) => { | ||
351 | let full = path.to_string(); | ||
352 | let short = | ||
353 | path.segments[path.segments.len().saturating_sub(2)..].iter().join("::"); | ||
354 | (full, short) | ||
355 | } | ||
356 | None => (name.to_string(), name.to_string()), | ||
357 | }; | ||
358 | let detail_types = variant | ||
359 | .fields(ctx.db) | ||
360 | .into_iter() | ||
361 | .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db))); | ||
362 | let variant_kind = variant.kind(ctx.db); | ||
363 | let detail = match variant_kind { | ||
364 | StructKind::Tuple | StructKind::Unit => format!( | ||
365 | "({})", | ||
366 | detail_types.map(|(_, t)| t.display(ctx.db).to_string()).format(", ") | ||
367 | ), | ||
368 | StructKind::Record => format!( | ||
369 | "{{ {} }}", | ||
370 | detail_types | ||
371 | .map(|(n, t)| format!("{}: {}", n, t.display(ctx.db).to_string())) | ||
372 | .format(", ") | ||
373 | ), | ||
374 | }; | ||
375 | let mut res = CompletionItem::new( | ||
376 | CompletionKind::Reference, | ||
377 | ctx.source_range(), | ||
378 | qualified_name.clone(), | ||
379 | ) | ||
380 | .kind(CompletionItemKind::EnumVariant) | ||
381 | .set_documentation(variant.docs(ctx.db)) | ||
382 | .set_deprecated(is_deprecated) | ||
383 | .detail(detail); | ||
384 | |||
385 | if variant_kind == StructKind::Tuple { | ||
386 | mark::hit!(inserts_parens_for_tuple_enums); | ||
387 | let params = Params::Anonymous(variant.fields(ctx.db).len()); | ||
388 | res = res.add_call_parens(ctx, short_qualified_name, params) | ||
389 | } else if path.is_some() { | ||
390 | res = res.lookup_by(short_qualified_name); | ||
391 | } | ||
392 | |||
393 | res.add_to(self); | ||
394 | } | ||
395 | } | ||
396 | |||
397 | fn compute_score_from_active( | ||
398 | active_type: &Type, | ||
399 | active_name: &str, | ||
400 | ty: &Type, | ||
401 | name: &str, | ||
402 | ) -> Option<CompletionScore> { | ||
403 | // Compute score | ||
404 | // For the same type | ||
405 | if active_type != ty { | ||
406 | return None; | ||
407 | } | ||
408 | |||
409 | let mut res = CompletionScore::TypeMatch; | ||
410 | |||
411 | // If same type + same name then go top position | ||
412 | if active_name == name { | ||
413 | res = CompletionScore::TypeAndNameMatch | ||
414 | } | ||
415 | |||
416 | Some(res) | ||
417 | } | ||
418 | fn refed_type_matches( | ||
419 | active_type: &Type, | ||
420 | active_name: &str, | ||
421 | ty: &Type, | ||
422 | name: &str, | ||
423 | ) -> Option<(Mutability, CompletionScore)> { | ||
424 | let derefed_active = active_type.remove_ref()?; | ||
425 | let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?; | ||
426 | Some(( | ||
427 | if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared }, | ||
428 | score, | ||
429 | )) | ||
430 | } | ||
431 | |||
432 | fn compute_score(ctx: &CompletionContext, ty: &Type, name: &str) -> Option<CompletionScore> { | ||
433 | let (active_name, active_type) = ctx.active_name_and_type()?; | ||
434 | compute_score_from_active(&active_type, &active_name, ty, name) | ||
435 | } | ||
436 | |||
437 | enum Params { | ||
438 | Named(Vec<String>), | ||
439 | Anonymous(usize), | ||
440 | } | ||
441 | |||
442 | impl Params { | ||
443 | fn len(&self) -> usize { | ||
444 | match self { | ||
445 | Params::Named(xs) => xs.len(), | ||
446 | Params::Anonymous(len) => *len, | ||
447 | } | ||
448 | } | ||
449 | |||
450 | fn is_empty(&self) -> bool { | ||
451 | self.len() == 0 | ||
452 | } | ||
453 | } | ||
454 | |||
455 | impl Builder { | ||
456 | fn add_call_parens(mut self, ctx: &CompletionContext, name: String, params: Params) -> Builder { | ||
457 | if !ctx.config.add_call_parenthesis { | ||
458 | return self; | ||
459 | } | ||
460 | if ctx.use_item_syntax.is_some() { | ||
461 | mark::hit!(no_parens_in_use_item); | ||
462 | return self; | ||
463 | } | ||
464 | if ctx.is_pattern_call { | ||
465 | mark::hit!(dont_duplicate_pattern_parens); | ||
466 | return self; | ||
467 | } | ||
468 | if ctx.is_call { | ||
469 | return self; | ||
470 | } | ||
471 | |||
472 | // Don't add parentheses if the expected type is some function reference. | ||
473 | if let Some(ty) = &ctx.expected_type { | ||
474 | if ty.is_fn() { | ||
475 | mark::hit!(no_call_parens_if_fn_ptr_needed); | ||
476 | return self; | ||
477 | } | ||
478 | } | ||
479 | |||
480 | let cap = match ctx.config.snippet_cap { | ||
481 | Some(it) => it, | ||
482 | None => return self, | ||
483 | }; | ||
484 | // If not an import, add parenthesis automatically. | ||
485 | mark::hit!(inserts_parens_for_function_calls); | ||
486 | |||
487 | let (snippet, label) = if params.is_empty() { | ||
488 | (format!("{}()$0", name), format!("{}()", name)) | ||
489 | } else { | ||
490 | self = self.trigger_call_info(); | ||
491 | let snippet = match (ctx.config.add_call_argument_snippets, params) { | ||
492 | (true, Params::Named(params)) => { | ||
493 | let function_params_snippet = | ||
494 | params.iter().enumerate().format_with(", ", |(index, param_name), f| { | ||
495 | f(&format_args!("${{{}:{}}}", index + 1, param_name)) | ||
496 | }); | ||
497 | format!("{}({})$0", name, function_params_snippet) | ||
498 | } | ||
499 | _ => { | ||
500 | mark::hit!(suppress_arg_snippets); | ||
501 | format!("{}($0)", name) | ||
502 | } | ||
503 | }; | ||
504 | |||
505 | (snippet, format!("{}(…)", name)) | ||
506 | }; | ||
507 | self.lookup_by(name).label(label).insert_snippet(cap, snippet) | ||
508 | } | ||
509 | } | ||
510 | |||
511 | fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool { | ||
512 | node.attrs(db).by_key("deprecated").exists() | ||
513 | } | ||
514 | |||
515 | fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { | ||
516 | let mut votes = [0, 0, 0]; | ||
517 | for (idx, s) in docs.match_indices(¯o_name) { | ||
518 | let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); | ||
519 | // Ensure to match the full word | ||
520 | if after.starts_with('!') | ||
521 | && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) | ||
522 | { | ||
523 | // It may have spaces before the braces like `foo! {}` | ||
524 | match after[1..].chars().find(|&c| !c.is_whitespace()) { | ||
525 | Some('{') => votes[0] += 1, | ||
526 | Some('[') => votes[1] += 1, | ||
527 | Some('(') => votes[2] += 1, | ||
528 | _ => {} | ||
529 | } | ||
530 | } | ||
531 | } | ||
532 | |||
533 | // Insert a space before `{}`. | ||
534 | // We prefer the last one when some votes equal. | ||
535 | let (_vote, (bra, ket)) = votes | ||
536 | .iter() | ||
537 | .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) | ||
538 | .max_by_key(|&(&vote, _)| vote) | ||
539 | .unwrap(); | ||
540 | (*bra, *ket) | ||
541 | } | ||
542 | |||
543 | #[cfg(test)] | ||
544 | mod tests { | ||
545 | use std::cmp::Reverse; | ||
546 | |||
547 | use expect_test::{expect, Expect}; | ||
548 | use test_utils::mark; | ||
549 | |||
550 | use crate::{ | ||
551 | test_utils::{check_edit, check_edit_with_config, do_completion, get_all_items}, | ||
552 | CompletionConfig, CompletionKind, CompletionScore, | ||
553 | }; | ||
554 | |||
555 | fn check(ra_fixture: &str, expect: Expect) { | ||
556 | let actual = do_completion(ra_fixture, CompletionKind::Reference); | ||
557 | expect.assert_debug_eq(&actual); | ||
558 | } | ||
559 | |||
560 | fn check_scores(ra_fixture: &str, expect: Expect) { | ||
561 | fn display_score(score: Option<CompletionScore>) -> &'static str { | ||
562 | match score { | ||
563 | Some(CompletionScore::TypeMatch) => "[type]", | ||
564 | Some(CompletionScore::TypeAndNameMatch) => "[type+name]", | ||
565 | None => "[]".into(), | ||
566 | } | ||
567 | } | ||
568 | |||
569 | let mut completions = get_all_items(CompletionConfig::default(), ra_fixture); | ||
570 | completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); | ||
571 | let actual = completions | ||
572 | .into_iter() | ||
573 | .filter(|it| it.completion_kind == CompletionKind::Reference) | ||
574 | .map(|it| { | ||
575 | let tag = it.kind().unwrap().tag(); | ||
576 | let score = display_score(it.score()); | ||
577 | format!("{} {} {}\n", tag, it.label(), score) | ||
578 | }) | ||
579 | .collect::<String>(); | ||
580 | expect.assert_eq(&actual); | ||
581 | } | ||
582 | |||
583 | #[test] | ||
584 | fn enum_detail_includes_record_fields() { | ||
585 | check( | ||
586 | r#" | ||
587 | enum Foo { Foo { x: i32, y: i32 } } | ||
588 | |||
589 | fn main() { Foo::Fo<|> } | ||
590 | "#, | ||
591 | expect![[r#" | ||
592 | [ | ||
593 | CompletionItem { | ||
594 | label: "Foo", | ||
595 | source_range: 54..56, | ||
596 | delete: 54..56, | ||
597 | insert: "Foo", | ||
598 | kind: EnumVariant, | ||
599 | detail: "{ x: i32, y: i32 }", | ||
600 | }, | ||
601 | ] | ||
602 | "#]], | ||
603 | ); | ||
604 | } | ||
605 | |||
606 | #[test] | ||
607 | fn enum_detail_doesnt_include_tuple_fields() { | ||
608 | check( | ||
609 | r#" | ||
610 | enum Foo { Foo (i32, i32) } | ||
611 | |||
612 | fn main() { Foo::Fo<|> } | ||
613 | "#, | ||
614 | expect![[r#" | ||
615 | [ | ||
616 | CompletionItem { | ||
617 | label: "Foo(…)", | ||
618 | source_range: 46..48, | ||
619 | delete: 46..48, | ||
620 | insert: "Foo($0)", | ||
621 | kind: EnumVariant, | ||
622 | lookup: "Foo", | ||
623 | detail: "(i32, i32)", | ||
624 | trigger_call_info: true, | ||
625 | }, | ||
626 | ] | ||
627 | "#]], | ||
628 | ); | ||
629 | } | ||
630 | |||
631 | #[test] | ||
632 | fn enum_detail_just_parentheses_for_unit() { | ||
633 | check( | ||
634 | r#" | ||
635 | enum Foo { Foo } | ||
636 | |||
637 | fn main() { Foo::Fo<|> } | ||
638 | "#, | ||
639 | expect![[r#" | ||
640 | [ | ||
641 | CompletionItem { | ||
642 | label: "Foo", | ||
643 | source_range: 35..37, | ||
644 | delete: 35..37, | ||
645 | insert: "Foo", | ||
646 | kind: EnumVariant, | ||
647 | detail: "()", | ||
648 | }, | ||
649 | ] | ||
650 | "#]], | ||
651 | ); | ||
652 | } | ||
653 | |||
654 | #[test] | ||
655 | fn lookup_enums_by_two_qualifiers() { | ||
656 | check( | ||
657 | r#" | ||
658 | mod m { | ||
659 | pub enum Spam { Foo, Bar(i32) } | ||
660 | } | ||
661 | fn main() { let _: m::Spam = S<|> } | ||
662 | "#, | ||
663 | expect![[r#" | ||
664 | [ | ||
665 | CompletionItem { | ||
666 | label: "Spam::Bar(…)", | ||
667 | source_range: 75..76, | ||
668 | delete: 75..76, | ||
669 | insert: "Spam::Bar($0)", | ||
670 | kind: EnumVariant, | ||
671 | lookup: "Spam::Bar", | ||
672 | detail: "(i32)", | ||
673 | trigger_call_info: true, | ||
674 | }, | ||
675 | CompletionItem { | ||
676 | label: "m", | ||
677 | source_range: 75..76, | ||
678 | delete: 75..76, | ||
679 | insert: "m", | ||
680 | kind: Module, | ||
681 | }, | ||
682 | CompletionItem { | ||
683 | label: "m::Spam::Foo", | ||
684 | source_range: 75..76, | ||
685 | delete: 75..76, | ||
686 | insert: "m::Spam::Foo", | ||
687 | kind: EnumVariant, | ||
688 | lookup: "Spam::Foo", | ||
689 | detail: "()", | ||
690 | }, | ||
691 | CompletionItem { | ||
692 | label: "main()", | ||
693 | source_range: 75..76, | ||
694 | delete: 75..76, | ||
695 | insert: "main()$0", | ||
696 | kind: Function, | ||
697 | lookup: "main", | ||
698 | detail: "fn main()", | ||
699 | }, | ||
700 | ] | ||
701 | "#]], | ||
702 | ) | ||
703 | } | ||
704 | |||
705 | #[test] | ||
706 | fn sets_deprecated_flag_in_items() { | ||
707 | check( | ||
708 | r#" | ||
709 | #[deprecated] | ||
710 | fn something_deprecated() {} | ||
711 | #[deprecated(since = "1.0.0")] | ||
712 | fn something_else_deprecated() {} | ||
713 | |||
714 | fn main() { som<|> } | ||
715 | "#, | ||
716 | expect![[r#" | ||
717 | [ | ||
718 | CompletionItem { | ||
719 | label: "main()", | ||
720 | source_range: 121..124, | ||
721 | delete: 121..124, | ||
722 | insert: "main()$0", | ||
723 | kind: Function, | ||
724 | lookup: "main", | ||
725 | detail: "fn main()", | ||
726 | }, | ||
727 | CompletionItem { | ||
728 | label: "something_deprecated()", | ||
729 | source_range: 121..124, | ||
730 | delete: 121..124, | ||
731 | insert: "something_deprecated()$0", | ||
732 | kind: Function, | ||
733 | lookup: "something_deprecated", | ||
734 | detail: "fn something_deprecated()", | ||
735 | deprecated: true, | ||
736 | }, | ||
737 | CompletionItem { | ||
738 | label: "something_else_deprecated()", | ||
739 | source_range: 121..124, | ||
740 | delete: 121..124, | ||
741 | insert: "something_else_deprecated()$0", | ||
742 | kind: Function, | ||
743 | lookup: "something_else_deprecated", | ||
744 | detail: "fn something_else_deprecated()", | ||
745 | deprecated: true, | ||
746 | }, | ||
747 | ] | ||
748 | "#]], | ||
749 | ); | ||
750 | |||
751 | check( | ||
752 | r#" | ||
753 | struct A { #[deprecated] the_field: u32 } | ||
754 | fn foo() { A { the<|> } } | ||
755 | "#, | ||
756 | expect![[r#" | ||
757 | [ | ||
758 | CompletionItem { | ||
759 | label: "the_field", | ||
760 | source_range: 57..60, | ||
761 | delete: 57..60, | ||
762 | insert: "the_field", | ||
763 | kind: Field, | ||
764 | detail: "u32", | ||
765 | deprecated: true, | ||
766 | }, | ||
767 | ] | ||
768 | "#]], | ||
769 | ); | ||
770 | } | ||
771 | |||
772 | #[test] | ||
773 | fn renders_docs() { | ||
774 | check( | ||
775 | r#" | ||
776 | struct S { | ||
777 | /// Field docs | ||
778 | foo: | ||
779 | } | ||
780 | impl S { | ||
781 | /// Method docs | ||
782 | fn bar(self) { self.<|> } | ||
783 | }"#, | ||
784 | expect![[r#" | ||
785 | [ | ||
786 | CompletionItem { | ||
787 | label: "bar()", | ||
788 | source_range: 94..94, | ||
789 | delete: 94..94, | ||
790 | insert: "bar()$0", | ||
791 | kind: Method, | ||
792 | lookup: "bar", | ||
793 | detail: "fn bar(self)", | ||
794 | documentation: Documentation( | ||
795 | "Method docs", | ||
796 | ), | ||
797 | }, | ||
798 | CompletionItem { | ||
799 | label: "foo", | ||
800 | source_range: 94..94, | ||
801 | delete: 94..94, | ||
802 | insert: "foo", | ||
803 | kind: Field, | ||
804 | detail: "{unknown}", | ||
805 | documentation: Documentation( | ||
806 | "Field docs", | ||
807 | ), | ||
808 | }, | ||
809 | ] | ||
810 | "#]], | ||
811 | ); | ||
812 | |||
813 | check( | ||
814 | r#" | ||
815 | use self::my<|>; | ||
816 | |||
817 | /// mod docs | ||
818 | mod my { } | ||
819 | |||
820 | /// enum docs | ||
821 | enum E { | ||
822 | /// variant docs | ||
823 | V | ||
824 | } | ||
825 | use self::E::*; | ||
826 | "#, | ||
827 | expect![[r#" | ||
828 | [ | ||
829 | CompletionItem { | ||
830 | label: "E", | ||
831 | source_range: 10..12, | ||
832 | delete: 10..12, | ||
833 | insert: "E", | ||
834 | kind: Enum, | ||
835 | documentation: Documentation( | ||
836 | "enum docs", | ||
837 | ), | ||
838 | }, | ||
839 | CompletionItem { | ||
840 | label: "V", | ||
841 | source_range: 10..12, | ||
842 | delete: 10..12, | ||
843 | insert: "V", | ||
844 | kind: EnumVariant, | ||
845 | detail: "()", | ||
846 | documentation: Documentation( | ||
847 | "variant docs", | ||
848 | ), | ||
849 | }, | ||
850 | CompletionItem { | ||
851 | label: "my", | ||
852 | source_range: 10..12, | ||
853 | delete: 10..12, | ||
854 | insert: "my", | ||
855 | kind: Module, | ||
856 | documentation: Documentation( | ||
857 | "mod docs", | ||
858 | ), | ||
859 | }, | ||
860 | ] | ||
861 | "#]], | ||
862 | ) | ||
863 | } | ||
864 | |||
865 | #[test] | ||
866 | fn dont_render_attrs() { | ||
867 | check( | ||
868 | r#" | ||
869 | struct S; | ||
870 | impl S { | ||
871 | #[inline] | ||
872 | fn the_method(&self) { } | ||
873 | } | ||
874 | fn foo(s: S) { s.<|> } | ||
875 | "#, | ||
876 | expect![[r#" | ||
877 | [ | ||
878 | CompletionItem { | ||
879 | label: "the_method()", | ||
880 | source_range: 81..81, | ||
881 | delete: 81..81, | ||
882 | insert: "the_method()$0", | ||
883 | kind: Method, | ||
884 | lookup: "the_method", | ||
885 | detail: "fn the_method(&self)", | ||
886 | }, | ||
887 | ] | ||
888 | "#]], | ||
889 | ) | ||
890 | } | ||
891 | |||
892 | #[test] | ||
893 | fn inserts_parens_for_function_calls() { | ||
894 | mark::check!(inserts_parens_for_function_calls); | ||
895 | check_edit( | ||
896 | "no_args", | ||
897 | r#" | ||
898 | fn no_args() {} | ||
899 | fn main() { no_<|> } | ||
900 | "#, | ||
901 | r#" | ||
902 | fn no_args() {} | ||
903 | fn main() { no_args()$0 } | ||
904 | "#, | ||
905 | ); | ||
906 | |||
907 | check_edit( | ||
908 | "with_args", | ||
909 | r#" | ||
910 | fn with_args(x: i32, y: String) {} | ||
911 | fn main() { with_<|> } | ||
912 | "#, | ||
913 | r#" | ||
914 | fn with_args(x: i32, y: String) {} | ||
915 | fn main() { with_args(${1:x}, ${2:y})$0 } | ||
916 | "#, | ||
917 | ); | ||
918 | |||
919 | check_edit( | ||
920 | "foo", | ||
921 | r#" | ||
922 | struct S; | ||
923 | impl S { | ||
924 | fn foo(&self) {} | ||
925 | } | ||
926 | fn bar(s: &S) { s.f<|> } | ||
927 | "#, | ||
928 | r#" | ||
929 | struct S; | ||
930 | impl S { | ||
931 | fn foo(&self) {} | ||
932 | } | ||
933 | fn bar(s: &S) { s.foo()$0 } | ||
934 | "#, | ||
935 | ); | ||
936 | |||
937 | check_edit( | ||
938 | "foo", | ||
939 | r#" | ||
940 | struct S {} | ||
941 | impl S { | ||
942 | fn foo(&self, x: i32) {} | ||
943 | } | ||
944 | fn bar(s: &S) { | ||
945 | s.f<|> | ||
946 | } | ||
947 | "#, | ||
948 | r#" | ||
949 | struct S {} | ||
950 | impl S { | ||
951 | fn foo(&self, x: i32) {} | ||
952 | } | ||
953 | fn bar(s: &S) { | ||
954 | s.foo(${1:x})$0 | ||
955 | } | ||
956 | "#, | ||
957 | ); | ||
958 | } | ||
959 | |||
960 | #[test] | ||
961 | fn suppress_arg_snippets() { | ||
962 | mark::check!(suppress_arg_snippets); | ||
963 | check_edit_with_config( | ||
964 | CompletionConfig { add_call_argument_snippets: false, ..CompletionConfig::default() }, | ||
965 | "with_args", | ||
966 | r#" | ||
967 | fn with_args(x: i32, y: String) {} | ||
968 | fn main() { with_<|> } | ||
969 | "#, | ||
970 | r#" | ||
971 | fn with_args(x: i32, y: String) {} | ||
972 | fn main() { with_args($0) } | ||
973 | "#, | ||
974 | ); | ||
975 | } | ||
976 | |||
977 | #[test] | ||
978 | fn strips_underscores_from_args() { | ||
979 | check_edit( | ||
980 | "foo", | ||
981 | r#" | ||
982 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
983 | fn main() { f<|> } | ||
984 | "#, | ||
985 | r#" | ||
986 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
987 | fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 } | ||
988 | "#, | ||
989 | ); | ||
990 | } | ||
991 | |||
992 | #[test] | ||
993 | fn insert_ref_when_matching_local_in_scope() { | ||
994 | check_edit( | ||
995 | "ref_arg", | ||
996 | r#" | ||
997 | struct Foo {} | ||
998 | fn ref_arg(x: &Foo) {} | ||
999 | fn main() { | ||
1000 | let x = Foo {}; | ||
1001 | ref_ar<|> | ||
1002 | } | ||
1003 | "#, | ||
1004 | r#" | ||
1005 | struct Foo {} | ||
1006 | fn ref_arg(x: &Foo) {} | ||
1007 | fn main() { | ||
1008 | let x = Foo {}; | ||
1009 | ref_arg(${1:&x})$0 | ||
1010 | } | ||
1011 | "#, | ||
1012 | ); | ||
1013 | } | ||
1014 | |||
1015 | #[test] | ||
1016 | fn insert_mut_ref_when_matching_local_in_scope() { | ||
1017 | check_edit( | ||
1018 | "ref_arg", | ||
1019 | r#" | ||
1020 | struct Foo {} | ||
1021 | fn ref_arg(x: &mut Foo) {} | ||
1022 | fn main() { | ||
1023 | let x = Foo {}; | ||
1024 | ref_ar<|> | ||
1025 | } | ||
1026 | "#, | ||
1027 | r#" | ||
1028 | struct Foo {} | ||
1029 | fn ref_arg(x: &mut Foo) {} | ||
1030 | fn main() { | ||
1031 | let x = Foo {}; | ||
1032 | ref_arg(${1:&mut x})$0 | ||
1033 | } | ||
1034 | "#, | ||
1035 | ); | ||
1036 | } | ||
1037 | |||
1038 | #[test] | ||
1039 | fn insert_ref_when_matching_local_in_scope_for_method() { | ||
1040 | check_edit( | ||
1041 | "apply_foo", | ||
1042 | r#" | ||
1043 | struct Foo {} | ||
1044 | struct Bar {} | ||
1045 | impl Bar { | ||
1046 | fn apply_foo(&self, x: &Foo) {} | ||
1047 | } | ||
1048 | |||
1049 | fn main() { | ||
1050 | let x = Foo {}; | ||
1051 | let y = Bar {}; | ||
1052 | y.<|> | ||
1053 | } | ||
1054 | "#, | ||
1055 | r#" | ||
1056 | struct Foo {} | ||
1057 | struct Bar {} | ||
1058 | impl Bar { | ||
1059 | fn apply_foo(&self, x: &Foo) {} | ||
1060 | } | ||
1061 | |||
1062 | fn main() { | ||
1063 | let x = Foo {}; | ||
1064 | let y = Bar {}; | ||
1065 | y.apply_foo(${1:&x})$0 | ||
1066 | } | ||
1067 | "#, | ||
1068 | ); | ||
1069 | } | ||
1070 | |||
1071 | #[test] | ||
1072 | fn trim_mut_keyword_in_func_completion() { | ||
1073 | check_edit( | ||
1074 | "take_mutably", | ||
1075 | r#" | ||
1076 | fn take_mutably(mut x: &i32) {} | ||
1077 | |||
1078 | fn main() { | ||
1079 | take_m<|> | ||
1080 | } | ||
1081 | "#, | ||
1082 | r#" | ||
1083 | fn take_mutably(mut x: &i32) {} | ||
1084 | |||
1085 | fn main() { | ||
1086 | take_mutably(${1:x})$0 | ||
1087 | } | ||
1088 | "#, | ||
1089 | ); | ||
1090 | } | ||
1091 | |||
1092 | #[test] | ||
1093 | fn inserts_parens_for_tuple_enums() { | ||
1094 | mark::check!(inserts_parens_for_tuple_enums); | ||
1095 | check_edit( | ||
1096 | "Some", | ||
1097 | r#" | ||
1098 | enum Option<T> { Some(T), None } | ||
1099 | use Option::*; | ||
1100 | fn main() -> Option<i32> { | ||
1101 | Som<|> | ||
1102 | } | ||
1103 | "#, | ||
1104 | r#" | ||
1105 | enum Option<T> { Some(T), None } | ||
1106 | use Option::*; | ||
1107 | fn main() -> Option<i32> { | ||
1108 | Some($0) | ||
1109 | } | ||
1110 | "#, | ||
1111 | ); | ||
1112 | check_edit( | ||
1113 | "Some", | ||
1114 | r#" | ||
1115 | enum Option<T> { Some(T), None } | ||
1116 | use Option::*; | ||
1117 | fn main(value: Option<i32>) { | ||
1118 | match value { | ||
1119 | Som<|> | ||
1120 | } | ||
1121 | } | ||
1122 | "#, | ||
1123 | r#" | ||
1124 | enum Option<T> { Some(T), None } | ||
1125 | use Option::*; | ||
1126 | fn main(value: Option<i32>) { | ||
1127 | match value { | ||
1128 | Some($0) | ||
1129 | } | ||
1130 | } | ||
1131 | "#, | ||
1132 | ); | ||
1133 | } | ||
1134 | |||
1135 | #[test] | ||
1136 | fn dont_duplicate_pattern_parens() { | ||
1137 | mark::check!(dont_duplicate_pattern_parens); | ||
1138 | check_edit( | ||
1139 | "Var", | ||
1140 | r#" | ||
1141 | enum E { Var(i32) } | ||
1142 | fn main() { | ||
1143 | match E::Var(92) { | ||
1144 | E::<|>(92) => (), | ||
1145 | } | ||
1146 | } | ||
1147 | "#, | ||
1148 | r#" | ||
1149 | enum E { Var(i32) } | ||
1150 | fn main() { | ||
1151 | match E::Var(92) { | ||
1152 | E::Var(92) => (), | ||
1153 | } | ||
1154 | } | ||
1155 | "#, | ||
1156 | ); | ||
1157 | } | ||
1158 | |||
1159 | #[test] | ||
1160 | fn no_call_parens_if_fn_ptr_needed() { | ||
1161 | mark::check!(no_call_parens_if_fn_ptr_needed); | ||
1162 | check_edit( | ||
1163 | "foo", | ||
1164 | r#" | ||
1165 | fn foo(foo: u8, bar: u8) {} | ||
1166 | struct ManualVtable { f: fn(u8, u8) } | ||
1167 | |||
1168 | fn main() -> ManualVtable { | ||
1169 | ManualVtable { f: f<|> } | ||
1170 | } | ||
1171 | "#, | ||
1172 | r#" | ||
1173 | fn foo(foo: u8, bar: u8) {} | ||
1174 | struct ManualVtable { f: fn(u8, u8) } | ||
1175 | |||
1176 | fn main() -> ManualVtable { | ||
1177 | ManualVtable { f: foo } | ||
1178 | } | ||
1179 | "#, | ||
1180 | ); | ||
1181 | } | ||
1182 | |||
1183 | #[test] | ||
1184 | fn no_parens_in_use_item() { | ||
1185 | mark::check!(no_parens_in_use_item); | ||
1186 | check_edit( | ||
1187 | "foo", | ||
1188 | r#" | ||
1189 | mod m { pub fn foo() {} } | ||
1190 | use crate::m::f<|>; | ||
1191 | "#, | ||
1192 | r#" | ||
1193 | mod m { pub fn foo() {} } | ||
1194 | use crate::m::foo; | ||
1195 | "#, | ||
1196 | ); | ||
1197 | } | ||
1198 | |||
1199 | #[test] | ||
1200 | fn no_parens_in_call() { | ||
1201 | check_edit( | ||
1202 | "foo", | ||
1203 | r#" | ||
1204 | fn foo(x: i32) {} | ||
1205 | fn main() { f<|>(); } | ||
1206 | "#, | ||
1207 | r#" | ||
1208 | fn foo(x: i32) {} | ||
1209 | fn main() { foo(); } | ||
1210 | "#, | ||
1211 | ); | ||
1212 | check_edit( | ||
1213 | "foo", | ||
1214 | r#" | ||
1215 | struct Foo; | ||
1216 | impl Foo { fn foo(&self){} } | ||
1217 | fn f(foo: &Foo) { foo.f<|>(); } | ||
1218 | "#, | ||
1219 | r#" | ||
1220 | struct Foo; | ||
1221 | impl Foo { fn foo(&self){} } | ||
1222 | fn f(foo: &Foo) { foo.foo(); } | ||
1223 | "#, | ||
1224 | ); | ||
1225 | } | ||
1226 | |||
1227 | #[test] | ||
1228 | fn inserts_angle_brackets_for_generics() { | ||
1229 | mark::check!(inserts_angle_brackets_for_generics); | ||
1230 | check_edit( | ||
1231 | "Vec", | ||
1232 | r#" | ||
1233 | struct Vec<T> {} | ||
1234 | fn foo(xs: Ve<|>) | ||
1235 | "#, | ||
1236 | r#" | ||
1237 | struct Vec<T> {} | ||
1238 | fn foo(xs: Vec<$0>) | ||
1239 | "#, | ||
1240 | ); | ||
1241 | check_edit( | ||
1242 | "Vec", | ||
1243 | r#" | ||
1244 | type Vec<T> = (T,); | ||
1245 | fn foo(xs: Ve<|>) | ||
1246 | "#, | ||
1247 | r#" | ||
1248 | type Vec<T> = (T,); | ||
1249 | fn foo(xs: Vec<$0>) | ||
1250 | "#, | ||
1251 | ); | ||
1252 | check_edit( | ||
1253 | "Vec", | ||
1254 | r#" | ||
1255 | struct Vec<T = i128> {} | ||
1256 | fn foo(xs: Ve<|>) | ||
1257 | "#, | ||
1258 | r#" | ||
1259 | struct Vec<T = i128> {} | ||
1260 | fn foo(xs: Vec) | ||
1261 | "#, | ||
1262 | ); | ||
1263 | check_edit( | ||
1264 | "Vec", | ||
1265 | r#" | ||
1266 | struct Vec<T> {} | ||
1267 | fn foo(xs: Ve<|><i128>) | ||
1268 | "#, | ||
1269 | r#" | ||
1270 | struct Vec<T> {} | ||
1271 | fn foo(xs: Vec<i128>) | ||
1272 | "#, | ||
1273 | ); | ||
1274 | } | ||
1275 | |||
1276 | #[test] | ||
1277 | fn dont_insert_macro_call_parens_unncessary() { | ||
1278 | mark::check!(dont_insert_macro_call_parens_unncessary); | ||
1279 | check_edit( | ||
1280 | "frobnicate!", | ||
1281 | r#" | ||
1282 | //- /main.rs crate:main deps:foo | ||
1283 | use foo::<|>; | ||
1284 | //- /foo/lib.rs crate:foo | ||
1285 | #[macro_export] | ||
1286 | macro_rules frobnicate { () => () } | ||
1287 | "#, | ||
1288 | r#" | ||
1289 | use foo::frobnicate; | ||
1290 | "#, | ||
1291 | ); | ||
1292 | |||
1293 | check_edit( | ||
1294 | "frobnicate!", | ||
1295 | r#" | ||
1296 | macro_rules frobnicate { () => () } | ||
1297 | fn main() { frob<|>!(); } | ||
1298 | "#, | ||
1299 | r#" | ||
1300 | macro_rules frobnicate { () => () } | ||
1301 | fn main() { frobnicate!(); } | ||
1302 | "#, | ||
1303 | ); | ||
1304 | } | ||
1305 | |||
1306 | #[test] | ||
1307 | fn active_param_score() { | ||
1308 | mark::check!(active_param_type_match); | ||
1309 | check_scores( | ||
1310 | r#" | ||
1311 | struct S { foo: i64, bar: u32, baz: u32 } | ||
1312 | fn test(bar: u32) { } | ||
1313 | fn foo(s: S) { test(s.<|>) } | ||
1314 | "#, | ||
1315 | expect![[r#" | ||
1316 | fd bar [type+name] | ||
1317 | fd baz [type] | ||
1318 | fd foo [] | ||
1319 | "#]], | ||
1320 | ); | ||
1321 | } | ||
1322 | |||
1323 | #[test] | ||
1324 | fn record_field_scores() { | ||
1325 | mark::check!(record_field_type_match); | ||
1326 | check_scores( | ||
1327 | r#" | ||
1328 | struct A { foo: i64, bar: u32, baz: u32 } | ||
1329 | struct B { x: (), y: f32, bar: u32 } | ||
1330 | fn foo(a: A) { B { bar: a.<|> }; } | ||
1331 | "#, | ||
1332 | expect![[r#" | ||
1333 | fd bar [type+name] | ||
1334 | fd baz [type] | ||
1335 | fd foo [] | ||
1336 | "#]], | ||
1337 | ) | ||
1338 | } | ||
1339 | |||
1340 | #[test] | ||
1341 | fn record_field_and_call_scores() { | ||
1342 | check_scores( | ||
1343 | r#" | ||
1344 | struct A { foo: i64, bar: u32, baz: u32 } | ||
1345 | struct B { x: (), y: f32, bar: u32 } | ||
1346 | fn f(foo: i64) { } | ||
1347 | fn foo(a: A) { B { bar: f(a.<|>) }; } | ||
1348 | "#, | ||
1349 | expect![[r#" | ||
1350 | fd foo [type+name] | ||
1351 | fd bar [] | ||
1352 | fd baz [] | ||
1353 | "#]], | ||
1354 | ); | ||
1355 | check_scores( | ||
1356 | r#" | ||
1357 | struct A { foo: i64, bar: u32, baz: u32 } | ||
1358 | struct B { x: (), y: f32, bar: u32 } | ||
1359 | fn f(foo: i64) { } | ||
1360 | fn foo(a: A) { f(B { bar: a.<|> }); } | ||
1361 | "#, | ||
1362 | expect![[r#" | ||
1363 | fd bar [type+name] | ||
1364 | fd baz [type] | ||
1365 | fd foo [] | ||
1366 | "#]], | ||
1367 | ); | ||
1368 | } | ||
1369 | |||
1370 | #[test] | ||
1371 | fn prioritize_exact_ref_match() { | ||
1372 | check_scores( | ||
1373 | r#" | ||
1374 | struct WorldSnapshot { _f: () }; | ||
1375 | fn go(world: &WorldSnapshot) { go(w<|>) } | ||
1376 | "#, | ||
1377 | expect![[r#" | ||
1378 | bn world [type+name] | ||
1379 | st WorldSnapshot [] | ||
1380 | fn go(…) [] | ||
1381 | "#]], | ||
1382 | ); | ||
1383 | } | ||
1384 | |||
1385 | #[test] | ||
1386 | fn too_many_arguments() { | ||
1387 | check_scores( | ||
1388 | r#" | ||
1389 | struct Foo; | ||
1390 | fn f(foo: &Foo) { f(foo, w<|>) } | ||
1391 | "#, | ||
1392 | expect![[r#" | ||
1393 | st Foo [] | ||
1394 | fn f(…) [] | ||
1395 | bn foo [] | ||
1396 | "#]], | ||
1397 | ); | ||
1398 | } | ||
1399 | |||
1400 | #[test] | ||
1401 | fn guesses_macro_braces() { | ||
1402 | check_edit( | ||
1403 | "vec!", | ||
1404 | r#" | ||
1405 | /// Creates a [`Vec`] containing the arguments. | ||
1406 | /// | ||
1407 | /// ``` | ||
1408 | /// let v = vec![1, 2, 3]; | ||
1409 | /// assert_eq!(v[0], 1); | ||
1410 | /// assert_eq!(v[1], 2); | ||
1411 | /// assert_eq!(v[2], 3); | ||
1412 | /// ``` | ||
1413 | macro_rules! vec { () => {} } | ||
1414 | |||
1415 | fn fn main() { v<|> } | ||
1416 | "#, | ||
1417 | r#" | ||
1418 | /// Creates a [`Vec`] containing the arguments. | ||
1419 | /// | ||
1420 | /// ``` | ||
1421 | /// let v = vec![1, 2, 3]; | ||
1422 | /// assert_eq!(v[0], 1); | ||
1423 | /// assert_eq!(v[1], 2); | ||
1424 | /// assert_eq!(v[2], 3); | ||
1425 | /// ``` | ||
1426 | macro_rules! vec { () => {} } | ||
1427 | |||
1428 | fn fn main() { vec![$0] } | ||
1429 | "#, | ||
1430 | ); | ||
1431 | |||
1432 | check_edit( | ||
1433 | "foo!", | ||
1434 | r#" | ||
1435 | /// Foo | ||
1436 | /// | ||
1437 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
1438 | /// call as `let _=foo! { hello world };` | ||
1439 | macro_rules! foo { () => {} } | ||
1440 | fn main() { <|> } | ||
1441 | "#, | ||
1442 | r#" | ||
1443 | /// Foo | ||
1444 | /// | ||
1445 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
1446 | /// call as `let _=foo! { hello world };` | ||
1447 | macro_rules! foo { () => {} } | ||
1448 | fn main() { foo! {$0} } | ||
1449 | "#, | ||
1450 | ) | ||
1451 | } | 138 | } |
1452 | } | 139 | } |
diff --git a/crates/completion/src/context.rs b/crates/completion/src/context.rs index dca304a8f..bf70ee478 100644 --- a/crates/completion/src/context.rs +++ b/crates/completion/src/context.rs | |||
@@ -245,19 +245,6 @@ impl<'a> CompletionContext<'a> { | |||
245 | } | 245 | } |
246 | } | 246 | } |
247 | 247 | ||
248 | pub(crate) fn active_name_and_type(&self) -> Option<(String, Type)> { | ||
249 | if let Some(record_field) = &self.record_field_syntax { | ||
250 | mark::hit!(record_field_type_match); | ||
251 | let (struct_field, _local) = self.sema.resolve_record_field(record_field)?; | ||
252 | Some((struct_field.name(self.db).to_string(), struct_field.signature_ty(self.db))) | ||
253 | } else if let Some(active_parameter) = &self.active_parameter { | ||
254 | mark::hit!(active_param_type_match); | ||
255 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | ||
256 | } else { | ||
257 | None | ||
258 | } | ||
259 | } | ||
260 | |||
261 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | 248 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { |
262 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | 249 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); |
263 | let syntax_element = NodeOrToken::Token(fake_ident_token); | 250 | let syntax_element = NodeOrToken::Token(fake_ident_token); |
diff --git a/crates/completion/src/lib.rs b/crates/completion/src/lib.rs index 89c0a9978..cb6e0554e 100644 --- a/crates/completion/src/lib.rs +++ b/crates/completion/src/lib.rs | |||
@@ -7,6 +7,7 @@ mod patterns; | |||
7 | mod generated_lint_completions; | 7 | mod generated_lint_completions; |
8 | #[cfg(test)] | 8 | #[cfg(test)] |
9 | mod test_utils; | 9 | mod test_utils; |
10 | mod render; | ||
10 | 11 | ||
11 | mod completions; | 12 | mod completions; |
12 | 13 | ||
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs new file mode 100644 index 000000000..1fa02c375 --- /dev/null +++ b/crates/completion/src/render.rs | |||
@@ -0,0 +1,848 @@ | |||
1 | //! `render` module provides utilities for rendering completion suggestions | ||
2 | //! into code pieces that will be presented to user. | ||
3 | |||
4 | pub(crate) mod macro_; | ||
5 | pub(crate) mod function; | ||
6 | pub(crate) mod enum_variant; | ||
7 | pub(crate) mod const_; | ||
8 | pub(crate) mod type_alias; | ||
9 | |||
10 | mod builder_ext; | ||
11 | |||
12 | use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type}; | ||
13 | use ide_db::RootDatabase; | ||
14 | use syntax::TextRange; | ||
15 | use test_utils::mark; | ||
16 | |||
17 | use crate::{ | ||
18 | config::SnippetCap, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, | ||
19 | CompletionScore, | ||
20 | }; | ||
21 | |||
22 | use crate::render::{enum_variant::render_enum_variant, function::render_fn, macro_::render_macro}; | ||
23 | |||
24 | pub(crate) fn render_field<'a>( | ||
25 | ctx: RenderContext<'a>, | ||
26 | field: hir::Field, | ||
27 | ty: &Type, | ||
28 | ) -> CompletionItem { | ||
29 | Render::new(ctx).add_field(field, ty) | ||
30 | } | ||
31 | |||
32 | pub(crate) fn render_tuple_field<'a>( | ||
33 | ctx: RenderContext<'a>, | ||
34 | field: usize, | ||
35 | ty: &Type, | ||
36 | ) -> CompletionItem { | ||
37 | Render::new(ctx).add_tuple_field(field, ty) | ||
38 | } | ||
39 | |||
40 | pub(crate) fn render_resolution<'a>( | ||
41 | ctx: RenderContext<'a>, | ||
42 | local_name: String, | ||
43 | resolution: &ScopeDef, | ||
44 | ) -> Option<CompletionItem> { | ||
45 | Render::new(ctx).render_resolution(local_name, resolution) | ||
46 | } | ||
47 | |||
48 | /// Interface for data and methods required for items rendering. | ||
49 | #[derive(Debug)] | ||
50 | pub(crate) struct RenderContext<'a> { | ||
51 | completion: &'a CompletionContext<'a>, | ||
52 | } | ||
53 | |||
54 | impl<'a> RenderContext<'a> { | ||
55 | pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { | ||
56 | RenderContext { completion } | ||
57 | } | ||
58 | |||
59 | fn snippet_cap(&self) -> Option<SnippetCap> { | ||
60 | self.completion.config.snippet_cap.clone() | ||
61 | } | ||
62 | |||
63 | fn db(&self) -> &'a RootDatabase { | ||
64 | &self.completion.db | ||
65 | } | ||
66 | |||
67 | fn source_range(&self) -> TextRange { | ||
68 | self.completion.source_range() | ||
69 | } | ||
70 | |||
71 | fn is_deprecated(&self, node: impl HasAttrs) -> bool { | ||
72 | node.attrs(self.db()).by_key("deprecated").exists() | ||
73 | } | ||
74 | |||
75 | fn docs(&self, node: impl HasAttrs) -> Option<Documentation> { | ||
76 | node.docs(self.db()) | ||
77 | } | ||
78 | |||
79 | fn active_name_and_type(&self) -> Option<(String, Type)> { | ||
80 | if let Some(record_field) = &self.completion.record_field_syntax { | ||
81 | mark::hit!(record_field_type_match); | ||
82 | let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?; | ||
83 | Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db()))) | ||
84 | } else if let Some(active_parameter) = &self.completion.active_parameter { | ||
85 | mark::hit!(active_param_type_match); | ||
86 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | ||
87 | } else { | ||
88 | None | ||
89 | } | ||
90 | } | ||
91 | } | ||
92 | |||
93 | /// Generic renderer for completion items. | ||
94 | #[derive(Debug)] | ||
95 | struct Render<'a> { | ||
96 | ctx: RenderContext<'a>, | ||
97 | } | ||
98 | |||
99 | impl<'a> Render<'a> { | ||
100 | fn new(ctx: RenderContext<'a>) -> Render<'a> { | ||
101 | Render { ctx } | ||
102 | } | ||
103 | |||
104 | fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem { | ||
105 | let is_deprecated = self.ctx.is_deprecated(field); | ||
106 | let name = field.name(self.ctx.db()); | ||
107 | let mut item = CompletionItem::new( | ||
108 | CompletionKind::Reference, | ||
109 | self.ctx.source_range(), | ||
110 | name.to_string(), | ||
111 | ) | ||
112 | .kind(CompletionItemKind::Field) | ||
113 | .detail(ty.display(self.ctx.db()).to_string()) | ||
114 | .set_documentation(field.docs(self.ctx.db())) | ||
115 | .set_deprecated(is_deprecated); | ||
116 | |||
117 | if let Some(score) = compute_score(&self.ctx, &ty, &name.to_string()) { | ||
118 | item = item.set_score(score); | ||
119 | } | ||
120 | |||
121 | item.build() | ||
122 | } | ||
123 | |||
124 | fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { | ||
125 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), field.to_string()) | ||
126 | .kind(CompletionItemKind::Field) | ||
127 | .detail(ty.display(self.ctx.db()).to_string()) | ||
128 | .build() | ||
129 | } | ||
130 | |||
131 | fn render_resolution( | ||
132 | self, | ||
133 | local_name: String, | ||
134 | resolution: &ScopeDef, | ||
135 | ) -> Option<CompletionItem> { | ||
136 | use hir::ModuleDef::*; | ||
137 | |||
138 | let completion_kind = match resolution { | ||
139 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, | ||
140 | _ => CompletionKind::Reference, | ||
141 | }; | ||
142 | |||
143 | let kind = match resolution { | ||
144 | ScopeDef::ModuleDef(Function(func)) => { | ||
145 | let item = render_fn(self.ctx, Some(local_name), *func); | ||
146 | return Some(item); | ||
147 | } | ||
148 | ScopeDef::ModuleDef(EnumVariant(var)) => { | ||
149 | let item = render_enum_variant(self.ctx, Some(local_name), *var, None); | ||
150 | return Some(item); | ||
151 | } | ||
152 | ScopeDef::MacroDef(mac) => { | ||
153 | let item = render_macro(self.ctx, local_name, *mac); | ||
154 | return item; | ||
155 | } | ||
156 | |||
157 | ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, | ||
158 | ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, | ||
159 | // FIXME: add CompletionItemKind::Union | ||
160 | ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, | ||
161 | ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, | ||
162 | ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, | ||
163 | ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, | ||
164 | ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, | ||
165 | ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, | ||
166 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, | ||
167 | ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, | ||
168 | ScopeDef::Local(..) => CompletionItemKind::Binding, | ||
169 | // (does this need its own kind?) | ||
170 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, | ||
171 | ScopeDef::Unknown => { | ||
172 | let item = CompletionItem::new( | ||
173 | CompletionKind::Reference, | ||
174 | self.ctx.source_range(), | ||
175 | local_name, | ||
176 | ) | ||
177 | .kind(CompletionItemKind::UnresolvedReference) | ||
178 | .build(); | ||
179 | return Some(item); | ||
180 | } | ||
181 | }; | ||
182 | |||
183 | let docs = self.docs(resolution); | ||
184 | |||
185 | let mut item = | ||
186 | CompletionItem::new(completion_kind, self.ctx.source_range(), local_name.clone()); | ||
187 | if let ScopeDef::Local(local) = resolution { | ||
188 | let ty = local.ty(self.ctx.db()); | ||
189 | if !ty.is_unknown() { | ||
190 | item = item.detail(ty.display(self.ctx.db()).to_string()); | ||
191 | } | ||
192 | }; | ||
193 | |||
194 | let mut ref_match = None; | ||
195 | if let ScopeDef::Local(local) = resolution { | ||
196 | if let Some((active_name, active_type)) = self.ctx.active_name_and_type() { | ||
197 | let ty = local.ty(self.ctx.db()); | ||
198 | if let Some(score) = | ||
199 | compute_score_from_active(&active_type, &active_name, &ty, &local_name) | ||
200 | { | ||
201 | item = item.set_score(score); | ||
202 | } | ||
203 | ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name); | ||
204 | } | ||
205 | } | ||
206 | |||
207 | // Add `<>` for generic types | ||
208 | if self.ctx.completion.is_path_type | ||
209 | && !self.ctx.completion.has_type_args | ||
210 | && self.ctx.completion.config.add_call_parenthesis | ||
211 | { | ||
212 | if let Some(cap) = self.ctx.snippet_cap() { | ||
213 | let has_non_default_type_params = match resolution { | ||
214 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(self.ctx.db()), | ||
215 | ScopeDef::ModuleDef(TypeAlias(it)) => { | ||
216 | it.has_non_default_type_params(self.ctx.db()) | ||
217 | } | ||
218 | _ => false, | ||
219 | }; | ||
220 | if has_non_default_type_params { | ||
221 | mark::hit!(inserts_angle_brackets_for_generics); | ||
222 | item = item | ||
223 | .lookup_by(local_name.clone()) | ||
224 | .label(format!("{}<…>", local_name)) | ||
225 | .insert_snippet(cap, format!("{}<$0>", local_name)); | ||
226 | } | ||
227 | } | ||
228 | } | ||
229 | |||
230 | let item = item.kind(kind).set_documentation(docs).set_ref_match(ref_match).build(); | ||
231 | Some(item) | ||
232 | } | ||
233 | |||
234 | fn docs(&self, resolution: &ScopeDef) -> Option<Documentation> { | ||
235 | use hir::ModuleDef::*; | ||
236 | match resolution { | ||
237 | ScopeDef::ModuleDef(Module(it)) => it.docs(self.ctx.db()), | ||
238 | ScopeDef::ModuleDef(Adt(it)) => it.docs(self.ctx.db()), | ||
239 | ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(self.ctx.db()), | ||
240 | ScopeDef::ModuleDef(Const(it)) => it.docs(self.ctx.db()), | ||
241 | ScopeDef::ModuleDef(Static(it)) => it.docs(self.ctx.db()), | ||
242 | ScopeDef::ModuleDef(Trait(it)) => it.docs(self.ctx.db()), | ||
243 | ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(self.ctx.db()), | ||
244 | _ => None, | ||
245 | } | ||
246 | } | ||
247 | } | ||
248 | |||
249 | fn compute_score_from_active( | ||
250 | active_type: &Type, | ||
251 | active_name: &str, | ||
252 | ty: &Type, | ||
253 | name: &str, | ||
254 | ) -> Option<CompletionScore> { | ||
255 | // Compute score | ||
256 | // For the same type | ||
257 | if active_type != ty { | ||
258 | return None; | ||
259 | } | ||
260 | |||
261 | let mut res = CompletionScore::TypeMatch; | ||
262 | |||
263 | // If same type + same name then go top position | ||
264 | if active_name == name { | ||
265 | res = CompletionScore::TypeAndNameMatch | ||
266 | } | ||
267 | |||
268 | Some(res) | ||
269 | } | ||
270 | fn refed_type_matches( | ||
271 | active_type: &Type, | ||
272 | active_name: &str, | ||
273 | ty: &Type, | ||
274 | name: &str, | ||
275 | ) -> Option<(Mutability, CompletionScore)> { | ||
276 | let derefed_active = active_type.remove_ref()?; | ||
277 | let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?; | ||
278 | Some(( | ||
279 | if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared }, | ||
280 | score, | ||
281 | )) | ||
282 | } | ||
283 | |||
284 | fn compute_score(ctx: &RenderContext, ty: &Type, name: &str) -> Option<CompletionScore> { | ||
285 | let (active_name, active_type) = ctx.active_name_and_type()?; | ||
286 | compute_score_from_active(&active_type, &active_name, ty, name) | ||
287 | } | ||
288 | |||
289 | #[cfg(test)] | ||
290 | mod tests { | ||
291 | use std::cmp::Reverse; | ||
292 | |||
293 | use expect_test::{expect, Expect}; | ||
294 | use test_utils::mark; | ||
295 | |||
296 | use crate::{ | ||
297 | test_utils::{check_edit, do_completion, get_all_items}, | ||
298 | CompletionConfig, CompletionKind, CompletionScore, | ||
299 | }; | ||
300 | |||
301 | fn check(ra_fixture: &str, expect: Expect) { | ||
302 | let actual = do_completion(ra_fixture, CompletionKind::Reference); | ||
303 | expect.assert_debug_eq(&actual); | ||
304 | } | ||
305 | |||
306 | fn check_scores(ra_fixture: &str, expect: Expect) { | ||
307 | fn display_score(score: Option<CompletionScore>) -> &'static str { | ||
308 | match score { | ||
309 | Some(CompletionScore::TypeMatch) => "[type]", | ||
310 | Some(CompletionScore::TypeAndNameMatch) => "[type+name]", | ||
311 | None => "[]".into(), | ||
312 | } | ||
313 | } | ||
314 | |||
315 | let mut completions = get_all_items(CompletionConfig::default(), ra_fixture); | ||
316 | completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); | ||
317 | let actual = completions | ||
318 | .into_iter() | ||
319 | .filter(|it| it.completion_kind == CompletionKind::Reference) | ||
320 | .map(|it| { | ||
321 | let tag = it.kind().unwrap().tag(); | ||
322 | let score = display_score(it.score()); | ||
323 | format!("{} {} {}\n", tag, it.label(), score) | ||
324 | }) | ||
325 | .collect::<String>(); | ||
326 | expect.assert_eq(&actual); | ||
327 | } | ||
328 | |||
329 | #[test] | ||
330 | fn enum_detail_includes_record_fields() { | ||
331 | check( | ||
332 | r#" | ||
333 | enum Foo { Foo { x: i32, y: i32 } } | ||
334 | |||
335 | fn main() { Foo::Fo<|> } | ||
336 | "#, | ||
337 | expect![[r#" | ||
338 | [ | ||
339 | CompletionItem { | ||
340 | label: "Foo", | ||
341 | source_range: 54..56, | ||
342 | delete: 54..56, | ||
343 | insert: "Foo", | ||
344 | kind: EnumVariant, | ||
345 | detail: "{ x: i32, y: i32 }", | ||
346 | }, | ||
347 | ] | ||
348 | "#]], | ||
349 | ); | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | fn enum_detail_doesnt_include_tuple_fields() { | ||
354 | check( | ||
355 | r#" | ||
356 | enum Foo { Foo (i32, i32) } | ||
357 | |||
358 | fn main() { Foo::Fo<|> } | ||
359 | "#, | ||
360 | expect![[r#" | ||
361 | [ | ||
362 | CompletionItem { | ||
363 | label: "Foo(…)", | ||
364 | source_range: 46..48, | ||
365 | delete: 46..48, | ||
366 | insert: "Foo($0)", | ||
367 | kind: EnumVariant, | ||
368 | lookup: "Foo", | ||
369 | detail: "(i32, i32)", | ||
370 | trigger_call_info: true, | ||
371 | }, | ||
372 | ] | ||
373 | "#]], | ||
374 | ); | ||
375 | } | ||
376 | |||
377 | #[test] | ||
378 | fn enum_detail_just_parentheses_for_unit() { | ||
379 | check( | ||
380 | r#" | ||
381 | enum Foo { Foo } | ||
382 | |||
383 | fn main() { Foo::Fo<|> } | ||
384 | "#, | ||
385 | expect![[r#" | ||
386 | [ | ||
387 | CompletionItem { | ||
388 | label: "Foo", | ||
389 | source_range: 35..37, | ||
390 | delete: 35..37, | ||
391 | insert: "Foo", | ||
392 | kind: EnumVariant, | ||
393 | detail: "()", | ||
394 | }, | ||
395 | ] | ||
396 | "#]], | ||
397 | ); | ||
398 | } | ||
399 | |||
400 | #[test] | ||
401 | fn lookup_enums_by_two_qualifiers() { | ||
402 | check( | ||
403 | r#" | ||
404 | mod m { | ||
405 | pub enum Spam { Foo, Bar(i32) } | ||
406 | } | ||
407 | fn main() { let _: m::Spam = S<|> } | ||
408 | "#, | ||
409 | expect![[r#" | ||
410 | [ | ||
411 | CompletionItem { | ||
412 | label: "Spam::Bar(…)", | ||
413 | source_range: 75..76, | ||
414 | delete: 75..76, | ||
415 | insert: "Spam::Bar($0)", | ||
416 | kind: EnumVariant, | ||
417 | lookup: "Spam::Bar", | ||
418 | detail: "(i32)", | ||
419 | trigger_call_info: true, | ||
420 | }, | ||
421 | CompletionItem { | ||
422 | label: "m", | ||
423 | source_range: 75..76, | ||
424 | delete: 75..76, | ||
425 | insert: "m", | ||
426 | kind: Module, | ||
427 | }, | ||
428 | CompletionItem { | ||
429 | label: "m::Spam::Foo", | ||
430 | source_range: 75..76, | ||
431 | delete: 75..76, | ||
432 | insert: "m::Spam::Foo", | ||
433 | kind: EnumVariant, | ||
434 | lookup: "Spam::Foo", | ||
435 | detail: "()", | ||
436 | }, | ||
437 | CompletionItem { | ||
438 | label: "main()", | ||
439 | source_range: 75..76, | ||
440 | delete: 75..76, | ||
441 | insert: "main()$0", | ||
442 | kind: Function, | ||
443 | lookup: "main", | ||
444 | detail: "fn main()", | ||
445 | }, | ||
446 | ] | ||
447 | "#]], | ||
448 | ) | ||
449 | } | ||
450 | |||
451 | #[test] | ||
452 | fn sets_deprecated_flag_in_items() { | ||
453 | check( | ||
454 | r#" | ||
455 | #[deprecated] | ||
456 | fn something_deprecated() {} | ||
457 | #[deprecated(since = "1.0.0")] | ||
458 | fn something_else_deprecated() {} | ||
459 | |||
460 | fn main() { som<|> } | ||
461 | "#, | ||
462 | expect![[r#" | ||
463 | [ | ||
464 | CompletionItem { | ||
465 | label: "main()", | ||
466 | source_range: 121..124, | ||
467 | delete: 121..124, | ||
468 | insert: "main()$0", | ||
469 | kind: Function, | ||
470 | lookup: "main", | ||
471 | detail: "fn main()", | ||
472 | }, | ||
473 | CompletionItem { | ||
474 | label: "something_deprecated()", | ||
475 | source_range: 121..124, | ||
476 | delete: 121..124, | ||
477 | insert: "something_deprecated()$0", | ||
478 | kind: Function, | ||
479 | lookup: "something_deprecated", | ||
480 | detail: "fn something_deprecated()", | ||
481 | deprecated: true, | ||
482 | }, | ||
483 | CompletionItem { | ||
484 | label: "something_else_deprecated()", | ||
485 | source_range: 121..124, | ||
486 | delete: 121..124, | ||
487 | insert: "something_else_deprecated()$0", | ||
488 | kind: Function, | ||
489 | lookup: "something_else_deprecated", | ||
490 | detail: "fn something_else_deprecated()", | ||
491 | deprecated: true, | ||
492 | }, | ||
493 | ] | ||
494 | "#]], | ||
495 | ); | ||
496 | |||
497 | check( | ||
498 | r#" | ||
499 | struct A { #[deprecated] the_field: u32 } | ||
500 | fn foo() { A { the<|> } } | ||
501 | "#, | ||
502 | expect![[r#" | ||
503 | [ | ||
504 | CompletionItem { | ||
505 | label: "the_field", | ||
506 | source_range: 57..60, | ||
507 | delete: 57..60, | ||
508 | insert: "the_field", | ||
509 | kind: Field, | ||
510 | detail: "u32", | ||
511 | deprecated: true, | ||
512 | }, | ||
513 | ] | ||
514 | "#]], | ||
515 | ); | ||
516 | } | ||
517 | |||
518 | #[test] | ||
519 | fn renders_docs() { | ||
520 | check( | ||
521 | r#" | ||
522 | struct S { | ||
523 | /// Field docs | ||
524 | foo: | ||
525 | } | ||
526 | impl S { | ||
527 | /// Method docs | ||
528 | fn bar(self) { self.<|> } | ||
529 | }"#, | ||
530 | expect![[r#" | ||
531 | [ | ||
532 | CompletionItem { | ||
533 | label: "bar()", | ||
534 | source_range: 94..94, | ||
535 | delete: 94..94, | ||
536 | insert: "bar()$0", | ||
537 | kind: Method, | ||
538 | lookup: "bar", | ||
539 | detail: "fn bar(self)", | ||
540 | documentation: Documentation( | ||
541 | "Method docs", | ||
542 | ), | ||
543 | }, | ||
544 | CompletionItem { | ||
545 | label: "foo", | ||
546 | source_range: 94..94, | ||
547 | delete: 94..94, | ||
548 | insert: "foo", | ||
549 | kind: Field, | ||
550 | detail: "{unknown}", | ||
551 | documentation: Documentation( | ||
552 | "Field docs", | ||
553 | ), | ||
554 | }, | ||
555 | ] | ||
556 | "#]], | ||
557 | ); | ||
558 | |||
559 | check( | ||
560 | r#" | ||
561 | use self::my<|>; | ||
562 | |||
563 | /// mod docs | ||
564 | mod my { } | ||
565 | |||
566 | /// enum docs | ||
567 | enum E { | ||
568 | /// variant docs | ||
569 | V | ||
570 | } | ||
571 | use self::E::*; | ||
572 | "#, | ||
573 | expect![[r#" | ||
574 | [ | ||
575 | CompletionItem { | ||
576 | label: "E", | ||
577 | source_range: 10..12, | ||
578 | delete: 10..12, | ||
579 | insert: "E", | ||
580 | kind: Enum, | ||
581 | documentation: Documentation( | ||
582 | "enum docs", | ||
583 | ), | ||
584 | }, | ||
585 | CompletionItem { | ||
586 | label: "V", | ||
587 | source_range: 10..12, | ||
588 | delete: 10..12, | ||
589 | insert: "V", | ||
590 | kind: EnumVariant, | ||
591 | detail: "()", | ||
592 | documentation: Documentation( | ||
593 | "variant docs", | ||
594 | ), | ||
595 | }, | ||
596 | CompletionItem { | ||
597 | label: "my", | ||
598 | source_range: 10..12, | ||
599 | delete: 10..12, | ||
600 | insert: "my", | ||
601 | kind: Module, | ||
602 | documentation: Documentation( | ||
603 | "mod docs", | ||
604 | ), | ||
605 | }, | ||
606 | ] | ||
607 | "#]], | ||
608 | ) | ||
609 | } | ||
610 | |||
611 | #[test] | ||
612 | fn dont_render_attrs() { | ||
613 | check( | ||
614 | r#" | ||
615 | struct S; | ||
616 | impl S { | ||
617 | #[inline] | ||
618 | fn the_method(&self) { } | ||
619 | } | ||
620 | fn foo(s: S) { s.<|> } | ||
621 | "#, | ||
622 | expect![[r#" | ||
623 | [ | ||
624 | CompletionItem { | ||
625 | label: "the_method()", | ||
626 | source_range: 81..81, | ||
627 | delete: 81..81, | ||
628 | insert: "the_method()$0", | ||
629 | kind: Method, | ||
630 | lookup: "the_method", | ||
631 | detail: "fn the_method(&self)", | ||
632 | }, | ||
633 | ] | ||
634 | "#]], | ||
635 | ) | ||
636 | } | ||
637 | |||
638 | #[test] | ||
639 | fn no_call_parens_if_fn_ptr_needed() { | ||
640 | mark::check!(no_call_parens_if_fn_ptr_needed); | ||
641 | check_edit( | ||
642 | "foo", | ||
643 | r#" | ||
644 | fn foo(foo: u8, bar: u8) {} | ||
645 | struct ManualVtable { f: fn(u8, u8) } | ||
646 | |||
647 | fn main() -> ManualVtable { | ||
648 | ManualVtable { f: f<|> } | ||
649 | } | ||
650 | "#, | ||
651 | r#" | ||
652 | fn foo(foo: u8, bar: u8) {} | ||
653 | struct ManualVtable { f: fn(u8, u8) } | ||
654 | |||
655 | fn main() -> ManualVtable { | ||
656 | ManualVtable { f: foo } | ||
657 | } | ||
658 | "#, | ||
659 | ); | ||
660 | } | ||
661 | |||
662 | #[test] | ||
663 | fn no_parens_in_use_item() { | ||
664 | mark::check!(no_parens_in_use_item); | ||
665 | check_edit( | ||
666 | "foo", | ||
667 | r#" | ||
668 | mod m { pub fn foo() {} } | ||
669 | use crate::m::f<|>; | ||
670 | "#, | ||
671 | r#" | ||
672 | mod m { pub fn foo() {} } | ||
673 | use crate::m::foo; | ||
674 | "#, | ||
675 | ); | ||
676 | } | ||
677 | |||
678 | #[test] | ||
679 | fn no_parens_in_call() { | ||
680 | check_edit( | ||
681 | "foo", | ||
682 | r#" | ||
683 | fn foo(x: i32) {} | ||
684 | fn main() { f<|>(); } | ||
685 | "#, | ||
686 | r#" | ||
687 | fn foo(x: i32) {} | ||
688 | fn main() { foo(); } | ||
689 | "#, | ||
690 | ); | ||
691 | check_edit( | ||
692 | "foo", | ||
693 | r#" | ||
694 | struct Foo; | ||
695 | impl Foo { fn foo(&self){} } | ||
696 | fn f(foo: &Foo) { foo.f<|>(); } | ||
697 | "#, | ||
698 | r#" | ||
699 | struct Foo; | ||
700 | impl Foo { fn foo(&self){} } | ||
701 | fn f(foo: &Foo) { foo.foo(); } | ||
702 | "#, | ||
703 | ); | ||
704 | } | ||
705 | |||
706 | #[test] | ||
707 | fn inserts_angle_brackets_for_generics() { | ||
708 | mark::check!(inserts_angle_brackets_for_generics); | ||
709 | check_edit( | ||
710 | "Vec", | ||
711 | r#" | ||
712 | struct Vec<T> {} | ||
713 | fn foo(xs: Ve<|>) | ||
714 | "#, | ||
715 | r#" | ||
716 | struct Vec<T> {} | ||
717 | fn foo(xs: Vec<$0>) | ||
718 | "#, | ||
719 | ); | ||
720 | check_edit( | ||
721 | "Vec", | ||
722 | r#" | ||
723 | type Vec<T> = (T,); | ||
724 | fn foo(xs: Ve<|>) | ||
725 | "#, | ||
726 | r#" | ||
727 | type Vec<T> = (T,); | ||
728 | fn foo(xs: Vec<$0>) | ||
729 | "#, | ||
730 | ); | ||
731 | check_edit( | ||
732 | "Vec", | ||
733 | r#" | ||
734 | struct Vec<T = i128> {} | ||
735 | fn foo(xs: Ve<|>) | ||
736 | "#, | ||
737 | r#" | ||
738 | struct Vec<T = i128> {} | ||
739 | fn foo(xs: Vec) | ||
740 | "#, | ||
741 | ); | ||
742 | check_edit( | ||
743 | "Vec", | ||
744 | r#" | ||
745 | struct Vec<T> {} | ||
746 | fn foo(xs: Ve<|><i128>) | ||
747 | "#, | ||
748 | r#" | ||
749 | struct Vec<T> {} | ||
750 | fn foo(xs: Vec<i128>) | ||
751 | "#, | ||
752 | ); | ||
753 | } | ||
754 | |||
755 | #[test] | ||
756 | fn active_param_score() { | ||
757 | mark::check!(active_param_type_match); | ||
758 | check_scores( | ||
759 | r#" | ||
760 | struct S { foo: i64, bar: u32, baz: u32 } | ||
761 | fn test(bar: u32) { } | ||
762 | fn foo(s: S) { test(s.<|>) } | ||
763 | "#, | ||
764 | expect![[r#" | ||
765 | fd bar [type+name] | ||
766 | fd baz [type] | ||
767 | fd foo [] | ||
768 | "#]], | ||
769 | ); | ||
770 | } | ||
771 | |||
772 | #[test] | ||
773 | fn record_field_scores() { | ||
774 | mark::check!(record_field_type_match); | ||
775 | check_scores( | ||
776 | r#" | ||
777 | struct A { foo: i64, bar: u32, baz: u32 } | ||
778 | struct B { x: (), y: f32, bar: u32 } | ||
779 | fn foo(a: A) { B { bar: a.<|> }; } | ||
780 | "#, | ||
781 | expect![[r#" | ||
782 | fd bar [type+name] | ||
783 | fd baz [type] | ||
784 | fd foo [] | ||
785 | "#]], | ||
786 | ) | ||
787 | } | ||
788 | |||
789 | #[test] | ||
790 | fn record_field_and_call_scores() { | ||
791 | check_scores( | ||
792 | r#" | ||
793 | struct A { foo: i64, bar: u32, baz: u32 } | ||
794 | struct B { x: (), y: f32, bar: u32 } | ||
795 | fn f(foo: i64) { } | ||
796 | fn foo(a: A) { B { bar: f(a.<|>) }; } | ||
797 | "#, | ||
798 | expect![[r#" | ||
799 | fd foo [type+name] | ||
800 | fd bar [] | ||
801 | fd baz [] | ||
802 | "#]], | ||
803 | ); | ||
804 | check_scores( | ||
805 | r#" | ||
806 | struct A { foo: i64, bar: u32, baz: u32 } | ||
807 | struct B { x: (), y: f32, bar: u32 } | ||
808 | fn f(foo: i64) { } | ||
809 | fn foo(a: A) { f(B { bar: a.<|> }); } | ||
810 | "#, | ||
811 | expect![[r#" | ||
812 | fd bar [type+name] | ||
813 | fd baz [type] | ||
814 | fd foo [] | ||
815 | "#]], | ||
816 | ); | ||
817 | } | ||
818 | |||
819 | #[test] | ||
820 | fn prioritize_exact_ref_match() { | ||
821 | check_scores( | ||
822 | r#" | ||
823 | struct WorldSnapshot { _f: () }; | ||
824 | fn go(world: &WorldSnapshot) { go(w<|>) } | ||
825 | "#, | ||
826 | expect![[r#" | ||
827 | bn world [type+name] | ||
828 | st WorldSnapshot [] | ||
829 | fn go(…) [] | ||
830 | "#]], | ||
831 | ); | ||
832 | } | ||
833 | |||
834 | #[test] | ||
835 | fn too_many_arguments() { | ||
836 | check_scores( | ||
837 | r#" | ||
838 | struct Foo; | ||
839 | fn f(foo: &Foo) { f(foo, w<|>) } | ||
840 | "#, | ||
841 | expect![[r#" | ||
842 | st Foo [] | ||
843 | fn f(…) [] | ||
844 | bn foo [] | ||
845 | "#]], | ||
846 | ); | ||
847 | } | ||
848 | } | ||
diff --git a/crates/completion/src/render/builder_ext.rs b/crates/completion/src/render/builder_ext.rs new file mode 100644 index 000000000..37b0d0459 --- /dev/null +++ b/crates/completion/src/render/builder_ext.rs | |||
@@ -0,0 +1,94 @@ | |||
1 | //! Extensions for `Builder` structure required for item rendering. | ||
2 | |||
3 | use itertools::Itertools; | ||
4 | use test_utils::mark; | ||
5 | |||
6 | use crate::{item::Builder, CompletionContext}; | ||
7 | |||
8 | pub(super) enum Params { | ||
9 | Named(Vec<String>), | ||
10 | Anonymous(usize), | ||
11 | } | ||
12 | |||
13 | impl Params { | ||
14 | pub(super) fn len(&self) -> usize { | ||
15 | match self { | ||
16 | Params::Named(xs) => xs.len(), | ||
17 | Params::Anonymous(len) => *len, | ||
18 | } | ||
19 | } | ||
20 | |||
21 | pub(super) fn is_empty(&self) -> bool { | ||
22 | self.len() == 0 | ||
23 | } | ||
24 | } | ||
25 | |||
26 | impl Builder { | ||
27 | pub(super) fn should_add_parems(&self, ctx: &CompletionContext) -> bool { | ||
28 | if !ctx.config.add_call_parenthesis { | ||
29 | return false; | ||
30 | } | ||
31 | if ctx.use_item_syntax.is_some() { | ||
32 | mark::hit!(no_parens_in_use_item); | ||
33 | return false; | ||
34 | } | ||
35 | if ctx.is_pattern_call { | ||
36 | mark::hit!(dont_duplicate_pattern_parens); | ||
37 | return false; | ||
38 | } | ||
39 | if ctx.is_call { | ||
40 | return false; | ||
41 | } | ||
42 | |||
43 | // Don't add parentheses if the expected type is some function reference. | ||
44 | if let Some(ty) = &ctx.expected_type { | ||
45 | if ty.is_fn() { | ||
46 | mark::hit!(no_call_parens_if_fn_ptr_needed); | ||
47 | return false; | ||
48 | } | ||
49 | } | ||
50 | |||
51 | // Nothing prevents us from adding parentheses | ||
52 | true | ||
53 | } | ||
54 | |||
55 | pub(super) fn add_call_parens( | ||
56 | mut self, | ||
57 | ctx: &CompletionContext, | ||
58 | name: String, | ||
59 | params: Params, | ||
60 | ) -> Builder { | ||
61 | if !self.should_add_parems(ctx) { | ||
62 | return self; | ||
63 | } | ||
64 | |||
65 | let cap = match ctx.config.snippet_cap { | ||
66 | Some(it) => it, | ||
67 | None => return self, | ||
68 | }; | ||
69 | // If not an import, add parenthesis automatically. | ||
70 | mark::hit!(inserts_parens_for_function_calls); | ||
71 | |||
72 | let (snippet, label) = if params.is_empty() { | ||
73 | (format!("{}()$0", name), format!("{}()", name)) | ||
74 | } else { | ||
75 | self = self.trigger_call_info(); | ||
76 | let snippet = match (ctx.config.add_call_argument_snippets, params) { | ||
77 | (true, Params::Named(params)) => { | ||
78 | let function_params_snippet = | ||
79 | params.iter().enumerate().format_with(", ", |(index, param_name), f| { | ||
80 | f(&format_args!("${{{}:{}}}", index + 1, param_name)) | ||
81 | }); | ||
82 | format!("{}({})$0", name, function_params_snippet) | ||
83 | } | ||
84 | _ => { | ||
85 | mark::hit!(suppress_arg_snippets); | ||
86 | format!("{}($0)", name) | ||
87 | } | ||
88 | }; | ||
89 | |||
90 | (snippet, format!("{}(…)", name)) | ||
91 | }; | ||
92 | self.lookup_by(name).label(label).insert_snippet(cap, snippet) | ||
93 | } | ||
94 | } | ||
diff --git a/crates/completion/src/render/const_.rs b/crates/completion/src/render/const_.rs new file mode 100644 index 000000000..039bdabc0 --- /dev/null +++ b/crates/completion/src/render/const_.rs | |||
@@ -0,0 +1,55 @@ | |||
1 | //! Renderer for `const` fields. | ||
2 | |||
3 | use hir::HasSource; | ||
4 | use syntax::{ | ||
5 | ast::{Const, NameOwner}, | ||
6 | display::const_label, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
11 | render::RenderContext, | ||
12 | }; | ||
13 | |||
14 | pub(crate) fn render_const<'a>( | ||
15 | ctx: RenderContext<'a>, | ||
16 | const_: hir::Const, | ||
17 | ) -> Option<CompletionItem> { | ||
18 | ConstRender::new(ctx, const_).render() | ||
19 | } | ||
20 | |||
21 | #[derive(Debug)] | ||
22 | struct ConstRender<'a> { | ||
23 | ctx: RenderContext<'a>, | ||
24 | const_: hir::Const, | ||
25 | ast_node: Const, | ||
26 | } | ||
27 | |||
28 | impl<'a> ConstRender<'a> { | ||
29 | fn new(ctx: RenderContext<'a>, const_: hir::Const) -> ConstRender<'a> { | ||
30 | let ast_node = const_.source(ctx.db()).value; | ||
31 | ConstRender { ctx, const_, ast_node } | ||
32 | } | ||
33 | |||
34 | fn render(self) -> Option<CompletionItem> { | ||
35 | let name = self.name()?; | ||
36 | let detail = self.detail(); | ||
37 | |||
38 | let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name) | ||
39 | .kind(CompletionItemKind::Const) | ||
40 | .set_documentation(self.ctx.docs(self.const_)) | ||
41 | .set_deprecated(self.ctx.is_deprecated(self.const_)) | ||
42 | .detail(detail) | ||
43 | .build(); | ||
44 | |||
45 | Some(item) | ||
46 | } | ||
47 | |||
48 | fn name(&self) -> Option<String> { | ||
49 | self.ast_node.name().map(|name| name.text().to_string()) | ||
50 | } | ||
51 | |||
52 | fn detail(&self) -> String { | ||
53 | const_label(&self.ast_node) | ||
54 | } | ||
55 | } | ||
diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs new file mode 100644 index 000000000..fd412ed0e --- /dev/null +++ b/crates/completion/src/render/enum_variant.rs | |||
@@ -0,0 +1,180 @@ | |||
1 | //! Renderer for `enum` variants. | ||
2 | |||
3 | use hir::{HasAttrs, HirDisplay, ModPath, StructKind}; | ||
4 | use itertools::Itertools; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::{ | ||
8 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
9 | render::{builder_ext::Params, RenderContext}, | ||
10 | }; | ||
11 | |||
12 | pub(crate) fn render_enum_variant<'a>( | ||
13 | ctx: RenderContext<'a>, | ||
14 | local_name: Option<String>, | ||
15 | variant: hir::EnumVariant, | ||
16 | path: Option<ModPath>, | ||
17 | ) -> CompletionItem { | ||
18 | EnumVariantRender::new(ctx, local_name, variant, path).render() | ||
19 | } | ||
20 | |||
21 | #[derive(Debug)] | ||
22 | struct EnumVariantRender<'a> { | ||
23 | ctx: RenderContext<'a>, | ||
24 | name: String, | ||
25 | variant: hir::EnumVariant, | ||
26 | path: Option<ModPath>, | ||
27 | qualified_name: String, | ||
28 | short_qualified_name: String, | ||
29 | variant_kind: StructKind, | ||
30 | } | ||
31 | |||
32 | impl<'a> EnumVariantRender<'a> { | ||
33 | fn new( | ||
34 | ctx: RenderContext<'a>, | ||
35 | local_name: Option<String>, | ||
36 | variant: hir::EnumVariant, | ||
37 | path: Option<ModPath>, | ||
38 | ) -> EnumVariantRender<'a> { | ||
39 | let name = local_name.unwrap_or_else(|| variant.name(ctx.db()).to_string()); | ||
40 | let variant_kind = variant.kind(ctx.db()); | ||
41 | |||
42 | let (qualified_name, short_qualified_name) = match &path { | ||
43 | Some(path) => { | ||
44 | let full = path.to_string(); | ||
45 | let short = | ||
46 | path.segments[path.segments.len().saturating_sub(2)..].iter().join("::"); | ||
47 | (full, short) | ||
48 | } | ||
49 | None => (name.to_string(), name.to_string()), | ||
50 | }; | ||
51 | |||
52 | EnumVariantRender { | ||
53 | ctx, | ||
54 | name, | ||
55 | variant, | ||
56 | path, | ||
57 | qualified_name, | ||
58 | short_qualified_name, | ||
59 | variant_kind, | ||
60 | } | ||
61 | } | ||
62 | |||
63 | fn render(self) -> CompletionItem { | ||
64 | let mut builder = CompletionItem::new( | ||
65 | CompletionKind::Reference, | ||
66 | self.ctx.source_range(), | ||
67 | self.qualified_name.clone(), | ||
68 | ) | ||
69 | .kind(CompletionItemKind::EnumVariant) | ||
70 | .set_documentation(self.variant.docs(self.ctx.db())) | ||
71 | .set_deprecated(self.ctx.is_deprecated(self.variant)) | ||
72 | .detail(self.detail()); | ||
73 | |||
74 | if self.variant_kind == StructKind::Tuple { | ||
75 | mark::hit!(inserts_parens_for_tuple_enums); | ||
76 | let params = Params::Anonymous(self.variant.fields(self.ctx.db()).len()); | ||
77 | builder = | ||
78 | builder.add_call_parens(self.ctx.completion, self.short_qualified_name, params); | ||
79 | } else if self.path.is_some() { | ||
80 | builder = builder.lookup_by(self.short_qualified_name); | ||
81 | } | ||
82 | |||
83 | builder.build() | ||
84 | } | ||
85 | |||
86 | fn detail(&self) -> String { | ||
87 | let detail_types = self | ||
88 | .variant | ||
89 | .fields(self.ctx.db()) | ||
90 | .into_iter() | ||
91 | .map(|field| (field.name(self.ctx.db()), field.signature_ty(self.ctx.db()))); | ||
92 | |||
93 | match self.variant_kind { | ||
94 | StructKind::Tuple | StructKind::Unit => format!( | ||
95 | "({})", | ||
96 | detail_types.map(|(_, t)| t.display(self.ctx.db()).to_string()).format(", ") | ||
97 | ), | ||
98 | StructKind::Record => format!( | ||
99 | "{{ {} }}", | ||
100 | detail_types | ||
101 | .map(|(n, t)| format!("{}: {}", n, t.display(self.ctx.db()).to_string())) | ||
102 | .format(", ") | ||
103 | ), | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | |||
108 | #[cfg(test)] | ||
109 | mod tests { | ||
110 | use test_utils::mark; | ||
111 | |||
112 | use crate::test_utils::check_edit; | ||
113 | |||
114 | #[test] | ||
115 | fn inserts_parens_for_tuple_enums() { | ||
116 | mark::check!(inserts_parens_for_tuple_enums); | ||
117 | check_edit( | ||
118 | "Some", | ||
119 | r#" | ||
120 | enum Option<T> { Some(T), None } | ||
121 | use Option::*; | ||
122 | fn main() -> Option<i32> { | ||
123 | Som<|> | ||
124 | } | ||
125 | "#, | ||
126 | r#" | ||
127 | enum Option<T> { Some(T), None } | ||
128 | use Option::*; | ||
129 | fn main() -> Option<i32> { | ||
130 | Some($0) | ||
131 | } | ||
132 | "#, | ||
133 | ); | ||
134 | check_edit( | ||
135 | "Some", | ||
136 | r#" | ||
137 | enum Option<T> { Some(T), None } | ||
138 | use Option::*; | ||
139 | fn main(value: Option<i32>) { | ||
140 | match value { | ||
141 | Som<|> | ||
142 | } | ||
143 | } | ||
144 | "#, | ||
145 | r#" | ||
146 | enum Option<T> { Some(T), None } | ||
147 | use Option::*; | ||
148 | fn main(value: Option<i32>) { | ||
149 | match value { | ||
150 | Some($0) | ||
151 | } | ||
152 | } | ||
153 | "#, | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn dont_duplicate_pattern_parens() { | ||
159 | mark::check!(dont_duplicate_pattern_parens); | ||
160 | check_edit( | ||
161 | "Var", | ||
162 | r#" | ||
163 | enum E { Var(i32) } | ||
164 | fn main() { | ||
165 | match E::Var(92) { | ||
166 | E::<|>(92) => (), | ||
167 | } | ||
168 | } | ||
169 | "#, | ||
170 | r#" | ||
171 | enum E { Var(i32) } | ||
172 | fn main() { | ||
173 | match E::Var(92) { | ||
174 | E::Var(92) => (), | ||
175 | } | ||
176 | } | ||
177 | "#, | ||
178 | ); | ||
179 | } | ||
180 | } | ||
diff --git a/crates/completion/src/render/function.rs b/crates/completion/src/render/function.rs new file mode 100644 index 000000000..4fa6eafd7 --- /dev/null +++ b/crates/completion/src/render/function.rs | |||
@@ -0,0 +1,303 @@ | |||
1 | //! Renderer for function calls. | ||
2 | |||
3 | use hir::{HasSource, Type}; | ||
4 | use syntax::{ast::Fn, display::function_declaration}; | ||
5 | |||
6 | use crate::{ | ||
7 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
8 | render::{builder_ext::Params, RenderContext}, | ||
9 | }; | ||
10 | |||
11 | pub(crate) fn render_fn<'a>( | ||
12 | ctx: RenderContext<'a>, | ||
13 | local_name: Option<String>, | ||
14 | fn_: hir::Function, | ||
15 | ) -> CompletionItem { | ||
16 | FunctionRender::new(ctx, local_name, fn_).render() | ||
17 | } | ||
18 | |||
19 | #[derive(Debug)] | ||
20 | struct FunctionRender<'a> { | ||
21 | ctx: RenderContext<'a>, | ||
22 | name: String, | ||
23 | fn_: hir::Function, | ||
24 | ast_node: Fn, | ||
25 | } | ||
26 | |||
27 | impl<'a> FunctionRender<'a> { | ||
28 | fn new( | ||
29 | ctx: RenderContext<'a>, | ||
30 | local_name: Option<String>, | ||
31 | fn_: hir::Function, | ||
32 | ) -> FunctionRender<'a> { | ||
33 | let name = local_name.unwrap_or_else(|| fn_.name(ctx.db()).to_string()); | ||
34 | let ast_node = fn_.source(ctx.db()).value; | ||
35 | |||
36 | FunctionRender { ctx, name, fn_, ast_node } | ||
37 | } | ||
38 | |||
39 | fn render(self) -> CompletionItem { | ||
40 | let params = self.params(); | ||
41 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), self.name.clone()) | ||
42 | .kind(self.kind()) | ||
43 | .set_documentation(self.ctx.docs(self.fn_)) | ||
44 | .set_deprecated(self.ctx.is_deprecated(self.fn_)) | ||
45 | .detail(self.detail()) | ||
46 | .add_call_parens(self.ctx.completion, self.name, params) | ||
47 | .build() | ||
48 | } | ||
49 | |||
50 | fn detail(&self) -> String { | ||
51 | function_declaration(&self.ast_node) | ||
52 | } | ||
53 | |||
54 | fn add_arg(&self, arg: &str, ty: &Type) -> String { | ||
55 | if let Some(derefed_ty) = ty.remove_ref() { | ||
56 | for (name, local) in self.ctx.completion.locals.iter() { | ||
57 | if name == arg && local.ty(self.ctx.db()) == derefed_ty { | ||
58 | let mutability = if ty.is_mutable_reference() { "&mut " } else { "&" }; | ||
59 | return format!("{}{}", mutability, arg); | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | arg.to_string() | ||
64 | } | ||
65 | |||
66 | fn params(&self) -> Params { | ||
67 | let params_ty = self.fn_.params(self.ctx.db()); | ||
68 | let params = self | ||
69 | .ast_node | ||
70 | .param_list() | ||
71 | .into_iter() | ||
72 | .flat_map(|it| it.params()) | ||
73 | .zip(params_ty) | ||
74 | .flat_map(|(it, param_ty)| { | ||
75 | if let Some(pat) = it.pat() { | ||
76 | let name = pat.to_string(); | ||
77 | let arg = name.trim_start_matches("mut ").trim_start_matches('_'); | ||
78 | return Some(self.add_arg(arg, param_ty.ty())); | ||
79 | } | ||
80 | None | ||
81 | }) | ||
82 | .collect(); | ||
83 | Params::Named(params) | ||
84 | } | ||
85 | |||
86 | fn kind(&self) -> CompletionItemKind { | ||
87 | if self.fn_.self_param(self.ctx.db()).is_some() { | ||
88 | CompletionItemKind::Method | ||
89 | } else { | ||
90 | CompletionItemKind::Function | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | |||
95 | #[cfg(test)] | ||
96 | mod tests { | ||
97 | use test_utils::mark; | ||
98 | |||
99 | use crate::{ | ||
100 | test_utils::{check_edit, check_edit_with_config}, | ||
101 | CompletionConfig, | ||
102 | }; | ||
103 | |||
104 | #[test] | ||
105 | fn inserts_parens_for_function_calls() { | ||
106 | mark::check!(inserts_parens_for_function_calls); | ||
107 | check_edit( | ||
108 | "no_args", | ||
109 | r#" | ||
110 | fn no_args() {} | ||
111 | fn main() { no_<|> } | ||
112 | "#, | ||
113 | r#" | ||
114 | fn no_args() {} | ||
115 | fn main() { no_args()$0 } | ||
116 | "#, | ||
117 | ); | ||
118 | |||
119 | check_edit( | ||
120 | "with_args", | ||
121 | r#" | ||
122 | fn with_args(x: i32, y: String) {} | ||
123 | fn main() { with_<|> } | ||
124 | "#, | ||
125 | r#" | ||
126 | fn with_args(x: i32, y: String) {} | ||
127 | fn main() { with_args(${1:x}, ${2:y})$0 } | ||
128 | "#, | ||
129 | ); | ||
130 | |||
131 | check_edit( | ||
132 | "foo", | ||
133 | r#" | ||
134 | struct S; | ||
135 | impl S { | ||
136 | fn foo(&self) {} | ||
137 | } | ||
138 | fn bar(s: &S) { s.f<|> } | ||
139 | "#, | ||
140 | r#" | ||
141 | struct S; | ||
142 | impl S { | ||
143 | fn foo(&self) {} | ||
144 | } | ||
145 | fn bar(s: &S) { s.foo()$0 } | ||
146 | "#, | ||
147 | ); | ||
148 | |||
149 | check_edit( | ||
150 | "foo", | ||
151 | r#" | ||
152 | struct S {} | ||
153 | impl S { | ||
154 | fn foo(&self, x: i32) {} | ||
155 | } | ||
156 | fn bar(s: &S) { | ||
157 | s.f<|> | ||
158 | } | ||
159 | "#, | ||
160 | r#" | ||
161 | struct S {} | ||
162 | impl S { | ||
163 | fn foo(&self, x: i32) {} | ||
164 | } | ||
165 | fn bar(s: &S) { | ||
166 | s.foo(${1:x})$0 | ||
167 | } | ||
168 | "#, | ||
169 | ); | ||
170 | } | ||
171 | |||
172 | #[test] | ||
173 | fn suppress_arg_snippets() { | ||
174 | mark::check!(suppress_arg_snippets); | ||
175 | check_edit_with_config( | ||
176 | CompletionConfig { add_call_argument_snippets: false, ..CompletionConfig::default() }, | ||
177 | "with_args", | ||
178 | r#" | ||
179 | fn with_args(x: i32, y: String) {} | ||
180 | fn main() { with_<|> } | ||
181 | "#, | ||
182 | r#" | ||
183 | fn with_args(x: i32, y: String) {} | ||
184 | fn main() { with_args($0) } | ||
185 | "#, | ||
186 | ); | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn strips_underscores_from_args() { | ||
191 | check_edit( | ||
192 | "foo", | ||
193 | r#" | ||
194 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
195 | fn main() { f<|> } | ||
196 | "#, | ||
197 | r#" | ||
198 | fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {} | ||
199 | fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 } | ||
200 | "#, | ||
201 | ); | ||
202 | } | ||
203 | |||
204 | #[test] | ||
205 | fn insert_ref_when_matching_local_in_scope() { | ||
206 | check_edit( | ||
207 | "ref_arg", | ||
208 | r#" | ||
209 | struct Foo {} | ||
210 | fn ref_arg(x: &Foo) {} | ||
211 | fn main() { | ||
212 | let x = Foo {}; | ||
213 | ref_ar<|> | ||
214 | } | ||
215 | "#, | ||
216 | r#" | ||
217 | struct Foo {} | ||
218 | fn ref_arg(x: &Foo) {} | ||
219 | fn main() { | ||
220 | let x = Foo {}; | ||
221 | ref_arg(${1:&x})$0 | ||
222 | } | ||
223 | "#, | ||
224 | ); | ||
225 | } | ||
226 | |||
227 | #[test] | ||
228 | fn insert_mut_ref_when_matching_local_in_scope() { | ||
229 | check_edit( | ||
230 | "ref_arg", | ||
231 | r#" | ||
232 | struct Foo {} | ||
233 | fn ref_arg(x: &mut Foo) {} | ||
234 | fn main() { | ||
235 | let x = Foo {}; | ||
236 | ref_ar<|> | ||
237 | } | ||
238 | "#, | ||
239 | r#" | ||
240 | struct Foo {} | ||
241 | fn ref_arg(x: &mut Foo) {} | ||
242 | fn main() { | ||
243 | let x = Foo {}; | ||
244 | ref_arg(${1:&mut x})$0 | ||
245 | } | ||
246 | "#, | ||
247 | ); | ||
248 | } | ||
249 | |||
250 | #[test] | ||
251 | fn insert_ref_when_matching_local_in_scope_for_method() { | ||
252 | check_edit( | ||
253 | "apply_foo", | ||
254 | r#" | ||
255 | struct Foo {} | ||
256 | struct Bar {} | ||
257 | impl Bar { | ||
258 | fn apply_foo(&self, x: &Foo) {} | ||
259 | } | ||
260 | |||
261 | fn main() { | ||
262 | let x = Foo {}; | ||
263 | let y = Bar {}; | ||
264 | y.<|> | ||
265 | } | ||
266 | "#, | ||
267 | r#" | ||
268 | struct Foo {} | ||
269 | struct Bar {} | ||
270 | impl Bar { | ||
271 | fn apply_foo(&self, x: &Foo) {} | ||
272 | } | ||
273 | |||
274 | fn main() { | ||
275 | let x = Foo {}; | ||
276 | let y = Bar {}; | ||
277 | y.apply_foo(${1:&x})$0 | ||
278 | } | ||
279 | "#, | ||
280 | ); | ||
281 | } | ||
282 | |||
283 | #[test] | ||
284 | fn trim_mut_keyword_in_func_completion() { | ||
285 | check_edit( | ||
286 | "take_mutably", | ||
287 | r#" | ||
288 | fn take_mutably(mut x: &i32) {} | ||
289 | |||
290 | fn main() { | ||
291 | take_m<|> | ||
292 | } | ||
293 | "#, | ||
294 | r#" | ||
295 | fn take_mutably(mut x: &i32) {} | ||
296 | |||
297 | fn main() { | ||
298 | take_mutably(${1:x})$0 | ||
299 | } | ||
300 | "#, | ||
301 | ); | ||
302 | } | ||
303 | } | ||
diff --git a/crates/completion/src/render/macro_.rs b/crates/completion/src/render/macro_.rs new file mode 100644 index 000000000..96be59cc3 --- /dev/null +++ b/crates/completion/src/render/macro_.rs | |||
@@ -0,0 +1,216 @@ | |||
1 | //! Renderer for macro invocations. | ||
2 | |||
3 | use hir::{Documentation, HasSource}; | ||
4 | use syntax::display::macro_label; | ||
5 | use test_utils::mark; | ||
6 | |||
7 | use crate::{ | ||
8 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
9 | render::RenderContext, | ||
10 | }; | ||
11 | |||
12 | pub(crate) fn render_macro<'a>( | ||
13 | ctx: RenderContext<'a>, | ||
14 | name: String, | ||
15 | macro_: hir::MacroDef, | ||
16 | ) -> Option<CompletionItem> { | ||
17 | MacroRender::new(ctx, name, macro_).render() | ||
18 | } | ||
19 | |||
20 | #[derive(Debug)] | ||
21 | struct MacroRender<'a> { | ||
22 | ctx: RenderContext<'a>, | ||
23 | name: String, | ||
24 | macro_: hir::MacroDef, | ||
25 | docs: Option<Documentation>, | ||
26 | bra: &'static str, | ||
27 | ket: &'static str, | ||
28 | } | ||
29 | |||
30 | impl<'a> MacroRender<'a> { | ||
31 | fn new(ctx: RenderContext<'a>, name: String, macro_: hir::MacroDef) -> MacroRender<'a> { | ||
32 | let docs = ctx.docs(macro_); | ||
33 | let docs_str = docs.as_ref().map_or("", |s| s.as_str()); | ||
34 | let (bra, ket) = guess_macro_braces(&name, docs_str); | ||
35 | |||
36 | MacroRender { ctx, name, macro_, docs, bra, ket } | ||
37 | } | ||
38 | |||
39 | fn render(&self) -> Option<CompletionItem> { | ||
40 | // FIXME: Currently proc-macro do not have ast-node, | ||
41 | // such that it does not have source | ||
42 | if self.macro_.is_proc_macro() { | ||
43 | return None; | ||
44 | } | ||
45 | |||
46 | let mut builder = | ||
47 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), &self.label()) | ||
48 | .kind(CompletionItemKind::Macro) | ||
49 | .set_documentation(self.docs.clone()) | ||
50 | .set_deprecated(self.ctx.is_deprecated(self.macro_)) | ||
51 | .detail(self.detail()); | ||
52 | |||
53 | let needs_bang = self.needs_bang(); | ||
54 | builder = match self.ctx.snippet_cap() { | ||
55 | Some(cap) if needs_bang => { | ||
56 | let snippet = self.snippet(); | ||
57 | let lookup = self.lookup(); | ||
58 | builder.insert_snippet(cap, snippet).lookup_by(lookup) | ||
59 | } | ||
60 | None if needs_bang => builder.insert_text(self.banged_name()), | ||
61 | _ => { | ||
62 | mark::hit!(dont_insert_macro_call_parens_unncessary); | ||
63 | builder.insert_text(&self.name) | ||
64 | } | ||
65 | }; | ||
66 | |||
67 | Some(builder.build()) | ||
68 | } | ||
69 | |||
70 | fn needs_bang(&self) -> bool { | ||
71 | self.ctx.completion.use_item_syntax.is_none() && !self.ctx.completion.is_macro_call | ||
72 | } | ||
73 | |||
74 | fn label(&self) -> String { | ||
75 | if self.needs_bang() && self.ctx.snippet_cap().is_some() { | ||
76 | format!("{}!{}…{}", self.name, self.bra, self.ket) | ||
77 | } else { | ||
78 | self.banged_name() | ||
79 | } | ||
80 | } | ||
81 | |||
82 | fn snippet(&self) -> String { | ||
83 | format!("{}!{}$0{}", self.name, self.bra, self.ket) | ||
84 | } | ||
85 | |||
86 | fn lookup(&self) -> String { | ||
87 | self.banged_name() | ||
88 | } | ||
89 | |||
90 | fn banged_name(&self) -> String { | ||
91 | format!("{}!", self.name) | ||
92 | } | ||
93 | |||
94 | fn detail(&self) -> String { | ||
95 | let ast_node = self.macro_.source(self.ctx.db()).value; | ||
96 | macro_label(&ast_node) | ||
97 | } | ||
98 | } | ||
99 | |||
100 | fn guess_macro_braces(macro_name: &str, docs: &str) -> (&'static str, &'static str) { | ||
101 | let mut votes = [0, 0, 0]; | ||
102 | for (idx, s) in docs.match_indices(¯o_name) { | ||
103 | let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); | ||
104 | // Ensure to match the full word | ||
105 | if after.starts_with('!') | ||
106 | && !before.ends_with(|c: char| c == '_' || c.is_ascii_alphanumeric()) | ||
107 | { | ||
108 | // It may have spaces before the braces like `foo! {}` | ||
109 | match after[1..].chars().find(|&c| !c.is_whitespace()) { | ||
110 | Some('{') => votes[0] += 1, | ||
111 | Some('[') => votes[1] += 1, | ||
112 | Some('(') => votes[2] += 1, | ||
113 | _ => {} | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | // Insert a space before `{}`. | ||
119 | // We prefer the last one when some votes equal. | ||
120 | let (_vote, (bra, ket)) = votes | ||
121 | .iter() | ||
122 | .zip(&[(" {", "}"), ("[", "]"), ("(", ")")]) | ||
123 | .max_by_key(|&(&vote, _)| vote) | ||
124 | .unwrap(); | ||
125 | (*bra, *ket) | ||
126 | } | ||
127 | |||
128 | #[cfg(test)] | ||
129 | mod tests { | ||
130 | use test_utils::mark; | ||
131 | |||
132 | use crate::test_utils::check_edit; | ||
133 | |||
134 | #[test] | ||
135 | fn dont_insert_macro_call_parens_unncessary() { | ||
136 | mark::check!(dont_insert_macro_call_parens_unncessary); | ||
137 | check_edit( | ||
138 | "frobnicate!", | ||
139 | r#" | ||
140 | //- /main.rs crate:main deps:foo | ||
141 | use foo::<|>; | ||
142 | //- /foo/lib.rs crate:foo | ||
143 | #[macro_export] | ||
144 | macro_rules frobnicate { () => () } | ||
145 | "#, | ||
146 | r#" | ||
147 | use foo::frobnicate; | ||
148 | "#, | ||
149 | ); | ||
150 | |||
151 | check_edit( | ||
152 | "frobnicate!", | ||
153 | r#" | ||
154 | macro_rules frobnicate { () => () } | ||
155 | fn main() { frob<|>!(); } | ||
156 | "#, | ||
157 | r#" | ||
158 | macro_rules frobnicate { () => () } | ||
159 | fn main() { frobnicate!(); } | ||
160 | "#, | ||
161 | ); | ||
162 | } | ||
163 | |||
164 | #[test] | ||
165 | fn guesses_macro_braces() { | ||
166 | check_edit( | ||
167 | "vec!", | ||
168 | r#" | ||
169 | /// Creates a [`Vec`] containing the arguments. | ||
170 | /// | ||
171 | /// ``` | ||
172 | /// let v = vec![1, 2, 3]; | ||
173 | /// assert_eq!(v[0], 1); | ||
174 | /// assert_eq!(v[1], 2); | ||
175 | /// assert_eq!(v[2], 3); | ||
176 | /// ``` | ||
177 | macro_rules! vec { () => {} } | ||
178 | |||
179 | fn fn main() { v<|> } | ||
180 | "#, | ||
181 | r#" | ||
182 | /// Creates a [`Vec`] containing the arguments. | ||
183 | /// | ||
184 | /// ``` | ||
185 | /// let v = vec![1, 2, 3]; | ||
186 | /// assert_eq!(v[0], 1); | ||
187 | /// assert_eq!(v[1], 2); | ||
188 | /// assert_eq!(v[2], 3); | ||
189 | /// ``` | ||
190 | macro_rules! vec { () => {} } | ||
191 | |||
192 | fn fn main() { vec![$0] } | ||
193 | "#, | ||
194 | ); | ||
195 | |||
196 | check_edit( | ||
197 | "foo!", | ||
198 | r#" | ||
199 | /// Foo | ||
200 | /// | ||
201 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
202 | /// call as `let _=foo! { hello world };` | ||
203 | macro_rules! foo { () => {} } | ||
204 | fn main() { <|> } | ||
205 | "#, | ||
206 | r#" | ||
207 | /// Foo | ||
208 | /// | ||
209 | /// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`, | ||
210 | /// call as `let _=foo! { hello world };` | ||
211 | macro_rules! foo { () => {} } | ||
212 | fn main() { foo! {$0} } | ||
213 | "#, | ||
214 | ) | ||
215 | } | ||
216 | } | ||
diff --git a/crates/completion/src/render/type_alias.rs b/crates/completion/src/render/type_alias.rs new file mode 100644 index 000000000..9605c7fa9 --- /dev/null +++ b/crates/completion/src/render/type_alias.rs | |||
@@ -0,0 +1,55 @@ | |||
1 | //! Renderer for type aliases. | ||
2 | |||
3 | use hir::HasSource; | ||
4 | use syntax::{ | ||
5 | ast::{NameOwner, TypeAlias}, | ||
6 | display::type_label, | ||
7 | }; | ||
8 | |||
9 | use crate::{ | ||
10 | item::{CompletionItem, CompletionItemKind, CompletionKind}, | ||
11 | render::RenderContext, | ||
12 | }; | ||
13 | |||
14 | pub(crate) fn render_type_alias<'a>( | ||
15 | ctx: RenderContext<'a>, | ||
16 | type_alias: hir::TypeAlias, | ||
17 | ) -> Option<CompletionItem> { | ||
18 | TypeAliasRender::new(ctx, type_alias).render() | ||
19 | } | ||
20 | |||
21 | #[derive(Debug)] | ||
22 | struct TypeAliasRender<'a> { | ||
23 | ctx: RenderContext<'a>, | ||
24 | type_alias: hir::TypeAlias, | ||
25 | ast_node: TypeAlias, | ||
26 | } | ||
27 | |||
28 | impl<'a> TypeAliasRender<'a> { | ||
29 | fn new(ctx: RenderContext<'a>, type_alias: hir::TypeAlias) -> TypeAliasRender<'a> { | ||
30 | let ast_node = type_alias.source(ctx.db()).value; | ||
31 | TypeAliasRender { ctx, type_alias, ast_node } | ||
32 | } | ||
33 | |||
34 | fn render(self) -> Option<CompletionItem> { | ||
35 | let name = self.name()?; | ||
36 | let detail = self.detail(); | ||
37 | |||
38 | let item = CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name) | ||
39 | .kind(CompletionItemKind::TypeAlias) | ||
40 | .set_documentation(self.ctx.docs(self.type_alias)) | ||
41 | .set_deprecated(self.ctx.is_deprecated(self.type_alias)) | ||
42 | .detail(detail) | ||
43 | .build(); | ||
44 | |||
45 | Some(item) | ||
46 | } | ||
47 | |||
48 | fn name(&self) -> Option<String> { | ||
49 | self.ast_node.name().map(|name| name.text().to_string()) | ||
50 | } | ||
51 | |||
52 | fn detail(&self) -> String { | ||
53 | type_label(&self.ast_node) | ||
54 | } | ||
55 | } | ||
diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs index f93337a6e..ec9d589a3 100644 --- a/crates/hir_def/src/nameres/tests/mod_resolution.rs +++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs | |||
@@ -323,13 +323,26 @@ pub struct Baz; | |||
323 | fn module_resolution_relative_path_outside_root() { | 323 | fn module_resolution_relative_path_outside_root() { |
324 | check( | 324 | check( |
325 | r#" | 325 | r#" |
326 | //- /main.rs | 326 | //- /a/b/c/d/e/main.rs crate:main |
327 | #[path="../../../../../outside.rs"] | 327 | #[path="../../../../../outside.rs"] |
328 | mod foo; | 328 | mod foo; |
329 | |||
330 | //- /outside.rs | ||
331 | mod bar; | ||
332 | |||
333 | //- /bar.rs | ||
334 | pub struct Baz; | ||
329 | "#, | 335 | "#, |
330 | expect![[r#" | 336 | expect![[r#" |
331 | crate | 337 | crate |
332 | "#]], | 338 | foo: t |
339 | |||
340 | crate::foo | ||
341 | bar: t | ||
342 | |||
343 | crate::foo::bar | ||
344 | Baz: t v | ||
345 | "#]], | ||
333 | ); | 346 | ); |
334 | } | 347 | } |
335 | 348 | ||
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs index f179c62b7..4b3e2fa8f 100644 --- a/crates/hir_ty/src/diagnostics/decl_check.rs +++ b/crates/hir_ty/src/diagnostics/decl_check.rs | |||
@@ -16,7 +16,7 @@ use hir_def::{ | |||
16 | adt::VariantData, | 16 | adt::VariantData, |
17 | expr::{Pat, PatId}, | 17 | expr::{Pat, PatId}, |
18 | src::HasSource, | 18 | src::HasSource, |
19 | AdtId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, | 19 | AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, |
20 | }; | 20 | }; |
21 | use hir_expand::{ | 21 | use hir_expand::{ |
22 | diagnostics::DiagnosticSink, | 22 | diagnostics::DiagnosticSink, |
@@ -32,6 +32,12 @@ use crate::{ | |||
32 | diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase}, | 32 | diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase}, |
33 | }; | 33 | }; |
34 | 34 | ||
35 | mod allow { | ||
36 | pub(super) const NON_SNAKE_CASE: &str = "non_snake_case"; | ||
37 | pub(super) const NON_UPPER_CASE_GLOBAL: &str = "non_upper_case_globals"; | ||
38 | pub(super) const NON_CAMEL_CASE_TYPES: &str = "non_camel_case_types"; | ||
39 | } | ||
40 | |||
35 | pub(super) struct DeclValidator<'a, 'b: 'a> { | 41 | pub(super) struct DeclValidator<'a, 'b: 'a> { |
36 | owner: ModuleDefId, | 42 | owner: ModuleDefId, |
37 | sink: &'a mut DiagnosticSink<'b>, | 43 | sink: &'a mut DiagnosticSink<'b>, |
@@ -72,11 +78,29 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
72 | } | 78 | } |
73 | } | 79 | } |
74 | 80 | ||
81 | /// Checks whether not following the convention is allowed for this item. | ||
82 | /// | ||
83 | /// Currently this method doesn't check parent attributes. | ||
84 | fn allowed(&self, db: &dyn HirDatabase, id: AttrDefId, allow_name: &str) -> bool { | ||
85 | db.attrs(id).by_key("allow").tt_values().any(|tt| tt.to_string().contains(allow_name)) | ||
86 | } | ||
87 | |||
75 | fn validate_func(&mut self, db: &dyn HirDatabase, func: FunctionId) { | 88 | fn validate_func(&mut self, db: &dyn HirDatabase, func: FunctionId) { |
76 | let data = db.function_data(func); | 89 | let data = db.function_data(func); |
77 | let body = db.body(func.into()); | 90 | let body = db.body(func.into()); |
78 | 91 | ||
79 | // 1. Check the function name. | 92 | // Recursively validate inner scope items, such as static variables and constants. |
93 | for (item_id, _) in body.item_scope.values() { | ||
94 | let mut validator = DeclValidator::new(item_id, self.sink); | ||
95 | validator.validate_item(db); | ||
96 | } | ||
97 | |||
98 | // Check whether non-snake case identifiers are allowed for this function. | ||
99 | if self.allowed(db, func.into(), allow::NON_SNAKE_CASE) { | ||
100 | return; | ||
101 | } | ||
102 | |||
103 | // Check the function name. | ||
80 | let function_name = data.name.to_string(); | 104 | let function_name = data.name.to_string(); |
81 | let fn_name_replacement = if let Some(new_name) = to_lower_snake_case(&function_name) { | 105 | let fn_name_replacement = if let Some(new_name) = to_lower_snake_case(&function_name) { |
82 | let replacement = Replacement { | 106 | let replacement = Replacement { |
@@ -89,7 +113,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
89 | None | 113 | None |
90 | }; | 114 | }; |
91 | 115 | ||
92 | // 2. Check the param names. | 116 | // Check the param names. |
93 | let mut fn_param_replacements = Vec::new(); | 117 | let mut fn_param_replacements = Vec::new(); |
94 | 118 | ||
95 | for pat_id in body.params.iter().cloned() { | 119 | for pat_id in body.params.iter().cloned() { |
@@ -111,7 +135,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
111 | } | 135 | } |
112 | } | 136 | } |
113 | 137 | ||
114 | // 3. Check the patterns inside the function body. | 138 | // Check the patterns inside the function body. |
115 | let mut pats_replacements = Vec::new(); | 139 | let mut pats_replacements = Vec::new(); |
116 | 140 | ||
117 | for (pat_idx, pat) in body.pats.iter() { | 141 | for (pat_idx, pat) in body.pats.iter() { |
@@ -136,7 +160,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
136 | } | 160 | } |
137 | } | 161 | } |
138 | 162 | ||
139 | // 4. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | 163 | // If there is at least one element to spawn a warning on, go to the source map and generate a warning. |
140 | self.create_incorrect_case_diagnostic_for_func( | 164 | self.create_incorrect_case_diagnostic_for_func( |
141 | func, | 165 | func, |
142 | db, | 166 | db, |
@@ -144,12 +168,6 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
144 | fn_param_replacements, | 168 | fn_param_replacements, |
145 | ); | 169 | ); |
146 | self.create_incorrect_case_diagnostic_for_variables(func, db, pats_replacements); | 170 | self.create_incorrect_case_diagnostic_for_variables(func, db, pats_replacements); |
147 | |||
148 | // 5. Recursively validate inner scope items, such as static variables and constants. | ||
149 | for (item_id, _) in body.item_scope.values() { | ||
150 | let mut validator = DeclValidator::new(item_id, self.sink); | ||
151 | validator.validate_item(db); | ||
152 | } | ||
153 | } | 171 | } |
154 | 172 | ||
155 | /// Given the information about incorrect names in the function declaration, looks up into the source code | 173 | /// Given the information about incorrect names in the function declaration, looks up into the source code |
@@ -169,7 +187,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
169 | let fn_loc = func.lookup(db.upcast()); | 187 | let fn_loc = func.lookup(db.upcast()); |
170 | let fn_src = fn_loc.source(db.upcast()); | 188 | let fn_src = fn_loc.source(db.upcast()); |
171 | 189 | ||
172 | // 1. Diagnostic for function name. | 190 | // Diagnostic for function name. |
173 | if let Some(replacement) = fn_name_replacement { | 191 | if let Some(replacement) = fn_name_replacement { |
174 | let ast_ptr = match fn_src.value.name() { | 192 | let ast_ptr = match fn_src.value.name() { |
175 | Some(name) => name, | 193 | Some(name) => name, |
@@ -196,7 +214,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
196 | self.sink.push(diagnostic); | 214 | self.sink.push(diagnostic); |
197 | } | 215 | } |
198 | 216 | ||
199 | // 2. Diagnostics for function params. | 217 | // Diagnostics for function params. |
200 | let fn_params_list = match fn_src.value.param_list() { | 218 | let fn_params_list = match fn_src.value.param_list() { |
201 | Some(params) => params, | 219 | Some(params) => params, |
202 | None => { | 220 | None => { |
@@ -312,7 +330,11 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
312 | fn validate_struct(&mut self, db: &dyn HirDatabase, struct_id: StructId) { | 330 | fn validate_struct(&mut self, db: &dyn HirDatabase, struct_id: StructId) { |
313 | let data = db.struct_data(struct_id); | 331 | let data = db.struct_data(struct_id); |
314 | 332 | ||
315 | // 1. Check the structure name. | 333 | let non_camel_case_allowed = |
334 | self.allowed(db, struct_id.into(), allow::NON_CAMEL_CASE_TYPES); | ||
335 | let non_snake_case_allowed = self.allowed(db, struct_id.into(), allow::NON_SNAKE_CASE); | ||
336 | |||
337 | // Check the structure name. | ||
316 | let struct_name = data.name.to_string(); | 338 | let struct_name = data.name.to_string(); |
317 | let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) { | 339 | let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) { |
318 | let replacement = Replacement { | 340 | let replacement = Replacement { |
@@ -320,29 +342,35 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
320 | suggested_text: new_name, | 342 | suggested_text: new_name, |
321 | expected_case: CaseType::UpperCamelCase, | 343 | expected_case: CaseType::UpperCamelCase, |
322 | }; | 344 | }; |
323 | Some(replacement) | 345 | if non_camel_case_allowed { |
346 | None | ||
347 | } else { | ||
348 | Some(replacement) | ||
349 | } | ||
324 | } else { | 350 | } else { |
325 | None | 351 | None |
326 | }; | 352 | }; |
327 | 353 | ||
328 | // 2. Check the field names. | 354 | // Check the field names. |
329 | let mut struct_fields_replacements = Vec::new(); | 355 | let mut struct_fields_replacements = Vec::new(); |
330 | 356 | ||
331 | if let VariantData::Record(fields) = data.variant_data.as_ref() { | 357 | if !non_snake_case_allowed { |
332 | for (_, field) in fields.iter() { | 358 | if let VariantData::Record(fields) = data.variant_data.as_ref() { |
333 | let field_name = field.name.to_string(); | 359 | for (_, field) in fields.iter() { |
334 | if let Some(new_name) = to_lower_snake_case(&field_name) { | 360 | let field_name = field.name.to_string(); |
335 | let replacement = Replacement { | 361 | if let Some(new_name) = to_lower_snake_case(&field_name) { |
336 | current_name: field.name.clone(), | 362 | let replacement = Replacement { |
337 | suggested_text: new_name, | 363 | current_name: field.name.clone(), |
338 | expected_case: CaseType::LowerSnakeCase, | 364 | suggested_text: new_name, |
339 | }; | 365 | expected_case: CaseType::LowerSnakeCase, |
340 | struct_fields_replacements.push(replacement); | 366 | }; |
367 | struct_fields_replacements.push(replacement); | ||
368 | } | ||
341 | } | 369 | } |
342 | } | 370 | } |
343 | } | 371 | } |
344 | 372 | ||
345 | // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | 373 | // If there is at least one element to spawn a warning on, go to the source map and generate a warning. |
346 | self.create_incorrect_case_diagnostic_for_struct( | 374 | self.create_incorrect_case_diagnostic_for_struct( |
347 | struct_id, | 375 | struct_id, |
348 | db, | 376 | db, |
@@ -442,7 +470,12 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
442 | fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) { | 470 | fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) { |
443 | let data = db.enum_data(enum_id); | 471 | let data = db.enum_data(enum_id); |
444 | 472 | ||
445 | // 1. Check the enum name. | 473 | // Check whether non-camel case names are allowed for this enum. |
474 | if self.allowed(db, enum_id.into(), allow::NON_CAMEL_CASE_TYPES) { | ||
475 | return; | ||
476 | } | ||
477 | |||
478 | // Check the enum name. | ||
446 | let enum_name = data.name.to_string(); | 479 | let enum_name = data.name.to_string(); |
447 | let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) { | 480 | let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) { |
448 | let replacement = Replacement { | 481 | let replacement = Replacement { |
@@ -455,7 +488,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
455 | None | 488 | None |
456 | }; | 489 | }; |
457 | 490 | ||
458 | // 2. Check the field names. | 491 | // Check the field names. |
459 | let mut enum_fields_replacements = Vec::new(); | 492 | let mut enum_fields_replacements = Vec::new(); |
460 | 493 | ||
461 | for (_, variant) in data.variants.iter() { | 494 | for (_, variant) in data.variants.iter() { |
@@ -470,7 +503,7 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
470 | } | 503 | } |
471 | } | 504 | } |
472 | 505 | ||
473 | // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning. | 506 | // If there is at least one element to spawn a warning on, go to the source map and generate a warning. |
474 | self.create_incorrect_case_diagnostic_for_enum( | 507 | self.create_incorrect_case_diagnostic_for_enum( |
475 | enum_id, | 508 | enum_id, |
476 | db, | 509 | db, |
@@ -572,6 +605,10 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
572 | fn validate_const(&mut self, db: &dyn HirDatabase, const_id: ConstId) { | 605 | fn validate_const(&mut self, db: &dyn HirDatabase, const_id: ConstId) { |
573 | let data = db.const_data(const_id); | 606 | let data = db.const_data(const_id); |
574 | 607 | ||
608 | if self.allowed(db, const_id.into(), allow::NON_UPPER_CASE_GLOBAL) { | ||
609 | return; | ||
610 | } | ||
611 | |||
575 | let name = match &data.name { | 612 | let name = match &data.name { |
576 | Some(name) => name, | 613 | Some(name) => name, |
577 | None => return, | 614 | None => return, |
@@ -612,6 +649,10 @@ impl<'a, 'b> DeclValidator<'a, 'b> { | |||
612 | fn validate_static(&mut self, db: &dyn HirDatabase, static_id: StaticId) { | 649 | fn validate_static(&mut self, db: &dyn HirDatabase, static_id: StaticId) { |
613 | let data = db.static_data(static_id); | 650 | let data = db.static_data(static_id); |
614 | 651 | ||
652 | if self.allowed(db, static_id.into(), allow::NON_UPPER_CASE_GLOBAL) { | ||
653 | return; | ||
654 | } | ||
655 | |||
615 | let name = match &data.name { | 656 | let name = match &data.name { |
616 | Some(name) => name, | 657 | Some(name) => name, |
617 | None => return, | 658 | None => return, |
@@ -854,4 +895,29 @@ fn main() { | |||
854 | "#, | 895 | "#, |
855 | ); | 896 | ); |
856 | } | 897 | } |
898 | |||
899 | #[test] | ||
900 | fn allow_attributes() { | ||
901 | check_diagnostics( | ||
902 | r#" | ||
903 | #[allow(non_snake_case)] | ||
904 | fn NonSnakeCaseName(SOME_VAR: u8) -> u8{ | ||
905 | let OtherVar = SOME_VAR + 1; | ||
906 | OtherVar | ||
907 | } | ||
908 | |||
909 | #[allow(non_snake_case, non_camel_case_types)] | ||
910 | pub struct some_type { | ||
911 | SOME_FIELD: u8, | ||
912 | SomeField: u16, | ||
913 | } | ||
914 | |||
915 | #[allow(non_upper_case_globals)] | ||
916 | pub const some_const: u8 = 10; | ||
917 | |||
918 | #[allow(non_upper_case_globals)] | ||
919 | pub static SomeStatic: u8 = 10; | ||
920 | "#, | ||
921 | ); | ||
922 | } | ||
857 | } | 923 | } |
diff --git a/editors/code/rust.tmGrammar.json b/editors/code/rust.tmGrammar.json index b3eec327b..77595aa00 100644 --- a/editors/code/rust.tmGrammar.json +++ b/editors/code/rust.tmGrammar.json | |||
@@ -462,7 +462,7 @@ | |||
462 | "name": "punctuation.brackets.angle.rust" | 462 | "name": "punctuation.brackets.angle.rust" |
463 | } | 463 | } |
464 | }, | 464 | }, |
465 | "end": "\\{", | 465 | "end": "\\{|;", |
466 | "endCaptures": { | 466 | "endCaptures": { |
467 | "0": { | 467 | "0": { |
468 | "name": "punctuation.brackets.curly.rust" | 468 | "name": "punctuation.brackets.curly.rust" |
@@ -545,9 +545,66 @@ | |||
545 | "include": "#lvariables" | 545 | "include": "#lvariables" |
546 | }, | 546 | }, |
547 | { | 547 | { |
548 | "include": "#constants" | ||
549 | }, | ||
550 | { | ||
551 | "include": "#gtypes" | ||
552 | }, | ||
553 | { | ||
554 | "include": "#functions" | ||
555 | }, | ||
556 | { | ||
557 | "include": "#lifetimes" | ||
558 | }, | ||
559 | { | ||
560 | "include": "#macros" | ||
561 | }, | ||
562 | { | ||
548 | "include": "#namespaces" | 563 | "include": "#namespaces" |
549 | }, | 564 | }, |
550 | { | 565 | { |
566 | "include": "#punctuation" | ||
567 | }, | ||
568 | { | ||
569 | "include": "#strings" | ||
570 | }, | ||
571 | { | ||
572 | "include": "#types" | ||
573 | }, | ||
574 | { | ||
575 | "include": "#variables" | ||
576 | } | ||
577 | ] | ||
578 | }, | ||
579 | { | ||
580 | "comment": "function/method calls with turbofish", | ||
581 | "name": "meta.function.call.rust", | ||
582 | "begin": "((?:r#(?!crate|[Ss]elf|super))?[A-Za-z0-9_]+)(?=::<.*>\\()", | ||
583 | "beginCaptures": { | ||
584 | "1": { | ||
585 | "name": "entity.name.function.rust" | ||
586 | } | ||
587 | }, | ||
588 | "end": "\\)", | ||
589 | "endCaptures": { | ||
590 | "0": { | ||
591 | "name": "punctuation.brackets.round.rust" | ||
592 | } | ||
593 | }, | ||
594 | "patterns": [ | ||
595 | { | ||
596 | "include": "#block-comments" | ||
597 | }, | ||
598 | { | ||
599 | "include": "#comments" | ||
600 | }, | ||
601 | { | ||
602 | "include": "#keywords" | ||
603 | }, | ||
604 | { | ||
605 | "include": "#lvariables" | ||
606 | }, | ||
607 | { | ||
551 | "include": "#constants" | 608 | "include": "#constants" |
552 | }, | 609 | }, |
553 | { | 610 | { |
@@ -563,6 +620,9 @@ | |||
563 | "include": "#macros" | 620 | "include": "#macros" |
564 | }, | 621 | }, |
565 | { | 622 | { |
623 | "include": "#namespaces" | ||
624 | }, | ||
625 | { | ||
566 | "include": "#punctuation" | 626 | "include": "#punctuation" |
567 | }, | 627 | }, |
568 | { | 628 | { |