diff options
Diffstat (limited to 'crates/ide_completion/src/render.rs')
-rw-r--r-- | crates/ide_completion/src/render.rs | 355 |
1 files changed, 269 insertions, 86 deletions
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 8c8b149a1..2514dda7c 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -10,8 +10,10 @@ pub(crate) mod type_alias; | |||
10 | 10 | ||
11 | mod builder_ext; | 11 | mod builder_ext; |
12 | 12 | ||
13 | use base_db::Upcast; | ||
13 | use hir::{ | 14 | use hir::{ |
14 | AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, ScopeDef, Type, | 15 | db::HirDatabase, AsAssocItem, Documentation, HasAttrs, HirDisplay, ModuleDef, Mutability, |
16 | ScopeDef, Type, | ||
15 | }; | 17 | }; |
16 | use ide_db::{ | 18 | use ide_db::{ |
17 | helpers::{item_name, SnippetCap}, | 19 | helpers::{item_name, SnippetCap}, |
@@ -20,7 +22,7 @@ use ide_db::{ | |||
20 | use syntax::TextRange; | 22 | use syntax::TextRange; |
21 | 23 | ||
22 | use crate::{ | 24 | use crate::{ |
23 | item::{ImportEdit, Relevance}, | 25 | item::{CompletionRelevance, ImportEdit}, |
24 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, | 26 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, |
25 | }; | 27 | }; |
26 | 28 | ||
@@ -116,19 +118,6 @@ impl<'a> RenderContext<'a> { | |||
116 | fn docs(&self, node: impl HasAttrs) -> Option<Documentation> { | 118 | fn docs(&self, node: impl HasAttrs) -> Option<Documentation> { |
117 | node.docs(self.db()) | 119 | node.docs(self.db()) |
118 | } | 120 | } |
119 | |||
120 | fn expected_name_and_type(&self) -> Option<(String, Type)> { | ||
121 | if let Some(record_field) = &self.completion.record_field_syntax { | ||
122 | cov_mark::hit!(record_field_type_match); | ||
123 | let (struct_field, _local) = self.completion.sema.resolve_record_field(record_field)?; | ||
124 | Some((struct_field.name(self.db()).to_string(), struct_field.signature_ty(self.db()))) | ||
125 | } else if let Some(active_parameter) = &self.completion.active_parameter { | ||
126 | cov_mark::hit!(active_param_type_match); | ||
127 | Some((active_parameter.name.clone(), active_parameter.ty.clone())) | ||
128 | } else { | ||
129 | None | ||
130 | } | ||
131 | } | ||
132 | } | 121 | } |
133 | 122 | ||
134 | /// Generic renderer for completion items. | 123 | /// Generic renderer for completion items. |
@@ -149,24 +138,27 @@ impl<'a> Render<'a> { | |||
149 | CompletionKind::Reference, | 138 | CompletionKind::Reference, |
150 | self.ctx.source_range(), | 139 | self.ctx.source_range(), |
151 | name.to_string(), | 140 | name.to_string(), |
152 | ) | 141 | ); |
153 | .kind(SymbolKind::Field) | 142 | item.kind(SymbolKind::Field) |
154 | .detail(ty.display(self.ctx.db()).to_string()) | 143 | .detail(ty.display(self.ctx.db()).to_string()) |
155 | .set_documentation(field.docs(self.ctx.db())) | 144 | .set_documentation(field.docs(self.ctx.db())) |
156 | .set_deprecated(is_deprecated); | 145 | .set_deprecated(is_deprecated); |
157 | 146 | ||
158 | if let Some(relevance) = compute_relevance(&self.ctx, &ty, &name.to_string()) { | 147 | item.set_relevance(compute_relevance(&self.ctx, &ty, &name.to_string())); |
159 | item = item.set_relevance(relevance); | ||
160 | } | ||
161 | 148 | ||
162 | item.build() | 149 | item.build() |
163 | } | 150 | } |
164 | 151 | ||
165 | fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { | 152 | fn add_tuple_field(&mut self, field: usize, ty: &Type) -> CompletionItem { |
166 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), field.to_string()) | 153 | let mut item = CompletionItem::new( |
167 | .kind(SymbolKind::Field) | 154 | CompletionKind::Reference, |
168 | .detail(ty.display(self.ctx.db()).to_string()) | 155 | self.ctx.source_range(), |
169 | .build() | 156 | field.to_string(), |
157 | ); | ||
158 | |||
159 | item.kind(SymbolKind::Field).detail(ty.display(self.ctx.db()).to_string()); | ||
160 | |||
161 | item.build() | ||
170 | } | 162 | } |
171 | 163 | ||
172 | fn render_resolution( | 164 | fn render_resolution( |
@@ -225,15 +217,13 @@ impl<'a> Render<'a> { | |||
225 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) | 217 | CompletionItemKind::SymbolKind(SymbolKind::SelfParam) |
226 | } | 218 | } |
227 | ScopeDef::Unknown => { | 219 | ScopeDef::Unknown => { |
228 | let item = CompletionItem::new( | 220 | let mut item = CompletionItem::new( |
229 | CompletionKind::Reference, | 221 | CompletionKind::Reference, |
230 | self.ctx.source_range(), | 222 | self.ctx.source_range(), |
231 | local_name, | 223 | local_name, |
232 | ) | 224 | ); |
233 | .kind(CompletionItemKind::UnresolvedReference) | 225 | item.kind(CompletionItemKind::UnresolvedReference).add_import(import_to_add); |
234 | .add_import(import_to_add) | 226 | return Some(item.build()); |
235 | .build(); | ||
236 | return Some(item); | ||
237 | } | 227 | } |
238 | }; | 228 | }; |
239 | 229 | ||
@@ -242,25 +232,29 @@ impl<'a> Render<'a> { | |||
242 | if let ScopeDef::Local(local) = resolution { | 232 | if let ScopeDef::Local(local) = resolution { |
243 | let ty = local.ty(self.ctx.db()); | 233 | let ty = local.ty(self.ctx.db()); |
244 | if !ty.is_unknown() { | 234 | if !ty.is_unknown() { |
245 | item = item.detail(ty.display(self.ctx.db()).to_string()); | 235 | item.detail(ty.display(self.ctx.db()).to_string()); |
246 | } | 236 | } |
247 | }; | 237 | }; |
248 | 238 | ||
249 | if let ScopeDef::Local(local) = resolution { | 239 | if let ScopeDef::Local(local) = resolution { |
250 | let ty = local.ty(self.ctx.db()); | 240 | let ty = local.ty(self.ctx.db()); |
251 | if let Some(relevance) = compute_relevance(&self.ctx, &ty, &local_name) { | 241 | |
252 | item = item.set_relevance(relevance) | 242 | let mut relevance = compute_relevance(&self.ctx, &ty, &local_name); |
253 | } | 243 | relevance.is_local = true; |
254 | if let Some((_expected_name, expected_type)) = self.ctx.expected_name_and_type() { | 244 | item.set_relevance(relevance); |
255 | if let Some(ty_without_ref) = expected_type.remove_ref() { | 245 | |
256 | if ty_without_ref == ty { | 246 | if let Some(expected_type) = self.ctx.completion.expected_type.as_ref() { |
257 | cov_mark::hit!(suggest_ref); | 247 | if &ty != expected_type { |
258 | let mutability = if expected_type.is_mutable_reference() { | 248 | if let Some(ty_without_ref) = expected_type.remove_ref() { |
259 | Mutability::Mut | 249 | if relevance_type_match(self.ctx.db().upcast(), &ty, &ty_without_ref) { |
260 | } else { | 250 | cov_mark::hit!(suggest_ref); |
261 | Mutability::Shared | 251 | let mutability = if expected_type.is_mutable_reference() { |
262 | }; | 252 | Mutability::Mut |
263 | item = item.ref_match(mutability) | 253 | } else { |
254 | Mutability::Shared | ||
255 | }; | ||
256 | item.ref_match(mutability); | ||
257 | } | ||
264 | } | 258 | } |
265 | } | 259 | } |
266 | } | 260 | } |
@@ -281,21 +275,17 @@ impl<'a> Render<'a> { | |||
281 | }; | 275 | }; |
282 | if has_non_default_type_params { | 276 | if has_non_default_type_params { |
283 | cov_mark::hit!(inserts_angle_brackets_for_generics); | 277 | cov_mark::hit!(inserts_angle_brackets_for_generics); |
284 | item = item | 278 | item.lookup_by(local_name.clone()) |
285 | .lookup_by(local_name.clone()) | ||
286 | .label(format!("{}<…>", local_name)) | 279 | .label(format!("{}<…>", local_name)) |
287 | .insert_snippet(cap, format!("{}<$0>", local_name)); | 280 | .insert_snippet(cap, format!("{}<$0>", local_name)); |
288 | } | 281 | } |
289 | } | 282 | } |
290 | } | 283 | } |
291 | 284 | item.kind(kind) | |
292 | Some( | 285 | .add_import(import_to_add) |
293 | item.kind(kind) | 286 | .set_documentation(self.docs(resolution)) |
294 | .add_import(import_to_add) | 287 | .set_deprecated(self.is_deprecated(resolution)); |
295 | .set_documentation(self.docs(resolution)) | 288 | Some(item.build()) |
296 | .set_deprecated(self.is_deprecated(resolution)) | ||
297 | .build(), | ||
298 | ) | ||
299 | } | 289 | } |
300 | 290 | ||
301 | fn docs(&self, resolution: &ScopeDef) -> Option<Documentation> { | 291 | fn docs(&self, resolution: &ScopeDef) -> Option<Documentation> { |
@@ -323,23 +313,27 @@ impl<'a> Render<'a> { | |||
323 | } | 313 | } |
324 | } | 314 | } |
325 | 315 | ||
326 | fn compute_relevance(ctx: &RenderContext, ty: &Type, name: &str) -> Option<Relevance> { | 316 | fn compute_relevance(ctx: &RenderContext, ty: &Type, name: &str) -> CompletionRelevance { |
327 | let (expected_name, expected_type) = ctx.expected_name_and_type()?; | 317 | let mut res = CompletionRelevance::default(); |
328 | let mut res = Relevance::default(); | 318 | |
329 | res.exact_type_match = ty == &expected_type; | 319 | res.exact_type_match = Some(ty) == ctx.completion.expected_type.as_ref(); |
330 | res.exact_name_match = name == &expected_name; | 320 | res.exact_name_match = Some(name) == ctx.completion.expected_name.as_deref(); |
331 | Some(res) | 321 | |
322 | res | ||
323 | } | ||
324 | |||
325 | fn relevance_type_match(db: &dyn HirDatabase, ty: &Type, expected_type: &Type) -> bool { | ||
326 | ty == expected_type || ty.autoderef(db).any(|deref_ty| &deref_ty == expected_type) | ||
332 | } | 327 | } |
333 | 328 | ||
334 | #[cfg(test)] | 329 | #[cfg(test)] |
335 | mod tests { | 330 | mod tests { |
336 | use std::cmp::Reverse; | ||
337 | |||
338 | use expect_test::{expect, Expect}; | 331 | use expect_test::{expect, Expect}; |
332 | use itertools::Itertools; | ||
339 | 333 | ||
340 | use crate::{ | 334 | use crate::{ |
341 | test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG}, | 335 | test_utils::{check_edit, do_completion, get_all_items, TEST_CONFIG}, |
342 | CompletionKind, Relevance, | 336 | CompletionKind, CompletionRelevance, |
343 | }; | 337 | }; |
344 | 338 | ||
345 | fn check(ra_fixture: &str, expect: Expect) { | 339 | fn check(ra_fixture: &str, expect: Expect) { |
@@ -348,26 +342,40 @@ mod tests { | |||
348 | } | 342 | } |
349 | 343 | ||
350 | fn check_relevance(ra_fixture: &str, expect: Expect) { | 344 | fn check_relevance(ra_fixture: &str, expect: Expect) { |
351 | fn display_relevance(relevance: Relevance) -> &'static str { | 345 | fn display_relevance(relevance: CompletionRelevance) -> String { |
352 | match relevance { | 346 | let relevance_factors = vec![ |
353 | Relevance { exact_type_match: true, exact_name_match: true } => "[type+name]", | 347 | (relevance.exact_type_match, "type"), |
354 | Relevance { exact_type_match: true, exact_name_match: false } => "[type]", | 348 | (relevance.exact_name_match, "name"), |
355 | Relevance { exact_type_match: false, exact_name_match: true } => "[name]", | 349 | (relevance.is_local, "local"), |
356 | Relevance { exact_type_match: false, exact_name_match: false } => "[]", | 350 | ] |
357 | } | 351 | .into_iter() |
352 | .filter_map(|(cond, desc)| if cond { Some(desc) } else { None }) | ||
353 | .join("+"); | ||
354 | |||
355 | format!("[{}]", relevance_factors) | ||
358 | } | 356 | } |
359 | 357 | ||
360 | let mut completions = get_all_items(TEST_CONFIG, ra_fixture); | 358 | let actual = get_all_items(TEST_CONFIG, ra_fixture) |
361 | completions.sort_by_key(|it| (Reverse(it.relevance()), it.label().to_string())); | ||
362 | let actual = completions | ||
363 | .into_iter() | 359 | .into_iter() |
364 | .filter(|it| it.completion_kind == CompletionKind::Reference) | 360 | .filter(|it| it.completion_kind == CompletionKind::Reference) |
365 | .map(|it| { | 361 | .flat_map(|it| { |
362 | let mut items = vec![]; | ||
363 | |||
366 | let tag = it.kind().unwrap().tag(); | 364 | let tag = it.kind().unwrap().tag(); |
367 | let relevance = display_relevance(it.relevance()); | 365 | let relevance = display_relevance(it.relevance()); |
368 | format!("{} {} {}\n", tag, it.label(), relevance) | 366 | items.push(format!("{} {} {}\n", tag, it.label(), relevance)); |
367 | |||
368 | if let Some((mutability, relevance)) = it.ref_match() { | ||
369 | let label = format!("&{}{}", mutability.as_keyword_for_ref(), it.label()); | ||
370 | let relevance = display_relevance(relevance); | ||
371 | |||
372 | items.push(format!("{} {} {}\n", tag, label, relevance)); | ||
373 | } | ||
374 | |||
375 | items | ||
369 | }) | 376 | }) |
370 | .collect::<String>(); | 377 | .collect::<String>(); |
378 | |||
371 | expect.assert_eq(&actual); | 379 | expect.assert_eq(&actual); |
372 | } | 380 | } |
373 | 381 | ||
@@ -829,7 +837,6 @@ fn foo(xs: Vec<i128>) | |||
829 | 837 | ||
830 | #[test] | 838 | #[test] |
831 | fn active_param_relevance() { | 839 | fn active_param_relevance() { |
832 | cov_mark::check!(active_param_type_match); | ||
833 | check_relevance( | 840 | check_relevance( |
834 | r#" | 841 | r#" |
835 | struct S { foo: i64, bar: u32, baz: u32 } | 842 | struct S { foo: i64, bar: u32, baz: u32 } |
@@ -837,16 +844,15 @@ fn test(bar: u32) { } | |||
837 | fn foo(s: S) { test(s.$0) } | 844 | fn foo(s: S) { test(s.$0) } |
838 | "#, | 845 | "#, |
839 | expect![[r#" | 846 | expect![[r#" |
847 | fd foo [] | ||
840 | fd bar [type+name] | 848 | fd bar [type+name] |
841 | fd baz [type] | 849 | fd baz [type] |
842 | fd foo [] | ||
843 | "#]], | 850 | "#]], |
844 | ); | 851 | ); |
845 | } | 852 | } |
846 | 853 | ||
847 | #[test] | 854 | #[test] |
848 | fn record_field_relevances() { | 855 | fn record_field_relevances() { |
849 | cov_mark::check!(record_field_type_match); | ||
850 | check_relevance( | 856 | check_relevance( |
851 | r#" | 857 | r#" |
852 | struct A { foo: i64, bar: u32, baz: u32 } | 858 | struct A { foo: i64, bar: u32, baz: u32 } |
@@ -854,9 +860,9 @@ struct B { x: (), y: f32, bar: u32 } | |||
854 | fn foo(a: A) { B { bar: a.$0 }; } | 860 | fn foo(a: A) { B { bar: a.$0 }; } |
855 | "#, | 861 | "#, |
856 | expect![[r#" | 862 | expect![[r#" |
863 | fd foo [] | ||
857 | fd bar [type+name] | 864 | fd bar [type+name] |
858 | fd baz [type] | 865 | fd baz [type] |
859 | fd foo [] | ||
860 | "#]], | 866 | "#]], |
861 | ) | 867 | ) |
862 | } | 868 | } |
@@ -884,9 +890,9 @@ fn f(foo: i64) { } | |||
884 | fn foo(a: A) { f(B { bar: a.$0 }); } | 890 | fn foo(a: A) { f(B { bar: a.$0 }); } |
885 | "#, | 891 | "#, |
886 | expect![[r#" | 892 | expect![[r#" |
893 | fd foo [] | ||
887 | fd bar [type+name] | 894 | fd bar [type+name] |
888 | fd baz [type] | 895 | fd baz [type] |
889 | fd foo [] | ||
890 | "#]], | 896 | "#]], |
891 | ); | 897 | ); |
892 | } | 898 | } |
@@ -899,7 +905,7 @@ struct WorldSnapshot { _f: () }; | |||
899 | fn go(world: &WorldSnapshot) { go(w$0) } | 905 | fn go(world: &WorldSnapshot) { go(w$0) } |
900 | "#, | 906 | "#, |
901 | expect![[r#" | 907 | expect![[r#" |
902 | lc world [type+name] | 908 | lc world [type+name+local] |
903 | st WorldSnapshot [] | 909 | st WorldSnapshot [] |
904 | fn go(…) [] | 910 | fn go(…) [] |
905 | "#]], | 911 | "#]], |
@@ -914,9 +920,69 @@ struct Foo; | |||
914 | fn f(foo: &Foo) { f(foo, w$0) } | 920 | fn f(foo: &Foo) { f(foo, w$0) } |
915 | "#, | 921 | "#, |
916 | expect![[r#" | 922 | expect![[r#" |
923 | lc foo [local] | ||
917 | st Foo [] | 924 | st Foo [] |
918 | fn f(…) [] | 925 | fn f(…) [] |
919 | lc foo [] | 926 | "#]], |
927 | ); | ||
928 | } | ||
929 | |||
930 | #[test] | ||
931 | fn score_fn_type_and_name_match() { | ||
932 | check_relevance( | ||
933 | r#" | ||
934 | struct A { bar: u8 } | ||
935 | fn baz() -> u8 { 0 } | ||
936 | fn bar() -> u8 { 0 } | ||
937 | fn f() { A { bar: b$0 }; } | ||
938 | "#, | ||
939 | expect![[r#" | ||
940 | fn baz() [type] | ||
941 | st A [] | ||
942 | fn bar() [type+name] | ||
943 | fn f() [] | ||
944 | "#]], | ||
945 | ); | ||
946 | } | ||
947 | |||
948 | #[test] | ||
949 | fn score_method_type_and_name_match() { | ||
950 | check_relevance( | ||
951 | r#" | ||
952 | fn baz(aaa: u32){} | ||
953 | struct Foo; | ||
954 | impl Foo { | ||
955 | fn aaa(&self) -> u32 { 0 } | ||
956 | fn bbb(&self) -> u32 { 0 } | ||
957 | fn ccc(&self) -> u64 { 0 } | ||
958 | } | ||
959 | fn f() { | ||
960 | baz(Foo.$0 | ||
961 | } | ||
962 | "#, | ||
963 | expect![[r#" | ||
964 | me aaa() [type+name] | ||
965 | me bbb() [type] | ||
966 | me ccc() [] | ||
967 | "#]], | ||
968 | ); | ||
969 | } | ||
970 | |||
971 | #[test] | ||
972 | fn score_method_name_match_only() { | ||
973 | check_relevance( | ||
974 | r#" | ||
975 | fn baz(aaa: u32){} | ||
976 | struct Foo; | ||
977 | impl Foo { | ||
978 | fn aaa(&self) -> u64 { 0 } | ||
979 | } | ||
980 | fn f() { | ||
981 | baz(Foo.$0 | ||
982 | } | ||
983 | "#, | ||
984 | expect![[r#" | ||
985 | me aaa() [name] | ||
920 | "#]], | 986 | "#]], |
921 | ); | 987 | ); |
922 | } | 988 | } |
@@ -976,9 +1042,10 @@ fn main() { | |||
976 | Local, | 1042 | Local, |
977 | ), | 1043 | ), |
978 | detail: "S", | 1044 | detail: "S", |
979 | relevance: Relevance { | 1045 | relevance: CompletionRelevance { |
980 | exact_name_match: true, | 1046 | exact_name_match: true, |
981 | exact_type_match: false, | 1047 | exact_type_match: false, |
1048 | is_local: true, | ||
982 | }, | 1049 | }, |
983 | ref_match: "&mut ", | 1050 | ref_match: "&mut ", |
984 | }, | 1051 | }, |
@@ -986,4 +1053,120 @@ fn main() { | |||
986 | "#]], | 1053 | "#]], |
987 | ) | 1054 | ) |
988 | } | 1055 | } |
1056 | |||
1057 | #[test] | ||
1058 | fn suggest_deref() { | ||
1059 | check_relevance( | ||
1060 | r#" | ||
1061 | #[lang = "deref"] | ||
1062 | trait Deref { | ||
1063 | type Target; | ||
1064 | fn deref(&self) -> &Self::Target; | ||
1065 | } | ||
1066 | |||
1067 | struct S; | ||
1068 | struct T(S); | ||
1069 | |||
1070 | impl Deref for T { | ||
1071 | type Target = S; | ||
1072 | |||
1073 | fn deref(&self) -> &Self::Target { | ||
1074 | &self.0 | ||
1075 | } | ||
1076 | } | ||
1077 | |||
1078 | fn foo(s: &S) {} | ||
1079 | |||
1080 | fn main() { | ||
1081 | let t = T(S); | ||
1082 | let m = 123; | ||
1083 | |||
1084 | foo($0); | ||
1085 | } | ||
1086 | "#, | ||
1087 | expect![[r#" | ||
1088 | lc m [local] | ||
1089 | lc t [local] | ||
1090 | lc &t [type+local] | ||
1091 | st T [] | ||
1092 | st S [] | ||
1093 | fn main() [] | ||
1094 | tt Deref [] | ||
1095 | fn foo(…) [] | ||
1096 | "#]], | ||
1097 | ) | ||
1098 | } | ||
1099 | |||
1100 | #[test] | ||
1101 | fn suggest_deref_mut() { | ||
1102 | check_relevance( | ||
1103 | r#" | ||
1104 | #[lang = "deref"] | ||
1105 | trait Deref { | ||
1106 | type Target; | ||
1107 | fn deref(&self) -> &Self::Target; | ||
1108 | } | ||
1109 | |||
1110 | #[lang = "deref_mut"] | ||
1111 | pub trait DerefMut: Deref { | ||
1112 | fn deref_mut(&mut self) -> &mut Self::Target; | ||
1113 | } | ||
1114 | |||
1115 | struct S; | ||
1116 | struct T(S); | ||
1117 | |||
1118 | impl Deref for T { | ||
1119 | type Target = S; | ||
1120 | |||
1121 | fn deref(&self) -> &Self::Target { | ||
1122 | &self.0 | ||
1123 | } | ||
1124 | } | ||
1125 | |||
1126 | impl DerefMut for T { | ||
1127 | fn deref_mut(&mut self) -> &mut Self::Target { | ||
1128 | &mut self.0 | ||
1129 | } | ||
1130 | } | ||
1131 | |||
1132 | fn foo(s: &mut S) {} | ||
1133 | |||
1134 | fn main() { | ||
1135 | let t = T(S); | ||
1136 | let m = 123; | ||
1137 | |||
1138 | foo($0); | ||
1139 | } | ||
1140 | "#, | ||
1141 | expect![[r#" | ||
1142 | lc m [local] | ||
1143 | lc t [local] | ||
1144 | lc &mut t [type+local] | ||
1145 | tt DerefMut [] | ||
1146 | tt Deref [] | ||
1147 | fn foo(…) [] | ||
1148 | st T [] | ||
1149 | st S [] | ||
1150 | fn main() [] | ||
1151 | "#]], | ||
1152 | ) | ||
1153 | } | ||
1154 | |||
1155 | #[test] | ||
1156 | fn locals() { | ||
1157 | check_relevance( | ||
1158 | r#" | ||
1159 | fn foo(bar: u32) { | ||
1160 | let baz = 0; | ||
1161 | |||
1162 | f$0 | ||
1163 | } | ||
1164 | "#, | ||
1165 | expect![[r#" | ||
1166 | lc baz [local] | ||
1167 | lc bar [local] | ||
1168 | fn foo(…) [] | ||
1169 | "#]], | ||
1170 | ); | ||
1171 | } | ||
989 | } | 1172 | } |