aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-05-28 22:21:37 +0100
committerGitHub <[email protected]>2021-05-28 22:21:37 +0100
commit7869b01b702d1f12acb03ac131b6eb90ad82e9bb (patch)
treee6d76df5212db7589ef8dc6583b32d6c64871a33
parentd5f7b2e52a41a7d3b841f4d0e2225eb703f6a50a (diff)
parente42c448077ca2b7675320da3c5294bf4bebaedeb (diff)
Merge #9041
9041: internal: Implement prev sibling determination for `CompletionContext ` r=Veykril a=Veykril bors r+ Co-authored-by: Lukas Wirth <[email protected]>
-rw-r--r--crates/base_db/src/fixture.rs10
-rw-r--r--crates/ide/src/fixture.rs17
-rw-r--r--crates/ide_completion/src/completions/keyword.rs192
-rw-r--r--crates/ide_completion/src/context.rs49
-rw-r--r--crates/ide_completion/src/patterns.rs265
-rw-r--r--crates/ide_completion/src/test_utils.rs16
-rw-r--r--crates/ide_db/src/call_info/tests.rs6
-rw-r--r--crates/ide_db/src/traits/tests.rs6
-rw-r--r--crates/test_utils/src/lib.rs15
9 files changed, 328 insertions, 248 deletions
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index 0132565e4..69ceba735 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -34,19 +34,13 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
34 34
35 fn with_position(ra_fixture: &str) -> (Self, FilePosition) { 35 fn with_position(ra_fixture: &str) -> (Self, FilePosition) {
36 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); 36 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
37 let offset = match range_or_offset { 37 let offset = range_or_offset.expect_offset();
38 RangeOrOffset::Range(_) => panic!("Expected a cursor position, got a range instead"),
39 RangeOrOffset::Offset(it) => it,
40 };
41 (db, FilePosition { file_id, offset }) 38 (db, FilePosition { file_id, offset })
42 } 39 }
43 40
44 fn with_range(ra_fixture: &str) -> (Self, FileRange) { 41 fn with_range(ra_fixture: &str) -> (Self, FileRange) {
45 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture); 42 let (db, file_id, range_or_offset) = Self::with_range_or_offset(ra_fixture);
46 let range = match range_or_offset { 43 let range = range_or_offset.expect_range();
47 RangeOrOffset::Range(it) => it,
48 RangeOrOffset::Offset(_) => panic!("Expected a cursor range, got a position instead"),
49 };
50 (db, FileRange { file_id, range }) 44 (db, FileRange { file_id, range })
51 } 45 }
52 46
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs
index cc6641ba1..6780af617 100644
--- a/crates/ide/src/fixture.rs
+++ b/crates/ide/src/fixture.rs
@@ -1,7 +1,7 @@
1//! Utilities for creating `Analysis` instances for tests. 1//! Utilities for creating `Analysis` instances for tests.
2use ide_db::base_db::fixture::ChangeFixture; 2use ide_db::base_db::fixture::ChangeFixture;
3use syntax::{TextRange, TextSize}; 3use syntax::{TextRange, TextSize};
4use test_utils::{extract_annotations, RangeOrOffset}; 4use test_utils::extract_annotations;
5 5
6use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange}; 6use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
7 7
@@ -27,10 +27,7 @@ pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) {
27 let change_fixture = ChangeFixture::parse(ra_fixture); 27 let change_fixture = ChangeFixture::parse(ra_fixture);
28 host.db.apply_change(change_fixture.change); 28 host.db.apply_change(change_fixture.change);
29 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); 29 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
30 let offset = match range_or_offset { 30 let offset = range_or_offset.expect_offset();
31 RangeOrOffset::Range(_) => panic!(),
32 RangeOrOffset::Offset(it) => it,
33 };
34 (host.analysis(), FilePosition { file_id, offset }) 31 (host.analysis(), FilePosition { file_id, offset })
35} 32}
36 33
@@ -40,10 +37,7 @@ pub(crate) fn range(ra_fixture: &str) -> (Analysis, FileRange) {
40 let change_fixture = ChangeFixture::parse(ra_fixture); 37 let change_fixture = ChangeFixture::parse(ra_fixture);
41 host.db.apply_change(change_fixture.change); 38 host.db.apply_change(change_fixture.change);
42 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); 39 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
43 let range = match range_or_offset { 40 let range = range_or_offset.expect_range();
44 RangeOrOffset::Range(it) => it,
45 RangeOrOffset::Offset(_) => panic!(),
46 };
47 (host.analysis(), FileRange { file_id, range }) 41 (host.analysis(), FileRange { file_id, range })
48} 42}
49 43
@@ -53,10 +47,7 @@ pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(Fil
53 let change_fixture = ChangeFixture::parse(ra_fixture); 47 let change_fixture = ChangeFixture::parse(ra_fixture);
54 host.db.apply_change(change_fixture.change); 48 host.db.apply_change(change_fixture.change);
55 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); 49 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
56 let offset = match range_or_offset { 50 let offset = range_or_offset.expect_offset();
57 RangeOrOffset::Range(_) => panic!(),
58 RangeOrOffset::Offset(it) => it,
59 };
60 51
61 let annotations = change_fixture 52 let annotations = change_fixture
62 .files 53 .files
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs
index c9673df85..e71a04b6e 100644
--- a/crates/ide_completion/src/completions/keyword.rs
+++ b/crates/ide_completion/src/completions/keyword.rs
@@ -48,91 +48,92 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
48 cov_mark::hit!(no_keyword_completion_in_record_lit); 48 cov_mark::hit!(no_keyword_completion_in_record_lit);
49 return; 49 return;
50 } 50 }
51 let mut add_keyword = |kw, snippet| add_keyword(ctx, acc, kw, snippet);
51 52
52 let expects_assoc_item = ctx.expects_assoc_item(); 53 let expects_assoc_item = ctx.expects_assoc_item();
53 let has_block_expr_parent = ctx.has_block_expr_parent(); 54 let has_block_expr_parent = ctx.has_block_expr_parent();
54 let expects_item = ctx.expects_item(); 55 let expects_item = ctx.expects_item();
56
55 if ctx.has_impl_or_trait_prev_sibling() { 57 if ctx.has_impl_or_trait_prev_sibling() {
56 add_keyword(ctx, acc, "where", "where "); 58 // FIXME this also incorrectly shows up after a complete trait/impl
59 add_keyword("where", "where ");
57 return; 60 return;
58 } 61 }
59 if ctx.previous_token_is(T![unsafe]) { 62 if ctx.previous_token_is(T![unsafe]) {
60 if expects_item || has_block_expr_parent { 63 if expects_item || expects_assoc_item || has_block_expr_parent {
61 add_keyword(ctx, acc, "fn", "fn $1($2) {\n $0\n}") 64 add_keyword("fn", "fn $1($2) {\n $0\n}")
62 } 65 }
63 66
64 if expects_item || has_block_expr_parent { 67 if expects_item || has_block_expr_parent {
65 add_keyword(ctx, acc, "trait", "trait $1 {\n $0\n}"); 68 add_keyword("trait", "trait $1 {\n $0\n}");
66 add_keyword(ctx, acc, "impl", "impl $1 {\n $0\n}"); 69 add_keyword("impl", "impl $1 {\n $0\n}");
67 } 70 }
68 71
69 return; 72 return;
70 } 73 }
74
75 if expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_record_field() {
76 add_keyword("pub(crate)", "pub(crate) ");
77 add_keyword("pub", "pub ");
78 }
79
80 if expects_item || expects_assoc_item || has_block_expr_parent || ctx.is_match_arm {
81 add_keyword("unsafe", "unsafe ");
82 }
83
71 if expects_item || expects_assoc_item || has_block_expr_parent { 84 if expects_item || expects_assoc_item || has_block_expr_parent {
72 add_keyword(ctx, acc, "fn", "fn $1($2) {\n $0\n}"); 85 add_keyword("fn", "fn $1($2) {\n $0\n}");
86 add_keyword("const", "const $0");
87 add_keyword("type", "type $0");
73 } 88 }
89
74 if expects_item || has_block_expr_parent { 90 if expects_item || has_block_expr_parent {
75 add_keyword(ctx, acc, "use", "use "); 91 add_keyword("use", "use $0");
76 add_keyword(ctx, acc, "impl", "impl $1 {\n $0\n}"); 92 add_keyword("impl", "impl $1 {\n $0\n}");
77 add_keyword(ctx, acc, "trait", "trait $1 {\n $0\n}"); 93 add_keyword("trait", "trait $1 {\n $0\n}");
94 add_keyword("static", "static $0");
95 add_keyword("extern", "extern $0");
96 add_keyword("mod", "mod $0");
78 } 97 }
79 98
80 if expects_item { 99 if expects_item {
81 add_keyword(ctx, acc, "enum", "enum $1 {\n $0\n}"); 100 add_keyword("enum", "enum $1 {\n $0\n}");
82 add_keyword(ctx, acc, "struct", "struct $0"); 101 add_keyword("struct", "struct $0");
83 add_keyword(ctx, acc, "union", "union $1 {\n $0\n}"); 102 add_keyword("union", "union $1 {\n $0\n}");
84 } 103 }
85 104
86 if ctx.is_expr { 105 if ctx.expects_expression() {
87 add_keyword(ctx, acc, "match", "match $1 {\n $0\n}"); 106 add_keyword("match", "match $1 {\n $0\n}");
88 add_keyword(ctx, acc, "while", "while $1 {\n $0\n}"); 107 add_keyword("while", "while $1 {\n $0\n}");
89 add_keyword(ctx, acc, "while let", "while let $1 = $2 {\n $0\n}"); 108 add_keyword("while let", "while let $1 = $2 {\n $0\n}");
90 add_keyword(ctx, acc, "loop", "loop {\n $0\n}"); 109 add_keyword("loop", "loop {\n $0\n}");
91 add_keyword(ctx, acc, "if", "if $1 {\n $0\n}"); 110 add_keyword("if", "if $1 {\n $0\n}");
92 add_keyword(ctx, acc, "if let", "if let $1 = $2 {\n $0\n}"); 111 add_keyword("if let", "if let $1 = $2 {\n $0\n}");
93 add_keyword(ctx, acc, "for", "for $1 in $2 {\n $0\n}"); 112 add_keyword("for", "for $1 in $2 {\n $0\n}");
94 } 113 }
95 114
96 if ctx.previous_token_is(T![if]) || ctx.previous_token_is(T![while]) || has_block_expr_parent { 115 if ctx.previous_token_is(T![if]) || ctx.previous_token_is(T![while]) || has_block_expr_parent {
97 add_keyword(ctx, acc, "let", "let "); 116 add_keyword("let", "let ");
98 } 117 }
99 118
100 if ctx.after_if { 119 if ctx.after_if() {
101 add_keyword(ctx, acc, "else", "else {\n $0\n}"); 120 add_keyword("else", "else {\n $0\n}");
102 add_keyword(ctx, acc, "else if", "else if $1 {\n $0\n}"); 121 add_keyword("else if", "else if $1 {\n $0\n}");
103 }
104 if expects_item || has_block_expr_parent {
105 add_keyword(ctx, acc, "mod", "mod $0");
106 } 122 }
123
107 if ctx.expects_ident_pat_or_ref_expr() { 124 if ctx.expects_ident_pat_or_ref_expr() {
108 add_keyword(ctx, acc, "mut", "mut "); 125 add_keyword("mut", "mut ");
109 }
110 if expects_item || expects_assoc_item || has_block_expr_parent {
111 add_keyword(ctx, acc, "const", "const ");
112 add_keyword(ctx, acc, "type", "type ");
113 }
114 if expects_item || has_block_expr_parent {
115 add_keyword(ctx, acc, "static", "static ");
116 };
117 if expects_item || has_block_expr_parent {
118 add_keyword(ctx, acc, "extern", "extern ");
119 }
120 if expects_item || expects_assoc_item || has_block_expr_parent || ctx.is_match_arm {
121 add_keyword(ctx, acc, "unsafe", "unsafe ");
122 } 126 }
127
123 if ctx.in_loop_body { 128 if ctx.in_loop_body {
124 if ctx.can_be_stmt { 129 if ctx.can_be_stmt {
125 add_keyword(ctx, acc, "continue", "continue;"); 130 add_keyword("continue", "continue;");
126 add_keyword(ctx, acc, "break", "break;"); 131 add_keyword("break", "break;");
127 } else { 132 } else {
128 add_keyword(ctx, acc, "continue", "continue"); 133 add_keyword("continue", "continue");
129 add_keyword(ctx, acc, "break", "break"); 134 add_keyword("break", "break");
130 } 135 }
131 } 136 }
132 if expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_record_field() {
133 add_keyword(ctx, acc, "pub(crate)", "pub(crate) ");
134 add_keyword(ctx, acc, "pub", "pub ");
135 }
136 137
137 if !ctx.is_trivial_path { 138 if !ctx.is_trivial_path {
138 return; 139 return;
@@ -143,8 +144,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
143 }; 144 };
144 145
145 add_keyword( 146 add_keyword(
146 ctx,
147 acc,
148 "return", 147 "return",
149 match (ctx.can_be_stmt, fn_def.ret_type().is_some()) { 148 match (ctx.can_be_stmt, fn_def.ret_type().is_some()) {
150 (true, true) => "return $0;", 149 (true, true) => "return $0;",
@@ -161,15 +160,12 @@ fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet
161 160
162 match ctx.config.snippet_cap { 161 match ctx.config.snippet_cap {
163 Some(cap) => { 162 Some(cap) => {
164 let tmp; 163 if snippet.ends_with('}') && ctx.incomplete_let {
165 let snippet = if snippet.ends_with('}') && ctx.incomplete_let {
166 cov_mark::hit!(let_semi); 164 cov_mark::hit!(let_semi);
167 tmp = format!("{};", snippet); 165 item.insert_snippet(cap, format!("{};", snippet));
168 &tmp
169 } else { 166 } else {
170 snippet 167 item.insert_snippet(cap, snippet);
171 }; 168 }
172 item.insert_snippet(cap, snippet);
173 } 169 }
174 None => { 170 None => {
175 item.insert_text(if snippet.contains('$') { kw } else { snippet }); 171 item.insert_text(if snippet.contains('$') { kw } else { snippet });
@@ -232,21 +228,21 @@ mod tests {
232 check( 228 check(
233 r"m$0", 229 r"m$0",
234 expect![[r#" 230 expect![[r#"
231 kw pub(crate)
232 kw pub
233 kw unsafe
235 kw fn 234 kw fn
235 kw const
236 kw type
236 kw use 237 kw use
237 kw impl 238 kw impl
238 kw trait 239 kw trait
240 kw static
241 kw extern
242 kw mod
239 kw enum 243 kw enum
240 kw struct 244 kw struct
241 kw union 245 kw union
242 kw mod
243 kw const
244 kw type
245 kw static
246 kw extern
247 kw unsafe
248 kw pub(crate)
249 kw pub
250 "#]], 246 "#]],
251 ); 247 );
252 } 248 }
@@ -256,10 +252,16 @@ mod tests {
256 check( 252 check(
257 r"fn quux() { $0 }", 253 r"fn quux() { $0 }",
258 expect![[r#" 254 expect![[r#"
255 kw unsafe
259 kw fn 256 kw fn
257 kw const
258 kw type
260 kw use 259 kw use
261 kw impl 260 kw impl
262 kw trait 261 kw trait
262 kw static
263 kw extern
264 kw mod
263 kw match 265 kw match
264 kw while 266 kw while
265 kw while let 267 kw while let
@@ -268,12 +270,6 @@ mod tests {
268 kw if let 270 kw if let
269 kw for 271 kw for
270 kw let 272 kw let
271 kw mod
272 kw const
273 kw type
274 kw static
275 kw extern
276 kw unsafe
277 kw return 273 kw return
278 "#]], 274 "#]],
279 ); 275 );
@@ -284,10 +280,16 @@ mod tests {
284 check( 280 check(
285 r"fn quux() { if true { $0 } }", 281 r"fn quux() { if true { $0 } }",
286 expect![[r#" 282 expect![[r#"
283 kw unsafe
287 kw fn 284 kw fn
285 kw const
286 kw type
288 kw use 287 kw use
289 kw impl 288 kw impl
290 kw trait 289 kw trait
290 kw static
291 kw extern
292 kw mod
291 kw match 293 kw match
292 kw while 294 kw while
293 kw while let 295 kw while let
@@ -296,12 +298,6 @@ mod tests {
296 kw if let 298 kw if let
297 kw for 299 kw for
298 kw let 300 kw let
299 kw mod
300 kw const
301 kw type
302 kw static
303 kw extern
304 kw unsafe
305 kw return 301 kw return
306 "#]], 302 "#]],
307 ); 303 );
@@ -312,10 +308,16 @@ mod tests {
312 check( 308 check(
313 r#"fn quux() { if true { () } $0 }"#, 309 r#"fn quux() { if true { () } $0 }"#,
314 expect![[r#" 310 expect![[r#"
311 kw unsafe
315 kw fn 312 kw fn
313 kw const
314 kw type
316 kw use 315 kw use
317 kw impl 316 kw impl
318 kw trait 317 kw trait
318 kw static
319 kw extern
320 kw mod
319 kw match 321 kw match
320 kw while 322 kw while
321 kw while let 323 kw while let
@@ -326,12 +328,6 @@ mod tests {
326 kw let 328 kw let
327 kw else 329 kw else
328 kw else if 330 kw else if
329 kw mod
330 kw const
331 kw type
332 kw static
333 kw extern
334 kw unsafe
335 kw return 331 kw return
336 "#]], 332 "#]],
337 ); 333 );
@@ -353,6 +349,7 @@ fn quux() -> i32 {
353} 349}
354"#, 350"#,
355 expect![[r#" 351 expect![[r#"
352 kw unsafe
356 kw match 353 kw match
357 kw while 354 kw while
358 kw while let 355 kw while let
@@ -360,7 +357,6 @@ fn quux() -> i32 {
360 kw if 357 kw if
361 kw if let 358 kw if let
362 kw for 359 kw for
363 kw unsafe
364 kw return 360 kw return
365 "#]], 361 "#]],
366 ); 362 );
@@ -371,10 +367,10 @@ fn quux() -> i32 {
371 check( 367 check(
372 r"trait My { $0 }", 368 r"trait My { $0 }",
373 expect![[r#" 369 expect![[r#"
370 kw unsafe
374 kw fn 371 kw fn
375 kw const 372 kw const
376 kw type 373 kw type
377 kw unsafe
378 "#]], 374 "#]],
379 ); 375 );
380 } 376 }
@@ -384,12 +380,12 @@ fn quux() -> i32 {
384 check( 380 check(
385 r"impl My { $0 }", 381 r"impl My { $0 }",
386 expect![[r#" 382 expect![[r#"
383 kw pub(crate)
384 kw pub
385 kw unsafe
387 kw fn 386 kw fn
388 kw const 387 kw const
389 kw type 388 kw type
390 kw unsafe
391 kw pub(crate)
392 kw pub
393 "#]], 389 "#]],
394 ); 390 );
395 } 391 }
@@ -399,12 +395,12 @@ fn quux() -> i32 {
399 check( 395 check(
400 r"impl My { #[foo] $0 }", 396 r"impl My { #[foo] $0 }",
401 expect![[r#" 397 expect![[r#"
398 kw pub(crate)
399 kw pub
400 kw unsafe
402 kw fn 401 kw fn
403 kw const 402 kw const
404 kw type 403 kw type
405 kw unsafe
406 kw pub(crate)
407 kw pub
408 "#]], 404 "#]],
409 ); 405 );
410 } 406 }
@@ -414,10 +410,16 @@ fn quux() -> i32 {
414 check( 410 check(
415 r"fn my() { loop { $0 } }", 411 r"fn my() { loop { $0 } }",
416 expect![[r#" 412 expect![[r#"
413 kw unsafe
417 kw fn 414 kw fn
415 kw const
416 kw type
418 kw use 417 kw use
419 kw impl 418 kw impl
420 kw trait 419 kw trait
420 kw static
421 kw extern
422 kw mod
421 kw match 423 kw match
422 kw while 424 kw while
423 kw while let 425 kw while let
@@ -426,12 +428,6 @@ fn quux() -> i32 {
426 kw if let 428 kw if let
427 kw for 429 kw for
428 kw let 430 kw let
429 kw mod
430 kw const
431 kw type
432 kw static
433 kw extern
434 kw unsafe
435 kw continue 431 kw continue
436 kw break 432 kw break
437 kw return 433 kw return
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index 923e35dbb..8d6440cb2 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -17,8 +17,8 @@ use text_edit::Indel;
17 17
18use crate::{ 18use crate::{
19 patterns::{ 19 patterns::{
20 determine_location, for_is_prev2, has_prev_sibling, inside_impl_trait_block, 20 determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block,
21 is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, 21 is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, ImmediatePrevSibling,
22 }, 22 },
23 CompletionConfig, 23 CompletionConfig,
24}; 24};
@@ -29,12 +29,6 @@ pub(crate) enum PatternRefutability {
29 Irrefutable, 29 Irrefutable,
30} 30}
31 31
32#[derive(Copy, Clone, Debug, PartialEq, Eq)]
33pub(crate) enum PrevSibling {
34 Trait,
35 Impl,
36}
37
38/// `CompletionContext` is created early during completion to figure out, where 32/// `CompletionContext` is created early during completion to figure out, where
39/// exactly is the cursor, syntax-wise. 33/// exactly is the cursor, syntax-wise.
40#[derive(Debug)] 34#[derive(Debug)]
@@ -76,6 +70,7 @@ pub(crate) struct CompletionContext<'a> {
76 pub(super) is_param: bool, 70 pub(super) is_param: bool,
77 71
78 pub(super) completion_location: Option<ImmediateLocation>, 72 pub(super) completion_location: Option<ImmediateLocation>,
73 pub(super) prev_sibling: Option<ImmediatePrevSibling>,
79 74
80 /// FIXME: `ActiveParameter` is string-based, which is very very wrong 75 /// FIXME: `ActiveParameter` is string-based, which is very very wrong
81 pub(super) active_parameter: Option<ActiveParameter>, 76 pub(super) active_parameter: Option<ActiveParameter>,
@@ -83,7 +78,6 @@ pub(crate) struct CompletionContext<'a> {
83 pub(super) is_trivial_path: bool, 78 pub(super) is_trivial_path: bool,
84 /// If not a trivial path, the prefix (qualifier). 79 /// If not a trivial path, the prefix (qualifier).
85 pub(super) path_qual: Option<ast::Path>, 80 pub(super) path_qual: Option<ast::Path>,
86 pub(super) after_if: bool,
87 /// `true` if we are a statement or a last expr in the block. 81 /// `true` if we are a statement or a last expr in the block.
88 pub(super) can_be_stmt: bool, 82 pub(super) can_be_stmt: bool,
89 /// `true` if we expect an expression at the cursor position. 83 /// `true` if we expect an expression at the cursor position.
@@ -107,7 +101,6 @@ pub(crate) struct CompletionContext<'a> {
107 101
108 // keyword patterns 102 // keyword patterns
109 pub(super) previous_token: Option<SyntaxToken>, 103 pub(super) previous_token: Option<SyntaxToken>,
110 pub(super) prev_sibling: Option<PrevSibling>,
111 pub(super) in_loop_body: bool, 104 pub(super) in_loop_body: bool,
112 pub(super) is_match_arm: bool, 105 pub(super) is_match_arm: bool,
113 pub(super) incomplete_let: bool, 106 pub(super) incomplete_let: bool,
@@ -173,7 +166,6 @@ impl<'a> CompletionContext<'a> {
173 is_pat_or_const: None, 166 is_pat_or_const: None,
174 is_trivial_path: false, 167 is_trivial_path: false,
175 path_qual: None, 168 path_qual: None,
176 after_if: false,
177 can_be_stmt: false, 169 can_be_stmt: false,
178 is_expr: false, 170 is_expr: false,
179 is_new_item: false, 171 is_new_item: false,
@@ -288,6 +280,10 @@ impl<'a> CompletionContext<'a> {
288 matches!(self.completion_location, Some(ImmediateLocation::ItemList)) 280 matches!(self.completion_location, Some(ImmediateLocation::ItemList))
289 } 281 }
290 282
283 pub(crate) fn expects_expression(&self) -> bool {
284 self.is_expr
285 }
286
291 pub(crate) fn has_block_expr_parent(&self) -> bool { 287 pub(crate) fn has_block_expr_parent(&self) -> bool {
292 matches!(self.completion_location, Some(ImmediateLocation::BlockExpr)) 288 matches!(self.completion_location, Some(ImmediateLocation::BlockExpr))
293 } 289 }
@@ -304,7 +300,14 @@ impl<'a> CompletionContext<'a> {
304 } 300 }
305 301
306 pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool { 302 pub(crate) fn has_impl_or_trait_prev_sibling(&self) -> bool {
307 self.prev_sibling.is_some() 303 matches!(
304 self.prev_sibling,
305 Some(ImmediatePrevSibling::ImplDefType) | Some(ImmediatePrevSibling::TraitDefName)
306 )
307 }
308
309 pub(crate) fn after_if(&self) -> bool {
310 matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr))
308 } 311 }
309 312
310 pub(crate) fn is_path_disallowed(&self) -> bool { 313 pub(crate) fn is_path_disallowed(&self) -> bool {
@@ -316,15 +319,10 @@ impl<'a> CompletionContext<'a> {
316 319
317 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { 320 fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) {
318 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); 321 let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
319 let syntax_element = NodeOrToken::Token(fake_ident_token.clone()); 322 let syntax_element = NodeOrToken::Token(fake_ident_token);
320 self.previous_token = previous_token(syntax_element.clone()); 323 self.previous_token = previous_token(syntax_element.clone());
321 self.in_loop_body = is_in_loop_body(syntax_element.clone()); 324 self.in_loop_body = is_in_loop_body(syntax_element.clone());
322 self.is_match_arm = is_match_arm(syntax_element.clone()); 325 self.is_match_arm = is_match_arm(syntax_element.clone());
323 if has_prev_sibling(syntax_element.clone(), IMPL) {
324 self.prev_sibling = Some(PrevSibling::Impl)
325 } else if has_prev_sibling(syntax_element.clone(), TRAIT) {
326 self.prev_sibling = Some(PrevSibling::Trait)
327 }
328 326
329 self.mod_declaration_under_caret = 327 self.mod_declaration_under_caret =
330 find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) 328 find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset)
@@ -338,8 +336,6 @@ impl<'a> CompletionContext<'a> {
338 let fn_is_prev = self.previous_token_is(T![fn]); 336 let fn_is_prev = self.previous_token_is(T![fn]);
339 let for_is_prev2 = for_is_prev2(syntax_element.clone()); 337 let for_is_prev2 = for_is_prev2(syntax_element.clone());
340 self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2; 338 self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2;
341
342 self.completion_location = determine_location(fake_ident_token);
343 } 339 }
344 340
345 fn fill_impl_def(&mut self) { 341 fn fill_impl_def(&mut self) {
@@ -465,6 +461,8 @@ impl<'a> CompletionContext<'a> {
465 Some(it) => it, 461 Some(it) => it,
466 None => return, 462 None => return,
467 }; 463 };
464 self.completion_location = determine_location(&name_like);
465 self.prev_sibling = determine_prev_sibling(&name_like);
468 match name_like { 466 match name_like {
469 ast::NameLike::Lifetime(lifetime) => { 467 ast::NameLike::Lifetime(lifetime) => {
470 self.classify_lifetime(original_file, lifetime, offset); 468 self.classify_lifetime(original_file, lifetime, offset);
@@ -653,17 +651,6 @@ impl<'a> CompletionContext<'a> {
653 }) 651 })
654 .unwrap_or(false); 652 .unwrap_or(false);
655 self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some(); 653 self.is_expr = path.syntax().parent().and_then(ast::PathExpr::cast).is_some();
656
657 if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) {
658 if let Some(if_expr) =
659 self.sema.find_node_at_offset_with_macros::<ast::IfExpr>(original_file, off)
660 {
661 if if_expr.syntax().text_range().end() < name_ref.syntax().text_range().start()
662 {
663 self.after_if = true;
664 }
665 }
666 }
667 } 654 }
668 655
669 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { 656 if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index c8a88367d..caf0ef39f 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -4,12 +4,19 @@ use syntax::{
4 algo::non_trivia_sibling, 4 algo::non_trivia_sibling,
5 ast::{self, LoopBodyOwner}, 5 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, 6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7 SyntaxKind::{self, *}, 7 SyntaxKind::*,
8 SyntaxNode, SyntaxToken, T, 8 SyntaxNode, SyntaxToken, T,
9}; 9};
10 10
11#[cfg(test)] 11#[cfg(test)]
12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; 12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
13/// Direct parent container of the cursor position
14#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub(crate) enum ImmediatePrevSibling {
16 IfExpr,
17 TraitDefName,
18 ImplDefType,
19}
13 20
14/// Direct parent container of the cursor position 21/// Direct parent container of the cursor position
15#[derive(Copy, Clone, Debug, PartialEq, Eq)] 22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -24,35 +31,61 @@ pub(crate) enum ImmediateLocation {
24 ItemList, 31 ItemList,
25} 32}
26 33
27pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> { 34pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
28 // First walk the element we are completing up to its highest node that has the same text range 35 let node = maximize_name_ref(name_like)?;
29 // as the element so that we can check in what context it immediately lies. We only do this for 36 let node = match node.parent().and_then(ast::MacroCall::cast) {
30 // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. 37 // When a path is being typed after the name of a trait/type of an impl it is being
31 // We only wanna do this if the NameRef is the last segment of the path. 38 // parsed as a macro, so when the trait/impl has a block following it an we are between the
32 let node = match tok.parent().and_then(ast::NameLike::cast)? { 39 // name and block the macro will attach the block to itself so maximizing fails to take
33 ast::NameLike::NameRef(name_ref) => { 40 // that into account
34 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { 41 // FIXME path expr and statement have a similar problem with attrs
35 let p = segment.parent_path(); 42 Some(call)
36 if p.parent_path().is_none() { 43 if call.excl_token().is_none()
37 p.syntax() 44 && call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
38 .ancestors() 45 && call.semicolon_token().is_none() =>
39 .take_while(|it| it.text_range() == p.syntax().text_range()) 46 {
40 .last()? 47 call.syntax().clone()
41 } else { 48 }
42 return None; 49 _ => node,
50 };
51 let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
52 let res = match_ast! {
53 match prev_sibling {
54 ast::ExprStmt(it) => {
55 let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone();
56 match_ast! {
57 match node {
58 ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
59 _ => return None,
60 }
43 } 61 }
44 } else { 62 },
45 return None; 63 ast::Trait(it) => if it.assoc_item_list().is_none() {
46 } 64 ImmediatePrevSibling::TraitDefName
65 } else {
66 return None
67 },
68 ast::Impl(it) => if it.assoc_item_list().is_none()
69 && (it.for_token().is_none() || it.self_ty().is_some()) {
70 ImmediatePrevSibling::ImplDefType
71 } else {
72 return None
73 },
74 _ => return None,
47 } 75 }
48 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
49 }; 76 };
77 Some(res)
78}
79
80pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> {
81 let node = maximize_name_ref(name_like)?;
50 let parent = match node.parent() { 82 let parent = match node.parent() {
51 Some(parent) => match ast::MacroCall::cast(parent.clone()) { 83 Some(parent) => match ast::MacroCall::cast(parent.clone()) {
52 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. 84 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
53 // This is usually fine as the node expansion code above already accounts for that with 85 // This is usually fine as the node expansion code above already accounts for that with
54 // the ancestors call, but there is one exception to this which is that when an attribute 86 // the ancestors call, but there is one exception to this which is that when an attribute
55 // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ. 87 // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ.
88 // FIXME path expr and statement have a similar problem
56 Some(call) 89 Some(call)
57 if call.excl_token().is_none() 90 if call.excl_token().is_none()
58 && call.token_tree().is_none() 91 && call.token_tree().is_none()
@@ -90,56 +123,30 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation>
90 Some(res) 123 Some(res)
91} 124}
92 125
93#[cfg(test)] 126fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> {
94fn check_location(code: &str, loc: ImmediateLocation) { 127 // First walk the element we are completing up to its highest node that has the same text range
95 check_pattern_is_applicable(code, |e| { 128 // as the element so that we can check in what context it immediately lies. We only do this for
96 assert_eq!(determine_location(e.into_token().expect("Expected a token")), Some(loc)); 129 // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically.
97 true 130 // We only wanna do this if the NameRef is the last segment of the path.
98 }); 131 let node = match name_like {
99} 132 ast::NameLike::NameRef(name_ref) => {
100 133 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
101#[test] 134 let p = segment.parent_path();
102fn test_has_trait_parent() { 135 if p.parent_path().is_none() {
103 check_location(r"trait A { f$0 }", ImmediateLocation::Trait); 136 p.syntax()
104} 137 .ancestors()
105 138 .take_while(|it| it.text_range() == p.syntax().text_range())
106#[test] 139 .last()?
107fn test_has_use_parent() { 140 } else {
108 check_location(r"use f$0", ImmediateLocation::Use); 141 return None;
109} 142 }
110 143 } else {
111#[test] 144 return None;
112fn test_has_impl_parent() { 145 }
113 check_location(r"impl A { f$0 }", ImmediateLocation::Impl); 146 }
114} 147 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
115#[test] 148 };
116fn test_has_field_list_parent() { 149 Some(node)
117 check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
118 check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
119}
120
121#[test]
122fn test_has_block_expr_parent() {
123 check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
124}
125
126#[test]
127fn test_has_ident_pat_parent() {
128 check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
129 check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
130 check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
131 check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
132}
133
134#[test]
135fn test_has_ref_expr_parent() {
136 check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
137}
138
139#[test]
140fn test_has_item_list_or_source_file_parent() {
141 check_location(r"i$0", ImmediateLocation::ItemList);
142 check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
143} 150}
144 151
145pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 152pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
@@ -191,14 +198,6 @@ fn test_for_is_prev2() {
191 check_pattern_is_applicable(r"for i i$0", for_is_prev2); 198 check_pattern_is_applicable(r"for i i$0", for_is_prev2);
192} 199}
193 200
194pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool {
195 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some()
196}
197#[test]
198fn test_has_impl_as_prev_sibling() {
199 check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL));
200}
201
202pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { 201pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
203 element 202 element
204 .ancestors() 203 .ancestors()
@@ -247,3 +246,111 @@ fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<Syntax
247 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) 246 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
248 } 247 }
249} 248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) {
255 check_pattern_is_applicable(code, |e| {
256 let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
257 assert_eq!(determine_location(name), loc.into());
258 true
259 });
260 }
261
262 fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {
263 check_pattern_is_applicable(code, |e| {
264 let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
265 assert_eq!(determine_prev_sibling(name), sibling.into());
266 true
267 });
268 }
269
270 #[test]
271 fn test_trait_loc() {
272 check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
273 check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait);
274 check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait);
275 check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait);
276 check_location(r"trait A$0 {}", None);
277 check_location(r"trait A { fn f$0 }", None);
278 }
279
280 #[test]
281 fn test_impl_loc() {
282 check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
283 check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl);
284 check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl);
285 check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl);
286 check_location(r"impl A$0 {}", None);
287 check_location(r"impl A { fn f$0 }", None);
288 }
289
290 #[test]
291 fn test_use_loc() {
292 check_location(r"use f$0", ImmediateLocation::Use);
293 check_location(r"use f$0;", ImmediateLocation::Use);
294 check_location(r"use f::{f$0}", None);
295 check_location(r"use {f$0}", None);
296 }
297
298 #[test]
299 fn test_record_field_loc() {
300 check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
301 check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
302 check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField);
303 }
304
305 #[test]
306 fn test_block_expr_loc() {
307 check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
308 check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::BlockExpr);
309 }
310
311 #[test]
312 fn test_ident_pat_loc() {
313 check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
314 check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
315 check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
316 check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
317 }
318
319 #[test]
320 fn test_ref_expr_loc() {
321 check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
322 }
323
324 #[test]
325 fn test_item_list_loc() {
326 check_location(r"i$0", ImmediateLocation::ItemList);
327 check_location(r"#[attr] i$0", ImmediateLocation::ItemList);
328 check_location(r"fn f() {} i$0", ImmediateLocation::ItemList);
329 check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
330 check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList);
331 check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList);
332 check_location(r"mod foo$0 {}", None);
333 }
334
335 #[test]
336 fn test_impl_prev_sibling() {
337 check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType);
338 check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType);
339 check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType);
340 check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType);
341 check_prev_sibling(r"impl A for w$0 {}", None);
342 check_prev_sibling(r"impl A for w$0", None);
343 }
344
345 #[test]
346 fn test_trait_prev_sibling() {
347 check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
348 check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
349 }
350
351 #[test]
352 fn test_if_expr_prev_sibling() {
353 check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
354 check_prev_sibling(r"fn foo() { if true {}; w$0", None);
355 }
356}
diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs
index 6656fd725..93c7c872c 100644
--- a/crates/ide_completion/src/test_utils.rs
+++ b/crates/ide_completion/src/test_utils.rs
@@ -12,7 +12,7 @@ use ide_db::{
12use itertools::Itertools; 12use itertools::Itertools;
13use stdx::{format_to, trim_indent}; 13use stdx::{format_to, trim_indent};
14use syntax::{AstNode, NodeOrToken, SyntaxElement}; 14use syntax::{AstNode, NodeOrToken, SyntaxElement};
15use test_utils::{assert_eq_text, RangeOrOffset}; 15use test_utils::assert_eq_text;
16 16
17use crate::{item::CompletionKind, CompletionConfig, CompletionItem}; 17use crate::{item::CompletionKind, CompletionConfig, CompletionItem};
18 18
@@ -36,10 +36,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
36 let mut database = RootDatabase::default(); 36 let mut database = RootDatabase::default();
37 database.apply_change(change_fixture.change); 37 database.apply_change(change_fixture.change);
38 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); 38 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
39 let offset = match range_or_offset { 39 let offset = range_or_offset.expect_offset();
40 RangeOrOffset::Range(_) => panic!(),
41 RangeOrOffset::Offset(it) => it,
42 };
43 (database, FilePosition { file_id, offset }) 40 (database, FilePosition { file_id, offset })
44} 41}
45 42
@@ -52,10 +49,11 @@ pub(crate) fn do_completion_with_config(
52 code: &str, 49 code: &str,
53 kind: CompletionKind, 50 kind: CompletionKind,
54) -> Vec<CompletionItem> { 51) -> Vec<CompletionItem> {
55 let mut kind_completions: Vec<CompletionItem> = 52 get_all_items(config, code)
56 get_all_items(config, code).into_iter().filter(|c| c.completion_kind == kind).collect(); 53 .into_iter()
57 kind_completions.sort_by(|l, r| l.label().cmp(r.label())); 54 .filter(|c| c.completion_kind == kind)
58 kind_completions 55 .sorted_by(|l, r| l.label().cmp(r.label()))
56 .collect()
59} 57}
60 58
61pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { 59pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String {
diff --git a/crates/ide_db/src/call_info/tests.rs b/crates/ide_db/src/call_info/tests.rs
index 1aeda08e5..b585085f3 100644
--- a/crates/ide_db/src/call_info/tests.rs
+++ b/crates/ide_db/src/call_info/tests.rs
@@ -1,6 +1,5 @@
1use base_db::{fixture::ChangeFixture, FilePosition}; 1use base_db::{fixture::ChangeFixture, FilePosition};
2use expect_test::{expect, Expect}; 2use expect_test::{expect, Expect};
3use test_utils::RangeOrOffset;
4 3
5use crate::RootDatabase; 4use crate::RootDatabase;
6 5
@@ -10,10 +9,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
10 let mut database = RootDatabase::default(); 9 let mut database = RootDatabase::default();
11 database.apply_change(change_fixture.change); 10 database.apply_change(change_fixture.change);
12 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); 11 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
13 let offset = match range_or_offset { 12 let offset = range_or_offset.expect_offset();
14 RangeOrOffset::Range(_) => panic!(),
15 RangeOrOffset::Offset(it) => it,
16 };
17 (database, FilePosition { file_id, offset }) 13 (database, FilePosition { file_id, offset })
18} 14}
19 15
diff --git a/crates/ide_db/src/traits/tests.rs b/crates/ide_db/src/traits/tests.rs
index 2a5482024..de994407c 100644
--- a/crates/ide_db/src/traits/tests.rs
+++ b/crates/ide_db/src/traits/tests.rs
@@ -2,7 +2,6 @@ use base_db::{fixture::ChangeFixture, FilePosition};
2use expect_test::{expect, Expect}; 2use expect_test::{expect, Expect};
3use hir::Semantics; 3use hir::Semantics;
4use syntax::ast::{self, AstNode}; 4use syntax::ast::{self, AstNode};
5use test_utils::RangeOrOffset;
6 5
7use crate::RootDatabase; 6use crate::RootDatabase;
8 7
@@ -12,10 +11,7 @@ pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
12 let mut database = RootDatabase::default(); 11 let mut database = RootDatabase::default();
13 database.apply_change(change_fixture.change); 12 database.apply_change(change_fixture.change);
14 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); 13 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)");
15 let offset = match range_or_offset { 14 let offset = range_or_offset.expect_offset();
16 RangeOrOffset::Range(_) => panic!(),
17 RangeOrOffset::Offset(it) => it,
18 };
19 (database, FilePosition { file_id, offset }) 15 (database, FilePosition { file_id, offset })
20} 16}
21 17
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index fce4fd6bf..bd017567c 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -96,6 +96,21 @@ pub enum RangeOrOffset {
96 Offset(TextSize), 96 Offset(TextSize),
97} 97}
98 98
99impl RangeOrOffset {
100 pub fn expect_offset(self) -> TextSize {
101 match self {
102 RangeOrOffset::Offset(it) => it,
103 RangeOrOffset::Range(_) => panic!("expected an offset but got a range instead"),
104 }
105 }
106 pub fn expect_range(self) -> TextRange {
107 match self {
108 RangeOrOffset::Range(it) => it,
109 RangeOrOffset::Offset(_) => panic!("expected a range but got an offset"),
110 }
111 }
112}
113
99impl From<RangeOrOffset> for TextRange { 114impl From<RangeOrOffset> for TextRange {
100 fn from(selection: RangeOrOffset) -> Self { 115 fn from(selection: RangeOrOffset) -> Self {
101 match selection { 116 match selection {