diff options
Diffstat (limited to 'crates/ide_completion/src/render.rs')
-rw-r--r-- | crates/ide_completion/src/render.rs | 945 |
1 files changed, 945 insertions, 0 deletions
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs new file mode 100644 index 000000000..eddaaa6f3 --- /dev/null +++ b/crates/ide_completion/src/render.rs | |||
@@ -0,0 +1,945 @@ | |||
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 pattern; | ||
9 | pub(crate) mod type_alias; | ||
10 | |||
11 | mod builder_ext; | ||
12 | |||
13 | use hir::{ | ||
14 | AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, | ||
15 | }; | ||
16 | use ide_db::{helpers::SnippetCap, RootDatabase, SymbolKind}; | ||
17 | use syntax::TextRange; | ||
18 | use test_utils::mark; | ||
19 | |||
20 | use crate::{ | ||
21 | item::ImportEdit, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, | ||
22 | CompletionScore, | ||
23 | }; | ||
24 | |||
25 | use crate::render::{enum_variant::render_variant, function::render_fn, macro_::render_macro}; | ||
26 | |||
27 | pub(crate) fn render_field<'a>( | ||
28 | ctx: RenderContext<'a>, | ||
29 | field: hir::Field, | ||
30 | ty: &Type, | ||
31 | ) -> CompletionItem { | ||
32 | Render::new(ctx).add_field(field, ty) | ||
33 | } | ||
34 | |||
35 | pub(crate) fn render_tuple_field<'a>( | ||
36 | ctx: RenderContext<'a>, | ||
37 | field: usize, | ||
38 | ty: &Type, | ||
39 | ) -> CompletionItem { | ||
40 | Render::new(ctx).add_tuple_field(field, ty) | ||
41 | } | ||
42 | |||
43 | pub(crate) fn render_resolution<'a>( | ||
44 | ctx: RenderContext<'a>, | ||
45 | local_name: String, | ||
46 | resolution: &ScopeDef, | ||
47 | ) -> Option<CompletionItem> { | ||
48 | Render::new(ctx).render_resolution(local_name, None, resolution) | ||
49 | } | ||
50 | |||
51 | pub(crate) fn render_resolution_with_import<'a>( | ||
52 | ctx: RenderContext<'a>, | ||
53 | import_edit: ImportEdit, | ||
54 | resolution: &ScopeDef, | ||
55 | ) -> Option<CompletionItem> { | ||
56 | let local_name = match resolution { | ||
57 | ScopeDef::ModuleDef(ModuleDef::Function(f)) => f.name(ctx.completion.db).to_string(), | ||
58 | ScopeDef::ModuleDef(ModuleDef::Const(c)) => c.name(ctx.completion.db)?.to_string(), | ||
59 | ScopeDef::ModuleDef(ModuleDef::TypeAlias(t)) => t.name(ctx.completion.db).to_string(), | ||
60 | _ => import_edit.import_path.segments().last()?.to_string(), | ||
61 | }; | ||
62 | Render::new(ctx).render_resolution(local_name, Some(import_edit), resolution).map(|mut item| { | ||
63 | item.completion_kind = CompletionKind::Magic; | ||
64 | item | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | /// Interface for data and methods required for items rendering. | ||
69 | #[derive(Debug)] | ||
70 | pub(crate) struct RenderContext<'a> { | ||
71 | completion: &'a CompletionContext<'a>, | ||
72 | } | ||
73 | |||
74 | impl<'a> RenderContext<'a> { | ||
75 | pub(crate) fn new(completion: &'a CompletionContext<'a>) -> RenderContext<'a> { | ||
76 | RenderContext { completion } | ||
77 | } | ||
78 | |||
79 | fn snippet_cap(&self) -> Option<SnippetCap> { | ||
80 | self.completion.config.snippet_cap.clone() | ||
81 | } | ||
82 | |||
83 | fn db(&self) -> &'a RootDatabase { | ||
84 | &self.completion.db | ||
85 | } | ||
86 | |||
87 | fn source_range(&self) -> TextRange { | ||
88 | self.completion.source_range() | ||
89 | } | ||
90 | |||
91 | fn is_deprecated(&self, node: impl HasAttrs) -> bool { | ||
92 | let attrs = node.attrs(self.db()); | ||
93 | attrs.by_key("deprecated").exists() || attrs.by_key("rustc_deprecated").exists() | ||
94 | } | ||
95 | |||
96 | fn is_deprecated_assoc_item(&self, as_assoc_item: impl AsAssocItem) -> bool { | ||
97 | let db = self.db(); | ||
98 | let assoc = match as_assoc_item.as_assoc_item(db) { | ||
99 | Some(assoc) => assoc, | ||
100 | None => return false, | ||
101 | }; | ||
102 | |||
103 | let is_assoc_deprecated = match assoc { | ||
104 | hir::AssocItem::Function(it) => self.is_deprecated(it), | ||
105 | hir::AssocItem::Const(it) => self.is_deprecated(it), | ||
106 | hir::AssocItem::TypeAlias(it) => self.is_deprecated(it), | ||
107 | }; | ||
108 | is_assoc_deprecated | ||
109 | || assoc.containing_trait(db).map(|trait_| self.is_deprecated(trait_)).unwrap_or(false) | ||
110 | } | ||
111 | |||
112 | fn docs(&self, node: impl HasAttrs) -> Option<Documentation> { | ||
113 | node.docs(self.db()) | ||
114 | } | ||
115 | |||
116 | fn active_name_and_type(&self) -> Option<(String, Type)> { | ||
117 | if let Some(record_field) = &self.completion.record_field_syntax { | ||
118 | mark::hit!(record_field_type_match); | ||
119 | let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?; | ||
120 | Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db()))) | ||
121 | } else if let Some(active_parameter) = &self.completion.active_parameter { | ||
122 | mark::hit!(active_param_type_match); | ||
123 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | ||
124 | } else { | ||
125 | None | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | |||
130 | /// Generic renderer for completion items. | ||
131 | #[derive(Debug)] | ||
132 | struct Render<'a> { | ||
133 | ctx: RenderContext<'a>, | ||
134 | } | ||
135 | |||
136 | impl<'a> Render<'a> { | ||
137 | fn new(ctx: RenderContext<'a>) -> Render<'a> { | ||
138 | Render { ctx } | ||
139 | } | ||
140 | |||
141 | fn add_field(&mut self, field: hir::Field, ty: &Type) -> CompletionItem { | ||
142 | let is_deprecated = self.ctx.is_deprecated(field); | ||
143 | let name = field.name(self.ctx.db()); | ||
144 | let mut item = CompletionItem::new( | ||
145 | CompletionKind::Reference, | ||
146 | self.ctx.source_range(), | ||
147 | name.to_string(), | ||
148 | ) | ||
149 | .kind(SymbolKind::Field) | ||
150 | .detail(ty.display(self.ctx.db()).to_string()) | ||
151 | .set_documentation(field.docs(self.ctx.db())) | ||
152 | .set_deprecated(is_deprecated); | ||
153 | |||
154 | if let Some(score) = compute_score(&self.ctx, &ty, &name.to_string()) { | ||
155 | item = item.set_score(score); | ||
156 | } | ||
157 | |||
158 | item.build() | ||
159 | } | ||
160 | |||
161 | fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { | ||
162 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), field.to_string()) | ||
163 | .kind(SymbolKind::Field) | ||
164 | .detail(ty.display(self.ctx.db()).to_string()) | ||
165 | .build() | ||
166 | } | ||
167 | |||
168 | fn render_resolution( | ||
169 | self, | ||
170 | local_name: String, | ||
171 | import_to_add: Option<ImportEdit>, | ||
172 | resolution: &ScopeDef, | ||
173 | ) -> Option<CompletionItem> { | ||
174 | let _p = profile::span("render_resolution"); | ||
175 | use hir::ModuleDef::*; | ||
176 | |||
177 | let completion_kind = match resolution { | ||
178 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, | ||
179 | _ => CompletionKind::Reference, | ||
180 | }; | ||
181 | |||
182 | let kind = match resolution { | ||
183 | ScopeDef::ModuleDef(Function(func)) => { | ||
184 | return render_fn(self.ctx, import_to_add, Some(local_name), *func); | ||
185 | } | ||
186 | ScopeDef::ModuleDef(Variant(_)) | ||
187 | if self.ctx.completion.is_pat_binding_or_const | ||
188 | | self.ctx.completion.is_irrefutable_pat_binding => | ||
189 | { | ||
190 | CompletionItemKind::SymbolKind(SymbolKind::Variant) | ||
191 | } | ||
192 | ScopeDef::ModuleDef(Variant(var)) => { | ||
193 | let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None); | ||
194 | return Some(item); | ||
195 | } | ||
196 | ScopeDef::MacroDef(mac) => { | ||
197 | let item = render_macro(self.ctx, import_to_add, local_name, *mac); | ||
198 | return item; | ||
199 | } | ||
200 | |||
201 | ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::SymbolKind(SymbolKind::Module), | ||
202 | ScopeDef::ModuleDef(Adt(adt)) => CompletionItemKind::SymbolKind(match adt { | ||
203 | hir::Adt::Struct(_) => SymbolKind::Struct, | ||
204 | hir::Adt::Union(_) => SymbolKind::Union, | ||
205 | hir::Adt::Enum(_) => SymbolKind::Enum, | ||
206 | }), | ||
207 | ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::SymbolKind(SymbolKind::Const), | ||
208 | ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::SymbolKind(SymbolKind::Static), | ||
209 | ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::SymbolKind(SymbolKind::Trait), | ||
210 | ScopeDef::ModuleDef(TypeAlias(..)) => { | ||
211 | CompletionItemKind::SymbolKind(SymbolKind::TypeAlias) | ||
212 | } | ||
213 | ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, | ||
214 | ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param { | ||
215 | hir::GenericParam::TypeParam(_) => SymbolKind::TypeParam, | ||
216 | hir::GenericParam::LifetimeParam(_) => SymbolKind::LifetimeParam, | ||
217 | hir::GenericParam::ConstParam(_) => SymbolKind::ConstParam, | ||
218 | }), | ||
219 | ScopeDef::Local(..) => CompletionItemKind::SymbolKind(SymbolKind::Local), | ||
220 | ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => { | ||
221 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) | ||
222 | } | ||
223 | ScopeDef::Unknown => { | ||
224 | let item = CompletionItem::new( | ||
225 | CompletionKind::Reference, | ||
226 | self.ctx.source_range(), | ||
227 | local_name, | ||
228 | ) | ||
229 | .kind(CompletionItemKind::UnresolvedReference) | ||
230 | .add_import(import_to_add) | ||
231 | .build(); | ||
232 | return Some(item); | ||
233 | } | ||
234 | }; | ||
235 | |||
236 | let mut item = | ||
237 | CompletionItem::new(completion_kind, self.ctx.source_range(), local_name.clone()); | ||
238 | if let ScopeDef::Local(local) = resolution { | ||
239 | let ty = local.ty(self.ctx.db()); | ||
240 | if !ty.is_unknown() { | ||
241 | item = item.detail(ty.display(self.ctx.db()).to_string()); | ||
242 | } | ||
243 | }; | ||
244 | |||
245 | let mut ref_match = None; | ||
246 | if let ScopeDef::Local(local) = resolution { | ||
247 | if let Some((active_name, active_type)) = self.ctx.active_name_and_type() { | ||
248 | let ty = local.ty(self.ctx.db()); | ||
249 | if let Some(score) = | ||
250 | compute_score_from_active(&active_type, &active_name, &ty, &local_name) | ||
251 | { | ||
252 | item = item.set_score(score); | ||
253 | } | ||
254 | ref_match = refed_type_matches(&active_type, &active_name, &ty, &local_name); | ||
255 | } | ||
256 | } | ||
257 | |||
258 | // Add `<>` for generic types | ||
259 | if self.ctx.completion.is_path_type | ||
260 | && !self.ctx.completion.has_type_args | ||
261 | && self.ctx.completion.config.add_call_parenthesis | ||
262 | { | ||
263 | if let Some(cap) = self.ctx.snippet_cap() { | ||
264 | let has_non_default_type_params = match resolution { | ||
265 | ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(self.ctx.db()), | ||
266 | ScopeDef::ModuleDef(TypeAlias(it)) => { | ||
267 | it.has_non_default_type_params(self.ctx.db()) | ||
268 | } | ||
269 | _ => false, | ||
270 | }; | ||
271 | if has_non_default_type_params { | ||
272 | mark::hit!(inserts_angle_brackets_for_generics); | ||
273 | item = item | ||
274 | .lookup_by(local_name.clone()) | ||
275 | .label(format!("{}<…>", local_name)) | ||
276 | .insert_snippet(cap, format!("{}<$0>", local_name)); | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | Some( | ||
282 | item.kind(kind) | ||
283 | .add_import(import_to_add) | ||
284 | .set_ref_match(ref_match) | ||
285 | .set_documentation(self.docs(resolution)) | ||
286 | .set_deprecated(self.is_deprecated(resolution)) | ||
287 | .build(), | ||
288 | ) | ||
289 | } | ||
290 | |||
291 | fn docs(&self, resolution: &ScopeDef) -> Option<Documentation> { | ||
292 | use hir::ModuleDef::*; | ||
293 | match resolution { | ||
294 | ScopeDef::ModuleDef(Module(it)) => it.docs(self.ctx.db()), | ||
295 | ScopeDef::ModuleDef(Adt(it)) => it.docs(self.ctx.db()), | ||
296 | ScopeDef::ModuleDef(Variant(it)) => it.docs(self.ctx.db()), | ||
297 | ScopeDef::ModuleDef(Const(it)) => it.docs(self.ctx.db()), | ||
298 | ScopeDef::ModuleDef(Static(it)) => it.docs(self.ctx.db()), | ||
299 | ScopeDef::ModuleDef(Trait(it)) => it.docs(self.ctx.db()), | ||
300 | ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(self.ctx.db()), | ||
301 | _ => None, | ||
302 | } | ||
303 | } | ||
304 | |||
305 | fn is_deprecated(&self, resolution: &ScopeDef) -> bool { | ||
306 | match resolution { | ||
307 | ScopeDef::ModuleDef(it) => self.ctx.is_deprecated_assoc_item(*it), | ||
308 | ScopeDef::MacroDef(it) => self.ctx.is_deprecated(*it), | ||
309 | ScopeDef::GenericParam(it) => self.ctx.is_deprecated(*it), | ||
310 | ScopeDef::AdtSelfType(it) => self.ctx.is_deprecated(*it), | ||
311 | _ => false, | ||
312 | } | ||
313 | } | ||
314 | } | ||
315 | |||
316 | fn compute_score_from_active( | ||
317 | active_type: &Type, | ||
318 | active_name: &str, | ||
319 | ty: &Type, | ||
320 | name: &str, | ||
321 | ) -> Option<CompletionScore> { | ||
322 | // Compute score | ||
323 | // For the same type | ||
324 | if active_type != ty { | ||
325 | return None; | ||
326 | } | ||
327 | |||
328 | let mut res = CompletionScore::TypeMatch; | ||
329 | |||
330 | // If same type + same name then go top position | ||
331 | if active_name == name { | ||
332 | res = CompletionScore::TypeAndNameMatch | ||
333 | } | ||
334 | |||
335 | Some(res) | ||
336 | } | ||
337 | fn refed_type_matches( | ||
338 | active_type: &Type, | ||
339 | active_name: &str, | ||
340 | ty: &Type, | ||
341 | name: &str, | ||
342 | ) -> Option<(Mutability, CompletionScore)> { | ||
343 | let derefed_active = active_type.remove_ref()?; | ||
344 | let score = compute_score_from_active(&derefed_active, &active_name, &ty, &name)?; | ||
345 | Some(( | ||
346 | if active_type.is_mutable_reference() { Mutability::Mut } else { Mutability::Shared }, | ||
347 | score, | ||
348 | )) | ||
349 | } | ||
350 | |||
351 | fn compute_score(ctx: &RenderContext, ty: &Type, name: &str) -> Option<CompletionScore> { | ||
352 | let (active_name, active_type) = ctx.active_name_and_type()?; | ||
353 | compute_score_from_active(&active_type, &active_name, ty, name) | ||
354 | } | ||
355 | |||
356 | #[cfg(test)] | ||
357 | mod tests { | ||
358 | use std::cmp::Reverse; | ||
359 | |||
360 | use expect_test::{expect, Expect}; | ||
361 | use test_utils::mark; | ||
362 | |||
363 | use crate::{ | ||
364 | test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG}, | ||
365 | CompletionKind, CompletionScore, | ||
366 | }; | ||
367 | |||
368 | fn check(ra_fixture: &str, expect: Expect) { | ||
369 | let actual = do_completion(ra_fixture, CompletionKind::Reference); | ||
370 | expect.assert_debug_eq(&actual); | ||
371 | } | ||
372 | |||
373 | fn check_scores(ra_fixture: &str, expect: Expect) { | ||
374 | fn display_score(score: Option<CompletionScore>) -> &'static str { | ||
375 | match score { | ||
376 | Some(CompletionScore::TypeMatch) => "[type]", | ||
377 | Some(CompletionScore::TypeAndNameMatch) => "[type+name]", | ||
378 | None => "[]".into(), | ||
379 | } | ||
380 | } | ||
381 | |||
382 | let mut completions = get_all_items(TEST_CONFIG, ra_fixture); | ||
383 | completions.sort_by_key(|it| (Reverse(it.score()), it.label().to_string())); | ||
384 | let actual = completions | ||
385 | .into_iter() | ||
386 | .filter(|it| it.completion_kind == CompletionKind::Reference) | ||
387 | .map(|it| { | ||
388 | let tag = it.kind().unwrap().tag(); | ||
389 | let score = display_score(it.score()); | ||
390 | format!("{} {} {}\n", tag, it.label(), score) | ||
391 | }) | ||
392 | .collect::<String>(); | ||
393 | expect.assert_eq(&actual); | ||
394 | } | ||
395 | |||
396 | #[test] | ||
397 | fn enum_detail_includes_record_fields() { | ||
398 | check( | ||
399 | r#" | ||
400 | enum Foo { Foo { x: i32, y: i32 } } | ||
401 | |||
402 | fn main() { Foo::Fo$0 } | ||
403 | "#, | ||
404 | expect![[r#" | ||
405 | [ | ||
406 | CompletionItem { | ||
407 | label: "Foo", | ||
408 | source_range: 54..56, | ||
409 | delete: 54..56, | ||
410 | insert: "Foo", | ||
411 | kind: SymbolKind( | ||
412 | Variant, | ||
413 | ), | ||
414 | detail: "{ x: i32, y: i32 }", | ||
415 | }, | ||
416 | ] | ||
417 | "#]], | ||
418 | ); | ||
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn enum_detail_doesnt_include_tuple_fields() { | ||
423 | check( | ||
424 | r#" | ||
425 | enum Foo { Foo (i32, i32) } | ||
426 | |||
427 | fn main() { Foo::Fo$0 } | ||
428 | "#, | ||
429 | expect![[r#" | ||
430 | [ | ||
431 | CompletionItem { | ||
432 | label: "Foo(…)", | ||
433 | source_range: 46..48, | ||
434 | delete: 46..48, | ||
435 | insert: "Foo($0)", | ||
436 | kind: SymbolKind( | ||
437 | Variant, | ||
438 | ), | ||
439 | lookup: "Foo", | ||
440 | detail: "(i32, i32)", | ||
441 | trigger_call_info: true, | ||
442 | }, | ||
443 | ] | ||
444 | "#]], | ||
445 | ); | ||
446 | } | ||
447 | |||
448 | #[test] | ||
449 | fn enum_detail_just_parentheses_for_unit() { | ||
450 | check( | ||
451 | r#" | ||
452 | enum Foo { Foo } | ||
453 | |||
454 | fn main() { Foo::Fo$0 } | ||
455 | "#, | ||
456 | expect![[r#" | ||
457 | [ | ||
458 | CompletionItem { | ||
459 | label: "Foo", | ||
460 | source_range: 35..37, | ||
461 | delete: 35..37, | ||
462 | insert: "Foo", | ||
463 | kind: SymbolKind( | ||
464 | Variant, | ||
465 | ), | ||
466 | detail: "()", | ||
467 | }, | ||
468 | ] | ||
469 | "#]], | ||
470 | ); | ||
471 | } | ||
472 | |||
473 | #[test] | ||
474 | fn lookup_enums_by_two_qualifiers() { | ||
475 | check( | ||
476 | r#" | ||
477 | mod m { | ||
478 | pub enum Spam { Foo, Bar(i32) } | ||
479 | } | ||
480 | fn main() { let _: m::Spam = S$0 } | ||
481 | "#, | ||
482 | expect![[r#" | ||
483 | [ | ||
484 | CompletionItem { | ||
485 | label: "Spam::Bar(…)", | ||
486 | source_range: 75..76, | ||
487 | delete: 75..76, | ||
488 | insert: "Spam::Bar($0)", | ||
489 | kind: SymbolKind( | ||
490 | Variant, | ||
491 | ), | ||
492 | lookup: "Spam::Bar", | ||
493 | detail: "(i32)", | ||
494 | trigger_call_info: true, | ||
495 | }, | ||
496 | CompletionItem { | ||
497 | label: "m", | ||
498 | source_range: 75..76, | ||
499 | delete: 75..76, | ||
500 | insert: "m", | ||
501 | kind: SymbolKind( | ||
502 | Module, | ||
503 | ), | ||
504 | }, | ||
505 | CompletionItem { | ||
506 | label: "m::Spam::Foo", | ||
507 | source_range: 75..76, | ||
508 | delete: 75..76, | ||
509 | insert: "m::Spam::Foo", | ||
510 | kind: SymbolKind( | ||
511 | Variant, | ||
512 | ), | ||
513 | lookup: "Spam::Foo", | ||
514 | detail: "()", | ||
515 | }, | ||
516 | CompletionItem { | ||
517 | label: "main()", | ||
518 | source_range: 75..76, | ||
519 | delete: 75..76, | ||
520 | insert: "main()$0", | ||
521 | kind: SymbolKind( | ||
522 | Function, | ||
523 | ), | ||
524 | lookup: "main", | ||
525 | detail: "-> ()", | ||
526 | }, | ||
527 | ] | ||
528 | "#]], | ||
529 | ) | ||
530 | } | ||
531 | |||
532 | #[test] | ||
533 | fn sets_deprecated_flag_in_items() { | ||
534 | check( | ||
535 | r#" | ||
536 | #[deprecated] | ||
537 | fn something_deprecated() {} | ||
538 | #[rustc_deprecated(since = "1.0.0")] | ||
539 | fn something_else_deprecated() {} | ||
540 | |||
541 | fn main() { som$0 } | ||
542 | "#, | ||
543 | expect![[r#" | ||
544 | [ | ||
545 | CompletionItem { | ||
546 | label: "main()", | ||
547 | source_range: 127..130, | ||
548 | delete: 127..130, | ||
549 | insert: "main()$0", | ||
550 | kind: SymbolKind( | ||
551 | Function, | ||
552 | ), | ||
553 | lookup: "main", | ||
554 | detail: "-> ()", | ||
555 | }, | ||
556 | CompletionItem { | ||
557 | label: "something_deprecated()", | ||
558 | source_range: 127..130, | ||
559 | delete: 127..130, | ||
560 | insert: "something_deprecated()$0", | ||
561 | kind: SymbolKind( | ||
562 | Function, | ||
563 | ), | ||
564 | lookup: "something_deprecated", | ||
565 | detail: "-> ()", | ||
566 | deprecated: true, | ||
567 | }, | ||
568 | CompletionItem { | ||
569 | label: "something_else_deprecated()", | ||
570 | source_range: 127..130, | ||
571 | delete: 127..130, | ||
572 | insert: "something_else_deprecated()$0", | ||
573 | kind: SymbolKind( | ||
574 | Function, | ||
575 | ), | ||
576 | lookup: "something_else_deprecated", | ||
577 | detail: "-> ()", | ||
578 | deprecated: true, | ||
579 | }, | ||
580 | ] | ||
581 | "#]], | ||
582 | ); | ||
583 | |||
584 | check( | ||
585 | r#" | ||
586 | struct A { #[deprecated] the_field: u32 } | ||
587 | fn foo() { A { the$0 } } | ||
588 | "#, | ||
589 | expect![[r#" | ||
590 | [ | ||
591 | CompletionItem { | ||
592 | label: "the_field", | ||
593 | source_range: 57..60, | ||
594 | delete: 57..60, | ||
595 | insert: "the_field", | ||
596 | kind: SymbolKind( | ||
597 | Field, | ||
598 | ), | ||
599 | detail: "u32", | ||
600 | deprecated: true, | ||
601 | }, | ||
602 | ] | ||
603 | "#]], | ||
604 | ); | ||
605 | } | ||
606 | |||
607 | #[test] | ||
608 | fn renders_docs() { | ||
609 | check( | ||
610 | r#" | ||
611 | struct S { | ||
612 | /// Field docs | ||
613 | foo: | ||
614 | } | ||
615 | impl S { | ||
616 | /// Method docs | ||
617 | fn bar(self) { self.$0 } | ||
618 | }"#, | ||
619 | expect![[r#" | ||
620 | [ | ||
621 | CompletionItem { | ||
622 | label: "bar()", | ||
623 | source_range: 94..94, | ||
624 | delete: 94..94, | ||
625 | insert: "bar()$0", | ||
626 | kind: Method, | ||
627 | lookup: "bar", | ||
628 | detail: "-> ()", | ||
629 | documentation: Documentation( | ||
630 | "Method docs", | ||
631 | ), | ||
632 | }, | ||
633 | CompletionItem { | ||
634 | label: "foo", | ||
635 | source_range: 94..94, | ||
636 | delete: 94..94, | ||
637 | insert: "foo", | ||
638 | kind: SymbolKind( | ||
639 | Field, | ||
640 | ), | ||
641 | detail: "{unknown}", | ||
642 | documentation: Documentation( | ||
643 | "Field docs", | ||
644 | ), | ||
645 | }, | ||
646 | ] | ||
647 | "#]], | ||
648 | ); | ||
649 | |||
650 | check( | ||
651 | r#" | ||
652 | use self::my$0; | ||
653 | |||
654 | /// mod docs | ||
655 | mod my { } | ||
656 | |||
657 | /// enum docs | ||
658 | enum E { | ||
659 | /// variant docs | ||
660 | V | ||
661 | } | ||
662 | use self::E::*; | ||
663 | "#, | ||
664 | expect![[r#" | ||
665 | [ | ||
666 | CompletionItem { | ||
667 | label: "E", | ||
668 | source_range: 10..12, | ||
669 | delete: 10..12, | ||
670 | insert: "E", | ||
671 | kind: SymbolKind( | ||
672 | Enum, | ||
673 | ), | ||
674 | documentation: Documentation( | ||
675 | "enum docs", | ||
676 | ), | ||
677 | }, | ||
678 | CompletionItem { | ||
679 | label: "V", | ||
680 | source_range: 10..12, | ||
681 | delete: 10..12, | ||
682 | insert: "V", | ||
683 | kind: SymbolKind( | ||
684 | Variant, | ||
685 | ), | ||
686 | detail: "()", | ||
687 | documentation: Documentation( | ||
688 | "variant docs", | ||
689 | ), | ||
690 | }, | ||
691 | CompletionItem { | ||
692 | label: "my", | ||
693 | source_range: 10..12, | ||
694 | delete: 10..12, | ||
695 | insert: "my", | ||
696 | kind: SymbolKind( | ||
697 | Module, | ||
698 | ), | ||
699 | documentation: Documentation( | ||
700 | "mod docs", | ||
701 | ), | ||
702 | }, | ||
703 | ] | ||
704 | "#]], | ||
705 | ) | ||
706 | } | ||
707 | |||
708 | #[test] | ||
709 | fn dont_render_attrs() { | ||
710 | check( | ||
711 | r#" | ||
712 | struct S; | ||
713 | impl S { | ||
714 | #[inline] | ||
715 | fn the_method(&self) { } | ||
716 | } | ||
717 | fn foo(s: S) { s.$0 } | ||
718 | "#, | ||
719 | expect![[r#" | ||
720 | [ | ||
721 | CompletionItem { | ||
722 | label: "the_method()", | ||
723 | source_range: 81..81, | ||
724 | delete: 81..81, | ||
725 | insert: "the_method()$0", | ||
726 | kind: Method, | ||
727 | lookup: "the_method", | ||
728 | detail: "-> ()", | ||
729 | }, | ||
730 | ] | ||
731 | "#]], | ||
732 | ) | ||
733 | } | ||
734 | |||
735 | #[test] | ||
736 | fn no_call_parens_if_fn_ptr_needed() { | ||
737 | mark::check!(no_call_parens_if_fn_ptr_needed); | ||
738 | check_edit( | ||
739 | "foo", | ||
740 | r#" | ||
741 | fn foo(foo: u8, bar: u8) {} | ||
742 | struct ManualVtable { f: fn(u8, u8) } | ||
743 | |||
744 | fn main() -> ManualVtable { | ||
745 | ManualVtable { f: f$0 } | ||
746 | } | ||
747 | "#, | ||
748 | r#" | ||
749 | fn foo(foo: u8, bar: u8) {} | ||
750 | struct ManualVtable { f: fn(u8, u8) } | ||
751 | |||
752 | fn main() -> ManualVtable { | ||
753 | ManualVtable { f: foo } | ||
754 | } | ||
755 | "#, | ||
756 | ); | ||
757 | } | ||
758 | |||
759 | #[test] | ||
760 | fn no_parens_in_use_item() { | ||
761 | mark::check!(no_parens_in_use_item); | ||
762 | check_edit( | ||
763 | "foo", | ||
764 | r#" | ||
765 | mod m { pub fn foo() {} } | ||
766 | use crate::m::f$0; | ||
767 | "#, | ||
768 | r#" | ||
769 | mod m { pub fn foo() {} } | ||
770 | use crate::m::foo; | ||
771 | "#, | ||
772 | ); | ||
773 | } | ||
774 | |||
775 | #[test] | ||
776 | fn no_parens_in_call() { | ||
777 | check_edit( | ||
778 | "foo", | ||
779 | r#" | ||
780 | fn foo(x: i32) {} | ||
781 | fn main() { f$0(); } | ||
782 | "#, | ||
783 | r#" | ||
784 | fn foo(x: i32) {} | ||
785 | fn main() { foo(); } | ||
786 | "#, | ||
787 | ); | ||
788 | check_edit( | ||
789 | "foo", | ||
790 | r#" | ||
791 | struct Foo; | ||
792 | impl Foo { fn foo(&self){} } | ||
793 | fn f(foo: &Foo) { foo.f$0(); } | ||
794 | "#, | ||
795 | r#" | ||
796 | struct Foo; | ||
797 | impl Foo { fn foo(&self){} } | ||
798 | fn f(foo: &Foo) { foo.foo(); } | ||
799 | "#, | ||
800 | ); | ||
801 | } | ||
802 | |||
803 | #[test] | ||
804 | fn inserts_angle_brackets_for_generics() { | ||
805 | mark::check!(inserts_angle_brackets_for_generics); | ||
806 | check_edit( | ||
807 | "Vec", | ||
808 | r#" | ||
809 | struct Vec<T> {} | ||
810 | fn foo(xs: Ve$0) | ||
811 | "#, | ||
812 | r#" | ||
813 | struct Vec<T> {} | ||
814 | fn foo(xs: Vec<$0>) | ||
815 | "#, | ||
816 | ); | ||
817 | check_edit( | ||
818 | "Vec", | ||
819 | r#" | ||
820 | type Vec<T> = (T,); | ||
821 | fn foo(xs: Ve$0) | ||
822 | "#, | ||
823 | r#" | ||
824 | type Vec<T> = (T,); | ||
825 | fn foo(xs: Vec<$0>) | ||
826 | "#, | ||
827 | ); | ||
828 | check_edit( | ||
829 | "Vec", | ||
830 | r#" | ||
831 | struct Vec<T = i128> {} | ||
832 | fn foo(xs: Ve$0) | ||
833 | "#, | ||
834 | r#" | ||
835 | struct Vec<T = i128> {} | ||
836 | fn foo(xs: Vec) | ||
837 | "#, | ||
838 | ); | ||
839 | check_edit( | ||
840 | "Vec", | ||
841 | r#" | ||
842 | struct Vec<T> {} | ||
843 | fn foo(xs: Ve$0<i128>) | ||
844 | "#, | ||
845 | r#" | ||
846 | struct Vec<T> {} | ||
847 | fn foo(xs: Vec<i128>) | ||
848 | "#, | ||
849 | ); | ||
850 | } | ||
851 | |||
852 | #[test] | ||
853 | fn active_param_score() { | ||
854 | mark::check!(active_param_type_match); | ||
855 | check_scores( | ||
856 | r#" | ||
857 | struct S { foo: i64, bar: u32, baz: u32 } | ||
858 | fn test(bar: u32) { } | ||
859 | fn foo(s: S) { test(s.$0) } | ||
860 | "#, | ||
861 | expect![[r#" | ||
862 | fd bar [type+name] | ||
863 | fd baz [type] | ||
864 | fd foo [] | ||
865 | "#]], | ||
866 | ); | ||
867 | } | ||
868 | |||
869 | #[test] | ||
870 | fn record_field_scores() { | ||
871 | mark::check!(record_field_type_match); | ||
872 | check_scores( | ||
873 | r#" | ||
874 | struct A { foo: i64, bar: u32, baz: u32 } | ||
875 | struct B { x: (), y: f32, bar: u32 } | ||
876 | fn foo(a: A) { B { bar: a.$0 }; } | ||
877 | "#, | ||
878 | expect![[r#" | ||
879 | fd bar [type+name] | ||
880 | fd baz [type] | ||
881 | fd foo [] | ||
882 | "#]], | ||
883 | ) | ||
884 | } | ||
885 | |||
886 | #[test] | ||
887 | fn record_field_and_call_scores() { | ||
888 | check_scores( | ||
889 | r#" | ||
890 | struct A { foo: i64, bar: u32, baz: u32 } | ||
891 | struct B { x: (), y: f32, bar: u32 } | ||
892 | fn f(foo: i64) { } | ||
893 | fn foo(a: A) { B { bar: f(a.$0) }; } | ||
894 | "#, | ||
895 | expect![[r#" | ||
896 | fd foo [type+name] | ||
897 | fd bar [] | ||
898 | fd baz [] | ||
899 | "#]], | ||
900 | ); | ||
901 | check_scores( | ||
902 | r#" | ||
903 | struct A { foo: i64, bar: u32, baz: u32 } | ||
904 | struct B { x: (), y: f32, bar: u32 } | ||
905 | fn f(foo: i64) { } | ||
906 | fn foo(a: A) { f(B { bar: a.$0 }); } | ||
907 | "#, | ||
908 | expect![[r#" | ||
909 | fd bar [type+name] | ||
910 | fd baz [type] | ||
911 | fd foo [] | ||
912 | "#]], | ||
913 | ); | ||
914 | } | ||
915 | |||
916 | #[test] | ||
917 | fn prioritize_exact_ref_match() { | ||
918 | check_scores( | ||
919 | r#" | ||
920 | struct WorldSnapshot { _f: () }; | ||
921 | fn go(world: &WorldSnapshot) { go(w$0) } | ||
922 | "#, | ||
923 | expect![[r#" | ||
924 | lc world [type+name] | ||
925 | st WorldSnapshot [] | ||
926 | fn go(…) [] | ||
927 | "#]], | ||
928 | ); | ||
929 | } | ||
930 | |||
931 | #[test] | ||
932 | fn too_many_arguments() { | ||
933 | check_scores( | ||
934 | r#" | ||
935 | struct Foo; | ||
936 | fn f(foo: &Foo) { f(foo, w$0) } | ||
937 | "#, | ||
938 | expect![[r#" | ||
939 | st Foo [] | ||
940 | fn f(…) [] | ||
941 | lc foo [] | ||
942 | "#]], | ||
943 | ); | ||
944 | } | ||
945 | } | ||