]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use hir::Semantics; |
2 | use ide_db::{ | |
3 | base_db::{FileId, FilePosition}, | |
4 | defs::{Definition, IdentClass}, | |
5 | helpers::pick_best_token, | |
6 | search::{FileReference, ReferenceCategory, SearchScope}, | |
7 | syntax_helpers::node_ext::{for_each_break_and_continue_expr, for_each_tail_expr, walk_expr}, | |
8 | FxHashSet, RootDatabase, | |
9 | }; | |
10 | use syntax::{ | |
11 | ast::{self, HasLoopBody}, | |
12 | match_ast, AstNode, | |
13 | SyntaxKind::{self, IDENT, INT_NUMBER}, | |
14 | SyntaxNode, SyntaxToken, TextRange, T, | |
15 | }; | |
16 | ||
17 | use crate::{references, NavigationTarget, TryToNav}; | |
18 | ||
19 | #[derive(PartialEq, Eq, Hash)] | |
20 | pub struct HighlightedRange { | |
21 | pub range: TextRange, | |
22 | // FIXME: This needs to be more precise. Reference category makes sense only | |
23 | // for references, but we also have defs. And things like exit points are | |
24 | // neither. | |
25 | pub category: Option<ReferenceCategory>, | |
26 | } | |
27 | ||
28 | #[derive(Default, Clone)] | |
29 | pub struct HighlightRelatedConfig { | |
30 | pub references: bool, | |
31 | pub exit_points: bool, | |
32 | pub break_points: bool, | |
33 | pub yield_points: bool, | |
34 | } | |
35 | ||
36 | // Feature: Highlight Related | |
37 | // | |
38 | // Highlights constructs related to the thing under the cursor: | |
39 | // | |
40 | // . if on an identifier, highlights all references to that identifier in the current file | |
41 | // . if on an `async` or `await token, highlights all yield points for that async context | |
42 | // . if on a `return` or `fn` keyword, `?` character or `->` return type arrow, highlights all exit points for that context | |
43 | // . if on a `break`, `loop`, `while` or `for` token, highlights all break points for that loop or block context | |
44 | // | |
45 | // Note: `?` and `->` do not currently trigger this behavior in the VSCode editor. | |
46 | pub(crate) fn highlight_related( | |
47 | sema: &Semantics<'_, RootDatabase>, | |
48 | config: HighlightRelatedConfig, | |
49 | FilePosition { offset, file_id }: FilePosition, | |
50 | ) -> Option<Vec<HighlightedRange>> { | |
51 | let _p = profile::span("highlight_related"); | |
52 | let syntax = sema.parse(file_id).syntax().clone(); | |
53 | ||
54 | let token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind { | |
55 | T![?] => 4, // prefer `?` when the cursor is sandwiched like in `await$0?` | |
56 | T![->] => 3, | |
57 | kind if kind.is_keyword() => 2, | |
58 | IDENT | INT_NUMBER => 1, | |
59 | _ => 0, | |
60 | })?; | |
61 | match token.kind() { | |
62 | T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => { | |
63 | highlight_exit_points(sema, token) | |
64 | } | |
65 | T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token), | |
66 | T![await] | T![async] if config.yield_points => highlight_yield_points(token), | |
67 | T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => { | |
68 | highlight_break_points(token) | |
69 | } | |
70 | T![break] | T![loop] | T![while] | T![continue] if config.break_points => { | |
71 | highlight_break_points(token) | |
72 | } | |
73 | _ if config.references => highlight_references(sema, &syntax, token, file_id), | |
74 | _ => None, | |
75 | } | |
76 | } | |
77 | ||
78 | fn highlight_references( | |
79 | sema: &Semantics<'_, RootDatabase>, | |
80 | node: &SyntaxNode, | |
81 | token: SyntaxToken, | |
82 | file_id: FileId, | |
83 | ) -> Option<Vec<HighlightedRange>> { | |
84 | let defs = find_defs(sema, token); | |
85 | let usages = defs | |
86 | .iter() | |
87 | .filter_map(|&d| { | |
88 | d.usages(sema) | |
89 | .set_scope(Some(SearchScope::single_file(file_id))) | |
90 | .include_self_refs() | |
91 | .all() | |
92 | .references | |
93 | .remove(&file_id) | |
94 | }) | |
95 | .flatten() | |
96 | .map(|FileReference { category: access, range, .. }| HighlightedRange { | |
97 | range, | |
98 | category: access, | |
99 | }); | |
100 | let mut res = FxHashSet::default(); | |
101 | ||
102 | let mut def_to_hl_range = |def| { | |
103 | let hl_range = match def { | |
104 | Definition::Module(module) => { | |
105 | Some(NavigationTarget::from_module_to_decl(sema.db, module)) | |
106 | } | |
107 | def => def.try_to_nav(sema.db), | |
108 | } | |
109 | .filter(|decl| decl.file_id == file_id) | |
110 | .and_then(|decl| decl.focus_range) | |
111 | .map(|range| { | |
112 | let category = | |
9c376795 | 113 | references::decl_mutability(&def, node, range).then_some(ReferenceCategory::Write); |
064997fb FG |
114 | HighlightedRange { range, category } |
115 | }); | |
116 | if let Some(hl_range) = hl_range { | |
117 | res.insert(hl_range); | |
118 | } | |
119 | }; | |
120 | for &def in &defs { | |
121 | match def { | |
122 | Definition::Local(local) => local | |
123 | .associated_locals(sema.db) | |
124 | .iter() | |
125 | .for_each(|&local| def_to_hl_range(Definition::Local(local))), | |
126 | def => def_to_hl_range(def), | |
127 | } | |
128 | } | |
129 | ||
130 | res.extend(usages); | |
131 | if res.is_empty() { | |
132 | None | |
133 | } else { | |
134 | Some(res.into_iter().collect()) | |
135 | } | |
136 | } | |
137 | ||
138 | fn highlight_exit_points( | |
139 | sema: &Semantics<'_, RootDatabase>, | |
140 | token: SyntaxToken, | |
141 | ) -> Option<Vec<HighlightedRange>> { | |
142 | fn hl( | |
143 | sema: &Semantics<'_, RootDatabase>, | |
144 | body: Option<ast::Expr>, | |
145 | ) -> Option<Vec<HighlightedRange>> { | |
146 | let mut highlights = Vec::new(); | |
147 | let body = body?; | |
148 | walk_expr(&body, &mut |expr| match expr { | |
149 | ast::Expr::ReturnExpr(expr) => { | |
150 | if let Some(token) = expr.return_token() { | |
151 | highlights.push(HighlightedRange { category: None, range: token.text_range() }); | |
152 | } | |
153 | } | |
154 | ast::Expr::TryExpr(try_) => { | |
155 | if let Some(token) = try_.question_mark_token() { | |
156 | highlights.push(HighlightedRange { category: None, range: token.text_range() }); | |
157 | } | |
158 | } | |
159 | ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => { | |
160 | if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) { | |
161 | highlights.push(HighlightedRange { | |
162 | category: None, | |
163 | range: expr.syntax().text_range(), | |
164 | }); | |
165 | } | |
166 | } | |
167 | _ => (), | |
168 | }); | |
169 | let tail = match body { | |
170 | ast::Expr::BlockExpr(b) => b.tail_expr(), | |
171 | e => Some(e), | |
172 | }; | |
173 | ||
174 | if let Some(tail) = tail { | |
175 | for_each_tail_expr(&tail, &mut |tail| { | |
176 | let range = match tail { | |
177 | ast::Expr::BreakExpr(b) => b | |
178 | .break_token() | |
179 | .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()), | |
180 | _ => tail.syntax().text_range(), | |
181 | }; | |
182 | highlights.push(HighlightedRange { category: None, range }) | |
183 | }); | |
184 | } | |
185 | Some(highlights) | |
186 | } | |
187 | for anc in token.parent_ancestors() { | |
188 | return match_ast! { | |
189 | match anc { | |
190 | ast::Fn(fn_) => hl(sema, fn_.body().map(ast::Expr::BlockExpr)), | |
191 | ast::ClosureExpr(closure) => hl(sema, closure.body()), | |
192 | ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) { | |
193 | hl(sema, Some(block_expr.into())) | |
194 | } else { | |
195 | continue; | |
196 | }, | |
197 | _ => continue, | |
198 | } | |
199 | }; | |
200 | } | |
201 | None | |
202 | } | |
203 | ||
204 | fn highlight_break_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> { | |
205 | fn hl( | |
206 | cursor_token_kind: SyntaxKind, | |
207 | token: Option<SyntaxToken>, | |
208 | label: Option<ast::Label>, | |
209 | body: Option<ast::StmtList>, | |
210 | ) -> Option<Vec<HighlightedRange>> { | |
211 | let mut highlights = Vec::new(); | |
212 | let range = cover_range( | |
213 | token.map(|tok| tok.text_range()), | |
214 | label.as_ref().map(|it| it.syntax().text_range()), | |
215 | ); | |
216 | highlights.extend(range.map(|range| HighlightedRange { category: None, range })); | |
217 | for_each_break_and_continue_expr(label, body, &mut |expr| { | |
218 | let range: Option<TextRange> = match (cursor_token_kind, expr) { | |
219 | (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => { | |
220 | cover_range( | |
221 | break_.break_token().map(|it| it.text_range()), | |
222 | break_.lifetime().map(|it| it.syntax().text_range()), | |
223 | ) | |
224 | } | |
225 | ( | |
226 | T![for] | T![while] | T![loop] | T![continue], | |
227 | ast::Expr::ContinueExpr(continue_), | |
228 | ) => cover_range( | |
229 | continue_.continue_token().map(|it| it.text_range()), | |
230 | continue_.lifetime().map(|it| it.syntax().text_range()), | |
231 | ), | |
232 | _ => None, | |
233 | }; | |
234 | highlights.extend(range.map(|range| HighlightedRange { category: None, range })); | |
235 | }); | |
236 | Some(highlights) | |
237 | } | |
238 | let parent = token.parent()?; | |
239 | let lbl = match_ast! { | |
240 | match parent { | |
241 | ast::BreakExpr(b) => b.lifetime(), | |
242 | ast::ContinueExpr(c) => c.lifetime(), | |
243 | ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()), | |
244 | ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()), | |
245 | ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()), | |
246 | ast::BlockExpr(b) => Some(b.label().and_then(|it| it.lifetime())?), | |
247 | _ => return None, | |
248 | } | |
249 | }; | |
250 | let lbl = lbl.as_ref(); | |
251 | let label_matches = |def_lbl: Option<ast::Label>| match lbl { | |
252 | Some(lbl) => { | |
253 | Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text()) | |
254 | } | |
255 | None => true, | |
256 | }; | |
257 | let token_kind = token.kind(); | |
258 | for anc in token.parent_ancestors().flat_map(ast::Expr::cast) { | |
259 | return match anc { | |
260 | ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl( | |
261 | token_kind, | |
262 | l.loop_token(), | |
263 | l.label(), | |
264 | l.loop_body().and_then(|it| it.stmt_list()), | |
265 | ), | |
266 | ast::Expr::ForExpr(f) if label_matches(f.label()) => hl( | |
267 | token_kind, | |
268 | f.for_token(), | |
269 | f.label(), | |
270 | f.loop_body().and_then(|it| it.stmt_list()), | |
271 | ), | |
272 | ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl( | |
273 | token_kind, | |
274 | w.while_token(), | |
275 | w.label(), | |
276 | w.loop_body().and_then(|it| it.stmt_list()), | |
277 | ), | |
278 | ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => { | |
279 | hl(token_kind, None, e.label(), e.stmt_list()) | |
280 | } | |
281 | _ => continue, | |
282 | }; | |
283 | } | |
284 | None | |
285 | } | |
286 | ||
287 | fn highlight_yield_points(token: SyntaxToken) -> Option<Vec<HighlightedRange>> { | |
288 | fn hl( | |
289 | async_token: Option<SyntaxToken>, | |
290 | body: Option<ast::Expr>, | |
291 | ) -> Option<Vec<HighlightedRange>> { | |
292 | let mut highlights = | |
293 | vec![HighlightedRange { category: None, range: async_token?.text_range() }]; | |
294 | if let Some(body) = body { | |
295 | walk_expr(&body, &mut |expr| { | |
296 | if let ast::Expr::AwaitExpr(expr) = expr { | |
297 | if let Some(token) = expr.await_token() { | |
298 | highlights | |
299 | .push(HighlightedRange { category: None, range: token.text_range() }); | |
300 | } | |
301 | } | |
302 | }); | |
303 | } | |
304 | Some(highlights) | |
305 | } | |
306 | for anc in token.parent_ancestors() { | |
307 | return match_ast! { | |
308 | match anc { | |
309 | ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)), | |
310 | ast::BlockExpr(block_expr) => { | |
311 | if block_expr.async_token().is_none() { | |
312 | continue; | |
313 | } | |
314 | hl(block_expr.async_token(), Some(block_expr.into())) | |
315 | }, | |
316 | ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()), | |
317 | _ => continue, | |
318 | } | |
319 | }; | |
320 | } | |
321 | None | |
322 | } | |
323 | ||
324 | fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange> { | |
325 | match (r0, r1) { | |
326 | (Some(r0), Some(r1)) => Some(r0.cover(r1)), | |
327 | (Some(range), None) => Some(range), | |
328 | (None, Some(range)) => Some(range), | |
329 | (None, None) => None, | |
330 | } | |
331 | } | |
332 | ||
333 | fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> { | |
334 | sema.descend_into_macros(token) | |
335 | .into_iter() | |
f2b60f7d FG |
336 | .filter_map(|token| IdentClass::classify_token(sema, &token)) |
337 | .map(IdentClass::definitions_no_ops) | |
064997fb FG |
338 | .flatten() |
339 | .collect() | |
340 | } | |
341 | ||
342 | #[cfg(test)] | |
343 | mod tests { | |
344 | use crate::fixture; | |
345 | ||
346 | use super::*; | |
347 | ||
348 | #[track_caller] | |
349 | fn check(ra_fixture: &str) { | |
350 | let config = HighlightRelatedConfig { | |
351 | break_points: true, | |
352 | exit_points: true, | |
353 | references: true, | |
354 | yield_points: true, | |
355 | }; | |
356 | ||
357 | check_with_config(ra_fixture, config); | |
358 | } | |
359 | ||
360 | #[track_caller] | |
361 | fn check_with_config(ra_fixture: &str, config: HighlightRelatedConfig) { | |
362 | let (analysis, pos, annotations) = fixture::annotations(ra_fixture); | |
363 | ||
364 | let hls = analysis.highlight_related(config, pos).unwrap().unwrap_or_default(); | |
365 | ||
366 | let mut expected = annotations | |
367 | .into_iter() | |
9c376795 | 368 | .map(|(r, access)| (r.range, (!access.is_empty()).then_some(access))) |
064997fb FG |
369 | .collect::<Vec<_>>(); |
370 | ||
371 | let mut actual = hls | |
372 | .into_iter() | |
373 | .map(|hl| { | |
374 | ( | |
375 | hl.range, | |
376 | hl.category.map(|it| { | |
377 | match it { | |
378 | ReferenceCategory::Read => "read", | |
379 | ReferenceCategory::Write => "write", | |
2b03887a | 380 | ReferenceCategory::Import => "import", |
064997fb FG |
381 | } |
382 | .to_string() | |
383 | }), | |
384 | ) | |
385 | }) | |
386 | .collect::<Vec<_>>(); | |
387 | actual.sort_by_key(|(range, _)| range.start()); | |
388 | expected.sort_by_key(|(range, _)| range.start()); | |
389 | ||
390 | assert_eq!(expected, actual); | |
391 | } | |
392 | ||
393 | #[test] | |
394 | fn test_hl_tuple_fields() { | |
395 | check( | |
396 | r#" | |
397 | struct Tuple(u32, u32); | |
398 | ||
399 | fn foo(t: Tuple) { | |
400 | t.0$0; | |
401 | // ^ read | |
402 | t.0; | |
403 | // ^ read | |
404 | } | |
405 | "#, | |
406 | ); | |
407 | } | |
408 | ||
409 | #[test] | |
410 | fn test_hl_module() { | |
411 | check( | |
412 | r#" | |
413 | //- /lib.rs | |
414 | mod foo$0; | |
415 | // ^^^ | |
416 | //- /foo.rs | |
417 | struct Foo; | |
418 | "#, | |
419 | ); | |
420 | } | |
421 | ||
422 | #[test] | |
423 | fn test_hl_self_in_crate_root() { | |
424 | check( | |
425 | r#" | |
426 | use crate$0; | |
2b03887a | 427 | //^^^^^ import |
064997fb | 428 | use self; |
2b03887a | 429 | //^^^^ import |
064997fb FG |
430 | mod __ { |
431 | use super; | |
2b03887a | 432 | //^^^^^ import |
064997fb FG |
433 | } |
434 | "#, | |
435 | ); | |
436 | check( | |
437 | r#" | |
438 | //- /main.rs crate:main deps:lib | |
439 | use lib$0; | |
2b03887a | 440 | //^^^ import |
064997fb FG |
441 | //- /lib.rs crate:lib |
442 | "#, | |
443 | ); | |
444 | } | |
445 | ||
446 | #[test] | |
447 | fn test_hl_self_in_module() { | |
448 | check( | |
449 | r#" | |
450 | //- /lib.rs | |
451 | mod foo; | |
452 | //- /foo.rs | |
453 | use self$0; | |
2b03887a | 454 | // ^^^^ import |
064997fb FG |
455 | "#, |
456 | ); | |
457 | } | |
458 | ||
459 | #[test] | |
460 | fn test_hl_local() { | |
461 | check( | |
462 | r#" | |
463 | fn foo() { | |
464 | let mut bar = 3; | |
465 | // ^^^ write | |
466 | bar$0; | |
467 | // ^^^ read | |
468 | } | |
469 | "#, | |
470 | ); | |
471 | } | |
472 | ||
473 | #[test] | |
474 | fn test_hl_local_in_attr() { | |
475 | check( | |
476 | r#" | |
477 | //- proc_macros: identity | |
478 | #[proc_macros::identity] | |
479 | fn foo() { | |
480 | let mut bar = 3; | |
481 | // ^^^ write | |
482 | bar$0; | |
483 | // ^^^ read | |
484 | } | |
485 | "#, | |
486 | ); | |
487 | } | |
488 | ||
489 | #[test] | |
490 | fn test_multi_macro_usage() { | |
491 | check( | |
492 | r#" | |
493 | macro_rules! foo { | |
494 | ($ident:ident) => { | |
495 | fn $ident() -> $ident { loop {} } | |
496 | struct $ident; | |
497 | } | |
498 | } | |
499 | ||
500 | foo!(bar$0); | |
501 | // ^^^ | |
502 | fn foo() { | |
503 | let bar: bar = bar(); | |
504 | // ^^^ | |
505 | // ^^^ | |
506 | } | |
507 | "#, | |
508 | ); | |
509 | check( | |
510 | r#" | |
511 | macro_rules! foo { | |
512 | ($ident:ident) => { | |
513 | fn $ident() -> $ident { loop {} } | |
514 | struct $ident; | |
515 | } | |
516 | } | |
517 | ||
518 | foo!(bar); | |
519 | // ^^^ | |
520 | fn foo() { | |
521 | let bar: bar$0 = bar(); | |
522 | // ^^^ | |
523 | } | |
524 | "#, | |
525 | ); | |
526 | } | |
527 | ||
528 | #[test] | |
529 | fn test_hl_yield_points() { | |
530 | check( | |
531 | r#" | |
532 | pub async fn foo() { | |
533 | // ^^^^^ | |
534 | let x = foo() | |
535 | .await$0 | |
536 | // ^^^^^ | |
537 | .await; | |
538 | // ^^^^^ | |
539 | || { 0.await }; | |
540 | (async { 0.await }).await | |
541 | // ^^^^^ | |
542 | } | |
543 | "#, | |
544 | ); | |
545 | } | |
546 | ||
547 | #[test] | |
548 | fn test_hl_yield_points2() { | |
549 | check( | |
550 | r#" | |
551 | pub async$0 fn foo() { | |
552 | // ^^^^^ | |
553 | let x = foo() | |
554 | .await | |
555 | // ^^^^^ | |
556 | .await; | |
557 | // ^^^^^ | |
558 | || { 0.await }; | |
559 | (async { 0.await }).await | |
560 | // ^^^^^ | |
561 | } | |
562 | "#, | |
563 | ); | |
564 | } | |
565 | ||
566 | #[test] | |
567 | fn test_hl_yield_nested_fn() { | |
568 | check( | |
569 | r#" | |
570 | async fn foo() { | |
571 | async fn foo2() { | |
572 | // ^^^^^ | |
573 | async fn foo3() { | |
574 | 0.await | |
575 | } | |
576 | 0.await$0 | |
577 | // ^^^^^ | |
578 | } | |
579 | 0.await | |
580 | } | |
581 | "#, | |
582 | ); | |
583 | } | |
584 | ||
585 | #[test] | |
586 | fn test_hl_yield_nested_async_blocks() { | |
587 | check( | |
588 | r#" | |
589 | async fn foo() { | |
590 | (async { | |
591 | // ^^^^^ | |
592 | (async { | |
593 | 0.await | |
594 | }).await$0 } | |
595 | // ^^^^^ | |
596 | ).await; | |
597 | } | |
598 | "#, | |
599 | ); | |
600 | } | |
601 | ||
602 | #[test] | |
603 | fn test_hl_exit_points() { | |
604 | check( | |
605 | r#" | |
606 | fn foo() -> u32 { | |
607 | if true { | |
608 | return$0 0; | |
609 | // ^^^^^^ | |
610 | } | |
611 | ||
612 | 0?; | |
613 | // ^ | |
614 | 0xDEAD_BEEF | |
615 | // ^^^^^^^^^^^ | |
616 | } | |
617 | "#, | |
618 | ); | |
619 | } | |
620 | ||
621 | #[test] | |
622 | fn test_hl_exit_points2() { | |
623 | check( | |
624 | r#" | |
625 | fn foo() ->$0 u32 { | |
626 | if true { | |
627 | return 0; | |
628 | // ^^^^^^ | |
629 | } | |
630 | ||
631 | 0?; | |
632 | // ^ | |
633 | 0xDEAD_BEEF | |
634 | // ^^^^^^^^^^^ | |
635 | } | |
636 | "#, | |
637 | ); | |
638 | } | |
639 | ||
640 | #[test] | |
641 | fn test_hl_exit_points3() { | |
642 | check( | |
643 | r#" | |
644 | fn$0 foo() -> u32 { | |
645 | if true { | |
646 | return 0; | |
647 | // ^^^^^^ | |
648 | } | |
649 | ||
650 | 0?; | |
651 | // ^ | |
652 | 0xDEAD_BEEF | |
653 | // ^^^^^^^^^^^ | |
654 | } | |
655 | "#, | |
656 | ); | |
657 | } | |
658 | ||
659 | #[test] | |
660 | fn test_hl_prefer_ref_over_tail_exit() { | |
661 | check( | |
662 | r#" | |
663 | fn foo() -> u32 { | |
664 | // ^^^ | |
665 | if true { | |
666 | return 0; | |
667 | } | |
668 | ||
669 | 0?; | |
670 | ||
671 | foo$0() | |
672 | // ^^^ | |
673 | } | |
674 | "#, | |
675 | ); | |
676 | } | |
677 | ||
678 | #[test] | |
679 | fn test_hl_never_call_is_exit_point() { | |
680 | check( | |
681 | r#" | |
682 | struct Never; | |
683 | impl Never { | |
684 | fn never(self) -> ! { loop {} } | |
685 | } | |
686 | macro_rules! never { | |
687 | () => { never() } | |
688 | } | |
689 | fn never() -> ! { loop {} } | |
690 | fn foo() ->$0 u32 { | |
691 | never(); | |
692 | // ^^^^^^^ | |
693 | never!(); | |
694 | // ^^^^^^^^ | |
695 | ||
696 | Never.never(); | |
697 | // ^^^^^^^^^^^^^ | |
698 | ||
699 | 0 | |
700 | // ^ | |
701 | } | |
702 | "#, | |
703 | ); | |
704 | } | |
705 | ||
706 | #[test] | |
707 | fn test_hl_inner_tail_exit_points() { | |
708 | check( | |
709 | r#" | |
710 | fn foo() ->$0 u32 { | |
711 | if true { | |
712 | unsafe { | |
713 | return 5; | |
714 | // ^^^^^^ | |
715 | 5 | |
716 | // ^ | |
717 | } | |
718 | } else if false { | |
719 | 0 | |
720 | // ^ | |
721 | } else { | |
722 | match 5 { | |
723 | 6 => 100, | |
724 | // ^^^ | |
725 | 7 => loop { | |
726 | break 5; | |
727 | // ^^^^^ | |
728 | } | |
729 | 8 => 'a: loop { | |
730 | 'b: loop { | |
731 | break 'a 5; | |
732 | // ^^^^^ | |
733 | break 'b 5; | |
734 | break 5; | |
735 | }; | |
736 | } | |
737 | // | |
738 | _ => 500, | |
739 | // ^^^ | |
740 | } | |
741 | } | |
742 | } | |
743 | "#, | |
744 | ); | |
745 | } | |
746 | ||
747 | #[test] | |
748 | fn test_hl_inner_tail_exit_points_labeled_block() { | |
749 | check( | |
750 | r#" | |
751 | fn foo() ->$0 u32 { | |
752 | 'foo: { | |
753 | break 'foo 0; | |
754 | // ^^^^^ | |
755 | loop { | |
756 | break; | |
757 | break 'foo 0; | |
758 | // ^^^^^ | |
759 | } | |
760 | 0 | |
761 | // ^ | |
762 | } | |
763 | } | |
764 | "#, | |
765 | ); | |
766 | } | |
767 | ||
9c376795 FG |
768 | #[test] |
769 | fn test_hl_inner_tail_exit_points_loops() { | |
770 | check( | |
771 | r#" | |
772 | fn foo() ->$0 u32 { | |
773 | 'foo: while { return 0; true } { | |
774 | // ^^^^^^ | |
775 | break 'foo 0; | |
776 | // ^^^^^ | |
777 | return 0; | |
778 | // ^^^^^^ | |
779 | } | |
780 | } | |
781 | "#, | |
782 | ); | |
783 | } | |
784 | ||
064997fb FG |
785 | #[test] |
786 | fn test_hl_break_loop() { | |
787 | check( | |
788 | r#" | |
789 | fn foo() { | |
790 | 'outer: loop { | |
791 | // ^^^^^^^^^^^^ | |
792 | break; | |
793 | // ^^^^^ | |
794 | 'inner: loop { | |
795 | break; | |
796 | 'innermost: loop { | |
797 | break 'outer; | |
798 | // ^^^^^^^^^^^^ | |
799 | break 'inner; | |
800 | } | |
801 | break$0 'outer; | |
802 | // ^^^^^^^^^^^^ | |
803 | break; | |
804 | } | |
805 | break; | |
806 | // ^^^^^ | |
807 | } | |
808 | } | |
809 | "#, | |
810 | ); | |
811 | } | |
812 | ||
813 | #[test] | |
814 | fn test_hl_break_loop2() { | |
815 | check( | |
816 | r#" | |
817 | fn foo() { | |
818 | 'outer: loop { | |
819 | break; | |
820 | 'inner: loop { | |
821 | // ^^^^^^^^^^^^ | |
822 | break; | |
823 | // ^^^^^ | |
824 | 'innermost: loop { | |
825 | break 'outer; | |
826 | break 'inner; | |
827 | // ^^^^^^^^^^^^ | |
828 | } | |
829 | break 'outer; | |
830 | break$0; | |
831 | // ^^^^^ | |
832 | } | |
833 | break; | |
834 | } | |
835 | } | |
836 | "#, | |
837 | ); | |
838 | } | |
839 | ||
840 | #[test] | |
841 | fn test_hl_break_for() { | |
842 | check( | |
843 | r#" | |
844 | fn foo() { | |
845 | 'outer: for _ in () { | |
846 | // ^^^^^^^^^^^ | |
847 | break; | |
848 | // ^^^^^ | |
849 | 'inner: for _ in () { | |
850 | break; | |
851 | 'innermost: for _ in () { | |
852 | break 'outer; | |
853 | // ^^^^^^^^^^^^ | |
854 | break 'inner; | |
855 | } | |
856 | break$0 'outer; | |
857 | // ^^^^^^^^^^^^ | |
858 | break; | |
859 | } | |
860 | break; | |
861 | // ^^^^^ | |
862 | } | |
863 | } | |
864 | "#, | |
865 | ); | |
866 | } | |
867 | ||
868 | #[test] | |
869 | fn test_hl_break_for_but_not_continue() { | |
870 | check( | |
871 | r#" | |
872 | fn foo() { | |
873 | 'outer: for _ in () { | |
874 | // ^^^^^^^^^^^ | |
875 | break; | |
876 | // ^^^^^ | |
877 | continue; | |
878 | 'inner: for _ in () { | |
879 | break; | |
880 | continue; | |
881 | 'innermost: for _ in () { | |
882 | continue 'outer; | |
883 | break 'outer; | |
884 | // ^^^^^^^^^^^^ | |
885 | continue 'inner; | |
886 | break 'inner; | |
887 | } | |
888 | break$0 'outer; | |
889 | // ^^^^^^^^^^^^ | |
890 | continue 'outer; | |
891 | break; | |
892 | continue; | |
893 | } | |
894 | break; | |
895 | // ^^^^^ | |
896 | continue; | |
897 | } | |
898 | } | |
899 | "#, | |
900 | ); | |
901 | } | |
902 | ||
903 | #[test] | |
904 | fn test_hl_continue_for_but_not_break() { | |
905 | check( | |
906 | r#" | |
907 | fn foo() { | |
908 | 'outer: for _ in () { | |
909 | // ^^^^^^^^^^^ | |
910 | break; | |
911 | continue; | |
912 | // ^^^^^^^^ | |
913 | 'inner: for _ in () { | |
914 | break; | |
915 | continue; | |
916 | 'innermost: for _ in () { | |
917 | continue 'outer; | |
918 | // ^^^^^^^^^^^^^^^ | |
919 | break 'outer; | |
920 | continue 'inner; | |
921 | break 'inner; | |
922 | } | |
923 | break 'outer; | |
924 | continue$0 'outer; | |
925 | // ^^^^^^^^^^^^^^^ | |
926 | break; | |
927 | continue; | |
928 | } | |
929 | break; | |
930 | continue; | |
931 | // ^^^^^^^^ | |
932 | } | |
933 | } | |
934 | "#, | |
935 | ); | |
936 | } | |
937 | ||
938 | #[test] | |
939 | fn test_hl_break_and_continue() { | |
940 | check( | |
941 | r#" | |
942 | fn foo() { | |
943 | 'outer: fo$0r _ in () { | |
944 | // ^^^^^^^^^^^ | |
945 | break; | |
946 | // ^^^^^ | |
947 | continue; | |
948 | // ^^^^^^^^ | |
949 | 'inner: for _ in () { | |
950 | break; | |
951 | continue; | |
952 | 'innermost: for _ in () { | |
953 | continue 'outer; | |
954 | // ^^^^^^^^^^^^^^^ | |
955 | break 'outer; | |
956 | // ^^^^^^^^^^^^ | |
957 | continue 'inner; | |
958 | break 'inner; | |
959 | } | |
960 | break 'outer; | |
961 | // ^^^^^^^^^^^^ | |
962 | continue 'outer; | |
963 | // ^^^^^^^^^^^^^^^ | |
964 | break; | |
965 | continue; | |
966 | } | |
967 | break; | |
968 | // ^^^^^ | |
969 | continue; | |
970 | // ^^^^^^^^ | |
971 | } | |
972 | } | |
973 | "#, | |
974 | ); | |
975 | } | |
976 | ||
977 | #[test] | |
978 | fn test_hl_break_while() { | |
979 | check( | |
980 | r#" | |
981 | fn foo() { | |
982 | 'outer: while true { | |
983 | // ^^^^^^^^^^^^^ | |
984 | break; | |
985 | // ^^^^^ | |
986 | 'inner: while true { | |
987 | break; | |
988 | 'innermost: while true { | |
989 | break 'outer; | |
990 | // ^^^^^^^^^^^^ | |
991 | break 'inner; | |
992 | } | |
993 | break$0 'outer; | |
994 | // ^^^^^^^^^^^^ | |
995 | break; | |
996 | } | |
997 | break; | |
998 | // ^^^^^ | |
999 | } | |
1000 | } | |
1001 | "#, | |
1002 | ); | |
1003 | } | |
1004 | ||
1005 | #[test] | |
1006 | fn test_hl_break_labeled_block() { | |
1007 | check( | |
1008 | r#" | |
1009 | fn foo() { | |
1010 | 'outer: { | |
1011 | // ^^^^^^^ | |
1012 | break; | |
1013 | // ^^^^^ | |
1014 | 'inner: { | |
1015 | break; | |
1016 | 'innermost: { | |
1017 | break 'outer; | |
1018 | // ^^^^^^^^^^^^ | |
1019 | break 'inner; | |
1020 | } | |
1021 | break$0 'outer; | |
1022 | // ^^^^^^^^^^^^ | |
1023 | break; | |
1024 | } | |
1025 | break; | |
1026 | // ^^^^^ | |
1027 | } | |
1028 | } | |
1029 | "#, | |
1030 | ); | |
1031 | } | |
1032 | ||
1033 | #[test] | |
1034 | fn test_hl_break_unlabeled_loop() { | |
1035 | check( | |
1036 | r#" | |
1037 | fn foo() { | |
1038 | loop { | |
1039 | // ^^^^ | |
1040 | break$0; | |
1041 | // ^^^^^ | |
1042 | } | |
1043 | } | |
1044 | "#, | |
1045 | ); | |
1046 | } | |
1047 | ||
1048 | #[test] | |
1049 | fn test_hl_break_unlabeled_block_in_loop() { | |
1050 | check( | |
1051 | r#" | |
1052 | fn foo() { | |
1053 | loop { | |
1054 | // ^^^^ | |
1055 | { | |
1056 | break$0; | |
1057 | // ^^^^^ | |
1058 | } | |
1059 | } | |
1060 | } | |
1061 | "#, | |
1062 | ); | |
1063 | } | |
1064 | ||
1065 | #[test] | |
1066 | fn test_hl_field_shorthand() { | |
1067 | check( | |
1068 | r#" | |
1069 | struct Struct { field: u32 } | |
1070 | //^^^^^ | |
1071 | fn function(field: u32) { | |
1072 | //^^^^^ | |
1073 | Struct { field$0 } | |
1074 | //^^^^^ read | |
1075 | } | |
1076 | "#, | |
1077 | ); | |
1078 | } | |
1079 | ||
1080 | #[test] | |
1081 | fn test_hl_disabled_ref_local() { | |
1082 | let config = HighlightRelatedConfig { | |
1083 | references: false, | |
1084 | break_points: true, | |
1085 | exit_points: true, | |
1086 | yield_points: true, | |
1087 | }; | |
1088 | ||
1089 | check_with_config( | |
1090 | r#" | |
1091 | fn foo() { | |
1092 | let x$0 = 5; | |
1093 | let y = x * 2; | |
1094 | } | |
1095 | "#, | |
1096 | config, | |
1097 | ); | |
1098 | } | |
1099 | ||
1100 | #[test] | |
1101 | fn test_hl_disabled_ref_local_preserved_break() { | |
1102 | let config = HighlightRelatedConfig { | |
1103 | references: false, | |
1104 | break_points: true, | |
1105 | exit_points: true, | |
1106 | yield_points: true, | |
1107 | }; | |
1108 | ||
1109 | check_with_config( | |
1110 | r#" | |
1111 | fn foo() { | |
1112 | let x$0 = 5; | |
1113 | let y = x * 2; | |
1114 | ||
1115 | loop { | |
1116 | break; | |
1117 | } | |
1118 | } | |
1119 | "#, | |
1120 | config.clone(), | |
1121 | ); | |
1122 | ||
1123 | check_with_config( | |
1124 | r#" | |
1125 | fn foo() { | |
1126 | let x = 5; | |
1127 | let y = x * 2; | |
1128 | ||
1129 | loop$0 { | |
1130 | // ^^^^ | |
1131 | break; | |
1132 | // ^^^^^ | |
1133 | } | |
1134 | } | |
1135 | "#, | |
1136 | config, | |
1137 | ); | |
1138 | } | |
1139 | ||
1140 | #[test] | |
1141 | fn test_hl_disabled_ref_local_preserved_yield() { | |
1142 | let config = HighlightRelatedConfig { | |
1143 | references: false, | |
1144 | break_points: true, | |
1145 | exit_points: true, | |
1146 | yield_points: true, | |
1147 | }; | |
1148 | ||
1149 | check_with_config( | |
1150 | r#" | |
1151 | async fn foo() { | |
1152 | let x$0 = 5; | |
1153 | let y = x * 2; | |
1154 | ||
1155 | 0.await; | |
1156 | } | |
1157 | "#, | |
1158 | config.clone(), | |
1159 | ); | |
1160 | ||
1161 | check_with_config( | |
1162 | r#" | |
1163 | async fn foo() { | |
1164 | // ^^^^^ | |
1165 | let x = 5; | |
1166 | let y = x * 2; | |
1167 | ||
1168 | 0.await$0; | |
1169 | // ^^^^^ | |
1170 | } | |
1171 | "#, | |
1172 | config, | |
1173 | ); | |
1174 | } | |
1175 | ||
1176 | #[test] | |
1177 | fn test_hl_disabled_ref_local_preserved_exit() { | |
1178 | let config = HighlightRelatedConfig { | |
1179 | references: false, | |
1180 | break_points: true, | |
1181 | exit_points: true, | |
1182 | yield_points: true, | |
1183 | }; | |
1184 | ||
1185 | check_with_config( | |
1186 | r#" | |
1187 | fn foo() -> i32 { | |
1188 | let x$0 = 5; | |
1189 | let y = x * 2; | |
1190 | ||
1191 | if true { | |
1192 | return y; | |
1193 | } | |
1194 | ||
1195 | 0? | |
1196 | } | |
1197 | "#, | |
1198 | config.clone(), | |
1199 | ); | |
1200 | ||
1201 | check_with_config( | |
1202 | r#" | |
1203 | fn foo() ->$0 i32 { | |
1204 | let x = 5; | |
1205 | let y = x * 2; | |
1206 | ||
1207 | if true { | |
1208 | return y; | |
1209 | // ^^^^^^ | |
1210 | } | |
1211 | ||
1212 | 0? | |
1213 | // ^ | |
1214 | "#, | |
1215 | config, | |
1216 | ); | |
1217 | } | |
1218 | ||
1219 | #[test] | |
1220 | fn test_hl_disabled_break() { | |
1221 | let config = HighlightRelatedConfig { | |
1222 | references: true, | |
1223 | break_points: false, | |
1224 | exit_points: true, | |
1225 | yield_points: true, | |
1226 | }; | |
1227 | ||
1228 | check_with_config( | |
1229 | r#" | |
1230 | fn foo() { | |
1231 | loop { | |
1232 | break$0; | |
1233 | } | |
1234 | } | |
1235 | "#, | |
1236 | config, | |
1237 | ); | |
1238 | } | |
1239 | ||
1240 | #[test] | |
1241 | fn test_hl_disabled_yield() { | |
1242 | let config = HighlightRelatedConfig { | |
1243 | references: true, | |
1244 | break_points: true, | |
1245 | exit_points: true, | |
1246 | yield_points: false, | |
1247 | }; | |
1248 | ||
1249 | check_with_config( | |
1250 | r#" | |
1251 | async$0 fn foo() { | |
1252 | 0.await; | |
1253 | } | |
1254 | "#, | |
1255 | config, | |
1256 | ); | |
1257 | } | |
1258 | ||
1259 | #[test] | |
1260 | fn test_hl_disabled_exit() { | |
1261 | let config = HighlightRelatedConfig { | |
1262 | references: true, | |
1263 | break_points: true, | |
1264 | exit_points: false, | |
1265 | yield_points: true, | |
1266 | }; | |
1267 | ||
1268 | check_with_config( | |
1269 | r#" | |
1270 | fn foo() ->$0 i32 { | |
1271 | if true { | |
1272 | return -1; | |
1273 | } | |
1274 | ||
1275 | 42 | |
1276 | }"#, | |
1277 | config, | |
1278 | ); | |
1279 | } | |
1280 | ||
1281 | #[test] | |
1282 | fn test_hl_multi_local() { | |
1283 | check( | |
1284 | r#" | |
1285 | fn foo(( | |
1286 | foo$0 | |
1287 | //^^^ | |
1288 | | foo | |
1289 | //^^^ | |
1290 | | foo | |
1291 | //^^^ | |
1292 | ): ()) { | |
1293 | foo; | |
1294 | //^^^read | |
1295 | let foo; | |
1296 | } | |
1297 | "#, | |
1298 | ); | |
1299 | check( | |
1300 | r#" | |
1301 | fn foo(( | |
1302 | foo | |
1303 | //^^^ | |
1304 | | foo$0 | |
1305 | //^^^ | |
1306 | | foo | |
1307 | //^^^ | |
1308 | ): ()) { | |
1309 | foo; | |
1310 | //^^^read | |
1311 | let foo; | |
1312 | } | |
1313 | "#, | |
1314 | ); | |
1315 | check( | |
1316 | r#" | |
1317 | fn foo(( | |
1318 | foo | |
1319 | //^^^ | |
1320 | | foo | |
1321 | //^^^ | |
1322 | | foo | |
1323 | //^^^ | |
1324 | ): ()) { | |
1325 | foo$0; | |
1326 | //^^^read | |
1327 | let foo; | |
1328 | } | |
1329 | "#, | |
1330 | ); | |
1331 | } | |
1332 | ||
1333 | #[test] | |
1334 | fn test_hl_trait_impl_methods() { | |
1335 | check( | |
1336 | r#" | |
1337 | trait Trait { | |
1338 | fn func$0(self) {} | |
1339 | //^^^^ | |
1340 | } | |
1341 | ||
1342 | impl Trait for () { | |
1343 | fn func(self) {} | |
1344 | //^^^^ | |
1345 | } | |
1346 | ||
1347 | fn main() { | |
1348 | <()>::func(()); | |
1349 | //^^^^ | |
1350 | ().func(); | |
1351 | //^^^^ | |
1352 | } | |
1353 | "#, | |
1354 | ); | |
1355 | check( | |
1356 | r#" | |
1357 | trait Trait { | |
1358 | fn func(self) {} | |
064997fb FG |
1359 | } |
1360 | ||
1361 | impl Trait for () { | |
1362 | fn func$0(self) {} | |
1363 | //^^^^ | |
1364 | } | |
1365 | ||
1366 | fn main() { | |
1367 | <()>::func(()); | |
1368 | //^^^^ | |
1369 | ().func(); | |
1370 | //^^^^ | |
1371 | } | |
1372 | "#, | |
1373 | ); | |
1374 | check( | |
1375 | r#" | |
1376 | trait Trait { | |
1377 | fn func(self) {} | |
064997fb FG |
1378 | } |
1379 | ||
1380 | impl Trait for () { | |
1381 | fn func(self) {} | |
1382 | //^^^^ | |
1383 | } | |
1384 | ||
1385 | fn main() { | |
1386 | <()>::func(()); | |
1387 | //^^^^ | |
1388 | ().func$0(); | |
1389 | //^^^^ | |
1390 | } | |
2b03887a FG |
1391 | "#, |
1392 | ); | |
1393 | } | |
1394 | ||
1395 | #[test] | |
1396 | fn test_assoc_type_highlighting() { | |
1397 | check( | |
1398 | r#" | |
1399 | trait Trait { | |
1400 | type Output; | |
1401 | // ^^^^^^ | |
1402 | } | |
1403 | impl Trait for () { | |
1404 | type Output$0 = (); | |
1405 | // ^^^^^^ | |
1406 | } | |
064997fb FG |
1407 | "#, |
1408 | ); | |
1409 | } | |
1410 | } |