aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/completion/src/completions.rs1371
-rw-r--r--crates/completion/src/context.rs13
-rw-r--r--crates/completion/src/lib.rs1
-rw-r--r--crates/completion/src/render.rs848
-rw-r--r--crates/completion/src/render/builder_ext.rs94
-rw-r--r--crates/completion/src/render/const_.rs55
-rw-r--r--crates/completion/src/render/enum_variant.rs180
-rw-r--r--crates/completion/src/render/function.rs303
-rw-r--r--crates/completion/src/render/macro_.rs216
-rw-r--r--crates/completion/src/render/type_alias.rs55
-rw-r--r--crates/hir_def/src/nameres/tests/mod_resolution.rs17
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check.rs126
-rw-r--r--editors/code/rust.tmGrammar.json62
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;
14pub(crate) mod trait_impl; 14pub(crate) mod trait_impl;
15pub(crate) mod mod_; 15pub(crate) mod mod_;
16 16
17use hir::{HasAttrs, HasSource, HirDisplay, ModPath, Mutability, ScopeDef, StructKind, Type}; 17use hir::{ModPath, ScopeDef, Type};
18use itertools::Itertools;
19use syntax::{ast::NameOwner, display::*};
20use test_utils::mark;
21 18
22use crate::{ 19use 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
397fn 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}
418fn 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
432fn 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
437enum Params {
438 Named(Vec<String>),
439 Anonymous(usize),
440}
441
442impl 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
455impl 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
511fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
512 node.attrs(db).by_key("deprecated").exists()
513}
514
515fn 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(&macro_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)]
544mod 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#"
587enum Foo { Foo { x: i32, y: i32 } }
588
589fn 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#"
610enum Foo { Foo (i32, i32) }
611
612fn 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#"
635enum Foo { Foo }
636
637fn 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#"
658mod m {
659 pub enum Spam { Foo, Bar(i32) }
660}
661fn 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]
710fn something_deprecated() {}
711#[deprecated(since = "1.0.0")]
712fn something_else_deprecated() {}
713
714fn 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#"
753struct A { #[deprecated] the_field: u32 }
754fn 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#"
776struct S {
777 /// Field docs
778 foo:
779}
780impl 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#"
815use self::my<|>;
816
817/// mod docs
818mod my { }
819
820/// enum docs
821enum E {
822 /// variant docs
823 V
824}
825use 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#"
869struct S;
870impl S {
871 #[inline]
872 fn the_method(&self) { }
873}
874fn 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#"
898fn no_args() {}
899fn main() { no_<|> }
900"#,
901 r#"
902fn no_args() {}
903fn main() { no_args()$0 }
904"#,
905 );
906
907 check_edit(
908 "with_args",
909 r#"
910fn with_args(x: i32, y: String) {}
911fn main() { with_<|> }
912"#,
913 r#"
914fn with_args(x: i32, y: String) {}
915fn main() { with_args(${1:x}, ${2:y})$0 }
916"#,
917 );
918
919 check_edit(
920 "foo",
921 r#"
922struct S;
923impl S {
924 fn foo(&self) {}
925}
926fn bar(s: &S) { s.f<|> }
927"#,
928 r#"
929struct S;
930impl S {
931 fn foo(&self) {}
932}
933fn bar(s: &S) { s.foo()$0 }
934"#,
935 );
936
937 check_edit(
938 "foo",
939 r#"
940struct S {}
941impl S {
942 fn foo(&self, x: i32) {}
943}
944fn bar(s: &S) {
945 s.f<|>
946}
947"#,
948 r#"
949struct S {}
950impl S {
951 fn foo(&self, x: i32) {}
952}
953fn 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#"
967fn with_args(x: i32, y: String) {}
968fn main() { with_<|> }
969"#,
970 r#"
971fn with_args(x: i32, y: String) {}
972fn main() { with_args($0) }
973"#,
974 );
975 }
976
977 #[test]
978 fn strips_underscores_from_args() {
979 check_edit(
980 "foo",
981 r#"
982fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
983fn main() { f<|> }
984"#,
985 r#"
986fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
987fn 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#"
997struct Foo {}
998fn ref_arg(x: &Foo) {}
999fn main() {
1000 let x = Foo {};
1001 ref_ar<|>
1002}
1003"#,
1004 r#"
1005struct Foo {}
1006fn ref_arg(x: &Foo) {}
1007fn 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#"
1020struct Foo {}
1021fn ref_arg(x: &mut Foo) {}
1022fn main() {
1023 let x = Foo {};
1024 ref_ar<|>
1025}
1026"#,
1027 r#"
1028struct Foo {}
1029fn ref_arg(x: &mut Foo) {}
1030fn 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#"
1043struct Foo {}
1044struct Bar {}
1045impl Bar {
1046 fn apply_foo(&self, x: &Foo) {}
1047}
1048
1049fn main() {
1050 let x = Foo {};
1051 let y = Bar {};
1052 y.<|>
1053}
1054"#,
1055 r#"
1056struct Foo {}
1057struct Bar {}
1058impl Bar {
1059 fn apply_foo(&self, x: &Foo) {}
1060}
1061
1062fn 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#"
1076fn take_mutably(mut x: &i32) {}
1077
1078fn main() {
1079 take_m<|>
1080}
1081"#,
1082 r#"
1083fn take_mutably(mut x: &i32) {}
1084
1085fn 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#"
1098enum Option<T> { Some(T), None }
1099use Option::*;
1100fn main() -> Option<i32> {
1101 Som<|>
1102}
1103"#,
1104 r#"
1105enum Option<T> { Some(T), None }
1106use Option::*;
1107fn main() -> Option<i32> {
1108 Some($0)
1109}
1110"#,
1111 );
1112 check_edit(
1113 "Some",
1114 r#"
1115enum Option<T> { Some(T), None }
1116use Option::*;
1117fn main(value: Option<i32>) {
1118 match value {
1119 Som<|>
1120 }
1121}
1122"#,
1123 r#"
1124enum Option<T> { Some(T), None }
1125use Option::*;
1126fn 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#"
1141enum E { Var(i32) }
1142fn main() {
1143 match E::Var(92) {
1144 E::<|>(92) => (),
1145 }
1146}
1147"#,
1148 r#"
1149enum E { Var(i32) }
1150fn 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#"
1165fn foo(foo: u8, bar: u8) {}
1166struct ManualVtable { f: fn(u8, u8) }
1167
1168fn main() -> ManualVtable {
1169 ManualVtable { f: f<|> }
1170}
1171"#,
1172 r#"
1173fn foo(foo: u8, bar: u8) {}
1174struct ManualVtable { f: fn(u8, u8) }
1175
1176fn 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#"
1189mod m { pub fn foo() {} }
1190use crate::m::f<|>;
1191"#,
1192 r#"
1193mod m { pub fn foo() {} }
1194use crate::m::foo;
1195"#,
1196 );
1197 }
1198
1199 #[test]
1200 fn no_parens_in_call() {
1201 check_edit(
1202 "foo",
1203 r#"
1204fn foo(x: i32) {}
1205fn main() { f<|>(); }
1206"#,
1207 r#"
1208fn foo(x: i32) {}
1209fn main() { foo(); }
1210"#,
1211 );
1212 check_edit(
1213 "foo",
1214 r#"
1215struct Foo;
1216impl Foo { fn foo(&self){} }
1217fn f(foo: &Foo) { foo.f<|>(); }
1218"#,
1219 r#"
1220struct Foo;
1221impl Foo { fn foo(&self){} }
1222fn 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#"
1233struct Vec<T> {}
1234fn foo(xs: Ve<|>)
1235"#,
1236 r#"
1237struct Vec<T> {}
1238fn foo(xs: Vec<$0>)
1239"#,
1240 );
1241 check_edit(
1242 "Vec",
1243 r#"
1244type Vec<T> = (T,);
1245fn foo(xs: Ve<|>)
1246"#,
1247 r#"
1248type Vec<T> = (T,);
1249fn foo(xs: Vec<$0>)
1250"#,
1251 );
1252 check_edit(
1253 "Vec",
1254 r#"
1255struct Vec<T = i128> {}
1256fn foo(xs: Ve<|>)
1257"#,
1258 r#"
1259struct Vec<T = i128> {}
1260fn foo(xs: Vec)
1261"#,
1262 );
1263 check_edit(
1264 "Vec",
1265 r#"
1266struct Vec<T> {}
1267fn foo(xs: Ve<|><i128>)
1268"#,
1269 r#"
1270struct Vec<T> {}
1271fn 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
1283use foo::<|>;
1284//- /foo/lib.rs crate:foo
1285#[macro_export]
1286macro_rules frobnicate { () => () }
1287"#,
1288 r#"
1289use foo::frobnicate;
1290"#,
1291 );
1292
1293 check_edit(
1294 "frobnicate!",
1295 r#"
1296macro_rules frobnicate { () => () }
1297fn main() { frob<|>!(); }
1298"#,
1299 r#"
1300macro_rules frobnicate { () => () }
1301fn 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#"
1311struct S { foo: i64, bar: u32, baz: u32 }
1312fn test(bar: u32) { }
1313fn 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#"
1328struct A { foo: i64, bar: u32, baz: u32 }
1329struct B { x: (), y: f32, bar: u32 }
1330fn 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#"
1344struct A { foo: i64, bar: u32, baz: u32 }
1345struct B { x: (), y: f32, bar: u32 }
1346fn f(foo: i64) { }
1347fn 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#"
1357struct A { foo: i64, bar: u32, baz: u32 }
1358struct B { x: (), y: f32, bar: u32 }
1359fn f(foo: i64) { }
1360fn 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#"
1374struct WorldSnapshot { _f: () };
1375fn 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#"
1389struct Foo;
1390fn 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/// ```
1413macro_rules! vec { () => {} }
1414
1415fn 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/// ```
1426macro_rules! vec { () => {} }
1427
1428fn 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 };`
1439macro_rules! foo { () => {} }
1440fn main() { <|> }
1441"#,
1442 r#"
1443/// Foo
1444///
1445/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
1446/// call as `let _=foo! { hello world };`
1447macro_rules! foo { () => {} }
1448fn 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;
7mod generated_lint_completions; 7mod generated_lint_completions;
8#[cfg(test)] 8#[cfg(test)]
9mod test_utils; 9mod test_utils;
10mod render;
10 11
11mod completions; 12mod 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
4pub(crate) mod macro_;
5pub(crate) mod function;
6pub(crate) mod enum_variant;
7pub(crate) mod const_;
8pub(crate) mod type_alias;
9
10mod builder_ext;
11
12use hir::{Documentation, HasAttrs, HirDisplay, Mutability, ScopeDef, Type};
13use ide_db::RootDatabase;
14use syntax::TextRange;
15use test_utils::mark;
16
17use crate::{
18 config::SnippetCap, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind,
19 CompletionScore,
20};
21
22use crate::render::{enum_variant::render_enum_variant, function::render_fn, macro_::render_macro};
23
24pub(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
32pub(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
40pub(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)]
50pub(crate) struct RenderContext<'a> {
51 completion: &'a CompletionContext<'a>,
52}
53
54impl<'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)]
95struct Render<'a> {
96 ctx: RenderContext<'a>,
97}
98
99impl<'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
249fn 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}
270fn 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
284fn 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)]
290mod 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#"
333enum Foo { Foo { x: i32, y: i32 } }
334
335fn 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#"
356enum Foo { Foo (i32, i32) }
357
358fn 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#"
381enum Foo { Foo }
382
383fn 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#"
404mod m {
405 pub enum Spam { Foo, Bar(i32) }
406}
407fn 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]
456fn something_deprecated() {}
457#[deprecated(since = "1.0.0")]
458fn something_else_deprecated() {}
459
460fn 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#"
499struct A { #[deprecated] the_field: u32 }
500fn 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#"
522struct S {
523 /// Field docs
524 foo:
525}
526impl 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#"
561use self::my<|>;
562
563/// mod docs
564mod my { }
565
566/// enum docs
567enum E {
568 /// variant docs
569 V
570}
571use 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#"
615struct S;
616impl S {
617 #[inline]
618 fn the_method(&self) { }
619}
620fn 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#"
644fn foo(foo: u8, bar: u8) {}
645struct ManualVtable { f: fn(u8, u8) }
646
647fn main() -> ManualVtable {
648 ManualVtable { f: f<|> }
649}
650"#,
651 r#"
652fn foo(foo: u8, bar: u8) {}
653struct ManualVtable { f: fn(u8, u8) }
654
655fn 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#"
668mod m { pub fn foo() {} }
669use crate::m::f<|>;
670"#,
671 r#"
672mod m { pub fn foo() {} }
673use crate::m::foo;
674"#,
675 );
676 }
677
678 #[test]
679 fn no_parens_in_call() {
680 check_edit(
681 "foo",
682 r#"
683fn foo(x: i32) {}
684fn main() { f<|>(); }
685"#,
686 r#"
687fn foo(x: i32) {}
688fn main() { foo(); }
689"#,
690 );
691 check_edit(
692 "foo",
693 r#"
694struct Foo;
695impl Foo { fn foo(&self){} }
696fn f(foo: &Foo) { foo.f<|>(); }
697"#,
698 r#"
699struct Foo;
700impl Foo { fn foo(&self){} }
701fn 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#"
712struct Vec<T> {}
713fn foo(xs: Ve<|>)
714"#,
715 r#"
716struct Vec<T> {}
717fn foo(xs: Vec<$0>)
718"#,
719 );
720 check_edit(
721 "Vec",
722 r#"
723type Vec<T> = (T,);
724fn foo(xs: Ve<|>)
725"#,
726 r#"
727type Vec<T> = (T,);
728fn foo(xs: Vec<$0>)
729"#,
730 );
731 check_edit(
732 "Vec",
733 r#"
734struct Vec<T = i128> {}
735fn foo(xs: Ve<|>)
736"#,
737 r#"
738struct Vec<T = i128> {}
739fn foo(xs: Vec)
740"#,
741 );
742 check_edit(
743 "Vec",
744 r#"
745struct Vec<T> {}
746fn foo(xs: Ve<|><i128>)
747"#,
748 r#"
749struct Vec<T> {}
750fn 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#"
760struct S { foo: i64, bar: u32, baz: u32 }
761fn test(bar: u32) { }
762fn 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#"
777struct A { foo: i64, bar: u32, baz: u32 }
778struct B { x: (), y: f32, bar: u32 }
779fn 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#"
793struct A { foo: i64, bar: u32, baz: u32 }
794struct B { x: (), y: f32, bar: u32 }
795fn f(foo: i64) { }
796fn 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#"
806struct A { foo: i64, bar: u32, baz: u32 }
807struct B { x: (), y: f32, bar: u32 }
808fn f(foo: i64) { }
809fn 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#"
823struct WorldSnapshot { _f: () };
824fn 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#"
838struct Foo;
839fn 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
3use itertools::Itertools;
4use test_utils::mark;
5
6use crate::{item::Builder, CompletionContext};
7
8pub(super) enum Params {
9 Named(Vec<String>),
10 Anonymous(usize),
11}
12
13impl 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
26impl 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
3use hir::HasSource;
4use syntax::{
5 ast::{Const, NameOwner},
6 display::const_label,
7};
8
9use crate::{
10 item::{CompletionItem, CompletionItemKind, CompletionKind},
11 render::RenderContext,
12};
13
14pub(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)]
22struct ConstRender<'a> {
23 ctx: RenderContext<'a>,
24 const_: hir::Const,
25 ast_node: Const,
26}
27
28impl<'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
3use hir::{HasAttrs, HirDisplay, ModPath, StructKind};
4use itertools::Itertools;
5use test_utils::mark;
6
7use crate::{
8 item::{CompletionItem, CompletionItemKind, CompletionKind},
9 render::{builder_ext::Params, RenderContext},
10};
11
12pub(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)]
22struct 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
32impl<'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)]
109mod 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#"
120enum Option<T> { Some(T), None }
121use Option::*;
122fn main() -> Option<i32> {
123 Som<|>
124}
125"#,
126 r#"
127enum Option<T> { Some(T), None }
128use Option::*;
129fn main() -> Option<i32> {
130 Some($0)
131}
132"#,
133 );
134 check_edit(
135 "Some",
136 r#"
137enum Option<T> { Some(T), None }
138use Option::*;
139fn main(value: Option<i32>) {
140 match value {
141 Som<|>
142 }
143}
144"#,
145 r#"
146enum Option<T> { Some(T), None }
147use Option::*;
148fn 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#"
163enum E { Var(i32) }
164fn main() {
165 match E::Var(92) {
166 E::<|>(92) => (),
167 }
168}
169"#,
170 r#"
171enum E { Var(i32) }
172fn 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
3use hir::{HasSource, Type};
4use syntax::{ast::Fn, display::function_declaration};
5
6use crate::{
7 item::{CompletionItem, CompletionItemKind, CompletionKind},
8 render::{builder_ext::Params, RenderContext},
9};
10
11pub(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)]
20struct FunctionRender<'a> {
21 ctx: RenderContext<'a>,
22 name: String,
23 fn_: hir::Function,
24 ast_node: Fn,
25}
26
27impl<'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)]
96mod 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#"
110fn no_args() {}
111fn main() { no_<|> }
112"#,
113 r#"
114fn no_args() {}
115fn main() { no_args()$0 }
116"#,
117 );
118
119 check_edit(
120 "with_args",
121 r#"
122fn with_args(x: i32, y: String) {}
123fn main() { with_<|> }
124"#,
125 r#"
126fn with_args(x: i32, y: String) {}
127fn main() { with_args(${1:x}, ${2:y})$0 }
128"#,
129 );
130
131 check_edit(
132 "foo",
133 r#"
134struct S;
135impl S {
136 fn foo(&self) {}
137}
138fn bar(s: &S) { s.f<|> }
139"#,
140 r#"
141struct S;
142impl S {
143 fn foo(&self) {}
144}
145fn bar(s: &S) { s.foo()$0 }
146"#,
147 );
148
149 check_edit(
150 "foo",
151 r#"
152struct S {}
153impl S {
154 fn foo(&self, x: i32) {}
155}
156fn bar(s: &S) {
157 s.f<|>
158}
159"#,
160 r#"
161struct S {}
162impl S {
163 fn foo(&self, x: i32) {}
164}
165fn 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#"
179fn with_args(x: i32, y: String) {}
180fn main() { with_<|> }
181"#,
182 r#"
183fn with_args(x: i32, y: String) {}
184fn main() { with_args($0) }
185"#,
186 );
187 }
188
189 #[test]
190 fn strips_underscores_from_args() {
191 check_edit(
192 "foo",
193 r#"
194fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
195fn main() { f<|> }
196"#,
197 r#"
198fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
199fn 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#"
209struct Foo {}
210fn ref_arg(x: &Foo) {}
211fn main() {
212 let x = Foo {};
213 ref_ar<|>
214}
215"#,
216 r#"
217struct Foo {}
218fn ref_arg(x: &Foo) {}
219fn 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#"
232struct Foo {}
233fn ref_arg(x: &mut Foo) {}
234fn main() {
235 let x = Foo {};
236 ref_ar<|>
237}
238"#,
239 r#"
240struct Foo {}
241fn ref_arg(x: &mut Foo) {}
242fn 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#"
255struct Foo {}
256struct Bar {}
257impl Bar {
258 fn apply_foo(&self, x: &Foo) {}
259}
260
261fn main() {
262 let x = Foo {};
263 let y = Bar {};
264 y.<|>
265}
266"#,
267 r#"
268struct Foo {}
269struct Bar {}
270impl Bar {
271 fn apply_foo(&self, x: &Foo) {}
272}
273
274fn 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#"
288fn take_mutably(mut x: &i32) {}
289
290fn main() {
291 take_m<|>
292}
293"#,
294 r#"
295fn take_mutably(mut x: &i32) {}
296
297fn 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
3use hir::{Documentation, HasSource};
4use syntax::display::macro_label;
5use test_utils::mark;
6
7use crate::{
8 item::{CompletionItem, CompletionItemKind, CompletionKind},
9 render::RenderContext,
10};
11
12pub(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)]
21struct 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
30impl<'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
100fn 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(&macro_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)]
129mod 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
141use foo::<|>;
142//- /foo/lib.rs crate:foo
143#[macro_export]
144macro_rules frobnicate { () => () }
145"#,
146 r#"
147use foo::frobnicate;
148"#,
149 );
150
151 check_edit(
152 "frobnicate!",
153 r#"
154macro_rules frobnicate { () => () }
155fn main() { frob<|>!(); }
156"#,
157 r#"
158macro_rules frobnicate { () => () }
159fn 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/// ```
177macro_rules! vec { () => {} }
178
179fn 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/// ```
190macro_rules! vec { () => {} }
191
192fn 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 };`
203macro_rules! foo { () => {} }
204fn main() { <|> }
205"#,
206 r#"
207/// Foo
208///
209/// Don't call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`,
210/// call as `let _=foo! { hello world };`
211macro_rules! foo { () => {} }
212fn 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
3use hir::HasSource;
4use syntax::{
5 ast::{NameOwner, TypeAlias},
6 display::type_label,
7};
8
9use crate::{
10 item::{CompletionItem, CompletionItemKind, CompletionKind},
11 render::RenderContext,
12};
13
14pub(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)]
22struct TypeAliasRender<'a> {
23 ctx: RenderContext<'a>,
24 type_alias: hir::TypeAlias,
25 ast_node: TypeAlias,
26}
27
28impl<'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;
323fn module_resolution_relative_path_outside_root() { 323fn 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"]
328mod foo; 328mod foo;
329
330//- /outside.rs
331mod bar;
332
333//- /bar.rs
334pub 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};
21use hir_expand::{ 21use 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
35mod 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
35pub(super) struct DeclValidator<'a, 'b: 'a> { 41pub(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 {